/// <summary> /// Gets the type that results from evaluating this expression. /// </summary> public override Type GetResultType(OptimizationInfo optimizationInfo) { var type = this.OperatorType; switch (this.OperatorType) { // Add case OperatorType.Add: { var lhs = this.Left.GetResultType(optimizationInfo); var rhs = this.Right.GetResultType(optimizationInfo); if (lhs == typeof(string) || rhs == typeof(string)) { return(typeof(ConcatenatedString)); } if (lhs == typeof(ConcatenatedString) || rhs == typeof(ConcatenatedString)) { return(typeof(ConcatenatedString)); } // If the two types are numeric integers, retain the one with the most accuracy. Type numeric = TypeConverter.MostAccurateInteger(lhs, rhs); if (numeric != null) { return(numeric); } if (lhs == typeof(object) || lhs == typeof(Library.ObjectInstance) || rhs == typeof(object) || rhs == typeof(Library.ObjectInstance)) { return(typeof(object)); } return(typeof(double)); } // Arithmetic operations. case OperatorType.Subtract: case OperatorType.Multiply: case OperatorType.Divide: case OperatorType.Modulo: return(typeof(double)); // Bitwise operations. case OperatorType.BitwiseAnd: case OperatorType.BitwiseOr: case OperatorType.BitwiseXor: case OperatorType.LeftShift: case OperatorType.SignedRightShift: return(typeof(int)); case OperatorType.UnsignedRightShift: return(typeof(double)); // Relational operations. case OperatorType.LessThan: case OperatorType.LessThanOrEqual: case OperatorType.GreaterThan: case OperatorType.GreaterThanOrEqual: return(typeof(bool)); // Equality operations. case OperatorType.Equal: case OperatorType.StrictlyEqual: case OperatorType.NotEqual: case OperatorType.StrictlyNotEqual: return(typeof(bool)); // Logical operations. case OperatorType.LogicalAnd: { var lhs = this.Left.GetResultType(optimizationInfo); var rhs = this.Right.GetResultType(optimizationInfo); // If lhs is true (regardless of whatever it's type is) then we respond with rhs. // If it's false, we respond with typeof(bool). // Try and eval left statically: object leftEval = Left.Evaluate(); if (leftEval == null) { // Can't statically eval it. // If rhs is a bool then great, we know for sure we're returning a bool. if (rhs == typeof(bool)) { return(typeof(bool)); } // Either a bool or rhs. For now this is an error condition. optimizationInfo.TypeError( "Ambiguous type && statement. It returns either a boolean or '" + rhs + "'. Add ==true to the right hand side to make sure it only returns a boolean." ); return(typeof(object)); } if (leftEval != null && TypeConverter.ToBoolean(leftEval)) { // Ok! We'll be returning rhs no matter what. return(rhs); } // Left eval was false so we'll be returning false. return(typeof(bool)); } case OperatorType.LogicalOr: { // The result is either the left-hand side or the right-hand side. var lhs = this.Left.GetResultType(optimizationInfo); var rhs = this.Right.GetResultType(optimizationInfo); if (lhs == rhs) { return(lhs); } if (PrimitiveTypeUtilities.IsNumeric(lhs) && PrimitiveTypeUtilities.IsNumeric(rhs)) { return(typeof(double)); } // If either evaluates statically to 0 then we also know the correct return type. object leftEval = Left.Evaluate(); if (leftEval != null && !TypeConverter.ToBoolean(leftEval)) { return(rhs); } object rightEval = Right.Evaluate(); if (rightEval != null && !TypeConverter.ToBoolean(rightEval)) { return(lhs); } return(typeof(object)); } // Misc case OperatorType.InstanceOf: case OperatorType.In: return(typeof(bool)); } throw new NotImplementedException(); }
/// <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)); } }