/// <summary> /// Creates an instance of the JSParser class that can be used to parse the given source code. /// </summary> /// <param name="source">Source code to parse.</param> public JsParser(string source) { m_severity = 5; m_blockType = new List<BlockType>(16); m_labelTable = new Dictionary<string, LabelInfo>(); m_noSkipTokenSet = new NoSkipTokenSet(); m_importantComments = new List<JsContext>(); m_document = new JsDocumentContext(this, source); m_scanner = new JsScanner(new JsContext(m_document)); m_currentToken = new JsContext(m_document); // if the scanner encounters a special "globals" comment, it'll fire this event // at which point we will define a field with that name in the global scope. m_scanner.GlobalDefine += (sender, ea) => { var globalScope = GlobalScope; if (globalScope[ea.Name] == null) { var field = globalScope.CreateField(ea.Name, null, FieldAttributes.SpecialName); globalScope.AddField(field); } }; // this event is fired whenever a ///#SOURCE comment is encountered m_scanner.NewModule += (sender, ea) => { m_newModule = true; // we also want to assume that we found a newline character after // the comment m_foundEndOfLine = true; }; }
public JsDirectivePrologue(string value, JsContext context, JsParser parser) : base(value, JsPrimitiveType.String, context, parser) { // this is a "use strict" directive if the source context is EXACTLY "use strict" // don't consider the quotes so it can be " or ' delimiters UseStrict = string.CompareOrdinal(Context.Code, 1, "use strict", 0, 10) == 0; }
internal JsException(JsError errorNumber, JsContext context) { Value = null; m_context = (context == null ? null : context.Clone()); FileContext = (context == null ? null : context.Document.FileContext); ErrorCode = errorNumber; CanRecover = true; }
public JsConstantWrapper(Object value, JsPrimitiveType primitiveType, JsContext context, JsParser parser) : base(context, parser) { PrimitiveType = primitiveType; // force numerics to be of type double Value = (primitiveType == JsPrimitiveType.Number ? System.Convert.ToDouble(value, CultureInfo.InvariantCulture) : value); }
private JsContext m_context; // = null; #endregion Fields #region Constructors public JsBlockScope(JsActivationObject parent, JsContext context, JsSettings settings) : base(parent, settings) { if (context == null) { throw new ArgumentNullException("context"); } m_context = context.Clone(); }
public void OutputNumber(double numericValue, JsContext originalContext) { // numerics are doubles in JavaScript, so force it now as a shortcut if (double.IsNaN(numericValue) || double.IsInfinity(numericValue)) { // weird number -- just return the original source code as-is. if (originalContext != null && !string.IsNullOrEmpty(originalContext.Code) && !originalContext.Document.IsGenerated) { m_writer.Write(originalContext.Code); return; } // Hmmm... don't have an original source. // Must be generated. Just generate the proper JS literal. // // DANGER! If we just output NaN and Infinity and -Infinity blindly, that assumes // that there aren't any local variables in this scope chain with that // name, and we're pulling the GLOBAL properties. Might want to use properties // on the Number object -- which, of course, assumes that Number doesn't // resolve to a local variable... string objectName = double.IsNaN(numericValue) ? "NaN" : "Infinity"; // we're good to go -- just return the name because it will resolve to the // global properties (make a special case for negative infinity) m_writer.Write(double.IsNegativeInfinity(numericValue) ? "-Infinity" : objectName); } else if (numericValue == 0) { // special case zero because we don't need to go through all those // gyrations to get a "0" -- and because negative zero is different // than a positive zero m_writer.Write(1 / numericValue < 0 ? "-0" : "0"); } else { // normal string representations m_writer.Write(GetSmallestRep(numericValue.ToString("R", CultureInfo.InvariantCulture))); } }
internal JsCatchScope(JsActivationObject parent, JsContext catchContext, JsSettings settings, JsParameterDeclaration catchParameter) : base(parent, catchContext, settings) { CatchParameter = catchParameter; }
public JsObjectLiteralField(Object value, JsPrimitiveType primitiveType, JsContext context, JsParser parser) : base(value, primitiveType, context, parser) { }
public JsLabeledStatement(JsContext context, JsParser parser) : base(context, parser) { }
public JsCustomNode(JsContext context, JsParser parser) : base(context, parser) { }
public JsSwitchCase(JsContext context, JsParser parser) : base(context, parser) { }
public JsConstStatement(JsContext context, JsParser parser) : base(context, parser) { }
protected JsExpression(JsContext context, JsParser parser) : base(context, parser) { }
public JsVariableDeclaration(JsContext context, JsParser parser) : base(context, parser) { }
private JsFunctionObject ParseFunction(JsFunctionType functionType, JsContext fncCtx) { JsLookup name = null; JsAstNodeList formalParameters = null; JsBlock body = null; bool inExpression = (functionType == JsFunctionType.Expression); JsContext paramsContext = null; GetNextToken(); // get the function name or make an anonymous function if in expression "position" if (JsToken.Identifier == m_currentToken.Token) { name = new JsLookup(m_currentToken.Clone(), this) { Name = m_scanner.Identifier }; GetNextToken(); } else { string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { name = new JsLookup(m_currentToken.Clone(), this) { Name = identifier }; GetNextToken(); } else { if (!inExpression) { // if this isn't a function expression, then we need to throw an error because // function DECLARATIONS always need a valid identifier name ReportError(JsError.NoIdentifier, m_currentToken.Clone(), true); // BUT if the current token is a left paren, we don't want to use it as the name. // (fix for issue #14152) if (m_currentToken.Token != JsToken.LeftParenthesis && m_currentToken.Token != JsToken.LeftCurly) { identifier = m_currentToken.Code; name = new JsLookup(CurrentPositionContext(), this) { Name = identifier }; GetNextToken(); } } } } // make a new state and save the old one List<BlockType> blockType = m_blockType; m_blockType = new List<BlockType>(16); Dictionary<string, LabelInfo> labelTable = m_labelTable; m_labelTable = new Dictionary<string, LabelInfo>(); try { // get the formal parameters if (JsToken.LeftParenthesis != m_currentToken.Token) { // we expect a left paren at this point for standard cross-browser support. // BUT -- some versions of IE allow an object property expression to be a function name, like window.onclick. // we still want to throw the error, because it syntax errors on most browsers, but we still want to // be able to parse it and return the intended results. // Skip to the open paren and use whatever is in-between as the function name. Doesn't matter that it's // an invalid identifier; it won't be accessible as a valid field anyway. bool expandedIndentifier = false; while (m_currentToken.Token != JsToken.LeftParenthesis && m_currentToken.Token != JsToken.LeftCurly && m_currentToken.Token != JsToken.Semicolon && m_currentToken.Token != JsToken.EndOfFile) { name.Context.UpdateWith(m_currentToken); GetNextToken(); expandedIndentifier = true; } // if we actually expanded the identifier context, then we want to report that // the function name needs to be an identifier. Otherwise we didn't expand the // name, so just report that we expected an open paren at this point. if (expandedIndentifier) { name.Name = name.Context.Code; name.Context.HandleError(JsError.FunctionNameMustBeIdentifier, false); } else { ReportError(JsError.NoLeftParenthesis, true); } } if (m_currentToken.Token == JsToken.LeftParenthesis) { // create the parameter list formalParameters = new JsAstNodeList(m_currentToken.Clone(), this); paramsContext = m_currentToken.Clone(); // skip the open paren GetNextToken(); // create the list of arguments and update the context while (JsToken.RightParenthesis != m_currentToken.Token) { String id = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet); try { JsParameterDeclaration paramDecl = null; if (JsToken.Identifier != m_currentToken.Token && (id = JsKeyword.CanBeIdentifier(m_currentToken.Token)) == null) { if (JsToken.LeftCurly == m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); break; } else if (JsToken.Comma == m_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 = m_scanner.Identifier; } paramDecl = new JsParameterDeclaration(m_currentToken.Clone(), this) { Name = id, Position = formalParameters.Count }; formalParameters.Append(paramDecl); GetNextToken(); } // got an arg, it should be either a ',' or ')' if (JsToken.RightParenthesis == m_currentToken.Token) { break; } else if (JsToken.Comma == m_currentToken.Token) { // append the comma context as the terminator for the parameter paramDecl.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone()); } else { // deal with error in some "intelligent" way if (JsToken.LeftCurly == m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); break; } else { if (JsToken.Identifier == m_currentToken.Token) { // 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; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet); } } fncCtx.UpdateWith(m_currentToken); GetNextToken(); } // read the function body of non-abstract functions. if (JsToken.LeftCurly != m_currentToken.Token) ReportError(JsError.NoLeftCurly, true); m_blockType.Add(BlockType.Block); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); try { // parse the block locally to get the exact end of function body = new JsBlock(m_currentToken.Clone(), this); body.BraceOnNewLine = m_foundEndOfLine; GetNextToken(); var possibleDirectivePrologue = true; while (JsToken.RightCurly != m_currentToken.Token) { try { // function body's are SourceElements (Statements + FunctionDeclarations) var statement = ParseStatement(true); if (possibleDirectivePrologue) { var constantWrapper = statement as JsConstantWrapper; if (constantWrapper != null && constantWrapper.PrimitiveType == JsPrimitiveType.String) { // if it's already a directive prologues, we're good to go if (!(constantWrapper is JsDirectivePrologue)) { // make the statement a directive prologue instead of a constant wrapper statement = new JsDirectivePrologue(constantWrapper.Value.ToString(), constantWrapper.Context, constantWrapper.Parser) { MayHaveIssues = constantWrapper.MayHaveIssues }; } } else if (!m_newModule) { // no longer considering constant wrappers possibleDirectivePrologue = false; } } else if (m_newModule) { // we scanned into a new module -- we might find directive prologues again possibleDirectivePrologue = true; } // add it to the body body.Append(statement); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) { body.Append(exc._partiallyComputedNode); } if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1) throw; } } // make sure any important comments before the closing brace are kept AppendImportantComments(body); body.Context.UpdateWith(m_currentToken); fncCtx.UpdateWith(m_currentToken); } catch (EndOfStreamException) { // if we get an EOF here, we never had a chance to find the closing curly-brace fncCtx.HandleError(JsError.UnclosedFunction, true); } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new JsFunctionObject(fncCtx, this) { FunctionType = (inExpression ? JsFunctionType.Expression : JsFunctionType.Declaration), IdContext = name.IfNotNull(n => n.Context), Name = name.IfNotNull(n => n.Name), ParameterDeclarations = formalParameters, ParametersContext = paramsContext, Body = body }; throw; } } finally { m_blockType.RemoveAt(m_blockType.Count - 1); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet); } GetNextToken(); } finally { // restore state m_blockType = blockType; m_labelTable = labelTable; } return new JsFunctionObject(fncCtx, this) { FunctionType = functionType, IdContext = name.IfNotNull(n => n.Context), Name = name.IfNotNull(n => n.Name), ParameterDeclarations = formalParameters, ParametersContext = paramsContext, Body = body }; }
public JsVar(JsContext context, JsParser parser) : base(context, parser) { }
public JsTryNode(JsContext context, JsParser parser) : base(context, parser) { }
public JsUnaryOperator(JsContext context, JsParser parser) : base(context, parser) { }
public JsLookup(JsContext context, JsParser parser) : base(context, parser) { RefType = JsReferenceType.Variable; }
public JsCommaOperator(JsContext context, JsParser parser) : base(context, parser) { this.OperatorToken = JsToken.Comma; }
private void CCTooComplicated(JsContext context) { // we ONLY support /*@id@*/ or /*@cc_on@id@*/ or /*@!@*/ or /*@cc_on!@*/ in expressions right now. // throw an error, skip to the end of the comment, then ignore it and start // looking for the next token. (context ?? m_currentToken).HandleError(JsError.ConditionalCompilationTooComplex); // skip to end of conditional comment while (m_currentToken.Token != JsToken.EndOfFile && m_currentToken.Token != JsToken.ConditionalCommentEnd) { GetNextToken(); } GetNextToken(); }
//--------------------------------------------------------------------------------------- // 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, JsContext context, bool skipToken) { Debug.Assert(context != null); int previousSeverity = m_severity; m_severity = JsException.GetSeverity(errorId); // 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 (m_goodTokensProcessed > 0 || m_severity < previousSeverity) context.HandleError(errorId); // reset proper info if (skipToken) m_goodTokensProcessed = -1; else { m_useCurrentForNext = true; m_goodTokensProcessed = 0; } } }
protected JsIterationStatement(JsContext context, JsParser parser) : base(context, parser) { }
//--------------------------------------------------------------------------------------- // CreateExpressionNode // // Create the proper AST object according to operator //--------------------------------------------------------------------------------------- private JsAstNode CreateExpressionNode(JsContext op, JsAstNode operand1, JsAstNode operand2) { JsContext context = operand1.Context.CombineWith(operand2.Context); switch (op.Token) { case JsToken.Assign: case JsToken.BitwiseAnd: case JsToken.BitwiseAndAssign: case JsToken.BitwiseOr: case JsToken.BitwiseOrAssign: case JsToken.BitwiseXor: case JsToken.BitwiseXorAssign: case JsToken.Divide: case JsToken.DivideAssign: case JsToken.Equal: case JsToken.GreaterThan: case JsToken.GreaterThanEqual: case JsToken.In: case JsToken.InstanceOf: case JsToken.LeftShift: case JsToken.LeftShiftAssign: case JsToken.LessThan: case JsToken.LessThanEqual: case JsToken.LogicalAnd: case JsToken.LogicalOr: case JsToken.Minus: case JsToken.MinusAssign: case JsToken.Modulo: case JsToken.ModuloAssign: case JsToken.Multiply: case JsToken.MultiplyAssign: case JsToken.NotEqual: case JsToken.Plus: case JsToken.PlusAssign: case JsToken.RightShift: case JsToken.RightShiftAssign: case JsToken.StrictEqual: case JsToken.StrictNotEqual: case JsToken.UnsignedRightShift: case JsToken.UnsignedRightShiftAssign: // regular binary operator return new JsBinaryOperator(context, this) { Operand1 = operand1, Operand2 = operand2, OperatorContext = op, OperatorToken = op.Token }; case JsToken.Comma: // use the special comma-operator class derived from binary operator. // it has special logic to combine adjacent comma operators into a single // node with an ast node list rather than nested binary operators return JsCommaOperator.CombineWithComma(context, this, operand1, operand2); default: // shouldn't get here! Debug.Assert(false); return null; } }
public JsMember(JsContext context, JsParser parser) : base(context, parser) { }
protected JsDeclaration(JsContext context, JsParser parser) : base(context, parser) { m_list = new List <JsVariableDeclaration>(); }
public JsWhileNode(JsContext context, JsParser parser) : base(context, parser) { }
//--------------------------------------------------------------------------------------- // 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 (m_useCurrentForNext) { // we just want to keep using the current token. // but don't get into an infinite loop -- after a while, // give up and grab the next token from the scanner anyway m_useCurrentForNext = false; if (m_breakRecursion++ > 10) { m_currentToken = ScanNextToken(); } } else { m_goodTokensProcessed++; m_breakRecursion = 0; // the scanner reuses the same context object for performance, // so if we ever mean to hold onto it later, we need to clone it. m_currentToken = ScanNextToken(); } }
public JsForIn(JsContext context, JsParser parser) : base(context, parser) { }
public JsGroupingOperator(JsContext context, JsParser parser) : base(context, parser) { }
public JsWithScope(JsActivationObject parent, JsContext context, JsSettings settings) : base(parent, context, settings) { IsInWithScope = true; }
public JsConditionalCompilationIf(JsContext context, JsParser parser) : base(context, parser) { }
public void MarkSegment(JsAstNode node, int startLine, int startColumn, string name, JsContext context) { if (startLine == int.MaxValue) { throw new ArgumentOutOfRangeException("startLine"); } // add the offsets startLine += m_lineOffset; startColumn += m_columnOffset; // if we have a name, try adding it to the hash set of names. If it already exists, the call to Add // will return false. If it doesn't already exist, Add will return true and we'll append it to the list // of names. That way we have a nice list of names ordered by their first occurrence in the minified file. if (!string.IsNullOrEmpty(name) && m_names.Add(name)) { m_nameList.Add(name); } // if this is a newline, the startline will be bigger than the largest line we've had so far m_maxMinifiedLine = Math.Max(m_maxMinifiedLine, startLine); // save the file context in our list of files if (context != null && context.Document != null && context.Document.FileContext != null) { // if this is the first instance of this file... if (m_sourceFiles.Add(context.Document.FileContext)) { // ...add it to the list, so we end up with a list of unique files // sorted by their first occurence in the minified file. m_sourceFileList.Add(MakeRelative(context.Document.FileContext, m_mapPath)); } } // create the segment object and add it to the list. // the destination line/col numbers are zero-based. The format expects line to be 1-based and col 0-based. // the context line is one-based; col is zero-based. The format expects both to be zero-based. var segment = CreateSegment( startLine + 1, startColumn, context == null || context.StartLineNumber < 1 ? -1 : context.StartLineNumber - 1, context == null || context.StartColumn < 0 ? -1 : context.StartColumn, context.IfNotNull(c => MakeRelative(c.Document.FileContext, m_mapPath)), name); m_segments.Add(segment); }
public JsBlock(JsContext context, JsParser parser) : base(context, parser) { m_list = new List<JsAstNode>(); }
internal UndefinedReferenceException(JsLookup lookup, JsContext context) { m_lookup = lookup; m_name = lookup.Name; m_type = lookup.RefType; m_context = context; }
public static JsAstNode CombineWithComma(JsContext context, JsParser parser, JsAstNode operand1, JsAstNode operand2) { var comma = new JsCommaOperator(context, parser); // if the left is a comma-operator already.... var leftBinary = operand1 as JsBinaryOperator; var rightBinary = operand2 as JsBinaryOperator; if (leftBinary != null && leftBinary.OperatorToken == JsToken.Comma) { // the left-hand side is already a comma operator. Instead of nesting these, we're // going to combine them // move the old list's left-hand side to our left-hand side comma.Operand1 = leftBinary.Operand1; JsAstNodeList list; if (rightBinary != null && rightBinary.OperatorToken == JsToken.Comma) { // the right is ALSO a comma operator. Create a new list, append all the rest of the operands // and set our right-hand side to be the list list = new JsAstNodeList(null, parser); list.Append(leftBinary.Operand2).Append(rightBinary.Operand1).Append(rightBinary.Operand2); } else { // the right is not a comma operator. // see if the left-hand side already has a list we can use list = leftBinary.Operand2 as JsAstNodeList; if (list == null) { // it's not a list already // create a new list with the left's right and our right and set it to our right list = new JsAstNodeList(null, parser); list.Append(leftBinary.Operand2); } // and add our right-hand operand to the end of the list list.Append(operand2); } // set the list on the right comma.Operand2 = list; } else if (rightBinary != null && rightBinary.OperatorToken == JsToken.Comma) { // the left hand side is NOT a comma operator. comma.Operand1 = operand1; // the right-hand side is already a comma-operator, but the left is not. // see if it already has a list we can reuse var rightList = rightBinary.Operand2 as JsAstNodeList; if (rightList != null) { // it does. Prepend its right-hand operand and use the list rightList.Insert(0, rightBinary.Operand1); } else { // it's not -- create a new list containing the operands rightList = new JsAstNodeList(rightBinary.Context, parser); rightList.Append(rightBinary.Operand1); rightList.Append(rightBinary.Operand2); } comma.Operand2 = rightList; } else { comma.Operand1 = operand1; comma.Operand2 = operand2; } return(comma); }