├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples └── haskell-prelude.pl └── src ├── heap.rs ├── lexer.rs ├── likes.pl ├── main.rs ├── parser.lalrpop ├── prelude.pl ├── solve.rs ├── sort.pl ├── syntax.rs ├── token.rs └── unify.rs /.gitignore: -------------------------------------------------------------------------------- 1 | src/parser.rs 2 | *.swo 3 | *.swp 4 | target/ 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.13" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 9 | ] 10 | 11 | [[package]] 12 | name = "ansi_term" 13 | version = "0.11.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "arrayref" 21 | version = "0.3.6" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "arrayvec" 26 | version = "0.5.1" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "ascii-canvas" 31 | version = "2.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "atty" 39 | version = "0.2.14" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | dependencies = [ 42 | "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "autocfg" 49 | version = "1.0.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "base64" 54 | version = "0.12.3" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | 57 | [[package]] 58 | name = "bit-set" 59 | version = "0.5.2" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | dependencies = [ 62 | "bit-vec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 63 | ] 64 | 65 | [[package]] 66 | name = "bit-vec" 67 | version = "0.6.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | 70 | [[package]] 71 | name = "bitflags" 72 | version = "1.2.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | 75 | [[package]] 76 | name = "blake2b_simd" 77 | version = "0.5.10" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | dependencies = [ 80 | "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 83 | ] 84 | 85 | [[package]] 86 | name = "block-buffer" 87 | version = "0.7.3" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | dependencies = [ 90 | "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 94 | ] 95 | 96 | [[package]] 97 | name = "block-padding" 98 | version = "0.1.5" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | dependencies = [ 101 | "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 102 | ] 103 | 104 | [[package]] 105 | name = "byte-tools" 106 | version = "0.3.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | 109 | [[package]] 110 | name = "byteorder" 111 | version = "1.3.4" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "cc" 116 | version = "1.0.59" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | 119 | [[package]] 120 | name = "cfg-if" 121 | version = "0.1.10" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | 124 | [[package]] 125 | name = "clap" 126 | version = "2.33.3" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | dependencies = [ 129 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 130 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 136 | ] 137 | 138 | [[package]] 139 | name = "constant_time_eq" 140 | version = "0.1.5" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | 143 | [[package]] 144 | name = "crossbeam-utils" 145 | version = "0.7.2" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | dependencies = [ 148 | "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 151 | ] 152 | 153 | [[package]] 154 | name = "ctrlc" 155 | version = "3.1.6" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | dependencies = [ 158 | "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 160 | ] 161 | 162 | [[package]] 163 | name = "diff" 164 | version = "0.1.12" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | 167 | [[package]] 168 | name = "digest" 169 | version = "0.8.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | dependencies = [ 172 | "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 173 | ] 174 | 175 | [[package]] 176 | name = "directories" 177 | version = "3.0.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "dirs-sys 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 181 | ] 182 | 183 | [[package]] 184 | name = "dirs" 185 | version = "1.0.5" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 189 | "redox_users 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 190 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 191 | ] 192 | 193 | [[package]] 194 | name = "dirs-next" 195 | version = "1.0.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | dependencies = [ 198 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 199 | "dirs-sys-next 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 200 | ] 201 | 202 | [[package]] 203 | name = "dirs-sys" 204 | version = "0.3.5" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | dependencies = [ 207 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 208 | "redox_users 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 210 | ] 211 | 212 | [[package]] 213 | name = "dirs-sys-next" 214 | version = "0.1.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | dependencies = [ 217 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 218 | "redox_users 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 219 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 220 | ] 221 | 222 | [[package]] 223 | name = "docopt" 224 | version = "1.1.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | dependencies = [ 227 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 228 | "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 229 | "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", 230 | "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "either" 235 | version = "1.6.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | 238 | [[package]] 239 | name = "ena" 240 | version = "0.14.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | dependencies = [ 243 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 244 | ] 245 | 246 | [[package]] 247 | name = "fake-simd" 248 | version = "0.1.2" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | 251 | [[package]] 252 | name = "fixedbitset" 253 | version = "0.2.0" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | 256 | [[package]] 257 | name = "gc" 258 | version = "0.3.6" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | 261 | [[package]] 262 | name = "gc_derive" 263 | version = "0.3.6" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | dependencies = [ 266 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 267 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 268 | "syn 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 269 | "synstructure 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", 270 | ] 271 | 272 | [[package]] 273 | name = "generic-array" 274 | version = "0.12.3" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | dependencies = [ 277 | "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 278 | ] 279 | 280 | [[package]] 281 | name = "getrandom" 282 | version = "0.1.14" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | dependencies = [ 285 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 288 | ] 289 | 290 | [[package]] 291 | name = "hashbrown" 292 | version = "0.8.2" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | dependencies = [ 295 | "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 296 | ] 297 | 298 | [[package]] 299 | name = "heck" 300 | version = "0.3.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | dependencies = [ 303 | "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 304 | ] 305 | 306 | [[package]] 307 | name = "hermit-abi" 308 | version = "0.1.15" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | dependencies = [ 311 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 312 | ] 313 | 314 | [[package]] 315 | name = "indexmap" 316 | version = "1.5.1" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | dependencies = [ 319 | "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 320 | "hashbrown 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 321 | ] 322 | 323 | [[package]] 324 | name = "itertools" 325 | version = "0.9.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | dependencies = [ 328 | "either 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 329 | ] 330 | 331 | [[package]] 332 | name = "lalrpop" 333 | version = "0.19.0" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | dependencies = [ 336 | "ascii-canvas 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 337 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 338 | "bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 339 | "diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 340 | "docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 341 | "ena 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", 342 | "itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 343 | "lalrpop-util 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 344 | "petgraph 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 345 | "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", 347 | "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", 348 | "serde_derive 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", 349 | "sha2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 350 | "string_cache 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 351 | "term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 352 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 353 | ] 354 | 355 | [[package]] 356 | name = "lalrpop-util" 357 | version = "0.19.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | 360 | [[package]] 361 | name = "lazy_static" 362 | version = "1.4.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | 365 | [[package]] 366 | name = "libc" 367 | version = "0.2.76" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | 370 | [[package]] 371 | name = "log" 372 | version = "0.4.11" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | dependencies = [ 375 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 376 | ] 377 | 378 | [[package]] 379 | name = "memchr" 380 | version = "2.3.3" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | 383 | [[package]] 384 | name = "new_debug_unreachable" 385 | version = "1.0.4" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | 388 | [[package]] 389 | name = "nix" 390 | version = "0.17.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | dependencies = [ 393 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "cc 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 396 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 397 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 398 | ] 399 | 400 | [[package]] 401 | name = "opaque-debug" 402 | version = "0.2.3" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | 405 | [[package]] 406 | name = "petgraph" 407 | version = "0.5.1" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | dependencies = [ 410 | "fixedbitset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 411 | "indexmap 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 412 | ] 413 | 414 | [[package]] 415 | name = "phf_shared" 416 | version = "0.8.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | dependencies = [ 419 | "siphasher 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 420 | ] 421 | 422 | [[package]] 423 | name = "precomputed-hash" 424 | version = "0.1.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | 427 | [[package]] 428 | name = "proc-macro-error" 429 | version = "1.0.4" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | dependencies = [ 432 | "proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 433 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 434 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 435 | "syn 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", 437 | ] 438 | 439 | [[package]] 440 | name = "proc-macro-error-attr" 441 | version = "1.0.4" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | dependencies = [ 444 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 445 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 446 | "version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", 447 | ] 448 | 449 | [[package]] 450 | name = "proc-macro2" 451 | version = "1.0.19" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | dependencies = [ 454 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 455 | ] 456 | 457 | [[package]] 458 | name = "quote" 459 | version = "1.0.7" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | dependencies = [ 462 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 463 | ] 464 | 465 | [[package]] 466 | name = "redox_syscall" 467 | version = "0.1.57" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | 470 | [[package]] 471 | name = "redox_users" 472 | version = "0.3.5" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | dependencies = [ 475 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 476 | "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", 477 | "rust-argon2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", 478 | ] 479 | 480 | [[package]] 481 | name = "regex" 482 | version = "1.3.9" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | dependencies = [ 485 | "aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", 486 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 487 | "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", 488 | "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 489 | ] 490 | 491 | [[package]] 492 | name = "regex-syntax" 493 | version = "0.6.18" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | 496 | [[package]] 497 | name = "rust-argon2" 498 | version = "0.8.2" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | dependencies = [ 501 | "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 502 | "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", 503 | "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 504 | "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", 505 | ] 506 | 507 | [[package]] 508 | name = "rust-prolog" 509 | version = "0.1.0" 510 | dependencies = [ 511 | "ctrlc 3.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 512 | "directories 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 513 | "gc 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 514 | "gc_derive 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 515 | "lalrpop 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 516 | "lalrpop-util 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 517 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 518 | "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 519 | "rustyline 6.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 520 | "structopt 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", 521 | ] 522 | 523 | [[package]] 524 | name = "rustyline" 525 | version = "6.2.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | dependencies = [ 528 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 529 | "dirs-next 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 530 | "libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)", 531 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 532 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 533 | "nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", 534 | "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 535 | "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 536 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 537 | "utf8parse 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 538 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 539 | ] 540 | 541 | [[package]] 542 | name = "scopeguard" 543 | version = "1.1.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | 546 | [[package]] 547 | name = "serde" 548 | version = "1.0.115" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | dependencies = [ 551 | "serde_derive 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", 552 | ] 553 | 554 | [[package]] 555 | name = "serde_derive" 556 | version = "1.0.115" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | dependencies = [ 559 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 560 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "syn 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 562 | ] 563 | 564 | [[package]] 565 | name = "sha2" 566 | version = "0.8.2" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | dependencies = [ 569 | "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 570 | "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 571 | "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 572 | "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 573 | ] 574 | 575 | [[package]] 576 | name = "siphasher" 577 | version = "0.3.3" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | 580 | [[package]] 581 | name = "string_cache" 582 | version = "0.8.0" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | dependencies = [ 585 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 586 | "new_debug_unreachable 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 587 | "phf_shared 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 588 | "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 589 | "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", 590 | ] 591 | 592 | [[package]] 593 | name = "strsim" 594 | version = "0.8.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | 597 | [[package]] 598 | name = "strsim" 599 | version = "0.9.3" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | 602 | [[package]] 603 | name = "structopt" 604 | version = "0.3.17" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | dependencies = [ 607 | "clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)", 608 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 609 | "structopt-derive 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 610 | ] 611 | 612 | [[package]] 613 | name = "structopt-derive" 614 | version = "0.4.10" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | dependencies = [ 617 | "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 618 | "proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 619 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 620 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 621 | "syn 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 622 | ] 623 | 624 | [[package]] 625 | name = "syn" 626 | version = "1.0.39" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | dependencies = [ 629 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 630 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 631 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 632 | ] 633 | 634 | [[package]] 635 | name = "synstructure" 636 | version = "0.12.4" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | dependencies = [ 639 | "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", 640 | "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 641 | "syn 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", 642 | "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 643 | ] 644 | 645 | [[package]] 646 | name = "term" 647 | version = "0.5.2" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | dependencies = [ 650 | "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 651 | "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 652 | "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 653 | ] 654 | 655 | [[package]] 656 | name = "textwrap" 657 | version = "0.11.0" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | dependencies = [ 660 | "unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 661 | ] 662 | 663 | [[package]] 664 | name = "thread_local" 665 | version = "1.0.1" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | dependencies = [ 668 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 669 | ] 670 | 671 | [[package]] 672 | name = "typenum" 673 | version = "1.12.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | 676 | [[package]] 677 | name = "unicode-segmentation" 678 | version = "1.6.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | 681 | [[package]] 682 | name = "unicode-width" 683 | version = "0.1.8" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | 686 | [[package]] 687 | name = "unicode-xid" 688 | version = "0.2.1" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | 691 | [[package]] 692 | name = "utf8parse" 693 | version = "0.2.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | 696 | [[package]] 697 | name = "vec_map" 698 | version = "0.8.2" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | 701 | [[package]] 702 | name = "version_check" 703 | version = "0.9.2" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | 706 | [[package]] 707 | name = "void" 708 | version = "1.0.2" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | 711 | [[package]] 712 | name = "wasi" 713 | version = "0.9.0+wasi-snapshot-preview1" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | 716 | [[package]] 717 | name = "winapi" 718 | version = "0.3.9" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | dependencies = [ 721 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 722 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 723 | ] 724 | 725 | [[package]] 726 | name = "winapi-i686-pc-windows-gnu" 727 | version = "0.4.0" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | 730 | [[package]] 731 | name = "winapi-x86_64-pc-windows-gnu" 732 | version = "0.4.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | 735 | [metadata] 736 | "checksum aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 737 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 738 | "checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 739 | "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 740 | "checksum ascii-canvas 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff8eb72df928aafb99fe5d37b383f2fe25bd2a765e3e5f7c365916b6f2463a29" 741 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 742 | "checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 743 | "checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 744 | "checksum bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" 745 | "checksum bit-vec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" 746 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 747 | "checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 748 | "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 749 | "checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 750 | "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 751 | "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 752 | "checksum cc 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)" = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381" 753 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 754 | "checksum clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 755 | "checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 756 | "checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 757 | "checksum ctrlc 3.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d0b676fa23f995faf587496dcd1c80fead847ed58d2da52ac1caca9a72790dd2" 758 | "checksum diff 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 759 | "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 760 | "checksum directories 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8fed639d60b58d0f53498ab13d26f621fd77569cc6edb031f4cc36a2ad9da0f" 761 | "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" 762 | "checksum dirs-next 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" 763 | "checksum dirs-sys 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 764 | "checksum dirs-sys-next 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" 765 | "checksum docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" 766 | "checksum either 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" 767 | "checksum ena 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" 768 | "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 769 | "checksum fixedbitset 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" 770 | "checksum gc 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c94b8d46989f31474299e1483db243a9ed1f8a7d8508a78c91449e76e9ef6756" 771 | "checksum gc_derive 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "52c734b94d1d53fcc70e42c83cd7c6e108c9a4f6da8e78098a7d212805e86151" 772 | "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 773 | "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 774 | "checksum hashbrown 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" 775 | "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 776 | "checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 777 | "checksum indexmap 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9" 778 | "checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 779 | "checksum lalrpop 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6f55673d283313791404be21209bb433f128f7e5c451986df107eb5fdbd68d2" 780 | "checksum lalrpop-util 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f7e88f15a7d31dfa8fb607986819039127f0161058a3b248a146142d276cbd28" 781 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 782 | "checksum libc 0.2.76 (registry+https://github.com/rust-lang/crates.io-index)" = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" 783 | "checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 784 | "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 785 | "checksum new_debug_unreachable 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 786 | "checksum nix 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 787 | "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 788 | "checksum petgraph 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" 789 | "checksum phf_shared 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" 790 | "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 791 | "checksum proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 792 | "checksum proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 793 | "checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" 794 | "checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 795 | "checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 796 | "checksum redox_users 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 797 | "checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 798 | "checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 799 | "checksum rust-argon2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" 800 | "checksum rustyline 6.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777" 801 | "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 802 | "checksum serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)" = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" 803 | "checksum serde_derive 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)" = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" 804 | "checksum sha2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" 805 | "checksum siphasher 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" 806 | "checksum string_cache 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" 807 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 808 | "checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 809 | "checksum structopt 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5" 810 | "checksum structopt-derive 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f" 811 | "checksum syn 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" 812 | "checksum synstructure 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" 813 | "checksum term 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" 814 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 815 | "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 816 | "checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 817 | "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 818 | "checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 819 | "checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 820 | "checksum utf8parse 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 821 | "checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 822 | "checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 823 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 824 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 825 | "checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 826 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 827 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 828 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-prolog" 3 | version = "0.1.0" 4 | authors = ["dagit "] 5 | edition = "2018" 6 | build = "build.rs" # LALRPOP preprocessing 7 | 8 | [profile.dev] 9 | opt-level = 2 10 | debug = true 11 | 12 | [dependencies] 13 | rustyline = "*" 14 | regex = "*" 15 | lazy_static = "*" 16 | ctrlc = "*" 17 | gc = "*" 18 | gc_derive = "*" 19 | lalrpop-util = ">=0.16.2" 20 | structopt = "*" 21 | directories = "*" 22 | 23 | [build-dependencies] 24 | lalrpop = ">=0.16.2" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Jason Dagit 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of rust-prolog nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Rust implementation of prolog (originally) based on miniprolog: http://andrej.com/plzoo/html/miniprolog.html 3 | 4 | I translated miniprolog from the above SML sources to Rust for two primary reasons: 5 | 6 | * Firstly, I wanted a project to explore programming in Rust 7 | * Secondly, prolog is interesting but I never felt like I properly learned it 8 | 9 | Starting from an existing implementation was nice because it shortened the time to a working 10 | version. The current version in this repository is very much a work in progress and overly simplistic. 11 | I plan to improve it to better learn Rust and I plan to improve it as I learn Rust :) 12 | 13 | In other words, this is really a playground for me to play with both Rust and prolog. 14 | 15 | # Installation 16 | 17 | Tested on Linux (Fedora), OSX, and Windows. 18 | 19 | Should be as simple as: `cargo run` 20 | 21 | # Example 22 | 23 | ``` 24 | $ cargo run 25 | Welcome to rust-prolog! 26 | This prolog interpreter is based on the ML code at the PLZoo: 27 | http://andrej.com/plzoo/html/miniprolog.html 28 | 29 | Input syntax: 30 | ?- query. Make a query. 31 | a(t1, ..., tn). Assert an atomic proposition. 32 | A :- B1, ..., Bn. Assert an inference rule. 33 | $quit Exit interpreter. 34 | $use "filename" Execute commands from a file. 35 | Prolog> $use "src/likes.pl" 36 | Prolog> ?- likes(X,Y). 37 | Y = mary 38 | X = john 39 | 40 | more? (y/n) [y] 41 | ``` 42 | 43 | # Roadmap 44 | 45 | I haven't figured out exactly which directions I want to take this in, but some 46 | ideas I've been kicking around include: 47 | 48 | * [ ] Add a [type system](http://www.cs.bham.ac.uk/~udr/papers/TypedProlog.pdf) 49 | * [ ] Add constraints and constraint checker. 50 | * [ ] Add a bottom-up search procedure (perhaps even replace the existing 51 | top-down search procedure entirely). 52 | * [ ] Add program transformations to generate more efficient queries (for 53 | instance, magic sets and entailment elimination). 54 | * [ ] Change the syntax to one that supports mixfix declarations. 55 | 56 | Completed: 57 | 58 | * [x] Change the search procedure to a [complete search](http://www.ai.sri.com/~stickel/pttp.html). 59 | This is now done. It uses iterative deepening with an admissable heuristic 60 | that does a lower bound estimate on the number of unifications required to 61 | solve the current goal. It also generates contrapositives (instead of using 62 | restarts). 63 | * [x] Add more primitive types, lists, numbers, etc and make a small standard library. 64 | * [x] Improve the garbage collection. Currently cycles are allowed but due to 65 | the hash consing, nothing is ever collected. A simple scheme that should 66 | allow some collection is to throw away terms generated by unification 67 | and terms that are part of a query after the query has terminated. This 68 | should lend itself to a simplistic form of regions. 69 | 70 | Old goals that I may not pursue after all: 71 | 72 | * [ ] Compile to the [Warren Abstract Machine](http://wambook.sourceforge.net/). 73 | The reason I've reconsidered this one is because my search procedure is 74 | slowing moving towards a more general constraint logic search procedure. The 75 | WAM is still rather elegant and I may follow the development at some point 76 | and see if I can adapt it to fit the general CPL algorithm. Perhaps someone 77 | else has already done this work. 78 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | 3 | fn main() { 4 | lalrpop::process_root().unwrap() 5 | } 6 | -------------------------------------------------------------------------------- /examples/haskell-prelude.pl: -------------------------------------------------------------------------------- 1 | # Booleans 2 | type(true, bool). 3 | type(false, bool). 4 | 5 | # Maybe 6 | type(nothing, maybe(A)). 7 | type(just, arr(A,maybe(A))). 8 | type(maybe_, arr(B,arr(arr(A,B),arr(maybe(A),B)))). 9 | 10 | # Either 11 | type(left, arr(A,either(A,B))). 12 | type(right, arr(B,either(A,B))). 13 | type(either, arr(arr(A, C), arr(arr(B, C), arr(either(A,B), C)))). 14 | 15 | # Char TODO: how to represent chars? 16 | type(char,char). 17 | 18 | # tuples 19 | type(fst, arr(tuple(A,B),A)). 20 | type(comma,arr(A,arr(B,tuple(A,B)))). 21 | type(snd, arr(tuple(A,B),B)). 22 | type(curry, arr(arr(tuple(A,B), C), arr(A, arr(B, C)))). 23 | type(uncurry, arr(arr(A, arr(B, C)), arr(tuple(A,B), C))). 24 | 25 | type(compose, arr(arr(B, C), arr(arr(A, B), arr(A, C)))). 26 | 27 | # No idea why this rule for tuple is neccesary 28 | type(app(comma,X),arr(B,tuple(A,B))) :- 29 | type(X,A). 30 | # function application 31 | type(app(F,X), B) :- 32 | type(F, arr(A,B)), 33 | type(X, A). 34 | -------------------------------------------------------------------------------- /src/heap.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::hash::Hash; 3 | use gc::{Gc,Trace}; 4 | 5 | use crate::syntax::Term; 6 | 7 | type TermSet = HashSet>; 8 | type StringSet = HashSet>; 9 | pub struct Heap { 10 | // Values defined at the "top level" should 11 | // live forever. 12 | terms: TermSet, 13 | strings: StringSet, 14 | 15 | // Values constructed during the search procedure 16 | // or as the query need only live until the query 17 | // has been satisfied. 18 | ephemeral_terms: TermSet, 19 | ephemeral_strings: StringSet, 20 | } 21 | 22 | #[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)] 23 | pub enum Lifetime { 24 | Perm, 25 | Ephemeral, 26 | } 27 | 28 | fn insert_thing(perm_heap: &mut HashSet>, 29 | ephemeral_heap: &mut HashSet>, 30 | t: A, lt: Lifetime) -> Gc 31 | where A: Trace, 32 | Gc: Eq, 33 | Gc: Hash 34 | { 35 | let gc_thing = Gc::new(t); 36 | match perm_heap.get(&gc_thing) { 37 | Some(gc) => return gc.clone(), 38 | None => match ephemeral_heap.get(&gc_thing) { 39 | Some(gc) => return gc.clone(), 40 | None => (), 41 | } 42 | } 43 | match lt { 44 | Lifetime::Perm => perm_heap.insert(gc_thing.clone()), 45 | Lifetime::Ephemeral => ephemeral_heap.insert(gc_thing.clone()), 46 | }; 47 | gc_thing 48 | } 49 | 50 | impl Heap { 51 | pub fn new() -> Self { 52 | Heap { 53 | terms: HashSet::new(), 54 | strings: HashSet::new(), 55 | 56 | ephemeral_terms: HashSet::new(), 57 | ephemeral_strings: HashSet::new(), 58 | } 59 | } 60 | 61 | pub fn insert_term(&mut self, t: Term, lt: Lifetime) -> Gc 62 | { 63 | insert_thing(&mut self.terms, &mut self.ephemeral_terms, t, lt) 64 | } 65 | 66 | pub fn insert_string(&mut self, s: String, lt: Lifetime) -> Gc 67 | { 68 | insert_thing(&mut self.strings, &mut self.ephemeral_strings, s, lt) 69 | } 70 | 71 | pub fn cleanup(&mut self){ 72 | self.ephemeral_terms.clear(); 73 | self.ephemeral_strings.clear(); 74 | self.ephemeral_terms.shrink_to_fit(); 75 | self.ephemeral_strings.shrink_to_fit(); 76 | gc::force_collect(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/lexer.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(trivial_regex))] 2 | use regex::Regex; 3 | 4 | use crate::token::Token; 5 | use crate::token::Error; 6 | 7 | use lazy_static::lazy_static; 8 | 9 | pub type Spanned = (usize, T, usize); 10 | 11 | /* By default Regex looks for substring matches, that's why we prefix each of these 12 | with ^ so that it always matches from the start of the remaining input */ 13 | lazy_static! { 14 | static ref CONST : Regex = Regex::new(r"^[a-z][_a-zA-Z0-9]*").unwrap(); 15 | static ref VAR : Regex = Regex::new(r"^[A-Z][_a-zA-Z0-9]*").unwrap(); 16 | static ref NUMBER : Regex = Regex::new(r"^[0-9][0-9]*" ).unwrap(); 17 | static ref COMMENT : Regex = Regex::new(r"^#[^\n]*\n" ).unwrap(); 18 | static ref NEWLINE : Regex = Regex::new(r"^\n" ).unwrap(); 19 | static ref WS : Regex = Regex::new(r"^[[:blank:]]" ).unwrap(); 20 | static ref USE : Regex = Regex::new(r"^\$use" ).unwrap(); 21 | static ref QUIT : Regex = Regex::new(r"^\$quit" ).unwrap(); 22 | static ref GOAL : Regex = Regex::new(r"^\?-" ).unwrap(); 23 | static ref FROM : Regex = Regex::new(r"^:-" ).unwrap(); 24 | static ref TRUE : Regex = Regex::new(r"^true" ).unwrap(); 25 | static ref STRING : Regex = Regex::new(r#"^"[^"]*""# ).unwrap(); 26 | static ref LPAREN : Regex = Regex::new(r"^\(" ).unwrap(); 27 | static ref RPAREN : Regex = Regex::new(r"^\)" ).unwrap(); 28 | static ref LBRACKET: Regex = Regex::new(r"^\[" ).unwrap(); 29 | static ref RBRACKET: Regex = Regex::new(r"^\]" ).unwrap(); 30 | static ref COMMA : Regex = Regex::new(r"^," ).unwrap(); 31 | static ref PERIOD : Regex = Regex::new(r"^\." ).unwrap(); 32 | static ref PIPE : Regex = Regex::new(r"^\|" ).unwrap(); 33 | } 34 | 35 | pub struct Lexer<'input> { 36 | text : &'input str, 37 | line : usize, 38 | pos : usize, 39 | } 40 | 41 | impl<'input> Lexer<'input> { 42 | pub fn new(text: &'input str) -> Lexer<'input> { 43 | Lexer { 44 | text : text, 45 | line : 1, 46 | pos : 0, 47 | } 48 | } 49 | 50 | fn match_and_consume(text: &mut &'input str, pos: &mut usize, re: &Regex, action: F) 51 | -> Option> 52 | where F: Fn(&'input str) -> Token 53 | { 54 | if let Some(mat) = re.find(text) { 55 | *pos += mat.end(); 56 | let ret = Some(action(&text[mat.start()..mat.end()])); 57 | *text = &text[mat.end()..]; 58 | ret 59 | } else { 60 | None 61 | } 62 | } 63 | 64 | pub fn next_token(&mut self) -> Option> { 65 | /* Ignore comments and whitespace. We separate newline from the other 66 | whitespace so that we can count line numbers 67 | */ 68 | loop { 69 | if let Some(mat) = COMMENT.find(self.text) { 70 | self.line += 1; 71 | self.pos += mat.end(); 72 | self.text = &self.text[mat.end()..]; 73 | continue 74 | } else if let Some(mat) = NEWLINE.find(self.text) { 75 | self.line += 1; 76 | self.pos += mat.end(); 77 | self.text = &self.text[mat.end()..]; 78 | continue 79 | } else if let Some(mat) = WS.find(self.text) { 80 | self.pos += mat.end(); 81 | self.text = &self.text[mat.end()..]; 82 | continue 83 | } 84 | break; 85 | } 86 | 87 | macro_rules! actions { 88 | ( $( $x:expr => $y:expr ),* ) => { 89 | if false { None } /* 'if false' just to make the rust syntax below more uniform */ 90 | $( 91 | else if let t@Some(_) = Lexer::match_and_consume(&mut self.text, 92 | &mut self.pos, 93 | &$x, 94 | $y) { t } 95 | )* 96 | else { None } 97 | }; 98 | } 99 | 100 | /* Lexers should match the longest string they can, so we list the 101 | variable length regular expressions first to achieve maximal munch */ 102 | actions![ CONST => |s:&'input str| Token::CONST(s), 103 | VAR => |s:&'input str| Token::VAR(s), 104 | NUMBER => |s:&'input str| Token::NUMBER(s), 105 | USE => |_| Token::USE, 106 | QUIT => |_| Token::QUIT, 107 | GOAL => |_| Token::GOAL, 108 | FROM => |_| Token::FROM, 109 | TRUE => |_| Token::TRUE, 110 | STRING => |s:&'input str| Token::STRING(&s[1..s.len()-1]), 111 | LPAREN => |_| Token::LPAREN, 112 | RPAREN => |_| Token::RPAREN, 113 | LBRACKET => |_| Token::LBRACKET, 114 | RBRACKET => |_| Token::RBRACKET, 115 | COMMA => |_| Token::COMMA, 116 | PERIOD => |_| Token::PERIOD, 117 | PIPE => |_| Token::PIPE 118 | ] 119 | } 120 | } 121 | 122 | impl<'input> Iterator for Lexer<'input> { 123 | type Item = Result>, Error>; 124 | 125 | fn next(&mut self) -> Option { 126 | match self.next_token() { 127 | /* TODO: fix this span */ 128 | Some(t) => Some(Ok((0,t,0))), 129 | None => None, 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/likes.pl: -------------------------------------------------------------------------------- 1 | # An example of the likes predicate 2 | likes(mary, food). 3 | likes(mary, wine). 4 | likes(john, wine). 5 | likes(john, mary). 6 | 7 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod syntax; 2 | pub mod unify; 3 | pub mod solve; 4 | pub mod token; 5 | pub mod lexer; 6 | pub mod heap; 7 | 8 | use std::fs::File; 9 | use std::io::Error; 10 | 11 | use std::collections::vec_deque::VecDeque; 12 | use std::sync::atomic::{AtomicBool, Ordering}; 13 | use std::sync::Arc; 14 | 15 | use rustyline::Editor; 16 | use lazy_static::lazy_static; 17 | 18 | // Option parsing 19 | use std::path::PathBuf; 20 | use structopt::StructOpt; 21 | 22 | use directories::{ProjectDirs}; 23 | 24 | use crate::parser::ToplevelParser; 25 | use crate::solve::{solve_toplevel, assert}; 26 | use crate::syntax::Database; 27 | use crate::syntax::ToplevelCmd; 28 | use crate::syntax::ToplevelCmd::*; 29 | use crate::heap::Heap; 30 | use crate::lexer::Lexer; 31 | 32 | use lalrpop_util::lalrpop_mod; 33 | 34 | lalrpop_mod!(pub parser); // lalrpop generated parser 35 | 36 | #[derive(Debug, StructOpt)] 37 | #[structopt(name = "prolog", about = "prolog usage")] 38 | struct Opt { 39 | /// Activate verbose mode (currently does nothing) 40 | #[structopt(short = "v", long = "verbose")] 41 | verbose: bool, 42 | /// Print usage and quit 43 | #[structopt(short = "h", long = "help")] 44 | help: bool, 45 | /// Optionally read a file on start up 46 | #[structopt(parse(from_os_str))] 47 | input: Option, 48 | } 49 | 50 | enum Status { 51 | Ok, 52 | Quit, 53 | Err(E), 54 | } 55 | 56 | lazy_static! { 57 | static ref PARSER: ToplevelParser = ToplevelParser::new(); 58 | } 59 | 60 | /* [exec_cmd cmd] executes the toplevel command [cmd]. 61 | Returns Some() when the computation succeeded and None 62 | when the command failed. 63 | */ 64 | fn exec_cmd(db: &mut Database, heap: &mut Heap, cmd: &ToplevelCmd, rl: &mut Editor<()>, interrupted: &Arc, max_depth: i32) 65 | -> Status { 66 | match *cmd { 67 | Assert(ref a) => { assert(db, heap, a); Status::Ok }, 68 | Goal(ref g) => { 69 | interrupted.store(false, Ordering::SeqCst); 70 | solve_toplevel(db, heap, g, rl, interrupted, max_depth); 71 | heap.cleanup(); 72 | Status::Ok 73 | }, 74 | Quit => Status::Quit, 75 | Use(ref file) => match exec_file(db, heap, file, rl, interrupted, max_depth) { 76 | Status::Err(e) => { 77 | println!("Failed to execute file {}, {}", file, e); 78 | /* We could return the error here, but that causes the interpreter 79 | to quit on every parse error from loading a file */ 80 | Status::Ok 81 | }, 82 | s => s 83 | } 84 | } 85 | } 86 | 87 | /* [exec_file fn] executes the contents of file [fn]. */ 88 | fn exec_file(db: &mut Database, heap: &mut Heap, filename: &str, rl: &mut Editor<()>, interrupted: &Arc, max_depth: i32) 89 | -> Status { 90 | use std::io::prelude::Read; 91 | match File::open(filename) { 92 | Err(e) => Status::Err(e), 93 | Ok(mut f) => { 94 | let mut s = String::new(); 95 | match f.read_to_string(&mut s) { 96 | Err(e) => Status::Err(e), 97 | Ok(_) => { 98 | match PARSER.parse(heap, &s, Lexer::new(&s)) { 99 | Ok(cmds) => exec_cmds(db, heap, &cmds, rl, interrupted, max_depth), 100 | Err(_) => { println!("Parse error"); Status::Ok } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | /* [exec_cmds cmds] executes the list of toplevel commands [cmds]. */ 109 | fn exec_cmds(db: &mut Database, heap: &mut Heap, cmds: &[ToplevelCmd], rl: &mut Editor<()>, interrupted: &Arc, max_depth: i32) 110 | -> Status 111 | { 112 | let mut ret : Status = Status::Ok; 113 | for cmd in cmds.iter() { 114 | match exec_cmd(db, heap, cmd, rl, interrupted, max_depth) { 115 | Status::Quit => { ret = Status::Quit; break }, 116 | Status::Err(e) => { ret = Status::Err(e); break }, 117 | _ => continue 118 | } 119 | } 120 | ret 121 | } 122 | 123 | fn main() { 124 | use rustyline::error::ReadlineError; 125 | use std::thread; 126 | 127 | /* We create a new thread for the interpreter because that's the 128 | * simplest way to increase the stack size. Which we need to do 129 | * because we allocated everything on the stack and the default 130 | * stack size is pretty small. 131 | */ 132 | let max_depth = 10_000i32; 133 | let interpreter = thread::Builder::new().stack_size(128 * 1024 * 1024).spawn(move || { 134 | let opt = Opt::from_args(); 135 | 136 | // This is for handling Ctrl-C. We note the interruption 137 | // so that we can check for it inside the computations, and 138 | // abort as requested. 139 | let interrupted = Arc::new(AtomicBool::new(false)); 140 | let i = Arc::clone(&interrupted); 141 | ctrlc::set_handler(move || { 142 | i.store(true, Ordering::SeqCst); 143 | println!("Interrupted by ctrl-c"); 144 | }).expect("Error setting Ctrl-C handler"); 145 | 146 | let mut rl = Editor::<()>::new(); 147 | let mut db: Database = VecDeque::new(); 148 | let mut heap = Heap::new(); 149 | /* Load up the standard prelude */ 150 | let prelude_str = include_str!("prelude.pl"); 151 | match PARSER.parse(&mut heap, prelude_str, Lexer::new(prelude_str)) { 152 | Ok(cmds) => match exec_cmds(&mut db, &mut heap, &cmds, &mut rl, &interrupted, max_depth) { 153 | Status::Quit => panic!("$quit from prelude"), 154 | Status::Err(_) => panic!("Exiting due to unexpected error in prelude"), 155 | _ => () 156 | }, 157 | _ => panic!("Failed to parse prelude") 158 | } 159 | 160 | println!(r#"Welcome to rust-prolog!"#); 161 | println!(r#"This prolog interpreter is based on the ML code at the PLZoo:"#); 162 | println!(r#" http://andrej.com/plzoo/html/miniprolog.html"#); 163 | println!(r#""#); 164 | println!(r#"Input syntax: "#); 165 | println!(r#" ?- query. Make a query."#); 166 | println!(r#" a(t1, ..., tn). Assert an atomic proposition."#); 167 | println!(r#" A :- B1, ..., Bn. Assert an inference rule."#); 168 | println!(r#" $quit Exit interpreter."#); 169 | println!(r#" $use "filename" Execute commands from a file."#); 170 | 171 | if opt.verbose { 172 | println!("{:?}", opt); 173 | } 174 | 175 | if let Some(path) = opt.input { 176 | use std::io::prelude::*; 177 | let mut file = File::open(path).expect("Unable to open file"); 178 | let mut contents = String::new(); 179 | file.read_to_string(&mut contents).expect("Unable to read file"); 180 | 181 | match PARSER.parse(&mut heap, &contents, Lexer::new(&contents)) { 182 | Ok(cmds) => match exec_cmds(&mut db, &mut heap, &cmds, &mut rl, &interrupted, max_depth) { 183 | Status::Quit => panic!("$quit from user input"), 184 | Status::Err(_) => panic!("Exiting due to unexpected error in user input"), 185 | _ => () 186 | }, 187 | _ => panic!("Failed to parse user input") 188 | } 189 | 190 | } 191 | 192 | // failure to load history is a silent failure :( 193 | if let Some(proj_dirs) = ProjectDirs::from("com", "dagit", "rust-prolog") { 194 | let hist_dir = proj_dirs.cache_dir(); 195 | if let Ok(()) = std::fs::create_dir_all(hist_dir) { 196 | let mut hist_path = hist_dir.to_path_buf(); 197 | hist_path.push("history"); 198 | match rl.load_history(&hist_path) { 199 | Err(_) => (), // :( 200 | Ok(()) => (), 201 | } 202 | } 203 | } 204 | 205 | let prompt = "Prolog> "; 206 | 207 | loop { 208 | let readline = rl.readline(prompt); 209 | match readline { 210 | Ok(s) => { 211 | if s == "" { continue }; 212 | // First add it to the history 213 | rl.add_history_entry(s.clone()); 214 | let parse_result = PARSER.parse(&mut heap, &s, Lexer::new(&s)); 215 | match parse_result { 216 | Ok(commands) => match exec_cmds(&mut db, &mut heap, &commands, &mut rl, &interrupted, max_depth) { 217 | Status::Quit => return, 218 | Status::Err(e) => { 219 | println!("Exiting due to unexpected error: {}", e); 220 | return 221 | }, 222 | _ => continue 223 | }, 224 | Err(_) => println!("Parse error") 225 | } 226 | }, 227 | Err(ReadlineError::Interrupted) => { 228 | println!("Interrupted by user"); 229 | }, 230 | Err(ReadlineError::Eof) => { 231 | break 232 | }, 233 | Err(err) => { 234 | println!("Error: {:?}", err); 235 | break 236 | } 237 | } 238 | } 239 | // failure to save history is a silent failure :( 240 | if let Some(proj_dirs) = ProjectDirs::from("com", "dagit", "rust-prolog") { 241 | let hist_dir = proj_dirs.cache_dir(); 242 | if let Ok(()) = std::fs::create_dir_all(hist_dir) { 243 | let mut hist_path = hist_dir.to_path_buf(); 244 | hist_path.push("history"); 245 | match rl.save_history(&hist_path) { 246 | Err(_) => (), // :( 247 | Ok(()) => (), 248 | } 249 | } 250 | } 251 | }).unwrap(); 252 | interpreter.join().unwrap(); 253 | 254 | } 255 | -------------------------------------------------------------------------------- /src/parser.lalrpop: -------------------------------------------------------------------------------- 1 | use std::iter::once; 2 | use gc::Gc; 3 | 4 | use crate::heap::{Heap, Lifetime}; 5 | 6 | use crate::syntax::*; 7 | use crate::syntax::Term::*; 8 | use crate::syntax::ToplevelCmd::*; 9 | 10 | use crate::token::*; 11 | 12 | grammar<'input>(heap: &mut Heap, text: &'input str); 13 | 14 | // This defines the interface with the lexer 15 | extern { 16 | type Location = usize; 17 | type Error = Error; 18 | enum Token<'input> { 19 | "VAR" => Token::VAR(<&'input str>), 20 | "NUMBER" => Token::NUMBER(<&'input str>), 21 | "CONST" => Token::CONST(<&'input str>), 22 | "FROM" => Token::FROM{..}, 23 | "COMMA" => Token::COMMA{..}, 24 | "TRUE" => Token::TRUE{..}, 25 | "PERIOD" => Token::PERIOD{..}, 26 | "PIPE" => Token::PIPE{..}, 27 | "LPAREN" => Token::LPAREN{..}, 28 | "RPAREN" => Token::RPAREN{..}, 29 | "LBRACKET" => Token::LBRACKET{..}, 30 | "RBRACKET" => Token::RBRACKET{..}, 31 | "GOAL" => Token::GOAL{..}, 32 | "QUIT" => Token::QUIT{..}, 33 | "USE" => Token::USE{..}, 34 | "STRING" => Token::STRING(<&'input str>), 35 | } 36 | } 37 | // Productions 38 | 39 | // toplevel: 40 | // | EOF { [] } 41 | // | exprtop { $1 } 42 | // | cmdtop { $1 } 43 | pub Toplevel: Vec = { 44 | Exprtop, 45 | Cmdtop 46 | }; 47 | 48 | // exprtop: 49 | // | expr EOF { [$1] } 50 | // | expr toplevel { $1 :: $2 } 51 | Exprtop: Vec = { 52 | => vec![<>], 53 | => once(e).chain(t.into_iter()).collect() 54 | }; 55 | 56 | // cmdtop: 57 | // | cmd EOF { [$1] } 58 | // | cmd toplevel { $1 :: $2 } 59 | Cmdtop: Vec = { 60 | => vec![<>], 61 | => once(c).chain(t.into_iter()).collect() 62 | }; 63 | 64 | // cmd: 65 | // | USE STRING { Use $2 } 66 | // | QUIT { Quit } 67 | Cmd: ToplevelCmd = { 68 | "USE" <"STRING"> => Use(<>.to_string()), 69 | "QUIT" => Quit 70 | }; 71 | 72 | // expr: 73 | // | goal { $1 } 74 | // | assertion { $1 } 75 | Expr = { 76 | Goal, 77 | Assertion 78 | }; 79 | 80 | // This is a clever little trick to make it so there is less duplication in the 81 | // grammar. When we hit a "Goal" production, we would like to use the ephemeral 82 | // heap for parsing the expressions. To achieve this, all the productions 83 | // rechable from "Goal" have both "Perm" and "Ephemeral" versions. We can use 84 | // the macro feature of lalrpop to fold these back into a single definition. 85 | // these two productions are here because we can use them to move the lifetime 86 | // parameter from the lalrpop macro into the rust value level. See for example, 87 | // "Atom" where it uses at the start of each production. 88 | // Perm/Ephemeral always match and always produce the corresponding value of 89 | // type Lifetime. So this allows us to gain access to the parameter to the 90 | // lalrpop macro. 91 | Perm: Lifetime = { => Lifetime::Perm }; 92 | Ephemeral: Lifetime = { => Lifetime::Ephemeral }; 93 | 94 | // goal: 95 | // | GOAL clause PERIOD { Goal $2 } 96 | Goal: ToplevelCmd = { 97 | "GOAL" > "PERIOD" => Goal(<>) 98 | }; 99 | 100 | // assertion: 101 | // | atom PERIOD { Assert ($1, []) } 102 | // | atom FROM clause PERIOD { Assert ($1, $3) } 103 | Assertion: ToplevelCmd = { 104 | > "PERIOD" => Assert((<>, vec![])), 105 | > "FROM" > "PERIOD" => Assert((a,c)) 106 | }; 107 | 108 | // atom: 109 | // | CONST { ($1, []) } 110 | // | CONST LPAREN args RPAREN { ($1, $3) } 111 | Atom : (Constant, Vec>) = { 112 | 113 | => 114 | (heap.insert_string(c.to_string(), l), vec![]), 115 | 116 | "LPAREN" > "RPAREN" => 117 | (heap.insert_string(c.to_string(), l), a) 118 | 119 | }; 120 | 121 | // clause: 122 | // | TRUE { [] } 123 | // | atom { [$1] } 124 | // | atom COMMA clause { $1 :: $3 } 125 | Clause: Vec = { 126 | "TRUE" => vec![], 127 | > => vec![<>], 128 | > "COMMA" > => once(a).chain(c.into_iter()).collect() 129 | }; 130 | 131 | // args: 132 | // | literal { [$1] } 133 | // | literal COMMA args { $1 :: $3 } 134 | Args: Vec> = { 135 | > => vec![<>], 136 | > "COMMA" > => once(l).chain(a.into_iter()).collect() 137 | }; 138 | 139 | // literal: 140 | // | CONST { Const $1 } 141 | // | VAR { Var ($1, 0) } 142 | // | NUMBER { convert to calls to succ } 143 | // | CONST LPAREN args RPAREN { App ($1, $3) } 144 | Literal: Gc = { 145 | => { 146 | let c_str = heap.insert_string(c.to_string(), l); 147 | heap.insert_term( Const(c_str), l ) 148 | }, 149 | => { 150 | let v_str = heap.insert_string(v.to_string(), l); 151 | heap.insert_term( Var((v_str, 0)), l ) 152 | }, 153 | => { 154 | str_to_nat(heap, n, l) 155 | }, 156 | "LPAREN" > "RPAREN" => { 157 | let app = heap.insert_string(c.to_string(), l); 158 | heap.insert_term( App(app, a), l ) 159 | }, 160 | ListLiteral => <> 161 | }; 162 | 163 | ListLiteral: Gc = { 164 | "LBRACKET" "RBRACKET" => vec_to_list(heap, vec![], l), 165 | "LBRACKET" > "RBRACKET" => { 166 | vec_to_list(heap, a, l) 167 | }, 168 | "LBRACKET" > "PIPE" > "RBRACKET" => { 169 | // "cons" is a common string mentioned in the prelude so it can be hardcoded 170 | // to Perm. 171 | let cons = heap.insert_string("cons".to_string(), Lifetime::Perm); 172 | heap.insert_term(Term::App(cons, vec![head, tail]), l) 173 | } 174 | }; 175 | 176 | -------------------------------------------------------------------------------- /src/prelude.pl: -------------------------------------------------------------------------------- 1 | # Equality of terms 2 | eq(X,X). 3 | 4 | # Lists 5 | 6 | ## Constructors: 7 | # nil. 8 | # cons(X,Y). 9 | 10 | ## List relation: 11 | list(nil). 12 | list(cons(X,Y)) :- list(Y). 13 | 14 | ## List append 15 | append(nil,X,X). 16 | append([H|T], L2, [H|L3]) :- append(T,L2,L3). 17 | 18 | ## List delete 19 | delete(X,[X|T],T). 20 | delete(X,[H|T],[H|T2]) :- delete(X,T,T2). 21 | 22 | ## Head & Tail 23 | head(H,[H|T]). 24 | tail(T,[H|T]). 25 | 26 | ## First and last 27 | first(H,cons(H,T)). 28 | last(Y,cons(H,T)) :- last(Y,T). 29 | 30 | ## Prefix and suffix 31 | prefix(nil, X). 32 | prefix([H|X],[H|Y]) :- prefix(X,Y). 33 | suffix(X,X). 34 | suffix([H|X],Y) :- suffix(X,Y). 35 | 36 | ## Membership 37 | member(X, [X|T]). 38 | member(X, [H|L]) :- member(X, L). 39 | 40 | ## Sublist 41 | sublist(S,L) :- append(X,S,P), append(P,Y,L). 42 | 43 | ## Permutations 44 | perm(nil,nil). 45 | perm(L,[H|P]) :- delete(H,L,R), perm(R,P). 46 | 47 | 48 | # Natural Numbers 49 | 50 | ## Constructors: 51 | # zero. 52 | # succ(X). 53 | 54 | ## Nat relation: 55 | nat(zero). 56 | nat(succ(X)) :- nat(X). 57 | 58 | ## Addition: 59 | 60 | add(zero,S,S). 61 | add(succ(I),J,succ(K)) :- add(I,J,K). 62 | 63 | ## Subtraction: 64 | 65 | # sub(x,y,z) => x - y = z 66 | sub(S,zero,S). 67 | # if I - J = K, then (I + 1) - (J + 1) = I - J = K. 68 | sub(succ(I), succ(J), K) :- sub(I,J,K). 69 | 70 | ## Natural number Comparisons 71 | lt(zero,succ(X)). 72 | lt(succ(X), succ(Y)) :- lt(X,Y). 73 | 74 | lte(X,X). 75 | lte(X,Y) :- lt(X,Y). 76 | 77 | gt(succ(X),zero). 78 | gt(succ(X),succ(Y)) :- gt(X,Y). 79 | 80 | gte(X,X). 81 | gte(X,Y) :- gt(X,Y). 82 | 83 | -------------------------------------------------------------------------------- /src/solve.rs: -------------------------------------------------------------------------------- 1 | /* The prolog solver. */ 2 | use std::iter::once; 3 | use std::collections::HashMap; 4 | use std::collections::vec_deque::VecDeque; 5 | use std::sync::atomic::{AtomicBool, Ordering}; 6 | use std::sync::Arc; 7 | use gc::Gc; 8 | 9 | use crate::syntax::{Database, Environment, Assertion, Term, Atom, 10 | string_of_env, make_complementary, generate_contrapositives}; 11 | use crate::unify::{unify_atoms, unify_terms}; 12 | use crate::heap::{Heap, Lifetime}; 13 | use rustyline::Editor; 14 | 15 | /* A value of type [choice] represents a choice point in the proof 16 | search at which we may continue searching for another solution. It 17 | is a tuple [(asrl, enn, c, n)] where [asrl] for other solutions of 18 | clause [c] in environment [env], using assertion list [asrl], where [n] 19 | is the search depth. */ 20 | type Choice = (Database, Environment, FramableClause, i32); 21 | 22 | /* As part of model elimination, it is useful to track assumptions 23 | * separately from the rest of the search state. We accomplish this 24 | * by "framing" atoms. Because this is state specific to the solver 25 | * these types shouldn't be exposed outside of this module. 26 | */ 27 | #[derive(PartialEq, Copy, Clone, Debug)] 28 | enum FrameStatus { 29 | Unframed, 30 | Framed, 31 | } 32 | 33 | type FramableAtom = (Atom, FrameStatus); 34 | type FramableClause = VecDeque; 35 | 36 | /* The global database of assertions cannot be represented with a 37 | global variable, like in ML */ 38 | 39 | /* Add a new assertion at the end of the current database. */ 40 | pub fn assert(database: &mut Database, heap: &mut Heap, a: &Assertion) { 41 | let mut contrapositives = generate_contrapositives(heap, a, Lifetime::Perm); 42 | database.append(&mut contrapositives); 43 | } 44 | 45 | /* Exception [NoSolution] is raised when a goal cannot be proved. */ 46 | enum Error { 47 | NoSolution, 48 | DepthExhausted, 49 | } 50 | 51 | /* [renumber_term t n] renumbers all variable instances occurring in 52 | term [t] so that they have level [n]. */ 53 | fn renumber_term(heap: &mut Heap, n: i32, t: &Term) -> Gc { 54 | match *t { 55 | Term::Var((ref x, _)) => heap.insert_term(Term::Var((x.clone(),n)), Lifetime::Ephemeral), 56 | Term::Const(ref c) => heap.insert_term(Term::Const(c.clone()), Lifetime::Ephemeral), 57 | Term::App(ref c, ref ts) => { 58 | let new_t = Term::App(c.clone(), 59 | ts.iter() 60 | .map( |t| renumber_term(heap, n, t) ) 61 | .collect::>>()); 62 | heap.insert_term(new_t, Lifetime::Ephemeral) 63 | } 64 | } 65 | } 66 | 67 | /* [renumber_atom n a] renumbers all variable instances occurring in 68 | atom [a] so that they have level [n]. */ 69 | fn renumber_atom(heap: &mut Heap, n: i32, &(ref c, ref ts):&Atom) -> Atom { 70 | (c.clone(), ts.iter() 71 | .map( |t| renumber_term(heap, n, t) ) 72 | .collect::>>() ) 73 | } 74 | 75 | struct Solver<'a> { 76 | database: &'a Database, 77 | choices: Vec, 78 | env: Environment, 79 | heap: &'a mut Heap, 80 | interrupted: &'a Arc, 81 | rl: &'a mut Editor<()>, 82 | // Maximum depth for the current iteration. 83 | max_depth: i32, 84 | // The maximum seen depth in the current iteration. 85 | // Tracking this allows us to exit iterative deepening when 86 | // we've visited the entire search space but haven't yet hit 87 | // the search depth limit. 88 | cur_depth: i32, 89 | } 90 | 91 | impl<'a> Solver<'a> { 92 | 93 | fn new(db: &'a Database, heap: &'a mut Heap, rl: &'a mut Editor<()>, interrupted: &'a Arc, max_depth: i32) -> Self { 94 | Solver { 95 | database: db, 96 | choices: vec![], 97 | env: HashMap::new(), 98 | heap: heap, 99 | interrupted: interrupted, 100 | rl: rl, 101 | max_depth: max_depth, 102 | cur_depth: 0, 103 | } 104 | } 105 | 106 | /* [display_solution] displays the solution of a goal encoded 107 | by [env] and the current search depth. It then gives the user the option to search for other 108 | solutions, as described by the list of choice points, or to abort the current proof search. */ 109 | fn display_solution(&mut self, n: i32) -> Result<(), Error> 110 | { 111 | /* Due to the way iterative deepening works, we only need to print an answer the first time 112 | * we find it. That is, at the first depth we see it. 113 | */ 114 | if n < self.max_depth { return self.continue_search() } 115 | /* This is probably the least efficient way to figure out 116 | when we're done */ 117 | let answer = string_of_env(&self.env, self.heap); 118 | if answer == "Yes" { 119 | Ok(println!("Yes")) 120 | } else if self.choices.is_empty() { 121 | Ok(println!("{}", answer)) 122 | } else { 123 | println!("{} \n", answer); 124 | let readline = self.rl.readline("more? (y/n) [y] "); 125 | match readline { 126 | Ok(s) => { 127 | let input = s.trim(); 128 | if input == "y" || input == "yes" || input == "" { 129 | self.continue_search() 130 | } else { 131 | Err(Error::NoSolution) 132 | } 133 | }, 134 | _ => { Err(Error::NoSolution) } 135 | } 136 | } 137 | } 138 | 139 | /* [continue_search a] looks for other answers. It uses the choices list of 140 | choices. It continues the search at the first choice in the list. 141 | */ 142 | fn continue_search(&mut self) -> Result<(), Error> 143 | { 144 | if self.choices.is_empty() && self.cur_depth < self.max_depth { 145 | Err(Error::NoSolution) 146 | } else if self.choices.is_empty() { 147 | Err(Error::DepthExhausted) 148 | } else { 149 | let (asrl, env, gs, n) = self.choices.pop().expect(concat!(file!(), ":", line!())); 150 | self.env = env; 151 | self.solve(&asrl, &gs, n) 152 | } 153 | } 154 | 155 | 156 | /* [solve asrl c n] looks for the proof of clause [c]. Other 157 | arguments are: 158 | 159 | [asrl] is the list of assertions that are used to reduce [c] to subgoals, 160 | 161 | [n] is the search depth, which is increased at each level of search. 162 | 163 | When a solution is found, it is printed on the screen. The user 164 | then decides whether other solutions should be searched for. 165 | */ 166 | fn solve(&mut self, 167 | asrl: &Database, 168 | c: &FramableClause, 169 | n: i32,) 170 | -> Result<(), Error> 171 | { 172 | // TODO: make these println into debugging diagnostics 173 | //println!("c = {}", string_of_clauses(c)); 174 | 175 | self.cur_depth = std::cmp::max(self.cur_depth, n); 176 | //First check all of our early exit conditions 177 | 178 | // All atoms are solved, we found a solution 179 | if c.is_empty() { return self.display_solution(n) } 180 | // user requested we abort 181 | if self.interrupted.load(Ordering::SeqCst) { return Err(Error::NoSolution) } 182 | // abort this branch, and backtrack according to iterated deepening search 183 | if n > self.max_depth { return self.continue_search() } 184 | 185 | // Now we're ready to do one step of solving the goal 186 | let mut new_c = c.to_owned(); 187 | // this pop cannot fail because we made sure that c is non-empty 188 | match new_c.pop_front().unwrap() { 189 | /* if the left most atom is framed we remove it and call solve with essentially the 190 | * same state */ 191 | (_a, FrameStatus::Framed) => { 192 | //println!("removing framed: {}", string_of_clauses(&[(_a,FrameStatus::Framed)])); 193 | self.solve(asrl, &new_c, n) 194 | }, 195 | (a, FrameStatus::Unframed) => { 196 | //println!("a = {}", string_of_clauses(&[(a.to_owned(),FrameStatus::Unframed)])); 197 | if is_complementary(self.heap, &a, &new_c) { 198 | //println!("found complementary: {}", string_of_clauses(&[(a,FrameStatus::Unframed)])); 199 | return self.solve(asrl, &new_c, n) 200 | } 201 | match reduce_atom(&self.env, self.heap, n, &a, asrl) { 202 | None => 203 | /* This clause cannot be solved, look for other solutions */ 204 | self.continue_search(), 205 | Some((new_asrl, new_env, d)) => { 206 | /* The atom was reduced to subgoals [d]. Continue 207 | search with the subgoals added to the list of goals. */ 208 | /* Add a new choice */ 209 | //let mut ch = self.choices.to_owned(); 210 | self.choices.push((new_asrl,self.env.clone(),c.to_owned(),n)); 211 | self.env = new_env; 212 | //println!("inserting: {} and {}", string_of_clauses(&new_c), string_of_clauses(&d)); 213 | let d = d.into_iter() 214 | .chain(once((a,FrameStatus::Framed))) 215 | .chain(new_c.into_iter()) 216 | .collect::(); 217 | self.solve(self.database, &d, n+1) 218 | } 219 | } 220 | } 221 | } 222 | } 223 | } 224 | 225 | /* uses unification to search for framed atoms whose complement unifies with the given atom. */ 226 | fn is_complementary(heap: &mut Heap, a: &Atom, c: &FramableClause) -> bool 227 | { 228 | // this attemps to find a "complementary" match using unification 229 | // eg., not(p) is complementary to p (and vice-versa) 230 | let try_complement = make_complementary(heap, a, Lifetime::Ephemeral); 231 | match try_complement { 232 | Some(t) => { 233 | //println!("negation, t = {}", string_of_term(&t)); 234 | for x in c { 235 | match *x { 236 | ((ref c, ref ts), FrameStatus::Framed) => { 237 | let t2 = if ts.is_empty() { 238 | heap.insert_term(Term::Const(c.to_owned()), Lifetime::Ephemeral) 239 | } else { 240 | heap.insert_term(Term::App(c.to_owned(), ts.to_owned()), Lifetime::Ephemeral) 241 | }; 242 | match unify_terms(&HashMap::new(), heap, &t, &t2) { 243 | Err(_) => (), 244 | Ok(_) => return true, 245 | } 246 | } 247 | _ => () 248 | } 249 | } 250 | } 251 | None => () 252 | } 253 | false 254 | } 255 | 256 | /* [reduce_atom a asrl] reduces atom [a] to subgoals by using the 257 | first assertion in the assertion list [asrl] whose conclusion matches 258 | [a]. It returns [None] if the atom cannot be reduced. */ 259 | fn reduce_atom(env: &Environment, heap: &mut Heap, n: i32, a: &Atom, local_asrl: &Database) 260 | -> Option<(Database, Environment, FramableClause)> 261 | { 262 | if local_asrl.is_empty() { 263 | None 264 | } else { 265 | let mut asrl2 = local_asrl.to_owned(); 266 | let (b, lst) = asrl2.pop_front().expect(concat!(file!(), ":", line!())); 267 | let new_b = renumber_atom(heap, n, &b); 268 | let try_env = unify_atoms(env, heap, a, &new_b); 269 | match try_env { 270 | Err(_) => reduce_atom(env, heap, n, a, &asrl2), 271 | Ok(new_env) => Some(( 272 | asrl2, 273 | new_env, 274 | lst.iter() 275 | .map( |l| (renumber_atom(heap, n, l), FrameStatus::Unframed)) 276 | .collect::() 277 | )) 278 | } 279 | } 280 | } 281 | 282 | /* [solve_toplevel c] searches for the proof of clause [c] using 283 | the "global" database. This function is called from the main 284 | program */ 285 | pub fn solve_toplevel(db: &Database, heap: &mut Heap, c: &[Atom], rl: &mut Editor<()>, interrupted: &Arc, max_depth: i32) { 286 | let mut depth = 0; 287 | let c = c.iter() 288 | .map(|x| (x.to_owned(),FrameStatus::Unframed)) 289 | .collect::(); 290 | loop { 291 | if depth >= max_depth { return println!("Search depth exhausted") } 292 | let mut s = Solver::new(db, heap, rl, interrupted, depth); 293 | match s.solve(db, &c, 1) { 294 | Err(Error::DepthExhausted) => depth += 1, 295 | Err(Error::NoSolution) => return println!("No"), 296 | Ok(()) => return () 297 | } 298 | } 299 | } 300 | 301 | -------------------------------------------------------------------------------- /src/sort.pl: -------------------------------------------------------------------------------- 1 | # Sorting 2 | naive_sort(L,S) :- perm(L,S), is_sorted(S). 3 | 4 | is_sorted(nil). 5 | is_sorted(cons(X,nil)). 6 | is_sorted(cons(X,[Y|T])) :- lte(X,Y), is_sorted([Y|T]). 7 | 8 | ?- naive_sort([2, 1, 0], X). 9 | -------------------------------------------------------------------------------- /src/syntax.rs: -------------------------------------------------------------------------------- 1 | /* This is a rust translation of miniprolog from the plzoo: 2 | * http://andrej.com/plzoo/html/miniprolog.html 3 | */ 4 | /* Abstract syntax */ 5 | 6 | use std::iter::once; 7 | use std::collections::vec_deque::VecDeque; 8 | use std::collections::HashMap; 9 | use gc::Gc; 10 | use gc_derive::{Trace, Finalize}; 11 | 12 | use crate::heap::{Heap, Lifetime}; 13 | 14 | /* Constants and atoms are strings starting with lower-case letters. */ 15 | pub type Constant = Gc; 16 | 17 | /* Variables are strings starting with upper-case letters, followed by 18 | a number which indicates an instance of the variable. Thus a 19 | variable instance is a pair [(x,n)] where [x] is a variable and [n] is an 20 | integer. When the proof search depth is [n] all variables that we need to use 21 | are renamed from [(x,0)] to [(x,n)]. This is necessary so that we do not use 22 | the same variable name in two different applications of the same assertion. */ 23 | pub type Variable = (Gc, i32); 24 | 25 | /* The datatype of terms */ 26 | #[derive(Hash, Eq, PartialEq, Clone, Debug, Trace, Finalize)] 27 | pub enum Term { 28 | Var(Variable), // Variable `X1`, `Y0`, `Z2`, ... 29 | Const(Constant), // Constant `a`, `b`, `c`, ... 30 | App(Constant, Vec>), // Compound term `f(t_1, ..., t_n)` 31 | } 32 | 33 | /* Atomic proposition [p(t_1, ..., t_n)] */ 34 | pub type Atom = (Constant, Vec>); 35 | 36 | /* A conjunction of atomic propositions [p_1, ..., p_n]. The empty 37 | list represens [true]. */ 38 | pub type Clause = Vec; 39 | pub type ClauseSlice = [Atom]; 40 | 41 | /* An assertion [(a,b_1,...,b_n)] is a Horn formula 42 | [b_1 & ... & b_n => a]. */ 43 | pub type Assertion = (Atom, Clause); 44 | 45 | /* An environment is a list of pairs [(x, e)] where [x] is a variable 46 | instance and [e] is a term. An environment represents the current values 47 | of variables. */ 48 | pub type Environment = HashMap>; 49 | 50 | /* A database is a list of assertions. It represents the current program. */ 51 | pub type Database = VecDeque; 52 | 53 | /* Toplevel commands */ 54 | #[derive(PartialEq, Clone)] 55 | pub enum ToplevelCmd { 56 | Assert(Assertion), /* Assertion [a :- b_1, ..., b_n.] or [a.] */ 57 | Goal(Clause), /* Query [?- a] */ 58 | Quit, /* The [$quit] command. */ 59 | Use(String) /* The [$use "filename"] command. */ 60 | } 61 | 62 | static NOT: &'static str = "not"; 63 | 64 | /* [lookup env x] returns the value of variable instance [x] in 65 | environment [env]. It returns [Var x] if the variable does not 66 | occur in [env]. */ 67 | fn lookup(env: &Environment, heap: &mut Heap, x: &Variable) -> Gc { 68 | match env.get(x) { 69 | Some(y) => y.clone(), 70 | None => { 71 | heap.insert_term(Term::Var(x.clone()), Lifetime::Ephemeral) 72 | } 73 | } 74 | } 75 | 76 | /* [subst_term sub t] substitutes in term [t] values for variables, 77 | as specified by the associative list [s]. It substitutes 78 | repeatedly until the terms stop changing, so this is not the 79 | usual kind of substitution. It is what we need during unification */ 80 | pub fn subst_term(env: &Environment, heap: &mut Heap, t: &Term) -> Gc { 81 | match *t { 82 | Term::Var(ref x) => { 83 | let new_t = lookup(env, heap, x); 84 | if *t == *new_t { 85 | new_t 86 | } else { 87 | subst_term(env, heap, &new_t) 88 | } 89 | }, 90 | Term::Const(_) => heap.insert_term(t.clone(), Lifetime::Ephemeral), 91 | Term::App(ref c, ref ls) => { 92 | let mut new_ls = Vec::with_capacity(ls.len()); 93 | for l in ls.iter() { 94 | new_ls.push(subst_term(env, heap, l)); 95 | } 96 | heap.insert_term(Term::App(c.clone(), new_ls), Lifetime::Ephemeral) 97 | } 98 | } 99 | } 100 | 101 | /* [string_of_term t] converts term [t] to its string representation. */ 102 | pub fn string_of_term(t: &Term) -> String { 103 | match *t { 104 | Term::Var((ref v, 0)) => v.to_string(), 105 | Term::Var((ref v, n)) => v.to_string() + &n.to_string(), 106 | Term::Const(ref c) => string_of_const(c), 107 | Term::App(..) => string_of_app(t), 108 | } 109 | } 110 | 111 | fn string_of_const(t: &str) -> String { 112 | match t { 113 | "zero" => "0", 114 | "nil" => "[]", 115 | _ => t, 116 | }.to_string() 117 | } 118 | 119 | fn string_of_app(t: &Term) -> String { 120 | match *t { 121 | Term::App(ref f, ref args) => match (**f).as_str() { 122 | "cons" => { 123 | let list_str : Vec = list_map(t, &string_of_term); 124 | "[".to_string() + &list_str.join(", ") + "]" 125 | }, 126 | "succ" => { 127 | nat_to_word(t).to_string() 128 | }, 129 | _ => { 130 | let list_str = string_of_list(args); 131 | f.to_string() + "(" + &list_str + ")" 132 | } 133 | }, 134 | _ => panic!() 135 | } 136 | } 137 | 138 | fn list_map(list: &Term, f: &dyn Fn(&Term) -> A) -> Vec 139 | { 140 | match *list { 141 | Term::App(ref t, ref elts) => match (**t).as_str () { 142 | "cons" => { 143 | if elts.len() == 2 { 144 | let head = &elts[0]; 145 | let tail = &elts[1]; 146 | let mut r = vec![f(head)]; 147 | let ts = list_map(&tail, f); 148 | r.extend::>(ts); 149 | r 150 | } else if elts.len() == 1 { 151 | let head = &elts[0]; 152 | vec![f(head)] 153 | } else { 154 | vec![] 155 | } 156 | } 157 | _ => vec![] 158 | }, 159 | _ => vec![] 160 | } 161 | } 162 | 163 | fn nat_to_word(list: &Term) -> u64 164 | { 165 | match *list { 166 | Term::Const(ref t) => match (**t).as_str() { 167 | "zero" => 0, 168 | _ => 0, 169 | }, 170 | Term::App(ref t, ref elts) => { 171 | match (**t).as_str () { 172 | "succ" => { 173 | if elts.len() == 1 { 174 | let arg = &elts[0]; 175 | let acc = nat_to_word(&arg); 176 | 1+acc 177 | } else { 178 | 1 179 | } 180 | } 181 | _ => 0 182 | } 183 | }, 184 | _ => 0 185 | } 186 | } 187 | 188 | fn string_of_list(args: &[Gc]) -> String { 189 | let mut strings = Vec::with_capacity(args.len()); 190 | for l in args.iter() { 191 | strings.push(string_of_term(l)); 192 | } 193 | strings.join(", ") 194 | } 195 | 196 | /* [string_of_env env] converts environment [env] to its string 197 | representation. It only keeps instance variables at level 0, i.e., 198 | those that appear in the toplevel goal. */ 199 | pub fn string_of_env(env: &Environment, heap: &mut Heap) -> String { 200 | let toplevels = env.iter() 201 | .filter( |&(&(_,n),_) | n==0) 202 | /* This creates copies and is unnecessary */ 203 | .map( |(&(ref x,ref y), z)| 204 | ((x.clone(),*y),z.clone()) ) 205 | .collect::(); 206 | if toplevels.is_empty() { 207 | "Yes".to_string() 208 | } else { 209 | let res = toplevels.iter() 210 | .map( |(&(ref x, _), e)| 211 | x.to_string() + " = " + 212 | &string_of_term(&subst_term(env,heap,e))) 213 | .collect::>(); 214 | res.join("\n") 215 | } 216 | } 217 | 218 | /* [exists fn ls] returns [true] if [fn] returns true on at least 219 | one element of [ls], and returns [false] otherwise. 220 | This was added to mimic the standard ML List.exists function. */ 221 | pub fn exists(predicate: P, ls: &[A]) -> bool 222 | where P: Fn(&A) -> bool { 223 | for x in ls.iter() { 224 | if predicate(x) { 225 | return true; 226 | } 227 | } 228 | false 229 | } 230 | 231 | /* [occurs x t] returns [true] when variable instance [x] appears in 232 | term [t]. */ 233 | pub fn occurs(x: &Variable, t: &Term) -> bool { 234 | match *t { 235 | Term::Var(ref y) => x == y, 236 | Term::Const(_) => false, 237 | Term::App(_, ref ts) => exists(|t| occurs(x, t), ts) 238 | } 239 | } 240 | 241 | // Look through the user's inference rule [a :- b1, ..., bn] and 242 | // compute all the contrapositives of the rule: 243 | // not(b1) :- not(a), b2, ..., bn 244 | // not(b2) :- not(a), b1, b3, ..., bn 245 | // ... 246 | // not(bn) :- not(a), b1, ..., b(n-1) 247 | // For convenience, we also include the original rule. 248 | pub fn generate_contrapositives(heap: &mut Heap, a: &(Atom, Vec), lt: Lifetime) -> Database 249 | { 250 | fn term_to_atom(t: &Term) -> Option { 251 | match *t { 252 | // because we are processing inference rules, this case shouldn't come up. For example, 253 | // this is a parse error: 254 | // p :- X. 255 | Term::Var(_) => None, 256 | Term::Const(ref c) => Some((c.to_owned(),vec![])), 257 | Term::App(ref c, ref ts) => Some((c.to_owned(),ts.to_owned())), 258 | } 259 | } 260 | 261 | let mut ret: Database = once(a.to_owned()).collect(); 262 | 263 | match make_complementary(heap, &a.0, lt) { 264 | None => (), 265 | Some(not_head) => { 266 | for (idx, t) in a.1.iter().enumerate() { 267 | match make_complementary(heap, t, lt) { 268 | None => (), 269 | Some(not_t) => { 270 | if let (Some(not_head), Some(not_t)) = (term_to_atom(¬_head), term_to_atom(¬_t)) { 271 | let mut new_tail: Vec = a.1.to_owned(); 272 | new_tail.remove(idx); 273 | new_tail.insert(0, not_head); 274 | let new_rule = (not_t, new_tail); 275 | ret.push_back(new_rule); 276 | } 277 | } 278 | } 279 | } 280 | } 281 | } 282 | ret 283 | } 284 | 285 | // Compute the complementary term for an atom. We assume the arity of `not` 286 | // is exactly 1 and fail to produce a term if it is used at any other arity. 287 | // 288 | // Note: this also applies double negation elimination (eg., not(not(p)) = p). 289 | // 290 | pub fn make_complementary(heap: &mut Heap, t: &Atom, lt: Lifetime) -> Option> 291 | { 292 | match *t { 293 | // this case bakes in double negation elimnation, so that 294 | // not(p(X1,...,Xn)) => 295 | // not(not(p(X1,...,Xn))) => 296 | // p(X1,...,Xn) 297 | (ref c, ref ts) if **c == NOT => { 298 | // 'not()' should take exactly 1 argument. If not, then 299 | // this code doesn't know how to construct the complement 300 | match ts.len() { 301 | 1 => Some(ts.first().unwrap().to_owned()), 302 | _ => None 303 | } 304 | }, 305 | // the `not` introduction case 306 | (ref c, ref ts) => { 307 | match ts.len() { 308 | 0 => { 309 | let tail = heap.insert_term(Term::Const(c.to_owned()), lt); 310 | let not = heap.insert_string(NOT.to_string(), Lifetime::Perm); 311 | Some(heap.insert_term(Term::App(not, vec![tail]), lt)) 312 | } 313 | _ => { 314 | let tail = heap.insert_term(Term::App(c.to_owned(), ts.to_owned()), lt); 315 | let not = heap.insert_string(NOT.to_string(), Lifetime::Perm); 316 | Some(heap.insert_term(Term::App(not, vec![tail]), lt)) 317 | } 318 | } 319 | } 320 | } 321 | } 322 | 323 | pub fn str_to_nat(heap: &mut Heap, input: & str, lt: Lifetime) -> Gc 324 | { 325 | let value : u64 = input.parse::().unwrap(); 326 | let zero = heap.insert_string("zero".to_string(), Lifetime::Perm); 327 | let succ = heap.insert_string("succ".to_string(), Lifetime::Perm); 328 | let mut t = heap.insert_term(Term::Const(zero), Lifetime::Perm); 329 | for _ in 0 .. value { 330 | t = heap.insert_term(Term::App(succ.clone(), vec![t]), lt); 331 | } 332 | t 333 | } 334 | 335 | pub fn vec_to_list(heap: &mut Heap, elts: Vec>, lt: Lifetime) -> Gc 336 | { 337 | let nil_str = heap.insert_string("nil".to_string(), Lifetime::Perm); 338 | let nil = heap.insert_term(Term::Const(nil_str), Lifetime::Perm); 339 | let cons = heap.insert_string("cons".to_string(), Lifetime::Perm); 340 | let mut t = nil; 341 | for e in elts.iter().rev() { 342 | t = heap.insert_term(Term::App(cons.clone(), vec![e.to_owned(), t]), lt); 343 | } 344 | t 345 | } 346 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub enum ErrorCode { 3 | UnrecognizedToken, 4 | UnterminatedStringLiteral, 5 | ExpectedStringLiteral, 6 | } 7 | 8 | #[derive(Clone, Debug, PartialEq, Eq)] 9 | pub struct Error { 10 | pub location: usize, 11 | pub code: ErrorCode 12 | } 13 | 14 | #[derive(Clone, Debug, PartialEq, Eq)] 15 | pub enum Token<'input> { 16 | VAR(&'input str), 17 | NUMBER(&'input str), 18 | CONST(&'input str), 19 | FROM, 20 | COMMA, 21 | TRUE, 22 | PERIOD, 23 | PIPE, 24 | LPAREN, 25 | RPAREN, 26 | LBRACKET, 27 | RBRACKET, 28 | GOAL, 29 | QUIT, 30 | USE, 31 | STRING(&'input str), 32 | EOF, 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/unify.rs: -------------------------------------------------------------------------------- 1 | use gc::Gc; 2 | 3 | use crate::syntax::{Environment, Term, Atom, subst_term, occurs}; 4 | use crate::heap::{Heap, Lifetime}; 5 | 6 | /* [NoUnify] is used when terms cannot be unified. */ 7 | pub struct NoUnify; 8 | 9 | /* [unify_terms env t1 t2] unifies terms [t1] and [t2] in the current 10 | environment [env]. On success it returns the environment extended with 11 | the result of unification. On failure it raises [NoUnify]. */ 12 | pub fn unify_terms(env:&Environment, heap: &mut Heap, t1: &Term, t2: &Term) 13 | -> Result { 14 | let new_t1 = subst_term(env, heap, t1); 15 | let new_t2 = subst_term(env, heap, t2); 16 | if new_t1 == new_t2 { 17 | Ok(env.clone()) 18 | } else { 19 | match (&*new_t1, &*new_t2) { 20 | (&Term::Var(ref y), t) | 21 | (t, &Term::Var(ref y)) => if occurs(&y,&t) { 22 | Err(NoUnify) 23 | } else { 24 | let mut new_env = env.clone(); 25 | new_env.insert(y.clone(),heap.insert_term(t.clone(), Lifetime::Ephemeral)); 26 | Ok(new_env) 27 | }, 28 | (&Term::App (ref c1, ref ts1), &Term::App (ref c2, ref ts2)) => 29 | if c1 == c2 { 30 | unify_lists(env, heap, &ts1, &ts2) 31 | } else { 32 | Err(NoUnify) 33 | }, 34 | (&Term::Const(_), _) | 35 | (_ , _) => Err(NoUnify) 36 | } 37 | } 38 | } 39 | 40 | /* [unify_lists env lst1 lst2] unifies two lists of terms in current 41 | environment [env] and returns a new environment [env'] on success. It 42 | returns [NoUnify] on failure or if the lists are not equal length. 43 | */ 44 | fn unify_lists(env: &Environment, heap: &mut Heap, lst1: &[Gc], lst2: &[Gc]) 45 | -> Result 46 | { 47 | if lst1.len() != lst2.len() { 48 | Err(NoUnify) 49 | } else { 50 | lst1.iter() 51 | .zip(lst2.iter()) 52 | .fold( Ok(env.clone()), 53 | |ne, (l1, l2)| 54 | match ne { 55 | Ok(new_env) => unify_terms(&new_env, heap, l1, l2), 56 | Err(_) => Err(NoUnify) 57 | }) 58 | } 59 | } 60 | 61 | /* [unify_atoms env a1 a2] unifies atomic propositions [a1] and [a2] 62 | in current environment [env] and returns a new environment [env'] on 63 | success. It raises [NoUnify] on failure. */ 64 | pub fn unify_atoms(env: &Environment, 65 | heap: &mut Heap, 66 | &(ref c1, ref ts1): &Atom, 67 | &(ref c2, ref ts2): &Atom) 68 | -> Result 69 | { 70 | if c1 == c2 { 71 | unify_lists(env, heap, ts1, ts2) 72 | } else { 73 | Err(NoUnify) 74 | } 75 | } 76 | --------------------------------------------------------------------------------