You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2016/02/26 20:36:24 UTC

[2/4] incubator-tinkerpop git commit: Allow ScriptEngine to accept scripts in interpreter mode.

Allow ScriptEngine to accept scripts in interpreter mode.

Scripts sent with variables normally considered "local" by use of the def keyword or the inclusion of a type will be treated as global variables when the InterpreterModeCustomizerProvider configuration is included in the configuration of the ScriptEngine.


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

Branch: refs/heads/tp31
Commit: 566c4889d05175057ecfa51ebf34cc9aa651b123
Parents: d0005b7
Author: Stephen Mallette <sp...@genoprime.com>
Authored: Thu Feb 18 10:47:28 2016 -0500
Committer: Stephen Mallette <sp...@genoprime.com>
Committed: Thu Feb 25 07:40:47 2016 -0500

----------------------------------------------------------------------
 CHANGELOG.asciidoc                              |   1 +
 .../jsr223/GremlinVariableAnalyzer.groovy       | 104 +++++++++++++++++
 .../groovy/jsr223/ast/InterpreterMode.groovy    |  30 +++++
 .../ast/InterpreterModeASTTransformation.groovy | 115 +++++++++++++++++++
 .../jsr223/GremlinGroovyScriptEngine.java       |  41 ++++++-
 .../InterpreterModeCustomizerProvider.java      |  37 ++++++
 .../jsr223/GremlinGroovyScriptEngineTest.java   |  27 +++++
 .../server/GremlinServerIntegrateTest.java      |  57 +++++++++
 8 files changed, 408 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/CHANGELOG.asciidoc
