//--------------------------------------------------------------------------------------- // ParseStatements // // statements : // <empty> | // statement statements // //--------------------------------------------------------------------------------------- private Block ParseStatements() { var program = new Block(CurrentPositionContext(), this); m_blockType.Add(BlockType.Block); m_useCurrentForNext = false; try { // get the first token GetNextToken(); // if the block doesn't have a proper file context, then let's set it from the // first token -- that token might have had a ///#source directive! if (string.IsNullOrEmpty(program.Context.Document.FileContext)) { program.Context.Document.FileContext = m_currentToken.Document.FileContext; } m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_TopLevelNoSkipTokenSet); try { var possibleDirectivePrologue = true; int lastEndPosition = m_currentToken.EndPosition; while (m_currentToken.Token != JSToken.EndOfFile) { AstNode ast = null; try { // parse a statement -- pass true because we really want a SourceElement, // which is a Statement OR a FunctionDeclaration. Technically, FunctionDeclarations // are not statements! ast = ParseStatement(true); // if we are still possibly looking for directive prologues if (possibleDirectivePrologue) { var constantWrapper = ast as ConstantWrapper; if (constantWrapper != null && constantWrapper.PrimitiveType == PrimitiveType.String) { if (!(constantWrapper is DirectivePrologue)) { // use a directive prologue node instead ast = new DirectivePrologue(constantWrapper.Value.ToString(), ast.Context, ast.Parser) { MayHaveIssues = constantWrapper.MayHaveIssues }; } } else if (!m_newModule) { // nope -- no longer finding directive prologues possibleDirectivePrologue = false; } } else if (m_newModule) { // we aren't looking for directive prologues anymore, but we did scan // into a new module after that last AST, so reset the flag because that // new module might have directive prologues for next time possibleDirectivePrologue = true; } } catch (RecoveryTokenException exc) { if (TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, exc) || TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc)) { ast = exc._partiallyComputedNode; GetNextToken(); } else { m_useCurrentForNext = false; do { GetNextToken(); } while (m_currentToken.Token != JSToken.EndOfFile && !TokenInList(NoSkipTokenSet.s_TopLevelNoSkipTokenSet, m_currentToken.Token) && !TokenInList(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, m_currentToken.Token)); } } if (null != ast) { // append the token to the program program.Append(ast); // set the last end position to be the start of the current token. // if we parse the next statement and the end is still the start, we know // something is up and might get into an infinite loop. lastEndPosition = m_currentToken.EndPosition; } else if (!m_scanner.IsEndOfFile && m_currentToken.StartLinePosition == lastEndPosition) { // didn't parse a statement, we're not at the EOF, and we didn't move // anywhere in the input stream. If we just keep looping around, we're going // to get into an infinite loop. Break it. m_currentToken.HandleError(JSError.ApplicationError, true); break; } } if (m_importantComments.Count > 0 && m_settings.PreserveImportantComments && m_settings.IsModificationAllowed(TreeModifications.PreserveImportantComments)) { // we have important comments before the EOF. Add the comment(s) to the program. foreach(var importantComment in m_importantComments) { program.Append(new ImportantComment(importantComment, this)); } m_importantComments.Clear(); } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_TopLevelNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); } } catch (EndOfFileException) { } 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.Error); } program.UpdateWith(CurrentPositionContext()); return program; }
/// <summary> /// Parse the source code using the given settings, getting back an abstract syntax tree Block node as the root /// representing the list of statements in the source code. /// </summary> /// <param name="settings">code settings to use to process the source code</param> /// <returns>root Block node representing the top-level statements</returns> public Block Parse(CodeSettings settings) { // initialize the scanner with our settings // make sure the RawTokens setting is OFF or we won't be able to create our AST InitializeScanner(settings); // make sure we initialize the global scope's strict mode to our flag, whether or not it // is true. This means if the setting is false, we will RESET the flag to false if we are // reusing the scope and a previous Parse call had code that set it to strict with a // program directive. GlobalScope.UseStrict = m_settings.StrictMode; // make sure the global scope knows about our known global names GlobalScope.SetAssumedGlobals(m_settings); // start of a new module m_newModule = true; Block scriptBlock; Block returnBlock; switch (m_settings.SourceMode) { case JavaScriptSourceMode.Program: // simply parse a block of statements returnBlock = scriptBlock = ParseStatements(); break; case JavaScriptSourceMode.Expression: // create a block, get the first token, add in the parse of a single expression, // and we'll go fron there. returnBlock = scriptBlock = new Block(CurrentPositionContext(), this); GetNextToken(); try { var expr = ParseExpression(); if (expr != null) { scriptBlock.Append(expr); scriptBlock.UpdateWith(expr.Context); } } catch (EndOfFileException) { Debug.WriteLine("EOF"); } break; case JavaScriptSourceMode.EventHandler: // we're going to create the global block, add in a function expression with a single // parameter named "event", and then we're going to parse the input as the body of that // function expression. We're going to resolve the global block, but only return the body // of the function. scriptBlock = new Block(null, this); var parameters = new AstNodeList(null, this); parameters.Append(new ParameterDeclaration(null, this) { Name = "event", RenameNotAllowed = true }); var funcExpression = new FunctionObject(null, this) { FunctionType = FunctionType.Expression, ParameterDeclarations = parameters }; scriptBlock.Append(funcExpression); returnBlock = ParseStatements(); funcExpression.Body = returnBlock; break; default: Debug.Fail("Unexpected source mode enumeration"); return null; } // resolve everything ResolutionVisitor.Apply(scriptBlock, GlobalScope, m_settings); if (scriptBlock != null && Settings.MinifyCode) { // this visitor doesn't just reorder scopes. It also combines the adjacent var variables, // unnests blocks, identifies prologue directives, and sets the strict mode on scopes. ReorderScopeVisitor.Apply(scriptBlock, this); // analyze the entire node tree (needed for hypercrunch) // root to leaf (top down) var analyzeVisitor = new AnalyzeNodeVisitor(this); scriptBlock.Accept(analyzeVisitor); // analyze the scope chain (also needed for hypercrunch) // root to leaf (top down) m_globalScope.AnalyzeScope(); // if we want to crunch any names.... if (m_settings.LocalRenaming != LocalRenaming.KeepAll && m_settings.IsModificationAllowed(TreeModifications.LocalRenaming)) { // then do a top-down traversal of the scope tree. For each field that had not // already been crunched (globals and outers will already be crunched), crunch // the name with a crunch iterator that does not use any names in the verboten set. m_globalScope.AutoRenameFields(); } // if we want to evaluate literal expressions, do so now if (m_settings.EvalLiteralExpressions) { var visitor = new EvaluateLiteralVisitor(this); scriptBlock.Accept(visitor); } // if any of the conditions we check for in the final pass are available, then // make the final pass if (m_settings.IsModificationAllowed(TreeModifications.BooleanLiteralsToNotOperators)) { var visitor = new FinalPassVisitor(this); scriptBlock.Accept(visitor); } // we want to walk all the scopes to make sure that any generated // variables that haven't been crunched have been assigned valid // variable names that don't collide with any existing variables. m_globalScope.ValidateGeneratedNames(); } if (returnBlock.Parent != null) { returnBlock.Parent = null; } return returnBlock; }