JSPP – Morph C++ Into Javascript

C++ has a new standard called C++0x (Wikipedia,Bjarne Stroustrup) that includes many interesting features such as Lambda, For Each, List Initialization ... Those features are so powerful that they allow to write C++ as if it was Javascript.

The goal of this project is to transform C++ into Javascript. We want to be able to copy & paste Javascript into C++ and be able to run it. While this is not 100% feasible, the result is quite amazing.

This is only a prototype. In about 600 lines of code we manage to make the core of the Javascript language.

You can view the source and compile examples at the JSPP Github Repository.

JSON

The Javascript Object notation can be emulated thanks to C++0x initialization lists and a bit of operator overload hackery._has an operator[]that returns aKeyValueobject, that has an operator=overload that fills both keys and values. For each value of the initialization listL If that's an objet, it is treated like anArray(add one to the lenght and use the length as key). If that's aKeyValue, both key and value are set.

There is an ambiguity with nested initialization lists, we use_()to cast the list into an Object. It is probably possible to fix it.

C++
var json = {
    _["number"] = 42,
    _["string"] = "vjeux",
    _["array"] = {1, 2, "three"},

    _["nested"] = _({
        _["first"] = 1
    })
};

std::cout < < json;
// {array: [1, 2, three], nested: {first: 1},
//  number: 42, string: vjeux}
Javascript
var json = {
    "number": 42,
    "string": "vjeux",
    "array": [1, 2, "three"],

    "nested": {
        "first": 1
    }
};

console.log(json);
// {number: 42, string: 'vjeux',
//  array: [1, 2, three], nested: {first: 1}}

Function

C++0x added lambda to the language with the following syntax:[capture] (arguments) -> returnType { body }.functionis a macro that transformsfunction (var i)into[=] (Object This, Object arguments, var i) -> Object. This allows to use the Javascript syntax and let us sneakily add thethisandargumentsmagic variables.

C++ is strongly typed and even lambdas have types. We can overload the Object constructor on
lambda arityand have a typed container for each one. Then, we overload the() operatorthat will call the stored lambda. We we carefully addundefinedvalues for unspecified arguments and fill theThisandargumentsvariables.

In Javascript, when a function does not return a value, it returns undefined. Sadly, we cannot have a default return value in C++, you have to write it yourself.

Since everything must be typed in C++, we have to addvarbefore the argument name.

C++
var Utils = {
  _["map"] = function (var array, var func) {
    for (var i = 0; i < array["length"]; ++i) {
      array[i] = func(i, array[i]);
    }
    return undefined;
  }
};

var a = {"a", "b", "c"};
std::cout << a;
// [a, b, c]

Utils["map"](a, function (var key, var value) {
  return "(" + key + ":" + value + ")";
});
std::cout << a;
// [(0:a), (1:b), (2:c)]
Javascript
var Utils = {
  "map": function (array, func) {
    for (var i = 0; i < array["length"]; ++i) {
      array[i] = func(i, array[i]);
    }

  }
};

var a = ["a", "b", "c"];
console.log(a);
// [a, b, c]

Utils["map"](a, function (key, value) {
  return "(" + key + ":" + value + ")";
});
console.log(a);
// [(0:a), (1:b), (2:c)]

Closure

There are two ways to capture variables with lambda in C++: either by reference or by value. What we would like is to capture by reference in order for all the variables to be bound to the same object. However, when the initial variable gets out of scope it is destroyed, and any attempt to read it results in a Segmentation Fault!

Instead, we have to capture it by value. It means that a new object is created for each lambda capturing the variable. Our objects are manipulated by reference, meaning that assigning a new value to the object will just update it and not all the other copies. We introduce a new assignement operatorobj |= valuethat updates all the copies.

C++
var container = function (var data) { 
  var secret = data;

  return {
    _["set"] = function (var x) {
        secret |= x;
        return undefined;
    },
    _["get"] = function () { return secret; }
  };
};

var a = container("secret-a");
var b = container("secret-b");

a["set"]("override-a");

std::cout < < a["get"](); // override-a
std::cout << b["get"](); // secret-b
Javascript
var container = function (data) {
  var secret = data;

  return {
    set: function (x) {
        secret = x;

    },
    get: function () { return secret; }
  };
};

var a = container("secret-a");
var b = container("secret-b");

