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.
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 a KeyValue
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 an Array
(add one to the lenght and use the length as key). If that's a KeyValue
, 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} |
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}} |
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 }
. function
is a macro that transforms function (var i)
into [=] (Object This, Object arguments, var i) -> Object
. This allows to use the Javascript syntax and let us sneakily add the this
and arguments
magic variables.
C++ is strongly typed and even lambdas have types. We can overload the Object constructor on
lambda arity and have a typed container for each one. Then, we overload the () operator
that will call the stored lambda. We we carefully add undefined
values for unspecified arguments and fill the This
and arguments
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 add var
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)] |
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)] |
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 operator obj |= 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 |
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 |
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 are four ways to set the this
value:
- Function call:
foo()
. this
is set to the global object. As this is not a proper way to do things, I set it to undefined.
- Method call:
object.foo()
. this
is set to object
.
- Constructor:
new foo()
. foo
is called with a new instance of this
.
- 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 what this
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 setting this
that works for usual usage patterns.
We associate a this
value for every object, by default being undefined
. If we obtain the object through another object(test.foo
), this
is set to be the base object.
New
creates a new function object with this
set to itself. Therefore it can be called to initialize the object. Contrary to Javascript, the constructor function has to return this
.
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] |
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] |
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 use Douglas 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 |
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 |
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 with for(var in)
Javascript syntax. We just define in
to be :
.
As this is a prototype, it currently loops over all the keys of the object. However, it is possible to implement the isEnumerable
functionnality.
C++
var array = {10, 42, 30};
for (var i in 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 in object) {
std::cout << i << " - " << object[i];
}
// a - 1
// b - 2
// c - 3
// prototype - undefined |
var array = {10, 42, 30};
for (var i in 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 in 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
// |
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 called var
. 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" |
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" |
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 |
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 |
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 |
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 |
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 (e) {
std::cout < < "Error: " << e;
}
// Error: Exception! |
var go_die = function () {
throw "Exception!";
};
try {
go_die();
} catch (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! |
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 under g++ 4.6, Visual Studio 2010 and the latest version of ICC. 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 the example/
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 } } |
> 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 as
eval
, with
, named functions ...
- No REPL.
- A bit more verbose than Javascript.
How to Improve
- Code the
arguments
management.
- 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.
Stoyan Stefanov did a similar proof of concept but instead of targetting C++ he did it for PHP.