Spidermonkey

Embeddable Javascript engine

Embeddable Javascript engine

History

It has its roots in as the javascript engine of firefox. It is a moving target with every 6 weeks an release. It is C++ and crossplatform and provides many es6 features.

FAQ

What is it?

Javascript runtime.

How cool is it?

Very.

Competitors?

mjs, V8, ChacraCore, DMDScript. . When to use it? When you want to have a javascript interpreter inside your c++ application.

Is it dead?

Nope, it is still under active development and used in firefox.

Where is it?

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey

How to use it

The documentation is rather sparse, outdated and full with linkrot. But the library is rather impressive, easy to start but difficult to scale up. There is a nice video from the nidium on scaling spidermonkey.

Download

file: compile_and_link.sh

#!/bin/bash

mkdir -p ./tmp
cd ./tmp
wget -q https://ftp.mozilla.org/pub/firefox/releases/54.0b2/source/firefox-54.0b2.source.tar.xz
tar -xaf firefox-54.0b2.source.tar.xz
cd firefox-54.0b2/nsprpub
./configure  --enable-ipv6  --enable-64bit --enable-strip --disable-debug --enable-optimize=-O2
make -j1
cd ../firefox-54.0b2/js/src
configure --enable-static --disable-shared
make -j1

file: person.js

var person = new Person("John");
person.echo("Hello World"); // John says : "Hello world"

file: main.cpp

#include <stdlib.h>

#include <jsapi.h>
#include <js/Initialization.h>

struct Person {
    char *name;
};

static bool person_constructor(JSContext *cx, unsigned argc, JS::Value *vp);
static bool person_echo(JSContext *cx, unsigned argc, JS::Value *vp);
static void person_finalizer(JSFreeOp *fop, JSObject *obj);

static const JSClassOps person_classOps = {
    nullptr, nullptr, nullptr, nullptr,
    nullptr, nullptr, nullptr, person_finalizer,
};

static JSClass Person_class = { "Person", JSCLASS_HAS_PRIVATE, &person_classOps };

static JSFunctionSpec Person_funcs[] = {
    JS_FN("echo", person_echo, 1, JSPROP_ENUMERATE | JSPROP_PERMANENT),
    JS_FS_END
};

static bool person_constructor(JSContext *cx, unsigned argc, JS::Value *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
    if (!args.isConstructing()) {
        JS_ReportErrorLatin1(cx, "Bad constructor");
        return false;
    }

    if (argc != 1 || !args[0].isString()) {
        JS_ReportErrorLatin1(cx, "Invalid arguments");
        return false;
    }

    // Create the JS object for the Person
    JS::RootedObject person(cx, JS_NewObjectForConstructor(cx, &Person_class, args));

    // Create the C object for the Person
    Person *nativePerson = new Person();

    // Convert the JS string to a C char array
    // and assign the name to the Person struct
    JSAutoByteString name(cx, args[0].toString());
    if (!name) {
        return false;
    }
    nativePerson->name = strdup(name.ptr());

    // Store a pointer to the Person struct
    // inside the JS object for later use
    JS_SetPrivate(person, nativePerson);

    // Returns to the JS the newly created object
    args.rval().setObjectOrNull(person);

    return true;
}

static bool person_echo(JSContext *cx, unsigned argc, JS::Value *vp)
{
    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);

    // Retrieve |this| object
    JS::RootedValue thisValue(cx, args.thisv());
    JS::RootedObject thisObj(cx, thisValue.toObjectOrNull());
    if (!thisObj) {
        JS_ReportErrorLatin1(cx, "Invalid context");
        return false;
    }

    // Get the pointer to our C Person struct
    Person *person = static_cast<Person *>(JS_GetInstancePrivate(cx, thisObj, &Person_class, &args));
    if (!person) {
        return false;
    }

    // Arguments checking and converting
    if (argc != 1 || !args[0].isString()) {
        JS_ReportErrorLatin1(cx, "Invalid arguments");
        return false;
    }

    JS::RootedString str(cx, args[0].toString());
    if (!str) {
        return false;
    }

    JSAutoByteString bytes(cx, str);
    if (!bytes) {
        return false;
    }

    printf("Person %s says : \"%s\"\n", person->name, bytes.ptr());
    return true;
}

void person_finalizer(JSFreeOp *fop, JSObject *obj)
{
    Person *person = static_cast<Person *>(JS_GetPrivate(obj));
    if (!person) {
        return;
    }

    free(person->name);
    delete person;
}

static const JSClassOps global_classOps = {
    nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    nullptr, nullptr, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook };

static JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, &global_classOps};

void reportError(JSContext *cx, JSErrorReport * report) {
    const char * message = report->message().c_str();
    fprintf(stderr, "%s:%u:%s\n",
            report->filename ? report->filename : "[no filename]",
            (unsigned int) report->lineno,
            message);
}

int run(JSContext *cx)
{
    // Enter a request before running anything in the context.
    JSAutoRequest ar(cx);

    // Create the global object and a new compartment.
    JS::CompartmentOptions options;
    JS::CompartmentCreationOptions& creationOptions = options.creationOptions();
    JS::CompartmentBehaviors& behaviours = options.behaviors();
    options.behaviors().setVersion(JSVERSION_DEFAULT);
    options.creationOptions().setSharedMemoryAndAtomicsEnabled(1);
    JS::RootedObject global(cx);
    global = JS_NewGlobalObject(cx, &global_class, nullptr,
                                JS::DontFireOnNewGlobalHook, options);
    if (!global) {
        return 1;
    }

    // Enter the new global object's compartment.
    JSAutoCompartment ac(cx, global);

    // Populate the global object with the standard globals, like Object and
    // Array.
    if (!JS_InitStandardClasses(cx, global)) {
        return 1;
    }

    // Expose Person JS object on the global object
    JS_InitClass(cx, global, nullptr, &Person_class, person_constructor, 0, nullptr, Person_funcs, nullptr, nullptr);

    JS::RootedValue rval(cx);
    JS::CompileOptions opts(cx);

    bool ok = JS::Evaluate(cx, opts, "person.js", &rval);

    return ok ? 0 : 1;
}

int main(int argc, const char *argv[]) {
    if (!JS_Init()) {
       return 1;
    }

    size_t nurseryBytes = JS::DefaultNurseryBytes;

    JSContext *cx = JS_NewContext(JS::DefaultHeapMaxBytes, nurseryBytes);
    if (!cx) {
       return 1;
    }

    JS::SetWarningReporter(cx, reportError);

    int status = run(cx);

    JS_DestroyContext(cx);
    JS_ShutDown();

    return status;
}

file: build_main.sh

#!/bin/bash

gcc -o ./tmp/main \
    main.cpp \
    --std=c++11 \
    -I./tmp/firefox-54.0b2/js/src/dist/include/ \
    -L./tmp/firefox-54.0b2/js/src/dist/lib \
    -L./tmp/firefox-54.0b2/js/src/js/src/ \
    -L./tmp/firefox-54.0b2/js/src/mozglue/build \
    -L./tmp/firefox-54.0b2/nsprpub/dist/lib \
    -lmozglue -ljs_static -lnspr4 -lz -lm -lpthread -ldl -lstdc++

Further reading:

We only tipped the top of the iceberg here. There is much more, but if you want to kickstart your c++ application with a full javascript environment you should checkout libnidiumcore at www.nidium.com.

They provide a library that is ready for embedders.

The file js/src/shell/js.cpp is full of interesting stuff that will raise your c++ knowlegdge.