├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── ucc ├── Cargo.toml ├── build.rs └── src │ ├── builtin.rs │ ├── core.rs │ ├── display.rs │ ├── interp.rs │ ├── lib.rs │ ├── parse.lalrpop │ ├── test_core.rs │ ├── test_interp.rs │ └── test_parse.rs ├── ucci-web ├── Cargo.toml ├── index.html ├── src │ └── lib.rs └── ucci.js └── ucci ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | ucci-web/pkg 3 | -------------------------------------------------------------------------------- /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 = "ahash" 7 | version = "0.7.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98" 10 | dependencies = [ 11 | "getrandom 0.2.3", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "aho-corasick" 18 | version = "0.7.18" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 21 | dependencies = [ 22 | "memchr", 23 | ] 24 | 25 | [[package]] 26 | name = "arrayref" 27 | version = "0.3.6" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 30 | 31 | [[package]] 32 | name = "arrayvec" 33 | version = "0.5.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 36 | 37 | [[package]] 38 | name = "ascii-canvas" 39 | version = "3.0.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 42 | dependencies = [ 43 | "term", 44 | ] 45 | 46 | [[package]] 47 | name = "atty" 48 | version = "0.2.14" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 51 | dependencies = [ 52 | "hermit-abi", 53 | "libc", 54 | "winapi", 55 | ] 56 | 57 | [[package]] 58 | name = "autocfg" 59 | version = "1.0.1" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 62 | 63 | [[package]] 64 | name = "base64" 65 | version = "0.13.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 68 | 69 | [[package]] 70 | name = "bit-set" 71 | version = "0.5.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" 74 | dependencies = [ 75 | "bit-vec", 76 | ] 77 | 78 | [[package]] 79 | name = "bit-vec" 80 | version = "0.6.3" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 83 | 84 | [[package]] 85 | name = "bitflags" 86 | version = "1.3.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 89 | 90 | [[package]] 91 | name = "blake2b_simd" 92 | version = "0.5.11" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 95 | dependencies = [ 96 | "arrayref", 97 | "arrayvec", 98 | "constant_time_eq", 99 | ] 100 | 101 | [[package]] 102 | name = "bumpalo" 103 | version = "3.7.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" 106 | 107 | [[package]] 108 | name = "byteorder" 109 | version = "1.4.3" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 112 | 113 | [[package]] 114 | name = "cc" 115 | version = "1.0.70" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" 118 | 119 | [[package]] 120 | name = "cfg-if" 121 | version = "0.1.10" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 124 | 125 | [[package]] 126 | name = "cfg-if" 127 | version = "1.0.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 130 | 131 | [[package]] 132 | name = "constant_time_eq" 133 | version = "0.1.5" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 136 | 137 | [[package]] 138 | name = "crossbeam-utils" 139 | version = "0.8.5" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 142 | dependencies = [ 143 | "cfg-if 1.0.0", 144 | "lazy_static", 145 | ] 146 | 147 | [[package]] 148 | name = "crunchy" 149 | version = "0.2.2" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 152 | 153 | [[package]] 154 | name = "diff" 155 | version = "0.1.12" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 158 | 159 | [[package]] 160 | name = "dirs" 161 | version = "1.0.5" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" 164 | dependencies = [ 165 | "libc", 166 | "redox_users 0.3.5", 167 | "winapi", 168 | ] 169 | 170 | [[package]] 171 | name = "dirs" 172 | version = "2.0.2" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 175 | dependencies = [ 176 | "cfg-if 0.1.10", 177 | "dirs-sys", 178 | ] 179 | 180 | [[package]] 181 | name = "dirs-next" 182 | version = "2.0.0" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 185 | dependencies = [ 186 | "cfg-if 1.0.0", 187 | "dirs-sys-next", 188 | ] 189 | 190 | [[package]] 191 | name = "dirs-sys" 192 | version = "0.3.6" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" 195 | dependencies = [ 196 | "libc", 197 | "redox_users 0.4.0", 198 | "winapi", 199 | ] 200 | 201 | [[package]] 202 | name = "dirs-sys-next" 203 | version = "0.1.2" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 206 | dependencies = [ 207 | "libc", 208 | "redox_users 0.4.0", 209 | "winapi", 210 | ] 211 | 212 | [[package]] 213 | name = "either" 214 | version = "1.6.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 217 | 218 | [[package]] 219 | name = "ena" 220 | version = "0.14.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" 223 | dependencies = [ 224 | "log", 225 | ] 226 | 227 | [[package]] 228 | name = "fixedbitset" 229 | version = "0.2.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" 232 | 233 | [[package]] 234 | name = "fnv" 235 | version = "1.0.7" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 238 | 239 | [[package]] 240 | name = "fxhash" 241 | version = "0.2.1" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 244 | dependencies = [ 245 | "byteorder", 246 | ] 247 | 248 | [[package]] 249 | name = "getrandom" 250 | version = "0.1.16" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 253 | dependencies = [ 254 | "cfg-if 1.0.0", 255 | "libc", 256 | "wasi 0.9.0+wasi-snapshot-preview1", 257 | ] 258 | 259 | [[package]] 260 | name = "getrandom" 261 | version = "0.2.3" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 264 | dependencies = [ 265 | "cfg-if 1.0.0", 266 | "libc", 267 | "wasi 0.10.2+wasi-snapshot-preview1", 268 | ] 269 | 270 | [[package]] 271 | name = "hashbrown" 272 | version = "0.11.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 275 | dependencies = [ 276 | "ahash", 277 | ] 278 | 279 | [[package]] 280 | name = "hermit-abi" 281 | version = "0.1.19" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 284 | dependencies = [ 285 | "libc", 286 | ] 287 | 288 | [[package]] 289 | name = "indexmap" 290 | version = "1.7.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 293 | dependencies = [ 294 | "autocfg", 295 | "hashbrown", 296 | ] 297 | 298 | [[package]] 299 | name = "itertools" 300 | version = "0.10.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" 303 | dependencies = [ 304 | "either", 305 | ] 306 | 307 | [[package]] 308 | name = "js-sys" 309 | version = "0.3.55" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 312 | dependencies = [ 313 | "wasm-bindgen", 314 | ] 315 | 316 | [[package]] 317 | name = "lalrpop" 318 | version = "0.19.6" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988" 321 | dependencies = [ 322 | "ascii-canvas", 323 | "atty", 324 | "bit-set", 325 | "diff", 326 | "ena", 327 | "itertools", 328 | "lalrpop-util", 329 | "petgraph", 330 | "pico-args", 331 | "regex", 332 | "regex-syntax", 333 | "string_cache", 334 | "term", 335 | "tiny-keccak", 336 | "unicode-xid", 337 | ] 338 | 339 | [[package]] 340 | name = "lalrpop-util" 341 | version = "0.19.6" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278" 344 | dependencies = [ 345 | "regex", 346 | ] 347 | 348 | [[package]] 349 | name = "lasso" 350 | version = "0.6.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "aeb7b21a526375c5ca55f1a6dfd4e1fad9fa4edd750f530252a718a44b2608f0" 353 | dependencies = [ 354 | "hashbrown", 355 | ] 356 | 357 | [[package]] 358 | name = "lazy_static" 359 | version = "1.4.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 362 | 363 | [[package]] 364 | name = "libc" 365 | version = "0.2.103" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" 368 | 369 | [[package]] 370 | name = "linefeed" 371 | version = "0.6.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "28715d08e35c6c074f9ae6b2e6a2420bac75d050c66ecd669d7d5b98e2caa036" 374 | dependencies = [ 375 | "dirs 1.0.5", 376 | "mortal", 377 | "winapi", 378 | ] 379 | 380 | [[package]] 381 | name = "log" 382 | version = "0.4.14" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 385 | dependencies = [ 386 | "cfg-if 1.0.0", 387 | ] 388 | 389 | [[package]] 390 | name = "memchr" 391 | version = "2.4.1" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 394 | 395 | [[package]] 396 | name = "mortal" 397 | version = "0.2.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "998fd6a991497275567703b6f435e27958b633878ec991f5734b96dd46675e9f" 400 | dependencies = [ 401 | "bitflags", 402 | "libc", 403 | "nix", 404 | "smallstr", 405 | "terminfo", 406 | "unicode-normalization", 407 | "unicode-width", 408 | "winapi", 409 | ] 410 | 411 | [[package]] 412 | name = "new_debug_unreachable" 413 | version = "1.0.4" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 416 | 417 | [[package]] 418 | name = "nix" 419 | version = "0.17.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 422 | dependencies = [ 423 | "bitflags", 424 | "cc", 425 | "cfg-if 0.1.10", 426 | "libc", 427 | "void", 428 | ] 429 | 430 | [[package]] 431 | name = "nom" 432 | version = "5.1.2" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" 435 | dependencies = [ 436 | "memchr", 437 | "version_check", 438 | ] 439 | 440 | [[package]] 441 | name = "once_cell" 442 | version = "1.8.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 445 | 446 | [[package]] 447 | name = "petgraph" 448 | version = "0.5.1" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" 451 | dependencies = [ 452 | "fixedbitset", 453 | "indexmap", 454 | ] 455 | 456 | [[package]] 457 | name = "phf" 458 | version = "0.8.0" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" 461 | dependencies = [ 462 | "phf_shared", 463 | ] 464 | 465 | [[package]] 466 | name = "phf_codegen" 467 | version = "0.8.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" 470 | dependencies = [ 471 | "phf_generator", 472 | "phf_shared", 473 | ] 474 | 475 | [[package]] 476 | name = "phf_generator" 477 | version = "0.8.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" 480 | dependencies = [ 481 | "phf_shared", 482 | "rand", 483 | ] 484 | 485 | [[package]] 486 | name = "phf_shared" 487 | version = "0.8.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 490 | dependencies = [ 491 | "siphasher", 492 | ] 493 | 494 | [[package]] 495 | name = "pico-args" 496 | version = "0.4.2" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" 499 | 500 | [[package]] 501 | name = "ppv-lite86" 502 | version = "0.2.10" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 505 | 506 | [[package]] 507 | name = "precomputed-hash" 508 | version = "0.1.1" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 511 | 512 | [[package]] 513 | name = "proc-macro2" 514 | version = "1.0.30" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" 517 | dependencies = [ 518 | "unicode-xid", 519 | ] 520 | 521 | [[package]] 522 | name = "quote" 523 | version = "1.0.10" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 526 | dependencies = [ 527 | "proc-macro2", 528 | ] 529 | 530 | [[package]] 531 | name = "rand" 532 | version = "0.7.3" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 535 | dependencies = [ 536 | "getrandom 0.1.16", 537 | "libc", 538 | "rand_chacha", 539 | "rand_core", 540 | "rand_hc", 541 | "rand_pcg", 542 | ] 543 | 544 | [[package]] 545 | name = "rand_chacha" 546 | version = "0.2.2" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 549 | dependencies = [ 550 | "ppv-lite86", 551 | "rand_core", 552 | ] 553 | 554 | [[package]] 555 | name = "rand_core" 556 | version = "0.5.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 559 | dependencies = [ 560 | "getrandom 0.1.16", 561 | ] 562 | 563 | [[package]] 564 | name = "rand_hc" 565 | version = "0.2.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 568 | dependencies = [ 569 | "rand_core", 570 | ] 571 | 572 | [[package]] 573 | name = "rand_pcg" 574 | version = "0.2.1" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 577 | dependencies = [ 578 | "rand_core", 579 | ] 580 | 581 | [[package]] 582 | name = "redox_syscall" 583 | version = "0.1.57" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 586 | 587 | [[package]] 588 | name = "redox_syscall" 589 | version = "0.2.10" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 592 | dependencies = [ 593 | "bitflags", 594 | ] 595 | 596 | [[package]] 597 | name = "redox_users" 598 | version = "0.3.5" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 601 | dependencies = [ 602 | "getrandom 0.1.16", 603 | "redox_syscall 0.1.57", 604 | "rust-argon2", 605 | ] 606 | 607 | [[package]] 608 | name = "redox_users" 609 | version = "0.4.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 612 | dependencies = [ 613 | "getrandom 0.2.3", 614 | "redox_syscall 0.2.10", 615 | ] 616 | 617 | [[package]] 618 | name = "regex" 619 | version = "1.5.4" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 622 | dependencies = [ 623 | "aho-corasick", 624 | "memchr", 625 | "regex-syntax", 626 | ] 627 | 628 | [[package]] 629 | name = "regex-syntax" 630 | version = "0.6.25" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 633 | 634 | [[package]] 635 | name = "rust-argon2" 636 | version = "0.8.3" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" 639 | dependencies = [ 640 | "base64", 641 | "blake2b_simd", 642 | "constant_time_eq", 643 | "crossbeam-utils", 644 | ] 645 | 646 | [[package]] 647 | name = "rustversion" 648 | version = "1.0.5" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" 651 | 652 | [[package]] 653 | name = "siphasher" 654 | version = "0.3.7" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 657 | 658 | [[package]] 659 | name = "smallstr" 660 | version = "0.2.0" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "1e922794d168678729ffc7e07182721a14219c65814e66e91b839a272fe5ae4f" 663 | dependencies = [ 664 | "smallvec", 665 | ] 666 | 667 | [[package]] 668 | name = "smallvec" 669 | version = "1.7.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 672 | 673 | [[package]] 674 | name = "string_cache" 675 | version = "0.8.1" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" 678 | dependencies = [ 679 | "lazy_static", 680 | "new_debug_unreachable", 681 | "phf_shared", 682 | "precomputed-hash", 683 | ] 684 | 685 | [[package]] 686 | name = "syn" 687 | version = "1.0.80" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 690 | dependencies = [ 691 | "proc-macro2", 692 | "quote", 693 | "unicode-xid", 694 | ] 695 | 696 | [[package]] 697 | name = "term" 698 | version = "0.7.0" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 701 | dependencies = [ 702 | "dirs-next", 703 | "rustversion", 704 | "winapi", 705 | ] 706 | 707 | [[package]] 708 | name = "terminfo" 709 | version = "0.7.3" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" 712 | dependencies = [ 713 | "dirs 2.0.2", 714 | "fnv", 715 | "nom", 716 | "phf", 717 | "phf_codegen", 718 | ] 719 | 720 | [[package]] 721 | name = "tiny-keccak" 722 | version = "2.0.2" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 725 | dependencies = [ 726 | "crunchy", 727 | ] 728 | 729 | [[package]] 730 | name = "tinyvec" 731 | version = "1.5.0" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" 734 | dependencies = [ 735 | "tinyvec_macros", 736 | ] 737 | 738 | [[package]] 739 | name = "tinyvec_macros" 740 | version = "0.1.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 743 | 744 | [[package]] 745 | name = "ucc" 746 | version = "0.1.0" 747 | dependencies = [ 748 | "fxhash", 749 | "lalrpop", 750 | "lalrpop-util", 751 | "lasso", 752 | "regex", 753 | ] 754 | 755 | [[package]] 756 | name = "ucci" 757 | version = "0.1.0" 758 | dependencies = [ 759 | "linefeed", 760 | "ucc", 761 | ] 762 | 763 | [[package]] 764 | name = "ucci-web" 765 | version = "0.1.0" 766 | dependencies = [ 767 | "js-sys", 768 | "ucc", 769 | "wasm-bindgen", 770 | ] 771 | 772 | [[package]] 773 | name = "unicode-normalization" 774 | version = "0.1.19" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 777 | dependencies = [ 778 | "tinyvec", 779 | ] 780 | 781 | [[package]] 782 | name = "unicode-width" 783 | version = "0.1.9" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 786 | 787 | [[package]] 788 | name = "unicode-xid" 789 | version = "0.2.2" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 792 | 793 | [[package]] 794 | name = "version_check" 795 | version = "0.9.3" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 798 | 799 | [[package]] 800 | name = "void" 801 | version = "1.0.2" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 804 | 805 | [[package]] 806 | name = "wasi" 807 | version = "0.9.0+wasi-snapshot-preview1" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 810 | 811 | [[package]] 812 | name = "wasi" 813 | version = "0.10.2+wasi-snapshot-preview1" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 816 | 817 | [[package]] 818 | name = "wasm-bindgen" 819 | version = "0.2.78" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 822 | dependencies = [ 823 | "cfg-if 1.0.0", 824 | "wasm-bindgen-macro", 825 | ] 826 | 827 | [[package]] 828 | name = "wasm-bindgen-backend" 829 | version = "0.2.78" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 832 | dependencies = [ 833 | "bumpalo", 834 | "lazy_static", 835 | "log", 836 | "proc-macro2", 837 | "quote", 838 | "syn", 839 | "wasm-bindgen-shared", 840 | ] 841 | 842 | [[package]] 843 | name = "wasm-bindgen-macro" 844 | version = "0.2.78" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 847 | dependencies = [ 848 | "quote", 849 | "wasm-bindgen-macro-support", 850 | ] 851 | 852 | [[package]] 853 | name = "wasm-bindgen-macro-support" 854 | version = "0.2.78" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 857 | dependencies = [ 858 | "proc-macro2", 859 | "quote", 860 | "syn", 861 | "wasm-bindgen-backend", 862 | "wasm-bindgen-shared", 863 | ] 864 | 865 | [[package]] 866 | name = "wasm-bindgen-shared" 867 | version = "0.2.78" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 870 | 871 | [[package]] 872 | name = "winapi" 873 | version = "0.3.9" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 876 | dependencies = [ 877 | "winapi-i686-pc-windows-gnu", 878 | "winapi-x86_64-pc-windows-gnu", 879 | ] 880 | 881 | [[package]] 882 | name = "winapi-i686-pc-windows-gnu" 883 | version = "0.4.0" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 886 | 887 | [[package]] 888 | name = "winapi-x86_64-pc-windows-gnu" 889 | version = "0.4.0" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 892 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "ucc", 4 | "ucci", 5 | "ucci-web", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at https://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Untyped Concatenative Calculus 2 | 3 | The untyped concatenative calculus, implemented in Rust. A toy programming language and prototype for [Dawn](https://www.dawn-lang.org/). 4 | 5 | ## Native REPL 6 | 7 | To build and run the native REPL: 8 | 9 | ```sh 10 | cargo run 11 | ``` 12 | 13 | ## Web REPL 14 | 15 | To build the web REPL: 16 | 17 | ```sh 18 | (cd ucci-web; wasm-pack build --target web) 19 | ``` 20 | 21 | To serve the web REPL using python's built-in http server: 22 | 23 | ```sh 24 | (cd ucci-web; python3 -m http.server) 25 | ``` 26 | 27 | ## License 28 | 29 | Licensed under the [Mozilla Public License, v. 2.0](LICENSE). 30 | 31 | ## Contribution 32 | 33 | Unless You explicitly state otherwise, any Contribution intentionally submitted 34 | for inclusion in the Covered Software by You, as defined in the Mozilla Public 35 | License, v. 2.0, shall be licensed as above, without any additional terms or 36 | conditions. 37 | -------------------------------------------------------------------------------- /ucc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ucc" 3 | version = "0.1.0" 4 | edition = "2018" 5 | authors = ["Scott J Maddox"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | fxhash = "0.2" 11 | lasso = "0.6.0" 12 | lalrpop-util = "0.19" 13 | regex = "1" 14 | 15 | [build-dependencies] 16 | lalrpop = "0.19" 17 | -------------------------------------------------------------------------------- /ucc/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | lalrpop::process_root().unwrap(); 3 | } 4 | -------------------------------------------------------------------------------- /ucc/src/builtin.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | pub(crate) static FN_DEF_SRCS: [&'static str; 23] = [ 8 | "{fn v1 = []}", 9 | "{fn v2 = []}", 10 | "{fn v3 = []}", 11 | "{fn v4 = []}", 12 | "{fn false = [drop]}", 13 | "{fn true = [swap drop]}", 14 | "{fn or = clone apply}", 15 | "{fn quote2 = quote swap quote swap compose}", 16 | "{fn quote3 = quote2 swap quote swap compose}", 17 | "{fn rotate3 = quote2 swap quote compose apply}", 18 | "{fn rotate4 = quote3 swap quote compose apply}", 19 | "{fn compose2 = compose}", 20 | "{fn compose3 = compose compose2}", 21 | "{fn compose4 = compose compose3}", 22 | "{fn compose5 = compose compose4}", 23 | "{fn n0 = [drop]}", 24 | "{fn n1 = [[clone] n0 apply [compose] n0 apply apply]}", 25 | "{fn n2 = [[clone] n1 apply [compose] n1 apply apply]}", 26 | "{fn n3 = [[clone] n2 apply [compose] n2 apply apply]}", 27 | "{fn n4 = [[clone] n3 apply [compose] n3 apply apply]}", 28 | "{fn succ = quote [apply] compose [[clone]] swap clone [[compose]] swap [apply] compose5}", 29 | "{fn add = [succ] swap apply}", 30 | "{fn mul = n0 rotate3 quote [add] compose rotate3 apply}", 31 | ]; 32 | -------------------------------------------------------------------------------- /ucc/src/core.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | pub(crate) use lasso::Rodeo as Interner; 8 | use std::hash::Hash; 9 | 10 | pub(crate) type Map = fxhash::FxHashMap; 11 | 12 | #[macro_export] 13 | macro_rules! map { 14 | ($($k:expr => $v:expr),* $(,)?) => { 15 | std::iter::Iterator::collect(std::array::IntoIter::new([$(($k, $v),)*])) 16 | }; 17 | } 18 | 19 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 20 | #[repr(transparent)] 21 | pub struct Symbol(pub(crate) lasso::Spur); 22 | 23 | //////////// 24 | // Syntax // 25 | //////////// 26 | 27 | /// Expressions 28 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 29 | pub enum Expr { 30 | Intrinsic(Intrinsic), 31 | Call(Symbol), 32 | Quote(Box), 33 | Compose(Vec), 34 | } 35 | 36 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 37 | pub enum Intrinsic { 38 | Swap, 39 | Clone, 40 | Drop, 41 | Quote, 42 | Compose, 43 | Apply, 44 | } 45 | 46 | impl Default for Expr { 47 | fn default() -> Self { 48 | Expr::Compose(vec![]) 49 | } 50 | } 51 | 52 | /////////////// 53 | // Semantics // 54 | /////////////// 55 | 56 | #[derive(Debug, Clone, PartialEq, Eq)] 57 | pub enum Value { 58 | Call(Symbol), 59 | Quote(Box), 60 | } 61 | 62 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 63 | pub struct ValueStack(pub(crate) Vec); 64 | 65 | pub struct Context { 66 | pub(crate) interner: Interner, 67 | pub(crate) fns: Map, 68 | pub(crate) exprs: Map, 69 | } 70 | 71 | #[derive(Debug, Clone, PartialEq, Eq)] 72 | pub enum EvalError { 73 | TooFewValues { available: usize, expected: usize }, 74 | UndefinedFn(Symbol), 75 | } 76 | 77 | impl Default for Context { 78 | fn default() -> Self { 79 | let interner = Interner::default(); 80 | Context { 81 | interner, 82 | fns: Map::default(), 83 | exprs: Map::default(), 84 | } 85 | } 86 | } 87 | 88 | impl Context { 89 | fn unquote_value(&self, v: Value) -> Result { 90 | match v { 91 | Value::Call(sym) => { 92 | if let Some(e) = self.fns.get(&sym) { 93 | match e { 94 | Expr::Quote(e) => Ok((**e).clone()), 95 | _ => panic!(), 96 | } 97 | } else { 98 | Err(EvalError::UndefinedFn(sym)) 99 | } 100 | } 101 | Value::Quote(e) => Ok(*e), 102 | } 103 | } 104 | 105 | pub fn small_step(&mut self, vs: &mut ValueStack, e: &mut Expr) -> Result<(), EvalError> { 106 | match e { 107 | Expr::Intrinsic(intr) => match intr { 108 | Intrinsic::Swap => { 109 | if vs.0.len() < 2 { 110 | Err(EvalError::TooFewValues { 111 | available: vs.0.len(), 112 | expected: 2, 113 | }) 114 | } else { 115 | let v = vs.0.remove(vs.0.len() - 2); 116 | vs.0.push(v); 117 | *e = Expr::default(); 118 | Ok(()) 119 | } 120 | } 121 | Intrinsic::Clone => { 122 | if vs.0.len() < 1 { 123 | Err(EvalError::TooFewValues { 124 | available: vs.0.len(), 125 | expected: 1, 126 | }) 127 | } else { 128 | vs.0.push(vs.0.last().unwrap().clone()); 129 | *e = Expr::default(); 130 | Ok(()) 131 | } 132 | } 133 | Intrinsic::Drop => { 134 | if vs.0.len() < 1 { 135 | Err(EvalError::TooFewValues { 136 | available: vs.0.len(), 137 | expected: 1, 138 | }) 139 | } else { 140 | vs.0.pop(); 141 | *e = Expr::default(); 142 | Ok(()) 143 | } 144 | } 145 | Intrinsic::Quote => { 146 | if vs.0.len() < 1 { 147 | Err(EvalError::TooFewValues { 148 | available: vs.0.len(), 149 | expected: 1, 150 | }) 151 | } else { 152 | let v = vs.0.pop().unwrap(); 153 | let qe = match v { 154 | Value::Call(sym) => Expr::Call(sym), 155 | Value::Quote(e) => Expr::Quote(e), 156 | }; 157 | vs.0.push(Value::Quote(Box::new(qe))); 158 | *e = Expr::default(); 159 | Ok(()) 160 | } 161 | } 162 | Intrinsic::Compose => { 163 | if vs.0.len() < 2 { 164 | Err(EvalError::TooFewValues { 165 | available: vs.0.len(), 166 | expected: 2, 167 | }) 168 | } else { 169 | let e2 = self.unquote_value(vs.0.pop().unwrap())?; 170 | let e1 = self.unquote_value(vs.0.pop().unwrap())?; 171 | let mut new_es = match (e1, e2) { 172 | (Expr::Compose(mut e1s), Expr::Compose(mut e2s)) => { 173 | e1s.extend(e2s.drain(..)); 174 | e1s 175 | } 176 | (Expr::Compose(mut e1s), e2) => { 177 | e1s.push(e2); 178 | e1s 179 | } 180 | (e1, Expr::Compose(mut e2s)) => { 181 | e2s.insert(0, e1); 182 | e2s 183 | } 184 | (e1, e2) => vec![e1, e2], 185 | }; 186 | let new_e = if new_es.len() == 1 { 187 | new_es.drain(..).next().unwrap() 188 | } else { 189 | Expr::Compose(new_es) 190 | }; 191 | vs.0.push(Value::Quote(Box::new(new_e))); 192 | *e = Expr::default(); 193 | Ok(()) 194 | } 195 | } 196 | Intrinsic::Apply => { 197 | if vs.0.len() < 1 { 198 | Err(EvalError::TooFewValues { 199 | available: vs.0.len(), 200 | expected: 1, 201 | }) 202 | } else { 203 | let e1 = self.unquote_value(vs.0.pop().unwrap())?; 204 | *e = e1; 205 | Ok(()) 206 | } 207 | } 208 | }, 209 | Expr::Call(sym) => { 210 | if let Some(new_e) = self.fns.get(sym) { 211 | match new_e { 212 | Expr::Quote(_) => { 213 | vs.0.push(Value::Call(*sym)); 214 | *e = Expr::default(); 215 | Ok(()) 216 | } 217 | _ => { 218 | *e = new_e.clone(); 219 | Ok(()) 220 | } 221 | } 222 | } else { 223 | Err(EvalError::UndefinedFn(*sym)) 224 | } 225 | } 226 | Expr::Quote(qe) => { 227 | vs.0.push(Value::Quote(qe.clone())); 228 | *e = Expr::default(); 229 | Ok(()) 230 | } 231 | Expr::Compose(ref mut es) => { 232 | let es_len = es.len(); 233 | if es_len == 0 { 234 | Ok(()) 235 | } else { 236 | let e1 = es.first_mut().unwrap(); 237 | self.small_step(vs, e1)?; 238 | match e1 { 239 | Expr::Compose(e1s) => { 240 | let mut new_es = Vec::with_capacity(e1s.len() + es_len - 1); 241 | new_es.extend(e1s.drain(..)); 242 | new_es.extend(es.drain(1..)); 243 | let new_e = if new_es.len() == 1 { 244 | new_es.drain(..).next().unwrap() 245 | } else { 246 | Expr::Compose(new_es) 247 | }; 248 | *e = new_e; 249 | } 250 | _ => {} 251 | } 252 | Ok(()) 253 | } 254 | } 255 | } 256 | } 257 | 258 | pub fn compress(&mut self, vs: &mut ValueStack) -> bool { 259 | let mut compressed = false; 260 | for v in vs.0.iter_mut() { 261 | match v { 262 | Value::Call(_) => {} 263 | Value::Quote(e) => { 264 | // TODO: we shouldn't have to clone this expr in order to hash it 265 | if let Some(sym) = self.exprs.get(&Expr::Quote((*e).clone())) { 266 | *v = Value::Call(*sym); 267 | compressed = true; 268 | } 269 | } 270 | } 271 | } 272 | compressed 273 | } 274 | } 275 | 276 | ////////////////////////// 277 | // Function Definitions // 278 | ////////////////////////// 279 | 280 | #[derive(Debug, Clone, PartialEq, Eq)] 281 | pub struct FnDef(pub Symbol, pub Expr); 282 | 283 | impl Context { 284 | pub fn define_fn(&mut self, fn_def: FnDef) -> Option { 285 | let result = self.fns.remove(&fn_def.0).map(|e| FnDef(fn_def.0, e)); 286 | self.fns.insert(fn_def.0, fn_def.1.clone()); 287 | self.exprs.insert(fn_def.1, fn_def.0); 288 | result 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /ucc/src/display.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | use crate::core::{EvalError, Expr, Interner, Intrinsic, Symbol, Value, ValueStack}; 8 | use std::fmt; 9 | 10 | pub(crate) type ResolvedSymbol = String; 11 | 12 | #[derive(Debug, Clone, PartialEq, Eq)] 13 | pub enum ResolvedExpr { 14 | Empty, 15 | Intrinsic(Intrinsic), 16 | Call(ResolvedSymbol), 17 | Quote(Box), 18 | Compose(Vec), 19 | } 20 | 21 | #[derive(Debug, Clone, PartialEq, Eq)] 22 | pub enum ResolvedValue { 23 | Call(ResolvedSymbol), 24 | Quote(Box), 25 | } 26 | 27 | #[derive(Debug, Clone, PartialEq, Eq)] 28 | pub struct ResolvedValueStack(pub(crate) Vec); 29 | 30 | #[derive(Debug, Clone, PartialEq, Eq)] 31 | pub enum ResolvedEvalError { 32 | TooFewValues { available: usize, expected: usize }, 33 | UndefinedFn(String), 34 | } 35 | 36 | pub(crate) trait Resolve { 37 | type Output; 38 | fn resolve(&self, interner: &Interner) -> Self::Output; 39 | } 40 | 41 | impl Resolve for () { 42 | type Output = (); 43 | fn resolve(&self, _: &Interner) -> Self::Output { 44 | () 45 | } 46 | } 47 | 48 | impl Resolve for Result 49 | where 50 | T: Resolve, 51 | E: Resolve, 52 | { 53 | type Output = Result<::Output, ::Output>; 54 | fn resolve(&self, interner: &Interner) -> Self::Output { 55 | match self { 56 | Ok(t) => Ok(t.resolve(interner)), 57 | Err(e) => Err(e.resolve(interner)), 58 | } 59 | } 60 | } 61 | 62 | impl Resolve for Symbol { 63 | type Output = ResolvedSymbol; 64 | fn resolve(&self, interner: &Interner) -> Self::Output { 65 | interner.resolve(&self.0).to_owned() 66 | } 67 | } 68 | 69 | impl Resolve for Expr { 70 | type Output = ResolvedExpr; 71 | fn resolve(&self, interner: &Interner) -> Self::Output { 72 | match self { 73 | Expr::Intrinsic(i) => ResolvedExpr::Intrinsic(*i), 74 | Expr::Call(sym) => ResolvedExpr::Call(sym.resolve(interner)), 75 | Expr::Quote(e) => ResolvedExpr::Quote(Box::new(e.resolve(interner))), 76 | Expr::Compose(es) => { 77 | ResolvedExpr::Compose(es.iter().map(|e| e.resolve(interner)).collect()) 78 | } 79 | } 80 | } 81 | } 82 | 83 | impl Resolve for Value { 84 | type Output = ResolvedValue; 85 | fn resolve(&self, interner: &Interner) -> Self::Output { 86 | match self { 87 | Value::Call(sym) => ResolvedValue::Call(sym.resolve(interner)), 88 | Value::Quote(e) => ResolvedValue::Quote(Box::new(e.resolve(interner))), 89 | } 90 | } 91 | } 92 | 93 | impl Resolve for ValueStack { 94 | type Output = ResolvedValueStack; 95 | fn resolve(&self, interner: &Interner) -> Self::Output { 96 | ResolvedValueStack(self.0.iter().map(|v| v.resolve(interner)).collect()) 97 | } 98 | } 99 | 100 | impl Resolve for EvalError { 101 | type Output = ResolvedEvalError; 102 | fn resolve(&self, interner: &Interner) -> Self::Output { 103 | match self { 104 | &EvalError::TooFewValues { 105 | available, 106 | expected, 107 | } => ResolvedEvalError::TooFewValues { 108 | available, 109 | expected, 110 | }, 111 | &EvalError::UndefinedFn(sym) => ResolvedEvalError::UndefinedFn(sym.resolve(interner)), 112 | } 113 | } 114 | } 115 | 116 | impl ResolvedExpr { 117 | fn is_compose(&self) -> bool { 118 | match self { 119 | ResolvedExpr::Compose(..) => true, 120 | _ => false, 121 | } 122 | } 123 | } 124 | 125 | impl fmt::Display for Intrinsic { 126 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 127 | match self { 128 | Intrinsic::Swap => "swap".fmt(f), 129 | Intrinsic::Clone => "clone".fmt(f), 130 | Intrinsic::Drop => "drop".fmt(f), 131 | Intrinsic::Quote => "quote".fmt(f), 132 | Intrinsic::Compose => "compose".fmt(f), 133 | Intrinsic::Apply => "apply".fmt(f), 134 | } 135 | } 136 | } 137 | 138 | impl fmt::Display for ResolvedExpr { 139 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 140 | match self { 141 | ResolvedExpr::Empty => Ok(()), 142 | ResolvedExpr::Intrinsic(i) => i.fmt(f), 143 | ResolvedExpr::Call(sym) => sym.fmt(f), 144 | ResolvedExpr::Quote(e) => write!(f, "[{}]", e), 145 | ResolvedExpr::Compose(es) => { 146 | if let Some(e) = es.first() { 147 | if e.is_compose() { 148 | write!(f, "({})", e)?; 149 | } else { 150 | write!(f, "{}", e)?; 151 | } 152 | } 153 | for e in es.iter().skip(1) { 154 | " ".fmt(f)?; 155 | if e.is_compose() { 156 | write!(f, "({})", e)?; 157 | } else { 158 | write!(f, "{}", e)?; 159 | } 160 | } 161 | Ok(()) 162 | } 163 | } 164 | } 165 | } 166 | 167 | impl fmt::Display for ResolvedValue { 168 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 169 | match self { 170 | ResolvedValue::Call(sym) => sym.fmt(f), 171 | ResolvedValue::Quote(v) => write!(f, "[{}]", v), 172 | } 173 | } 174 | } 175 | 176 | impl fmt::Display for ResolvedValueStack { 177 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 178 | "⟨".fmt(f)?; 179 | if let Some(v) = self.0.first() { 180 | v.fmt(f)?; 181 | } 182 | for v in self.0.iter().skip(1) { 183 | " ".fmt(f)?; 184 | v.fmt(f)?; 185 | } 186 | "⟩".fmt(f) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /ucc/src/interp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | use crate::builtin::FN_DEF_SRCS; 8 | use crate::core::*; 9 | use crate::display::*; 10 | use crate::parse::*; 11 | use std::io; 12 | 13 | #[derive(Debug, Clone, PartialEq, Eq)] 14 | pub(crate) enum InterpItem { 15 | FnDef(FnDef), 16 | Expr(Expr), 17 | } 18 | 19 | #[derive(Debug, Clone, PartialEq, Eq)] 20 | pub(crate) enum InterpCommand { 21 | Eval(Vec), 22 | Trace(Expr), 23 | Show(Symbol), 24 | List, 25 | Drop, 26 | Clear, 27 | Reset, 28 | Help, 29 | } 30 | 31 | pub(crate) static HELP: &'static str = "\ 32 | Commands available: 33 | 34 | evaluate 35 | {fn = } define as 36 | :trace trace the evaluation of 37 | :show show the definition of 38 | :list list the defined symbols 39 | :drop drop the current value stack 40 | :clear clear all definitions 41 | :reset reset the interpreter 42 | :help display this list of commands 43 | "; 44 | 45 | pub struct Interp { 46 | ctx: Context, 47 | vs: ValueStack, 48 | command: Option, 49 | is_first_eval_step: bool, 50 | } 51 | 52 | impl Default for Interp { 53 | fn default() -> Self { 54 | let mut ctx = Context::default(); 55 | for fn_def_src in FN_DEF_SRCS.iter() { 56 | let fn_def = FnDefParser::new() 57 | .parse(&mut ctx.interner, fn_def_src) 58 | .unwrap(); 59 | assert_eq!(ctx.define_fn(fn_def), None); 60 | } 61 | Self { 62 | ctx, 63 | vs: ValueStack::default(), 64 | command: None, 65 | is_first_eval_step: true, 66 | } 67 | } 68 | } 69 | 70 | impl Interp { 71 | pub fn is_done(&self) -> bool { 72 | self.command.is_none() 73 | } 74 | 75 | pub fn interp_start(&mut self, input: &str, w: &mut dyn io::Write) -> io::Result<()> { 76 | match InterpCommandParser::new().parse(&mut self.ctx.interner, input) { 77 | Err(err) => { 78 | // TODO: better error messages 79 | w.write_fmt(format_args!("{:?}\n", err))?; 80 | } 81 | Ok(InterpCommand::Eval(is)) => { 82 | self.is_first_eval_step = true; 83 | self.command = Some(InterpCommand::Eval(is)); 84 | } 85 | Ok(InterpCommand::Trace(e)) => { 86 | w.write_fmt(format_args!( 87 | "{} {}\n", 88 | self.vs.resolve(&self.ctx.interner), 89 | e.resolve(&self.ctx.interner) 90 | ))?; 91 | self.command = Some(InterpCommand::Trace(e)); 92 | } 93 | Ok(InterpCommand::Show(sym)) => { 94 | if let Some(e) = self.ctx.fns.get(&sym) { 95 | w.write_fmt(format_args!( 96 | "{{fn {} = {}}}\n", 97 | sym.resolve(&self.ctx.interner), 98 | e.resolve(&self.ctx.interner) 99 | ))?; 100 | } else { 101 | w.write_fmt(format_args!("Not defined.\n"))?; 102 | } 103 | } 104 | Ok(InterpCommand::List) => { 105 | let mut names: Vec = self 106 | .ctx 107 | .fns 108 | .keys() 109 | .map(|sym| sym.resolve(&self.ctx.interner)) 110 | .collect(); 111 | names.sort_unstable(); 112 | if let Some(name) = names.first() { 113 | w.write_all(name.as_bytes())?; 114 | } 115 | for name in names.iter().skip(1) { 116 | w.write_all(" ".as_bytes())?; 117 | w.write_all(name.as_bytes())?; 118 | } 119 | w.write_all("\n".as_bytes())?; 120 | } 121 | Ok(InterpCommand::Drop) => { 122 | self.vs = ValueStack::default(); 123 | w.write_fmt(format_args!("Values dropped.\n"))?; 124 | } 125 | Ok(InterpCommand::Clear) => { 126 | self.ctx.fns.clear(); 127 | self.ctx.exprs.clear(); 128 | w.write_fmt(format_args!("Definitions cleared.\n"))?; 129 | } 130 | Ok(InterpCommand::Reset) => { 131 | *self = Self::default(); 132 | w.write_fmt(format_args!("Reset.\n"))?; 133 | } 134 | Ok(InterpCommand::Help) => { 135 | w.write_all(HELP.as_bytes())?; 136 | } 137 | } 138 | w.flush() 139 | } 140 | 141 | pub fn interp_step(&mut self, w: &mut dyn io::Write) -> io::Result<()> { 142 | match self.command.take() { 143 | Some(InterpCommand::Eval(mut is)) => { 144 | if !is.is_empty() { 145 | match is.remove(0) { 146 | InterpItem::FnDef(fn_def) => { 147 | let name = fn_def.0.resolve(&self.ctx.interner); 148 | if let Some(_) = self.ctx.define_fn(fn_def) { 149 | w.write_fmt(format_args!("Redefined `{}`.\n", name))?; 150 | } else { 151 | w.write_fmt(format_args!("Defined `{}`.\n", name))?; 152 | } 153 | } 154 | InterpItem::Expr(mut e) => { 155 | if self.is_first_eval_step { 156 | w.write_fmt(format_args!( 157 | "{} {}\n", 158 | self.vs.resolve(&self.ctx.interner), 159 | e.resolve(&self.ctx.interner) 160 | ))?; 161 | } 162 | if e != Expr::default() { 163 | if let Err(err) = self.ctx.small_step(&mut self.vs, &mut e) { 164 | w.write_fmt(format_args!( 165 | "⇓ {} {}\n", 166 | self.vs.resolve(&self.ctx.interner), 167 | e.resolve(&self.ctx.interner) 168 | ))?; 169 | // TODO: better error messages 170 | w.write_fmt(format_args!( 171 | "{:?}\n", 172 | err.resolve(&self.ctx.interner) 173 | ))?; 174 | return w.flush(); 175 | } else { 176 | self.ctx.compress(&mut self.vs); 177 | is.insert(0, InterpItem::Expr(e)); 178 | self.is_first_eval_step = false; 179 | } 180 | } else { 181 | w.write_fmt(format_args!( 182 | "⇓ {} {}\n", 183 | self.vs.resolve(&self.ctx.interner), 184 | e.resolve(&self.ctx.interner) 185 | ))?; 186 | self.is_first_eval_step = true; 187 | } 188 | } 189 | } 190 | self.command = Some(InterpCommand::Eval(is)); 191 | } 192 | } 193 | Some(InterpCommand::Trace(mut e)) => { 194 | if e != Expr::default() { 195 | if let Err(err) = self.ctx.small_step(&mut self.vs, &mut e) { 196 | // TODO: better error messages 197 | w.write_fmt(format_args!("{:?}\n", err.resolve(&self.ctx.interner)))?; 198 | return w.flush(); 199 | } 200 | // TODO: show function expansion as equality, not as small step? 201 | w.write_fmt(format_args!( 202 | "⟶ {} {}\n", 203 | self.vs.resolve(&self.ctx.interner), 204 | e.resolve(&self.ctx.interner) 205 | ))?; 206 | if self.ctx.compress(&mut self.vs) { 207 | w.write_fmt(format_args!( 208 | "= {} {}\n", 209 | self.vs.resolve(&self.ctx.interner), 210 | e.resolve(&self.ctx.interner) 211 | ))?; 212 | } 213 | self.command = Some(InterpCommand::Trace(e)); 214 | } 215 | } 216 | _ => panic!(), 217 | } 218 | w.flush() 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /ucc/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | pub mod core; 8 | #[cfg(test)] 9 | mod test_core; 10 | 11 | mod builtin; 12 | 13 | pub mod display; 14 | 15 | pub mod interp; 16 | #[cfg(test)] 17 | mod test_interp; 18 | 19 | use lalrpop_util::lalrpop_mod; 20 | lalrpop_mod!(pub parse); 21 | #[cfg(test)] 22 | mod test_parse; 23 | -------------------------------------------------------------------------------- /ucc/src/parse.lalrpop: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | use crate::core::*; 8 | use crate::interp::*; 9 | 10 | grammar(interner: &mut Interner); 11 | 12 | pub(crate) InterpCommand: InterpCommand = { 13 | => InterpCommand::Eval(<>), 14 | COLON_TRACE => InterpCommand::Trace(<>), 15 | COLON_SHOW => InterpCommand::Show(<>), 16 | COLON_LIST => InterpCommand::List, 17 | COLON_DROP => InterpCommand::Drop, 18 | COLON_CLEAR => InterpCommand::Clear, 19 | COLON_RESET => InterpCommand::Reset, 20 | COLON_HELP => InterpCommand::Help, 21 | } 22 | 23 | pub(crate) InterpItems: Vec = { 24 | => { 25 | let mut is = vec![]; 26 | for (e, fn_def) in pairs { 27 | if e != Expr::default() { 28 | is.push(InterpItem::Expr(e)); 29 | } 30 | is.push(InterpItem::FnDef(fn_def)); 31 | } 32 | if last_e != Expr::default() { 33 | is.push(InterpItem::Expr(last_e)); 34 | } 35 | is 36 | } 37 | } 38 | 39 | pub FnDef: FnDef = { 40 | LBRACE FN EQ RBRACE 41 | => FnDef(sym, e), 42 | } 43 | 44 | pub(crate) Symbol: Symbol = { 45 | IDENT => Symbol(interner.get_or_intern(<>)) 46 | } 47 | 48 | pub(crate) SmallStepAssertion: (ValueStack, Expr, ValueStack, Expr) = { 49 | LONG_RARROW => { 50 | (ivs, ie, ovs, oe) 51 | } 52 | } 53 | 54 | pub(crate) BigStepAssertion: (ValueStack, Expr, ValueStack, Expr) = { 55 | DDARROW => { 56 | (ivs, ie, ovs, oe) 57 | } 58 | } 59 | 60 | pub ValueStack: ValueStack = { 61 | BRA KET => ValueStack(vs), 62 | } 63 | 64 | pub Value: Value = { 65 | IDENT => Value::Call(Symbol(interner.get_or_intern(<>))), 66 | LBRACKET RBRACKET => Value::Quote(Box::new(<>)), 67 | } 68 | 69 | pub Expr: Expr = { 70 | => { 71 | if es.len() == 1 { 72 | es.drain(..).next().unwrap() 73 | } else { 74 | Expr::Compose(es) 75 | } 76 | }, 77 | } 78 | 79 | SingleExpr: Expr = { 80 | SWAP => Expr::Intrinsic(Intrinsic::Swap), 81 | CLONE => Expr::Intrinsic(Intrinsic::Clone), 82 | DROP => Expr::Intrinsic(Intrinsic::Drop), 83 | QUOTE => Expr::Intrinsic(Intrinsic::Quote), 84 | COMPOSE => Expr::Intrinsic(Intrinsic::Compose), 85 | APPLY => Expr::Intrinsic(Intrinsic::Apply), 86 | IDENT => Expr::Call(Symbol(interner.get_or_intern(<>))), 87 | LBRACKET RBRACKET => Expr::Quote(Box::new(<>)), 88 | LPAREN RPAREN, 89 | } 90 | 91 | match { 92 | r"\s*" => { }, // skip whitespace 93 | r"--[^\n\r]*[\n\r]*" => { }, // Skip `--` comments 94 | r"\(" => LPAREN, 95 | r"\)" => RPAREN, 96 | r"\[" => LBRACKET, 97 | r"\]" => RBRACKET, 98 | r"\{" => LBRACE, 99 | r"\}" => RBRACE, 100 | r"=" => EQ, 101 | r"⟨" => BRA, 102 | r"⟩" => KET, 103 | r"⟶" => LONG_RARROW, 104 | r"⇓" => DDARROW, 105 | r"swap" => SWAP, 106 | r"clone" => CLONE, 107 | r"drop" => DROP, 108 | r"quote" => QUOTE, 109 | r"compose" => COMPOSE, 110 | r"apply" => APPLY, 111 | r"fn" => FN, 112 | r":trace" => COLON_TRACE, 113 | r":show" => COLON_SHOW, 114 | r":list" => COLON_LIST, 115 | r":drop" => COLON_DROP, 116 | r":clear" => COLON_CLEAR, 117 | r":reset" => COLON_RESET, 118 | r":help" => COLON_HELP, 119 | } else { 120 | r"[_a-zA-Z][_a-zA-Z0-9]*" => IDENT, 121 | } 122 | -------------------------------------------------------------------------------- /ucc/src/test_core.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | use crate::builtin::FN_DEF_SRCS; 8 | use crate::core::*; 9 | use crate::display::*; 10 | use crate::parse::*; 11 | 12 | #[test] 13 | fn test_small_step() { 14 | let cases = [ 15 | "⟨[e1] [e2]⟩ swap ⟶ ⟨[e2] [e1]⟩", 16 | "⟨[e1]⟩ clone ⟶ ⟨[e1] [e1]⟩", 17 | "⟨[e1]⟩ drop ⟶ ⟨⟩", 18 | "⟨[e1]⟩ quote ⟶ ⟨[[e1]]⟩", 19 | "⟨[e1] [e2]⟩ compose ⟶ ⟨[e1 e2]⟩", 20 | "⟨[e1]⟩ apply ⟶ ⟨⟩ e1", 21 | ]; 22 | for case in cases { 23 | let mut ctx = Context::default(); 24 | let mut ssa = SmallStepAssertionParser::new() 25 | .parse(&mut ctx.interner, case) 26 | .unwrap(); 27 | let result = ctx.small_step(&mut ssa.0, &mut ssa.1); 28 | assert_eq!(result, Ok(()), "Failed on {}", case); 29 | assert_eq!(ssa.0, ssa.2, "Failed on {}", case); 30 | assert_eq!(ssa.1, ssa.3, "Failed on {}", case); 31 | } 32 | } 33 | 34 | #[test] 35 | fn test_compress() { 36 | let cases = [ 37 | ("⟨[swap drop]⟩", "⟨true⟩", true), 38 | ("⟨[drop]⟩", "⟨n0⟩", true), 39 | ( 40 | "⟨[[clone] n0 apply [compose] n0 apply apply]⟩", 41 | "⟨n1⟩", 42 | true, 43 | ), 44 | ( 45 | "⟨[[clone] n1 apply [compose] n1 apply apply]⟩", 46 | "⟨n2⟩", 47 | true, 48 | ), 49 | ( 50 | "⟨[[clone] n2 apply [compose] n2 apply apply]⟩", 51 | "⟨n3⟩", 52 | true, 53 | ), 54 | ( 55 | "⟨[[clone] n3 apply [compose] n3 apply apply]⟩", 56 | "⟨n4⟩", 57 | true, 58 | ), 59 | ]; 60 | for (input_src, expected_src, expected_result) in cases { 61 | let mut ctx = Context::default(); 62 | for fn_def_src in FN_DEF_SRCS.iter() { 63 | let fn_def = FnDefParser::new() 64 | .parse(&mut ctx.interner, fn_def_src) 65 | .unwrap(); 66 | assert_eq!(ctx.define_fn(fn_def), None); 67 | } 68 | let mut input = ValueStackParser::new() 69 | .parse(&mut ctx.interner, input_src) 70 | .unwrap(); 71 | let expected = ValueStackParser::new() 72 | .parse(&mut ctx.interner, expected_src) 73 | .unwrap(); 74 | let result = ctx.compress(&mut input); 75 | assert_eq!( 76 | (input.resolve(&ctx.interner), result), 77 | (expected.resolve(&ctx.interner), expected_result), 78 | "Failed on ({}, {})", 79 | input_src, 80 | expected_src 81 | ); 82 | } 83 | } 84 | 85 | #[test] 86 | fn test_define_fn() { 87 | let mut ctx = Context::default(); 88 | let sym = Symbol(ctx.interner.get_or_intern_static("foo")); 89 | let fn_def1 = FnDefParser::new() 90 | .parse(&mut ctx.interner, "{fn foo = e1}") 91 | .unwrap(); 92 | let e1 = ExprParser::new().parse(&mut ctx.interner, "e1").unwrap(); 93 | let fn_def2 = FnDefParser::new() 94 | .parse(&mut ctx.interner, "{fn foo = e2}") 95 | .unwrap(); 96 | let e2 = ExprParser::new().parse(&mut ctx.interner, "e2").unwrap(); 97 | assert_eq!(ctx.fns.get(&sym), None); 98 | assert_eq!(ctx.define_fn(fn_def1), None); 99 | assert_eq!(ctx.fns.get(&sym), Some(&e1)); 100 | assert_eq!(ctx.define_fn(fn_def2), Some(FnDef(sym, e1))); 101 | assert_eq!(ctx.fns.get(&sym), Some(&e2)); 102 | } 103 | 104 | #[test] 105 | fn test_big_step() { 106 | const MAX_SMALL_STEPS: usize = 1000; 107 | let cases = [ 108 | "⟨v1 v2⟩ swap swap ⇓ ⟨v1 v2⟩", 109 | "⟨v1 v2⟩ false apply ⇓ ⟨v1⟩", 110 | "⟨v1 v2⟩ true apply ⇓ ⟨v2⟩", 111 | "⟨false false⟩ or ⇓ ⟨false⟩", 112 | "⟨false true⟩ or ⇓ ⟨true⟩", 113 | "⟨true false⟩ or ⇓ ⟨true⟩", 114 | "⟨true true⟩ or ⇓ ⟨true⟩", 115 | "⟨v1 v2⟩ quote2 ⇓ ⟨[v1 v2]⟩", 116 | "⟨v1 v2 v3⟩ quote3 ⇓ ⟨[v1 v2 v3]⟩", 117 | "⟨v1 v2 v3⟩ rotate3 ⇓ ⟨v2 v3 v1⟩", 118 | "⟨v1 v2 v3 v4⟩ rotate4 ⇓ ⟨v2 v3 v4 v1⟩", 119 | "⟨[v1] [v2]⟩ compose2 ⇓ ⟨[v1 v2]⟩", 120 | "⟨[v1] [v2] [v3]⟩ compose3 ⇓ ⟨[v1 v2 v3]⟩", 121 | "⟨[v1] [v2] [v3] [v4]⟩ compose4 ⇓ ⟨[v1 v2 v3 v4]⟩", 122 | "⟨[v1] [v2] [v3] [v4] [v5]⟩ compose5 ⇓ ⟨[v1 v2 v3 v4 v5]⟩", 123 | "⟨[e]⟩ n0 apply ⇓ ⟨⟩", 124 | "⟨[e]⟩ n1 apply ⇓ ⟨⟩ e", 125 | "⟨[e]⟩ n2 apply ⇓ ⟨⟩ e e", 126 | "⟨[e]⟩ n3 apply ⇓ ⟨⟩ e e e", 127 | "⟨[e]⟩ n4 apply ⇓ ⟨⟩ e e e e", 128 | "⟨[e] n0⟩ succ apply ⇓ ⟨⟩ e", 129 | "⟨[e] n0⟩ succ succ apply ⇓ ⟨⟩ e e", 130 | "⟨[e] n0⟩ succ succ succ apply ⇓ ⟨⟩ e e e", 131 | "⟨[e] n1⟩ succ apply ⇓ ⟨⟩ e e", 132 | "⟨[e] n2⟩ succ apply ⇓ ⟨⟩ e e e", 133 | "⟨[e] n0 n0⟩ add apply ⇓ ⟨⟩", 134 | "⟨[e] n0 n1⟩ add apply ⇓ ⟨⟩ e", 135 | "⟨[e] n1 n0⟩ add apply ⇓ ⟨⟩ e", 136 | "⟨[e] n1 n1⟩ add apply ⇓ ⟨⟩ e e", 137 | "⟨[e] n1 n2⟩ add apply ⇓ ⟨⟩ e e e", 138 | "⟨[e] n2 n1⟩ add apply ⇓ ⟨⟩ e e e", 139 | "⟨[e] n2 n2⟩ add apply ⇓ ⟨⟩ e e e e", 140 | "⟨[e] n0 n0⟩ mul apply ⇓ ⟨⟩", 141 | "⟨[e] n0 n1⟩ mul apply ⇓ ⟨⟩", 142 | "⟨[e] n1 n0⟩ mul apply ⇓ ⟨⟩", 143 | "⟨[e] n1 n1⟩ mul apply ⇓ ⟨⟩ e", 144 | "⟨[e] n1 n2⟩ mul apply ⇓ ⟨⟩ e e", 145 | "⟨[e] n2 n1⟩ mul apply ⇓ ⟨⟩ e e", 146 | "⟨[e] n2 n2⟩ mul apply ⇓ ⟨⟩ e e e e", 147 | "⟨⟩ [clone apply] clone apply ⇓ ⟨[clone apply]⟩ clone apply", 148 | ]; 149 | let mut ctx = Context::default(); 150 | for fn_def_src in FN_DEF_SRCS.iter() { 151 | let fn_def = FnDefParser::new() 152 | .parse(&mut ctx.interner, fn_def_src) 153 | .unwrap(); 154 | assert_eq!(ctx.define_fn(fn_def), None); 155 | } 156 | for case in cases { 157 | println!("\n{}", case); 158 | let mut ssa = BigStepAssertionParser::new() 159 | .parse(&mut ctx.interner, case) 160 | .unwrap(); 161 | 'eval: for step in 1..=MAX_SMALL_STEPS { 162 | assert_eq!( 163 | ctx.small_step(&mut ssa.0, &mut ssa.1) 164 | .resolve(&ctx.interner), 165 | Ok(()), 166 | "Failed on {}", 167 | case 168 | ); 169 | println!( 170 | "⟶ {} {}", 171 | ssa.0.resolve(&ctx.interner), 172 | ssa.1.resolve(&ctx.interner) 173 | ); 174 | if ssa.0 == ssa.2 && ssa.1 == ssa.3 { 175 | break 'eval; 176 | } else if step == MAX_SMALL_STEPS { 177 | panic!("Reached MAX_SMALL_STEPS on {}", case); 178 | } 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /ucc/src/test_interp.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | use crate::interp::{Interp, HELP}; 8 | 9 | #[test] 10 | fn test_non_blocking_interp() { 11 | let sessions = [ 12 | &[ 13 | (":drop", "Values dropped.\n"), 14 | (":clear", "Definitions cleared.\n"), 15 | ("{fn foo = }", "Defined `foo`.\n"), 16 | (":list", "foo\n"), 17 | (":show foo", "{fn foo = }\n"), 18 | ("{fn foo = drop}", "Redefined `foo`.\n"), 19 | (":show foo", "{fn foo = drop}\n"), 20 | ("{fn bar = }", "Defined `bar`.\n"), 21 | (":list", "bar foo\n"), 22 | ( 23 | "{fn foo = }{fn bar = }", 24 | "Redefined `foo`.\nRedefined `bar`.\n", 25 | ), 26 | (":reset", "Reset.\n"), 27 | ("false", "⟨⟩ false\n⇓ ⟨false⟩ \n"), 28 | ("true", "⟨false⟩ true\n⇓ ⟨false true⟩ \n"), 29 | ( 30 | "drop\n\n{fn foo =}drop", 31 | concat!( 32 | "⟨false true⟩ drop\n", 33 | "⇓ ⟨false⟩ \n", 34 | "Defined `foo`.\n", 35 | "⟨false⟩ drop\n", 36 | "⇓ ⟨⟩ \n", 37 | ), 38 | ), 39 | ][..], 40 | &[ 41 | ( 42 | "true true or", 43 | "⟨⟩ true true or\n⇓ ⟨true⟩ \n", 44 | ), 45 | ("drop", "⟨true⟩ drop\n⇓ ⟨⟩ \n"), 46 | ][..], 47 | &[( 48 | ":trace false false or", 49 | concat!( 50 | "⟨⟩ false false or\n", 51 | "⟶ ⟨false⟩ false or\n", 52 | "⟶ ⟨false false⟩ or\n", 53 | "⟶ ⟨false false⟩ clone apply\n", 54 | "⟶ ⟨false false false⟩ apply\n", 55 | "⟶ ⟨false false⟩ drop\n", 56 | "⟶ ⟨false⟩ \n" 57 | ), 58 | )][..], 59 | &[(":help", HELP)][..], 60 | &[("n0 succ", "⟨⟩ n0 succ\n⇓ ⟨n1⟩ \n")][..], 61 | &[("n0 n1 add", "⟨⟩ n0 n1 add\n⇓ ⟨n1⟩ \n")][..], 62 | &[("n1 n1 add", "⟨⟩ n1 n1 add\n⇓ ⟨n2⟩ \n")][..], 63 | &[("n1 n1 mul", "⟨⟩ n1 n1 mul\n⇓ ⟨n1⟩ \n")][..], 64 | &[("n2 n2 mul", "⟨⟩ n2 n2 mul\n⇓ ⟨n4⟩ \n")][..], 65 | &[( 66 | "true foo", 67 | "⟨⟩ true foo\n⇓ ⟨true⟩ foo\nUndefinedFn(\"foo\")\n", 68 | )][..], 69 | &[( 70 | ":trace true foo", 71 | "⟨⟩ true foo\n⟶ ⟨true⟩ foo\nUndefinedFn(\"foo\")\n", 72 | )][..], 73 | ]; 74 | let mut buffer = Vec::with_capacity(4096); 75 | for session in sessions { 76 | let mut interp = Interp::default(); 77 | for &(input, expected_output) in session { 78 | buffer.clear(); 79 | interp.interp_start(input, &mut buffer).unwrap(); 80 | while !interp.is_done() { 81 | interp.interp_step(&mut buffer).unwrap(); 82 | } 83 | let output = unsafe { std::str::from_utf8_unchecked(&buffer[..]) }; 84 | assert_eq!( 85 | output, 86 | expected_output, 87 | "Failed on {:?}", 88 | (input, expected_output) 89 | ); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ucc/src/test_parse.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | use crate::core::*; 8 | use crate::interp::*; 9 | use crate::parse::*; 10 | 11 | #[test] 12 | fn test_parse_interp_items() { 13 | let interner = &mut Interner::default(); 14 | let foo_sym = Symbol(interner.get_or_intern_static("foo")); 15 | let bar_sym = Symbol(interner.get_or_intern_static("bar")); 16 | let cases = [ 17 | ( 18 | "swap", 19 | vec![InterpItem::Expr(Expr::Intrinsic(Intrinsic::Swap))], 20 | ), 21 | ( 22 | "{fn foo = }", 23 | vec![InterpItem::FnDef(FnDef(foo_sym, Expr::default()))], 24 | ), 25 | ( 26 | "swap{fn foo = }", 27 | vec![ 28 | InterpItem::Expr(Expr::Intrinsic(Intrinsic::Swap)), 29 | InterpItem::FnDef(FnDef(foo_sym, Expr::default())), 30 | ], 31 | ), 32 | ( 33 | "{fn foo = }{fn bar = }", 34 | vec![ 35 | InterpItem::FnDef(FnDef(foo_sym, Expr::default())), 36 | InterpItem::FnDef(FnDef(bar_sym, Expr::default())), 37 | ], 38 | ), 39 | ( 40 | "{fn foo = }\n{fn bar = }", 41 | vec![ 42 | InterpItem::FnDef(FnDef(foo_sym, Expr::default())), 43 | InterpItem::FnDef(FnDef(bar_sym, Expr::default())), 44 | ], 45 | ), 46 | ( 47 | "{fn foo = }swap{fn bar = }drop", 48 | vec![ 49 | InterpItem::FnDef(FnDef(foo_sym, Expr::default())), 50 | InterpItem::Expr(Expr::Intrinsic(Intrinsic::Swap)), 51 | InterpItem::FnDef(FnDef(bar_sym, Expr::default())), 52 | InterpItem::Expr(Expr::Intrinsic(Intrinsic::Drop)), 53 | ], 54 | ), 55 | ]; 56 | for (src, expected) in cases { 57 | let items = InterpItemsParser::new().parse(interner, src).unwrap(); 58 | assert_eq!(items, expected, "Failed on {}", src); 59 | } 60 | } 61 | 62 | #[test] 63 | fn test_parse_fn_def() { 64 | let interner = &mut Interner::default(); 65 | let cases = &[("{fn foo = }", "foo", "")]; 66 | for (fn_def_src, sym_src, e_src) in cases { 67 | let fn_def = FnDefParser::new().parse(interner, fn_def_src).unwrap(); 68 | let sym = Symbol(interner.get_or_intern_static(sym_src)); 69 | let e = ExprParser::new().parse(interner, e_src).unwrap(); 70 | assert_eq!(fn_def, FnDef(sym, e), "{}", fn_def_src); 71 | } 72 | } 73 | 74 | #[test] 75 | fn test_parse_expr_empty() { 76 | let interner = &mut Interner::default(); 77 | let input = ""; 78 | let e = ExprParser::new().parse(interner, input).unwrap(); 79 | assert_eq!(e, Expr::default()); 80 | } 81 | 82 | #[test] 83 | fn test_parse_expr_intrinsic() { 84 | let cases = [ 85 | ("swap", Expr::Intrinsic(Intrinsic::Swap)), 86 | ("clone", Expr::Intrinsic(Intrinsic::Clone)), 87 | ("drop", Expr::Intrinsic(Intrinsic::Drop)), 88 | ("quote", Expr::Intrinsic(Intrinsic::Quote)), 89 | ("compose", Expr::Intrinsic(Intrinsic::Compose)), 90 | ("apply", Expr::Intrinsic(Intrinsic::Apply)), 91 | ]; 92 | for (e_src, e_expected) in cases { 93 | let interner = &mut Interner::default(); 94 | let e = ExprParser::new().parse(interner, e_src).unwrap(); 95 | assert_eq!(e, e_expected); 96 | } 97 | } 98 | 99 | #[test] 100 | fn test_parse_expr_call() { 101 | let interner = &mut Interner::default(); 102 | let input = "foo"; 103 | let e = ExprParser::new().parse(interner, input).unwrap(); 104 | assert_eq!(e, Expr::Call(Symbol(interner.get("foo").unwrap()))); 105 | } 106 | 107 | #[test] 108 | fn test_parse_expr_call2() { 109 | let interner = &mut Interner::default(); 110 | let inputs = &["foo bar", "(foo bar)", "((foo bar))"]; 111 | for input in inputs { 112 | let e = ExprParser::new().parse(interner, input).unwrap(); 113 | let e2 = Expr::Compose(vec![ 114 | Expr::Call(Symbol(interner.get("foo").unwrap())), 115 | Expr::Call(Symbol(interner.get("bar").unwrap())), 116 | ]); 117 | assert_eq!(e, e2); 118 | } 119 | } 120 | 121 | #[test] 122 | fn test_parse_expr_quote_call() { 123 | let interner = &mut Interner::default(); 124 | let inputs = &["[foo]", "[(foo)]", "[((foo))]"]; 125 | for input in inputs { 126 | let e = ExprParser::new().parse(interner, input).unwrap(); 127 | let e2 = Expr::Quote(Box::new(Expr::Call(Symbol(interner.get("foo").unwrap())))); 128 | assert_eq!(e, e2); 129 | } 130 | } 131 | 132 | #[test] 133 | fn test_parse_expr_quote_call2() { 134 | let interner = &mut Interner::default(); 135 | let inputs = &["[foo bar]", "[(foo bar)]", "[((foo bar))]"]; 136 | for input in inputs { 137 | let e = ExprParser::new().parse(interner, input).unwrap(); 138 | let e2 = Expr::Quote(Box::new(Expr::Compose(vec![ 139 | Expr::Call(Symbol(interner.get("foo").unwrap())), 140 | Expr::Call(Symbol(interner.get("bar").unwrap())), 141 | ]))); 142 | assert_eq!(e, e2); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /ucci-web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ucci-web" 3 | version = "0.1.0" 4 | edition = "2018" 5 | authors = ["Scott J Maddox"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | ucc = { path = "../ucc" } 14 | wasm-bindgen = "0.2.78" 15 | js-sys = "0.3.55" 16 | -------------------------------------------------------------------------------- /ucci-web/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | UCCI 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ucci-web/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | use ucc::interp::Interp; 7 | use wasm_bindgen::prelude::*; 8 | 9 | #[wasm_bindgen] 10 | pub struct Ucci { 11 | interp: Interp, 12 | buffer: Vec, 13 | } 14 | 15 | #[wasm_bindgen] 16 | impl Ucci { 17 | #[wasm_bindgen(constructor)] 18 | pub fn new() -> Self { 19 | Self { 20 | interp: Interp::default(), 21 | buffer: Vec::with_capacity(4096), 22 | } 23 | } 24 | 25 | pub fn is_done(&self) -> bool { 26 | self.interp.is_done() 27 | } 28 | 29 | pub fn interp_start(&mut self, input: &str, write_output: &js_sys::Function) { 30 | self.buffer.clear(); 31 | self.interp.interp_start(input, &mut self.buffer).unwrap(); 32 | let output = unsafe { std::str::from_utf8_unchecked(&self.buffer[..]) }; 33 | write_output 34 | .call1(&JsValue::null(), &JsValue::from(output)) 35 | .unwrap(); 36 | } 37 | 38 | pub fn interp_step(&mut self, write_output: &js_sys::Function) { 39 | self.buffer.clear(); 40 | self.interp.interp_step(&mut self.buffer).unwrap(); 41 | let output = unsafe { std::str::from_utf8_unchecked(&self.buffer[..]) }; 42 | write_output 43 | .call1(&JsValue::null(), &JsValue::from(output)) 44 | .unwrap(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ucci-web/ucci.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | import init, { Ucci } from "./pkg/ucci_web.js"; 8 | 9 | const PROMPT = "\n>>> "; 10 | 11 | async function run() { 12 | await init(); 13 | let textarea = document.createElement("textarea"); 14 | document.body.appendChild(textarea); 15 | textarea.focus(); 16 | textarea.setAttribute("autocapitalize", "off"); 17 | textarea.setAttribute("autocomplete", "off"); 18 | textarea.setAttribute("autocorrect", "off"); 19 | textarea.setAttribute("spellcheck", "false"); 20 | document.body.style = ` 21 | margin: 0; 22 | border: 0; 23 | padding: 0; 24 | `; 25 | textarea.style = ` 26 | margin: 0; 27 | border: 0; 28 | outline: none; 29 | padding: 0; 30 | position: absolute; 31 | top: 10px; 32 | left: 10px; 33 | width: calc(100vw - 20px); 34 | height: calc(100vh - 20px); 35 | resize: none; 36 | font-family: monospace,monospace; 37 | font-size: 10pt; 38 | autocorrect: off; 39 | autocapitalize: none; 40 | spellcheck: false; 41 | `; 42 | 43 | textarea.value = 44 | `\ 45 | Untyped Concatenative Calculus Interpreter (UCCI) 46 | Type ":help" to see the available commands. 47 | ` + PROMPT; 48 | 49 | function write_output(output) { 50 | textarea.value += output; 51 | textarea.scrollTop = textarea.scrollHeight; 52 | } 53 | 54 | let ucci = new Ucci(); 55 | 56 | function step() { 57 | if (ucci.is_done()) { 58 | textarea.value += PROMPT; 59 | textarea.scrollTop = textarea.scrollHeight; 60 | } else { 61 | ucci.interp_step(write_output); 62 | setTimeout(step); 63 | } 64 | } 65 | 66 | textarea.addEventListener("keydown", (ev) => { 67 | if (ev.key == "Enter") { 68 | if ( 69 | textarea.selectionStart === textarea.selectionEnd && 70 | textarea.selectionEnd === textarea.value.length 71 | ) { 72 | ev.preventDefault(); 73 | textarea.value += "\n"; 74 | let input = textarea.value.slice( 75 | textarea.value.lastIndexOf(PROMPT) + PROMPT.length, 76 | textarea.selectionEnd 77 | ); 78 | ucci.interp_start(input, write_output); 79 | setTimeout(step); 80 | } 81 | } 82 | if ( 83 | ev.ctrlKey && 84 | ev.key == "c" && 85 | textarea.selectionStart === textarea.selectionEnd && 86 | textarea.selectionEnd === textarea.value.length 87 | ) { 88 | ucci.interp_start("", write_output); 89 | setTimeout(step); 90 | } 91 | }); 92 | } 93 | 94 | run(); 95 | -------------------------------------------------------------------------------- /ucci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ucci" 3 | version = "0.1.0" 4 | edition = "2018" 5 | authors = ["Scott J Maddox"] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | ucc = { path = "../ucc" } 11 | linefeed = "0.6" 12 | -------------------------------------------------------------------------------- /ucci/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Scott J Maddox 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | use linefeed::{Interface, ReadResult}; 8 | use std::error::Error; 9 | use std::io::stdout; 10 | use ucc::interp::Interp; 11 | 12 | fn main() -> Result<(), Box> { 13 | let mut interp = Interp::default(); 14 | 15 | println!("Untyped Concatenative Calculus Interpreter (UCCI)"); 16 | println!("Type \":help\" to see the available commands."); 17 | let reader = Interface::new("ucci")?; 18 | reader.set_prompt("\n>>> ")?; 19 | while let ReadResult::Input(input) = reader.read_line()? { 20 | reader.add_history(input.clone()); 21 | interp.interp_start(input.as_str(), &mut stdout()).unwrap(); 22 | while !interp.is_done() { 23 | interp.interp_step(&mut stdout()).unwrap(); 24 | } 25 | } 26 | Ok(()) 27 | } 28 | --------------------------------------------------------------------------------