You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by ok...@apache.org on 2017/11/06 16:26:41 UTC

[03/14] tinkerpop git commit: Make Gremlin.Net graph traversal API type-safe TINKERPOP-1752

Make Gremlin.Net graph traversal API type-safe TINKERPOP-1752

All steps are now type-safe and the original argument names from
Gremlin-Java are used. However, we currently don't support some Java
types like Comparator. Those were simply replaced by object until we
find a better solution. A problem of this workaround is that some
overloads from Gremlin-Java are not supported in Gremlin.Net as they
would result in the same method signature.
This required to change how Bindings work as Bindings.Of() can no longer
return a Binding object. The implementation for Bindings is now
basically the same as in Gremlin-Java.

This also revealed a bug in the tests that called the WithoutStrategies
source step with objects of strategies instead of just with their types.
However, WithoutStrategies still can't work right now as a GraphSON
serializer is missing for Type.


Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo
Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/b8a278c6
Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/b8a278c6
Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/b8a278c6

Branch: refs/heads/TINKERPOP-1802
Commit: b8a278c6ce1ab7337fd6563cbf43c2f152c4aac4
Parents: 9a69516
Author: Florian Hockmann <fh...@florian-hockmann.de>
Authored: Thu Aug 17 22:57:07 2017 +0200
Committer: florianhockmann <fh...@florian-hockmann.de>
Committed: Wed Sep 20 18:37:16 2017 +0200

----------------------------------------------------------------------
 gremlin-dotnet/glv/AnonymousTraversal.template  |   11 +-
 gremlin-dotnet/glv/GraphTraversal.template      |   12 +-
 .../glv/GraphTraversalSource.template           |   24 +-
 gremlin-dotnet/glv/generate.groovy              |  228 +++-
 .../Gremlin.Net/Process/Traversal/Bindings.cs   |   35 +-
 .../Gremlin.Net/Process/Traversal/Bytecode.cs   |   80 +-
 .../Process/Traversal/GraphTraversal.cs         | 1123 +++++++++++++++---
 .../Process/Traversal/GraphTraversalSource.cs   |   92 +-
 .../src/Gremlin.Net/Process/Traversal/__.cs     |  910 +++++++++++---
 .../BytecodeGenerationTests.cs                  |   14 +-
 .../BytecodeGeneration/StrategiesTests.cs       |   14 +-
 .../GraphTraversalTests.cs                      |   22 +-
 .../Process/Traversal/BytecodeTests.cs          |  142 ++-
 13 files changed, 2219 insertions(+), 488 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b8a278c6/gremlin-dotnet/glv/AnonymousTraversal.template
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/glv/AnonymousTraversal.template b/gremlin-dotnet/glv/AnonymousTraversal.template
index 9bc7257..6b1de9c 100644
--- a/gremlin-dotnet/glv/AnonymousTraversal.template
+++ b/gremlin-dotnet/glv/AnonymousTraversal.template
@@ -43,9 +43,16 @@ namespace Gremlin.Net.Process.Traversal
         /// <summary>
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the <%= method.methodName %> step to that traversal.
         /// </summary>
-        public static GraphTraversal<object, <%= method.t2 %>> <%= toCSharpMethodName.call(method.methodName) %><%= method.tParam %>(params object[] args)
+        public static GraphTraversal<object, <%= method.t2 %>> <%= toCSharpMethodName.call(method.methodName) %><%= method.tParam %>(<%= method.parameters %>)
         {
-            return new GraphTraversal<object, object>().<%= toCSharpMethodName.call(method.methodName) %><%= method.tParam %>(args);
+        <%  if (method.parameters.contains("params ")) {
+      %>    return <%= method.paramNames.last() %>.Length == 0
+                ? new GraphTraversal<object, <%= method.graphTraversalT2 %>>().<%= toCSharpMethodName.call(method.methodName) %><%= method.callGenericTypeArg %>(<%= method.paramNames.init().join(", ") %>)
+                : new GraphTraversal<object, <%= method.graphTraversalT2 %>>().<%= toCSharpMethodName.call(method.methodName) %><%= method.callGenericTypeArg %>(<%= method.paramNames.join(", ") %>);<%
+            }
+            else {
+      %>    return new GraphTraversal<object, <%= method.graphTraversalT2 %>>().<%= toCSharpMethodName.call(method.methodName) %><%= method.callGenericTypeArg %>(<%= method.paramNames.join(", ") %>);<%
+            } %>            
         }
 <% } %>
     }

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b8a278c6/gremlin-dotnet/glv/GraphTraversal.template
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/glv/GraphTraversal.template b/gremlin-dotnet/glv/GraphTraversal.template
index 5c3e03e..8d88fcb 100644
--- a/gremlin-dotnet/glv/GraphTraversal.template
+++ b/gremlin-dotnet/glv/GraphTraversal.template
@@ -65,9 +65,17 @@ namespace Gremlin.Net.Process.Traversal
         /// <summary>
         ///     Adds the <%= method.methodName %> step to this <see cref="GraphTraversal{SType, EType}" />.
         /// </summary>
