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.
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 aKeyValue
object, 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}} |
C++0x added lambda to the language with the following syntax:[capture] (arguments) -> returnType { body }
.function
is 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 thethis
andarguments
magic 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() operator
that will call the stored lambda. We we carefully addundefined
values for unspecified arguments and fill theThis
andarguments
variables.
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 addvar
before 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)] |
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 |= value
that 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 |
There arefour waysto set thethis
value:
foo()
.this
is set to the global object. As this is not a proper way to do things, I set it to undefined.object.foo()
.this
is set toobject
.new foo()
.foo
is called with a new instance ofthis
.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 whatthis
is 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 settingthis
that works for usual usage patterns.
We associate athis
value for every object, by default beingundefined
. If we obtain the object through another object(test.foo
),this
is set to be the base object.
New
creates a new function object withthis
set 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] |
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 |
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 theisEnumerable
functionnality.
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 // |
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 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 |
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 |
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! |
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 } }
The awesome part is the fact that it is possible to develop nearly all the concepts of Javascript in C++.
Pros
Cons
eval
,with
, named functions ...How to Improve
arguments
management._()
).