//--------------------------------------------------------------------------------------- // ParseStatements // // statements : // <empty> | // statement statements // //--------------------------------------------------------------------------------------- private JsBlock ParseStatements() { var program = new JsBlock(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) { JsAstNode 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 JsConstantWrapper; if (constantWrapper != null && constantWrapper.PrimitiveType == JsPrimitiveType.String) { if (!(constantWrapper is JsDirectivePrologue)) { // use a directive prologue node instead ast = new JsDirectivePrologue(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; } } AppendImportantComments(program); } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_TopLevelNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); } } catch (EndOfStreamException) { } 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 JsBlock Parse(JsSettings 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; var timePoints = m_timingPoints = new long[9]; var timeIndex = timePoints.Length; var stopWatch = new Stopwatch(); stopWatch.Start(); JsBlock scriptBlock = null; JsBlock returnBlock = null; try { switch (m_settings.SourceMode) { case JsSourceMode.Program: // simply parse a block of statements returnBlock = scriptBlock = ParseStatements(); break; case JsSourceMode.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 JsBlock(CurrentPositionContext(), this); GetNextToken(); try { var expr = ParseExpression(); if (expr != null) { scriptBlock.Append(expr); scriptBlock.UpdateWith(expr.Context); } } catch (EndOfStreamException) { Debug.WriteLine("EOF"); } break; case JsSourceMode.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 JsBlock(null, this); var parameters = new JsAstNodeList(null, this); parameters.Append(new JsParameterDeclaration(null, this) { Name = "event", RenameNotAllowed = true }); var funcExpression = new JsFunctionObject(null, this) { FunctionType = JsFunctionType.Expression, ParameterDeclarations = parameters }; scriptBlock.Append(funcExpression); funcExpression.Body = returnBlock = ParseStatements(); break; default: Debug.Fail("Unexpected source mode enumeration"); return null; } } catch (RecoveryTokenException) { // this should never happen but let's make SURE we don't expose our // private exception object to the outside world m_currentToken.HandleError(JsError.ApplicationError, true); } timePoints[--timeIndex] = stopWatch.ElapsedTicks; if (scriptBlock != null) { // resolve everything JsResolutionVisitor.Apply(scriptBlock, GlobalScope, m_settings); } timePoints[--timeIndex] = stopWatch.ElapsedTicks; if (scriptBlock != null && Settings.MinifyCode && !Settings.PreprocessOnly) { // 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. JsReorderScopeVisitor.Apply(scriptBlock, this); timePoints[--timeIndex] = stopWatch.ElapsedTicks; // analyze the entire node tree (needed for hypercrunch) // root to leaf (top down) var analyzeVisitor = new JsAnalyzeNodeVisitor(this); scriptBlock.Accept(analyzeVisitor); timePoints[--timeIndex] = stopWatch.ElapsedTicks; // analyze the scope chain (also needed for hypercrunch) // root to leaf (top down) GlobalScope.AnalyzeScope(); timePoints[--timeIndex] = stopWatch.ElapsedTicks; // if we want to crunch any names.... if (m_settings.LocalRenaming != JsLocalRenaming.KeepAll && m_settings.IsModificationAllowed(JsTreeModifications.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. GlobalScope.AutoRenameFields(); } timePoints[--timeIndex] = stopWatch.ElapsedTicks; // if we want to evaluate literal expressions, do so now if (m_settings.EvalLiteralExpressions) { var visitor = new JsEvaluateLiteralVisitor(this); scriptBlock.Accept(visitor); } timePoints[--timeIndex] = stopWatch.ElapsedTicks; // make the final cleanup pass JsFinalPassVisitor.Apply(scriptBlock, this); timePoints[--timeIndex] = stopWatch.ElapsedTicks; // 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. GlobalScope.ValidateGeneratedNames(); timePoints[--timeIndex] = stopWatch.ElapsedTicks; } if (returnBlock != null && returnBlock.Parent != null) { returnBlock.Parent = null; } return returnBlock; }