You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by fl...@apache.org on 2022/03/23 11:25:59 UTC

[tinkerpop] branch TINKERPOP-2367 updated (3c52ff9 -> 2ed7de1)

This is an automated email from the ASF dual-hosted git repository.

florianhockmann pushed a change to branch TINKERPOP-2367
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git.


 discard 3c52ff9  Finished adding tests from gremlin-python. Everything should be working now, except for WithOptions
 discard 7aa1843  WIP Start adding tests copied from gremlin-python
 discard 8bd7a32  TINKERPOP-2367 Add first version of a translator .NET -> Groovy
     new 2ed7de1  TINKERPOP-2367 Add a GroovyTranslator to .NET

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (3c52ff9)
            \
             N -- N -- N   refs/heads/TINKERPOP-2367 (2ed7de1)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../process/traversal/step/util/WithOptions.java   |  2 +-
 .../Traversal/Translator/GroovyTranslator.cs       | 47 +++++++++++++--
 .../Gremlin.Net/Process/Traversal/WithOptions.cs   | 70 ++++++++++++----------
 .../Traversal/Translator/GroovyTranslatorTests.cs  | 40 ++++++++-----
 4 files changed, 107 insertions(+), 52 deletions(-)

[tinkerpop] 01/01: TINKERPOP-2367 Add a GroovyTranslator to .NET

Posted by fl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

florianhockmann pushed a commit to branch TINKERPOP-2367
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 2ed7de10f22f79cfcd4e96ca3993459259fe7c04
Author: Florian Hockmann <fh...@florian-hockmann.de>
AuthorDate: Wed Mar 23 12:22:59 2022 +0100

    TINKERPOP-2367 Add a GroovyTranslator to .NET
---
 .../process/traversal/step/util/WithOptions.java   |   2 +-
 .../Gremlin.Net/Process/Traversal/EnumWrapper.cs   |   3 +
 .../Traversal/Translator/GroovyTranslator.cs       | 192 ++++++++
 .../Gremlin.Net/Process/Traversal/WithOptions.cs   |  70 +--
 .../Traversal/Translator/GroovyTranslatorTests.cs  | 494 +++++++++++++++++++++
 5 files changed, 730 insertions(+), 31 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java
index 8e23976..43671c8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/WithOptions.java
@@ -63,7 +63,7 @@ public class WithOptions {
     public static int keys = 4;
 
     /**
-     * Include keys (affects all {@link VertexProperty} value maps).
+     * Include values (affects all {@link VertexProperty} value maps).
      */
     public static int values = 8;
 
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/EnumWrapper.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/EnumWrapper.cs
index 20a0348..ac0185c 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/EnumWrapper.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/EnumWrapper.cs
@@ -77,5 +77,8 @@ namespace Gremlin.Net.Process.Traversal
                        (EnumValue != null ? EnumValue.GetHashCode() : 0);
             }
         }