-        public GraphTraversal< <%= method.t1 %> , <%= method.t2 %> > <%= toCSharpMethodName.call(method.methodName) %><%= method.tParam %> (params object[] args)
+        public GraphTraversal< <%= method.t1 %> , <%= method.t2 %> > <%= toCSharpMethodName.call(method.methodName) %><%= method.tParam %> (<%= method.parameters %>)
         {
-            Bytecode.AddStep("<%= method.methodName %>", args);
+        <%  if (method.parameters.contains("params ")) {
+      %>    var args = new List<object> {<%= method.paramNames.init().join(", ") %>};
+            args.AddRange(<%= method.paramNames.last() %>);
+            Bytecode.AddStep("<%= method.methodName %>", args.ToArray());<%
+            }
+            else {
+      %>    Bytecode.AddStep("<%= method.methodName %>"<% if (method.parameters) out << ', '+ method.paramNames.join(", ") %>);<%
+            }
+        %>
             return Wrap< <%= method.t1 %> , <%= method.t2 %> >(this);
         }
 <% } %>

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b8a278c6/gremlin-dotnet/glv/GraphTraversalSource.template
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/glv/GraphTraversalSource.template b/gremlin-dotnet/glv/GraphTraversalSource.template
index 0d98433..b67dfd7 100644
--- a/gremlin-dotnet/glv/GraphTraversalSource.template
+++ b/gremlin-dotnet/glv/GraphTraversalSource.template
@@ -72,11 +72,19 @@ namespace Gremlin.Net.Process.Traversal
         }
 
 <% sourceStepMethods.each{ method -> %>
-        public GraphTraversalSource <%= toCSharpMethodName.call(method) %>(params object[] args)
+        public GraphTraversalSource <%= toCSharpMethodName.call(method.methodName) %>(<%= method.parameters %>)
         {
             var source = new GraphTraversalSource(new List<ITraversalStrategy>(TraversalStrategies),
                                                   new Bytecode(Bytecode));
-            source.Bytecode.AddSource("<%= method %>", args);
+            <%  if (method.parameters.contains("params ")) {
+          %>var args = new List<object> {<%= method.paramNames.init().join(", ") %>};
+            args.AddRange(<%= method.paramNames.last() %>);
+            source.Bytecode.AddSource("<%= method.methodName %>", args.ToArray());<%
+            }
+            else {
+          %>source.Bytecode.AddSource("<%= method.methodName %>"<% if (method.parameters) out << ', '+ method.paramNames.join(", ") %>);<%
+            }
+        %>
             return source;
         }
 <% } %>
@@ -119,10 +127,18 @@ namespace Gremlin.Net.Process.Traversal
         ///     Spawns a <see cref="GraphTraversal{SType, EType}" /> off this graph traversal source and adds the <%= method.methodName %> step to that
         ///     traversal.
         /// </summary>