----------------------------------------------------------------------
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 6a57a60..b3a05fe 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -26,6 +26,7 @@ image::https://raw.githubusercontent.com/apache/incubator-tinkerpop/master/docs/
 TinkerPop 3.1.2 (NOT OFFICIALLY RELEASED YET)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+* Added "interpreter mode" for the `ScriptEngine` and Gremlin Server which allows variables defined with `def` or a type to be recognized as "global".
 * Bumped to Apache Groovy 2.4.6.
 * Added the `gremlin-archetype-server` archetype that demonstrates
 * Added the `gremlin-archetype-tinkergraph` archetype that demonstrates a basic project that uses TinkerGraph.

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinVariableAnalyzer.groovy
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinVariableAnalyzer.groovy b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinVariableAnalyzer.groovy
new file mode 100644
index 0000000..1d1bcbe
--- /dev/null
+++ b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinVariableAnalyzer.groovy
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+package org.apache.tinkerpop.gremlin.groovy.jsr223
+
+import org.codehaus.groovy.ast.DynamicVariable
+import org.codehaus.groovy.ast.GroovyClassVisitor
+import org.codehaus.groovy.ast.expr.VariableExpression
+import org.codehaus.groovy.control.CompilationUnit
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.control.Phases
+import org.codehaus.groovy.tools.shell.util.ScriptVariableAnalyzer
+
+import java.security.CodeSource
+
+/**
+ * An extension of Groovy's {@code VariableVisitor} that exposes the bound and unbound variables publicly. This
+ * class can likely be removed with the next update of Groovy (after 2.4.5) as the {code ScriptVariableAnalyzer} ends
+ * up exposing {@code getBoundVars() in such a way as to allow for the {@code ClassLoader} to be supplied.
+ */
+class GremlinVariableAnalyzer {
+    public static class GremlinVariableVisitor extends ScriptVariableAnalyzer.VariableVisitor {
+        String lastBound
+
+        @Override
+        void visitVariableExpression(VariableExpression expression) {
+            if (!(expression.variable in ['args', 'context', 'this', 'super'])) {
+                if (expression.accessedVariable instanceof DynamicVariable) {
+                    unbound << expression.variable
+                } else {
+                    bound << expression.variable
+                    lastBound = bound
+                }
+            }
+            super.visitVariableExpression(expression)
+        }
+
+        @Override
+        public Set<String> getBound() {
+            return super.getBound()
+        }
+
+        @Override
+        public Set<String> getUnbound() {
+            return super.getUnbound()
+        }
+    }
+
+    public static class GremlinVisitorClassLoader extends GroovyClassLoader {
+        private final GroovyClassVisitor visitor
+
+        public GremlinVisitorClassLoader(final GroovyClassVisitor visitor, ClassLoader parent) {
+            super(parent == null ?  Thread.currentThread().getContextClassLoader() : parent)
+            this.visitor = visitor
+        }
+
+        @Override
+        protected CompilationUnit createCompilationUnit(final CompilerConfiguration config, final CodeSource source) {
+            CompilationUnit cu = super.createCompilationUnit(config, source)
+            cu.addPhaseOperation(new ScriptVariableAnalyzer.VisitorSourceOperation(visitor), Phases.CLASS_GENERATION)
+            return cu
+        }
+    }
+
+    public static BoundVars getBoundVars(final String scriptText, ClassLoader parent) {
+        assert scriptText != null
+        final GroovyClassVisitor visitor = new GremlinVariableVisitor()
+        new GremlinVisitorClassLoader(visitor, parent).parseClass(scriptText)
+        return new BoundVars(visitor.getLastBound(), visitor.getBound())
+    }
+
+    public static class BoundVars {
+        private String lastBound;
+        private Set<String> bound;
+
+        BoundVars(String lastBound, Set<String> bound) {
+            this.lastBound = lastBound
+            this.bound = bound
+        }
+
+        String getLastBound() {
+            return lastBound
+        }
+
+        Set<String> getBound() {
+            return bound
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterMode.groovy
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterMode.groovy b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterMode.groovy
new file mode 100644
index 0000000..a8da72f
--- /dev/null
+++ b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterMode.groovy
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+package org.apache.tinkerpop.gremlin.groovy.jsr223.ast
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass
+
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+
+@Retention(RetentionPolicy.SOURCE)
+@GroovyASTTransformationClass(["org.apache.tinkerpop.gremlin.groovy.jsr223.ast.InterpreterModeASTTransformation"])
+public @interface InterpreterMode {
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterModeASTTransformation.groovy
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterModeASTTransformation.groovy b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterModeASTTransformation.groovy
new file mode 100644
index 0000000..66638b8
--- /dev/null
+++ b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/InterpreterModeASTTransformation.groovy
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+package org.apache.tinkerpop.gremlin.groovy.jsr223.ast
+
+import groovy.transform.CompileStatic
+import org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine
+import org.codehaus.groovy.ast.ASTNode
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.ast.MethodNode
+import org.codehaus.groovy.ast.Parameter
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.expr.BinaryExpression
+import org.codehaus.groovy.ast.expr.ConstantExpression
+import org.codehaus.groovy.ast.expr.DeclarationExpression
+import org.codehaus.groovy.ast.expr.MapExpression
+import org.codehaus.groovy.ast.expr.MethodCallExpression
+import org.codehaus.groovy.ast.expr.VariableExpression
+import org.codehaus.groovy.ast.stmt.BlockStatement
+import org.codehaus.groovy.ast.stmt.CatchStatement
+import org.codehaus.groovy.ast.stmt.EmptyStatement
+import org.codehaus.groovy.ast.stmt.ExpressionStatement
+import org.codehaus.groovy.ast.stmt.Statement
+import org.codehaus.groovy.ast.stmt.TryCatchStatement
+import org.codehaus.groovy.control.CompilePhase
+import org.codehaus.groovy.control.SourceUnit
+import org.codehaus.groovy.syntax.Token
+import org.codehaus.groovy.syntax.Types
+import org.codehaus.groovy.transform.ASTTransformation
+import org.codehaus.groovy.transform.GroovyASTTransformation
+
+/**
+ * An {@code ASTTransformation} that promotes "local" variables to global ones.  In this case, "local" refers to those
+ * variables that are defined in a script with "def" at the root of the script.  These would typically be interpreted
+ * as local to the script, but this transform changes that, by wrapping the entire script in a try/catch where such
+ * variables are written to a "hidden" {@link Map} so that the {@code ScriptEngine} can later access them to place
+ * them into the global context.
+ */
+@CompileStatic
+@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
+class InterpreterModeASTTransformation implements ASTTransformation {
+
+    @Override
+    void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
+        ClassNode scriptNode = (ClassNode) astNodes[1]
+        def runMethodOfScript = scriptNode.declaredMethodsMap["java.lang.Object run()"]
+        runMethodOfScript.code = wrap(runMethodOfScript)
+    }
+
+    private static BlockStatement wrap(MethodNode method) {
+        BlockStatement wrappedBlock = new BlockStatement()
+        BlockStatement existingBlock = ((BlockStatement) method.code)
+
+        // the variable names that will be written back to the global context
+        def variableNames = [] as Set<String>
+        variableNames.addAll(findTopLevelVariableDeclarations(existingBlock.statements))
+        method.variableScope.referencedClassVariablesIterator.each{variableNames << it.name}
+
+        // the map to hold the variables and values
+        wrappedBlock.addStatement(createGlobalMapAST())
+
+        // the finally block will capture all the vars in the "globals" map
+        BlockStatement finallyBlock = new BlockStatement()
+        variableNames.each {
+            finallyBlock.addStatement(createAssignToGlobalMapAST(it))
+        }
+
+        wrappedBlock.addStatement(new TryCatchStatement(existingBlock, finallyBlock))
+
+        return wrappedBlock
+    }
+
+    private static List<String> findTopLevelVariableDeclarations(def existingStatements) {
+        existingStatements.findAll{ it instanceof ExpressionStatement }
+                .collect{ ((ExpressionStatement) it).expression }
+                .findAll{ it instanceof DeclarationExpression}
+                .collect{ ((DeclarationExpression) it).leftExpression }
+                .collect{ ((VariableExpression) it).name }
+    }
+
+    private static Statement createAssignToGlobalMapAST(String varName) {
+        def tryCatch = new TryCatchStatement(new ExpressionStatement(
+                new MethodCallExpression(
+                        new VariableExpression(GremlinGroovyScriptEngine.COLLECTED_BOUND_VARS_MAP_VARNAME),
+                        "put",
+                        new ArgumentListExpression(new ConstantExpression(varName), new VariableExpression(varName)))), EmptyStatement.INSTANCE)
+
+        tryCatch.addCatch(new CatchStatement(new Parameter(ClassHelper.make(MissingPropertyException), "ex"), EmptyStatement.INSTANCE))
+        return tryCatch
+    }
+
+    private static Statement createGlobalMapAST() {
+        new ExpressionStatement(
+                new BinaryExpression(
+                        new VariableExpression(GremlinGroovyScriptEngine.COLLECTED_BOUND_VARS_MAP_VARNAME),
+                        Token.newSymbol(Types.EQUAL, 0, 0),
+                        new MapExpression()))
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
index 72e38c3..23240cb 100644
--- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
+++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngine.java
@@ -23,6 +23,7 @@ import org.apache.tinkerpop.gremlin.groovy.DefaultImportCustomizerProvider;
 import org.apache.tinkerpop.gremlin.groovy.EmptyImportCustomizerProvider;
 import org.apache.tinkerpop.gremlin.groovy.ImportCustomizerProvider;
 import org.apache.tinkerpop.gremlin.groovy.NoImportCustomizerProvider;
+import org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.InterpreterModeCustomizerProvider;
 import org.apache.tinkerpop.gremlin.groovy.loaders.GremlinLoader;
 import org.apache.tinkerpop.gremlin.groovy.plugin.Artifact;
 import org.apache.tinkerpop.gremlin.groovy.plugin.GremlinPlugin;
@@ -125,6 +126,12 @@ public class GremlinGroovyScriptEngine extends GroovyScriptEngineImpl implements
      */
     public static final String REFERENCE_TYPE_HARD = "hard";
 
+    /**
+     * Name of variable that holds local variables to be globally bound if "interpreter mode" is enabled with
+     * {@link InterpreterModeCustomizerProvider}.
+     */
+    public static final String COLLECTED_BOUND_VARS_MAP_VARNAME = "gremlin_script_engine_collected_boundvars";
+
     private static final Pattern patternImportStatic = Pattern.compile("\\Aimport\\sstatic.*");
 
     public static final ThreadLocal<Map<String, Object>> COMPILE_OPTIONS = new ThreadLocal<Map<String, Object>>(){
@@ -146,7 +153,7 @@ public class GremlinGroovyScriptEngine extends GroovyScriptEngineImpl implements
 
     private GremlinGroovyClassLoader loader;
 
-    private AtomicLong counter = new AtomicLong(0l);
+    private AtomicLong counter = new AtomicLong(0L);
 
     /**
      * The list of loaded plugins for the console.
@@ -164,6 +171,7 @@ public class GremlinGroovyScriptEngine extends GroovyScriptEngineImpl implements
     private final List<CompilerCustomizerProvider> customizerProviders;
 
     private final Set<Artifact> artifactsToUse = new HashSet<>();
+    private final boolean interpreterModeEnabled;
 
     /**
      * Creates a new instance using the {@link DefaultImportCustomizerProvider}.
@@ -193,9 +201,14 @@ public class GremlinGroovyScriptEngine extends GroovyScriptEngineImpl implements
                 .map(p -> (ImportCustomizerProvider) p)
                 .findFirst().orElse(NoImportCustomizerProvider.INSTANCE);
 
+        // determine if interpreter mode should be enabled
+        interpreterModeEnabled = providers.stream()
+                .anyMatch(p -> p.getClass().equals(InterpreterModeCustomizerProvider.class));
+
         // remove used providers as the rest will be applied directly
         customizerProviders = providers.stream()
-                .filter(p -> p != null && !(p instanceof ImportCustomizerProvider))
+                .filter(p -> p != null &&
+                             !((p instanceof ImportCustomizerProvider)))
                 .collect(Collectors.toList());
 
         createClassLoader();
@@ -531,7 +544,28 @@ public class GremlinGroovyScriptEngine extends GroovyScriptEngineImpl implements
                     }
                 }
             });
-            return scriptObject.run();
+
+            final Object o = scriptObject.run();
+
+            // if interpreter mode is enable then local vars of the script are promoted to engine scope bindings.
+            if (interpreterModeEnabled) {
+                final Map<String, Object> localVars = (Map<String, Object>) context.getAttribute(COLLECTED_BOUND_VARS_MAP_VARNAME);
+                if (localVars != null) {
+                    localVars.entrySet().forEach(e -> {
+                        // closures need to be cached for later use
+                        if (e.getValue() instanceof Closure)
+                            globalClosures.put(e.getKey(), (Closure) e.getValue());
+
+                        context.setAttribute(e.getKey(), e.getValue(), ScriptContext.ENGINE_SCOPE);
+                    });
+
+                    // get rid of the temporary collected vars
+                    context.removeAttribute(COLLECTED_BOUND_VARS_MAP_VARNAME, ScriptContext.ENGINE_SCOPE);
+                    localVars.clear();
+                }
+            }
+
+            return o;
         } catch (Exception e) {
             throw new ScriptException(e);
         }
@@ -642,5 +676,4 @@ public class GremlinGroovyScriptEngine extends GroovyScriptEngineImpl implements
         }
         return buf.toString();
     }
-
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/customizer/InterpreterModeCustomizerProvider.java
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/customizer/InterpreterModeCustomizerProvider.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/customizer/InterpreterModeCustomizerProvider.java
new file mode 100644
index 0000000..e3f95f4
--- /dev/null
+++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/customizer/InterpreterModeCustomizerProvider.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+package org.apache.tinkerpop.gremlin.groovy.jsr223.customizer;
+
+import org.apache.tinkerpop.gremlin.groovy.CompilerCustomizerProvider;
+import org.apache.tinkerpop.gremlin.groovy.jsr223.ast.InterpreterMode;
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
+import org.codehaus.groovy.control.customizers.CompilationCustomizer;
+
+/**
+ * Places the {@code ScriptEngine} in "interpreter mode" where local variables of a script are treated as global
+ * bindings. This implementation is technically not a true {@link CompilerCustomizerProvider} instance as the
+ * "interpreter mode" feature does not require a {@code CompilerCustomizer}. This class merely acts as a flag that
+ * tells the {@link org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine} to turn this feature on.
+ */
+public class InterpreterModeCustomizerProvider implements CompilerCustomizerProvider {
+    @Override
+    public CompilationCustomizer create() {
+        return new ASTTransformationCustomizer(InterpreterMode.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
----------------------------------------------------------------------
diff --git a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
index 4740cd2..17cd809 100644
--- a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
+++ b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyScriptEngineTest.java
@@ -22,6 +22,7 @@ import groovy.lang.Closure;
 import org.apache.commons.lang3.concurrent.BasicThreadFactory;
 import org.apache.tinkerpop.gremlin.groovy.CompilerCustomizerProvider;
 import org.apache.tinkerpop.gremlin.groovy.NoImportCustomizerProvider;
+import org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.InterpreterModeCustomizerProvider;
 import org.apache.tinkerpop.gremlin.structure.Vertex;
 import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
 import org.javatuples.Pair;
@@ -85,7 +86,33 @@ public class GremlinGroovyScriptEngineTest {
     @Test
     public void shouldEvalWithNoBindings() throws Exception {
         final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine();
+        engine.eval("def addItUp(x,y){x+y}");
         assertEquals(3, engine.eval("1+2"));
+        assertEquals(3, engine.eval("addItUp(1,2)"));
+    }
+
+    @Test
+    public void shouldPromoteDefinedVarsInInterpreterModeWithNoBindings() throws Exception {
+        final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(new InterpreterModeCustomizerProvider());
+        engine.eval("def addItUp = { x, y -> x + y }");
+        assertEquals(3, engine.eval("int xxx = 1 + 2"));
+        assertEquals(4, engine.eval("yyy = xxx + 1"));
+        assertEquals(7, engine.eval("def zzz = yyy + xxx"));
+        assertEquals(4, engine.eval("zzz - xxx"));
+        assertEquals(10, engine.eval("addItUp(zzz,xxx)"));
+    }
+
+    @Test
+    public void shouldPromoteDefinedVarsInInterpreterModeWithBindings() throws Exception {
+        final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(new InterpreterModeCustomizerProvider());
+        final Bindings b = new SimpleBindings();
+        b.put("x", 2);
+        engine.eval("def addItUp = { x, y -> x + y }", b);
+        assertEquals(3, engine.eval("int xxx = 1 + x", b));
+        assertEquals(4, engine.eval("yyy = xxx + 1", b));
+        assertEquals(7, engine.eval("def zzz = yyy + xxx", b));
+        assertEquals(4, engine.eval("zzz - xxx", b));
+        assertEquals(10, engine.eval("addItUp(zzz,xxx)", b));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-tinkerpop/blob/566c4889/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
----------------------------------------------------------------------
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
index 7714a93..61c1a98 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
@@ -32,6 +32,7 @@ import org.apache.tinkerpop.gremlin.driver.simple.SimpleClient;
 import org.apache.tinkerpop.gremlin.driver.simple.WebSocketClient;
 import org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine;
 import org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.CompileStaticCustomizerProvider;
+import org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.InterpreterModeCustomizerProvider;
 import org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.SimpleSandboxExtension;
 import org.apache.tinkerpop.gremlin.groovy.jsr223.customizer.TimedInterruptCustomizerProvider;
 import org.apache.tinkerpop.gremlin.structure.T;
@@ -45,6 +46,7 @@ import org.junit.Test;
 
 import java.nio.channels.ClosedChannelException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -136,6 +138,9 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
             case "shouldUseSimpleSandbox":
                 settings.scriptEngines.get("gremlin-groovy").config = getScriptEngineConfForSimpleSandbox();
                 break;
+            case "shouldUseInterpreterMode":
+                settings.scriptEngines.get("gremlin-groovy").config = getScriptEngineConfForInterpreterMode();
+                break;
             case "shouldReceiveFailureTimeOutOnScriptEvalOfOutOfControlLoop":
                 settings.scriptEngines.get("gremlin-groovy").config = getScriptEngineConfForTimedInterrupt();
                 break;
@@ -164,6 +169,58 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
         return scriptEngineConf;
     }
 
+    private static Map<String, Object> getScriptEngineConfForInterpreterMode() {
+        final Map<String,Object> scriptEngineConf = new HashMap<>();
+        final Map<String,Object> interpreterProviderConf = new HashMap<>();
+        interpreterProviderConf.put(InterpreterModeCustomizerProvider.class.getName(), Collections.EMPTY_LIST);
+        scriptEngineConf.put("compilerCustomizerProviders", interpreterProviderConf);
+        return scriptEngineConf;
+    }
+
+    @Test
+    public void shouldUseInterpreterMode() throws Exception {
+        final Cluster cluster = Cluster.open();
+        final Client client = cluster.connect(name.getMethodName());
+
+        client.submit("def subtractAway(x,y){x-y};[]").all().get();
+        client.submit("multiplyIt = { x,y -> x * y};[]").all().get();
+
+        assertEquals(2, client.submit("x = 1 + 1").all().get().get(0).getInt());
+        assertEquals(3, client.submit("int y = x + 1").all().get().get(0).getInt());
+        assertEquals(5, client.submit("def z = x + y").all().get().get(0).getInt());
+
+        final Map<String,Object> m = new HashMap<>();
+        m.put("x", 10);
+        assertEquals(-5, client.submit("z - x", m).all().get().get(0).getInt());
+        assertEquals(15, client.submit("addItUp(x,z)", m).all().get().get(0).getInt());
+        assertEquals(5, client.submit("subtractAway(x,z)", m).all().get().get(0).getInt());
+        assertEquals(50, client.submit("multiplyIt(x,z)", m).all().get().get(0).getInt());
+
+        cluster.close();
+    }
+
+    @Test
+    public void shouldNotUseInterpreterMode() throws Exception {
+        final Cluster cluster = Cluster.open();
+        final Client client = cluster.connect(name.getMethodName());
+
+        client.submit("def subtractAway(x,y){x-y};[]").all().get();
+        client.submit("multiplyIt = { x,y -> x * y};[]").all().get();
+
+        assertEquals(2, client.submit("x = 1 + 1").all().get().get(0).getInt());
+        assertEquals(3, client.submit("y = x + 1").all().get().get(0).getInt());
+        assertEquals(5, client.submit("z = x + y").all().get().get(0).getInt());
+
+        final Map<String,Object> m = new HashMap<>();
+        m.put("x", 10);
+        assertEquals(-5, client.submit("z - x", m).all().get().get(0).getInt());
+        assertEquals(15, client.submit("addItUp(x,z)", m).all().get().get(0).getInt());
+        assertEquals(5, client.submit("subtractAway(x,z)", m).all().get().get(0).getInt());
+        assertEquals(50, client.submit("multiplyIt(x,z)", m).all().get().get(0).getInt());
+
+        cluster.close();
+    }
+
     @Test
     public void shouldUseSimpleSandbox() throws Exception {
         final Cluster cluster = Cluster.open();