Files
yallie 9fa3874800 Added the contents of the archive.
Moved the original file to the archive subfolder.
2017-11-20 16:11:42 +03:00

5013 lines
216 KiB
C#

// ==++==
//
//
// Copyright (c) 2002 Microsoft Corporation. All rights reserved.
//
// The use and distribution terms for this software are contained in the file
// named license.txt, which can be found in the root of this distribution.
// By using this software in any fashion, you are agreeing to be bound by the
// terms of this license.
//
// You must not remove this notice, or any other, from this software.
//
//
// ==--==
namespace Microsoft.JScript
{
using System;
using System.Collections;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Vsa;
//***************************************************************************************
// JSParser
//
// The JScript Parser.
//***************************************************************************************
public class JSParser{
private Context sourceContext;
private JSScanner scanner;
private Context currentToken;
private Context errorToken; // used for errors to flag that the same token has to be returned.
// We could have used just a boolean but having a Context does not
// add any overhead and allow to really save the info, if that will ever be needed
private int tokensSkipped;
private const int c_MaxSkippedTokenNumber = 50;
private NoSkipTokenSet noSkipTokenSet;
private long goodTokensProcessed;
private Block program;
// label related info
private ArrayList blockType ;
private SimpleHashtable labelTable;
enum BlockType {Block, Loop, Switch, Finally}
private int finallyEscaped;
private int breakRecursion = 0;
private static int s_cDummyName;
#if DEBUG
private static int s_filenameSuffix = 0;
#endif
Globals Globals;
private int Severity;
//---------------------------------------------------------------------------------------
// JSParser
//
// create a parser with a context. The context is the code that has to be compiled.
// Typically used by the runtime
//---------------------------------------------------------------------------------------
public JSParser(Context context){
this.sourceContext = context;
this.currentToken = context.Clone();
this.scanner = new JSScanner(this.currentToken);
this.noSkipTokenSet = new NoSkipTokenSet();
this.errorToken = null;
this.program = null;
this.blockType = new ArrayList(16);
this.labelTable = new SimpleHashtable(16);
this.finallyEscaped = 0;
this.Globals = context.document.engine.Globals;
this.Severity = 5;
}
//---------------------------------------------------------------------------------------
// Parse
//
// Parser main entry point. Parse all the source code and return the root of the AST
//---------------------------------------------------------------------------------------
public ScriptBlock Parse(){
Block ast = ParseStatements(false);
return new ScriptBlock(this.sourceContext.Clone(), ast);
}
//---------------------------------------------------------------------------------------
// ParseEvalBody
//
// Parser main entry point for Eval. Parse all the source code and return the root of the AST
//---------------------------------------------------------------------------------------
public Block ParseEvalBody(){
return ParseStatements(true);
}
//---------------------------------------------------------------------------------------
// ParseExpressionItem
//
// A VSAITEMTYPE2.EXPRESSION call into here to parse an expression
//---------------------------------------------------------------------------------------
internal ScriptBlock ParseExpressionItem(){
try{
Block block = new Block(this.sourceContext.Clone());
GetNextToken();
block.Append(new Expression(this.sourceContext.Clone(), ParseExpression()));
return new ScriptBlock(this.sourceContext.Clone(), block);
}catch (EndOfFile){
}catch (ScannerException se){
// a scanner exception implies that the end of file has been reached with an error.
// Mark the end of file as the error location
EOFError(se.m_errorId);
}
return null;
}
//---------------------------------------------------------------------------------------
// ParseStatements
//
// statements :
// <empty> |
// statement statements |
// ImportStatement
//
//---------------------------------------------------------------------------------------
private Block ParseStatements(bool insideEval){
this.program = new Block(this.sourceContext.Clone());
this.blockType.Add(BlockType.Block);
this.errorToken = null;
try{
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_TopLevelNoSkipTokenSet);
try{
while (this.currentToken.token != JSToken.EndOfFile){
AST ast = null;
try{
if (this.currentToken.token == JSToken.Package && !insideEval){
ast = ParsePackage(this.currentToken.Clone());
}else if (this.currentToken.token == JSToken.Import && !insideEval){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
try{
ast = ParseImportStatement();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet, exc) != -1){
ast = exc._partiallyComputedNode;
if (exc._token == JSToken.Semicolon)
GetNextToken();
}else{
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
}
}else
ast = ParseStatement();
}catch(RecoveryTokenException exc){
if (TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, exc)
|| TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc)){
ast = exc._partiallyComputedNode;
}else{
this.errorToken = null;
do{
GetNextToken();
}while(this.currentToken.token != JSToken.EndOfFile && !TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, this.currentToken.token)
&& !TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, this.currentToken.token));
}
}
if (null != ast)
this.program.Append(ast);
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_TopLevelNoSkipTokenSet);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
}
}catch (EndOfFile){
}catch (ScannerException se){
// a scanner exception implies that the end of file has been reached with an error.
// Mark the end of file as the error location
EOFError(se.m_errorId);
}
return this.program;
}
#if DEBUG
//---------------------------------------------------------------------------------------
// Tokenize
//
// Debug only
//---------------------------------------------------------------------------------------
public void Tokenize(){
try{
GetNextToken();
long i = 1;
while(this.currentToken.token != JSToken.EndOfFile){
//Console.Out.WriteLine(this.currentToken.GetCode());
GetNextToken();
i++;
}
Console.Out.WriteLine("\nNumber of Tokens: " + i);
}catch (ScannerException se){
Console.Out.WriteLine("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
Console.Out.WriteLine("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
Console.Out.WriteLine("Scanner Error:\t");
Console.Out.WriteLine(se.Message);
}
}
#endif //DEBUG
//---------------------------------------------------------------------------------------
// ParseStatement
//
// OptionalStatement:
// Statement |
// <empty>
//
// Statement :
// Block |
// VariableStatement |
// EmptyStatement |
// ExpressionStatement |
// IfStatement |
// IterationStatement |
// ContinueStatement |
// BreakStatement |
// ReturnStatement |
// WithStatement |
// LabeledStatement |
// SwitchStatement |
// ThrowStatement |
// TryStatement |
// QualifiedDeclaration |
// Class |
// FunctionDeclaration
//
// IterationStatement :
// 'for' '(' ForLoopControl ')' | ===> ForStatement
// 'do' Statement 'while' '(' Expression ')' | ===> DoStatement
// 'while' '(' Expression ')' Statement ===> WhileStatement
//
//---------------------------------------------------------------------------------------
// ParseStatement deals with the end of statement issue (EOL vs ';') so if any of the
// ParseXXX routine does it as well, it should return directly from the switch statement
// without any further execution in the ParseStatement
private AST ParseStatement(){
AST statement = null;
String id = null;
switch (this.currentToken.token){
case JSToken.EndOfFile:
EOFError(JSError.ErrEOF);
throw new EndOfFile(); // abort parsing, get back to the main parse routine
case JSToken.Debugger:
statement = new DebugBreak(this.currentToken.Clone());
GetNextToken();
break;
case JSToken.Semicolon:
// make an empty statement
statement = new Block(this.currentToken.Clone());
GetNextToken();
return statement;
case JSToken.RightCurly:
ReportError(JSError.SyntaxError);
SkipTokensAndThrow();
break;
case JSToken.LeftCurly:
return ParseBlock();
case JSToken.Var:
case JSToken.Const:
return ParseVariableStatement((FieldAttributes)0, null, this.currentToken.token);
case JSToken.If:
return ParseIfStatement();
case JSToken.For:
return ParseForStatement();
case JSToken.Do:
return ParseDoStatement();
case JSToken.While:
return ParseWhileStatement();
case JSToken.Continue:
statement = ParseContinueStatement();
if (null == statement)
return new Block(CurrentPositionContext());
else
return statement;
case JSToken.Break:
statement = ParseBreakStatement();
if (null == statement)
return new Block(CurrentPositionContext());
else
return statement;
case JSToken.Return:
statement = ParseReturnStatement();
if (null == statement)
return new Block(CurrentPositionContext());
else
return statement;
case JSToken.With:
return ParseWithStatement();
case JSToken.Switch:
return ParseSwitchStatement();
case JSToken.Super:
case JSToken.This:
Context superCtx = this.currentToken.Clone();
if (JSToken.LeftParen == this.scanner.PeekToken())
statement = ParseConstructorCall(superCtx);
else
goto default;
break;
case JSToken.Throw:
statement = ParseThrowStatement();
if (statement == null)
return new Block(CurrentPositionContext());
else
break;
case JSToken.Try:
return ParseTryStatement();
case JSToken.Internal:
case JSToken.Public:
case JSToken.Static:
case JSToken.Private:
case JSToken.Protected:
case JSToken.Abstract:
case JSToken.Final:
bool parsedOK;
statement = ParseAttributes(null, false, false, out parsedOK);
if (!parsedOK){
statement = ParseExpression(statement, false, true, JSToken.None);
statement = new Expression(statement.context.Clone(), statement);
break;
}else
return statement;
case JSToken.Package:
Context packageContext = this.currentToken.Clone();
statement = ParsePackage(packageContext);
if (statement is Package){
// handle common error of using import in function
ReportError(JSError.PackageInWrongContext, packageContext, true);
// make an empty statement
statement = new Block(packageContext);
}
break;
case JSToken.Interface:
case JSToken.Class:
return ParseClass((FieldAttributes)0, false, this.currentToken.Clone(), false, false, null);
case JSToken.Enum:
return ParseEnum((FieldAttributes)0, this.currentToken.Clone(), null);
case JSToken.Function:
return ParseFunction((FieldAttributes)0, false, this.currentToken.Clone(), false, false, false, false, null); //Parse a function as a statement
case JSToken.Else:
ReportError(JSError.InvalidElse);
SkipTokensAndThrow();
break;
case JSToken.Import:
// handle common error of using import in function
ReportError(JSError.InvalidImport, true);
// make an empty statement
statement = new Block(this.currentToken.Clone());
try{
ParseImportStatement();
}catch(RecoveryTokenException){
}
break;
default:
this.noSkipTokenSet.Add(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
bool exprError = false;
try{
bool bAssign, canBeAttribute = true;
statement = ParseUnaryExpression(out bAssign, ref canBeAttribute, false);
if (canBeAttribute){
// look for labels
if (statement is Lookup){
if (JSToken.Colon == this.currentToken.token){
// can be a label
id = statement.ToString();
if (null != this.labelTable[id]){
// there is already a label with that name. Ignore the current label
ReportError(JSError.BadLabel, statement.context.Clone(), true);
id = null;
GetNextToken(); // skip over ':'
return new Block(CurrentPositionContext());
}else{
GetNextToken();
this.labelTable[id] = this.blockType.Count;
if (JSToken.EndOfFile != this.currentToken.token)
statement = ParseStatement();
else
statement = new Block(CurrentPositionContext());
this.labelTable.Remove(id);
return statement;
}
}
}
// look for custom attributes
if (JSToken.Semicolon != this.currentToken.token && !this.scanner.GotEndOfLine()){
bool parsed;
statement = ParseAttributes(statement, false, false, out parsed);
if (parsed)
return statement;
}
}
statement = ParseExpression(statement, false, bAssign, JSToken.None);
statement = new Expression(statement.context.Clone(), statement);
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
statement = exc._partiallyComputedNode;
if (statement == null){
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
exprError = true;
SkipTokensAndThrow();
}
if (IndexOfToken(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = statement;
throw exc;
}
}finally{
if (!exprError)
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
}
break;
}
if (JSToken.Semicolon == this.currentToken.token){
statement.context.UpdateWith(this.currentToken);
GetNextToken();
}else if (!this.scanner.GotEndOfLine() && JSToken.RightCurly != this.currentToken.token && JSToken.EndOfFile != this.currentToken.token)
ReportError(JSError.NoSemicolon, true);
return statement;
}
//---------------------------------------------------------------------------------------
// ParseAttributes
//
// Package :
// Attributes 'package' QualifiedIdentifier '{' ClassList '}'
// AttributesStart 'package' QualifiedIdentifier '{' ClassList '}'
//
// Class :
// Attributes 'class' Identifier ['extends' QualifiedIdentifier] '{' ClassBody '}'
// AttributesStart 'class' Identifier ['extends' QualifiedIdentifier] '{' ClassBody '}'
//
// FunctionDeclaration :
// Attributes 'function' Identifier '(' [FormalParameterList] ')' [':' TypeExpression] '{' FunctionBody '}'
// AttributesStart 'function' Identifier '(' [FormalParameterList] ')' [':' TypeExpression] '{' FunctionBody '}'
//
// VariableStatement :
// Attributes 'var' VariableDeclarationList
// AttributesStart 'var' VariableDeclarationList
//
// Attributes :
// AttributesStart AttributesNext |
// Modifier [AttributeNext]
//
// AttributesStart :
// QualifiedIdentifier /no LineTerminator here/ |
// QualifiedIdentifier Arguments /no LineTerminator here/
//
// AttributesNext :
// QualifiedIdentifier [AttributesNext] |
// QualifiedIdentifier Arguments [AttributesNext] |
// Modifier [AttributesNext]
//
// Modifier :
// 'private' |
// 'public' |
// 'protected' |
// 'internal' |
// 'package' | //This is going away
// 'static' |
// 'abstract'
//
//
// Parse the list of attributes and the statement those attributes refer to.
// It comes back with one of the following: package, class, function, variable declaration.
// When in error it may return other AST constructs depending on the context.
// The input is a possible attribute in the form of 'QualifiedIdentifier' or a sequence
// 'QualifiedIdentifier Arguments' or null when the first attribute was a Modifier.
// The whole sequence may be an error in the first place, so we want to keep
// things such that we can always parse the supposed list of attributes as an ast list
//---------------------------------------------------------------------------------------
AST ParseAttributes(AST statement, bool unambiguousContext, bool isInsideClass, out bool parsedOK){
// used later to verify whether anything at all has been processed
AST inputAST = statement;
// keep two list of errors. One holds the 'missing ;' errors that would result in the token
// stream being parsed as a list of expressions, the other is the list of attributes errors
// that would result from the token stream being parsed as a list of attributes. We need both because
// we don't know until the end which is which
ArrayList semicolonErrors = new ArrayList();
// keep track of the modifiers context in case they'll have to be parsed as identifiers, we give the warning out
ArrayList modifiersAsIdentifier = new ArrayList();
// this list has two elements for each error, the first (even position - 0 based) is the error type
// the second is the context
ArrayList attributesErrors = new ArrayList();
// keep the last ast node parsed. Used if we have to "revert" to the expression list production.
// We just return the last ast processed, after all is an error condition
AST lastAST = null;
// keep the list of attributes
ArrayList attributes = new ArrayList();
// hold the context for the 'abstract' keyword and it is used as a flag for its presence
Context abstractContext = null;
// hold the context for the 'static' keyword and it is used as a flag for its presence
Context staticContext = null;
// hold the context for the 'final' keyword and it is used as a flag for its presence
Context finalContext = null;
// keep the number of consecutive token no separated by end of line. Once that number
// reaches 2 the token stream is parsed *only* as a sequence of attributes
int TokenCountNoEOL = 0;
if (unambiguousContext)
TokenCountNoEOL = 2;
FieldAttributes visibilitySpec = (FieldAttributes)0, currVis = (FieldAttributes)0;
Context statementContext = null;
// null when coming from a visibility modifier
if (statement != null){
lastAST = statement;
attributes.Add(statement);
semicolonErrors.Add(CurrentPositionContext());
statementContext = statement.context.Clone();
TokenCountNoEOL = 1;
}else{
statementContext = this.currentToken.Clone();
}
parsedOK = true;
while (true){
JSToken attributeToken = JSToken.None;
switch (this.currentToken.token){
case JSToken.Public:
case JSToken.Static:
case JSToken.Private:
case JSToken.Protected:
case JSToken.Abstract:
case JSToken.Final:
case JSToken.Internal:
attributeToken = this.currentToken.token;
goto case JSToken.Identifier;
case JSToken.Var:
case JSToken.Const:
for (int i = 0, n = attributesErrors.Count; i < n; i += 2)
ReportError((JSError)attributesErrors[i], (Context)attributesErrors[i + 1], true);
if (abstractContext != null)
ReportError(JSError.IllegalVisibility, abstractContext, true);
if (finalContext != null)
ReportError(JSError.IllegalVisibility, finalContext, true);
statementContext.UpdateWith(this.currentToken);
return ParseVariableStatement(visibilitySpec, FromASTListToCustomAttributeList(attributes), this.currentToken.token);
case JSToken.Function:
for (int i = 0, n = attributesErrors.Count; i < n; i += 2)
ReportError((JSError)attributesErrors[i], (Context)attributesErrors[i + 1], true);
statementContext.UpdateWith(this.currentToken);
if (staticContext != null)
if (abstractContext != null){
staticContext.HandleError(JSError.AbstractCannotBeStatic);
staticContext = null;
}else if (finalContext != null){
finalContext.HandleError(JSError.StaticIsAlreadyFinal);
finalContext = null;
}
if (abstractContext != null){
if (finalContext != null){
finalContext.HandleError(JSError.FinalPrecludesAbstract);
finalContext = null;
}
if (currVis == FieldAttributes.Private){
abstractContext.HandleError(JSError.AbstractCannotBePrivate);
currVis = FieldAttributes.Family;
}
}
return ParseFunction(visibilitySpec, false, statementContext, isInsideClass,
abstractContext != null, finalContext != null, false, FromASTListToCustomAttributeList(attributes));
case JSToken.Interface:
if (abstractContext != null){
ReportError(JSError.IllegalVisibility, abstractContext, true);
abstractContext = null;
}
if (finalContext != null){
ReportError(JSError.IllegalVisibility, finalContext, true);
finalContext = null;
}
if (staticContext != null){
ReportError(JSError.IllegalVisibility, staticContext, true);
staticContext = null;
}
goto case JSToken.Class;
case JSToken.Class:
for (int i = 0, n = attributesErrors.Count; i < n; i += 2)
ReportError((JSError)attributesErrors[i], (Context)attributesErrors[i + 1], true);
statementContext.UpdateWith(this.currentToken);
if (finalContext != null && abstractContext != null)
finalContext.HandleError(JSError.FinalPrecludesAbstract);
return ParseClass(visibilitySpec, staticContext != null, statementContext,
abstractContext != null, finalContext != null, FromASTListToCustomAttributeList(attributes));
case JSToken.Enum:
for (int i = 0, n = attributesErrors.Count; i < n; i += 2)
ReportError((JSError)attributesErrors[i], (Context)attributesErrors[i + 1], true);
statementContext.UpdateWith(this.currentToken);
if (abstractContext != null)
ReportError(JSError.IllegalVisibility, abstractContext, true);
if (finalContext != null)
ReportError(JSError.IllegalVisibility, finalContext, true);
if (staticContext != null)
ReportError(JSError.IllegalVisibility, staticContext, true);
return ParseEnum(visibilitySpec, statementContext, FromASTListToCustomAttributeList(attributes));
case JSToken.Void:
case JSToken.Boolean:
case JSToken.Byte:
case JSToken.Char:
case JSToken.Double:
case JSToken.Float:
case JSToken.Int:
case JSToken.Long:
case JSToken.Short: {
// The user has some sequence like "public int foo()", which
// is probably a C-style function declaration. Save that away,
// keep parsing and see if this ends in what looks like a function
// or variable declaration.
parsedOK = false;
lastAST = new Lookup(this.currentToken);
attributeToken = JSToken.None;
attributes.Add(lastAST);
GetNextToken();
goto continueLoop;
}
case JSToken.Identifier: {
bool bAssign, canBeAttribute = true;
statement = ParseUnaryExpression(out bAssign, ref canBeAttribute, false, attributeToken == JSToken.None);
lastAST = statement;
if (attributeToken != JSToken.None){
if (statement is Lookup)
break;
else{
if (TokenCountNoEOL != 2)
modifiersAsIdentifier.Add(this.currentToken.Clone());
}
}
attributeToken = JSToken.None;
if (canBeAttribute){
attributes.Add(statement);
goto continueLoop;
}else
goto default;
}
// the following cases are somewhat interesting because they can be determined by a user missing
// the keyword 'var' or 'function', as, for instance, when coming from C/C++/C# code
default:
parsedOK = false;
if (TokenCountNoEOL != 2){
if (inputAST != statement || statement == null){
Debug.Assert(lastAST != null);
// only return the last statement
statement = lastAST;
// output the semicolon errors
for (int i = 0, n = modifiersAsIdentifier.Count; i < n; i++)
ForceReportInfo((Context)modifiersAsIdentifier[i], JSError.KeywordUsedAsIdentifier);
for (int i = 0, n = semicolonErrors.Count; i < n; i++)
if (!this.currentToken.Equals((Context)semicolonErrors[i]))
ReportError(JSError.NoSemicolon, (Context)semicolonErrors[i], true);
}
return statement;
}else{
if (attributes.Count > 0){
// check for possible common mistakes
AST ast = (AST)attributes[attributes.Count - 1];
if (ast is Lookup){
if (JSToken.Semicolon == this.currentToken.token
|| JSToken.Colon == this.currentToken.token){
ReportError(JSError.BadVariableDeclaration, ast.context.Clone());
SkipTokensAndThrow();
}
}else if (ast is Call && ((Call)ast).CanBeFunctionDeclaration()){
if (JSToken.Colon == this.currentToken.token
|| JSToken.LeftCurly == this.currentToken.token){
ReportError(JSError.BadFunctionDeclaration, ast.context.Clone(), true);
if (JSToken.Colon == this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_StartBlockNoSkipTokenSet);
try{
SkipTokensAndThrow();
}catch(RecoveryTokenException){
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_StartBlockNoSkipTokenSet);
}
}
this.errorToken = null;
if (JSToken.LeftCurly == this.currentToken.token){
FunctionScope fscope = new FunctionScope(Globals.ScopeStack.Peek(), isInsideClass);
Globals.ScopeStack.Push(fscope);
try{
ParseBlock();
}finally{
Globals.ScopeStack.Pop();
}
SkipTokensAndThrow();
}
}
else
ReportError(JSError.SyntaxError, ast.context.Clone());
SkipTokensAndThrow();
}
}
if (JSToken.LeftCurly == this.currentToken.token){
if (isInsideClass){
// parse it like a static initializer
// flush all the possible attribute errors
for (int i = 0, n = attributesErrors.Count; i < n; i += 2)
ReportError((JSError)attributesErrors[i], (Context)attributesErrors[i + 1]);
if (staticContext == null)
ReportError(JSError.StaticMissingInStaticInit, CurrentPositionContext());
String className = ((ClassScope)Globals.ScopeStack.Peek()).name;
bool reportNoIdentifier = true;
foreach (Object attr in attributes){
reportNoIdentifier = false;
if (staticContext != null && attr is Lookup && attr.ToString() == className &&
((Lookup)attr).context.StartColumn > staticContext.StartColumn)
continue;
ReportError(JSError.SyntaxError, ((AST)attr).context);
}
if (reportNoIdentifier)
ReportError(JSError.NoIdentifier, CurrentPositionContext());
this.errorToken = null; // we want to go to next token regardless of whether or not we had an error
// return a static initializer
parsedOK = true;
return ParseStaticInitializer(statementContext);
}
}
//mark them all as errors
ReportError(JSError.MissingConstructForAttributes, statementContext.CombineWith(this.currentToken));
}
SkipTokensAndThrow();
break;
}
switch (attributeToken){
case JSToken.Public:
currVis = FieldAttributes.Public;
break;
case JSToken.Static:
if (isInsideClass){
currVis = FieldAttributes.Static;
if (staticContext != null){
attributesErrors.Add(JSError.SyntaxError);
attributesErrors.Add(statement.context.Clone());
}else
staticContext = statement.context.Clone();
}else{
attributesErrors.Add(JSError.NotInsideClass);
attributesErrors.Add(statement.context.Clone());
}
break;
case JSToken.Private:
if (isInsideClass)
currVis = FieldAttributes.Private;
else{
attributesErrors.Add(JSError.NotInsideClass);
attributesErrors.Add(statement.context.Clone());
}
break;
case JSToken.Protected:
if (isInsideClass){
currVis = FieldAttributes.Family;
}else{
attributesErrors.Add(JSError.NotInsideClass);
attributesErrors.Add(statement.context.Clone());
}
break;
case JSToken.Abstract:
if (abstractContext != null){
attributesErrors.Add(JSError.SyntaxError);
attributesErrors.Add(statement.context.Clone());
}else
abstractContext = statement.context.Clone();
goto continueLoop;
case JSToken.Final:
if (finalContext != null){
attributesErrors.Add(JSError.SyntaxError);
attributesErrors.Add(statement.context.Clone());
}else
finalContext = statement.context.Clone();
goto continueLoop;
case JSToken.Internal:
currVis = FieldAttributes.Assembly;
break;
default:
break;
}
// come here only after a visibility Modifer
if ((visibilitySpec & FieldAttributes.FieldAccessMask) == currVis && currVis != (FieldAttributes)0){
attributesErrors.Add(JSError.DupVisibility);
attributesErrors.Add(statement.context.Clone());
}else if ((visibilitySpec & FieldAttributes.FieldAccessMask) > (FieldAttributes)0 && (currVis & FieldAttributes.FieldAccessMask) > (FieldAttributes)0){
if ((currVis == FieldAttributes.Family && (visibilitySpec & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly) ||
(currVis == FieldAttributes.Assembly && (visibilitySpec & FieldAttributes.FieldAccessMask) == FieldAttributes.Family)){
visibilitySpec &= ~FieldAttributes.FieldAccessMask;
visibilitySpec |= FieldAttributes.FamORAssem;
}else{
attributesErrors.Add(JSError.IncompatibleVisibility);
attributesErrors.Add(statement.context.Clone());
}
}else{
visibilitySpec |= currVis;
statementContext.UpdateWith(statement.context);
}
continueLoop:
if (TokenCountNoEOL != 2){
if (this.scanner.GotEndOfLine()){
TokenCountNoEOL = 0;
}else{
TokenCountNoEOL++;
semicolonErrors.Add(this.currentToken.Clone());
}
}
}
}
//---------------------------------------------------------------------------------------
// ParseBlock
//
// Block :
// '{' OptionalStatements '}'
//---------------------------------------------------------------------------------------
Block ParseBlock(){
Context ctx;
return ParseBlock(out ctx);
}
Block ParseBlock(out Context closingBraceContext){
closingBraceContext = null;
this.blockType.Add(BlockType.Block);
Block codeBlock = new Block(this.currentToken.Clone());
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
try{
try{
while (JSToken.RightCurly != this.currentToken.token){
try{
codeBlock.Append(ParseStatement());
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
codeBlock.Append(exc._partiallyComputedNode);
if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
throw exc;
}
}
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = codeBlock;
throw exc;
}
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
this.blockType.RemoveAt(this.blockType.Count - 1);
}
closingBraceContext = this.currentToken.Clone();
// update the block context
codeBlock.context.UpdateWith(this.currentToken);
GetNextToken();
return codeBlock;
}
//---------------------------------------------------------------------------------------
// ParseVariableStatement
//
// VariableStatement :
// ('var' | 'const') VariableDeclarationList
//
// VariableDeclarationList :
// VariableDeclaration |
// VariableDeclaration ',' VariableDeclarationList
//
// VariableDeclaration :
// Identifier Type Initializer
//
// Type :
// <empty> |
// ':' TypeExpression
//
// Initializer :
// <empty> |
// '=' AssignmentExpression
//---------------------------------------------------------------------------------------
private AST ParseVariableStatement(FieldAttributes visibility, CustomAttributeList customAttributes, JSToken kind){
Block varList = new Block(this.currentToken.Clone());
bool single = true;
AST vdecl = null;
for (;;){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_EndOfLineToken);
try{
vdecl = ParseIdentifierInitializer(JSToken.None, visibility, customAttributes, kind);
}catch(RecoveryTokenException exc){
// an exception is passing by, possibly bringing some info, save the info if any
if (exc._partiallyComputedNode != null){
if (!single){
varList.Append(exc._partiallyComputedNode);
varList.context.UpdateWith(exc._partiallyComputedNode.context);
exc._partiallyComputedNode = varList;
}
}
if (IndexOfToken(NoSkipTokenSet.s_EndOfLineToken, exc) == -1)
throw exc;
else{
if (single)
vdecl = exc._partiallyComputedNode;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EndOfLineToken);
}
if (JSToken.Semicolon == this.currentToken.token || JSToken.RightCurly == this.currentToken.token){
if (JSToken.Semicolon == this.currentToken.token){
vdecl.context.UpdateWith(this.currentToken);
GetNextToken();
}
break;
}else if (JSToken.Comma == this.currentToken.token){
single = false;
varList.Append(vdecl);
continue;
}else if (this.scanner.GotEndOfLine()){
break;
}else{
// assume the variable statement was terminated and move on
ReportError(JSError.NoSemicolon, true);
break;
}
}
if (single)
return vdecl;
else{
varList.Append(vdecl);
varList.context.UpdateWith(vdecl.context);
return varList;
}
}
//---------------------------------------------------------------------------------------
// ParseIdentifierInitializer
//
// Does the real work of parsing a single variable declaration.
// inToken is JSToken.In whenever the potential expression that initialize a variable
// cannot contain an 'in', as in the for statement. inToken is JSToken.None otherwise
//---------------------------------------------------------------------------------------
private AST ParseIdentifierInitializer(JSToken inToken,
FieldAttributes visibility,
CustomAttributeList customAttributes,
JSToken kind){
Lookup id = null;
TypeExpression typeExpr = null;
AST assignmentExpr = null;
RecoveryTokenException except = null;
GetNextToken();
if (JSToken.Identifier != this.currentToken.token){
String identifier = JSKeyword.CanBeIdentifier(this.currentToken.token);
if (null != identifier){
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
id = new Lookup(identifier, this.currentToken.Clone());
}else{
// make up an identifier and keep going; life goes on...
ReportError(JSError.NoIdentifier);
id = new Lookup("#_Missing Identifier_#" + s_cDummyName++, CurrentPositionContext());
}
}else
id = new Lookup(this.scanner.GetIdentifier(), this.currentToken.Clone());
GetNextToken();
Context context = id.context.Clone();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet);
try{
if (JSToken.Colon == this.currentToken.token){
try{
typeExpr = ParseTypeExpression();
}catch(RecoveryTokenException exc){
typeExpr = (TypeExpression)exc._partiallyComputedNode;
throw exc;
}finally{
if (null != typeExpr)
context.UpdateWith(typeExpr.context);
}
}
if (JSToken.Assign == this.currentToken.token || JSToken.Equal == this.currentToken.token){
if (JSToken.Equal == this.currentToken.token)
ReportError(JSError.NoEqual, true);
GetNextToken();
try{
assignmentExpr = ParseExpression(true, inToken);
}catch(RecoveryTokenException exc){
assignmentExpr = exc._partiallyComputedNode;
throw exc;
}finally{
if (null != assignmentExpr)
context.UpdateWith(assignmentExpr.context);
}
}
}catch(RecoveryTokenException exc){
// If the exception is in the vardecl no-skip set then we successfully
// recovered to the end of the declaration and can just return
// normally. Otherwise we re-throw after constructing the partial result.
if (IndexOfToken(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet, exc) == -1)
except = exc;
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet);
}
AST result = null;
if (JSToken.Var == kind)
result = new VariableDeclaration(context, id, typeExpr, assignmentExpr, visibility, customAttributes);
else{
if (assignmentExpr == null)
ForceReportInfo(JSError.NoEqual);
result = new Constant(context, id, typeExpr, assignmentExpr, visibility, customAttributes);
}
if (customAttributes != null)
customAttributes.SetTarget(result);
if (null != except){
except._partiallyComputedNode = result;
throw except;
}
return result;
}
//---------------------------------------------------------------------------------------
// ParseQualifiedIdentifier
//
// QualifiedIdentifier :
// 'double' | 'float' | 'int' | 'long' | Scope Identifier
//
// Scope
// <empty> |
// Identifier '.' Scope
//
// The argument error is passed by the caller and it is used in error situation
// to provide better error information. The caller knows in which context this
// qualified identifier is parsed (i.e. var x : QualifiedIdentifier vs.
// import QualifiedIdentifier)
// On error condition this method may return null. Regardless of its return value, though,
// this method will always be pointing to the next token and no error token will be set.
//---------------------------------------------------------------------------------------
private AST ParseQualifiedIdentifier(JSError error){
GetNextToken();
AST qualid = null;
string identifier = null;
Context idContext = this.currentToken.Clone();
if (JSToken.Identifier != this.currentToken.token){
identifier = JSKeyword.CanBeIdentifier(this.currentToken.token);
if (null != identifier){
switch (this.currentToken.token){
case JSToken.Boolean :
case JSToken.Byte :
case JSToken.Char :
case JSToken.Double :
case JSToken.Float :
case JSToken.Int :
case JSToken.Long :
case JSToken.Short :
case JSToken.Void :
break;
default:
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
break;
}
qualid = new Lookup(identifier, idContext);
}else{
ReportError(error, true);
SkipTokensAndThrow(); // this will always throw
}
}else{
qualid = new Lookup(this.scanner.GetIdentifier(), idContext);
}
GetNextToken();
if (JSToken.AccessField == this.currentToken.token)
qualid = ParseScopeSequence(qualid, error);
return qualid;
}
//---------------------------------------------------------------------------------------
// ParseScopeSequence
//
// Scope
// <empty> |
// Identifier '.' Scope
//
// The argument error is passed by the caller and it is used in error situation
// to provide better error information. The caller knows in which context this
// qualified identifier is parsed (i.e. var x : QualifiedIdentifier vs.
// import QualifiedIdentifier)
// This function is called after a 'JSToken.Identifier JSToken.AccessField' sequence
// has been processed and the whole scope production has to be returned
//---------------------------------------------------------------------------------------
private AST ParseScopeSequence(AST qualid, JSError error){
ConstantWrapper id = null;
string identifier = null;
do{
GetNextToken(); // move after '.'
if (JSToken.Identifier != this.currentToken.token){
identifier = JSKeyword.CanBeIdentifier(this.currentToken.token);
if (null != identifier){
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
id = new ConstantWrapper(identifier, this.currentToken.Clone());
}else{
ReportError(error, true);
SkipTokensAndThrow(qualid); // this will always throw
}
}else
id = new ConstantWrapper(this.scanner.GetIdentifier(), this.currentToken.Clone());
qualid = new Member(qualid.context.CombineWith(this.currentToken), qualid, id);
GetNextToken();
} while (JSToken.AccessField == this.currentToken.token);
return qualid;
}
//---------------------------------------------------------------------------------------
// ParseTypeExpression
//
// TypeExpression :
// QualifiedIdentifier |
// TypeExpression <no line break> '[' ']'
//
//--------------------------------------------------------------------------------------
private TypeExpression ParseTypeExpression(){
AST typeId = null;
try{
typeId = ParseQualifiedIdentifier(JSError.NeedType);
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null){
exc._partiallyComputedNode = new TypeExpression(exc._partiallyComputedNode);
}
throw exc;
}
TypeExpression type = new TypeExpression(typeId);
if (type != null){
while (!this.scanner.GotEndOfLine() && JSToken.LeftBracket == this.currentToken.token){
GetNextToken();
int rank = 1;
while (JSToken.Comma == this.currentToken.token){
GetNextToken(); rank++;
}
if (JSToken.RightBracket != this.currentToken.token)
ReportError(JSError.NoRightBracket);
GetNextToken();
if (type.isArray)
type = new TypeExpression(type);
type.isArray = true;
type.rank = rank;
}
}
return type;
}
//---------------------------------------------------------------------------------------
// ParseIfStatement
//
// IfStatement :
// 'if' '(' Expression ')' Statement ElseStatement
//
// ElseStatement :
// <empty> |
// 'else' Statement
//---------------------------------------------------------------------------------------
private If ParseIfStatement(){
Context ifCtx = this.currentToken.Clone();
AST condition = null;
AST trueBranch = null;
AST falseBranch = null;
this.blockType.Add(BlockType.Block);
try{
// parse condition
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
try{
if (JSToken.LeftParen != this.currentToken.token)
ReportError(JSError.NoLeftParen);
GetNextToken();
condition = ParseExpression();
// parse statements
if (JSToken.RightParen != this.currentToken.token){
ifCtx.UpdateWith(condition.context);
ReportError(JSError.NoRightParen);
}else
ifCtx.UpdateWith(this.currentToken);
GetNextToken();
}catch(RecoveryTokenException exc){
// make up an if condition
if (exc._partiallyComputedNode != null)
condition = exc._partiallyComputedNode;
else
condition = new ConstantWrapper(true, CurrentPositionContext());
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null; // really not much to pass up
// the if condition was so bogus we do not have a chance to make an If node, give up
throw exc;
}else{
if (exc._token == JSToken.RightParen)
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
if (condition is Assign)
condition.context.HandleError(JSError.SuspectAssignment);
if (JSToken.Semicolon == this.currentToken.token)
ForceReportInfo(JSError.SuspectSemicolon);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_IfBodyNoSkipTokenSet);
try{
trueBranch = ParseStatement();
}catch(RecoveryTokenException exc){
// make up a block for the if part
if (exc._partiallyComputedNode != null)
trueBranch = exc._partiallyComputedNode;
else
trueBranch = new Block(CurrentPositionContext());
if (IndexOfToken(NoSkipTokenSet.s_IfBodyNoSkipTokenSet, exc) == -1){
// we have to pass the exception to someone else, make as much as you can from the if
exc._partiallyComputedNode = new If(ifCtx, condition, trueBranch, falseBranch);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_IfBodyNoSkipTokenSet);
}
// parse else, if any
if (JSToken.Else == this.currentToken.token){
GetNextToken();
if (JSToken.Semicolon == this.currentToken.token)
ForceReportInfo(JSError.SuspectSemicolon);
try{
falseBranch = ParseStatement();
}catch(RecoveryTokenException exc){
// make up a block for the else part
if (exc._partiallyComputedNode != null)
falseBranch = exc._partiallyComputedNode;
else
falseBranch = new Block(CurrentPositionContext());
exc._partiallyComputedNode = new If(ifCtx, condition, trueBranch, falseBranch);
throw exc;
}
}
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
return new If(ifCtx, condition, trueBranch, falseBranch);
}
//---------------------------------------------------------------------------------------
// ParseForStatement
//
// ForStatement :
// 'for' '(' OptionalExpressionNoIn ';' OptionalExpression ';' OptionalExpression ')'
// 'for' '(' 'var' VariableDeclarationListNoIn ';' OptionalExpression ';' OptionalExpression ')'
// 'for' '(' LeftHandSideExpression 'in' Expression')'
// 'for' '(' 'var' Identifier OptionalInitializerNoIn 'in' Expression')'
//
// OptionalExpressionNoIn :
// <empty> |
// ExpressionNoIn // same as Expression but does not process 'in' as an operator
//
// OptionalInitializerNoIn :
// <empty> |
// InitializerNoIn // same as initializer but does not process 'in' as an operator
//---------------------------------------------------------------------------------------
private AST ParseForStatement(){
this.blockType.Add(BlockType.Loop);
AST forNode = null;
try{
Context forCtx = this.currentToken.Clone();
GetNextToken();
if (JSToken.LeftParen != this.currentToken.token)
ReportError(JSError.NoLeftParen);
GetNextToken();
bool isForIn = false, recoveryInForIn = false;
AST lhs = null, initializer = null, condOrColl = null, increment = null;
try{
if (JSToken.Var == this.currentToken.token){
isForIn = true;
initializer = ParseIdentifierInitializer(JSToken.In, (FieldAttributes)0, null, JSToken.Var);
// a list of variable initializers is allowed only in a for(;;)
AST var = null;
while (JSToken.Comma == this.currentToken.token){
isForIn = false;
var = ParseIdentifierInitializer(JSToken.In, (FieldAttributes)0, null, JSToken.Var);
initializer = new Comma(initializer.context.CombineWith(var.context), initializer, var);
}
// if it could still be a for..in, now it's time to get the 'in'
if (isForIn){
if (JSToken.In == this.currentToken.token){
GetNextToken();
condOrColl = ParseExpression();
}else
isForIn = false;
}
}else{
if (JSToken.Semicolon != this.currentToken.token){
bool isLHS;
initializer = ParseUnaryExpression(out isLHS, false);
if (isLHS && JSToken.In == this.currentToken.token){
isForIn = true;
lhs = initializer;
initializer = null;
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
try{
condOrColl = ParseExpression();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null;
throw exc;
}else{
if (exc._partiallyComputedNode == null)
condOrColl = new ConstantWrapper(true, CurrentPositionContext()); // what could we put here?
else
condOrColl = exc._partiallyComputedNode;
}
if (exc._token == JSToken.RightParen){
GetNextToken();
recoveryInForIn = true;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
}else
initializer = ParseExpression(initializer, false, isLHS, JSToken.In);
}else
initializer = new EmptyLiteral(CurrentPositionContext());
}
}catch(RecoveryTokenException exc){
// error is too early abort for
exc._partiallyComputedNode = null;
throw exc;
}
// at this point we know whether or not is a for..in
if (isForIn){
if (!recoveryInForIn){
if (JSToken.RightParen != this.currentToken.token)
ReportError(JSError.NoRightParen);
forCtx.UpdateWith(this.currentToken);
GetNextToken();
}
AST body = null;
try{
body = ParseStatement();
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode == null)
body = new Block(CurrentPositionContext());
else
body = exc._partiallyComputedNode;
exc._partiallyComputedNode = new ForIn(forCtx, lhs, initializer, condOrColl, body);
throw exc;
}
forNode = new ForIn(forCtx, lhs, initializer, condOrColl, body);
}else{
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
try{
if (JSToken.Semicolon != this.currentToken.token){
ReportError(JSError.NoSemicolon);
if (JSToken.Colon == this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet);
try{
SkipTokensAndThrow();
}catch(RecoveryTokenException exc){
if (JSToken.Semicolon == this.currentToken.token)
this.errorToken = null;
else
throw exc;
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_VariableDeclNoSkipTokenSet);
}
}
}
GetNextToken();
if (JSToken.Semicolon != this.currentToken.token){
condOrColl = ParseExpression();
if (JSToken.Semicolon != this.currentToken.token)
ReportError(JSError.NoSemicolon);
}else
condOrColl = new ConstantWrapper(true, CurrentPositionContext());
GetNextToken();
if (JSToken.RightParen != this.currentToken.token)
increment = ParseExpression();
else
increment = new EmptyLiteral(CurrentPositionContext());
if (JSToken.RightParen != this.currentToken.token)
ReportError(JSError.NoRightParen);
forCtx.UpdateWith(this.currentToken);
GetNextToken();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null;
throw exc;
}else{
// discard any partial info, just genrate empty condition and increment and keep going
exc._partiallyComputedNode = null;
if (condOrColl == null)
condOrColl = new ConstantWrapper(true, CurrentPositionContext());
if (increment == null)
increment = new EmptyLiteral(CurrentPositionContext());
}
if (exc._token == JSToken.RightParen){
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
AST body = null;
try{
body = ParseStatement();
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode == null)
body = new Block(CurrentPositionContext());
else
body = exc._partiallyComputedNode;
exc._partiallyComputedNode = new For(forCtx, initializer, condOrColl, increment, body);
throw exc;
}
forNode = new For(forCtx, initializer, condOrColl, increment, body);
}
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
return forNode;
}
//---------------------------------------------------------------------------------------
// ParseDoStatement
//
// DoStatement:
// 'do' Statement 'while' '(' Expression ')'
//---------------------------------------------------------------------------------------
private DoWhile ParseDoStatement(){
Context doCtx = null; // this.currentToken.Clone();
AST body = null;
AST condition = null;
this.blockType.Add(BlockType.Loop);
try{
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet);
try{
body = ParseStatement();
}catch(RecoveryTokenException exc){
// make up a block for the do while
if (exc._partiallyComputedNode != null)
body = exc._partiallyComputedNode;
else
body = new Block(CurrentPositionContext());
if (IndexOfToken(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet, exc) == -1){
// we have to pass the exception to someone else, make as much as you can from the 'do while'
exc._partiallyComputedNode = new DoWhile(CurrentPositionContext(),
body,
new ConstantWrapper(false, CurrentPositionContext()));
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_DoWhileBodyNoSkipTokenSet);
}
if (JSToken.While != this.currentToken.token){
ReportError(JSError.NoWhile);
}
doCtx = this.currentToken.Clone();
GetNextToken();
if (JSToken.LeftParen != this.currentToken.token){
ReportError(JSError.NoLeftParen);
}
GetNextToken();
// catch here so the body of the do_while is not thrown away
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
try{
condition = ParseExpression();
if (JSToken.RightParen != this.currentToken.token){
ReportError(JSError.NoRightParen);
doCtx.UpdateWith(condition.context);
}else
doCtx.UpdateWith(this.currentToken);
GetNextToken();
}catch(RecoveryTokenException exc){
// make up a condition
if (exc._partiallyComputedNode != null)
condition = exc._partiallyComputedNode;
else
condition = new ConstantWrapper(false, CurrentPositionContext());
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = new DoWhile(doCtx, body, condition);
throw exc;
}else{
if (JSToken.RightParen == this.currentToken.token)
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
if (JSToken.Semicolon == this.currentToken.token){
// JScript 5 allowed statements like
// do{print(++x)}while(x<10) print(0)
// even though that does not strictly follow the automatic semicolon insertion
// rules for the required semi after the while(). For backwards compatibility
// we should continue to support this.
doCtx.UpdateWith(this.currentToken);
GetNextToken();
}
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
return new DoWhile(doCtx, body, condition);
}
//---------------------------------------------------------------------------------------
// ParseWhileStatement
//
// WhileStatement :
// 'while' '(' Expression ')' Statement
//---------------------------------------------------------------------------------------
private While ParseWhileStatement(){
Context whileCtx = this.currentToken.Clone();
AST condition = null;
AST body = null;
this.blockType.Add(BlockType.Loop);
try{
GetNextToken();
if (JSToken.LeftParen != this.currentToken.token){
ReportError(JSError.NoLeftParen);
}
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
try{
condition = ParseExpression();
if (JSToken.RightParen != this.currentToken.token){
ReportError(JSError.NoRightParen);
whileCtx.UpdateWith(condition.context);
}else
whileCtx.UpdateWith(this.currentToken);
GetNextToken();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1){
// abort the while there is really no much to do here
exc._partiallyComputedNode = null;
throw exc;
}else{
// make up a condition
if (exc._partiallyComputedNode != null)
condition = exc._partiallyComputedNode;
else
condition = new ConstantWrapper(false, CurrentPositionContext());
if (JSToken.RightParen == this.currentToken.token)
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
try{
body = ParseStatement();
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
body = exc._partiallyComputedNode;
else
body = new Block(CurrentPositionContext());
exc._partiallyComputedNode = new While(whileCtx, condition, body);
throw exc;
}
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
return new While(whileCtx, condition, body);
}
//---------------------------------------------------------------------------------------
// ParseContinueStatement
//
// ContinueStatement :
// 'continue' OptionalLabel
//
// OptionalLabel :
// <empty> |
// Identifier
//
// This function may return a null AST under error condition. The caller should handle
// that case.
// Regardless of error conditions, on exit the parser points to the first token after
// the continue statement
//---------------------------------------------------------------------------------------
private Continue ParseContinueStatement(){
Context context = this.currentToken.Clone();
int blocks = 0;
GetNextToken();
string label = null;
if (!this.scanner.GotEndOfLine() && (JSToken.Identifier == this.currentToken.token || (label = JSKeyword.CanBeIdentifier(this.currentToken.token)) != null)){
context.UpdateWith(this.currentToken);
// get the label block
if (null != label)
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
else
label = this.scanner.GetIdentifier();
Object value = this.labelTable[label];
if (null == value){
// the label does not exist. Continue as if it there was no continue at all
ReportError(JSError.NoLabel, true);
GetNextToken();
return null; // ignore it
}else{
blocks = (int)value;
if ((BlockType)this.blockType[blocks] != BlockType.Loop){
ReportError(JSError.BadContinue, context.Clone(), true);
}
GetNextToken();
}
}else{
blocks = this.blockType.Count - 1;
while (blocks >= 0 && (BlockType)this.blockType[blocks] != BlockType.Loop) blocks--;
if (blocks < 0){
// the continue is malformed. Continue as if there was no continue at all
ReportError(JSError.BadContinue, context, true);
return null;
}
}
if (JSToken.Semicolon == this.currentToken.token){
context.UpdateWith(this.currentToken);
GetNextToken();
}else if (JSToken.RightCurly != this.currentToken.token && !this.scanner.GotEndOfLine()){
ReportError(JSError.NoSemicolon, true);
}
// must ignore the Finally block
int finallyNum = 0;
for (int i = blocks, n = this.blockType.Count; i < n; i++)
if ((BlockType)this.blockType[i] == BlockType.Finally){
blocks++;
finallyNum++;
}
if (finallyNum > this.finallyEscaped)
this.finallyEscaped = finallyNum;
return new Continue(context, this.blockType.Count - blocks, finallyNum > 0);
}
//---------------------------------------------------------------------------------------
// ParseBreakStatement
//
// BreakStatement :
// 'break' OptionalLabel
//
// This function may return a null AST under error condition. The caller should handle
// that case.
// Regardless of error conditions, on exit the parser points to the first token after
// the break statement.
//---------------------------------------------------------------------------------------
private Break ParseBreakStatement(){
Context context = this.currentToken.Clone();
int blocks = 0;
GetNextToken();
string label = null;
if (!this.scanner.GotEndOfLine() && (JSToken.Identifier == this.currentToken.token || (label = JSKeyword.CanBeIdentifier(this.currentToken.token)) != null)){
context.UpdateWith(this.currentToken);
// get the label block
if (null != label)
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
else
label = this.scanner.GetIdentifier();
Object value = this.labelTable[label];
if (null == value){
// as if it was a non label case
ReportError(JSError.NoLabel, true);
GetNextToken();
return null; // ignore it
}else{
blocks = (int)value - 1; // the outer block
Debug.Assert((BlockType)this.blockType[blocks] != BlockType.Finally);
GetNextToken();
}
}else{
blocks = this.blockType.Count - 1;
// search for an enclosing loop, if there is no loop it is an error
while (((BlockType)this.blockType[blocks] == BlockType.Block || (BlockType)this.blockType[blocks] == BlockType.Finally) && --blocks >= 0);
--blocks;
if (blocks < 0){
ReportError(JSError.BadBreak, context, true);
return null;
}
}
if (JSToken.Semicolon == this.currentToken.token){
context.UpdateWith(this.currentToken);
GetNextToken();
}else if (JSToken.RightCurly != this.currentToken.token && !this.scanner.GotEndOfLine()){
ReportError(JSError.NoSemicolon, true);
}
// must ignore the Finally block
int finallyNum = 0;
for (int i = blocks, n = this.blockType.Count; i < n; i++)
if ((BlockType)this.blockType[i] == BlockType.Finally){
blocks++;
finallyNum++;
}
if (finallyNum > this.finallyEscaped)
this.finallyEscaped = finallyNum;
return new Break(context, this.blockType.Count - blocks - 1, finallyNum > 0);
}
private bool CheckForReturnFromFinally(){
int finallyNum = 0;
for (int i = this.blockType.Count - 1; i >= 0; i--)
if ((BlockType)this.blockType[i] == BlockType.Finally)
finallyNum++;
if (finallyNum > this.finallyEscaped)
this.finallyEscaped = finallyNum;
return finallyNum > 0;
}
//---------------------------------------------------------------------------------------
// ParseReturnStatement
//
// ReturnStatement :
// 'return' Expression
//
// This function may return a null AST under error condition. The caller should handle
// that case.
// Regardless of error conditions, on exit the parser points to the first token after
// the return statement.
//---------------------------------------------------------------------------------------
private Return ParseReturnStatement(){
Context retCtx = this.currentToken.Clone();
if (Globals.ScopeStack.Peek() is FunctionScope){
AST expr = null;
GetNextToken();
if (!this.scanner.GotEndOfLine()){
if (JSToken.Semicolon != this.currentToken.token && JSToken.RightCurly != this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
try{
expr = ParseExpression();
}catch(RecoveryTokenException exc){
expr = exc._partiallyComputedNode;
if (IndexOfToken(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet, exc) == -1){
if (expr != null)
retCtx.UpdateWith(expr.context);
exc._partiallyComputedNode = new Return(retCtx, expr, CheckForReturnFromFinally());
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
}
if (JSToken.Semicolon != this.currentToken.token
&& JSToken.RightCurly != this.currentToken.token
&& !this.scanner.GotEndOfLine()){
ReportError(JSError.NoSemicolon, true);
}
}
if (JSToken.Semicolon == this.currentToken.token){
retCtx.UpdateWith(this.currentToken);
GetNextToken();
}else if (expr != null)
retCtx.UpdateWith(expr.context);
}
return new Return(retCtx, expr, CheckForReturnFromFinally());
}else{
// the return is not inside a function, report an error and skip to the next token
ReportError(JSError.BadReturn, retCtx, true);
GetNextToken();
return null;
}
}
//---------------------------------------------------------------------------------------
// ParseImportStatement
//
// ImportStatement :
// 'import' QualifiedIdentifier
//---------------------------------------------------------------------------------------
private Import ParseImportStatement(){
Context context = this.currentToken.Clone();
AST name = null;
try{
name = ParseQualifiedIdentifier(JSError.PackageExpected);
}catch(RecoveryTokenException exc){
// an exception is passing by, mae an Import if possible and rethrow
if (exc._partiallyComputedNode != null){
exc._partiallyComputedNode = new Import(context, exc._partiallyComputedNode);
}
}
if (this.currentToken.token != JSToken.Semicolon && !this.scanner.GotEndOfLine())
ReportError(JSError.NoSemicolon, this.currentToken.Clone());
return new Import(context, name);
}
//---------------------------------------------------------------------------------------
// ParseWithStatement
//
// WithStatement :
// 'with' '(' Expression ')' Statement
//---------------------------------------------------------------------------------------
private With ParseWithStatement(){
Context withCtx = this.currentToken.Clone();
AST obj = null;
AST block = null;
this.blockType.Add(BlockType.Block);
try{
GetNextToken();
if (JSToken.LeftParen != this.currentToken.token)
ReportError(JSError.NoLeftParen);
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
try{
obj = ParseExpression();
if (JSToken.RightParen != this.currentToken.token){
withCtx.UpdateWith(obj.context);
ReportError(JSError.NoRightParen);
}else
withCtx.UpdateWith(this.currentToken);
GetNextToken();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1){
// give up
exc._partiallyComputedNode = null;
throw exc;
}else{
if (exc._partiallyComputedNode == null)
obj = new ConstantWrapper(true, CurrentPositionContext());
else
obj = exc._partiallyComputedNode;
withCtx.UpdateWith(obj.context);
if (exc._token == JSToken.RightParen)
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
try{
block = ParseStatement();
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode == null)
block = new Block(CurrentPositionContext());
else
block = exc._partiallyComputedNode;
exc._partiallyComputedNode = new With(withCtx, obj, block);
}
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
return new With(withCtx, obj, block);
}
//---------------------------------------------------------------------------------------
// ParseSwitchStatement
//
// SwitchStatement :
// 'switch' '(' Expression ')' '{' CaseBlock '}'
//
// CaseBlock :
// CaseList DefaultCaseClause CaseList
//
// CaseList :
// <empty> |
// CaseClause CaseList
//
// CaseClause :
// 'case' Expression ':' OptionalStatements
//
// DefaultCaseClause :
// <empty> |
// 'default' ':' OptionalStatements
//---------------------------------------------------------------------------------------
private AST ParseSwitchStatement(){
Context switchCtx = this.currentToken.Clone();
AST expr = null;
ASTList cases = null;
this.blockType.Add(BlockType.Switch);
try{
// read switch(expr)
GetNextToken();
if (JSToken.LeftParen != this.currentToken.token)
ReportError(JSError.NoLeftParen);
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
try{
expr = ParseExpression();
if (JSToken.RightParen != this.currentToken.token){
ReportError(JSError.NoRightParen);
}
GetNextToken();
if (JSToken.LeftCurly != this.currentToken.token){
ReportError(JSError.NoLeftCurly);
}
GetNextToken();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1
&& IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1 ){
// give up
exc._partiallyComputedNode = null;
throw exc;
}else{
if (exc._partiallyComputedNode == null)
expr = new ConstantWrapper(true, CurrentPositionContext());
else
expr = exc._partiallyComputedNode;
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) != -1){
if (exc._token == JSToken.RightParen)
GetNextToken();
if (JSToken.LeftCurly != this.currentToken.token){
ReportError(JSError.NoLeftCurly);
}
GetNextToken();
}
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
// parse the switch body
cases = new ASTList(this.currentToken.Clone());
bool defaultStatement = false;
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
try{
while (JSToken.RightCurly != this.currentToken.token){
SwitchCase caseClause = null;
AST caseValue = null;
Context caseCtx = this.currentToken.Clone();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_CaseNoSkipTokenSet);
try{
if (JSToken.Case == this.currentToken.token){
// get the case
GetNextToken();
caseValue = ParseExpression();
}else if (JSToken.Default == this.currentToken.token){
// get the default
if (defaultStatement)
// we report an error but we still accept the default
ReportError(JSError.DupDefault, true);
else
defaultStatement = true;
GetNextToken();
}else{
// This is an error, there is no case or default. Assume a default was missing and keep going
defaultStatement = true;
ReportError(JSError.BadSwitch);
}
if (JSToken.Colon != this.currentToken.token)
ReportError(JSError.NoColon);
// read the statements inside the case or default
GetNextToken();
}catch(RecoveryTokenException exc){
// right now we can only get here for the 'case' statement
if (IndexOfToken(NoSkipTokenSet.s_CaseNoSkipTokenSet, exc) == -1){
// ignore the current case or default
exc._partiallyComputedNode = null;
throw exc;
}else{
caseValue = exc._partiallyComputedNode;
if (exc._token == JSToken.Colon)
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_CaseNoSkipTokenSet);
}
this.blockType.Add(BlockType.Block);
try{
Block statements = new Block(this.currentToken.Clone());
this.noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
try{
while (JSToken.RightCurly != this.currentToken.token && JSToken.Case != this.currentToken.token && JSToken.Default != this.currentToken.token){
try{
statements.Append(ParseStatement());
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null){
statements.Append(exc._partiallyComputedNode);
exc._partiallyComputedNode = null;
}
if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
throw exc;
}
}
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1){
if (null == caseValue)
caseClause = new SwitchCase(caseCtx, statements);
else
caseClause = new SwitchCase(caseCtx, caseValue, statements);
cases.Append(caseClause);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet);
}
if (JSToken.RightCurly == this.currentToken.token)
statements.context.UpdateWith(this.currentToken);
if (null == caseValue){
caseCtx.UpdateWith(statements.context);
caseClause = new SwitchCase(caseCtx, statements);
}else{
caseCtx.UpdateWith(statements.context);
caseClause = new SwitchCase(caseCtx, caseValue, statements);
}
cases.Append(caseClause);
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
}
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1){
//save what you can a rethrow
switchCtx.UpdateWith(CurrentPositionContext());
exc._partiallyComputedNode = new Switch(switchCtx, expr, cases);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
}
switchCtx.UpdateWith(this.currentToken);
GetNextToken();
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
return new Switch(switchCtx, expr, cases);
}
//---------------------------------------------------------------------------------------
// ParseThrowStatement
//
// ThrowStatement :
// throw |
// throw Expression
//---------------------------------------------------------------------------------------
private AST ParseThrowStatement(){
Context throwCtx = this.currentToken.Clone();
GetNextToken();
AST operand = null;
if (!this.scanner.GotEndOfLine()){
if (JSToken.Semicolon != this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
try{
operand = ParseExpression();
}catch(RecoveryTokenException exc){
operand = exc._partiallyComputedNode;
if (IndexOfToken(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet, exc) == -1){
if (operand != null)
exc._partiallyComputedNode = new Throw(throwCtx, exc._partiallyComputedNode);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
}
}
}
if (operand != null)
throwCtx.UpdateWith(operand.context);
return new Throw(throwCtx, operand);
}
//---------------------------------------------------------------------------------------
// ParseTryStatement
//
// TryStatement :
// 'try' Block CatchList Finally
//
// CatchList :
// <empty> |
// CatchList Catch
//
// Catch :
// 'catch' '(' Identifier Type ')' Block
//
// Finally :
// <empty> |
// 'finally' Block
//---------------------------------------------------------------------------------------
private AST ParseTryStatement(){
Context tryCtx = this.currentToken.Clone();
Context tryEndContext = null;
AST body = null;
AST id = null;
AST handler = null;
AST finally_block = null;
RecoveryTokenException excInFinally = null;
TypeExpression type = null;
this.blockType.Add(BlockType.Block);
try{
bool catchOrFinally = false;
bool foundCatchAll = false;
GetNextToken();
if (JSToken.LeftCurly != this.currentToken.token)
ReportError(JSError.NoLeftCurly);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_NoTrySkipTokenSet);
try{
body = ParseBlock(out tryEndContext);
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_NoTrySkipTokenSet, exc) == -1)
// do nothing and just return the containing block, if any
throw exc;
else
body = exc._partiallyComputedNode;
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_NoTrySkipTokenSet);
}
while (JSToken.Catch == this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_NoTrySkipTokenSet);
try{
if (handler != null){
body = new Try(tryCtx, body, id, type, handler, null, false, tryEndContext);
id = null;
type = null;
handler = null;
}
catchOrFinally = true;
GetNextToken();
if (JSToken.LeftParen != this.currentToken.token)
ReportError(JSError.NoLeftParen);
GetNextToken();
if (JSToken.Identifier != this.currentToken.token){
string identifier = JSKeyword.CanBeIdentifier(this.currentToken.token);
if (null != identifier){
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
id = new Lookup(identifier, this.currentToken.Clone());
}else{
ReportError(JSError.NoIdentifier);
id = new Lookup("##Exc##" + s_cDummyName++, CurrentPositionContext());
}
}else
id = new Lookup(this.scanner.GetIdentifier(), this.currentToken.Clone());
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
try{
if (JSToken.Colon == this.currentToken.token)
type = ParseTypeExpression();
else{
if (foundCatchAll) //no point in having another
ForceReportInfo(id.context, JSError.UnreachableCatch);
foundCatchAll = true;
}
if (JSToken.RightParen != this.currentToken.token)
ReportError(JSError.NoRightParen);
GetNextToken();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null;
// rethrow
throw exc;
}else{
type = (TypeExpression)exc._partiallyComputedNode;
if (this.currentToken.token == JSToken.RightParen)
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet);
}
if (JSToken.LeftCurly != this.currentToken.token)
ReportError(JSError.NoLeftCurly);
handler = ParseBlock();
tryCtx.UpdateWith(handler.context);
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode == null)
handler = new Block(CurrentPositionContext());
else
handler = exc._partiallyComputedNode;
if (IndexOfToken(NoSkipTokenSet.s_NoTrySkipTokenSet, exc) == -1){
Debug.Assert((type == null) ? exc._partiallyComputedNode == null : true);
if (type != null)
exc._partiallyComputedNode = new Try(tryCtx, body, id, type, handler, null, false, tryEndContext);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_NoTrySkipTokenSet);
}
}
try{
if (JSToken.Finally == this.currentToken.token){
GetNextToken();
this.blockType.Add(BlockType.Finally);
try{
finally_block = ParseBlock();
catchOrFinally = true;
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
tryCtx.UpdateWith(finally_block.context);
}
}catch(RecoveryTokenException exc){
excInFinally = exc; // thrown later so we can execute code below
}
if (!catchOrFinally){
ReportError(JSError.NoCatch, true);
finally_block = new Block(CurrentPositionContext()); // make a dummy empty block
}
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
bool isFinallyEscaped = false;
if (this.finallyEscaped > 0){
this.finallyEscaped--;
isFinallyEscaped = true;
}
if (excInFinally != null){
excInFinally._partiallyComputedNode = new Try(tryCtx, body, id, type, handler, finally_block, isFinallyEscaped, tryEndContext);
throw excInFinally;
}else
return new Try(tryCtx, body, id, type, handler, finally_block, isFinallyEscaped, tryEndContext);
}
//---------------------------------------------------------------------------------------
// ParseClass
//
// Class :
// 'class' identifier OptionalExtends ClassBody
//
// Extends :
// 'extends' QualifiedIdentifier
//
//---------------------------------------------------------------------------------------
private AST ParseClass(FieldAttributes visibilitySpec, bool isStatic, Context classCtx, bool isAbstract, bool isFinal, CustomAttributeList customAttributes){
AST name = null;
AST baseId = null;
TypeExpression baseType = null;
Block body = null;
ArrayList interfaces = new ArrayList();
bool isInterface = JSToken.Interface == this.currentToken.token;
GetNextToken();
if (JSToken.Identifier == this.currentToken.token){
name = new IdentifierLiteral(this.scanner.GetIdentifier(), this.currentToken.Clone());
}else{
ReportError(JSError.NoIdentifier);
if (JSToken.Extends != this.currentToken.token
&& JSToken.Implements != this.currentToken.token
&& JSToken.LeftCurly != this.currentToken.token)
SkipTokensAndThrow(); // what the is this?
name = new IdentifierLiteral("##Missing Class Name##" + s_cDummyName++, CurrentPositionContext());
}
GetNextToken();
if (JSToken.Extends == this.currentToken.token || JSToken.Implements == this.currentToken.token){
if (isInterface && JSToken.Extends == this.currentToken.token)
this.currentToken.token = JSToken.Implements;
if (JSToken.Extends == this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ClassExtendsNoSkipTokenSet);
try{
baseId = ParseQualifiedIdentifier(JSError.NeedType);
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_ClassExtendsNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null;
throw exc;
}else{
baseId = exc._partiallyComputedNode;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ClassExtendsNoSkipTokenSet);
}
}
if (JSToken.Implements == this.currentToken.token){
do{
AST typeId = null;
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ClassImplementsNoSkipTokenSet);
try{
typeId = ParseQualifiedIdentifier(JSError.NeedType);
interfaces.Add(new TypeExpression(typeId));
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_ClassImplementsNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null;
throw exc;
}else{
if (exc._partiallyComputedNode != null)
interfaces.Add(new TypeExpression(exc._partiallyComputedNode));
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ClassImplementsNoSkipTokenSet);
}
}while (JSToken.Comma == this.currentToken.token);
}
}
if (baseId != null)
baseType = new TypeExpression(baseId);
if (JSToken.LeftCurly != this.currentToken.token){
ReportError(JSError.NoLeftCurly);
}
// make a new state and save the old one
ArrayList blockType = this.blockType;
this.blockType = new ArrayList(16);
SimpleHashtable labelTable = this.labelTable;
this.labelTable = new SimpleHashtable(16);
Globals.ScopeStack.Push(new ClassScope(name, ((IActivationObject)Globals.ScopeStack.Peek()).GetGlobalScope())); //Give declarations a place to go while building AST
TypeExpression[] ifaces;
try{
body = ParseClassBody(false, isInterface);
classCtx.UpdateWith(body.context);
ifaces = new TypeExpression[interfaces.Count]; interfaces.CopyTo(ifaces);
Class result = new Class(classCtx, name, baseType, ifaces, body, visibilitySpec, isAbstract, isFinal, isStatic, isInterface, customAttributes);
if (customAttributes != null) customAttributes.SetTarget(result);
return result;
}catch(RecoveryTokenException exc){
classCtx.UpdateWith(exc._partiallyComputedNode.context);
ifaces = new TypeExpression[interfaces.Count]; interfaces.CopyTo(ifaces);
exc._partiallyComputedNode =
new Class(classCtx, name, baseType, ifaces, (Block)exc._partiallyComputedNode, visibilitySpec, isAbstract, isFinal, isStatic, isInterface, customAttributes);
if (customAttributes != null) customAttributes.SetTarget(exc._partiallyComputedNode);
throw exc;
}finally{
Globals.ScopeStack.Pop();
this.blockType = blockType;
this.labelTable = labelTable;
}
}
//---------------------------------------------------------------------------------------
// ParseClassBody
//
// ClassBody :
// '{' OptionalClassMembers '}'
//---------------------------------------------------------------------------------------
Block ParseClassBody(bool isEnum, bool isInterface){
this.blockType.Add(BlockType.Block);
Block codeBlock = new Block(this.currentToken.Clone());
try{
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
JSToken[] noSkip = null;
if (isEnum)
noSkip = NoSkipTokenSet.s_EnumBodyNoSkipTokenSet;
else if (isInterface)
noSkip = NoSkipTokenSet.s_InterfaceBodyNoSkipTokenSet;
else
noSkip = NoSkipTokenSet.s_ClassBodyNoSkipTokenSet;
try{
while (JSToken.RightCurly != this.currentToken.token){
if (JSToken.EndOfFile == this.currentToken.token){
ReportError(JSError.NoRightCurly, true);
SkipTokensAndThrow();
}
this.noSkipTokenSet.Add(noSkip);
try{
AST classMember = isEnum ? ParseEnumMember() : ParseClassMember(isInterface);
if (classMember != null)
codeBlock.Append(classMember);
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
codeBlock.Append(exc._partiallyComputedNode);
if (IndexOfToken(noSkip, exc) == -1){
exc._partiallyComputedNode = null;
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(noSkip);
}
}
}catch(RecoveryTokenException exc){
exc._partiallyComputedNode = codeBlock;
throw exc;
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
}
codeBlock.context.UpdateWith(this.currentToken);
GetNextToken();
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
}
return codeBlock;
}
//---------------------------------------------------------------------------------------
// ParseClassMember
//
// OptionalClassMembers:
// ClassMember OptionalClassMembers |
// <empty>
//
// ClassMember :
// VariableStatement |
// Class |
// Enum |
// FunctionDeclaration
//
//---------------------------------------------------------------------------------------
private AST ParseClassMember(bool isInterface){
bool parsed = false;
// Interface members can be declared public ( as a no-op).
if (isInterface && this.currentToken.token == JSToken.Public)
GetNextToken();
switch (this.currentToken.token){
case JSToken.RightCurly:
return null;
case JSToken.Semicolon:
GetNextToken();
return ParseClassMember(isInterface);
case JSToken.Const:
case JSToken.Var:
if (isInterface){
ReportError(JSError.VarIllegalInInterface, true);
GetNextToken();
SkipTokensAndThrow();
}
return ParseVariableStatement((FieldAttributes)0, null, this.currentToken.token);
case JSToken.Internal:
case JSToken.Public:
case JSToken.Static:
case JSToken.Private:
case JSToken.Protected:
case JSToken.Abstract:
case JSToken.Final:
if (isInterface){
ReportError(JSError.BadModifierInInterface, true);
GetNextToken();
SkipTokensAndThrow();
}
return ParseAttributes(null, true, true, out parsed);
case JSToken.Interface:
if (isInterface){
ReportError(JSError.InterfaceIllegalInInterface, true);
GetNextToken();
SkipTokensAndThrow();
}
return ParseClass((FieldAttributes)0, false, this.currentToken.Clone(), false, false, null);
case JSToken.Class:
if (isInterface){
ReportError(JSError.SyntaxError, true);
GetNextToken();
SkipTokensAndThrow();
}
return ParseClass((FieldAttributes)0, false, this.currentToken.Clone(), false, false, null);
case JSToken.Enum:
return ParseEnum((FieldAttributes)0, this.currentToken.Clone(), null);
case JSToken.Function:
return ParseFunction((FieldAttributes)0, false, this.currentToken.Clone(), true, isInterface, false, isInterface, null);
case JSToken.Identifier:
if (isInterface){
ReportError(JSError.SyntaxError, true);
GetNextToken();
SkipTokensAndThrow();
}
bool bAssign, canBeAttribute = true;
AST ast = ParseUnaryExpression(out bAssign, ref canBeAttribute, false);
if (canBeAttribute){
ast = ParseAttributes(ast, true, true, out parsed);
if (parsed)
return ast;
}
ReportError(JSError.SyntaxError, ast.context.Clone(), true);
SkipTokensAndThrow();
return null; // we'll never be executed, make the compiler happy
case JSToken.Import:
// handle common error of using import in class
ReportError(JSError.InvalidImport, true);
try{
ParseImportStatement();
}catch(RecoveryTokenException){
}
return null;
case JSToken.Package:
// handle common error of using package in class
Context packageContext = this.currentToken.Clone();
AST statement = ParsePackage(packageContext);
if (statement is Package)
ReportError(JSError.PackageInWrongContext, packageContext, true);
return null;
default:
ReportError(JSError.SyntaxError, true);
GetNextToken();
SkipTokensAndThrow();
return null; // we'll never be executed, make the compiler happy
}
}
//---------------------------------------------------------------------------------------
// ParseEnum
//
// Enum :
// 'enum' identifier [':' baseType] EnumBody (in the guise of ClassBody with a param)
//
//---------------------------------------------------------------------------------------
private AST ParseEnum(FieldAttributes visibilitySpec, Context enumCtx, CustomAttributeList customAttributes){
IdentifierLiteral name = null;
AST baseId = null;
TypeExpression baseType = null;
Block body = null;
GetNextToken();
if (JSToken.Identifier == this.currentToken.token){
name = new IdentifierLiteral(this.scanner.GetIdentifier(), this.currentToken.Clone());
}else{
ReportError(JSError.NoIdentifier);
if (JSToken.Colon != this.currentToken.token && JSToken.LeftCurly != this.currentToken.token)
SkipTokensAndThrow(); // what the is this?
name = new IdentifierLiteral("##Missing Enum Name##" + s_cDummyName++, CurrentPositionContext());
}
GetNextToken();
if (JSToken.Colon == this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_EnumBaseTypeNoSkipTokenSet);
try{
baseId = ParseQualifiedIdentifier(JSError.NeedType);
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_ClassExtendsNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null;
throw exc;
}else{
baseId = exc._partiallyComputedNode;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EnumBaseTypeNoSkipTokenSet);
}
}
if (baseId != null)
baseType = new TypeExpression(baseId);
if (JSToken.LeftCurly != this.currentToken.token)
ReportError(JSError.NoLeftCurly);
// make a new state and save the old one
ArrayList blockType = this.blockType;
this.blockType = new ArrayList(16);
SimpleHashtable labelTable = this.labelTable;
this.labelTable = new SimpleHashtable(16);
Globals.ScopeStack.Push(new ClassScope(name, ((IActivationObject)Globals.ScopeStack.Peek()).GetGlobalScope())); //Give declarations a place to go while building AST
try{
body = ParseClassBody(true, false);
enumCtx.UpdateWith(body.context);
EnumDeclaration result = new EnumDeclaration(enumCtx, name, baseType, body, visibilitySpec, customAttributes);
if (customAttributes != null) customAttributes.SetTarget(result);
return result;
}catch(RecoveryTokenException exc){
enumCtx.UpdateWith(exc._partiallyComputedNode.context);
exc._partiallyComputedNode = new EnumDeclaration(enumCtx, name, baseType, (Block)exc._partiallyComputedNode, visibilitySpec, customAttributes);
if (customAttributes != null) customAttributes.SetTarget(exc._partiallyComputedNode);
throw exc;
}finally{
Globals.ScopeStack.Pop();
this.blockType = blockType;
this.labelTable = labelTable;
}
}
//---------------------------------------------------------------------------------------
// ParseEnumMember
//
// OptionalEnumMembers:
// EnumMember ',' OptionalEnumMembers |
// <empty>
//
// EnumMember :
// Identifier |
// Identifer '=' IntegerLiteral
//
//---------------------------------------------------------------------------------------
private AST ParseEnumMember(){
AST ast = null;
Lookup memberName = null;
AST memberValue = null;
switch (this.currentToken.token){
case JSToken.Semicolon:
GetNextToken();
return ParseEnumMember();
case JSToken.Identifier:
memberName = new Lookup(this.currentToken.Clone());
Context context = this.currentToken.Clone();
GetNextToken();
if (JSToken.Assign == this.currentToken.token){
GetNextToken();
memberValue = ParseExpression(true);
}
if (JSToken.Comma == this.currentToken.token)
GetNextToken();
else if (JSToken.RightCurly != this.currentToken.token)
ReportError(JSError.NoComma, true);
return new Constant(context, memberName, null, memberValue, FieldAttributes.Public, null);
case JSToken.Var:
// handle common error
ReportError(JSError.NoVarInEnum, true);
GetNextToken();
return ParseEnumMember();
default:
ReportError(JSError.SyntaxError, true);
SkipTokensAndThrow();
return ast; // will never be executed, but make the C# compiler happy
}
}
private bool GuessIfAbstract(){
//
// We have a class method declaraction without the "abstract"
// attribute and need to know whether to treat it as abstract
// for error reporting purposes.
//
// function bar(); -- clearly abstract, "missing abstract" error
// function bar(){ -- clearly concrete, correct
// function bar() -- clearly an error, but should it be
// "missing abstract" or "missing left curly"?
//
// In the last case we look what comes next.
//
// * If it looks like something that would start a function body -- a "var" statement or
// an expression for instance -- then we assume that the curly is missing.
//
// * If it looks like a global/package/class level declaration then we assume that it was supposed
// to be abstract.
//
// * If it is a right-curly then we assume that the class scope is being
// closed and this is therefore an abstract function.
//
switch(this.currentToken.token){
case JSToken.Const:
case JSToken.Package:
case JSToken.Internal:
case JSToken.Public:
case JSToken.Static:
case JSToken.Private:
case JSToken.Protected:
case JSToken.Abstract:
case JSToken.Final:
case JSToken.Interface:
case JSToken.Class:
case JSToken.Enum:
case JSToken.Function:
case JSToken.RightCurly:
return true;
case JSToken.Semicolon:
GetNextToken();
return true;
case JSToken.Var:
case JSToken.LeftCurly:
default:
return false;
}
}
//---------------------------------------------------------------------------------------
// ParseFunction
//
// FunctionDeclaration :
// VisibilityModifier 'function' GetSet Identifier '('
// FormalParameterList ')' '{' FunctionBody '}'
//
// GetSet:
// <empty> |
// 'get' |
// 'set'
//
// FormalParameterList :
// <empty> |
// IdentifierList Identifier
//
// IdentifierList :
// <empty> |
// Identifier, IdentifierList
//---------------------------------------------------------------------------------------
private AST ParseFunction(FieldAttributes visibilitySpec,
bool inExpression,
Context fncCtx,
bool isMethod,
bool isAbstract,
bool isFinal,
bool isInterface,
CustomAttributeList customAttributes){
return ParseFunction(visibilitySpec, inExpression, fncCtx, isMethod, isAbstract, isFinal, isInterface, customAttributes, null);
}
private AST ParseFunction(FieldAttributes visibilitySpec,
bool inExpression,
Context fncCtx,
bool isMethod,
bool isAbstract,
bool isFinal,
bool isInterface,
CustomAttributeList customAttributes,
Call function){
IdentifierLiteral name = null;
AST interfaceName = null;
ArrayList formalParameters = null;
TypeExpression returnType = null;
Block body = null;
bool isGetter = false;
bool isSetter = false;
if (function == null){
GetNextToken();
if (isMethod)
if (JSToken.Get == this.currentToken.token){
isGetter = true;
GetNextToken();
}else if (JSToken.Set == this.currentToken.token){
isSetter = true;
GetNextToken();
}
// get the function name or make an anonymous function if in expression "position"
if (JSToken.Identifier == this.currentToken.token){
name = new IdentifierLiteral(this.scanner.GetIdentifier(), this.currentToken.Clone());
GetNextToken();
if (JSToken.AccessField == this.currentToken.token){
if (isInterface) // "function IBar.foo()" is illegal in an interface declaration
ReportError(JSError.SyntaxError, true);
GetNextToken();
if (JSToken.Identifier == this.currentToken.token){
interfaceName = new Lookup(name.context);
name = new IdentifierLiteral(this.scanner.GetIdentifier(), this.currentToken.Clone());
GetNextToken();
while (JSToken.AccessField == this.currentToken.token){
GetNextToken();
if (JSToken.Identifier == this.currentToken.token){
interfaceName = new Member(interfaceName.context.CombineWith(this.currentToken), interfaceName,
new ConstantWrapper(name.ToString(), name.context));
name = new IdentifierLiteral(this.scanner.GetIdentifier(), this.currentToken.Clone());
GetNextToken();
}else
ReportError(JSError.NoIdentifier, true);
}
}else
ReportError(JSError.NoIdentifier, true);
}
}else{
string identifier = JSKeyword.CanBeIdentifier(this.currentToken.token);
if (null != identifier){
ForceReportInfo(JSError.KeywordUsedAsIdentifier, isMethod);
name = new IdentifierLiteral(identifier, this.currentToken.Clone());
GetNextToken();
}else{
if (!inExpression){
identifier = this.currentToken.GetCode();
ReportError(JSError.NoIdentifier, true);
GetNextToken();
}else
identifier = "";
name = new IdentifierLiteral(identifier, CurrentPositionContext());
}
}
}else{ // function was passed in, this is an error condition
name = function.GetName();
}
// make a new state and save the old one
ArrayList blockType = this.blockType;
this.blockType = new ArrayList(16);
SimpleHashtable labelTable = this.labelTable;
this.labelTable = new SimpleHashtable(16);
FunctionScope fscope = new FunctionScope(Globals.ScopeStack.Peek(), isMethod);
Globals.ScopeStack.Push(fscope); //Give declarations a place to go while building AST
try{
formalParameters = new ArrayList();
Context paramArrayContext = null;
if (function == null){
// get the formal parameters
if (JSToken.LeftParen != this.currentToken.token)
ReportError(JSError.NoLeftParen);
GetNextToken();
// create the list of arguments and update the context
while (JSToken.RightParen != this.currentToken.token){
if (paramArrayContext != null){
ReportError(JSError.ParamListNotLast, paramArrayContext, true);
paramArrayContext = null;
}
String id = null;
TypeExpression typeExpr = null;
this.noSkipTokenSet.Add(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
try{
if (JSToken.ParamArray == this.currentToken.token){
paramArrayContext = this.currentToken.Clone();
GetNextToken();
}
if (JSToken.Identifier != this.currentToken.token && (id = JSKeyword.CanBeIdentifier(this.currentToken.token)) == null){
if (JSToken.LeftCurly == this.currentToken.token){
ReportError(JSError.NoRightParen);
break;
}else if (JSToken.Comma == this.currentToken.token){
// We're missing an argument (or previous argument was malformed and
// we skipped to the comma.) Keep trying to parse the argument list --
// we will skip the comma below.
ReportError(JSError.SyntaxError, true);
}else{
ReportError(JSError.SyntaxError, true);
SkipTokensAndThrow();
}
}else{
if (null == id)
id = this.scanner.GetIdentifier();
else
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
Context paramCtx = this.currentToken.Clone();
GetNextToken();
if (JSToken.Colon == this.currentToken.token){
typeExpr = ParseTypeExpression();
if (null != typeExpr)
paramCtx.UpdateWith(typeExpr.context);
}
CustomAttributeList custAttrs = null;
if (paramArrayContext != null){
custAttrs = new CustomAttributeList(paramArrayContext);
custAttrs.Append(new CustomAttribute(paramArrayContext, new Lookup("...", paramArrayContext), new ASTList(null)));
}
formalParameters.Add(new ParameterDeclaration(paramCtx, id, typeExpr, custAttrs));
}
// got an arg, it should be either a ',' or ')'
if (JSToken.RightParen == this.currentToken.token)
break;
else if (JSToken.Comma != this.currentToken.token){
// deal with error in some "intelligent" way
if (JSToken.LeftCurly == this.currentToken.token){
ReportError(JSError.NoRightParen);
break;
}else{
if (JSToken.Identifier == this.currentToken.token && typeExpr == null){
// it's possible that the guy was writing the type in C/C++ style (i.e. int x)
ReportError(JSError.NoCommaOrTypeDefinitionError);
}else
ReportError(JSError.NoComma);
}
}
GetNextToken();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet, exc) == -1)
throw exc;
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
}
}
fncCtx.UpdateWith(this.currentToken);
// if it is a getter/setter must have 0/1 arg only
if (isGetter && formalParameters.Count != 0){
ReportError(JSError.BadPropertyDeclaration, true);
isGetter = false;
}else if (isSetter && formalParameters.Count != 1){
ReportError(JSError.BadPropertyDeclaration, true);
isSetter = false;
}
GetNextToken();
// check the return type
if (JSToken.Colon == this.currentToken.token){
if (isSetter)
ReportError(JSError.SyntaxError);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_StartBlockNoSkipTokenSet);
try{
returnType = ParseTypeExpression();
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_StartBlockNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = null;
throw exc;
}else{
if (exc._partiallyComputedNode != null)
returnType = (TypeExpression)exc._partiallyComputedNode;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_StartBlockNoSkipTokenSet);
}
if (isSetter)
returnType = null;
}
}else{ // function was passed in, this is an error condition
function.GetParameters(formalParameters);
}
// read the function body of non-abstract functions.
if (JSToken.LeftCurly != this.currentToken.token && (isAbstract || (isMethod && GuessIfAbstract()))){
if (!isAbstract){
isAbstract = true;
ReportError(JSError.ShouldBeAbstract, fncCtx, true);
}
body = new Block(this.currentToken.Clone());
}else{
if (JSToken.LeftCurly != this.currentToken.token)
ReportError(JSError.NoLeftCurly, true);
else if (isAbstract)
ReportError(JSError.AbstractWithBody, fncCtx, true);
this.blockType.Add(BlockType.Block);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
try{
// parse the block locally to get the exact end of function
body = new Block(this.currentToken.Clone());
GetNextToken();
while (JSToken.RightCurly != this.currentToken.token){
try{
body.Append(ParseStatement());
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null){
body.Append(exc._partiallyComputedNode);
}
if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
throw exc;
}
}
body.context.UpdateWith(this.currentToken);
fncCtx.UpdateWith(this.currentToken);
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1){
Globals.ScopeStack.Pop(); //Pop current scope so that FunctionDeclaration sees proper scope stack
try{
ParameterDeclaration[] foParameters = new ParameterDeclaration[formalParameters.Count]; formalParameters.CopyTo(foParameters);
if (inExpression)
exc._partiallyComputedNode =
new FunctionExpression(fncCtx, name, foParameters, returnType, body, fscope, visibilitySpec);
else
exc._partiallyComputedNode =
new FunctionDeclaration(fncCtx, interfaceName, name, foParameters, returnType, body, fscope, visibilitySpec,
isMethod, isGetter, isSetter, isAbstract, isFinal, customAttributes);
if (customAttributes != null) customAttributes.SetTarget(exc._partiallyComputedNode);
}finally{
Globals.ScopeStack.Push(fscope); //Push it back so that the next finally can pop it
}
throw exc;
}
}finally{
this.blockType.RemoveAt(this.blockType.Count - 1);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
}
GetNextToken();
}
}finally{
// restore state
this.blockType = blockType;
this.labelTable = labelTable;
Globals.ScopeStack.Pop();
}
ParameterDeclaration[] fParameters = new ParameterDeclaration[formalParameters.Count]; formalParameters.CopyTo(fParameters);
AST func;
if (inExpression)
func = new FunctionExpression(fncCtx, name, fParameters, returnType, body, fscope, visibilitySpec);
else
func = new FunctionDeclaration(fncCtx, interfaceName, name, fParameters, returnType, body, fscope, visibilitySpec,
isMethod, isGetter, isSetter, isAbstract, isFinal, customAttributes);
if (customAttributes != null) customAttributes.SetTarget(func);
return func;
}
//used by FunctionConstructor
internal AST ParseFunctionExpression(){
GetNextToken(); //skip over function keyword
return ParseFunction((FieldAttributes)0, true, this.currentToken.Clone(), false, false, false, false, null);
}
//---------------------------------------------------------------------------------------
// ParseNamedBreakpoint
//
// Used by the debugger to parsed a named breakpoint, that is a name of a function with
// possible arguments and optionally followed by an il offset
//---------------------------------------------------------------------------------------
internal String[] ParseNamedBreakpoint(out int argNumber){
argNumber = 0;
// parse the function name
AST function = ParseQualifiedIdentifier(JSError.SyntaxError);
if (function != null){
String[] parsedFunction = new String[4];
parsedFunction[0] = function.ToString();
if (JSToken.LeftParen == this.currentToken.token){
String id = null;
String typeString = null;
AST qualid = null;
parsedFunction[1] = "";
GetNextToken();
// parse the formal parameters
while (JSToken.RightParen != this.currentToken.token){
id = null;
if (JSToken.Identifier != this.currentToken.token && (id = JSKeyword.CanBeIdentifier(this.currentToken.token)) == null){
return null;
}else{
if (null == id)
id = this.scanner.GetIdentifier();
qualid = new Lookup(id, this.currentToken.Clone());
GetNextToken();
if (JSToken.AccessField == this.currentToken.token){
qualid = ParseScopeSequence(qualid, JSError.SyntaxError);
typeString = qualid.ToString();
while (JSToken.LeftBracket == this.currentToken.token){
GetNextToken();
if (JSToken.RightBracket != this.currentToken.token)
return null;
typeString += "[]";
GetNextToken();
}
}else if (JSToken.Colon == this.currentToken.token){
GetNextToken();
if (JSToken.RightParen == this.currentToken.token)
return null;
continue;
}else{
typeString = qualid.ToString();
}
parsedFunction[1] += typeString + " ";
}
argNumber++;
if (JSToken.Comma == this.currentToken.token){
GetNextToken();
if (JSToken.RightParen == this.currentToken.token)
return null;
}
}
GetNextToken();
// parse a return value if any
if (JSToken.Colon == this.currentToken.token){
GetNextToken();
id = null;
if (JSToken.Identifier != this.currentToken.token && (id = JSKeyword.CanBeIdentifier(this.currentToken.token)) == null){
return null;
}else{
if (null == id)
id = this.scanner.GetIdentifier();
qualid = new Lookup(id, this.currentToken.Clone());
GetNextToken();
if (JSToken.AccessField == this.currentToken.token){
qualid = ParseScopeSequence(qualid, JSError.SyntaxError);
parsedFunction[2] = qualid.ToString();
while (JSToken.LeftBracket == this.currentToken.token){
GetNextToken();
if (JSToken.RightBracket != this.currentToken.token)
return null;
parsedFunction[2] += "[]";
GetNextToken();
}
}else{
parsedFunction[2] = qualid.ToString();
}
}
}
}
if (JSToken.Plus == this.currentToken.token){
GetNextToken();
if (JSToken.IntegerLiteral != this.currentToken.token)
return null;
parsedFunction[3] = this.currentToken.GetCode();
GetNextToken();
}
if (JSToken.EndOfFile != this.currentToken.token)
return null; // some extra crap
return parsedFunction;
}
return null;
}
//---------------------------------------------------------------------------------------
// ParsePackage
//
// Package :
// 'package' QualifiedIdentifier '{' ClassList '}'
//
// ClassList :
// <empty> |
// Class ClassList |
// Attributes Class ClassList |
// Attributes Enum ClassList
//---------------------------------------------------------------------------------------
// Because 'package' is not a reserved word in JS5 we have to deal with an ambiguity
// in the grammar. A source sequence like the following
// package
// x
// { }
// can be legally parsed in two ways:
// Identifier Identifier Block or
// Package
// we give Package priority in this situation.
// Here is how we deal with some possible cases:
// 1- ** package <no line break> QualifiedIdentifier ** is parsed unambiguously as a package production regardless of what comes after Identifier
// 2- ** package <no line break> NotOneOf(Operator | '[' | '.' | '(' | Identifier) '{' ** is parsed as a package production with an error
// 3- ** package <line break> '{' ** is parsed as a package (anonymous) with an error
// 4- ** package <line break> Not(Identifier) ** is never parsed as a package
private AST ParsePackage(Context packageContext){
GetNextToken();
AST qualid = null;
bool gotLineBreak = this.scanner.GotEndOfLine();
// erroneous package production
if (JSToken.Identifier != this.currentToken.token){
if (JSScanner.CanParseAsExpression(this.currentToken.token)){
// it's an expression. Report a warning. package and this.currentToken can be an expression (i.e. 'package +')
ReportError(JSError.KeywordUsedAsIdentifier, packageContext.Clone(), true);
qualid = new Lookup("package", packageContext);
// get the member expression
qualid = MemberExpression(qualid, null);
bool isLeftHandSide;
qualid = ParsePostfixExpression(qualid, out isLeftHandSide);
qualid = ParseExpression(qualid, false, isLeftHandSide, JSToken.None);
return new Expression(qualid.context.Clone(), qualid);
}else if (!gotLineBreak){
if (JSToken.Increment == this.currentToken.token || JSToken.Decrement == this.currentToken.token){
// it's a postfix expression. Report a warning
ReportError(JSError.KeywordUsedAsIdentifier, packageContext.Clone(), true);
bool dummy;
qualid = new Lookup("package", packageContext);
qualid = ParsePostfixExpression(qualid, out dummy);
qualid = ParseExpression(qualid, false, false, JSToken.None);
return new Expression(qualid.context.Clone(), qualid);
}
}else{
// it's an expression. Report a warning which, as a side effect, will make the current token be the next token fetched
ReportError(JSError.KeywordUsedAsIdentifier, packageContext.Clone(), true);
return new Lookup("package", packageContext);
}
}else{
// it is an identifier, parse it as a qualified identifier
this.errorToken = this.currentToken; // this will make GetNextToken() in ParseQualifiedIdentifier() return this.currentToken
qualid = ParseQualifiedIdentifier(JSError.NoIdentifier);
}
// if we are here we have:
// ** package QualifiedIdentifier ** or
// ** package SomeNonSenseToken **, that is a token that does not make an expression
Context nonSenseToken = null;
if (JSToken.LeftCurly != this.currentToken.token && qualid == null){
// we want to peek and see whether the next token is a LeftCurly
nonSenseToken = this.currentToken.Clone();
GetNextToken();
}
if (JSToken.LeftCurly == this.currentToken.token){
// sounds like a package, possibly with an error. If qualid is not null is actually a good package, otherwise we treat it
// as an anonymous package and keep going.
if (qualid == null){
if (nonSenseToken == null)
nonSenseToken = this.currentToken.Clone();
ReportError(JSError.NoIdentifier, nonSenseToken, true);
}
}else{
if (qualid == null){
// this is pretty screwy, let's ignore the package keyword for a start
ReportError(JSError.SyntaxError, packageContext);
if (JSScanner.CanStartStatement(nonSenseToken.token)){
// this is tricky we assign nonSenseToken to this.currentToken and call ParseStatement, because we know it is a statement start token.
// The parser should then call GetNextToken() which will return the this.currentToken that is assigned to this.errorToken
this.currentToken = nonSenseToken;
return ParseStatement();
}else{
//ReportError(JSError.SyntaxError, nonSenseToken);
if (JSScanner.CanStartStatement(this.currentToken.token)){
this.errorToken = null;
return ParseStatement();
}else{
ReportError(JSError.SyntaxError);
SkipTokensAndThrow();
}
}
}else{
if (gotLineBreak){
// we are here with the following: 'package' <line break> QalifiedIdentifier' however we do not have a left curly.
// if the token in our hand can start an expression we go with two expressions, otherwise we accept it as a package
//if (JSScanner.CanParseAsExpression(this.currentToken.token)){
ReportError(JSError.KeywordUsedAsIdentifier, packageContext.Clone(), true);
Block block = new Block(packageContext.Clone());
block.Append(new Lookup("package", packageContext));
qualid = MemberExpression(qualid, null);
bool isLeftHandSide;
qualid = ParsePostfixExpression(qualid, out isLeftHandSide);
qualid = ParseExpression(qualid, false, true, JSToken.None);
block.Append(new Expression(qualid.context.Clone(), qualid));
block.context.UpdateWith(qualid.context);
return block;
//}
}
// the package production rule is entered regardless of the presence of a left curly.
ReportError(JSError.NoLeftCurly);
}
}
PackageScope pscope = new PackageScope(Globals.ScopeStack.Peek());
Globals.ScopeStack.Push(pscope); //Give declarations a place to go while building AST
try{
string name = (qualid != null) ? qualid.ToString() : "anonymous package";
pscope.name = name;
packageContext.UpdateWith(this.currentToken);
ASTList classList = new ASTList(packageContext);
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_PackageBodyNoSkipTokenSet);
try{
while (this.currentToken.token != JSToken.RightCurly){
AST ast = null;
try{
switch (this.currentToken.token){
case JSToken.Interface:
case JSToken.Class:
classList.Append(ParseClass((FieldAttributes)0, false, this.currentToken.Clone(), false, false, null));
break;
case JSToken.Enum:
classList.Append(ParseEnum((FieldAttributes)0, this.currentToken.Clone(), null));
break;
case JSToken.Internal:
case JSToken.Public:
case JSToken.Static:
case JSToken.Private:
case JSToken.Protected:
case JSToken.Abstract:
case JSToken.Final:
bool parsedOK;
ast = ParseAttributes(null, true, false, out parsedOK);
if (parsedOK){
if (ast is Class){
classList.Append(ast);
break;
}
}
ReportError(JSError.OnlyClassesAllowed, ast.context.Clone(), true);
SkipTokensAndThrow();
break;
case JSToken.Identifier:
bool bAssign, canBeAttribute = true;
ast = ParseUnaryExpression(out bAssign, ref canBeAttribute, false);
if (canBeAttribute){
bool parsed;
ast = ParseAttributes(ast, true, false, out parsed);
if (parsed){
if (ast is Class){
classList.Append(ast);
break;
}
}
}
ReportError(JSError.OnlyClassesAllowed, ast.context.Clone(), true);
SkipTokensAndThrow();
break;
case JSToken.EndOfFile:
EOFError(JSError.ErrEOF);
throw new EndOfFile(); // abort parsing, get back to the main parse routine
case JSToken.Semicolon: // ignore any spurious semicolon
GetNextToken();
break;
case JSToken.Import:
// handle common error of using import in package
ReportError(JSError.InvalidImport, true);
try{
ParseImportStatement();
}catch(RecoveryTokenException){
}
break;
case JSToken.Package:
// handle common error of using package in package
Context nestedPackageContext = this.currentToken.Clone();
AST statement = ParsePackage(nestedPackageContext);
if (statement is Package)
ReportError(JSError.PackageInWrongContext, nestedPackageContext, true);
break;
default:
ReportError(JSError.OnlyClassesAllowed, (ast != null) ? ast.context.Clone() : CurrentPositionContext(), true);
SkipTokensAndThrow();
break;
}
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null && exc._partiallyComputedNode is Class){
classList.Append((Class)exc._partiallyComputedNode);
exc._partiallyComputedNode = null;
}
if (IndexOfToken(NoSkipTokenSet.s_PackageBodyNoSkipTokenSet, exc) == -1)
throw exc;
}
}
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1){
ReportError(JSError.NoRightCurly, CurrentPositionContext());
exc._partiallyComputedNode = new Package(name, qualid, classList, packageContext);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_PackageBodyNoSkipTokenSet);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
}
GetNextToken();
return new Package(name, qualid, classList, packageContext);
}finally{
Globals.ScopeStack.Pop();
}
}
//---------------------------------------------------------------------------------------
// ParseStaticInitializer
//
// StaticInitializer :
// '{' FunctionBody '}'
//---------------------------------------------------------------------------------------
private AST ParseStaticInitializer(Context initContext){
Block body = null;
FunctionScope scope = new FunctionScope(Globals.ScopeStack.Peek());
scope.isStatic = true;
// make a new state and save the old one
ArrayList blockType = this.blockType;
this.blockType = new ArrayList(16);
SimpleHashtable labelTable = this.labelTable;
this.labelTable = new SimpleHashtable(16);
this.blockType.Add(BlockType.Block);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
try{
Globals.ScopeStack.Push(scope); //Give declarations a place to go while building AST
// parse the block locally to get the exact end of function
body = new Block(this.currentToken.Clone());
GetNextToken();
while (JSToken.RightCurly != this.currentToken.token){
try{
body.Append(ParseStatement());
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
body.Append(exc._partiallyComputedNode);
if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
throw exc;
}
}
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = new StaticInitializer(initContext, body, scope);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
this.blockType = blockType;
this.labelTable = labelTable;
Globals.ScopeStack.Pop();
}
body.context.UpdateWith(this.currentToken);
initContext.UpdateWith(this.currentToken);
GetNextToken();
return new StaticInitializer(initContext, body, scope);
}
//---------------------------------------------------------------------------------------
// ParseExpression
//
// Expression :
// AssignmentExpressionList AssignmentExpression
//
// AssignmentExpressionList :
// <empty> |
// AssignmentExpression ',' AssignmentExpressionList
//
// AssignmentExpression :
// ConditionalExpression |
// LeftHandSideExpression AssignmentOperator AssignmentExpression
//
// ConditionalExpression :
// LogicalORExpression OptionalConditionalExpression
//
// OptionalConditionalExpression :
// <empty> |
// '?' AssignmentExpression ':' AssignmentExpression
//
// LogicalORExpression :
// LogicalANDExpression OptionalLogicalOrExpression
//
// OptionalLogicalOrExpression :
// <empty> |
// '||' LogicalANDExpression OptionalLogicalOrExpression
//
// LogicalANDExpression :
// BitwiseORExpression OptionalLogicalANDExpression
//
// OptionalLogicalANDExpression :
// <empty> |
// '&&' BitwiseORExpression OptionalLogicalANDExpression
//
// BitwiseORExpression :
// BitwiseXORExpression OptionalBitwiseORExpression
//
// OptionalBitwiseORExpression :
// <empty> |
// '|' BitwiseXORExpression OptionalBitwiseORExpression
//
// BitwiseXORExpression :
// BitwiseANDExpression OptionalBitwiseXORExpression
//
// OptionalBitwiseXORExpression :
// <empty> |
// '^' BitwiseANDExpression OptionalBitwiseXORExpression
//
// BitwiseANDExpression :
// EqualityExpression OptionalBitwiseANDExpression
//
// OptionalBitwiseANDExpression :
// <empty> |
// '&' EqualityExpression OptionalBitwiseANDExpression
//
// EqualityExpression :
// RelationalExpression |
// RelationalExpression '==' EqualityExpression |
// RelationalExpression '!=' EqualityExpression |
// RelationalExpression '===' EqualityExpression |
// RelationalExpression '!==' EqualityExpression
//
// RelationalExpression :
// ShiftExpression |
// ShiftExpression '<' RelationalExpression |
// ShiftExpression '>' RelationalExpression |
// ShiftExpression '<=' RelationalExpression |
// ShiftExpression '>=' RelationalExpression
//
// ShiftExpression :
// AdditiveExpression |
// AdditiveExpression '<<' ShiftExpression |
// AdditiveExpression '>>' ShiftExpression |
// AdditiveExpression '>>>' ShiftExpression
//
// AdditiveExpression :
// MultiplicativeExpression |
// MultiplicativeExpression '+' AdditiveExpression |
// MultiplicativeExpression '-' AdditiveExpression
//
// MultiplicativeExpression :
// UnaryExpression |
// UnaryExpression '*' MultiplicativeExpression |
// UnaryExpression '/' MultiplicativeExpression |
// UnaryExpression '%' MultiplicativeExpression
//---------------------------------------------------------------------------------------
private AST ParseExpression(){
bool bAssign;
AST lhs = ParseUnaryExpression(out bAssign, false);
return ParseExpression(lhs, false, bAssign, JSToken.None);
}
private AST ParseExpression(bool single){
bool bAssign;
AST lhs = ParseUnaryExpression(out bAssign, false);
return ParseExpression(lhs, single, bAssign, JSToken.None);
}
private AST ParseExpression(bool single, JSToken inToken){
bool bAssign;
AST lhs = ParseUnaryExpression(out bAssign, false);
return ParseExpression(lhs, single, bAssign, inToken);
}
private AST ParseExpression(AST leftHandSide, bool single, bool bCanAssign, JSToken inToken){
OpListItem opsStack = new OpListItem(JSToken.None, OpPrec.precNone, null); // dummy element
AstListItem termStack = new AstListItem(leftHandSide, null);
AST expr = null;
try{
for (;;){
// if 'binary op' or 'conditional' but not 'comma'
// inToken is a special case because of the for..in crap. When ParseExpression is called from
// for, inToken = JSToken.In which excludes JSToken.In from the list of operators, otherwise
// inToken = JSToken.None which is always true if the first condition is true
if (JSScanner.IsProcessableOperator(this.currentToken.token) && inToken != this.currentToken.token){
OpPrec prec = JSScanner.GetOperatorPrecedence(this.currentToken.token);
bool rightAssoc = JSScanner.IsRightAssociativeOperator(this.currentToken.token);
// the current operator has lower precedence than the operator at the top of the stack
// or it has the same precedence and it is left associative (that is, no 'assign op' or 'conditional')
while (prec < opsStack._prec || prec == opsStack._prec && !rightAssoc){
//Console.Out.WriteLine("lower prec or same and left assoc");
expr = CreateExpressionNode(opsStack._operator, termStack._prev._term, termStack._term);
// pop the operator stack
opsStack = opsStack._prev;
// pop the term stack twice
termStack = termStack._prev._prev;
// push node onto the stack
termStack = new AstListItem(expr, termStack);
}
// the current operator has higher precedence that every scanned operators on the stack, or
// it has the same precedence as the one at the top of the stack and it is right associative
// push operator and next term
// special case conditional '?:'
if (JSToken.ConditionalIf == this.currentToken.token){
//Console.Out.WriteLine("Condition expression");
AST condition = termStack._term;
// pop term stack
termStack = termStack._prev;
GetNextToken();
// get expr1 in logOrExpr ? expr1 : expr2
AST operand1 = ParseExpression(true);
if (JSToken.Colon != this.currentToken.token)
ReportError(JSError.NoColon);
GetNextToken();
// get expr2 in logOrExpr ? expr1 : expr2
AST operand2 = ParseExpression(true, inToken);
expr = new Conditional(condition.context.CombineWith(operand2.context), condition, operand1, operand2);
termStack = new AstListItem(expr, termStack);
}else{
//Console.Out.WriteLine("higher prec or right assoc");
if (JSScanner.IsAssignmentOperator(this.currentToken.token)){
if (!bCanAssign){
ReportError(JSError.IllegalAssignment);
SkipTokensAndThrow();
}
}else
bCanAssign = false;
// push the operator onto the operators stack
opsStack = new OpListItem(this.currentToken.token, prec, opsStack);
// push new term
GetNextToken();
if (bCanAssign)
termStack = new AstListItem(ParseUnaryExpression(out bCanAssign, false), termStack);
else{
bool dummy;
termStack = new AstListItem(ParseUnaryExpression(out dummy, false), termStack);
dummy = dummy;
}
}
}else
break; // done, go and unwind the stack of expressions/operators
}
//Console.Out.WriteLine("unwinding stack");
// there are still operators to be processed
while (opsStack._operator != JSToken.None){
// make the ast operator node
expr = CreateExpressionNode(opsStack._operator, termStack._prev._term, termStack._term);
// pop the operator stack
opsStack = opsStack._prev;
// pop the term stack twice
termStack = termStack._prev._prev;
// push node onto the stack
termStack = new AstListItem(expr, termStack);
}
// if we have a ',' and we are not looking for a single expression reenter
if (!single && JSToken.Comma == this.currentToken.token){
//Console.Out.WriteLine("Next expr");
GetNextToken();
AST expr2 = ParseExpression(false, inToken);
termStack._term = new Comma(termStack._term.context.CombineWith(expr2.context), termStack._term, expr2);
}
Debug.Assert(termStack._prev == null);
return termStack._term;
}catch(RecoveryTokenException exc){
exc._partiallyComputedNode = leftHandSide;
throw exc;
}
}
//---------------------------------------------------------------------------------------
// ParseUnaryExpression
//
// UnaryExpression :
// PostfixExpression |
// 'delete' UnaryExpression |
// 'void' UnaryExpression |
// 'typeof' UnaryExpression |
// '++' UnaryExpression |
// '--' UnaryExpression |
// '+' UnaryExpression |
// '-' UnaryExpression |
// '~' UnaryExpression |
// '!' UnaryExpression
//
//---------------------------------------------------------------------------------------
private AST ParseUnaryExpression(out bool isLeftHandSideExpr, bool isMinus){
bool canBeAttribute = false;
return ParseUnaryExpression(out isLeftHandSideExpr, ref canBeAttribute, isMinus, false);
}
private AST ParseUnaryExpression(out bool isLeftHandSideExpr, ref bool canBeAttribute, bool isMinus){
return ParseUnaryExpression(out isLeftHandSideExpr, ref canBeAttribute, isMinus, true);
}
private AST ParseUnaryExpression(out bool isLeftHandSideExpr, ref bool canBeAttribute, bool isMinus, bool warnForKeyword){
AST ast = null;
isLeftHandSideExpr = false;
bool dummy = false;
Context exprCtx = null;
AST expr = null;
switch (this.currentToken.token){
case JSToken.Void:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new VoidOp(exprCtx, expr);
break;
case JSToken.Typeof:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new Typeof(exprCtx, expr);
break;
case JSToken.Plus:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new NumericUnary(exprCtx, expr, JSToken.Plus);
break;
case JSToken.Minus:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, true);
// deal with '-floatNumber' being parsed as a single entity (NumericLiteral) and not as a -NumericLiteral
if (expr.context.token == JSToken.NumericLiteral){
exprCtx.UpdateWith(expr.context);
expr.context = exprCtx;
ast = expr;
}else{
exprCtx.UpdateWith(expr.context);
ast = new NumericUnary(exprCtx, expr, JSToken.Minus);
}
break;
case JSToken.BitwiseNot:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new NumericUnary(exprCtx, expr, JSToken.BitwiseNot);
break;
case JSToken.LogicalNot:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new NumericUnary(exprCtx, expr, JSToken.LogicalNot);
break;
case JSToken.Delete:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new Delete(exprCtx, expr);
break;
case JSToken.Increment:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new PostOrPrefixOperator(exprCtx, expr, PostOrPrefix.PrefixIncrement);
break;
case JSToken.Decrement:
exprCtx = this.currentToken.Clone();
GetNextToken();
canBeAttribute = false;
expr = ParseUnaryExpression(out dummy, ref canBeAttribute, false);
exprCtx.UpdateWith(expr.context);
ast = new PostOrPrefixOperator(exprCtx, expr, PostOrPrefix.PrefixDecrement);
break;
default:
this.noSkipTokenSet.Add(NoSkipTokenSet.s_PostfixExpressionNoSkipTokenSet);
try{
ast = ParseLeftHandSideExpression(isMinus, ref canBeAttribute, warnForKeyword);
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_PostfixExpressionNoSkipTokenSet, exc) == -1){
throw exc;
}else{
if (exc._partiallyComputedNode == null)
SkipTokensAndThrow();
else
ast = exc._partiallyComputedNode;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_PostfixExpressionNoSkipTokenSet);
}
ast = ParsePostfixExpression(ast, out isLeftHandSideExpr, ref canBeAttribute);
break;
}
dummy = dummy;
return ast;
}
//---------------------------------------------------------------------------------------
// ParsePostfixExpression
//
// PostfixExpression:
// LeftHandSideExpression |
// LeftHandSideExpression '++' |
// LeftHandSideExpression '--'
//
//---------------------------------------------------------------------------------------
private AST ParsePostfixExpression(AST ast, out bool isLeftHandSideExpr){
bool canBeAttribute = false;
return ParsePostfixExpression(ast, out isLeftHandSideExpr, ref canBeAttribute);
}
private AST ParsePostfixExpression(AST ast, out bool isLeftHandSideExpr, ref bool canBeAttribute){
isLeftHandSideExpr = true;
Context exprCtx = null;
if (null != ast){
if (!this.scanner.GotEndOfLine()){
if (JSToken.Increment == this.currentToken.token){
isLeftHandSideExpr = false;
exprCtx = ast.context.Clone();
exprCtx.UpdateWith(this.currentToken);
canBeAttribute = false;
ast = new PostOrPrefixOperator(exprCtx, ast, PostOrPrefix.PostfixIncrement);
GetNextToken();
}else if (JSToken.Decrement == this.currentToken.token){
isLeftHandSideExpr = false;
exprCtx = ast.context.Clone();
exprCtx.UpdateWith(this.currentToken);
canBeAttribute = false;
ast = new PostOrPrefixOperator(exprCtx, ast, PostOrPrefix.PostfixDecrement);
GetNextToken();
}
}
}
return ast;
}
//---------------------------------------------------------------------------------------
// ParseLeftHandSideExpression
//
// LeftHandSideExpression :
// PrimaryExpression Accessor |
// 'new' LeftHandSideExpression |
// FunctionExpression
//
// PrimaryExpression :
// 'this' |
// Identifier |
// Literal |
// '(' Expression ')'
//
// FunctionExpression :
// 'function' OptionalFuncName '(' FormalParameterList ')' { FunctionBody }
//
// OptionalFuncName :
// <empty> |
// Identifier
//---------------------------------------------------------------------------------------
private AST ParseLeftHandSideExpression(){
return ParseLeftHandSideExpression(false);
}
private AST ParseLeftHandSideExpression(bool isMinus){
bool canBeAttribute = false;
return ParseLeftHandSideExpression(isMinus, ref canBeAttribute, false);
}
private AST ParseLeftHandSideExpression(bool isMinus, ref bool canBeAttribute, bool warnForKeyword){
AST ast = null;
bool isFunction = false;
ArrayList newContexts = null;
// new expression
while (JSToken.New == this.currentToken.token){
if (null == newContexts)
newContexts = new ArrayList(4);
newContexts.Add(this.currentToken.Clone());
GetNextToken();
}
JSToken token = this.currentToken.token;
switch (token){
// primary expression
case JSToken.Identifier:
ast = new Lookup(this.scanner.GetIdentifier(), this.currentToken.Clone());
break;
case JSToken.This:
canBeAttribute = false;
ast = new ThisLiteral(this.currentToken.Clone(), false);
break;
case JSToken.Super:
canBeAttribute = false;
ast = new ThisLiteral(this.currentToken.Clone(), true);
break;
case JSToken.StringLiteral:
canBeAttribute = false;
ast = new ConstantWrapper(this.scanner.GetStringLiteral(), this.currentToken.Clone());
break;
case JSToken.IntegerLiteral:
{canBeAttribute = false;
String number = this.currentToken.GetCode();
Object n = Convert.LiteralToNumber(number, this.currentToken);
if (n == null)
n = 0;
ast = new ConstantWrapper(n, this.currentToken.Clone());
((ConstantWrapper)ast).isNumericLiteral = true;
break;}
case JSToken.NumericLiteral:
{canBeAttribute = false;
String number = (isMinus) ? "-" + this.currentToken.GetCode() : this.currentToken.GetCode();
double d = Convert.ToNumber(number, false, false, Missing.Value);
ast = new ConstantWrapper(d, this.currentToken.Clone());
((ConstantWrapper)ast).isNumericLiteral = true;
break;}
case JSToken.True:
canBeAttribute = false;
ast = new ConstantWrapper(true, this.currentToken.Clone());
break;
case JSToken.False:
canBeAttribute = false;
ast = new ConstantWrapper(false, this.currentToken.Clone());
break;
case JSToken.Null:
canBeAttribute = false;
ast = new NullLiteral(this.currentToken.Clone());
break;
case JSToken.PreProcessorConstant:
canBeAttribute = false;
ast = new ConstantWrapper(this.scanner.GetPreProcessorValue(), this.currentToken.Clone());
break;
case JSToken.Divide:
canBeAttribute = false;
// could it be a regexp?
String source = this.scanner.ScanRegExp();
if (source != null){
bool badRegExp = false;
try {
new Regex(source, RegexOptions.ECMAScript);
} catch (System.ArgumentException) {
// Replace the invalid source with the trivial regular expression.
source = "";
badRegExp = true;
}
String flags = this.scanner.ScanRegExpFlags();
if (flags == null)
ast = new RegExpLiteral(source, null, this.currentToken.Clone());
else
try{
ast = new RegExpLiteral(source, flags, this.currentToken.Clone());
}catch (JScriptException){
// The flags are invalid, so use null instead.
ast = new RegExpLiteral(source, null, this.currentToken.Clone());
badRegExp = true;
}
if (badRegExp){
ReportError(JSError.RegExpSyntax, true);
}
break;
}
goto default;
// expression
case JSToken.LeftParen:
canBeAttribute = false;
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ParenExpressionNoSkipToken);
try{
ast = ParseExpression();
if (JSToken.RightParen != this.currentToken.token)
ReportError(JSError.NoRightParen);
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_ParenExpressionNoSkipToken, exc) == -1)
throw exc;
else
ast = exc._partiallyComputedNode;
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenExpressionNoSkipToken);
}
if (ast == null) //this can only happen when catching the exception and nothing was sent up by the caller
SkipTokensAndThrow();
break;
// array initializer
case JSToken.LeftBracket:
canBeAttribute = false;
Context listCtx = this.currentToken.Clone();
ASTList list = new ASTList(this.currentToken.Clone());
GetNextToken();
if (this.currentToken.token == JSToken.Identifier && this.scanner.PeekToken() == JSToken.Colon){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BracketToken);
try{
if (this.currentToken.GetCode() == "assembly"){
GetNextToken(); GetNextToken();
return new AssemblyCustomAttributeList(this.ParseCustomAttributeList());
}else{
ReportError(JSError.ExpectedAssembly);
SkipTokensAndThrow();
}
}catch(RecoveryTokenException exc){
exc._partiallyComputedNode = new Block(listCtx);
return exc._partiallyComputedNode;
}finally{
if (this.currentToken.token == JSToken.RightBracket){
this.errorToken = null;
GetNextToken();
}
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken);
}
}
while (JSToken.RightBracket != this.currentToken.token){
if (JSToken.Comma != this.currentToken.token){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet);
try{
list.Append(ParseExpression(true));
if (JSToken.Comma != this.currentToken.token){
if (JSToken.RightBracket != this.currentToken.token)
ReportError(JSError.NoRightBracket);
break;
}
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
list.Append(exc._partiallyComputedNode);
if (IndexOfToken(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet, exc) == -1){
listCtx.UpdateWith(CurrentPositionContext());
exc._partiallyComputedNode = new ArrayLiteral(listCtx, list);
throw exc;
}else{
if (JSToken.RightBracket == this.currentToken.token)
break;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet);
}
}else{
list.Append(new ConstantWrapper(Missing.Value, this.currentToken.Clone()));
}
GetNextToken();
}
listCtx.UpdateWith(this.currentToken);
ast = new ArrayLiteral(listCtx, list);
break;
// object initializer
case JSToken.LeftCurly:
canBeAttribute = false;
Context objCtx = this.currentToken.Clone();
GetNextToken();
ASTList fields = new ASTList(this.currentToken.Clone());
if (JSToken.RightCurly != this.currentToken.token){
for (;;){
AST field = null;
AST value = null;
if (JSToken.Identifier == this.currentToken.token)
field = new ConstantWrapper(this.scanner.GetIdentifier(), this.currentToken.Clone());
else if (JSToken.StringLiteral == this.currentToken.token)
field = new ConstantWrapper(this.scanner.GetStringLiteral(), this.currentToken.Clone());
else if (JSToken.IntegerLiteral == this.currentToken.token || JSToken.NumericLiteral == this.currentToken.token ){
String numberString = this.currentToken.GetCode();
double dValue = Convert.ToNumber(numberString, true, true, Missing.Value);
field = new ConstantWrapper(dValue, this.currentToken.Clone());
((ConstantWrapper)field).isNumericLiteral = true;
}else{
ReportError(JSError.NoMemberIdentifier);
field = new IdentifierLiteral("_#Missing_Field#_" + s_cDummyName++, CurrentPositionContext());
}
ASTList pair = new ASTList(field.context.Clone());
GetNextToken();
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet);
try{
// get the value
if (JSToken.Colon != this.currentToken.token){
ReportError(JSError.NoColon, true);
value = ParseExpression(true);
}else{
GetNextToken();
value = ParseExpression(true);
}
// put the pair into the list of fields
pair.Append(field);
pair.Append(value);
fields.Append(pair);
if (JSToken.RightCurly == this.currentToken.token)
break;
else{
if (JSToken.Comma == this.currentToken.token)
GetNextToken();
else{
if (this.scanner.GotEndOfLine()){
ReportError(JSError.NoRightCurly);
}else
ReportError(JSError.NoComma, true);
SkipTokensAndThrow();
}
}
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null){
// the problem was in ParseExpression trying to determine value
value = exc._partiallyComputedNode;
pair.Append(field);
pair.Append(value);
fields.Append(pair);
}
if (IndexOfToken(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = new ObjectLiteral(objCtx, fields);
throw exc;
}else{
if (JSToken.Comma == this.currentToken.token)
GetNextToken();
if (JSToken.RightCurly == this.currentToken.token)
break;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet);
}
}
}
fields.context.UpdateWith(this.currentToken);
objCtx.UpdateWith(this.currentToken);
ast = new ObjectLiteral(objCtx, fields);
break;
// function expression
case JSToken.Function:
canBeAttribute = false;
ast = ParseFunction((FieldAttributes)0, true, this.currentToken.Clone(), false, false, false, false, null);
isFunction = true;
break;
default:
string identifier = JSKeyword.CanBeIdentifier(this.currentToken.token);
if (null != identifier){
if (warnForKeyword){
switch (this.currentToken.token){
case JSToken.Boolean :
case JSToken.Byte :
case JSToken.Char :
case JSToken.Double :
case JSToken.Float :
case JSToken.Int :
case JSToken.Long :
case JSToken.Short :
case JSToken.Void :
break;
default:
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
break;
}
}
canBeAttribute = false;
ast = new Lookup(identifier, this.currentToken.Clone());
}else if (this.currentToken.token == JSToken.BitwiseAnd){ //& expr used outside of a parameter list
ReportError(JSError.WrongUseOfAddressOf);
this.errorToken = null;
GetNextToken();
return this.ParseLeftHandSideExpression(isMinus, ref canBeAttribute, warnForKeyword);
}else{
ReportError(JSError.ExpressionExpected);
SkipTokensAndThrow();
}
break;
}
// can be a CallExpression, that is, followed by '.' or '(' or '['
if (!isFunction)
GetNextToken();
return MemberExpression(ast, newContexts, ref canBeAttribute);
}
//-------------------------------------------------------------------------------------------
// ParseConstructorCall
//
// ConstructorCall :
// 'this' Arguments
// 'super' Arguments
//--------------------------------------------------------------------------------------------
private AST ParseConstructorCall(Context superCtx){
bool isSuperConstructorCall = JSToken.Super == this.currentToken.token;
GetNextToken();
Context listCtx = this.currentToken.Clone();
ASTList args = new ASTList(listCtx);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken);
try{
args = ParseExpressionList(JSToken.RightParen);
GetNextToken(); //Skip the )
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
args = (ASTList)exc._partiallyComputedNode;
if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1
&& IndexOfToken(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = new ConstructorCall(superCtx, args, isSuperConstructorCall);
throw exc;
}else{
if (exc._token == JSToken.RightParen)
GetNextToken();
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken);
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_EndOfStatementNoSkipTokenSet);
}
superCtx.UpdateWith(listCtx);
return new ConstructorCall(superCtx, args, isSuperConstructorCall);
}
private CustomAttributeList ParseCustomAttributeList(){
CustomAttributeList result = new CustomAttributeList(this.currentToken.Clone());
do{
Context attrStart = currentToken.Clone();
bool bAssign, canBeAttribute = true;
AST statement = ParseUnaryExpression(out bAssign, ref canBeAttribute, false, false);
if (canBeAttribute){
if (statement is Lookup || statement is Member)
result.Append(new CustomAttribute(statement.context, statement, new ASTList(null)));
else
result.Append(((Call)statement).ToCustomAttribute());
}else if (this.tokensSkipped == 0)
ReportError(JSError.SyntaxError, attrStart);
if (this.currentToken.token == JSToken.RightBracket)
return result;
else if (this.currentToken.token == JSToken.Comma)
this.GetNextToken();
else{
ReportError(JSError.NoRightBracketOrComma);
SkipTokensAndThrow();
}
}while(true);
}
//---------------------------------------------------------------------------------------
// MemberExpression
//
// Accessor :
// <empty> |
// Arguments Accessor
// '[' Expression ']' Accessor |
// '.' Identifier Accessor |
//
// Don't have this function throwing an exception without checking all the calling sites.
// There is state in instance variable that is saved on the calling stack in some function
// (i.e ParseFunction and ParseClass) and you don't want to blow up the stack
//---------------------------------------------------------------------------------------
private AST MemberExpression(AST expression, ArrayList newContexts){
bool canBeAttribute = false;
return MemberExpression(expression, newContexts, ref canBeAttribute);
}
private AST MemberExpression(AST expression, ArrayList newContexts, ref bool canBeAttribute){
bool canBeQualid;
return MemberExpression(expression, newContexts, out canBeQualid, ref canBeAttribute);
}
private AST MemberExpression(AST expression, ArrayList newContexts, out bool canBeQualid, ref bool canBeAttribute){
bool noMoreForAttr = false;
canBeQualid = true;
for(;;){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_MemberExprNoSkipTokenSet);
try{
switch (this.currentToken.token){
case JSToken.LeftParen:
if (noMoreForAttr)
canBeAttribute = false;
else
noMoreForAttr = true;
canBeQualid = false;
ASTList args = null;
RecoveryTokenException callError = null;
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken);
try{
args = ParseExpressionList(JSToken.RightParen);
}catch(RecoveryTokenException exc){
args = (ASTList)exc._partiallyComputedNode;
if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1)
callError = exc; // thrown later on
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken);
}
//treat eval and print specially
if (expression is Lookup){
String name = expression.ToString();
if (name.Equals("eval")){
expression.context.UpdateWith(args.context);
if (args.count > 0)
expression = new Eval(expression.context, args[0]);
else
expression = new Eval(expression.context, new ConstantWrapper("", CurrentPositionContext()));
}else if (this.Globals.engine.doPrint && name.Equals("print")){
expression.context.UpdateWith(args.context);
expression = new Print(expression.context, args);
}else{
if (name == "GetObject")
this.ForceReportInfo(JSError.GetObjectNotSupportedOnRotor);
expression = new Call(expression.context.CombineWith(args.context), expression, args, false);
}
}else
expression = new Call(expression.context.CombineWith(args.context), expression, args, false);
if (null != newContexts && newContexts.Count > 0){
((Context)newContexts[newContexts.Count - 1]).UpdateWith(expression.context);
if (!(expression is Call))
expression = new Call((Context)newContexts[newContexts.Count - 1], expression, new ASTList(CurrentPositionContext()), false);
else
expression.context = (Context)newContexts[newContexts.Count - 1];
((Call)expression).isConstructor = true;
newContexts.RemoveAt(newContexts.Count - 1);
}
if (callError != null){
callError._partiallyComputedNode = expression;
throw callError;
}
GetNextToken();
break;
case JSToken.LeftBracket:
canBeQualid = false;
canBeAttribute = false;
this.noSkipTokenSet.Add(NoSkipTokenSet.s_BracketToken);
try{
args = ParseExpressionList(JSToken.RightBracket);
}catch(RecoveryTokenException exc){
if(IndexOfToken(NoSkipTokenSet.s_BracketToken, exc) == -1){
if (exc._partiallyComputedNode != null){
exc._partiallyComputedNode =
new Call(expression.context.CombineWith(this.currentToken.Clone()), expression, (ASTList)exc._partiallyComputedNode, true);
}else{
exc._partiallyComputedNode = expression;
}
throw exc;
}else
args = (ASTList)exc._partiallyComputedNode;
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken);
}
expression = new Call(expression.context.CombineWith(this.currentToken.Clone()), expression, args, true);
if (null != newContexts && newContexts.Count > 0){
((Context)newContexts[newContexts.Count - 1]).UpdateWith(expression.context);
expression.context = (Context)newContexts[newContexts.Count - 1];
((Call)expression).isConstructor = true;
newContexts.RemoveAt(newContexts.Count - 1);
}
GetNextToken();
break;
case JSToken.AccessField:
if (noMoreForAttr)
canBeAttribute = false;
ConstantWrapper id = null;
GetNextToken();
if (JSToken.Identifier != this.currentToken.token){
string identifier = JSKeyword.CanBeIdentifier(this.currentToken.token);
if (null != identifier){
ForceReportInfo(JSError.KeywordUsedAsIdentifier);
id = new ConstantWrapper(identifier, this.currentToken.Clone());
}else{
ReportError(JSError.NoIdentifier);
SkipTokensAndThrow(expression);
}
}else
id = new ConstantWrapper(this.scanner.GetIdentifier(), this.currentToken.Clone());
GetNextToken();
expression = new Member(expression.context.CombineWith(id.context), expression, id);
break;
default:
if (null != newContexts){
while (newContexts.Count > 0){
((Context)newContexts[newContexts.Count - 1]).UpdateWith(expression.context);
expression = new Call((Context)newContexts[newContexts.Count - 1],
expression,
new ASTList(CurrentPositionContext()),
false);
((Call)expression).isConstructor = true;
newContexts.RemoveAt(newContexts.Count - 1);
}
}
return expression;
}
}catch(RecoveryTokenException exc){
if (IndexOfToken(NoSkipTokenSet.s_MemberExprNoSkipTokenSet, exc) != -1)
expression = exc._partiallyComputedNode;
else{
Debug.Assert(exc._partiallyComputedNode == expression);
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_MemberExprNoSkipTokenSet);
}
}
}
//---------------------------------------------------------------------------------------
// ParseExpressionList
//
// Given a starting this.currentToken '(' or '[', parse a list of expression separated by
// ',' until matching ')' or ']'
//---------------------------------------------------------------------------------------
private ASTList ParseExpressionList(JSToken terminator){
Context listCtx = this.currentToken.Clone();
int line = this.scanner.GetCurrentLine();
GetNextToken();
ASTList list = new ASTList(listCtx);
if (terminator != this.currentToken.token){
for (;;){
this.noSkipTokenSet.Add(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet);
try{
if (JSToken.BitwiseAnd == this.currentToken.token){ // address of operator
Context addressOfCtx = this.currentToken.Clone();
GetNextToken();
AST lhexpr = ParseLeftHandSideExpression();
if (lhexpr is Member || lhexpr is Lookup){
addressOfCtx.UpdateWith(lhexpr.context);
list.Append(new AddressOf(addressOfCtx, lhexpr));
}else{
ReportError(JSError.DoesNotHaveAnAddress, addressOfCtx.Clone());
list.Append(lhexpr);
}
}else if (JSToken.Comma == this.currentToken.token){
list.Append(new ConstantWrapper(System.Reflection.Missing.Value, this.currentToken.Clone()));
}else if (terminator == this.currentToken.token){
break;
}else
list.Append(ParseExpression(true));
if (terminator == this.currentToken.token)
break;
else{
if (JSToken.Comma != this.currentToken.token){
if (terminator == JSToken.RightParen){
// in ASP+ it's easy to write a semicolon at the end of an expression
// not realizing it is going to go inside a function call
// (ie. Response.Write()), so make a special check here
if (JSToken.Semicolon == this.currentToken.token){
if (JSToken.RightParen == this.scanner.PeekToken()){
ReportError(JSError.UnexpectedSemicolon, true);
GetNextToken();
break;
}
}
ReportError(JSError.NoRightParenOrComma);
}else
ReportError(JSError.NoRightBracketOrComma);
SkipTokensAndThrow();
}
}
}catch(RecoveryTokenException exc){
if (exc._partiallyComputedNode != null)
list.Append(exc._partiallyComputedNode);
if (IndexOfToken(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet, exc) == -1){
exc._partiallyComputedNode = list;
throw exc;
}
}finally{
this.noSkipTokenSet.Remove(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet);
}
GetNextToken();
}
}
listCtx.UpdateWith(this.currentToken);
return list;
}
//---------------------------------------------------------------------------------------
// CreateExpressionNode
//
// Create the proper AST object according to operator
//---------------------------------------------------------------------------------------
private AST CreateExpressionNode(JSToken op, AST operand1, AST operand2){
Context context = operand1.context.CombineWith(operand2.context);
switch (op){
case JSToken.Assign:
return new Assign(context, operand1, operand2);
case JSToken.BitwiseAnd:
return new BitwiseBinary(context, operand1, operand2, JSToken.BitwiseAnd);
case JSToken.BitwiseAndAssign:
return new BitwiseBinaryAssign(context, operand1, operand2, JSToken.BitwiseAnd);
case JSToken.BitwiseOr:
return new BitwiseBinary(context, operand1, operand2, JSToken.BitwiseOr);
case JSToken.BitwiseOrAssign:
return new BitwiseBinaryAssign(context, operand1, operand2, JSToken.BitwiseOr);
case JSToken.BitwiseXor:
return new BitwiseBinary(context, operand1, operand2, JSToken.BitwiseXor);
case JSToken.BitwiseXorAssign:
return new BitwiseBinaryAssign(context, operand1, operand2, JSToken.BitwiseXor);
case JSToken.Comma:
return new Comma(context, operand1, operand2);
case JSToken.Divide:
return new NumericBinary(context, operand1, operand2, JSToken.Divide);
case JSToken.DivideAssign:
return new NumericBinaryAssign(context, operand1, operand2, JSToken.Divide);
case JSToken.Equal:
return new Equality(context, operand1, operand2, JSToken.Equal);
case JSToken.GreaterThan:
return new Relational(context, operand1, operand2, JSToken.GreaterThan);
case JSToken.GreaterThanEqual:
return new Relational(context, operand1, operand2, JSToken.GreaterThanEqual);
case JSToken.In:
return new In(context, operand1, operand2);
case JSToken.Instanceof:
return new Instanceof(context, operand1, operand2);
case JSToken.LeftShift:
return new BitwiseBinary(context, operand1, operand2, JSToken.LeftShift);
case JSToken.LeftShiftAssign:
return new BitwiseBinaryAssign(context, operand1, operand2, JSToken.LeftShift);
case JSToken.LessThan:
return new Relational(context, operand1, operand2, JSToken.LessThan);
case JSToken.LessThanEqual:
return new Relational(context, operand1, operand2, JSToken.LessThanEqual);
case JSToken.LogicalAnd:
return new Logical_and(context, operand1, operand2);
case JSToken.LogicalOr:
return new Logical_or(context, operand1, operand2);
case JSToken.Minus:
return new NumericBinary(context, operand1, operand2, JSToken.Minus);
case JSToken.MinusAssign:
return new NumericBinaryAssign(context, operand1, operand2, JSToken.Minus);
case JSToken.Modulo:
return new NumericBinary(context, operand1, operand2, JSToken.Modulo);
case JSToken.ModuloAssign:
return new NumericBinaryAssign(context, operand1, operand2, JSToken.Modulo);
case JSToken.Multiply:
return new NumericBinary(context, operand1, operand2, JSToken.Multiply);
case JSToken.MultiplyAssign:
return new NumericBinaryAssign(context, operand1, operand2, JSToken.Multiply);
case JSToken.NotEqual:
return new Equality(context, operand1, operand2, JSToken.NotEqual);
case JSToken.Plus:
return new Plus(context, operand1, operand2);
case JSToken.PlusAssign:
return new PlusAssign(context, operand1, operand2);
case JSToken.RightShift:
return new BitwiseBinary(context, operand1, operand2, JSToken.RightShift);
case JSToken.RightShiftAssign:
return new BitwiseBinaryAssign(context, operand1, operand2, JSToken.RightShift);
case JSToken.StrictEqual:
return new StrictEquality(context, operand1, operand2, JSToken.StrictEqual);
case JSToken.StrictNotEqual:
return new StrictEquality(context, operand1, operand2, JSToken.StrictNotEqual);
case JSToken.UnsignedRightShift:
return new BitwiseBinary(context, operand1, operand2, JSToken.UnsignedRightShift);
case JSToken.UnsignedRightShiftAssign:
return new BitwiseBinaryAssign(context, operand1, operand2, JSToken.UnsignedRightShift);
default:
Debug.Assert(false);
return null;
}
}
//---------------------------------------------------------------------------------------
// GetNextToken
//
// Return the next token or peeked token if this.errorToken is not null.
// Usually this.errorToken is set by AddError even though any code can look ahead
// by assigning this.errorToken.
// At this point the context is not saved so if position information is needed
// they have to be saved explicitely
//---------------------------------------------------------------------------------------
private void GetNextToken(){
if (null != this.errorToken){
if (this.breakRecursion > 10){
#if DEBUG
JSParser.WriteToFile(this.errorToken);
#endif
this.errorToken = null;
this.scanner.GetNextToken();
return;
}
this.breakRecursion++;
this.currentToken = this.errorToken;
this.errorToken = null;
}else{
this.goodTokensProcessed++;
this.breakRecursion = 0;
// the scanner shares this.currentToken with the parser
this.scanner.GetNextToken();
}
}
#if DEBUG
private static void WriteToFile(Context context){
String filename = "error_" + (s_filenameSuffix++).ToString() + ".js";
ScriptStream.WriteLine("possible infinite recursion in parser recovery code...");
ScriptStream.WriteLine("Please send the file " + filename + " to hermanv");
System.IO.TextWriter stream = new System.IO.StreamWriter(System.IO.File.Create(filename));
String code = context.source_string.Substring(0, context.EndPosition);
stream.Write(code.ToCharArray());
stream.Flush();
stream.Close();
}
#endif
private Context CurrentPositionContext(){
Context context = this.currentToken.Clone();
context.endPos = (context.startPos < context.source_string.Length) ? context.startPos + 1 : context.startPos;
return context;
}
//---------------------------------------------------------------------------------------
// ReportError
//
// Generate a parser error.
// When no context is provided the token is missing so the context is the current position
//---------------------------------------------------------------------------------------
private void ReportError(JSError errorId){
ReportError(errorId, false);
}
//---------------------------------------------------------------------------------------
// ReportError
//
// Generate a parser error.
// When no context is provided the token is missing so the context is the current position
// The function is told whether or not next call to GetToken() should return the same
// token or not
//---------------------------------------------------------------------------------------
private void ReportError(JSError errorId, bool skipToken){
// get the current position token
Context context = this.currentToken.Clone();
context.endPos = context.startPos + 1;
ReportError(errorId, context, skipToken);
}
//---------------------------------------------------------------------------------------
// ReportError
//
// Generate a parser error.
// This is usually generated when a bad token is found, the context identifies the
// bad token
//---------------------------------------------------------------------------------------
private void ReportError(JSError errorId, Context context){
ReportError(errorId, context, false);
}
//---------------------------------------------------------------------------------------
// ReportError
//
// Generate a parser error.
// The function is told whether or not next call to GetToken() should return the same
// token or not
//---------------------------------------------------------------------------------------
private void ReportError(JSError errorId, Context context, bool skipToken){
Debug.Assert(context != null);
int previousSeverity = this.Severity;
this.Severity = (new JScriptException(errorId)).Severity;
// EOF error is special and it's the last error we can possibly get
if (JSToken.EndOfFile == context.token)
EOFError(errorId); // EOF context is special
else{
// report the error if not in error condition and the
// error for this token is not worse than the one for the
// previous token
if (this.goodTokensProcessed > 0 || this.Severity < previousSeverity)
context.HandleError(errorId);
// reset proper info
if (skipToken)
this.goodTokensProcessed = -1;
else{
this.errorToken = this.currentToken;
this.goodTokensProcessed = 0;
}
}
}
//---------------------------------------------------------------------------------------
// ForceReportInfo
//
// Generate a parser error (info), does not change the error state in the parse
//---------------------------------------------------------------------------------------
private void ForceReportInfo(JSError errorId){
ForceReportInfo(this.currentToken.Clone(), errorId);
}
//---------------------------------------------------------------------------------------
// ForceReportInfo
//
// Generate a parser error (info), does not change the error state in the parse
//---------------------------------------------------------------------------------------
private void ForceReportInfo(Context context, JSError errorId){
Debug.Assert(context != null);
context.HandleError(errorId);
}
//---------------------------------------------------------------------------------------
// ForceReportInfo
//
// Generate a parser error (info), does not change the error state in the parse
//---------------------------------------------------------------------------------------
private void ForceReportInfo(JSError errorId, bool treatAsError){
this.currentToken.Clone().HandleError(errorId, treatAsError);
}
//---------------------------------------------------------------------------------------
// EOFError
//
// Create a context for EOF error. The created context points to the end of the source
// code. Assume the the scanner actually reached the end of file
//---------------------------------------------------------------------------------------
private void EOFError(JSError errorId){
Context eofCtx = this.sourceContext.Clone();
eofCtx.lineNumber = this.scanner.GetCurrentLine();
eofCtx.endLineNumber = eofCtx.lineNumber;
eofCtx.startLinePos = this.scanner.GetStartLinePosition();
eofCtx.endLinePos = eofCtx.startLinePos;
eofCtx.startPos = this.sourceContext.endPos;
eofCtx.endPos++;
eofCtx.HandleError(errorId);
}
//---------------------------------------------------------------------------------------
// SkipTokensAndThrow
//
// Skip tokens until one in the no skip set is found.
// A call to this function always ends in a throw statement that will be caught by the
// proper rule
//---------------------------------------------------------------------------------------
private void SkipTokensAndThrow(){
SkipTokensAndThrow(null);
}
private void SkipTokensAndThrow(AST partialAST){
this.errorToken = null; // make sure we go to the next token
bool checkForEndOfLine = this.noSkipTokenSet.HasToken(JSToken.EndOfLine);
while (!this.noSkipTokenSet.HasToken(this.currentToken.token)){
if (checkForEndOfLine){
if (this.scanner.GotEndOfLine()){
this.errorToken = this.currentToken;
throw new RecoveryTokenException(JSToken.EndOfLine, partialAST);
}
}
GetNextToken();
if (++this.tokensSkipped > c_MaxSkippedTokenNumber){
ForceReportInfo(JSError.TooManyTokensSkipped);
throw new EndOfFile();
}
if (JSToken.EndOfFile == this.currentToken.token)
throw new EndOfFile();
}
this.errorToken = this.currentToken;
// got a token in the no skip set, throw
throw new RecoveryTokenException(this.currentToken.token, partialAST);
}
//---------------------------------------------------------------------------------------
// IndexOfToken
//
// check whether the recovery token is a good one for the caller
//---------------------------------------------------------------------------------------
private int IndexOfToken(JSToken[] tokens, RecoveryTokenException exc){
return IndexOfToken(tokens, exc._token);
}
private int IndexOfToken(JSToken[] tokens, JSToken token){
int i, c;
for (i = 0, c = tokens.Length; i < c; i++)
if (tokens[i] == token)
break;
if (i >= c)
i = -1;
else{
// assume that the caller will deal with the token so move the state back to normal
this.errorToken = null;
}
return i;
}
private bool TokenInList(JSToken[] tokens, JSToken token){
return (-1 != IndexOfToken(tokens, token));
}
private bool TokenInList(JSToken[] tokens, RecoveryTokenException exc){
return (-1 != IndexOfToken(tokens, exc._token));
}
//---------------------------------------------------------------------------------------
// FromASTListToCustomAttributeList
//
// Utility function that takes a list of AST nodes (Call and Lookup nodes) and makes
// a CustomAttributeList. It is invoked once a list of calls and lookups is unambiguosly
// parsed as a custom attribute list
//---------------------------------------------------------------------------------------
private CustomAttributeList FromASTListToCustomAttributeList(ArrayList attributes){
CustomAttributeList customAttributes = null;
if (attributes != null && attributes.Count > 0)
customAttributes = new CustomAttributeList(((AST)attributes[0]).context);
for (int i = 0, n = attributes.Count; i < n; i++){
ASTList args = new ASTList(null);
if (attributes[i] is Lookup || attributes[i] is Member)
customAttributes.Append(new CustomAttribute(((AST)attributes[i]).context, (AST)attributes[i], args));
else
customAttributes.Append(((Call)attributes[i]).ToCustomAttribute());
}
return customAttributes;
}
internal bool HasAborted{
get{
return this.tokensSkipped > c_MaxSkippedTokenNumber;
}
}
}
// helper classes
//***************************************************************************************
//
//***************************************************************************************
internal class AstListItem{
internal AstListItem _prev;
internal AST _term;
internal AstListItem(AST term, AstListItem prev){
_prev = prev;
_term = term;
}
}
//***************************************************************************************
//
//***************************************************************************************
internal class OpListItem{
internal OpListItem _prev;
internal JSToken _operator;
internal OpPrec _prec;
//internal OpAssoc _assoc;
internal OpListItem(JSToken op, OpPrec prec, /*OpAssoc assoc,*/ OpListItem prev){
_prev = prev;
_operator = op;
_prec = prec;
//_assoc = assoc;
}
}
//***************************************************************************************
//
//***************************************************************************************
public class ParserException : Exception{
internal ParserException() : base("JSParser Exception"){
}
}
internal class UnexpectedToken : ParserException{
internal JSToken _unexpectedToken;
internal UnexpectedToken() : base(){
}
internal UnexpectedToken(JSToken unexpectedToken) : base(){
_unexpectedToken = unexpectedToken;
}
}
internal class RecoveryTokenException : ParserException{
internal JSToken _token;
internal AST _partiallyComputedNode;
internal RecoveryTokenException(JSToken token, AST partialAST) : base(){
_token = token;
_partiallyComputedNode = partialAST;
}
}
public class EndOfFile : ParserException{
internal EndOfFile() : base(){
}
}
//***************************************************************************************
// NoSkipTokenSet
//
// This class is a possible implementation of the no skip token set. It relies on the
// fact that the array passed in are static. Should you change it, this implementation
// should change as well.
// It keeps a linked list of token arrays that are passed in during parsing, on error
// condition the list is traversed looking for a matching token. If a match is found
// the token should not be skipped and an exception is thrown to let the proper
// rule deal with the token
//***************************************************************************************
internal class NoSkipTokenSet{
TokenSetListItem _tokenSet;
internal NoSkipTokenSet(){
_tokenSet = null;
}
internal void Add(JSToken[] tokens){
_tokenSet = new TokenSetListItem(tokens, _tokenSet);
}
internal void Remove(JSToken[] tokens){
TokenSetListItem curr = _tokenSet, prev = null;
while (curr != null){
if (curr._tokens == tokens){
if (prev == null){
Debug.Assert(_tokenSet == curr);
_tokenSet = _tokenSet._next;
}else{
prev._next = curr._next;
}
return;
}
prev = curr;
curr = curr._next;
}
Debug.Assert(false, "Token set not in no skip token");
}
internal bool HasToken(JSToken token){
TokenSetListItem curr = _tokenSet;
while (curr != null){
for (int i = 0, c = curr._tokens.Length; i < c; i++){
if (curr._tokens[i] == token)
return true;
}
curr = curr._next;
}
return false;
}
// list of static no skip token set for specifc rules
internal static readonly JSToken[] s_ArrayInitNoSkipTokenSet = new JSToken[]{JSToken.RightBracket,
JSToken.Comma};
internal static readonly JSToken[] s_BlockConditionNoSkipTokenSet = new JSToken[]{JSToken.RightParen,
JSToken.LeftCurly,
JSToken.EndOfLine};
internal static readonly JSToken[] s_BlockNoSkipTokenSet = new JSToken[]{JSToken.RightCurly};
internal static readonly JSToken[] s_BracketToken = new JSToken[]{JSToken.RightBracket};
internal static readonly JSToken[] s_CaseNoSkipTokenSet = new JSToken[]{JSToken.Case,
JSToken.Default,
JSToken.Colon,
JSToken.EndOfLine};
internal static readonly JSToken[] s_ClassBodyNoSkipTokenSet = new JSToken[]{JSToken.Class,
JSToken.Interface,
JSToken.Enum,
JSToken.Function,
JSToken.Var,
JSToken.Const,
JSToken.Static,
JSToken.Public,
JSToken.Private,
JSToken.Protected};
internal static readonly JSToken[] s_InterfaceBodyNoSkipTokenSet = new JSToken[]{JSToken.Enum,
JSToken.Function,
JSToken.Public,
JSToken.EndOfLine,
JSToken.Semicolon};
internal static readonly JSToken[] s_ClassExtendsNoSkipTokenSet = new JSToken[]{JSToken.LeftCurly,
JSToken.Implements};
internal static readonly JSToken[] s_ClassImplementsNoSkipTokenSet = new JSToken[]{JSToken.LeftCurly,
JSToken.Comma};
internal static readonly JSToken[] s_DoWhileBodyNoSkipTokenSet = new JSToken[]{JSToken.While};
internal static readonly JSToken[] s_EndOfLineToken = new JSToken[]{JSToken.EndOfLine};
internal static readonly JSToken[] s_EndOfStatementNoSkipTokenSet = new JSToken[]{JSToken.Semicolon,
JSToken.EndOfLine};
internal static readonly JSToken[] s_EnumBaseTypeNoSkipTokenSet = new JSToken[]{JSToken.LeftCurly};
internal static readonly JSToken[] s_EnumBodyNoSkipTokenSet = new JSToken[]{JSToken.Identifier};
internal static readonly JSToken[] s_ExpressionListNoSkipTokenSet = new JSToken[]{JSToken.Comma};
internal static readonly JSToken[] s_FunctionDeclNoSkipTokenSet = new JSToken[]{JSToken.RightParen,
JSToken.LeftCurly,
JSToken.Comma};
internal static readonly JSToken[] s_IfBodyNoSkipTokenSet = new JSToken[]{JSToken.Else};
internal static readonly JSToken[] s_MemberExprNoSkipTokenSet = new JSToken[]{JSToken.LeftBracket,
JSToken.LeftParen,
JSToken.AccessField};
internal static readonly JSToken[] s_NoTrySkipTokenSet = new JSToken[]{JSToken.Catch,
JSToken.Finally};
internal static readonly JSToken[] s_ObjectInitNoSkipTokenSet = new JSToken[]{JSToken.RightCurly,
JSToken.Comma};
internal static readonly JSToken[] s_PackageBodyNoSkipTokenSet = new JSToken[]{JSToken.Class,
JSToken.Interface,
JSToken.Enum};
internal static readonly JSToken[] s_ParenExpressionNoSkipToken = new JSToken[]{JSToken.RightParen};
internal static readonly JSToken[] s_ParenToken = new JSToken[]{JSToken.RightParen};
internal static readonly JSToken[] s_PostfixExpressionNoSkipTokenSet = new JSToken[]{JSToken.Increment,
JSToken.Decrement};
internal static readonly JSToken[] s_StartBlockNoSkipTokenSet = new JSToken[]{JSToken.LeftCurly};
internal static readonly JSToken[] s_StartStatementNoSkipTokenSet = new JSToken[]{JSToken.LeftCurly,
JSToken.Var,
JSToken.Const,
JSToken.If,
JSToken.For,
JSToken.Do,
JSToken.While,
JSToken.With,
JSToken.Switch,
JSToken.Try};
internal static readonly JSToken[] s_SwitchNoSkipTokenSet = new JSToken[]{JSToken.Case,
JSToken.Default};
internal static readonly JSToken[] s_TopLevelNoSkipTokenSet = new JSToken[]{JSToken.Package,
JSToken.Class,
JSToken.Interface,
JSToken.Enum,
JSToken.Function,
JSToken.Import};
internal static readonly JSToken[] s_VariableDeclNoSkipTokenSet = new JSToken[]{JSToken.Comma,
JSToken.Semicolon};
private class TokenSetListItem{
internal TokenSetListItem _next;
internal JSToken[] _tokens;
internal TokenSetListItem(JSToken[] tokens, TokenSetListItem next){
_next = next;
_tokens = tokens;
}
}
}
}