-        public GraphTraversal< <%= method.typeArguments.join(",") %> > <%= toCSharpMethodName.call(method.methodName) %>(params object[] args)
+        public GraphTraversal< <%= method.typeArguments.join(",") %> > <%= toCSharpMethodName.call(method.methodName) %>(<%= method.parameters %>)
         {
             var traversal = new GraphTraversal< <%= method.typeArguments.join(",") %> >(TraversalStrategies, new Bytecode(Bytecode));
-            traversal.Bytecode.AddStep("<%= method.methodName %>", args);
+            <%  if (method.parameters.contains("params ")) {
+          %>var args = new List<object> {<%= method.paramNames.init().join(", ") %>};
+            args.AddRange(<%= method.paramNames.last() %>);
+            traversal.Bytecode.AddStep("<%= method.methodName %>", args.ToArray());<%
+            }
+            else {
+      %>    traversal.Bytecode.AddStep("<%= method.methodName %>"<% if (method.parameters) out << ', '+ method.paramNames.join(", ") %>);<%
+            }
+        %>
             return traversal;
         }
 <% } %>

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b8a278c6/gremlin-dotnet/glv/generate.groovy
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/glv/generate.groovy b/gremlin-dotnet/glv/generate.groovy
index 8f66c26..ad22116 100644
--- a/gremlin-dotnet/glv/generate.groovy
+++ b/gremlin-dotnet/glv/generate.groovy
@@ -29,15 +29,35 @@ import java.lang.reflect.Modifier
 def toCSharpTypeMap = ["Long": "long",
                        "Integer": "int",
                        "String": "string",
+                       "boolean": "bool",
                        "Object": "object",
+                       "String[]": "string[]",
+                       "Object[]": "object[]",
+                       "Class": "Type",
+                       "Class[]": "Type[]",
                        "java.util.Map<java.lang.String, E2>": "IDictionary<string, E2>",
                        "java.util.Map<java.lang.String, B>": "IDictionary<string, E2>",
                        "java.util.List<E>": "IList<E>",
+                       "java.util.List<A>": "IList<E2>",
                        "java.util.Map<K, V>": "IDictionary<K, V>",
                        "java.util.Collection<E2>": "ICollection<E2>",
                        "java.util.Collection<B>": "ICollection<E2>",
                        "java.util.Map<K, java.lang.Long>": "IDictionary<K, long>",
-                       "TraversalMetrics": "E2"]
+                       "TraversalMetrics": "E2",
+                       "Traversal": "ITraversal",
+                       "Traversal[]": "ITraversal[]",
+                       "Predicate": "TraversalPredicate",
+                       "P": "TraversalPredicate",
+                       "TraversalStrategy": "ITraversalStrategy",
+                       "TraversalStrategy[]": "ITraversalStrategy[]",
+                       "Function": "object",
+                       "BiFunction": "object",
+                       "UnaryOperator": "object",
+                       "BinaryOperator": "object",
+                       "Consumer": "object",
+                       "Supplier": "object",
+                       "Comparator": "object",
+                       "VertexProgram": "object"]
 
 def useE2 = ["E2", "E2"];
 def methodsWithSpecificTypes = ["constant": useE2,
@@ -45,11 +65,9 @@ def methodsWithSpecificTypes = ["constant": useE2,
                                 "mean": useE2,
                                 "optional": useE2,
                                 "range": useE2,
-                                "select": ["IDictionary<string, E2>", "E2"],
                                 "sum": useE2,
                                 "tail": useE2,
-                                "tree": ["object"],
-                                "unfold": useE2]
+                                "unfold": useE2]                                                        
 
 def getCSharpGenericTypeParam = { typeName ->
     def tParam = ""
@@ -75,7 +93,7 @@ def toCSharpType = { name ->
 
 def toCSharpMethodName = { symbol -> (String) Character.toUpperCase(symbol.charAt(0)) + symbol.substring(1) }
 
-def getJavaParameterTypeNames = { method ->
+def getJavaGenericTypeParameterTypeNames = { method ->
     def typeArguments = method.genericReturnType.actualTypeArguments;
     return typeArguments.
             collect { (it instanceof Class) ? ((Class)it).simpleName : it.typeName }.
@@ -90,6 +108,89 @@ def getJavaParameterTypeNames = { method ->
             }
 }
 
+def getJavaParameterTypeNames = { method ->
+    return method.parameters.
+            collect { param ->
+                param.type.simpleName
+            } 
+}
+
+def toCSharpParamString = { param ->
+    csharpParamTypeName = toCSharpType(param.type.simpleName)
+    "${csharpParamTypeName} ${param.name}"
+    }
+
+def getJavaParamTypeString = { method ->
+    getJavaParameterTypeNames(method).join(",")
+}
+
+def getCSharpParamTypeString = { method ->
+    return method.parameters.
+            collect { param ->
+                toCSharpType(param.type.simpleName)
+            }.join(",")
+}
+
+def getCSharpParamString = { method ->
+    def parameters = method.parameters;
+    if (parameters.length == 0)
+        return ""        
+    def csharpParameters = parameters.
+            init().
+            collect { param ->
+                toCSharpParamString(param)
+            };
+    def lastCSharpParam = "";
+    if (method.isVarArgs())
+        lastCSharpParam += "params ";
+    lastCSharpParam += toCSharpParamString(parameters.last())
+    csharpParameters += lastCSharpParam
+    csharpParamString = csharpParameters.join(", ")
+    csharpParamString
+}
+
+def getParamNames = { parameters ->
+    return parameters.
+        collect { param ->
+            param.name
+        }
+}
+
+def hasMethodNoGenericCounterPartInGraphTraversal = { method ->
+    def parameterTypeNames = getJavaParameterTypeNames(method)
+    if (method.name.equals("fold")) {
+        return parameterTypeNames.size() == 0
+    }
+    if (method.name.equals("limit")) {
+        if (parameterTypeNames.size() == 1) {
+            return parameterTypeNames[0].equals("long")
+        }
+    }
+    if (method.name.equals("range")) {
+        if (parameterTypeNames.size() == 2) {
+            return parameterTypeNames[0].equals("long") && parameterTypeNames[1].equals("long")
+        }
+    }
+    if (method.name.equals("tail")) {
+        if (parameterTypeNames.size() == 0) {
+            return true
+        }
+        if (parameterTypeNames.size() == 1) {
+            return parameterTypeNames[0].equals("long")
+        }
+    }
+    return false
+}
+
+def t2withSpecialGraphTraversalt2 = ["IList<E2>": "E2"]
+
+def getGraphTraversalT2ForT2 = { t2 ->
+    if (t2withSpecialGraphTraversalt2.containsKey(t2)) {
+        return t2withSpecialGraphTraversalt2.get(t2)
+    }
+    return t2
+}
+
 def binding = ["pmethods": P.class.getMethods().
         findAll { Modifier.isStatic(it.getModifiers()) }.
         findAll { P.class.isAssignableFrom(it.returnType) }.
@@ -97,54 +198,69 @@ def binding = ["pmethods": P.class.getMethods().
         unique().
         sort { a, b -> a <=> b },
                "sourceStepMethods": GraphTraversalSource.getMethods(). // SOURCE STEPS
-                       findAll { GraphTraversalSource.class.equals(it.returnType) }.
-                       findAll {
-                           !it.name.equals("clone") &&
-                                   !it.name.equals(TraversalSource.Symbols.withBindings) &&
-                                   !it.name.equals(TraversalSource.Symbols.withRemote) &&
-                                   !it.name.equals(TraversalSource.Symbols.withComputer)
-                       }.
-                       collect { it.name }.
-                       unique().
-                       sort { a, b -> a <=> b },
+                        findAll { GraphTraversalSource.class.equals(it.returnType) }.
+                        findAll {
+                            !it.name.equals("clone") &&
+                                    !it.name.equals(TraversalSource.Symbols.withBindings) &&
+                                    !it.name.equals(TraversalSource.Symbols.withRemote) &&
+                                    !it.name.equals(TraversalSource.Symbols.withComputer)
+                        }.
+                // Select unique combination of C# parameter types and sort by Java parameter type combination
+                        sort { a, b -> a.name <=> b.name ?: getJavaParamTypeString(a) <=> getJavaParamTypeString(b) }.
+                        unique { a,b -> a.name <=> b.name ?: getCSharpParamTypeString(a) <=> getCSharpParamTypeString(b) }.
+                        collect { javaMethod ->
+                            def parameters = getCSharpParamString(javaMethod)
+                            def paramNames = getParamNames(javaMethod.parameters)
+                            return ["methodName": javaMethod.name, "parameters":parameters, "paramNames":paramNames]
+                        },
                "sourceSpawnMethods": GraphTraversalSource.getMethods(). // SPAWN STEPS
-                       findAll { GraphTraversal.class.equals(it.returnType) && !it.name.equals('inject')}.
-                       collect { [methodName: it.name, typeArguments: it.genericReturnType.actualTypeArguments.collect{t -> ((java.lang.Class)t).simpleName}] }.
-                       unique().
-                       sort { a, b -> a.methodName <=> b.methodName },
+                        findAll { GraphTraversal.class.equals(it.returnType) && !it.name.equals('inject')}.          
+                // Select unique combination of C# parameter types and sort by Java parameter type combination                                                                    
+                        sort { a, b -> a.name <=> b.name ?: getJavaParamTypeString(a) <=> getJavaParamTypeString(b) }.
+                        unique { a,b -> a.name <=> b.name ?: getCSharpParamTypeString(a) <=> getCSharpParamTypeString(b) }.
+                        collect { javaMethod ->
+                            def typeArguments = javaMethod.genericReturnType.actualTypeArguments.collect{t -> ((java.lang.Class)t).simpleName}
+                            def parameters = getCSharpParamString(javaMethod)
+                            def paramNames = getParamNames(javaMethod.parameters)
+                            return ["methodName": javaMethod.name, "typeArguments": typeArguments, "parameters":parameters, "paramNames":paramNames]
+                        },
                "graphStepMethods": GraphTraversal.getMethods().
-                       findAll { GraphTraversal.class.equals(it.returnType) }.
-                       findAll { !it.name.equals("clone") && !it.name.equals("iterate") }.
-                       groupBy { it.name }.
-               // Select unique by name, with the most amount of parameters
-                       collect { it.value.sort { a, b -> b.parameterCount <=> a.parameterCount }.first() }.
-                       sort { a, b -> a.name <=> b.name }.
-                       collect { javaMethod ->
-                           def typeNames = getJavaParameterTypeNames(javaMethod)
-                           def t1 = toCSharpType(typeNames[0])
-                           def t2 = toCSharpType(typeNames[1])
-                           def tParam = getCSharpGenericTypeParam(t2)
-                           return ["methodName": javaMethod.name, "t1":t1, "t2":t2, "tParam":tParam]
-                       },
+                        findAll { GraphTraversal.class.equals(it.returnType) }.
+                        findAll { !it.name.equals("clone") && !it.name.equals("iterate") }.
+                // Select unique combination of C# parameter types and sort by Java parameter type combination
+                        sort { a, b -> a.name <=> b.name ?: getJavaParamTypeString(a) <=> getJavaParamTypeString(b) }.
+                        unique { a,b -> a.name <=> b.name ?: getCSharpParamTypeString(a) <=> getCSharpParamTypeString(b) }.
+                        collect { javaMethod ->
+                            def typeNames = getJavaGenericTypeParameterTypeNames(javaMethod)
+                            def t1 = toCSharpType(typeNames[0])
+                            def t2 = toCSharpType(typeNames[1])
+                            def tParam = getCSharpGenericTypeParam(t2)
+                            def parameters = getCSharpParamString(javaMethod)
+                            def paramNames = getParamNames(javaMethod.parameters)
+                            return ["methodName": javaMethod.name, "t1":t1, "t2":t2, "tParam":tParam, "parameters":parameters, "paramNames":paramNames]
+                        },
                "anonStepMethods": __.class.getMethods().
-                       findAll { GraphTraversal.class.equals(it.returnType) }.
-                       findAll { Modifier.isStatic(it.getModifiers()) }.
-                       findAll { !it.name.equals("__") && !it.name.equals("start") }.
-                       groupBy { it.name }.
-               // Select unique by name, with the most amount of parameters
-                       collect { it.value.sort { a, b -> b.parameterCount <=> a.parameterCount }.first() }.
-                       sort { it.name }.
-                       collect { javaMethod ->
-                           def typeNames = getJavaParameterTypeNames(javaMethod)
-                           def t2 = toCSharpType(typeNames[1])
-                           def tParam = getCSharpGenericTypeParam(t2)
-                           def specificTypes = methodsWithSpecificTypes.get(javaMethod.name)
-                           if (specificTypes) {
-                               t2 = specificTypes[0]
-                               tParam = specificTypes.size() > 1 ? "<" + specificTypes[1] + ">" : ""
-                           }
-                           return ["methodName": javaMethod.name, "t2":t2, "tParam":tParam]
-                       },
+                        findAll { GraphTraversal.class.equals(it.returnType) }.
+                        findAll { Modifier.isStatic(it.getModifiers()) }.
+                        findAll { !it.name.equals("__") && !it.name.equals("start") }.
+                // Select unique combination of C# parameter types and sort by Java parameter type combination
+                        sort { a, b -> a.name <=> b.name ?: getJavaParamTypeString(a) <=> getJavaParamTypeString(b) }.
+                        unique { a,b -> a.name <=> b.name ?: getCSharpParamTypeString(a) <=> getCSharpParamTypeString(b) }.
+                        collect { javaMethod ->
+                            def typeNames = getJavaGenericTypeParameterTypeNames(javaMethod)
+                            def t2 = toCSharpType(typeNames[1])
+                            def tParam = getCSharpGenericTypeParam(t2)
+                            def specificTypes = methodsWithSpecificTypes.get(javaMethod.name)
+                            if (specificTypes) {
+                                t2 = specificTypes[0]
+                                tParam = specificTypes.size() > 1 ? "<" + specificTypes[1] + ">" : ""
+                            }
+                            def parameters = getCSharpParamString(javaMethod)
+                            def paramNames = getParamNames(javaMethod.parameters)
+                            def callGenericTypeArg = hasMethodNoGenericCounterPartInGraphTraversal(javaMethod) ? "" : tParam
+                            def graphTraversalT2 = getGraphTraversalT2ForT2(t2)
+                            return ["methodName": javaMethod.name, "t2":t2, "tParam":tParam, "parameters":parameters, "paramNames":paramNames, "callGenericTypeArg":callGenericTypeArg, "graphTraversalT2":graphTraversalT2]
+                        },
                "toCSharpMethodName": toCSharpMethodName]
 
 def engine = new groovy.text.GStringTemplateEngine()
@@ -176,12 +292,12 @@ def toCSharpName = { enumClass, itemName ->
 def createEnum = { enumClass, csharpToJava ->
     def b = ["enumClass": enumClass,
              "constants": enumClass.getEnumConstants().
-                     sort { a, b -> a.name() <=> b.name() }.
-                     collect { value ->
-                         def csharpName = toCSharpName(enumClass, value.name())
-                         csharpToJava.put(enumClass.simpleName + "." + csharpName, value.name())
-                         return csharpName
-                     }.join(",\n\t\t")]
+                    sort { a, b -> a.name() <=> b.name() }.
+                    collect { value ->
+                        def csharpName = toCSharpName(enumClass, value.name())
+                        csharpToJava.put(enumClass.simpleName + "." + csharpName, value.name())
+                        return csharpName
+                    }.join(",\n\t\t")]
 
     def enumTemplate = engine.createTemplate(new File("${projectBaseDir}/glv/Enum.template")).make(b)
     def enumFile = new File("${projectBaseDir}/src/Gremlin.Net/Process/Traversal/" + enumClass.getSimpleName() + ".cs")

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b8a278c6/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bindings.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bindings.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bindings.cs
index 985369e..2aa532b 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bindings.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bindings.cs
@@ -21,6 +21,9 @@
 
 #endregion
 
+using System.Collections.Generic;
+using System.Threading;
+
 namespace Gremlin.Net.Process.Traversal
 {
     /// <summary>
@@ -29,14 +32,42 @@ namespace Gremlin.Net.Process.Traversal
     public class Bindings
     {
         /// <summary>
+        ///     Gets an instance of the <see cref="Bindings" /> class.
+        /// </summary>
+        public static Bindings Instance { get; } = new Bindings();
+
+        private static readonly ThreadLocal<Dictionary<object, string>> BoundVariableByValue =
+            new ThreadLocal<Dictionary<object, string>>();
+
+        /// <summary>
         ///     Binds the variable to the specified value.
         /// </summary>
         /// <param name="variable">The variable to bind.</param>
         /// <param name="value">The value to which the variable should be bound.</param>
         /// <returns>The bound value.</returns>
-        public Binding Of(string variable, object value)
+        public TV Of<TV>(string variable, TV value)
+        {
+            var dict = BoundVariableByValue.Value;
+            if (dict == null)
+            {
+                dict = new Dictionary<object, string>();
+                BoundVariableByValue.Value = dict;
+            }
+            dict[value] = variable;
+            return value;
+        }
+
+        internal static string GetBoundVariable<TV>(TV value)
+        {
+            var dict = BoundVariableByValue.Value;
+            if (dict == null)
+                return null;
+            return !dict.ContainsKey(value) ? null : dict[value];
+        }
+
+        internal static void Clear()
         {
-            return new Binding(variable, value);
+            BoundVariableByValue.Value?.Clear();
         }
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/b8a278c6/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
index b76f395..e09c533 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Bytecode.cs
@@ -21,7 +21,10 @@
 
 #endregion
 
+using System;
+using System.Collections;
 using System.Collections.Generic;
+using System.Linq;
 
 namespace Gremlin.Net.Process.Traversal
 {
@@ -35,6 +38,8 @@ namespace Gremlin.Net.Process.Traversal
     /// </remarks>
     public class Bytecode
     {
+        private static readonly object[] EmptyArray = new object[0];
+
         /// <summary>
         ///     Initializes a new instance of the <see cref="Bytecode" /> class.
         /// </summary>
@@ -69,7 +74,8 @@ namespace Gremlin.Net.Process.Traversal
         /// <param name="args">The traversal source method arguments.</param>
         public void AddSource(string sourceName, params object[] args)
         {
-            SourceInstructions.Add(new Instruction(sourceName, args));
+            SourceInstructions.Add(new Instruction(sourceName, FlattenArguments(args)));
+            Bindings.Clear();
         }
 
         /// <summary>
@@ -79,7 +85,77 @@ namespace Gremlin.Net.Process.Traversal
         /// <param name="args">The traversal method arguments.</param>
         public void AddStep(string stepName, params object[] args)
         {
-            StepInstructions.Add(new Instruction(stepName, args));
+            StepInstructions.Add(new Instruction(stepName, FlattenArguments(args)));
+            Bindings.Clear();
+        }
+
+        private object[] FlattenArguments(object[] arguments)
+        {
+            if (arguments.Length == 0)
+                return EmptyArray;
+            var flatArguments = new List<object>();
+            foreach (var arg in arguments)
+            {
+                if (arg is object[] objects)
+                {
+                    flatArguments.AddRange(objects.Select(nestObject => ConvertArgument(nestObject, true)));
+                }
+                else
+                {
+                    flatArguments.Add(ConvertArgument(arg, true));
+                }
+            }
+            return flatArguments.ToArray();
+        }
+
+        private object ConvertArgument(object argument, bool searchBindings)
+        {
+            if (searchBindings)
+            {
+                var variable = Bindings.GetBoundVariable(argument);
+                if (variable != null)
+                    return new Binding(variable, ConvertArgument(argument, false));
+            }
+            if (IsDictionaryType(argument.GetType()))
+            {
+                var dict = new Dictionary<object, object>();
+                foreach (DictionaryEntry item in (IDictionary)argument)
+                {
+                    dict[ConvertArgument(item.Key, true)] = ConvertArgument(item.Value, true);
+                }
+                return dict;
+            }
+            if (IsListType(argument.GetType()))
+            {
+                var list = new List<object>(((IList) argument).Count);
+                list.AddRange(from object item in (IList) argument select ConvertArgument(item, true));
+                return list;
+            }
+            if (IsHashSetType(argument.GetType()))
+            {
+                var set = new HashSet<object>();
+                foreach (var item in (IEnumerable)argument)
+                {
+                    set.Add(ConvertArgument(item, true));
+                }
+                return set;
+            }
+            return argument;
+        }
+
+        private bool IsDictionaryType(Type type)
+        {
+            return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>);
+        }
+
+        private bool IsListType(Type type)
+        {
+            return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(List<>);
+        }
+
+        private bool IsHashSetType(Type type)
+        {
+            return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>);
         }
     }
 }
\ No newline at end of file