+
+        /// <inheritdoc />
+        public override string ToString() => $"{EnumName}.{EnumValue}";
     }
 }
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Translator/GroovyTranslator.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Translator/GroovyTranslator.cs
new file mode 100644
index 0000000..4781ed9
--- /dev/null
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Translator/GroovyTranslator.cs
@@ -0,0 +1,192 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Gremlin.Net.Process.Traversal.Step.Util;
+using Gremlin.Net.Process.Traversal.Strategy;
+using Gremlin.Net.Process.Traversal.Strategy.Decoration;
+
+namespace Gremlin.Net.Process.Traversal.Translator
+{
+    /// <summary>
+    ///     Converts bytecode to a Groovy string of Gremlin.
+    /// </summary>
+    public class GroovyTranslator
+    {
+        private GroovyTranslator(string traversalSource)
+        {
+            TraversalSource = traversalSource;
+        }
+
+        /// <summary>
+        ///     Creates the translator.
+        /// </summary>
+        /// <param name="traversalSource">The traversal source for the traversal to be translated.</param>
+        /// <returns>The created translator instance.</returns>
+        public static GroovyTranslator Of(string traversalSource)
+        {
+            return new GroovyTranslator(traversalSource);
+        }
+
+        /// <summary>
+        ///     Get the language that the translator is converting the traversal byte code to.
+        /// </summary>
+        public string TargetLanguage => "gremlin-groovy";
+
+        /// <summary>
+        ///     Gets the <see cref="TraversalSource"/> representation rooting this translator.
+        ///     This is typically a "g".
+        /// </summary>
+        public string TraversalSource { get; }
+
+        /// <summary>
+        ///     Translate <see cref="Bytecode"/> into gremlin-groovy.
+        /// </summary>
+        /// <param name="bytecode">The bytecode representing traversal source and traversal manipulations.</param>
+        /// <param name="isChildTraversal">Whether this is an anonymous traversal (started via '__').</param>
+        /// <returns>The translated gremlin-groovy traversal.</returns>
+        public string Translate(Bytecode bytecode, bool isChildTraversal = false)
+        {
+            var sb = new StringBuilder(isChildTraversal ? "__" : TraversalSource);
+            
+            foreach (var step in bytecode.SourceInstructions)
+            {
+                sb.Append(TranslateStep(step));
+            }
+            
+            foreach (var step in bytecode.StepInstructions)
+            {
+                sb.Append(TranslateStep(step));
+            }
+
+            return sb.ToString();
+        }
+
+        private string TranslateStep(Instruction step)
+        {
+            if (step.OperatorName == "with")
+            {
+                return $".{step.OperatorName}({TranslateWithArguments(step.Arguments)})";
+            }
+            return $".{step.OperatorName}({TranslateArguments(step.Arguments)})";
+        }
+        
+        private string TranslateWithArguments(IEnumerable<object> arguments) =>
+            string.Join(", ", arguments.Select(TranslateWithArgument));
+
+        private string TranslateWithArgument(object argument)
+        {
+            if (WithOptionsTranslation.ContainsKey(argument))
+            {
+                return WithOptionsTranslation[argument];
+            }
+
+            return TranslateArgument(argument);
+        }
+
+        private static readonly Dictionary<object, string> WithOptionsTranslation = new Dictionary<object, string>
+        {
+            { WithOptions.Tokens, "WithOptions.tokens" },
+            { WithOptions.None, "WithOptions.none" },
+            { WithOptions.Ids, "WithOptions.ids" },
+            { WithOptions.Labels, "WithOptions.labels" },
+            { WithOptions.Keys, "WithOptions.keys" },
+            { WithOptions.Values, "WithOptions.values" },
+            { WithOptions.All, "WithOptions.all" },
+            { WithOptions.Indexer, "WithOptions.indexer" },
+            { WithOptions.List, "WithOptions.list" },
+            { WithOptions.Map, "WithOptions.map" }
+        };
+
+        private string TranslateArguments(IEnumerable<object> arguments) =>
+            string.Join(", ", arguments.Select(TranslateArgument));
+
+        private string TranslateArgument(object argument)
+        {
+            return argument switch
+            {
+                null => "null",
+                string str => $"'{str}'",
+                char c => $"'{c}'",
+                bool b => b ? "true" : "false",
+                DateTimeOffset dto => TranslateDateTimeOffset(dto),
+                DateTime dt => TranslateDateTimeOffset(dt),
+                Guid guid => $"UUID.fromString('{guid}')",
+                P p => TranslateP(p),
+                IDictionary dict => TranslateDictionary(dict),
+                IEnumerable e => TranslateCollection(e),
+                ITraversal t => TranslateTraversal(t),
+                AbstractTraversalStrategy strategy => TranslateStrategy(strategy),
+                _ => Convert.ToString(argument, CultureInfo.InvariantCulture)
+            };
+        }
+        
+        private static string TranslateDateTimeOffset(DateTimeOffset dto)
+        {
+            var year = dto.Year - 1900;
+            var month = dto.Month - 1;
+            var dayOfMonth = dto.Day;
+            var hour = dto.Hour;
+            var minute = dto.Minute;
+            var second = dto.Second;
+            return $"new Date({year}, {month}, {dayOfMonth}, {hour}, {minute}, {second})";
+        }
+        
+        private string TranslateP(P p)
+        {
+            return p.Other == null
+                ? $"P.{p.OperatorName}({TranslateArgument(p.Value)})"
+                : $"P.{p.OperatorName}({TranslateArgument(p.Value)}, {TranslateArgument(p.Other)})";
+        }
+        
+        private string TranslateDictionary(IDictionary dict)
+        {
+            var kvStrings = new List<string>(dict.Count);
+            foreach (DictionaryEntry kv in dict)
+            {
+                kvStrings.Add($"{TranslateArgument(kv.Key)}: {TranslateArgument(kv.Value)}");
+            }            
+
+            return $"[{string.Join(", ", kvStrings)}]";
+        }
+
+        private string TranslateCollection(IEnumerable enumerable) =>
+            $"[{TranslateArguments(enumerable.Cast<object>().ToArray())}]";
+        
+        private string TranslateTraversal(ITraversal traversal) => Translate(traversal.Bytecode, true);
+
+        private string TranslateStrategy(AbstractTraversalStrategy strategy)
+        {
+            var config = string.Join(", ",
+                strategy.Configuration.Select(opt =>
+                    $"{(strategy.StrategyName == nameof(OptionsStrategy) ? $"'{opt.Key}'" : opt.Key)}: {TranslateArgument(opt.Value)}"));
+
+            return $"new {strategy.StrategyName}({config})";
+        }
+    }
+}
\ No newline at end of file
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/WithOptions.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/WithOptions.cs
index 0229d4c..0875480 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/WithOptions.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/WithOptions.cs
@@ -21,53 +21,63 @@
 
 #endregion
 
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
+using Gremlin.Net.Structure;
 
 namespace Gremlin.Net.Process.Traversal.Step.Util
 {
-#pragma warning disable 1591
-
     /// <summary>
     ///     Configuration options to be passed to the <c>With()</c> modulator.
     /// </summary>
     public class WithOptions
     {
-
-
+        /// <summary>
+        ///     Configures the tokens to be included in value maps.
+        /// </summary>
         public static readonly string Tokens = "~tinkerpop.valueMap.tokens";
-
-
+        
+        /// <summary>
+        ///     Include no tokens.
+        /// </summary>
         public static readonly int None = 0;
-
-
+        
+        /// <summary>
+        ///     Include ids (affects all <see cref="Element" /> value maps).
+        /// </summary>
         public static readonly int Ids = 1;
-
-
+        
+        /// <summary>
+        ///     Include labels (affects all <see cref="Vertex" /> and <see cref="Edge" /> value maps).
+        /// </summary>
         public static readonly int Labels = 2;
-
-
+        
+        /// <summary>
+        ///     Include keys (affects all <see cref="VertexProperty" /> value maps).
+        /// </summary>
         public static readonly int Keys = 4;
-
-
+        
+        /// <summary>
+        ///     Include values (affects all <see cref="VertexProperty" /> value maps).
+        /// </summary>
         public static readonly int Values = 8;
-
-
+        
+        /// <summary>
+        ///     Include all tokens;
+        /// </summary>
         public static readonly int All = 15;
-
-
+        
+        /// <summary>
+        ///     Configures the indexer to be used in <see cref="GraphTraversal{S,E}.Index{E2}" />.
+        /// </summary>
         public static readonly string Indexer = "~tinkerpop.index.indexer";
-
-
+        
+        /// <summary>
+        ///     Index items using 2-item lists.
+        /// </summary>
         public static readonly int List = 0;
-
-
+        
+        /// <summary>
+        ///     Index items using a map.
+        /// </summary>
         public static readonly int Map = 1;
-
     }
-
-#pragma warning restore 1591
 }
diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/Translator/GroovyTranslatorTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/Translator/GroovyTranslatorTests.cs
new file mode 100644
index 0000000..0a75af9
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/Translator/GroovyTranslatorTests.cs
@@ -0,0 +1,494 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using Gremlin.Net.Process.Traversal;
+using Gremlin.Net.Process.Traversal.Step.Util;
+using Gremlin.Net.Process.Traversal.Strategy.Decoration;
+using Gremlin.Net.Process.Traversal.Strategy.Verification;
+using Gremlin.Net.Process.Traversal.Translator;
+using Gremlin.Net.Structure;
+using Xunit;
+
+namespace Gremlin.Net.UnitTest.Process.Traversal.Translator;
+
+public class GroovyTranslatorTests
+{
+    private readonly GraphTraversalSource _g = AnonymousTraversalSource.Traversal();
+    
+    [Fact]
+    public void ShouldTranslateStepsWithSingleArguments()
+    {
+        var translator = GroovyTranslator.Of("g");
+
+        var translated = translator.Translate(_g.V().Values<string>("name").Bytecode);
+        
+        Assert.Equal("g.V().values('name')", translated);
+    }
+    
+    [Fact]
+    public void ShouldTranslateStepsWithMultipleArguments()
+    {
+        var translator = GroovyTranslator.Of("g");
+
+        var translated = translator.Translate(_g.V().Values<string>("name", "age").Bytecode);
+        
+        Assert.Equal("g.V().values('name', 'age')", translated);
+    }
+
+    [Fact]
+    public void ShouldTranslateNullArgument()
+    {
+        AssertTranslation("null", null);
+    }
+    
+    [Theory]
+    [InlineData("3, 5", 3, 5)]
+    [InlineData("3.2, 5.1", 3.2, 5.1)]
+    [InlineData("'c'", 'c')]
+    [InlineData("true", true)]
+    [InlineData("false", false)]
+    public void ShouldTranslateSimpleTypes(string expectedGroovy, params object[] simpleTypes)
+    {
+        AssertTranslation(expectedGroovy, simpleTypes);
+    }
+
+    [Fact]
+    public void ShouldTranslateDateTimeOffsetArgument()
+    {
+        AssertTranslation("new Date(122, 11, 30, 12, 0, 1)", DateTimeOffset.Parse("2022-12-30T12:00:01Z"));
+    }
+    
+    [Fact]
+    public void ShouldTranslateDateTimeArgument()
+    {
+        AssertTranslation("new Date(122, 11, 30, 12, 0, 1)", DateTime.Parse("2022-12-30T12:00:01"));
+    }
+
+    [Fact]
+    public void ShouldTranslateGuid()
+    {
+        AssertTranslation("UUID.fromString('ffffffff-fd49-1e4b-0000-00000d4b8a1d')",
+            Guid.Parse("ffffffff-fd49-1e4b-0000-00000d4b8a1d"));
+    }
+
+    [Fact]
+    public void ShouldTranslateCollection()
+    {
+        AssertTranslation("['test1', 'test2']", new List<string>{"test1", "test2"});
+    }
+
+    [Fact]
+    public void ShouldTranslateDictionary()
+    {
+        var dictionary = new Dictionary<object, object>
+        {
+            { "key1", "value1" },
+            { 1, "value2" }
+        };
+        AssertTranslation("['key1': 'value1', 1: 'value2']", dictionary);
+    }
+
+    [Fact]
+    public void ShouldTranslateColumn()
+    {
+        AssertTranslation("Column.keys", Column.Keys);
+    }
+    
+    [Fact]
+    public void ShouldTranslateDirection()
+    {
+        AssertTranslation("Direction.BOTH", Direction.Both);
+    }
+    
+    [Fact]
+    public void ShouldTranslateOrder()
+    {
+        AssertTranslation("Order.desc", Order.Desc);
+    }
+    
+    [Fact]
+    public void ShouldTranslatePop()
+    {
+        AssertTranslation("Pop.last", Pop.Last);
+    }
+    
+    [Fact]
+    public void ShouldTranslateScope()
+    {
+        AssertTranslation("Scope.local", Scope.Local);
+    }
+
+    [Fact]
+    public void ShouldTranslateP()
+    {
+        AssertTranslation("P.and(P.gt(20), P.lt(30))", P.Gt(20).And(P.Lt(30)));
+    }
+
+    [Fact]
+    public void ShouldTranslatePBetween()
+    {
+        AssertTranslation("P.between([20, 30])", P.Between(20, 30));
+    }
+    
+    [Fact]
+    public void ShouldTranslateGraphTraversalOptions()
+    {
+        AssertTraversalTranslation("g.V().valueMap().with(WithOptions.tokens).V()",
+            _g.V().ValueMap<object, object>().With(WithOptions.Tokens).V());
+    }
+
+    [Fact]
+    public void ShouldTranslateGraphTraversalSourceOptions()
+    {
+        AssertTraversalTranslation("g.withStrategies(new OptionsStrategy('~tinkerpop.valueMap.tokens': true)).V()",
+            _g.With(WithOptions.Tokens).V());
+    }
+    
+    [Fact]
+    public void TranslationTest()
+    {
+        var expectedGroovyScriptByTraversal = new Dictionary<ITraversal, string>
+        {
+            { _g.V(), "g.V()" },
+            { _g.V("1", "2", "3", "4"), "g.V('1', '2', '3', '4')" },
+            { _g.V("3").ValueMap<object, object>(true), "g.V('3').valueMap(true)" },
+            { _g.V().Constant(5), "g.V().constant(5)" },
+            { _g.V().Constant(1.5), "g.V().constant(1.5)" },
+            { _g.V().Constant("Hello"), "g.V().constant('Hello')" },
+            { _g.V().HasLabel("airport").Limit<Vertex>(5), "g.V().hasLabel('airport').limit(5)" },
+            {
+                _g.V().HasLabel(P.Within(new List<string> { "a", "b", "c" })),
+                "g.V().hasLabel(P.within(['a', 'b', 'c']))"
+            },
+            {
+                _g.V().HasLabel("airport", "continent").Out().Limit<Vertex>(5),
+                "g.V().hasLabel('airport', 'continent').out().limit(5)"
+            },
+            {
+                _g.V().HasLabel("airport").Out().Values<string>("code").Limit<Vertex>(5),
+                "g.V().hasLabel('airport').out().values('code').limit(5)"
+            },
+            {
+                _g.V("3").As("a").Out("route").Limit<Vertex>(10).Where(P.Eq("a")).By("region"),
+                "g.V('3').as('a').out('route').limit(10).where(P.eq('a')).by('region')"
+            },
+            {
+                _g.V("3").Repeat(__.Out("route").SimplePath()).Times(2).Path().By("code"),
+                "g.V('3').repeat(__.out('route').simplePath()).times(2).path().by('code')"
+            },
+            {
+                _g.V().HasLabel("airport").Out().Has("region", "US-TX").Values<string>("code").Limit<string>(5),
+                "g.V().hasLabel('airport').out().has('region', 'US-TX').values('code').limit(5)"
+            },
+            {
+                _g.V().HasLabel("airport").Union<string>(__.Values<string>("city"), __.Values<string>("region"))
+                    .Limit<string>(5),
+                "g.V().hasLabel('airport').union(__.values('city'), __.values('region')).limit(5)"
+            },
+            { _g.V("3").As("a").Out("route", "routes"), "g.V('3').as('a').out('route', 'routes')" },
+            { _g.V().Where(__.Values<int>("runways").Is(5)), "g.V().where(__.values('runways').is(5))" },
+            {
+                _g.V("3").Repeat(__.Out().SimplePath()).Until(__.Has("code", "AGR")).Path().By("code").Limit<Path>(5),
+                "g.V('3').repeat(__.out().simplePath()).until(__.has('code', 'AGR')).path().by('code').limit(5)"
+            },
+            { _g.V().HasLabel("airport").Order().By(__.Id()), "g.V().hasLabel('airport').order().by(__.id())" },
+            { _g.V().HasLabel("airport").Order().By(T.Id), "g.V().hasLabel('airport').order().by(T.id)" },
+            {
+                _g.V().HasLabel("airport").Order().By(__.Id(), Order.Desc),
+                "g.V().hasLabel('airport').order().by(__.id(), Order.desc)"
+            },
+            {
+                _g.V().HasLabel("airport").Order().By("code", Order.Desc),
+                "g.V().hasLabel('airport').order().by('code', Order.desc)"
+            },
+            {
+                _g.V("1", "2", "3").Local<object>(__.Out().Out().Dedup().Fold()),
+                "g.V('1', '2', '3').local(__.out().out().dedup().fold())"
+            },
+            { _g.V("3").Out().Path().Count(Scope.Local), "g.V('3').out().path().count(Scope.local)" },
+            { _g.E().Count(), "g.E().count()" },
+            { _g.V('5').OutE("route").InV().Path().Limit<Path>(10), "g.V('5').outE('route').inV().path().limit(10)" },
+            {
+                _g.V('5').PropertyMap<object>().Select<object>(Column.Keys),
+                "g.V('5').propertyMap().select(Column.keys)"
+            },
+            {
+                _g.V('5').PropertyMap<object>().Select<object>(Column.Values),
+                "g.V('5').propertyMap().select(Column.values)"
+            },
+            { _g.V("3").Values<string>("runways").Math("_ + 1"), "g.V('3').values('runways').math('_ + 1')" },
+            {
+                _g.V("3").Emit().Repeat(__.Out().SimplePath()).Times(3).Limit<Vertex>(5).Path(),
+                "g.V('3').emit().repeat(__.out().simplePath()).times(3).limit(5).path()"
+            },
+            {
+                _g.V().Match<object>(__.As("a").Has("code", "LHR").As("b")).Select<object>("b").By("code"),
+                "g.V().match(__.as('a').has('code', 'LHR').as('b')).select('b').by('code')"
+            },
+            {
+                _g.V().Has("test-using-keyword-as-property", "repeat"),
+                "g.V().has('test-using-keyword-as-property', 'repeat')"
+            },
+            { _g.V('1').AddE("test").To(__.V('4')), "g.V('1').addE('test').to(__.V('4'))" },
+            { _g.V().Values<int>("runways").Max<int>(), "g.V().values('runways').max()" },
+            { _g.V().Values<int>("runways").Min<int>(), "g.V().values('runways').min()" },
+            { _g.V().Values<int>("runways").Sum<int>(), "g.V().values('runways').sum()" },
+            { _g.V().Values<int>("runways").Mean<double>(), "g.V().values('runways').mean()" },
+            {
+                _g.WithSack(0).V('3', '5').Sack(Operator.Sum).By("runways").Sack<object>(),
+                "g.withSack(0).V('3', '5').sack(Operator.sum).by('runways').sack()"
+            },
+            {
+                _g.V("3").Values<object>("runways").Store("x").V('4').Values<object>("runways").Store("x")
+                    .By(__.Constant(1)).V('6').Store("x").By(__.Constant(1)).Select<object>("x").Unfold<object>()
+                    .Sum<object>(),
+                "g.V('3').values('runways').store('x').V('4').values('runways').store('x').by(__.constant(1)).V('6').store('x').by(__.constant(1)).select('x').unfold().sum()"
+            },
+            { _g.Inject(3, 4, 5), "g.inject(3, 4, 5)" },
+            { _g.Inject(new List<int> { 3, 4, 5 }), "g.inject([3, 4, 5])" },
+            { _g.Inject(3, 4, 5).Count(), "g.inject(3, 4, 5).count()" },
+            { _g.V().Has("runways", P.Gt(5)).Count(), "g.V().has('runways', P.gt(5)).count()" },
+            { _g.V().Has("runways", P.Lte(5.3)).Count(), "g.V().has('runways', P.lte(5.3)).count()" },
+            { _g.V().Has("code", P.Within(new List<int> { 123, 124 })), "g.V().has('code', P.within([123, 124]))" },
+            {
+                _g.V('1', '2').Has("region", P.Within(new List<string> { "US-TX", "US-GA" })),
+                "g.V('1', '2').has('region', P.within(['US-TX', 'US-GA']))"
+            },
+            {
+                _g.V().And(__.Has("runways", P.Gt(5)), __.Has("region", "US-TX")),
+                "g.V().and(__.has('runways', P.gt(5)), __.has('region', 'US-TX'))"
+            },
+            {
+                _g.V().Union<object>(__.Has("runways", P.Gt(5)), __.Has("region", "US-TX")),
+                "g.V().union(__.has('runways', P.gt(5)), __.has('region', 'US-TX'))"
+            },
+            {
+                _g.V("3").Choose<object>(__.Values<object>("runways").Is(3), __.Constant("three"),
+                    __.Constant("not three")),
+                "g.V('3').choose(__.values('runways').is(3), __.constant('three'), __.constant('not three'))"
+            },
+            {
+                _g.V("3").Choose<object>(__.Values<object>("runways")).Option(1, __.Constant("three"))
+                    .Option(2, __.Constant("not three")),
+                "g.V('3').choose(__.values('runways')).option(1, __.constant('three')).option(2, __.constant('not three'))"
+            },
+            {
+                _g.V("3").Choose<object>(__.Values<object>("runways")).Option(1.5, __.Constant("one and a half"))
+                    .Option(2, __.Constant("not three")),
+                "g.V('3').choose(__.values('runways')).option(1.5, __.constant('one and a half')).option(2, __.constant('not three'))"
+            },
+            {
+                _g.V("3").Repeat(__.Out().SimplePath()).Until(__.Loops().Is(1)).Count(),
+                "g.V('3').repeat(__.out().simplePath()).until(__.loops().is(1)).count()"
+            },
+            {
+                _g.V().HasLabel("airport").Limit<Vertex>(20).Group<Vertex, object>().By("region").By("code")
+                    .Order(Scope.Local).By(Column.Keys),
+                "g.V().hasLabel('airport').limit(20).group().by('region').by('code').order(Scope.local).by(Column.keys)"
+            },
+            {
+                _g.V('1').As("a").V("2").As("a").Select<object>(Pop.All, "a"),
+                "g.V('1').as('a').V('2').as('a').select(Pop.all, 'a')"
+            },
+            {
+                _g.AddV("test").Property(Cardinality.Set, "p1", 10),
+                "g.addV('test').property(Cardinality.set, 'p1', 10)"
+            },
+            {
+                _g.AddV("test").Property(Cardinality.List, "p1", 10),
+                "g.addV('test').property(Cardinality.list, 'p1', 10)"
+            },
+
+            {
+                _g.AddV("test").Property(Cardinality.Single, "p1", 10),
+                "g.addV('test').property(Cardinality.single, 'p1', 10)"
+            },
+            { _g.V().Limit<Vertex>(5).Order().By(T.Label), "g.V().limit(5).order().by(T.label)" },
+
+            { _g.V().Range<Vertex>(1, 5), "g.V().range(1, 5)" },
+
+            { _g.AddV("test").Property("p1", 123), "g.addV('test').property('p1', 123)" },
+
+            {
+                _g.AddV("test").Property("date", new DateTime(2021, 3, 1, 9, 30, 0)),
+                "g.addV('test').property('date', new Date(121, 2, 1, 9, 30, 0))"
+            },
+            {
+                _g.AddV("test").Property("date", new DateTime(2021, 3, 1, 0, 0, 0)),
+                "g.addV('test').property('date', new Date(121, 2, 1, 0, 0, 0))"
+            },
+            { _g.AddE("route").From(__.V('1')).To(__.V('2')), "g.addE('route').from(__.V('1')).to(__.V('2'))" },
+            {
+                _g.WithSideEffect("a", new List<int> { 1, 2 }).V('3').Select<object>("a"),
+                "g.withSideEffect('a', [1, 2]).V('3').select('a')"
+            },
+            { _g.WithSideEffect("a", 1).V('3').Select<object>("a"), "g.withSideEffect('a', 1).V('3').select('a')" },
+            {
+                _g.WithSideEffect("a", "abc").V("3").Select<object>("a"),
+                "g.withSideEffect('a', 'abc').V('3').select('a')"
+            },
+            {
+                _g.V().Has("airport", "region", "US-NM").Limit<Vertex>(3).Values<object>("elev").Fold().Index<object>(),
+                "g.V().has('airport', 'region', 'US-NM').limit(3).values('elev').fold().index()"
+            },
+            {
+                _g.V("3").Repeat(__.TimeLimit(1000).Out().SimplePath()).Until(__.Has("code", "AGR")).Path(),
+                "g.V('3').repeat(__.timeLimit(1000).out().simplePath()).until(__.has('code', 'AGR')).path()"
+            },
+            {
+                _g.V().HasLabel("airport").Where(__.Values<object>("elev").Is(P.Gt(14000))),
+                "g.V().hasLabel('airport').where(__.values('elev').is(P.gt(14000)))"
+            },
+            {
+                _g.V().HasLabel("airport").Where(__.Out().Count().Is(P.Gt(250))).Values<object>("code"),
+                "g.V().hasLabel('airport').where(__.out().count().is(P.gt(250))).values('code')"
+            },
+            {
+                _g.V().HasLabel("airport").Filter(__.Out().Count().Is(P.Gt(250))).Values<object>("code"),
+                "g.V().hasLabel('airport').filter(__.out().count().is(P.gt(250))).values('code')"
+            },
+            {
+                _g.WithSack(0).V('3').Repeat(__.OutE("route").Sack(Operator.Sum).By("dist").InV())
+                    .Until(__.Has("code", "AGR").Or().Loops().Is(4)).Has("code", "AGR")
+                    .Local<object>(__.Union<object>(__.Path().By("code").By("dist"), __.Sack<object>()).Fold())
+                    .Limit<object>(10),
+                "g.withSack(0).V('3').repeat(__.outE('route').sack(Operator.sum).by('dist').inV()).until(__.has('code', 'AGR').or().loops().is(4)).has('code', 'AGR').local(__.union(__.path().by('code').by('dist'), __.sack()).fold()).limit(10)"
+            },
+            {
+                _g.AddV().As("a").AddV().As("b").AddE("knows").From("a").To("b"),
+                "g.addV().as('a').addV().as('b').addE('knows').from('a').to('b')"
+            },
+            {
+                _g.AddV("Person").As("a").AddV("Person").As("b").AddE("knows").From("a").To("b"),
+                "g.addV('Person').as('a').addV('Person').as('b').addE('knows').from('a').to('b')"
+            },
+            {
+                _g.V("3").Project<object>("Out", "In").By(__.Out().Count()).By(__.In().Count()),
+                "g.V('3').project('Out', 'In').by(__.out().count()).by(__.in().count())"
+            },
+            {
+                _g.V("44").Out().Aggregate("a").Out().Where(P.Within(new List<string> { "a" })).Path(),
+                "g.V('44').out().aggregate('a').out().where(P.within(['a'])).path()"
+            },
+            { _g.V().Has("date", new DateTime(2021, 3, 22)), "g.V().has('date', new Date(121, 2, 22, 0, 0, 0))" },
+            {
+                _g.V().Has("date", P.Within(new DateTime(2021, 3, 22, 0, 0, 0), new DateTime(2021, 2, 1, 0, 0, 0))),
+                "g.V().has('date', P.within([new Date(121, 2, 22, 0, 0, 0), new Date(121, 1, 1, 0, 0, 0)]))"
+            },
+            {
+                _g.V().Has("date", P.Between(new DateTime(2021, 2, 1, 0, 0, 0), new DateTime(2021, 3, 22, 0, 0, 0))),
+                "g.V().has('date', P.between([new Date(121, 1, 1, 0, 0, 0), new Date(121, 2, 22, 0, 0, 0)]))"
+            },
+            {
+                _g.V().Has("date", P.Inside(new DateTime(2021, 2, 1, 0, 0, 0), new DateTime(2021, 3, 22, 0, 0, 0))),
+                "g.V().has('date', P.inside([new Date(121, 1, 1, 0, 0, 0), new Date(121, 2, 22, 0, 0, 0)]))"
+            },
+            {
+                _g.V().Has("date", P.Gt(new DateTime(2021, 2, 1, 9, 30, 0))),
+                "g.V().has('date', P.gt(new Date(121, 1, 1, 9, 30, 0)))"
+            },
+            { _g.V().Has("runways", P.Between(3, 5)), "g.V().has('runways', P.between([3, 5]))" },
+            { _g.V().Has("runways", P.Inside(3, 5)), "g.V().has('runways', P.inside([3, 5]))" },
+            { _g.V("44").OutE().ElementMap<object>(), "g.V('44').outE().elementMap()" },
+            { _g.V("44").ValueMap<object, object>().By(__.Unfold<object>()), "g.V('44').valueMap().by(__.unfold())" },
+
+            // TODO: Support WithOptions
+            {
+                _g.V("44").ValueMap<object, object>().With(WithOptions.Tokens, WithOptions.Labels),
+                "g.V('44').valueMap().with(WithOptions.tokens, WithOptions.labels)"
+            },
+            {
+                _g.V("44").ValueMap<object, object>().With(WithOptions.Tokens),
+                "g.V('44').valueMap().with(WithOptions.tokens)"
+            },
+            {
+                _g.WithStrategies(new ReadOnlyStrategy()).AddV("test"),
+                "g.withStrategies(new ReadOnlyStrategy()).addV('test')"
+            },
+            {
+                _g.WithStrategies(
+                    new SubgraphStrategy(vertices: __.Has("region", "US-TX"), edges: __.HasLabel("route"))).V().Count(),
+                "g.withStrategies(new SubgraphStrategy(vertices: __.has('region', 'US-TX'), edges: __.hasLabel('route'))).V().count()"
+            },
+            {
+                _g.WithStrategies(new SubgraphStrategy(vertexProperties: __.HasNot("runways"))).V().Count(),
+                "g.withStrategies(new SubgraphStrategy(vertexProperties: __.hasNot('runways'))).V().count()"
+            },
+            {
+                _g.WithStrategies(new SubgraphStrategy(vertices: __.Has("region", "US-TX"),
+                    vertexProperties: __.HasNot("runways"))).V().Count(),
+                "g.withStrategies(new SubgraphStrategy(vertices: __.has('region', 'US-TX'), vertexProperties: __.hasNot('runways'))).V().count()"
+            },
+            {
+                _g.WithStrategies(new ReadOnlyStrategy(),
+                    new SubgraphStrategy(vertices: __.Has("region", "US-TX"), edges: __.HasLabel("route"))).V().Count(),
+                "g.withStrategies(new ReadOnlyStrategy(), new SubgraphStrategy(vertices: __.has('region', 'US-TX'), edges: __.hasLabel('route'))).V().count()"
+            },
+            {
+                _g.WithStrategies(new ReadOnlyStrategy(), new SubgraphStrategy(vertices: __.Has("region", "US-TX"))).V()
+                    .Count(),
+                "g.withStrategies(new ReadOnlyStrategy(), new SubgraphStrategy(vertices: __.has('region', 'US-TX'))).V().count()"
+            },
+            {
+                _g.With("evaluationTimeout", 500).V().Count(),
+                "g.withStrategies(new OptionsStrategy('evaluationTimeout': 500)).V().count()"
+            },
+            {
+                _g.WithStrategies(new OptionsStrategy(new Dictionary<string, object> { { "evaluationTimeout", 500 } }))
+                    .V().Count(),
+                "g.withStrategies(new OptionsStrategy('evaluationTimeout': 500)).V().count()"
+            },
+            {
+                _g.WithStrategies(new PartitionStrategy(partitionKey: "partition", writePartition: "a",
+                    readPartitions: new List<string> { "a" })).AddV("test"),
+                "g.withStrategies(new PartitionStrategy(partitionKey: 'partition', writePartition: 'a', readPartitions: ['a'])).addV('test')"
+            },
+            {
+                _g.WithStrategies(new VertexProgramStrategy()).V().ShortestPath()
+                    .With(ShortestPath.target, __.Has("name", "peter")),
+                "g.withStrategies(new VertexProgramStrategy()).V().shortestPath().with('~tinkerpop.shortestPath.target', __.has('name', 'peter'))"
+            }
+        };
+
+        foreach (var ( traversal, expectedGroovyScript) in expectedGroovyScriptByTraversal)
+        {
+            AssertTraversalTranslation(expectedGroovyScript, traversal);
+        }
+    }
+
+    private void AssertTranslation(string expectedTranslation, params object[] objs)
+    {
+        AssertTraversalTranslation($"g.inject({expectedTranslation})", _g.Inject(objs));
+    }
+    
+    private static void AssertTraversalTranslation(string expectedTranslation, ITraversal traversal)
+    {
+        var translator = GroovyTranslator.Of("g");
+        
+        var translated = translator.Translate(traversal.Bytecode);
+        
+        Assert.Equal(expectedTranslation, translated);
+    }
+}
\ No newline at end of file