├── test └── cpp │ ├── src │ ├── CMakeLists.txt │ └── main.cpp │ ├── README.md │ └── CMakeLists.txt └── README.md /test/cpp/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | set(Boost_USE_STATIC_LIBS ON) 4 | find_package(Boost COMPONENTS unit_test_framework) 5 | 6 | find_path(GSL_INCLUDE_DIR gsl/gsl) 7 | find_path(DEFERREDPTR_INCLUDE_DIR deferred_heap.h) 8 | 9 | add_executable(main main.cpp) 10 | set_property(TARGET main PROPERTY CXX_STANDARD 14) 11 | target_link_libraries(main PRIVATE Boost::boost Boost::unit_test_framework) 12 | target_include_directories(main PRIVATE 13 | "${GSL_INCLUDE_DIR}" 14 | "${DEFERREDPTR_INCLUDE_DIR}" 15 | ) 16 | 17 | if(MSVC) 18 | # https://support.microsoft.com/en-us/help/143208/prb-using-stl-in-windows-program-can-cause-min-max-conflicts 19 | target_compile_definitions(main PRIVATE -DNOMINMAX) 20 | endif() 21 | 22 | enable_testing() 23 | add_test(NAME test COMMAND main) 24 | -------------------------------------------------------------------------------- /test/cpp/README.md: -------------------------------------------------------------------------------- 1 | ## Synopsis 2 | 3 | This project mirrors and tests the C++ code samples used in "JavaScript/C++ Rosetta Stone" to verify they compile and work as advertised. 4 | 5 | ## Prerequisites 6 | 7 | To build and run these tests, you'll need [CMake](https://cmake.org/) and a C++ compiler. 8 | 9 | - On Windows, that probably means [Visual Studio](https://www.visualstudio.com/vs/community/). 10 | - On Mac, [Xcode Command Line Tools](https://developer.apple.com/download/more/). 11 | - And on Linux, [build-essential package](https://packages.ubuntu.com/xenial/build-essential). 12 | 13 | ## Build and test 14 | 15 | ``` 16 | cmake 17 | cmake --build . --config Release 18 | ``` 19 | 20 | The build step will download C++ dependencies -- including Boost, so be patient. If all goes well, you should see `100% tests passed` near the end of the build output. 21 | 22 | ## Copyright 23 | 24 | Copyright 2017 Jeff Mott. [MIT License](https://opensource.org/licenses/MIT). 25 | -------------------------------------------------------------------------------- /test/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | include(ExternalProject) 4 | set_property(DIRECTORY PROPERTY EP_BASE "${CMAKE_CURRENT_BINARY_DIR}/ExternalProjects") 5 | 6 | # Boost 7 | ExternalProject_Add( 8 | boost URL https://dl.bintray.com/boostorg/release/1.64.0/source/boost_1_64_0.tar.gz 9 | BUILD_IN_SOURCE 1 10 | # Platform-specific configure command will follow 11 | CONFIGURE_COMMAND "" 12 | # An out-of-source install would be ideal, 13 | # but that involves copying all the header files, 14 | # which is super slow on windows 15 | BUILD_COMMAND ./b2 "--stagedir=" --with-test variant=release link=static stage 16 | INSTALL_COMMAND "" 17 | ) 18 | if(CMAKE_HOST_UNIX) 19 | ExternalProject_Add_Step( 20 | boost unix_configure DEPENDEES patch DEPENDERS configure 21 | WORKING_DIRECTORY "" COMMAND ./bootstrap.sh 22 | ) 23 | else() 24 | ExternalProject_Add_Step( 25 | boost win_configure DEPENDEES patch DEPENDERS configure 26 | WORKING_DIRECTORY "" COMMAND bootstrap.bat 27 | ) 28 | endif() 29 | ExternalProject_Get_Property(boost SOURCE_DIR) 30 | set(BOOST_SOURCE_DIR "${SOURCE_DIR}") 31 | 32 | # GSL 33 | ExternalProject_Add( 34 | gsl URL https://github.com/Microsoft/GSL/archive/1f82596e1dada0067712527262a3d561ad51ddac.zip 35 | CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" 36 | ) 37 | ExternalProject_Get_Property(gsl SOURCE_DIR) 38 | set(GSL_SOURCE_DIR "${SOURCE_DIR}") 39 | 40 | # DeferredPtr 41 | ExternalProject_Add( 42 | deferredptr DEPENDS gsl 43 | URL https://github.com/hsutter/gcpp/archive/9da9af2b87d55b899f1476f2d24104cb9606e45c.zip 44 | CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" 45 | ) 46 | ExternalProject_Get_Property(deferredptr SOURCE_DIR) 47 | set(DEFERREDPTR_SOURCE_DIR "${SOURCE_DIR}") 48 | 49 | # Main 50 | ExternalProject_Add( 51 | js_cpp_rs DEPENDS boost deferredptr 52 | DOWNLOAD_COMMAND "" SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src" 53 | CMAKE_ARGS "-DCMAKE_PREFIX_PATH=${BOOST_SOURCE_DIR}$${GSL_SOURCE_DIR}$${DEFERREDPTR_SOURCE_DIR}" 54 | INSTALL_COMMAND "" 55 | ) 56 | ExternalProject_Add_Step( 57 | js_cpp_rs test DEPENDEES build 58 | WORKING_DIRECTORY "" COMMAND ctest -C $ 59 | ALWAYS 1 60 | ) 61 | -------------------------------------------------------------------------------- /test/cpp/src/main.cpp: -------------------------------------------------------------------------------- 1 | #define BOOST_TEST_MODULE JS_CPP_rosetta_stone_samples_test 2 | 3 | // (MSVC) Suppress warnings from dependencies; they're not ours to fix 4 | #pragma warning(push, 0) 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #pragma warning(pop) 20 | 21 | using std::accumulate; 22 | using std::for_each; 23 | using std::function; 24 | using std::string; 25 | using namespace std::string_literals; 26 | using std::stringstream; 27 | using std::to_string; 28 | using std::unordered_map; 29 | using std::vector; 30 | using boost::any; 31 | using boost::any_cast; 32 | using boost::get; 33 | using boost::variant; 34 | using gcpp::deferred_heap; 35 | using gcpp::deferred_ptr; 36 | 37 | BOOST_AUTO_TEST_CASE(variant_test) { 38 | variant x; 39 | 40 | x = true; 41 | 42 | BOOST_TEST(get(x) == true); 43 | 44 | x = 42; 45 | 46 | BOOST_TEST(get(x) == 42); 47 | 48 | x = "Hello"s; 49 | 50 | BOOST_TEST(get(x) == "Hello"s); 51 | } 52 | 53 | BOOST_AUTO_TEST_CASE(any_test) { 54 | class Some_arbitrary_type {}; 55 | 56 | any x; 57 | 58 | x = true; 59 | 60 | BOOST_TEST(any_cast(x) == true); 61 | 62 | x = 42; 63 | 64 | BOOST_TEST(any_cast(x) == 42); 65 | 66 | x = "Hello"s; 67 | 68 | BOOST_TEST(any_cast(x) == "Hello"s); 69 | 70 | x = Some_arbitrary_type{}; 71 | 72 | any_cast(x); // will throw if fails 73 | } 74 | 75 | BOOST_AUTO_TEST_CASE(objects_test) { 76 | unordered_map my_car { 77 | {"make", "Ford"s}, 78 | {"model", "Mustang"s}, 79 | {"year", 1969} 80 | }; 81 | 82 | BOOST_TEST(any_cast(my_car["make"]) == "Ford"s); 83 | BOOST_TEST(any_cast(my_car["model"]) == "Mustang"s); 84 | BOOST_TEST(any_cast(my_car["year"]) == 1969); 85 | } 86 | 87 | BOOST_AUTO_TEST_CASE(arrays_test) { 88 | unordered_map fruits { 89 | {"0", "Mango"s}, 90 | {"1", "Apple"s}, 91 | {"2", "Orange"s} 92 | }; 93 | 94 | BOOST_TEST(any_cast(fruits["0"]) == "Mango"s); 95 | BOOST_TEST(any_cast(fruits["1"]) == "Apple"s); 96 | BOOST_TEST(any_cast(fruits["2"]) == "Orange"s); 97 | 98 | fruits["model"] = "Mustang"s; 99 | 100 | BOOST_TEST(any_cast(fruits["model"]) == "Mustang"s); 101 | } 102 | 103 | class Delegating_unordered_map : private unordered_map { 104 | public: 105 | Delegating_unordered_map* __proto__ {}; 106 | 107 | auto find_in_chain(const string& key) { 108 | // Check own property 109 | auto found_value = find(key); 110 | if (found_value != end()) return found_value; 111 | 112 | // Else, delegate to prototype 113 | if (__proto__) { 114 | auto found_value = __proto__->find_in_chain(key); 115 | if (found_value != __proto__->end()) return found_value; 116 | } 117 | 118 | return end(); 119 | } 120 | 121 | any& operator[](const string& key) { 122 | auto found_value = find_in_chain(key); 123 | if (found_value != end()) return found_value->second; 124 | 125 | // Else, super call, which will create and return an empty `any` 126 | return unordered_map::operator[](key); 127 | } 128 | 129 | // Borrow constructor 130 | using unordered_map::unordered_map; 131 | }; 132 | 133 | BOOST_AUTO_TEST_CASE(prototypal_inheritance_test) { 134 | Delegating_unordered_map o {{"a", 1}, {"b", 2}}; 135 | Delegating_unordered_map o_proto {{"b", 3}, {"c", 4}}; 136 | o.__proto__ = &o_proto; 137 | 138 | // Is there an "a" own property on o? Yes, and its value is 1. 139 | BOOST_TEST(any_cast(o["a"]) == 1); 140 | 141 | // Is there a "b" own property on o? Yes, and its value is 2. 142 | // The prototype also has a "b" property, but it's not visited. 143 | BOOST_TEST(any_cast(o["b"]) == 2); 144 | 145 | // Is there a "c" own property on o? No, check its prototype. 146 | // Is there a "c" own property on o.__proto__? Yes, its value is 4. 147 | BOOST_TEST(any_cast(o["c"]) == 4); 148 | 149 | // Is there a "d" own property on o? No, check its prototype. 150 | // Is there a "d" own property on o.__proto__? No, check its prototype. 151 | // o.__proto__.__proto__ is null, stop searching. 152 | // No property found, return undefined. 153 | BOOST_TEST(o["d"].empty()); 154 | } 155 | 156 | using js_object = Delegating_unordered_map; 157 | 158 | namespace variadic { 159 | any plus_all(vector arguments) { 160 | auto sum = 0; 161 | for (auto i = 0; i < arguments.size(); ++i) { 162 | sum += any_cast(arguments[i]); 163 | } 164 | return sum; 165 | } 166 | 167 | BOOST_AUTO_TEST_CASE(variadic_test) { 168 | BOOST_TEST(any_cast(plus_all({4, 8})) == 12); 169 | BOOST_TEST(any_cast(plus_all({4, 8, 15, 16, 23, 42})) == 108); 170 | } 171 | } 172 | 173 | namespace variadic_stl { 174 | any plus_all(vector arguments) { 175 | return accumulate( 176 | arguments.begin(), arguments.end(), 0, 177 | [] (auto accumulator, auto current_value) { 178 | return accumulator + any_cast(current_value); 179 | } 180 | ); 181 | } 182 | 183 | BOOST_AUTO_TEST_CASE(variadic_stl_test) { 184 | BOOST_TEST(any_cast(plus_all({4, 8})) == 12); 185 | BOOST_TEST(any_cast(plus_all({4, 8, 15, 16, 23, 42})) == 108); 186 | } 187 | } 188 | 189 | any js_plus(const any& lval, const any& rval) { 190 | // If either operand is a string... 191 | if ( 192 | lval.type() == typeid(string) || 193 | rval.type() == typeid(string) 194 | ) { 195 | // Convert both operands to a string and do concatenation 196 | auto lval_str = ( 197 | lval.type() != typeid(string) ? 198 | to_string(any_cast(lval)) : 199 | any_cast(lval) 200 | ); 201 | auto rval_str = ( 202 | rval.type() != typeid(string) ? 203 | to_string(any_cast(rval)) : 204 | any_cast(rval) 205 | ); 206 | 207 | return lval_str + rval_str; 208 | } 209 | 210 | // Else, numeric addition 211 | return any_cast(lval) + any_cast(rval); 212 | } 213 | 214 | namespace variadic_mixedtype { 215 | any plus_all(vector arguments) { 216 | return accumulate( 217 | arguments.begin(), arguments.end(), any{0}, 218 | [] (auto accumulator, auto current_value) { 219 | return js_plus(accumulator, current_value); 220 | } 221 | ); 222 | } 223 | 224 | BOOST_AUTO_TEST_CASE(variadic_mixedtype_test) { 225 | BOOST_TEST(any_cast(plus_all({4, 8, "!"s, 15, 16, 23, 42})) == "12!15162342"s); 226 | } 227 | } 228 | 229 | namespace this_ { 230 | any add(any this_, vector arguments) { 231 | return ( 232 | any_cast(any_cast(this_)["a"]) + 233 | any_cast(any_cast(this_)["b"]) + 234 | any_cast(arguments[0]) + 235 | any_cast(arguments[1]) 236 | ); 237 | } 238 | 239 | BOOST_AUTO_TEST_CASE(this_test) { 240 | js_object o {{"a", 1}, {"b", 3}}; 241 | 242 | // The first parameter is the object to use as 243 | // "this"; the second is an array whose 244 | // elements are used as the arguments in the function call 245 | BOOST_TEST(any_cast(add(o, {5, 7})) == 16); 246 | BOOST_TEST(any_cast(add(o, {10, 20})) == 34); 247 | } 248 | } 249 | 250 | namespace closures { 251 | auto outside(int x) { 252 | // A class that privately stores "x" and 253 | // can be called as if it were a function 254 | class Inside { 255 | int x_; 256 | 257 | public: 258 | Inside(int x) : x_ {x} {} 259 | 260 | auto operator()(int y) { 261 | return x_ + y; 262 | } 263 | }; 264 | 265 | // This is our closure, an instance of the above class, a callable object 266 | // that is constructed with and stores a value from its environment 267 | Inside inside {x}; 268 | 269 | return inside; 270 | } 271 | 272 | BOOST_AUTO_TEST_CASE(closures_test) { 273 | auto fn_inside = outside(3); 274 | BOOST_TEST(fn_inside(5) == 8); 275 | 276 | BOOST_TEST(outside(3)(5) == 8); 277 | } 278 | } 279 | 280 | namespace closures_lambda { 281 | auto outside(int x) { 282 | // This is our closure, a callable object that 283 | // stores a value from its environment 284 | auto inside = [x] (int y) { 285 | return x + y; 286 | }; 287 | 288 | return inside; 289 | } 290 | 291 | BOOST_AUTO_TEST_CASE(closures_lambda_test) { 292 | auto fn_inside = outside(3); 293 | BOOST_TEST(fn_inside(5) == 8); 294 | 295 | BOOST_TEST(outside(3)(5) == 8); 296 | } 297 | } 298 | 299 | class Callable_delegating_unordered_map : public Delegating_unordered_map { 300 | function)> function_body_; 301 | 302 | public: 303 | Callable_delegating_unordered_map(function)> function_body) : 304 | function_body_ {function_body} 305 | {} 306 | 307 | auto operator()(any this_ = {}, vector arguments = {}) { 308 | return function_body_(this_, arguments); 309 | } 310 | }; 311 | 312 | BOOST_AUTO_TEST_CASE(function_object) { 313 | Callable_delegating_unordered_map square {[] (any this_, vector arguments) { 314 | return any_cast(arguments[0]) * any_cast(arguments[0]); 315 | }}; 316 | 317 | square["make"] = "Ford"s; 318 | square["model"] = "Mustang"s; 319 | square["year"] = 1969; 320 | 321 | BOOST_TEST(any_cast(square(nullptr, {4})) == 16); 322 | } 323 | 324 | using js_function = Callable_delegating_unordered_map; 325 | 326 | BOOST_AUTO_TEST_CASE(scope_chains_test) { 327 | Delegating_unordered_map global_environment; 328 | global_environment["globalVariable"] = "xyz"s; 329 | 330 | global_environment["f"] = js_function{[&] (any this_, vector arguments) { 331 | Delegating_unordered_map f_environment; 332 | f_environment.__proto__ = &global_environment; 333 | 334 | f_environment["localVariable"] = true; 335 | 336 | f_environment["g"] = js_function{[&] (any this_, vector arguments) { 337 | Delegating_unordered_map g_environment; 338 | g_environment.__proto__ = &f_environment; 339 | 340 | g_environment["anotherLocalVariable"] = 123; 341 | 342 | BOOST_TEST(any_cast(g_environment["globalVariable"]) == "xyz"s); 343 | BOOST_TEST(any_cast(g_environment["localVariable"]) == true); 344 | BOOST_TEST(any_cast(g_environment["anotherLocalVariable"]) == 123); 345 | 346 | // All variables of surrounding scopes are accessible 347 | g_environment["localVariable"] = false; 348 | g_environment["globalVariable"] = "abc"s; 349 | 350 | BOOST_TEST(any_cast(g_environment["globalVariable"]) == "abc"s); 351 | BOOST_TEST(any_cast(g_environment["localVariable"]) == false); 352 | BOOST_TEST(any_cast(g_environment["anotherLocalVariable"]) == 123); 353 | 354 | return any{}; 355 | }}; 356 | 357 | any_cast(f_environment["g"])(); 358 | BOOST_TEST(any_cast(f_environment["globalVariable"]) == "abc"s); 359 | BOOST_TEST(any_cast(f_environment["localVariable"]) == false); 360 | BOOST_TEST(global_environment["anotherLocalVariable"].empty()); 361 | 362 | return any{}; 363 | }}; 364 | 365 | any_cast(global_environment["f"])(); 366 | BOOST_TEST(any_cast(global_environment["globalVariable"]) == "abc"s); 367 | BOOST_TEST(global_environment["localVariable"].empty()); 368 | BOOST_TEST(global_environment["anotherLocalVariable"].empty()); 369 | } 370 | 371 | namespace closures_in_loop { 372 | stringstream cout; // mock cout 373 | 374 | BOOST_AUTO_TEST_CASE(closures_in_loop_test) { 375 | vector functions_by_value; 376 | vector functions_by_ref; 377 | 378 | for (auto& i = *(new int{0}); i < 3; ++i) { 379 | // This closure will capture the value of "i" 380 | // at the moment the closure is created 381 | functions_by_value.push_back(js_function{[i] (any this_, vector arguments) { 382 | cout << i; 383 | return any{}; 384 | }}); 385 | 386 | // This closure will capture a reference to the same "i" 387 | functions_by_ref.push_back(js_function{[&i] (any this_, vector arguments) { 388 | cout << i; 389 | return any{}; 390 | }}); 391 | } 392 | 393 | // 0, 1, 2 394 | for_each(functions_by_value.begin(), functions_by_value.end(), [] (auto fn) { 395 | fn(); 396 | }); 397 | 398 | // 3, 3, 3 399 | for_each(functions_by_ref.begin(), functions_by_ref.end(), [] (auto fn) { 400 | fn(); 401 | }); 402 | 403 | BOOST_TEST(cout.str() == "012333"s); 404 | } 405 | } 406 | 407 | namespace closures_peritercopy_in_loop { 408 | stringstream cout; // mock cout 409 | 410 | BOOST_AUTO_TEST_CASE(closures_peritercopy_in_loop) { 411 | vector functions; 412 | 413 | for (auto i = 0; i < 3; ++i) { 414 | // Create a per-iteration copy of "i" 415 | auto& i_copy = *(new int{i}); 416 | 417 | // Every closure we push captures a reference to a per-iteration copy of "i" 418 | functions.push_back(js_function{[&i_copy] (any this_, vector arguments) { 419 | cout << i_copy; 420 | return any{}; 421 | }}); 422 | } 423 | 424 | // 0, 1, 2 425 | for_each(functions.begin(), functions.end(), [] (auto fn) { 426 | fn(); 427 | }); 428 | 429 | BOOST_TEST(cout.str() == "012"s); 430 | } 431 | } 432 | 433 | namespace garbage_collection { 434 | stringstream cout; // mock cout 435 | 436 | BOOST_AUTO_TEST_CASE(garbage_collection_test) { 437 | deferred_heap my_heap; 438 | 439 | vector functions; 440 | 441 | for (auto i = 0; i < 3; ++i) { 442 | // Create a per-iteration copy of "i" 443 | auto i_copy = my_heap.make(i); 444 | 445 | // Every closure we push captures a reference to a per-iteration copy of "i" 446 | functions.push_back(js_function{[i_copy] (any this_, vector arguments) { 447 | cout << *i_copy; 448 | return any{}; 449 | }}); 450 | } 451 | 452 | // 0, 1, 2 453 | for_each(functions.begin(), functions.end(), [] (auto fn) { 454 | fn(); 455 | }); 456 | 457 | // Destroy and deallocate any unreachable objects 458 | my_heap.collect(); 459 | 460 | BOOST_TEST(cout.str() == "012"s); 461 | } 462 | 463 | class Delegating_unordered_map : private unordered_map { 464 | public: 465 | deferred_ptr __proto__ {}; 466 | 467 | auto find_in_chain(const string& key) { 468 | // Check own property 469 | auto found_value = find(key); 470 | if (found_value != end()) return found_value; 471 | 472 | // Else, delegate to prototype 473 | if (__proto__) { 474 | auto found_value = __proto__->find_in_chain(key); 475 | if (found_value != __proto__->end()) return found_value; 476 | } 477 | 478 | return end(); 479 | } 480 | 481 | any& operator[](const string& key) { 482 | auto found_value = find_in_chain(key); 483 | if (found_value != end()) return found_value->second; 484 | 485 | // Else, super call, which will create and return an empty `any` 486 | return unordered_map::operator[](key); 487 | } 488 | 489 | // Borrow constructor 490 | using unordered_map::unordered_map; 491 | 492 | using unordered_map::find; 493 | using unordered_map::end; 494 | }; 495 | 496 | using js_object = Delegating_unordered_map; 497 | 498 | class Callable_delegating_unordered_map : public Delegating_unordered_map { 499 | function)> function_body_; 500 | 501 | public: 502 | Callable_delegating_unordered_map(function)> function_body) : 503 | function_body_ {function_body} 504 | {} 505 | 506 | auto operator()(any this_ = {}, vector arguments = {}) { 507 | return function_body_(this_, arguments); 508 | } 509 | }; 510 | 511 | using js_function = Callable_delegating_unordered_map; 512 | 513 | deferred_heap my_heap; 514 | 515 | auto make_js_object(const js_object& obj = {}) { 516 | return my_heap.make(obj); 517 | } 518 | 519 | auto make_js_function(const js_function& func) { 520 | return my_heap.make(func); 521 | } 522 | 523 | using js_object_ref = deferred_ptr; 524 | using js_function_ref = deferred_ptr; 525 | 526 | BOOST_AUTO_TEST_CASE(classes_ff_test) { 527 | auto thing = make_js_function({[] (any this_, vector arguments) { 528 | return make_js_object({ 529 | {"x", 42}, 530 | {"y", 3.14}, 531 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 532 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 533 | }); 534 | }}); 535 | 536 | auto o = (*thing)(); 537 | } 538 | 539 | BOOST_AUTO_TEST_CASE(classes_delegating_ff_test) { 540 | auto thing_prototype = make_js_object({ 541 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 542 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 543 | }); 544 | 545 | auto thing = make_js_function({[=] (any this_, vector arguments) { 546 | auto o = make_js_object({ 547 | {"x", 42}, 548 | {"y", 3.14} 549 | }); 550 | 551 | o->__proto__ = thing_prototype; 552 | 553 | return o; 554 | }}); 555 | 556 | auto o = (*thing)(); 557 | } 558 | 559 | BOOST_AUTO_TEST_CASE(classes_delegating_to_prototype_ff_test) { 560 | js_function_ref thing = make_js_function({[=] (any this_, vector arguments) { 561 | auto o = make_js_object({ 562 | {"x", 42}, 563 | {"y", 3.14} 564 | }); 565 | 566 | o->__proto__ = any_cast((*thing)["prototype"]); 567 | 568 | return o; 569 | }}); 570 | 571 | (*thing)["prototype"] = make_js_object({ 572 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 573 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 574 | }); 575 | 576 | auto o = (*thing)(); 577 | } 578 | 579 | auto js_new(js_function_ref constructor, vector arguments = {}) { 580 | auto o = make_js_object(); 581 | o->__proto__ = any_cast((*constructor)["prototype"]); 582 | 583 | (*constructor)(o, arguments); 584 | 585 | return o; 586 | } 587 | 588 | BOOST_AUTO_TEST_CASE(classes_new_test) { 589 | auto Thing = make_js_function({[] (any this_, vector arguments) { 590 | (*any_cast(this_))["x"] = 42; 591 | (*any_cast(this_))["y"] = 3.14; 592 | 593 | return any{}; 594 | }}); 595 | 596 | (*Thing)["prototype"] = make_js_object({ 597 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 598 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 599 | }); 600 | 601 | auto o = js_new(Thing); 602 | } 603 | } 604 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript/C++ Rosetta Stone 2 | 3 | I'm going to reproduce the behavior of the JavaScript language in C++. Dynamic duck typing, objects out of nothing, prototypal inheritance, lexical closures -- all of it -- in C++. The goal is to gain insights into JavaScript's inner workings. For JavaScript developers, this exercise can help to understand what the language is doing under the hood and let us talk about its behavior in concrete terms rather than in metaphors. And for developers coming from other languages and new to JavaScript, this exercise can help to understand the concrete operations that lie beneath such a dynamic language. 4 | 5 | 6 | 7 | 1. [Dynamic types](#dynamic-types) 8 | 1. [Variant](#variant) 9 | 1. [Any](#any) 10 | 1. [Objects](#objects) 11 | 1. [Arrays](#arrays) 12 | 1. [Prototypal inheritance](#prototypal-inheritance) 13 | 1. [Variadic functions](#variadic-functions) 14 | 1. [Mixed-type arguments](#mixed-type-arguments) 15 | 1. ["This"](#this) 16 | 1. [Closures](#closures) 17 | 1. [Function objects](#function-objects) 18 | 1. [Lexical environment and scope chains](#lexical-environment-and-scope-chains) 19 | 1. [Capture by reference vs by value](#capture-by-reference-vs-by-value) 20 | 1. [`let` and capturing by value](#let-and-capturing-by-value) 21 | 1. [Garbage collection](#garbage-collection) 22 | 1. [Classes](#classes) 23 | 1. [Factory functions](#factory-functions) 24 | 1. [Prototype chains](#prototype-chains) 25 | 1. [Constructor functions](#constructor-functions) 26 | 1. [That's all folks! \(...for now\)](#thats-all-folks-for-now) 27 | 1. [Copyright](#copyright) 28 | 29 | 30 | 31 | ## Dynamic types 32 | 33 | First up, dynamic types. JavaScript, of course, is dynamically typed. A variable can be assigned and reassigned any type of value. C++, on the other hand, is statically typed. If you declare a variable to be an `int`, then C++ will allocate a fixed-size block of memory, maybe 4 bytes, which means that later trying to reassign an 8-byte double or a 32-byte string into that variable wouldn't work because there simply isn't enough space. 34 | 35 | ### Variant 36 | 37 | One way around that problem is to allocate as much space as the largest of several types. C++ supports this with a feature called unions. The union of a 4-byte int, an 8-byte double, and a 32-byte string would yield a 32-byte data type -- as large as the largest member. This means a variable of that union type could be reassigned at any time an int or a double or a string. 38 | 39 | It's dangerously easy, however, to assign an int, for example, and then try to read and interpret that binary data as if it were a string, so for safety, we wrap and control the usage of a union with a well behaved abstraction, a user-defined type that we traditionally name `variant`. 40 | 41 | ###### JavaScript 42 | ```javascript 43 | let x; 44 | 45 | x = true; 46 | x = 42; 47 | x = "Hello"; 48 | ``` 49 | 50 | ###### C++ 51 | ```c++ 52 | variant x; 53 | 54 | x = true; 55 | x = 42; 56 | x = "Hello"s; 57 | ``` 58 | 59 | ### Any 60 | 61 | But because the size of a union is at least as large as its largest member, space is wasted. A variable that needs only 1 byte for a bool might nonetheless allocate 32 bytes just in case we later wanted to assign a string into it. But also as bad, we have to know about and list every type that might be assigned to the variant. Ideally we'd like to be able to assign any arbitrary type. That brings us to another user-defined type named, unsurprisingly, `any`. It works by allocating the value on the free store and using it through a pointer. If all we need is a 1-byte bool, then it dynamically allocates and points to just 1 byte. Later if we reassign a string into that variable, then it frees the 1-byte bool and dynamically allocates and points to 32 bytes for the string. Now we don't need to know about every type ahead of time, and it can store (nearly) any arbitrary type we want. 62 | 63 | ###### JavaScript 64 | ```javascript 65 | class SomeArbitraryType {} 66 | 67 | let x; 68 | 69 | x = true; 70 | x = 42; 71 | x = "Hello"; 72 | x = new SomeArbitraryType(); 73 | ``` 74 | 75 | ###### C++ 76 | ```c++ 77 | class Some_arbitrary_type {}; 78 | 79 | any x; 80 | 81 | x = true; 82 | x = 42; 83 | x = "Hello"s; 84 | x = Some_arbitrary_type{}; 85 | ``` 86 | 87 | ## Objects 88 | 89 | In JavaScript, an object is a collection of key-value pairs. In other languages, a collection of key-value pairs is instead known as an associative array, or a map, or a dictionary, and it's often implemented as a hash table. Which means we can reproduce JavaScript's objects simply by instantiating a hash table. In C++, the standard hash table type is named `unordered_map`. 90 | 91 | 92 | ###### JavaScript 93 | ```javascript 94 | let myCar = { 95 | make: "Ford", 96 | model: "Mustang", 97 | year: 1969 98 | }; 99 | ``` 100 | 101 | ###### C++ 102 | ```c++ 103 | unordered_map my_car { 104 | {"make", "Ford"s}, 105 | {"model", "Mustang"s}, 106 | {"year", 1969} 107 | }; 108 | ``` 109 | 110 | I'll admit this revelation in particular ruined some of JavaScript's mystique for me. We in the JavaScript community often tout that JavaScript can [create objects ex nihilo ("out of nothing")](https://en.wikipedia.org/wiki/Prototype-based_programming#Object_construction), something we're told few languages can do. But I've realized this isn't a *technical* achievement; it's a *branding* achievement. Every language I'm aware of can create hash tables ex nihilo, and it seems JavaScript simply re-branded hash tables as the generic object. 111 | 112 | ## Arrays 113 | 114 | In JavaScript, an array is itself an object -- in the JavaScript sense of the word. Which is to say, a JavaScript array is a hash table where the string keys we insert just happen to look like integer indexes. 115 | 116 | ###### JavaScript 117 | ```javascript 118 | let fruits = ["Mango", "Apple", "Orange"]; 119 | ``` 120 | 121 | ###### C++ 122 | ```c++ 123 | unordered_map fruits { 124 | {"0", "Mango"s}, 125 | {"1", "Apple"s}, 126 | {"2", "Orange"s} 127 | }; 128 | ``` 129 | 130 | And, of course, since arrays are objects, we can still assign string keys that *don't* look like integer indexes into our array. 131 | 132 | ###### JavaScript 133 | ```javascript 134 | let fruits = ["Mango", "Apple", "Orange"]; 135 | fruits.model = "Mustang"; 136 | ``` 137 | 138 | ###### C++ 139 | ```c++ 140 | unordered_map fruits { 141 | {"0", "Mango"s}, 142 | {"1", "Apple"s}, 143 | {"2", "Orange"s} 144 | }; 145 | fruits["model"] = "Mustang"s; 146 | ``` 147 | 148 | ## Prototypal inheritance 149 | 150 | Next up, the now famous prototypal inheritance. I'm assuming my audience here already knows that [JavaScript objects delegate to other objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model). Since JavaScript's objects are ultimately hash tables, we can likewise describe JavaScript's prototypal inheritance as hash tables delegating element access to other hash tables. 151 | 152 | This time, unfortunately, C++ does not have a ready-made type for this task. We'll have to make it ourselves. We in the JavaScript community sometimes say it's easy for prototypal to emulate classical but hard for classical to emulate prototypal, but implementing prototypal inheritance turned out to be far simplier than even I originally expected. Up to this point, we've been using `unordered_map` as our equivalent to JavaScript's objects. Now we're going to extend that type to add the so-called `[[Prototype]]` link, and we're going to override the element access operator so it will delegate access misses to that link. 153 | 154 | ###### C++ 155 | ```c++ 156 | class Delegating_unordered_map : private unordered_map { 157 | public: 158 | Delegating_unordered_map* __proto__ {}; 159 | 160 | auto find_in_chain(const string& key) { 161 | // Check own property 162 | auto found_value = find(key); 163 | if (found_value != end()) return found_value; 164 | 165 | // Else, delegate to prototype 166 | if (__proto__) { 167 | auto found_value = __proto__->find_in_chain(key); 168 | if (found_value != __proto__->end()) return found_value; 169 | } 170 | 171 | return end(); 172 | } 173 | 174 | any& operator[](const string& key) { 175 | auto found_value = find_in_chain(key); 176 | if (found_value != end()) return found_value->second; 177 | 178 | // Else, super call, which will create and return an empty `any` 179 | return unordered_map::operator[](key); 180 | } 181 | 182 | // Borrow constructor 183 | using unordered_map::unordered_map; 184 | }; 185 | ``` 186 | 187 | With that done, let's take a look at prototypal inheritance side-by-side in JavaScript and C++. 188 | 189 | ###### JavaScript 190 | ```javascript 191 | let o = {a: 1, b: 2}; 192 | let oProto = {b: 3, c: 4}; 193 | Object.setPrototypeOf(o, oProto); 194 | 195 | // Is there an "a" own property on o? Yes, and its value is 1. 196 | o.a; // 1 197 | 198 | // Is there a "b" own property on o? Yes, and its value is 2. 199 | // The prototype also has a "b" property, but it's not visited. 200 | o.b; // 2 201 | 202 | // Is there a "c" own property on o? No, check its prototype. 203 | // Is there a "c" own property on o.[[Prototype]]? Yes, its value is 4. 204 | o.c; // 4 205 | 206 | // Is there a "d" own property on o? No, check its prototype. 207 | // Is there a "d" own property on o.[[Prototype]]? No, check its prototype. 208 | // o.[[Prototype]].[[Prototype]] is null, stop searching. 209 | // No property found, return undefined. 210 | o.d; // undefined 211 | ``` 212 | 213 | ###### C++ 214 | ```c++ 215 | Delegating_unordered_map o {{"a", 1}, {"b", 2}}; 216 | Delegating_unordered_map o_proto {{"b", 3}, {"c", 4}}; 217 | o.__proto__ = &o_proto; 218 | 219 | // Is there an "a" own property on o? Yes, and its value is 1. 220 | o["a"]; // 1 221 | 222 | // Is there a "b" own property on o? Yes, and its value is 2. 223 | // The prototype also has a "b" property, but it's not visited. 224 | o["b"]; // 2 225 | 226 | // Is there a "c" own property on o? No, check its prototype. 227 | // Is there a "c" own property on o.__proto__? Yes, its value is 4. 228 | o["c"]; // 4 229 | 230 | // Is there a "d" own property on o? No, check its prototype. 231 | // Is there a "d" own property on o.__proto__? No, check its prototype. 232 | // o.__proto__.__proto__ is null, stop searching. 233 | // No property found, return undefined. 234 | o["d"]; // undefined (an empty `any`) 235 | ``` 236 | 237 | We've finally settled on the finished object structure to reproduce JavaScript's behavior (or as close as we'll get here, anyway), so for convenience in the rest of the samples, I'm going to alias that object type to a shorter, friendlier name. 238 | 239 | ###### C++ 240 | ```c++ 241 | using js_object = Delegating_unordered_map; 242 | ``` 243 | 244 | ## Variadic functions 245 | 246 | Variadic functions are functions that take a variable number of arguments. In JavaScript, *every* function is variadic. You can call any function with any number of arguments, and they will all be available in a special array-like variable called `arguments`. C++, on the other hand, needs to know the exact number and types of arguments to be able to call a function correctly. But as it turns out, it's simple to reproduce JavaScript's behavior. Our C++ functions could accept just a single parameter, an array of `any`s, and we can name that parameter `arguments`. In C++, the standard dynamically-sized array type is named `vector`. 247 | 248 | ###### JavaScript 249 | ```javascript 250 | function plusAll() { 251 | let sum = 0; 252 | for (let i = 0; i < arguments.length; i++) { 253 | sum += arguments[i]; 254 | } 255 | return sum; 256 | } 257 | 258 | plusAll(4, 8); // 12 259 | plusAll(4, 8, 15, 16, 23, 42); // 108 260 | ``` 261 | 262 | ###### C++ 263 | ```c++ 264 | any plus_all(vector arguments) { 265 | auto sum = 0; 266 | for (auto i = 0; i < arguments.size(); ++i) { 267 | sum += any_cast(arguments[i]); 268 | } 269 | return sum; 270 | } 271 | 272 | plus_all({4, 8}); // 12 273 | plus_all({4, 8, 15, 16, 23, 42}); // 108 274 | ``` 275 | 276 | Admittedly both of these code samples were written to highlight the similarities, but neither adheres to what each language would consider good style. The JavaScript sample should use rest parameters and reduce, and the C++ sample should use iterators and standard algorithms. 277 | 278 | 279 | ###### JavaScript 280 | ```javascript 281 | function plusAll(...args) { 282 | return args.reduce((accumulator, currentValue) => { 283 | return accumulator + currentValue; 284 | }, 0); 285 | } 286 | ``` 287 | 288 | ###### C++ 289 | ```c++ 290 | any plus_all(vector arguments) { 291 | return accumulate( 292 | arguments.begin(), arguments.end(), 0, 293 | [] (auto accumulator, auto current_value) { 294 | return accumulator + any_cast(current_value); 295 | } 296 | ); 297 | } 298 | ``` 299 | 300 | ### Mixed-type arguments 301 | 302 | You may have noticed that the C++ version assumes every item in the argument list is an `int`. In JavaScript, the arguments could have been a mix of strings and numbers, but the C++ version, as currently written, will only add numbers. How does JavaScript pull this off? Turns out [the addition operator does some type checking](https://www.ecma-international.org/ecma-262/7.0/index.html#sec-addition-operator-plus). Every time we use the `+` operator, JavaScript will check if either operand is a string, and if so, it will do concatention. Otherwise, it will do numeric addition. 303 | 304 | ###### JavaScript 305 | ```javascript 306 | function plusAll(...args) { 307 | return args.reduce((accumulator, currentValue) => { 308 | return accumulator + currentValue; 309 | }, 0); 310 | } 311 | 312 | plusAll(4, 8, "!", 15, 16, 23, 42); // "12!15162342" 313 | ``` 314 | 315 | ###### C++ 316 | ```c++ 317 | any js_plus(const any& lval, const any& rval) { 318 | // If either operand is a string... 319 | if ( 320 | lval.type() == typeid(string) || 321 | rval.type() == typeid(string) 322 | ) { 323 | // Convert both operands to a string and do concatenation 324 | auto lval_str = ( 325 | lval.type() != typeid(string) ? 326 | to_string(any_cast(lval)) : 327 | any_cast(lval) 328 | ); 329 | auto rval_str = ( 330 | rval.type() != typeid(string) ? 331 | to_string(any_cast(rval)) : 332 | any_cast(rval) 333 | ); 334 | 335 | return lval_str + rval_str; 336 | } 337 | 338 | // Else, numeric addition 339 | return any_cast(lval) + any_cast(rval); 340 | } 341 | 342 | any plus_all(vector arguments) { 343 | return accumulate( 344 | arguments.begin(), arguments.end(), any{0}, 345 | [] (auto accumulator, auto current_value) { 346 | return js_plus(accumulator, current_value); 347 | } 348 | ); 349 | } 350 | 351 | plus_all({4, 8, "!"s, 15, 16, 23, 42}); // "12!15162342" 352 | ``` 353 | 354 | ## "This" 355 | 356 | Although `this` is handled separately from other parameters, nonetheless in a lot of ways, JavaScript's `this` behaves just like a parameter, albeit one that is often passed and received implicitly. We can reproduce JavaScript's behavior by adding one extra parameter before our arguments list, and we can name that parameter `this_`. The trailing underscore is there because `this` is a reserved name in C++. 357 | 358 | ###### JavaScript 359 | ```javascript 360 | function add(c, d) { 361 | return this.a + this.b + c + d; 362 | } 363 | 364 | let o = {a: 1, b: 3}; 365 | 366 | // The first parameter is the object to use as 367 | // "this"; subsequent parameters are passed as 368 | // arguments in the function call 369 | add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 370 | 371 | // The first parameter is the object to use as 372 | // "this"; the second is an array whose 373 | // elements are used as the arguments in the function call 374 | add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34 375 | ``` 376 | 377 | ###### C++ 378 | ```c++ 379 | any add(any this_, vector arguments) { 380 | return ( 381 | any_cast(any_cast(this_)["a"]) + 382 | any_cast(any_cast(this_)["b"]) + 383 | any_cast(arguments[0]) + 384 | any_cast(arguments[1]) 385 | ); 386 | } 387 | 388 | js_object o {{"a", 1}, {"b", 3}}; 389 | 390 | // The first parameter is the object to use as 391 | // "this"; the second is an array whose 392 | // elements are used as the arguments in the function call 393 | add(o, {5, 7}); // 1 + 3 + 5 + 7 = 16 394 | add(o, {10, 20}); // 1 + 3 + 10 + 20 = 34 395 | ``` 396 | 397 | The way we now call our C++ functions exactly matches JavaScript's `apply`, where the first argument is the `this` value and the second argument is an array that lists all other arguments. We're sometimes told that using `this` is inherently stateful, but at the end of the day, `this` is just a parameter. If we want our object methods to be pure, for example, then we can choose to not mutate `this` just like we would choose to not mutate any other parameter. 398 | 399 | ## Closures 400 | 401 | Conceptually, a closure is a function with non-local ([free](https://en.wikipedia.org/wiki/Free_variables_and_bound_variables)) variables. The concrete technique we use to implement that concept is a structure containing a function and an environment record. Or, put another way, a closure is an object with a single member function and private data. When we execute that function, it can access the "captured" variables from the environment stored in the closure object's private data. 402 | 403 | Depending on the language, we can make the closure object itself callable. You may have already seen callable objects in other languages. Python, for example, lets you define a [`__call__`](https://docs.python.org/3/reference/datamodel.html#object.__call__) method, and PHP has a special [`__invoke`](http://php.net/manual/en/language.oop5.magic.php#object.invoke) method. In C++, we define an [`operator()`](http://en.cppreference.com/w/cpp/language/operators#Function_call_operator) member function. And it's these functions that are executed when an object is called as if it were a function. 404 | 405 | As you read these descriptions, keep in mind that C++'s notion of a function is not the same as JavaScript's notion of a function. In C++, a function can be distinct from a closure and not associated with any environment. But in JavaScript, *every* function has an associated environment. What JavaScript calls a function isn't the function *part* of a closure. Rather, what JavaScript calls a function is the closure itself -- a callable object that contains an environment. 406 | 407 | ###### JavaScript 408 | ```javascript 409 | function outside(x) { 410 | function inside(y) { 411 | return x + y; 412 | } 413 | 414 | return inside; 415 | } 416 | 417 | let fnInside = outside(3); 418 | fnInside(5); // 8 419 | 420 | outside(3)(5); // 8 421 | ``` 422 | 423 | ###### C++ 424 | ```c++ 425 | auto outside(int x) { 426 | // A class that privately stores "x" and 427 | // can be called as if it were a function 428 | class Inside { 429 | int x_; 430 | 431 | public: 432 | Inside(int x) : x_ {x} {} 433 | 434 | auto operator()(int y) { 435 | return x_ + y; 436 | } 437 | }; 438 | 439 | // This is our closure, an instance of the above class, a callable object 440 | // that is constructed with and stores a value from its environment 441 | Inside inside {x}; 442 | 443 | return inside; 444 | } 445 | 446 | auto fn_inside = outside(3); 447 | fn_inside(5); // 8 448 | 449 | outside(3)(5); // 8 450 | ``` 451 | 452 | This C++ sample demonstrates what's ultimately happening under the hood, but admittedly it's verbose. In 2011, C++ introduced a lambda expression syntax, which produces a closure object and is syntactic sugar for defining and instantiating a callable class, same as we did above. 453 | 454 | ###### C++ 455 | ```c++ 456 | auto outside(int x) { 457 | // This is our closure, a callable object that 458 | // stores a value from its environment 459 | auto inside = [x] (int y) { 460 | return x + y; 461 | }; 462 | 463 | return inside; 464 | } 465 | 466 | auto fn_inside = outside(3); 467 | fn_inside(5); // 8 468 | 469 | outside(3)(5); // 8 470 | ``` 471 | 472 | In these C++ samples, I defined only `inside` as a callable object and I left `outside` as a plain function because that's all that was necessary to reproduce JavaScript's behavior for this particular code sample. But in truth, *every* function in JavaScript is a callable object. *Every* function is a closure. 473 | 474 | ## Function objects 475 | 476 | In fact, JavaScript's functions aren't just objects in the C++ sense of the word, they're also objects in the JavaScript sense of the word. Which means JavaScript's functions are callable hash tables. We can both invoke them as a function *and* assign to them key-value pairs. To reproduce this behavior in C++, we'll extend the `Delegating_unordered_map` we made earlier and specialize it to also be callable. 477 | 478 | ###### C++ 479 | ```c++ 480 | class Callable_delegating_unordered_map : public Delegating_unordered_map { 481 | function)> function_body_; 482 | 483 | public: 484 | Callable_delegating_unordered_map(function)> function_body) : 485 | function_body_ {function_body} 486 | {} 487 | 488 | auto operator()(any this_ = {}, vector arguments = {}) { 489 | return function_body_(this_, arguments); 490 | } 491 | }; 492 | ``` 493 | 494 | With that done, let's take a look at callable hash tables. 495 | 496 | ###### JavaScript 497 | ```javascript 498 | function square(number) { 499 | return number * number; 500 | } 501 | 502 | square.make = "Ford"; 503 | square.model = "Mustang"; 504 | square.year = 1969; 505 | 506 | square(4); // 16 507 | ``` 508 | 509 | ###### C++ 510 | ```c++ 511 | Callable_delegating_unordered_map square {[] (any this_, vector arguments) { 512 | return any_cast(arguments[0]) * any_cast(arguments[0]); 513 | }}; 514 | 515 | square["make"] = "Ford"s; 516 | square["model"] = "Mustang"s; 517 | square["year"] = 1969; 518 | 519 | square(nullptr, {4}); // 16 520 | ``` 521 | 522 | We've finally settled on the finished function object structure to reproduce JavaScript's behavior (or as close as we'll get here, anyway), so for convenience in the rest of the samples, I'm going to alias that object type to a shorter, friendlier name. 523 | 524 | ###### C++ 525 | ```c++ 526 | using js_function = Callable_delegating_unordered_map; 527 | ``` 528 | 529 | ## Lexical environment and scope chains 530 | 531 | The C++ closure examples from earlier do all we need to capture *individual* variables from any scope level, but that's not quite how JavaScript works. In JavaScript, what we see as local variables are actually entries in an object called an environment record, which pairs variable names with values. And each environment delegates accesses to the environment record of the next outer scope. 532 | 533 | That sounds familiar. We've seen this pattern before. As it turns out, the `Delegating_unordered_map` we made to reproduce prototypal inheritance is exactly what we need to also reproduce JavaScript's scope chains. 534 | 535 | ###### JavaScript 536 | ```javascript 537 | let globalVariable = "xyz"; 538 | 539 | function f() { 540 | let localVariable = true; 541 | 542 | function g() { 543 | let anotherLocalVariable = 123; 544 | 545 | // All variables of surrounding scopes are accessible 546 | localVariable = false; 547 | globalVariable = "abc"; 548 | } 549 | } 550 | ``` 551 | 552 | ###### C++ 553 | ```c++ 554 | Delegating_unordered_map global_environment; 555 | global_environment["globalVariable"] = "xyz"s; 556 | 557 | global_environment["f"] = js_function{[&] (any this_, vector arguments) { 558 | Delegating_unordered_map f_environment; 559 | f_environment.__proto__ = &global_environment; 560 | 561 | f_environment["localVariable"] = true; 562 | 563 | f_environment["g"] = js_function{[&] (any this_, vector arguments) { 564 | Delegating_unordered_map g_environment; 565 | g_environment.__proto__ = &f_environment; 566 | 567 | g_environment["anotherLocalVariable"] = 123; 568 | 569 | // All variables of surrounding scopes are accessible 570 | g_environment["localVariable"] = false; 571 | g_environment["globalVariable"] = "abc"s; 572 | 573 | return any{}; 574 | }}; 575 | 576 | return any{}; 577 | }}; 578 | ``` 579 | 580 | ## Capture by reference vs by value 581 | 582 | In C++, a closure's environment can store either a copy of values in scope or a reference to values in scope. But JavaScript's closures will only ever capture by reference. The difference is especially apparent in the famous problem with creating closures inside a loop. 583 | 584 | ###### JavaScript 585 | ```javascript 586 | let functions = []; 587 | 588 | for (var i = 0; i < 3; i++) { 589 | // Every closure we push captures a reference to the same "i" 590 | functions.push(() => { 591 | console.log(i); 592 | }); 593 | } 594 | 595 | // 3, 3, 3 596 | functions.forEach(fn => { 597 | fn(); 598 | }); 599 | ``` 600 | 601 | ###### C++ 602 | ```c++ 603 | vector functions_by_value; 604 | vector functions_by_ref; 605 | 606 | for (auto& i = *(new int{0}); i < 3; ++i) { 607 | // This closure will capture the value of "i" 608 | // at the moment the closure is created 609 | functions_by_value.push_back(js_function{[i] (any this_, vector arguments) { 610 | cout << i; 611 | return any{}; 612 | }}); 613 | 614 | // This closure will capture a reference to the same "i" 615 | functions_by_ref.push_back(js_function{[&i] (any this_, vector arguments) { 616 | cout << i; 617 | return any{}; 618 | }}); 619 | } 620 | 621 | // 0, 1, 2 622 | for_each(functions_by_value.begin(), functions_by_value.end(), [] (auto fn) { 623 | fn(); 624 | }); 625 | 626 | // 3, 3, 3 627 | for_each(functions_by_ref.begin(), functions_by_ref.end(), [] (auto fn) { 628 | fn(); 629 | }); 630 | ``` 631 | 632 | ### `let` and capturing by value 633 | 634 | Recently JavaScript added the [`let` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), which brings block scoping to the language. But when it comes to for-loops, then `let` does more than mere block scoping. At every iteration of the loop, [JavaScript creates a brand new *copy* of the loop variable](http://www.ecma-international.org/ecma-262/7.0/index.html#sec-createperiterationenvironment). That's not normal block scoping behavior. Rather, this seems to emulate capture by value in a language that otherwise only supports capture by reference. A JavaScript closure within the loop will still capture by reference, but it won't capture the loop variable itself; it will capture a per-iteration *copy* of the loop variable. 635 | 636 | ###### JavaScript 637 | ```javascript 638 | let functions = []; 639 | 640 | for (let i = 0; i < 3; i++) { 641 | // Every closure we push captures a reference to a per-iteration copy of "i" 642 | functions.push(() => { 643 | console.log(i); 644 | }); 645 | } 646 | 647 | // 0, 1, 2 648 | functions.forEach((fn) => { 649 | fn(); 650 | }); 651 | ``` 652 | 653 | ###### C++ 654 | ```c++ 655 | vector functions; 656 | 657 | for (auto i = 0; i < 3; ++i) { 658 | // Create a per-iteration copy of "i" 659 | auto& i_copy = *(new int{i}); 660 | 661 | // Every closure we push captures a reference to a per-iteration copy of "i" 662 | functions.push_back(js_function{[&i_copy] (any this_, vector arguments) { 663 | cout << i_copy; 664 | return any{}; 665 | }}); 666 | } 667 | 668 | // 0, 1, 2 669 | for_each(functions.begin(), functions.end(), [] (auto fn) { 670 | fn(); 671 | }); 672 | ``` 673 | 674 | ## Garbage collection 675 | 676 | One problem with capturing by reference is we now have to be conscientious of memory allocation lifetimes. Normally the lifetime of a value in memory is tied to a particular block, such as a for-loop or a function, but since the closures in the last sample will outlive their block, then any values they capture by reference will also need to outlive their block. In the last C++ sample, we avoided that issue by allowing the per-iteration copies to stay alive forever. But in JavaScript, memory is garbage collected. The lifetime of *any* value isn't necessarily tied to any particular block. If something somewhere retains a reference to a value, then that value will be kept alive. 677 | 678 | In C++, a guiding principle for the language is you don't pay for what you don't use, so there are several garbage collection strategies available -- such as [`unique_ptr`](http://en.cppreference.com/w/cpp/memory/unique_ptr), [`shared_ptr`](http://en.cppreference.com/w/cpp/memory/shared_ptr), and [`deferred_ptr`](https://github.com/hsutter/gcpp) -- each with incrementally more features but also with more overhead. Since we're reproducing the behavior of JavaScript, we'll go straight to `deferred_ptr` for garbage collection. 679 | 680 | Here's the last C++ sample again but this time with a garbage collector to alleviate the object lifetime issue. 681 | 682 | ###### C++ 683 | ```c++ 684 | deferred_heap my_heap; 685 | 686 | vector functions; 687 | 688 | for (auto i = 0; i < 3; ++i) { 689 | // Create a per-iteration copy of "i" 690 | auto i_copy = my_heap.make(i); 691 | 692 | // Every closure we push captures a reference to a per-iteration copy of "i" 693 | functions.push_back(js_function{[i_copy] (any this_, vector arguments) { 694 | cout << *i_copy; 695 | return any{}; 696 | }}); 697 | } 698 | 699 | // 0, 1, 2 700 | for_each(functions.begin(), functions.end(), [] (auto fn) { 701 | fn(); 702 | }); 703 | 704 | // Destroy and deallocate any unreachable objects 705 | my_heap.collect(); 706 | ``` 707 | 708 | In the sample above, only the per-iteration copy is garbage collected. If we want *all* of our objects, arrays, and function objects to also be garbage collected, then we'll have to make a few changes. First, we need to update the `Delegating_unordered_map` and change the raw `__proto__` pointer to the garbage collector `deferred_ptr`. 709 | 710 | ###### C++ 711 | ```c++ 712 | class Delegating_unordered_map : private unordered_map { 713 | public: 714 | deferred_ptr __proto__ {}; 715 | 716 | // ... 717 | }; 718 | ``` 719 | 720 | And second, when we want a new `js_object`, we'll have to ask the garbage collector to make it for us by calling, for example, `my_heap.make( js_object{{"a", 1}, {"b", 2}} )`. And we'll have to refer to those garbage collected object types as `deferred_ptr`. That can be just a little tedious and verbose, so let's define a couple helper functions and a couple type aliases to be a touch cleaner. 721 | 722 | ###### C++ 723 | ```c++ 724 | auto make_js_object(const js_object& obj = {}) { 725 | return my_heap.make(obj); 726 | } 727 | 728 | auto make_js_function(const js_function& func) { 729 | return my_heap.make(func); 730 | } 731 | 732 | using js_object_ref = deferred_ptr; 733 | using js_function_ref = deferred_ptr; 734 | ``` 735 | 736 | You may notice I aliased a type name with the suffix "ptr" to a name with the suffix "ref". Admittedly the terminology here gets a bit awkward. What JavaScript calls a reference behaves like what C++ calls a pointer. C++ also has a feature it calls a reference, but it's different than what JavaScript calls a reference. 737 | 738 | ## Classes 739 | 740 | JavaScript has a multitude of styles for creating objects, but despite the variety and how different the syntax for each may look, they build on each other in incremental steps. 741 | 742 | ### Factory functions 743 | 744 | The first and simplest way to create a large set of objects that share a common interface and implementation is a factory function. We just create a `js_object` "ex nilo" inside a function and return it. This lets us create the same type of object multiple times and in multiple places just by invoking a function. 745 | 746 | ###### JavaScript 747 | ```javascript 748 | function thing() { 749 | return { 750 | x: 42, 751 | y: 3.14, 752 | f: function() {}, 753 | g: function() {} 754 | }; 755 | } 756 | 757 | let o = thing(); 758 | ``` 759 | 760 | ###### C++ 761 | ```c++ 762 | auto thing = make_js_function({[] (any this_, vector arguments) { 763 | return make_js_object({ 764 | {"x", 42}, 765 | {"y", 3.14}, 766 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 767 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 768 | }); 769 | }}); 770 | 771 | auto o = (*thing)(); 772 | ``` 773 | 774 | But there's a drawback. This approach can cause memory bloat because each object contains its own unique copy of each function. Ideally we want every object to share just one copy of its functions. 775 | 776 | ### Prototype chains 777 | 778 | We can take advantage of JavaScript's prototypal inheritance and change our factory function so that each object it creates contains only the data unique to that particular object, and delegate all other property requests to a single, shared object. 779 | 780 | ###### JavaScript 781 | ```javascript 782 | let thingPrototype = { 783 | f: function() {}, 784 | g: function() {} 785 | }; 786 | 787 | function thing() { 788 | let o = { 789 | x: 42, 790 | y: 3.14 791 | }; 792 | 793 | Object.setPrototypeOf(o, thingPrototype); 794 | 795 | return o; 796 | } 797 | 798 | let o = thing(); 799 | ``` 800 | 801 | ###### C++ 802 | ```c++ 803 | auto thing_prototype = make_js_object({ 804 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 805 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 806 | }); 807 | 808 | auto thing = make_js_function({[=] (any this_, vector arguments) { 809 | auto o = make_js_object({ 810 | {"x", 42}, 811 | {"y", 3.14} 812 | }); 813 | 814 | o->__proto__ = thing_prototype; 815 | 816 | return o; 817 | }}); 818 | 819 | auto o = (*thing)(); 820 | ``` 821 | 822 | In fact, this is such a common pattern in JavaScript that the language has built-in support for it. We don't need to create our own shared object (the prototype object). Instead, a prototype object is created for us automatically, attached to every function under the property name `prototype`, and we can put our shared data there. 823 | 824 | ###### JavaScript 825 | ```javascript 826 | function thing() { 827 | let o = { 828 | x: 42, 829 | y: 3.14 830 | }; 831 | 832 | Object.setPrototypeOf(o, thing.prototype); 833 | 834 | return o; 835 | } 836 | 837 | thing.prototype.f = function() {}; 838 | thing.prototype.g = function() {}; 839 | 840 | let o = thing(); 841 | ``` 842 | 843 | ###### C++ 844 | ```c++ 845 | js_function_ref thing = make_js_function({[=] (any this_, vector arguments) { 846 | auto o = make_js_object({ 847 | {"x", 42}, 848 | {"y", 3.14} 849 | }); 850 | 851 | o->__proto__ = any_cast((*thing)["prototype"]); 852 | 853 | return o; 854 | }}); 855 | 856 | (*thing)["prototype"] = make_js_object({ 857 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 858 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 859 | }); 860 | 861 | auto o = (*thing)(); 862 | ``` 863 | 864 | But there's a drawback. This is going to result in some repetition during object creation in every such delegating-to-prototype-factory-function. 865 | 866 | ### Constructor functions 867 | 868 | JavaScript has some built-in support to isolate the repetition. The `new` keyword will create an object that delegates to some other arbitrary function's prototype. Then it will invoke that function to perform initialization with the newly created object as the `this` argument. And finally it will return the object. 869 | 870 | ###### JavaScript 871 | ```javascript 872 | function Thing() { 873 | this.x = 42; 874 | this.y = 3.14; 875 | } 876 | 877 | Thing.prototype.f = function() {}; 878 | Thing.prototype.g = function() {}; 879 | 880 | let o = new Thing(); 881 | ``` 882 | 883 | ###### C++ 884 | ```c++ 885 | auto js_new(js_function_ref constructor, vector arguments = {}) { 886 | auto o = make_js_object(); 887 | o->__proto__ = any_cast((*constructor)["prototype"]); 888 | 889 | (*constructor)(o, arguments); 890 | 891 | return o; 892 | } 893 | 894 | auto Thing = make_js_function({[] (any this_, vector arguments) { 895 | (*any_cast(this_))["x"] = 42; 896 | (*any_cast(this_))["y"] = 3.14; 897 | 898 | return any{}; 899 | }}); 900 | 901 | (*Thing)["prototype"] = make_js_object({ 902 | {"f", make_js_function({[] (any this_, vector arguments) { return any{}; }})}, 903 | {"g", make_js_function({[] (any this_, vector arguments) { return any{}; }})} 904 | }); 905 | 906 | auto o = js_new(Thing); 907 | ``` 908 | 909 | ## That's all folks! (...for now) 910 | 911 | I hope you found this look under the hood just as interesting and enlightening as I did. 912 | 913 | I'd like this to become a living and growing document. That's why I'm hosting it here on GitHub rather than on some blog site. I'm always looking to make the explanations better and the code clearer, as well as to lift the hood of even more dark corners of the language. I welcome and appreciate any contributions to make it better. 914 | 915 | ## Copyright 916 | 917 | Copyright 2017 Jeff Mott. Creative Commons [Attribution-NonCommercial 4.0 International](https://creativecommons.org/licenses/by-nc/4.0/) license. 918 | --------------------------------------------------------------------------------