public override void ConvertToJavascript(JavascriptConversionContext context) { var methodCall = context.Node as MethodCallExpression; if (methodCall != null) { if (methodCall.Method.DeclaringType == typeof(string)) { switch (methodCall.Method.Name) { case "Concat": { context.PreventDefault(); var writer = context.GetWriter(); using (writer.Operation(JavascriptOperationTypes.Concat)) { if (methodCall.Arguments.Count == 0) { writer.Write("''"); } else { if (GetTypeOfExpression(methodCall.Arguments[0]) != typeof(string)) { writer.Write("''+"); } context.WriteMany('+', methodCall.Arguments); } } return; } case "Join": { context.PreventDefault(); var writer = context.GetWriter(); using (writer.Operation(JavascriptOperationTypes.Call)) { using (writer.Operation(JavascriptOperationTypes.IndexerProperty)) { var pars = methodCall.Method.GetParameters(); if (pars.Length == 4 && pars[1].ParameterType.IsArray && pars[2].ParameterType == typeof(int) && pars[3].ParameterType == typeof(int)) { throw new NotSupportedException("The `String.Join` method with start and count paramaters is not supported."); } if (pars.Length != 2 || !TypeHelpers.IsEnumerableType(pars[1].ParameterType)) { throw new NotSupportedException("This `String.Join` method is not supported."); } // if second parameter is an enumerable, render it directly context.Visitor.Visit(methodCall.Arguments[1]); writer.Write(".join"); } writer.Write('('); // separator using (writer.Operation(0)) context.Visitor.Visit(methodCall.Arguments[0]); writer.Write(')'); } return; } } } } }
protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.IsSpecialName) { var isIndexer = node.Method.Name == "get_Item" || node.Method.Name == "get_Chars"; if (isIndexer) { using (this.result.Operation(JavascriptOperationTypes.IndexerProperty)) { this.Visit(node.Object); this.result.Write('['); using (this.result.Operation(0)) { var posStart0 = this.result.Length; foreach (var arg in node.Arguments) { if (this.result.Length != posStart0) { this.result.Write(','); } this.Visit(arg); } } this.result.Write(']'); return(node); } } if (node.Method.Name == "set_Item") { using (this.result.Operation(0)) { using (this.result.Operation(JavascriptOperationTypes.AssignRhs)) { using (this.result.Operation(JavascriptOperationTypes.IndexerProperty)) { this.Visit(node.Object); this.result.Write('['); using (this.result.Operation(0)) { var posStart0 = this.result.Length; foreach (var arg in node.Arguments) { if (this.result.Length != posStart0) { this.result.Write(','); } this.Visit(arg); } } this.result.Write(']'); } } this.result.Write('='); this.Visit(node.Arguments.Single()); } return(node); } } else { if (node.Method.DeclaringType != null && (node.Method.Name == "ContainsKey" && TypeHelpers.IsDictionaryType(node.Method.DeclaringType))) { using (this.result.Operation(JavascriptOperationTypes.Call)) { using (this.result.Operation(JavascriptOperationTypes.IndexerProperty)) this.Visit(node.Object); this.result.Write(".hasOwnProperty("); using (this.result.Operation(0)) this.Visit(node.Arguments.Single()); this.result.Write(')'); return(node); } } } if (node.Method.DeclaringType == typeof(string)) { if (node.Method.Name == "Contains") { using (this.result.Operation(JavascriptOperationTypes.Comparison)) { using (this.result.Operation(JavascriptOperationTypes.Call)) { using (this.result.Operation(JavascriptOperationTypes.IndexerProperty)) this.Visit(node.Object); this.result.Write(".indexOf("); using (this.result.Operation(0)) { var posStart = this.result.Length; foreach (var arg in node.Arguments) { if (this.result.Length > posStart) { this.result.Write(','); } this.Visit(arg); } } this.result.Write(')'); } this.result.Write(">=0"); return(node); } } } if (node.Method.Name == "ToString" && node.Type == typeof(string) && node.Object != null) { string methodName = null; if (node.Arguments.Count == 0 || typeof(IFormatProvider).IsAssignableFrom(node.Arguments[0].Type)) { methodName = "toString()"; } else if (TypeHelpers.IsNumericType(node.Object.Type) && node.Arguments.Count >= 1 && node.Arguments[0].Type == typeof(string) && node.Arguments[0].NodeType == ExpressionType.Constant) { var str = (string)((ConstantExpression)node.Arguments[0]).Value; var match = Regex.Match(str, @"^([DEFGNX])(\d*)$", RegexOptions.IgnoreCase); var f = match.Groups[1].Value.ToUpper(); var n = match.Groups[2].Value; if (f == "D") { methodName = "toString()"; } else if (f == "E") { methodName = "toExponential(" + n + ")"; } else if (f == "F" || f == "G") { methodName = "toFixed(" + n + ")"; } else if (f == "N") { methodName = "toLocaleString()"; } else if (f == "X") { methodName = "toString(16)"; } } if (methodName != null) { using (this.result.Operation(JavascriptOperationTypes.Call)) { using (this.result.Operation(JavascriptOperationTypes.IndexerProperty)) this.Visit(node.Object); this.result.WriteFormat(".{0}", methodName); return(node); } } } if (!node.Method.IsStatic) { throw new NotSupportedException(string.Format("By default, Lambda2Js cannot convert custom instance methods, only static ones. `{0}` is not static.", node.Method.Name)); } using (this.result.Operation(JavascriptOperationTypes.Call)) if (node.Method.DeclaringType != null) { this.result.Write(node.Method.DeclaringType.FullName); this.result.Write('.'); this.result.Write(node.Method.Name); this.result.Write('('); var posStart = this.result.Length; using (this.result.Operation(0)) foreach (var arg in node.Arguments) { if (this.result.Length != posStart) { this.result.Write(','); } this.Visit(arg); } this.result.Write(')'); return(node); } return(node); }
protected override Expression VisitListInit(ListInitExpression node) { // Detecting a new dictionary if (TypeHelpers.IsDictionaryType(node.Type)) { using (this.result.Operation(0)) { this.result.Write('{'); var posStart = this.result.Length; foreach (var init in node.Initializers) { if (this.result.Length > posStart) { this.result.Write(','); } if (init.Arguments.Count != 2) { throw new NotSupportedException( "Objects can only be initialized with methods that receive pairs: key -> name"); } var nameArg = init.Arguments[0]; if (nameArg.NodeType != ExpressionType.Constant || nameArg.Type != typeof(string)) { throw new NotSupportedException("The key of an object must be a constant string value"); } var name = (string)((ConstantExpression)nameArg).Value; if (Regex.IsMatch(name, @"^\w[\d\w]*$")) { this.result.Write(name); } else { this.WriteStringLiteral(name); } this.result.Write(':'); this.Visit(init.Arguments[1]); } this.result.Write('}'); } return(node); } // Detecting a new dictionary if (TypeHelpers.IsListType(node.Type)) { using (this.result.Operation(0)) { this.result.Write('['); var posStart = this.result.Length; foreach (var init in node.Initializers) { if (this.result.Length > posStart) { this.result.Write(','); } if (init.Arguments.Count != 1) { throw new Exception( "Arrays can only be initialized with methods that receive a single parameter for the value"); } this.Visit(init.Arguments[0]); } this.result.Write(']'); } return(node); } return(node); }
protected override Expression VisitMember(MemberExpression node) { if (node.Expression == null) { var decl = node.Member.DeclaringType; if (decl == typeof(string)) { if (node.Member.Name == "Empty") { using (this.result.Operation(JavascriptOperationTypes.Literal)) this.result.Write("\"\""); return(node); } } } using (this.result.Operation(node)) { var metadataProvider = this.Options.GetMetadataProvider(); var pos = this.result.Length; if (node.Expression == null) { var decl = node.Member.DeclaringType; if (decl != null) { // TODO: there should be a way to customize the name of types through metadata this.result.Write(decl.FullName); this.result.Write('.'); this.result.Write(decl.Name); } } else if (node.Expression != this.contextParameter) { this.Visit(node.Expression); } else { this.usedScopeMembers = this.usedScopeMembers ?? new List <string>(); var meta = metadataProvider.GetMemberMetadata(node.Member); Debug.Assert(!string.IsNullOrEmpty(meta?.MemberName), "!string.IsNullOrEmpty(meta?.MemberName)"); this.usedScopeMembers.Add(meta?.MemberName ?? node.Member.Name); } if (this.result.Length > pos) { this.result.Write('.'); } var propInfo = node.Member as PropertyInfo; if (propInfo?.DeclaringType != null && node.Type == typeof(int) && node.Member.Name == "Count" && TypeHelpers.IsListType(propInfo.DeclaringType)) { this.result.Write("length"); } else { var meta = metadataProvider.GetMemberMetadata(node.Member); Debug.Assert(!string.IsNullOrEmpty(meta?.MemberName), "!string.IsNullOrEmpty(meta?.MemberName)"); this.result.Write(meta?.MemberName); } return(node); } }
public override void ConvertToJavascript(JavascriptConversionContext context) { var methodCall = context.Node as MethodCallExpression; if (methodCall != null) { if (methodCall.Method.DeclaringType == typeof(Math)) { NameLength jsInfo; if (membersMap.TryGetValue(methodCall.Method.Name, out jsInfo)) { if (methodCall.Arguments.Count == jsInfo.length) { using (context.Operation(JavascriptOperationTypes.Call)) { using (context.Operation(JavascriptOperationTypes.IndexerProperty)) context.Write("Math." + jsInfo.name); context.WriteManyIsolated('(', ')', ',', methodCall.Arguments); } return; } else if (methodCall.Method.Name == "Log" && methodCall.Arguments.Count == 2) { // JavaScript does not support `Math.log` with 2 parameters, // But it is easy enough for us to give a little help! using (context.Operation(JavascriptOperationTypes.MulDivMod)) { using (context.Operation(JavascriptOperationTypes.Call)) { using (context.Operation(JavascriptOperationTypes.IndexerProperty)) context.Write("Math.log"); context.Write('('); using (context.Operation(0)) context.Write(methodCall.Arguments[0]); context.Write(')'); } context.Write('/'); using (context.Operation(JavascriptOperationTypes.Call)) { using (context.Operation(JavascriptOperationTypes.IndexerProperty)) context.Write("Math.log"); context.Write('('); using (context.Operation(0)) context.Write(methodCall.Arguments[1]); context.Write(')'); } } return; } else if (methodCall.Method.Name == "Round" && methodCall.Arguments.Count == 2 && TypeHelpers.IsNumericType(methodCall.Arguments[1].Type)) { // We won't support `Math.Round` with two parameters by default. // To do it, we'd have to repeat an input value in the expression (unacceptable): // Math.Round(A, B) => Math.round(A * Math.pow(10, B)) / Math.pow(10, B) // Or start helping with hacky things (acceptable, but not by default): // Math.Round(A, B) => (function(a, b) { return Math.round(a * b) / b; })(A, Math.pow(10, B)); if (this.round2) { using (context.Operation(JavascriptOperationTypes.Call)) { context.WriteLambda <Func <double, double, double> >((a, b) => Math.Round(a * b) / b); context.Write('('); using (context.Operation(0)) context.Write(methodCall.Arguments[0]); context.Write(','); using (context.Operation(0)) using (context.Operation(JavascriptOperationTypes.Call)) { using (context.Operation(JavascriptOperationTypes.IndexerProperty)) context.Write("Math.pow"); context.Write('('); context.Write("10"); context.Write(','); using (context.Operation(0)) context.Write(methodCall.Arguments[1]); context.Write(')'); } context.Write(')'); return; } } } } } } // E and PI are constant values, they will never result in // a member access expression. We will have to catch the // exact numbers, and convert them instead. var constVal = context.Node as ConstantExpression; if (constVal != null) { if (constVal.Value.Equals(Math.E)) { using (context.Operation(JavascriptOperationTypes.IndexerProperty)) context.Write("Math.E"); } else if (constVal.Value.Equals(Math.PI)) { using (context.Operation(JavascriptOperationTypes.IndexerProperty)) context.Write("Math.PI"); } } }