├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── runtime ├── exceptions.cpp ├── exceptions.hpp ├── global_io.cpp ├── global_io.hpp ├── global_json.cpp ├── global_json.hpp ├── global_symbol.cpp ├── global_symbol.hpp ├── js_primitives.cpp ├── js_primitives.hpp ├── js_value.cpp └── js_value.hpp └── src ├── command_utils.rs ├── globals ├── io.rs ├── json.rs ├── mod.rs └── symbol.rs ├── main.rs ├── test.rs └── transpiler.rs /.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | *.o 3 | *.dSYM 4 | testprog 5 | *.a 6 | target 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "Inflector" 7 | version = "0.11.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" 10 | dependencies = [ 11 | "lazy_static", 12 | "regex", 13 | ] 14 | 15 | [[package]] 16 | name = "ahash" 17 | version = "0.7.6" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 20 | dependencies = [ 21 | "getrandom", 22 | "once_cell", 23 | "version_check", 24 | ] 25 | 26 | [[package]] 27 | name = "aho-corasick" 28 | version = "0.7.18" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 31 | dependencies = [ 32 | "memchr", 33 | ] 34 | 35 | [[package]] 36 | name = "anyhow" 37 | version = "1.0.60" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" 40 | 41 | [[package]] 42 | name = "ast_node" 43 | version = "0.8.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "1a36288803cd1605bc4f0e3189970a0db8e602bb01a39f8133889f35ece7ddde" 46 | dependencies = [ 47 | "darling", 48 | "pmutil", 49 | "proc-macro2", 50 | "quote", 51 | "swc_macros_common", 52 | "syn", 53 | ] 54 | 55 | [[package]] 56 | name = "atty" 57 | version = "0.2.14" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 60 | dependencies = [ 61 | "hermit-abi", 62 | "libc", 63 | "winapi", 64 | ] 65 | 66 | [[package]] 67 | name = "autocfg" 68 | version = "1.1.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 71 | 72 | [[package]] 73 | name = "better_scoped_tls" 74 | version = "0.1.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "b73e8ecdec39e98aa3b19e8cd0b8ed8f77ccb86a6b0b2dc7cd86d105438a2123" 77 | dependencies = [ 78 | "scoped-tls", 79 | ] 80 | 81 | [[package]] 82 | name = "bitflags" 83 | version = "1.3.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 86 | 87 | [[package]] 88 | name = "cfg-if" 89 | version = "1.0.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 92 | 93 | [[package]] 94 | name = "clap" 95 | version = "3.2.16" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" 98 | dependencies = [ 99 | "atty", 100 | "bitflags", 101 | "clap_derive", 102 | "clap_lex", 103 | "indexmap", 104 | "once_cell", 105 | "strsim", 106 | "termcolor", 107 | "textwrap", 108 | ] 109 | 110 | [[package]] 111 | name = "clap_derive" 112 | version = "3.2.15" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" 115 | dependencies = [ 116 | "heck", 117 | "proc-macro-error", 118 | "proc-macro2", 119 | "quote", 120 | "syn", 121 | ] 122 | 123 | [[package]] 124 | name = "clap_lex" 125 | version = "0.2.4" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 128 | dependencies = [ 129 | "os_str_bytes", 130 | ] 131 | 132 | [[package]] 133 | name = "darling" 134 | version = "0.13.4" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" 137 | dependencies = [ 138 | "darling_core", 139 | "darling_macro", 140 | ] 141 | 142 | [[package]] 143 | name = "darling_core" 144 | version = "0.13.4" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" 147 | dependencies = [ 148 | "fnv", 149 | "ident_case", 150 | "proc-macro2", 151 | "quote", 152 | "strsim", 153 | "syn", 154 | ] 155 | 156 | [[package]] 157 | name = "darling_macro" 158 | version = "0.13.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" 161 | dependencies = [ 162 | "darling_core", 163 | "quote", 164 | "syn", 165 | ] 166 | 167 | [[package]] 168 | name = "debug_unreachable" 169 | version = "0.1.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3" 172 | dependencies = [ 173 | "unreachable", 174 | ] 175 | 176 | [[package]] 177 | name = "either" 178 | version = "1.7.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" 181 | 182 | [[package]] 183 | name = "enum_kind" 184 | version = "0.2.1" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "78b940da354ae81ef0926c5eaa428207b8f4f091d3956c891dfbd124162bed99" 187 | dependencies = [ 188 | "pmutil", 189 | "proc-macro2", 190 | "swc_macros_common", 191 | "syn", 192 | ] 193 | 194 | [[package]] 195 | name = "fnv" 196 | version = "1.0.7" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 199 | 200 | [[package]] 201 | name = "form_urlencoded" 202 | version = "1.0.1" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 205 | dependencies = [ 206 | "matches", 207 | "percent-encoding", 208 | ] 209 | 210 | [[package]] 211 | name = "from_variant" 212 | version = "0.1.3" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "0951635027ca477be98f8774abd6f0345233439d63f307e47101acb40c7cc63d" 215 | dependencies = [ 216 | "pmutil", 217 | "proc-macro2", 218 | "swc_macros_common", 219 | "syn", 220 | ] 221 | 222 | [[package]] 223 | name = "getrandom" 224 | version = "0.2.7" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 227 | dependencies = [ 228 | "cfg-if", 229 | "libc", 230 | "wasi", 231 | ] 232 | 233 | [[package]] 234 | name = "hashbrown" 235 | version = "0.12.3" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 238 | 239 | [[package]] 240 | name = "heck" 241 | version = "0.4.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 244 | 245 | [[package]] 246 | name = "hermit-abi" 247 | version = "0.1.19" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 250 | dependencies = [ 251 | "libc", 252 | ] 253 | 254 | [[package]] 255 | name = "ident_case" 256 | version = "1.0.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 259 | 260 | [[package]] 261 | name = "idna" 262 | version = "0.2.3" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 265 | dependencies = [ 266 | "matches", 267 | "unicode-bidi", 268 | "unicode-normalization", 269 | ] 270 | 271 | [[package]] 272 | name = "indexmap" 273 | version = "1.9.1" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 276 | dependencies = [ 277 | "autocfg", 278 | "hashbrown", 279 | ] 280 | 281 | [[package]] 282 | name = "is-macro" 283 | version = "0.2.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "1c068d4c6b922cd6284c609cfa6dec0e41615c9c5a1a4ba729a970d8daba05fb" 286 | dependencies = [ 287 | "Inflector", 288 | "pmutil", 289 | "proc-macro2", 290 | "quote", 291 | "syn", 292 | ] 293 | 294 | [[package]] 295 | name = "jsxx" 296 | version = "0.1.0" 297 | dependencies = [ 298 | "anyhow", 299 | "clap", 300 | "swc_common", 301 | "swc_ecma_ast", 302 | "swc_ecma_parser", 303 | "swc_ecma_visit", 304 | "thiserror", 305 | "uuid", 306 | ] 307 | 308 | [[package]] 309 | name = "lazy_static" 310 | version = "1.4.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 313 | 314 | [[package]] 315 | name = "lexical" 316 | version = "6.1.1" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6" 319 | dependencies = [ 320 | "lexical-core", 321 | ] 322 | 323 | [[package]] 324 | name = "lexical-core" 325 | version = "0.8.5" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" 328 | dependencies = [ 329 | "lexical-parse-float", 330 | "lexical-parse-integer", 331 | "lexical-util", 332 | "lexical-write-float", 333 | "lexical-write-integer", 334 | ] 335 | 336 | [[package]] 337 | name = "lexical-parse-float" 338 | version = "0.8.5" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" 341 | dependencies = [ 342 | "lexical-parse-integer", 343 | "lexical-util", 344 | "static_assertions", 345 | ] 346 | 347 | [[package]] 348 | name = "lexical-parse-integer" 349 | version = "0.8.6" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" 352 | dependencies = [ 353 | "lexical-util", 354 | "static_assertions", 355 | ] 356 | 357 | [[package]] 358 | name = "lexical-util" 359 | version = "0.8.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" 362 | dependencies = [ 363 | "static_assertions", 364 | ] 365 | 366 | [[package]] 367 | name = "lexical-write-float" 368 | version = "0.8.5" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" 371 | dependencies = [ 372 | "lexical-util", 373 | "lexical-write-integer", 374 | "static_assertions", 375 | ] 376 | 377 | [[package]] 378 | name = "lexical-write-integer" 379 | version = "0.8.5" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" 382 | dependencies = [ 383 | "lexical-util", 384 | "static_assertions", 385 | ] 386 | 387 | [[package]] 388 | name = "libc" 389 | version = "0.2.127" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" 392 | 393 | [[package]] 394 | name = "lock_api" 395 | version = "0.4.7" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 398 | dependencies = [ 399 | "autocfg", 400 | "scopeguard", 401 | ] 402 | 403 | [[package]] 404 | name = "matches" 405 | version = "0.1.9" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 408 | 409 | [[package]] 410 | name = "memchr" 411 | version = "2.5.0" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 414 | 415 | [[package]] 416 | name = "new_debug_unreachable" 417 | version = "1.0.4" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 420 | 421 | [[package]] 422 | name = "num-bigint" 423 | version = "0.4.3" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 426 | dependencies = [ 427 | "autocfg", 428 | "num-integer", 429 | "num-traits", 430 | "serde", 431 | ] 432 | 433 | [[package]] 434 | name = "num-integer" 435 | version = "0.1.45" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 438 | dependencies = [ 439 | "autocfg", 440 | "num-traits", 441 | ] 442 | 443 | [[package]] 444 | name = "num-traits" 445 | version = "0.2.15" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 448 | dependencies = [ 449 | "autocfg", 450 | ] 451 | 452 | [[package]] 453 | name = "once_cell" 454 | version = "1.13.0" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 457 | 458 | [[package]] 459 | name = "os_str_bytes" 460 | version = "6.2.0" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" 463 | 464 | [[package]] 465 | name = "parking_lot" 466 | version = "0.12.1" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 469 | dependencies = [ 470 | "lock_api", 471 | "parking_lot_core", 472 | ] 473 | 474 | [[package]] 475 | name = "parking_lot_core" 476 | version = "0.9.3" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 479 | dependencies = [ 480 | "cfg-if", 481 | "libc", 482 | "redox_syscall", 483 | "smallvec", 484 | "windows-sys", 485 | ] 486 | 487 | [[package]] 488 | name = "percent-encoding" 489 | version = "2.1.0" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 492 | 493 | [[package]] 494 | name = "phf_generator" 495 | version = "0.10.0" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 498 | dependencies = [ 499 | "phf_shared", 500 | "rand", 501 | ] 502 | 503 | [[package]] 504 | name = "phf_shared" 505 | version = "0.10.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 508 | dependencies = [ 509 | "siphasher", 510 | ] 511 | 512 | [[package]] 513 | name = "pin-project-lite" 514 | version = "0.2.9" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 517 | 518 | [[package]] 519 | name = "pmutil" 520 | version = "0.5.3" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" 523 | dependencies = [ 524 | "proc-macro2", 525 | "quote", 526 | "syn", 527 | ] 528 | 529 | [[package]] 530 | name = "ppv-lite86" 531 | version = "0.2.16" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 534 | 535 | [[package]] 536 | name = "precomputed-hash" 537 | version = "0.1.1" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 540 | 541 | [[package]] 542 | name = "proc-macro-error" 543 | version = "1.0.4" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 546 | dependencies = [ 547 | "proc-macro-error-attr", 548 | "proc-macro2", 549 | "quote", 550 | "syn", 551 | "version_check", 552 | ] 553 | 554 | [[package]] 555 | name = "proc-macro-error-attr" 556 | version = "1.0.4" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 559 | dependencies = [ 560 | "proc-macro2", 561 | "quote", 562 | "version_check", 563 | ] 564 | 565 | [[package]] 566 | name = "proc-macro2" 567 | version = "1.0.43" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 570 | dependencies = [ 571 | "unicode-ident", 572 | ] 573 | 574 | [[package]] 575 | name = "quote" 576 | version = "1.0.21" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 579 | dependencies = [ 580 | "proc-macro2", 581 | ] 582 | 583 | [[package]] 584 | name = "rand" 585 | version = "0.8.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 588 | dependencies = [ 589 | "libc", 590 | "rand_chacha", 591 | "rand_core", 592 | ] 593 | 594 | [[package]] 595 | name = "rand_chacha" 596 | version = "0.3.1" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 599 | dependencies = [ 600 | "ppv-lite86", 601 | "rand_core", 602 | ] 603 | 604 | [[package]] 605 | name = "rand_core" 606 | version = "0.6.3" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 609 | dependencies = [ 610 | "getrandom", 611 | ] 612 | 613 | [[package]] 614 | name = "redox_syscall" 615 | version = "0.2.16" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 618 | dependencies = [ 619 | "bitflags", 620 | ] 621 | 622 | [[package]] 623 | name = "regex" 624 | version = "1.6.0" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 627 | dependencies = [ 628 | "aho-corasick", 629 | "memchr", 630 | "regex-syntax", 631 | ] 632 | 633 | [[package]] 634 | name = "regex-syntax" 635 | version = "0.6.27" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 638 | 639 | [[package]] 640 | name = "rustc-hash" 641 | version = "1.1.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 644 | 645 | [[package]] 646 | name = "scoped-tls" 647 | version = "1.0.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" 650 | 651 | [[package]] 652 | name = "scopeguard" 653 | version = "1.1.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 656 | 657 | [[package]] 658 | name = "serde" 659 | version = "1.0.142" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" 662 | dependencies = [ 663 | "serde_derive", 664 | ] 665 | 666 | [[package]] 667 | name = "serde_derive" 668 | version = "1.0.142" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" 671 | dependencies = [ 672 | "proc-macro2", 673 | "quote", 674 | "syn", 675 | ] 676 | 677 | [[package]] 678 | name = "siphasher" 679 | version = "0.3.10" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 682 | 683 | [[package]] 684 | name = "smallvec" 685 | version = "1.9.0" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 688 | 689 | [[package]] 690 | name = "static_assertions" 691 | version = "1.1.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 694 | 695 | [[package]] 696 | name = "string_cache" 697 | version = "0.8.4" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" 700 | dependencies = [ 701 | "new_debug_unreachable", 702 | "once_cell", 703 | "parking_lot", 704 | "phf_shared", 705 | "precomputed-hash", 706 | "serde", 707 | ] 708 | 709 | [[package]] 710 | name = "string_cache_codegen" 711 | version = "0.5.2" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" 714 | dependencies = [ 715 | "phf_generator", 716 | "phf_shared", 717 | "proc-macro2", 718 | "quote", 719 | ] 720 | 721 | [[package]] 722 | name = "string_enum" 723 | version = "0.3.1" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "f584cc881e9e5f1fd6bf827b0444aa94c30d8fe6378cf241071b5f5700b2871f" 726 | dependencies = [ 727 | "pmutil", 728 | "proc-macro2", 729 | "quote", 730 | "swc_macros_common", 731 | "syn", 732 | ] 733 | 734 | [[package]] 735 | name = "strsim" 736 | version = "0.10.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 739 | 740 | [[package]] 741 | name = "swc_atoms" 742 | version = "0.4.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "48195a9ae467bf6bd47f84ef949d1debfcad41b9f3e34387bc80586953132cb7" 745 | dependencies = [ 746 | "once_cell", 747 | "rustc-hash", 748 | "serde", 749 | "string_cache", 750 | "string_cache_codegen", 751 | ] 752 | 753 | [[package]] 754 | name = "swc_common" 755 | version = "0.27.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "fb3a8e10619952fb1b60b2f039eb1954419cc611f6efd639d200dfd15fa078d3" 758 | dependencies = [ 759 | "ahash", 760 | "ast_node", 761 | "better_scoped_tls", 762 | "cfg-if", 763 | "debug_unreachable", 764 | "either", 765 | "from_variant", 766 | "num-bigint", 767 | "once_cell", 768 | "rustc-hash", 769 | "serde", 770 | "siphasher", 771 | "string_cache", 772 | "swc_atoms", 773 | "swc_eq_ignore_macros", 774 | "swc_visit", 775 | "tracing", 776 | "unicode-width", 777 | "url", 778 | ] 779 | 780 | [[package]] 781 | name = "swc_ecma_ast" 782 | version = "0.90.0" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "790097ba94811fd9a386fd3065b0ede143db4e7048a5174b193230615bc8ea14" 785 | dependencies = [ 786 | "bitflags", 787 | "is-macro", 788 | "num-bigint", 789 | "scoped-tls", 790 | "serde", 791 | "string_enum", 792 | "swc_atoms", 793 | "swc_common", 794 | "unicode-id", 795 | ] 796 | 797 | [[package]] 798 | name = "swc_ecma_parser" 799 | version = "0.117.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "8487764045bbc8bee67dea5deffde74ca3c2d67178f63bb6658cbccf08e2d225" 802 | dependencies = [ 803 | "either", 804 | "enum_kind", 805 | "lexical", 806 | "num-bigint", 807 | "serde", 808 | "smallvec", 809 | "swc_atoms", 810 | "swc_common", 811 | "swc_ecma_ast", 812 | "tracing", 813 | "typed-arena", 814 | ] 815 | 816 | [[package]] 817 | name = "swc_ecma_visit" 818 | version = "0.76.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "d98a58a923368e824bb9025cf751a93255641aef603a9b19fbb94e863c4786da" 821 | dependencies = [ 822 | "num-bigint", 823 | "swc_atoms", 824 | "swc_common", 825 | "swc_ecma_ast", 826 | "swc_visit", 827 | "tracing", 828 | ] 829 | 830 | [[package]] 831 | name = "swc_eq_ignore_macros" 832 | version = "0.1.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "8c8f200a2eaed938e7c1a685faaa66e6d42fa9e17da5f62572d3cbc335898f5e" 835 | dependencies = [ 836 | "pmutil", 837 | "proc-macro2", 838 | "quote", 839 | "syn", 840 | ] 841 | 842 | [[package]] 843 | name = "swc_macros_common" 844 | version = "0.3.6" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "a4be988307882648d9bc7c71a6a73322b7520ef0211e920489a98f8391d8caa2" 847 | dependencies = [ 848 | "pmutil", 849 | "proc-macro2", 850 | "quote", 851 | "syn", 852 | ] 853 | 854 | [[package]] 855 | name = "swc_visit" 856 | version = "0.5.1" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "ce1b826c9d4c0416bbed55d245c853bc1a60da55bf92f8b00dd22b37baf72080" 859 | dependencies = [ 860 | "either", 861 | "swc_visit_macros", 862 | ] 863 | 864 | [[package]] 865 | name = "swc_visit_macros" 866 | version = "0.5.2" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "9fda2daf67d99e8bc63d61b12818994863f65b7bcf52d4faab338154c7058546" 869 | dependencies = [ 870 | "Inflector", 871 | "pmutil", 872 | "proc-macro2", 873 | "quote", 874 | "swc_macros_common", 875 | "syn", 876 | ] 877 | 878 | [[package]] 879 | name = "syn" 880 | version = "1.0.99" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 883 | dependencies = [ 884 | "proc-macro2", 885 | "quote", 886 | "unicode-ident", 887 | ] 888 | 889 | [[package]] 890 | name = "termcolor" 891 | version = "1.1.3" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 894 | dependencies = [ 895 | "winapi-util", 896 | ] 897 | 898 | [[package]] 899 | name = "textwrap" 900 | version = "0.15.0" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 903 | 904 | [[package]] 905 | name = "thiserror" 906 | version = "1.0.32" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" 909 | dependencies = [ 910 | "thiserror-impl", 911 | ] 912 | 913 | [[package]] 914 | name = "thiserror-impl" 915 | version = "1.0.32" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" 918 | dependencies = [ 919 | "proc-macro2", 920 | "quote", 921 | "syn", 922 | ] 923 | 924 | [[package]] 925 | name = "tinyvec" 926 | version = "1.6.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 929 | dependencies = [ 930 | "tinyvec_macros", 931 | ] 932 | 933 | [[package]] 934 | name = "tinyvec_macros" 935 | version = "0.1.0" 936 | source = "registry+https://github.com/rust-lang/crates.io-index" 937 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 938 | 939 | [[package]] 940 | name = "tracing" 941 | version = "0.1.36" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" 944 | dependencies = [ 945 | "cfg-if", 946 | "pin-project-lite", 947 | "tracing-attributes", 948 | "tracing-core", 949 | ] 950 | 951 | [[package]] 952 | name = "tracing-attributes" 953 | version = "0.1.22" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" 956 | dependencies = [ 957 | "proc-macro2", 958 | "quote", 959 | "syn", 960 | ] 961 | 962 | [[package]] 963 | name = "tracing-core" 964 | version = "0.1.29" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" 967 | dependencies = [ 968 | "once_cell", 969 | ] 970 | 971 | [[package]] 972 | name = "typed-arena" 973 | version = "2.0.1" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" 976 | 977 | [[package]] 978 | name = "unicode-bidi" 979 | version = "0.3.8" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 982 | 983 | [[package]] 984 | name = "unicode-id" 985 | version = "0.3.2" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "69fe8d9274f490a36442acb4edfd0c4e473fdfc6a8b5cd32f28a0235761aedbe" 988 | 989 | [[package]] 990 | name = "unicode-ident" 991 | version = "1.0.3" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 994 | 995 | [[package]] 996 | name = "unicode-normalization" 997 | version = "0.1.21" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" 1000 | dependencies = [ 1001 | "tinyvec", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "unicode-width" 1006 | version = "0.1.9" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1009 | 1010 | [[package]] 1011 | name = "unreachable" 1012 | version = "0.1.1" 1013 | source = "registry+https://github.com/rust-lang/crates.io-index" 1014 | checksum = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" 1015 | dependencies = [ 1016 | "void", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "url" 1021 | version = "2.2.2" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1024 | dependencies = [ 1025 | "form_urlencoded", 1026 | "idna", 1027 | "matches", 1028 | "percent-encoding", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "uuid" 1033 | version = "1.1.2" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" 1036 | dependencies = [ 1037 | "getrandom", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "version_check" 1042 | version = "0.9.4" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1045 | 1046 | [[package]] 1047 | name = "void" 1048 | version = "1.0.2" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1051 | 1052 | [[package]] 1053 | name = "wasi" 1054 | version = "0.11.0+wasi-snapshot-preview1" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1057 | 1058 | [[package]] 1059 | name = "winapi" 1060 | version = "0.3.9" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1063 | dependencies = [ 1064 | "winapi-i686-pc-windows-gnu", 1065 | "winapi-x86_64-pc-windows-gnu", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "winapi-i686-pc-windows-gnu" 1070 | version = "0.4.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1073 | 1074 | [[package]] 1075 | name = "winapi-util" 1076 | version = "0.1.5" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1079 | dependencies = [ 1080 | "winapi", 1081 | ] 1082 | 1083 | [[package]] 1084 | name = "winapi-x86_64-pc-windows-gnu" 1085 | version = "0.4.0" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1088 | 1089 | [[package]] 1090 | name = "windows-sys" 1091 | version = "0.36.1" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1094 | dependencies = [ 1095 | "windows_aarch64_msvc", 1096 | "windows_i686_gnu", 1097 | "windows_i686_msvc", 1098 | "windows_x86_64_gnu", 1099 | "windows_x86_64_msvc", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "windows_aarch64_msvc" 1104 | version = "0.36.1" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1107 | 1108 | [[package]] 1109 | name = "windows_i686_gnu" 1110 | version = "0.36.1" 1111 | source = "registry+https://github.com/rust-lang/crates.io-index" 1112 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1113 | 1114 | [[package]] 1115 | name = "windows_i686_msvc" 1116 | version = "0.36.1" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1119 | 1120 | [[package]] 1121 | name = "windows_x86_64_gnu" 1122 | version = "0.36.1" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1125 | 1126 | [[package]] 1127 | name = "windows_x86_64_msvc" 1128 | version = "0.36.1" 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" 1130 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1131 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsxx" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | swc_ecma_parser = "0.117.0" 8 | swc_ecma_visit = "0.76.0" 9 | swc_common = "0.27.0" 10 | thiserror = "1.0.32" 11 | anyhow = "1.0.43" 12 | swc_ecma_ast = "0.90.0" 13 | clap = { version = "3.2.16", features = ["derive"] } 14 | 15 | [dev-dependencies] 16 | anyhow = "1.0.43" 17 | uuid = { version = "1.1.2", features = ["v4"] } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Surma 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsxx 2 | 3 | `jsxx` is a transpiler that compiles JavaScript to C++. 4 | 5 | More details can be found in the [blog post]. 6 | 7 | ## Usage 8 | 9 | `jsxx` reads JavaScript from stdin and compiles it to C++ and then uses `clang++` to create a binary. 10 | 11 | ``` 12 | $ cat testprog.js | cargo run 13 | $ ./output 14 | 1.000000,2.000000,3.000000 15 | ``` 16 | 17 | If you have [WASI-SDK] installed, you can also compile straight to WebAssembly: 18 | 19 | ``` 20 | $ cat testprog.js | \ 21 | cargo run -- \ 22 | --wasm \ 23 | --clang-path $HOME/Downloads/wasi-sdk-16.0/bin/clang++ \ 24 | -- -Oz -flto -Wl,--lto-O3 25 | $ wasmtime output.wasm 26 | 1.000000,2.000000,3.000000 27 | ``` 28 | 29 | If you want to inspect the generated C++ code, use `--emit-cpp`. 30 | 31 | 32 | --- 33 | Apache 2.0 34 | 35 | [blog post]: https://surma.dev/things/compile-js 36 | [WASI-SDK]: https://github.com/WebAssembly/wasi-sdk 37 | -------------------------------------------------------------------------------- /runtime/exceptions.cpp: -------------------------------------------------------------------------------- 1 | #include "js_value.hpp" 2 | 3 | #include 4 | 5 | #ifdef FEATURE_EXCEPTIONS 6 | void js_throw(JSValue v) { throw v; } 7 | #else 8 | void js_throw(JSValue v) { std::terminate(); } 9 | #endif 10 | -------------------------------------------------------------------------------- /runtime/exceptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | class JSValue; 3 | 4 | void js_throw(JSValue v); 5 | -------------------------------------------------------------------------------- /runtime/global_io.cpp: -------------------------------------------------------------------------------- 1 | #include "global_io.hpp" 2 | #include "exceptions.hpp" 3 | 4 | #include 5 | #include 6 | 7 | static JSValue write_to_stdout(JSValue thisArg, std::vector &args) { 8 | JSValue data = args[0]; 9 | std::string str = data.coerce_to_string(); 10 | write(1, str.c_str(), str.size()); 11 | return JSValue{true}; 12 | } 13 | 14 | static JSValue read_from_stdin(JSValue thisArg, std::vector &args) { 15 | char buf[1024]; 16 | std::string input{}; 17 | while (true) { 18 | auto n = read(0, buf, 1023); 19 | buf[n] = 0; 20 | input += std::string{buf}; 21 | if (n < 1023) 22 | break; 23 | }; 24 | return JSValue{input}; 25 | } 26 | 27 | JSValue create_IO_global() { 28 | JSValue global = JSValue::new_object( 29 | {{JSValue{"read_from_stdin"}, JSValue::new_function(read_from_stdin)}, 30 | {JSValue{"write_to_stdout"}, JSValue::new_function(write_to_stdout)}}); 31 | 32 | return global; 33 | } 34 | -------------------------------------------------------------------------------- /runtime/global_io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "js_value.hpp" 4 | 5 | class JSValue; 6 | 7 | JSValue create_IO_global(); 8 | -------------------------------------------------------------------------------- /runtime/global_json.cpp: -------------------------------------------------------------------------------- 1 | #include "global_json.hpp" 2 | #include "exceptions.hpp" 3 | #include 4 | #include 5 | 6 | static JSValue json_parse_value(const char **input); 7 | 8 | static void eat_whitespace(const char **cur) { 9 | while (true) { 10 | bool is_whitespace = **cur == ' ' || **cur == '\n' || **cur == '\t'; 11 | if (!is_whitespace) 12 | break; 13 | (*cur)++; 14 | } 15 | } 16 | 17 | static JSValue json_parse_string(const char **input) { 18 | const char *cur = *input + 1; 19 | std::string output; 20 | while (*cur != '"') { 21 | if (*cur == '\\') { 22 | cur++; 23 | } 24 | output += *cur; 25 | cur++; 26 | } 27 | *input = ++cur; 28 | return JSValue{output}; 29 | } 30 | 31 | static bool is_digit(const char x) { return x >= '0' && x <= '9'; } 32 | 33 | static JSValue json_parse_number(const char **input) { 34 | const char *cur = *input; 35 | if (*cur == '-') 36 | cur++; 37 | while (true) { 38 | bool is_valid_number_char = is_digit(*cur) || *cur == '.'; 39 | if (!is_valid_number_char) 40 | break; 41 | cur++; 42 | } 43 | JSValue result{std::stod(std::string(*input, cur - *input))}; 44 | *input = cur; 45 | return result; 46 | } 47 | 48 | static JSValue json_parse_object(const char **cur) { 49 | JSObject obj{}; 50 | (*cur)++; 51 | eat_whitespace(cur); 52 | while (**cur != '}') { 53 | auto key = json_parse_string(cur); 54 | eat_whitespace(cur); 55 | if (**cur != ':') 56 | js_throw(JSValue{"Expected `:` after property name"}); 57 | (*cur)++; 58 | eat_whitespace(cur); 59 | auto value = json_parse_value(cur); 60 | obj.internal->push_back({key, value}); 61 | eat_whitespace(cur); 62 | if (**cur == ',') 63 | (*cur)++; 64 | eat_whitespace(cur); 65 | } 66 | (*cur)++; 67 | return JSValue{obj}; 68 | } 69 | 70 | static JSValue json_parse_array(const char **cur) { 71 | JSArray arr{}; 72 | (*cur)++; 73 | eat_whitespace(cur); 74 | while (**cur != ']') { 75 | auto value = json_parse_value(cur); 76 | arr.internal->push_back(value); 77 | eat_whitespace(cur); 78 | if (**cur == ',') 79 | (*cur)++; 80 | eat_whitespace(cur); 81 | } 82 | (*cur)++; 83 | return JSValue{arr}; 84 | } 85 | 86 | static JSValue json_parse_value(const char **input) { 87 | eat_whitespace(input); 88 | if (**input == '"') 89 | return json_parse_string(input); 90 | if (**input == '{') 91 | return json_parse_object(input); 92 | if (**input == '[') 93 | return json_parse_array(input); 94 | if (is_digit(**input) || **input == '.' || **input == '-') 95 | return json_parse_number(input); 96 | if (*input[0] == 't' && *input[1] == 'r' && *input[2] == 'u' && 97 | *input[3] == 'e') { 98 | *input += 4; 99 | return JSValue{true}; 100 | } 101 | if (*input[0] == 'f' && *input[1] == 'a' && *input[2] == 'l' && 102 | *input[3] == 's' && *input[4] == 'e') { 103 | *input += 5; 104 | return JSValue{false}; 105 | } 106 | js_throw(JSValue{"Unexpected token"}); 107 | return JSValue::undefined(); // unreachable 108 | } 109 | 110 | static JSValue json_parse(JSValue thisArg, std::vector &args) { 111 | if (args[0].type() != JSValueType::STRING) 112 | js_throw(JSValue{"Can only parse strings"}); 113 | std::string input = args[0].coerce_to_string(); 114 | const char *c = input.c_str(); 115 | return json_parse_value(&c); 116 | } 117 | 118 | static std::string json_stringify_value(JSValue v); 119 | 120 | static std::string json_stringify_object(JSObject v) { 121 | std::string result = "{"; 122 | for (auto v : *v.internal) { 123 | result += json_stringify_value(v.first); 124 | result += ":"; 125 | result += json_stringify_value(v.second); 126 | result += ","; 127 | } 128 | result = result.substr(0, result.size() - 1); 129 | result += "}"; 130 | return result; 131 | } 132 | 133 | static std::string json_stringify_array(JSArray v) { 134 | std::string result = "["; 135 | for (auto v : *v.internal) { 136 | result += json_stringify_value(v); 137 | result += ","; 138 | } 139 | if (v.internal->size() >= 1) { 140 | result = result.substr(0, result.size() - 1); 141 | } 142 | result += "]"; 143 | return result; 144 | } 145 | 146 | static std::string json_stringify_number(JSNumber v) { 147 | return std::to_string(v.internal); 148 | } 149 | 150 | static std::string json_stringify_string(JSString v) { 151 | return std::string("\"" + v.internal + "\""); 152 | } 153 | 154 | static std::string json_stringify_value(JSValue v) { 155 | if (v.type() == JSValueType::ARRAY) 156 | return json_stringify_array(*std::get(*v.value)); 157 | if (v.type() == JSValueType::OBJECT) 158 | return json_stringify_object(*std::get(*v.value)); 159 | if (v.type() == JSValueType::NUMBER) 160 | return json_stringify_number(std::get(*v.value)); 161 | if (v.type() == JSValueType::STRING) 162 | return json_stringify_string(std::get(*v.value)); 163 | if (v.type() == JSValueType::BOOL) 164 | return v.coerce_to_string(); 165 | return std::string(""); 166 | } 167 | 168 | static JSValue json_stringify(JSValue thisArg, std::vector &args) { 169 | return JSValue{json_stringify_value(args[0])}; 170 | } 171 | 172 | JSValue create_JSON_global() { 173 | JSValue global = JSValue::new_object( 174 | {{JSValue{"parse"}, JSValue::new_function(&json_parse)}, 175 | {JSValue{"stringify"}, JSValue::new_function(&json_stringify)}}); 176 | 177 | return global; 178 | } 179 | -------------------------------------------------------------------------------- /runtime/global_json.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "js_value.hpp" 4 | 5 | class JSValue; 6 | 7 | JSValue create_JSON_global(); 8 | -------------------------------------------------------------------------------- /runtime/global_symbol.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "global_json.hpp" 3 | 4 | JSValue create_symbol_global() { 5 | JSValue global = 6 | JSValue::new_object({{JSValue{"iterator"}, iterator_symbol}}); 7 | 8 | return global; 9 | } 10 | -------------------------------------------------------------------------------- /runtime/global_symbol.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "js_value.hpp" 4 | 5 | class JSValue; 6 | 7 | JSValue create_symbol_global(); 8 | -------------------------------------------------------------------------------- /runtime/js_primitives.cpp: -------------------------------------------------------------------------------- 1 | #include "js_primitives.hpp" 2 | #include "exceptions.hpp" 3 | 4 | JSBase::JSBase() {} 5 | 6 | JSValue JSUndefined::operator==(JSValue &other) { 7 | return JSValue{other.is_undefined()}; 8 | } 9 | 10 | JSValue JSBase::get_property(JSValue key, JSValue parent) { 11 | auto result = this->get_property_from_list(this->properties, key, parent); 12 | if (result.has_value()) { 13 | return result.value(); 14 | } 15 | 16 | return JSValue::with_getter_setter( 17 | JSValue::new_function( 18 | [](JSValue thisArg, std::vector &args) mutable -> JSValue { 19 | return JSValue::undefined(); 20 | }), 21 | JSValue::new_function( 22 | [=, that = this](JSValue thisArg, 23 | std::vector &args) mutable -> JSValue { 24 | JSValue v = JSValue::undefined(); 25 | if (args.size() > 0) { 26 | v = args[0]; 27 | } 28 | that->properties.push_back({key, v}); 29 | return JSValue::undefined(); 30 | })); 31 | } 32 | 33 | std::optional JSBase::get_property_from_list( 34 | const std::vector> &list, JSValue key, 35 | JSValue parent) { 36 | auto obj = std::find_if(list.begin(), list.end(), 37 | [&](const std::pair &item) -> bool { 38 | return (item.first == key).coerce_to_bool(); 39 | }); 40 | if (obj == list.end()) { 41 | return std::nullopt; 42 | } 43 | JSValue v = (*obj).second; 44 | if (v.getter.has_value()) { 45 | *v.value = (*v.getter)(parent).boxed_value(); 46 | } 47 | return std::optional{v}; 48 | } 49 | 50 | JSBool::JSBool(bool v) : JSBase(), internal{v} {}; 51 | 52 | JSNumber::JSNumber(double v) : JSBase(), internal{v} {}; 53 | 54 | JSString::JSString(const char *v) : JSBase(), internal{std::string(v)} {}; 55 | 56 | JSString::JSString(std::string v) : JSBase(), internal{v} {}; 57 | 58 | std::vector> JSArray_prototype{ 59 | {JSValue{"push"}, JSValue::new_function(&JSArray::push_impl)}, 60 | {JSValue{"map"}, JSValue::new_function(&JSArray::map_impl)}, 61 | {JSValue{"filter"}, JSValue::new_function(&JSArray::filter_impl)}, 62 | {JSValue{"reduce"}, JSValue::new_function(&JSArray::reduce_impl)}, 63 | {JSValue{"join"}, JSValue::new_function(&JSArray::join_impl)}, 64 | }; 65 | 66 | JSArray::JSArray() : JSBase(), internal{new std::vector{}} { 67 | for (const auto &entry : JSArray_prototype) { 68 | this->properties.push_back(entry); 69 | } 70 | std::vector *data = &(*this->internal); 71 | auto length_prop = JSValue::with_getter_setter( 72 | JSValue::new_function( 73 | [=](JSValue thisArg, std::vector &args) mutable -> JSValue { 74 | return JSValue{static_cast(data->size())}; 75 | }), 76 | JSValue::new_function( 77 | [=](JSValue thisArg, std::vector &args) mutable -> JSValue { 78 | if (args.size() < 1 || args[0].type() != JSValueType::NUMBER) 79 | return JSValue::undefined(); 80 | JSValue v = args[0]; 81 | data->resize(static_cast(v.coerce_to_double()), 82 | JSValue::undefined()); 83 | return JSValue::undefined(); 84 | })); 85 | this->properties.push_back({JSValue{"length"}, length_prop}); 86 | this->properties.push_back( 87 | {iterator_symbol, JSValue::new_function(&JSArray::iterator_impl)}); 88 | }; 89 | 90 | JSArray::JSArray(std::vector data) : JSArray() { 91 | for (auto v : data) { 92 | this->internal->push_back(v); 93 | } 94 | } 95 | 96 | JSValue JSArray::push_impl(JSValue thisArg, std::vector &args) { 97 | if (thisArg.type() != JSValueType::ARRAY) 98 | js_throw(JSValue{"Called push on non-array"}); 99 | auto arr = std::get(*thisArg.value); 100 | for (auto v : args) { 101 | arr->internal->push_back(v); 102 | } 103 | return JSValue::undefined(); 104 | } 105 | 106 | JSValue JSArray::map_impl(JSValue thisArg, std::vector &args) { 107 | if (thisArg.type() != JSValueType::ARRAY) 108 | js_throw(JSValue{"Called map on non-array"}); 109 | JSValue f = args[0]; 110 | auto arr = std::get(*thisArg.value); 111 | JSArray result_arr{}; 112 | for (int i = 0; i < arr->internal->size(); i++) { 113 | result_arr.internal->push_back( 114 | f({(*arr->internal)[i], JSValue{static_cast(i)}})); 115 | } 116 | return JSValue{result_arr}; 117 | } 118 | 119 | JSValue JSArray::filter_impl(JSValue thisArg, std::vector &args) { 120 | if (thisArg.type() != JSValueType::ARRAY) 121 | js_throw(JSValue{"Called filter on non-array"}); 122 | JSValue f = args[0]; 123 | auto arr = std::get(*thisArg.value); 124 | JSArray result_arr{}; 125 | for (int i = 0; i < arr->internal->size(); i++) { 126 | if (f({(*arr->internal)[i], JSValue{static_cast(i)}}) 127 | .coerce_to_bool()) { 128 | result_arr.internal->push_back((*arr->internal)[i]); 129 | } 130 | } 131 | return JSValue{result_arr}; 132 | } 133 | 134 | JSValue JSArray::reduce_impl(JSValue thisArg, std::vector &args) { 135 | if (thisArg.type() != JSValueType::ARRAY) 136 | js_throw(JSValue{"Called reduce on non-array"}); 137 | auto arr = std::get(*thisArg.value); 138 | 139 | JSValue f = args[0]; 140 | 141 | int i; 142 | JSValue acc; 143 | if (args.size() >= 2 && !args[1].is_undefined()) { 144 | i = 0; 145 | acc = args[1]; 146 | } else if (arr->internal->size() >= 1) { 147 | i = 1; 148 | acc = (*arr->internal)[0]; 149 | } 150 | 151 | for (; i < arr->internal->size(); i++) { 152 | acc = f({acc, (*arr->internal)[i], JSValue{static_cast(i)}}); 153 | } 154 | return acc; 155 | } 156 | 157 | JSValue JSArray::join_impl(JSValue thisArg, std::vector &args) { 158 | if (thisArg.type() != JSValueType::ARRAY) 159 | js_throw(JSValue{"Called join on non-array"}); 160 | 161 | std::string delimiter = ""; 162 | std::string result = ""; 163 | if (args.size() > 0 && args[0].type() == JSValueType::STRING) { 164 | delimiter = args[0].coerce_to_string(); 165 | } 166 | auto arr = std::get(*thisArg.value); 167 | for (auto v : *arr->internal) { 168 | result += v.coerce_to_string() + delimiter; 169 | } 170 | result = result.substr(0, result.size() - delimiter.size()); 171 | return JSValue{result}; 172 | } 173 | 174 | JSValue JSArray::iterator_impl(JSValue thisArg, std::vector &args) { 175 | auto gen = JSValue::new_generator_function( 176 | [=](JSValue thisArg, 177 | std::vector &args) mutable -> JSGeneratorAdapter { 178 | if (thisArg.type() != JSValueType::ARRAY) { 179 | js_throw(JSValue{"Called array iterator with a non-array value"}); 180 | } 181 | auto arr = std::get(*thisArg.value); 182 | for (auto value : *arr->internal) { 183 | co_yield value; 184 | } 185 | co_return; 186 | }); 187 | gen.set_parent(thisArg); 188 | return gen(args); 189 | } 190 | 191 | JSValue JSArray::get_property(const JSValue key, JSValue parent) { 192 | if (key.type() == JSValueType::NUMBER) { 193 | auto idx = static_cast(key.coerce_to_double()); 194 | if (idx >= this->internal->size()) 195 | js_throw(JSValue{"Array access out of bounds"}); 196 | return (*this->internal)[idx]; 197 | } 198 | return JSBase::get_property(key, parent); 199 | } 200 | 201 | JSObject::JSObject() 202 | : JSBase(), internal{new std::vector>{}} {}; 203 | 204 | JSObject::JSObject(std::vector> data) : JSObject() { 205 | *this->internal = data; 206 | }; 207 | 208 | JSValue JSObject::get_property(const JSValue key, JSValue parent) { 209 | auto v = this->get_property_from_list(*this->internal, key, parent); 210 | if (v.has_value()) { 211 | return v.value(); 212 | } 213 | auto proto_v = JSBase::get_property_from_list(this->properties, key, parent); 214 | if (proto_v.has_value()) { 215 | return proto_v.value(); 216 | } 217 | return JSValue::with_getter_setter( 218 | JSValue::new_function( 219 | [](JSValue thisArg, std::vector &args) mutable -> JSValue { 220 | return JSValue::undefined(); 221 | }), 222 | JSValue::new_function( 223 | [=, that = *this](JSValue thisArg, 224 | std::vector &args) mutable -> JSValue { 225 | JSValue v = JSValue::undefined(); 226 | if (args.size() > 0) { 227 | v = args[0]; 228 | } 229 | that.internal->push_back({key, v}); 230 | return JSValue::undefined(); 231 | })); 232 | } 233 | 234 | JSFunction::JSFunction(ExternFunc f) : JSBase(), internal{f} {}; 235 | 236 | JSValue JSFunction::call(JSValue thisArg, std::vector &args) { 237 | return this->internal(thisArg, args); 238 | } 239 | 240 | JSGeneratorAdapter JSGeneratorAdapter::promise_type::get_return_object() { 241 | return {.h = std::experimental::coroutine_handle::from_promise( 242 | *this)}; 243 | } 244 | 245 | std::experimental::suspend_never 246 | JSGeneratorAdapter::promise_type::initial_suspend() { 247 | return {}; 248 | } 249 | 250 | std::experimental::suspend_never 251 | JSGeneratorAdapter::promise_type::final_suspend() noexcept { 252 | return {}; 253 | } 254 | 255 | void JSGeneratorAdapter::promise_type::return_void() noexcept { 256 | this->value = std::nullopt; 257 | } 258 | 259 | void JSGeneratorAdapter::promise_type::unhandled_exception() { 260 | auto ptr = std::current_exception(); 261 | if (ptr) { 262 | std::rethrow_exception(ptr); 263 | } 264 | } 265 | 266 | std::experimental::suspend_always 267 | JSGeneratorAdapter::promise_type::yield_value(JSValue value) { 268 | this->value = 269 | std::optional>{std::make_shared(value)}; 270 | return {}; 271 | } 272 | 273 | JSValue iterator_symbol = JSValue::new_object({}); 274 | 275 | JSIterator::JSIterator() : JSIterator{JSValue::undefined()} {} 276 | 277 | JSIterator::JSIterator(JSValue val) : it{std::make_shared(val)} {} 278 | 279 | JSIterator::JSIterator(JSValue val, JSValue parent) : JSIterator(val) { 280 | this->parent = {std::make_shared(parent)}; 281 | } 282 | 283 | JSIterator JSIterator::end_marker() { 284 | JSIterator it{}; 285 | it.last_value = 286 | std::optional{shared_ptr{new JSValue{JSValue::new_object({ 287 | {JSValue{"value"}, JSValue::undefined()}, 288 | {JSValue{"done"}, JSValue{true}}, 289 | })}}}; 290 | return it; 291 | } 292 | 293 | JSValue JSIterator::operator*() { return this->value()["value"]; } 294 | 295 | JSIterator JSIterator::operator++() { 296 | if (!this->it->is_undefined()) { 297 | if (this->parent.has_value()) { 298 | this->last_value = std::optional{std::make_shared( 299 | (*this->it)["next"].apply(*this->parent.value(), {}))}; 300 | } else { 301 | this->last_value = 302 | std::optional{std::make_shared((*this->it)["next"]({}))}; 303 | } 304 | } 305 | return *this; 306 | } 307 | 308 | bool JSIterator::operator!=(const JSIterator &other) { 309 | if (other.last_value.has_value() != this->last_value.has_value()) { 310 | return true; 311 | } 312 | JSValue left = *this->last_value.value(); 313 | JSValue right = *other.last_value.value(); 314 | bool left_done = left["done"].coerce_to_bool(); 315 | bool right_done = right["done"].coerce_to_bool(); 316 | if (left_done && right_done) { 317 | return false; 318 | } 319 | return left_done != right_done || 320 | (left["value"] != right["value"]).coerce_to_bool(); 321 | } 322 | 323 | JSValue JSIterator::value() { 324 | if (!this->last_value.has_value()) { 325 | ++(*this); 326 | } 327 | return *this->last_value.value(); 328 | } 329 | -------------------------------------------------------------------------------- /runtime/js_primitives.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "js_value.hpp" 11 | 12 | using std::optional; 13 | using std::shared_ptr; 14 | 15 | class JSValue; 16 | 17 | class JSUndefined { 18 | JSValue operator==(JSValue &other); 19 | }; 20 | 21 | class JSBase { 22 | public: 23 | JSBase(); 24 | 25 | virtual JSValue get_property(JSValue key, JSValue parent); 26 | virtual optional 27 | get_property_from_list(const std::vector> &list, 28 | JSValue key, JSValue parent); 29 | 30 | std::vector> properties; 31 | }; 32 | 33 | class JSBool : public JSBase { 34 | public: 35 | JSBool(bool v); 36 | 37 | bool internal; 38 | }; 39 | 40 | class JSNumber : public JSBase { 41 | public: 42 | JSNumber(double v); 43 | double internal; 44 | }; 45 | 46 | class JSString : public JSBase { 47 | public: 48 | JSString(const char *v); 49 | JSString(std::string v); 50 | std::string internal; 51 | }; 52 | 53 | class JSArray : public JSBase { 54 | public: 55 | JSArray(); 56 | JSArray(std::vector data); 57 | 58 | virtual JSValue get_property(JSValue key, JSValue parent); 59 | 60 | shared_ptr> internal; 61 | 62 | static JSValue push_impl(JSValue thisArg, std::vector &args); 63 | static JSValue map_impl(JSValue thisArg, std::vector &args); 64 | static JSValue join_impl(JSValue thisArg, std::vector &args); 65 | static JSValue reduce_impl(JSValue thisArg, std::vector &args); 66 | static JSValue filter_impl(JSValue thisArg, std::vector &args); 67 | static JSValue iterator_impl(JSValue thisArg, std::vector &args); 68 | }; 69 | 70 | class JSObject : public JSBase { 71 | public: 72 | JSObject(); 73 | JSObject(std::vector> data); 74 | 75 | virtual JSValue get_property(JSValue key, JSValue parent); 76 | 77 | shared_ptr>> internal; 78 | }; 79 | 80 | using ExternFunc = std::function &)>; 81 | using ExternFuncPtr = JSValue (*)(JSValue, std::vector &); 82 | class JSFunction : public JSBase { 83 | 84 | public: 85 | JSFunction(ExternFunc f); 86 | ExternFunc internal; 87 | 88 | JSValue call(JSValue thisArg, std::vector &); 89 | }; 90 | 91 | struct JSGeneratorAdapter { 92 | struct promise_type { 93 | JSGeneratorAdapter get_return_object(); 94 | std::experimental::suspend_never initial_suspend(); 95 | std::experimental::suspend_never final_suspend() noexcept; 96 | void return_void() noexcept; 97 | void unhandled_exception(); 98 | 99 | std::experimental::suspend_always yield_value(JSValue value); 100 | 101 | optional> value; 102 | }; 103 | 104 | std::experimental::coroutine_handle h; 105 | }; 106 | 107 | extern JSValue iterator_symbol; 108 | 109 | class JSIterator { 110 | public: 111 | JSIterator(); 112 | JSIterator(JSValue val); 113 | JSIterator(JSValue val, JSValue parent); 114 | static JSIterator end_marker(); 115 | 116 | JSValue operator*(); 117 | JSIterator operator++(); 118 | bool operator!=(const JSIterator &other); 119 | 120 | JSValue value(); 121 | 122 | shared_ptr it; 123 | optional> last_value = std::nullopt; 124 | optional> parent = std::nullopt; 125 | }; 126 | -------------------------------------------------------------------------------- /runtime/js_value.cpp: -------------------------------------------------------------------------------- 1 | #include "js_value.hpp" 2 | #include "exceptions.hpp" 3 | #include 4 | 5 | JSValue::JSValue() 6 | : value{new Box{std::in_place_index, 7 | JSUndefined{}}} {}; 8 | 9 | JSValue::JSValue(bool v) 10 | : value{new Box{std::in_place_index, JSBool{v}}}, 11 | parent_value{} {}; 12 | 13 | JSValue::JSValue(JSBool v) 14 | : value{new Box{std::in_place_index, v}}, 15 | parent_value{} {}; 16 | 17 | JSValue::JSValue(double v) 18 | : value{new Box{std::in_place_index, JSNumber{v}}}, 19 | parent_value{} {}; 20 | 21 | JSValue::JSValue(JSNumber v) 22 | : value{new Box{std::in_place_index, v}}, 23 | parent_value{} {}; 24 | 25 | JSValue::JSValue(const char *v) 26 | : value{new Box{std::in_place_index, JSString{v}}}, 27 | parent_value{} {}; 28 | 29 | JSValue::JSValue(std::string v) 30 | : value{new Box{std::in_place_index, JSString{v}}}, 31 | parent_value{} {}; 32 | 33 | JSValue::JSValue(JSString v) 34 | : value{new Box{std::in_place_index, v}}, 35 | parent_value{} {}; 36 | 37 | JSValue::JSValue(JSFunction v) 38 | : value{new Box{std::in_place_index, v}}, 39 | parent_value{} {}; 40 | 41 | JSValue::JSValue(JSObject v) 42 | : value{new Box{std::in_place_index, 43 | shared_ptr{new JSObject{v}}}}, 44 | parent_value{} {}; 45 | 46 | JSValue::JSValue(JSArray v) 47 | : value{new Box{std::in_place_index, 48 | shared_ptr{new JSArray{v}}}}, 49 | parent_value{} {}; 50 | 51 | JSValue::JSValue(Box v) : value{new Box{v}}, parent_value{} {}; 52 | 53 | JSValue JSValue::undefined() { return JSValue{}; } 54 | 55 | JSValue JSValue::new_object(std::vector> pairs) { 56 | return JSValue{JSObject{pairs}}; 57 | } 58 | 59 | JSValue JSValue::new_array(std::vector values) { 60 | return JSValue{JSArray{values}}; 61 | } 62 | 63 | JSValue JSValue::new_function(ExternFunc f) { return JSValue{JSFunction{f}}; } 64 | 65 | JSValue JSValue::new_generator_function(CoroutineFunc gen_f) { 66 | return JSValue::new_function([=](JSValue thisArg, 67 | std::vector &args) mutable 68 | -> JSValue { 69 | std::shared_ptr>> 71 | corot = 72 | std::make_shared>>(std::nullopt); 74 | return JSValue::iterator_from_next_func(JSValue::new_function( 75 | [corot, gen_f](JSValue thisArg, 76 | std::vector &args) mutable -> JSValue { 77 | if (!corot->has_value()) { 78 | *corot = std::optional{gen_f(thisArg, args).h}; 79 | } else { 80 | corot->value()(); 81 | } 82 | auto v = corot->value().promise().value; 83 | return JSValue::new_object( 84 | {{JSValue{"value"}, 85 | *v.value_or(std::make_shared(JSValue::undefined()))}, 86 | {JSValue{"done"}, JSValue{!v.has_value()}}}); 87 | })); 88 | }); 89 | } 90 | 91 | JSValue &JSValue::operator++() { 92 | if (this->type() != JSValueType::NUMBER) { 93 | js_throw(JSValue{"Can’t ++ something that is not a number"}); 94 | } 95 | this->get_number() = this->get_number() + 1.0; 96 | return *this; 97 | } 98 | 99 | JSValue JSValue::operator++(int) { 100 | if (this->type() != JSValueType::NUMBER) { 101 | js_throw(JSValue{"Can’t ++ something that is not a number"}); 102 | } 103 | JSValue prev{this->get_number()}; 104 | this->get_number() = this->get_number() + 1.0; 105 | return prev; 106 | } 107 | 108 | JSValue &JSValue::operator--() { 109 | if (this->type() != JSValueType::NUMBER) { 110 | js_throw(JSValue{"Can’t -- something that is not a number"}); 111 | } 112 | this->get_number() = this->get_number() - 1.0; 113 | return *this; 114 | } 115 | 116 | JSValue JSValue::operator--(int) { 117 | if (this->type() != JSValueType::NUMBER) { 118 | js_throw(JSValue{"Can’t -- something that is not a number"}); 119 | } 120 | JSValue prev{this->get_number()}; 121 | this->get_number() = this->get_number() - 1.0; 122 | return prev; 123 | } 124 | 125 | JSValue JSValue::operator=(const Box &other) { 126 | if (this->setter.has_value()) { 127 | return (*this->setter)(*this, {other}); 128 | } 129 | *this->value = other; 130 | return other; 131 | } 132 | 133 | JSValue JSValue::operator!() { return JSValue{!this->coerce_to_bool()}; } 134 | 135 | JSValue JSValue::operator==(const JSValue other) const { 136 | if (this->type() == JSValueType::NUMBER) { 137 | return JSValue{std::get(*this->value).internal == 138 | other.coerce_to_double()}; 139 | } 140 | if (this->type() == JSValueType::STRING) { 141 | return JSValue{std::get(*this->value).internal == 142 | other.coerce_to_string()}; 143 | } 144 | if (this->type() == JSValueType::BOOL) { 145 | return JSValue{std::get(*this->value).internal == 146 | other.coerce_to_bool()}; 147 | } 148 | if (this->type() == JSValueType::ARRAY) { 149 | if (other.type() != JSValueType::ARRAY) 150 | return JSValue{false}; 151 | return JSValue{std::get(*this->value).get() == 152 | std::get(*other.value).get()}; 153 | } 154 | if (this->type() == JSValueType::OBJECT) { 155 | if (other.type() != JSValueType::OBJECT) 156 | return JSValue{false}; 157 | return JSValue{std::get(*this->value).get() == 158 | std::get(*other.value).get()}; 159 | } 160 | return JSValue{"Equality not implemented for this type yet"}; 161 | } 162 | 163 | JSValue JSValue::operator<(const JSValue other) { 164 | JSValue v1{this->boxed_value()}; 165 | JSValue v2{other.boxed_value()}; 166 | if (v1.type() == JSValueType::NUMBER) { 167 | return JSValue{std::get(*v1.value).internal < 168 | v2.coerce_to_double()}; 169 | } 170 | return JSValue{false}; 171 | } 172 | 173 | JSValue JSValue::operator&&(const JSValue other) { 174 | if (!this->coerce_to_bool()) 175 | return *this; 176 | return other; 177 | } 178 | 179 | JSValue JSValue::operator||(const JSValue other) { 180 | if (!this->coerce_to_bool()) 181 | return other; 182 | return *this; 183 | } 184 | 185 | JSValue JSValue::operator<=(const JSValue other) { 186 | return *this == other || *this < other; 187 | } 188 | 189 | JSValue JSValue::operator>(const JSValue other) { return !(*this <= other); } 190 | 191 | JSValue JSValue::operator!=(const JSValue other) { return !(*this == other); } 192 | 193 | JSValue JSValue::operator>=(const JSValue other) { return !(*this < other); } 194 | 195 | JSValue JSValue::operator+(JSValue other) { 196 | if (this->type() == JSValueType::NUMBER) { 197 | return JSValue{std::get(*this->value).internal + 198 | other.coerce_to_double()}; 199 | } 200 | if (this->type() == JSValueType::STRING) { 201 | return JSValue{std::get(*this->value).internal + 202 | other.coerce_to_string()}; 203 | } 204 | return JSValue{"Addition not implemented for this type yet"}; 205 | } 206 | 207 | JSValue JSValue::operator*(JSValue other) { 208 | if (this->type() == JSValueType::NUMBER) { 209 | return JSValue{std::get(*this->value).internal * 210 | other.coerce_to_double()}; 211 | } 212 | return JSValue{"Multiplication not implemented for this type yet"}; 213 | } 214 | 215 | JSValue JSValue::operator%(JSValue other) { 216 | if (this->type() == JSValueType::NUMBER) { 217 | return JSValue{static_cast( 218 | static_cast( 219 | std::get(*this->value).internal) % 220 | static_cast(other.coerce_to_double()))}; 221 | } 222 | return JSValue{"Modulo not implemented for this type yet"}; 223 | } 224 | 225 | JSValue JSValue::operator[](const JSValue key) { 226 | return this->get_property(key, *this); 227 | } 228 | 229 | JSValue JSValue::operator[](const char *index) { 230 | return (*this)[JSValue{index}]; 231 | } 232 | 233 | JSValue JSValue::operator[](const size_t index) { 234 | return (*this)[JSValue{static_cast(index)}]; 235 | } 236 | 237 | JSValue JSValue::operator()(std::vector args) { 238 | auto this_arg_ptr = this->parent_value.value_or( 239 | shared_ptr{new JSValue{JSValue::undefined()}}); 240 | return this->apply(*this_arg_ptr, args); 241 | } 242 | 243 | JSIterator JSValue::begin() { 244 | return JSIterator{(*this)[iterator_symbol]({}), *this}; 245 | } 246 | 247 | JSIterator JSValue::end() { return JSIterator::end_marker(); } 248 | 249 | JSValue JSValue::get_property(const JSValue key, JSValue parent) { 250 | JSValue v; 251 | switch (this->type()) { 252 | case JSValueType::UNDEFINED: 253 | js_throw(JSValue{"Can’t read property of undefined"}); 254 | break; 255 | case JSValueType::BOOL: 256 | v = std::get(*this->value).get_property(key, parent); 257 | break; 258 | case JSValueType::NUMBER: 259 | v = std::get(*this->value).get_property(key, parent); 260 | break; 261 | case JSValueType::STRING: 262 | v = std::get(*this->value).get_property(key, parent); 263 | break; 264 | case JSValueType::ARRAY: 265 | v = std::get(*this->value)->get_property(key, parent); 266 | break; 267 | case JSValueType::OBJECT: 268 | v = std::get(*this->value)->get_property(key, parent); 269 | break; 270 | case JSValueType::FUNCTION: 271 | v = std::get(*this->value).get_property(key, parent); 272 | break; 273 | }; 274 | v.set_parent(*this); 275 | return v; 276 | } 277 | 278 | JSValue JSValue::with_getter_setter(JSValue getter, JSValue setter) { 279 | JSValue b{}; 280 | b.getter = std::optional{[=](JSValue v) -> JSValue { 281 | if (getter.type() != JSValueType::FUNCTION) 282 | return JSValue::undefined(); 283 | auto f = std::get(*getter.value); 284 | std::vector params{}; 285 | return f.call(v, params); 286 | }}; 287 | b.setter = std::optional{[=](JSValue v, JSValue new_v) -> JSValue { 288 | if (setter.type() != JSValueType::FUNCTION) 289 | return JSValue::undefined(); 290 | auto f = std::get(*setter.value); 291 | std::vector params{new_v}; 292 | return f.call(v, params); 293 | }}; 294 | return b; 295 | } 296 | 297 | JSValueType JSValue::type() const { 298 | return static_cast(this->value->index()); 299 | } 300 | 301 | bool JSValue::is_undefined() const { 302 | return this->type() == JSValueType::UNDEFINED; 303 | } 304 | 305 | double JSValue::coerce_to_double() const { 306 | switch (this->type()) { 307 | case JSValueType::BOOL: 308 | return std::get(*this->value).internal ? 1 : 0; 309 | case JSValueType::NUMBER: 310 | return std::get(*this->value).internal; 311 | case JSValueType::STRING: 312 | return std::stod(std::get(*this->value).internal); 313 | default: 314 | return NAN; 315 | } 316 | } 317 | 318 | double &JSValue::get_number() { 319 | return std::get(*this->value).internal; 320 | } 321 | 322 | std::string JSValue::coerce_to_string() const { 323 | switch (this->type()) { 324 | case JSValueType::UNDEFINED: 325 | return "undefined"; 326 | case JSValueType::BOOL: 327 | return std::get(*this->value).internal 328 | ? std::string{"true"} 329 | : std::string{"false"}; 330 | case JSValueType::NUMBER: 331 | return std::to_string(std::get(*this->value).internal); 332 | case JSValueType::STRING: 333 | return std::get(*this->value).internal; 334 | case JSValueType::ARRAY: 335 | return "[Array]"; 336 | case JSValueType::OBJECT: 337 | return "[Object object]"; 338 | case JSValueType::FUNCTION: 339 | return ""; 340 | } 341 | return "?"; 342 | } 343 | 344 | bool JSValue::coerce_to_bool() const { 345 | switch (this->type()) { 346 | case JSValueType::UNDEFINED: 347 | return false; 348 | case JSValueType::BOOL: 349 | return std::get(*this->value).internal; 350 | case JSValueType::NUMBER: 351 | return std::get(*this->value).internal > 0; 352 | case JSValueType::STRING: 353 | return std::get(*this->value).internal.length() > 0; 354 | case JSValueType::ARRAY: 355 | return std::get(*this->value)->internal->size() > 0; 356 | case JSValueType::OBJECT: 357 | return true; 358 | case JSValueType::FUNCTION: 359 | return true; 360 | } 361 | return "?"; 362 | } 363 | 364 | JSValue JSValue::apply(JSValue thisArg, std::vector args) { 365 | if (this->type() != JSValueType::FUNCTION) { 366 | js_throw(JSValue{"Calling a non-function"}); 367 | } 368 | JSFunction f = std::get(*this->value); 369 | return f.call(thisArg, args); 370 | } 371 | 372 | void JSValue::set_parent(JSValue parent) { 373 | this->parent_value = std::optional{std::make_shared(parent)}; 374 | } 375 | 376 | JSValue JSValue::get_parent() { 377 | return *this->parent_value.value_or( 378 | std::make_shared(JSValue::undefined())); 379 | } 380 | 381 | const JSValue::Box &JSValue::boxed_value() const { return *this->value; } 382 | 383 | JSValue JSValue::iterator_from_next_func(JSValue next_func) { 384 | auto obj = JSValue::new_object({{JSValue{"next"}, next_func}}); 385 | obj[iterator_symbol] = 386 | JSValue::new_function([=](JSValue thisArg, 387 | std::vector &args) mutable -> JSValue { 388 | return obj; 389 | }).boxed_value(); 390 | return obj; 391 | }; 392 | -------------------------------------------------------------------------------- /runtime/js_value.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "js_primitives.hpp" 8 | 9 | using std::optional; 10 | using std::shared_ptr; 11 | using std::unique_ptr; 12 | 13 | class JSUndefined; 14 | class JSBool; 15 | class JSNumber; 16 | class JSString; 17 | class JSArray; 18 | class JSObject; 19 | class JSFunction; 20 | class JSIterator; 21 | class JSGeneratorAdapter; 22 | class JSValue; 23 | 24 | // Idk what C++ wants from me... This type alias is defined in 25 | // `js_primitives.hpp` but the cyclic includes seem to make it impossible to see 26 | // that here. 27 | using ExternFunc = std::function &)>; 28 | using CoroutineFunc = 29 | std::function &)>; 30 | 31 | enum JSValueType : char { 32 | UNDEFINED, 33 | BOOL, 34 | NUMBER, 35 | STRING, 36 | ARRAY, 37 | OBJECT, 38 | FUNCTION 39 | }; 40 | 41 | class JSValue { 42 | using Box = std::variant, std::shared_ptr, 44 | JSFunction, std::shared_ptr>; 45 | 46 | using Getter = std::function; 47 | using Setter = std::function; 48 | 49 | public: 50 | JSValue(); 51 | JSValue(bool v); 52 | JSValue(JSBool v); 53 | JSValue(double v); 54 | JSValue(JSNumber v); 55 | JSValue(const char *v); 56 | JSValue(std::string v); 57 | JSValue(JSString v); 58 | JSValue(JSFunction v); 59 | JSValue(JSObject v); 60 | JSValue(JSArray v); 61 | JSValue(Box v); 62 | 63 | JSValue operator=(const Box &other); 64 | JSValue &operator++(); // Prefix 65 | JSValue operator++(int); // Postfix 66 | JSValue &operator--(); // Prefix 67 | JSValue operator--(int); // Postfix 68 | JSValue operator==(const JSValue other) const; 69 | JSValue operator!(); 70 | JSValue operator<(const JSValue other); 71 | JSValue operator<=(const JSValue other); 72 | JSValue operator>(const JSValue other); 73 | JSValue operator!=(const JSValue other); 74 | JSValue operator>=(const JSValue other); 75 | JSValue operator&&(const JSValue other); 76 | JSValue operator||(const JSValue other); 77 | JSValue operator+(JSValue other); 78 | JSValue operator*(JSValue other); 79 | JSValue operator%(JSValue other); 80 | JSValue operator[](const JSValue index); 81 | JSValue operator[](const char *index); 82 | JSValue operator[](const size_t index); 83 | JSValue operator()(std::vector args); 84 | 85 | JSIterator begin(); 86 | JSIterator end(); 87 | 88 | static JSValue new_object(std::vector>); 89 | static JSValue new_array(std::vector); 90 | static JSValue new_function(ExternFunc f); 91 | static JSValue new_generator_function(CoroutineFunc gen_f); 92 | static JSValue undefined(); 93 | static JSValue iterator_from_next_func(JSValue next_func); 94 | static JSValue with_getter_setter(JSValue getter, JSValue setter); 95 | 96 | JSValue get_property(const JSValue key, JSValue parent); 97 | JSValue apply(JSValue thisArg, std::vector args); 98 | 99 | JSValueType type() const; 100 | double coerce_to_double() const; 101 | std::string coerce_to_string() const; 102 | bool coerce_to_bool() const; 103 | 104 | bool is_undefined() const; 105 | double &get_number(); 106 | void set_parent(JSValue parent_value); 107 | JSValue get_parent(); 108 | const Box &boxed_value() const; 109 | 110 | shared_ptr value; 111 | optional> parent_value; 112 | 113 | std::optional getter = std::nullopt; 114 | std::optional setter = std::nullopt; 115 | }; 116 | -------------------------------------------------------------------------------- /src/command_utils.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Result, Write}; 2 | use std::process::{Command, ExitStatus, Stdio}; 3 | 4 | pub fn pipe_through_shell>( 5 | command: &str, 6 | args: &[T], 7 | input: &[u8], 8 | ) -> Result<(ExitStatus, Vec, Vec)> { 9 | let mut child = Command::new(command) 10 | .stdin(Stdio::piped()) 11 | .stdout(Stdio::piped()) 12 | .stderr(Stdio::piped()) 13 | .args(args) 14 | .spawn() 15 | .expect(format!("Couldn’t spawn {}", command).as_str()); 16 | child 17 | .stdin 18 | .as_ref() 19 | .expect(format!("Couldn’t claim {}’s stdin", command).as_str()) 20 | .write_all(input) 21 | .expect(format!("Couldn’t write to {}’s stdin", command).as_str()); 22 | let exit_status = child.wait()?; 23 | let mut stdout: Vec = vec![]; 24 | child.stdout.take().unwrap().read_to_end(&mut stdout)?; 25 | let mut stderr: Vec = vec![]; 26 | child.stderr.take().unwrap().read_to_end(&mut stderr)?; 27 | Ok((exit_status, stdout, stderr)) 28 | } 29 | -------------------------------------------------------------------------------- /src/globals/io.rs: -------------------------------------------------------------------------------- 1 | use crate::globals::Global; 2 | 3 | pub fn io_global() -> Global { 4 | Global { 5 | name: "IO".into(), 6 | additional_headers: Some(vec!["runtime/global_io.hpp".into()]), 7 | init: None, 8 | factory: "create_IO_global()".into(), 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/globals/json.rs: -------------------------------------------------------------------------------- 1 | use crate::globals::Global; 2 | 3 | pub fn json_global() -> Global { 4 | Global { 5 | name: "JSON".into(), 6 | additional_headers: Some(vec!["runtime/global_json.hpp".into()]), 7 | init: None, 8 | factory: "create_JSON_global()".into(), 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/globals/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod io; 2 | pub mod json; 3 | pub mod symbol; 4 | 5 | pub struct Global { 6 | pub name: String, 7 | pub additional_headers: Option>, 8 | pub init: Option, 9 | pub factory: String, 10 | } 11 | -------------------------------------------------------------------------------- /src/globals/symbol.rs: -------------------------------------------------------------------------------- 1 | use crate::globals::Global; 2 | 3 | pub fn symbol_global() -> Global { 4 | Global { 5 | name: "Symbol".into(), 6 | additional_headers: Some(vec!["runtime/global_symbol.hpp".into()]), 7 | init: None, 8 | factory: "create_symbol_global()".into(), 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{Read, Write}, 4 | process::{Command, Stdio}, 5 | }; 6 | 7 | use anyhow::{anyhow, Result}; 8 | use clap::Parser; 9 | use swc_common::BytePos; 10 | use swc_ecma_parser::{lexer::Lexer, EsConfig, Parser as ESParser, StringInput, Syntax}; 11 | 12 | mod command_utils; 13 | mod globals; 14 | mod transpiler; 15 | 16 | #[cfg(test)] 17 | mod test; 18 | 19 | #[derive(Parser)] 20 | #[clap(author, version, about)] 21 | struct Args { 22 | /// Path to clang++ 23 | #[clap(long = "clang-path", default_value = "clang++", value_parser)] 24 | clang_path: String, 25 | 26 | /// Emit cpp code to stdout rather than compiling it 27 | #[clap(long = "emit-cpp", default_value_t = false, value_parser)] 28 | emit_cpp: bool, 29 | 30 | /// Target WebAssembly 31 | #[clap(long = "wasm", default_value_t = false, value_parser)] 32 | wasm: bool, 33 | 34 | /// Extra flags to path to clang++ 35 | extra_flags: Vec, 36 | } 37 | 38 | fn js_to_cpp>(mut transpiler: transpiler::Transpiler, input: T) -> Result { 39 | let syntax = Syntax::Es(EsConfig::default()); 40 | let lexer = Lexer::new( 41 | syntax, 42 | swc_ecma_visit::swc_ecma_ast::EsVersion::Es2022, 43 | StringInput::new( 44 | input.as_ref(), 45 | swc_common::BytePos(0), 46 | BytePos(input.as_ref().as_bytes().len().try_into().unwrap()), 47 | ), 48 | None, 49 | ); 50 | let mut parser = ESParser::new_from(lexer); 51 | let module = parser 52 | .parse_module() 53 | .map_err(|err| anyhow!(format!("{:?}", err)))?; 54 | 55 | transpiler.globals.push(globals::io::io_global()); 56 | transpiler.globals.push(globals::json::json_global()); 57 | transpiler.globals.push(globals::symbol::symbol_global()); 58 | transpiler.transpile_module(&module) 59 | } 60 | 61 | fn cpp_to_binary( 62 | code: String, 63 | outputname: String, 64 | clang_path: String, 65 | flags: &[String], 66 | ) -> Result<()> { 67 | let cpp_file_name = format!("./{}.cpp", outputname); 68 | let mut tempfile = File::create(&cpp_file_name)?; 69 | tempfile.write_all(code.as_bytes())?; 70 | drop(tempfile); 71 | 72 | let args = flags 73 | .into_iter() 74 | .map(|i| i.as_ref()) 75 | .chain( 76 | [ 77 | "--std=c++20", 78 | "-o", 79 | outputname.as_ref(), 80 | cpp_file_name.as_ref(), 81 | "runtime/global_json.cpp", 82 | "runtime/global_symbol.cpp", 83 | "runtime/global_io.cpp", 84 | "runtime/js_primitives.cpp", 85 | "runtime/js_value.cpp", 86 | "runtime/exceptions.cpp", 87 | ] 88 | .into_iter(), 89 | ) 90 | .collect::>(); 91 | 92 | let mut child = Command::new(&clang_path) 93 | .stdin(Stdio::inherit()) 94 | .stdout(Stdio::inherit()) 95 | .stderr(Stdio::inherit()) 96 | .args(args) 97 | .spawn()?; 98 | 99 | child.wait()?; 100 | std::fs::remove_file(cpp_file_name)?; 101 | Ok(()) 102 | } 103 | 104 | fn main() -> Result<()> { 105 | let args = Args::parse(); 106 | 107 | let mut input: String = String::new(); 108 | std::io::stdin().read_to_string(&mut input)?; 109 | 110 | let mut transpiler = transpiler::Transpiler::new(); 111 | transpiler.feature_exceptions = !args.wasm; 112 | let cpp_code = js_to_cpp(transpiler, &input)?; 113 | 114 | if args.emit_cpp { 115 | let (_status, stdout, _stderr) = 116 | command_utils::pipe_through_shell::("clang-format", &[], cpp_code.as_bytes())?; 117 | println!("{}", String::from_utf8(stdout)?); 118 | } else { 119 | let mut flags = args.extra_flags.clone(); 120 | let mut extension = "".to_string(); 121 | if args.wasm { 122 | flags.push("-fno-exceptions".to_string()); 123 | flags.push("--target=wasm32-wasi".to_string()); 124 | if let Ok(wasi_sdk_prefix) = std::env::var("WASI_SDK_PREFIX") { 125 | flags.push(format!("--sysroot={}/share/wasi-sysroot", wasi_sdk_prefix)); 126 | } 127 | extension = ".wasm".to_string(); 128 | } else { 129 | flags.push("-DFEATURE_EXCEPTIONS".to_string()); 130 | } 131 | cpp_to_binary( 132 | cpp_code, 133 | format!("output{}", extension), 134 | args.clang_path, 135 | &flags, 136 | )?; 137 | } 138 | Ok(()) 139 | } 140 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::transpiler::Transpiler; 2 | 3 | use super::*; 4 | use anyhow::Result; 5 | use uuid::Uuid; 6 | 7 | #[test] 8 | fn increment_postfix() -> Result<()> { 9 | let output = compile_and_run( 10 | r#" 11 | let a = 1; 12 | let b = a++; 13 | IO.write_to_stdout((a+b) < 4 ? "y" : "n"); 14 | "#, 15 | )?; 16 | assert_eq!(output, "y"); 17 | Ok(()) 18 | } 19 | 20 | #[test] 21 | fn increment_prefix() -> Result<()> { 22 | let output = compile_and_run( 23 | r#" 24 | let a = 1; 25 | let b = ++a; 26 | IO.write_to_stdout((a+b) >= 4 ? "y" : "n"); 27 | "#, 28 | )?; 29 | assert_eq!(output, "y"); 30 | Ok(()) 31 | } 32 | 33 | #[test] 34 | fn basic_program() -> Result<()> { 35 | let output = compile_and_run( 36 | r#" 37 | IO.write_to_stdout("hello"); 38 | "#, 39 | )?; 40 | assert_eq!(output, "hello"); 41 | Ok(()) 42 | } 43 | 44 | #[test] 45 | fn variable() -> Result<()> { 46 | let output = compile_and_run( 47 | r#" 48 | let a = "hello"; 49 | IO.write_to_stdout(a); 50 | "#, 51 | )?; 52 | assert_eq!(output, "hello"); 53 | Ok(()) 54 | } 55 | 56 | #[test] 57 | fn copy_behavior_string() -> Result<()> { 58 | let output = compile_and_run( 59 | r#" 60 | let a = "hello"; 61 | let b = a; 62 | b = b + "!"; 63 | IO.write_to_stdout(a + b); 64 | "#, 65 | )?; 66 | assert_eq!(output, "hellohello!"); 67 | Ok(()) 68 | } 69 | 70 | #[test] 71 | fn copy_behavior_number() -> Result<()> { 72 | let output = compile_and_run( 73 | r#" 74 | let a = 1.0; 75 | let b = a; 76 | b = 3; 77 | IO.write_to_stdout(((a + b) == 4) ? "y" : "n"); 78 | "#, 79 | )?; 80 | assert_eq!(output, "y"); 81 | Ok(()) 82 | } 83 | 84 | #[test] 85 | fn copy_behavior_functions() -> Result<()> { 86 | let output = compile_and_run( 87 | r#" 88 | let a = "x"; 89 | function f(v) { 90 | v = v + "!"; 91 | } 92 | f(a); 93 | IO.write_to_stdout(a); 94 | "#, 95 | )?; 96 | assert_eq!(output, "x"); 97 | Ok(()) 98 | } 99 | 100 | #[test] 101 | fn variable_assign() -> Result<()> { 102 | let output = compile_and_run( 103 | r#" 104 | let a = "hi"; 105 | a = "hello"; 106 | IO.write_to_stdout(a); 107 | "#, 108 | )?; 109 | assert_eq!(output, "hello"); 110 | Ok(()) 111 | } 112 | 113 | #[test] 114 | fn ternary() -> Result<()> { 115 | let output = compile_and_run( 116 | r#" 117 | IO.write_to_stdout(2 == 3 ? "yes" : "no"); 118 | "#, 119 | )?; 120 | assert_eq!(output, "no"); 121 | Ok(()) 122 | } 123 | 124 | #[test] 125 | fn compare() -> Result<()> { 126 | let output = compile_and_run( 127 | r#" 128 | let b = (2 == 2) && (3 != 4) && (1 < 2) && (2<=2) && (3>=3) && (4 > 3); 129 | IO.write_to_stdout(b ? "yes" : "no"); 130 | "#, 131 | )?; 132 | assert_eq!(output, "yes"); 133 | Ok(()) 134 | } 135 | 136 | #[test] 137 | fn arrow_func() -> Result<()> { 138 | let output = compile_and_run( 139 | r#" 140 | IO.write_to_stdout("" + (() => "test")()); 141 | "#, 142 | )?; 143 | assert!(output.starts_with("test")); 144 | Ok(()) 145 | } 146 | 147 | #[test] 148 | fn arrow_func_with_body() -> Result<()> { 149 | let output = compile_and_run( 150 | r#" 151 | IO.write_to_stdout("" + (() => { 1 + 1; return "test";})()); 152 | "#, 153 | )?; 154 | assert!(output.starts_with("test")); 155 | Ok(()) 156 | } 157 | 158 | #[test] 159 | fn closure_simple() -> Result<()> { 160 | let output = compile_and_run( 161 | r#" 162 | let x = "wrong"; 163 | function a() { 164 | x = "hi"; 165 | } 166 | 167 | a(); 168 | IO.write_to_stdout(x); 169 | "#, 170 | )?; 171 | assert_eq!(output, "hi"); 172 | Ok(()) 173 | } 174 | 175 | #[test] 176 | fn closure_obj() -> Result<()> { 177 | let output = compile_and_run( 178 | r#" 179 | let x = {value: "wrong"}; 180 | function a() { 181 | x.value = "hi"; 182 | } 183 | 184 | a(); 185 | IO.write_to_stdout(x.value); 186 | "#, 187 | )?; 188 | assert!(output.starts_with("hi")); 189 | Ok(()) 190 | } 191 | 192 | #[test] 193 | fn func_decl() -> Result<()> { 194 | let output = compile_and_run( 195 | r#" 196 | function a() { 197 | return "test"; 198 | } 199 | 200 | IO.write_to_stdout("" + a()); 201 | "#, 202 | )?; 203 | assert!(output.starts_with("test")); 204 | Ok(()) 205 | } 206 | 207 | #[test] 208 | fn full_func() -> Result<()> { 209 | let output = compile_and_run( 210 | r#" 211 | IO.write_to_stdout("" + (function () { return "test";})()); 212 | "#, 213 | )?; 214 | assert!(output.starts_with("test")); 215 | Ok(()) 216 | } 217 | 218 | #[test] 219 | fn if_else() -> Result<()> { 220 | let output = compile_and_run( 221 | r#" 222 | let a; 223 | if(1 == 1) { 224 | a = "y"; 225 | } else { 226 | a = "n"; 227 | } 228 | let b; 229 | if(1 == 2) { 230 | b = "y"; 231 | } else { 232 | b = "n"; 233 | } 234 | IO.write_to_stdout(a + b); 235 | "#, 236 | )?; 237 | assert_eq!(output, "yn"); 238 | Ok(()) 239 | } 240 | #[test] 241 | fn number_coalesc() -> Result<()> { 242 | let output = compile_and_run( 243 | r#" 244 | IO.write_to_stdout("" + 123); 245 | "#, 246 | )?; 247 | assert!(output.starts_with("123.")); 248 | Ok(()) 249 | } 250 | 251 | #[test] 252 | fn array_literals() -> Result<()> { 253 | let output = compile_and_run( 254 | r#" 255 | let v = ["a", "b", "c"] 256 | IO.write_to_stdout(v.join(",")); 257 | "#, 258 | )?; 259 | assert_eq!(output, "a,b,c"); 260 | Ok(()) 261 | } 262 | 263 | #[test] 264 | fn array_reference() -> Result<()> { 265 | let output = compile_and_run( 266 | r#" 267 | let v = ["a", "b", "c"] 268 | let x = v; 269 | x.push("d"); 270 | IO.write_to_stdout(v.join(",")); 271 | "#, 272 | )?; 273 | assert_eq!(output, "a,b,c,d"); 274 | Ok(()) 275 | } 276 | 277 | #[test] 278 | fn array_access() -> Result<()> { 279 | let output = compile_and_run( 280 | r#" 281 | let v = ["a", "b"]; 282 | IO.write_to_stdout(v[0] + v[1]); 283 | "#, 284 | )?; 285 | assert_eq!(output, "ab"); 286 | Ok(()) 287 | } 288 | 289 | #[test] 290 | fn array_push() -> Result<()> { 291 | let output = compile_and_run( 292 | r#" 293 | let v = ["a", "b"]; 294 | v.push("c"); 295 | IO.write_to_stdout(v.join(",")); 296 | "#, 297 | )?; 298 | assert_eq!(output, "a,b,c"); 299 | Ok(()) 300 | } 301 | 302 | #[test] 303 | fn array_map() -> Result<()> { 304 | let output = compile_and_run( 305 | r#" 306 | let v = ["a", "b", "c"]; 307 | IO.write_to_stdout(v.map(v => v + "!").join(",")); 308 | "#, 309 | )?; 310 | assert_eq!(output, "a!,b!,c!"); 311 | Ok(()) 312 | } 313 | 314 | #[test] 315 | fn array_filter() -> Result<()> { 316 | let output = compile_and_run( 317 | r#" 318 | let v = [1, 2, 3, 4, 5]; 319 | IO.write_to_stdout(v.filter(v => v % 2 == 0).length == 2 ? "yes" : "no"); 320 | "#, 321 | )?; 322 | assert_eq!(output, "yes"); 323 | Ok(()) 324 | } 325 | 326 | #[test] 327 | fn array_reduce() -> Result<()> { 328 | let output = compile_and_run( 329 | r#" 330 | let v = ["a", "b", "c"].reduce((acc, c) => acc + c, "X"); 331 | let v2 = ["a", "b", "c"].reduce((acc, c) => acc + c); 332 | IO.write_to_stdout(v + v2); 333 | "#, 334 | )?; 335 | assert_eq!(output, "Xabcabc"); 336 | Ok(()) 337 | } 338 | 339 | #[test] 340 | fn array_set_length() -> Result<()> { 341 | let output = compile_and_run( 342 | r#" 343 | let v = ["a", "b", "c"]; 344 | v.length = 2; 345 | IO.write_to_stdout(v.join(",")); 346 | "#, 347 | )?; 348 | assert_eq!(output, "a,b"); 349 | Ok(()) 350 | } 351 | 352 | #[test] 353 | fn array_length() -> Result<()> { 354 | let output = compile_and_run( 355 | r#" 356 | let v = ["a", "b", "c"]; 357 | IO.write_to_stdout(v.length > 2 ? "yes" : "no"); 358 | "#, 359 | )?; 360 | assert_eq!(output, "yes"); 361 | Ok(()) 362 | } 363 | 364 | #[test] 365 | fn object_lit() -> Result<()> { 366 | let output = compile_and_run( 367 | r#" 368 | let v = {a: "v"}; 369 | IO.write_to_stdout(v.a); 370 | "#, 371 | )?; 372 | assert_eq!(output, "v"); 373 | Ok(()) 374 | } 375 | 376 | #[test] 377 | fn object_func() -> Result<()> { 378 | let output = compile_and_run( 379 | r#" 380 | let v = {a: () => "hi"}; 381 | IO.write_to_stdout(v.a()); 382 | "#, 383 | )?; 384 | assert_eq!(output, "hi"); 385 | Ok(()) 386 | } 387 | 388 | #[test] 389 | fn object_func_prop() -> Result<()> { 390 | let output = compile_and_run( 391 | r#" 392 | let v = {a() { return "hi"; }}; 393 | IO.write_to_stdout(v.a()); 394 | "#, 395 | )?; 396 | assert_eq!(output, "hi"); 397 | Ok(()) 398 | } 399 | 400 | #[test] 401 | fn object_func_this() -> Result<()> { 402 | let output = compile_and_run( 403 | r#" 404 | let v = {marker: "flag", a: function() { return this.marker; }}; 405 | IO.write_to_stdout(v.a()); 406 | "#, 407 | )?; 408 | assert_eq!(output, "flag"); 409 | Ok(()) 410 | } 411 | 412 | #[test] 413 | fn object_assign() -> Result<()> { 414 | let output = compile_and_run( 415 | r#" 416 | let v = {marker: "flag"}; 417 | v.marker = "hi"; 418 | IO.write_to_stdout(v.marker); 419 | "#, 420 | )?; 421 | assert_eq!(output, "hi"); 422 | Ok(()) 423 | } 424 | 425 | #[test] 426 | fn object_assign2() -> Result<()> { 427 | let output = compile_and_run( 428 | r#" 429 | let v = {}; 430 | v["marker"] = "hi"; 431 | IO.write_to_stdout(v.marker); 432 | "#, 433 | )?; 434 | assert_eq!(output, "hi"); 435 | Ok(()) 436 | } 437 | 438 | #[test] 439 | fn object_shorthand() -> Result<()> { 440 | let output = compile_and_run( 441 | r#" 442 | let a = "hi"; 443 | let v = {a}; 444 | IO.write_to_stdout(v.a); 445 | "#, 446 | )?; 447 | assert_eq!(output, "hi"); 448 | Ok(()) 449 | } 450 | 451 | #[test] 452 | fn object_getter() -> Result<()> { 453 | let output = compile_and_run( 454 | r#" 455 | let state = "hi"; 456 | let v = { 457 | get prop() { 458 | return state; 459 | }, 460 | }; 461 | IO.write_to_stdout(v.prop); 462 | "#, 463 | )?; 464 | assert_eq!(output, "hi"); 465 | Ok(()) 466 | } 467 | 468 | #[test] 469 | fn object_setter() -> Result<()> { 470 | let output = compile_and_run( 471 | r#" 472 | let state = {v: "test"}; 473 | let v = { 474 | set prop(v) { 475 | state.v = v; 476 | }, 477 | }; 478 | v.prop = "hi"; 479 | IO.write_to_stdout(state.v); 480 | "#, 481 | )?; 482 | assert_eq!(output, "hi"); 483 | Ok(()) 484 | } 485 | 486 | #[test] 487 | fn object_getter_this() -> Result<()> { 488 | let output = compile_and_run( 489 | r#" 490 | let v = { 491 | state: "hi", 492 | get prop() { 493 | return this.state; 494 | }, 495 | }; 496 | IO.write_to_stdout(v.prop); 497 | "#, 498 | )?; 499 | assert_eq!(output, "hi"); 500 | Ok(()) 501 | } 502 | 503 | #[test] 504 | fn object_equality() -> Result<()> { 505 | let output = compile_and_run( 506 | r#" 507 | let v = {}; 508 | let v1 = v; 509 | let v2 = {}; 510 | let c = 0; 511 | if(v == v1) { 512 | c = c+1; 513 | } 514 | if(v != v2) { 515 | c = c+2; 516 | } 517 | 518 | IO.write_to_stdout("" + c); 519 | "#, 520 | )?; 521 | assert_eq!(&output[0..2], "3."); 522 | Ok(()) 523 | } 524 | 525 | #[test] 526 | fn object_computed() -> Result<()> { 527 | let output = compile_and_run( 528 | r#" 529 | let k = "abc"; 530 | let v = { 531 | [k]: "hi", 532 | ["x"+"y"]: "hi2" 533 | }; 534 | IO.write_to_stdout(v.abc + v.xy); 535 | "#, 536 | )?; 537 | assert_eq!(output, "hihi2"); 538 | Ok(()) 539 | } 540 | 541 | #[test] 542 | fn json_stringify_array() -> Result<()> { 543 | let output = compile_and_run( 544 | r#" 545 | let v = {x: []}; 546 | IO.write_to_stdout(JSON.stringify(v)); 547 | "#, 548 | )?; 549 | assert_eq!(output, r#"{"x":[]}"#); 550 | Ok(()) 551 | } 552 | 553 | #[test] 554 | fn json_parse_string_escapes() -> Result<()> { 555 | let output = compile_and_run( 556 | r#" 557 | let v = JSON.parse("\"x\n\""); 558 | IO.write_to_stdout(v); 559 | "#, 560 | )?; 561 | assert_eq!(output, "x\n"); 562 | Ok(()) 563 | } 564 | 565 | #[test] 566 | fn for_loop() -> Result<()> { 567 | let output = compile_and_run( 568 | r#" 569 | let v = []; 570 | for(let i = 0; i < 4; i++) { 571 | v.push(i) 572 | } 573 | IO.write_to_stdout(v.length == 4 ? "y" : "n"); 574 | "#, 575 | )?; 576 | assert_eq!(output, "y"); 577 | Ok(()) 578 | } 579 | 580 | #[test] 581 | fn while_loop() -> Result<()> { 582 | let output = compile_and_run( 583 | r#" 584 | let v = []; 585 | let i = 0; 586 | while(i < 4) { 587 | v.push("a") 588 | i = i + 1; 589 | } 590 | IO.write_to_stdout(v.join("")); 591 | "#, 592 | )?; 593 | assert_eq!(output, "aaaa"); 594 | Ok(()) 595 | } 596 | 597 | #[test] 598 | fn while_loop_break() -> Result<()> { 599 | let output = compile_and_run( 600 | r#" 601 | let v = []; 602 | let i = 0; 603 | while(true) { 604 | v.push("a") 605 | i = i + 1; 606 | if(i >= 4) break; 607 | } 608 | IO.write_to_stdout(v.join("")); 609 | "#, 610 | )?; 611 | assert_eq!(output, "aaaa"); 612 | Ok(()) 613 | } 614 | 615 | #[test] 616 | fn iterator() -> Result<()> { 617 | let output = compile_and_run( 618 | r#" 619 | let it = { 620 | i: 0, 621 | next() { 622 | if(this.i > 4) { 623 | return {done: true}; 624 | } 625 | return { 626 | value: this.i++, 627 | done: false 628 | }; 629 | }, 630 | [Symbol.iterator]() { 631 | return this; 632 | } 633 | }; 634 | let arr = []; 635 | for(let v of it) { 636 | arr.push(v) 637 | } 638 | let sum = arr.reduce((sum, c) => sum +c, 0); 639 | IO.write_to_stdout(sum == 10 ? "y" : "n"); 640 | "#, 641 | )?; 642 | assert_eq!(output, "y"); 643 | Ok(()) 644 | } 645 | 646 | #[test] 647 | fn iterator_array() -> Result<()> { 648 | let output = compile_and_run( 649 | r#" 650 | let arr = []; 651 | for(let v of [1,2,3,4]) { 652 | arr.push(v) 653 | } 654 | let sum = arr.reduce((sum, c) => sum +c, 0); 655 | IO.write_to_stdout(sum == 10 ? "y" : "n"); 656 | "#, 657 | )?; 658 | assert_eq!(output, "y"); 659 | Ok(()) 660 | } 661 | 662 | #[test] 663 | fn generator_iterator_protocol() -> Result<()> { 664 | let output = compile_and_run( 665 | r#" 666 | function* gen() { 667 | yield "hi"; 668 | return; 669 | } 670 | let it = gen(); 671 | IO.write_to_stdout(it.next().value); 672 | "#, 673 | )?; 674 | assert_eq!(output, "hi"); 675 | Ok(()) 676 | } 677 | 678 | #[test] 679 | fn generator_iterator_protocol_indirect() -> Result<()> { 680 | let output = compile_and_run( 681 | r#" 682 | function* gen() { 683 | yield "hi"; 684 | return; 685 | } 686 | let it = gen()[Symbol.iterator](); 687 | IO.write_to_stdout(it.next().value); 688 | "#, 689 | )?; 690 | assert_eq!(output, "hi"); 691 | Ok(()) 692 | } 693 | 694 | #[test] 695 | fn generator() -> Result<()> { 696 | let output = compile_and_run( 697 | r#" 698 | function* gen() { 699 | yield 1; 700 | yield 2; 701 | yield 3; 702 | yield 4; 703 | return; 704 | } 705 | let arr = []; 706 | for(let v of gen()) { 707 | arr.push(v) 708 | } 709 | let sum = arr.reduce((sum, c) => sum +c, 0); 710 | IO.write_to_stdout(sum == 10 ? "y" : "n"); 711 | "#, 712 | )?; 713 | assert_eq!(output, "y"); 714 | Ok(()) 715 | } 716 | 717 | #[test] 718 | fn generator_delegate_builtin() -> Result<()> { 719 | let output = compile_and_run( 720 | r#" 721 | function* gen() { 722 | yield* [1, 2]; 723 | yield* [3, 4]; 724 | } 725 | let arr = []; 726 | for(let v of gen()) { 727 | arr.push(v) 728 | } 729 | let sum = arr.reduce((sum, c) => sum +c, 0); 730 | IO.write_to_stdout(sum == 10 ? "y" : "n"); 731 | "#, 732 | )?; 733 | assert_eq!(output, "y"); 734 | Ok(()) 735 | } 736 | 737 | #[test] 738 | fn generator_delegate() -> Result<()> { 739 | let output = compile_and_run( 740 | r#" 741 | function* gen2() { 742 | yield 1; 743 | yield 2; 744 | yield 3; 745 | yield 4; 746 | return; 747 | } 748 | function* gen() { 749 | yield* gen2(); 750 | } 751 | let arr = []; 752 | for(let v of gen()) { 753 | arr.push(v) 754 | } 755 | let sum = arr.reduce((sum, c) => sum +c, 0); 756 | IO.write_to_stdout(sum == 10 ? "y" : "n"); 757 | "#, 758 | )?; 759 | assert_eq!(output, "y"); 760 | Ok(()) 761 | } 762 | 763 | #[test] 764 | fn generator_exception() -> Result<()> { 765 | let output = compile_and_run( 766 | r#" 767 | function* gen() { 768 | throw "omg"; 769 | } 770 | try { 771 | for(let v of gen()) {} 772 | } catch(e) { 773 | IO.write_to_stdout(e); 774 | } 775 | "#, 776 | )?; 777 | assert_eq!(output, "omg"); 778 | Ok(()) 779 | } 780 | 781 | #[test] 782 | fn exceptions() -> Result<()> { 783 | let output = compile_and_run( 784 | r#" 785 | try { 786 | throw "y"; 787 | } catch(e) { 788 | IO.write_to_stdout(e); 789 | } 790 | "#, 791 | )?; 792 | assert_eq!(output, "y"); 793 | Ok(()) 794 | } 795 | 796 | fn compile_and_run>(code: T) -> Result { 797 | let name = Uuid::new_v4().to_string(); 798 | let transpiler = Transpiler::new(); 799 | let cpp = js_to_cpp(transpiler, code)?; 800 | cpp_to_binary( 801 | cpp, 802 | name.clone(), 803 | "clang++".to_string(), 804 | &Vec::::new(), 805 | )?; 806 | let child = Command::new(format!("./{}", &name)) 807 | .stdout(Stdio::piped()) 808 | .spawn()?; 809 | let output = child.wait_with_output()?; 810 | std::fs::remove_file(&name)?; 811 | if output.stderr.len() > 0 { 812 | return Err(anyhow!( 813 | "Program printed to stderr: {}", 814 | String::from_utf8(output.stderr)? 815 | )); 816 | } 817 | Ok(String::from_utf8(output.stdout)?) 818 | } 819 | -------------------------------------------------------------------------------- /src/transpiler.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use swc_ecma_ast::*; 5 | 6 | pub struct Transpiler { 7 | pub globals: Vec, 8 | pub feature_exceptions: bool, 9 | is_generator: bool, 10 | } 11 | 12 | impl Transpiler { 13 | pub fn new() -> Transpiler { 14 | Transpiler { 15 | globals: vec![], 16 | is_generator: false, 17 | feature_exceptions: true, 18 | } 19 | } 20 | 21 | pub fn transpile_module(&mut self, module: &Module) -> Result { 22 | let additional_headers: HashSet = self 23 | .globals 24 | .iter() 25 | .flat_map(|global| { 26 | global 27 | .additional_headers 28 | .clone() 29 | .unwrap_or(vec![]) 30 | .into_iter() 31 | }) 32 | .collect(); 33 | let additional_includes: String = additional_headers 34 | .into_iter() 35 | .map(|include| format!(r#"#include "{}""#, include)) 36 | .collect::>() 37 | .join("\n"); 38 | 39 | let inits = self 40 | .globals 41 | .iter() 42 | .map(|global| global.init.clone().unwrap_or("".into())) 43 | .collect::>() 44 | .join("\n"); 45 | let global_exprs = self 46 | .globals 47 | .iter() 48 | .map(|global| format!("auto {} = {};", global.name, global.factory)) 49 | .collect::>() 50 | .join("\n"); 51 | 52 | let transpiled_items: Vec> = module 53 | .body 54 | .iter() 55 | .map(|item| -> Result { 56 | match item { 57 | ModuleItem::ModuleDecl(_decl) => { 58 | return Err(anyhow!("Module imports/exports are not support")) 59 | } 60 | ModuleItem::Stmt(stmt) => self.transpile_stmt(stmt), 61 | } 62 | }) 63 | .collect(); 64 | 65 | let main = if self.feature_exceptions { 66 | r#" 67 | try {{ 68 | prog(); 69 | }} catch(std::string e) {{ 70 | printf("EXCEPTION: %s\n", e.c_str()); 71 | }} 72 | "# 73 | } else { 74 | r#" 75 | {{ 76 | prog(); 77 | }} 78 | "# 79 | }; 80 | 81 | Ok(format!( 82 | r#" 83 | {additional_includes} 84 | #include 85 | #include "runtime/js_value.hpp" 86 | #include "runtime/exceptions.hpp" 87 | 88 | int prog() {{ 89 | {inits} 90 | {global_exprs} 91 | {program} 92 | return 0; 93 | }} 94 | 95 | int main() {{ 96 | {main} 97 | }} 98 | "#, 99 | additional_includes = additional_includes, 100 | inits = inits, 101 | global_exprs = global_exprs, 102 | program = Result::>::from_iter(transpiled_items)?.join(";\n"), 103 | main = main 104 | )) 105 | } 106 | 107 | fn transpile_stmt(&mut self, stmt: &Stmt) -> Result { 108 | let transpiled_stmt = match stmt { 109 | Stmt::Decl(decl) => self.transpile_decl(decl)?, 110 | Stmt::Expr(expr_stmt) => self.transpile_expr(&expr_stmt.expr)?, 111 | Stmt::Block(block_stmt) => self.transpile_block_stmt(block_stmt)?, 112 | Stmt::Return(return_stmt) => self.transpile_return_stmt(return_stmt)?, 113 | Stmt::If(if_stmt) => self.transpile_if_stmt(if_stmt)?, 114 | Stmt::For(for_stmt) => self.transpile_for_stmt(for_stmt)?, 115 | Stmt::ForOf(for_of_stmt) => self.transpile_for_of_stmt(for_of_stmt)?, 116 | Stmt::While(while_stmt) => self.transpile_while_stmt(while_stmt)?, 117 | Stmt::Break(break_stmt) => self.transpile_break_stmt(break_stmt)?, 118 | Stmt::Try(try_stmt) => self.transpile_try_stmt(try_stmt)?, 119 | Stmt::Throw(throw_stmt) => self.transpile_throw_stmt(throw_stmt)?, 120 | _ => return Err(anyhow!("Unsupported statemt: {:?}", stmt)), 121 | }; 122 | Ok(format!("{};", transpiled_stmt)) 123 | } 124 | 125 | fn transpile_throw_stmt(&mut self, throw_stmt: &ThrowStmt) -> Result { 126 | let expr = self.transpile_expr(&throw_stmt.arg)?; 127 | Ok(format!("js_throw({})", expr)) 128 | } 129 | 130 | fn transpile_catch_clause(&mut self, catch_clause: &CatchClause) -> Result { 131 | let ident = catch_clause 132 | .param 133 | .as_ref() 134 | .map(|pat| { 135 | pat.as_ident() 136 | .map(|ident| format!("{}", ident.sym)) 137 | .ok_or(anyhow!( 138 | "Only straight-up identifiers are supported as function parameters" 139 | )) 140 | }) 141 | .transpose()? 142 | .unwrap_or(format!("__unused")); 143 | let body = self.transpile_block_stmt(&catch_clause.body)?; 144 | 145 | Ok(format!( 146 | r#" 147 | catch(JSValue {}) {{ 148 | {} 149 | }} 150 | "#, 151 | ident, body 152 | )) 153 | } 154 | 155 | fn transpile_try_stmt(&mut self, try_stmt: &TryStmt) -> Result { 156 | if try_stmt.finalizer.is_some() { 157 | return Err(anyhow!("`finally` not supported yet")); 158 | } 159 | let block = self.transpile_block_stmt(&try_stmt.block)?; 160 | let handler = self.transpile_catch_clause( 161 | try_stmt 162 | .handler 163 | .as_ref() 164 | .ok_or(anyhow!("Missing catch handler"))?, 165 | )?; 166 | 167 | if self.feature_exceptions { 168 | Ok(format!( 169 | r#" 170 | try {{ 171 | {} 172 | }} 173 | {} 174 | "#, 175 | block, handler 176 | )) 177 | } else { 178 | Ok(format!( 179 | r#" 180 | {{ 181 | {} 182 | }} 183 | "#, 184 | block 185 | )) 186 | } 187 | } 188 | 189 | fn transpile_for_of_stmt(&mut self, for_of_stmt: &ForOfStmt) -> Result { 190 | let left = match &for_of_stmt.left { 191 | VarDeclOrPat::VarDecl(var_decl) => self.transpile_var_decl(&var_decl)?, 192 | _ => return Err(anyhow!("Only simple variables are supported in for-of")), 193 | }; 194 | 195 | let right = self.transpile_expr(&for_of_stmt.right)?; 196 | let body = self.transpile_stmt(&for_of_stmt.body)?; 197 | 198 | Ok(format!( 199 | r#" 200 | for({left} : {right}) {{ 201 | {body} 202 | }} 203 | "#, 204 | left = left, 205 | right = right, 206 | body = body, 207 | )) 208 | } 209 | 210 | fn transpile_break_stmt(&mut self, _break_stmt: &BreakStmt) -> Result { 211 | Ok(format!("break")) 212 | } 213 | 214 | fn transpile_while_stmt(&mut self, while_stmt: &WhileStmt) -> Result { 215 | let test = self.transpile_expr(&while_stmt.test)?; 216 | let body = self.transpile_stmt(&while_stmt.body)?; 217 | Ok(format!("while(({}).coerce_to_bool()) {{ {} }}", test, body)) 218 | } 219 | 220 | fn transpile_for_stmt(&mut self, for_stmt: &ForStmt) -> Result { 221 | let init = for_stmt 222 | .init 223 | .as_ref() 224 | .map(|var_decl_or_expr| match var_decl_or_expr { 225 | VarDeclOrExpr::Expr(expr) => self.transpile_expr(expr), 226 | VarDeclOrExpr::VarDecl(var_decl) => self.transpile_var_decl(var_decl), 227 | }) 228 | .transpose()? 229 | .unwrap_or("".to_string()); 230 | 231 | let test = for_stmt 232 | .test 233 | .as_ref() 234 | .map(|expr| self.transpile_expr(expr)) 235 | .transpose()? 236 | .unwrap_or("".to_string()); 237 | 238 | let update = for_stmt 239 | .update 240 | .as_ref() 241 | .map(|expr| self.transpile_expr(expr)) 242 | .transpose()? 243 | .unwrap_or("".to_string()); 244 | 245 | let body = self.transpile_stmt(for_stmt.body.as_ref())?; 246 | 247 | Ok(format!( 248 | r#" 249 | for({init};({test}).coerce_to_bool();{update}) {{ 250 | {body} 251 | }} 252 | "#, 253 | init = init, 254 | test = test, 255 | update = update, 256 | body = body, 257 | )) 258 | } 259 | 260 | fn transpile_if_stmt(&mut self, if_stmt: &IfStmt) -> Result { 261 | let test = self.transpile_expr(&if_stmt.test)?; 262 | let cons = self.transpile_stmt(&if_stmt.cons)?; 263 | let alt = if_stmt 264 | .alt 265 | .as_ref() 266 | .map(|alt| self.transpile_stmt(alt.as_ref())) 267 | .transpose()? 268 | .unwrap_or("".into()); 269 | Ok(format!( 270 | r#" 271 | if(({}).coerce_to_bool()) {{ 272 | {} 273 | }} else {{ 274 | {} 275 | }} 276 | "#, 277 | test, cons, alt 278 | )) 279 | } 280 | 281 | fn transpile_return_stmt(&mut self, return_stmt: &ReturnStmt) -> Result { 282 | let arg = match &return_stmt.arg { 283 | None => "".to_string(), 284 | Some(expr) => self.transpile_expr(expr)?, 285 | }; 286 | let ret_style = if self.is_generator { 287 | "co_return" 288 | } else { 289 | "return" 290 | }; 291 | Ok(format!("{} {};", ret_style, arg)) 292 | } 293 | 294 | fn transpile_decl(&mut self, decl: &Decl) -> Result { 295 | match decl { 296 | Decl::Var(var_decl) => self.transpile_var_decl(var_decl), 297 | Decl::Fn(fn_decl) => self.transpile_fn_decl(fn_decl), 298 | _ => return Err(anyhow!("Unsupported declaration: {:?}", decl)), 299 | } 300 | } 301 | 302 | fn transpile_fn_decl(&mut self, fn_decl: &FnDecl) -> Result { 303 | let name = format!("{}", fn_decl.ident.sym); 304 | let func = self.transpile_function(&fn_decl.function)?; 305 | Ok(format!("JSValue {} = {};", name, func)) 306 | } 307 | 308 | fn transpile_var_decl(&mut self, var_decl: &VarDecl) -> Result { 309 | if var_decl.kind != VarDeclKind::Let { 310 | return Err(anyhow!("Only `let` variable declarations are supported.")); 311 | } 312 | if var_decl.decls.len() > 1 { 313 | return Err(anyhow!("Only single-variable let statements for now")); 314 | } 315 | self.transpile_var_declarator(&var_decl.decls[0]) 316 | } 317 | 318 | fn transpile_var_declarator(&mut self, var_decl: &VarDeclarator) -> Result { 319 | let ident = var_decl.name.as_ident().ok_or(anyhow!( 320 | "Only straight-up identifiers are supported for variable declarations for now." 321 | ))?; 322 | let init = var_decl 323 | .init 324 | .as_ref() 325 | .map(|init| -> Result { 326 | Ok(format!( 327 | " = ({}).boxed_value()", 328 | self.transpile_expr(&init)? 329 | )) 330 | }) 331 | .unwrap_or(Ok("".to_string()))?; 332 | Ok(format!("JSValue {}{}", ident.sym, init)) 333 | } 334 | 335 | fn transpile_expr(&mut self, expr: &Expr) -> Result { 336 | match expr { 337 | Expr::Ident(ident) => Ok(format!("{}", ident.sym)), 338 | Expr::Lit(literal) => self.transpile_literal(literal), 339 | Expr::Array(array_lit) => self.transpile_array_literal(array_lit), 340 | Expr::Call(call_expr) => self.transpile_call_expr(call_expr), 341 | Expr::Member(member_expr) => self.transpile_member_expr(member_expr), 342 | Expr::Arrow(arrow_expr) => self.transpile_arrow_expr(arrow_expr), 343 | Expr::Bin(bin_expr) => self.transpile_bin_expr(bin_expr), 344 | Expr::Tpl(tpl_expr) => self.transpile_tpl_expr(tpl_expr), 345 | Expr::TaggedTpl(tagged_tpl_expr) => self.transpile_tagged_tpl_expr(tagged_tpl_expr), 346 | Expr::Object(object_lit) => self.transpile_object_lit(object_lit), 347 | Expr::Paren(paren_expr) => self.transpile_paren_expr(paren_expr), 348 | Expr::Fn(fn_expr) => self.transpile_fn_expr(fn_expr), 349 | Expr::This(this_expr) => self.transpile_this_expr(this_expr), 350 | Expr::Assign(assign_expr) => self.transpile_assign_expr(assign_expr), 351 | Expr::Cond(cond_expr) => self.transpile_cond_expr(cond_expr), 352 | Expr::Update(update_expr) => self.transpile_update_expr(update_expr), 353 | Expr::Yield(yield_expr) => self.transpile_yield_expr(yield_expr), 354 | _ => Err(anyhow!("Unsupported expression {:?}", expr)), 355 | } 356 | } 357 | 358 | fn transpile_yield_expr(&mut self, yield_expr: &YieldExpr) -> Result { 359 | let value = yield_expr 360 | .arg 361 | .as_ref() 362 | .map(|arg| self.transpile_expr(arg)) 363 | .transpose()? 364 | .unwrap_or(format!("JSValue::undefined()")); 365 | 366 | if !yield_expr.delegate { 367 | Ok(format!("co_yield {}", value)) 368 | } else { 369 | Ok(format!( 370 | r#" 371 | for(auto v : {}) {{ 372 | co_yield v; 373 | }} 374 | "#, 375 | value 376 | )) 377 | } 378 | } 379 | 380 | fn transpile_update_expr(&mut self, update_expr: &UpdateExpr) -> Result { 381 | let expr = self.transpile_expr(update_expr.arg.as_ref())?; 382 | let op = match update_expr.op { 383 | UpdateOp::MinusMinus => "--", 384 | UpdateOp::PlusPlus => "++", 385 | }; 386 | Ok(match update_expr.prefix { 387 | true => format!("{}({})", op, expr), 388 | false => format!("({}){}", expr, op), 389 | }) 390 | } 391 | 392 | fn transpile_cond_expr(&mut self, cond_expr: &CondExpr) -> Result { 393 | let test = self.transpile_expr(&cond_expr.test)?; 394 | let cons = self.transpile_expr(&cond_expr.cons)?; 395 | let alt = self.transpile_expr(&cond_expr.alt)?; 396 | Ok(format!("({}).coerce_to_bool()?({}):({})", test, cons, alt)) 397 | } 398 | 399 | fn transpile_assign_expr(&mut self, assign_expr: &AssignExpr) -> Result { 400 | let left = match &assign_expr.left { 401 | PatOrExpr::Expr(expr) => self.transpile_expr(expr)?, 402 | PatOrExpr::Pat(pat) => match pat.as_ref() { 403 | Pat::Expr(expr) => self.transpile_expr(expr)?, 404 | Pat::Ident(ident) => format!("{}", ident.sym), 405 | _ => { 406 | return Err(anyhow!( 407 | "Unsupported assignment pattern {:?}", 408 | assign_expr.left 409 | )) 410 | } 411 | }, 412 | }; 413 | let right = self.transpile_expr(&assign_expr.right)?; 414 | let op = match assign_expr.op { 415 | AssignOp::Assign => "=", 416 | _ => return Err(anyhow!("Unsupported assign operation {:?}", assign_expr.op)), 417 | }; 418 | Ok(format!("{} {} ({}).boxed_value()", left, op, right)) 419 | } 420 | 421 | fn transpile_this_expr(&mut self, _this_expr: &ThisExpr) -> Result { 422 | Ok(format!("thisArg")) 423 | } 424 | 425 | fn transpile_function(&mut self, function: &Function) -> Result { 426 | if function.is_async { 427 | return Err(anyhow!("Async functions not supported yet")); 428 | } 429 | if function.is_generator { 430 | return self.transpile_generator_function(function); 431 | } 432 | return self.transpile_plain_function(function); 433 | } 434 | 435 | fn transpile_generator_function(&mut self, function: &Function) -> Result { 436 | assert!(function.is_generator); 437 | self.is_generator = true; 438 | let param_destructure = 439 | self.transpile_param_destructure(function.params.iter().map(|param| ¶m.pat))?; 440 | let body = match &function.body { 441 | Some(block_stmt) => self.transpile_block_stmt(block_stmt)?, 442 | _ => return Err(anyhow!("Function lacks a body")), 443 | }; 444 | self.is_generator = false; 445 | Ok(format!( 446 | "JSValue::new_generator_function([=](JSValue thisArg, std::vector& args) mutable -> JSGeneratorAdapter {{ 447 | {} 448 | {} 449 | co_return; 450 | }})", 451 | param_destructure, body 452 | )) 453 | } 454 | 455 | fn transpile_plain_function(&mut self, function: &Function) -> Result { 456 | let param_destructure = 457 | self.transpile_param_destructure(function.params.iter().map(|param| ¶m.pat))?; 458 | let body = match &function.body { 459 | Some(block_stmt) => self.transpile_block_stmt(block_stmt)?, 460 | _ => return Err(anyhow!("Function lacks a body")), 461 | }; 462 | Ok(format!( 463 | "JSValue::new_function([=](JSValue thisArg, std::vector& args) mutable -> JSValue {{ 464 | {} 465 | {} 466 | return JSValue::undefined(); 467 | }})", 468 | param_destructure, body 469 | )) 470 | } 471 | 472 | fn transpile_fn_expr(&mut self, fn_expr: &FnExpr) -> Result { 473 | self.transpile_function(&fn_expr.function) 474 | } 475 | 476 | fn transpile_paren_expr(&mut self, paren_expr: &ParenExpr) -> Result { 477 | Ok(format!("({})", self.transpile_expr(&paren_expr.expr)?)) 478 | } 479 | 480 | fn transpile_object_lit(&mut self, object_lit: &ObjectLit) -> Result { 481 | let transpiled_props: Vec> = object_lit 482 | .props 483 | .iter() 484 | .map(|prop| match prop { 485 | PropOrSpread::Spread(_) => return Err(anyhow!("Object spread unsupported")), 486 | PropOrSpread::Prop(prop) => match prop.as_ref() { 487 | Prop::Shorthand(ident) => self.transpile_prop_shorthand(ident), 488 | Prop::KeyValue(key_value) => self.transpile_prop_keyvalue(key_value), 489 | Prop::Getter(getter) => self.transpile_prop_getter(getter), 490 | Prop::Setter(setter) => self.transpile_prop_setter(setter), 491 | Prop::Method(method) => self.transpile_prop_method(method), 492 | _ => Err(anyhow!("Unsupported object property {:?}", prop)), 493 | }, 494 | }) 495 | .collect(); 496 | let prop_defs = Result::>::from_iter(transpiled_props)?.join(",\n"); 497 | Ok(format!("JSValue::new_object({{ {} }})", prop_defs)) 498 | } 499 | 500 | fn transpile_prop_method(&mut self, method: &MethodProp) -> Result { 501 | Ok(format!( 502 | "{{ {}, {} }}", 503 | self.transpile_prop_name(&method.key)?, 504 | self.transpile_function(&method.function)? 505 | )) 506 | } 507 | 508 | fn transpile_prop_setter(&mut self, setter: &SetterProp) -> Result { 509 | let ident = setter 510 | .param 511 | .as_ident() 512 | .ok_or(anyhow!("Setter parameter must be an ident"))?; 513 | Ok(format!( 514 | r#"{{ 515 | {}, 516 | JSValue::with_getter_setter( 517 | JSValue::undefined(), 518 | JSValue::new_function([=](JSValue thisArg, std::vector& args) mutable -> JSValue {{ 519 | JSValue {} = args[0]; 520 | {} 521 | return JSValue::undefined(); 522 | }}) 523 | ) 524 | }}"#, 525 | self.transpile_prop_name(&setter.key)?, 526 | ident.sym, 527 | self.transpile_block_stmt(setter.body.as_ref().ok_or(anyhow!("Getter needs a body"))?)? 528 | )) 529 | } 530 | 531 | fn transpile_prop_getter(&mut self, getter: &GetterProp) -> Result { 532 | Ok(format!( 533 | r#"{{ 534 | {}, 535 | JSValue::with_getter_setter( 536 | JSValue::new_function([=](JSValue thisArg, std::vector& args) mutable -> JSValue {{ 537 | {} 538 | return JSValue::undefined(); 539 | }}), 540 | JSValue::undefined() 541 | ) 542 | }}"#, 543 | self.transpile_prop_name(&getter.key)?, 544 | self.transpile_block_stmt(getter.body.as_ref().ok_or(anyhow!("Getter needs a body"))?)? 545 | )) 546 | } 547 | 548 | fn transpile_prop_keyvalue(&mut self, key_value: &KeyValueProp) -> Result { 549 | Ok(format!( 550 | "{{{}, {}}}", 551 | self.transpile_prop_name(&key_value.key)?, 552 | self.transpile_expr(&key_value.value)? 553 | )) 554 | } 555 | 556 | fn transpile_prop_shorthand(&mut self, ident: &Ident) -> Result { 557 | Ok(format!(r#"{{JSValue{{"{0}"}}, {0}}}"#, ident.sym)) 558 | } 559 | 560 | fn transpile_prop_name(&mut self, prop_name: &PropName) -> Result { 561 | match prop_name { 562 | PropName::Ident(ident) => Ok(format!(r#"JSValue{{"{}"}}"#, ident.sym)), 563 | PropName::Str(str) => { 564 | let v = str 565 | .raw 566 | .as_ref() 567 | .map(|v| format!("{}", v)) 568 | .unwrap_or(format!("{}", str.value)); 569 | Ok(format!(r#""{}""#, v)) 570 | } 571 | PropName::Computed(computed_prop_name) => self.transpile_expr(&computed_prop_name.expr), 572 | _ => Err(anyhow!("Unsupported property name {:?}", prop_name)), 573 | } 574 | } 575 | 576 | fn transpile_tpl_expr(&mut self, tpl_expr: &Tpl) -> Result { 577 | if tpl_expr.quasis.len() > 1 { 578 | return Err(anyhow!("No support for template string interpolation yet")); 579 | } 580 | Ok(format!(r#"JSValue{{"{}"}}"#, tpl_expr.quasis[0].raw)) 581 | } 582 | 583 | fn transpile_tagged_tpl_expr(&mut self, tagged_tpl_expr: &TaggedTpl) -> Result { 584 | let tag = self.transpile_expr(&tagged_tpl_expr.tag)?; 585 | let tpl = self.transpile_tpl_expr(&tagged_tpl_expr.tpl)?; 586 | if tag == "raw_cpp" { 587 | return Ok(tpl[1..tpl.len() - 1].to_string()); 588 | } 589 | Err(anyhow!("No support for tagged template expressions")) 590 | } 591 | 592 | fn transpile_bin_expr(&mut self, bin_expr: &BinExpr) -> Result { 593 | let left = self.transpile_expr(&bin_expr.left)?; 594 | let right = self.transpile_expr(&bin_expr.right)?; 595 | let op = match bin_expr.op { 596 | BinaryOp::Add => "+", 597 | BinaryOp::Mul => "*", 598 | BinaryOp::Gt => ">", 599 | BinaryOp::GtEq => ">=", 600 | BinaryOp::EqEq => "==", 601 | BinaryOp::EqEqEq => "==", 602 | BinaryOp::Lt => "<", 603 | BinaryOp::LtEq => "<=", 604 | BinaryOp::NotEq => "!=", 605 | BinaryOp::NotEqEq => "!=", 606 | BinaryOp::LogicalAnd => "&&", 607 | BinaryOp::LogicalOr => "||", 608 | BinaryOp::Mod => "%", 609 | _ => return Err(anyhow!("Unsupported binary operation {:?}", bin_expr.op)), 610 | }; 611 | Ok(format!("({}){}({})", left, op, right)) 612 | } 613 | 614 | fn transpile_param_destructure<'a>( 615 | &mut self, 616 | params: impl Iterator, 617 | ) -> Result { 618 | let transpiled_params: Vec> = params 619 | .enumerate() 620 | .map(|(idx, param)| { 621 | param 622 | .as_ident() 623 | .map(|ident| format!("JSValue {} = args[{}];", ident.sym, idx)) 624 | .ok_or(anyhow!( 625 | "Only straight-up identifiers are supported as function parameters" 626 | )) 627 | }) 628 | .collect(); 629 | Ok(Result::>::from_iter(transpiled_params)?.join(";")) 630 | } 631 | 632 | fn transpile_block_stmt(&mut self, block_stmt: &BlockStmt) -> Result { 633 | let stmts: Vec> = block_stmt 634 | .stmts 635 | .iter() 636 | .map(|stmt| self.transpile_stmt(stmt)) 637 | .collect(); 638 | let block: String = Result::>::from_iter(stmts)?.join(";\n"); 639 | Ok(format!("{{ {} }}", block)) 640 | } 641 | 642 | fn transpile_arrow_expr(&mut self, arrow_expr: &ArrowExpr) -> Result { 643 | let param_destructure = self.transpile_param_destructure(arrow_expr.params.iter())?; 644 | let body = match &arrow_expr.body { 645 | BlockStmtOrExpr::Expr(expr) => format!("return {};", self.transpile_expr(expr)?), 646 | BlockStmtOrExpr::BlockStmt(block_stmt) => self.transpile_block_stmt(block_stmt)?, 647 | }; 648 | Ok(format!( 649 | "JSValue::new_function([=](JSValue thisArg, std::vector& args) mutable {{ 650 | {} 651 | {} 652 | return JSValue::undefined(); 653 | }})", 654 | param_destructure, body 655 | )) 656 | } 657 | 658 | fn transpile_member_expr(&mut self, member_expr: &MemberExpr) -> Result { 659 | let obj = self.transpile_expr(&member_expr.obj)?; 660 | let prop = match &member_expr.prop { 661 | MemberProp::Ident(ident) => format!(r#"JSValue{{"{}"}}"#, ident.sym), 662 | MemberProp::Computed(computed_prop_name) => { 663 | self.transpile_expr(&computed_prop_name.expr)? 664 | } 665 | _ => return Err(anyhow!("Unsupported member prop {:?}", member_expr.prop)), 666 | }; 667 | Ok(format!(r#"{}[{}]"#, obj, prop)) 668 | } 669 | 670 | fn transpile_call_expr(&mut self, call_expr: &CallExpr) -> Result { 671 | let callee = self.transpile_expr( 672 | call_expr 673 | .callee 674 | .as_expr() 675 | .ok_or(anyhow!("Unsupported callee expr {:?}", call_expr.callee))?, 676 | )?; 677 | let transpiled_args: Vec> = call_expr 678 | .args 679 | .iter() 680 | .map(|arg| { 681 | Ok(format!( 682 | "({}).boxed_value()", 683 | self.transpile_expr(&arg.expr)? 684 | )) 685 | }) 686 | .collect(); 687 | let arg_expr = Result::>::from_iter(transpiled_args)?.join(","); 688 | Ok(format!("{}({{{}}})", callee, arg_expr)) 689 | } 690 | 691 | fn transpile_array_literal(&mut self, array_lit: &ArrayLit) -> Result { 692 | let transpiled_elems: Vec> = array_lit 693 | .elems 694 | .iter() 695 | .map(|item| { 696 | let item = item 697 | .as_ref() 698 | .ok_or(anyhow!("No support for empty array items"))?; 699 | self.transpile_expr(&item.expr) 700 | }) 701 | .collect(); 702 | 703 | Ok(format!( 704 | "JSValue::new_array({{{}}})", 705 | Result::>::from_iter(transpiled_elems)?.join(",") 706 | )) 707 | } 708 | 709 | fn transpile_literal(&mut self, lit: &Lit) -> Result { 710 | match lit { 711 | Lit::Num(num) => self.transpile_number(num), 712 | Lit::Str(str) => self.transpile_string(str), 713 | Lit::Bool(bool) => self.transpile_bool(bool), 714 | _ => Err(anyhow!("Unsupported literal {:?}", lit)), 715 | } 716 | } 717 | 718 | fn transpile_bool(&mut self, bool: &Bool) -> Result { 719 | let bool_str = match bool.value { 720 | true => "true", 721 | false => "false", 722 | }; 723 | 724 | Ok(format!("JSValue{{{}}}", bool_str)) 725 | } 726 | fn transpile_string(&mut self, string: &Str) -> Result { 727 | Ok(format!( 728 | r#"JSValue{{"{}"}}"#, 729 | string 730 | .raw 731 | .as_ref() 732 | .map(|v| format!("{}", &v[1..v.len() - 1])) 733 | .unwrap_or(format!("{}", string.value)) 734 | )) 735 | } 736 | 737 | fn transpile_number(&mut self, num: &Number) -> Result { 738 | Ok(format!("JSValue{{static_cast({})}}", num.value)) 739 | } 740 | } 741 | --------------------------------------------------------------------------------