a.set("override-a");

console.log(a.get()); // override-a
console.log(b.get()); // secret-b

This

There arefour waysto set thethisvalue:

  • Function call:foo().thisis set to the global object. As this is not a proper way to do things, I set it to undefined.
  • Method call:object.foo().thisis set toobject.
  • Constructor:new foo().foois called with a new instance ofthis.
  • Explicit:foo.call(this, arguments...). We explicitely set the this value.

All four ways are implemented in jspp but in a different way than Javascript. In Javascript, the language knows the construction and therefore can deduce whatthisis going to be. In C++, on the other hand, have a local view of what is going on. We have to develop another strategy for settingthisthat works for usual usage patterns.

We associate athisvalue for every object, by default beingundefined. If we obtain the object through another object(test.foo),thisis set to be the base object.

Newcreates a new function object withthisset to itself. Therefore it can be called to initialize the object. Contrary to Javascript, the constructor function has to returnthis.

C++
var f = function (var x, var y) {
    std::cout < < "this: " << This;
    This["x"] = x;
    This["y"] = y;
    return This;
};

// New creates a new object this
var a = New(f)(1, 2); // this: [function 40d0]
var b = New(f)(3, 4); // this: [function 48e0]

// Unbound call, 
var c = f(5, 6); // this: undefined

// Bound call
var obj = {42};
obj["f"] = f;

var d = obj["f"](1, 2); // this: [42]

// Call
var e = f["call"](obj, 1, 2); // this: [42]
Javascript
var f = function (x, y) {
    console.log("this:", this);
    this["x"] = x;
    this["y"] = y;

};

// New creates a new object this
var a = new f(1, 2); // this: [object]
var b = new f(3, 4); // this: [object]

// Unbound call, 
var c = f(5, 6); // this: global object

// Bound call
var obj = [42];
obj["f"] = f;

var d = obj["f"](1, 2); // this: [42]

// Call
var e = f["call"](obj, 1, 2); // this: [42]

Prototypal Inheritance

In order to use prototypal inheritance, we can useDouglas Crockford Object.Create.

When reading a property, we try to read it on the current object, and if it does not exist we try again on the prototype. However, when writing a property we want to write it on the object itself. Therefore the returned object contains in fact two objects, one used for reading and one for writing.

C++
var createObject = function (var o) {
    var F = function () {return This;};
    F["prototype"] = o;
    return New(F)();
};

var Person = {
    _["name"] = "Default",
    _["greet"] = function () {
        return "My name is " + This["name"];
    }
};

var vjeux = createObject(Person);
vjeux["name"] = "Vjeux";

var blog = createObject(Person);
blog["name"] = "Blog";

var def = createObject(Person);

std::cout < < vjeux["greet"](); // Vjeux
std::cout << blog["greet"]();  // Blog
std::cout << def["greet"]();   // Default
Javascript
var createObject = function (o) {
    var F = function () {};
    F.prototype = o;
    return new F();
};

var Person = {
    name: "Default",
    greet: function () {
        return "My name is " + this.name;
    }
};

var vjeux = createObject(Person);
vjeux.name = "Vjeux";

var blog = createObject(Person);
blog.name = "Blog";

var def = createObject(Person);

console.log(vjeux.greet()); // Vjeux
console.log(blog.greet());  // Blog
console.log(def.greet());   // Default

Iteration

We use the new iteration facility of C++0x to deal withfor(var in)Javascript syntax. As this is a prototype, it currently loops over all the keys of the object. However, it is possible to implement theisEnumerablefunctionnality.

C++
var array = {10, 42, 30};
for (var i : array) {
    std::cout < < i << " - " << array[i];
}
// 0 - 10
// 1 - 42
// 2 - 30
// length - 3
// prototype - undefined

var object = {
    _["a"] = 1,
    _["b"] = 2,
    _["c"] = 3
};
for (var i : object) {
    std::cout << i << " - " << object[i];
}
// a - 1
// b - 2
// c - 3
// prototype - undefined
Javascript
var array = [10, 42, 30];
for (var i in array) {
    console.log(i, array[i]);
}
// 0 - 10
// 1 - 42
// 2 - 30



var object = {
    "a": 1,
    "b": 2,
    "c": 3
};
for (var i in object) {
    console.log(i, object[i]);
}
// a - 1
// b - 2
// c - 3
//

