Update Javascript GLV Address feedback and provide maven integration: - Reorganize gremlin-javascript into node.js project - Simplify javascript code generators - Generate package.json to match project version - Introduce Promise factory for third-party promise library integration (ie: bluebird) - Use toList() and next() traversal methods - Include Remote connection implementation - Fix enum casing - Use Maven nodejs plugin - .gitignore and .npmignore at gremlin-javascript level - Run integration tests using a gremlin-server instance - Add gremlin-javascript doc in gremlin-variants.asciidoc
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/2b4e8e3b Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/2b4e8e3b Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/2b4e8e3b Branch: refs/heads/TINKERPOP-1489 Commit: 2b4e8e3b517098dd29b3cfd1ed48f066eb120d66 Parents: e94d415 Author: Jorge Bay Gondra <jorgebaygon...@gmail.com> Authored: Tue Jun 6 15:05:14 2017 +0200 Committer: Jorge Bay Gondra <jorgebaygon...@gmail.com> Committed: Fri Jan 19 09:30:14 2018 +0100 ---------------------------------------------------------------------- docs/src/reference/gremlin-variants.asciidoc | 75 +- gremlin-javascript/pom.xml | 171 +- .../javascript/GenerateGremlinJavascript.groovy | 36 + .../GraphTraversalSourceGenerator.groovy | 226 +- .../javascript/PackageJsonGenerator.groovy | 72 + .../gremlin/javascript/SymbolHelper.groovy | 50 + .../javascript/TraversalSourceGenerator.groovy | 437 ++-- .../javascript/GenerateGremlinJavascript.java | 32 - .../gremlin/javascript/jsr223/SymbolHelper.java | 59 - .../javascript/gremlin-javascript/.gitignore | 5 + .../javascript/gremlin-javascript/.npmignore | 26 + .../javascript/gremlin-javascript/README.md | 39 + .../driver/remote-connection.js | 107 - .../main/javascript/gremlin-javascript/index.js | 113 +- .../lib/driver/driver-remote-connection.js | 200 ++ .../lib/driver/remote-connection.js | 84 + .../gremlin-javascript/lib/process/bytecode.js | 99 + .../lib/process/graph-traversal.js | 2095 ++++++++++++++++++ .../lib/process/traversal-strategy.js | 88 + .../gremlin-javascript/lib/process/traversal.js | 236 ++ .../gremlin-javascript/lib/structure/graph.js | 138 ++ .../lib/structure/io/graph-serializer.js | 398 ++++ .../javascript/gremlin-javascript/lib/utils.js | 62 + .../javascript/gremlin-javascript/package.json | 36 + .../process/graph-traversal.js | 2016 ----------------- .../gremlin-javascript/process/traversal.js | 392 ---- .../gremlin-javascript/structure/graph.js | 157 -- .../structure/io/graph-serializer.js | 415 ---- .../test/integration/remote-connection-tests.js | 64 + .../test/integration/traversal-test.js | 71 + .../test/unit/exports-test.js | 73 + .../test/unit/graphson-test.js | 112 + .../test/unit/traversal-test.js | 119 + .../javascript/gremlin-javascript/helper.js | 84 - .../gremlin-javascript/test-exports.js | 87 - .../gremlin-javascript/test-graphson.js | 108 - .../gremlin-javascript/test-traversal.js | 69 - pom.xml | 4 + 38 files changed, 4624 insertions(+), 4031 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/docs/src/reference/gremlin-variants.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index 0c48c97..76952eb 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -392,8 +392,8 @@ g.V().Repeat(Out()).Times(2).Values("name").Fold().ToList() === Bindings -When a traversal bytecode is sent over a `IRemoteConnection` (e.g. Gremlin Server), it will be translated, compiled, -and then executed. If the same traversal is sent again, translation and compilation can be skipped as the previously +When a traversal bytecode is sent over a `IRemoteConnection` (e.g. Gremlin Server), it will be translated, compiled, +and then executed. If the same traversal is sent again, translation and compilation can be skipped as the previously compiled version should be cached. Many traversals are unique up to some parameterization. For instance, `g.V(1).Out("created").Values("name")` is considered different from `g.V(4).Out("created").Values("Name")` as they have different script "string" representations. However, `g.V(x).Out("created").Values("name")` with bindings of @@ -422,7 +422,7 @@ g = g.WithoutStrategies(typeof(SubgraphStrategy)); names = g.V().Values("name").ToList(); // names: [marko, vadas, lop, josh, ripple, peter] var edgeValueMaps = g.V().OutE().ValueMap(true).ToList(); -// edgeValueMaps: [[label:created, id:9, weight:0.4], [label:knows, id:7, weight:0.5], [label:knows, id:8, weight:1.0], +// edgeValueMaps: [[label:created, id:9, weight:0.4], [label:knows, id:7, weight:0.5], [label:knows, id:8, weight:1.0], // [label:created, id:10, weight:1.0], [label:created, id:11, weight:0.4], [label:created, id:12, weight:0.2]] g = g.WithComputer(workers: 2, vertices: Has("name", "marko")); @@ -435,3 +435,72 @@ edgeValueMaps = g.V().OutE().ValueMap(true).ToList(); NOTE: Many of the TraversalStrategy classes in Gremlin.Net are proxies to the respective strategy on Apache TinkerPopâs JVM-based Gremlin traversal machine. As such, their `Apply(ITraversal)` method does nothing. However, the strategy is encoded in the Gremlin.Net bytecode and transmitted to the Gremlin traversal machine for re-construction machine-side. + +[[gremlin-javascript]] +== Gremlin-JavaScript + + +Apache TinkerPop's Gremlin-JavaScript implements Gremlin within the JavaScript language. It targets Node.js runtime +and can be used on different operating systems on any Node.js 4 or above. Since the JavaScript naming conventions are +very similar to that of Java, it should be very easy to switch between Gremlin-Java and Gremlin-JavaScript. + +[source,bash] +npm install gremlin-javascript + +In Gremlin-JavaScript there exists `GraphTraversalSource`, `GraphTraversal`, and `__` which mirror the respective classes +in Gremlin-Java. The `GraphTraversalSource` requires a RemoteConnection implementation in order to communicate with +<<gremlin-server,GremlinServer>>. + +A traversal source can be spawned with `RemoteStrategy` from an empty `Graph`. + +[source,javascript] +---- +const graph = new Graph(); +const g = graph.traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin')); +---- + +When a traversal from the `GraphTraversalSource` is iterated, the traversalâs `Bytecode` is sent over the wire via +the registered `RemoteConnection`. The bytecode is used to construct the equivalent traversal at the remote +traversal source. + +Since Gremlin-JavaScript currently doesn't support lambda expressions, all traversals can be translated to +Gremlin-Java on the remote location (e.g. Gremlin Server). + +IMPORTANT: Gremlin-JavaScriptâs `Traversal` base class supports the standard Gremlin methods such as `next()` and +`toList()` Such "terminal" methods trigger the evaluation of the traversal. + +=== RemoteConnection Submission + +Very similar to Gremlin-Python and Gremlin-Java, there are various ways to submit a traversal to a +`RemoteConnection` using terminal/action methods off of `Traversal`. + +* `Traversal.next()` +* `Traversal.toList()` + +=== Static Enums and Methods + +Gremlin has various tokens (e.g. `t`, `P`, `order`, `direction`, etc.) that are represented in Gremlin-JavaScript as +objects. + +These can be used analogously to how they are used in Gremlin-Java. + +[source,javascript] +g.V().hasLabel("person").has("age",P.gt(30)).Order().By("age", order.decr).toList() + +These objects must be required manually from the `process` namespace: + +[source,javascript] +---- +const gremlin = require('gremlin-javascript'); +const P = gremlin.process.P; +---- + +Finally, using static `__` anonymous traversals like `__.out()` can be expressed as below: + +[source,javascript] +---- +const gremlin = require('gremlin-javascript'); +const __ = gremlin.process.statics; + +g.V().repeat(__.out()).times(2).values("name").fold().toList(); +---- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/pom.xml ---------------------------------------------------------------------- diff --git a/gremlin-javascript/pom.xml b/gremlin-javascript/pom.xml index b9b78cc..1c9ffcc 100644 --- a/gremlin-javascript/pom.xml +++ b/gremlin-javascript/pom.xml @@ -37,6 +37,7 @@ limitations under the License. <version>${groovy.version}</version> <classifier>indy</classifier> </dependency> + <!-- TESTING --> <dependency> <groupId>org.apache.tinkerpop</groupId> <artifactId>tinkergraph-gremlin</artifactId> @@ -58,12 +59,10 @@ limitations under the License. <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> - <version>${slf4j.version}</version> <scope>test</scope> </dependency> </dependencies> <properties> - <!-- provides a way to convert maven.test.skip value to skipTests for use in skipping python tests --> <maven.test.skip>false</maven.test.skip> <skipTests>${maven.test.skip}</skipTests> <gremlin.server.dir>${project.parent.basedir}/gremlin-server</gremlin.server.dir> @@ -86,33 +85,92 @@ limitations under the License. <configuration> <mainClass>org.apache.tinkerpop.gremlin.javascript.GenerateGremlinJavascript</mainClass> <arguments> - <argument>${basedir}/src/main/javascript/gremlin-javascript/process/traversal.js</argument> - <argument>${basedir}/src/main/javascript/gremlin-javascript/process/graph-traversal.js</argument> + <argument>${project.version}</argument> + <argument>${basedir}</argument> </arguments> </configuration> </execution> + </executions> + </plugin> + <plugin> + <groupId>org.codehaus.gmavenplus</groupId> + <artifactId>gmavenplus-plugin</artifactId> + <dependencies> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.17</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.tinkerpop</groupId> + <artifactId>gremlin-server</artifactId> + <version>${project.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-ant</artifactId> + <version>${groovy.version}</version> + </dependency> + </dependencies> + <executions> <execution> - <id>js-tests</id> - <phase>test</phase> + <id>gremlin-server-start</id> + <phase>pre-integration-test</phase> <goals> - <goal>exec</goal> + <goal>execute</goal> </goals> <configuration> - <executable>jjs</executable> - <arguments> - <argument>${basedir}/src/test/javascript/gremlin-javascript/test-exports.js</argument> - <argument>${basedir}/src/test/javascript/gremlin-javascript/test-graphson.js</argument> - <argument>${basedir}/src/test/javascript/gremlin-javascript/test-traversal.js</argument> - </arguments> + <scripts> + <script> + <![CDATA[ +import org.apache.tinkerpop.gremlin.server.GremlinServer +import org.apache.tinkerpop.gremlin.server.Settings +import org.apache.tinkerpop.gremlin.server.Settings.ScriptEngineSettings +import org.apache.tinkerpop.gremlin.server.op.session.Session + +if (${skipIntegrationTests}) return +log.info("Starting Gremlin Server instances for native testing of gremlin-javascript") +def settings = Settings.read("${gremlin.server.dir}/conf/gremlin-server-modern.yaml") +settings.graphs.graph = "${gremlin.server.dir}/conf/tinkergraph-empty.properties" +settings.scriptEngines["gremlin-groovy"].scripts = ["${gremlin.server.dir}/scripts/generate-modern.groovy"] +settings.port = 45950 +def server = new GremlinServer(settings) +server.start().join() +project.setContextValue("gremlin.javascript.server", server) +log.info("Gremlin Server started on port 45950") +]]> + </script> + </scripts> + </configuration> + </execution> + <execution> + <id>gremlin-server-stop</id> + <phase>post-integration-test</phase> + <goals> + <goal>execute</goal> + </goals> + <configuration> + <scripts> + <script> + <![CDATA[ +import org.apache.tinkerpop.gremlin.server.GremlinServer +import org.apache.tinkerpop.gremlin.server.op.session.Session + +if (${skipIntegrationTests}) return +log.info("Tests for native gremlin-javascript complete") +def server = project.getContextValue("gremlin.javascript.server") +log.info("Shutting down $server") +server.stop().join() +]]> + </script> + </scripts> </configuration> </execution> </executions> </plugin> <plugin> - <groupId>org.codehaus.gmavenplus</groupId> - <artifactId>gmavenplus-plugin</artifactId> - </plugin> - <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> @@ -124,6 +182,85 @@ limitations under the License. </execution> </executions> </plugin> + <plugin> + <groupId>com.github.eirslett</groupId> + <artifactId>frontend-maven-plugin</artifactId> + <version>1.4</version> + <executions> + <execution> + <id>install node and npm</id> + <goals> + <goal>install-node-and-npm</goal> + </goals> + <phase>generate-test-resources</phase> + </execution> + <execution> + <id>npm install</id> + <goals> + <goal>npm</goal> + </goals> + <phase>generate-test-resources</phase> + <configuration> + <arguments>install</arguments> + </configuration> + </execution> + <execution> + <id>npm test</id> + <goals> + <goal>npm</goal> + </goals> + <phase>integration-test</phase> + <configuration> + <arguments>test</arguments> + </configuration> + </execution> + </executions> + <configuration> + <skip>${skipIntegrationTests}</skip> + <workingDirectory>src/main/javascript/gremlin-javascript</workingDirectory> + <nodeVersion>v4.8.3</nodeVersion> + </configuration> + </plugin> </plugins> </build> + <profiles> + <!-- + Provides a way to deploy the gremlin-javascript GLV to npm. This cannot be part of the standard maven execution + because npm does not have a staging environment like sonatype for releases. As soon as the release is + published it is public. In our release workflow, deploy occurs prior to vote on the release and we can't + make this stuff public until the vote is over. + --> + <profile> + <id>glv-javascript-deploy</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <build> + <plugins> + <plugin> + <groupId>com.github.eirslett</groupId> + <artifactId>frontend-maven-plugin</artifactId> + <version>1.4</version> + <executions> + <execution> + <id>npm publish</id> + <phase>deploy</phase> + <goals> + <goal>npm</goal> + </goals> + <configuration> + <arguments>publish</arguments> + </configuration> + </execution> + </executions> + <configuration> + <skip>${skipIntegrationTests}</skip> + <workingDirectory>src/main/javascript/gremlin-javascript</workingDirectory> + <nodeVersion>v4.8.3</nodeVersion> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy new file mode 100644 index 0000000..ab9eaf1 --- /dev/null +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy @@ -0,0 +1,36 @@ +/* + * 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.javascript + +public class GenerateGremlinJavascript { + public static void main(String[] args) { + String projectVersion = args[0]; + final String baseDir = args[1]; + final String jsModuleDir = "${baseDir}/src/main/javascript/gremlin-javascript"; + Integer versionSnapshotIndex = projectVersion.indexOf("-SNAPSHOT"); + if (versionSnapshotIndex > 0) { + // Use a alpha version x.y.z-alpha1 + projectVersion = projectVersion.substring(0, versionSnapshotIndex) + "-alpha1"; + } + TraversalSourceGenerator.create("$jsModuleDir/lib/process/traversal.js"); + GraphTraversalSourceGenerator.create("$jsModuleDir/lib/process/graph-traversal.js"); + PackageJsonGenerator.create("$jsModuleDir/package.json", projectVersion); + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy index 0ae6079..3227f2c 100644 --- a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy @@ -23,7 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__ -import org.apache.tinkerpop.gremlin.javascript.jsr223.SymbolHelper +import org.apache.tinkerpop.gremlin.javascript.SymbolHelper import java.lang.reflect.Modifier @@ -63,45 +63,46 @@ class GraphTraversalSourceGenerator { /** * @author Jorge Bay Gondra */ -(function defineGraphTraversalModule() { - "use strict"; - - var t = loadModule.call(this, './traversal.js'); - var remote = loadModule.call(this, '../driver/remote-connection.js'); - var Bytecode = t.Bytecode; - var inherits = t.inherits; - var parseArgs = t.parseArgs; - - /** - * - * @param {Graph} graph - * @param {TraversalStrategies} traversalStrategies - * @param {Bytecode} [bytecode] - * @constructor - */ - function GraphTraversalSource(graph, traversalStrategies, bytecode) { - this.graph = graph; - this.traversalStrategies = traversalStrategies; - this.bytecode = bytecode || new Bytecode(); - } - - /** - * @param remoteConnection - * @returns {GraphTraversalSource} - */ - GraphTraversalSource.prototype.withRemote = function (remoteConnection) { - var traversalStrategy = new t.TraversalStrategies(this.traversalStrategies); - traversalStrategy.addStrategy(new remote.RemoteStrategy(remoteConnection)); - return new GraphTraversalSource(this.graph, traversalStrategy, new Bytecode(this.bytecode)); - }; - - /** - * Returns the string representation of the GraphTraversalSource. - * @returns {string} - */ - GraphTraversalSource.prototype.toString = function () { - return 'graphtraversalsource[' + this.graph.toString() + ']'; - }; +'use strict'; + +var t = require('./traversal.js'); +var remote = require('../driver/remote-connection'); +var utils = require('../utils'); +var Bytecode = require('./bytecode'); +var TraversalStrategies = require('./traversal-strategy').TraversalStrategies; +var inherits = utils.inherits; +var parseArgs = utils.parseArgs; + +/** + * + * @param {Graph} graph + * @param {TraversalStrategies} traversalStrategies + * @param {Bytecode} [bytecode] + * @constructor + */ +function GraphTraversalSource(graph, traversalStrategies, bytecode) { + this.graph = graph; + this.traversalStrategies = traversalStrategies; + this.bytecode = bytecode || new Bytecode(); +} + +/** + * @param remoteConnection + * @returns {GraphTraversalSource} + */ +GraphTraversalSource.prototype.withRemote = function (remoteConnection) { + var traversalStrategy = new TraversalStrategies(this.traversalStrategies); + traversalStrategy.addStrategy(new remote.RemoteStrategy(remoteConnection)); + return new GraphTraversalSource(this.graph, traversalStrategy, new Bytecode(this.bytecode)); +}; + +/** + * Returns the string representation of the GraphTraversalSource. + * @returns {string} + */ +GraphTraversalSource.prototype.toString = function () { + return 'graphtraversalsource[' + this.graph.toString() + ']'; +}; """) GraphTraversalSource.getMethods(). // SOURCE STEPS findAll { GraphTraversalSource.class.equals(it.returnType) }. @@ -110,40 +111,40 @@ class GraphTraversalSourceGenerator { !it.name.equals(TraversalSource.Symbols.withBindings) && !it.name.equals(TraversalSource.Symbols.withRemote) }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - forEach { method -> + forEach { methodName -> moduleOutput.append( """ - /** - * ${method} GraphTraversalSource method. - * @param {...Object} args - * @returns {GraphTraversalSource} - */ - GraphTraversalSource.prototype.${method} = function (args) { - var b = new Bytecode(this.bytecode).addSource('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - return new GraphTraversalSource(this.graph, new t.TraversalStrategies(this.traversalStrategies), b); - }; +/** + * Graph Traversal Source ${methodName} method. + * @param {...Object} args + * @returns {GraphTraversalSource} + */ +GraphTraversalSource.prototype.${SymbolHelper.toJs(methodName)} = function (args) { + var b = new Bytecode(this.bytecode).addSource('$methodName', parseArgs.apply(null, arguments)); + return new GraphTraversalSource(this.graph, new TraversalStrategies(this.traversalStrategies), b); +}; """) } - GraphTraversalSource.getMethods(). // SPAWN STEPS + GraphTraversalSource.getMethods(). findAll { GraphTraversal.class.equals(it.returnType) }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - forEach { method -> + forEach { methodName -> moduleOutput.append( """ - /** - * ${method} GraphTraversalSource step method. - * @param {...Object} args - * @returns {GraphTraversal} - */ - GraphTraversalSource.prototype.${method} = function (args) { - var b = new Bytecode(this.bytecode).addStep('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - return new GraphTraversal(this.graph, new t.TraversalStrategies(this.traversalStrategies), b); - }; +/** + * $methodName GraphTraversalSource step method. + * @param {...Object} args + * @returns {GraphTraversal} + */ +GraphTraversalSource.prototype.${SymbolHelper.toJs(methodName)} = function (args) { + var b = new Bytecode(this.bytecode).addStep('$methodName', parseArgs.apply(null, arguments)); + return new GraphTraversal(this.graph, new TraversalStrategies(this.traversalStrategies), b); +}; """) } //////////////////// @@ -151,34 +152,35 @@ class GraphTraversalSourceGenerator { //////////////////// moduleOutput.append( """ - /** - * Represents a graph traversal. - * @extends Traversal - * @constructor - */ - function GraphTraversal(graph, traversalStrategies, bytecode) { - t.Traversal.call(this, graph, traversalStrategies, bytecode); - } - - inherits(GraphTraversal, t.Traversal); +/** + * Represents a graph traversal. + * @extends Traversal + * @constructor + */ +function GraphTraversal(graph, traversalStrategies, bytecode) { + t.Traversal.call(this, graph, traversalStrategies, bytecode); +} + +inherits(GraphTraversal, t.Traversal); """) GraphTraversal.getMethods(). findAll { GraphTraversal.class.equals(it.returnType) }. findAll { !it.name.equals("clone") && !it.name.equals("iterate") }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - forEach { method -> + forEach { methodName -> moduleOutput.append( """ - /** - * @param {...Object} args - * @returns {GraphTraversal} - */ - GraphTraversal.prototype.${method} = function (args) { - this.bytecode.addStep('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - return this; - }; +/** + * Graph traversal $methodName method. + * @param {...Object} args + * @returns {GraphTraversal} + */ +GraphTraversal.prototype.${SymbolHelper.toJs(methodName)} = function (args) { + this.bytecode.addStep('$methodName', parseArgs.apply(null, arguments)); + return this; +}; """) }; @@ -186,11 +188,11 @@ class GraphTraversalSourceGenerator { // AnonymousTraversal // //////////////////////// moduleOutput.append(""" - /** - * Contains the static method definitions - * @type {Object} - */ - var statics = {}; +/** + * Contains the static method definitions + * @type {Object} + */ +var statics = {}; """); __.class.getMethods(). findAll { GraphTraversal.class.equals(it.returnType) }. @@ -202,44 +204,24 @@ class GraphTraversalSourceGenerator { forEach { method -> moduleOutput.append( """ - /** - * ${method}() static method - * @param {...Object} args - * @returns {GraphTraversal} - */ - statics.${method} = function (args) { - var g = new GraphTraversal(null, null, new Bytecode()); - return g.${method}.apply(g, arguments); - }; +/** + * ${method}() static method + * @param {...Object} args + * @returns {GraphTraversal} + */ +statics.${method} = function (args) { + var g = new GraphTraversal(null, null, new Bytecode()); + return g.${method}.apply(g, arguments); +}; """) }; moduleOutput.append(""" - function loadModule(moduleName) { - if (typeof require !== 'undefined') { - return require(moduleName); - } - if (typeof load !== 'undefined') { - var path = new java.io.File(__DIR__ + moduleName).getCanonicalPath(); - this.__dependencies = this.__dependencies || {}; - return this.__dependencies[path] = (this.__dependencies[path] || load(path)); - } - throw new Error('No module loader was found'); - } - - var toExport = { - GraphTraversal: GraphTraversal, - GraphTraversalSource: GraphTraversalSource, - statics: statics - }; - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; - } - // Nashorn and rest - return toExport; -}).call(this);""") +module.exports = { + GraphTraversal: GraphTraversal, + GraphTraversalSource: GraphTraversalSource, + statics: statics +};"""); // save to file final File file = new File(graphTraversalSourceFile); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy new file mode 100644 index 0000000..4b4d012 --- /dev/null +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy @@ -0,0 +1,72 @@ +/* + * 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.javascript + +/** + * @author Jorge Bay Gondra + */ +class PackageJsonGenerator { + + public static void create(final String traversalSourceFile, final String version) { + + final StringBuilder moduleOutput = new StringBuilder(); + moduleOutput.append("""{ + "name": "gremlin-javascript", + "version": "${version}", + "description": "JavaScript Gremlin Language Variant", + "author": "Apache TinkerPop team", + "keywords": [ + "graph", + "gremlin", + "tinkerpop", + "connection", + "glv", + "driver", + "graphdb" + ], + "license": "Apache-2.0", + "dependencies": { + "ws": "^3.0.0" + }, + "devDependencies": { + "mocha": ">= 1.14.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/apache/tinkerpop.git" + }, + "bugs": { + "url": "https://issues.apache.org/jira/browse/TINKERPOP" + }, + "scripts": { + "test": "./node_modules/.bin/mocha test --recursive -t 5000", + "unit-test": "./node_modules/.bin/mocha test/unit" + }, + "engines": { + "node": ">=4" + } +}""" ); + + // save to a file + final File file = new File(traversalSourceFile); + file.delete() + moduleOutput.eachLine { file.append(it + "\n") } + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy new file mode 100644 index 0000000..ed45cdb --- /dev/null +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy @@ -0,0 +1,50 @@ +/* + * 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.javascript + +/** + * @author Jorge Bay Gondra + */ +public final class SymbolHelper { + + private final static Map<String, String> TO_JS_MAP = new HashMap<>(); + + static { + TO_JS_MAP.put("in", "in_"); + TO_JS_MAP.put("from", "from_"); + } + + private SymbolHelper() { + // static methods only, do not instantiate + } + + public static String toJs(final String symbol) { + return TO_JS_MAP.getOrDefault(symbol, symbol); + } + + public static String decapitalize(String string) { + if (string == null || string.length() == 0) { + return string; + } + def c = string.toCharArray(); + c[0] = Character.toLowerCase(c[0]); + return new String(c); + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy index d5899f0..f3405c3 100644 --- a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy @@ -21,7 +21,6 @@ package org.apache.tinkerpop.gremlin.javascript import org.apache.tinkerpop.gremlin.process.traversal.P import org.apache.tinkerpop.gremlin.util.CoreImports -import org.apache.tinkerpop.gremlin.javascript.jsr223.SymbolHelper import java.lang.reflect.Modifier @@ -57,334 +56,178 @@ class TraversalSourceGenerator { /** * @author Jorge Bay Gondra */ -(function defineTraversalModule() { - "use strict"; - - function Traversal(graph, traversalStrategies, bytecode) { - this.graph = graph; - this.traversalStrategies = traversalStrategies; - this.bytecode = bytecode; - this.traversers = null; - this.sideEffects = null; - } +'use strict'; + +var utils = require('../utils'); +var parseArgs = utils.parseArgs; +var itemDone = Object.freeze({ value: null, done: true }); +var emptyArray = Object.freeze([]); + +function Traversal(graph, traversalStrategies, bytecode) { + this.graph = graph; + this.traversalStrategies = traversalStrategies; + this.bytecode = bytecode; + this.traversers = null; + this.sideEffects = null; + this._traversalStrategiesPromise = null; + this._traversersIteratorIndex = 0; +} - /** @returns {Bytecode} */ - Traversal.prototype.getBytecode = function () { - return this.bytecode; - }; +/** @returns {Bytecode} */ +Traversal.prototype.getBytecode = function () { + return this.bytecode; +}; - /** @param {Function} callback */ - Traversal.prototype.list = function (callback) { - var self = this; - this.traversalStrategies.applyStrategies(this, function (err) { - if (err) { - return callback(err); - } - callback(err, self.traversers); - }); - }; +/** + * Returns an Array containing the traverser objects. + * @returns {Promise.<Array>} + */ +Traversal.prototype.toList = function () { + var self = this; + return this._applyStrategies().then(function () { + if (!self.traversers || self._traversersIteratorIndex === self.traversers.length) { + return emptyArray; + } + var arr = new Array(self.traversers.length - self._traversersIteratorIndex); + for (var i = self._traversersIteratorIndex; i < self.traversers.length; i++) { + arr[i] = self.traversers[i].object; + } + self._traversersIteratorIndex = self.traversers.length; + return arr; + }); +}; - /** @param {Function} callback */ - Traversal.prototype.one = function (callback) { - this.list(function (err, result) { - callback(err, result && result.length > 0 ? result[0] : null); - }); - }; +/** + * Async iterator method implementation. + * Returns a promise containing an iterator item. + * @returns {Promise.<{value, done}>} + */ +Traversal.prototype.next = function () { + var self = this; + return this._applyStrategies().then(function () { + if (!self.traversers || self._traversersIteratorIndex === self.traversers.length) { + return itemDone; + } + return { value: self.traversers[self._traversersIteratorIndex++].object, done: false }; + }); +}; + +Traversal.prototype._applyStrategies = function () { + if (this._traversalStrategiesPromise) { + // Apply strategies only once + return this._traversalStrategiesPromise; + } + return this._traversalStrategiesPromise = this.traversalStrategies.applyStrategies(this); +}; - /** - * Returns the Bytecode JSON representation of the traversal - * @returns {String} - */ - Traversal.prototype.toString = function () { - return this.bytecode.toString(); - }; +/** + * Returns the Bytecode JSON representation of the traversal + * @returns {String} + */ +Traversal.prototype.toString = function () { + return this.bytecode.toString(); +}; """); moduleOutput.append(""" - /** - * Represents an operation. - * @constructor - */ - function P(operator, value, other) { - this.operator = operator; - this.value = value; - this.other = other; - } - - /** - * Returns the string representation of the instance. - * @returns {string} - */ - P.prototype.toString = function () { - if (this.other === undefined) { - return this.operator + '(' + this.value + ')'; - } - return this.operator + '(' + this.value + ', ' + this.other + ')'; - }; +/** + * Represents an operation. + * @constructor + */ +function P(operator, value, other) { + this.operator = operator; + this.value = value; + this.other = other; +} - function createP(operator, args) { - args.unshift(null, operator); - return new (Function.prototype.bind.apply(P, args)); +/** + * Returns the string representation of the instance. + * @returns {string} + */ +P.prototype.toString = function () { + if (this.other === undefined) { + return this.operator + '(' + this.value + ')'; } + return this.operator + '(' + this.value + ', ' + this.other + ')'; +}; + +function createP(operator, args) { + args.unshift(null, operator); + return new (Function.prototype.bind.apply(P, args)); +} """) P.class.getMethods(). findAll { Modifier.isStatic(it.getModifiers()) }. findAll { P.class.isAssignableFrom(it.returnType) }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - each { method -> + each { methodName -> moduleOutput.append( """ - /** @param {...Object} args */ - P.${method} = function (args) { - return createP('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - }; +/** @param {...Object} args */ +P.${SymbolHelper.toJs(methodName)} = function (args) { + return createP('$methodName', parseArgs.apply(null, arguments)); +}; """) }; moduleOutput.append(""" - P.prototype.and = function (arg) { - return new P('and', this, arg); - }; +P.prototype.and = function (arg) { + return new P('and', this, arg); +}; - P.prototype.or = function (arg) { - return new P('or', this, arg); - }; +P.prototype.or = function (arg) { + return new P('or', this, arg); +}; """) moduleOutput.append(""" - function Traverser(object, bulk) { - this.object = object; - this.bulk = bulk == undefined ? 1 : bulk; - } - - function TraversalSideEffects() { - - } - - /** - * Creates a new instance of TraversalStrategies. - * @param {TraversalStrategies} [traversalStrategies] - * @constructor - */ - function TraversalStrategies(traversalStrategies) { - /** @type {Array<TraversalStrategy>} */ - this.strategies = traversalStrategies ? traversalStrategies.strategies : []; - } - - /** @param {TraversalStrategy} strategy */ - TraversalStrategies.prototype.addStrategy = function (strategy) { - this.strategies.push(strategy); - }; - - /** - * @param {Traversal} traversal - * @param {Function} callback - */ - TraversalStrategies.prototype.applyStrategies = function (traversal, callback) { - eachSeries(this.strategies, function eachStrategy(s, next) { - s.apply(traversal, next); - }, callback); - }; - - /** - * @abstract - * @constructor - */ - function TraversalStrategy() { - - } - - /** - * @abstract - * @param {Traversal} traversal - * @param {Function} callback - */ - TraversalStrategy.prototype.apply = function (traversal, callback) { - - }; - - /** - * Creates a new instance of Bytecode - * @param {Bytecode} [toClone] - * @constructor - */ - function Bytecode(toClone) { - this._bindings = {}; - if (!toClone) { - this.sourceInstructions = []; - this.stepInstructions = []; - } - else { - this.sourceInstructions = toClone.sourceInstructions.slice(0); - this.stepInstructions = toClone.sourceInstructions.slice(0); - } - } - - /** - * Adds a new source instructions - * @param {String} name - * @param {Array} values - * @returns {Bytecode} - */ - Bytecode.prototype.addSource = function (name, values) { - if (name === undefined) { - throw new Error('Name is not defined'); - } - var instruction = new Array(values.length + 1); - instruction[0] = name; - for (var i = 0; i < values.length; ++i) { - instruction[i + 1] = this._convertToArgument(values[i]); - } - this.sourceInstructions.push(this._generateInstruction(name, values)); - return this; - }; - - /** - * Adds a new step instructions - * @param {String} name - * @param {Array} values - * @returns {Bytecode} - */ - Bytecode.prototype.addStep = function (name, values) { - if (name === undefined) { - throw new Error('Name is not defined'); - } - this.stepInstructions.push(this._generateInstruction(name, values)); - return this; - }; - - Bytecode.prototype._generateInstruction = function (name, values) { - var instruction = new Array(values.length + 1); - instruction[0] = name; - for (var i = 0; i < values.length; ++i) { - instruction[i + 1] = this._convertToArgument(values[i]); - } - return instruction; - }; - - /** - * Returns the JSON representation of the source and step instructions - * @returns {String} - */ - Bytecode.prototype.toString = function () { - return ( - (this.sourceInstructions.length > 0 ? JSON.stringify(this.sourceInstructions) : '') + - (this.stepInstructions.length > 0 ? JSON.stringify(this.stepInstructions) : '') - ); - }; - - Bytecode.prototype._convertToArgument = function (value) { - return value; - }; - - function toEnum(typeName, keys) { - var result = {}; - keys.split(' ').forEach(function (k) { - if (k === k.toUpperCase()) { - k = k.toLowerCase(); - } - result[k] = new EnumValue(typeName, k); - }); - return result; - } - - function EnumValue(typeName, elementName) { - this.typeName = typeName; - this.elementName = elementName; - } +function Traverser(object, bulk) { + this.object = object; + this.bulk = bulk == undefined ? 1 : bulk; +} - // Utility functions - /** @returns {Array} */ - function parseArgs() { - return (arguments.length === 1 ? [ arguments[0] ] : Array.apply(null, arguments)); - } +function TraversalSideEffects() { - /** - * @param {Array} arr - * @param {Function} fn - * @param {Function} [callback] - */ - function eachSeries(arr, fn, callback) { - if (!Array.isArray(arr)) { - throw new TypeError('First parameter is not an Array'); - } - callback = callback || noop; - var length = arr.length; - if (length === 0) { - return callback(); - } - var sync; - var index = 1; - fn(arr[0], next); - if (sync === undefined) { - sync = false; - } +} - function next(err) { - if (err) { - return callback(err); - } - if (index >= length) { - return callback(); - } - if (sync === undefined) { - sync = true; - } - if (sync) { - return process.nextTick(function () { - fn(arr[index++], next); - }); - } - fn(arr[index++], next); +function toEnum(typeName, keys) { + var result = {}; + keys.split(' ').forEach(function (k) { + var jsKey = k; + if (jsKey === jsKey.toUpperCase()) { + jsKey = jsKey.toLowerCase(); } - } + result[jsKey] = new EnumValue(typeName, k); + }); + return result; +} - function inherits(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - } +function EnumValue(typeName, elementName) { + this.typeName = typeName; + this.elementName = elementName; +} - var toExport = { - Bytecode: Bytecode, - EnumValue: EnumValue, - inherits: inherits, - P: P, - parseArgs: parseArgs, - Traversal: Traversal, - TraversalSideEffects: TraversalSideEffects, - TraversalStrategies: TraversalStrategies, - TraversalStrategy: TraversalStrategy, - Traverser: Traverser""") - for (final Class<? extends Enum> enumClass : CoreImports.getClassImports() - .findAll { Enum.class.isAssignableFrom(it) } - .sort { a, b -> a.getSimpleName() <=> b.getSimpleName() } - .collect()) { - moduleOutput.append(",\n ${SymbolHelper.decapitalize(enumClass.getSimpleName())}: " + - "toEnum('${SymbolHelper.toJs(enumClass.getSimpleName())}', '"); - enumClass.getEnumConstants() - .sort { a, b -> a.name() <=> b.name() } - .each { value -> moduleOutput.append("${SymbolHelper.toJs(value.name())} "); } - moduleOutput.deleteCharAt(moduleOutput.length() - 1).append("')") +module.exports = { + EnumValue: EnumValue, + P: P, + Traversal: Traversal, + TraversalSideEffects: TraversalSideEffects, + Traverser: Traverser""") + for (final Class<? extends Enum> enumClass : CoreImports.getClassImports(). + findAll { Enum.class.isAssignableFrom(it) }. + sort { a, b -> a.simpleName <=> b.simpleName }) { + moduleOutput.append(",\n ${SymbolHelper.decapitalize(enumClass.simpleName)}: " + + "toEnum('${SymbolHelper.toJs(enumClass.simpleName)}', '"); + moduleOutput.append( + enumClass.getEnumConstants(). + sort { a, b -> a.name() <=> b.name() }. + collect { SymbolHelper.toJs(it.name()) }. + join(' ')); + moduleOutput.append("\')"); } - moduleOutput.append(""" - }; - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; - } - // Nashorn and rest - return toExport; -}).call(this);""") + moduleOutput.append("""\n};"""); // save to a file final File file = new File(traversalSourceFile); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java b/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java deleted file mode 100644 index 1656db4..0000000 --- a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.javascript; - -public class GenerateGremlinJavascript { - - private GenerateGremlinJavascript() { - // just need the main method - } - - public static void main(String[] args) { - TraversalSourceGenerator.create(args[0]); - GraphTraversalSourceGenerator.create(args[1]); - } -} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java b/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java deleted file mode 100644 index 535de44..0000000 --- a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.javascript.jsr223; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Jorge Bay Gondra - */ -public final class SymbolHelper { - - private final static Map<String, String> TO_JS_MAP = new HashMap<>(); - private final static Map<String, String> FROM_JS_MAP = new HashMap<>(); - - static { - TO_JS_MAP.put("in", "in_"); - TO_JS_MAP.put("from", "from_"); - TO_JS_MAP.forEach((k, v) -> FROM_JS_MAP.put(v, k)); - } - - private SymbolHelper() { - // static methods only, do not instantiate - } - - public static String toJs(final String symbol) { - return TO_JS_MAP.getOrDefault(symbol, symbol); - } - - public static String toJava(final String symbol) { - return FROM_JS_MAP.getOrDefault(symbol, symbol); - } - - public static String decapitalize(String string) { - if (string == null || string.length() == 0) { - return string; - } - char c[] = string.toCharArray(); - c[0] = Character.toLowerCase(c[0]); - return new String(c); - } -} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore b/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore new file mode 100644 index 0000000..4c7120c --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.idea/ +npm-debug.log +# maven plugin com.github.eirslett frontend-maven-plugin installs node.js runtime in "node" folder +node/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore b/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore new file mode 100644 index 0000000..82943d0 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore @@ -0,0 +1,26 @@ +# 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. + +*.gz +*.tgz +test/ +.DS_Store +.idea +node_modules/ +npm-debug.log +# maven plugin com.github.eirslett frontend-maven-plugin installs node.js runtime in "node" folder +node/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md b/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md new file mode 100644 index 0000000..388e175 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md @@ -0,0 +1,39 @@ +<!-- + + 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. + +--> + +# JavaScript Gremlin Language Variant + +[Apache TinkerPopâ¢][tk] is a graph computing framework for both graph databases (OLTP) and graph analytic systems +(OLAP). [Gremlin][gremlin] is the graph traversal language of TinkerPop. It can be described as a functional, +data-flow language that enables users to succinctly express complex traversals on (or queries of) their application's +property graph. + +Gremlin-Javascript implements Gremlin within the JavaScript language and can be used on Node.js. + +```bash +npm install gremlin-javascript +``` + +Please see the [reference documentation][docs] at Apache TinkerPop for more information. + +[tk]: http://tinkerpop.apache.org +[gremlin]: http://tinkerpop.apache.org/gremlin.html +[docs]: http://tinkerpop.apache.org/docs/current/reference/#gremlin-javascript \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js deleted file mode 100644 index e19b537..0000000 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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. - */ - -/** - * @author Jorge Bay Gondra - */ -(function defineRemoteConnectionModule() { - "use strict"; - - var t = loadModule.call(this, '../process/traversal.js'); - var inherits = t.inherits; - - function RemoteConnection(url, traversalSource) { - this.url = url; - this.traversalSource = traversalSource; - } - - /** - * @abstract - * @param {Bytecode} bytecode - * @param {Function} callback - */ - RemoteConnection.prototype.submit = function (bytecode, callback) { - throw new Error('submit() needs to be implemented'); - }; - - /** - * @extends {Traversal} - * @constructor - */ - function RemoteTraversal(traversers, sideEffects) { - t.Traversal.call(this, null, null, null); - this.traversers = traversers; - this.sideEffects = sideEffects; - } - - inherits(RemoteTraversal, t.Traversal); - - /** - * - * @param {RemoteConnection} connection - * @extends {TraversalStrategy} - * @constructor - */ - function RemoteStrategy(connection) { - t.TraversalStrategy.call(this); - this.connection = connection; - } - - inherits(RemoteStrategy, t.TraversalStrategy); - - /** @override */ - RemoteStrategy.prototype.apply = function (traversal, callback) { - if (traversal.traversers) { - return callback(); - } - this.connection.submit(traversal.getBytecode(), function (err, remoteTraversal) { - if (err) { - return callback(err); - } - traversal.sideEffects = remoteTraversal.sideEffects; - traversal.traversers = remoteTraversal.traversers; - callback(); - }); - }; - - function loadModule(moduleName) { - if (typeof require !== 'undefined') { - return require(moduleName); - } - if (typeof load !== 'undefined') { - var path = new java.io.File(__DIR__ + moduleName).getCanonicalPath(); - this.__dependencies = this.__dependencies || {}; - return this.__dependencies[path] = (this.__dependencies[path] || load(path)); - } - throw new Error('No module loader was found'); - } - - var toExport = { - RemoteConnection: RemoteConnection, - RemoteStrategy: RemoteStrategy, - RemoteTraversal: RemoteTraversal - }; - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; - } - // Nashorn and rest - return toExport; -}).call(this); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js index ddcdac2..600ac53 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js @@ -20,73 +20,54 @@ /** * @author Jorge Bay Gondra */ -(function exportModule() { - "use strict"; +'use strict'; - function loadModule(moduleName) { - if (typeof require !== 'undefined') { - return require(moduleName); - } - if (typeof load !== 'undefined') { - var path = new java.io.File(__DIR__ + moduleName).getCanonicalPath(); - this.__dependencies = this.__dependencies || {}; - return this.__dependencies[path] = (this.__dependencies[path] || load(path)); - } - throw new Error('No module loader was found'); - } +var t = require('./lib/process/traversal'); +var gt = require('./lib/process/graph-traversal'); +var strategiesModule = require('./lib/process/traversal-strategy'); +var graph = require('./lib/structure/graph'); +var gs = require('./lib/structure/io/graph-serializer'); +var rc = require('./lib/driver/remote-connection'); +var Bytecode = require('./lib/process/bytecode'); - var t = loadModule.call(this, './process/traversal.js'); - var gt = loadModule.call(this, './process/graph-traversal.js'); - var graph = loadModule.call(this, './structure/graph.js'); - var gs = loadModule.call(this, './structure/io/graph-serializer.js'); - var rc = loadModule.call(this, './driver/remote-connection.js'); - var toExport = { - driver: { - RemoteConnection: rc.RemoteConnection, - RemoteStrategy: rc.RemoteStrategy, - RemoteTraversal: rc.RemoteTraversal - }, - process: { - Bytecode: t.Bytecode, - EnumValue: t.EnumValue, - P: t.P, - Traversal: t.Traversal, - TraversalSideEffects: t.TraversalSideEffects, - TraversalStrategies: t.TraversalStrategies, - TraversalStrategy: t.TraversalStrategy, - Traverser: t.Traverser, - barrier: t.barrier, - cardinality: t.cardinality, - column: t.column, - direction: t.direction, - operator: t.operator, - order: t.order, - pop: t.pop, - scope: t.scope, - t: t.t, - GraphTraversal: gt.GraphTraversal, - GraphTraversalSource: gt.GraphTraversalSource, - statics: gt.statics +module.exports = { + driver: { + RemoteConnection: rc.RemoteConnection, + RemoteStrategy: rc.RemoteStrategy, + RemoteTraversal: rc.RemoteTraversal + }, + process: { + Bytecode: Bytecode, + EnumValue: t.EnumValue, + P: t.P, + Traversal: t.Traversal, + TraversalSideEffects: t.TraversalSideEffects, + TraversalStrategies: strategiesModule.TraversalStrategies, + TraversalStrategy: strategiesModule.TraversalStrategy, + Traverser: t.Traverser, + barrier: t.barrier, + cardinality: t.cardinality, + column: t.column, + direction: t.direction, + operator: t.operator, + order: t.order, + pop: t.pop, + scope: t.scope, + t: t.t, + GraphTraversal: gt.GraphTraversal, + GraphTraversalSource: gt.GraphTraversalSource, + statics: gt.statics + }, + structure: { + io: { + GraphSONReader: gs.GraphSONReader, + GraphSONWriter: gs.GraphSONWriter }, - structure: { - io: { - GraphSONReader: gs.GraphSONReader, - GraphSONWriter: gs.GraphSONWriter - }, - Edge: graph.Edge, - Graph: graph.Graph, - Path: graph.Path, - Property: graph.Property, - Vertex: graph.Vertex, - VertexProperty: graph.VertexProperty - } - }; - - - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; + Edge: graph.Edge, + Graph: graph.Graph, + Path: graph.Path, + Property: graph.Property, + Vertex: graph.Vertex, + VertexProperty: graph.VertexProperty } - return toExport; -}).call(this); \ No newline at end of file +}; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js new file mode 100644 index 0000000..0d6d507 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -0,0 +1,200 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +var crypto = require('crypto'); +var WebSocket = require('ws'); +var util = require('util'); +var RemoteConnection = require('./remote-connection').RemoteConnection; +var utils = require('../utils'); +var serializer = require('../structure/io/graph-serializer'); +var inherits = utils.inherits; +var mimeType = 'application/vnd.gremlin-v2.0+json'; +var header = String.fromCharCode(mimeType.length) + mimeType; +var responseStatusCode = { + success: 200, + noContent: 204, + partialContent: 206 +}; + +/** + * Creates a new instance of DriverRemoteConnection. + * @param {String} url The resource uri. + * @param {Object} [options] The connection options. + * @param {Array} [options.ca] Trusted certificates. + * @param {String|Array|Buffer} [options.cert] The certificate key. + * @param {String|Buffer} [options.pfx] The private key, certificate, and CA certs. + * @param {GraphSONReader} [options.reader] The GraphSON2 reader to use. + * @param {Boolean} [options.rejectUnauthorized] Determines whether to verify or not the server certificate. + * @param {GraphSONWriter} [options.writer] The GraphSON2 writer to use. + * @constructor + */ +function DriverRemoteConnection(url, options) { + options = options || {}; + this._ws = new WebSocket(url, { + ca: options.ca, + cert: options.cert, + pfx: options.pfx, + rejectUnauthorized: options.rejectUnauthorized + }); + var self = this; + this._ws.on('open', function opened () { + self.isOpen = true; + if (self._openCallback) { + self._openCallback(); + } + }); + this._ws.on('message', function incoming (data) { + self._handleMessage(data); + }); + // A map containing the request id and the handler + this._responseHandlers = {}; + this._reader = options.reader || new serializer.GraphSONReader(); + this._writer = options.writer || new serializer.GraphSONWriter(); + this._openPromise = null; + this._openCallback = null; + this._closePromise = null; + this.isOpen = false; +} + +inherits(DriverRemoteConnection, RemoteConnection); + +/** + * Opens the connection, if its not already opened. + * @returns {Promise} + */ +DriverRemoteConnection.prototype.open = function (promiseFactory) { + if (this._closePromise) { + return this._openPromise = utils.toPromise(promiseFactory, function promiseHandler(callback) { + callback(new Error('Connection has been closed')); + }); + } + if (this._openPromise) { + return this._openPromise; + } + var self = this; + return this._openPromise = utils.toPromise(promiseFactory, function promiseHandler(callback) { + if (self.isOpen) { + return callback(); + } + // It will be invoked when opened + self._openCallback = callback; + }); +}; + +/** @override */ +DriverRemoteConnection.prototype.submit = function (bytecode, promiseFactory) { + var self = this; + return this.open().then(function () { + return utils.toPromise(promiseFactory, function promiseHandler(callback) { + var requestId = getUuid(); + self._responseHandlers[requestId] = { + callback: callback, + result: null + }; + var message = bufferFromString(header + JSON.stringify(self._getRequest(requestId, bytecode))); + self._ws.send(message); + }); + }); +}; + +DriverRemoteConnection.prototype._getRequest = function (id, bytecode) { + return ({ + 'requestId': { '@type': 'g:UUID', '@value': id }, + 'op': 'bytecode', + 'processor': 'traversal', + 'args': { + 'gremlin': this._writer.adaptObject(bytecode), + 'aliases': { 'g': 'g'} + } + }); +}; + +DriverRemoteConnection.prototype._handleMessage = function (data) { + var response = this._reader.read(JSON.parse(data.toString())); + var handler = this._responseHandlers[response.requestId]; + if (response.status.code >= 400) { + // callback in error + return handler.callback( + new Error(util.format('Server error: %s (%d)', response.status.message, response.status.code))); + } + switch (response.status.code) { + case responseStatusCode.noContent: + return handler.callback(null, { traversers: []}); + case responseStatusCode.partialContent: + handler.result = handler.result || []; + handler.result.push.apply(handler.result, response.result.data); + break; + default: + if (handler.result) { + handler.result.push.apply(handler.result, response.result.data); + } + else { + handler.result = response.result.data; + } + return handler.callback(null, { traversers: handler.result }); + } +}; + +/** + * Closes the Connection. + * @return {Promise} + */ +DriverRemoteConnection.prototype.close = function (promiseFactory) { + if (this._closePromise) { + return this._closePromise; + } + var self = this; + return this._closePromise = utils.toPromise(promiseFactory, function promiseHandler(callback) { + self._ws.on('close', function () { + self.isOpen = false; + callback(); + }); + self._ws.close(); + }); +}; + +function getUuid() { + var buffer = crypto.randomBytes(16); + //clear the version + buffer[6] &= 0x0f; + //set the version 4 + buffer[6] |= 0x40; + //clear the variant + buffer[8] &= 0x3f; + //set the IETF variant + buffer[8] |= 0x80; + var hex = buffer.toString('hex'); + return ( + hex.substr(0, 8) + '-' + + hex.substr(8, 4) + '-' + + hex.substr(12, 4) + '-' + + hex.substr(16, 4) + '-' + + hex.substr(20, 12)); +} + +var bufferFromString = Buffer.from || function newBuffer(text) { + return new Buffer(text, 'utf8'); +}; + +module.exports = DriverRemoteConnection; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js new file mode 100644 index 0000000..8176c37 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js @@ -0,0 +1,84 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +var t = require('../process/traversal'); +var TraversalStrategy = require('../process/traversal-strategy').TraversalStrategy; +var utils = require('../utils'); +var inherits = utils.inherits; + +function RemoteConnection(url, traversalSource) { + this.url = url; +} + +/** + * @abstract + * @param {Bytecode} bytecode + * @param {Function|undefined} promiseFactory + * @returns {Promise} + */ +RemoteConnection.prototype.submit = function (bytecode, promiseFactory) { + throw new Error('submit() was not implemented'); +}; + +/** + * @extends {Traversal} + * @constructor + */ +function RemoteTraversal(traversers, sideEffects) { + t.Traversal.call(this, null, null, null); + this.traversers = traversers; + this.sideEffects = sideEffects; +} + +inherits(RemoteTraversal, t.Traversal); + +/** + * + * @param {RemoteConnection} connection + * @extends {TraversalStrategy} + * @constructor + */ +function RemoteStrategy(connection) { + TraversalStrategy.call(this); + this.connection = connection; +} + +inherits(RemoteStrategy, TraversalStrategy); + +/** @override */ +RemoteStrategy.prototype.apply = function (traversal, promiseFactory) { + if (traversal.traversers) { + return utils.resolvedPromise(promiseFactory); + } + return this.connection.submit(traversal.getBytecode(), promiseFactory).then(function (remoteTraversal) { + traversal.sideEffects = remoteTraversal.sideEffects; + traversal.traversers = remoteTraversal.traversers; + }); +}; + +module.exports = { + RemoteConnection: RemoteConnection, + RemoteStrategy: RemoteStrategy, + RemoteTraversal: RemoteTraversal +}; http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/2b4e8e3b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js new file mode 100644 index 0000000..9256e6a --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js @@ -0,0 +1,99 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +/** + * Creates a new instance of Bytecode + * @param {Bytecode} [toClone] + * @constructor + */ +function Bytecode(toClone) { + if (!toClone) { + this.sourceInstructions = []; + this.stepInstructions = []; + } + else { + this.sourceInstructions = toClone.sourceInstructions.slice(0); + this.stepInstructions = toClone.sourceInstructions.slice(0); + } +} + +/** + * Adds a new source instructions + * @param {String} name + * @param {Array} values + * @returns {Bytecode} + */ +Bytecode.prototype.addSource = function (name, values) { + if (name === undefined) { + throw new Error('Name is not defined'); + } + var instruction = new Array(values.length + 1); + instruction[0] = name; + for (var i = 0; i < values.length; ++i) { + instruction[i + 1] = this._convertToArgument(values[i]); + } + this.sourceInstructions.push(this._generateInstruction(name, values)); + return this; +}; + +/** + * Adds a new step instructions + * @param {String} name + * @param {Array} values + * @returns {Bytecode} + */ +Bytecode.prototype.addStep = function (name, values) { + if (name === undefined) { + throw new Error('Name is not defined'); + } + this.stepInstructions.push(this._generateInstruction(name, values)); + return this; +}; + +Bytecode.prototype._generateInstruction = function (name, values) { + var length = (values ? values.length : 0) + 1; + var instruction = new Array(length); + instruction[0] = name; + for (var i = 1; i < length; i++) { + instruction[i] = this._convertToArgument(values[i - 1]); + } + return instruction; +}; + +/** + * Returns the JSON representation of the source and step instructions + * @returns {String} + */ +Bytecode.prototype.toString = function () { + return ( + (this.sourceInstructions.length > 0 ? JSON.stringify(this.sourceInstructions) : '') + + (this.stepInstructions.length > 0 ? JSON.stringify(this.stepInstructions) : '') + ); +}; + +Bytecode.prototype._convertToArgument = function (value) { + return value; +}; + +module.exports = Bytecode; \ No newline at end of file