├── .cargo └── config.toml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── src ├── builtins.rs ├── context.rs ├── error.rs ├── main.rs ├── parser.lalrpop ├── parser.rs ├── runner.rs ├── unify.rs └── vars.rs └── sudoku.pl /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-Zpolonius"] 3 | 4 | [target.'cfg(not(FALSE))'] 5 | rustflags = ["-Zpolonius"] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ahash" 5 | version = "0.3.8" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" 8 | 9 | [[package]] 10 | name = "ahash" 11 | version = "0.4.6" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "f6789e291be47ace86a60303502173d84af8327e3627ecf334356ee0f87a164c" 14 | dependencies = [ 15 | "const-random", 16 | ] 17 | 18 | [[package]] 19 | name = "aho-corasick" 20 | version = "0.7.15" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 23 | dependencies = [ 24 | "memchr", 25 | ] 26 | 27 | [[package]] 28 | name = "arrayref" 29 | version = "0.3.6" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 32 | 33 | [[package]] 34 | name = "arrayvec" 35 | version = "0.5.2" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 38 | 39 | [[package]] 40 | name = "ascii-canvas" 41 | version = "2.0.0" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "ff8eb72df928aafb99fe5d37b383f2fe25bd2a765e3e5f7c365916b6f2463a29" 44 | dependencies = [ 45 | "term", 46 | ] 47 | 48 | [[package]] 49 | name = "atty" 50 | version = "0.2.14" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 53 | dependencies = [ 54 | "hermit-abi", 55 | "libc", 56 | "winapi", 57 | ] 58 | 59 | [[package]] 60 | name = "autocfg" 61 | version = "1.0.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 64 | 65 | [[package]] 66 | name = "base64" 67 | version = "0.12.3" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 70 | 71 | [[package]] 72 | name = "beef" 73 | version = "0.4.4" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "474a626a67200bd107d44179bb3d4fc61891172d11696609264589be6a0e6a43" 76 | 77 | [[package]] 78 | name = "bit-set" 79 | version = "0.5.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" 82 | dependencies = [ 83 | "bit-vec", 84 | ] 85 | 86 | [[package]] 87 | name = "bit-vec" 88 | version = "0.6.2" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" 91 | 92 | [[package]] 93 | name = "bitflags" 94 | version = "1.2.1" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 97 | 98 | [[package]] 99 | name = "blake2b_simd" 100 | version = "0.5.11" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 103 | dependencies = [ 104 | "arrayref", 105 | "arrayvec", 106 | "constant_time_eq", 107 | ] 108 | 109 | [[package]] 110 | name = "block-buffer" 111 | version = "0.7.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 114 | dependencies = [ 115 | "block-padding", 116 | "byte-tools", 117 | "byteorder", 118 | "generic-array", 119 | ] 120 | 121 | [[package]] 122 | name = "block-padding" 123 | version = "0.1.5" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 126 | dependencies = [ 127 | "byte-tools", 128 | ] 129 | 130 | [[package]] 131 | name = "byte-tools" 132 | version = "0.3.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 135 | 136 | [[package]] 137 | name = "byteorder" 138 | version = "1.3.4" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 141 | 142 | [[package]] 143 | name = "cc" 144 | version = "1.0.62" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" 147 | 148 | [[package]] 149 | name = "cfg-if" 150 | version = "0.1.10" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 153 | 154 | [[package]] 155 | name = "cfg-if" 156 | version = "1.0.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 159 | 160 | [[package]] 161 | name = "codespan-reporting" 162 | version = "0.9.5" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "6e0762455306b1ed42bc651ef6a2197aabda5e1d4a43c34d5eab5c1a3634e81d" 165 | dependencies = [ 166 | "termcolor", 167 | "unicode-width", 168 | ] 169 | 170 | [[package]] 171 | name = "const-random" 172 | version = "0.1.11" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "02dc82c12dc2ee6e1ded861cf7d582b46f66f796d1b6c93fa28b911ead95da02" 175 | dependencies = [ 176 | "const-random-macro", 177 | "proc-macro-hack", 178 | ] 179 | 180 | [[package]] 181 | name = "const-random-macro" 182 | version = "0.1.11" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "fc757bbb9544aa296c2ae00c679e81f886b37e28e59097defe0cf524306f6685" 185 | dependencies = [ 186 | "getrandom 0.2.0", 187 | "proc-macro-hack", 188 | ] 189 | 190 | [[package]] 191 | name = "constant_time_eq" 192 | version = "0.1.5" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 195 | 196 | [[package]] 197 | name = "cpu-time" 198 | version = "1.0.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "e9e393a7668fe1fad3075085b86c781883000b4ede868f43627b34a87c8b7ded" 201 | dependencies = [ 202 | "libc", 203 | "winapi", 204 | ] 205 | 206 | [[package]] 207 | name = "crossbeam-utils" 208 | version = "0.7.2" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 211 | dependencies = [ 212 | "autocfg", 213 | "cfg-if 0.1.10", 214 | "lazy_static", 215 | ] 216 | 217 | [[package]] 218 | name = "diff" 219 | version = "0.1.12" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 222 | 223 | [[package]] 224 | name = "digest" 225 | version = "0.8.1" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 228 | dependencies = [ 229 | "generic-array", 230 | ] 231 | 232 | [[package]] 233 | name = "dirs" 234 | version = "1.0.5" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" 237 | dependencies = [ 238 | "libc", 239 | "redox_users", 240 | "winapi", 241 | ] 242 | 243 | [[package]] 244 | name = "dirs-next" 245 | version = "1.0.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" 248 | dependencies = [ 249 | "cfg-if 1.0.0", 250 | "dirs-sys-next", 251 | ] 252 | 253 | [[package]] 254 | name = "dirs-sys-next" 255 | version = "0.1.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" 258 | dependencies = [ 259 | "libc", 260 | "redox_users", 261 | "winapi", 262 | ] 263 | 264 | [[package]] 265 | name = "docopt" 266 | version = "1.1.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" 269 | dependencies = [ 270 | "lazy_static", 271 | "regex", 272 | "serde", 273 | "strsim", 274 | ] 275 | 276 | [[package]] 277 | name = "either" 278 | version = "1.6.1" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 281 | 282 | [[package]] 283 | name = "ena" 284 | version = "0.14.0" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" 287 | dependencies = [ 288 | "log", 289 | ] 290 | 291 | [[package]] 292 | name = "env_logger" 293 | version = "0.7.1" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 296 | dependencies = [ 297 | "atty", 298 | "humantime", 299 | "log", 300 | "regex", 301 | "termcolor", 302 | ] 303 | 304 | [[package]] 305 | name = "fake-simd" 306 | version = "0.1.2" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 309 | 310 | [[package]] 311 | name = "fixedbitset" 312 | version = "0.2.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" 315 | 316 | [[package]] 317 | name = "fnv" 318 | version = "1.0.7" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 321 | 322 | [[package]] 323 | name = "generic-array" 324 | version = "0.12.3" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 327 | dependencies = [ 328 | "typenum", 329 | ] 330 | 331 | [[package]] 332 | name = "getrandom" 333 | version = "0.1.15" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 336 | dependencies = [ 337 | "cfg-if 0.1.10", 338 | "libc", 339 | "wasi", 340 | ] 341 | 342 | [[package]] 343 | name = "getrandom" 344 | version = "0.2.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" 347 | dependencies = [ 348 | "cfg-if 0.1.10", 349 | "libc", 350 | "wasi", 351 | ] 352 | 353 | [[package]] 354 | name = "hashbrown" 355 | version = "0.8.2" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" 358 | dependencies = [ 359 | "ahash 0.3.8", 360 | "autocfg", 361 | ] 362 | 363 | [[package]] 364 | name = "hashbrown" 365 | version = "0.9.1" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 368 | 369 | [[package]] 370 | name = "hermit-abi" 371 | version = "0.1.17" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 374 | dependencies = [ 375 | "libc", 376 | ] 377 | 378 | [[package]] 379 | name = "humantime" 380 | version = "1.3.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 383 | dependencies = [ 384 | "quick-error", 385 | ] 386 | 387 | [[package]] 388 | name = "indexmap" 389 | version = "1.6.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" 392 | dependencies = [ 393 | "autocfg", 394 | "hashbrown 0.9.1", 395 | ] 396 | 397 | [[package]] 398 | name = "itertools" 399 | version = "0.9.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 402 | dependencies = [ 403 | "either", 404 | ] 405 | 406 | [[package]] 407 | name = "lalrpop" 408 | version = "0.19.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "60fb56191fb8ed5311597e5750debe6779c9fdb487dbaa5ff302592897d7a2c8" 411 | dependencies = [ 412 | "ascii-canvas", 413 | "atty", 414 | "bit-set", 415 | "diff", 416 | "docopt", 417 | "ena", 418 | "itertools", 419 | "lalrpop-util", 420 | "petgraph", 421 | "regex", 422 | "regex-syntax", 423 | "serde", 424 | "serde_derive", 425 | "sha2", 426 | "string_cache", 427 | "term", 428 | "unicode-xid", 429 | ] 430 | 431 | [[package]] 432 | name = "lalrpop-util" 433 | version = "0.19.1" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "6771161eff561647fad8bb7e745e002c304864fb8f436b52b30acda51fca4408" 436 | 437 | [[package]] 438 | name = "lasso" 439 | version = "0.3.1" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "bf1a626ea51398f5acf36666c8046ff4bfd048aab88e92db676d2a6eac8805d0" 442 | dependencies = [ 443 | "hashbrown 0.8.2", 444 | ] 445 | 446 | [[package]] 447 | name = "lazy_static" 448 | version = "1.4.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 451 | 452 | [[package]] 453 | name = "libc" 454 | version = "0.2.80" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" 457 | 458 | [[package]] 459 | name = "log" 460 | version = "0.4.11" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 463 | dependencies = [ 464 | "cfg-if 0.1.10", 465 | ] 466 | 467 | [[package]] 468 | name = "logos" 469 | version = "0.11.4" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "b91c49573597a5d6c094f9031617bb1fed15c0db68c81e6546d313414ce107e4" 472 | dependencies = [ 473 | "logos-derive", 474 | ] 475 | 476 | [[package]] 477 | name = "logos-derive" 478 | version = "0.11.5" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "797b1f8a0571b331c1b47e7db245af3dc634838da7a92b3bef4e30376ae1c347" 481 | dependencies = [ 482 | "beef", 483 | "fnv", 484 | "proc-macro2", 485 | "quote", 486 | "regex-syntax", 487 | "syn", 488 | "utf8-ranges", 489 | ] 490 | 491 | [[package]] 492 | name = "memchr" 493 | version = "2.3.4" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 496 | 497 | [[package]] 498 | name = "new_debug_unreachable" 499 | version = "1.0.4" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 502 | 503 | [[package]] 504 | name = "nix" 505 | version = "0.18.0" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "83450fe6a6142ddd95fb064b746083fc4ef1705fe81f64a64e1d4b39f54a1055" 508 | dependencies = [ 509 | "bitflags", 510 | "cc", 511 | "cfg-if 0.1.10", 512 | "libc", 513 | ] 514 | 515 | [[package]] 516 | name = "opaque-debug" 517 | version = "0.2.3" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 520 | 521 | [[package]] 522 | name = "petgraph" 523 | version = "0.5.1" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" 526 | dependencies = [ 527 | "fixedbitset", 528 | "indexmap", 529 | ] 530 | 531 | [[package]] 532 | name = "phf_shared" 533 | version = "0.8.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 536 | dependencies = [ 537 | "siphasher", 538 | ] 539 | 540 | [[package]] 541 | name = "precomputed-hash" 542 | version = "0.1.1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 545 | 546 | [[package]] 547 | name = "pretty_env_logger" 548 | version = "0.4.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" 551 | dependencies = [ 552 | "env_logger", 553 | "log", 554 | ] 555 | 556 | [[package]] 557 | name = "proc-macro-hack" 558 | version = "0.5.19" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 561 | 562 | [[package]] 563 | name = "proc-macro2" 564 | version = "1.0.24" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 567 | dependencies = [ 568 | "unicode-xid", 569 | ] 570 | 571 | [[package]] 572 | name = "prolog" 573 | version = "0.1.0" 574 | dependencies = [ 575 | "codespan-reporting", 576 | "cpu-time", 577 | "itertools", 578 | "lalrpop", 579 | "lalrpop-util", 580 | "lasso", 581 | "log", 582 | "logos", 583 | "pretty_env_logger", 584 | "rustyline", 585 | "scoped_map", 586 | "typed-arena", 587 | "unicode-segmentation", 588 | ] 589 | 590 | [[package]] 591 | name = "quick-error" 592 | version = "1.2.3" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 595 | 596 | [[package]] 597 | name = "quote" 598 | version = "1.0.7" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 601 | dependencies = [ 602 | "proc-macro2", 603 | ] 604 | 605 | [[package]] 606 | name = "redox_syscall" 607 | version = "0.1.57" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 610 | 611 | [[package]] 612 | name = "redox_users" 613 | version = "0.3.5" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 616 | dependencies = [ 617 | "getrandom 0.1.15", 618 | "redox_syscall", 619 | "rust-argon2", 620 | ] 621 | 622 | [[package]] 623 | name = "regex" 624 | version = "1.4.2" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" 627 | dependencies = [ 628 | "aho-corasick", 629 | "memchr", 630 | "regex-syntax", 631 | "thread_local", 632 | ] 633 | 634 | [[package]] 635 | name = "regex-syntax" 636 | version = "0.6.21" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" 639 | 640 | [[package]] 641 | name = "rust-argon2" 642 | version = "0.8.2" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" 645 | dependencies = [ 646 | "base64", 647 | "blake2b_simd", 648 | "constant_time_eq", 649 | "crossbeam-utils", 650 | ] 651 | 652 | [[package]] 653 | name = "rustyline" 654 | version = "6.3.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "6f0d5e7b0219a3eadd5439498525d4765c59b7c993ef0c12244865cd2d988413" 657 | dependencies = [ 658 | "cfg-if 0.1.10", 659 | "dirs-next", 660 | "libc", 661 | "log", 662 | "memchr", 663 | "nix", 664 | "scopeguard", 665 | "unicode-segmentation", 666 | "unicode-width", 667 | "utf8parse", 668 | "winapi", 669 | ] 670 | 671 | [[package]] 672 | name = "scoped_map" 673 | version = "0.2.0" 674 | source = "git+https://github.com/mb64/scoped_map.git?branch=main#79b67877d513756ace7c54e7574112deafe56851" 675 | dependencies = [ 676 | "ahash 0.4.6", 677 | "typed-arena", 678 | ] 679 | 680 | [[package]] 681 | name = "scopeguard" 682 | version = "1.1.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 685 | 686 | [[package]] 687 | name = "serde" 688 | version = "1.0.117" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" 691 | dependencies = [ 692 | "serde_derive", 693 | ] 694 | 695 | [[package]] 696 | name = "serde_derive" 697 | version = "1.0.117" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" 700 | dependencies = [ 701 | "proc-macro2", 702 | "quote", 703 | "syn", 704 | ] 705 | 706 | [[package]] 707 | name = "sha2" 708 | version = "0.8.2" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" 711 | dependencies = [ 712 | "block-buffer", 713 | "digest", 714 | "fake-simd", 715 | "opaque-debug", 716 | ] 717 | 718 | [[package]] 719 | name = "siphasher" 720 | version = "0.3.3" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" 723 | 724 | [[package]] 725 | name = "string_cache" 726 | version = "0.8.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" 729 | dependencies = [ 730 | "lazy_static", 731 | "new_debug_unreachable", 732 | "phf_shared", 733 | "precomputed-hash", 734 | "serde", 735 | ] 736 | 737 | [[package]] 738 | name = "strsim" 739 | version = "0.9.3" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 742 | 743 | [[package]] 744 | name = "syn" 745 | version = "1.0.48" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" 748 | dependencies = [ 749 | "proc-macro2", 750 | "quote", 751 | "unicode-xid", 752 | ] 753 | 754 | [[package]] 755 | name = "term" 756 | version = "0.5.2" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" 759 | dependencies = [ 760 | "byteorder", 761 | "dirs", 762 | "winapi", 763 | ] 764 | 765 | [[package]] 766 | name = "termcolor" 767 | version = "1.1.0" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 770 | dependencies = [ 771 | "winapi-util", 772 | ] 773 | 774 | [[package]] 775 | name = "thread_local" 776 | version = "1.0.1" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 779 | dependencies = [ 780 | "lazy_static", 781 | ] 782 | 783 | [[package]] 784 | name = "typed-arena" 785 | version = "2.0.1" 786 | source = "git+https://github.com/mb64/rust-typed-arena#43586bc0f634aeefbd4bb4196298a4c9ba410a0a" 787 | 788 | [[package]] 789 | name = "typenum" 790 | version = "1.12.0" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 793 | 794 | [[package]] 795 | name = "unicode-segmentation" 796 | version = "1.7.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae" 799 | 800 | [[package]] 801 | name = "unicode-width" 802 | version = "0.1.8" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 805 | 806 | [[package]] 807 | name = "unicode-xid" 808 | version = "0.2.1" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 811 | 812 | [[package]] 813 | name = "utf8-ranges" 814 | version = "1.0.4" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "b4ae116fef2b7fea257ed6440d3cfcff7f190865f170cdad00bb6465bf18ecba" 817 | 818 | [[package]] 819 | name = "utf8parse" 820 | version = "0.2.0" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 823 | 824 | [[package]] 825 | name = "wasi" 826 | version = "0.9.0+wasi-snapshot-preview1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 829 | 830 | [[package]] 831 | name = "winapi" 832 | version = "0.3.9" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 835 | dependencies = [ 836 | "winapi-i686-pc-windows-gnu", 837 | "winapi-x86_64-pc-windows-gnu", 838 | ] 839 | 840 | [[package]] 841 | name = "winapi-i686-pc-windows-gnu" 842 | version = "0.4.0" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 845 | 846 | [[package]] 847 | name = "winapi-util" 848 | version = "0.1.5" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 851 | dependencies = [ 852 | "winapi", 853 | ] 854 | 855 | [[package]] 856 | name = "winapi-x86_64-pc-windows-gnu" 857 | version = "0.4.0" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 860 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "prolog" 3 | version = "0.1.0" 4 | authors = ["Mark Barbone "] 5 | edition = "2018" 6 | build = "build.rs" # For LALRPOP 7 | 8 | [dependencies] 9 | scoped_map = { git = "https://github.com/mb64/scoped_map.git", branch = "main" } 10 | typed-arena = { git = "https://github.com/mb64/rust-typed-arena" } 11 | lasso = "0.3.1" 12 | logos = "0.11.4" 13 | lalrpop-util = "0.19.0" 14 | codespan-reporting = "0.9.5" 15 | itertools = "0.9" 16 | rustyline = "6.3.0" 17 | log = "0.4.11" 18 | pretty_env_logger = "0.4" 19 | cpu-time = "1.0.0" 20 | unicode-segmentation = "1.7" 21 | 22 | [build-dependencies] 23 | lalrpop = "0.19.0" 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Mark Barbone 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A simple Prolog 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | A simple Prolog implementation in Rust. It can solve (mini) sudoku! See `sudoku.pl` for details. 6 | 7 | Example interaction: 8 | 9 | ```prolog 10 | $ cargo run 11 | Finished dev [unoptimized + debuginfo] target(s) in 0.03s 12 | Running `target/debug/prolog` 13 | > edge(x, y). 14 | > edge(y, z). 15 | > edge(y, w). 16 | > 17 | > path(A, A). 18 | > path(A, B) :- edge(A, X), path(X, B). 19 | > 20 | > path(x, Place) ? 21 | 22 | Solution: 23 | Place = x 24 | ? ; 25 | 26 | Solution: 27 | Place = y 28 | ? ; 29 | 30 | Solution: 31 | Place = z 32 | ? ; 33 | 34 | Solution: 35 | Place = w 36 | ? 37 | 38 | Yes. 39 | > 40 | 41 | Bye! 42 | ``` 43 | 44 | ## TODO 45 | 46 | More legit Prolog-ness: 47 | - Exceptions 48 | - Actual toplevel 49 | - Operators 50 | - ISO builtins 51 | 52 | More datatypes: 53 | - ~~Integers~~ Done! 54 | - Strings 55 | - Maybe floats, and maybe integers with constraints? (Would be difficult) 56 | 57 | More builtins: 58 | - ~~Parse arithmetic operators~~ (but somehow keep negated literals as literals) 59 | - ~~`is`~~ 60 | - ~~`call`~~ 61 | - ~~Negation: `not`, `\=`, `\+`~~ 62 | - ~~`cpu_time`, to run benchmarks~~ 63 | - load clauses from file 64 | - Cuts: `!` (will probably require a new `Command` variant) 65 | - `->` and `*->` (soft cut) 66 | - standard library 67 | - IO 68 | 69 | More usability: 70 | - Refactor core engine into library 71 | - Load file in REPL 72 | - Reset REPL 73 | - Command-line args to load files, run queries, optional REPL 74 | - Nicer lexer: allow unicode, base-n literals, etc 75 | - ~~Allow multiple goals in a query~~ 76 | - ~~Print atoms as `x`, not `x()`~~ 77 | - ~~Allow `_Variables`, and~~ generate warnings for singleton variables 78 | * Question -- should it report the found values of `_Variables`? SWI Prolog 79 | does but GNU Prolog doesn't 80 | - ~~Errors? Would be nice to have debug info on each clause, and be able to 81 | give pretty stacktraces on fatal errors~~ Done! 82 | - Maybe don't abuse `codespan_reporting` for stack traces 83 | - Debugging facilities? 84 | - Investigate alternatives to rustyline 85 | 86 | More fancy datastructures: 87 | - Probably not 88 | - ~~Allocate functor arguments in an arena -- switch `Box<[VarId]>` to `&'arena [VarId]`~~ Done! 89 | - Rewrite `scoped_map` to give a different interface: single map, mutable 90 | operations to push and pop scope 91 | - For `,` operator, figure out something better than CPS 92 | - Build it on a disjoint-stack style typed arena, with unsafe reset operations 93 | - Bytecode VM? Almost certainly not 94 | 95 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | 3 | fn main() { 4 | lalrpop::process_root().unwrap(); 5 | } 6 | -------------------------------------------------------------------------------- /src/builtins.rs: -------------------------------------------------------------------------------- 1 | //! Built-in Prolog operations 2 | 3 | use lasso::{Rodeo, Spur}; 4 | use std::collections::HashMap; 5 | use std::convert::TryInto; 6 | 7 | use crate::context::*; 8 | use crate::runner::*; 9 | use crate::unify::State; 10 | use crate::vars::*; 11 | 12 | /// Part of the `Context`, it stores the `Spur`s associated with a bunch of built-in functors 13 | pub struct Builtins { 14 | pub cons: Spur, 15 | pub nil: Spur, 16 | pub add: Spur, 17 | pub sub: Spur, 18 | pub mul: Spur, 19 | pub div: Spur, 20 | } 21 | 22 | impl Builtins { 23 | pub fn new(rodeo: &mut Rodeo) -> Self { 24 | Self { 25 | cons: rodeo.get_or_intern_static("."), 26 | nil: rodeo.get_or_intern_static("[]"), 27 | add: rodeo.get_or_intern_static("+"), 28 | sub: rodeo.get_or_intern_static("-"), 29 | mul: rodeo.get_or_intern_static("*"), 30 | div: rodeo.get_or_intern_static("/"), 31 | } 32 | } 33 | } 34 | 35 | type Builtin = fn(&Context, &mut VarTable<'_>, &[VarId], &mut dyn Runner) -> SolverResult; 36 | 37 | /// `fail` builtin -- immediately backtracks 38 | fn fail( 39 | _ctx: &Context, 40 | _vars: &mut VarTable<'_>, 41 | _args: &[VarId], 42 | _runner: &mut dyn Runner, 43 | ) -> SolverResult { 44 | Ok(Command::KeepGoing) 45 | } 46 | 47 | /// `print` builtin -- prints its argument 48 | fn print( 49 | ctx: &Context, 50 | vars: &mut VarTable<'_>, 51 | args: &[VarId], 52 | runner: &mut dyn Runner, 53 | ) -> SolverResult { 54 | match *args { 55 | [x] => print!("{}", vars.show(x, ctx)), 56 | _ => panic!("Wrong number of arguments"), 57 | } 58 | runner.solution(ctx, vars) 59 | } 60 | 61 | /// `println` builtin -- prints its argument, followed by a newline 62 | fn println( 63 | ctx: &Context, 64 | vars: &mut VarTable<'_>, 65 | args: &[VarId], 66 | runner: &mut dyn Runner, 67 | ) -> SolverResult { 68 | match *args { 69 | [x] => println!("{}", vars.show(x, ctx)), 70 | _ => panic!("Wrong number of arguments"), 71 | } 72 | runner.solution(ctx, vars) 73 | } 74 | 75 | /// `nl` builtin -- prints a newline 76 | fn nl( 77 | ctx: &Context, 78 | vars: &mut VarTable<'_>, 79 | args: &[VarId], 80 | runner: &mut dyn Runner, 81 | ) -> SolverResult { 82 | match *args { 83 | [] => println!(""), 84 | _ => panic!("Wrong number of arguments"), 85 | } 86 | runner.solution(ctx, vars) 87 | } 88 | 89 | /// `=` builtin -- unifies its arguments 90 | fn unify( 91 | ctx: &Context, 92 | vars: &mut VarTable<'_>, 93 | args: &[VarId], 94 | runner: &mut dyn Runner, 95 | ) -> SolverResult { 96 | let (a, b) = match *args { 97 | [a, b] => (a, b), 98 | _ => panic!("Wrong number of arguments"), 99 | }; 100 | State { ctx, vars, runner }.unify(a, b) 101 | } 102 | 103 | /// `not`/1 builtin, aka `\+` -- fails if its goal can be met 104 | fn not( 105 | ctx: &Context, 106 | vars: &mut VarTable<'_>, 107 | args: &[VarId], 108 | runner: &mut dyn Runner, 109 | ) -> SolverResult { 110 | let arg = match *args { 111 | [arg] => arg, 112 | _ => panic!("Wrong number of arguments"), 113 | }; 114 | 115 | log::trace!("trying goal {}", vars.dbg(arg, ctx)); 116 | let mut new_vars = vars.backtrackable(); 117 | let mut state = State { 118 | ctx, 119 | vars: &mut new_vars, 120 | runner: &mut OneSoln, 121 | }; 122 | match state.solve(arg)? { 123 | // Stop requested, it must have reached a solution 124 | // Fail 125 | Command::Stop => { 126 | log::trace!("subgoal succeeded, so not(subgoal) fails"); 127 | Ok(Command::KeepGoing) 128 | } 129 | // Did not reach a solution -- successfully unsolvable 130 | Command::KeepGoing => { 131 | drop(new_vars); 132 | log::trace!("subgoal failed, so not(subgoal) succeeds"); 133 | runner.solution(ctx, vars) 134 | } 135 | } 136 | } 137 | 138 | /// `'\='/2` -- fails if its args can be unified 139 | fn not_unify( 140 | ctx: &Context, 141 | vars: &mut VarTable<'_>, 142 | args: &[VarId], 143 | runner: &mut dyn Runner, 144 | ) -> SolverResult { 145 | let (a, b) = match *args { 146 | [a, b] => (a, b), 147 | _ => panic!("Wrong number of arguments"), 148 | }; 149 | 150 | log::trace!( 151 | "trying to unify {} and {}", 152 | vars.dbg(a, ctx), 153 | vars.dbg(b, ctx) 154 | ); 155 | let mut new_vars = vars.backtrackable(); 156 | let mut state = State { 157 | ctx, 158 | vars: &mut new_vars, 159 | runner: &mut OneSoln, 160 | }; 161 | match state.unify(a, b)? { 162 | // Stop requested, it must have reached a solution 163 | // Fail 164 | Command::Stop => { 165 | log::trace!("subgoal succeeded, so not(subgoal) fails"); 166 | Ok(Command::KeepGoing) 167 | } 168 | // Did not reach a solution -- successfully unsolvable 169 | Command::KeepGoing => { 170 | drop(new_vars); 171 | log::trace!("subgoal failed, so not(subgoal) succeeds"); 172 | runner.solution(ctx, vars) 173 | } 174 | } 175 | } 176 | 177 | /// A helper for `is` 178 | fn compute(ctx: &Context, vars: &mut VarTable<'_>, var: VarId) -> SolverResult { 179 | match vars.lookup(var) { 180 | Item::Unresolved => Err("Can't compute: contains uninstantiated variable(s)".into()), 181 | Item::Var(_) => panic!("lookup {} returned var", var), 182 | Item::Number(x) => Ok(x), 183 | Item::Functor { name, args } if name == ctx.builtins.add => { 184 | let mut sum = 0; 185 | for &arg in args { 186 | sum += compute(ctx, vars, arg)?; 187 | } 188 | Ok(sum) 189 | } 190 | Item::Functor { name, args } if name == ctx.builtins.sub => match *args { 191 | [x] => Ok(-compute(ctx, vars, x)?), 192 | [x, y] => Ok(compute(ctx, vars, x)? - compute(ctx, vars, y)?), 193 | _ => Err(format!( 194 | "Can't compute: wrong number of arguments for '-' ({})", 195 | args.len() 196 | ) 197 | .into()), 198 | }, 199 | Item::Functor { name, args } if name == ctx.builtins.mul => { 200 | let mut prod = 0; 201 | for &arg in args { 202 | prod *= compute(ctx, vars, arg)?; 203 | } 204 | Ok(prod) 205 | } 206 | Item::Functor { name, args } if name == ctx.builtins.div => match *args { 207 | [x, y] => Ok(compute(ctx, vars, x)? / compute(ctx, vars, y)?), 208 | _ => Err(format!( 209 | "Can't compute: wrong number of arguments for '/' ({})", 210 | args.len() 211 | ) 212 | .into()), 213 | }, 214 | Item::Functor { name, args } => Err(format!( 215 | "Can't compute: unknown operator {}/{}", 216 | ctx.rodeo.resolve(&name), 217 | args.len() 218 | ) 219 | .into()), 220 | } 221 | } 222 | 223 | /// `is/2` builtin: `A is B` performs computation on B, then unifies it with A 224 | fn is( 225 | ctx: &Context, 226 | vars: &mut VarTable<'_>, 227 | args: &[VarId], 228 | runner: &mut dyn Runner, 229 | ) -> SolverResult { 230 | let (var, eqn) = match *args { 231 | [var, eqn] => (var, eqn), 232 | _ => panic!("Wrong number of arguments"), 233 | }; 234 | 235 | let result = compute(ctx, vars, eqn)?; 236 | 237 | State { ctx, vars, runner }.unify_with_known(var, Item::Number(result)) 238 | } 239 | 240 | /// `<`/2 -- `x < y` succeeds if `x` is less than `y` 241 | fn less_than( 242 | ctx: &Context, 243 | vars: &mut VarTable<'_>, 244 | args: &[VarId], 245 | runner: &mut dyn Runner, 246 | ) -> SolverResult { 247 | let (lhs, rhs) = match *args { 248 | [lhs, rhs] => (lhs, rhs), 249 | _ => panic!("Wrong number of arguments"), 250 | }; 251 | 252 | if let (Item::Number(x), Item::Number(y)) = (vars.lookup(lhs), vars.lookup(rhs)) { 253 | if x < y { 254 | // Success! 255 | runner.solution(ctx, vars) 256 | } else { 257 | // Nope 258 | Ok(Command::KeepGoing) 259 | } 260 | } else { 261 | Err("`/2 -- `x > y` succeeds if `x` is greater than `y` 266 | fn greater_than( 267 | ctx: &Context, 268 | vars: &mut VarTable<'_>, 269 | args: &[VarId], 270 | runner: &mut dyn Runner, 271 | ) -> SolverResult { 272 | let (lhs, rhs) = match *args { 273 | [lhs, rhs] => (lhs, rhs), 274 | _ => panic!("Wrong number of arguments"), 275 | }; 276 | 277 | if let (Item::Number(x), Item::Number(y)) = (vars.lookup(lhs), vars.lookup(rhs)) { 278 | if x > y { 279 | // Success! 280 | runner.solution(ctx, vars) 281 | } else { 282 | // Nope 283 | Ok(Command::KeepGoing) 284 | } 285 | } else { 286 | Err(">/2: args are not numbers".into()) // TODO better error message 287 | } 288 | } 289 | 290 | /// `cpu_time/1` builtin: `cpu_time(X)` unifies `X` with the CPU time since the start of the 291 | /// program, in milliseconds 292 | fn cpu_time( 293 | ctx: &Context, 294 | vars: &mut VarTable<'_>, 295 | args: &[VarId], 296 | runner: &mut dyn Runner, 297 | ) -> SolverResult { 298 | let arg = match *args { 299 | [arg] => arg, 300 | _ => panic!("Wrong number of arguments"), 301 | }; 302 | 303 | let result = cpu_time::ThreadTime::now() 304 | .as_duration() 305 | .as_millis() 306 | .try_into() 307 | .unwrap(); 308 | 309 | State { ctx, vars, runner }.unify_with_known(arg, Item::Number(result)) 310 | } 311 | 312 | /// `call/n` -- add some extra args to a functor, then call it as the goal 313 | fn call( 314 | ctx: &Context, 315 | vars: &mut VarTable<'_>, 316 | args: &[VarId], 317 | runner: &mut dyn Runner, 318 | ) -> SolverResult { 319 | if args.len() == 0 { 320 | panic!("Wrong number of arguments"); 321 | } 322 | 323 | let extra = &args[1..]; 324 | match vars.lookup(args[0]) { 325 | Item::Functor { 326 | name, 327 | args: ref orig, 328 | } => { 329 | let new = 330 | vars.new_var_of_functor(name, orig.iter().copied().chain(extra.iter().copied())); 331 | 332 | State { ctx, vars, runner }.solve(new) 333 | } 334 | _ => Err("call: type error: not a functor".into()), 335 | } 336 | } 337 | 338 | /// `functor/3` -- give the name and arity of a functor 339 | fn functor( 340 | ctx: &Context, 341 | vars: &mut VarTable<'_>, 342 | args: &[VarId], 343 | runner: &mut dyn Runner, 344 | ) -> SolverResult { 345 | let (x, f, n) = match *args { 346 | [x, f, n] => (x, f, n), 347 | _ => panic!("Wrong number of arguments"), 348 | }; 349 | 350 | match vars.lookup_with_varid(x) { 351 | (_, Item::Var(_)) => panic!("got var from lookup"), 352 | (_, Item::Number(_)) => { 353 | log::trace!("functor/3: a number, not a functor"); 354 | Ok(Command::KeepGoing) 355 | } 356 | (_, Item::Functor { name, args }) => { 357 | // Unify f with name, and n with args.len() 358 | 359 | // Runner to unify n with args.len() 360 | struct Temp<'a> { 361 | n: VarId, 362 | len: usize, 363 | base: &'a mut dyn Runner, 364 | } 365 | impl Runner for Temp<'_> { 366 | fn solution(&mut self, ctx: &Context, vars: &mut VarTable) -> SolverResult { 367 | State { 368 | ctx, 369 | vars, 370 | runner: self.base, 371 | } 372 | .unify_with_known(self.n, Item::Number(self.len as i64)) 373 | } 374 | } 375 | 376 | State { 377 | ctx, 378 | vars, 379 | runner: &mut Temp { 380 | n, 381 | len: args.len(), 382 | base: runner, 383 | }, 384 | } 385 | .unify_with_known(f, Item::Functor { name, args: &[] }) 386 | } 387 | (vx, Item::Unresolved) => { 388 | // Make sure f and n are cool, then allocate new vars for args and resolve vx to 389 | // Functor { f, args } 390 | match (vars.lookup(f), vars.lookup(n)) { 391 | (Item::Functor { name, args: &[] }, Item::Number(arity)) if arity >= 0 => { 392 | vars.update_to_functor(vx, name, arity as usize); 393 | runner.solution(ctx, vars) 394 | } 395 | _ => { 396 | // TODO: Different error depending on what it is 397 | // (instantiation error, type error) 398 | Err("functor/3: bad arguments".into()) 399 | } 400 | } 401 | } 402 | } 403 | } 404 | 405 | pub fn builtins(rodeo: &mut Rodeo) -> HashMap { 406 | [ 407 | ("=", 2, unify as Builtin), 408 | ("\\=", 2, not_unify as Builtin), 409 | ("fail", 0, fail as Builtin), 410 | ("not", 1, not as Builtin), 411 | ("\\+", 1, not as Builtin), 412 | ("is", 2, is as Builtin), 413 | ("print", 1, print as Builtin), 414 | ("write", 1, print as Builtin), 415 | ("println", 1, println as Builtin), 416 | ("nl", 0, nl as Builtin), 417 | ("<", 2, less_than as Builtin), 418 | (">", 2, greater_than as Builtin), 419 | ("cpu_time", 1, cpu_time as Builtin), 420 | ("functor", 3, functor as Builtin), 421 | // call takes any number of arguments, but unfortunately there's no great way to express 422 | // that rn 423 | ("call", 2, call as Builtin), 424 | ("call", 3, call as Builtin), 425 | ("call", 4, call as Builtin), 426 | ("call", 5, call as Builtin), 427 | ] 428 | .iter() 429 | .map(|&(name, arity, action)| { 430 | ( 431 | RelId { 432 | name: rodeo.get_or_intern_static(name), 433 | arity, 434 | }, 435 | Relation::Builtin(action), 436 | ) 437 | }) 438 | .collect() 439 | } 440 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | //! The main datastructures 2 | 3 | use codespan_reporting::files::SimpleFiles; 4 | use itertools::Itertools; 5 | use lasso::{Rodeo, Spur}; 6 | use std::collections::{hash_map::Entry, HashMap}; 7 | use unicode_segmentation::UnicodeSegmentation; 8 | 9 | use crate::builtins::Builtins; 10 | use crate::parser::{self, Span}; 11 | use crate::runner::{Runner, SolverResult}; 12 | use crate::vars::*; 13 | 14 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | pub struct RelId { 16 | pub name: Spur, 17 | pub arity: u32, 18 | } 19 | 20 | pub struct Context { 21 | pub rels: HashMap, 22 | pub rodeo: Rodeo, 23 | pub files: SimpleFiles, 24 | pub builtins: Builtins, 25 | pub var_names: HashMap, 26 | } 27 | 28 | impl Context { 29 | pub fn add_clause(&mut self, rel_id: RelId, clause: Clause) -> Result<(), &'static str> { 30 | let entry = self 31 | .rels 32 | .entry(rel_id) 33 | .or_insert(Relation::User(Vec::new())); 34 | 35 | match *entry { 36 | Relation::User(ref mut cs) => { 37 | cs.push(clause); 38 | Ok(()) 39 | } 40 | // TODO: report as a full error, with its span 41 | Relation::Builtin(_) => Err("Cannot extend builtin relation"), 42 | } 43 | } 44 | 45 | pub fn add_ast_clause(&mut self, ast_clause: parser::Clause) -> Result<(), &'static str> { 46 | let rel_id = RelId { 47 | name: ast_clause.functor, 48 | arity: ast_clause.args.len() as u32, 49 | }; 50 | let clause = Clause::from_ast(&ast_clause, &mut self.rodeo); 51 | self.add_clause(rel_id, clause) 52 | } 53 | 54 | pub fn dbg_var_names(&self) -> String { 55 | format!( 56 | "Top-level unification vars:\n {}", 57 | self.var_names 58 | .iter() 59 | .map(|(n, v)| format!("{} = {}", self.rodeo.resolve(n), v)) 60 | .format("\n ") 61 | ) 62 | } 63 | } 64 | 65 | #[derive(Clone)] 66 | pub enum Relation { 67 | Builtin(fn(&Context, &mut VarTable<'_>, &[VarId], &mut dyn Runner) -> SolverResult), 68 | User(Vec), 69 | } 70 | 71 | /// A local variable 72 | // Negative number is arguments, positive number is local var 73 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 74 | pub struct Local(pub(crate) i32); 75 | 76 | impl Local { 77 | fn arg(n: i32) -> Self { 78 | assert!(n >= 0); 79 | Self(-n - 1) 80 | } 81 | } 82 | 83 | #[derive(Debug, Clone, PartialEq)] 84 | pub enum ClauseItem { 85 | Var(Local), 86 | Number(i64), 87 | Functor { name: Spur, args: Box<[ClauseItem]> }, 88 | } 89 | 90 | #[derive(Debug, Clone, PartialEq)] 91 | pub struct Clause { 92 | /// The number of local variables used in the clause. 93 | /// The first `n` locals are the parameters. 94 | pub(crate) locals: u32, 95 | /// The requirements 96 | pub reqs: Vec<(Span, ClauseItem)>, 97 | } 98 | 99 | impl Clause { 100 | pub fn from_ast(ast: &parser::Clause, rodeo: &mut Rodeo) -> Clause { 101 | AstTranslator { 102 | next_local: 0, 103 | locals: HashMap::new(), 104 | reqs: vec![], 105 | rodeo, 106 | } 107 | .translate_clause(ast) 108 | } 109 | } 110 | 111 | /// A helper struct for translating AST clauses into ClauseItems 112 | struct AstTranslator<'a> { 113 | next_local: i32, 114 | locals: HashMap, 115 | reqs: Vec<(Span, ClauseItem)>, 116 | rodeo: &'a mut Rodeo, 117 | } 118 | 119 | impl AstTranslator<'_> { 120 | fn nil(&mut self) -> ClauseItem { 121 | ClauseItem::Functor { 122 | name: self.rodeo.get_or_intern("[]"), 123 | args: vec![].into_boxed_slice(), 124 | } 125 | } 126 | 127 | fn cons(&mut self, head: ClauseItem, tail: ClauseItem) -> ClauseItem { 128 | ClauseItem::Functor { 129 | name: self.rodeo.get_or_intern("."), 130 | args: vec![head, tail].into_boxed_slice(), 131 | } 132 | } 133 | 134 | fn list(&mut self, items: &[parser::Expr], tail: Option<&parser::Expr>) -> ClauseItem { 135 | let mut result = if let Some(tail) = tail { 136 | self.translate_expr(tail) 137 | } else { 138 | self.nil() 139 | }; 140 | 141 | for e in items.iter().rev() { 142 | let i = self.translate_expr(e); 143 | result = self.cons(i, result); 144 | } 145 | 146 | result 147 | } 148 | 149 | fn string(&mut self, s: &str) -> ClauseItem { 150 | // Aaaa really wish there was TRMC 151 | // If there was TRMC, iterating backwards wouldn't be necessary 152 | 153 | let mut result = self.nil(); 154 | 155 | for ch in s.graphemes(/* extended: */ true).rev() { 156 | let item = ClauseItem::Functor { 157 | name: self.rodeo.get_or_intern(ch), 158 | args: vec![].into_boxed_slice(), 159 | }; 160 | result = self.cons(item, result); 161 | } 162 | 163 | result 164 | } 165 | 166 | fn translate_expr(&mut self, ast: &parser::Expr) -> ClauseItem { 167 | use parser::Expr; 168 | match *ast { 169 | Expr::Paren { ref inner, .. } => self.translate_expr(inner), 170 | Expr::Wildcard { .. } => { 171 | let l = Local(self.next_local); 172 | self.next_local += 1; 173 | ClauseItem::Var(l) 174 | } 175 | Expr::Var { name, .. } => { 176 | let next_local = &mut self.next_local; 177 | let l = self.locals.entry(name).or_insert_with(|| { 178 | let l = Local(*next_local); 179 | *next_local += 1; 180 | l 181 | }); 182 | ClauseItem::Var(*l) 183 | } 184 | Expr::Number { value, .. } => ClauseItem::Number(value), 185 | Expr::Functor { name, ref args, .. } => ClauseItem::Functor { 186 | name, 187 | args: args 188 | .iter() 189 | .map(|arg| self.translate_expr(arg)) 190 | .collect::>() 191 | .into_boxed_slice(), 192 | }, 193 | Expr::String { ref value, .. } => self.string(&value), 194 | Expr::List { 195 | ref items, 196 | ref tail, 197 | .. 198 | } => self.list(&items[..], tail.as_ref().map(|x| &**x)), 199 | } 200 | } 201 | 202 | fn unify_arg(&mut self, arg: i32, item: ClauseItem) -> ClauseItem { 203 | ClauseItem::Functor { 204 | name: self.rodeo.get_or_intern("="), 205 | args: Box::new([ClauseItem::Var(Local::arg(arg)), item]), 206 | } 207 | } 208 | 209 | fn handle_arg(&mut self, i: i32, arg: &parser::Expr) { 210 | use parser::Expr; 211 | match *arg { 212 | Expr::Paren { ref inner, .. } => self.handle_arg(i, inner), 213 | Expr::Wildcard { .. } => (), 214 | Expr::Var { span, name } => match self.locals.entry(name) { 215 | Entry::Occupied(entry) => { 216 | let l = *entry.get(); 217 | let req = self.unify_arg(i, ClauseItem::Var(l)); 218 | self.reqs.push((span, req)); 219 | } 220 | Entry::Vacant(entry) => { 221 | entry.insert(Local::arg(i as i32)); 222 | } 223 | }, 224 | Expr::Number { span, value } => { 225 | let req = self.unify_arg(i, ClauseItem::Number(value)); 226 | self.reqs.push((span, req)); 227 | } 228 | Expr::Functor { span, .. } => { 229 | let e = self.translate_expr(arg); 230 | let req = self.unify_arg(i, e); 231 | self.reqs.push((span, req)); 232 | } 233 | Expr::String { span, ref value } => { 234 | let e = self.string(value); 235 | let req = self.unify_arg(i, e); 236 | self.reqs.push((span, req)); 237 | } 238 | Expr::List { 239 | span, 240 | ref items, 241 | ref tail, 242 | } => { 243 | let e = self.list(items, tail.as_ref().map(|x| &**x)); 244 | let req = self.unify_arg(i, e); 245 | self.reqs.push((span, req)); 246 | } 247 | } 248 | } 249 | 250 | fn translate_subgoals(&mut self, subgoals: &[parser::Expr]) { 251 | for goal in subgoals { 252 | let req = self.translate_expr(goal); 253 | self.reqs.push((goal.span(), req)); 254 | } 255 | } 256 | 257 | fn translate_clause(mut self, ast: &parser::Clause) -> Clause { 258 | for (i, arg) in ast.args.iter().enumerate() { 259 | self.handle_arg(i as i32, arg); 260 | } 261 | 262 | self.translate_subgoals(&ast.subgoals[..]); 263 | 264 | Clause { 265 | locals: self.next_local as u32, 266 | reqs: self.reqs, 267 | } 268 | } 269 | } 270 | 271 | /// Returns a tuple `(locals, subgoals)`, allocates initial variables, and stores their names in 272 | /// `ctx.var_names`. 273 | /// 274 | /// Next, run unify::State{..}.solve_clause_items(locals, ) 275 | pub fn translate_query( 276 | query: &[parser::Expr], 277 | ctx: &mut Context, 278 | vars: &mut VarTable, 279 | ) -> (LocalVars<'static>, Vec<(Span, ClauseItem)>) { 280 | let mut translator = AstTranslator { 281 | next_local: 0, 282 | locals: HashMap::new(), 283 | reqs: vec![], 284 | rodeo: &mut ctx.rodeo, 285 | }; 286 | translator.translate_subgoals(query); 287 | 288 | let locals = vars.allocate_locals(translator.next_local as u32, &[]); 289 | 290 | ctx.var_names = HashMap::new(); 291 | for (name, l) in translator.locals { 292 | ctx.var_names.insert(name, locals.get(l)); 293 | } 294 | 295 | (locals, translator.reqs) 296 | } 297 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Fatal errors 2 | 3 | use codespan_reporting::term::DisplayStyle; 4 | 5 | use crate::context::Context; 6 | use crate::parser::Span; 7 | 8 | /// A fatal, unrecoverable error 9 | #[derive(Debug, Clone, Eq, PartialEq)] 10 | pub struct SolveError { 11 | pub message: String, 12 | /// A trace of every clause item on the way to encountering this error 13 | pub trace: Vec, 14 | } 15 | 16 | impl> From for Box { 17 | fn from(message: T) -> Self { 18 | Box::new(SolveError { 19 | message: message.into(), 20 | trace: vec![], 21 | }) 22 | } 23 | } 24 | impl SolveError { 25 | pub fn add_trace(mut self: Box, span: Span) -> Box { 26 | self.trace.push(span); 27 | self 28 | } 29 | 30 | /// Print this error 31 | /// The given display style is used for all notes 32 | pub fn report(&self, ctx: &Context, display_style: DisplayStyle) { 33 | use codespan_reporting::diagnostic::Severity::*; 34 | use codespan_reporting::diagnostic::{Diagnostic, Label}; 35 | use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; 36 | 37 | let writer = StandardStream::stderr(ColorChoice::Always); 38 | let mut config = codespan_reporting::term::Config::default(); 39 | 40 | for (i, span) in self.trace.iter().copied().enumerate() { 41 | let (severity, message) = if i == 0 { 42 | (Error, self.message.as_str()) 43 | } else { 44 | (Note, "Called from here") 45 | }; 46 | let diagnostic = Diagnostic::new(severity) 47 | .with_message(message) 48 | .with_labels(vec![Label::primary( 49 | span.file_id as usize, 50 | span.start..span.start + span.len as usize, 51 | )]); 52 | 53 | codespan_reporting::term::emit(&mut writer.lock(), &config, &ctx.files, &diagnostic) 54 | .unwrap(); 55 | 56 | config.display_style = display_style.clone(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! WIP 2 | 3 | #![allow(mutable_borrow_reservation_conflict)] 4 | 5 | use codespan_reporting::term::DisplayStyle; 6 | use lasso::Rodeo; 7 | use rustyline::Editor; 8 | 9 | pub mod builtins; 10 | pub mod context; 11 | pub mod error; 12 | pub mod parser; 13 | pub mod runner; 14 | pub mod unify; 15 | pub mod vars; 16 | 17 | use context::Context; 18 | use parser::ReplItem; 19 | use runner::{Command, Runner}; 20 | use vars::{VarTable, VarTableBase}; 21 | 22 | fn process_query( 23 | ast: Vec, 24 | ctx: &mut Context, 25 | vars_base: &VarTableBase, 26 | runner: &mut R, 27 | ) { 28 | let mut vars = VarTable::new(&vars_base); 29 | 30 | let result = runner::do_query(&ast[..], runner, ctx, &mut vars); 31 | 32 | // TODO: configurable error display style 33 | let style = DisplayStyle::Short; 34 | 35 | match result { 36 | Ok(Command::KeepGoing) => println!("\nNo."), 37 | Ok(Command::Stop) => println!("\nYes."), 38 | Err(e) => e.report(ctx, style), 39 | } 40 | } 41 | 42 | fn repl(ctx: &mut Context) { 43 | let mut rl = Editor::<()>::new(); 44 | 45 | let base_map = VarTableBase::default(); 46 | 47 | while let Ok(line) = rl.readline("> ") { 48 | rl.add_history_entry(&line); 49 | match parser::parse_repl(ctx, line) { 50 | Some(ReplItem::Clauses(asts)) => { 51 | for ast in asts { 52 | if let Err(e) = ctx.add_ast_clause(ast) { 53 | println!("Error: {}", e); 54 | } 55 | } 56 | } 57 | Some(ReplItem::Question(ast)) => { 58 | process_query(ast, ctx, &base_map, &mut rl); 59 | } 60 | None => (), 61 | } 62 | } 63 | 64 | println!("\nBye!"); 65 | } 66 | 67 | fn load_file(filename: &str, ctx: &mut Context) { 68 | let contents = std::fs::read_to_string(filename).unwrap(); 69 | if let Some(ast) = parser::parse(ctx, filename.to_owned(), contents) { 70 | for ast_clause in ast { 71 | if let Err(e) = ctx.add_ast_clause(ast_clause) { 72 | println!("Error: {}", e); 73 | } 74 | } 75 | } 76 | 77 | println!("Successfully loaded {}", filename); 78 | } 79 | 80 | fn main() { 81 | pretty_env_logger::init(); 82 | 83 | let mut rodeo = Rodeo::default(); 84 | let rels = builtins::builtins(&mut rodeo); 85 | let files = codespan_reporting::files::SimpleFiles::new(); 86 | let builtins = builtins::Builtins::new(&mut rodeo); 87 | let var_names = Default::default(); 88 | 89 | let mut ctx = Context { 90 | rodeo, 91 | rels, 92 | files, 93 | builtins, 94 | var_names, 95 | }; 96 | 97 | // load_file("sudoku.pl", &mut ctx); 98 | 99 | repl(&mut ctx); 100 | } 101 | -------------------------------------------------------------------------------- /src/parser.lalrpop: -------------------------------------------------------------------------------- 1 | use crate::parser::{Tok, Expr, Clause, ReplItem, Span}; 2 | use lasso::{Rodeo, Spur}; 3 | 4 | grammar<'input, 'rodeo>(input: &'input str, rodeo: &'rodeo mut Rodeo, file_id: u32); 5 | 6 | extern { 7 | type Location = usize; 8 | type Error = (); 9 | 10 | enum Tok<'input> { 11 | "(" => Tok::LParen, 12 | ")" => Tok::RParen, 13 | "[" => Tok::LBracket, 14 | "]" => Tok::RBracket, 15 | "|" => Tok::Bar, 16 | 17 | "." => Tok::Period, 18 | "," => Tok::Comma, 19 | "?" => Tok::Question, 20 | ":-" => Tok::Turnstile, 21 | 22 | "\\+" => Tok::WeirdNot, 23 | "=" => Tok::Equals, 24 | "\\=" => Tok::NotEquals, 25 | is => Tok::Is, 26 | "<" => Tok::LessThan, 27 | ">" => Tok::GreaterThan, 28 | "+" => Tok::Plus, 29 | "-" => Tok::Minus, 30 | "*" => Tok::Times, 31 | "/" => Tok::Divide, 32 | 33 | "_" => Tok::Wildcard, 34 | functor => Tok::Functor(<&'input str>), 35 | Variable => Tok::Variable(<&'input str>), 36 | number => Tok::Number(), 37 | string => Tok::String(<&'input str>), 38 | } 39 | } 40 | 41 | pub Program: Vec = Clause*; 42 | pub ReplItem: ReplItem = { 43 | => ReplItem::Clauses(<>), 44 | => ReplItem::Question(<>), 45 | } 46 | 47 | Question = "?"; 48 | 49 | Clause: Clause = { 50 | "." => { 51 | let span = Span::new(file_id, l, r); 52 | let (_, functor, args) = f; 53 | Clause { span, functor, args, subgoals: vec![] } 54 | }, 55 | ":-" "." => { 56 | let span = Span::new(file_id, l, r); 57 | let (_, functor, args) = f; 58 | Clause { span, functor, args, subgoals } 59 | }, 60 | } 61 | 62 | // Allow trailing commas bc why not 63 | Exprs: Vec = { 64 | ",")*> => 65 | h.into_iter().chain(t).collect() 66 | } 67 | 68 | Expr: Expr = { 69 | CmpExpr, 70 | "\\+" => Expr::Functor { 71 | span: Span::new(file_id, l, r), 72 | name: rodeo.get_or_intern("\\+"), 73 | args: vec![e], 74 | } 75 | } 76 | 77 | Operator: Expr = { 78 | Right, 79 | => Expr::Functor { 80 | span: Span::new(file_id, l, r), 81 | name: rodeo.get_or_intern(op), 82 | args: vec![lhs, rhs], 83 | }, 84 | } 85 | 86 | CmpExpr = Operator; 87 | AddExpr = Operator; 88 | UnaryExpr = { 89 | MulExpr, 90 | => Expr::Functor { 91 | span: Span::new(file_id, l, r), 92 | name: rodeo.get_or_intern(op), 93 | args: vec![e], 94 | }, 95 | } 96 | MulExpr = Operator; 97 | 98 | Term: Expr = { 99 | "(" ")" => { 100 | let span = Span::new(file_id, l, r); 101 | Expr::Paren { span, inner: Box::new(inner) } 102 | }, 103 | "_" => { 104 | let span = Span::new(file_id, l, r); 105 | Expr::Wildcard { span } 106 | }, 107 | => { 108 | let span = Span::new(file_id, l, r); 109 | Expr::Number { span, value } 110 | }, 111 | => { 112 | let span = Span::new(file_id, l, r); 113 | Expr::Var { span, name: rodeo.get_or_intern(v) } 114 | }, 115 | Functor => { 116 | let (span, name, args) = <>; 117 | Expr::Functor { span, name, args } 118 | }, 119 | => { 120 | let span = Span::new(file_id, l, r); 121 | Expr::String { span, value: s.to_owned() } 122 | }, 123 | List, 124 | } 125 | 126 | List: Expr = { 127 | "[" "]" => { 128 | let span = Span::new(file_id, l, r); 129 | Expr::List { span, items, tail: None } 130 | }, 131 | 132 | "[" "|" "]" => { 133 | let span = Span::new(file_id, l, r); 134 | Expr::List { span, items, tail: Some(Box::new(tail)) } 135 | }, 136 | } 137 | 138 | Functor: (Span, Spur, Vec) = { 139 | "(" ")" => { 140 | let span = Span::new(file_id, l, r); 141 | (span, rodeo.get_or_intern(f), args) 142 | }, 143 | => { 144 | let span = Span::new(file_id, l, r); 145 | (span, rodeo.get_or_intern(f), vec![]) 146 | }, 147 | } 148 | 149 | 150 | // Operators 151 | 152 | CmpOp: &'static str = { 153 | "=" => "=", 154 | "\\=" => "\\=", 155 | is => "is", 156 | "<" => "<", 157 | ">" => ">", 158 | } 159 | 160 | AddOp: &'static str = { 161 | "+" => "+", 162 | "-" => "-", 163 | } 164 | 165 | MulOp: &'static str = { 166 | "*" => "*", 167 | "/" => "/", 168 | } 169 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | //! Lexer and parser 2 | 3 | // TODO: convert AST to Context, parse queries, wildcards, better error handling 4 | 5 | use codespan_reporting::diagnostic::{Diagnostic, Label}; 6 | use codespan_reporting::files::SimpleFiles; 7 | use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; 8 | use itertools::Itertools; 9 | use lalrpop_util::{lalrpop_mod, ParseError}; 10 | use lasso::Spur; 11 | use logos::Logos; 12 | use std::convert::TryInto; 13 | use std::iter::Iterator; 14 | use std::ops::Range; 15 | 16 | use crate::context::Context; 17 | 18 | lalrpop_mod!(parser); 19 | 20 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Logos)] 21 | pub enum Tok<'a> { 22 | #[error] 23 | #[regex(r"[ \t\n\f]+", logos::skip)] 24 | #[regex(r"/\*[^*]*(\*[^/][^*]*)*\*/", logos::skip)] 25 | // The final \n is not included in the regex so that it can terminate at EOF too 26 | #[regex(r"%[^\n]*", logos::skip)] 27 | Error, 28 | 29 | #[token("(")] 30 | LParen, 31 | #[token(")")] 32 | RParen, 33 | #[token("[")] 34 | LBracket, 35 | #[token("]")] 36 | RBracket, 37 | #[token("|")] 38 | Bar, 39 | 40 | #[token(".")] 41 | Period, 42 | #[token(",")] 43 | Comma, 44 | #[token("?")] 45 | Question, 46 | #[token(":-")] 47 | Turnstile, 48 | 49 | // These are vaguely in order of precedence 50 | #[token("\\+")] 51 | WeirdNot, 52 | #[token("is")] 53 | Is, 54 | #[token("=")] 55 | Equals, 56 | #[token("\\=")] 57 | NotEquals, 58 | #[token("<")] 59 | LessThan, 60 | #[token(">")] 61 | GreaterThan, 62 | #[token("+")] 63 | Plus, 64 | #[token("-")] 65 | Minus, 66 | #[token("*")] 67 | Times, 68 | #[token("/")] 69 | Divide, 70 | 71 | #[token("_")] 72 | Wildcard, 73 | 74 | // TODO: figure out a nice way to parse literally everything without ambiguity 75 | // Ideally, should also have =(a, b) and is(a, b) work 76 | #[regex(r"[a-z][a-zA-Z0-9_]*")] 77 | // TODO: expand escape sequences sometime? 78 | #[regex(r"'[^'\n]*(\\'[^'\n]*)*'", |lex| &lex.slice()[1..lex.slice().len()-1])] 79 | Functor(&'a str), 80 | #[regex(r"[A-Z_][a-zA-Z0-9_]*")] 81 | Variable(&'a str), 82 | 83 | // TODO: figure out a smarter way to have signed literals 84 | // Might need whitespace sensitivity 85 | // 86 | // Item | SWI Prolog | GNU prolog 87 | // -----+------------+----------- 88 | // -(5) | '-'(5) | '-'(5) 89 | // - 5 | '-'(5) | -5 (Different!) 90 | // -5 | -5 | -5 91 | // 0-5 | '-'(0, 5) | '-'(0, 5) 92 | #[regex(r"[0-9][0-9_]*", |lex| lex.slice().parse())] 93 | Number(i64), 94 | 95 | // TODO: expand escape sequences sometime? 96 | #[regex(r#""[^"\n]*(\\'[^"\n]*)*""#, |lex| &lex.slice()[1..lex.slice().len()-1])] 97 | String(&'a str), 98 | } 99 | 100 | struct Lexer<'input> { 101 | lexer: logos::Lexer<'input, Tok<'input>>, 102 | } 103 | 104 | impl<'input> Iterator for Lexer<'input> { 105 | type Item = (usize, Tok<'input>, usize); 106 | 107 | fn next(&mut self) -> Option { 108 | let tok = self.lexer.next()?; 109 | let Range { start, end } = self.lexer.span(); 110 | Some((start, tok, end)) 111 | } 112 | } 113 | 114 | /// A range of input 115 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 116 | pub struct Span { 117 | /// A handle for use with `codespan_reporting` 118 | pub file_id: u32, 119 | pub start: usize, 120 | pub len: u32, 121 | } 122 | 123 | impl Span { 124 | pub fn new(file_id: u32, start: usize, end: usize) -> Self { 125 | Self { 126 | file_id, 127 | start, 128 | len: (end - start).try_into().unwrap(), 129 | } 130 | } 131 | } 132 | 133 | /// A single clause for some functor. 134 | #[derive(Debug, Clone, PartialEq, Eq)] 135 | pub struct Clause { 136 | pub span: Span, 137 | pub functor: Spur, 138 | pub args: Vec, 139 | pub subgoals: Vec, 140 | } 141 | 142 | /// Either a functor or a variable 143 | #[derive(Debug, Clone, PartialEq, Eq)] 144 | pub enum Expr { 145 | /// Wildcard variable: `_` 146 | Wildcard { span: Span }, 147 | /// Actual variable: `A` 148 | Var { span: Span, name: Spur }, 149 | /// Number 150 | Number { span: Span, value: i64 }, 151 | /// A String is a list of characters 152 | String { span: Span, value: String }, 153 | /// A parenthesized expression 154 | /// For now, it's only useful to keep these separate so the parser knows the 155 | /// difference between -5 and -(5) 156 | Paren { span: Span, inner: Box }, 157 | /// Functor: `f(x, A)` 158 | Functor { 159 | span: Span, 160 | name: Spur, 161 | args: Vec, 162 | }, 163 | /// A list `[items... | tail]` 164 | List { 165 | span: Span, 166 | items: Vec, 167 | tail: Option>, 168 | }, 169 | } 170 | 171 | impl Expr { 172 | pub fn span(&self) -> Span { 173 | match *self { 174 | Expr::Wildcard { span } => span, 175 | Expr::Var { span, .. } => span, 176 | Expr::Number { span, .. } => span, 177 | Expr::String { span, .. } => span, 178 | Expr::Paren { span, .. } => span, 179 | Expr::Functor { span, .. } => span, 180 | Expr::List { span, .. } => span, 181 | } 182 | } 183 | } 184 | 185 | /// An input from the REPL 186 | /// Unlike a typical Prolog REPL, more like Makam's 187 | /// TODO: change it to be more prolog-y? 188 | pub enum ReplItem { 189 | Clauses(Vec), 190 | Question(Vec), 191 | } 192 | 193 | pub fn parse(ctx: &mut Context, file_name: String, input: String) -> Option> { 194 | let file_id = ctx.files.add(file_name, input); 195 | let source = ctx.files.get(file_id).unwrap().source(); 196 | let lexer = Lexer { 197 | lexer: Tok::lexer(source), 198 | }; 199 | parser::ProgramParser::new() 200 | .parse(source, &mut ctx.rodeo, file_id.try_into().unwrap(), lexer) 201 | .map_err(|e| print_err(&ctx.files, file_id, e)) 202 | .ok() 203 | } 204 | 205 | pub fn parse_repl(ctx: &mut Context, input: String) -> Option { 206 | let file_id = ctx.files.add("stdin".to_owned(), input); 207 | let source = ctx.files.get(file_id).unwrap().source(); 208 | let lexer = Lexer { 209 | lexer: Tok::lexer(source), 210 | }; 211 | parser::ReplItemParser::new() 212 | .parse(source, &mut ctx.rodeo, file_id.try_into().unwrap(), lexer) 213 | .map_err(|e| print_err(&ctx.files, file_id, e)) 214 | .ok() 215 | } 216 | 217 | fn print_err(files: &SimpleFiles, file_id: usize, err: ParseError) { 218 | use ParseError::*; 219 | let (label, notes) = match err { 220 | InvalidToken { location } => ( 221 | Label::primary(file_id, location..location).with_message("Invalid Token"), 222 | vec![], 223 | ), 224 | UnrecognizedEOF { location, expected } => ( 225 | Label::primary(file_id, location..location).with_message("Unexpeced EOF"), 226 | vec![format!("Expected one of {}", expected.iter().format(", "))], 227 | ), 228 | UnrecognizedToken { 229 | token: (l, _, r), 230 | expected, 231 | } => ( 232 | Label::primary(file_id, l..r).with_message("Unrecognized Token"), 233 | vec![format!("Expected one of {}", expected.iter().format(", "))], 234 | ), 235 | ExtraToken { token: (l, _, r) } => ( 236 | Label::primary(file_id, l..r).with_message("Extra Token"), 237 | vec![], 238 | ), 239 | User { .. } => panic!(), 240 | }; 241 | let diagnostic = Diagnostic::error() 242 | .with_message("Parse error") 243 | .with_labels(vec![label]) 244 | .with_notes(notes); 245 | 246 | let writer = StandardStream::stderr(ColorChoice::Always); 247 | let config = codespan_reporting::term::Config::default(); 248 | codespan_reporting::term::emit(&mut writer.lock(), &config, files, &diagnostic).unwrap(); 249 | } 250 | -------------------------------------------------------------------------------- /src/runner.rs: -------------------------------------------------------------------------------- 1 | //! Runner: the logic to control the unification engine 2 | 3 | use crate::context::*; 4 | use crate::error::*; 5 | use crate::parser::{Expr, Span}; 6 | use crate::unify::State; 7 | use crate::vars::*; 8 | use rustyline::Editor; 9 | 10 | /// What to do next 11 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 12 | pub enum Command { 13 | KeepGoing, 14 | Stop, 15 | } 16 | 17 | /// `SolverResult` is returned by basically every function 18 | pub type SolverResult = Result>; 19 | 20 | pub trait Runner { 21 | fn solution(&mut self, ctx: &Context, vars: &mut VarTable<'_>) -> SolverResult; 22 | } 23 | 24 | pub struct Printing<'r> { 25 | base: &'r mut dyn Runner, 26 | } 27 | 28 | impl Runner for Printing<'_> { 29 | fn solution(&mut self, ctx: &Context, vars: &mut VarTable<'_>) -> SolverResult { 30 | if ctx.var_names.len() == 0 { 31 | // No use in continuing -- no other solutions to be found 32 | return Ok(Command::Stop); 33 | } 34 | println!("\nSolution:"); 35 | for (&name, &var) in &ctx.var_names { 36 | println!(" {} = {}", ctx.rodeo.resolve(&name), vars.show(var, ctx)); 37 | } 38 | self.base.solution(ctx, vars) 39 | } 40 | } 41 | 42 | impl Runner for Editor<()> { 43 | fn solution(&mut self, _ctx: &Context, _vars: &mut VarTable<'_>) -> SolverResult { 44 | // TODO: prompt user in a better way 45 | let response = match self.readline("? ") { 46 | Ok(r) => r, 47 | Err(_) => return Ok(Command::Stop), 48 | }; 49 | if response.trim() == ";" { 50 | Ok(Command::KeepGoing) 51 | } else { 52 | Ok(Command::Stop) 53 | } 54 | } 55 | } 56 | 57 | /// A Runner which goes through all solutions. 58 | pub struct OneSoln; 59 | /// A Runner which only asks for the first solution. 60 | pub struct AllSolns; 61 | 62 | impl Runner for AllSolns { 63 | fn solution(&mut self, _ctx: &Context, _vars: &mut VarTable<'_>) -> SolverResult { 64 | Ok(Command::KeepGoing) 65 | } 66 | } 67 | impl Runner for OneSoln { 68 | fn solution(&mut self, _ctx: &Context, _vars: &mut VarTable<'_>) -> SolverResult { 69 | Ok(Command::Stop) 70 | } 71 | } 72 | 73 | // Aaaa this is really shitty 74 | // CPS without tail calls trashing the stack 75 | // whatever, it's doesn't need to be fancy 76 | 77 | pub struct All<'a> { 78 | pub items: &'a [(Span, VarId)], 79 | pub base: &'a mut dyn Runner, 80 | } 81 | 82 | impl<'a> Runner for All<'a> { 83 | fn solution(&mut self, ctx: &Context, vars: &mut VarTable<'_>) -> SolverResult { 84 | match *self.items { 85 | [] => self.base.solution(ctx, vars), 86 | [(span, head)] => { 87 | log::trace!("solving last clause"); 88 | let mut state = State { 89 | ctx, 90 | vars, 91 | runner: self.base, 92 | }; 93 | state.solve(head).map_err(|e| e.add_trace(span)) 94 | } 95 | [(span, head), ref tail @ ..] => { 96 | log::trace!("solving next clause"); 97 | let mut state = State { 98 | ctx, 99 | vars, 100 | runner: &mut All { 101 | items: tail, 102 | base: self.base, 103 | }, 104 | }; 105 | state.solve(head).map_err(|e| e.add_trace(span)) 106 | } 107 | } 108 | } 109 | } 110 | 111 | pub fn do_query<'e, 'v, R: Runner>( 112 | q: &[Expr], 113 | r: &'e mut R, 114 | ctx: &mut Context, 115 | vars: &mut VarTable<'v>, 116 | ) -> SolverResult { 117 | let (locals, goals) = translate_query(q, ctx, vars); 118 | 119 | log::debug!("{}", ctx.dbg_var_names()); 120 | 121 | let mut runner = Printing { base: r }; 122 | 123 | State { 124 | ctx, 125 | vars, 126 | runner: &mut runner, 127 | } 128 | .solve_clause_items(locals, &goals) 129 | } 130 | -------------------------------------------------------------------------------- /src/unify.rs: -------------------------------------------------------------------------------- 1 | //! Unification 2 | 3 | use crate::context::*; 4 | use crate::parser::Span; 5 | use crate::runner::*; 6 | use crate::vars::*; 7 | 8 | pub struct State<'a, 'v> { 9 | pub ctx: &'a Context, 10 | pub vars: &'a mut VarTable<'v>, 11 | pub runner: &'a mut dyn Runner, 12 | } 13 | 14 | impl ClauseItem { 15 | pub fn reify<'v>(&self, vars: &mut VarTable<'v>, locals: &LocalVars) -> VarId { 16 | use ClauseItem::*; 17 | match *self { 18 | Var(l) => locals.get(l), 19 | Number(x) => vars.new_var_of(Item::Number(x)), 20 | Functor { name, ref args } => { 21 | let new_args = args 22 | .iter() 23 | .map(|ci| ci.reify(vars, locals)) 24 | .collect::>(); 25 | vars.new_var_of_functor(name, new_args.into_iter()) 26 | } 27 | } 28 | } 29 | } 30 | 31 | impl<'a, 'v> State<'a, 'v> { 32 | pub fn solve(&mut self, v: VarId) -> SolverResult { 33 | log::debug!("Solving {}", self.vars.dbg(v, &self.ctx)); 34 | match self.vars.lookup(v) { 35 | Item::Unresolved => Err("can't solve ambiguous metavariable".into()), 36 | Item::Var(var) => panic!("Internal error: lookup {} returned var {}", v, var), 37 | Item::Number(n) => Err(format!("Type error: {} is not a functor", n).into()), 38 | Item::Functor { name, ref args } => match self.ctx.rels.get(&RelId { 39 | name, 40 | arity: args.len() as u32, 41 | }) { 42 | None => { 43 | let message = format!( 44 | "Unknown functor {}/{}", 45 | self.ctx.rodeo.resolve(&name), 46 | args.len() 47 | ); 48 | log::debug!("{}", message); 49 | Err(message.into()) 50 | } 51 | Some(&Relation::Builtin(func)) => { 52 | let args = args.clone(); 53 | func(self.ctx, self.vars, args, self.runner) 54 | } 55 | Some(Relation::User(clauses)) => match &**clauses { 56 | [] => Ok(Command::KeepGoing), 57 | [clause] => self.solve_clause(clause, args), 58 | [first @ .., last] => { 59 | for clause in first { 60 | let mut tmp_state = State { 61 | ctx: self.ctx, 62 | vars: &mut self.vars.backtrackable(), 63 | runner: self.runner, 64 | }; 65 | match tmp_state.solve_clause(clause, args)? { 66 | Command::Stop => return Ok(Command::Stop), 67 | Command::KeepGoing => continue, 68 | } 69 | } 70 | self.solve_clause(last, args) 71 | } 72 | }, 73 | }, 74 | } 75 | } 76 | } 77 | 78 | impl<'a, 'v> State<'a, 'v> { 79 | fn solve_clause(&mut self, clause: &Clause, args: &[VarId]) -> SolverResult { 80 | let locals = self.vars.allocate_locals(clause.locals, args); 81 | self.solve_clause_items(locals, &clause.reqs) 82 | } 83 | 84 | pub fn solve_clause_items( 85 | &mut self, 86 | locals: LocalVars, 87 | reqs: &[(Span, ClauseItem)], 88 | ) -> SolverResult { 89 | // loop thru and solve each clause item 90 | // (in an ultra-cursed CPS no tail calls way) 91 | let mut items = reqs 92 | .iter() 93 | .map(|(span, req)| (*span, req.reify(self.vars, &locals))); 94 | if let Some((span, first)) = items.next() { 95 | let rest = items.collect::>(); 96 | if rest.len() == 0 { 97 | // Oh well, no tail call :'( 98 | self.solve(first).map_err(|e| e.add_trace(span)) 99 | } else { 100 | let mut state = State { 101 | ctx: self.ctx, 102 | vars: self.vars, 103 | runner: &mut All { 104 | items: &rest[..], 105 | base: self.runner, 106 | }, 107 | }; 108 | state.solve(first).map_err(|e| e.add_trace(span)) 109 | } 110 | } else { 111 | drop(locals); 112 | self.runner.solution(self.ctx, self.vars) 113 | } 114 | } 115 | } 116 | 117 | struct UnifyAll<'a> { 118 | // Hate how there's a whole extra `usize` in there storing literally nothing 119 | // There's no way to specify that the two arrays have the same length, so two of the same value 120 | // need to be stored and then checked for equality 121 | lhs: &'a [VarId], 122 | rhs: &'a [VarId], 123 | base: &'a mut dyn Runner, 124 | } 125 | 126 | impl<'a> Runner for UnifyAll<'a> { 127 | fn solution(&mut self, ctx: &Context, vars: &mut VarTable) -> SolverResult { 128 | match (self.lhs, self.rhs) { 129 | ([], []) => self.base.solution(ctx, vars), 130 | (&[x], &[y]) => State { 131 | ctx, 132 | vars, 133 | runner: self.base, 134 | } 135 | .unify(x, y), 136 | (&[x, ref xs @ ..], &[y, ref ys @ ..]) => State { 137 | ctx, 138 | vars, 139 | runner: &mut UnifyAll { 140 | lhs: xs, 141 | rhs: ys, 142 | base: self.base, 143 | }, 144 | } 145 | .unify(x, y), 146 | _ => panic!("Internal error: mismatched lengths"), 147 | } 148 | } 149 | } 150 | 151 | // Finally 152 | impl<'a, 'v> State<'a, 'v> { 153 | // Invariant: a.len() == b.len() 154 | fn unify_args(&mut self, a: &[VarId], b: &[VarId]) -> SolverResult { 155 | debug_assert_eq!(a.len(), b.len()); 156 | let len = a.len(); 157 | if len == 0 { 158 | log::trace!("Identical atoms! Solved."); 159 | self.runner.solution(self.ctx, self.vars) 160 | } else if len == 1 { 161 | log::trace!("Unify the arguments"); 162 | self.unify(a[0], b[0]) 163 | } else { 164 | log::trace!("Unify the arguments"); 165 | let first_a = a[0]; 166 | let first_b = b[0]; 167 | let lhs = a[1..].to_owned(); 168 | let rhs = b[1..].to_owned(); 169 | State { 170 | ctx: self.ctx, 171 | vars: self.vars, 172 | runner: &mut UnifyAll { 173 | lhs: &lhs, 174 | rhs: &rhs, 175 | base: self.runner, 176 | }, 177 | } 178 | .unify(first_a, first_b) 179 | } 180 | } 181 | 182 | pub fn unify(&mut self, a: VarId, b: VarId) -> SolverResult { 183 | log::debug!( 184 | "Unifying {} and {}", 185 | self.vars.dbg(a, &self.ctx), 186 | self.vars.dbg(b, &self.ctx) 187 | ); 188 | match ( 189 | self.vars.lookup_with_varid(a), 190 | self.vars.lookup_with_varid(b), 191 | ) { 192 | // lookup should never return vars 193 | ((_, Item::Var(v)), _) => panic!("lookup {} returned var {}", a, v), 194 | (_, (_, Item::Var(v))) => panic!("lookup {} returned var {}", b, v), 195 | 196 | // Avoid creating cyclic things 197 | ((va, _), (vb, _)) if va == vb => { 198 | log::trace!("already unified: {} and {}", va, vb); 199 | self.runner.solution(self.ctx, self.vars) 200 | } 201 | 202 | // if either is unresolved, unify 203 | // TODO: here would be the right place to add occurs checks in the future 204 | ((va, Item::Unresolved), (vb, _)) => { 205 | log::trace!("updating {} to {}", va, vb); 206 | self.vars.update(va, Item::Var(vb)); 207 | self.runner.solution(self.ctx, self.vars) 208 | } 209 | ((va, _), (vb, Item::Unresolved)) => { 210 | log::trace!("updating {} to {}", vb, va); 211 | self.vars.update(vb, Item::Var(va)); 212 | self.runner.solution(self.ctx, self.vars) 213 | } 214 | 215 | // unify two identical numbers 216 | ((_, Item::Number(x)), (_, Item::Number(y))) if x == y => { 217 | log::trace!("Numbers {} and {} are equal! Solved.", x, y); 218 | self.runner.solution(self.ctx, self.vars) 219 | } 220 | 221 | // unify two functors recursively 222 | ( 223 | ( 224 | _, 225 | Item::Functor { 226 | name: name_a, 227 | args: args_a, 228 | }, 229 | ), 230 | ( 231 | _, 232 | Item::Functor { 233 | name: name_b, 234 | args: args_b, 235 | }, 236 | ), 237 | ) if name_a == name_b && args_a.len() == args_b.len() => { 238 | log::trace!("Same functor, unifying arguments"); 239 | self.unify_args(args_a, args_b) 240 | } 241 | 242 | // Different things 243 | ((va, _), (vb, _)) => { 244 | log::trace!( 245 | "Could not unify {} and {} -- backtrack", 246 | self.vars.dbg(va, &self.ctx), 247 | self.vars.dbg(vb, &self.ctx) 248 | ); 249 | Ok(Command::KeepGoing) 250 | } 251 | } 252 | } 253 | 254 | /// Don't give `Item::Unresolved` or `Item::Var` as the second argument 255 | #[inline] 256 | pub fn unify_with_known(&mut self, a: VarId, b: Item<'v>) -> SolverResult { 257 | log::debug!("Unifying {} and {:?}", self.vars.dbg(a, &self.ctx), b); 258 | 259 | match (self.vars.lookup_with_varid(a), b) { 260 | (_, Item::Unresolved) => panic!(), 261 | (_, Item::Var(_)) => panic!(), 262 | 263 | // Resolve unresolved things 264 | ((va, Item::Unresolved), _) => { 265 | log::trace!("Resolved {} to {:?}", va, b); 266 | self.vars.update(va, b); 267 | self.runner.solution(self.ctx, self.vars) 268 | } 269 | 270 | // Unify identical numbers 271 | ((_, Item::Number(x)), Item::Number(y)) if x == y => { 272 | log::trace!("Both are {} -- done!", x); 273 | self.runner.solution(self.ctx, self.vars) 274 | } 275 | 276 | // Unify identical functors 277 | ( 278 | ( 279 | _, 280 | Item::Functor { 281 | name: name_a, 282 | args: args_a, 283 | }, 284 | ), 285 | Item::Functor { 286 | name: name_b, 287 | args: args_b, 288 | }, 289 | ) if name_a == name_b && args_a.len() == args_b.len() => { 290 | log::trace!("Same functor, unifying arguments"); 291 | self.unify_args(args_a, args_b) 292 | } 293 | 294 | // Fail to unify different things 295 | _ => { 296 | log::trace!("Can't unify"); 297 | Ok(Command::KeepGoing) 298 | } 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/vars.rs: -------------------------------------------------------------------------------- 1 | //! The datastructures to keep track of variables 2 | 3 | use lasso::Spur; 4 | use scoped_map::{ScopedMap, ScopedMapBase}; 5 | use std::fmt::{self, Write}; 6 | use typed_arena::{Arena, SubArena}; 7 | 8 | use crate::context::*; 9 | 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | pub struct VarId(u64); 12 | 13 | impl fmt::Display for VarId { 14 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 15 | write!(f, "{{{}}}", self.0) 16 | } 17 | } 18 | 19 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 20 | pub enum Item<'a> { 21 | /// Could be anything -- not resolved/unified yet 22 | Unresolved, 23 | /// Look up what it is in the current State 24 | Var(VarId), 25 | /// An integer -- a simple representation since there's no fancy constraints 26 | // TODO: should it also have floats? 27 | // Also, look into adding constraints, and maybe a solver for finite domains 28 | Number(i64), 29 | /// A functor is like f(Args...) 30 | Functor { name: Spur, args: &'a [VarId] }, 31 | } 32 | 33 | /// Construct a `VarTableBase` with `::default()` 34 | /// Then, make a `VarTable` with `VarTable::new(&base)` 35 | #[derive(Default)] 36 | pub struct VarTableBase { 37 | map: ScopedMapBase>, 38 | arena: Arena, 39 | } 40 | 41 | // This is actually self-borrowing! 42 | // Really, it should be `Item<'self.arena>`, but `Item<'v>` is a good-enough approximation 43 | /// The VarTable stores all unification state 44 | /// Make one by going through `VarTableBase` 45 | pub struct VarTable<'v> { 46 | map: ScopedMap<'v, VarId, Item<'v>>, 47 | next_var: u64, 48 | arena: SubArena<'v, VarId>, 49 | } 50 | 51 | impl<'v> VarTable<'v> { 52 | pub fn new(base: &'v VarTableBase) -> Self { 53 | Self { 54 | map: base.map.make_map(), 55 | next_var: 0, 56 | arena: SubArena::new(&base.arena), 57 | } 58 | } 59 | 60 | pub fn new_var(&mut self) -> VarId { 61 | let new_id = VarId(self.next_var); 62 | self.next_var += 1; 63 | self.map.insert(new_id, Item::Unresolved); 64 | new_id 65 | } 66 | 67 | pub fn new_var_of(&mut self, item: Item<'v>) -> VarId { 68 | let new_id = VarId(self.next_var); 69 | self.next_var += 1; 70 | self.map.insert(new_id, item); 71 | new_id 72 | } 73 | 74 | unsafe fn alloc_functor_args( 75 | arena: &SubArena<'v, VarId>, 76 | args: impl Iterator, 77 | ) -> &'v [VarId] { 78 | let args_wrong_lifetime = arena.alloc_extend(args); 79 | std::mem::transmute::<&[VarId], &'v [VarId]>(args_wrong_lifetime) 80 | } 81 | 82 | pub fn new_var_of_functor(&mut self, name: Spur, args: impl Iterator) -> VarId { 83 | // SAFETY: only safe bc we're careful everywhere else 84 | // As noted, `VarTable` is self-borrowing, and `'v` is only an approximation of the 85 | // lifetime `&'self.arena` of things borrowed from the arena 86 | unsafe { 87 | let args = Self::alloc_functor_args(&self.arena, args); 88 | self.new_var_of(Item::Functor { name, args }) 89 | } 90 | } 91 | 92 | /// Note: Only do this on variables that are currently unresolved 93 | /// (checked by debug_assertion) 94 | pub fn update(&mut self, var: VarId, to: Item<'v>) { 95 | debug_assert_eq!(self.map.lookup(&var), Some(&Item::Unresolved)); 96 | self.map.insert(var, to); 97 | } 98 | 99 | /// Resolves the variable to a new functor, with the given name and arity 100 | /// Used by functor/3 101 | pub fn update_to_functor(&mut self, var: VarId, name: Spur, arity: usize) { 102 | let next_var = &mut self.next_var; 103 | let map = &mut self.map; 104 | // SAFETY: see `new_var_of_functor` 105 | unsafe { 106 | let args = Self::alloc_functor_args( 107 | &self.arena, 108 | (0..arity).map(|_| { 109 | let new_var = VarId(*next_var); 110 | map.insert(new_var, Item::Unresolved); 111 | *next_var += 1; 112 | new_var 113 | }), 114 | ); 115 | let item = Item::Functor { name, args }; 116 | self.update(var, item) 117 | } 118 | } 119 | 120 | pub fn backtrackable(&self) -> VarTable<'_> { 121 | VarTable { 122 | map: self.map.new_scope(), 123 | next_var: self.next_var, 124 | arena: SubArena::new(&*self.arena), 125 | } 126 | } 127 | 128 | fn lookup_helper(&mut self, var: VarId) -> VarId { 129 | log::trace!("Looking up {}", var); 130 | let i = self.map.lookup(&var).unwrap(); 131 | if let Item::Var(v) = *i { 132 | let res = self.lookup_helper(v); 133 | // Collapse indirection 134 | if v != res { 135 | self.map.insert(var, Item::Var(res)); 136 | } 137 | res 138 | } else { 139 | var 140 | } 141 | } 142 | 143 | /// Like `.lookup()`, but it also returns the main varid for that variable 144 | pub fn lookup_with_varid(&mut self, var: VarId) -> (VarId, Item<'v>) { 145 | log::trace!("Looking up {}", var); 146 | let i = *self.map.lookup(&var).unwrap(); 147 | if let Item::Var(v) = i { 148 | let w = self.lookup_helper(v); 149 | // Don't need to check if var != w -- it's guaranteed to be true 150 | debug_assert_ne!(var, w); 151 | self.map.insert(var, Item::Var(w)); 152 | (w, *self.map.lookup(&w).unwrap()) 153 | } else { 154 | (var, i) 155 | } 156 | } 157 | 158 | /// Lookup never returns `Item::Var` 159 | pub fn lookup(&mut self, var: VarId) -> Item<'v> { 160 | self.lookup_with_varid(var).1 161 | } 162 | } 163 | 164 | /// Intrusive linked list of variables that have been visited so far 165 | /// Used to avoid infinitely traversing cylic structures 166 | struct Seen<'a> { 167 | var: VarId, 168 | next: Option<&'a Seen<'a>>, 169 | } 170 | 171 | /// Whether the linked list contains that var 172 | fn has_seen(var: VarId, seen: Option<&Seen<'_>>) -> bool { 173 | let mut next = seen; 174 | while let Some(node) = next { 175 | if node.var == var { 176 | return true; 177 | } 178 | next = node.next; 179 | } 180 | false 181 | } 182 | 183 | /// Functions to display variables 184 | impl VarTable<'_> { 185 | pub fn show(&self, var: VarId, ctx: &Context) -> String { 186 | let mut s = String::new(); 187 | self.fmt_helper(&mut s, var, ctx, None, false).unwrap(); 188 | s 189 | } 190 | 191 | pub fn dbg(&self, var: VarId, ctx: &Context) -> String { 192 | let mut s = String::new(); 193 | self.fmt_helper(&mut s, var, ctx, None, true).unwrap(); 194 | s 195 | } 196 | 197 | fn fmt_helper( 198 | &self, 199 | f: &mut impl Write, 200 | var: VarId, 201 | ctx: &Context, 202 | seen: Option<&Seen<'_>>, 203 | dbg: bool, 204 | ) -> fmt::Result { 205 | if has_seen(var, seen) { 206 | for (name, &v) in &ctx.var_names { 207 | if var == v { 208 | return write!(f, "{}", ctx.rodeo.resolve(name)); 209 | } 210 | } 211 | return write!(f, "{}", var); 212 | } 213 | let new_seen_node = Seen { var, next: seen }; 214 | let seen = Some(&new_seen_node); 215 | 216 | if dbg { 217 | write!(f, "{}", var)?; 218 | } 219 | match *self.map.lookup(&var).unwrap() { 220 | Item::Unresolved => write!(f, "{}", var), 221 | Item::Var(v) => self.fmt_helper(f, v, ctx, seen, dbg), 222 | Item::Number(x) => write!(f, "{}", x), 223 | 224 | // Lists 225 | Item::Functor { 226 | name, 227 | args: &[head, tail], 228 | } if !dbg && name == ctx.builtins.cons => { 229 | write!(f, "[")?; 230 | self.fmt_helper(f, head, ctx, seen, dbg)?; 231 | self.fmt_list(f, tail, ctx, seen) 232 | } 233 | 234 | // Regular functors 235 | Item::Functor { name, args } => { 236 | write!(f, "{}", ctx.rodeo.resolve(&name))?; 237 | match *args { 238 | [] => Ok(()), 239 | [first, ref rest @ ..] => { 240 | write!(f, "(")?; 241 | self.fmt_helper(f, first, ctx, seen, dbg)?; 242 | for &arg in rest { 243 | write!(f, ", ")?; 244 | self.fmt_helper(f, arg, ctx, seen, dbg)?; 245 | } 246 | write!(f, ")") 247 | } 248 | } 249 | } 250 | } 251 | } 252 | 253 | fn fmt_list( 254 | &self, 255 | f: &mut impl Write, 256 | var: VarId, 257 | ctx: &Context, 258 | seen: Option<&Seen<'_>>, 259 | ) -> fmt::Result { 260 | if has_seen(var, seen) { 261 | for (name, &v) in &ctx.var_names { 262 | if var == v { 263 | return write!(f, " | {}]", ctx.rodeo.resolve(name)); 264 | } 265 | } 266 | return write!(f, " | {}]", var); 267 | } 268 | let new_seen_node = Seen { var, next: seen }; 269 | let seen = Some(&new_seen_node); 270 | 271 | let item = self.map.lookup(&var).unwrap(); 272 | match *item { 273 | Item::Var(v) => self.fmt_list(f, v, ctx, seen), 274 | 275 | // Nil 276 | Item::Functor { name, args: &[] } if name == ctx.builtins.nil => write!(f, "]"), 277 | // Cons 278 | Item::Functor { 279 | name, 280 | args: &[head, tail], 281 | } if name == ctx.builtins.cons => { 282 | write!(f, ", ")?; 283 | self.fmt_helper(f, head, ctx, seen, false)?; 284 | self.fmt_list(f, tail, ctx, seen) 285 | } 286 | 287 | // Not a list 288 | _ => { 289 | write!(f, " | ")?; 290 | self.fmt_helper(f, var, ctx, seen, false)?; 291 | write!(f, "]") 292 | } 293 | } 294 | } 295 | } 296 | 297 | /// A collection holding the local variables and parameters 298 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 299 | pub struct LocalVars<'a> { 300 | args: &'a [VarId], 301 | start: u64, 302 | count: u32, 303 | } 304 | 305 | impl VarTable<'_> { 306 | pub fn allocate_locals<'a>(&mut self, count: u32, args: &'a [VarId]) -> LocalVars<'a> { 307 | let start = self.next_var; 308 | let res = LocalVars { args, start, count }; 309 | self.next_var += count as u64; 310 | for v in start..self.next_var { 311 | self.map.insert(VarId(v), Item::Unresolved); 312 | } 313 | res 314 | } 315 | } 316 | 317 | impl LocalVars<'_> { 318 | pub fn get(&self, local: Local) -> VarId { 319 | if local.0 < 0 { 320 | self.args[(-local.0 - 1) as usize] 321 | } else { 322 | assert!(local.0 < self.count as i32); 323 | VarId(self.start + local.0 as u64) 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /sudoku.pl: -------------------------------------------------------------------------------- 1 | % vim:ft=prolog 2 | 3 | maplist(F, []). 4 | maplist(F, [X | Xs]) :- call(F, X), maplist(F, Xs). 5 | 6 | maplist(F, [], []). 7 | maplist(F, [X | Xs], [Y | Ys]) :- call(F, X, Y), maplist(F, Xs, Ys). 8 | 9 | same_length([], []). 10 | same_length([_ | X], [_ | Y]) :- same_length(X, Y). 11 | 12 | length([], 0). 13 | length([_ | Tail], N) :- length(Tail, N1), N is N1 + 1. 14 | 15 | member(X, [X | _]). 16 | member(X, [_ | Tail]) :- member(X, Tail). 17 | 18 | head([X | _], X). 19 | tail([_ | X], X). 20 | 21 | all_distinct([X | Xs]) :- \+ member(X, Xs), all_distinct(Xs). 22 | all_distinct([]). 23 | 24 | transpose([], []). 25 | transpose([[] | Xss], Yss) :- transpose(Xss, Yss). 26 | transpose(Xss, [Ys | Yss]) :- Xss = [_|_], 27 | maplist(head, Xss, Ys), maplist(tail, Xss, Tails), 28 | transpose(Tails, Yss). 29 | 30 | upto(X, X, [X]). 31 | upto(X, Y, [X | Tail]) :- X \= Y, X1 is X + 1, upto(X1, Y, Tail). 32 | 33 | time(Goal) :- 34 | cpu_time(Start), 35 | Goal, 36 | cpu_time(End), 37 | Duration is End - Start, 38 | nl, write('Took '), write(Duration), write('ms'), nl. 39 | 40 | %%%%% 41 | 42 | in_range(1). 43 | in_range(2). 44 | in_range(3). 45 | in_range(4). 46 | 47 | good_row(Row) :- maplist(in_range, Row), all_distinct(Row). 48 | 49 | mini_sudoku(Rows) :- 50 | length(Rows,4), maplist(same_length(Rows), Rows), 51 | transpose(Rows, Cols), 52 | Rows = [As,Bs,Cs,Ds], 53 | maplist(good_row, Rows), 54 | maplist(all_distinct, Cols), 55 | blocks(As, Bs), 56 | blocks(Cs, Ds). 57 | 58 | blocks([], []). 59 | blocks([N1,N2|Ns1], [N3,N4|Ns2]) :- 60 | all_distinct([N1,N2,N3,N4]), 61 | blocks(Ns1, Ns2). 62 | 63 | problem(1, [[_,2,_,_], 64 | [_,_,1,_], 65 | [_,1,_,_], 66 | [_,_,4,_]]). 67 | 68 | problem(2, [[1,_,2,_], 69 | [_,2,_,_], 70 | [_,_,3,_], 71 | [_,_,_,_]]). 72 | --------------------------------------------------------------------------------