├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── src ├── compiler.rs ├── lib.rs ├── main.rs └── scratch.rs ├── support.rs └── tests ├── out.rs └── out ├── fib_10_iter.out ├── fib_10_iter.sb3 ├── fib_10_recr.out ├── fib_10_recr.sb3 ├── forever_stop.out ├── forever_stop.sb3 ├── loop_if.out ├── loop_if.sb3 ├── multiscript.out ├── multiscript.sb3 ├── stop_all.out ├── stop_all.sb3 ├── stop_script.out └── stop_script.sb3 /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.2.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 8 | 9 | [[package]] 10 | name = "ansi_term" 11 | version = "0.11.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 14 | dependencies = [ 15 | "winapi", 16 | ] 17 | 18 | [[package]] 19 | name = "anyhow" 20 | version = "1.0.38" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" 23 | 24 | [[package]] 25 | name = "autocfg" 26 | version = "1.0.1" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 29 | 30 | [[package]] 31 | name = "bitflags" 32 | version = "1.2.1" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 35 | 36 | [[package]] 37 | name = "byteorder" 38 | version = "1.4.2" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" 41 | 42 | [[package]] 43 | name = "bzip2" 44 | version = "0.3.3" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" 47 | dependencies = [ 48 | "bzip2-sys", 49 | "libc", 50 | ] 51 | 52 | [[package]] 53 | name = "bzip2-sys" 54 | version = "0.1.10+1.0.8" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "17fa3d1ac1ca21c5c4e36a97f3c3eb25084576f6fc47bf0139c1123434216c6c" 57 | dependencies = [ 58 | "cc", 59 | "libc", 60 | "pkg-config", 61 | ] 62 | 63 | [[package]] 64 | name = "cc" 65 | version = "1.0.66" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" 68 | 69 | [[package]] 70 | name = "cfg-if" 71 | version = "0.1.10" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 74 | 75 | [[package]] 76 | name = "cfg-if" 77 | version = "1.0.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 80 | 81 | [[package]] 82 | name = "cranelift" 83 | version = "0.69.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "910322bd748b9b2450947659a48a928f35b8ba7212d80d719ff85e4b7cde62b7" 86 | dependencies = [ 87 | "cranelift-codegen", 88 | "cranelift-frontend", 89 | ] 90 | 91 | [[package]] 92 | name = "cranelift-bforest" 93 | version = "0.69.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "4066fd63b502d73eb8c5fa6bcab9c7962b05cd580f6b149ee83a8e730d8ce7fb" 96 | dependencies = [ 97 | "cranelift-entity", 98 | ] 99 | 100 | [[package]] 101 | name = "cranelift-codegen" 102 | version = "0.69.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "1a54e4beb833a3c873a18a8fe735d73d732044004c7539a072c8faa35ccb0c60" 105 | dependencies = [ 106 | "byteorder", 107 | "cranelift-bforest", 108 | "cranelift-codegen-meta", 109 | "cranelift-codegen-shared", 110 | "cranelift-entity", 111 | "log", 112 | "regalloc", 113 | "smallvec", 114 | "target-lexicon", 115 | "thiserror", 116 | ] 117 | 118 | [[package]] 119 | name = "cranelift-codegen-meta" 120 | version = "0.69.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "c54cac7cacb443658d8f0ff36a3545822613fa202c946c0891897843bc933810" 123 | dependencies = [ 124 | "cranelift-codegen-shared", 125 | "cranelift-entity", 126 | ] 127 | 128 | [[package]] 129 | name = "cranelift-codegen-shared" 130 | version = "0.69.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "a109760aff76788b2cdaeefad6875a73c2b450be13906524f6c2a81e05b8d83c" 133 | 134 | [[package]] 135 | name = "cranelift-entity" 136 | version = "0.69.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "3b044234aa32531f89a08b487630ddc6744696ec04c8123a1ad388de837f5de3" 139 | 140 | [[package]] 141 | name = "cranelift-frontend" 142 | version = "0.69.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "5452b3e4e97538ee5ef2cc071301c69a86c7adf2770916b9d04e9727096abd93" 145 | dependencies = [ 146 | "cranelift-codegen", 147 | "log", 148 | "smallvec", 149 | "target-lexicon", 150 | ] 151 | 152 | [[package]] 153 | name = "cranelift-module" 154 | version = "0.69.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "b17bc01cb9f176156d5cbd47ece19292e04e05a91a837dcf0ef69cc1e0e97a5a" 157 | dependencies = [ 158 | "anyhow", 159 | "cranelift-codegen", 160 | "cranelift-entity", 161 | "log", 162 | "thiserror", 163 | ] 164 | 165 | [[package]] 166 | name = "cranelift-native" 167 | version = "0.69.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "f68035c10b2e80f26cc29c32fa824380877f38483504c2a47b54e7da311caaf3" 170 | dependencies = [ 171 | "cranelift-codegen", 172 | "raw-cpuid", 173 | "target-lexicon", 174 | ] 175 | 176 | [[package]] 177 | name = "cranelift-object" 178 | version = "0.69.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "b25cdfa0f692f9d17626fb6ee1ecae545585f97c7a998c90423576d588ea7825" 181 | dependencies = [ 182 | "anyhow", 183 | "cranelift-codegen", 184 | "cranelift-module", 185 | "log", 186 | "object", 187 | "target-lexicon", 188 | ] 189 | 190 | [[package]] 191 | name = "cranelift-preopt" 192 | version = "0.69.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "71d492b53e3d2563a146771692cec51370073cbe098670779fa0fde3c11b7537" 195 | dependencies = [ 196 | "cranelift-codegen", 197 | "cranelift-entity", 198 | ] 199 | 200 | [[package]] 201 | name = "crc32fast" 202 | version = "1.2.1" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 205 | dependencies = [ 206 | "cfg-if 1.0.0", 207 | ] 208 | 209 | [[package]] 210 | name = "ctor" 211 | version = "0.1.19" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" 214 | dependencies = [ 215 | "quote 1.0.8", 216 | "syn 1.0.60", 217 | ] 218 | 219 | [[package]] 220 | name = "darling" 221 | version = "0.10.2" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" 224 | dependencies = [ 225 | "darling_core", 226 | "darling_macro", 227 | ] 228 | 229 | [[package]] 230 | name = "darling_core" 231 | version = "0.10.2" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" 234 | dependencies = [ 235 | "fnv", 236 | "ident_case", 237 | "proc-macro2 1.0.24", 238 | "quote 1.0.8", 239 | "strsim", 240 | "syn 1.0.60", 241 | ] 242 | 243 | [[package]] 244 | name = "darling_macro" 245 | version = "0.10.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" 248 | dependencies = [ 249 | "darling_core", 250 | "quote 1.0.8", 251 | "syn 1.0.60", 252 | ] 253 | 254 | [[package]] 255 | name = "difference" 256 | version = "2.0.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 259 | 260 | [[package]] 261 | name = "flate2" 262 | version = "1.0.14" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" 265 | dependencies = [ 266 | "cfg-if 0.1.10", 267 | "crc32fast", 268 | "libc", 269 | "miniz_oxide", 270 | ] 271 | 272 | [[package]] 273 | name = "fnv" 274 | version = "1.0.7" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 277 | 278 | [[package]] 279 | name = "glob" 280 | version = "0.3.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 283 | 284 | [[package]] 285 | name = "hashbrown" 286 | version = "0.9.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 289 | 290 | [[package]] 291 | name = "ident_case" 292 | version = "1.0.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 295 | 296 | [[package]] 297 | name = "indexmap" 298 | version = "1.6.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" 301 | dependencies = [ 302 | "autocfg", 303 | "hashbrown", 304 | ] 305 | 306 | [[package]] 307 | name = "itoa" 308 | version = "0.4.7" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 311 | 312 | [[package]] 313 | name = "libc" 314 | version = "0.2.86" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" 317 | 318 | [[package]] 319 | name = "log" 320 | version = "0.4.14" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 323 | dependencies = [ 324 | "cfg-if 1.0.0", 325 | ] 326 | 327 | [[package]] 328 | name = "miniz_oxide" 329 | version = "0.3.7" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 332 | dependencies = [ 333 | "adler32", 334 | ] 335 | 336 | [[package]] 337 | name = "object" 338 | version = "0.22.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" 341 | dependencies = [ 342 | "crc32fast", 343 | "indexmap", 344 | ] 345 | 346 | [[package]] 347 | name = "output_vt100" 348 | version = "0.1.2" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" 351 | dependencies = [ 352 | "winapi", 353 | ] 354 | 355 | [[package]] 356 | name = "pkg-config" 357 | version = "0.3.19" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 360 | 361 | [[package]] 362 | name = "pretty_assertions" 363 | version = "0.6.1" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" 366 | dependencies = [ 367 | "ansi_term", 368 | "ctor", 369 | "difference", 370 | "output_vt100", 371 | ] 372 | 373 | [[package]] 374 | name = "proc-macro2" 375 | version = "0.4.30" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" 378 | dependencies = [ 379 | "unicode-xid 0.1.0", 380 | ] 381 | 382 | [[package]] 383 | name = "proc-macro2" 384 | version = "1.0.24" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 387 | dependencies = [ 388 | "unicode-xid 0.2.1", 389 | ] 390 | 391 | [[package]] 392 | name = "quote" 393 | version = "0.6.13" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" 396 | dependencies = [ 397 | "proc-macro2 0.4.30", 398 | ] 399 | 400 | [[package]] 401 | name = "quote" 402 | version = "1.0.8" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" 405 | dependencies = [ 406 | "proc-macro2 1.0.24", 407 | ] 408 | 409 | [[package]] 410 | name = "raw-cpuid" 411 | version = "8.1.2" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "1fdf7d9dbd43f3d81d94a49c1c3df73cc2b3827995147e6cf7f89d4ec5483e73" 414 | dependencies = [ 415 | "bitflags", 416 | "cc", 417 | "rustc_version", 418 | ] 419 | 420 | [[package]] 421 | name = "regalloc" 422 | version = "0.0.31" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" 425 | dependencies = [ 426 | "log", 427 | "rustc-hash", 428 | "smallvec", 429 | ] 430 | 431 | [[package]] 432 | name = "rustc-hash" 433 | version = "1.1.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 436 | 437 | [[package]] 438 | name = "rustc_version" 439 | version = "0.2.3" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 442 | dependencies = [ 443 | "semver", 444 | ] 445 | 446 | [[package]] 447 | name = "ryu" 448 | version = "1.0.5" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 451 | 452 | [[package]] 453 | name = "scratchc" 454 | version = "0.1.0" 455 | dependencies = [ 456 | "cranelift", 457 | "cranelift-module", 458 | "cranelift-native", 459 | "cranelift-object", 460 | "cranelift-preopt", 461 | "pretty_assertions", 462 | "serde", 463 | "serde_json", 464 | "serde_with", 465 | "test-generator", 466 | "zip", 467 | ] 468 | 469 | [[package]] 470 | name = "semver" 471 | version = "0.9.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 474 | dependencies = [ 475 | "semver-parser", 476 | ] 477 | 478 | [[package]] 479 | name = "semver-parser" 480 | version = "0.7.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 483 | 484 | [[package]] 485 | name = "serde" 486 | version = "1.0.123" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" 489 | dependencies = [ 490 | "serde_derive", 491 | ] 492 | 493 | [[package]] 494 | name = "serde_derive" 495 | version = "1.0.123" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" 498 | dependencies = [ 499 | "proc-macro2 1.0.24", 500 | "quote 1.0.8", 501 | "syn 1.0.60", 502 | ] 503 | 504 | [[package]] 505 | name = "serde_json" 506 | version = "1.0.62" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" 509 | dependencies = [ 510 | "itoa", 511 | "ryu", 512 | "serde", 513 | ] 514 | 515 | [[package]] 516 | name = "serde_with" 517 | version = "1.6.2" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "42fa8fb0da0bf5aa7dd5d8fe1f9ec769833eb7cf97ff89942903809e600de908" 520 | dependencies = [ 521 | "serde", 522 | "serde_json", 523 | "serde_with_macros", 524 | ] 525 | 526 | [[package]] 527 | name = "serde_with_macros" 528 | version = "1.3.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "1197ff7de45494f290c1e3e1a6f80e108974681984c87a3e480991ef3d0f1950" 531 | dependencies = [ 532 | "darling", 533 | "proc-macro2 1.0.24", 534 | "quote 1.0.8", 535 | "syn 1.0.60", 536 | ] 537 | 538 | [[package]] 539 | name = "smallvec" 540 | version = "1.6.1" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 543 | 544 | [[package]] 545 | name = "strsim" 546 | version = "0.9.3" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 549 | 550 | [[package]] 551 | name = "syn" 552 | version = "0.15.44" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" 555 | dependencies = [ 556 | "proc-macro2 0.4.30", 557 | "quote 0.6.13", 558 | "unicode-xid 0.1.0", 559 | ] 560 | 561 | [[package]] 562 | name = "syn" 563 | version = "1.0.60" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" 566 | dependencies = [ 567 | "proc-macro2 1.0.24", 568 | "quote 1.0.8", 569 | "unicode-xid 0.2.1", 570 | ] 571 | 572 | [[package]] 573 | name = "target-lexicon" 574 | version = "0.11.1" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "4ee5a98e506fb7231a304c3a1bd7c132a55016cf65001e0282480665870dfcb9" 577 | 578 | [[package]] 579 | name = "test-generator" 580 | version = "0.3.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "ea97be90349ab3574f6e74d1566e1c5dd3a4bc74b89f4af4cc10ca010af103c0" 583 | dependencies = [ 584 | "glob", 585 | "proc-macro2 0.4.30", 586 | "quote 0.6.13", 587 | "syn 0.15.44", 588 | ] 589 | 590 | [[package]] 591 | name = "thiserror" 592 | version = "1.0.23" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" 595 | dependencies = [ 596 | "thiserror-impl", 597 | ] 598 | 599 | [[package]] 600 | name = "thiserror-impl" 601 | version = "1.0.23" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" 604 | dependencies = [ 605 | "proc-macro2 1.0.24", 606 | "quote 1.0.8", 607 | "syn 1.0.60", 608 | ] 609 | 610 | [[package]] 611 | name = "time" 612 | version = "0.1.44" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 615 | dependencies = [ 616 | "libc", 617 | "wasi", 618 | "winapi", 619 | ] 620 | 621 | [[package]] 622 | name = "unicode-xid" 623 | version = "0.1.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 626 | 627 | [[package]] 628 | name = "unicode-xid" 629 | version = "0.2.1" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 632 | 633 | [[package]] 634 | name = "wasi" 635 | version = "0.10.0+wasi-snapshot-preview1" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 638 | 639 | [[package]] 640 | name = "winapi" 641 | version = "0.3.9" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 644 | dependencies = [ 645 | "winapi-i686-pc-windows-gnu", 646 | "winapi-x86_64-pc-windows-gnu", 647 | ] 648 | 649 | [[package]] 650 | name = "winapi-i686-pc-windows-gnu" 651 | version = "0.4.0" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 654 | 655 | [[package]] 656 | name = "winapi-x86_64-pc-windows-gnu" 657 | version = "0.4.0" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 660 | 661 | [[package]] 662 | name = "zip" 663 | version = "0.5.9" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "cc2896475a242c41366941faa27264df2cb935185a92e059a004d0048feb2ac5" 666 | dependencies = [ 667 | "byteorder", 668 | "bzip2", 669 | "crc32fast", 670 | "flate2", 671 | "thiserror", 672 | "time", 673 | ] 674 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scratchc" 3 | version = "0.1.0" 4 | authors = ["snek"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | zip = "0.5" 9 | serde_json = "1.0" 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_with = { version = "1.6", features = ["json"] } 12 | cranelift = "0.69" 13 | cranelift-object = "0.69" 14 | cranelift-module = "0.69" 15 | cranelift-preopt = "0.69" 16 | cranelift-native = "0.69" 17 | 18 | [dev-dependencies] 19 | test-generator = "0.3.0" 20 | pretty_assertions = "0.6" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 devsnek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scratchc 2 | 3 | Compile [Scratch][] SB3 files to native executables. 4 | 5 | [Scratch]: https://scratch.mit.edu/ 6 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); 3 | 4 | println!("cargo:rerun-if-changed=./support.rs"); 5 | let o = std::process::Command::new("rustc") 6 | .args(&[ 7 | "-O", 8 | "./support.rs", 9 | "-o", 10 | out_dir.join("libsupport.a").to_str().unwrap(), 11 | "--print", 12 | "native-static-libs", 13 | ]) 14 | .output() 15 | .unwrap(); 16 | 17 | if !o.status.success() { 18 | panic!("{}", String::from_utf8(o.stderr).unwrap()); 19 | } 20 | 21 | let stderr = String::from_utf8(o.stderr).unwrap(); 22 | let static_libs: Vec = stderr 23 | .lines() 24 | .find(|l| l.starts_with("note: native-static-libs:")) 25 | .unwrap() 26 | .split(": ") 27 | .nth(2) 28 | .unwrap() 29 | .split(' ') 30 | .map(|s| s.to_owned()) 31 | .collect(); 32 | 33 | std::fs::write( 34 | out_dir.join("libsupport_libs.rs"), 35 | format!("&{:?}", static_libs), 36 | ) 37 | .unwrap(); 38 | } 39 | -------------------------------------------------------------------------------- /src/compiler.rs: -------------------------------------------------------------------------------- 1 | use crate::scratch; 2 | use cranelift::prelude::*; 3 | use cranelift_module::Module; 4 | use std::collections::HashMap; 5 | 6 | struct Compiler { 7 | module: M, 8 | data_id_counter: usize, 9 | var_id_counter: usize, 10 | scratch_vars: HashMap, 11 | procedures: HashMap, 12 | } 13 | 14 | impl Compiler { 15 | fn new(module: M) -> Compiler { 16 | Compiler { 17 | module, 18 | data_id_counter: 0, 19 | var_id_counter: 0, 20 | scratch_vars: HashMap::new(), 21 | procedures: HashMap::new(), 22 | } 23 | } 24 | 25 | fn compile_func( 26 | &mut self, 27 | name: &str, 28 | params: &[Type], 29 | ret: Option, 30 | export: bool, 31 | builder: F, 32 | ) -> cranelift_module::FuncId 33 | where 34 | F: Fn(&mut Compiler, &mut FunctionBuilder, cranelift_module::FuncId), 35 | { 36 | let mut sig = self.module.make_signature(); 37 | for param in params { 38 | sig.params.push(AbiParam::new(*param)); 39 | } 40 | if let Some(ret) = ret { 41 | sig.returns.push(AbiParam::new(ret)); 42 | } 43 | 44 | let func_id = self 45 | .module 46 | .declare_function( 47 | name, 48 | if export { 49 | cranelift_module::Linkage::Export 50 | } else { 51 | cranelift_module::Linkage::Local 52 | }, 53 | &sig, 54 | ) 55 | .unwrap(); 56 | 57 | let mut ctx = self.module.make_context(); 58 | let mut fn_builder_ctx = FunctionBuilderContext::new(); 59 | ctx.func = cranelift::codegen::ir::Function::with_name_signature( 60 | ExternalName::testcase(name), 61 | sig, 62 | ); 63 | 64 | let mut f = FunctionBuilder::new(&mut ctx.func, &mut fn_builder_ctx); 65 | 66 | builder(self, &mut f, func_id); 67 | 68 | f.seal_all_blocks(); 69 | f.finalize(); 70 | 71 | cranelift::codegen::verifier::verify_function(&ctx.func, self.module.isa().flags()) 72 | .unwrap(); 73 | 74 | // println!("{}", ctx.func.display(None)); 75 | 76 | self.module 77 | .define_function( 78 | func_id, 79 | &mut ctx, 80 | &mut cranelift::codegen::binemit::NullTrapSink {}, 81 | ) 82 | .unwrap(); 83 | 84 | func_id 85 | } 86 | 87 | fn new_var(&mut self) -> Variable { 88 | let id = self.var_id_counter; 89 | self.var_id_counter += 1; 90 | Variable::new(id) 91 | } 92 | 93 | fn create_data(&mut self, data: Box<[u8]>) -> cranelift_module::DataId { 94 | let data_id = self 95 | .module 96 | .declare_data( 97 | &format!("data_{}", { 98 | let id = self.data_id_counter; 99 | self.data_id_counter += 1; 100 | id 101 | }), 102 | cranelift_module::Linkage::Local, 103 | false, 104 | false, 105 | ) 106 | .unwrap(); 107 | let mut ctx = cranelift_module::DataContext::new(); 108 | ctx.define(data); 109 | self.module.define_data(data_id, &ctx).unwrap(); 110 | data_id 111 | } 112 | 113 | fn import_func( 114 | &mut self, 115 | name: &str, 116 | params: &[Type], 117 | ret: Option, 118 | f: &mut FunctionBuilder, 119 | ) -> cranelift::codegen::ir::FuncRef { 120 | let mut sig = self.module.make_signature(); 121 | for param in params { 122 | sig.params.push(AbiParam::new(*param)); 123 | } 124 | if let Some(ret) = ret { 125 | sig.returns.push(AbiParam::new(ret)); 126 | } 127 | let func = self 128 | .module 129 | .declare_function(name, cranelift_module::Linkage::Import, &sig) 130 | .unwrap(); 131 | self.module.declare_func_in_func(func, f.func) 132 | } 133 | 134 | fn create_scratch_var(&mut self, name: &str) { 135 | let data_id = self 136 | .module 137 | .declare_data(name, cranelift_module::Linkage::Local, true, false) 138 | .unwrap(); 139 | let mut ctx = cranelift_module::DataContext::new(); 140 | ctx.define(Box::new([0; std::mem::size_of::()])); 141 | self.module.define_data(data_id, &ctx).unwrap(); 142 | self.scratch_vars.insert(name.to_owned(), data_id); 143 | } 144 | 145 | fn scratch_var_ptr(&mut self, name: &str, f: &mut FunctionBuilder) -> Value { 146 | let data_id = self.scratch_vars[name]; 147 | let data_ref = self.module.declare_data_in_func(data_id, f.func); 148 | f.ins() 149 | .global_value(self.module.target_config().pointer_type(), data_ref) 150 | } 151 | 152 | fn load_scratch_var(&mut self, name: &str, f: &mut FunctionBuilder) -> Value { 153 | let ptr = self.scratch_var_ptr(name, f); 154 | f.ins().load(types::F64, MemFlags::new(), ptr, 0) 155 | } 156 | 157 | fn store_scratch_var(&mut self, name: &str, val: Value, f: &mut FunctionBuilder) { 158 | let ptr = self.scratch_var_ptr(name, f); 159 | f.ins().store(MemFlags::new(), val, ptr, 0); 160 | } 161 | } 162 | 163 | struct BlockCompiler<'a, 'b, M: Module> { 164 | c: &'b mut Compiler, 165 | f: &'b mut FunctionBuilder<'a>, 166 | ends: Vec, 167 | args: HashMap, 168 | } 169 | 170 | impl<'a, 'b, M: Module> BlockCompiler<'a, 'b, M> { 171 | fn fall_off_end(&mut self) { 172 | match self.ends.last() { 173 | Some(b) => self.f.ins().jump(*b, &[]), 174 | None => self.f.ins().return_(&[]), 175 | }; 176 | } 177 | 178 | fn import_func( 179 | &mut self, 180 | name: &str, 181 | params: &[Type], 182 | ret: Option, 183 | ) -> cranelift::codegen::ir::FuncRef { 184 | self.c.import_func(name, params, ret, self.f) 185 | } 186 | } 187 | 188 | impl scratch::Value { 189 | fn build(&self, c: &mut BlockCompiler) -> Value { 190 | match self { 191 | scratch::Value::Number(n) => c.f.ins().f64const(*n), 192 | scratch::Value::String(s) => match s.parse::() { 193 | Ok(n) => c.f.ins().f64const(n), 194 | Err(_) => c.f.ins().f64const(0.0), 195 | }, 196 | scratch::Value::Load(id) => c.c.load_scratch_var(id, c.f), 197 | scratch::Value::Expression(b) => match &**b { 198 | scratch::BlockExpression::OperatorEquals { left, right } => { 199 | let a1 = left.build(c); 200 | let a2 = right.build(c); 201 | c.f.ins().fcmp(FloatCC::Equal, a1, a2) 202 | } 203 | scratch::BlockExpression::OperatorGT { left, right } => { 204 | let a1 = left.build(c); 205 | let a2 = right.build(c); 206 | c.f.ins().fcmp(FloatCC::GreaterThan, a1, a2) 207 | } 208 | scratch::BlockExpression::OperatorAdd { left, right } => { 209 | let a1 = left.build(c); 210 | let a2 = right.build(c); 211 | c.f.ins().fadd(a1, a2) 212 | } 213 | scratch::BlockExpression::OperatorSubtract { left, right } => { 214 | let a1 = left.build(c); 215 | let a2 = right.build(c); 216 | c.f.ins().fsub(a1, a2) 217 | } 218 | scratch::BlockExpression::ArgumentReporterStringNumber { name } => { 219 | let var = c.args[name]; 220 | c.f.use_var(var) 221 | } 222 | }, 223 | } 224 | } 225 | } 226 | 227 | impl scratch::Block { 228 | fn build(&self, c: &mut BlockCompiler, block: Block) { 229 | c.f.switch_to_block(block); 230 | 231 | match &self.op { 232 | scratch::BlockOp::ControlRepeat { times, body } => { 233 | let head = c.f.create_block(); 234 | let bbody = c.f.create_block(); 235 | let bnext = c.f.create_block(); 236 | let vtimes = c.c.new_var(); 237 | 238 | { 239 | c.f.declare_var(vtimes, types::I32); 240 | 241 | let tmp = times.build(c); 242 | let tmp = c.f.ins().fcvt_to_uint(types::I32, tmp); 243 | let one = c.f.ins().iconst(types::I32, 1); 244 | let tmp = c.f.ins().iadd(tmp, one); 245 | c.f.def_var(vtimes, tmp); 246 | 247 | c.f.ins().jump(head, &[]); 248 | } 249 | 250 | { 251 | c.f.switch_to_block(head); 252 | let a1 = c.f.use_var(vtimes); 253 | let a2 = c.f.ins().iconst(types::I32, 1); 254 | let tmp = c.f.ins().isub(a1, a2); 255 | c.f.def_var(vtimes, tmp); 256 | c.f.ins().brz(tmp, bnext, &[]); 257 | c.f.ins().jump(bbody, &[]); 258 | } 259 | 260 | c.ends.push(head); 261 | body.build(c, bbody); 262 | c.ends.pop(); 263 | 264 | if let Some(next) = &self.next { 265 | next.build(c, bnext); 266 | } else { 267 | c.f.switch_to_block(bnext); 268 | c.fall_off_end(); 269 | } 270 | } 271 | scratch::BlockOp::ControlForever(body) => { 272 | c.ends.push(block); 273 | body.build(c, block); 274 | c.ends.pop(); 275 | } 276 | scratch::BlockOp::ControlWait(delay) => { 277 | let sleep = c.import_func("support_sleep", &[types::F64], None); 278 | 279 | let tmp = delay.build(c); 280 | c.f.ins().call(sleep, &[tmp]); 281 | } 282 | scratch::BlockOp::ControlIfElse { 283 | condition, 284 | consequent, 285 | alternative, 286 | } => { 287 | let bcons = c.f.create_block(); 288 | let balt = c.f.create_block(); 289 | let bnext = c.f.create_block(); 290 | 291 | let tmp = condition.build(c); 292 | if alternative.is_some() { 293 | c.f.ins().brz(tmp, balt, &[]); 294 | } else { 295 | c.f.ins().brz(tmp, bnext, &[]); 296 | } 297 | c.f.ins().jump(bcons, &[]); 298 | 299 | c.ends.push(bnext); 300 | consequent.build(c, bcons); 301 | if let Some(alternative) = alternative { 302 | alternative.build(c, balt); 303 | } 304 | c.ends.pop(); 305 | 306 | if let Some(next) = &self.next { 307 | next.build(c, bnext); 308 | } else { 309 | c.f.switch_to_block(bnext); 310 | c.fall_off_end(); 311 | } 312 | } 313 | scratch::BlockOp::ControlStopAll => { 314 | let libc_exit = c.import_func("exit", &[types::I32], None); 315 | let detach_scripts = c.import_func("support_detach_scripts", &[], None); 316 | 317 | c.f.ins().call(detach_scripts, &[]); 318 | 319 | let tmp = c.f.ins().iconst(types::I32, 0); 320 | c.f.ins().call(libc_exit, &[tmp]); 321 | 322 | c.f.ins().trap(TrapCode::UnreachableCodeReached); 323 | } 324 | scratch::BlockOp::ControlStopScript => { 325 | c.f.ins().return_(&[]); 326 | } 327 | scratch::BlockOp::LooksSay(s) => { 328 | let p = c.c.module.target_config().pointer_type(); 329 | match s { 330 | scratch::Value::String(s) => { 331 | let libc_write = c.import_func("write", &[types::I32, p, p], Some(p)); 332 | 333 | let fd = c.f.ins().iconst(types::I32, 1); 334 | 335 | let s = format!("{}\n", s); 336 | 337 | let data = c.c.create_data(s.as_bytes().into()); 338 | let tmp = c.c.module.declare_data_in_func(data, &mut c.f.func); 339 | let ptr = c.f.ins().global_value(p, tmp); 340 | 341 | let len = c.f.ins().iconst(p, s.len() as i64); 342 | 343 | c.f.ins().call(libc_write, &[fd, ptr, len]); 344 | } 345 | _ => { 346 | let write_float = c.import_func("support_write_float", &[types::F64], None); 347 | let tmp = s.build(c); 348 | c.f.ins().call(write_float, &[tmp]); 349 | } 350 | }; 351 | } 352 | scratch::BlockOp::EventWhenFlagClicked => {} 353 | scratch::BlockOp::DataSetVariableTo { id, value } => { 354 | let val = value.build(c); 355 | c.c.store_scratch_var(id, val, c.f); 356 | } 357 | scratch::BlockOp::DataChangeVariableBy { id, value } => { 358 | let val = c.c.load_scratch_var(id, c.f); 359 | let dif = value.build(c); 360 | let val = c.f.ins().fadd(val, dif); 361 | c.c.store_scratch_var(id, val, c.f); 362 | } 363 | scratch::BlockOp::ProceduresCall { proc, args } => { 364 | let mut arguments = vec![]; 365 | for v in args { 366 | arguments.push(v.build(c)); 367 | } 368 | let tmp = 369 | c.c.module 370 | .declare_func_in_func(c.c.procedures[proc], c.f.func); 371 | c.f.ins().call(tmp, &arguments); 372 | } 373 | } 374 | 375 | if !c.f.is_filled() { 376 | if let Some(next) = &self.next { 377 | let bnext = c.f.create_block(); 378 | c.f.ins().jump(bnext, &[]); 379 | next.build(c, bnext); 380 | } else { 381 | c.fall_off_end(); 382 | } 383 | } 384 | } 385 | } 386 | 387 | pub fn compile( 388 | m: &mut impl Module, 389 | variables: &[String], 390 | procedures: &[scratch::Procedure], 391 | scripts: &[scratch::Block], 392 | ) { 393 | let mut compiler = Compiler::new(m); 394 | 395 | let mut script_funcs = vec![]; 396 | 397 | for var in variables { 398 | compiler.create_scratch_var(var); 399 | } 400 | 401 | for proc in procedures { 402 | compiler.compile_func( 403 | &format!("proc_{}", proc.id), 404 | &vec![types::F64; proc.arguments.len()], 405 | None, 406 | false, 407 | |c, f, func_id| { 408 | c.procedures.insert(proc.id.clone(), func_id); // insert here to support recursion 409 | 410 | let block = f.create_block(); 411 | f.append_block_params_for_function_params(block); 412 | f.switch_to_block(block); 413 | let mut args = HashMap::new(); 414 | for (i, name) in proc.arguments.iter().enumerate() { 415 | let var = c.new_var(); 416 | f.declare_var(var, types::F64); 417 | let val = f.block_params(block)[i]; 418 | f.def_var(var, val); 419 | args.insert(name.to_owned(), var); 420 | } 421 | 422 | let mut bc = BlockCompiler { 423 | c, 424 | f, 425 | ends: Vec::new(), 426 | args, 427 | }; 428 | proc.body.build(&mut bc, block); 429 | }, 430 | ); 431 | } 432 | 433 | for (i, script) in scripts.iter().enumerate() { 434 | let func_id = 435 | compiler.compile_func(&format!("script_{}", i), &[], None, false, |c, f, _| { 436 | let mut bc = BlockCompiler { 437 | c, 438 | f, 439 | ends: Vec::new(), 440 | args: HashMap::new(), 441 | }; 442 | let block = bc.f.create_block(); 443 | script.build(&mut bc, block); 444 | }); 445 | 446 | script_funcs.push(func_id); 447 | } 448 | 449 | compiler.compile_func("main", &[], Some(types::I32), true, |compiler, f, _| { 450 | let block = f.create_block(); 451 | f.switch_to_block(block); 452 | 453 | let spawn_script = compiler.import_func( 454 | "support_spawn_script", 455 | &[compiler.module.target_config().pointer_type()], 456 | None, 457 | f, 458 | ); 459 | 460 | for func_id in &script_funcs { 461 | let tmp = compiler.module.declare_func_in_func(*func_id, &mut f.func); 462 | let tmp = f 463 | .ins() 464 | .func_addr(compiler.module.target_config().pointer_type(), tmp); 465 | f.ins().call(spawn_script, &[tmp]); 466 | } 467 | 468 | let join_scripts = compiler.import_func("support_join_scripts", &[], None, f); 469 | f.ins().call(join_scripts, &[]); 470 | 471 | let tmp = f.ins().iconst(types::I32, 0); 472 | f.ins().return_(&[tmp]); 473 | }); 474 | } 475 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod compiler; 2 | mod scratch; 3 | 4 | pub fn compile( 5 | module: &mut impl cranelift_module::Module, 6 | file: impl std::io::Read + std::io::Seek, 7 | ) { 8 | let project = scratch::ProjectInfo::new(file).unwrap(); 9 | 10 | let mut variables = vec![]; 11 | let mut procedures = vec![]; 12 | let mut scripts = vec![]; 13 | 14 | for target in project.targets { 15 | let target = scratch::Target::hydrate(target); 16 | for var in target.variables.keys() { 17 | variables.push(var.clone()); 18 | } 19 | 20 | for proc in target.procedures { 21 | procedures.push(proc); 22 | } 23 | 24 | for script in target.scripts { 25 | scripts.push(script); 26 | } 27 | } 28 | 29 | compiler::compile(module, &variables, &procedures, &scripts); 30 | } 31 | 32 | pub fn compile_native(file: impl std::io::Read + std::io::Seek, out_name: &str) { 33 | let o = { 34 | use cranelift::prelude::*; 35 | 36 | let mut flag_builder = settings::builder(); 37 | flag_builder.set("is_pic", "true").unwrap(); 38 | flag_builder.set("opt_level", "speed_and_size").unwrap(); 39 | let flags = settings::Flags::new(flag_builder); 40 | 41 | let isa = cranelift_native::builder().unwrap().finish(flags); 42 | 43 | let mut module = cranelift_object::ObjectModule::new( 44 | cranelift_object::ObjectBuilder::new( 45 | isa, 46 | "", 47 | cranelift_module::default_libcall_names(), 48 | ) 49 | .unwrap(), 50 | ); 51 | 52 | compile(&mut module, file); 53 | 54 | module.finish().emit().unwrap() 55 | }; 56 | 57 | // FIXME: this is terrible 58 | let tmp = std::env::temp_dir(); 59 | std::fs::write(tmp.join("out.o"), o).unwrap(); 60 | std::fs::write( 61 | tmp.join("libsupport.a"), 62 | include_bytes!(concat!(env!("OUT_DIR"), "/libsupport.a")), 63 | ) 64 | .unwrap(); 65 | 66 | let mut args = vec![ 67 | "-O3".to_owned(), 68 | tmp.join("out.o").to_str().unwrap().to_owned(), 69 | tmp.join("libsupport.a").to_str().unwrap().to_owned(), 70 | "-o".to_owned(), 71 | out_name.to_owned(), 72 | ]; 73 | for arg in include!(concat!(env!("OUT_DIR"), "/libsupport_libs.rs")) { 74 | args.push(arg.to_string()); 75 | } 76 | 77 | let r = std::process::Command::new("clang++") 78 | .args(args) 79 | .output() 80 | .unwrap(); 81 | 82 | if !r.status.success() { 83 | panic!("{}", String::from_utf8(r.stderr).unwrap()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let file = std::env::args().nth(1).unwrap(); 3 | let out_name = std::env::args().nth(2).unwrap(); 4 | let file = std::fs::File::open(file).unwrap(); 5 | 6 | scratchc::compile_native(file, &out_name); 7 | } 8 | -------------------------------------------------------------------------------- /src/scratch.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(serde::Deserialize, Debug)] 4 | pub struct ProjectInfo { 5 | pub targets: Vec, 6 | pub extensions: Vec, 7 | pub meta: serde_json::Value, 8 | } 9 | 10 | impl ProjectInfo { 11 | pub fn new( 12 | data: impl std::io::Read + std::io::Seek, 13 | ) -> Result> { 14 | let mut archive = zip::ZipArchive::new(data)?; 15 | let mut file = archive.by_name("project.json")?; 16 | let mut source = Vec::new(); 17 | std::io::copy(&mut file, &mut source)?; 18 | Ok(serde_json::from_slice(&source)?) 19 | } 20 | } 21 | 22 | #[derive(serde::Deserialize, Debug)] 23 | pub struct TargetInfo { 24 | #[serde(rename = "isStage")] 25 | pub is_stage: bool, 26 | pub name: String, 27 | pub variables: HashMap, 28 | pub lists: serde_json::Value, 29 | pub broadcasts: serde_json::Value, 30 | pub blocks: HashMap, 31 | pub comments: serde_json::Value, 32 | } 33 | 34 | #[derive(serde::Deserialize, Debug)] 35 | pub struct BlockInfo { 36 | pub opcode: String, 37 | pub next: Option, 38 | pub parent: Option, 39 | pub inputs: HashMap, 40 | pub fields: HashMap, 41 | pub mutation: Option, 42 | pub shadow: bool, 43 | #[serde(rename = "topLevel")] 44 | pub top_level: bool, 45 | } 46 | 47 | #[derive(serde::Deserialize, Debug)] 48 | struct NestedArguments(#[serde(with = "serde_with::json::nested")] Vec); 49 | 50 | #[derive(serde::Deserialize, Debug)] 51 | pub struct MutationInfo { 52 | argumentnames: Option, 53 | argumentids: Option, 54 | proccode: Option, 55 | } 56 | 57 | #[derive(Debug)] 58 | pub struct Target { 59 | pub variables: HashMap, 60 | pub scripts: Vec, 61 | pub procedures: Vec, 62 | } 63 | 64 | impl Target { 65 | pub fn hydrate(i: TargetInfo) -> Self { 66 | let mut scripts = vec![]; 67 | let mut procedures = vec![]; 68 | for b in i.blocks.values() { 69 | if b.opcode == "procedures_definition" { 70 | let body = build_block(&i.blocks[b.next.as_ref().unwrap()], &i.blocks); 71 | 72 | let prototype = &i.blocks[b.inputs["custom_block"][1].as_str().unwrap()]; 73 | let MutationInfo { 74 | argumentnames, 75 | proccode, 76 | .. 77 | } = prototype.mutation.as_ref().unwrap(); 78 | procedures.push(Procedure { 79 | id: proccode.clone().unwrap(), 80 | arguments: argumentnames.as_ref().unwrap().0.clone(), 81 | body, 82 | }); 83 | } else if b.top_level { 84 | scripts.push(build_block(b, &i.blocks)); 85 | } 86 | } 87 | Target { 88 | variables: i.variables, 89 | scripts, 90 | procedures, 91 | } 92 | } 93 | } 94 | 95 | #[derive(Debug)] 96 | pub struct Procedure { 97 | pub id: String, 98 | pub arguments: Vec, 99 | pub body: Block, 100 | } 101 | 102 | #[derive(Debug, Clone)] 103 | pub enum Value { 104 | Number(f64), 105 | String(String), 106 | Load(String), 107 | Expression(Box), 108 | } 109 | 110 | impl Value { 111 | pub fn hydrate(v: &serde_json::Value, blocks: &HashMap) -> Value { 112 | if v[1].is_array() { 113 | let kind = v[1][0].as_u64().unwrap(); 114 | match kind { 115 | 4 | 5 | 6 | 7 => Value::Number(v[1][1].as_str().unwrap().parse().unwrap()), 116 | 10 => Value::String(v[1][1].as_str().unwrap().to_owned()), 117 | 12 => Value::Load(v[1][2].as_str().unwrap().to_owned()), 118 | _ => panic!("{:#?}", v), 119 | } 120 | } else { 121 | Value::Expression(Box::new(build_block_expr( 122 | &blocks[v[1].as_str().unwrap()], 123 | blocks, 124 | ))) 125 | } 126 | } 127 | } 128 | 129 | #[derive(Debug, Clone)] 130 | pub enum BlockOp { 131 | ControlRepeat { 132 | times: Value, 133 | body: Box, 134 | }, 135 | ControlForever(Box), 136 | ControlWait(Value), 137 | ControlIfElse { 138 | condition: Value, 139 | consequent: Box, 140 | alternative: Option>, 141 | }, 142 | ControlStopAll, 143 | ControlStopScript, 144 | LooksSay(Value), 145 | EventWhenFlagClicked, 146 | DataSetVariableTo { 147 | id: String, 148 | value: Value, 149 | }, 150 | DataChangeVariableBy { 151 | id: String, 152 | value: Value, 153 | }, 154 | ProceduresCall { 155 | proc: String, 156 | args: Vec, 157 | }, 158 | } 159 | 160 | #[derive(Debug, Clone)] 161 | pub struct Block { 162 | pub op: BlockOp, 163 | pub next: Option>, 164 | } 165 | 166 | fn build_block(b: &BlockInfo, blocks: &HashMap) -> Block { 167 | let op = match b.opcode.as_str() { 168 | "control_repeat" => BlockOp::ControlRepeat { 169 | times: Value::hydrate(&b.inputs["TIMES"], blocks), 170 | body: Box::new(build_block( 171 | &blocks[b.inputs["SUBSTACK"][1].as_str().unwrap()], 172 | blocks, 173 | )), 174 | }, 175 | "control_forever" => BlockOp::ControlForever(Box::new(build_block( 176 | &blocks[b.inputs["SUBSTACK"][1].as_str().unwrap()], 177 | blocks, 178 | ))), 179 | "control_wait" => BlockOp::ControlWait(Value::hydrate(&b.inputs["DURATION"], blocks)), 180 | "control_if_else" => BlockOp::ControlIfElse { 181 | condition: Value::hydrate(&b.inputs["CONDITION"], blocks), 182 | consequent: Box::new(build_block( 183 | &blocks[b.inputs["SUBSTACK"][1].as_str().unwrap()], 184 | blocks, 185 | )), 186 | alternative: Some(Box::new(build_block( 187 | &blocks[b.inputs["SUBSTACK2"][1].as_str().unwrap()], 188 | blocks, 189 | ))), 190 | }, 191 | "control_if" => BlockOp::ControlIfElse { 192 | condition: Value::hydrate(&b.inputs["CONDITION"], blocks), 193 | consequent: Box::new(build_block( 194 | &blocks[b.inputs["SUBSTACK"][1].as_str().unwrap()], 195 | blocks, 196 | )), 197 | alternative: None, 198 | }, 199 | "control_stop" => match b.fields["STOP_OPTION"][0].as_str().unwrap() { 200 | "all" => BlockOp::ControlStopAll, 201 | "this script" => BlockOp::ControlStopScript, 202 | _ => unreachable!("{:?}", b), 203 | }, 204 | "looks_say" => BlockOp::LooksSay(Value::hydrate(&b.inputs["MESSAGE"], blocks)), 205 | "looks_sayforsecs" => { 206 | return Block { 207 | op: BlockOp::LooksSay(Value::hydrate(&b.inputs["MESSAGE"], blocks)), 208 | next: Some(Box::new(Block { 209 | op: BlockOp::ControlWait(Value::hydrate(&b.inputs["SECS"], blocks)), 210 | next: if let Some(id) = &b.next { 211 | Some(Box::new(build_block(&blocks[id], blocks))) 212 | } else { 213 | None 214 | }, 215 | })), 216 | }; 217 | } 218 | "event_whenflagclicked" => BlockOp::EventWhenFlagClicked, 219 | "data_setvariableto" => BlockOp::DataSetVariableTo { 220 | id: b.fields["VARIABLE"][1].as_str().unwrap().to_owned(), 221 | value: Value::hydrate(&b.inputs["VALUE"], blocks), 222 | }, 223 | "data_changevariableby" => BlockOp::DataChangeVariableBy { 224 | id: b.fields["VARIABLE"][1].as_str().unwrap().to_owned(), 225 | value: Value::hydrate(&b.inputs["VALUE"], blocks), 226 | }, 227 | "procedures_call" => BlockOp::ProceduresCall { 228 | proc: b.mutation.as_ref().unwrap().proccode.clone().unwrap(), 229 | args: b 230 | .mutation 231 | .as_ref() 232 | .unwrap() 233 | .argumentids 234 | .as_ref() 235 | .unwrap() 236 | .0 237 | .iter() 238 | .map(|id| Value::hydrate(&b.inputs[id], blocks)) 239 | .collect(), 240 | }, 241 | _ => panic!("{:#?}", b), 242 | }; 243 | Block { 244 | op, 245 | next: if let Some(id) = &b.next { 246 | Some(Box::new(build_block(&blocks[id], blocks))) 247 | } else { 248 | None 249 | }, 250 | } 251 | } 252 | 253 | #[derive(Debug, Clone)] 254 | pub enum BlockExpression { 255 | OperatorEquals { left: Value, right: Value }, 256 | OperatorGT { left: Value, right: Value }, 257 | OperatorAdd { left: Value, right: Value }, 258 | OperatorSubtract { left: Value, right: Value }, 259 | ArgumentReporterStringNumber { name: String }, 260 | } 261 | 262 | fn build_block_expr(b: &BlockInfo, blocks: &HashMap) -> BlockExpression { 263 | match b.opcode.as_str() { 264 | "operator_equals" => BlockExpression::OperatorEquals { 265 | left: Value::hydrate(&b.inputs["OPERAND1"], blocks), 266 | right: Value::hydrate(&b.inputs["OPERAND2"], blocks), 267 | }, 268 | "operator_gt" => BlockExpression::OperatorGT { 269 | left: Value::hydrate(&b.inputs["OPERAND1"], blocks), 270 | right: Value::hydrate(&b.inputs["OPERAND2"], blocks), 271 | }, 272 | "operator_add" => BlockExpression::OperatorAdd { 273 | left: Value::hydrate(&b.inputs["NUM1"], blocks), 274 | right: Value::hydrate(&b.inputs["NUM2"], blocks), 275 | }, 276 | "operator_subtract" => BlockExpression::OperatorSubtract { 277 | left: Value::hydrate(&b.inputs["NUM1"], blocks), 278 | right: Value::hydrate(&b.inputs["NUM2"], blocks), 279 | }, 280 | "argument_reporter_string_number" => BlockExpression::ArgumentReporterStringNumber { 281 | name: b.fields["VALUE"][0].as_str().unwrap().to_owned(), 282 | }, 283 | _ => panic!("{:#?}", b), 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /support.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "staticlib"] 2 | 3 | type ScriptFn = unsafe extern "C" fn() -> (); 4 | 5 | static mut THREADS: Vec> = Vec::new(); 6 | 7 | #[no_mangle] 8 | extern "C" fn support_spawn_script(f: ScriptFn) { 9 | unsafe { 10 | THREADS.push(std::thread::spawn(move || { 11 | f() 12 | })); 13 | } 14 | } 15 | 16 | #[no_mangle] 17 | extern "C" fn support_detach_scripts() { 18 | unsafe { THREADS.clear() } 19 | } 20 | 21 | #[no_mangle] 22 | extern "C" fn support_join_scripts() { 23 | unsafe { 24 | for t in THREADS.drain(0..) { 25 | t.join().unwrap(); 26 | } 27 | } 28 | } 29 | 30 | #[no_mangle] 31 | extern "C" fn support_write_float(f: f64) { 32 | println!("{}", f); 33 | } 34 | 35 | #[no_mangle] 36 | extern "C" fn support_sleep(s: f64) { 37 | std::thread::sleep(std::time::Duration::from_secs_f64(s)) 38 | } 39 | -------------------------------------------------------------------------------- /tests/out.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate pretty_assertions; 3 | 4 | #[test_generator::test_resources("tests/out/*.sb3")] 5 | fn test(test: &str) { 6 | let test = std::path::PathBuf::from(test); 7 | let file = std::fs::File::open(&test).unwrap(); 8 | 9 | let tmp = std::env::temp_dir() 10 | .join(test.file_name().unwrap()) 11 | .to_str() 12 | .unwrap() 13 | .to_owned(); 14 | 15 | scratchc::compile_native(file, &tmp); 16 | 17 | let o = std::process::Command::new(&tmp).output().unwrap(); 18 | 19 | assert!(o.status.success()); 20 | 21 | let mut out = test; 22 | out.set_extension("out"); 23 | assert_eq!( 24 | String::from_utf8(o.stdout).unwrap(), 25 | std::fs::read_to_string(&out).unwrap() 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /tests/out/fib_10_iter.out: -------------------------------------------------------------------------------- 1 | 55 2 | -------------------------------------------------------------------------------- /tests/out/fib_10_iter.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/fib_10_iter.sb3 -------------------------------------------------------------------------------- /tests/out/fib_10_recr.out: -------------------------------------------------------------------------------- 1 | 55 2 | -------------------------------------------------------------------------------- /tests/out/fib_10_recr.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/fib_10_recr.sb3 -------------------------------------------------------------------------------- /tests/out/forever_stop.out: -------------------------------------------------------------------------------- 1 | stop 2 | -------------------------------------------------------------------------------- /tests/out/forever_stop.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/forever_stop.sb3 -------------------------------------------------------------------------------- /tests/out/loop_if.out: -------------------------------------------------------------------------------- 1 | false 2 | false 3 | true 4 | -------------------------------------------------------------------------------- /tests/out/loop_if.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/loop_if.sb3 -------------------------------------------------------------------------------- /tests/out/multiscript.out: -------------------------------------------------------------------------------- 1 | thread 1 2 | thread 1 3 | thread 1 4 | thread 1 5 | thread 1 6 | thread 1 7 | thread 1 8 | thread 1 9 | thread 1 10 | thread 1 11 | thread 2 12 | thread 2 13 | thread 2 14 | thread 2 15 | thread 2 16 | thread 2 17 | thread 2 18 | thread 2 19 | thread 2 20 | thread 2 21 | -------------------------------------------------------------------------------- /tests/out/multiscript.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/multiscript.sb3 -------------------------------------------------------------------------------- /tests/out/stop_all.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/stop_all.out -------------------------------------------------------------------------------- /tests/out/stop_all.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/stop_all.sb3 -------------------------------------------------------------------------------- /tests/out/stop_script.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/stop_script.out -------------------------------------------------------------------------------- /tests/out/stop_script.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devsnek/scratchc/281b0251dc31596371156f6b18f2beb14ed43b2e/tests/out/stop_script.sb3 --------------------------------------------------------------------------------