internal UndefinedReferenceException(JsLookup lookup, JsContext context) { m_lookup = lookup; m_name = lookup.Name; m_type = lookup.RefType; m_context = context; }
public void Visit(JsLookup node) { if (node != null) { node.Index = NextOrderIndex; // add the lookup node to the current lexical scope, because // that's the starting point for this node's lookup resolution. CurrentLexicalScope.ScopeLookups.Add(node); } }
public override void Visit(JsLookup node) { // only lookups need to be detached. if (node != null) { // if the node's vairable field is set, remove it from the // field's list of references. var variableField = node.VariableField; if (variableField != null) { variableField.References.Remove(node); } } }
public void Visit(JsLookup node) { // we are only a match if we are looking for the first part if (node != null && m_index == 0) { // see if the name matches; and if there is a field, it should be a global if (string.CompareOrdinal(node.Name, m_parts[0]) == 0 && (node.VariableField == null || node.VariableField.FieldType == JsFieldType.UndefinedGlobal || node.VariableField.FieldType == JsFieldType.Global)) { // match! m_isMatch = true; } } }
public void MarkSegment(JsAstNode node, int startLine, int startColumn, string name, JsContext context) { if (node == null || string.IsNullOrEmpty(name)) { return; } // see if this is within a function object node, // AND if this segment has the same name as the function name // AND this context isn't the same as the entire function context. // this should only be true for the function NAME segment. var functionObject = node as JsFunctionObject; if (functionObject != null && string.CompareOrdinal(name, functionObject.Name) == 0 && context != functionObject.Context) { // adjust the offsets startLine += m_lineOffset; startColumn += m_columnOffset; // it does -- so this is the segment that corresponds to the function object's name, which // for this format we want to output a separate segment for. It used to be its own Lookup // node child of the function object, so we need to create a fake node here, start a new // symbol from it, end the symbol, then write it. var fakeLookup = new JsLookup(context, functionObject.Parser) { Name = name }; var nameSymbol = JavaScriptSymbol.StartNew(fakeLookup, startLine, startColumn, GetSourceFileIndex(functionObject.Context.Document.FileContext)); // the name will never end on a different line -- it's a single unbreakable token. The length is just // the length of the name, so add that number to the column start. And the parent context is the function // name (again) nameSymbol.End(startLine, startColumn + name.Length, name); nameSymbol.WriteTo(m_writer); } }
public void Visit(JsLookup node) { // invalid! ignore IsValid = false; }
private static int RelocateVar(JsBlock block, int insertAt, JsVar varStatement) { // if the var statement is at the next position to insert, then we don't need // to do anything. if (block[insertAt] != varStatement) { // check to see if the current position is a var and we are the NEXT statement. // if that's the case, we don't need to break out the initializer, just append all the // vardecls as-is to the current position. var existingVar = block[insertAt] as JsVar; if (existingVar != null && block[insertAt + 1] == varStatement) { // just append our vardecls to the insertion point, then delete our statement existingVar.Append(varStatement); block.RemoveAt(insertAt + 1); } else { // iterate through the decls and count how many have initializers var initializerCount = 0; for (var ndx = 0; ndx < varStatement.Count; ++ndx) { if (varStatement[ndx].Initializer != null) { ++initializerCount; } } // if there are more than two decls with initializers, then we won't actually // be gaining anything by moving the var to the top. We'll get rid of the four // bytes for the "var ", but we'll be adding two bytes for the name and comma // because name=init will still need to remain behind. if (initializerCount <= 2) { // first iterate through all the declarations in the var statement, // constructing an expression statement that is made up of assignment // operators for each of the declarations that have initializers (if any) // and removing all the initializers var assignments = new List<JsAstNode>(); for (var ndx = 0; ndx < varStatement.Count; ++ndx) { var varDecl = varStatement[ndx]; if (varDecl.Initializer != null) { if (varDecl.IsCCSpecialCase) { // create a vardecl with the same name and no initializer var copyDecl = new JsVariableDeclaration(varDecl.Context, varDecl.Parser) { Identifier = varDecl.Identifier, NameContext = varDecl.VariableField.OriginalContext, VariableField = varDecl.VariableField }; // replace the special vardecl with the copy varStatement[ndx] = copyDecl; // add the original vardecl to the list of "assignments" assignments.Add(varDecl); // add the new decl to the field's declaration list, and remove the old one // because we're going to change that to an assignment. varDecl.VariableField.Declarations.Add(copyDecl); varDecl.VariableField.Declarations.Remove(varDecl); } else { // hold on to the object so we don't lose it to the GC var initializer = varDecl.Initializer; // remove it from the vardecl varDecl.Initializer = null; // create an assignment operator for a lookup to the name // as the left, and the initializer as the right, and add it to the list var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varDecl.Parser) { Name = varDecl.Identifier, VariableField = varDecl.VariableField, }; assignments.Add(new JsBinaryOperator(varDecl.Context, varDecl.Parser) { Operand1 = lookup, Operand2 = initializer, OperatorToken = JsToken.Assign, OperatorContext = varDecl.AssignContext }); // add the new lookup to the field's references varDecl.VariableField.References.Add(lookup); } } } // now if there were any initializers... if (assignments.Count > 0) { // we want to create one big expression from all the assignments and replace the // var statement with the assignment(s) expression. Start at position n=1 and create // a binary operator of n-1 as the left, n as the right, and using a comma operator. var expression = assignments[0]; for (var ndx = 1; ndx < assignments.Count; ++ndx) { expression = JsCommaOperator.CombineWithComma(null, expression.Parser, expression, assignments[ndx]); } // replace the var with the expression. // we still have a pointer to the var, so we can insert it back into the proper // place next. varStatement.Parent.ReplaceChild(varStatement, expression); } else { // no initializers. // if the parent is a for-in statement... var forInParent = varStatement.Parent as JsForIn; if (forInParent != null) { // we want to replace the var statement with a lookup for the var // there should be only one vardecl var varDecl = varStatement[0]; var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varStatement.Parser) { Name = varDecl.Identifier, VariableField = varDecl.VariableField }; varStatement.Parent.ReplaceChild(varStatement, lookup); varDecl.VariableField.References.Add(lookup); } else { // just remove the var statement altogether varStatement.Parent.ReplaceChild(varStatement, null); } } // if the statement at the insertion point is a var-statement already, // then we just need to append our vardecls to it. Otherwise we'll insert our // var statement at the right point if (existingVar != null) { // append the varstatement we want to move to the existing var, which will // transfer all the vardecls to it. existingVar.Append(varStatement); } else { // move the var to the insert point, incrementing the position or next time block.Insert(insertAt, varStatement); } } } } return insertAt; }
private static void ResolveLookup(JsActivationObject scope, JsLookup lookup, JsSettings settings) { // resolve lookup via the lexical scope lookup.VariableField = scope.FindReference(lookup.Name); if (lookup.VariableField.FieldType == JsFieldType.UndefinedGlobal) { // couldn't find it. // if the lookup isn't generated and isn't the object of a typeof operator, // then we want to throw an error. if (!lookup.IsGenerated) { var parentUnaryOp = lookup.Parent as JsUnaryOperator; if (parentUnaryOp != null && parentUnaryOp.OperatorToken == JsToken.TypeOf) { // this undefined lookup is the target of a typeof operator. // I think it's safe to assume we're going to use it. Don't throw an error // and instead add it to the "known" expected globals of the global scope MakeExpectedGlobal(lookup.VariableField); } else { // report this undefined reference lookup.Context.ReportUndefined(lookup); // possibly undefined global (but definitely not local). // see if this is a function or a variable. var callNode = lookup.Parent as JsCallNode; var isFunction = callNode != null && callNode.Function == lookup; lookup.Context.HandleError((isFunction ? JsError.UndeclaredFunction : JsError.UndeclaredVariable), false); } } } else if (lookup.VariableField.FieldType == JsFieldType.Predefined) { if (string.CompareOrdinal(lookup.Name, "window") == 0) { // it's the global window object // see if it's the child of a member or call-brackets node var member = lookup.Parent as JsMember; if (member != null) { // we have window.XXXX. Add XXXX to the known globals if it // isn't already a known item. scope.AddGlobal(member.Name); } else { var callNode = lookup.Parent as JsCallNode; if (callNode != null && callNode.InBrackets && callNode.Arguments.Count == 1 && callNode.Arguments[0] is JsConstantWrapper && callNode.Arguments[0].FindPrimitiveType() == JsPrimitiveType.String) { // we have window["XXXX"]. See if XXXX is a valid identifier. // TODO: we could get rid of the ConstantWrapper restriction and use an Evaluate visitor // to evaluate the argument, since we know for sure that it's a string. var identifier = callNode.Arguments[0].ToString(); if (JsScanner.IsValidIdentifier(identifier)) { // Add XXXX to the known globals if it isn't already a known item. scope.AddGlobal(identifier); } } } } else if (settings.EvalTreatment != JsEvalTreatment.Ignore && string.CompareOrdinal(lookup.Name, "eval") == 0) { // it's an eval -- but are we calling it? // TODO: what if we are assigning it to a variable? Should we track that variable and see if we call it? // What about passing it as a parameter to a function? track that as well in case the function calls it? var parentCall = lookup.Parent as JsCallNode; if (parentCall != null && parentCall.Function == lookup) { scope.IsKnownAtCompileTime = false; } } } // add the reference lookup.VariableField.AddReference(lookup); // we are actually referencing this field, so it's no longer a placeholder field if it // happens to have been one. lookup.VariableField.IsPlaceholder = false; }
public void Visit(JsLookup node) { // we're good }
public void Visit(JsLookup node) { if (node != null) { // all identifier should be treated as if they start with a valid // identifier character. That might not always be the case, like when // we consider an ASP.NET block to output the start of an identifier. // so let's FORCE the insert-space logic here. if (JsScanner.IsValidIdentifierPart(m_lastCharacter)) { OutputSpaceOrLineBreak(); } var symbol = StartSymbol(node); Output(node.VariableField != null ? node.VariableField.ToString() : node.Name); MarkSegment(node, node.Name, node.Context); SetContextOutputPosition(node.Context); m_startOfStatement = false; EndSymbol(symbol); } }
internal void ReportUndefined(JsLookup lookup) { UndefinedReferenceException ex = new UndefinedReferenceException(lookup, this); Document.ReportUndefined(ex); }
public void Visit(JsLookup node) { // lookup identifier, so we don't care }
private JsAstNode ParseLeftHandSideExpression(bool isMinus) { JsAstNode ast = null; bool skipToken = true; List<JsContext> newContexts = null; TryItAgain: // new expression while (JsToken.New == m_currentToken.Token) { if (null == newContexts) newContexts = new List<JsContext>(4); newContexts.Add(m_currentToken.Clone()); GetNextToken(); } JsToken token = m_currentToken.Token; switch (token) { // primary expression case JsToken.Identifier: ast = new JsLookup(m_currentToken.Clone(), this) { Name = m_scanner.Identifier }; break; case JsToken.ConditionalCommentStart: // skip past the start to the next token GetNextToken(); if (m_currentToken.Token == JsToken.ConditionalCompilationVariable) { // we have /*@id ast = new JsConstantWrapperPP(m_currentToken.Clone(), this) { VarName = m_currentToken.Code, ForceComments = true }; GetNextToken(); if (m_currentToken.Token == JsToken.ConditionalCommentEnd) { // skip past the closing comment GetNextToken(); } else { // we ONLY support /*@id@*/ in expressions right now. If there's not // a closing comment after the ID, then we don't support it. // throw an error, skip to the end of the comment, then ignore it and start // looking for the next token. CCTooComplicated(null); goto TryItAgain; } } else if (m_currentToken.Token == JsToken.ConditionalCommentEnd) { // empty conditional comment! Ignore. GetNextToken(); goto TryItAgain; } else { // we DON'T have "/*@IDENT". We only support "/*@IDENT @*/", so since this isn't // and id, throw the error, skip to the end of the comment, and ignore it // by looping back and looking for the NEXT token. m_currentToken.HandleError(JsError.ConditionalCompilationTooComplex); // skip to end of conditional comment while (m_currentToken.Token != JsToken.EndOfFile && m_currentToken.Token != JsToken.ConditionalCommentEnd) { GetNextToken(); } GetNextToken(); goto TryItAgain; } break; case JsToken.This: ast = new JsThisLiteral(m_currentToken.Clone(), this); break; case JsToken.StringLiteral: ast = new JsConstantWrapper(m_scanner.StringLiteralValue, JsPrimitiveType.String, m_currentToken.Clone(), this) { MayHaveIssues = m_scanner.LiteralHasIssues }; break; case JsToken.IntegerLiteral: case JsToken.NumericLiteral: { JsContext numericContext = m_currentToken.Clone(); double doubleValue; if (ConvertNumericLiteralToDouble(m_currentToken.Code, (token == JsToken.IntegerLiteral), out doubleValue)) { // conversion worked fine // check for some boundary conditions var mayHaveIssues = m_scanner.LiteralHasIssues; if (doubleValue == double.MaxValue) { ReportError(JsError.NumericMaximum, numericContext, true); } else if (isMinus && -doubleValue == double.MinValue) { ReportError(JsError.NumericMinimum, numericContext, true); } // create the constant wrapper from the value ast = new JsConstantWrapper(doubleValue, JsPrimitiveType.Number, numericContext, this) { MayHaveIssues = mayHaveIssues }; } else { // if we went overflow or are not a number, then we will use the "Other" // primitive type so we don't try doing any numeric calcs with it. if (double.IsInfinity(doubleValue)) { // overflow // and if we ARE an overflow, report it ReportError(JsError.NumericOverflow, numericContext, true); } // regardless, we're going to create a special constant wrapper // that simply echos the input as-is ast = new JsConstantWrapper(m_currentToken.Code, JsPrimitiveType.Other, numericContext, this) { MayHaveIssues = true }; } break; } case JsToken.True: ast = new JsConstantWrapper(true, JsPrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JsToken.False: ast = new JsConstantWrapper(false, JsPrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JsToken.Null: ast = new JsConstantWrapper(null, JsPrimitiveType.Null, m_currentToken.Clone(), this); break; case JsToken.ConditionalCompilationVariable: ast = new JsConstantWrapperPP(m_currentToken.Clone(), this) { VarName = m_currentToken.Code, ForceComments = false }; break; case JsToken.DivideAssign: // normally this token is not allowed on the left-hand side of an expression. // BUT, this might be the start of a regular expression that begins with an equals sign! // we need to test to see if we can parse a regular expression, and if not, THEN // we can fail the parse. case JsToken.Divide: // could it be a regexp? ast = ScanRegularExpression(); if (ast != null) { // yup -- we're done here break; } // nope -- go to the default branch goto default; // expression case JsToken.LeftParenthesis: { var groupingOp = new JsGroupingOperator(m_currentToken.Clone(), this); ast = groupingOp; GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenExpressionNoSkipToken); try { // parse an expression groupingOp.Operand = ParseExpression(); if (JsToken.RightParenthesis != m_currentToken.Token) { ReportError(JsError.NoRightParenthesis); } else { // add the closing paren to the expression context ast.Context.UpdateWith(m_currentToken); } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_ParenExpressionNoSkipToken, exc) == -1) throw; else groupingOp.Operand = exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenExpressionNoSkipToken); } } break; // array initializer case JsToken.LeftBracket: JsContext listCtx = m_currentToken.Clone(); GetNextToken(); JsAstNodeList list = new JsAstNodeList(CurrentPositionContext(), this); var hasTrailingCommas = false; while (JsToken.RightBracket != m_currentToken.Token) { if (JsToken.Comma != m_currentToken.Token) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); try { var expression = ParseExpression(true); list.Append(expression); if (JsToken.Comma != m_currentToken.Token) { if (JsToken.RightBracket != m_currentToken.Token) { ReportError(JsError.NoRightBracket); } break; } else { // we have a comma -- skip it after adding it as a terminator // on the previous expression var commaContext = m_currentToken.Clone(); expression.IfNotNull(e => e.TerminatingContext = commaContext); GetNextToken(); // if the next token is the closing brackets, then we need to // add a missing value to the array because we end in a comma and // we need to keep it for cross-platform compat. // TECHNICALLY, that puts an extra item into the array for most modern browsers, but not ALL. if (m_currentToken.Token == JsToken.RightBracket) { hasTrailingCommas = true; list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this)); // throw a cross-browser warning about trailing commas commaContext.HandleError(JsError.ArrayLiteralTrailingComma); 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 JsArrayLiteral(listCtx, this) { Elements = list, MayHaveIssues = true }; throw; } else { if (JsToken.RightBracket == m_currentToken.Token) break; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); } } else { // comma -- missing array item in the list var commaContext = m_currentToken.Clone(); list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this) { TerminatingContext = commaContext }); // skip over the comma GetNextToken(); // if the next token is the closing brace, then we end with a comma -- and we need to // add ANOTHER missing value to make sure this last comma doesn't get left off. // TECHNICALLY, that puts an extra item into the array for most modern browsers, but not ALL. if (m_currentToken.Token == JsToken.RightBracket) { hasTrailingCommas = true; list.Append(new JsConstantWrapper(JsMissing.Value, JsPrimitiveType.Other, m_currentToken.Clone(), this)); // throw a cross-browser warning about trailing commas commaContext.HandleError(JsError.ArrayLiteralTrailingComma); break; } } } listCtx.UpdateWith(m_currentToken); ast = new JsArrayLiteral(listCtx, this) { Elements = list, MayHaveIssues = hasTrailingCommas }; break; // object initializer case JsToken.LeftCurly: JsContext objCtx = m_currentToken.Clone(); GetNextToken(); var propertyList = new JsAstNodeList(CurrentPositionContext(), this); if (JsToken.RightCurly != m_currentToken.Token) { for (; ; ) { JsObjectLiteralField field = null; JsAstNode value = null; bool getterSetter = false; string ident; switch (m_currentToken.Token) { case JsToken.Identifier: field = new JsObjectLiteralField(m_scanner.Identifier, JsPrimitiveType.String, m_currentToken.Clone(), this); break; case JsToken.StringLiteral: field = new JsObjectLiteralField(m_scanner.StringLiteralValue, JsPrimitiveType.String, m_currentToken.Clone(), this) { MayHaveIssues = m_scanner.LiteralHasIssues }; break; case JsToken.IntegerLiteral: case JsToken.NumericLiteral: { double doubleValue; if (ConvertNumericLiteralToDouble(m_currentToken.Code, (m_currentToken.Token == JsToken.IntegerLiteral), out doubleValue)) { // conversion worked fine field = new JsObjectLiteralField( doubleValue, JsPrimitiveType.Number, m_currentToken.Clone(), this ); } else { // something went wrong and we're not sure the string representation in the source is // going to convert to a numeric value well if (double.IsInfinity(doubleValue)) { ReportError(JsError.NumericOverflow, m_currentToken.Clone(), true); } // use the source as the field name, not the numeric value field = new JsObjectLiteralField( m_currentToken.Code, JsPrimitiveType.Other, m_currentToken.Clone(), this); } break; } case JsToken.Get: case JsToken.Set: if (PeekToken() == JsToken.Colon) { // the field is either "get" or "set" and isn't the special Mozilla getter/setter field = new JsObjectLiteralField(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this); } else { // ecma-script get/set property construct getterSetter = true; bool isGet = (m_currentToken.Token == JsToken.Get); value = ParseFunction( (JsToken.Get == m_currentToken.Token ? JsFunctionType.Getter : JsFunctionType.Setter), m_currentToken.Clone() ); JsFunctionObject funcExpr = value as JsFunctionObject; if (funcExpr != null) { // getter/setter is just the literal name with a get/set flag field = new JsGetterSetter( funcExpr.Name, isGet, funcExpr.IdContext.Clone(), this ); } else { ReportError(JsError.FunctionExpressionExpected); } } break; default: // NOT: identifier token, string, number, or getter/setter. // see if it's a token that COULD be an identifierName. ident = m_scanner.Identifier; if (JsScanner.IsValidIdentifier(ident)) { // BY THE SPEC, if it's a valid identifierName -- which includes reserved words -- then it's // okay for object literal syntax. However, reserved words here won't work in all browsers, // so if it is a reserved word, let's throw a low-sev cross-browser warning on the code. if (JsKeyword.CanBeIdentifier(m_currentToken.Token) == null) { ReportError(JsError.ObjectLiteralKeyword, m_currentToken.Clone(), true); } field = new JsObjectLiteralField(ident, JsPrimitiveType.String, m_currentToken.Clone(), this); } else { // throw an error but use it anyway, since that's what the developer has going on ReportError(JsError.NoMemberIdentifier, m_currentToken.Clone(), true); field = new JsObjectLiteralField(m_currentToken.Code, JsPrimitiveType.String, m_currentToken.Clone(), this); } break; } if (field != null) { if (!getterSetter) { GetNextToken(); } m_noSkipTokenSet.Add(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet); try { if (!getterSetter) { // get the value if (JsToken.Colon != m_currentToken.Token) { ReportError(JsError.NoColon, true); value = ParseExpression(true); } else { field.ColonContext = m_currentToken.Clone(); GetNextToken(); value = ParseExpression(true); } } // put the pair into the list of fields var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context)); var property = new JsObjectLiteralProperty(propCtx, this) { Name = field, Value = value }; propertyList.Append(property); if (JsToken.RightCurly == m_currentToken.Token) { break; } else { if (JsToken.Comma == m_currentToken.Token) { // skip the comma after adding it to the property as a terminating context property.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone()); GetNextToken(); // if the next token is the right-curly brace, then we ended // the list with a comma, which is perfectly fine if (m_currentToken.Token == JsToken.RightCurly) { break; } } else { if (m_foundEndOfLine) { 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; var propCtx = field.Context.Clone().CombineWith(value.IfNotNull(v => v.Context)); var property = new JsObjectLiteralProperty(propCtx, this) { Name = field, Value = value }; propertyList.Append(property); } if (IndexOfToken(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new JsObjectLiteral(objCtx, this) { Properties = propertyList }; throw; } else { if (JsToken.Comma == m_currentToken.Token) GetNextToken(); if (JsToken.RightCurly == m_currentToken.Token) break; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet); } } } } objCtx.UpdateWith(m_currentToken); ast = new JsObjectLiteral(objCtx, this) { Properties = propertyList }; break; // function expression case JsToken.Function: ast = ParseFunction(JsFunctionType.Expression, m_currentToken.Clone()); skipToken = false; break; case JsToken.AspNetBlock: ast = new JsAspNetBlockNode(m_currentToken.Clone(), this) { AspNetBlockText = m_currentToken.Code }; break; default: string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { ast = new JsLookup(m_currentToken.Clone(), this) { Name = identifier }; } else { ReportError(JsError.ExpressionExpected); SkipTokensAndThrow(); } break; } // can be a CallExpression, that is, followed by '.' or '(' or '[' if (skipToken) GetNextToken(); return MemberExpression(ast, newContexts); }
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 override void Visit(JsLookup node) { // same logic for most nodes TypicalHandler(node); }
private static int RelocateVar(JsBlock block, int insertAt, JsVar varStatement) { // if the var statement is at the next position to insert, then we don't need // to do anything. if (block[insertAt] != varStatement) { // check to see if the current position is a var and we are the NEXT statement. // if that's the case, we don't need to break out the initializer, just append all the // vardecls as-is to the current position. var existingVar = block[insertAt] as JsVar; if (existingVar != null && block[insertAt + 1] == varStatement) { // just append our vardecls to the insertion point, then delete our statement existingVar.Append(varStatement); block.RemoveAt(insertAt + 1); } else { // iterate through the decls and count how many have initializers var initializerCount = 0; for (var ndx = 0; ndx < varStatement.Count; ++ndx) { if (varStatement[ndx].Initializer != null) { ++initializerCount; } } // if there are more than two decls with initializers, then we won't actually // be gaining anything by moving the var to the top. We'll get rid of the four // bytes for the "var ", but we'll be adding two bytes for the name and comma // because name=init will still need to remain behind. if (initializerCount <= 2) { // first iterate through all the declarations in the var statement, // constructing an expression statement that is made up of assignment // operators for each of the declarations that have initializers (if any) // and removing all the initializers var assignments = new List <JsAstNode>(); for (var ndx = 0; ndx < varStatement.Count; ++ndx) { var varDecl = varStatement[ndx]; if (varDecl.Initializer != null) { if (varDecl.IsCCSpecialCase) { // create a vardecl with the same name and no initializer var copyDecl = new JsVariableDeclaration(varDecl.Context, varDecl.Parser) { Identifier = varDecl.Identifier, NameContext = varDecl.VariableField.OriginalContext, VariableField = varDecl.VariableField }; // replace the special vardecl with the copy varStatement[ndx] = copyDecl; // add the original vardecl to the list of "assignments" assignments.Add(varDecl); // add the new decl to the field's declaration list, and remove the old one // because we're going to change that to an assignment. varDecl.VariableField.Declarations.Add(copyDecl); varDecl.VariableField.Declarations.Remove(varDecl); } else { // hold on to the object so we don't lose it to the GC var initializer = varDecl.Initializer; // remove it from the vardecl varDecl.Initializer = null; // create an assignment operator for a lookup to the name // as the left, and the initializer as the right, and add it to the list var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varDecl.Parser) { Name = varDecl.Identifier, VariableField = varDecl.VariableField, }; assignments.Add(new JsBinaryOperator(varDecl.Context, varDecl.Parser) { Operand1 = lookup, Operand2 = initializer, OperatorToken = JsToken.Assign, OperatorContext = varDecl.AssignContext }); // add the new lookup to the field's references varDecl.VariableField.References.Add(lookup); } } } // now if there were any initializers... if (assignments.Count > 0) { // we want to create one big expression from all the assignments and replace the // var statement with the assignment(s) expression. Start at position n=1 and create // a binary operator of n-1 as the left, n as the right, and using a comma operator. var expression = assignments[0]; for (var ndx = 1; ndx < assignments.Count; ++ndx) { expression = JsCommaOperator.CombineWithComma(null, expression.Parser, expression, assignments[ndx]); } // replace the var with the expression. // we still have a pointer to the var, so we can insert it back into the proper // place next. varStatement.Parent.ReplaceChild(varStatement, expression); } else { // no initializers. // if the parent is a for-in statement... var forInParent = varStatement.Parent as JsForIn; if (forInParent != null) { // we want to replace the var statement with a lookup for the var // there should be only one vardecl var varDecl = varStatement[0]; var lookup = new JsLookup(varDecl.VariableField.OriginalContext, varStatement.Parser) { Name = varDecl.Identifier, VariableField = varDecl.VariableField }; varStatement.Parent.ReplaceChild(varStatement, lookup); varDecl.VariableField.References.Add(lookup); } else { // just remove the var statement altogether varStatement.Parent.ReplaceChild(varStatement, null); } } // if the statement at the insertion point is a var-statement already, // then we just need to append our vardecls to it. Otherwise we'll insert our // var statement at the right point if (existingVar != null) { // append the varstatement we want to move to the existing var, which will // transfer all the vardecls to it. existingVar.Append(varStatement); } else { // move the var to the insert point, incrementing the position or next time block.Insert(insertAt, varStatement); } } } } return(insertAt); }
public override void Visit(JsLookup node) { if (node != null) { // figure out if our reference type is a function or a constructor if (node.Parent is JsCallNode) { node.RefType = ( ((JsCallNode)(node.Parent)).IsConstructor ? JsReferenceType.Constructor : JsReferenceType.Function ); } // check the name of the variable for reserved words that aren't allowed JsActivationObject scope = m_scopeStack.Peek(); if (JsScanner.IsKeyword(node.Name, scope.UseStrict)) { node.Context.HandleError(JsError.KeywordUsedAsIdentifier, true); } // no variable field means ignore it if (node.VariableField != null && node.VariableField.FieldType == JsFieldType.Predefined) { // this is a predefined field. If it's Nan or Infinity, we should // replace it with the numeric value in case we need to later combine // some literal expressions. if (string.CompareOrdinal(node.Name, "NaN") == 0) { // don't analyze the new ConstantWrapper -- we don't want it to take part in the // duplicate constant combination logic should it be turned on. node.Parent.ReplaceChild(node, new JsConstantWrapper(double.NaN, JsPrimitiveType.Number, node.Context, m_parser)); } else if (string.CompareOrdinal(node.Name, "Infinity") == 0) { // don't analyze the new ConstantWrapper -- we don't want it to take part in the // duplicate constant combination logic should it be turned on. node.Parent.ReplaceChild(node, new JsConstantWrapper(double.PositiveInfinity, JsPrimitiveType.Number, node.Context, m_parser)); } } } }