public override bool ReplaceChild(AstNode oldNode, AstNode newNode) { // if the old node isn't our element list, ignore the cal if (oldNode == m_elements) { if (newNode == null) { // we want to remove the list altogether m_elements = null; return(true); } else { // if the new node isn't an AstNodeList, then ignore the call AstNodeList newList = newNode as AstNodeList; if (newList != null) { // replace it m_elements = newList; newList.Parent = this; return(true); } } } return(false); }
public override bool ReplaceChild(AstNode oldNode, AstNode newNode) { if (m_func == oldNode) { m_func = newNode; if (newNode != null) { newNode.Parent = this; } return(true); } if (m_args == oldNode) { if (newNode == null) { // remove it m_args = null; return(true); } else { // if the new node isn't an AstNodeList, ignore it AstNodeList newList = newNode as AstNodeList; if (newList != null) { m_args = newList; newNode.Parent = this; return(true); } } } return(false); }
public void Visit(AstNodeList node) { if (node != null) { DoesRequire = true; } }
public void Visit(AstNodeList node) { // add the names for each item in the list // this assumes that the items are name declarations! // (like parameter lists) node.IfNotNull(n => n.Children.ForEach(i => i.Accept(this))); }
public override bool ReplaceChild(AstNode oldNode, AstNode newNode) { if (m_expression == oldNode) { m_expression = newNode; if (newNode != null) { newNode.Parent = this; } return true; } if (m_cases == oldNode) { if (newNode == null) { // remove it m_cases = null; return true; } else { // if the new node isn't an AstNodeList, ignore the call AstNodeList newList = newNode as AstNodeList; if (newList != null) { m_cases = newList; newNode.Parent = this; return true; } } } return false; }
public Switch(Context context, JSParser parser, AstNode expression, AstNodeList cases) : base(context, parser) { m_expression = expression; m_cases = cases; if (m_expression != null) m_expression.Parent = this; if (m_cases != null) m_cases.Parent = this; }
public Switch(Context context, JSParser parser, AstNode expression, AstNodeList cases) : base(context, parser) { Expression = expression; Cases = cases; if (Expression != null) Expression.Parent = this; if (Cases != null) Cases.Parent = this; }
public ArrayLiteral(Context context, JSParser parser, AstNodeList elements) : base(context, parser) { m_elements = elements; if (m_elements != null) { m_elements.Parent = this; } }
public override AstNode Clone() { // create a new empty list AstNodeList newList = new AstNodeList((Context == null ? null : Context.Clone()), Parser); // and clone all the items into it (skipping nulls) for (int ndx = 0; ndx < m_list.Count; ++ndx) { if (m_list[ndx] != null) { newList.Append(m_list[ndx].Clone()); } } return(newList); }
public CallNode(Context context, JSParser parser, AstNode function, AstNodeList args, bool inBrackets) : base(context, parser) { m_func = function; m_args = args; m_inBrackets = inBrackets; if (m_func != null) { m_func.Parent = this; } if (m_args != null) { m_args.Parent = this; } }
private static bool NotJustPrimitives(AstNodeList nodeList) { // if any node in the list isn't a constant wrapper (boolean, number, string) // or a unary (presumably a negative number), then we've got something other than // a primitive in the list (array or object) foreach (var child in nodeList) { if (!(child is ConstantWrapper) && !(child is UnaryOperator)) { return(true); } } // if we get here, then everything is a primitive return(false); }
public void Visit(AstNodeList node) { // add the names for each item in the list // this assumes that the items are name declarations! // (like parameter lists) if (node != null) { var count = node.Count; for (var i = 0; i < count; i++) { var itemNode = node[i]; if (itemNode != null) { itemNode.Accept(this); } } } }
public override bool ReplaceChild(AstNode oldNode, AstNode newNode) { if (Expression == oldNode) { Expression = newNode; return(true); } if (Cases == oldNode) { AstNodeList newList = newNode as AstNodeList; if (newNode == null || newList != null) { // remove it Cases = newList; return(true); } } return(false); }
public void Visit(AstNodeList node) { if (node != null) { for (var ndx = 0; ndx < node.Count; ++ndx) { if (ndx > 0) { m_writer.Write(','); if (m_settings.OutputMode == OutputMode.MultipleLines) { m_writer.Write(' '); } } if (node[ndx] != null) { node[ndx].Accept(this); } } } }
/// <summary> /// an astlist is equivalent to another astlist if they both have the same number of /// items, and each item is equivalent to the corresponding item in the other /// </summary> /// <param name="otherNode"></param> /// <returns></returns> public override bool IsEquivalentTo(AstNode otherNode) { bool isEquivalent = false; AstNodeList otherList = otherNode as AstNodeList; if (otherList != null && m_list.Count == otherList.Count) { // now assume it's true unless we come across an item that ISN'T // equivalent, at which case we'll bail the test. isEquivalent = true; for (var ndx = 0; ndx < m_list.Count; ++ndx) { if (!m_list[ndx].IsEquivalentTo(otherList[ndx])) { isEquivalent = false; break; } } } return(isEquivalent); }
//--------------------------------------------------------------------------------------- // ParseSwitchStatement // // SwitchStatement : // 'switch' '(' Expression ')' '{' CaseBlock '}' // // CaseBlock : // CaseList DefaultCaseClause CaseList // // CaseList : // <empty> | // CaseClause CaseList // // CaseClause : // 'case' Expression ':' OptionalStatements // // DefaultCaseClause : // <empty> | // 'default' ':' OptionalStatements //--------------------------------------------------------------------------------------- private AstNode ParseSwitchStatement() { Context switchCtx = m_currentToken.Clone(); AstNode expr = null; AstNodeList cases = null; m_blockType.Add(BlockType.Switch); try { // read switch(expr) GetNextToken(); if (JSToken.LeftParenthesis != m_currentToken.Token) ReportError(JSError.NoLeftParenthesis); GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet); try { expr = ParseExpression(); if (JSToken.RightParenthesis != m_currentToken.Token) { ReportError(JSError.NoRightParenthesis); } GetNextToken(); if (JSToken.LeftCurly != m_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; } else { if (exc._partiallyComputedNode == null) expr = new ConstantWrapper(true, PrimitiveType.Boolean, CurrentPositionContext(), this); else expr = exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet, exc) != -1) { if (exc._token == JSToken.RightParenthesis) GetNextToken(); if (JSToken.LeftCurly != m_currentToken.Token) { ReportError(JSError.NoLeftCurly); } GetNextToken(); } } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockConditionNoSkipTokenSet); } // parse the switch body cases = new AstNodeList(m_currentToken.Clone(), this); bool defaultStatement = false; m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet); try { while (JSToken.RightCurly != m_currentToken.Token) { SwitchCase caseClause = null; AstNode caseValue = null; Context caseCtx = m_currentToken.Clone(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_CaseNoSkipTokenSet); try { if (JSToken.Case == m_currentToken.Token) { // get the case GetNextToken(); caseValue = ParseExpression(); } else if (JSToken.Default == m_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 != m_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; } else { caseValue = exc._partiallyComputedNode; if (exc._token == JSToken.Colon) { GetNextToken(); } } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_CaseNoSkipTokenSet); } m_blockType.Add(BlockType.Block); try { Block statements = new Block(m_currentToken.Clone(), this); m_noSkipTokenSet.Add(NoSkipTokenSet.s_SwitchNoSkipTokenSet); m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); try { while (JSToken.RightCurly != m_currentToken.Token && JSToken.Case != m_currentToken.Token && JSToken.Default != m_currentToken.Token) { try { // parse a Statement, not a SourceElement statements.Append(ParseStatement(false)); } catch (RecoveryTokenException exc) { if (exc._partiallyComputedNode != null) { statements.Append(exc._partiallyComputedNode); exc._partiallyComputedNode = null; } if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1) { throw; } } } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_SwitchNoSkipTokenSet, exc) == -1) { caseClause = new SwitchCase(caseCtx, this, caseValue, statements); cases.Append(caseClause); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet); m_noSkipTokenSet.Remove(NoSkipTokenSet.s_SwitchNoSkipTokenSet); } if (JSToken.RightCurly == m_currentToken.Token) { statements.Context.UpdateWith(m_currentToken); } caseCtx.UpdateWith(statements.Context); caseClause = new SwitchCase(caseCtx, this, caseValue, statements); cases.Append(caseClause); } finally { m_blockType.RemoveAt(m_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, this, expr, cases); throw; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet); } switchCtx.UpdateWith(m_currentToken); GetNextToken(); } finally { m_blockType.RemoveAt(m_blockType.Count - 1); } return new Switch(switchCtx, this, expr, cases); }
// // expression elements we shouldn't get to // public void Visit(AstNodeList node) { Debug.Fail("shouldn't get here"); }
internal override void AnalyzeNode() { // see if this is a member (we'll need it for a couple checks) Member member = m_func as Member; if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // if this is a member, and it's a debugger object, and it's a constructor.... if (member != null && member.IsDebuggerStatement && m_isConstructor) { // we need to replace our debugger object with a generic Object m_func = new Lookup("Object", m_func.Context, Parser); // and make sure the node list is empty if (m_args != null && m_args.Count > 0) { m_args = new AstNodeList(m_args.Context, Parser); } } } // if this is a constructor and we want to collapse // some of them to literals... if (m_isConstructor && Parser.Settings.CollapseToLiteral) { // see if this is a lookup, and if so, if it's pointing to one // of the two constructors we want to collapse Lookup lookup = m_func as Lookup; if (lookup != null) { if (lookup.Name == "Object" && Parser.Settings.IsModificationAllowed(TreeModifications.NewObjectToObjectLiteral)) { // no arguments -- the Object constructor with no arguments is the exact same as an empty // object literal if (m_args == null || m_args.Count == 0) { // replace our node with an object literal ObjectLiteral objLiteral = new ObjectLiteral(Context, Parser, null, null); if (Parent.ReplaceChild(this, objLiteral)) { // and bail now. No need to recurse -- it's an empty literal return; } } else if (m_args.Count == 1) { // one argument // check to see if it's an object literal. ObjectLiteral objectLiteral = m_args[0] as ObjectLiteral; if (objectLiteral != null) { // the Object constructor with an argument that is a JavaScript object merely returns the // argument. Since the argument is an object literal, it is by definition a JavaScript object // and therefore we can replace the constructor call with the object literal Parent.ReplaceChild(this, objectLiteral); // don't forget to recurse the object now objectLiteral.AnalyzeNode(); // and then bail -- we don't want to process this call // operation any more; we've gotten rid of it return; } } } else if (lookup.Name == "Array" && Parser.Settings.IsModificationAllowed(TreeModifications.NewArrayToArrayLiteral)) { // Array is trickier. // If there are no arguments, then just use []. // if there are multiple arguments, then use [arg0,arg1...argN]. // but if there is one argument and it's numeric, we can't crunch it. // also can't crunch if it's a function call or a member or something, since we won't // KNOW whether or not it's numeric. // // so first see if it even is a single-argument constant wrapper. ConstantWrapper constWrapper = (m_args != null && m_args.Count == 1 ? m_args[0] as ConstantWrapper : null); // if the argument count is not one, then we crunch. // if the argument count IS one, we only crunch if we have a constant wrapper, // AND it's not numeric. if (m_args == null || m_args.Count != 1 || (constWrapper != null && !constWrapper.IsNumericLiteral)) { // create the new array literal object ArrayLiteral arrayLiteral = new ArrayLiteral(Context, Parser, m_args); // replace ourself within our parent if (Parent.ReplaceChild(this, arrayLiteral)) { // recurse arrayLiteral.AnalyzeNode(); // and bail -- we don't want to recurse this node any more return; } } } } } // if we are replacing resource references with strings generated from resource files // and this is a brackets call: lookup[args] ResourceStrings resourceStrings = Parser.ResourceStrings; if (m_inBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = m_func as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // we're going to replace this node with a string constant wrapper // but first we need to make sure that this is a valid lookup. // if the parameter contains anything that would vary at run-time, // then we need to throw an error. // the parser will always have either one or zero nodes in the arguments // arg list. We're not interested in zero args, so just make sure there is one if (m_args.Count == 1) { // must be a constant wrapper ConstantWrapper argConstant = m_args[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.String, Context, Parser); // replace this node with localized string, analyze it, and bail // so we don't anaylze the tree we just replaced Parent.ReplaceChild(this, resourceLiteral); resourceLiteral.AnalyzeNode(); return; } else { // error! must be a constant Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } else { // error! can only be a single constant argument to the string resource object. // the parser will only have zero or one arguments, so this must be zero // (since the parser won't pass multiple args to a [] operator) Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } } // and finally, if this is a backets call and the argument is a constantwrapper that can // be an identifier, just change us to a member node: obj["prop"] to obj.prop. // but ONLY if the string value is "safe" to be an identifier. Even though the ECMA-262 // spec says certain Unicode categories are okay, in practice the various major browsers // all seem to have problems with certain characters in identifiers. Rather than risking // some browsers breaking when we change this syntax, don't do it for those "danger" categories. if (m_inBrackets && m_args != null) { // see if there is a single, constant argument string argText = m_args.SingleConstantArgument; if (argText != null) { // see if we want to replace the name string newName; if (Parser.Settings.HasRenamePairs && Parser.Settings.ManualRenamesProperties && Parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = Parser.Settings.GetNewName(argText))) { // yes -- we are going to replace the name, either as a string literal, or by converting // to a member-dot operation. // See if we can't turn it into a dot-operator. If we can't, then we just want to replace the operator with // a new constant wrapper. Otherwise we'll just replace the operator with a new constant wrapper. if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(newName) && !JSScanner.IsKeyword(newName)) { // the new name is safe to convert to a member-dot operator. // but we don't want to convert the node to the NEW name, because we still need to Analyze the // new member node -- and it might convert the new name to something else. So instead we're // just going to convert this existing string to a member node WITH THE OLD STRING, // and THEN analyze it (which will convert the old string to newName) Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); // this analyze call will convert the old-name member to the newName value replacementMember.AnalyzeNode(); return; } else { // nope; can't convert to a dot-operator. // we're just going to replace the first argument with a new string literal // and continue along our merry way. m_args.ReplaceChild(m_args[0], new ConstantWrapper(newName, PrimitiveType.String, m_args[0].Context, Parser)); } } else if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(argText) && !JSScanner.IsKeyword(argText)) { // not a replacement, but the string literal is a safe identifier. So we will // replace this call node with a Member-dot operation Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); replacementMember.AnalyzeNode(); return; } } } // call the base class to recurse base.AnalyzeNode(); // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!m_inBrackets && !m_isConstructor && (m_args == null || m_args.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && Parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(Context, Parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator Parent.ReplaceChild(this, unary); // don't need to AnalyzeNode on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else if (Parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // if this is a window.eval call, then we need to mark this scope as unknown just as // we would if this was a regular eval call. // (unless, of course, the parser settings say evals are safe) // call AFTER recursing so we know the left-hand side properties have had a chance to // lookup their fields to see if they are local or global if (member != null && string.CompareOrdinal(member.Name, "eval") == 0) { if (member.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } else { CallNode callNode = m_func as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } /* REVIEW: may be too late. lookups may alread have been analyzed and * found undefined * // check to see if this is an assignment to a window["prop"] structure * BinaryOperator binaryOp = Parent as BinaryOperator; * if (binaryOp != null && binaryOp.IsAssign * && m_inBrackets * && m_func.IsWindowLookup * && m_args != null) * { * // and IF the property is a non-empty constant that isn't currently * // a global field... * string propertyName = m_args.SingleConstantArgument; * if (!string.IsNullOrEmpty(propertyName) * && Parser.GlobalScope[propertyName] == null) * { * // we want to also add it to the global fields so it's not undefined * Parser.GlobalScope.DeclareField(propertyName, null, 0); * } * } */ }
//--------------------------------------------------------------------------------------- // 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 AstNode MemberExpression(AstNode expression, List<Context> newContexts) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); try { switch (m_currentToken.Token) { case JSToken.LeftParenthesis: AstNodeList args = null; RecoveryTokenException callError = null; m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenToken); try { args = ParseExpressionList(JSToken.RightParenthesis); } catch (RecoveryTokenException exc) { args = (AstNodeList)exc._partiallyComputedNode; if (IndexOfToken(NoSkipTokenSet.s_ParenToken, exc) == -1) callError = exc; // thrown later on } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ParenToken); } expression = new CallNode(expression.Context.CombineWith(args.Context), this, expression, args, false); if (null != newContexts && newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); if (!(expression is CallNode)) expression = new CallNode(newContexts[newContexts.Count - 1], this, expression, new AstNodeList(CurrentPositionContext(), this), false); else expression.Context = newContexts[newContexts.Count - 1]; ((CallNode)expression).IsConstructor = true; newContexts.RemoveAt(newContexts.Count - 1); } if (callError != null) { callError._partiallyComputedNode = expression; throw callError; } GetNextToken(); break; case JSToken.LeftBracket: m_noSkipTokenSet.Add(NoSkipTokenSet.s_BracketToken); try { // // ROTOR parses a[b,c] as a call to a, passing in the arguments b and c. // the correct parse is a member lookup on a of c -- the "b,c" should be // a single expression with a comma operator that evaluates b but only // returns c. // So we'll change the default behavior from parsing an expression list to // parsing a single expression, but returning a single-item list (or an empty // list if there is no expression) so the rest of the code will work. // //args = ParseExpressionList(JSToken.RightBracket); GetNextToken(); args = new AstNodeList(m_currentToken.Clone(), this); AstNode accessor = ParseExpression(); if (accessor != null) { args.Append(accessor); } } catch (RecoveryTokenException exc) { if (IndexOfToken(NoSkipTokenSet.s_BracketToken, exc) == -1) { if (exc._partiallyComputedNode != null) { exc._partiallyComputedNode = new CallNode(expression.Context.CombineWith(m_currentToken.Clone()), this, expression, (AstNodeList)exc._partiallyComputedNode, true); } else { exc._partiallyComputedNode = expression; } throw; } else args = (AstNodeList)exc._partiallyComputedNode; } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BracketToken); } expression = new CallNode(expression.Context.CombineWith(m_currentToken.Clone()), this, expression, args, true); // there originally was code here in the ROTOR sources that checked the new context list and // changed this member call to a constructor call, effectively combining the two. I believe they // need to remain separate. // remove the close bracket token GetNextToken(); break; case JSToken.AccessField: ConstantWrapper id = null; GetNextToken(); if (JSToken.Identifier != m_currentToken.Token) { string identifier = JSKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { // don't report an error here -- it's actually okay to have a property name // that is a keyword which is okay to be an identifier. For instance, // jQuery has a commonly-used method named "get" to make an ajax request //ForceReportInfo(JSError.KeywordUsedAsIdentifier); id = new ConstantWrapper(identifier, PrimitiveType.String, m_currentToken.Clone(), this); } else if (JSScanner.IsValidIdentifier(m_currentToken.Code)) { // it must be a keyword, because it can't technically be an indentifier, // but it IS a valid identifier format. Throw the error but still // create the constant wrapper so we can output it as-is ReportError(JSError.NoIdentifier, m_currentToken.Clone(), true); id = new ConstantWrapper(m_currentToken.Code, PrimitiveType.String, m_currentToken.Clone(), this); } else { ReportError(JSError.NoIdentifier); SkipTokensAndThrow(expression); } } else { id = new ConstantWrapper(m_scanner.GetIdentifier(), PrimitiveType.String, m_currentToken.Clone(), this); } GetNextToken(); expression = new Member(expression.Context.CombineWith(id.Context), this, expression, id.Context.Code, id.Context); break; default: if (null != newContexts) { while (newContexts.Count > 0) { (newContexts[newContexts.Count - 1]).UpdateWith(expression.Context); expression = new CallNode(newContexts[newContexts.Count - 1], this, expression, new AstNodeList(CurrentPositionContext(), this), false); ((CallNode)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; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_MemberExprNoSkipTokenSet); } } }
//--------------------------------------------------------------------------------------- // ParseExpressionList // // Given a starting this.currentToken '(' or '[', parse a list of expression separated by // ',' until matching ')' or ']' //--------------------------------------------------------------------------------------- private AstNodeList ParseExpressionList(JSToken terminator) { Context listCtx = m_currentToken.Clone(); GetNextToken(); AstNodeList list = new AstNodeList(listCtx, this); if (terminator != m_currentToken.Token) { for (; ; ) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet); try { if (JSToken.Comma == m_currentToken.Token) { list.Append(new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this)); } else if (terminator == m_currentToken.Token) { break; } else list.Append(ParseExpression(true)); if (terminator == m_currentToken.Token) break; else { if (JSToken.Comma != m_currentToken.Token) { if (terminator == JSToken.RightParenthesis) { // 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 == m_currentToken.Token) { if (JSToken.RightParenthesis == m_scanner.PeekToken()) { ReportError(JSError.UnexpectedSemicolon, true); GetNextToken(); break; } } ReportError(JSError.NoRightParenthesisOrComma); } 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; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ExpressionListNoSkipTokenSet); } GetNextToken(); } } listCtx.UpdateWith(m_currentToken); return list; }
public override bool ReplaceChild(AstNode oldNode, AstNode newNode) { if (m_func == oldNode) { m_func = newNode; if (newNode != null) { newNode.Parent = this; } return true; } if (m_args == oldNode) { if (newNode == null) { // remove it m_args = null; return true; } else { // if the new node isn't an AstNodeList, ignore it AstNodeList newList = newNode as AstNodeList; if (newList != null) { m_args = newList; newNode.Parent = this; return true; } } } return false; }
public void Visit(AstNodeList node) { // shoudn't get here DebugEx.Fail("shouldn't get here"); }
//--------------------------------------------------------------------------------------- // ParseLeftHandSideExpression // // LeftHandSideExpression : // PrimaryExpression Accessor | // 'new' LeftHandSideExpression | // FunctionExpression // // PrimaryExpression : // 'this' | // Identifier | // Literal | // '(' Expression ')' // // FunctionExpression : // 'function' OptionalFuncName '(' FormalParameterList ')' { FunctionBody } // // OptionalFuncName : // <empty> | // Identifier //--------------------------------------------------------------------------------------- private AstNode ParseLeftHandSideExpression(bool isMinus) { AstNode ast = null; bool isFunction = false; List<Context> newContexts = null; TryItAgain: // new expression while (JSToken.New == m_currentToken.Token) { if (null == newContexts) newContexts = new List<Context>(4); newContexts.Add(m_currentToken.Clone()); GetNextToken(); } JSToken token = m_currentToken.Token; switch (token) { // primary expression case JSToken.Identifier: ast = new Lookup(m_scanner.GetIdentifier(), m_currentToken.Clone(), this); break; case JSToken.ConditionalCommentStart: // skip past the start to the next token GetNextToken(); if (m_currentToken.Token == JSToken.PreprocessorConstant) { // we have /*@id ast = new ConstantWrapperPP(m_currentToken.Code, true, m_currentToken.Clone(), this); 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. 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; } } 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 ThisLiteral(m_currentToken.Clone(), this); break; case JSToken.StringLiteral: ast = new ConstantWrapper(m_scanner.StringLiteral, PrimitiveType.String, m_currentToken.Clone(), this); break; case JSToken.IntegerLiteral: case JSToken.NumericLiteral: { Context numericContext = m_currentToken.Clone(); double doubleValue; if (ConvertNumericLiteralToDouble(m_currentToken.Code, (token == JSToken.IntegerLiteral), out doubleValue)) { // conversion worked fine // check for some boundary conditions 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 ConstantWrapper(doubleValue, PrimitiveType.Number, numericContext, this); } else { // check to see if we went overflow if (double.IsInfinity(doubleValue)) { ReportError(JSError.NumericOverflow, numericContext, true); } // regardless, we're going to create a special constant wrapper // that simply echos the input as-is ast = new ConstantWrapper(m_currentToken.Code, PrimitiveType.Other, numericContext, this); } break; } case JSToken.True: ast = new ConstantWrapper(true, PrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JSToken.False: ast = new ConstantWrapper(false, PrimitiveType.Boolean, m_currentToken.Clone(), this); break; case JSToken.Null: ast = new ConstantWrapper(null, PrimitiveType.Null, m_currentToken.Clone(), this); break; case JSToken.PreprocessorConstant: ast = new ConstantWrapperPP(m_currentToken.Code, false, m_currentToken.Clone(), this); 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? String source = m_scanner.ScanRegExp(); if (source != null) { // parse the flags (if any) String flags = m_scanner.ScanRegExpFlags(); // create the literal ast = new RegExpLiteral(source, flags, m_currentToken.Clone(), this); break; } goto default; // expression case JSToken.LeftParenthesis: { // save the current context reference Context openParenContext = m_currentToken.Clone(); GetNextToken(); m_noSkipTokenSet.Add(NoSkipTokenSet.s_ParenExpressionNoSkipToken); try { // parse an expression ast = ParseExpression(); // update the expression's context with the context of the open paren ast.Context.UpdateWith(openParenContext); 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 ast = exc._partiallyComputedNode; } finally { m_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: Context listCtx = m_currentToken.Clone(); AstNodeList list = new AstNodeList(m_currentToken.Clone(), this); GetNextToken(); while (JSToken.RightBracket != m_currentToken.Token) { if (JSToken.Comma != m_currentToken.Token) { m_noSkipTokenSet.Add(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); try { list.Append(ParseExpression(true)); if (JSToken.Comma != m_currentToken.Token) { if (JSToken.RightBracket != m_currentToken.Token) ReportError(JSError.NoRightBracket); break; } else { // we have a comma -- skip it 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) { list.Append(new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this)); } } } 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, this, list); throw; } else { if (JSToken.RightBracket == m_currentToken.Token) break; } } finally { m_noSkipTokenSet.Remove(NoSkipTokenSet.s_ArrayInitNoSkipTokenSet); } } else { // comma -- missing array item in the list list.Append(new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this)); // 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) { list.Append(new ConstantWrapper(Missing.Value, PrimitiveType.Other, m_currentToken.Clone(), this)); } } } listCtx.UpdateWith(m_currentToken); ast = new ArrayLiteral(listCtx, this, list); break; // object initializer case JSToken.LeftCurly: Context objCtx = m_currentToken.Clone(); GetNextToken(); // we're going to keep the keys and values in separate lists, but make sure // that the indexes correlate (keyList[n] is associated with valueList[n]) List<ObjectLiteralField> keyList = new List<ObjectLiteralField>(); List<AstNode> valueList = new List<AstNode>(); if (JSToken.RightCurly != m_currentToken.Token) { for (; ; ) { ObjectLiteralField field = null; AstNode value = null; bool getterSetter = false; string ident; switch (m_currentToken.Token) { case JSToken.Identifier: field = new ObjectLiteralField(m_scanner.GetIdentifier(), PrimitiveType.String, m_currentToken.Clone(), this); break; case JSToken.StringLiteral: field = new ObjectLiteralField(m_scanner.StringLiteral, PrimitiveType.String, m_currentToken.Clone(), this); 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 ObjectLiteralField( doubleValue, PrimitiveType.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 ObjectLiteralField( m_currentToken.Code, PrimitiveType.Other, m_currentToken.Clone(), this); } break; } case JSToken.Get: case JSToken.Set: if (m_scanner.PeekToken() == JSToken.Colon) { // the field is either "get" or "set" and isn't the special Mozilla getter/setter field = new ObjectLiteralField(m_currentToken.Code, PrimitiveType.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 ? FunctionType.Getter : FunctionType.Setter), m_currentToken.Clone() ); FunctionObject funcExpr = value as FunctionObject; if (funcExpr != null) { // getter/setter is just the literal name with a get/set flag field = new GetterSetter( funcExpr.Name, isGet, funcExpr.IdContext.Clone(), this ); } else { ReportError(JSError.FunctionExpressionExpected); } } break; default: // NOT: identifier, string, number, or getter/setter. // see if it's a token that COULD be an identifier. ident = JSKeyword.CanBeIdentifier(m_currentToken.Token); if (ident != null) { // don't throw a warning -- it's okay to have a keyword that // can be an identifier here. field = new ObjectLiteralField(ident, PrimitiveType.String, m_currentToken.Clone(), this); } else { ReportError(JSError.NoMemberIdentifier); field = new ObjectLiteralField("_#Missing_Field#_" + s_cDummyName++, PrimitiveType.String, CurrentPositionContext(), 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 { GetNextToken(); value = ParseExpression(true); } } // put the pair into the list of fields keyList.Add(field); valueList.Add(value); if (JSToken.RightCurly == m_currentToken.Token) break; else { if (JSToken.Comma == m_currentToken.Token) { // skip the comma 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_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; keyList.Add(field); valueList.Add(value); } if (IndexOfToken(NoSkipTokenSet.s_ObjectInitNoSkipTokenSet, exc) == -1) { exc._partiallyComputedNode = new ObjectLiteral(objCtx, this, keyList.ToArray(), valueList.ToArray()); 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 ObjectLiteral(objCtx, this, keyList.ToArray(), valueList.ToArray()); break; // function expression case JSToken.Function: ast = ParseFunction(FunctionType.Expression, m_currentToken.Clone()); isFunction = true; break; case JSToken.AspNetBlock: ast = ParseAspNetBlock(consumeSemicolonIfPossible: false); break; default: string identifier = JSKeyword.CanBeIdentifier(m_currentToken.Token); if (null != identifier) { ast = new Lookup(identifier, m_currentToken.Clone(), this); } else { ReportError(JSError.ExpressionExpected); SkipTokensAndThrow(); } break; } // can be a CallExpression, that is, followed by '.' or '(' or '[' if (!isFunction) GetNextToken(); return MemberExpression(ast, newContexts); }