/// <summary> /// Resolves which function is being called where possible. /// </summary> internal override void ResolveVariables(OptimizationInfo optimizationInfo) { if (ResolvedMethod != null || UserDefined != null) { // Already resolved. return; } // Grab if this is a 'new' call: bool isConstructor = optimizationInfo.IsConstructCall; optimizationInfo.IsConstructCall = false; // Resolve kids: base.ResolveVariables(optimizationInfo); object resolvedMethod = null; if (this.Target is MemberAccessExpression) { // The function is a member access expression (e.g. "Math.cos()"). // Get the parent of the member (e.g. Math): MemberAccessExpression baseExpression = ((MemberAccessExpression)this.Target); // Get the property: Nitrassic.Library.PropertyVariable property = baseExpression.GetProperty(optimizationInfo); // It should be a callable method: if (property.Type == typeof(MethodGroup) || typeof(System.Reflection.MethodBase).IsAssignableFrom(property.Type)) { if (property.IsConstant) { // Great, grab the value: resolvedMethod = property.ConstantValue; } else { // This occurs when the method has collapsed. // The property is still a method though, so we know for sure it can be invoked. // It's now an instance property on the object. optimizationInfo.TypeError("Runtime method in use (not supported at the moment). Internal: #T1"); } } else if (property.Type == typeof(FunctionMethodGenerator)) { FunctionMethodGenerator fm = property.ConstantValue as FunctionMethodGenerator; if (fm != null) { // Get the arg types being passed into the method, including 'this': Type[] argTypes = GetArgumentTypes(optimizationInfo, true, isConstructor?fm:null); // Get a specific overload: Library.UserDefinedFunction udm = fm.GetCompiled(argTypes, optimizationInfo.Engine, isConstructor); resolvedMethod = udm.body; UserDefined = udm; } else { // Runtime resolve (property) optimizationInfo.TypeError("Runtime property in use (not supported at the moment). Internal: #T4"); } } else if (property.Type != typeof(object)) { throw new JavaScriptException( optimizationInfo.Engine, "TypeError", "Cannot run '" + property.Name + "' as a method because it's known to be a " + property.Type + "." ); } else { // Similar to above, but this time its something that might not even be a method optimizationInfo.TypeError("Runtime method in use (not supported at the moment). Internal: #T2"); } } if (resolvedMethod == null) { // Get target as a name expression: NameExpression nameExpr = Target as NameExpression; if (nameExpr != null && nameExpr.Variable != null) { if (nameExpr.Variable.IsConstant) { FunctionMethodGenerator fm = nameExpr.Variable.ConstantValue as FunctionMethodGenerator; if (fm != null) { // Get the arg types being passed into the method, including 'this': Type[] argTypes = GetArgumentTypes(optimizationInfo, true, isConstructor?fm:null); // Get a specific overload: Library.UserDefinedFunction udm = fm.GetCompiled(argTypes, optimizationInfo.Engine, isConstructor); resolvedMethod = udm.body; UserDefined = udm; } else { // This is a constructor for a built-in type. // Get the return type: Type returnType = Target.GetResultType(optimizationInfo); // Get the proto for it: Nitrassic.Library.Prototype proto = optimizationInfo.Engine.Prototypes.Get(returnType); // Note that these two special methods are always methods // or method groups so no checking is necessary. if (isConstructor) { resolvedMethod = proto.OnConstruct; } else { resolvedMethod = proto.OnCall; } } } else { #warning runtime resolve here. // -> E.g. new varName() or varName() optimizationInfo.TypeError("Runtime resolve in use (not supported at the moment). Internal: #T5"); } } else { // Something else (e.g. "eval()") // Get the return type: Type returnType = Target.GetResultType(optimizationInfo); // Get the proto for it: Nitrassic.Library.Prototype proto = optimizationInfo.Engine.Prototypes.Get(returnType); // Note that these two special methods are always methods // or method groups so no checking is necessary. if (isConstructor) { resolvedMethod = proto.OnConstruct; } else { resolvedMethod = proto.OnCall; } } } if (resolvedMethod == null) { // Runtime resolve only. ResolvedMethod = null; return; } // Note that it may be a MethodGroup, so let's resolve it further if needed. MethodGroup group = resolvedMethod as MethodGroup; if (group == null) { // It must be MethodBase - it can't be anything else: ResolvedMethod = resolvedMethod as System.Reflection.MethodBase; } else { // We have a group! Find the overload that we're after (excluding 'this' and it's never a constructor either): ResolvedMethod = group.Match(GetArgumentTypes(optimizationInfo, false, null)); } }
/// <summary> /// Generates CIL for the expression. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Get the target as a name expression: NameExpression nameExpr = Target as NameExpression; // Grab if this is a 'new' call: bool isConstructor = optimizationInfo.IsConstructCall; optimizationInfo.IsConstructCall = false; if (ResolvedMethod != null) { // We have a known method! if (UserDefined != null) { if (isConstructor) { // Generate code to produce the "this" value. Library.Prototype proto = UserDefined.GetInstancePrototype(optimizationInfo.Engine); // Create the object now: generator.NewObject(proto.TypeConstructor); // Duplicate (which will act as our return value): if (optimizationInfo.RootExpression != this) { generator.Duplicate(); } } else { // There are three cases for non-constructor calls. if (this.Target is NameExpression) { // 1. The function is a name expression (e.g. "parseInt()"). // In this case this = scope.ImplicitThisValue, if there is one, otherwise undefined. ((NameExpression)this.Target).GenerateThis(generator); } else if (this.Target is MemberAccessExpression) { // 2. The function is a member access expression (e.g. "Math.cos()"). // In this case this = Math. var baseExpression = ((MemberAccessExpression)this.Target).Base; baseExpression.GenerateCode(generator, optimizationInfo); EmitConversion.ToAny(generator, baseExpression.GetResultType(optimizationInfo)); } else { // 3. Neither of the above (e.g. "(function() { return 5 })()") // In this case this = undefined. EmitHelpers.EmitUndefined(generator); } } } // Emit the rest of the args: EmitArguments(generator, optimizationInfo); // Got a return type? Type returnType = GetResultType(optimizationInfo); // Then the call! if (typeof(System.Reflection.ConstructorInfo).IsAssignableFrom(ResolvedMethod.GetType())) { // Actual constructor call: generator.NewObject(ResolvedMethod as System.Reflection.ConstructorInfo); } else { // Ordinary method: generator.Call(ResolvedMethod); } if (isConstructor) { // Always a returned value here. Needed? if (optimizationInfo.RootExpression == this) { // Remove the return value: generator.Pop(); } } else { if (returnType == typeof(Nitrassic.Undefined)) { if (optimizationInfo.RootExpression != this) { // Put undef on the stack: EmitHelpers.EmitUndefined(generator); } } else if (optimizationInfo.RootExpression == this) { // Remove the return value: generator.Pop(); } } } else { // Either runtime resolve it or it's not actually a callable function throw new NotImplementedException("A function was called which was not supported (" + ToString() + ")"); } }
public override Statement ParseNoNewContext(Parser parser) { // Consume the for keyword. parser.Expect(KeywordToken.For); // Read the left parenthesis. parser.Expect(PunctuatorToken.LeftParenthesis); // The initialization statement. Statement initializationStatement = null; // The type of for statement. ForStatementType type = ForStatementType.Unknown; // The for-in and for-of expressions need a variable to assign to. Is null for a regular for statement. IReferenceExpression forInOfReference = null; if (parser.nextToken == KeywordToken.Var || parser.nextToken == KeywordToken.Let || parser.nextToken == KeywordToken.Const) { bool isVar = (parser.nextToken == KeywordToken.Var); // Read past the var/let/const token. parser.Expect(parser.nextToken); Scope scope = isVar?parser.currentVarScope : parser.currentLetScope; // There can be multiple initializers (but not for for-in statements). var varLetConstStatement = new VarStatement(scope); initializationStatement = parser.Labels(varLetConstStatement); while (true) { var declaration = new VariableDeclaration(); // The next token must be a variable name. declaration.VariableName = parser.ExpectIdentifier(); parser.ValidateVariableName(declaration.VariableName); // Add the variable to the current function's list of local variables. parser.currentVarScope.AddVariable(declaration.VariableName, null, parser.context == CodeContext.Function ? null : new LiteralExpression(Undefined.Value)); // The next token is either an equals sign (=), a semi-colon, a comma, or the "in" keyword. if (parser.nextToken == PunctuatorToken.Assignment) { // Read past the equals token (=). parser.Expect(PunctuatorToken.Assignment); // Read the setter expression. declaration.InitExpression = parser.ParseExpression(PunctuatorToken.Semicolon, PunctuatorToken.Comma); // This must be a regular for statement. type = ForStatementType.For; } // Add the declaration to the initialization statement. varLetConstStatement.Declarations.Add(declaration); if (parser.nextToken == PunctuatorToken.Semicolon) { // This is a regular for statement. break; } else if (parser.nextToken == KeywordToken.In && type == ForStatementType.Unknown) { // This is a for-in statement. forInOfReference = new NameExpression(scope, declaration.VariableName); type = ForStatementType.ForIn; break; } else if (parser.nextToken == IdentifierToken.Of && type == ForStatementType.Unknown) { // This is a for-of statement. forInOfReference = new NameExpression(scope, declaration.VariableName); type = ForStatementType.ForOf; break; } else if (parser.nextToken != PunctuatorToken.Comma) { throw new JavaScriptException(parser.engine, "SyntaxError", string.Format("Unexpected token {0}", Token.ToText(parser.nextToken)), parser.LineNumber, parser.SourcePath); } // Read past the comma token. parser.Expect(PunctuatorToken.Comma); // Multiple initializers are not allowed in for-in statements. type = ForStatementType.For; } } else { // Not a var initializer - can be a simple variable name then "in" or any expression ending with a semi-colon. // The expression can be empty. if (parser.nextToken != PunctuatorToken.Semicolon) { // Parse an expression. var initializationExpression = parser.ParseExpression(PunctuatorToken.Semicolon, KeywordToken.In, IdentifierToken.Of); // Record debug info for the expression. initializationStatement = new ExpressionStatement(initializationExpression); if (parser.nextToken == KeywordToken.In) { // This is a for-in statement. if ((initializationExpression is IReferenceExpression) == false) { throw new JavaScriptException(parser.engine, "SyntaxError", "Invalid left-hand side in for-in", parser.LineNumber, parser.SourcePath); } forInOfReference = (IReferenceExpression)initializationExpression; } else if (parser.nextToken == IdentifierToken.Of) { // This is a for-of statement. if ((initializationExpression is IReferenceExpression) == false) { throw new JavaScriptException(parser.engine, "SyntaxError", "Invalid left-hand side in for-of", parser.LineNumber, parser.SourcePath); } forInOfReference = (IReferenceExpression)initializationExpression; type = ForStatementType.ForOf; } } } if (type == ForStatementType.ForIn) { // for (x in y) // for (var x in y) var result = new ForInStatement(); var labelled = parser.Labels(result); result.Variable = forInOfReference; // Consume the "in". parser.Expect(KeywordToken.In); // Parse the right-hand-side expression. result.TargetObject = parser.ParseExpression(PunctuatorToken.RightParenthesis); // Read the right parenthesis. parser.Expect(PunctuatorToken.RightParenthesis); // Read the statements that will be executed in the loop body. result.Body = parser.ParseStatement(); return(labelled); } else if (type == ForStatementType.ForOf) { // for (x of y) // for (var x of y) var result = new ForOfStatement(); var labelled = parser.Labels(result); result.Variable = forInOfReference; // Consume the "of". parser.Expect(IdentifierToken.Of); // Parse the right-hand-side expression. result.TargetObject = parser.ParseExpression(PunctuatorToken.RightParenthesis, PunctuatorToken.Comma); // Comma is not allowed. // Read the right parenthesis. parser.Expect(PunctuatorToken.RightParenthesis); // Read the statements that will be executed in the loop body. result.Body = parser.ParseStatement(); return(labelled); } else { var result = new ForStatement(); var labelled = parser.Labels(result); // Set the initialization statement. if (initializationStatement != null) { result.InitStatement = initializationStatement; } // Read the semicolon. parser.Expect(PunctuatorToken.Semicolon); // Parse the optional condition expression. // Note: if the condition is omitted then it is considered to always be true. if (parser.nextToken != PunctuatorToken.Semicolon) { result.ConditionStatement = new ExpressionStatement(parser.ParseExpression(PunctuatorToken.Semicolon)); } // Read the semicolon. // Note: automatic semicolon insertion never inserts a semicolon in the header of a // for statement. parser.Expect(PunctuatorToken.Semicolon); // Parse the optional increment expression. if (parser.nextToken != PunctuatorToken.RightParenthesis) { result.IncrementStatement = new ExpressionStatement(parser.ParseExpression(PunctuatorToken.RightParenthesis)); } // Read the right parenthesis. parser.Expect(PunctuatorToken.RightParenthesis); // Read the statements that will be executed in the loop body. result.Body = parser.ParseStatement(); return(labelled); } }