Commits

Andy Gross  committed 4e9a272 Merge
  • Participants
  • Parent commits ae895cf, fe50153

Comments (0)

Files changed (102)

 www/LICENSE
 www/TODO
 www/javadoc/*
-ebin/.*\.(beam|app)$
+ebin/.*\.(beam)$
 (^|/)erl_crash.dump$
 .*~$
 log/.*.log$

File apps/erlang_js/.hgignore

+ebin/.*\.beam$
+c_src/deps/(js|mozilla|nspr_release)/.*
+c_src/include/js/.*
+c_src/.*(\.o|\.a)$
+

File apps/erlang_js/LICENSE

+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+

File apps/erlang_js/Makefile

+all:
+	./rebar compile
+
+clean:
+	rm -rf tests_ebin docs
+	./rebar clean
+	cd c_src;make jsclean
+
+realclean: clean
+	cd c_src;make dist
+
+test: all
+	@mkdir -p tests_ebin
+	@cd tests;erl -make
+	@erl -noshell -boot start_sasl -pa ebin -pa tests_ebin -s erlang_js -eval 'test_suite:test().' -s init stop
+	@rm -f ebin/test_* ebin/*_tests.erl
+
+docs: all
+	@mkdir -p docs
+	@ERL_LIBS=`cd ..;pwd`;erl -noshell -eval 'edoc:application(erlang_js, [{dir, "docs"}]), init:stop().'

File apps/erlang_js/c_src/Makefile

+# This Makefile builds the dependencies (libjs and libnspr) needed by
+# spidermonkey_drv.so
+
+js: libjs.a
+
+libnspr4.a: deps/nspr_release/dist/lib/libnspr4.a
+	@cp deps/nspr_release/dist/lib/libnspr4.a .
+
+libjs.a: libnspr4.a js/src/libjs.a
+	@cp deps/js/src/libjs.a .
+
+deps/nspr_release/dist/lib/libnspr4.a: deps/mozilla deps/nspr_release
+	@cd deps/nspr_release;../mozilla/nsprpub/configure --disable-debug --enable-optimize;make
+
+deps/mozilla:
+	@mkdir -p $(@)
+	@cvs -q -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot co -r NSPR_4_8_RTM mozilla/nsprpub
+	@mv mozilla deps
+
+deps/nspr_release:
+	@mkdir -p $(@)
+
+js/src/libjs.a: deps/js
+	@cd deps/js/src;make BUILD_OPT=1 JS_DIST=../../nspr_release/dist JS_THREADSAFE=1 XCFLAGS="-DHAVE_VA_COPY -DVA_COPY=va_copy" -f Makefile.ref
+	@cp deps/js/src/*_OPT.OBJ/libjs.a deps/js/src
+	@mkdir -p include/js;
+	@cp deps/js/src/*.h include/js
+	@cp deps/js/src/*.tbl include/js
+	@cp deps/js/src/*_OPT.OBJ/*.h include/js
+
+deps/js:
+	@tar -C deps -xzf js-1.8.0-rc1.tar.gz
+
+dist:
+	@rm -f libjs.a libnspr4.a
+	@rm -f *flymake*
+	@rm -rf include
+	@rm -rf deps
+
+jsclean:
+	@rm -f *flymake*
+	@rm -f libjs.a libnspr4.a
+	@rm -rf include

File apps/erlang_js/c_src/build_driver_deps.sh

+#!/bin/bash
+
+set -e
+
+if [ `basename $PWD` != "src" ]; then
+    pushd c_src
+fi
+
+unset CFLAGS LDFLAGS
+
+make $1

File apps/erlang_js/c_src/config.h

+/*
+Copyright (c) 2009 Hypothetical Labs, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/

File apps/erlang_js/c_src/driver_comm.c

+/* author Kevin Smith <ksmith@basho.com>
+   copyright 2009-2010 Basho Technologies
+
+   Licensed 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. */
+
+#define COMMAND_SIZE 2
+
+#include <string.h>
+#include <strings.h>
+
+#include <erl_driver.h>
+
+#include "driver_comm.h"
+
+inline int read_int32(char **data) {
+  char *d = *data;
+  int value = ((((int)(((unsigned char*) (d))[0]))  << 24) |
+	       (((int)(((unsigned char*) (d))[1]))  << 16) |
+	       (((int)(((unsigned char*) (d))[2]))  << 8)  |
+	       (((int)(((unsigned char*) (d))[3]))));
+  (*data) += 4;
+  return value;
+}
+
+char *read_command(char **data) {
+  char *buf = (char *) driver_alloc(COMMAND_SIZE + 1);
+  memset(buf, 0, COMMAND_SIZE + 1);
+  memcpy(buf, (const char *) *data, COMMAND_SIZE);
+  (*data) += 2;
+  return buf;
+}
+
+char *read_string(char **data) {
+  int length = read_int32(data);
+  char *buf = NULL;
+  if (length > 0) {
+    buf = (char *) driver_alloc(length + 1);
+    memset(buf, 0, length + 1);
+    memcpy(buf, (const char *) *data, length);
+    (*data) += length;
+  }
+  return buf;
+}