Dynamic Typing

There is only one class calledvar. All the operators+,+=,++,<,*... are overloaded in order to make the right behavior. Since this is only a prototype, all of them are not working properly nor following the ECMAScript standard.

C++
var repeat = function (var str, var times) {
    var ret = "";
    for (var i = 0; i < times; ++i) {
        ret += str + i;
    }
    return ret;
};

std::cout << repeat(" js++", 3);
// " js++0 js++1 js++2"
Javascript
var repeat = function (str, times) {
    var ret = "";
    for (var i = 0; i < times; ++i) {
        ret += str + i;
    }
    return ret;
};

console.log(repeat(" js++", 3));
// " js++0 js++1 js++2"

Scope

Scope management is done with lambdas. Since they are implemented in C++0x, it works without pain.

C++
var global = "global";
var $ = "prototype";
var jQuery = "jQuery";

_(function (var $) {
    var global = "local";

    std::cout < < "Inside:      $ = " << $;
    std::cout << "Inside: global = " << global;

    // Inside:      $ = jQuery
    // Inside: global = local

    return undefined;
})(jQuery);

std::cout << "Outside:      $ = " << $;
std::cout << "Outside: global = " << global;

// Outside:      $ = prototype
// Outside: global = global
Javascript
var global = "global";
var $ = "prototype";
var jQuery = "jQuery";

(function ($) {
    var global = "local";

    console.log("Inside:      $ = ", $);
    console.log("Inside: global = ", global);

    // Inside:      $ = jQuery
    // Inside: global = local

    return undefined;
})(jQuery);

console.log("Outside:      $ = ", $);
console.log("Outside: global = ", global);

// Outside:      $ = prototype
// Outside: global = global

Reference

As in Javascript, everything is passed by reference. The current implementation uses a simple reference count to handle garbage collection.

C++
var a = {};
a["key"] = "old";

var b = a;
b["key"] = "new";

std::cout < < a["key"] << " " << b["key"];
// new new
Javascript
var a = {};
a["key"] = "old";

var b = a;
b["key"] = "new";

console.log(a["key"], b["key"]);
// new new

Exception

Javascript exception mechanism is directly borrowed from C++, therefore we can use the native one.

We need to throw a Javascript object. We can either throw a new instance of a Javascript function or use_()to cast a string into an object.

C++
var go_die = function () {
    throw _("Exception!");
};

try {
    go_die();
} catch (var e) {
    std::cout < < "Error: " << e;
}
// Error: Exception!
Javascript
var go_die = function () {
    throw "Exception!";
};

try {
    go_die();
} catch (e) {
    console.log("Error:", e);
}
// Error: Exception!

How to use

Note: Only the strict minimum of code able to run the examples has been written. It is a prototype, do not try to use it for any serious development.

The library can be compiled underg++ 4.6,Visual Studio 2010and the latest version ofICC. However Visual Studio and ICC do not support the initialization lists, so you cannot use the JSON syntax. But all the other examples will compile.

All the examples of this page are available in theexample/folder. The following execution will let you run the examples.

> make
g++ -o example/dynamic.jspp example/dynamic.cpp -Wall -std=gnu++0x
g++ -o example/exception.jspp example/exception.cpp -Wall -std=gnu++0x
...
> cd example
> ./json.jspp
{array: [1, 2, three], nested: {first: 1}, number: 42, string: vjeux}
> node json.js
{ number: 42,
  string: 'vjeux',
  array: [ 1, 2, 'three' ],
  nested: { first: 1 } }

Pro / Cons

The awesome part is the fact that it is possible to develop nearly all the concepts of Javascript in C++.

Pros

  • Write C++ in a dynamic fashion!
  • Extremely easy to integrate all the existing C++ code base.
  • Fun:)

Cons

  • Not possible to optimize as much as the latest Javascript engines.
  • Some features are impossible to write such aseval,with, named functions ...
  • NoREPL.
  • A bit more verbose than Javascript.

How to Improve

  • Code theargumentsmanagement.
  • Develop the Javascript standard library (operators, Array, Regex ...).
  • Find ways to minimize the C++ overhead (remove the use of_()).
  • Find concepts that I did not introduce.

你可能感兴趣的:(JavaScript,jquery,C++,c,C#)