File apps/erlang_js/c_src/driver_comm.h

+/* author Kevin Smith <ksmith@basho.com>
+   copyright 2009-2010 Basho Technologies
+
+   Licensed 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. */
+
+#ifndef __DRIVER_COMM__
+#define __DRIVER_COMM__
+
+int read_int32(char **data);
+
+/* Command strings will be two characters long */
+/* and must be freed via driver_free()         */
+char *read_command(char **data);
+
+/* Any string read with this function must be */
+/* freed with driver_free()                   */
+char *read_string(char **data);
+
+#endif

File apps/erlang_js/c_src/js-1.8.0-rc1.tar.gz

Binary file added.

File apps/erlang_js/c_src/spidermonkey.c

+/* author Kevin Smith <ksmith@basho.com>
+   copyright 2009-2010 Basho Technologies
+
+   Licensed 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. */
+
+#include <string.h>
+#include <erl_driver.h>
+
+#include "spidermonkey.h"
+
+/* The class of the global object. */
+static JSClass global_class = {
+    "global", JSCLASS_GLOBAL_FLAGS,
+    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
+    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
+    JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+char *copy_string(const char *source) {
+  int size = strlen(source) + 1;
+  char *retval = driver_alloc(size);
+  memset(retval, 0, size);
+  strncpy(retval, source, size - 1);
+  return retval;
+}
+
+char *copy_jsstring(JSString *source) {
+  char *buf = JS_GetStringBytes(source);
+  return copy_string(buf);
+}
+
+inline void begin_request(spidermonkey_vm *vm) {
+  JS_SetContextThread(vm->context);
+  JS_BeginRequest(vm->context);
+}
+
+inline void end_request(spidermonkey_vm *vm) {
+  JS_EndRequest(vm->context);
+  JS_ClearContextThread(vm->context);
+}
+
+void on_error(JSContext *context, const char *message, JSErrorReport *report) {
+  if (report->flags & JSREPORT_EXCEPTION) {
+    spidermonkey_error *sm_error = (spidermonkey_error *) driver_alloc(sizeof(spidermonkey_error));
+    sm_error->msg = copy_string(message);
+    sm_error->lineno = report->lineno;
+    sm_error->offending_source = copy_string(report->linebuf);
+    JS_SetContextPrivate(context, sm_error);
+  }
+}
+
+spidermonkey_vm *sm_initialize() {
+  spidermonkey_vm *vm = (spidermonkey_vm*) driver_alloc(sizeof(spidermonkey_vm));
+  vm->runtime = JS_NewRuntime(MAX_GC_SIZE);
+  JS_SetGCParameter(vm->runtime, JSGC_MAX_BYTES, CONTEXT_HEAP_SIZE);
+  JS_SetGCParameter(vm->runtime, JSGC_MAX_MALLOC_BYTES, LAST_DITCH_GC_THRESHOLD);
+  vm->context = JS_NewContext(vm->runtime, CONTEXT_THREAD_STACK_SIZE);
+  begin_request(vm);
+  JS_SetOptions(vm->context, JSOPTION_VAROBJFIX);
+  JS_SetOptions(vm->context, JSOPTION_STRICT);
+  JS_SetOptions(vm->context, JSOPTION_COMPILE_N_GO);
+  JS_SetOptions(vm->context, JSVERSION_LATEST);
+  vm->global = JS_NewObject(vm->context, &global_class, NULL, NULL);
+  JS_InitStandardClasses(vm->context, vm->global);
+  JS_SetErrorReporter(vm->context, on_error);
+  end_request(vm);
+  return vm;
+}
+
+void sm_stop(spidermonkey_vm *vm) {
+  JS_SetContextThread(vm->context);
+  JS_DestroyContext(vm->context);
+  JS_DestroyRuntime(vm->runtime);
+  driver_free(vm);
+}
+
+void sm_shutdown() {
+  JS_ShutDown();
+}
+
+char *error_to_json(const spidermonkey_error *error) {
+  /* Allocate 1K to build error (hopefully that's enough!) */
+  int size = sizeof(char) * 1024;
+  char *retval = (char *) driver_alloc(size);
+  snprintf(retval, size, "{\"error\": {\"lineno\": %d, \"message\": \"%s\", \"source\": \"%s\"}}", error->lineno,
+	   error->msg, error->offending_source);
+  return retval;
+}
+
+void free_error(spidermonkey_error *error) {
+  driver_free(error->offending_source);
+  driver_free(error->msg);
+  driver_free(error);
+}
+
+char *sm_eval(spidermonkey_vm *vm, const char *filename, const char *code, int handle_retval) {
+  char *retval = NULL;
+  JSScript *script;
+  jsval result;
+
+  begin_request(vm);
+  script = JS_CompileScript(vm->context,
+			    vm->global,
+			    code, strlen(code),
+			    filename, 1);
+  spidermonkey_error *error = (spidermonkey_error *) JS_GetContextPrivate(vm->context);
+  if (error == NULL) {
+    JS_ClearPendingException(vm->context);
+    JS_ExecuteScript(vm->context, vm->global, script, &result);
+    if (handle_retval) {
+      if (JSVAL_IS_STRING(result)) {
+	JSString *str = JS_ValueToString(vm->context, result);
+	retval = copy_jsstring(str);
+      }
+      else {
+	retval = copy_string("{\"error\": \"non-JSON return value\"}");
+      }
+    }
+    JS_DestroyScript(vm->context, script);
+  }
+  else {
+    retval = error_to_json(error);
+    free_error(error);
+    JS_SetContextPrivate(vm->context, NULL);
+  }
+  JS_MaybeGC(vm->context);
+  end_request(vm);
+  return retval;
+}

File apps/erlang_js/c_src/spidermonkey.h

+/* author Kevin Smith <ksmith@basho.com>
+   copyright 2009-2010 Basho Technologies
+
+   Licensed 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. */
+
+#ifndef __SPIDERMONKEY_INTERFACE_
+#define __SPIDERMONKEY_INTERFACE_
+
+#include "jsapi.h"
+
+typedef struct _spidermonkey_error_t {
+  unsigned int lineno;
+  char *msg;
+  char *offending_source;
+} spidermonkey_error;
+
+typedef struct _spidermonkey_vm_t {
+  JSRuntime* runtime;
+  JSContext* context;
+  JSObject* global;
+} spidermonkey_vm;
+
+/* Bytes to allocate before GC */
+#define MAX_GC_SIZE 1024 * 1024
+
+/* 8K stack size for each context */
+#define CONTEXT_THREAD_STACK_SIZE 8192
+
+/* 8MB heap for each context */
+#define CONTEXT_HEAP_SIZE 8 * 1024 * 1024
+
+/* 8MB last ditch GC threshold */
+#define LAST_DITCH_GC_THRESHOLD CONTEXT_HEAP_SIZE
+
+spidermonkey_vm *sm_initialize();
+
+void sm_stop(spidermonkey_vm *vm);
+
+void sm_shutdown();
+
+char *sm_eval(spidermonkey_vm *vm, const char *filename, const char *code, int handle_retval);
+
+#endif

File apps/erlang_js/c_src/spidermonkey_drv.c

+/* author Kevin Smith <ksmith@basho.com>
+   copyright 2009-2010 Basho Technologies
+
+   Licensed 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. */
+
+#include <string.h>
+#include <erl_driver.h>
+
+#include "spidermonkey.h"
+#include "config.h"
+#include "driver_comm.h"
+
+typedef struct _spidermonkey_drv_t {
+  ErlDrvPort port;
+  spidermonkey_vm *vm;
+  ErlDrvTermData atom_ok;
+  ErlDrvTermData atom_error;
+  ErlDrvTermData atom_unknown_cmd;
+  int shutdown;
+} spidermonkey_drv_t;
+
+
+/* Forward declarations */
+static ErlDrvData start(ErlDrvPort port, char *cmd);
+static void stop(ErlDrvData handle);
+static void process(ErlDrvData handle, ErlIOVec *ev);
+
+static ErlDrvEntry spidermonkey_drv_entry = {
+    NULL,                             /* init */
+    start,                            /* startup */
+    stop,                             /* shutdown */
+    NULL,                             /* output */
+    NULL,                             /* ready_input */
+    NULL,                             /* ready_output */
+    (char *) "spidermonkey_drv",      /* the name of the driver */
+    NULL,                             /* finish */
+    NULL,                             /* handle */
+    NULL,                             /* control */
+    NULL,                             /* timeout */
+    process,                          /* process */
+    NULL,                             /* ready_async */
+    NULL,                             /* flush */
+    NULL,                             /* call */
+    NULL,                             /* event */
+    ERL_DRV_EXTENDED_MARKER,          /* ERL_DRV_EXTENDED_MARKER */
+    ERL_DRV_EXTENDED_MAJOR_VERSION,   /* ERL_DRV_EXTENDED_MAJOR_VERSION */
+    ERL_DRV_EXTENDED_MAJOR_VERSION,   /* ERL_DRV_EXTENDED_MINOR_VERSION */
+    ERL_DRV_FLAG_USE_PORT_LOCKING     /* ERL_DRV_FLAGs */
+};
+
+
+static void send_output(ErlDrvPort port, ErlDrvTermData *terms, int term_count) {
+  driver_output_term(port, terms, term_count);
+}
+
+static void send_ok_response(spidermonkey_drv_t *dd, const char *call_id) {
+  ErlDrvTermData terms[] = {ERL_DRV_BUF2BINARY, (ErlDrvTermData) call_id, strlen(call_id),
+			    ERL_DRV_ATOM, dd->atom_ok,
+			    ERL_DRV_TUPLE, 2};
+  send_output(dd->port, terms, sizeof(terms) / sizeof(terms[0]));
+}
+
+static void send_error_string_response(spidermonkey_drv_t *dd, const char *call_id, const char *msg) {
+  ErlDrvTermData terms[] = {ERL_DRV_BUF2BINARY, (ErlDrvTermData) call_id, strlen(call_id),
+                            ERL_DRV_ATOM, dd->atom_error,
+			    ERL_DRV_BUF2BINARY, (ErlDrvTermData) msg, strlen(msg),
+			    ERL_DRV_TUPLE, 3};
+  send_output(dd->port, terms, sizeof(terms) / sizeof(terms[0]));
+}
+
+static void send_string_response(spidermonkey_drv_t *dd, const char *call_id, const char *result) {
+  ErlDrvTermData terms[] = {ERL_DRV_BUF2BINARY, (ErlDrvTermData) call_id, strlen(call_id),
+                            ERL_DRV_ATOM, dd->atom_ok,
+			    ERL_DRV_BUF2BINARY, (ErlDrvTermData) result, strlen(result),
+			    ERL_DRV_TUPLE, 3};
+  send_output(dd->port, terms, sizeof(terms) / sizeof(terms[0]));
+}
+
+static void unknown_command(spidermonkey_drv_t *dd, const char *call_id) {
+  ErlDrvTermData terms[] = {ERL_DRV_BUF2BINARY, (ErlDrvTermData) call_id, strlen(call_id),
+                            ERL_DRV_ATOM, dd->atom_error,
+			    ERL_DRV_ATOM, dd->atom_unknown_cmd,
+			    ERL_DRV_TUPLE, 3};
+  send_output(dd->port, terms, sizeof(terms) / sizeof(terms[0]));
+}
+
+DRIVER_INIT(spidermonkey_drv) {
+  return &spidermonkey_drv_entry;
+}
+
+static ErlDrvData start(ErlDrvPort port, char *cmd) {
+  spidermonkey_drv_t *retval = (spidermonkey_drv_t*) driver_alloc(sizeof(spidermonkey_drv_t));
+  retval->port = port;
+  retval->shutdown = 0;
+  retval->atom_ok = driver_mk_atom((char *) "ok");
+  retval->atom_error = driver_mk_atom((char *) "error");
+  retval->atom_unknown_cmd = driver_mk_atom((char *) "unknown_command");
+  retval->vm = sm_initialize();
+  return (ErlDrvData) retval;
+}
+
+static void stop(ErlDrvData handle) {
+  spidermonkey_drv_t *dd = (spidermonkey_drv_t*) handle;
+  sm_stop(dd->vm);
+  if (dd->shutdown) {
+    sm_shutdown();
+  }
+  driver_free(dd);
+}
+
+static void process(ErlDrvData handle, ErlIOVec *ev) {
+  spidermonkey_drv_t *dd = (spidermonkey_drv_t*) handle;
+  ErlDrvBinary *args = ev->binv[1];
+  char *data = args->orig_bytes;
+  char *command = read_command(&data);
+  char *call_id = read_string(&data);
+  char *result = NULL;
+  if (strncmp(command, "ej", 2) == 0) {
+    char *filename = read_string(&data);
+    char *code = read_string(&data);
+    result = sm_eval(dd->vm, filename, code, 1);
+    if (strstr(result, "{\"error\"") != NULL) {
+      send_error_string_response(dd, call_id, result);
+    }
+    else {
+      send_string_response(dd, call_id, result);
+    }
+    driver_free(filename);
+    driver_free(code);
+    driver_free(result);
+  }
+  else if (strncmp(command, "dj", 2) == 0) {
+    char *filename = read_string(&data);
+    char *code = read_string(&data);
+    result = sm_eval(dd->vm, filename, code, 0);
+    if (result == NULL) {
+      send_ok_response(dd, call_id);
+    }
+    else {
+      send_error_string_response(dd, call_id, result);
+      driver_free(result);
+    }
+    driver_free(filename);
+    driver_free(code);
+  }
+  else if (strncmp(command, "sd", 2) == 0) {
+    dd->shutdown = 1;
+    send_ok_response(dd, call_id);
+  }
+  else {
+    unknown_command(dd, call_id);
+  }
+  driver_free(command);
+  driver_free(call_id);
+}

File apps/erlang_js/ebin/erlang_js.app

+% -*- mode: erlang -*-
+{application, erlang_js,
+ [{description,  "Interface between BEAM and JS"},
+  {vsn,          "0.1"},
+  {modules,      [erlang_js, erlang_js_sup, js, js_benchmark, js_cache, js_driver, js_drv_comm, js_memory, js_mochijson2, js_mochinum]},
+  {registered,   [erlang_js_sup, js_cache]},
+  {applications, [kernel, stdlib, sasl]},
+  {mod, {erlang_js, []}}]}.

File apps/erlang_js/priv/json2.js

+/*
+    http://www.JSON.org/json2.js
+    2009-08-17
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or '&nbsp;'),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+"use strict";
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+    this.JSON = {};
+}
+
+(function () {
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                   this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON =
+        Number.prototype.toJSON =
+        Boolean.prototype.toJSON = function (key) {
+            return this.valueOf();
+        };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ?
+            '"' + string.replace(escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string' ? c :
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' :
+                    gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                                mind + ']' :
+                          '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' :
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                     typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());

File apps/erlang_js/rebar

Binary file added.

File apps/erlang_js/rebar.config

+%% Linked-in driver config
+{so_name, "spidermonkey"}.
+{port_envs, [{"CFLAGS",  "$CFLAGS -I c_src/include/js -DXP_UNIX"},
+             {"LDFLAGS", "$LDFLAGS c_src/libjs.a c_src/libnspr4.a"}]}.
+{port_pre_script, {"c_src/build_driver_deps.sh", "js"}}.
+{port_cleanup_script, "c_src/build_driver_deps.sh jsclean"}.

File apps/erlang_js/src/erlang_js.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+%% @ doc This module is the entry point to start erlang_js as an OTP application.
+-module(erlang_js).
+
+-behaviour(application).
+
+%% Application callbacks
+-export([start/0, start/2, stop/1]).
+
+
+%% @spec start() -> ok | {error, any()}
+%% @doc Starts the erlang_js OTP application and all
+%% dependencies. Intended for use with the Erlang VM's
+%% -s option
+start() ->
+    start_deps([sasl]),
+    application:start(erlang_js).
+
+%% @private
+start(_StartType, _StartArgs) ->
+    erlang_js_sup:start_link().
+
+%% @private
+stop(_State) ->
+    ok.
+
+%% Internal functions
+start_deps([]) ->
+    ok;
+start_deps([App|T]) ->
+    case is_running(App, application:which_applications()) of
+        false ->
+            ok = application:start(App);
+        true ->
+            ok
+    end,
+    start_deps(T).
+
+is_running(_App, []) ->
+    false;
+is_running(App, [{App, _, _}|_]) ->
+    true;
+is_running(App, [_|T]) ->
+    is_running(App, T).

File apps/erlang_js/src/erlang_js_sup.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+%% @doc Top level supervisor for erlang_js. It is chiefly responsible for
+%% the js_cache file cache process.
+-module(erlang_js_sup).
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+-define(SERVER, ?MODULE).
+
+%% @private
+start_link() ->
+    supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%% @private
+init([]) ->
+    RestartStrategy = one_for_one,
+    MaxRestarts = 1000,
+    MaxSecondsBetweenRestarts = 3600,
+
+    SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
+
+    Restart = permanent,
+    Shutdown = 2000,
+    Type = worker,
+
+    case js_driver:load_driver() of
+        false ->
+            throw({error, {load_error, "Failed to load spidermonkey_drv.so"}});
+        true ->
+            Cache = {cache, {js_cache, start_link, []},
+                     Restart, Shutdown, Type, [js_cache]},
+
+            {ok, {SupFlags, [Cache]}}
+    end.

File apps/erlang_js/src/js.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+-module(js).
+
+-export([define/2, define/3, eval/2, call/3, call/4]).
+
+define(Ctx, Js) ->
+    define(Ctx, Js, []).
+
+define(Ctx, Js, Bindings) ->
+    JsBindings = list_to_binary(build_bindings(Bindings, [])),
+    FinalJs = iolist_to_binary([JsBindings, Js]),
+    js_driver:define_js(Ctx, FinalJs).
+
+eval(Ctx, Js) ->
+    js_driver:eval_js(Ctx, Js).
+
+call(Ctx, FunctionName, Args) ->
+    call(Ctx, FunctionName, Args, []).
+
+call(Ctx, FunctionName, Args, Bindings) ->
+    JsBindings = list_to_binary(build_bindings(Bindings, [])),
+    ArgList = build_arg_list(Args, []),
+    Js = iolist_to_binary([JsBindings, FunctionName, "(", ArgList, ");"]),
+    js_driver:eval_js(Ctx, Js).
+
+%% Internal functions
+build_bindings([], Accum) ->
+    Accum;
+build_bindings([{VarName, Value}|T], Accum) ->
+    FinalVarName = case is_atom(VarName) of
+                       true ->
+                           atom_to_list(VarName);
+                       false ->
+                           VarName
+                   end,
+    build_bindings(T, [["var ", FinalVarName, "=", js_mochijson2:encode(Value), ";\n"]|Accum]).
+
+build_arg_list([], Accum) ->
+    lists:reverse(Accum);
+build_arg_list([H|[]], Accum) ->
+    build_arg_list([], [js_mochijson2:encode(H)|Accum]);
+build_arg_list([H|T], Accum) ->
+    build_arg_list(T, [[js_mochijson2:encode(H), ","]|Accum]).

File apps/erlang_js/src/js_benchmark.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+%% @doc Utility module for measuring latencies across the Erlang/Javascript
+%% boundary. This module implements a very simple benchmark of defining and
+%% executing a simple Javascript function 1000, 10000, and 100000 times.
+
+-module(js_benchmark).
+
+-define(COUNTS, [1000, 10000, 100000]).
+
+-export([run/0]).
+
+%% @spec run() -> list(int(), int(), int())
+%% @doc Runs the benchmark and returns the average time to
+%% process a function call for each set of iterations in
+%% microseconds.
+run() ->
+    application:start(erlang_js),
+    {ok, Ctx} = js_driver:new(),
+    js:define(Ctx, "function add(x, y) { return x + y; }", []),
+    Result = [time_calls(Ctx, Count) || Count <- ?COUNTS],
+    js_driver:destroy(Ctx),
+    Result.
+
+%% @private
+time_calls(Ctx, Count) ->
+    io:format("Starting: ~p~n", [Count]),
+    Start = erlang:now(),
+    do_calls(Ctx, Count),
+    timer:now_diff(erlang:now(), Start) / Count.
+
+%% @private
+do_calls(_Ctx, 0) ->
+    ok;
+do_calls(Ctx, Count) ->
+    CorrectResult = Count * 2,
+    {ok, CorrectResult} = js:call(Ctx, "add", [Count, Count]),
+    do_calls(Ctx, Count - 1).

File apps/erlang_js/src/js_cache.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+%% @doc This module implements a basic cache. This cache is used to store
+%% files used to initialize each Javascript context. This is helpful because
+%% it prevents erlang_js from accessing the filesystem more than necessary.
+
+-module(js_cache).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0, store/2, delete/1, fetch/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+         terminate/2, code_change/3]).
+
+-define(SERVER, ?MODULE).
+
+-record(state, {cache=gb_trees:empty()}).
+
+%% @private
+start_link() ->
+    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
+
+%% @spec store(any(), any() -> ok
+%% @doc Store a key/value pair
+store(Key, Value) ->
+    gen_server:cast(?SERVER, {store, Key, Value}).
+
+%% @spec delete(any()) -> ok
+%% @doc Deletes a key/value pair, if present, from the cache.
+delete(Key) ->
+    gen_server:cast(?SERVER, {delete, Key}).
+
+%% @spec fetch(any()) -> any() | not_found
+%% @doc Retrieves a key/value pair from the cache. If the key
+%% is not in the cache, the atom 'not_found' is returned.
+fetch(Key) ->
+    gen_server:call(?SERVER, {fetch, Key}).
+
+init([]) ->
+    {ok, #state{}}.
+
+% @private
+handle_call({fetch, Key}, _From, #state{cache=Cache}=State) ->
+    Result = case gb_trees:lookup(Key, Cache) of
+                 {value, Value} ->
+                     Value;
+                 Other ->
+                     Other
+             end,
+    {reply, Result, State};
+
+% @private
+handle_call(_Request, _From, State) ->
+    {reply, ignore, State}.
+
+% @private
+handle_cast({store, Key, Value}, #state{cache=Cache}=State) ->
+    {noreply, State#state{cache=gb_trees:enter(Key, Value, Cache)}};
+
+% @private
+handle_cast({delete, Key}, #state{cache=Cache}=State) ->
+    {noreply, State#state{cache=gb_trees:delete(Key, Cache)}};
+
+% @private
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+% @private
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+% @private
+terminate(_Reason, _State) ->
+    ok.
+
+% @private
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.

File apps/erlang_js/src/js_driver.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+-module(js_driver).
+
+-export([load_driver/0, new/0, new/1, destroy/1, shutdown/1]).
+-export([define_js/2, define_js/3, eval_js/2, eval_js/3]).
+
+-define(SCRIPT_TIMEOUT, 5000).
+-define(DRIVER_NAME, "spidermonkey_drv").
+
+load_driver() ->
+    {ok, Drivers} = erl_ddll:loaded_drivers(),
+    case lists:member(?DRIVER_NAME, Drivers) of
+        false ->
+            case erl_ddll:load(priv_dir(), ?DRIVER_NAME) of
+                ok ->
+                    true;
+                _ ->
+                    false
+            end;
+        true ->
+            true
+    end.
+
+new() ->
+    {ok, Port} = new(no_json),
+    %% Load json converter for use later
+    case define_js(Port, <<"json2.js">>, json_converter(), ?SCRIPT_TIMEOUT) of
+        ok ->
+            {ok, Port};
+        {error, Reason} ->
+            port_close(Port),
+            {error, Reason}
+    end.
+
+new(no_json) ->
+    {ok, open_port({spawn, ?DRIVER_NAME}, [binary])};
+new(Initializer) when is_function(Initializer) ->
+    {ok, Port} = new(),
+    case Initializer(Port) of
+        ok ->
+            {ok, Port};
+        {error, Error} ->
+            error_logger:error_report(Error),
+            throw({error, init_failed})
+    end;
+new({InitMod, InitFun}) ->
+    {ok, Port} = new(),
+    case InitMod:InitFun(Port) of
+        ok ->
+            {ok, Port};
+        {error, Error} ->
+            error_logger:error_report(Error),
+            throw({error, init_failed})
+    end.
+
+destroy(Ctx) ->
+    port_close(Ctx).
+
+shutdown(Ctx) ->
+    call_driver(Ctx, "sd", [], 60000),
+    port_close(Ctx).
+
+define_js(Ctx, Js) ->
+    define_js(Ctx, Js, ?SCRIPT_TIMEOUT).
+
+define_js(Ctx, {file, FileName}, Timeout) ->
+    {ok, File} = file:read_file(FileName),
+    define_js(Ctx, list_to_binary(FileName), File, Timeout);
+define_js(Ctx, Js, Timeout) when is_binary(Js) ->
+    define_js(Ctx, <<"unnamed">>, Js, Timeout).
+
+define_js(Ctx, FileName, Js, Timeout) when is_binary(FileName),
+                                           is_binary(Js) ->
+    case call_driver(Ctx, "dj", [FileName, Js], Timeout) of
+        {error, ErrorJson} when is_binary(ErrorJson) ->
+            {struct, [{<<"error">>, {struct, Error}}]} = js_mochijson2:decode(ErrorJson),
+            {error, Error};
+        ok ->
+            ok
+    end.
+
+eval_js(Ctx, Js) ->
+    eval_js(Ctx, Js, ?SCRIPT_TIMEOUT).
+
+eval_js(Ctx, {file, FileName}, Timeout) ->
+    {ok, File} = file:read_file(FileName),
+    eval_js(Ctx, File, Timeout);
+eval_js(Ctx, Js, Timeout) when is_binary(Js) ->
+    case call_driver(Ctx, "ej", [<<"<unnamed>">>, jsonify(Js)], Timeout) of
+        {ok, Result} ->
+            {ok, js_mochijson2:decode(Result)};
+        {error, ErrorJson} when is_binary(ErrorJson) ->
+            case js_mochijson2:decode(ErrorJson) of
+                {struct, [{<<"error">>, {struct, Error}}]} ->
+                    {error, Error};
+                {struct, [{<<"error">>, Error}]} ->
+                    {error, Error}
+            end;
+        {error, Error} ->
+            {error, Error}
+    end.
+
+%% Internal functions
+jsonify(Code) when is_binary(Code) ->
+    {Body, <<LastChar:8>>} = split_binary(Code, size(Code) - 1),
+    C = case LastChar of
+            $; ->
+                Body;
+            _ ->
+                Code
+        end,
+    list_to_binary([<<"var result; try { result = JSON.stringify(">>, C, <<"); } catch(e) { result = JSON.stringify(e)} result;">>]).
+
+priv_dir() ->
+    %% Hacky workaround to handle running from a standard app directory
+    %% and .ez package
+    case code:priv_dir(erlang_js) of
+        {error, bad_name} ->
+            filename:join([filename:dirname(code:which(?MODULE)), "..", "priv"]);
+        Dir ->
+            Dir
+    end.
+
+call_driver(Ctx, Command, Args, Timeout) ->
+    CallToken = make_call_token(),
+    Marshalled = js_drv_comm:pack(Command, [CallToken] ++ Args),
+    port_command(Ctx, Marshalled),
+    Result = receive
+                 {CallToken, ok} ->
+                     ok;
+                 {CallToken, ok, R} ->
+                     {ok, R};
+                 {CallToken, error, Error} ->
+                     {error, Error}
+             after Timeout ->
+                     {error, timeout}
+             end,
+    Result.
+
+make_call_token() ->
+    list_to_binary(integer_to_list(erlang:phash2(erlang:make_ref()))).
+
+json_converter() ->
+    FileName = filename:join([priv_dir(), "json2.js"]),
+    case js_cache:fetch(FileName) of
+        none ->
+            {ok, Contents} = file:read_file(FileName),
+            js_cache:store(FileName, Contents),
+            Contents;
+        Contents ->
+            Contents
+    end.

File apps/erlang_js/src/js_drv_comm.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+-module(js_drv_comm).
+
+-export([pack/2]).
+
+pack(Cmd, Terms) when length(Cmd) == 2 ->
+    NewTerms = lists:foldr(fun(T, Acc) -> marshal_data(T, Acc) end, [], Terms),
+    list_to_binary(lists:flatten([[Cmd], [NewTerms]])).
+
+%% Internal functions
+marshal_data(Term, Acc) when is_integer(Term) ->
+    [<<Term:32>>|Acc];
+marshal_data(Term, Acc) when is_list(Term) ->
+    marshal_data(list_to_binary(Term), Acc);
+marshal_data(Term, Acc) when is_binary(Term) ->
+    S = size(Term),
+    [[<<S:32>>, Term]|Acc].

File apps/erlang_js/src/js_memory.erl

+%% @author Kevin Smith <ksmith@basho.com>
+%% @copyright 2009-2010 Basho Technologies
+%%
+%%    Licensed 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.
+
+-module(js_memory).
+
+-define(COUNT, 1000000).
+
+-export([stress/1]).
+
+stress(new) ->
+    Start = erlang:memory(total),
+    do(new, ?COUNT),
+    display(end_test() - Start);
+
+stress(error) ->
+    Start = erlang:memory(total),
+    do(error, ?COUNT),
+    display(end_test() - Start).
+
+%% Internal functions
+do(error, 0) ->
+    ok;
+do(error, Count) ->
+    show_count(Count),
+    {ok, P} = js_driver:new(),
+    {error, _Error} = js:define(P, <<"function foo(;">>),
+    js_driver:destroy(P),
+    do(error, Count - 1);
+
+do(new, 0) ->
+    ok;
+do(new, Count) ->
+    show_count(Count),
+    {ok, P} = js_driver:new(),
+    js_driver:destroy(P),
+    do(new, Count - 1).
+
+end_test() ->
+    [erlang:garbage_collect(P) || P <- erlang:processes()],
+    erlang:memory(total).
+
+display(Memory) ->
+    io:format("Used ~p bytes during test.~n", [Memory]).
+
+show_count(Count) ->
+    if
+        (?COUNT - Count) rem 1000 == 0 ->
+            io:format("~p~n", [Count]);
+        true ->
+            ok
+    end.

File apps/erlang_js/src/js_mochijson2.erl

+%% @author Bob Ippolito <bob@mochimedia.com>
+%% @copyright 2007 Mochi Media, Inc.
+
+%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works
+%%      with binaries as strings, arrays as lists (without an {array, _})
+%%      wrapper and it only knows how to decode UTF-8 (and ASCII).
+
+-module(js_mochijson2).
+-author('bob@mochimedia.com').
+-export([encoder/1, encode/1]).
+-export([decoder/1, decode/1]).
+
+% This is a macro to placate syntax highlighters..
+-define(Q, $\").
+-define(ADV_COL(S, N), S#decoder{offset=N+S#decoder.offset,
+                                 column=N+S#decoder.column}).
+-define(INC_COL(S), S#decoder{offset=1+S#decoder.offset,
+                              column=1+S#decoder.column}).
+-define(INC_LINE(S), S#decoder{offset=1+S#decoder.offset,
+                               column=1,
+                               line=1+S#decoder.line}).
+-define(INC_CHAR(S, C),
+        case C of
+            $\n ->
+                S#decoder{column=1,
+                          line=1+S#decoder.line,
+                          offset=1+S#decoder.offset};
+            _ ->
+                S#decoder{column=1+S#decoder.column,
+                          offset=1+S#decoder.offset}
+        end).
+-define(IS_WHITESPACE(C),
+        (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)).
+
+%% @type iolist() = [char() | binary() | iolist()]
+%% @type iodata() = iolist() | binary()
+%% @type json_string() = atom | binary()
+%% @type json_number() = integer() | float()
+%% @type json_array() = [json_term()]
+%% @type json_object() = {struct, [{json_string(), json_term()}]}
+%% @type json_iolist() = {json, iolist()}
+%% @type json_term() = json_string() | json_number() | json_array() |
+%%                     json_object() | json_iolist()
+