937 Commits

Author SHA1 Message Date
Mark Thom
4e7d9f9010 update README.md
Some checks failed
CI / style (push) Has been cancelled
CI / read-msrv (push) Has been cancelled
CI / build-test (--no-default-features --features=all-simple-cross, miri, true, ubuntu-22.04, nightly, s390x-unknown-linux-gnu, --no-run --no-default-features --features=all-simple-cross) (push) Has been cancelled
CI / build-test (--no-default-features, ubuntu-22.04, true, nightly, wasm32-unknown-unknown, --no-run --no-default-features, true) (push) Has been cancelled
CI / build-test (miri, true, ubuntu-22.04, nightly, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (true, macos-latest, true, stable, x86_64-apple-darwin) (push) Has been cancelled
CI / build-test (true, ubuntu-22.04, true, stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (true, windows-latest, true, stable, x86_64-pc-windows-msvc) (push) Has been cancelled
CI / build-test (ubuntu-22.04, ${{ needs.read-msrv.outputs.msrv }}, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, beta, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, true, stable, i686-unknown-linux-gnu) (push) Has been cancelled
CI / logtalk-test (push) Has been cancelled
CI / report (push) Has been cancelled
CI / release (push) Has been cancelled
Docker Publish / build (push) Has been cancelled
2026-07-21 23:51:19 -07:00
Mark Thom
d3edffb7fc update README.md 2026-07-21 23:48:56 -07:00
Mark Thom
295a642034 Merge pull request #3372 from triska/global_cardinality
FIXED: Correct global_cardinality/2 propagation with reified constraints.
2026-06-30 17:25:30 -07:00
Mark Thom
ae0999a3bb Merge pull request #3398 from Skgland/fix-3320
fix incorrect missing feature errors
2026-06-30 17:25:02 -07:00
Mark Thom
11fd39bbaf Merge pull request #3275 from abmclin/Issue-3223-migrate-to-Rust-edition-2024
Issue 3223 migrate to rust edition 2024
2026-06-30 17:22:27 -07:00
Skgland
eb06f19c32 fix incorrect missing feature errors
this was accidentally introduced in mthom/scryer-prolog#3320

fixes mthom/scryer-prolog#3397
2026-06-29 20:13:10 +02:00
Alexander McLin
ea88ca552d Issue 3223: reformat via cargo fmt 2026-06-16 21:18:49 -04:00
Alexander McLin
eabbf67e86 Issue 3223: add more unsafe scopes 2026-06-16 21:14:43 -04:00
Alexander McLin
7a636b0394 Issue 3223: trying again repair for the failing wasm32 build
Removed `mut` modifier completely.
2026-06-16 21:05:12 -04:00
Alexander McLin
71ea74a077 Issue 3223: repair for the failing wasm32 build
Removed `ref` modifier due to not being allowed when implicitly borrowing.
2026-06-16 21:05:12 -04:00
Alexander McLin
091af28368 Issue 3223: reformat via cargo fmt 2026-06-16 21:05:12 -04:00
Alexander McLin
fbc5345c9a Issue 3223: more unsafe scope refinements 2026-06-16 21:05:12 -04:00
Alexander McLin
9d49415449 Issue 3223: reformat via cargo fmt 2026-06-16 21:05:12 -04:00
Alexander McLin
83218bf0da Issue 3223: make unsafe scopes tighter 2026-06-16 21:05:12 -04:00
Alexander McLin
fcd6c3f127 Issue 3223: Second phase of migration to Rust Edition
Reformat via `cargo fmt`
2026-06-16 21:05:12 -04:00
Alexander McLin
efbddeaeee Issue 3223: First phase of migration to Rust Edition 2024
Update cargo dependencies
Apply cargo fix --edition
Change cargo.toml edition property to `2024`

^ Conflicts:
^	Cargo.lock
^	src/ffi.rs

^ Conflicts:
^	src/offset_table.rs
^	src/raw_block.rs
2026-06-16 20:37:52 -04:00
Mark Thom
daaab1cb37 Merge pull request #3331 from adri326/rawblock-safety
Prove safety of RawBlock and support multi-threaded usages
2026-06-11 00:38:04 -07:00
Markus Triska
9b73536cdd FIXED: Correct global_cardinality/2 propagation with reified constraints.
This issue was found by @david-sitsky in a Prolog formulation of an
interesting scheduling task:

    https://github.com/mthom/scryer-prolog/discussions/3341

See also the filed issue:

    https://github.com/mthom/scryer-prolog/issues/3369

Thank you a lot!

This issue needs more analysis to find out what exactly happened here,
whether other cases like it exist, and whether it can be addressed in
a better way. I would greatly appreciate any help!
2026-06-10 20:31:20 +02:00
Mark Thom
e12d3f14c1 Merge pull request #3363 from Skgland/adjust-discussion-3359-test
adjust regression test for discussion 3359
2026-06-05 13:55:21 -07:00
Mark Thom
ffb77473b2 Merge pull request #3364 from Skgland/finish-msrv-bump-cleanup
finish msrv bump cleanup
2026-06-05 13:54:49 -07:00
Skgland
49c0cd3733 remove unecessary use of LayzLock
AtomicBool::new const
2026-06-05 20:47:20 +02:00
Skgland
a236d00d7d fix lint warnings 2026-06-05 20:45:18 +02:00
Skgland
bb5a59170a cleanup after msrv bump 2026-06-05 20:44:38 +02:00
Skgland
2b757de73a adjust regression test for discussion 3359
- incorporate fix from 3a2d57db33 (commitcomment-187528793)
- mark as should panic rather than ignored, this ensures that the test will be updated should this get fixed
2026-06-05 20:29:50 +02:00
Mark Thom
03f796e9ff Merge pull request #3299 from danilp-id/interrupt_test
Test: SIGINT should be able to interrupt non-terminating goals on linux
2026-06-04 13:11:08 -07:00
Danil Platonov
d8be99b6ad fix merge 2026-06-04 12:25:54 -07:00
Danil Platonov
2a04025419 Merge branch 'master' into interrupt_test 2026-06-04 12:08:36 -07:00
Mark Thom
bc14e3aedd Merge pull request #3362 from danilp-id/socket_interrupt
Handle interrupts for socket_server_accept/4
2026-06-04 10:56:09 -07:00
Danil Platonov
bcb9dd0860 handle interrupts for tcp server 2026-06-04 02:41:11 -07:00
Mark Thom
18dd939159 Merge pull request #3352 from danilp-id/http_interrupt
Interrupts support for http server + other improvements
2026-06-03 13:36:21 -07:00
Mark Thom
1cbc95bd06 Merge pull request #3360 from Skgland/fix-discussion-3359
Fix discussion 3359
2026-06-03 13:35:56 -07:00
Skgland
d1e8ea4dd5 ignore added test on i686-unknown-linux-gnu
appears to have been broken even before d50d425099
so has a different cause that needs to be investigated
2026-06-03 22:05:20 +02:00
Skgland
068959bd07 fix discussion 3359 2026-06-03 22:05:20 +02:00
Skgland
3a2d57db33 add regression test for discussion 3359 2026-06-03 22:05:20 +02:00
Mark Thom
3ce16656ee Merge pull request #3357 from Skgland/fix-issue3346
ensure ParallelHeapIter pushes its stack in pairs and fix 3346
2026-06-02 22:19:21 -07:00
Danil Platonov
732f3ae10a use Notify instead of Sender 2026-06-02 00:49:00 -07:00
Skgland
a4993284cd fix parallel heap iter 2026-06-01 23:59:37 +02:00
Skgland
8aeb326e5c use a pair for ParallelHeapIter
to ensure it is pushed popped in pairs
2026-06-01 23:58:17 +02:00
Danil Platonov
304cf424ed unused code for unimplemented shutdown on timeout feature 2026-05-30 15:59:16 -07:00
Danil Platonov
3cd40419eb cargo fmt 2026-05-30 15:56:18 -07:00
Danil Platonov
f8f7b3b7e2 allow to stop and start http server at will with SIGINT, stop the server on uncaught throws, display timing info for each request, use setup_call_cleanup in more places to avoid potential leaks, when handler for a path doesn't have a matching case, throw a detailed error instead of silently continuing 2026-05-30 15:47:50 -07:00
Danil Platonov
39c850b4f4 Test that SIGINT interrupts non-terminating goals on unix.
Cleanup: use tokio::test instead of special test helpers

Added pty_exec.py helper script to run binaries with a pseudoterminal attached (bug won't trigger otherwise).

Tested CI to fail on all unixes (ubuntu+macos) with older rustyline, and pass with new one.
2026-05-30 11:45:57 -07:00
Mark Thom
8b6d68a4cd Merge pull request #3347 from drzv/im7-compat2
showterm.el: use new magick command by default, fallback to convert
2026-05-28 16:34:52 -07:00
Mark Thom
149d3932be Merge pull request #3272 from abmclin/windows_improve_notty_stdin_support
Windows improve notty stdin support to resolve issue 3264
2026-05-26 14:01:23 -06:00
drzv
36cbeb3d6c showterm.el: use new magick command by default, fallback to convert 2026-05-26 11:22:17 -04:00
drzv
875ae5a773 showterm.el: revert #3338 2026-05-26 08:10:24 -04:00
Alexander McLin
70d046499a Issue 3264: rebase and update dependencies 2026-05-25 18:24:24 -04:00
Alexander McLin
b6db1cde25 Issue 3264 add rationale comment & #[allow(...)]
Explain why no multibyte UTF-8 encoding support
Disable `bytes()` clippy warning about performance penalty due to unbuffered bytes
2026-05-25 18:13:02 -04:00
Alexander McLin
257209df75 Issue 3264: refactor supported_terminal
Use better code style
2026-05-25 18:13:02 -04:00
Alexander McLin
d5d514ec96 Issue 3264: reformat to conform to style guidelines 2026-05-25 18:13:01 -04:00
Alexander McLin
533594b6b6 Fixes issue 3264 by adding a fallback read mode when reading from non-tty stdin on Windows 2026-05-25 18:13:01 -04:00
Mark Thom
43b6afcdf3 Merge pull request #3342 from Skgland/pdl-pairs
ensure pdl is pushed/popped in pairs and reuse tabu_list allocation by moving it from a local variable into the machine state
2026-05-25 15:10:02 -06:00
Mark Thom
2c23f84808 Merge pull request #3310 from Skgland/process_wait-release
add `release(Bool)` option to `process_wait/3`
2026-05-25 15:08:37 -06:00
Mark Thom
ad13aa3801 Merge pull request #3320 from Skgland/all-pure-rust
add a feature for enabling all features that don't pull in non pure rust dependencies
2026-05-25 14:36:04 -06:00
Mark Thom
79597b63bf Merge pull request #3317 from Skgland/optimize-put_back_char
optimize put_back_char
2026-05-25 14:03:31 -06:00
Skgland
bdba7ba0f5 merge crypto-impure feature into feature crypto-full 2026-05-25 19:07:20 +02:00
Skgland
6c7d5e8278 more spelling fixes 2026-05-25 16:39:37 +02:00
Skgland
61ebcdb56e fix spelling 2026-05-24 23:55:19 +02:00
Skgland
f2195c2362 cleanup
- fix some warnings and mark others as expected
- make use of the msrv bump to 1.93.1 and cleanup compat and workarounds
2026-05-24 23:29:23 +02:00
Skgland
6150ca90d3 check individual features in CI 2026-05-24 23:00:27 +02:00
Skgland
938f588769 make crypto depending on ring optional 2026-05-24 23:00:27 +02:00
Mark Thom
61d94dceae Merge pull request #3290 from abmclin/bump-rustyline-version-18
bump `Rust` version to 1.93.1 and bump `rustyline` to version 18.0.0
2026-05-24 13:27:00 -06:00
Mark Thom
1411417a44 Merge pull request #3338 from drzv/master
Use make-process to call imagemagick in showterm.el
2026-05-24 13:22:33 -06:00
Mark Thom
609514b4b8 Merge pull request #3312 from Skgland/big-endian-miri
run miri for a big-endian target
2026-05-24 13:22:00 -06:00
Skgland
1fd9246b16 configure linker via env var insread of .cargo/config.toml
someone might already have a .cargo/config.toml
2026-05-24 19:08:23 +02:00
Skgland
19974e580b fix missing description
Github documentation states that it is a required field and my linter complained

https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#description
2026-05-24 19:02:23 +02:00
Skgland
550efb8416 add a feature for things that are simple to cross-compile 2026-05-24 10:39:20 +02:00
Skgland
e0c491e9f3 fix miri not being passed the target 2026-05-24 10:39:20 +02:00
Skgland
f16850e5f8 fix ci and setup cross-compilation 2026-05-24 10:39:20 +02:00
Skgland
ca0d4e9aac add miri CI for a big endian target 2026-05-24 10:39:20 +02:00
Skgland
6e50268efb fix spelling again 2026-05-24 10:30:30 +02:00
Skgland
54b3034905 remove out-dated comment and fix spelling/grammar 2026-05-24 10:25:24 +02:00
Mark Thom
cf9e667b50 Merge pull request #3311 from Skgland/cleanup2
some cleanup and fixes
2026-05-23 18:27:50 -06:00
Mark Thom
3f0b471227 Merge pull request #3333 from 0nkery/lazy-lock
replace lazy_static macro with std::sync::LazyLock
2026-05-23 09:51:22 -06:00
Mark Thom
bfca60aa86 Merge pull request #3345 from triska/meetups
link to Scryer Prolog Meetup 2026 page and its announcement
2026-05-23 09:51:06 -06:00
Skgland
6b9a291f4e fix spelling 2026-05-23 11:28:45 +02:00
Markus Triska
777637001f link to Scryer Prolog Meetup 2026 page and its announcement 2026-05-23 10:17:11 +02:00
Mark Thom
9fcd7776a9 Merge pull request #3336 from 0nkery/fix-between-doc
fix var mode in between/3 documentation
2026-05-22 23:04:05 -06:00
Mark Thom
4f166f50a8 Merge pull request #3334 from Skgland/stable-order
use BTree{Map,Set} to ensure a stable order based on the key/entry
2026-05-22 22:56:13 -06:00
Mark Thom
1547839c33 Merge pull request #3328 from Skgland/update-libffi
upgrade libffi dependency
2026-05-22 22:53:49 -06:00
Mark Thom
5a1423d0f0 Merge pull request #3316 from josd/master
Improve CharReader buffering
2026-05-22 22:52:27 -06:00
Mark Thom
3faa838ee2 Merge pull request #3323 from triska/VATmiraal
add link to VATmiraal, https://vatmiraal.be
2026-05-22 22:45:39 -06:00
Skgland
e4792429d3 reuse tabu list 2026-05-17 20:50:37 +02:00
Skgland
d50d425099 ensure pdl is pushed/popped in pairs 2026-05-17 19:48:52 +02:00
drzv
0df5f7e576 Call ImageMagick with make-process. Redirect stderr to separate buffer 2026-05-14 19:10:05 +00:00
Dmitrii Shlagov
b9f1443eb0 fix var mode in between/3 documentation 2026-05-14 16:25:33 +02:00
Skgland
ac18aed5a7 use BTree{Map,Set} to ensure a stable order based on the key/entry even if filesystem enumeration order changes
this should help with reproducibility
2026-05-14 01:02:19 +02:00
Dmitrii Shlagov
2c60e4183f roll back gitignore changes 2026-05-12 17:15:28 +02:00
Dmitry Shlagoff
2bbb6487ec remove redundant closure 2026-05-12 16:12:33 +02:00
Dmitry Shlagoff
095fa1227a replace lazy_static macro with std::sync::LazyLock 2026-05-12 15:40:38 +02:00
Emilie Burgun
f395d554a5 RawBlock: finish proof of safety and defragment after growing
After calling `grow()`, the new head would jump to `old_capacity` rather
than staying to the same offset.

In practice this only loses a few bytes at most.
2026-05-10 22:36:11 +02:00
Emilie Burgun
ad614b684c RawBlock: switch to Cell or AtomicPtr instead of UnsafeCell
This is the first step towards enabling multithreading on AtomTable.

For now RawBlock will default to using Cell, which yields a byte-
equivalent compiled output.

Also adds an `atomic` feature, which, when enabled, will make RawBlock
use an AtomicPtr instead, ensuring that it implements `Sync`.
2026-05-10 22:36:11 +02:00
Emilie Burgun
898b6b2b25 RawBlock: seal base and add Stack::index_dangling_or_frame
Direct accesses to `base` are replaced with dedicated methods with
explicit safety requirements.
2026-05-10 22:36:11 +02:00
Emilie Burgun
3c5818a040 RawBlock: seal most fields, replace top with a capacity field 2026-05-10 22:24:57 +02:00
Skgland
0c313a0849 upgrade libffi dependency 2026-05-08 22:36:41 +02:00
Alexander McLin
7c9075b4ee Bump Cargo.toml rust-version to 1.93.1
MSRV discussion settled on `1.93.1` to align with upcoming Debian stable releases
2026-05-05 09:58:05 -04:00
Markus Triska
4c14cbe39a explanation of product
See posting by @constraintAutomaton at:

    https://github.com/mthom/scryer-prolog/pull/3323#issuecomment-4365798844

Many thanks!
2026-05-03 11:00:38 +02:00
Markus Triska
afe744aa57 add link to VATmiraal, https://vatmiraal.be 2026-05-03 09:52:28 +02:00
Skgland
89dde3f370 un-ignore lorem_ipsum tests as they are no longer slow 2026-04-30 19:54:39 +02:00
Skgland
f8ddd78776 optimize CharReader 2026-04-30 19:46:40 +02:00
Skgland
327a677a7c use a function rather than a closure 2026-04-30 19:02:27 +02:00
Skgland
48a34aa2cc add a test with bad utf-8 2026-04-30 19:01:34 +02:00
Skgland
af54fb2aba optimize put_back_char
Always encode the char directly into the buffer.
Only shift the buffer content if there isn't enough room in the front.
2026-04-26 14:13:55 +02:00
josd
b2dad66270 Improve CharReader buffering 2026-04-26 13:42:41 +02:00
Skgland
eac7ff680c fix ArenaPtr payload offset logic
the old logic would be incorrect if the payload has higher alignment than the ArenaHeader i.e. when there is padding between the ArenaHeader and the Payload
2026-04-25 18:12:32 +02:00
Skgland
b4db85c8c3 don't erase ptr type early when construction a Cons HeapCellValue
rather than passing an address as usize pass the ArenaHeader pointer
similarly don't return a u8 ptr but use a ArenaHeader pointer instead

Don't convert the pointer to a ConsPtr by going through native endian
bytes in between.
We are exploiting the fact that the 3 least significant bytes are zero
for pointer to types of alignment 8 and we expect these to line up with
the f, m, and tag field at the end of the ConsPtr struct, but using
native endiannes for this would only work on big endian systems.
2026-04-25 18:12:31 +02:00
Skgland
6765921666 fix unused import warning on windows 2026-04-25 16:19:58 +02:00
Skgland
ff293a56e6 fix large enum variant size difference warning of PermVarAllocation
by wrapping BranchNumber in an Arc.
PermVarAllocation::Done had size 208 and is now down to 32.
A Box rather than an Arc would be smaller, but it looks like BranchNumber/BranchDesignator are clones a bunch so I expect it to be beneficial to reduce allocations both of the Box itself as well as its content.
2026-04-25 16:19:53 +02:00
Skgland
ab2b652c84 fix linux tests on 1.85
old rust versions didn't print the pid
2026-04-25 15:19:41 +02:00
Skgland
83add732e0 adjust test expectations 2026-04-25 14:43:52 +02:00
Skgland
f8c4679850 add process_wait/3 release(Bool) option
make process_wait/3 release the Process by default
2026-04-25 14:35:20 +02:00
Skgland
3da2b8fba2 add tests 2026-04-25 14:35:07 +02:00
Skgland
f7dc1d72f6 remove unused HeapCellValueView
it is identical to HeapCellValueTag
2026-04-22 22:16:36 +02:00
Mark Thom
8dffd72db5 replace tabs with spaces 2026-04-21 16:22:38 -07:00
Mark Thom
703a6dddd1 dereference compare_term_test args down from stack (#3286, #3295) 2026-04-21 16:02:55 -07:00
Mark Thom
5ea262e1d3 replace tabs with spaces 2026-04-21 15:02:14 -07:00
Mark Thom
010b00f349 take Lis index from rd in copy_var (#3298) 2026-04-21 14:31:53 -07:00
Mark Thom
974722bc1c Merge pull request #3269 from no382001/quads
fix library paths and module qualification in quadtests.pl
2026-04-18 18:20:03 -06:00
Mark Thom
695389bda1 Merge pull request #3293 from danilp-id/http_server_test
http_server test + unignore http_open_hanging
2026-04-18 18:17:18 -06:00
Mark Thom
9cf0d8b484 Merge pull request #3274 from no382001/issue3262
fix read/1 on non-TTY stdin blocking until newline
2026-04-18 18:16:53 -06:00
Mark Thom
381b4f2857 Merge pull request #3291 from Skgland/update-actions
Update actions
2026-04-18 18:16:00 -06:00
Danil Platonov
35aa4d9048 cargo fmt 2026-04-12 19:32:40 -07:00
Danil Platonov
8e2dcd905b pass scryer path through stream instead of env var 2026-04-12 19:29:39 -07:00
Danil Platonov
081f3c0827 http server test, and also unignore http client test 2026-04-11 08:59:01 -07:00
Skgland
8bbb484f94 update pyTooling/Actions/with-post-step@v4.1.0 -> pyTooling/Actions/with-post-step@v7.7.0 2026-04-11 00:06:13 +02:00
Skgland
717c3c2db8 update actions/cache@v4.3.0 -> actions/cache@v5.0.4 2026-04-10 23:59:59 +02:00
Skgland
0aa6abe517 pin 3rd-party actions
following githubs recommendations[^3rd-p-a] to use full-length commit SHA for 3rd-party actions

setup dependabot to help keep dependencies up-to-date

[^3rd-p-a]: https://docs.github.com/en/actions/reference/security/secure-use#using-third-party-actions
2026-04-10 23:52:43 +02:00
Skgland
4a9f690c16 update docker/build-push-action@v6 -> docker/build-push-action@v7 2026-04-10 23:22:38 +02:00
Skgland
e3d020ea74 update docker/metadata-action@v5 -> docker/metadata-action@v6 2026-04-10 23:22:38 +02:00
Skgland
82378c6338 update docker/login-action@v3 -> docker/login-action@v4 2026-04-10 23:22:38 +02:00
Skgland
f456c98c5b update docker/setup-buildx-action@v3 -> docker/setup-buildx-action@v4 2026-04-10 23:22:37 +02:00
Skgland
4db325e4b8 update actions/download-artifact@v4 -> actions/download-artifact@v8 2026-04-10 23:22:37 +02:00
Skgland
8f33eaf7d1 update actions/upload-artifact@v4 -> actions/upload-artifact@v7 2026-04-10 23:22:37 +02:00
Skgland
eba16a2213 update actions/checkout@v4 -> actions/checkout@v6 2026-04-10 23:22:37 +02:00
Mark Thom
87f97c7e5c Merge pull request #3276 from Skgland/cleanup
remove unused file
2026-04-08 22:18:32 -06:00
Alexander McLin
2fec440ba6 bump rustyline to version 18.0.0
Fixes regression discovered on Linux after upgrading `rustyline` to 17.0.2; `SIGINT` not handled as expected, breaking ability to interrupt nontermination in `scryer`.

This was due to a change in `rustyline`'s `SIGINT` behavior since version 16 and corrected in version 18.
2026-04-05 20:43:38 -04:00
Mark Thom
3e2f3ecfdb Merge pull request #3273 from no382001/issue2914
fix panic in current_prolog_flag/2 when arguments are the same variable
2026-04-04 13:06:11 -06:00
Mark Thom
2b6b11e6be Merge pull request #3271 from abmclin/update_rustyline
Resolves issue 2694 by updating rustyline to 17.0.2
2026-04-04 12:28:35 -06:00
Mark Thom
7da2ba2015 Merge pull request #3266 from Skgland/perf-issue-3265
perf improvements for 3265
2026-04-04 12:02:29 -06:00
Skgland
bb9c1cf5a2 remove unused file 2026-04-03 00:16:59 +02:00
no382001
28f8da5ce9 restructure based on suggestion
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-31 21:14:54 +02:00
no382001
c065b52778 fix read/1 on non-TTY stdin blocking until newline #3262 2026-03-31 15:52:27 +02:00
no382001
6a10c6d6a5 fix panic in current_prolog_flag/2 when arguments are the same variable 2026-03-31 12:01:00 +02:00
Alexander McLin
0623e8e7df Resolves issue 2694 by updating rustyline to 17.0.2 2026-03-29 14:49:51 -04:00
Skgland
3c9c7ade8d use checked_add to prevent silent overflow 2026-03-27 22:00:54 +01:00
no382001
13c85c74db fix library paths and module qualification in quadtests.pl 2026-03-27 21:27:15 +01:00
Skgland
5fc21241d6 hint to the compiler/branch-predictor that
we expect p to be in bound

if we are suffiently certain that p can never be oob
unsafe get_unchecked could be used to
eliminate bounds checking all together
which should improve performance further.
2026-03-21 19:01:52 +01:00
Skgland
833c3bd77f run rustfmt 2026-03-21 14:24:13 +01:00
Skgland
153f04b72a adjust interrupt handling
we used to spend ~5.8% of the time on getting the next value of the 0..INSTRUCTIONS_PER_INTERRUPT_POLL iterator

increment on Wrapping<u8> now only takes ~0.58 of the time
2026-03-21 14:20:11 +01:00
Skgland
8aab887388 switch from Integer to u128
- u128 is only 16 bytes instead of 24
- incrementing u128 does not involve heap allocations
- u128 should be sufficent
 it would take more than 2 sextilion years to overflow if we would be incrementing it every tick at 5GHz
   i.e. 2^128-1 / 5 GHz / 60 / 60 / 24 / 356 > 2 sextilion

before this ~3.2% of the execution time of the program in https://github.com/mthom/scryer-prolog/issues/3265#issuecomment-4103176469 was spend in the increment_call_count function, after this change it's down to 0.3%
2026-03-21 13:36:40 +01:00
Mark Thom
79a9b950cb Merge pull request #3243 from danilp-id/master
fix http stream not being closed fully
2026-03-19 23:36:58 -07:00
Mark Thom
fba2854f21 Merge pull request #3257 from triska/include
ISO: Implement the include/1 directive
2026-03-17 23:58:04 -07:00
Markus Triska
1c9fd1501a retain error context (load/1) when using :- D. 2026-03-16 07:36:55 +01:00
Mark Thom
b0b89a0aca Merge pull request #3258 from no382001/fix_load_xml/3
load_xml was returning a single term instead of a list of nodes
2026-03-14 22:46:03 -07:00
no382001
025da8315b load_xml was returning a single term instead of a list of nodes 2026-03-10 12:20:34 +01:00
Mark Thom
8002b9a78b Merge pull request #3234 from fusiongyro/patch-1
Fixed a typo in the documentation for atom_codes/2
2026-03-09 22:09:40 -07:00
Mark Thom
02f86587b5 Merge pull request #3232 from triska/not_si
ADDED: not_si/1
2026-03-09 22:09:09 -07:00
Mark Thom
4d144ea819 Merge pull request #3252 from Skgland/towards-a-crate-without-syn@1
replace custom modular_bitfields fork with update upstream
2026-03-09 22:08:28 -07:00
Mark Thom
02c927bb83 Merge pull request #3241 from Skgland/parse-error-improvements
change `ParserError` type
2026-03-09 22:07:03 -07:00
Mark Thom
4a8388a6e9 Merge pull request #3189 from Skgland/fix-function_casts_as_integer
fix `function_casts_as_integer` warning
2026-03-09 22:06:33 -07:00
Mark Thom
7bf014cdf5 Merge pull request #3260 from thierrymarianne/dealing-with-invalid-utf8-string
handle invalid UTF-8 string
2026-03-09 20:56:04 -07:00
Markus Triska
b41563e976 ENHANCED: support quads in included files
Suggested by @dcnorris, many thanks!

    https://github.com/mthom/scryer-prolog/issues/634#issuecomment-4003451420
2026-03-07 09:37:30 +01:00
Thierry Marianne
ead2fccdbb add documentation
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-05 09:03:31 +01:00
Thierry Marianne
817c584993 revise indentation of raw string
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 15:06:27 +01:00
Thierry M.
e00112b92f use C-string literal
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-04 15:01:23 +01:00
Thierry Marianne
4bd0ff0fcb cargo fmt fixes
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 14:04:47 +01:00
Thierry Marianne
ed59aa05b0 apply suggestion
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 14:01:17 +01:00
Thierry Marianne
16ed1040d2 move test to tests/scryer/ffi.rs
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 14:01:17 +01:00
Thierry M.
fa21ff4813 use CStr .to_str() method
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-04 14:00:44 +01:00
Thierry M.
891f7fc892 use C-string literal
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-04 13:59:53 +01:00
Thierry Marianne
fae9ca1bb9 fix utf-8 panic
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 12:45:58 +01:00
Thierry Marianne
ac71cff338 add failing test highlighting panic on invalid UTF-8 cstr
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 12:45:48 +01:00
Markus Triska
8f39bee463 ISO: Implement the include/1 directive
Quoting from the standard:

    7.4.2.7 include/1

    If F is an implementation defined ground term designating
    a Prolog text unit, then Prolog text P1 which contains
    a directive include(F) is identical to a Prolog text P2
    obtained by replacing the directive include(F) in P1 by
    the Prolog text denoted by F.

Example:

    :- include("hello.pl").

This addresses #583 and #634.
2026-02-28 14:49:13 +01:00
Skgland
2dc904aa73 run apt update before apt install
https://github.com/orgs/community/discussions/145882#discussioncomment-11447924
2026-02-20 23:00:45 +01:00
Skgland
0ceab41b39 fix warning in CI about using apt in a script 2026-02-20 22:57:44 +01:00
Skgland
2a79d6fc40 run cargo fmt 2026-02-20 22:52:25 +01:00
Skgland
49e49b425d fix clippy lint warning 2026-02-20 22:47:08 +01:00
Skgland
7d0d61d789 remove no longer necessary allows 2026-02-20 22:45:09 +01:00
Skgland
5ef5a3f30a replace custom modular_bitfields fork with update upstream
part of #2468
2026-02-20 22:40:33 +01:00
Danil Platonov
337d9c5583 move drop to impl Drop 2026-01-26 04:57:28 -08:00
Danil Platonov
26523139a8 fix http stream not being closed fully 2026-01-25 03:16:37 -08:00
Skgland
65aadb3b88 reduce visibility to pub(crate) to prevent accidentally exposing 2026-01-24 04:20:13 +01:00
Skgland
dfad71bc6e fix some lint warnings 2026-01-24 04:13:26 +01:00
Skgland
d8c6fa1fe5 don't expose lexical error and provide location 2026-01-24 04:08:41 +01:00
Skgland
18b476af28 make ParserError a struct with an enum kind field 2026-01-24 04:08:40 +01:00
Skgland
bd233fedfd add location to InvalidSingleQuotedCharacter 2026-01-24 04:08:40 +01:00
Skgland
083546442d make match exhaustive 2026-01-24 04:08:40 +01:00
Skgland
3343188756 replace pairs of usize with location struct 2026-01-24 04:08:40 +01:00
Daniel K Lyons
ed36aa7313 Fixed a typo in the documentation for atom_codes/2 2026-01-20 09:54:18 -07:00
Markus Triska
0dc94ecc0b ADDED: not_si/1
For a recent example that would benefit from this, see "Prolog Basics
Explained with Pokémon" by @alexpetros:

    https://unplannedobsolescence.com/blog/prolog-basics-pokemon/

Thank you a lot!
2026-01-18 09:58:31 +01:00
Mark Thom
453a88f03e Merge pull request #3211 from mthom/setof_correction_and_opt
Correct and optimize setof/3 and bagof/3
2026-01-14 22:07:10 -07:00
Mark Thom
c79fd74331 remove unnecessary extra work in findall_with_existential/5 2026-01-14 20:39:48 -08:00
Mark Thom
e446b13376 remove hashbrown crate 2026-01-14 20:39:48 -08:00
Mark Thom
a83f412251 replace sort_without_dedup/2 with keysort/2 2026-01-14 20:39:48 -08:00
Mark Thom
1a8c4f9b03 cargo fmt fixes 2026-01-14 20:39:48 -08:00
Mark Thom
c2e1ded852 remove variant_hashing.rs and related instructions 2026-01-14 20:39:48 -08:00
Mark Thom
70220687f4 find variant terms using just sort/2 and (==)/2 2026-01-14 20:39:48 -08:00
Mark Thom
69a367d1c9 do not retain attributes in solutions of findall (#3020) 2026-01-14 20:39:48 -08:00
Mark Thom
e2bdf59c81 fix cargo fmt 2026-01-14 20:39:48 -08:00
Mark Thom
6284aa3a3f add variant_hash and is_non_variant to fix setof/3, bagof/3 2026-01-14 20:39:48 -08:00
Mark Thom
29cd80510b replace compare_term_test with parallel iterator, add is_not_variant 2026-01-14 20:39:45 -08:00
Mark Thom
9089f9ddb4 use branch numbers to detect branch subsumption 2026-01-14 20:34:06 -08:00
Mark Thom
16dc10ee96 assert rational(3) as true in tests/builtins.pl 2026-01-14 20:34:06 -08:00
Mark Thom
4e8f338870 Merge pull request #3222 from Skgland/reduce-instructions_template
move static parts of instructions template directly into instructions module, fix clippy and typos
2026-01-12 23:22:44 -07:00
Skgland
b14e363d5e run rustfmt to fix formatting 2026-01-10 18:31:40 +01:00
Bennet Bleßmann
87f89545bb restore accidentally removed comment 2026-01-10 18:30:09 +01:00
Skgland
5064760b1e fix spelling 2026-01-10 18:13:59 +01:00
Skgland
eca4262be6 fix clippy lints 2026-01-10 17:52:34 +01:00
Skgland
73c26bed2a extaract the static part of the instructions template directly into the instructions module
This way goto source doesn't end up in a generated file for those parts and they can be edited directly.
I have way too often accidentally edited the generated file.
2026-01-10 17:48:07 +01:00
Mark Thom
172b6a6f8f Merge pull request #3207 from Skgland/quote-fix-unquote-segv
"fix" segv from #3198, #3199, and #3205
2025-12-17 14:11:21 -07:00
Skgland
129cca53ed throw error(resource_error(memory), []) when the list length exceeds usize. 2025-12-10 00:04:24 +01:00
Mark Thom
cd1ed106ed Merge pull request #3177 from triska/use_phrase_to_file
use more recently available phrase_[to|from]_file/3 in example
2025-12-08 19:23:52 -07:00
Skgland
00aba96185 remove overengineered RecursionGuard 2025-12-08 22:27:24 +01:00
Skgland
616f071cd9 fix another integer overflow 2025-12-08 19:59:43 +01:00
Skgland
580572aec6 don't calculate panic message eagerly 2025-12-07 23:06:09 +01:00
Skgland
47e908bf76 handle overflown in Heap::with_cell_capacity 2025-12-07 22:59:00 +01:00
Skgland
11901b5fde handle oob heap index calculation 2025-12-07 21:45:13 +01:00
Skgland
d63b0a192c use checked multiplication in heap_index! macro 2025-12-07 21:31:24 +01:00
Skgland
2fc08dde1e prevent multiply with overflow resulting in odd errors/segv 2025-12-07 21:27:24 +01:00
Skgland
193cfabc66 detect recursive throw_resource_error and panic instead of causing a segmentation fault 2025-12-07 21:08:17 +01:00
Skgland
7c3ffd0e87 deny function_casts_as_integer warnings 2025-12-04 23:35:35 +01:00
Skgland
797a8f8611 fix function_casts_as_integer warning
Comparing addresses of function pointers is brittle.

Functions may be duplicated resulting in function pointers to the same function to compare !=.
Functions may be merged/de-duplicated resulting in function pointers to different function to compare ==.

The later shouldn't be relevant here as the function differ in behavior, but mentioning it for completeness.
2025-12-04 23:35:23 +01:00
Mark Thom
f570e566f8 fix backtrack_on_resource_error around choice instructions 2025-12-03 18:13:00 -08:00
Mark Thom
96a9dfcc1b Merge pull request #3185 from mthom/install_verify_attr_opt
Optimize `verify_attr` by removing the need to scan instructions
2025-12-03 19:10:46 -07:00
Mark Thom
6fdcd17f32 Merge pull request #3164 from Skgland/alloc_errors2
handle machine heap/stack allocation error
2025-12-03 19:10:22 -07:00
Mark Thom
652e824e59 Merge pull request #3147 from jjtolton/error-termination-flag
Add -t flag for custom toplevel (replaces --halt-on-error)
2025-12-02 00:24:46 -07:00
Mark Thom
f595eaee2e Merge pull request #3179 from triska/meetups
update meetups
2025-12-01 23:55:46 -07:00
Mark Thom
9ddeeed683 Merge pull request #3180 from triska/foldl_6
ADDED: foldl/6, addressing #2973
2025-12-01 23:55:34 -07:00
Mark Thom
b287f0aedb corrections to arithmetic expression compilation 2025-11-27 21:03:56 -08:00
Mark Thom
3b019fca1d corrections to dispatch loop 2025-11-27 21:03:22 -08:00
Mark Thom
4bd16b8a2e revise InstallVerifyAttrs to remove need for predicate scanning (#3175) 2025-11-25 23:17:51 -08:00
Mark Thom
f2e044e5b8 remove interms field from MachineState 2025-11-24 22:19:36 -08:00
Markus Triska
1cd55d8131 ADDED: foldl/6, addressing #2973 2025-11-23 11:13:57 +01:00
Markus Triska
14265118f1 update meetups 2025-11-23 10:29:31 +01:00
Markus Triska
b66824ffce use more recently available phrase_[to|from]_file/3 in example 2025-11-23 09:42:14 +01:00
Skgland
6ec8c9064f fix pstr_iter_tests test 2025-11-20 23:08:46 +01:00
Skgland
d4f2f7ba2b remove RESOURCE_ERROR_OFFSET_INIT
With it when using multiple Machine in one process only the first would store the pre-allocated error.
Instead Heap.resource_err_loc is now Option<NonZero<usize>> instead of usize using None for uninitialized.
The cell at index 0 should alredy be used by a runtime reserved interstitial cell that is allocated prior. So requiring the offset to be non zero should be fine.
2025-11-20 21:44:29 +01:00
Skgland
a05dc79505 fix unecessary parens in macro 2025-11-20 21:24:47 +01:00
Skgland
e90c813528 return an AllocError instead of panicing in Heap::with_cell_capacity 2025-11-20 21:13:17 +01:00
Skgland
516848e214 wrap resource_error into an error/2 functor and don't wrap it into a syntax_error functor 2025-11-20 01:22:18 +01:00
Skgland
6063783e9d propagate AllocErrro instead of unwrapping 2025-11-20 00:19:24 +01:00
Skgland
e9e97fe82a handle machine heap/stack allocation error 2025-11-19 22:17:24 +01:00
Skgland
5686b69013 stub a clippy config
with things we need to get rid of replace if we want to eliminate panics due to oom
2025-11-19 19:23:42 +01:00
Skgland
248b05c992 use as_bytes().to_vec() instead of bytes().collect()
copying a slice into a vec should be easier to optimize by the complier than collecting a byte iteration into a vec
2025-11-19 19:23:41 +01:00
Skgland
a283c8bdc2 remove some uncessary allocations/copies 2025-11-19 19:23:41 +01:00
Skgland
6b5ec3fab8 ignore incompatible msrv in compat module 2025-11-19 19:23:41 +01:00
Skgland
a0b4162d7a improve count_to_letter_code
- reserve the complete required length at the beginning to reduce reallocations
- use u8 instead of char so that we can re-use the allocation for the string
2025-11-19 19:23:41 +01:00
Skgland
119761ad10 ignore unused_parens warning
parens are generated by external macro
2025-11-18 21:42:00 +01:00
Skgland
bf8651db29 remove two unecessary clones 2025-11-18 21:29:22 +01:00
J.J. Tolton
11d0215963 Fix -t flag help text to reflect it accepts any goal
The -t flag is not limited to arity 0 predicates - it accepts any
goal including goals with arguments (e.g., -t 'halt(1)'). Updated
the help text to remove the incorrect "(arity 0 predicate)" constraint.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 10:55:49 -05:00
J.J. Tolton
16fd7a8d51 Move -t and -g flags before filenames in tests
Per maintainer feedback, switches must come before files to follow
the convention: switches before files are Scryer-specific, switches
after files are application-specific.
2025-11-09 13:04:59 -05:00
J.J. Tolton
5357ecf8e3 Remove redundant unit test file
The custom_toplevel.pl unit tests were trivial and didn't actually test
the functionality. All real testing is done via comprehensive CLI tests
in tests/scryer/cli/src_tests/custom_toplevel.md
2025-11-09 12:56:47 -05:00
J.J. Tolton
e7c288f8f0 Move g_caused_exception/2 dynamic directive to toplevel.pl
- Add dynamic directive in toplevel.pl with other module-level directives
- Update test files to reference it as '':g_caused_exception/2
- Remove redundant dynamic directives from test files
- All tests passing
2025-11-09 12:44:48 -05:00
J.J. Tolton
95abc4017e Update tests to use format/2 instead of write/1 2025-11-09 12:44:48 -05:00
J.J. Tolton
b91ce76052 Add comprehensive tests for g_caused_exception/2
Following TESTING_GUIDE.md, added tests at layers 2 and 3:

Layer 2 - Prolog Integration Tests (src/tests/custom_toplevel.pl):
- Test that g_caused_exception/2 is not asserted when no exception occurs
- Test that g_caused_exception/2 can be checked from custom toplevel
- Added check_for_exception/0 helper predicate for testing

Layer 3 - CLI Tests (tests/scryer/cli/src_tests/custom_toplevel.md):
- Test g_caused_exception/2 with exception thrown
- Test g_caused_exception/2 with no exception
- Test g_caused_exception/2 with error/2 terms
- Added test helper predicates in fixtures/toplevel_test_helper.pl

All tests pass successfully.

Co-Authored-By: J.J.'s Robot <jjtolton@gmail.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
617a551a56 Add g_caused_exception/2 for custom toplevel error handling
When a goal throws an exception during initialization (-g flag), the
system now asserts g_caused_exception(Goal, Exception) in the user
module. This allows custom toplevels (-t flag) to check if an error
occurred and handle it appropriately.

Example usage:
  scryer-prolog -g "throw(error)" -t check_error

Where check_error can be:
  :- dynamic(g_caused_exception/2).

  check_error :-
      (   g_caused_exception(_, E) ->
          write('Error: '), write(E), nl, halt(1)
      ;   halt(0)
      ).

This enables scripts to use custom toplevels for sophisticated error
handling and exit code logic.

Addresses: https://github.com/mthom/scryer-prolog/pull/3147#issuecomment-3503875719

Co-Authored-By: J.J.'s Robot <jjtolton@gmail.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
31247545c3 Fix bug where -t argument was processed as filename
Fixed issue where `scryer-prolog -t halt` would try to load "halt.pl"
as a file instead of just using halt as the custom toplevel.

The bug was caused by an extra clause `delegate_task([], []).` that
would return control to the calling context instead of continuing to
start_toplevel. This caused the argument processing in delegate_task
to continue and treat the already-consumed toplevel argument as a
filename.

Removing this clause ensures that delegate_task([], Goals0) always
proceeds to load initialization files and start the toplevel, fixing
the double-processing bug.

Co-Authored-By: J.J.'s Robot <jjtolton@gmail.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
15d112485c Add comprehensive tests for -t custom toplevel flag
- Create Prolog integration tests in src/tests/custom_toplevel.pl
- Add CLI test configuration in tests/scryer/cli/src_tests/custom_toplevel_tests.toml
- Tests verify:
  * -t halt terminates after initialization
  * Custom toplevels can be user-defined predicates
  * Toplevel receives control after initialization completes
  * Default behavior is REPL when no -t specified
- All tests pass successfully

Following TESTING_GUIDE.md three-layer testing approach:
- Layer 2: Prolog integration tests with test_framework
- Layer 3: CLI snapshot tests with .toml configuration

Co-Authored-By: J.J.'s Robot <noreply@example.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
768f7ce9a7 -t custom toplevel option
- Add -t FLAG to specify custom toplevel (arity 0 predicate)
- Default toplevel is 'repl' if -t is not specified
- Using `-t halt` achieves original goal of guaranteed termination
- Custom toplevels enable flexible exit strategies (e.g., server mode)
- Update help text to document -t flag

Examples:
  scryer-prolog -t halt program.pl    # Exits after execution
  scryer-prolog -t my_repl program.pl # Custom REPL
  scryer-prolog program.pl            # Default REPL

Co-Authored-By: J.J.'s Robot <noreply@example.com>
2025-11-09 12:44:37 -05:00
Mark Thom
e4d9692535 Merge pull request #3120 from aarroyoc/master
Delete test page from learning section
2025-10-20 19:46:26 -07:00
Mark Thom
f050e75d81 Merge pull request #3124 from bakaq/mark-partial-string-tail
Fix marking of partial string tail in iteration
2025-10-20 19:46:11 -07:00
Mark Thom
3dcb42af3c Merge pull request #3117 from aarroyoc/update-releases-0100
Update web to show 0.10.0 releases
2025-10-20 19:45:19 -07:00
bakaq
fdcd92db08 Fix marking of partial string tail in iteration 2025-10-14 16:59:50 -03:00
Adrián Arroyo Calle
c82f1ccde1 Delete test page from learning section 2025-10-11 15:24:07 +02:00
Adrián Arroyo Calle
2b85a307ae Update web to show 0.10.0 releases 2025-10-07 22:16:40 +02:00
Mark Thom
d7d5dfe059 Merge pull request #3108 from Skgland/issue-3073
emit a more appropriate error when passing the wrong argument count when constructing an ffi struct with the wrong argument count
2025-10-02 23:43:13 -07:00
Mark Thom
109796122e Merge pull request #3111 from Skgland/ffi-non-fixed-sized-integers
add support for non-fixed-width integers types for ffi
2025-10-02 23:42:59 -07:00
Mark Thom
476c1718f9 Merge pull request #3109 from Skgland/issue-3107
move CI  job release from retired ubuntu-20.04 image to ubuntu-22.04
2025-09-29 20:47:55 -07:00
Skgland
f8b9944f5c update documentation 2025-09-28 19:50:21 +02:00
Skgland
6e7dbfc75e consistently treat bool as i8 2025-09-28 18:29:20 +02:00
Skgland
1add5a9a75 add ffi types for non-fixed-sized integers 2025-09-28 18:17:35 +02:00
Skgland
777e6de9ee fix missing quotes 2025-09-28 13:43:09 +02:00
Skgland
2443af57d0 fixup quad 2025-09-27 23:14:14 +02:00
Skgland
ed03d5b1b4 move CI job release from retired ubuntu-20.04 image to ubuntu-22.04
the new image is the same as all other ubuntu jobs
2025-09-27 22:54:45 +02:00
Skgland
9376bc6369 fix mthom/scryer-prolog#3073 2025-09-27 22:48:05 +02:00
Skgland
2f43c3ff40 add regression test for issue 3073 2025-09-27 22:05:51 +02:00
Mark Thom
e7ac3ae0f7 version bump to 0.10.0
Some checks failed
CI / style (push) Has been cancelled
CI / read-msrv (push) Has been cancelled
CI / build-test (--no-default-features, ubuntu-22.04, true, nightly, wasm32-unknown-unknown, --no-run --no-default-features, true) (push) Has been cancelled
CI / build-test (macos-latest, true, stable, x86_64-apple-darwin) (push) Has been cancelled
CI / build-test (miri, true, ubuntu-22.04, nightly, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, ${{ needs.read-msrv.outputs.msrv }}, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, beta, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, true, stable, i686-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, true, stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (windows-latest, true, stable, x86_64-pc-windows-msvc) (push) Has been cancelled
CI / logtalk-test (push) Has been cancelled
CI / report (push) Has been cancelled
CI / release (push) Has been cancelled
Docker Publish / build (push) Has been cancelled
2025-09-27 00:05:31 -07:00
Mark Thom
60595f990f Merge pull request #3095 from thierrymarianne/derefering-registers-in-files-module-instructions-functions
Dereferencing registers in `files` module instructions functions
2025-09-22 22:28:10 -07:00
Thierry Marianne
042631cb4c run rustfmt
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-21 22:52:12 +02:00
Thierry Marianne
32b6458deb dereferencing registers to access stack variables
deref register to access stack variable for `directory_exists/1`
deref register to access stack variable for `directory_files/2`
deref register to access stack variable for `delete_directory/1`
deref register to access stack variable for `delete_file/1`
deref registers to access stack variable for `file_copy/2`
deref register to access stack variable for `file_exists/1`
deref register to access stack variable for `file_size/2`
deref register to access stack variable from `file_time` function
deref register to access stack variable for `make_directory/1`
deref register to access stack variable for `make_directory_path/1`
deref register to access stack variable for `path_canonical/2`
deref registers to access stack variables for `rename_file/2`

Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-21 22:52:12 +02:00
Thierry Marianne
12db664bd9 add tests for file module predicates
reproduce failing `directory_exists/1` with working directory passed as first argument
reproduce failing `delete_directory/1` with ./test directory passed as first argument
reproduce failing `file_size/1` with 2 bytes files passed as first argument
reproduce failing `file_exists/1` with existing file passed as first argument
reproduce failing `file_modification_time/1`, `file_access_time/1` and `file_creation_time/1` with existing file passed as first argument
reproduce failing `directory_files/2` with existing directory passed as first argument
reproduce failing `delete_file/1` with existing file passed as first argument
reproduce failing `make_directory/1` with non-existing directory passed as first argument
reproduce failing `make_directory_path/1` with non-existing path passed as first argument
reproduce failing `path_canonical/2` with non-canonical path passed as first argument
reproduce failing `rename_file/2` with existing file passed as first argument, target file as second arg
revised `directory_exists/1` test case
renamed test files, test directories
use `path_segments/2`, remove path separators
do not write file size to current output
revised `directory_files/2` test
revised `path_canonical/2` test
revised `file_exists/1` test
removed hardcoded expected size
extracted hardcoded directory argument
remove path prefix containing path separator
revised expected ls cmd exit code so that it is platform-agnostic

Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-21 22:52:02 +02:00
Mark Thom
e9ff1348b1 Merge pull request #3084 from thierrymarianne/set_stream
Prevent top-level from panicking when calling  `set_input/1` with `Stream` variants.
2025-09-17 22:20:10 -07:00
Mark Thom
fc6e007ad1 add missing P increment to GetPartialString (#3089) 2025-09-17 20:31:03 -07:00
Mark Thom
7637a0f7d4 push cyclic pstr's tail to iterator stack of printer (#3086) 2025-09-17 20:21:54 -07:00
Mark Thom
ba7b54e8f8 fix attempted bind to bound StackVar (#3089) 2025-09-17 19:43:37 -07:00
Mark Thom
012e3de3a1 do not use self.fail in compare_term_test 2025-09-16 21:23:23 -07:00
Mark Thom
72cdba82f5 implement unify_ginteger to address FIXME in skip_max_list_cycle 2025-09-15 21:58:38 -07:00
Mark Thom
24f431e2ca fix off-by-one lam loop (#3081) 2025-09-15 21:52:43 -07:00
Thierry Marianne
8afcc7742b handle Stream variants qualified as input stream by is_input_stream
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-15 08:33:47 +02:00
Thierry Marianne
e9834ffa3e throw permission_error when non-input stream is read
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-15 08:28:23 +02:00
Thierry Marianne
28ec457159 merge if let, match structures
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-14 20:10:44 +02:00
Thierry Marianne
1ece558a71 match against Stream HttpRead(), InputFile(_) and NamedTcp(_) variants in read_term_from_user_input
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-14 19:19:26 +02:00
Thierry Marianne
be18eda696 do not reset user input Stream of kind different from Readline(_), Byte(_)
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-14 19:19:21 +02:00
Thierry Marianne
fdbaf6ac9d handle named tcp and http read stream on term reading from user input 2025-09-14 17:02:09 +02:00
Thierry Marianne
5c341ce371 handle input file stream on term reading from user input 2025-09-14 17:02:09 +02:00
Thierry Marianne
5f58e1cfd7 reproduce panick originating in set_input/1 call from top-level
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2025-09-14 17:02:02 +02:00
Mark Thom
dc05526c71 add FIXME for (presently inert & never invoked) race condition 2025-09-12 00:19:52 -07:00
Mark Thom
ffea37d899 fix f64 indexing, introduce bespoke F64Table type (#3065) 2025-09-11 23:58:36 -07:00
Mark Thom
368c9fd9dd Merge pull request #3063 from Skgland/ffi++
ffi API extension and fixes
2025-08-28 20:49:41 -07:00
Skgland
c067bfa832 allow 3rd argument of ffi:read_ptr to not be a variable 2025-08-29 00:36:35 +02:00
Mark Thom
aa3d8b8fba Merge pull request #3059 from pmikkelsen/raw-input
Don't use "readline" input functionality for certain builtins
2025-08-26 23:09:18 -07:00
Skgland
14ce052bf2 export array_type/3 2025-08-26 00:33:16 +02:00
Skgland
252361fc20 fix left over todo 2025-08-26 00:15:13 +02:00
Skgland
78c08b87b6 cleanup ffi error handling and use Atom instead of &str in appropriate places 2025-08-26 00:01:35 +02:00
Peter Mikkelsen
eb91b3c1fb Style tweak 2025-08-25 20:47:29 +02:00
Skgland
a140720c6f use representation_error rather than resource_error 2025-08-25 20:14:14 +02:00
Skgland
6f3f66c407 fix builds without ffi feature 2025-08-24 21:08:52 +02:00
Skgland
1f7a60dec9 add meta_predicate declaration for with_locals/2 2025-08-24 20:25:47 +02:00
Skgland
ce9b41815d add documentation 2025-08-24 20:23:17 +02:00
Skgland
80bf276e09 run rustfmt 2025-08-24 19:57:54 +02:00
Skgland
8bc8171ec7 fix define_foreign_struct 2025-08-24 19:56:26 +02:00
Skgland
3b5b768f4a make ffi error structure not found point to the correct culprit 2025-08-24 19:56:26 +02:00
Skgland
176858ad42 fix map_ffi_arg 2025-08-24 19:56:26 +02:00
Skgland
eba681786d make map_ffi_args a method and throw an instantiation error when encountering a variable 2025-08-24 19:56:25 +02:00
Skgland
f4297e365f don't crash on empty list and differentiate list head not being string like 2025-08-24 19:56:25 +02:00
Skgland
cd777294b9 use setup_call_cleanup/3 for with_locals
as suggested by triska
2025-08-24 19:56:25 +02:00
Skgland
3e929511bb add a test for with_locals 2025-08-24 19:56:24 +02:00
Skgland
d1356db7e3 include a culprit in ffi_error 2025-08-24 19:56:24 +02:00
Skgland
7b2bf73ba1 fix with_locals 2025-08-24 19:56:24 +02:00
Skgland
edd6d44bd6 require array_length to be > 0
C does not have 0-sized types
2025-08-24 19:56:24 +02:00
Skgland
4b5e4a2745 fix some things in ffi.pl
thanks triska for pointing out most of these
2025-08-24 19:56:23 +02:00
Skgland
3a4dfc46da add ffi helpers 2025-08-24 19:56:23 +02:00
Skgland
d8346b1651 don't place allocate, read_ptr and deallocate between use_foreign_module and its helper predicates 2025-08-24 19:56:23 +02:00
Skgland
20d52c093a add ffi:{allocate,read_ptr,deallocate} 2025-08-24 19:56:23 +02:00
Skgland
35d34231f2 don't fail the build script if we couldn't parse a file
this way we get to the actual compilation in which rustc shouldl fail with a more helpfull error message
2025-08-24 19:56:17 +02:00
Peter Mikkelsen
90e6e84312 Update src/read.rs
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2025-08-24 14:27:33 +02:00
Mark Thom
cd501beb0b very small cargo fmt 2025-08-23 14:57:56 -07:00
Mark Thom
39d02a0caf clear rustc warnings (#3051) 2025-08-23 14:34:49 -07:00
Mark Thom
e168b31963 make max_depth checking in heap_print.rs consistent across atomics 2025-08-23 14:24:14 -07:00
Mark Thom
eed0a5babb some max_depth improvements (#1876, #2666, #3008, #3027) 2025-08-23 14:01:10 -07:00
Mark Thom
ad29f0f180 further improvements to cyclic partial list printing (#2635) 2025-08-23 13:23:39 -07:00
Mark Thom
b14bda310f improve printer's handling of cyclic lists (#2111, #2635) 2025-08-23 13:23:39 -07:00
Peter Mikkelsen
39cf60bc05 Don't use "readline" input functionality for the following builtins:
* peek_byte
* peek_char
* peek_code
* get_byte
* get_char
* get_code
* get_n_chars
2025-08-22 11:02:59 +02:00
Mark Thom
02b8a6010c Merge pull request #3056 from Skgland/fix-msrv
Fix msrv
2025-08-21 23:24:30 -07:00
Mark Thom
b2cfc8b6f2 fix cyclic detection of partial strings in StackfulPreOrderHeapIter (#3050) 2025-08-21 22:52:25 -07:00
Mark Thom
c5389dbbf2 Use from_str_radix for BigInt in parse_integer_by_radix (#3025, #3033) 2025-08-21 18:25:00 -07:00
Skgland
633b86ddc4 make CI read msrv from Cargo.toml
instead of hardcoding it, so we have a single source of truth
2025-08-18 21:10:27 +02:00
Skgland
348c5029b3 reduce msrv back to 1.85 2025-08-18 20:52:15 +02:00
Mark Thom
7ffd93c0d7 Merge pull request #3055 from Skgland/build-debs
use cargo-deb in CI to build debian deb packages
2025-08-17 15:49:20 -07:00
Skgland
6338a40eed fixup logtalk-test 2025-08-17 22:52:45 +02:00
Skgland
3aa272e44d explicitly pass the target to cargo-deb 2025-08-17 22:31:28 +02:00
Skgland
52f3ddd20d fix paths 2025-08-17 22:12:54 +02:00
Mark Thom
f61c069c2b Merge pull request #3053 from Skgland/add-test-for-issue-3048
add tests for issue 3048
2025-08-17 13:12:28 -07:00
Skgland
f1171e2fb2 build debian packages on linux targets 2025-08-17 21:36:42 +02:00
Skgland
e38ee14580 add tests for issue 3048 2025-08-17 14:50:15 +02:00
Mark Thom
93cc4d4898 clear pdl after comparing partial strings (#3048) 2025-08-16 15:26:10 -07:00
Mark Thom
87f5295bd0 check for stack variables in ground_test before iterating (#3048) 2025-08-14 21:21:32 -07:00
Mark Thom
97607d2bcd small tweak for read_from_term performance (#2668) 2025-08-12 22:29:31 -07:00
Mark Thom
216c251c48 deref terms in read_from_term (#2668) 2025-08-12 22:23:52 -07:00
Mark Thom
8af0a19130 Merge pull request #3042 from dcnorris/numerics
library(numerics), special funs from crate puruspe
2025-08-11 22:26:01 -07:00
David C. Norris
9485ae2ca5 Add README entry for numerics/special_functions 2025-08-10 21:30:36 -04:00
David C. Norris
18c451e11c Drop 2 extra lines flagged by 'cargo fmt' 2025-08-10 18:44:08 -04:00
David C. Norris
a4c071a44b Address CI style complaints 2025-08-10 18:36:54 -04:00
Mark Thom
c0fb3fd429 Merge pull request #2786 from Skgland/ffi-f64-tests
add ffi tests & fix ffi
2025-08-10 13:14:15 -07:00
Mark Thom
9fb7d15f0a Merge pull request #3023 from triska/promote_call_with_error_context
ADDED: variants of errors promoting call_with_error_context/2
2025-08-10 13:04:03 -07:00
David C. Norris
a8fa8993e1 Address several comments by @triska 2025-08-10 11:45:21 -04:00
David C. Norris
af3792f79a library(numerics), special funs from crate puruspe 2025-08-10 10:01:01 -04:00
Mark Thom
d83827b21f Merge pull request #2746 from triska/quads
preliminary support for a subset of quads introduced by @UWN
2025-08-10 01:36:15 -07:00
Markus Triska
ee39be4361 ADDED: resource_error/1 2025-08-09 10:22:28 +02:00
Markus Triska
bc4689ca4a ADDED: representation_error/1 2025-08-09 10:19:49 +02:00
Markus Triska
86dc22a850 ADDED: variants of errors promoting call_with_error_context/2
A good example of call_with_error_context/2 was recently provided by
@Skgland in d907f86c8d. Many thanks!
2025-08-09 10:11:25 +02:00
Mark Thom
9cc170f2ad Merge pull request #3024 from triska/remove_operator_meta_predicate
ISO: remove nonstandard operator meta_predicate.
2025-08-08 21:12:19 -07:00
Mark Thom
61f8bc008c Merge pull request #3026 from triska/DEDUCTION
mention the DEDUCTION Programme
2025-08-08 21:11:46 -07:00
Mark Thom
948cbd8f40 Merge pull request #3029 from triska/process_item
add newly available library(process) to the list of libraries
2025-08-08 21:11:32 -07:00
Skgland
e1a52a4dde allow passing a null through a cstr arg/return 2025-08-08 22:45:24 +02:00
Skgland
7b960a7d63 fix clippy lint warning 2025-08-08 22:08:13 +02:00
Markus Triska
c5fd4c1d8a support newly added representation_error/1 in answer descriptions 2025-08-08 21:34:23 +02:00
Markus Triska
7dc6d6e4e0 preliminary support for a subset of quads introduced by @UWN
This lets us embed toplevel interactions in Prolog programs.
Embedded toplevel interactions are currently ignored.

Example:

   :- use_module(library(lists)).

   ?- member(X, "abc").
      X = a
   ;  X = b
   ;  X = c.
2025-08-08 18:44:25 +02:00
Markus Triska
1d643d8385 add newly available library(process) to the list of libraries
Thank you a lot @Skgland for this extremely valuable contribution!
2025-08-05 17:51:08 +02:00
Markus Triska
badd6304c7 mention the DEDUCTION Programme 2025-08-03 14:32:23 +02:00
Markus Triska
a8da2b6a99 ISO: remove nonstandard operator meta_predicate.
Use functional notation instead for declarations.

This addresses #2822.
2025-08-02 10:22:50 +02:00
Mark Thom
a43bac8fd8 Merge pull request #3021 from mthom/rebis-dev
Merge rebis-dev to master
2025-08-02 00:01:39 -07:00
Mark Thom
72d5574ff0 Merge pull request #3009 from Skgland/process
adds predicates for spawning new processes without a shell
2025-08-01 23:18:36 -07:00
Bennet Bleßmann
a2e19bd483 add process module to all_modules test 2025-08-01 21:31:58 +02:00
Bennet Bleßmann
d907f86c8d use call_with_error_context 2025-08-01 20:49:43 +02:00
Bennet Bleßmann
fadd3a0839 use named vars instead of wildcards
makes updating tests easier as globs are sometimes lost when using TRYCMD=overwrite
2025-08-01 20:49:35 +02:00
Bennet Bleßmann
45041be336 fix culprit 2025-08-01 20:49:24 +02:00
Bennet Bleßmann
b2d639b159 fix arity of prcess_wait builtin errors 2025-08-01 20:49:14 +02:00
Bennet Bleßmann
3a6b92d227 more tests 2025-08-01 20:49:03 +02:00
Bennet Bleßmann
409159d68d adjust/add tests 2025-08-01 20:48:55 +02:00
Bennet Bleßmann
143f32be23 adjust options checking and add more tests 2025-08-01 20:48:44 +02:00
Bennet Bleßmann
d0a6dc9df2 address comment by triska
https://github.com/mthom/scryer-prolog/pull/3009#discussion_r2233221091
2025-08-01 20:48:34 +02:00
Bennet Bleßmann
62e43a3ab0 use functor/3 for must_be_known_options 2025-08-01 20:48:24 +02:00
Bennet Bleßmann
eb82d1b6d4 reformat ; 2025-08-01 20:48:13 +02:00
Bennet Bleßmann
70d65bcea8 add another test and remove unecessary module qualification 2025-08-01 20:48:03 +02:00
Bennet Bleßmann
25edde2eb4 replace filter by tfiltert and fix Pipe{Reader,Writer} streams 2025-08-01 20:47:51 +02:00
Bennet Bleßmann
ab675e071a adjust error kind 2025-08-01 20:47:41 +02:00
Bennet Bleßmann
dc08a4ab11 get process_create working and add tests 2025-08-01 20:47:22 +02:00
Bennet Bleßmann
1120bcde29 add documentation 2025-08-01 20:47:11 +02:00
Bennet Bleßmann
dc495f10f8 change behaviour in supposedly unreachable cases 2025-08-01 20:46:31 +02:00
Bennet Bleßmann
d2ffd4f4bf adjust error for duplicate options 2025-08-01 20:35:12 +02:00
Bennet Bleßmann
87dbad2294 fix rename pid to process 2025-08-01 20:35:01 +02:00
Bennet Bleßmann
ca52c65902 don't remove the child on wait/kill
- important for wait with timout(0) as we may want to try again until the process has realy exited.

- make process_release release the process instead
2025-08-01 20:33:15 +02:00
Bennet Bleßmann
7f233da10c fix timeout default value in process_wait/3 2025-08-01 20:28:17 +02:00
Bennet Bleßmann
ff546a0f9a incorporate suggestion by triska 2025-08-01 20:28:07 +02:00
Bennet Bleßmann
746d4fd106 make atom!() with a new value less annoying 2025-08-01 20:27:53 +02:00
Bennet Bleßmann
97e2e5d7f0 implement process_release/1, process_wait/2, process_wait/3, and process_kill/1 2025-08-01 20:27:27 +02:00
Bennet Bleßmann
1ee4f7a55f store child process in machine state 2025-08-01 20:21:36 +02:00
Bennet Bleßmann
43dd1587fb handle some error cases and replace unwrap with expect 2025-08-01 20:21:23 +02:00
Bennet Bleßmann
8971809c83 adjust errors 2025-08-01 20:21:13 +02:00
Bennet Bleßmann
e321f29b9c rename pid to process 2025-08-01 20:21:02 +02:00
Bennet Bleßmann
129c80bf16 undo changes to error.pl 2025-08-01 20:20:52 +02:00
Bennet Bleßmann
183761eba4 adjust default cwd 2025-08-01 20:20:41 +02:00
Bennet Bleßmann
6c7833b9c7 restructure option parsing 2025-08-01 20:20:31 +02:00
Bennet Bleßmann
82e989f631 add comments and try to fix binding the child process pid 2025-08-01 20:20:20 +02:00
Bennet Bleßmann
2b052bf8dd fix more things 2025-08-01 20:20:09 +02:00
Bennet Bleßmann
003e9d4610 get it working 2025-08-01 20:19:54 +02:00
Bennet Bleßmann
d79ece2497 fix indices 2025-08-01 20:18:44 +02:00
Bennet Bleßmann
ae0baf4893 [WIP] add support to spawn new processes 2025-08-01 20:18:13 +02:00
Bennet Bleßmann
1114700ce6 ignore test making network requests 2025-08-01 20:06:07 +02:00
Bennet Bleßmann
dea43a0244 update docs 2025-08-01 19:42:02 +02:00
Bennet Bleßmann
0c2c6124eb support cstr as an ffi return type and do some more restructuring 2025-08-01 19:41:56 +02:00
Bennet Bleßmann
9cccd509e3 no longer skip f32 and f64 now that they work consistently 2025-08-01 19:11:02 +02:00
Bennet Bleßmann
36bdab84ba further cleanup
- replace macros with functions
- stop abusing allocation error
2025-08-01 19:10:49 +02:00
Bennet Bleßmann
cf51338a77 further ffi cleanup 2025-08-01 19:10:38 +02:00
Bennet Bleßmann
f738b42e49 don't panic on unexpected/invalid value 2025-08-01 19:10:27 +02:00
Bennet Bleßmann
f45426e8ab also accept rust type name 2025-08-01 19:10:15 +02:00
Bennet Bleßmann
3d2439c92b use libffi::middle instead of libffi::low were possible 2025-08-01 19:09:56 +02:00
Bennet Bleßmann
6205f2f1f1 add a simple ffi test using structs 2025-08-01 19:08:32 +02:00
Bennet Bleßmann
760e1d2aac fix UB in ffi tests 2025-08-01 19:08:24 +02:00
Bennet Bleßmann
e1246f0c83 add new test helper 2025-08-01 19:08:02 +02:00
Bennet Bleßmann
bd1f8bb37e make ffi support full {i,u}64 range 2025-08-01 19:07:07 +02:00
Bennet Bleßmann
620459ea1e fix {i,u}64 in ffi 2025-08-01 18:37:48 +02:00
Bennet Bleßmann
d2502b0520 fix all but {i,u}64 2025-08-01 18:37:35 +02:00
Bennet Bleßmann
34eab2e73a fix crash when trying to load and ffi library with an invalid type specification 2025-08-01 18:22:15 +02:00
Bennet Bleßmann
db3f2717bc add more ffi tests 2025-08-01 18:22:14 +02:00
Bennet Bleßmann
7227e1d97c cleanup and fix miri & cross-compile 2025-08-01 18:22:13 +02:00
Bennet Bleßmann
9d8906da30 add ffi tests using f64 2025-08-01 18:22:01 +02:00
Mark Thom
f524062e5b Merge pull request #2961 from rotu/patch-5
Document `when/2` Condition
2025-07-31 22:10:32 -07:00
Mark Thom
eb29390a79 Merge pull request #3017 from triska/adapt_link
adapt link to Scryer Prolog Meetup 2024
2025-07-31 22:09:54 -07:00
Mark Thom
f91aedcc2f Merge pull request #3019 from Skgland/rebis-dev_fix-clippy
fix clippy
2025-07-31 22:09:41 -07:00
Bennet Bleßmann
4f04d2dfca replace .skip(n).next() with .nth(n) 2025-07-31 21:47:12 +02:00
Bennet Bleßmann
de89a78cbb remove unecessary cast 2025-07-31 21:47:11 +02:00
Bennet Bleßmann
e7600884fb remove unecessary into_iter 2025-07-31 21:47:11 +02:00
Bennet Bleßmann
3365cffa96 prefere for loop 2025-07-31 21:47:10 +02:00
Bennet Bleßmann
df3f1236f7 replace always erroring of_else with map_err 2025-07-31 21:47:09 +02:00
Bennet Bleßmann
8766851919 replace single non-wildcard pattern match with if let 2025-07-31 21:47:09 +02:00
Bennet Bleßmann
014d1b0095 remove already implied must_use 2025-07-31 21:47:08 +02:00
Bennet Bleßmann
c885d1a7e7 collaps els-if / if-if 2025-07-31 21:47:08 +02:00
Bennet Bleßmann
7593f88d5a ingore nerver looping loop, but add a todo 2025-07-31 21:47:07 +02:00
Bennet Bleßmann
6885074006 fix clippy::unit_arg 2025-07-31 21:47:07 +02:00
Bennet Bleßmann
73cc872b50 check not empty instead of len > 0 2025-07-31 21:47:06 +02:00
Bennet Bleßmann
fae5e13bd5 impl From rather than Into 2025-07-31 21:47:05 +02:00
Bennet Bleßmann
6d7c217227 fix unecessary mut 2025-07-31 21:47:05 +02:00
Bennet Bleßmann
166ec02973 ignore clippy::unbuffered_bytes in test helper functions 2025-07-31 21:47:04 +02:00
Bennet Bleßmann
1d26f98688 fix legacy int constants 2025-07-31 21:47:04 +02:00
Bennet Bleßmann
58b9a6ad69 ignore wrong self convention 2025-07-31 21:47:03 +02:00
Bennet Bleßmann
a639fec153 fix match/if-let can be simplified to ? 2025-07-31 21:47:03 +02:00
Bennet Bleßmann
495bcd73f2 fix unecessary return 2025-07-31 21:46:52 +02:00
Bennet Bleßmann
ae3019d923 fix unecessary reference/dereference 2025-07-31 21:46:51 +02:00
Bennet Bleßmann
72631b3e0b fix clone on copy values 2025-07-31 21:28:39 +02:00
Bennet Bleßmann
8cc74b2af7 fix clippy::uninlined_format_args 2025-07-31 21:26:08 +02:00
Bennet Bleßmann
7199e9a2db remove outdated comment 2025-07-31 20:07:57 +02:00
Bennet Bleßmann
edf21494d9 also run CI on rebis-dev 2025-07-31 20:07:07 +02:00
Markus Triska
b364c9073a adapt link to Scryer Prolog Meetup 2024
An update of the site has caused a change in the address.
2025-07-28 18:55:59 +02:00
Mark Thom
9b293cf1b6 detect end_of_file before end_of_stream in get_char (#2990) 2025-07-22 20:39:52 -07:00
Mark Thom
65dda019a2 allow abolish_clause to abolish empty dynamic clauses (#3010) 2025-07-20 16:55:07 -07:00
Mark Thom
4aa2a45736 fix variadic_functor 2025-07-16 21:52:40 -07:00
Mark Thom
4efceda8c5 fix parsing partial number tokens in other radixes (#3000) 2025-07-14 23:05:10 -07:00
Mark Thom
5dc8983eec Merge pull request #3003 from rotu/patch-8
CI: run clippy even if fmt fails
2025-07-13 21:22:24 -07:00
Dan Rose
49b30e9f81 Merge branch 'mthom:master' into patch-5 2025-07-08 18:03:16 -05:00
Mark Thom
b78ccf8f28 fmt improvements 2025-07-08 13:47:08 -07:00
Dan Rose
2dc66d33dd CI: run clippy even if fmt fails 2025-07-08 11:51:13 -05:00
Mark Thom
e303e5a805 correct number_chars (#2976) 2025-07-07 22:44:05 -07:00
Mark Thom
762b63e1f4 use granular hierarchical locks in offset_table.rs 2025-07-07 22:44:02 -07:00
Mark Thom
2844b5a958 synchronize offset table growth with the borrowing of offset pointers 2025-07-07 22:39:47 -07:00
Mark Thom
1332611f83 use OffsetTableImpl without synchronization by default 2025-07-07 22:39:47 -07:00
Bennet Bleßmann
725def07cd fix crash when loading html 2025-07-07 22:39:47 -07:00
Bennet Bleßmann
cbdd0fbf15 make Fixnum::build_with harder to accidentally misuse
change trait bound order for better
2025-07-07 22:39:34 -07:00
bakaq
87ca083cfa Align the heap to the size of heap cells 2025-07-07 22:38:12 -07:00
bakaq
d01557f3c1 Add scan_slice_to_str_from_start 2025-07-07 22:38:12 -07:00
Mark Thom
8b009448f4 correct threshold marking in copy_structure (#2920) 2025-07-07 22:38:12 -07:00
Markus Triska
709dc041eb FIXED: correct partial string tail calculation in arg/3
This addresses another aspect of #2924, found by @haijinSk.
Thank you again!

Example:

    ?- "aaaaaaa" = [_,_,_,_,_,_|T], arg(2, T, 2).
       false.
2025-07-07 22:38:12 -07:00
Markus Triska
8763a42c98 FIXED: arg/3 for partial strings
This address #2924, reported by @haijinSk. Thank you a lot!

Example:

    ?- arg(2, "a", A).
       A = [].

The fact that such a mistake in macro usage is even possible could be
a sign that the macro definition should be stricter.
2025-07-07 22:38:12 -07:00
Mark Thom
d6b6eda77d fix cont function crashes (#2920) 2025-07-07 22:38:12 -07:00
Mark Thom
4e0493474e replace deprecated unify_complete_string call with allocate_cstr 2025-07-07 22:38:12 -07:00
bakaq
a9847eef65 Migrate to strict and exposed provenance 2025-07-07 22:38:12 -07:00
Mark Thom
cb9b988905 allow unused_parens around let statement in functor! macro 2025-07-07 22:38:12 -07:00
Markus Triska
073ec4c1d0 FIXED: invoke correct predicate
Noted by Oleg Finkelstein. Many thanks!
2025-07-07 22:38:12 -07:00
Markus Triska
d51cbd67e0 ENHANCED: partial_string/3 no longer creates atoms
As a consequence, resulting strings are now quickly reclaimed on
backtracking.

This addresses #2912.

Test case:

    :- use_module(library(iso_ext)).
    :- use_module(library(lists)).

    ab(a).
    ab(b).

Sample query:

    ?- length(Ls, 1_000_000),
       maplist(ab, Ls),
       partial_string(Ls, Es0, []),
       Es0 == Ls.
       Ls = "aaaaaaaaaaaaaaaaaaa ...", Es0 = "aaaaaaaaaaaaaaaaaaa ..."
    ;  Ls = "aaaaaaaaaaaaaaaaaaa ...", Es0 = "aaaaaaaaaaaaaaaaaaa ..."
    ;  Ls = "aaaaaaaaaaaaaaaaaaa ...", Es0 = "aaaaaaaaaaaaaaaaaaa ..."
    ;  Ls = "aaaaaaaaaaaaaaaaaaa ...", Es0 = "aaaaaaaaaaaaaaaaaaa ..."
    ;  Ls = "aaaaaaaaaaaaaaaaaaa ...", Es0 = "aaaaaaaaaaaaaaaaaaa ..."
    ;  ... .

running in constant memory.
2025-07-07 22:38:12 -07:00
Markus Triska
e9bb41f0d6 ENHANCED: use a fast test for the expected case of chars in atom_chars/2 etc.
Suggested by Oleg Finkelstein, thank you a lot!

Example, before this change:

    ?- t+\(length(As, 1_000_000), maplist(=(a), As), time(atom_chars(A, As))).
       % CPU time: 0.693s, 7_000_041 inferences
       true.

Now:

    ?- t+\(length(As, 1_000_000), maplist(=(a), As), time(atom_chars(A, As))).
       % CPU time: 0.080s, 40 inferences
       true.

This also partially ameliorates #2907.
2025-07-07 22:38:12 -07:00
Mark Thom
b12d1ece17 correct heap-to-cell-index comparison in copy_slice_to_end (#2906) 2025-07-07 22:38:12 -07:00
Mark Thom
706ffe7ad9 fix functor! size calculations around indexing_code_ptr 2025-07-07 22:38:12 -07:00
Mark Thom
258244caf2 restore tab completion to REPL (#2575) 2025-07-07 22:38:12 -07:00
Mark Thom
39ea2d5899 make self.s_offset use bytes in case of HeapPtr::PStr in UnifyVoid (#2897) 2025-07-07 22:38:12 -07:00
Mark Thom
41b90c6e46 correct heap_print.rs for null characters being excluded from pstr regions of heap (#2890) 2025-07-07 22:38:12 -07:00
Mark Thom
6bcbb91e08 correctly increment s_offset for partial strings (#2897) 2025-07-07 22:38:12 -07:00
Mark Thom
e1e52338d6 fix typo in try_from_partial_string 2025-07-07 22:38:12 -07:00
Mark Thom
995a39419a fix read_s logic around HeapPtr::PStrLoc (#2894) 2025-07-07 22:38:12 -07:00
Mark Thom
4644ea2404 repair cyclic PStrLoc handling in heap_print.rs 2025-07-07 22:38:12 -07:00
Mark Thom
f3cd6326a3 remove compare_pstr_to_string and related result type 2025-07-07 22:38:12 -07:00
Markus Triska
bf31d3f9a9 ENHANCED: dedicated faster branches for repositionable streams
rebis-dev makes the speed difference especially apparent due to the
linear scan of strings on the heap in partial_string_tail/2 which is
now avoided for repositionable streams, notably files.

This addresses #2888 reported and analyzed by @haijinSk. Many thanks!
2025-07-07 22:38:12 -07:00
Mark Thom
4c46f0e54d correct GetPartialString (#2887) 2025-07-07 22:38:12 -07:00
Mark Thom
e563fd8e4f generalize compare_pstr_segments 2025-07-07 22:38:12 -07:00
Mark Thom
1297a24469 ensure '\0' atom is static & never inlined (#2880) 2025-07-07 22:38:12 -07:00
Mark Thom
907e64e185 fix root reading of complete strings (#2882) 2025-07-07 22:38:12 -07:00
Mark Thom
46f259bf7d some cosmetic tweaks 2025-07-07 22:38:12 -07:00
Markus Triska
634ccf22aa mark done item 2025-07-07 22:38:12 -07:00
Mark Thom
16b3bea48e correct and simplify compute_pstr_size 2025-07-07 22:38:12 -07:00
Mark Thom
e20363b462 correct create_partial_string (#2593) 2025-07-07 22:38:12 -07:00
Mark Thom
549a26dd03 restore more tabu_list use to compare_term_tests (#2633) 2025-07-07 22:38:12 -07:00
Mark Thom
8981ab72d1 restore tabu_list insertions to PStr-Lis comparisons (#2636) 2025-07-07 22:38:12 -07:00
Mark Thom
98dae9aecc do not allow strings containing null characters to be inlined (#2848) 2025-07-07 22:38:12 -07:00
Mark Thom
22080f3787 move CodeIndex to F64Table-like table 2025-07-07 22:38:12 -07:00
Mark Thom
9e1e99f961 Revert "remove Term"
This reverts commit 3b5879841aedecba5057c70c71da0ba23e5cd84a.
2025-07-07 22:38:10 -07:00
Mark Thom
eef7b06919 make write_with forward return values, use it to correct partial string handling 2025-07-07 22:02:27 -07:00
Mark Thom
2ad870c740 read Str focus properly in build_meta_predicate_clause 2025-07-07 22:02:27 -07:00
Mark Thom
3dee07f648 some fixes in response to miri 2025-07-07 22:02:27 -07:00
bakaq
9daf016d63 Fix parsing of \x0\ in partial strings 2025-07-07 22:02:27 -07:00
Mark Thom
fadcfb966e copy partial string blocks properly in all solutions predicates 2025-07-07 22:02:27 -07:00
Mark Thom
57f76169fd run cargo fmt 2025-07-07 22:02:27 -07:00
Mark Thom
ae4d12a123 remove pstr_vec 2025-07-07 22:02:27 -07:00
Mark Thom
3c85ef2724 globalize ALIGN_CELL/ALIGN, fix compute_pstr_size 2025-07-07 21:59:50 -07:00
Emilie Burgun
15f1320d05 Fix allocate_pstr randomly refusing to properly allocate memory
This one was a toughie: it turns out that using `ptr::align_of()`` was
a bad idea, since the buffer in `Heap` itself is not aligned to
`Heap::heap_cell_alignment()`, so `ptr::align_of()` would sometimes
return lower values than expected.

That made for an heisenbug: if the alignment of the heap happened to be 4,
then the bug wouldn't trigger.
2025-07-07 21:59:50 -07:00
Emilie Burgun
7b357ba84d Fix Heap::drop not accounting for null-initialized HeapInner 2025-07-07 21:59:50 -07:00
Mark Thom
c0cd371056 dereference clause_clause value, reserve more parser space (#2579) 2025-07-07 21:59:50 -07:00
Mark Thom
79be0c0625 unmark_cell_bits! in push_literal (#2645) 2025-07-07 21:59:50 -07:00
Mark Thom
c0f72704ec introduce bespoke Heap type for in-heap partial strings 2025-07-07 21:59:50 -07:00
Mark Thom
f7bbdfe73a variable revision 2025-07-07 21:54:08 -07:00
Mark Thom
1ef681bd21 remove Term 2025-07-07 21:54:08 -07:00
Mark Thom
34ac85bb6d restore ubuntu testing to ci.yml with rust version 1.77 2025-07-07 21:40:36 -07:00
Mark Thom
a69303dc93 throw syntax error after parsing infinite floats (#2998) 2025-07-07 21:30:04 -07:00
Mark Thom
778068129f don't skip '.' char in parse_number_from_string (#2997) 2025-07-07 21:28:07 -07:00
Mark Thom
ce56b5ed6b modify number_token to produce partial lexing results (#2986) 2025-07-05 00:58:28 -07:00
Mark Thom
8e01eea440 be more careful about concatenating '.' in parse_number_from_string (#2986) 2025-07-01 18:40:49 -07:00
Mark Thom
1cf5de8f5c Merge pull request #2970 from notoria/clpz
Make `(mod)/2` stronger in CLP(Z)
2025-06-16 21:02:38 -07:00
Mark Thom
686f20db74 Merge pull request #2980 from triska/hophrase
use nonterminal indicators, correct meta_predicate/1 for higher-order phrase//N
2025-06-16 21:02:20 -07:00
Markus Triska
17377064cf use nonterminal indicators, correct meta_predicate/1 for higher-order phrase//N
Noted by @UWN in https://github.com/mthom/scryer-prolog/discussions/2872.

Many thanks!
2025-06-14 22:47:29 +02:00
Mark Thom
f49a0ae79b Merge pull request #2965 from rotu/zygomorphic-puma
Remove obsolete compatibility for Rust<1.80
2025-05-27 22:18:47 -07:00
notoria
4b480a7eab Strengthened (mod)/2 in CLP(Z)
?- 8 #= -1 mod #Y.
   clpz:(Y in 9..sup), clpz:(-1 mod#Y#=8). % unexpected
Expected: Y = 9
?- 8 #= -12 mod #Y.
   clpz:(Y in 9..sup), clpz:(-12 mod#Y#=8). % unexpected
Expected `Y` with finite domain
2025-05-25 13:39:31 +02:00
notoria
4a6f86591c Strengthened (mod)/2 in CLP(Z)
?- 0 #= 2 mod #Y.
   clpz:(Y in inf.. -1\/1..sup), clpz:(2 mod#Y#=0). % unexpected
Expected: clpz:(Y in-2.. -1\/1..2), clpz:(2 mod Y#=0)
2025-05-25 13:38:56 +02:00
notoria
37ac5bb3c1 Strengthened (mod)/2 in CLP(Z)
?- Z = 8, X = 9, #Z #= #X mod #Y.
   Z = 8, X = 9, clpz:(Y in 9..sup), clpz:(9 mod#Y#=8). % unexpected
Expected: false
2025-05-25 13:37:28 +02:00
Dan Rose
9456517bed Remove obsolete compatibility for Rust<1.80 2025-05-22 14:00:42 -05:00
Mark Thom
8f514ce419 Merge pull request #2955 from rotu/radical-cockroach
Remove xor as a builtin infix operator
2025-05-19 22:47:41 -07:00
Dan Rose
e40125ff3a Document when/2 Condition 2025-05-18 16:46:28 -05:00
Mark Thom
c790bbfce9 Merge pull request #2956 from bakaq/update_arcu_libffi
Update dependencies and fix breaking changes in libffi-sys and arcu
2025-05-13 21:26:29 -07:00
Dan Rose
b767e4de25 xor is not a standard operator 2025-05-13 20:32:59 -05:00
bakaq
ca173873af Update dependencies and fix breaking changes in libffi-sys and arcu 2025-05-13 19:53:13 -03:00
Mark Thom
036c59e5fa Merge pull request #2950 from triska/meetups
link to Scryer Prolog Meetup 2025 page and its announcement
2025-05-08 22:53:04 -07:00
Markus Triska
7809edca19 link to Scryer Prolog Meetup 2025 page and its announcement 2025-05-09 07:42:48 +02:00
Mark Thom
a42a88a2c9 Merge pull request #2802 from adri326/null-stream-safety
Fix UB when interacting with Stream::Null(_)
2025-05-01 17:35:30 -07:00
Mark Thom
9dab070c11 Merge pull request #2930 from triska/meetups
ADDED: Section about Scryer Prolog Meetups.
2025-04-30 18:51:43 -07:00
Markus Triska
ec28872a9a correct grammar 2025-04-30 19:58:45 +02:00
Markus Triska
eb96a0c957 ADDED: Section about Scryer Prolog Meetups. 2025-04-30 19:58:45 +02:00
Mark Thom
90f4716cc3 Merge branch 'null-stream-safety' of https://github.com/adri326/scryer-prolog into adri326-null-stream-safety 2025-04-29 23:32:48 -07:00
Mark Thom
3a6289324e Merge pull request #2901 from matteob8/patch-1
Update http_server.pl
2025-04-29 21:41:07 -07:00
Mark Thom
f496abbbef use loader:strip_subst_module/4 in second module check of retract/1 (#2891, #2893) 2025-04-22 23:28:19 -07:00
Mark Thom
f1793744bc Merge pull request #2905 from bakaq/bump-msrv
Bump MSRV and update flake.lock
2025-04-22 22:28:02 -07:00
bakaq
bd4b305649 Remove Ubuntu 20.04 runner 2025-04-22 18:21:50 -03:00
bakaq
638a765937 Bump MSRV and update flake.lock 2025-04-22 17:20:06 -03:00
Mark Thom
78fec46517 Merge pull request #2895 from hakujin/master
Update `libffi` and `libffi-sys` dependencies to fix aarch64 macOS build
2025-04-21 22:11:43 -07:00
matteob8
00a44921db Update http_server.pl
changed top/0 to the more meaningful run/0 name.
2025-04-21 20:54:43 +02:00
matteob8
d79bc199b3 Update http_server.pl
Avoid overwriting of predicate and made explicit reference to the loading of library(http/http_server).
2025-04-21 19:13:20 +02:00
Colin King
4da0f90097 Update libffi and libffi-sys dependencies to fix aarch64 macOS build
Pulls in latest `libffi` and `libffi-sys` dependencies to fix a
compilation error on aarch64 macOS builds.
2025-04-18 21:40:29 -05:00
Mark Thom
d18ef52e67 Merge pull request #2873 from constraintAutomaton/doc/update-readme-wasm-example
update of the WebAssembly example in README
2025-04-12 21:09:40 -07:00
Mark Thom
925921e9ab Merge pull request #2879 from triska/ad4m
enumerate applications, add AD4M by @coasys
2025-04-12 21:09:20 -07:00
Markus Triska
b98076b39e enumerate applications, add AD4M by @coasys 2025-04-12 09:05:28 +02:00
constraintAutomaton
3090cb0084 Example update to use iterable. 2025-04-12 07:18:00 +02:00
constraintAutomaton
971521791c Merge branch 'master' into doc/update-readme-wasm-example 2025-04-12 07:00:14 +02:00
Mark Thom
2c8041250d Merge pull request #2875 from constraintAutomaton/feature/wasm-result-iterator
Make `WasmQueryState` Iterable in JavaScript
2025-04-11 20:52:45 -07:00
Mark Thom
1b22c89211 mark cyclic lists with ellipses at the head (#2635) 2025-04-10 22:40:13 -07:00
constraintAutomaton
4e207fc34a Made WasmQueryState an iterable. 2025-04-10 19:02:04 +02:00
Mark Thom
8558bd2214 Merge pull request #2874 from triska/read_from_chars_instantiated
generalize read_from_chars/2 and read_term_from_chars/3 to allow instantiated Term
2025-04-10 08:47:21 -07:00
Markus Triska
ba9fed3eae generalize read_from_chars/2 and read_term_from_chars/3 to allow instantiated Term
This addresses part of #2871, raised by @haijinSk. Many thanks!
2025-04-10 08:18:23 +02:00
constraintAutomaton
024c498bee Fix DOM manipulation to avoid deprecated functions in Sudoku solver example. 2025-04-08 20:13:02 +02:00
constraintAutomaton
8c348a7160 better identation of the knowledge base. 2025-04-08 19:29:34 +02:00
constraintAutomaton
1f82dafcb6 Useless console.log in the example deleted. 2025-04-08 19:17:20 +02:00
constraintAutomaton
d2dfb7ccda Update README with working WebAssembly usage example. 2025-04-08 19:12:27 +02:00
Mark Thom
20525e5013 Merge pull request #2869 from triska/format_w
ENHANCED: format specifier ~w more faithfully emulates write/1
2025-04-06 15:31:59 -07:00
Markus Triska
14c8fc3e34 move ~w to least prominent position, point to more suitable specifiers
In particular, "~q" is a safe better choice.
2025-04-05 10:01:22 +02:00
Markus Triska
3d60094330 ENHANCED: format specifier ~w more faithfully emulates write/1
In particular, variables now start with "_".

Example:

    ?- format("~w", [X]).
    %@ _A   true.

Found thanks to a discussion initiated by @haijinSk:

    https://github.com/mthom/scryer-prolog/discussions/2863
2025-04-05 09:58:06 +02:00
Mark Thom
ae27e78d80 Merge pull request #2861 from averms-forks/update-deps
Update and clean up dependencies.
2025-03-31 19:49:54 -07:00
Mark Thom
3826ef38cd Merge pull request #2860 from averms-forks/push-spzqsznlnlnm
Use backticks when referencing predicate in docs.
2025-03-31 19:49:46 -07:00
Aman Verma
7b6f4439bb Install correct locked version of iai-callgrind-runner in CI. 2025-03-31 04:11:16 -05:00
Aman Verma
464cfd9213 Update lexical.
The previous versions of lexical and lexical-core we were using were
subject to the following security advisories:

- https://rustsec.org/advisories/RUSTSEC-2023-0086
- https://rustsec.org/advisories/RUSTSEC-2023-0055
2025-03-31 04:11:16 -05:00
Aman Verma
9dbf592a58 Remove unused deps: regex, assert_cmd, predicates-core.
regex was introduced in commit 9e85be11fe
but isn't used anymore.

assert_cmd and predicates-core were introduced in
e1c681fffe for tests but aren't used
anymore.

Dependency count in Cargo.lock drops from 428 to 421.
2025-03-31 04:11:15 -05:00
Aman Verma
e273b007a2 Update ordered-float to 5.0.0. 2025-03-31 04:11:15 -05:00
Aman Verma
6df4eaf809 Update scraper and ego-tree.
This gets rid of 4 dependencies, as verified by the output of

    yj -tj < Cargo.lock | jq -c '.package | length'

Previously it was 432, now it is 428.
2025-03-31 04:11:15 -05:00
Aman Verma
165d47c7b5 Bump dependencies except for arcu v0.1.1 and half 2.4.1.
arcu is not updated for reasons laid out in issue #2749.
half is not updated because going to 2.5.0 would require an MSRV bump 1.81.

Previous versions of idna, openssl, and ring were subject to the
following security advisories:

- https://rustsec.org/advisories/RUSTSEC-2025-0009
- https://rustsec.org/advisories/RUSTSEC-2025-0004
- https://rustsec.org/advisories/RUSTSEC-2024-0421
2025-03-31 04:10:34 -05:00
Aman Verma
a45a1f6197 Use backticks when referencing predicate in docs.
Trivial change but this prevents the HTML output from being erroneously italicized.
2025-03-27 18:56:38 -05:00
Mark Thom
16187390cd Merge pull request #2847 from triska/call_with_error_context
ADDED: type pair
2025-03-09 13:21:25 -07:00
Markus Triska
57bf1ec39b ADDED: type pair 2025-03-08 14:42:48 +01:00
Mark Thom
7ac84d3565 Merge pull request #2846 from triska/call_with_error_context
ADDED: call_with_error_context/2
2025-03-08 00:39:03 -08:00
Markus Triska
32d91449b5 ADDED: call_with_error_context/2
See https://github.com/mthom/scryer-prolog/discussions/2839 for more information.
2025-03-08 09:12:17 +01:00
Mark Thom
3799974b82 Merge pull request #2837 from notoria/next
Fix conversion from float to rational
2025-03-01 13:40:09 -08:00
Mark Thom
9da0a06afa Merge pull request #2836 from triska/format_floats
Correct handling of floats in format/2 and portray_clause/1
2025-03-01 01:45:55 -08:00
Markus Triska
b8526e0510 FIXED: portray_clause/1 for numbers 2025-02-28 08:41:19 +01:00
Markus Triska
2d4fcf9714 correct handling of floats with N digits (~f and ~Nf)
This addresses #2771. Many thanks to @tmerr for reporting the issue!

The code was posted by @UWN in:

    https://github.com/mthom/scryer-prolog/discussions/2805

Many thanks! With additional inputs by @adri326 and @notoria, who also
posted interesting approaches which could help to increase precision
in cases that are currently not ideally handled. Thank you all!

Please see the issue and discussion for more information.
2025-02-28 08:40:53 +01:00
notoria
4f3843742c Fix conversion from float to rational 2025-02-28 08:00:00 +01:00
Mark Thom
a0e3283d64 Merge pull request #2835 from thierrymarianne/fix-http_open-hanging-on-top-of-main-branch
Prevent http_open/3 from hanging
2025-02-27 21:38:58 -08:00
Mark Thom
c9a0f3332e Merge pull request #2792 from triska/must_be_chars
FIXED: instantiation and type check for string arguments
2025-02-27 21:26:40 -08:00
Mark Thom
aeacccbe4c Merge pull request #2825 from bakaq/wasm_rework
Wasm rework
2025-02-27 21:26:20 -08:00
revue_2_presse
5d468d3e19 Replace futures::executor::block_on with tokio::block_in_place 2025-02-25 20:31:19 +01:00
bakaq
080a4fed03 Documentation 2025-02-20 01:04:38 -03:00
bakaq
3b89c1124d Consult modules 2025-02-20 01:04:38 -03:00
bakaq
236ed93c6c Send pure objects instead of classes 2025-02-20 01:04:38 -03:00
bakaq
cea8e6b1c9 MachineBuilder constructor 2025-02-20 01:04:38 -03:00
bakaq
93e804eeaa Rework Wasm interface 2025-02-20 01:04:38 -03:00
bakaq
b32273fc79 Make Wasm compilable 2025-02-20 01:04:38 -03:00
Mark Thom
4fc4152eac Merge pull request #2817 from adri326/fix-2815-run_module_predicate-backtrack
Fix backtracking on the topmost predicate triggering UB in run_module_predicate
2025-02-16 22:47:49 -08:00
Mark Thom
914318b14e Merge pull request #2799 from bakaq/callback_streams
Callback streams for use as library
2025-02-16 22:47:34 -08:00
bakaq
3fc5709c50 Add builder style configuration of user input, output and error 2025-02-16 04:04:48 -03:00
bakaq
28926486e0 More stream tests 2025-02-16 04:04:48 -03:00
bakaq
ad211d5e05 Disallow null streams in output 2025-02-16 04:04:48 -03:00
bakaq
5386c183d2 Make input and output stream configuration public 2025-02-16 04:04:48 -03:00
bakaq
0a2457943e Configure streams separately 2025-02-16 04:04:48 -03:00
bakaq
baae1dca15 Refactor UserInput to use channels 2025-02-16 04:04:48 -03:00
bakaq
4e032c8a28 Test for callback streams 2025-02-16 04:04:48 -03:00
bakaq
7a6620b52d Add input stream channel 2025-02-16 04:04:48 -03:00
bakaq
dd6533e76c Add callback streams 2025-02-16 03:52:52 -03:00
Mark Thom
d262159399 Merge pull request #2791 from Skgland/patch-1
Expand the build/install section of the readme
2025-02-12 23:25:21 -08:00
Mark Thom
c77ea48fbd Merge pull request #2804 from dnmfarrell/line-count-off-by-one
Line numbers start at 1
2025-02-12 23:23:44 -08:00
Mark Thom
35c1880937 Merge pull request #2818 from adri326/stream-cleanup
Fix various issues around close/1 and stream realiasing
2025-02-12 23:23:11 -08:00
Mark Thom
e5cca9d10a Merge pull request #2812 from bakaq/attr_vars_arity_fix
Fix bug in finding arity for verify attributes
2025-02-12 23:19:34 -08:00
Emilie Burgun
f45b0bcfe8 Remove redundant alias resolution in at_end_of_stream/1, add corresponding tests for null streams
Also fixed at_end_of_stream/0 leaving a choicepoint.
2025-02-07 15:05:01 +01:00
Emilie Burgun
8966e175f1 [fixup] return that reading from a null stream wrote 0 bytes to the buffer 2025-02-07 14:35:27 +01:00
Emilie Burgun
2a218f34b9 Test corner cases of stream aliasing 2025-02-06 23:47:22 +01:00
Emilie Burgun
d8213e29c5 Fix set_output/1 and set_input/1 not updating the alias
Before this change, the following set of queries would behave incorrectly:

```
?- open("/tmp/out.log", write, S), set_output(S).
   prints(""), write("/tmp/out.log", "S = stream(...)").
?- write(user_output, hello).
   prints("hello"), unexpected.
   prints(""), write("/tmp/out.log", "hello"). % Expected, but not found.
```

Now, `set_output/1` and `set_input/1` properly bind the `user_output` and
`user_input` aliases, making the queries above behave as expected.
2025-02-06 23:01:38 +01:00
Emilie Burgun
7f2ce57ba7 Fix stream realiasing possibly shadowing other streams. 2025-02-06 23:01:33 +01:00
Emilie Burgun
949d316773 Fix realiased streams causing close/1 to leave a dangling stream 2025-02-06 23:00:00 +01:00
Emilie Burgun
0cf46d3ec4 Encapsulate accesses to IndexStore::streams and ::stream_aliases
These two fields are able to hold `Stream` instances, which predicates like `close/1`
expect to be managed properly for their correctness. To ensure that this is the case,
I have removed direct accesses to those two fields, so that they can be properly managed
in one place.
2025-02-06 23:00:00 +01:00
Emilie Burgun
e68ac8347f Document run_module_predicate and handle critical failure in toplevel.pl 2025-02-06 13:36:23 +01:00
Emilie Burgun
d4bf52e82c Disable test_run_module_predicate_throw under miri and support rustc < 1.83 2025-02-06 10:37:54 +01:00
Emilie Burgun
22538a05be Fix backtracking on the topmost predicate triggering UB in run_module_predicate
Fixes #2815, see that issue for my investigation.

This is a one-line fix that I'm quite proud of :)

If the topmost query for `run_module_predicate` needs to backtrack,
then before this commit, one of the following two things may happen:
- A dangling OrFrame is read at stack offset 0
- An AndFrame was at stack offset 0 would be read as an OrFrame

This can be seen by either calling `run_module_predicate` with a
throwing predicate (encountering the second scenario) or a failing
predicate (encountering the first scenario), or by running the following
in the REPL, which triggers a `throw/1` within the error handler, propagating
it all the way up (and encountering the second scenario):

```prolog
?- current_output(S), open(stream(S), write, S0, [type(binary)]).
```

Currently, `Stack` is not equipped with tools to detect this incorrect
behavior, so it would instead try to read an OrFrame at offset 0, which
triggers UB, since transmuting between AndFramePrelude and OrFramePrelude
isn't legal.

In practice, since `AndFramePrelude` is smaller, the later fields of
`OrFramePrelude` would read from the cells following the `AndFramePrelude`,
and would contain nonsensical data, triggering the panic that led to
my investigation in #2815 and that is fairly reliable to witness.

Surprisingly, this wouldn't happen with `run_query`, which led me to
look at how they operate differently. It turns out that `run_query`
inserts an OrFrame at offset 0, which covers both problematic scenarios.

The fix is thus to simply add a call to `Machine::allocate_stub_choice_point`
in `run_module_predicate` :)
2025-02-06 00:43:01 +01:00
bakaq
6d80c843e6 Scan entire predicate in InstallVerifyAttr 2025-02-05 11:40:05 -03:00
bakaq
b8ccebbf24 Fix bug in finding arity for verify attributes 2025-02-04 04:11:11 -03:00
Emilie Burgun
7108e87e92 Make Stream::Null behave like /dev/null 2025-02-03 00:12:03 +01:00
Emilie Burgun
fdc35f8b40 Fix UB caused by interactions with null streams 2025-02-03 00:08:37 +01:00
Emilie Burgun
2fe7b55343 Fix close/1 messing up stream_aliases when user_input or user_output aren't set to Stdin and Stdout 2025-02-03 00:07:39 +01:00
David Farrell
f509d3d66f Line numbers start at 1
Bumps the line number for the singleton warning. When the singleton
occurs on the same line at the term starts, the line number is correct:

    foo(X).

However it stills mis-reports this line number as 1 instead of 5:

    foo(1) :-
        true,
        true,
        true,
        Y.

See issue #1356.
2025-02-02 08:16:05 -05:00
Emilie Burgun
079a69396a Add debug asserts to UntypedArenaPtr::build_with, ::get_tag and raw_ptr_as_cell!
These two functions are pretty unsafe, but having these assertions makes
it easier to catch UB in testing.
2025-01-31 15:15:52 +01:00
Mark Thom
c548b14f50 Merge pull request #2798 from bakaq/clippy_warnings
Fix clippy warnings
2025-01-28 23:24:17 -07:00
bakaq
590d0daa24 Fix clippy warnings 2025-01-28 16:49:41 -03:00
Bennet Bleßmann
7a5a5bb795 fix spelling and grammar errors found by mthom
https://github.com/mthom/scryer-prolog/pull/2791#discussion_r1930025598
2025-01-27 20:40:12 +01:00
Mark Thom
5a869e8b48 Merge pull request #2777 from adri326/fix-2772-rnd_i-clipping
Fix invalid casts in is/2
2025-01-26 23:14:57 -07:00
Mark Thom
28678e708b Merge pull request #2738 from adri326/fix-2275-dcgs-call-module
Fix dcgs using call(M:Pred) when M was left unassigned
2025-01-26 23:14:30 -07:00
Mark Thom
f69c58c6b6 Merge pull request #2793 from Skgland/fix-ci
fix ci and update all used actions
2025-01-26 23:02:23 -07:00
Emilie Burgun
0cde8f9a43 Add integration tests for arithmetic operators
This extensively tests the behavior of is/2, both when compiled and in metacalls.
2025-01-27 00:10:56 +01:00
Emilie Burgun
20735ea34c Fix integer overflow in >>/2 and <</2 2025-01-27 00:10:47 +01:00
Emilie Burgun
f3a92c6551 Fix min/2 and max/2 returning the cast version of its arguments
It now behaves the same way as SWI-Prolog.
2025-01-27 00:06:06 +01:00
Emilie Burgun
e2d1a2b6bc Fix X is gcd(1, 2 ^ 64) triggering a panic
The implementation for gcd/2 would cast the second argument to an isize.
2025-01-27 00:06:06 +01:00
Emilie Burgun
d3361c16d8 Fix round() losing precision on bigints
The original issue can be reproduced with `X is round(2 ^ 54 + 1) - 2 ^ 54, X = 1.`
2025-01-27 00:05:30 +01:00
Emilie Burgun
9420c7e41e Fix rnd_i clipping floats that don't fit in Fixnum
Fixes #2772.

The current implementation of `rnd_i` incorrectly casts `f` (an `f64`)
into an `i64`, before casting it into an `Integer`.

This fixes that issue by using `Integer::try_from(f)` instead,
and failing if `f` is infinite or NaN.

A fixme is left for a future PR to properly handle the resulting errors
in floor/1 and friends (right now they can only be triggered through FFI).
2025-01-27 00:02:04 +01:00
Bennet Bleßmann
a7598184bf fix typo 2025-01-24 18:47:42 +01:00
Markus Triska
bc0f614bf9 FIXED: instantiation and type check for string arguments
This addresses #2790.

The issue first appeared in an example reported by @ak-1 in #2788, and
was successfully analyzed by @flexoron. Many thanks!
2025-01-23 20:18:55 +01:00
Bennet Bleßmann
b1e66f686b Update README.md
fix ambiguity regarding the install location for the cargo install method, also include the binary name
2025-01-23 20:04:43 +01:00
Bennet Bleßmann
b74c8139d1 fix ci and update all used actions 2025-01-23 19:53:59 +01:00
Bennet Bleßmann
75d499bf8a update install link 2025-01-23 19:20:19 +01:00
Bennet Bleßmann
f1100271e2 Update README.md 2025-01-23 19:18:15 +01:00
Bennet Bleßmann
143430dd9c Update README.md 2025-01-23 19:11:08 +01:00
Mark Thom
00e6e323f8 Merge pull request #2768 from robstolarz/RSTO-nix-macos-fix
fix: compile under Nix flake on macOS
2025-01-13 22:15:11 -07:00
Emilie Burgun
b76bdd75e4 Fix #2725 by calling load_context/1 in the unspecified branch of strip_module/3
This fixes #2725, by making it so that `strip_module(Pred, M, P), call(M:P)`
doesn't throw an `instanciation_error` when `Pred` isn't in the form `module:predicate`.

Now, `strip_module(hello, M, P)` will call `load_context(M)`, which unifies `M`
with the topmost module (or `user`).

Two new test cases are added: issue2725.pl, which tests the minimal case id(X) --> X.
and the strip_module(P, M, _), call(M:P) scenario, and module_resolution,
which tests the behavior of strip_module in a few scenarios.
2025-01-12 14:08:28 +01:00
Mark Thom
1ed4fe6555 Merge pull request #2756 from adri326/fix-load-context-unreachable
Fix load_context_module triggering unreachable!()
2025-01-11 21:27:32 -07:00
Rob Stolarz
894405149b fix: compile under Nix flake on macOS 2025-01-11 13:00:46 -08:00
Emilie Burgun
eff094cc29 Add tests for PR #2756 2025-01-08 17:57:40 +01:00
Emilie Burgun
6cedbc33c0 Fix load_context_module triggering unreachable code in unify_atom 2025-01-08 17:57:40 +01:00
Mark Thom
2856cc4371 Merge pull request #2742 from triska/clpb
Revert "FIXED: CLP(B): Delay BDD restriction until after the instantiation"
2025-01-04 13:26:08 -07:00
Mark Thom
a683d3e363 Merge pull request #2728 from aarroyoc/lets-play-brisca
Add tutorial: Let's play Brisca
2025-01-04 13:24:35 -07:00
Mark Thom
2257503951 Merge pull request #2743 from triska/crypto
atom --> string
2025-01-04 13:23:59 -07:00
Mark Thom
531d482ba7 Merge pull request #2744 from triska/files
state when the predicates are true
2025-01-04 13:23:44 -07:00
Markus Triska
28c386422b state when the predicates are true
"returns" is not used in this way in logic programming. "return"
suggests that something went away and is now coming back, but this is
never the case in these situations. The arguments may be variables or
also fully known at the time of the call in most cases.
2025-01-02 18:54:27 +01:00
Markus Triska
edaba024b2 atom --> string 2025-01-02 18:30:29 +01:00
Markus Triska
333d6740ae Revert "FIXED: CLP(B): Delay BDD restriction until after the instantiation."
This reverts commit e185b626bd.

This change is now no longer needed, and the underlying issue is
apparently somewhere else entirely. See the description at:

    https://github.com/mthom/scryer-prolog/issues/2732

Current master behaves differently from Scryer as it was at
099d9aaca6 (i.e., preceding
the commit that is now being reverted), even on the same file.

For an example, see:

    dd41176b97

Scryer now works as expected, and compatibly with SICStus. We still
need to find out what fixed the root cause of this issue.
2025-01-02 18:02:38 +01:00
Adrián Arroyo Calle
56cb27f71c Add tutorial: Let's play Brisca 2024-12-30 23:21:05 +01:00
Mark Thom
d57f871a12 Merge pull request #2681 from hurufu/remove-dcgs-that-have-failed-to-expand
Remove DCGs that have thrown an exception during term expansion
2024-12-25 14:29:38 -07:00
Mark Thom
afc7b74bbd qualify module quantification errors better (#2685) 2024-12-25 12:25:01 -08:00
Mark Thom
d287095e61 style corrections 2024-12-25 12:25:01 -08:00
Aleksy Grabowski
a599a11169 Remove DCGs that have thrown an exception during term expansion
Some DCG constructs aren't supported and can't be expanded, here we
remove offending DCG rule and don't compile it at all – in a similar
fashion to what we do when incorrect goal was found – whole predicate
isn't getting compiled.

Fixes #2675
2024-12-22 10:37:32 +01:00
Mark Thom
b7943837a0 Merge pull request #2714 from triska/graphic_token
prevent accidental amalgamation of graphic token chars in output
2024-12-20 18:42:09 -07:00
Mark Thom
e01a4cdc85 Merge pull request #2717 from jasagredo/js/small-doc
Ensure max_assoc/3 is shown as documented
2024-12-20 18:41:51 -07:00
Mark Thom
555dff5bad leave variable modules uninstantiated (#2685) 2024-12-19 20:20:29 -08:00
Javier Sagredo
75ac3fffae Ensure max_assoc/3 is shown as documented 2024-12-18 01:02:45 +01:00
Markus Triska
8dc80b815f use round brackets for goals involving operators
Examples:

    ?- portray_clause(((+) :- a=b,(-))).
    (+) :-
       a=b,
       (-).
       true.

    ?- portray_clause((a :- (b :- c))).
    a :-
       (b:-c).
       true.

This addresses #2715.

Currently, more round brackets than needed are emitted. Ideally, a
better approach to solve the issue here and also in toplevel.pl is
found in the future.
2024-12-17 22:51:47 +01:00
Markus Triska
6175e334a5 prevent accidental amalgamation of graphic token chars in output
This addresses #2713. Many thanks to @notoria for reporting this
excellent case!

Example:

    ?- portray_clause(A = @).
    A= @ .
       true.

At other positions the now inserted space is unnecessary, as in:

    ?- portray_clause((head:- @,b)).
    head :-
       @ ,
       b.
       true.

The toplevel has a similar issue:

    ?- C = # ; false.
       C = # |<-- cursor is here; redundant space after #

There may be a way to solve this issue for all cases like this.
2024-12-17 20:06:52 +01:00
Mark Thom
7e22c12a8d Merge pull request #2582 from bakaq/rework_library_interface
Rework library interface
2024-12-13 21:46:43 -07:00
Mark Thom
b5030be34f Merge pull request #2691 from aarroyoc/remove-meetup-2024
Remove Scryer Prolog Meetup 2024 announcements
2024-12-13 21:39:38 -07:00
Mark Thom
c59f54252c Merge pull request #2683 from ffortier/master
Support hermetic build environment
2024-12-13 21:39:12 -07:00
Mark Thom
a9a77779e3 Merge pull request #2689 from triska/weighted_maybe
introduce and use weighted_maybe/2
2024-12-13 21:38:47 -07:00
Mark Thom
5f035ec6ad Merge pull request #2690 from notoria/next
Add '$random_integer'/3
2024-12-13 21:38:32 -07:00
Mark Thom
9eef380764 Merge pull request #2696 from jasonhemann/patch-3
s/performance characters/performance characteristics/
2024-12-13 21:37:56 -07:00
Mark Thom
ce6a0a1541 Merge pull request #2698 from jasonhemann/patch-5
Documentation for read/2
2024-12-13 20:02:50 -07:00
Mark Thom
b919a39b96 Merge pull request #2703 from jasonhemann/patch-6
s/avaibale/available/
2024-12-13 20:02:21 -07:00
Mark Thom
84fc639f31 Merge pull request #2697 from jasonhemann/patch-4
"bound to an atom"
2024-12-13 19:57:21 -07:00
Jason Hemann
a15b2789e2 s/avaibale/available/
Quick typo fix to docs.
2024-12-13 13:41:05 -05:00
bakaq
2e910de485 cargo fmt 2024-12-12 06:03:43 -03:00
bakaq
74e3d32ca9 Use Cow<'a,B> for toplevel configuration 2024-12-12 05:59:59 -03:00
bakaq
500a6cdaf1 FIXME in Drop for QueryState 2024-12-12 05:49:52 -03:00
bakaq
17293a5753 Leave residual goals for later 2024-12-12 05:47:01 -03:00
bakaq
a75266c3ce Derive Default for StreamConfig 2024-12-12 05:47:01 -03:00
bakaq
a92eac919f Rename lib_machine_tests.rs to tests.rs 2024-12-12 05:46:54 -03:00
Jason Hemann
2ff50f06bc Update builtins.pl
s/compund/compound/
2024-12-11 08:05:03 -05:00
Jason Hemann
e219e1c483 Update builtins.pl
Simplifying docstring.
2024-12-11 05:46:28 -05:00
Jason Hemann
eef9f74a68 Documentation for read/2
Mirrored the wording in the docstrings for `read/1` and `read_term/3`.
2024-12-10 21:18:09 -05:00
Jason Hemann
eae1f53846 "bound to an atom"
Quick typo.
2024-12-10 21:07:48 -05:00
Jason Hemann
48a42ae140 s/performance characters/performance characteristics/
Small typo fix in docs.
2024-12-10 20:56:44 -05:00
Adrián Arroyo Calle
44facf07ab Remove Scryer Prolog Meetup 2024 announcements 2024-12-09 13:52:43 +01:00
bakaq
9265d66f58 Handle errors in QueryState 2024-12-08 20:18:06 -03:00
bakaq
3d3baeed82 Migrate benches 2024-12-08 20:18:06 -03:00
bakaq
e21c772181 Migrate tests to new API 2024-12-08 20:18:06 -03:00
bakaq
2f82c78bc0 Separate lib_machine tests into separate file 2024-12-08 20:18:06 -03:00
bakaq
ec6286ffab Shrink MVP API surface 2024-12-08 20:18:06 -03:00
bakaq
bb5adba4ef Rename PrologTerm to Term 2024-12-08 20:18:06 -03:00
bakaq
33d8abfed8 Remove parsed_results.rs 2024-12-08 20:18:06 -03:00
bakaq
d336cbc86a MachineBuilder 2024-12-08 20:18:06 -03:00
bakaq
79fbd9e90f Fix Machine links 2024-12-08 20:18:06 -03:00
bakaq
cab61738ad #[deny(missing_docs)] 2024-12-08 20:18:06 -03:00
bakaq
e6cc408ff2 Document test methods 2024-12-08 20:18:06 -03:00
bakaq
dc8348b843 Add interfaces for QueryState methods 2024-12-08 20:18:06 -03:00
bakaq
cb040dafc1 Docs for Machine and QueryState 2024-12-08 20:18:06 -03:00
bakaq
c13fc2d1b6 Docs for run_binary() 2024-12-08 20:18:06 -03:00
bakaq
1375f448a0 LeafAnswer docs and success checking methods 2024-12-08 20:18:06 -03:00
bakaq
0433706db6 More PrologTerm documentation 2024-12-08 20:18:06 -03:00
bakaq
e74fd11b0d Conjunctions, disjunction, and LeafAnswer to PrologTerm 2024-12-08 20:18:06 -03:00
bakaq
4480e7c066 Associated functions for creating PrologTerm 2024-12-08 20:18:06 -03:00
bakaq
71dec62ce2 Basic docs and non_exhaustive for PrologTerm 2024-12-08 20:18:05 -03:00
bakaq
658e39aae6 Machine and stream config rework 2024-12-08 20:18:05 -03:00
bakaq
29fc55cb28 Rename LeafAnswer 2024-12-08 20:18:05 -03:00
bakaq
8e7dc9dba8 Rename PrologTerm 2024-12-08 20:18:05 -03:00
Markus Triska
abe1f37167 introduce and use weighted_maybe/2
Many thanks to @hurufu for the very nice definition!

This is until library(random) provides this functionality,
and a good name is found for it. See #2670.
2024-12-08 21:33:03 +01:00
notoria
8cde3f8ce3 Add '$random_integer'/3 2024-12-08 20:00:00 +01:00
Mark Thom
dba9c9a0a7 instantiate module resolutions for meta-predicate lookups when expanding calls (#2255, #2619, #2684) 2024-12-06 23:30:36 -08:00
Mark Thom
ea02b54e7f Merge pull request #2677 from jasagredo/js/get-single-char-windows
Ignore `KeyEventKind::Release` events when reading a char
2024-12-05 20:25:25 -07:00
Mark Thom
0427d9ef11 Merge pull request #2679 from jasonhemann/patch-2
s/red/read/ typo in documentation.
2024-12-05 20:24:56 -07:00
Francis Fortier
c1571bf063 export toplevel repl/0 2024-12-05 09:38:38 -05:00
Francis Fortier
cef45de74a build: resolve include_str path during the compilation of libraries.rs instead of during he build.rs execution to support different build tools (bazel in this case) 2024-12-05 09:10:52 -05:00
Jason Hemann
d7bf7445f1 s/red/read/ typo in documentation.
Small quick fix.
2024-12-05 01:19:51 -05:00
Javier Sagredo
8e5ddebddb Ignore KeyEventKind::Release events when reading a char 2024-12-04 00:05:10 +01:00
Mark Thom
ff03432624 admit YF in current_op/3 (#2639) 2024-11-14 22:41:43 -07:00
Mark Thom
7fd409140c Merge pull request #2652 from gruhn/patch-1
update Docker install instructions
2024-11-15 05:51:03 +01:00
Mark Thom
9f6bbe949c Merge pull request #2653 from srenatus/sr/issue-2650
toplevel.pl: fix arity mismatch in read_input/2
2024-11-15 05:50:51 +01:00
Stephan Renatus
e78914219e toplevel: add more Stop=continue, remove nesting
Signed-off-by: Stephan Renatus <stephan@styra.com>
2024-11-03 13:31:47 +01:00
Stephan Renatus
620077d8c1 toplevel.pl: attempt to fix continue/stop logic
Signed-off-by: Stephan Renatus <stephan@styra.com>
2024-11-02 21:07:02 +01:00
Stephan Renatus
556b7c733f toplevel.pl: fix arity mismatch in read_input/2
I ran into the same problem today and I figured I'd take a stab at it.
Unfortunately, I'm not certain it is the right approach, but it no
longer errors like it did before.

Fixes #2650.

Signed-off-by: Stephan Renatus <stephan@styra.com>
2024-11-01 15:42:50 +01:00
Niklas Gruhn
e577a5430b README: update Docker install instructions 2024-11-01 10:58:44 +01:00
Mark Thom
e8ef9bc33a Merge pull request #2647 from panasenco/master
Updated Dockerfile
2024-10-29 13:15:08 -06:00
Aram Panasenco
7b18b1d47a Updated and tested Dockerfile 2024-10-28 10:31:05 -06:00
Mark Thom
c39ea4818a Merge pull request #2527 from bakaq/leaf_answers_callbacks
Toplevel reimplementation with leaf answer callbacks
2024-10-13 12:13:08 -06:00
bakaq
90d418a181 Fix priority in op/3 2024-10-12 23:37:58 -03:00
Mark Thom
9785a04b3a Merge pull request #2596 from Skgland/issue-2588
fix issue #2588
2024-10-05 22:12:45 -06:00
Bennet Bleßmann
6dd0ec0b98 fix miri 2024-10-04 20:25:02 +02:00
Bennet Bleßmann
e9f03c89a6 fix issue #2588 2024-10-04 19:25:34 +02:00
Bennet Bleßmann
8f41c1aa7e Merge remote-tracking branch 'upstream/master' into issue-2588 2024-10-04 19:11:26 +02:00
Bennet Bleßmann
442636c131 add test for load_html 2024-10-04 19:09:35 +02:00
bakaq
190929e4ac Remove trailing whitespace in toplevel.pl 2024-10-01 21:58:59 -03:00
Mark Thom
2f47343cb5 Merge pull request #2581 from bakaq/fix_hostname_feature
Fix not compiling without hostname feature
2024-09-29 21:09:22 -06:00
bakaq
3bc1885606 Fix not compiling without hostname feature 2024-09-29 16:51:11 -03:00
Mark Thom
0552530b41 Merge pull request #2573 from srenatus/sr/readme-link-to-latest-releases
README: link to latest release
2024-09-26 10:50:51 -06:00
Stephan Renatus
951c88d5f3 README: link to latest release
This will automatically redirect to the release marked "latest" in GitHub.
So the link won't become stale and doesn't need updating.

Signed-off-by: Stephan Renatus <stephan@styra.com>
2024-09-26 09:43:28 +02:00
Mark Thom
383eb5bdd9 Merge pull request #2523 from bakaq/visibility
Be conservative with visibility
2024-09-24 23:16:44 -06:00
Mark Thom
3fa6b69a79 Merge pull request #2533 from hurufu/fix-warnings
Assorted small fixes
2024-09-18 18:39:54 -06:00
Mark Thom
0bc3d67298 Merge pull request #2534 from hurufu/document_non_counted_backtracking
Document non counted backtracking
2024-09-16 17:41:53 -06:00
Mark Thom
a898d7a667 Merge pull request #2558 from bakaq/error_to_doclog
Convert library(error) docs to Doclog format
2024-09-16 17:41:18 -06:00
bakaq
238f1bd639 Convert library(error) docs to Doclog format 2024-09-16 16:47:00 -03:00
Aleksy Grabowski
4b0f9558b3 Change wording for non_counted_backtracking comment 2024-09-07 08:24:46 +02:00
Aleksy Grabowski
c922ed8b0a Explain non_counted_backtracking a little bit more 2024-09-07 08:14:37 +02:00
Aleksy Grabowski
3af24a8067 Revert "Explain non_counted_backtracking a little bit more"
This reverts commit 2b73107631.
2024-09-07 08:13:10 +02:00
bakaq
fdd6579230 Add mechanism to stop query 2024-09-07 02:06:01 -03:00
bakaq
16734ec7e4 Fix Clippy 2024-09-06 21:34:49 -03:00
bakaq
d32f3ee2f3 Fix warnings 2024-09-06 18:42:31 -03:00
bakaq
ada9ba98cc Make macros private 2024-09-06 17:17:04 -03:00
bakaq
a885e87ef7 Add argument with extra info to callback 2024-09-06 14:59:17 -03:00
bakaq
e369c15a27 Documentation for run_query/3 and run_query_goal/4 2024-09-06 14:55:39 -03:00
bakaq
afa7af3de6 Address review comments and remove old toplevel 2024-09-06 14:28:17 -03:00
Aleksy Grabowski
d3071988b2 Revert "Remove unnecessary "unsafe" block to fix compiler warning"
This reverts commit 7af93f1532.
2024-09-06 10:26:17 +02:00
Aleksy Grabowski
08aed04dd2 Revert "Fix rustc warning"
This reverts commit 7b09d447d6.
2024-09-06 10:25:45 +02:00
Aleksy Grabowski
7af93f1532 Remove unnecessary "unsafe" block to fix compiler warning 2024-09-06 08:20:13 +02:00
Aleksy Grabowski
2b73107631 Explain non_counted_backtracking a little bit more 2024-09-06 08:15:45 +02:00
Aleksy Grabowski
61c258f49e Fix mistake in predicate indicator 2024-09-06 08:07:21 +02:00
Aleksy Grabowski
6f0c7f42c6 Remove executable bit for files that don't need it 2024-09-06 08:06:10 +02:00
Aleksy Grabowski
7b09d447d6 Fix rustc warning 2024-09-06 08:06:10 +02:00
bakaq
9f4d31a825 Toplevel reimplementation with leaf answer callbacks 2024-09-03 13:43:31 -03:00
bakaq
806e980cca Be conservative with visibility 2024-09-03 01:02:01 -03:00
Mark Thom
12a61cdff8 Merge pull request #2491 from coasys/reqwest-version-relax
Relax version constraint on reqwest dependency
2024-08-29 12:22:46 -06:00
Nicolas Luck
808ea9fc79 Fix spelling of reqWest
Co-authored-by: Bennet Bleßmann <bennet.blessmann+github@googlemail.com>
2024-08-22 12:55:32 +02:00
Nicolas Luck
e1d830d680 Clean-up comment 2024-08-20 22:09:32 +02:00
Nicolas Luck
6be671d7d9 More precise comment mentioning Deno
Co-authored-by: Bennet Bleßmann <bennet.blessmann+github@googlemail.com>
2024-08-20 22:08:11 +02:00
Mark Thom
5c39e2a5a1 Merge pull request #2475 from bakaq/value_heap_walk
Value heap walk in lib_machine
2024-08-19 16:33:13 -06:00
bakaq
e24396d78a Fix order of variables in bindings 2024-08-19 14:38:53 -03:00
Mark Thom
264e3e9dd6 Merge pull request #2474 from Skgland/fix-looping-memory-allocation
Fix looping memory allocation
2024-08-17 20:58:03 -06:00
Mark Thom
3f25ee3749 Merge pull request #2495 from Skgland/fix2467
fix looping from issue #2467
2024-08-17 20:56:23 -06:00
Bennet Bleßmann
c24a51b633 fix looping from issue 2467 2024-08-17 23:37:12 +02:00
Nicolas Luck
8925133f40 Explanatory comment for the wildcard patch version 2024-08-16 16:16:16 +02:00
Nicolas Luck
2faab30af9 Explanatory comment for the wildcard patch version 2024-08-16 16:15:10 +02:00
bakaq
c1b88cee99 Fix partial strings ending in lists 2024-08-16 08:11:11 -03:00
bakaq
8736526b35 Fix lists of chars as strings 2024-08-16 00:34:46 -03:00
bakaq
7afc08af47 Represent atom [] as empty list 2024-08-16 00:01:25 -03:00
bakaq
12aaae5fc0 Differentiate anonymous variables 2024-08-15 20:08:29 -03:00
Nicolas Luck
e0ec5e75b4 typo 2024-08-15 22:41:48 +02:00
Nicolas Luck
7092e278d7 Relax constraint on version of reqwest dependency (Cargo will pick the latest patch that's possible, but not fail if another dep. has it pinned to something below patch 27) 2024-08-15 22:40:35 +02:00
bakaq
82a7d7efbd Value::from_heapcell() implemented with heap walk 2024-08-14 23:44:52 -03:00
bakaq
37a906cf37 Value::from_heapcell() 2024-08-14 23:44:52 -03:00
Mark Thom
bacfed8983 Merge pull request #2472 from bakaq/query_iterator
Add run_query_iter()
2024-08-14 20:26:03 -06:00
bakaq
ed1b25cb30 Tests for run_query_iter() 2024-08-14 20:02:03 -03:00
bakaq
bc262db6d7 Implement run_query() in terms of run_query_iter() 2024-08-14 19:59:42 -03:00
bakaq
86a2c5e2ed Add run_query_iter() 2024-08-14 19:59:36 -03:00
Mark Thom
e77c97e0d1 Merge pull request #2482 from bakaq/flake
Add flake.nix
2024-08-14 06:36:16 -06:00
Mark Thom
48f3a7b87e Merge pull request #2485 from aarroyoc/learn-pages-docs
Upgrade docs to DocLog 2.0. Includes a test learning page
2024-08-14 06:35:58 -06:00
Adrián Arroyo Calle
e5aaa9d277 Upgrade docs to DocLog 2.0. Includes a test learning page 2024-08-14 11:19:08 +02:00
bakaq
938401dbc6 flake.nix 2024-08-13 23:43:05 -03:00
Bennet Bleßmann
620bf385d1 include the type name for better pin-pointing of the error source
- only done here as all other places are non-generic and the panic already includes file and line info, only here are we in a generic context and might need more differentiation
2024-08-11 21:53:02 +02:00
Bennet Bleßmann
cc51b8150d check whether (re-)allocation succeeded
fixes mthom/scryer-prolog#2449
2024-08-11 19:20:33 +02:00
Bennet Bleßmann
775b14c537 add detail to comment 2024-08-11 19:20:27 +02:00
Mark Thom
7cfe8ee55c Merge pull request #2463 from Skgland/split-up-inconsitent-entry-error
Split up inconsistent entry error
2024-08-05 14:25:33 -06:00
Bennet Bleßmann
058b9c5e51 switch invalid term from type to domain error 2024-08-05 21:25:44 +02:00
Bennet Bleßmann
deed6fb984 fix remaining use of declaration instead of directive 2024-08-05 21:25:43 +02:00
Bennet Bleßmann
2d04c0dcf8 remove obsolete ExistenceError::Declaration 2024-08-05 21:25:43 +02:00
Bennet Bleßmann
5a0abd713e change declaration to directive 2024-08-05 21:25:43 +02:00
Bennet Bleßmann
df33da111e ajust/add devlaration errors to be more standard compliant 2024-08-05 21:25:43 +02:00
Bennet Bleßmann
17a816eff4 impl PartialEq<str> and PartialEq<&str> for Atom 2024-08-05 21:25:42 +02:00
Bennet Bleßmann
880e22d164 adjust errors to be more standard compliant 2024-08-05 21:25:42 +02:00
Bennet Bleßmann
c16d2d4147 fix compilation for 1.77 2024-08-05 21:25:42 +02:00
Bennet Bleßmann
59af900aaf split up CompilationError::InconsistentEntry 2024-08-05 21:25:42 +02:00
Bennet Bleßmann
add03cb4ee add test for declaration errors 2024-08-05 21:25:42 +02:00
Bennet Bleßmann
409f56ea37 use an enum for OpDesc spec 2024-08-05 21:25:36 +02:00
Mark Thom
2814d79b56 Merge pull request #2466 from Skgland/upgrade-deps
Upgrade dependencies
2024-08-05 12:59:42 -06:00
Bennet Bleßmann
4009b40029 replace derive_deref with derive_more
- the latter already used in our transitive dependencies
- derive_more appears better maintained
2024-08-04 02:40:26 +02:00
Bennet Bleßmann
260c52adec replace select by scrape
- the latter appears better maintained and more up to date
- removes some dupplicate/outdated transitive deps
2024-08-04 02:35:36 +02:00
Bennet Bleßmann
b1f057b67d resolve problems from deps upgrade
- downgrade reqwest to 0.11.27
  - fixes problems with missmatched transitive dependencies between it and warp, mainly http
- replace deprecated ripemd160 with ripemd (as specified in the formers readme)
- indexmap: replace deprecated remove with functionally identical swap_remove
2024-08-04 01:57:25 +02:00
Bennet Bleßmann
177bc16255 update/upgrade dependencies 2024-08-04 01:41:03 +02:00
Mark Thom
4ce4cd1bc1 Merge pull request #2462 from Skgland/fix-segfault
fix segfault by dropping loader state payload after resetting machine
2024-08-03 14:05:13 -06:00
Bennet Bleßmann
9e8d8aa19e prevent accidental double free by checking tag before drop 2024-08-03 19:36:05 +02:00
Bennet Bleßmann
4d3ceb387e drop payload after resetting machine
- under some circumstances the payload is accessed durring reset which would cause a segfault if we drop the payload too early
2024-08-03 19:27:32 +02:00
Mark Thom
5038b716eb Merge pull request #2457 from Skgland/library
simplify generated libraries map by using `include_str!`
2024-07-26 11:29:12 -06:00
Adrián Arroyo Calle
e310f9683e Fix build on WASM after #2442
(cherry-pick of 3acf4c637b6f8b17bb0230edeaaae0507d9030d1 from PR
mthom/scryer-prolog#2446)
2024-07-26 01:08:00 +02:00
Bennet Bleßmann
c29b76143c fix copy-past error 2024-07-26 00:13:49 +02:00
Bennet Bleßmann
3ad4b05a97 add a features for gating things we can use once 1.80 is msrv 2024-07-25 23:09:17 +02:00
Bennet Bleßmann
0da313eb84 use a OnceLock static for LIBRARIES instead of thread_local! static 2024-07-25 18:58:29 +02:00
Bennet Bleßmann
6b50ec328c move the creation of the LIBRARIES IndexMap out of the generated file
- only leave the filling of the map in the generated code
2024-07-25 18:53:36 +02:00
Bennet Bleßmann
1eb6a883f6 run rustfmt 2024-07-23 00:01:58 +02:00
Bennet Bleßmann
e7fd408548 use include_str! for the build in libraries
instead of "copy-paste-ing" the prolog source into rust string literals in the generated files
2024-07-23 00:01:29 +02:00
Mark Thom
eaa95293e4 Merge pull request #2439 from Skgland/miri
Some cleanup and miri fixes
2024-07-22 13:40:57 -06:00
Mark Thom
8ac663d703 Merge pull request #2442 from aarroyoc/fix-http-at-end-of-stream
Fix at_end_of_stream/1 for http read stream
2024-07-07 16:02:51 -06:00
Mark Thom
902712695c Merge pull request #2440 from triska/statistics
ADDED: Preliminary support for statistics/2.
2024-07-07 15:59:44 -06:00
Bennet Bleßmann
5d03be4b09 fix leak of slaps with dropped payload 2024-07-07 19:22:46 +02:00
Bennet Bleßmann
689632b51c cleanup the slab types
- get rid of HeaderOrIdxPtr
- add IndexPtrSlab
- add UntypedArenaSlab
- add to_untyped for converting a typed slab into an unsyped slab
- remove TypedArenaPtr::new, as they shouldn't be created outside of this module
2024-07-07 17:12:52 +02:00
Bennet Bleßmann
1b19ae81d5 wrap Payloads that are dropped eraly in ManuallyDrop 2024-07-07 15:58:31 +02:00
Bennet Bleßmann
a0d790445d check that we don't attempt to un-drop while evacuating 2024-07-07 14:37:41 +02:00
Bennet Bleßmann
213ee5ca45 add AllocateInArena as a Pivot for arena_alloc!
so that the value type passed to `arena_alloc!` can differ from the `ArenaAllocated::Payload` type
2024-07-07 14:36:53 +02:00
Bennet Bleßmann
74720d4d2e remove unsafe impl From<UntypedArenaPtr> for CodeIndex 2024-07-07 14:18:50 +02:00
Skgland
39bd520542 ignore pstr_iter_tests test in miri as it takes too long 2024-07-07 13:22:49 +02:00
Bennet Bleßmann
496e4e9f56 fix clippy again 2024-07-07 11:31:36 +02:00
Bennet Bleßmann
89c1ea4232 fix leak of ArenaAllocated IndexPtr 2024-07-07 11:30:56 +02:00
Bennet Bleßmann
a2a50586aa replace static Once and two mut statics with one static OnceLock 2024-07-07 11:30:56 +02:00
Bennet Bleßmann
0284a2092d reduce span of unsafe block 2024-07-07 11:30:37 +02:00
Bennet Bleßmann
c414f75329 switch macos to latest as macos-11 has been removed
See https://github.com/actions/runner-images/issues/9255
2024-07-06 18:47:35 +02:00
Bennet Bleßmann
943dce566b fix clippy 2024-07-06 18:26:34 +02:00
Bennet Bleßmann
b32498b37c adjust ignore reason 2024-07-06 18:24:38 +02:00
Bennet Bleßmann
ea041a40f9 if UB un IndexPtr ArenaAllocated impl 2024-07-06 17:43:13 +02:00
Bennet Bleßmann
1d66f91a41 replace ref_thread_local::ref_thread_local! with std::thread_local!
removes `ref_thread_local` which was still at 0.0.0 released October 2018 while latest 0.1.1 was released mid November 2021

fixes libraries.rs UB
2024-07-06 17:18:26 +02:00
Bennet Bleßmann
fee7ba58b0 fix stack alignement
- adjust align() in RawBlockTraits impl for Stack
- ensure ptr is always aligned in RawBlock::allock
2024-07-06 16:21:59 +02:00
Bennet Bleßmann
8e53d12776 make components a comma seperated string instead of a list 2024-07-06 16:01:52 +02:00
Adrián Arroyo Calle
08a9f7c068 Fix at_end_of_stream/1 for http read stream 2024-07-06 16:00:50 +02:00
Bennet Bleßmann
33793193cc add miri to CI 2024-07-06 15:46:04 +02:00
Bennet Bleßmann
285f11ccdc add associated Payload type to ArenaAllocated 2024-07-06 15:37:08 +02:00
Bennet Bleßmann
d87400afa0 switch Rcu to the arcu crate
The arcu crate is a more general implementation of the Rcu I implemented in here in scryer. It contains some bug-fixes regarding race-conditions in the Rcu update function, which could cause leaks and uses after free.

Source of the problem was the Relaxed load/strore/update of the reference count in side the Arc not being properly ordered with other load/stores.
2024-07-06 14:49:19 +02:00
Bennet Bleßmann
fcb41542c3 fix stream.rs UB 2024-07-06 14:30:03 +02:00
Bennet Bleßmann
87b4c9d736 somehow this was never marked as bad 2024-07-06 14:30:03 +02:00
Bennet Bleßmann
6386e70584 clippy: change ToString impl to Display
- write directly to formatter, eliminating intermediate String allocations
- take Value by reference eliminating clones
- remove trim() called on the result of  QueryResolution::to_string
    - we only  emit "true", "false", or "[<resolutions>]"
      neither of which contains trailing or leading withespace,
      so the calls was effectively a noop
2024-07-06 13:32:58 +02:00
Bennet Bleßmann
722975d77e clippy: use type associated constants 2024-07-06 13:08:59 +02:00
Bennet Bleßmann
8943962704 clippy: ptr dereference in safe function 2024-07-06 13:07:26 +02:00
Bennet Bleßmann
ed57ef3d01 clippy: identity conversion 2024-07-06 13:06:50 +02:00
Bennet Bleßmann
9e7d41025a fix saftey comment 2024-07-06 13:06:24 +02:00
Bennet Bleßmann
d2f236d116 clippy: explicit ptr addrs comparision
relevant for wide pointers i.e. pointers with metadata
2024-07-06 13:06:13 +02:00
Bennet Bleßmann
1ff995fedb clippy: allow new without default 2024-07-06 13:04:55 +02:00
Bennet Bleßmann
1b16c6c73f clippy: use clone_from rather than clone 2024-07-06 13:03:14 +02:00
Bennet Bleßmann
ceac9c9095 clippy: ignore unused fields on PrologBenchmark struct 2024-07-06 13:01:19 +02:00
Markus Triska
0005dcaacb ADDED: Preliminary support for statistics/2.
This is needed for benchmarking library(reif) and its newly provided
goal expansion (#2433).

It partly addresses #321.
2024-07-06 11:33:02 +02:00
Bennet Bleßmann
467a82fe46 run rustfmt 2024-07-06 04:18:02 +02:00
Bennet Bleßmann
ce40f8f10c update ignore reason 2024-07-06 04:15:30 +02:00
Bennet Bleßmann
d2d451b182 adjust the generation of the LIBRARIES map
pre-genrate constants instaed of driectly genrating the literal for the insert
2024-07-06 03:53:22 +02:00
Bennet Bleßmann
52fa51853e don't use env::current_dir() in miri 2024-07-06 03:53:22 +02:00
Bennet Bleßmann
6575b1b573 fix some more miri errors
probably relevant to mthom/scryer-prolog#2438
2024-07-06 03:52:46 +02:00
Bennet Bleßmann
cba81b4dc7 some test pass miri 2024-07-06 02:43:00 +02:00
Bennet Bleßmann
91253917b4 remove rust_beta_channel feature
- the msrv (i.e. rust-version in Cargo.toml) is high enough that all gated code can now be used on stabe
2024-07-06 02:24:26 +02:00
Bennet Bleßmann
821358c062 unify pointer width 2024-07-06 01:49:25 +02:00
Bennet Bleßmann
3de0a4e2ad replace transmut with pointer cast calls 2024-07-06 01:38:13 +02:00
Bennet Bleßmann
b8a3067744 resolve miri error for pstr_iter_tests in atom_table
relevant to mthom/scryer-prolog#2438
2024-07-06 01:21:03 +02:00
Bennet Bleßmann
7509cc1a07 adjust alignment calculation
- I think this used to overallocate when the alignment was already met
2024-07-06 00:13:46 +02:00
Bennet Bleßmann
dd2548453b rework some unsafe parts
- removed some unsafe
- added some safety comments
- add explicit types to transmute calls
- reworked UntypedArenaPtr -> TypedArenaPtr conversion

might help with mthom/scryer-prolog#2438, I noticed fewer complains from miri after changing the default impl for `ArenaAllocated::alloc`
2024-07-05 23:45:02 +02:00
Bennet Bleßmann
a93585080c remove remains of num feature
commit c41aba6b90 removed the num feature but a few cfgs remained
2024-07-05 21:29:38 +02:00
Mark Thom
5f30c9599a Merge pull request #2436 from triska/arxivlink
link to newly available paper about Grants4Companies
2024-07-04 16:30:00 -06:00
Markus Triska
4b3edfd370 link to newly available paper about Grants4Companies 2024-07-03 22:33:56 +02:00
Mark Thom
d6ac03552d Merge pull request #2417 from aarroyoc/fix-2416
Manage Float and Double FFI types in structs
2024-06-06 22:33:46 -06:00
Adrián Arroyo Calle
a72632ae85 Manage Float and Double FFI types in structs 2024-06-06 22:18:17 +02:00
Mark Thom
2d18e0d719 Merge pull request #2413 from Skgland/master
re-add CI job to test Cargo.toml rust-version
2024-05-29 12:28:25 -06:00
Bennet Bleßmann
45a8a7bb31 create swap file for wasm32 build 2024-05-29 19:50:27 +02:00
Bennet Bleßmann
1a9d10d298 fix two clippy lints 2024-05-28 21:28:18 +02:00
Bennet Bleßmann
80a0cab6ab re-add CI job to test Cargo.toml rust-version
was removed in #2393 instead of being updated, see

- https://github.com/mthom/scryer-prolog/pull/2393#discussion_r1585963855 and
- 79bc2d9c68 (commitcomment-141790142)
2024-05-28 21:27:29 +02:00
Mark Thom
96720457d0 Merge pull request #2410 from aarroyoc/fix-warn-dead-code-elimination
Fix warnings of dead code
2024-05-27 12:07:27 -06:00
Adrián Arroyo Calle
f4d2839dc7 Fix warnings of dead code 2024-05-27 19:52:02 +02:00
Mark Thom
070f1e8dfa more generally consider NEGATIVE_SIGN a non-op term (#2401) 2024-05-13 14:37:44 -06:00
Mark Thom
42a0d686a5 Merge pull request #2393 from mthom/unsafe_improvements
Improve use of unsafe Rust in arena.rs (#2391)
2024-04-29 16:17:14 -06:00
Mark Thom
79bc2d9c68 remove 1.70 version CI job 2024-04-29 15:40:05 -06:00
Mark Thom
f3eea6302d delay instantiation errors on lists in phrase/3 until runtime (#1665, #2396) 2024-04-29 15:28:17 -06:00
Mark Thom
bcfa0219e2 detect External indices more generally in internalize_constant (#2395) 2024-04-29 14:23:14 -06:00
Mark Thom
5e1effc69a fix failing clippy checks, remove unnecessary code 2024-04-25 20:05:22 -06:00
Mark Thom
be23c74b17 fix wasm tests in unsafe_improvements branch 2024-04-24 21:21:25 -06:00
Mark Thom
8fe1c206d3 bump required rust version for use of newly introduced macros 2024-04-24 20:33:42 -06:00
Mark Thom
a6157a926d improve use of unsafe Rust in arena.rs (#2391) 2024-04-24 20:28:06 -06:00
Mark Thom
9837187183 add nth_partial_strings_tests for bugs raised in #1827, #2381 2024-04-10 15:50:24 -06:00
Mark Thom
c2657cce37 use common test_framework module for several test suites 2024-04-10 15:47:31 -06:00
Mark Thom
7d6109feca fix char_offset values in add_pstr_offset_chars (#2381) 2024-04-10 15:46:11 -06:00
Mark Thom
cf14b222f2 fix skip_max_list crash on bigint max_steps (#2382) 2024-04-09 18:57:53 -06:00
Mark Thom
238343f389 add max_steps to PStr offset (#2381) 2024-04-09 18:40:08 -06:00
Mark Thom
77e90ca725 correct offsets around max_steps in skip_max_list (#2381) 2024-04-09 15:58:23 -06:00
Mark Thom
f734c273ca Merge pull request #2379 from aarroyoc/scryer-prolog-meetup-2024
website: add Scryer Prolog Meetup 2024 banner
2024-04-07 18:10:54 -06:00
Adrián Arroyo Calle
e23e5203de website: add Scryer Prolog Meetup 2024 banner 2024-04-07 19:44:13 +02:00
Mark Thom
a2dd189241 add Scryer Prolog Meetup 2024 to README 2024-04-05 12:29:02 -06:00
Mark Thom
0ed4e41000 Merge pull request #2376 from mthom/dependabot/cargo/h2-0.3.26
Bump h2 from 0.3.24 to 0.3.26
2024-04-05 11:19:50 -06:00
dependabot[bot]
352919862f Bump h2 from 0.3.24 to 0.3.26
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-05 16:18:08 +00:00
Mark Thom
aab6567750 treat Utf8Error::valid_up_to as an offset form self.pos, which it is (#2374) 2024-04-03 15:29:29 -06:00
Mark Thom
ea9e24f214 Merge pull request #2367 from triska/dcg_meta_predicates
ADDED: meta_predicate/1 declarations for (',')//2 and (;)//2
2024-03-26 15:21:37 -06:00
Mark
40fc4f9392 correct offset retrieval in add_pstr_chars_and_step (#2370) 2024-03-25 12:47:45 -06:00
Markus Triska
795a7c690d ADDED: meta_predicate/1 declarations for (',')//2 and (;)//2
This addresses #2366.
2024-03-21 22:14:40 +01:00
Mark Thom
2fdbb94c79 Merge pull request #2363 from triska/compile_format
ENHANCED: Partial evaluation of format_//2 and related constructs
2024-03-20 18:52:08 -06:00
Markus Triska
767d90d5e7 ENHANCED: further partial evaluation for the common control sequence ~d 2024-03-20 21:10:14 +01:00
Markus Triska
bc04790fd5 extend partial evaluation to computing lengths when statically possible 2024-03-20 21:10:14 +01:00
Markus Triska
58f7c94e74 goal expansion for format/2 and format/3
In this way, we benefit from partial evaluation of format strings.
2024-03-20 21:10:14 +01:00
Markus Triska
8af29c200d strengthen type check 2024-03-20 21:10:10 +01:00
Markus Triska
1db6bff890 ENHANCED: Partial evaluation of format_//2.
This moves analysis of the format string to compilation time when
possible, so that parsing it at run time is no longer necessary.
2024-03-20 20:59:43 +01:00
Mark
0292cb73d3 expand_call_goals/3 should expand meta-predicate subgoals (#2361) 2024-03-19 17:12:26 -06:00
Mark Thom
7b33eafbb0 Merge pull request #2358 from triska/octet_documentation
Documentation improvements for encoding(octet) option in several predicates
2024-03-16 19:56:49 -06:00
Markus Triska
ee60c463c8 indicate deprecated functionality 2024-03-16 21:23:12 +01:00
Markus Triska
e1a6b5eb03 DOC: Un-document the ability to use bytes with encoding(octet).
This usage is now considered deprecated. Use strings instead for
greater efficiency and type consistency in your applications.

This functionality is a candidate for removal in a future commit.
2024-03-16 20:37:06 +01:00
Markus Triska
4ebdd97ad3 DOC: Improve descriptions to make clear what encoding(octet) means.
In particular, octet does *not* mean that the input is a list of
Prolog bytes in the sense of "integers between 0..255", but that the
character codes are used directly as byte values.
2024-03-16 20:36:22 +01:00
Mark
6f7f979c05 don't skip to end of pstr in CommaSeparatedCharList token variant (#2356) 2024-03-12 18:37:03 -06:00
Mark Thom
ca8e280b4c Merge pull request #2357 from aarroyoc/update-website-0.9.4
Website: link release v0.9.4
2024-03-12 16:46:31 -06:00
Adrián Arroyo Calle
b26e8b0e34 Website: link release v0.9.4 2024-03-12 22:23:12 +01:00
Mark
df41eb1870 recompile original terms from compile_clause for term to term expansions (#2236) 2024-03-01 18:57:01 -07:00
Mark Thom
90c602f867 Merge pull request #2354 from triska/side_channel
explain potential side-channel attacks due to compact string representation
2024-03-01 16:11:22 -07:00
Markus Triska
6b8a679a51 explain potential side-channel attacks due to compact string representation
This legitimate concern was already raised by @infogulch in:

https://github.com/mthom/scryer-prolog/issues/1309#issuecomment-1080028854

Thank you a lot!
2024-03-01 22:03:57 +01:00
Mark Thom
8b16a0e1f6 Merge pull request #2353 from triska/hmac_verification
ENHANCED: Safe HMAC verification, using constant time string comparison.
2024-03-01 12:09:16 -07:00
Markus Triska
ec251b254c ENHANCED: Safe HMAC verification, using constant time string comparison.
Without this provision, the expected HMAC can be gathered from timing
differences depending on the position where the strings first diverge,
and hence an attacker can forge an authenticated message by supplying
the gathered HMAC.

Test case, using exp(E) to succeed exactly 2^E times:

    exp(E) :-
        N is 2^E,
        between(1, N, _).

yielding:

    ?- Options = [algorithm(sha512),hmac([1,2,3])],
       Ds = "test",
       crypto_data_hash(Ds, H, Options),
       phrase((seq(As),seq(Bs)), H),
       same_length(Bs, Cs),
       maplist(=(a), Cs),
       append(As, Cs, H1),
       time((exp(10),crypto_data_hash(Ds, H1, Options),false)).
    %@    % CPU time: 0.710s, 7_942_187 inferences
    %@    % CPU time: 0.713s, 7_942_187 inferences
    %@    % CPU time: 0.712s, 7_942_187 inferences
    %@    % CPU time: 0.711s, 7_942_187 inferences
    %@    % CPU time: 0.710s, 7_942_187 inferences
    %@    % CPU time: 0.711s, 7_942_187 inferences
    %@    % CPU time: 0.710s, 7_942_187 inferences

    ?- length(_, L), time((exp(10),crypto_data_hash("test", "3caebd1a0a2647930319a660b7d3642eb380fbd43202f9f6d08aabaa9ba50c39522a12ead10f0423f0af613cbc6fea74ad682ee11f563cc2e735722004fda2ba", [algorithm(sha512),hmac([0,L])]),false)).
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.734s, 7_878_699 inferences
    %@    % CPU time: 0.732s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.733s, 7_878_699 inferences
    %@    % CPU time: 0.515s, 5_525_404 inferences
    %@    error('$interrupt_thrown',repl/0).
2024-03-01 18:59:48 +01:00
Mark Thom
e0c8163211 Merge pull request #2351 from infogulch/install-current-iai
Install version of iai-callgrind set in Cargo.toml
2024-02-29 17:09:47 -07:00
infogulch
01f757c940 Install version of iai-callgrind set in Cargo.toml 2024-02-29 17:32:44 -06:00
Mark
42f4827854 make indexer downcast Integers to Fixnums when possible, be slightly more judicious about allocating Integers instead of Fixnums (#2340) 2024-02-29 16:15:28 -07:00
Mark
f2d3779fa9 add #2341 test to lib_machine.rs 2024-02-29 15:12:58 -07:00
260 changed files with 30743 additions and 19077 deletions

0
.dockerignore Executable file → Normal file
View File

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

View File

@@ -1,4 +1,7 @@
name: 'Setup Rust'
description: |
Setup the rust toolchain and environment for the selected toolchain
inputs:
rust-version:
required: true
@@ -16,7 +19,7 @@ inputs:
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@master
- uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master
id: toolchain
with:
toolchain: ${{ inputs.rust-version }}
@@ -33,7 +36,15 @@ runs:
echo "CC=clang" >> $GITHUB_ENV
echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV
- uses: actions/cache@v3
- name: Install s390x cross-compilation toolchain
if: ${{ matrix.target == 's390x-unknown-linux-gnu' }}
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y gcc-s390x-linux-gnu
echo "CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER=s390x-linux-gnu-gcc" >> $GITHUB_ENV
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: |
~/.cargo/bin/
@@ -46,7 +57,7 @@ runs:
# Remove build artifacts for the current crate, since it will be rebuilt every
# run anyway, but keep dependency artifacts to cache them.
# Must be placed after actions/cache so its post step runs first.
- uses: pyTooling/Actions/with-post-step@v0.4.6
- uses: pyTooling/Actions/with-post-step@41aed3e559cbf69bfdb46abb25d7c535900ed5b6 # v7.7.0
with:
main: bash ./.github/actions/setup-rust/cleanup.sh
post: bash ./.github/actions/setup-rust/cleanup.sh

View File

@@ -4,7 +4,7 @@ set -e
echo Cleanup workspace build artifacts and extra target output
# clean just the direct members of the current workspace, use cargo metadata to generalize to all rust projects
cargo clean -p `cargo metadata --no-deps --offline --format-version 1 | jq -r '[.workspace_members[]|split(" ")|.[0]]|join(" ")'`
cargo clean --workspace
# remove directories in /target/ that are not named `debug` or `release`
before=`du -s target | awk '{print $1}'`

52
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
version: 2
updates:
# Enable version updates for cargo
- package-ecosystem: "cargo"
# Look for `Cargo.toml` and `Cargo.lock` files in the `root` directory
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: "7"
groups:
cargo-incompatible:
applies-to: version-updates
# TODO: use incompatible update-type once available
# see issue https://github.com/dependabot/dependabot-core/issues/9681
update-types:
- "major"
- "minor" # pre-1.0 dependencies
cargo-compatible:
applies-to: version-updates
# TODO: use compatible update-type once available
# see issue https://github.com/dependabot/dependabot-core/issues/9681
update-types:
- "patch"
ignore:
# ignore all cargo updates for now while dependabot does not respect msrv/rust-version
# see issue https://github.com/dependabot/dependabot-core/issues/5423
- dependency-name: "*"
# Enable version updates for Docker
- package-ecosystem: "docker"
# Look for a `Dockerfile` in the `root` directory
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: "7"
# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
# Workflow files stored in the default location of `.github/workflows`
# You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: "7"
ignore:
# these actions doesn't have proper version tags
- dependency-name: "dtolnay/rust-toolchain"
- dependency-name: "logtalk-actions/setup-logtalk"

12
.github/workflows/check_features.sh vendored Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "Checking all feature at once"
cargo check -q --all-targets --all-features "$@"
features=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name = "scryer-prolog") | .features | keys | join(" ")')
for feature in ${features} ; do
echo "Checking feature ${feature} in isolation"
cargo check -q --all-targets --no-default-features --features=${feature} "$@"
done

View File

@@ -2,7 +2,9 @@ name: CI
on:
push:
branches: [master]
branches:
- master
- rebis-dev
tags:
- "v**"
pull_request:
@@ -17,7 +19,7 @@ jobs:
style:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
@@ -30,44 +32,74 @@ jobs:
run: cargo fmt --check
- name: Check clippy
run: cargo clippy --no-deps --all-targets
if: ${{ !cancelled() }}
read-msrv:
runs-on: ubuntu-22.04
outputs:
msrv: ${{ steps.read-declared-msrv.outputs.msrv }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- id: read-declared-msrv
name: Read msrv from Cargo.toml rust_version field
run: echo "msrv=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name = "scryer-prolog") | ."rust_version"')" >> "$GITHUB_OUTPUT"
build-test:
runs-on: ${{ matrix.os }}
needs: [read-msrv]
strategy:
fail-fast: false
matrix:
include:
# operating systems
- { os: windows-latest, rust-version: stable, target: 'x86_64-pc-windows-msvc', publish: true }
- { os: macos-11, rust-version: stable, target: 'x86_64-apple-darwin', publish: true }
- { os: ubuntu-20.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true }
- { os: windows-latest, rust-version: stable, target: 'x86_64-pc-windows-msvc', publish: true, check-features: true }
- { os: macos-latest, rust-version: stable, target: 'x86_64-apple-darwin', publish: true, check-features: true }
# architectures
- { os: ubuntu-22.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true }
- { os: ubuntu-22.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true, check-features: true }
- { os: ubuntu-22.04, rust-version: stable, target: 'i686-unknown-linux-gnu', publish: true }
# FIXME(issue #2138): run wasm tests, failing to run since https://github.com/mthom/scryer-prolog/pull/2137 removed wasm-pack
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features' }
# rust versions
- { os: ubuntu-22.04, rust-version: "1.70", target: 'x86_64-unknown-linux-gnu'}
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu'}
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu'}
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features', use_swap: true }
# Cargo.toml rust-version
- { os: ubuntu-22.04, rust-version: "${{ needs.read-msrv.outputs.msrv }}" , target: 'x86_64-unknown-linux-gnu' }
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu' }
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri" }
# run miri for a big-endian target, with all features that are simple to get cross-compiled
- { os: ubuntu-22.04, rust-version: nightly, target: 's390x-unknown-linux-gnu', miri: true, components: "miri", args: '--no-default-features --features=all-simple-cross', test-args: '--no-run --no-default-features --features=all-simple-cross' }
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actionhippie/swap-space@0cffa893f224708cfb6b011690d8ba819d69c10f # v1.1.0
if: matrix.use_swap
with:
size: 10G
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
rust-version: ${{ matrix.rust-version }}
targets: ${{ matrix.target }}
cache-context: ${{ matrix.os }}
components: ${{ matrix.components }}
# Build and test.
- name: Build library
run: cargo build --all-targets --target ${{ matrix.target }} ${{ matrix.args }} --verbose
- name: Test
run: cargo test --target ${{ matrix.target }} ${{ matrix.test-args }} --all
- name: Check features
if: matrix.check-features
run: bash ./.github/workflows/check_features.sh --target ${{ matrix.target }}
- name: Check miri
if: matrix.miri
run: cargo miri test --target ${{ matrix.target }} ${{ matrix.args }}
# On stable rust builds, build a binary and publish as a github actions
# artifact. These binaries could be useful for testing the pipeline but
# are only retained by github for 90 days.
@@ -76,27 +108,38 @@ jobs:
run: |
cargo rustc --target ${{ matrix.target }} ${{ matrix.args }} --verbose --bin scryer-prolog --release
echo "$PWD/target/release" >> $GITHUB_PATH
- name: Install cargo-deb for creating debian packages
if: ${{ matrix.publish && contains(matrix.target, 'linux') }}
run: cargo install cargo-deb --force --locked
- name: Build release debian packages
if: ${{ matrix.publish && contains(matrix.target, 'linux') }}
run: cargo deb --target ${{ matrix.target }}
- name: Publish release binary artifact
if: matrix.publish
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: target/${{ matrix.target }}/release/scryer-prolog*
path: |
target/${{ matrix.target }}/release/scryer-prolog*
target/${{ matrix.target }}/debian/scryer-prolog*.deb
name: scryer-prolog_${{ matrix.os }}_${{ matrix.target }}
logtalk-test:
# if: false # uncomment to disable job
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: [build-test]
steps:
# Download prebuilt ubuntu binary from build-test job, setup logtalk
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
with:
name: scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu
name: scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu
- run: |
chmod +x scryer-prolog
echo "$PWD" >> "$GITHUB_PATH"
chmod +x release/scryer-prolog
echo "$PWD/release" >> "$GITHUB_PATH"
- name: Install Logtalk
uses: logtalk-actions/setup-logtalk@master
uses: logtalk-actions/setup-logtalk@4ea002fe3037199afcf1c6c91bf1f57de0f995e6 # master
with:
logtalk-version: "3.70.0"
logtalk-tool-dependencies: false
@@ -113,17 +156,17 @@ jobs:
|| echo "::warning ::logtalk compliance suite failed"
# -u "https://github.com/LogtalkDotOrg/logtalk3/tree/$LOGTALK_GIT_HASH/tests/prolog/" \
- name: Publish Logtalk test logs
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: logtalk-test-logs
path: '${{ env.LOGTALKUSER }}/tests/prolog/logtalk_tester_logs'
- name: Publish Logtalk test results artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: logtalk-test-results
path: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
- name: Publish Logtalk test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
uses: EnricoMi/publish-unit-test-result-action/composite@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0
with:
check_name: Logtalk test summary
files: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
@@ -133,29 +176,32 @@ jobs:
report:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
rust-version: stable
targets: x86_64-unknown-linux-gnu
cache-context: report
- run: |
- name: Install CLI tools
run: |
cargo install cargo2junit --force
# cargo install iai-callgrind-runner --force --version `cargo metadata --format-version 1 | jq -r '.resolve.nodes[].id|split(" ")|select(.[0]=="iai-callgrind")|.[1]'`
cargo install iai-callgrind-runner --force --git https://github.com/iai-callgrind/iai-callgrind --rev c77bc3c83d7f4e976cc42d4597236a8db259e772
sudo apt install valgrind -y
version=`yq -ptoml -oj -r '.package[] | select(.name == "iai-callgrind") | .version' Cargo.lock`
echo installing iai-callgrind "$version"
cargo install iai-callgrind-runner --force --version "$version"
sudo apt-get update -y
sudo apt-get install valgrind -y
- name: Test and report
run: |
RUSTC_BOOTSTRAP=1 cargo test --all -- -Z unstable-options --format json --report-time | cargo2junit > cargo_test_results.xml
- name: Publish cargo test results artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cargo-test-results
path: cargo_test_results.xml
- name: Publish cargo test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
uses: EnricoMi/publish-unit-test-result-action/composite@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0
with:
check_name: Cargo test summary
files: cargo_test_results.xml
@@ -169,7 +215,7 @@ jobs:
- run: cargo bench --bench run_criterion -- --profile-time 60
- name: Publish benchmark results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-results
path: |
@@ -179,24 +225,24 @@ jobs:
# Publish binaries when building for a tag
release:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: [build-test]
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
- name: Zip binaries for release
run: |
zip scryer-prolog_macos-11.zip ./scryer-prolog_macos-11_x86_64-apple-darwin/scryer-prolog
zip scryer-prolog_ubuntu-20.04.zip ./scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu/scryer-prolog
zip scryer-prolog_ubuntu-22.04.zip ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/scryer-prolog
zip scryer-prolog_macos-latest.zip ./scryer-prolog_macos-latest_x86_64-apple-darwin/scryer-prolog
zip scryer-prolog_ubuntu-22.04_i686.zip ./scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu/release/scryer-prolog ./scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu/debian/scryer-prolog*.deb
zip scryer-prolog_ubuntu-22.04_x86_64.zip ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/release/scryer-prolog ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/debian/scryer-prolog*.deb
zip scryer-prolog_windows-latest.zip ./scryer-prolog_windows-latest_x86_64-pc-windows-msvc/scryer-prolog.exe
zip scryer-prolog_wasm32.zip ./scryer-prolog_ubuntu-22.04_wasm32-unknown-unknown/scryer-prolog.wasm
- name: Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
files: |
scryer-prolog_macos-11.zip
scryer-prolog_ubuntu-20.04.zip
scryer-prolog_ubuntu-22.04.zip
scryer-prolog_macos-latest.zip
scryer-prolog_ubuntu-22.04_i686.zip
scryer-prolog_ubuntu-22.04_x86_64.zip
scryer-prolog_windows-latest.zip
scryer-prolog_wasm32.zip

View File

@@ -14,17 +14,17 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
# https://github.com/docker/setup-buildx-action
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
# Login against Docker registry
- name: Log into registry
# https://github.com/docker/login-action
uses: docker/login-action@v2.1.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -35,7 +35,7 @@ jobs:
- name: Extract Docker metadata
id: meta
# https://github.com/docker/metadata-action
uses: docker/metadata-action@v4.1.1
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/scryer-prolog
tags: |
@@ -47,7 +47,13 @@ jobs:
- name: Build and push Docker image
id: build-and-push
# https://github.com/docker/build-push-action
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
# v4 adds SLSA Provenance attestation which is
# - unsupported by AWS Lambda
# - limited support by Google Cloud Run
# > If deploying a multi-architecture image, the manifest list must include linux/amd64.
# see https://github.com/docker/build-push-action/releases/tag/v4.0.0
# we might want to disable this if its a problem for someone
with:
context: .
push: true

4
.gitignore vendored
View File

@@ -1,5 +1,7 @@
src/static_atoms.rs
target/
.direnv/
__pycache__
*.pyc

2961
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
[package]
name = "scryer-prolog"
version = "0.9.4"
version = "0.10.0"
authors = ["Mark Thom <markjordanthom@gmail.com>"]
edition = "2021"
edition = "2024"
description = "A modern Prolog implementation written mostly in Rust."
readme = "README.md"
repository = "https://github.com/mthom/scryer-prolog"
@@ -10,86 +10,112 @@ license = "BSD-3-Clause"
keywords = ["prolog", "prolog-interpreter", "prolog-system"]
categories = ["command-line-utilities"]
build = "build/main.rs"
rust-version = "1.70"
# Remember to check CI
rust-version = "1.93.1"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["ffi", "repl", "hostname", "tls", "http", "crypto-full"]
default = ["all-simple-cross", "tls", "http"]
# activates all features that depend on no non pure-rust dependencies
#
# currently does not include
# ffi due to libffi
# tls, http due to openssl
# crypto-full due to ring
all-pure = ["repl", "hostname"]
# enables all features that are simple to get working for cross-compliation
# currently all but tls, http as those depend on openssl
all-simple-cross = ["all-pure", "ffi", "crypto-full"]
ffi = ["dep:libffi"]
repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
hostname = ["dep:hostname"]
tls = ["dep:native-tls"]
http = ["dep:warp", "dep:reqwest"]
rust_beta_channel = []
crypto-full = []
# crypto function that require non pure-rust dependencies
crypto-full = ["dep:ring"]
[lints.clippy]
collapsible_match = "allow"
[lints.rust]
unexpected_cfgs = "deny"
function_casts_as_integer = "deny"
[build-dependencies]
indexmap = "1.0.2"
proc-macro2 = "1.0.36"
quote = "1.0.15"
strum = "0.23"
strum_macros = "0.23"
syn = { version = "2.0.32", features = ['full', 'visit', 'extra-traits'] }
proc-macro2 = "1.0.86"
quote = "1.0.36"
strum = "0.26"
strum_macros = "0.26"
syn = { version = "2.0.72", features = ['full', 'visit', 'extra-traits'] }
to-syn-value = "0.1.1"
to-syn-value_derive = "0.1.1"
walkdir = "2"
[dependencies]
base64 = "0.12.3"
bit-set = "0.5.3"
arcu = { version = "0.1.2", features = ["thread_local_counter"] }
base64 = "0.22.1"
bit-set = "0.8.0"
bitvec = "1"
blake2 = "0.8.1"
blake2 = "0.10.6"
bytes = "1"
chrono = "0.4.11"
chrono = "0.4.38"
cpu-time = "1.0.0"
crrl = "0.6.0"
dashu = "0.4.0"
derive_deref = "1.1.1"
crrl = "0.9.0"
dashu = { version = "0.4.2", features = ["rand"] }
derive_more = "0.99.18"
dirs-next = "2.0.0"
divrem = "0.1.0"
divrem = "1.0.0"
futures = "0.3"
fxhash = "0.2.1"
git-version = "0.3.4"
indexmap = "1.0.2"
lazy_static = "1.4.0"
lexical = "5.2.2"
libc = "0.2.62"
libloading = "0.7"
scryer-modular-bitfield = "0.11.4"
git-version = "0.3.9"
indexmap = "2.3.0"
lexical = "7.0.4"
libc = "0.2.155"
libloading = "0.8"
modular-bitfield = "0.13.1"
num-order = { version = "1.2.0" }
ordered-float = "2.6.0"
phf = { version = "0.9", features = ["macros"] }
ordered-float = "5.0.0"
phf = { version = "0.11", features = ["macros"] }
puruspe = "0.4.1"
rand = "0.8.5"
ref_thread_local = "0.0.0"
regex = "1.9.1"
ring = { version = "0.17.5", features = ["wasm32_unknown_unknown_js"] }
ripemd160 = "0.8.0"
roxmltree = "0.11.0"
ryu = "1.0.9"
select = "0.6.0"
sha3 = "0.8.2"
smallvec = "1.8.0"
ring = { version = "0.17.8", features = [
"wasm32_unknown_unknown_js",
], optional = true }
ripemd = "0.1.3"
roxmltree = "0.20.0"
ryu = "1.0.18"
sha3 = "0.10.8"
smallvec = "1.13.2"
static_assertions = "1.1.0"
serde_json = "1.0.95"
serde = "1.0.159"
scraper = { version = "0.23.1", default-features = false, features = [
"errors",
] }
ego-tree = "0.10.0"
serde_json = "1.0.122"
serde = "1.0.204"
parking_lot = "0.12.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
crossterm = { version = "0.20.0", optional = true }
ctrlc = { version = "3.2.2", optional = true }
hostname = { version = "0.3.1", optional = true }
libffi = { version = "3.2.0", optional = true }
native-tls = { version = "0.2.4", optional = true }
reqwest = { version = "0.11.18", optional = true }
rustyline = { version = "12.0.0", optional = true }
tokio = { version = "1.28.2", features = ["full"] }
warp = { version = "=0.3.5", features = ["tls"], optional = true }
crossterm = { version = "0.28.1", optional = true }
ctrlc = { version = "3.4.4", optional = true }
hostname = { version = "0.4.0", optional = true }
libffi = { version = "5.1.0", optional = true }
native-tls = { version = "0.2.12", optional = true }
# the version requirement of reqwest is kept low for compatibility with old deno versions
# that pin reqwest to 0.11.20
reqwest = { version = "0.11.0", optional = true }
rustyline = { version = "18.0.0", optional = true }
tokio = { version = "1.39.2", features = ["full"] }
warp = { version = "0.3.7", features = ["tls"], optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.10", features = ["js"] }
tokio = { version = "1.28.2", features = [
getrandom = { version = "0.2.15", features = ["js"] }
tokio = { version = "1.39.2", features = [
"sync",
"macros",
"io-util",
@@ -99,9 +125,9 @@ tokio = { version = "1.28.2", features = [
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
console_error_panic_hook = "0.1"
wasm-bindgen = "0.2.87"
wasm-bindgen = "0.2.92"
wasm-bindgen-futures = "0.4"
serde-wasm-bindgen = "0.5"
serde-wasm-bindgen = "0.6"
web-sys = { version = "0.3", features = [
"Document",
"Window",
@@ -109,17 +135,17 @@ web-sys = { version = "0.3", features = [
"Performance",
] }
js-sys = "0.3"
ouroboros = "0.18"
[dev-dependencies]
current_platform = "0.2.0"
maplit = "1.0.2"
predicates-core = "1.0.2"
serial_test = "2.0.0"
serial_test = "3.1.1"
[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies]
assert_cmd = "1.0.3"
criterion = "0.5.1"
iai-callgrind = "0.9.0"
trycmd = "0.14.19"
iai-callgrind = "0.12.1"
trycmd = "0.15.6"
[target.'cfg(not(any(target_os = "windows", all(target_arch = "wasm32", target_os = "unknown"))))'.dev-dependencies]
pprof = { version = "0.13.0", features = ["criterion", "flamegraph"] }
@@ -131,6 +157,18 @@ opt-level = 3
[profile.release]
lto = true
opt-level = 3
debug = 2
[profile.wasm-dev]
inherits = "dev"
opt-level = 1
lto = "off"
[profile.wasm-release]
inherits = "release"
lto = "off"
panic = "abort"
codegen-units = 256
[[bench]]
name = "run_criterion"

17
Dockerfile Executable file → Normal file
View File

@@ -1,18 +1,19 @@
# See https://github.com/LukeMathWalker/cargo-chef
ARG RUST_VERSION=1-buster
FROM rust:${RUST_VERSION} as planner
ARG DEBIAN_RELEASE=bookworm
ARG RUST_VERSION=1-${DEBIAN_RELEASE}
FROM rust:${RUST_VERSION} AS planner
WORKDIR /scryer-prolog
RUN cargo install cargo-chef
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM rust:${RUST_VERSION} as cacher
FROM rust:${RUST_VERSION} AS cacher
WORKDIR /scryer-prolog
RUN cargo install cargo-chef
COPY --from=planner /scryer-prolog/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
FROM rust:${RUST_VERSION} as builder
FROM rust:${RUST_VERSION} AS builder
WORKDIR /scryer-prolog
COPY . .
# Copy over the cached dependencies
@@ -20,9 +21,11 @@ COPY --from=cacher /scryer-prolog/target target
COPY --from=cacher $CARGO_HOME $CARGO_HOME
RUN cargo build --release --bin scryer-prolog
# Newer versions of Debian (i.e. bookworm) contain libssl3 instead of libssl1.1
# which we depend on.
FROM debian:bullseye-slim
FROM debian:${DEBIAN_RELEASE}-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssl \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /scryer-prolog/target/release/scryer-prolog /usr/local/bin
ENV RUST_BACKTRACE=1
# Sanity check the binary: if it can't be executed (e.g. if there are missing libraries)

View File

@@ -55,11 +55,13 @@ the builtin Prolog modules and libraries in Scryer, check the documentation site
## Downloads
The latest version of Scryer Prolog is *0.9.3*. And it's already useful for lots of tasks.
The latest version of Scryer Prolog is *0.10.0*. And it's already useful for lots of tasks.
| Windows | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.3/scryer-prolog_windows-latest.zip) |
| macOS (Intel) | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.3/scryer-prolog_macos-11.zip) |
| Linux | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.3/scryer-prolog_ubuntu-20.04.zip) |
| Windows (64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_windows-latest_x86_64-pc-windows-msvc.zip) |
| macOS (Intel) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_macos-11_x86_64-apple-darwin.zip) |
| macOS (ARM) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog--0.10.0.arm64_tahoe.bottle.tar.gz) |
| Linux (Ubuntu 22.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu.zip) |
| Linux (Ubuntu 22.04, 32 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu.zip) |
Scryer Prolog can also be compiled from source, instructions are on the [GitHub README](https://github.com/mthom/scryer-prolog). It runs on Linux, macOS and Windows. Other operating systems may work but they're not regularly tested.

239
README.md
View File

@@ -1,4 +1,3 @@
# Scryer Prolog
Scryer Prolog aims to become to ISO Prolog what GHC is to Haskell: an open
@@ -73,10 +72,11 @@ Extend Scryer Prolog to include the following, among other features:
(`atom`, `var`, etc) with if/else ladders. (_in progress_)
- [ ] Inlining all built-ins and system call instructions.
- [x] Greatly reducing the number of instructions used to compile disjunctives.
- [ ] Storing short atoms to heap cells without writing them to the atom table.
- [x] Storing short atoms to heap cells without writing them to the atom table.
- [ ] Configurable JIT/on-demand indexing over all arguments
(documented in "[Demand-Driven Indexing of Prolog Clauses](https://user.it.uu.se/~kostis/Papers/iclp07.pdf)"). (_in progress_)
- [ ] A compacting garbage collector satisfying the five properties of
"[Precise Garbage Collection in Prolog](https://www.complang.tuwien.ac.at/ulrich/papers/PDF/2008-ciclops.pdf)." (_in progress_)
- [ ] Mode declarations.
## Phase 3
@@ -104,8 +104,6 @@ Gustafson's book "[The End of Error](http://www.johngustafson.net/unums.html)."
3. Add concurrent tables to manage shared references to atoms and
strings.
4. Add some form of JIT predicate indexing.
## Installing Scryer Prolog
### Binaries
@@ -113,12 +111,12 @@ strings.
Precompiled binaries for several platforms are available for download
at:
**https://github.com/mthom/scryer-prolog/releases/tag/v0.9.3**
**https://github.com/mthom/scryer-prolog/releases/latest**
### Native Compilation
First, install the latest stable version of
[Rust](https://www.rust-lang.org/en-US/install.html) using your
[Rust](https://www.rust-lang.org/tools/install) using your
preferred method. Scryer tends to use features from newer Rust
releases, whereas Rust packages in Linux distributions, Macports,
etc. tend to lag behind. [rustup](http://rustup.rs) will keep your
@@ -126,9 +124,13 @@ Rust updated to the latest stable release; any existing Rust
distribution should be uninstalled from your system before rustup is
used.
Currently the only way to install the latest version of Scryer is to
clone directly from this git repository, and compile the system. This
can be done as follows:
> [!NOTE]
> The minimum rust toolchain version required can be found in the [Cargo.toml](https://github.com/mthom/scryer-prolog/blob/master/Cargo.toml#L13)
under the `package.rust-version` key.
> The accuracy of this value is validated in CI
### From a local git checkout
```
$> git clone https://github.com/mthom/scryer-prolog
@@ -142,6 +144,32 @@ faster executable.
After compilation, the executable `scryer-prolog` is available in the
directory&nbsp;`target/release` and can be invoked to run the system.
### Via `cargo install`
#### From git
```
cargo install --locked --git https://github.com/mthom/scryer-prolog.git
```
Afterwards the `scryer-prolog` binary will be in the `$HOME/.cargo/bin` directory which is usually added to your PATH
during the installation of the rust toolchain.
#### From Crates.io [![Crates.io Version](https://img.shields.io/crates/v/scryer-prolog)](https://crates.io/crates/scryer-prolog) ![Crates.io MSRV](https://img.shields.io/crates/msrv/scryer-prolog)
> [!NOTE]
> The latest crates.io release can be significantly behind the version available in the git repository
> The crates.io badge in this sections title is a link to the crates.io page.
> The msrv badge in the section title references the minimum rust toolchain version required to compile the latest crates.io release
`scryer-prolog` is also release on crates.io and can be installed with
```
cargo install --locked scryer-prolog
```
### Caveats for Windows
On Windows, Scryer Prolog is easier to build inside a [MSYS2](https://www.msys2.org/)
environment as some crates may require native C compilation. However,
the resulting binary does not need MSYS2 to run. When executing Scryer in a shell, it is recommended to use a more advanced shell than mintty (the default MSYS2 shell). The [Windows Terminal](https://github.com/microsoft/terminal) works correctly.
@@ -153,8 +181,6 @@ light.exe scryer-prolog.wixobj
```
It will generate a very basic MSI file which installs the main executable and a shortcut in the Start Menu. It can be installed with a double-click. To uninstall, go to the Control Panel and uninstall as usual.
Scryer Prolog must be built with **Rust 1.70 and up**.
### Building WebAssembly
Scryer Prolog has basic WebAssembly support. You can follow `wasm-pack`'s [official instructions](https://rustwasm.github.io/docs/wasm-pack/quickstart.html) to install `wasm-pack` and build it in any way you like.
@@ -170,57 +196,91 @@ Then a `pkg` directory will be created, containing everything you need for a web
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Scryer Prolog - Sudoku Solver Example</title>
<script type="module">
import init, { eval_code } from './pkg/scryer_prolog.js';
const run = async () => {
await init("./pkg/scryer_prolog_bg.wasm");
let code = `
:- use_module(library(format)).
:- use_module(library(clpz)).
:- use_module(library(lists)).
sudoku(Rows) :-
length(Rows, 9), maplist(same_length(Rows), Rows),
append(Rows, Vs), Vs ins 1..9,
maplist(all_distinct, Rows),
transpose(Rows, Columns),
maplist(all_distinct, Columns),
Rows = [As,Bs,Cs,Ds,Es,Fs,Gs,Hs,Is],
blocks(As, Bs, Cs),
blocks(Ds, Es, Fs),
blocks(Gs, Hs, Is).
blocks([], [], []).
blocks([N1,N2,N3|Ns1], [N4,N5,N6|Ns2], [N7,N8,N9|Ns3]) :-
all_distinct([N1,N2,N3,N4,N5,N6,N7,N8,N9]),
blocks(Ns1, Ns2, Ns3).
problem(1, [[_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,3,_,8,5],
[_,_,1,_,2,_,_,_,_],
[_,_,_,5,_,7,_,_,_],
[_,_,4,_,_,_,1,_,_],
[_,9,_,_,_,_,_,_,_],
[5,_,_,_,_,_,_,7,3],
[_,_,2,_,1,_,_,_,_],
[_,_,_,_,4,_,_,_,9]]).
main :-
problem(1, Rows), sudoku(Rows), maplist(portray_clause, Rows).
:- initialization(main).
`;
const result = eval_code(code);
document.write(`<p>Sudoku solver returns:</p><pre>${result}</pre>`);
<head>
<meta charset="UTF-8" />
<title>Scryer Prolog - Sudoku Solver Example</title>
<script type="module">
import initScryer, { MachineBuilder } from "./pkg/scryer_prolog.js";
// Initialize Scryer Prolog with WASM
const wasm = await fetch("./pkg/scryer_prolog_bg.wasm");
const module = await WebAssembly.compile(await wasm.arrayBuffer());
await initScryer(module);
// Set up the Prolog machine
const machine = new MachineBuilder().build();
// Knowledge base: Sudoku rules and problem definition
const kb = `
:- use_module(library(format)).
:- use_module(library(clpz)).
:- use_module(library(lists)).
sudoku(Rows) :-
length(Rows, 9), maplist(same_length(Rows), Rows),
append(Rows, Vs), Vs ins 1..9,
maplist(all_distinct, Rows),
transpose(Rows, Columns),
maplist(all_distinct, Columns),
Rows = [A,B,C,D,E,F,G,H,I],
blocks(A, B, C),
blocks(D, E, F),
blocks(G, H, I).
blocks([], [], []).
blocks([A,B,C|T1], [D,E,F|T2], [G,H,I|T3]) :-
all_distinct([A,B,C,D,E,F,G,H,I]),
blocks(T1, T2, T3).
problem(1, [[_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,3,_,8,5],
[_,_,1,_,2,_,_,_,_],
[_,_,_,5,_,7,_,_,_],
[_,_,4,_,_,_,1,_,_],
[_,9,_,_,_,_,_,_,_],
[5,_,_,_,_,_,_,7,3],
[_,_,2,_,1,_,_,_,_],
[_,_,_,_,4,_,_,_,9]]).
`;
machine.consultModuleString("user", kb);
// Run the query
const query = "problem(1, Rows), sudoku(Rows), maplist(portray_clause, Rows).";
const answers = machine.runQuery(query);
const formattedSolutions = [];
// Format the answers
for (const solution of answers) {
const rows = solution.bindings["Rows"].list;
const grid = rows.map(row =>
row.list.map(cell => cell.integer)
);
const formatted = grid.map(row => `[${row.join(", ")}]`).join("\n");
formattedSolutions.push(formatted);
}
run();
</script>
</head>
<body></body>
// Output results
const solutionDiv = document.querySelector("#soduku-solution");
for (const solution of formattedSolutions) {
const newPre = document.createElement("pre");
newPre.textContent = solution;
solutionDiv.appendChild(newPre);
}
</script>
</head>
<body>
<p>Sudoku solver returns:</p>
<div id="soduku-solution">
</div>
</body>
</html>
```
@@ -228,6 +288,12 @@ Then you can serve it with your favorite http server like `python -m http.server
### Docker Install
Pre-built [Docker images are available on Docker Hub](https://hub.docker.com/r/mjt128/scryer-prolog/tags).
The `latest` tag reflects the state on `master`, which might be unstable.
There are also tags for Scryer releases 0.9.2 and up.
Note though, that the base images are not kept up to date at the moment,
so be wary of security vulnerabilities (see [#2646](https://github.com/mthom/scryer-prolog/issues/2646)).
First, install [Docker](https://docs.docker.com/get-docker/) on Linux,
Windows, or Mac.
@@ -462,7 +528,7 @@ is unified with a term that contains that variable as a proper
subterm. For efficiency, the *occurs&nbsp;check* is omitted by default
in Scryer&nbsp;Prolog and many other Prolog systems.
In Scryer Prolog, performing unifications which succeed only if the
In Scryer Prolog, unifications which succeed only if the
*occurs&nbsp;check* is omitted yield *cyclic&nbsp;terms*, also called
*rational&nbsp;trees*. For example:
@@ -694,9 +760,13 @@ The modules that ship with Scryer&nbsp;Prolog are also called
public key signatures and signature verification with&nbsp;Ed25519,
ECDH key&nbsp;exchange over Curve25519 (X25519), authenticated symmetric
encryption with ChaCha20-Poly1305, and reasoning about elliptic curves.
* [`process`](src/lib/process.pl)
Create and manage parallel processes.
* [`uuid`](src/lib/uuid.pl) UUIDv4 generation and hex representation
* [`tls`](src/lib/tls.pl)
Predicates for negotiating TLS connections explicitly.
* [`numerics/special_functions`](src/lib/numerics/special_functions.pl)
Predicates for erf, gamma, beta, and related special functions.
* [`ugraphs`](src/lib/ugraphs.pl) Graph manipulation library
* [`simplex`](src/lib/simplex.pl) Providing `assignment/2`,
`transportation/4` and other predicates for solving linear
@@ -797,21 +867,46 @@ ideally suited for use in corporations and government&nbsp;agencies
that are subject to strict regulations pertaining to interoperability,
standards&nbsp;compliance and warranty.
Successful existing applications of Scryer Prolog include the
[DocLog](https://github.com/aarroyoc/doclog)&nbsp;system which
generates Scryer's own documentation and homepage, [Symbolic
Analysis of Grants](https://www.brz.gv.at/en/BRZ-Tech-Blog/Tech-Blog-7-Symbolic-Analysis-of-Grants.html)
by the Austrian Federal Computing Center, and parts of the
[precautionary](https://github.com/dcnorris/precautionary/tree/main/exec/prolog)
package for the analysis of dose-escalation trials in the
safety-critical and highly regulated domain of oncology
trial&nbsp;design, described in [*An Executable Specification of
Oncology Dose-Escalation Protocols with&nbsp;Prolog*](https://arxiv.org/abs/2402.08334).
Successful existing applications of Scryer Prolog include:
- [DocLog](https://github.com/aarroyoc/doclog) which generates
Scryer's own documentation and homepage
- [Grants4Companies](https://arxiv.org/abs/2406.15293): reasoning
about business&nbsp;grants in the Austrian public&nbsp;administration
- parts of the [precautionary](https://github.com/dcnorris/precautionary/tree/main/exec/prolog)
package for the analysis of dose-escalation trials in the
safety-critical and highly regulated domain of oncology
trial&nbsp;design, described in [*An Executable Specification of
Oncology Dose-Escalation Protocols with&nbsp;Prolog*](https://arxiv.org/abs/2402.08334)
and culminating in&nbsp;[**DEDUCTION**](https://codeberg.org/dcnorris/DEDUCTION)
- the core tax engine for VAT reasoning and compliance of the
Belgian&nbsp;company [VATmiraal](https://vatmiraal.be/)
- semantic reasoning and queries in [AD4M](https://github.com/coasys/ad4m),
an agent-centric distributed application meta-ontology.
Scryer Prolog is also very well suited for teaching and learning
Prolog, and for testing syntactic conformance and hence portability of
existing Prolog&nbsp;programs.
## Scryer Prolog Meetups
Scryer Prolog Meetups are an excellent opportunity to present and get
to know the latest developments in Scryer&nbsp;Prolog and its
applications, to exchange ideas about current&nbsp;plans and
future&nbsp;directions, and to discuss projects and visions
in&nbsp;person.
- [Scryer Prolog Meetup 2023](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2023/)
in Düsseldorf, Germany. Its [announcement](https://github.com/mthom/scryer-prolog/discussions/1813)
and [discussion](https://github.com/mthom/scryer-prolog/discussions/2160).
- [Scryer Prolog Meetup 2024](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2024)
in Vienna, Austria. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2377).
- [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/)
in Düsseldorf, Germany. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2948).
- **Attend, present and represent:** The [Scryer Prolog Meetup 2026](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2026.html)
will take place on Oct.&nbsp;24th and 25th&nbsp;2026 in Vienna, Austria.
Its [announcement](https://github.com/mthom/scryer-prolog/discussions/3327).
## Support and discussions
If Scryer Prolog crashes or yields unexpected errors, consider filing

35
_typos.toml Normal file
View File

@@ -0,0 +1,35 @@
# config for https://github.com/crate-ci/typos
[default]
# example from https://github.com/crate-ci/typos/blob/master/docs/reference.md#example-configurations
extend-ignore-re = [
"(#|//)\\s*spellchecker:ignore-next-line\\n.*"
]
# correct word key to value
# can be used to ignore a typo by adding an entry <typo> = "<typo>"
[default.extend-words]
# correct identifier key to value
# can be used to ignore a typo by adding an entry <typo> = "<typo>"
[default.extend-identifiers]
interm = "interm"
IntermReg = "IntermReg"
[type.rust]
extend-glob = [ "*.rs" ]
[type.rust.extend-identifiers]
consts = "consts" # std::{f32,f64}::consts
[type.prolog]
extend-glob = ["*.pl"]
check-file = false
[type.stdout]
extend-glob = ["*.stdout"]
check-file = false
[files]
extend-exclude = ["lib_integration_test_commands.txt"]

View File

@@ -49,7 +49,7 @@ once.
## Adding benchmarks
This design is meant to suppoort defining lots of benchmarks.
This design is meant to support defining lots of benchmarks.
To add a new benchmark:
@@ -77,7 +77,7 @@ Some tips:
cumbersome to run.
* Consider that the library runtime actually parses the text output of the top
level. So don't use custom outputs or it will fail to parse. Also keep the
output small so it doesn't just benchmark the ouput parsing code.
output small so it doesn't just benchmark the output parsing code.
* DO test the output of the benchmark run, we don't want to count broken
benchmarks.

View File

@@ -1,5 +1,5 @@
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[cfg(not(target_os = "windows"))]

View File

@@ -5,7 +5,7 @@ mod setup;
mod iai {
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
use scryer_prolog::machine::parsed_results::QueryResolution;
use scryer_prolog::LeafAnswer;
use super::setup;
@@ -13,7 +13,7 @@ mod iai {
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())]
fn bench(mut run: impl FnMut() -> QueryResolution) -> QueryResolution {
fn bench(mut run: impl FnMut() -> Vec<LeafAnswer>) -> Vec<LeafAnswer> {
run()
}

View File

@@ -1,10 +1,7 @@
use std::{collections::BTreeMap, fs, path::Path};
use maplit::btreemap;
use scryer_prolog::machine::{
parsed_results::{QueryResolution, Value},
Machine,
};
use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term};
pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
[
@@ -13,21 +10,21 @@ pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
"benches/edges.pl", // name of the prolog module file to load. use the same file in multiple benchmarks
"independent_set_count(ky, Count).", // query to benchmark in the context of the loaded module. consider making the query adjustable to tune the run time to ~0.1s
Strategy::Reuse,
btreemap! { "Count" => Value::try_from("2869176".to_string()).unwrap() },
btreemap! { "Count" => Term::integer(2869176) },
),
(
"numlist",
"benches/numlist.pl",
"run_numlist(1000000, Head).",
Strategy::Reuse,
btreemap! { "Head" => Value::try_from("1".to_string()).unwrap()},
btreemap! { "Head" => Term::integer(1) },
),
(
"csv_codename",
"benches/csv.pl",
"get_codename(\"0020\",Name).",
Strategy::Reuse,
btreemap! { "Name" => Value::try_from("SPACE".to_string()).unwrap()},
btreemap! { "Name" => Term::string("SPACE") },
),
]
.map(|b| {
@@ -51,12 +48,13 @@ pub enum Strategy {
Reuse,
}
#[allow(dead_code)]
pub struct PrologBenchmark {
pub name: &'static str,
pub filename: &'static str,
pub query: &'static str,
pub strategy: Strategy,
pub bindings: BTreeMap<&'static str, Value>,
pub bindings: BTreeMap<&'static str, Term>,
}
impl PrologBenchmark {
@@ -66,28 +64,34 @@ impl PrologBenchmark {
.file_stem()
.and_then(|s| s.to_str())
.unwrap();
let mut machine = Machine::new_lib();
let mut machine = MachineBuilder::default().build();
machine.load_module_string(module_name, program);
machine
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub fn setup(&self) -> impl FnMut() -> QueryResolution {
pub fn setup(&self) -> impl FnMut() -> Vec<LeafAnswer> + use<> {
let mut machine = self.make_machine();
let query = self.query;
move || {
use criterion::black_box;
black_box(machine.run_query(black_box(query.to_string()))).unwrap()
black_box(
machine
.run_query(black_box(query))
.collect::<Result<Vec<_>, _>>()
.unwrap(),
)
}
}
}
#[cfg(test)]
mod test {
#[test]
fn validate_benchmarks() {
use super::prolog_benches;
use scryer_prolog::machine::parsed_results::{QueryMatch, QueryResolution};
use scryer_prolog::LeafAnswer;
use std::{fmt::Write, fs};
struct BenchResult {
@@ -102,10 +106,13 @@ mod test {
let mut machine = r.make_machine();
let setup_inference_count = machine.get_inference_count();
let result = machine.run_query(r.query.to_string()).unwrap();
let result: Vec<_> = machine
.run_query(r.query)
.collect::<Result<_, _>>()
.unwrap();
let query_inference_count = machine.get_inference_count() - setup_inference_count;
let expected = QueryResolution::Matches(vec![QueryMatch::from(r.bindings.clone())]);
let expected = [LeafAnswer::from_bindings(r.bindings.clone())];
assert_eq!(result, expected, "validating benchmark {}", r.name);
results.push(BenchResult {

File diff suppressed because it is too large Load Diff

View File

@@ -4,41 +4,44 @@ mod static_string_indexing;
use instructions_template::generate_instructions_rs;
use static_string_indexing::index_static_strings;
use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::MAIN_SEPARATOR_STR;
use std::path::Path;
use std::path::PathBuf;
use std::process::{Command, Stdio};
fn find_prolog_files(libraries: &mut File, prefix: &str, current_dir: &Path) {
fn find_prolog_files(path_prefix: &str, current_dir: &Path) -> Vec<(String, PathBuf)> {
// use a BTreeMap to get a stable order independent of fs enumeration order
let mut libraries = BTreeMap::new();
let entries = match current_dir.read_dir() {
Ok(entries) => entries,
Err(_) => return,
Err(_) => return vec![],
};
for entry in entries.filter_map(Result::ok).map(|e| e.path()) {
if entry.is_dir() {
if let Some(file_name) = entry.file_name() {
let new_prefix = prefix.to_owned() + file_name.to_str().unwrap() + "/";
find_prolog_files(libraries, &new_prefix, &entry);
let file_name = file_name.to_str().unwrap();
let new_path_prefix = format!("{path_prefix}{file_name}/");
let new_libs = find_prolog_files(&new_path_prefix, &entry);
libraries.extend(new_libs);
}
} else if entry.is_file() {
let ext = std::ffi::OsStr::new("pl");
if entry.extension() == Some(ext) {
let contain = String::from_utf8(fs::read(&entry).unwrap()).unwrap();
let name = entry.file_stem().unwrap().to_str().unwrap();
let lib_name = format!("{path_prefix}{name}");
let line = format!(
" m.insert(\"{}\",\n{:?});\n",
prefix.to_owned() + name,
contain
);
libraries.write_all(line.as_bytes()).unwrap();
libraries.insert(lib_name, entry);
}
}
}
libraries.into_iter().collect()
}
fn main() {
@@ -56,18 +59,22 @@ fn main() {
let dest_path = Path::new(&out_dir).join("libraries.rs");
let mut libraries = File::create(dest_path).unwrap();
let lib_path = Path::new("src/lib");
let lib_path = Path::new("src").join("lib");
libraries
.write_all(
b"ref_thread_local::ref_thread_local! {
pub(crate) static managed LIBRARIES: IndexMap<&'static str, &'static str> = {
let mut m = IndexMap::new();\n",
let constants = find_prolog_files("", &lib_path);
let out_dir = std::env::var("OUT_DIR").unwrap();
writeln!(libraries, "{{").unwrap();
for (name, lib_path) in constants {
let path = format!("{}{}", MAIN_SEPARATOR_STR, lib_path.display());
writeln!(
libraries,
"m.insert(\"{name}\", include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), {path:?})));"
)
.unwrap();
find_prolog_files(&mut libraries, "", lib_path);
libraries.write_all(b"\n m\n };\n}\n").unwrap();
}
writeln!(libraries, "}}").unwrap();
let instructions_path = Path::new(&out_dir).join("instructions.rs");
let mut instructions_file = File::create(&instructions_path).unwrap();

View File

@@ -1,18 +1,18 @@
use std::collections::BTreeSet;
use proc_macro2::TokenStream;
use syn::parse::*;
use syn::visit::*;
use syn::*;
use indexmap::IndexSet;
struct StaticStrVisitor {
static_strs: IndexSet<String>,
static_strs: BTreeSet<String>,
}
impl StaticStrVisitor {
fn new() -> Self {
Self {
static_strs: IndexSet::new(),
static_strs: BTreeSet::new(),
}
}
}
@@ -84,6 +84,18 @@ impl<'ast> Visit<'ast> for StaticStrVisitor {
}
}
const INLINED_ATOM_MAX_LEN: usize = 6;
fn static_string_index(string: &str, index: usize) -> u64 {
if !string.is_empty() && string.len() <= INLINED_ATOM_MAX_LEN && !string.contains('\u{0}') {
let mut string_buf: [u8; 8] = [0u8; 8];
string_buf[..string.len()].copy_from_slice(string.as_bytes());
(u64::from_le_bytes(string_buf) << 1) | 1
} else {
(index << 1) as u64
}
}
pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStream {
use quote::*;
@@ -114,14 +126,19 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
match file.read_to_string(&mut src) {
Ok(_) => {}
Err(e) => {
panic!("error reading file: {:?}", e);
panic!("error reading file: {e:?}");
}
}
let syntax = match syn::parse_file(&src) {
Ok(s) => s,
Err(e) => {
panic!("parse error: {} in file {:?}", e, path);
println!("cargo::warning=parse error: {e} in file {path:?}");
syn::File {
shebang: None,
attrs: vec![],
items: vec![],
}
}
};
Ok(syntax)
@@ -149,28 +166,42 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
visitor.visit_file(&syntax)
}
let indices = (0..visitor.static_strs.len()).map(|i| (i << 3) as u64);
let indices_iter = indices.clone();
let static_str_keys: Vec<_> = visitor.static_strs.iter().collect();
let mut static_strs = Vec::with_capacity(static_str_keys.len());
let mut static_str_indices = Vec::with_capacity(static_str_keys.len());
let static_strs_len = visitor.static_strs.len();
let static_strs: &Vec<_> = &visitor.static_strs.into_iter().collect();
let indices: Vec<u64> = visitor
.static_strs
.iter()
.map(|string| {
let index = static_string_index(string, static_strs.len());
if index & 1 == 1 {
index
} else {
static_str_indices.push(index);
static_strs.push(string);
index
}
})
.collect();
let static_strs_len = static_strs.len();
quote! {
use phf;
static STRINGS: [&str; #static_strs_len] = [
#(
#static_strs,
)*
];
#[macro_export]
macro_rules! atom {
#((#static_strs) => { Atom { index: #indices_iter } };)*
#((#static_str_keys) => { Atom { index: #indices } };)*
($name:literal) => {compile_error!(concat!("unknown static atom ", $name))};
}
pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {
#(#static_strs => { Atom { index: #indices } },)*
#(#static_strs => { Atom { index: #static_str_indices } },)*
};
}
}

26
clippy.toml Normal file
View File

@@ -0,0 +1,26 @@
disallowed-macros = [
# https://rust-lang.github.io/rust-clippy/master/#disallowed_macros
# list of macros that may panic on allocation failure e.g.
# "std::vec",
]
disallowed-methods = [
# https://rust-lang.github.io/rust-clippy/master/#disallowed_method
# list of methods that may panic on allocation failure
# though not including things that can be used correctly by reversing ahead of time (i.e. std::vec::Vec::try_reserve + std::iter::Extend::extend ).
# "std::iter::Iter::collect",
# { path = "std::vec::Vec::with_capacity", replacement = "std::vec::Vec::new + std::vec::Vec::try_reserve" },
# { path = "std::string::String::with_capacity", replacement = "std::string::String::new + std::string::String::try_reserve" },
]
disallowed-types = [
# https://rust-lang.github.io/rust-clippy/master/#disallowed_types
# list of types that can't be used without risking a panic due to allocation failure
# { path = "std::collections::BTreeMap", reason = "unlike Vec and HashMap BTreeMap cannot reserve capacity ahead of time (i.e. try_reserve) making it unusable without risk of oom panic"},
]

13
doclog.config.pl Normal file
View File

@@ -0,0 +1,13 @@
project_name("Scryer Prolog").
readme_file("INDEX.dj").
source_lib_folder("src/lib").
websource("https://github.com/mthom/scryer-prolog/tree/master/src/lib").
omit(["ops_and_meta_predicates.pl", "tabling"]).
learn_pages_source_folder("learn").
learn_pages_categories(["First steps", "Tutorials"]).
learn_pages([
page("Let's play Brisca", "Tutorials", "lets-play-brisca.dj")
]).
copy_file("logo/scryer.png", "scryer.png").
copy_file("learn/Spanish_deck_Fournier.jpg", "learn/Spanish_deck_Fournier.jpg").
copy_file("learn/brisca-interactive.png", "learn/brisca-interactive.png").

82
flake.lock generated Normal file
View File

@@ -0,0 +1,82 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1744868846,
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1745289264,
"narHash": "sha256-7nt+UJ7qaIUe2J7BdnEEph9n2eKEwxUwKS/QIr091uA=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "3b7171858c20d5293360042936058fb0c4cb93a9",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

85
flake.nix Normal file
View File

@@ -0,0 +1,85 @@
{
description = "A modern Prolog implementation written mostly in Rust";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { nixpkgs, flake-utils, rust-overlay, ... }:
let
meta = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package;
inherit (meta) name version;
overlays = [
(import rust-overlay)
(self: super: {
rustToolchainDev = super.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" ];
};
rustToolchainDevWasm = super.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" ];
targets = [ "wasm32-unknown-unknown" ];
};
rustToolchainNightly = super.rust-bin.selectLatestNightlyWith (toolchain:
toolchain.default.override {
extensions = [ "rust-src" "rust-analyzer" "miri" ];
}
);
})
];
in flake-utils.lib.eachDefaultSystem(system:
let
pkgs = import nixpkgs { inherit system overlays; };
nativeBuildInputs = with pkgs; [ pkg-config ];
buildInputs = with pkgs; [ openssl ] ++
lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.apple_sdk.frameworks.SystemConfiguration
];
in
{
devShells = {
default = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } {
nativeBuildInputs = nativeBuildInputs;
buildInputs = buildInputs ++ (with pkgs; [
rustToolchainDev
]);
};
wasm-js = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } {
nativeBuildInputs = nativeBuildInputs;
buildInputs = buildInputs ++ (with pkgs; [
wasm-pack
rustToolchainDevWasm
]);
TARGET_CC = "${pkgs.clangMultiStdenv.cc}/bin/clang";
hardeningDisable = [ "all" ];
};
# For use with Miri and stuff like it
nightly = pkgs.mkShell {
nativeBuildInputs = nativeBuildInputs;
buildInputs = buildInputs ++ (with pkgs; [
rustToolchainNightly
]);
};
};
packages = rec {
default = scryer-prolog;
scryer-prolog = pkgs.rustPlatform.buildRustPackage {
pname = name;
inherit version;
src = ./.;
nativeBuildInputs = nativeBuildInputs;
buildInputs = buildInputs;
cargoLock = {
lockFile = ./Cargo.lock;
};
release = true;
};
};
}
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

457
learn/lets-play-brisca.dj Normal file
View File

@@ -0,0 +1,457 @@
# Let's play Brisca
In this article, we'll see how to use modern Prolog to model the game of [Brisca](https://en.wikipedia.org/wiki/Brisca). First things first, what is Brisca?
## Rules of the game
Brisca is a traditional card game from Spain, although similar games both in name and in rules are spread across the Mediterranean. It's also similar to Tute. It is played using the traditional Spanish deck, usually with 40 cards in 4 suites (oros, espadas, bastos, copas). Here you have the famous Castillian pattern by Heraclio Fournier, the one I play in my family with my grandparents.
![Heraclio Fournier Spanish cards](Spanish_deck_Fournier.jpg)
How do you play it? Basically, players play in rounds. Every round each player on the table (usually four), selects one of his own three cards to leave one in the middle, and they do that in order. There are some rules, which I will explain later, and one person wins the round, taking all the cards with him. Players take new cards from the stock. Some cards get you points, some others don't. After there are no more cards in player's hands, we count the points, the players with more points, wins!
The rules for knowing which players takes the round are the following:
- Every game there's a trump suite, which was selected as a random card from the stock at the same time when the players got their three initial cards.
- The first player in a round (the winner of the previous round) can choose whatever card he wants.
- The rest of the players, in order to win the round, they need to improve the card of the first player. They can put a higher card of the same suite as the first player.
- Or they can put a card from the trump suite, which is always better than a card from a non-trump suite.
- But between cards in the trump suite, there's still an order.
- However players are not required to put cards that improve the game if they don't wish to do that, they can just lose the round to discard themselves a bad card.
- The order is the following: As (ace, number 1) -> 3 -> Rey (king, number 12) -> Caballo (horse, but usually it's a knight, number 11) -> Sota (a page, number 10) -> 7 -> 6 -> 5 -> 4 -> 2.
## Modeling cards
First, we need to decide a representation of our cards. Coming from another languages we can think that a good representation might be a class or a struct, with two fields, one for the number and the other for the suite, but Prolog doesn't have objects. We can use a list with two elements. But lists are better when we're dealing with variable length data. We could also use a compound term. This is the right choice if our fields are fixed.
A compound term is defined by an atom, followed by the data itself enclosed by parenthesis and separated by comma. Like this: `card(oros, 4)`. Yes, very similar to predicates. In fact the only difference is how we use them, because they're the same. If we pass a compound term in the first level of a query, or inside a call/N, Prolog will treat it as code instead of data. This is one the the examples of Prolog being a homoiconic language.
We can go further, Prolog is very flexible and we can define custom operators easily if we want. Those are also compound terms, but with a different syntax. There's an operator already defined that is very useful for us: the dash. We can just join two pieces of data with a dash, and they'll be together in the same structure. This is usually called "pair".
So, using pairs we can model cards like this: `oros-4`, `espadas-7`.
We can code a predicate that defines valid cards:
```
card(Suite-Number) :-
member(Suite, [oros, espadas, bastos, copas]),
member(Number, [rey, caballo, sota, 7, 6, 5, 4, 3, 2, as]).
```
## Counting points
We are going to implement a counting predicate. The suite doesn't matter, only the number. First, we define a `card_score/2` predicate which relates a card to a score and viceversa.
```
card_score(_-as, 11).
card_score(_-3, 10).
card_score(_-rey, 4).
card_score(_-caballo, 3).
card_score(_-sota, 2).
card_score(_-X, 0) :- member(X, [7, 6, 5, 4, 2]).
```
Now, if we want to know how many points gives an specific card, we can ask:
```
?- card_score(oros-rey, X).
X = 4
; false.
```
(Remember, to load a file named brisca.pl in Scryer Prolog, we can run `scryer-prolog brisca.pl` in your favourite shell.)
But we can also ask what cards can give you that value:
```
?- card(X), card_score(X, 11).
X = oros-as
; X = espadas-as
; X = bastos-as
; X = copas-as.
```
You have probably noticed that I included the card predicate in the toplevel (which kind of acts like a type restriction), not in the `card_score/2`. However, does it make sense for card_score to work on something that is not a also a card? Both approaches are valid if applied correctly. Some people prefer to set domain restrictions like this externally, as it allows you to write more concise code and it usually performs better. However, having a card predicate in `card_score/2` is more correct since it's not possible to use that predicate in something that is not a card. We can choose either option thanks to Prolog being a dynamic language.
In my case, my final version is going to be a predicate which includes the card restriction and uses a wrapper predicate to have the short and concise code:
```
card_score(X, N) :-
card(X),
card_score_(X, N).
card_score_(_-as, 11).
card_score_(_-3, 10).
card_score_(_-rey, 4).
card_score_(_-caballo, 3).
card_score_(_-sota, 2).
card_score_(_-X, 0) :- member(X, [7, 6, 5, 4, 2]).
```
Now we just need to work with a list of cards instead of a single card. This is a place where we can introduce DCGs. DCGs are the short name for [Definite Clause Grammars](https://www.metalevel.at/prolog/dcg), which is a shorthand notation used to describe sequences. We can use them to parse, generate, complete and check sequences in the form of lists.
We can define a base case. If there are no more elements in the sequence, the score should be zero. If there's a Card, we can calculate its score and sum it together with the rest:
```
cards_score_(0) --> [].
cards_score_(X) -->
[Card],
{ card_score(Card, X0), #X #= #X0 + #X1 },
cards_score_(X1).
```
In DCGs, to match an item of the sequence, we use brackets. We use braces to introduce normal Prolog code. Calling other DCGs (in this case, the same, as it's a recursive one), it's just calling it again. Notice in this code that we are doing the addition of X0 and X1 when we still don't know the value of X1. This would be an error in the traditional arithmetic system of Prolog, but it's valid with clpz. clpz allows us to have a more declarative arithmetic, at least with integers.
Now we can try this code using `phrase/2` which is needed to jump to a DCG.
```
?- phrase(cards_score_(X), [oros-as, oros-rey, oros-7]).
X = 15
; false.
```
It seems to work. However there's a small problem. if we try to do the reverse, generating a sequence of cards that give you a specific amount of points, we'll find trouble. The program won't end. This is basically because be default we are not fair enumerating. This means that it will go deep into the recursion, trying to create an infinite length sequence. We need a way so that it does iterative deepening (starts with sequences of length 1, then length 2, ...). Luckily, if we pass our list first through the `length/2` predicate, it will do exactly that. This is usually done at the outside.
```
cards_score(Cards, Score) :-
phrase(cards_score_(Score), Cards).
cards_score_(0) --> [].
cards_score_(X) -->
[Card],
{ card_score(Card, X0), #X #= #X0 + #X1 },
cards_score_(X1).
```
So now:
```
?- length(X, _), cards_score(X, 15).
X = [oros-rey,oros-as]
; X = [oros-rey,espadas-as]
; X = [oros-rey,bastos-as]
; X = [oros-rey,copas-as]
; X = [oros-as,oros-rey]
; X = [oros-as,espadas-rey]
; X = [oros-as,bastos-rey]
; ...
```
```
?- cards_score(X, Y).
X = [], Y = 0
; X = [oros-rey], Y = 4
; X = [oros-caballo], Y = 3
; X = [oros-sota], Y = 2
; X = [oros-7], Y = 0
; X = [oros-6], Y = 0
; X = [oros-5], Y = 0
; X = [oros-4], Y = 0
; X = [oros-3], Y = 10
; X = [oros-2], Y = 0
; ...
```
## Winner of a round
Now, let's try to model the winner of a round in the game. First, we can model the pseudo-numerical order of the cards in a suite. We can start with a simple idea, if we have a list with the right order, we can take an element and see if the other card is in the higher section of the rest of the list.
```
card_higher_n(N0, N1) :-
Order = [as, 3, rey, caballo, sota, 7, 6, 5, 4, 2],
append(Highers, [N0|_], Order),
member(N1, Highers).
```
This works well, and it can show all of the 'Y is higher than X' relationships:
```
?- card_higher_n(X, Y).
X = 3, Y = as
; X = rey, Y = as
; X = rey, Y = 3
; X = caballo, Y = as
; X = caballo, Y = 3
; ...
```
For some people this might be enough. However, since we're in Scryer Prolog, we can also use reificated predicates. Using this technique, we can show not just the 'Y is higher than X' but also, the complete set of solutions of 'Y is NOT higher than X'. In order to do that we need to add a third argument to our predicate, which will be true or false (the predicate is true for the given X and Y or it's false). And we need to change `member/2` to a reified version of it: `memberd_t/3`.
```
card_higher_n(N0, N1, T) :-
Order = [as, 3, rey, caballo, sota, 7, 6, 5, 4, 2],
append(Highers, [N0|_], Order),
memberd_t(N1, Highers, T).
```
Now, we get for every combination a T truth value and also, in the case of false queries, we get the constraints that need to be held to get that result:
```
?- card_higher_n(X, Y, T).
X = as, T = false
; X = 3, Y = as, T = true
; X = 3, T = false, dif:dif(as,Y)
; X = rey, Y = as, T = true
; X = rey, Y = 3, T = true
; X = rey, T = false, dif:dif(3,Y), dif:dif(as,Y)
; X = caballo, Y = as, T = true
; X = caballo, Y = 3, T = true
; X = caballo, Y = rey, T = true
; X = caballo, T = false, dif:dif(3,Y), dif:dif(as,Y), dif:dif(rey,Y)
```
For example, if X = 3 and Y = as, then Y is higher than X. But if Y is different from as, then it's false.
Using reified predicates can be wise if we want to raise ourselves to a more declarative way of working with Prolog, but it's newer and not applicable form some problems. In this case, it's fine, so we'll use them.
Now, to model the relatioships of whole cards, we need to take into account the suites too. We're going to compare the suites, if they're the same, we just apply the pseudonumerical order. If they're different we check if the suite of the supposedly higher card is the trump suite. We can continue to use reified predicates from `library(reif)` like `if_/3`, which takes a reified predicate as a condition and `=/3` which does reified unification. We also expose our T truth argument in this predicate:
```
card_higher(Trump, Card, Higher, T) :-
card(Card),
card(Higher),
Card = S0-N0,
Higher = S1-N1,
if_(S0 = S1,
card_higher_n(N0, N1, T),
=(S1, Trump, T)
).
```
The code allows us to generate every card combination with every trump value and their truth value:
```
?- card_higher(Trump, X, Y, T).
X = oros-rey, Y = oros-rey, T = false
; X = oros-rey, Y = oros-caballo, T = false
; X = oros-rey, Y = oros-sota, T = false
; X = oros-rey, Y = oros-7, T = false
; X = oros-rey, Y = oros-6, T = false
; X = oros-rey, Y = oros-5, T = false
; X = oros-rey, Y = oros-4, T = false
; X = oros-rey, Y = oros-3, T = true
; X = oros-rey, Y = oros-2, T = false
; X = oros-rey, Y = oros-as, T = true
; Trump = espadas, X = oros-rey, Y = espadas-rey, T = true
; X = oros-rey, Y = espadas-rey, T = false, dif:dif(espadas,Trump)
; Trump = espadas, X = oros-rey, Y = espadas-caballo, T = true
; X = oros-rey, Y = espadas-caballo, T = false, dif:dif(espadas,Trump)
; Trump = espadas, X = oros-rey, Y = espadas-sota, T = true
; X = oros-rey, Y = espadas-sota, T = false, dif:dif(espadas,Trump)
```
For example a espadas-caballo is higher than oros-rey if Trump = espadas, otherwise, it isn't.
Now, let's check for the winner. For every card we need to check if the current one is higher value than the current highest. It's very easy, we just need to to do a fold! Just like in functional programming, we have a fold _predicate_. Actually, it's a fold-left, but for this case we need the right order, so we will need to reverse the list first. The, for our foldl, we're going to use a lambda predicate from `library(lambda)`.
This library, allows us to write unnamed predicates that are very useful in metapredicates like `foldl/4`. The syntax might be a bit confusing if you're coming from other languages as it's a bit unique. You can find the complete description of the syntax in the [documentation page](https://www.scryer.pl/lambda.html).
The code looks like this:
```
round_winner(Cards, Trump, WinnerCard) :-
reverse(Cards, [FirstCard|RestCards]),
foldl(Trump+\X^Y^Z^if_(card_higher(Trump, X, Y), Y = Z, X = Z), RestCards, FirstCard, WinnerCard).
```
```
?- length(Cards, 4), round_winner(Cards, Trump, Winner).
Cards = [oros-rey,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-caballo,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-sota,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-7,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-6,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-5,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-4,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-3,oros-rey,oros-rey,oros-rey], Winner = oros-3
; Cards = [oros-2,oros-rey,oros-rey,oros-rey], Winner = oros-rey
; Cards = [oros-as,oros-rey,oros-rey,oros-rey], Winner = oros-as
; Cards = [espadas-rey,oros-rey,oros-rey,oros-rey], Trump = oros, Winner = oros-rey
; Cards = [espadas-rey,oros-rey,oros-rey,oros-rey], Winner = espadas-rey, dif:dif(oros,Trump)
```
# A complete game
Now we have the basic pieces of the game. But we don't have a complete game. A game starts with someone dealing the first three cards to each player, a trump suite is selected, then we start the rounds: players put one card each, we choose a winner, the winner takes the cards and players get a new card from the stock. This all seems very procedural, but we're using Prolog. What can we do about it?
Let's ask ourselves what is a procedure. It's a sequence. And we have already seen a way to describe sequences in Prolog! The DCGs.
The basic idea however is having _explicit_ states. The predicates that we're going to write will take an state (a view of the world at a certain point) and will give us the next state.
Let's define the state first. In a game of Brisca we have players. Each player has the three cards he can choose to put (less if we're running out of cards) and the cards he has got from winning rounds. Additionally we have a stock, a trump suite and the order to play, which is usually from the player who won the last round and going to the right. We could store the data in a list, with different compound terms:
```
[players([player(Name, PlayableCards, WonCards), player(Name, PlayableCards, WonCards), ...]), stock(Cards), trump(Trump)]
```
In order to express states using DCGs, we can use [semicontext notation](https://www.metalevel.at/prolog/dcg#semicontext):
```
state(S), [S] --> [S].
state(S0, S), [S] --> [S0].
```
So, for example our predicate to reset the game, and leave all the different cards in the stock would like this:
```
reset -->
state(_, [stock(Cards)]),
{
setof(Card, card(Card), Cards)
}.
```
To shuffle them:
```
shuffle_cards -->
state(S0, S),
{
select(stock(Cards), S0, S1),
shuffle(Cards, ShuffledCards),
S = [stock(ShuffledCards)|S1]
}.
```
Where `shuffle/2` is a predicate that implements the Fisher-Yates algorithm in Prolog:
```
shuffle([], []).
shuffle(Xs0, [Y|Ys]) :-
length(Xs0, N),
random_integer(0, N, R),
nth0(R, Xs0, Y, Xs),
shuffle(Xs, Ys).
```
We can also code more specific state accessors, which could be useful to know which part of the state each predicate can modify:
```
players(P0, P), [S] -->
[S0],
{ select(players(P0), S0, S1), S = [players(P)|S1] }.
```
The whole state management topic in DCGs could be the whole topic for another article.
The final DCG code for an interactive version of Brisca could be as follows:
```
brisca :-
phrase(brisca, [_], [_]).
brisca -->
reset,
shuffle_cards,
set_trump,
create_players([aarroyoc, xijinping, donalddtrump, vonderleyen]),
play_rounds,
show_scores.
reset -->
state(_, [stock(Cards)]),
{
setof(Card, card(Card), Cards)
}.
shuffle_cards -->
state(S0, S),
{
select(stock(Cards), S0, S1),
shuffle(Cards, ShuffledCards),
S = [stock(ShuffledCards)|S1]
}.
set_trump -->
state(S0, S),
{
member(stock(Cards), S0),
length(Cards, N),
nth1(N, Cards, LastCard),
LastCard = Trump-_,
S = [trump(Trump)|S0]
}.
create_players(Names) -->
state(S0, S),
{
same_length(Players, Names),
maplist(\N^X^(X=player(N, [], [])), Names, Players),
S = [players(Players)|S0]
},
deal_one_card_per_player,
deal_one_card_per_player,
deal_one_card_per_player.
deal_one_card_per_player -->
state(S0, S),
{
select(players(Players), S0, S1),
select(stock(Cards), S1, S2),
deal_one_card_per_player(Players, Players1, Cards, Cards1),
S = [players(Players1), stock(Cards1)|S2]
}.
deal_one_card_per_player([], [], Cs, Cs).
deal_one_card_per_player(Ps, Ps, [], []).
deal_one_card_per_player([P|Ps], [P1|Ps1], [C|Cs], Cs1) :-
P = player(N, A0, B0),
P1 = player(N, [C|A0], B0),
deal_one_card_per_player(Ps, Ps1, Cs, Cs1).
play_rounds -->
players(P, P),
{ P = [player(_, X, _)|_], length(X, 0) }.
play_rounds -->
players(P, P),
{ P = [player(_, X, _)|_], length(X, N), N > 0 },
play_round,
play_rounds.
play_round -->
state(S),
players(P0, P2),
{
member(trump(Trump), S),
format("Brisca round~nTrump is: ~a~n~n", [Trump])
},
play_players(P0, Cards),
{
round_winner(Cards, Trump, WinnerCard),
maplist(remove_card, P0, Cards, P1),
nth0(N, Cards, WinnerCard),
nth0(N, P1, WinnerPlayer0),
append(PBefore, [WinnerPlayer0|PAfter], P1),
WinnerPlayer0 = player(WinnerName, C, W0),
format("Winner card is ~w from ~w~n", [WinnerCard, WinnerName]),
append(W0, Cards, W1),
append([player(WinnerName, C, W1)|PAfter], PBefore, P2)
},
deal_one_card_per_player.
remove_card(P0, C, P) :-
P0 = player(N, C0, W),
select(C, C0, C1),
P = player(N, C1, W).
play_players([], []) --> [].
play_players([P|Ps], [C|Cs]) -->
{
P = player(Name, SelectableCards, _),
format("It's ~a's turn!~n", [Name]),
format("Selectable cards: ~w~n", [SelectableCards]),
read(C),
member(C, SelectableCards)
},
play_players(Ps, Cs).
play_players(Ps, Cs) --> play_players(Ps, Cs).
show_scores -->
players(P, P),
{
maplist(\X^(
X=player(N,_,Z),
cards_score(Z, Y),
format("Score of ~w is ~d~n", [N, Y])), P)
}.
```
Now, we can play! Of course, if you substitute interactive choosing (via `read/1`) with another method, you could simulate games, try AI strategies, etc
![Playing a game of Brisca](brisca-interactive.png)

View File

@@ -14,7 +14,7 @@ pub(crate) trait Allocator {
lvl: Level,
context: GenContext,
code: &mut CodeDeque,
);
) -> RegType;
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
&mut self,

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ use crate::debray_allocator::*;
use crate::forms::*;
use crate::instructions::*;
use crate::iterators::*;
use crate::offset_table::*;
use crate::targets::QueryInstruction;
use crate::types::*;
@@ -16,10 +17,10 @@ use crate::machine::machine_errors::*;
use dashu::base::Abs;
use dashu::base::BitTest;
use num_order::NumOrd;
use ordered_float::*;
use ordered_float::{Float, OrderedFloat};
use std::cell::Cell;
use std::cmp::{max, min, Ordering};
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::f64;
use std::num::FpCategory;
@@ -28,21 +29,11 @@ use std::vec::Vec;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ArithmeticTerm {
IntermReg(usize),
Reg(RegType),
Interm(usize),
Number(Number),
}
impl ArithmeticTerm {
pub(crate) fn interm_or(&self, interm: usize) -> usize {
if let &ArithmeticTerm::Interm(interm) = self {
interm
} else {
interm
}
}
}
impl Default for ArithmeticTerm {
fn default() -> Self {
ArithmeticTerm::Number(Number::default())
@@ -54,7 +45,7 @@ pub(crate) struct ArithInstructionIterator<'a> {
state_stack: Vec<TermIterState<'a>>,
}
pub(crate) type ArithCont = (CodeDeque, Option<ArithmeticTerm>);
pub(crate) type ArithCont = (CodeDeque, ArithmeticTerm);
impl<'a> ArithInstructionIterator<'a> {
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
@@ -73,7 +64,7 @@ impl<'a> ArithInstructionIterator<'a> {
return Err(ArithmeticError::NonEvaluableFunctor(
Literal::Atom(atom!(".")),
2,
))
));
}
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Shallow, cell, var_ptr.clone()),
};
@@ -86,8 +77,8 @@ impl<'a> ArithInstructionIterator<'a> {
#[derive(Debug)]
pub(crate) enum ArithTermRef<'a> {
Literal(&'a Literal),
Op(Atom, usize), // name, arity.
Literal(Literal),
Op(Level, &'a Cell<RegType>, Atom, usize), // name, arity.
Var(Level, &'a Cell<VarReg>, VarPtr),
}
@@ -102,7 +93,7 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
let arity = subterms.len();
if child_num == arity {
return Some(Ok(ArithTermRef::Op(name, arity)));
return Some(Ok(ArithTermRef::Op(lvl, cell, name, arity)));
} else {
self.state_stack.push(TermIterState::Clause(
lvl,
@@ -115,7 +106,7 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
self.push_subterm(lvl.child_level(), &subterms[child_num]);
}
}
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(c))),
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(*c))),
TermIterState::Var(lvl, cell, var_ptr) => {
return Some(Ok(ArithTermRef::Var(lvl, cell, var_ptr)));
}
@@ -136,7 +127,6 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
pub(crate) struct ArithmeticEvaluator<'a> {
marker: &'a mut DebrayAllocator,
interm: Vec<ArithmeticTerm>,
interm_c: usize,
}
pub(crate) trait ArithmeticTermIter<'a> {
@@ -157,7 +147,9 @@ fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), Ari
match c {
Literal::Fixnum(n) => interm.push(ArithmeticTerm::Number(Number::Fixnum(*n))),
Literal::Integer(n) => interm.push(ArithmeticTerm::Number(Number::Integer(*n))),
Literal::Float(n) => interm.push(ArithmeticTerm::Number(Number::Float(*n.as_ptr()))),
&Literal::F64(_offset, n) => {
interm.push(ArithmeticTerm::Number(Number::Float(n)));
}
Literal::Rational(n) => interm.push(ArithmeticTerm::Number(Number::Rational(*n))),
Literal::Atom(name) if name == &atom!("e") => interm.push(ArithmeticTerm::Number(
Number::Float(OrderedFloat(std::f64::consts::E)),
@@ -166,7 +158,7 @@ fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), Ari
Number::Float(OrderedFloat(std::f64::consts::PI)),
)),
Literal::Atom(name) if name == &atom!("epsilon") => interm.push(ArithmeticTerm::Number(
Number::Float(OrderedFloat(std::f64::EPSILON)),
Number::Float(OrderedFloat(f64::EPSILON)),
)),
_ => return Err(ArithmeticError::NonEvaluableFunctor(*c, 0)),
}
@@ -175,11 +167,10 @@ fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), Ari
}
impl<'a> ArithmeticEvaluator<'a> {
pub(crate) fn new(marker: &'a mut DebrayAllocator, target_int: usize) -> Self {
pub(crate) fn new(marker: &'a mut DebrayAllocator) -> Self {
ArithmeticEvaluator {
marker,
interm: Vec::new(),
interm_c: target_int,
}
}
@@ -247,56 +238,37 @@ impl<'a> ArithmeticEvaluator<'a> {
}
}
fn incr_interm(&mut self) -> usize {
let temp = self.interm_c;
self.interm.push(ArithmeticTerm::Interm(temp));
self.interm_c += 1;
temp
fn try_add_to_free_list(&mut self, a1: ArithmeticTerm) {
if let ArithmeticTerm::IntermReg(t) = a1 {
self.marker.add_reg_to_free_list(RegType::Temp(t));
}
}
fn instr_from_clause(
&mut self,
name: Atom,
arity: usize,
arg: usize,
) -> Result<Instruction, ArithmeticError> {
match arity {
1 => {
let a1 = self.interm.pop().unwrap();
let ninterm = if a1.interm_or(0) == 0 {
self.incr_interm()
} else {
self.interm.push(a1);
a1.interm_or(0)
};
self.interm.push(ArithmeticTerm::IntermReg(arg));
self.try_add_to_free_list(a1);
self.get_unary_instr(name, a1, ninterm)
self.get_unary_instr(name, a1, arg)
}
2 => {
let a2 = self.interm.pop().unwrap();
let a1 = self.interm.pop().unwrap();
let min_interm = min(a1.interm_or(0), a2.interm_or(0));
self.interm.push(ArithmeticTerm::IntermReg(arg));
let ninterm = if min_interm == 0 {
let max_interm = max(a1.interm_or(0), a2.interm_or(0));
self.try_add_to_free_list(a1);
self.try_add_to_free_list(a2);
if max_interm == 0 {
self.incr_interm()
} else {
self.interm.push(ArithmeticTerm::Interm(max_interm));
self.interm_c = max_interm + 1;
max_interm
}
} else {
self.interm.push(ArithmeticTerm::Interm(min_interm));
self.interm_c = min_interm + 1;
min_interm
};
self.get_binary_instr(name, a1, a2, ninterm)
self.get_binary_instr(name, a1, a2, arg)
}
_ => Err(ArithmeticError::NonEvaluableFunctor(
Literal::Atom(name),
@@ -315,7 +287,7 @@ impl<'a> ArithmeticEvaluator<'a> {
for term_ref in src.iter()? {
match term_ref? {
ArithTermRef::Literal(c) => push_literal(&mut self.interm, c)?,
ArithTermRef::Literal(c) => push_literal(&mut self.interm, &c)?,
ArithTermRef::Var(lvl, cell, name) => {
let var_num = name.to_var_num().unwrap();
@@ -341,47 +313,61 @@ impl<'a> ArithmeticEvaluator<'a> {
self.interm.push(ArithmeticTerm::Reg(r));
}
ArithTermRef::Op(name, arity) => {
code.push_back(self.instr_from_clause(name, arity)?);
ArithTermRef::Op(lvl, cell, name, arity) => {
self.marker
.mark_non_var::<QueryInstruction>(lvl, term_loc, cell, &mut code);
if let RegType::Temp(t) = cell.get() {
code.push_back(self.instr_from_clause(name, arity, t)?);
} else {
unreachable!()
}
}
}
}
Ok((code, self.interm.pop()))
Ok((code, self.interm.pop().unwrap()))
}
}
// integer division rounding function -- 9.1.3.1.
pub(crate) fn rnd_i(n: &'_ Number, arena: &mut Arena) -> Number {
pub(crate) fn rnd_i(n: &'_ Number, arena: &mut Arena) -> Result<Number, EvalError> {
match n {
&Number::Integer(i) => {
let result = (&*i).try_into();
if let Ok(value) = result {
fixnum!(Number, value, arena)
if let Ok(value) = Fixnum::build_with_checked(&*i) {
Ok(Number::Fixnum(value))
} else {
*n
Ok(*n)
}
}
Number::Fixnum(_) => *n,
Number::Fixnum(_) => Ok(*n),
&Number::Float(f) => {
let f = f.floor();
const I64_MIN_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MIN as f64);
const I64_MAX_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MAX as f64);
const FIXNUM_MIN_TO_F: OrderedFloat<f64> = OrderedFloat(Fixnum::MIN as f64);
const FIXNUM_MAX_TO_F: OrderedFloat<f64> = OrderedFloat(Fixnum::MAX as f64);
if I64_MIN_TO_F <= f && f <= I64_MAX_TO_F {
fixnum!(Number, f.into_inner() as i64, arena)
if (FIXNUM_MIN_TO_F..=FIXNUM_MAX_TO_F).contains(&f) {
Ok(Number::Fixnum(
// Safety: We checked that the value is in range
unsafe { Fixnum::build_with_unchecked(f.into_inner() as i64) },
))
} else {
Number::Integer(arena_alloc!(Integer::from(f.0 as i64), arena))
Ok(Number::Integer(arena_alloc!(
Integer::try_from(classify_float(f.0)?).unwrap_or_else(|_| {
unreachable!();
}),
arena
)))
}
}
Number::Rational(ref r) => {
let (_, floor) = (r.fract(), r.floor());
Number::Rational(r) => {
let floor = r.floor();
if let Ok(value) = (&floor).try_into() {
fixnum!(Number, value, arena)
if let Ok(value) = Fixnum::build_with_checked(&floor) {
Ok(Number::Fixnum(value))
} else {
Number::Integer(arena_alloc!(floor, arena))
Ok(Number::Integer(arena_alloc!(floor, arena)))
}
}
}
@@ -398,9 +384,9 @@ impl From<Fixnum> for Integer {
pub(crate) fn rnd_f(n: &Number) -> f64 {
match n {
&Number::Fixnum(n) => n.get_num() as f64,
Number::Integer(ref n) => n.to_f64().value(),
Number::Integer(n) => n.to_f64().value(),
&Number::Float(OrderedFloat(f)) => f,
Number::Rational(ref r) => r.to_f64().value(),
Number::Rational(r) => r.to_f64().value(),
}
}
@@ -528,51 +514,23 @@ impl PartialEq for Number {
fn eq(&self, rhs: &Self) -> bool {
match (self, rhs) {
(&Number::Fixnum(n1), &Number::Fixnum(n2)) => n1.eq(&n2),
(&Number::Fixnum(n1), Number::Integer(ref n2)) => n1.get_num().num_eq(&**n2),
(Number::Integer(ref n1), &Number::Fixnum(n2)) => n1.num_eq(&n2.get_num()),
(&Number::Fixnum(n1), Number::Rational(ref n2)) => {
(&Number::Fixnum(n1), Number::Integer(n2)) => n1.get_num().num_eq(&**n2),
(Number::Integer(n1), &Number::Fixnum(n2)) => n1.num_eq(&n2.get_num()),
(&Number::Fixnum(n1), Number::Rational(n2)) => {
Integer::from(n1.get_num()).num_eq(&**n2)
}
(Number::Rational(ref n1), &Number::Fixnum(n2)) => {
n1.num_eq(&Integer::from(n2.get_num()))
}
(Number::Rational(n1), &Number::Fixnum(n2)) => n1.num_eq(&Integer::from(n2.get_num())),
(&Number::Fixnum(n1), &Number::Float(n2)) => OrderedFloat(n1.get_num() as f64).eq(&n2),
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.eq(&OrderedFloat(n2.get_num() as f64)),
(Number::Integer(ref n1), Number::Integer(ref n2)) => n1.eq(n2),
(Number::Integer(ref n1), Number::Float(n2)) => {
OrderedFloat(n1.to_f64().value()).eq(n2)
}
(&Number::Float(n1), Number::Integer(ref n2)) => {
n1.eq(&OrderedFloat(n2.to_f64().value()))
}
(Number::Integer(ref n1), Number::Rational(ref n2)) => {
#[cfg(feature = "num")]
{
&Rational::from(&**n1) == &**n2
}
#[cfg(not(feature = "num"))]
{
n1.num_eq(&**n2)
}
}
(Number::Rational(ref n1), Number::Integer(ref n2)) => {
#[cfg(feature = "num")]
{
n1 == &Rational::from(&**n2)
}
#[cfg(not(feature = "num"))]
{
n1.num_eq(&**n2)
}
}
(Number::Rational(ref n1), &Number::Float(n2)) => {
OrderedFloat(n1.to_f64().value()).eq(&n2)
}
(&Number::Float(n1), Number::Rational(ref n2)) => {
n1.eq(&OrderedFloat(n2.to_f64().value()))
}
(Number::Integer(n1), Number::Integer(n2)) => n1.eq(n2),
(Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(n2),
(&Number::Float(n1), Number::Integer(n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
(Number::Integer(n1), Number::Rational(n2)) => n1.num_eq(&**n2),
(Number::Rational(n1), Number::Integer(n2)) => n1.num_eq(&**n2),
(Number::Rational(n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(&n2),
(&Number::Float(n1), Number::Rational(n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
(&Number::Float(f1), &Number::Float(f2)) => f1.eq(&f2),
(Number::Rational(ref r1), Number::Rational(ref r2)) => r1.eq(r2),
(Number::Rational(r1), Number::Rational(r2)) => r1.eq(r2),
}
}
}
@@ -639,28 +597,12 @@ impl Ord for Number {
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.cmp(&OrderedFloat(n2.get_num() as f64)),
(&Number::Integer(n1), &Number::Integer(n2)) => (*n1).cmp(&*n2),
(&Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).cmp(n2),
(&Number::Float(n1), Number::Integer(ref n2)) => {
n1.cmp(&OrderedFloat(n2.to_f64().value()))
}
(&Number::Float(n1), Number::Integer(n2)) => n1.cmp(&OrderedFloat(n2.to_f64().value())),
(&Number::Integer(n1), &Number::Rational(n2)) => {
#[cfg(feature = "num")]
{
Rational::from(&**n1).cmp(n2)
}
#[cfg(not(feature = "num"))]
{
(*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
}
(*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
}
(&Number::Rational(n1), &Number::Integer(n2)) => {
#[cfg(feature = "num")]
{
(&**n1).cmp(&Rational::from(&**n2))
}
#[cfg(not(feature = "num"))]
{
(*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
}
(*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
}
(&Number::Rational(n1), &Number::Float(n2)) => {
OrderedFloat(n1.to_f64().value()).cmp(&n2)
@@ -674,11 +616,11 @@ impl Ord for Number {
}
}
impl TryFrom<HeapCellValue> for Number {
impl TryFrom<(HeapCellValue, &'_ F64Table)> for Number {
type Error = ();
#[inline]
fn try_from(value: HeapCellValue) -> Result<Number, Self::Error> {
fn try_from((value, f64_tbl): (HeapCellValue, &F64Table)) -> Result<Number, Self::Error> {
read_heap_cell!(value,
(HeapCellValueTag::Cons, c) => {
match_untyped_arena_ptr!(c,
@@ -693,8 +635,9 @@ impl TryFrom<HeapCellValue> for Number {
}
)
}
(HeapCellValueTag::F64, n) => {
Ok(Number::Float(*n))
(HeapCellValueTag::F64Offset, offset) => {
let n = f64_tbl.get_entry(offset);
Ok(Number::Float(n))
}
(HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
Ok(Number::Fixnum(n))

View File

@@ -1,6 +1,6 @@
use crate::machine::heap::AllocError;
use crate::parser::ast::MAX_ARITY;
use crate::raw_block::*;
use crate::rcu::{Rcu, RcuRef};
use crate::types::*;
use std::cmp::Ordering;
@@ -8,26 +8,138 @@ use std::hash::{Hash, Hasher};
use std::mem;
use std::ops::Deref;
use std::ptr;
use std::slice;
use std::str;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::RwLock;
use std::sync::Weak;
use arcu::Rcu;
use arcu::atomic::Arcu;
use arcu::epoch_counters::GlobalEpochCounterPool;
use arcu::rcu_ref::RcuRef;
use indexmap::IndexSet;
use scryer_modular_bitfield::prelude::*;
use modular_bitfield::prelude::*;
#[bitfield]
#[repr(u64)]
#[derive(Copy, Clone, Debug)]
pub struct AtomCell {
name: B48,
arity: B8,
#[allow(unused)]
f: bool,
#[allow(unused)]
m: bool,
#[allow(unused)]
is_inlined: bool,
#[allow(unused)]
tag: B5,
}
const INLINED_ATOM_MAX_LEN: usize = 6;
const_assert!(INLINED_ATOM_MAX_LEN < mem::size_of::<AtomCell>());
const_assert!(mem::size_of::<AtomCell>() == 8);
const_assert!(INLINED_ATOM_MAX_LEN < mem::size_of::<Atom>());
const_assert!(mem::size_of::<Atom>() == 8);
impl AtomCell {
#[inline]
fn new_static(index: u64) -> Self {
// upper 23 bits of index must be 0
debug_assert_eq!(index & !((1 << 49) - 1), 0);
AtomCell::new()
.with_name(index)
.with_arity(0u8)
.with_m(false)
.with_f(false)
.with_is_inlined(false)
.with_tag(HeapCellValueTag::Atom as u8)
}
#[inline]
fn new_inlined(string: &str, arity: u8) -> Self {
debug_assert!(string.len() <= INLINED_ATOM_MAX_LEN);
let mut string_buf: [u8; 8] = [0u8; 8];
string_buf[..string.len()].copy_from_slice(string.as_bytes());
let encoding = u64::from_le_bytes(string_buf);
AtomCell::new()
.with_name(encoding)
.with_arity(arity)
.with_m(false)
.with_f(false)
.with_is_inlined(true)
.with_tag(HeapCellValueTag::Atom as u8)
}
#[inline]
pub fn new_char_inlined(c: char) -> Self {
if c == '\u{0}' {
return Self::new_static(NULL_ATOM.flat_index());
}
let mut char_buf = [0u8; 8];
c.encode_utf8(&mut char_buf);
let encoding = u64::from_le_bytes(char_buf);
AtomCell::new()
.with_name(encoding)
.with_arity(0u8)
.with_m(false)
.with_f(false)
.with_is_inlined(true)
.with_tag(HeapCellValueTag::Atom as u8)
}
#[inline]
pub fn build_with(atom_index: u64, arity: u8) -> Self {
debug_assert!((arity as usize) <= MAX_ARITY);
AtomCell::new()
.with_name(atom_index >> 1)
.with_arity(arity)
.with_f(false)
.with_m(false)
.with_is_inlined(atom_index & 1 == 1)
.with_tag(HeapCellValueTag::Atom as u8)
}
#[inline]
pub fn get_name(self) -> Atom {
Atom {
index: (self.name() << 1) | self.is_inlined() as u64,
}
}
#[inline]
pub fn get_arity(self) -> usize {
self.arity() as usize
}
#[inline]
pub fn get_name_and_arity(self) -> (Atom, usize) {
(self.get_name(), self.get_arity())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Atom {
pub index: u64,
}
const_assert!(mem::size_of::<Atom>() == 8);
include!(concat!(env!("OUT_DIR"), "/static_atoms.rs"));
// populate these in STRINGS so they can be used from build_functor
const _: Atom = atom!(".");
const _: Atom = atom!("[]");
const NULL_ATOM: Atom = atom!("\0");
impl<'a> From<&'a Atom> for Atom {
#[inline]
fn from(atom: &'a Atom) -> Self {
@@ -35,41 +147,31 @@ impl<'a> From<&'a Atom> for Atom {
}
}
impl From<bool> for Atom {
#[inline]
fn from(value: bool) -> Self {
if value {
atom!("true")
} else {
atom!("false")
}
}
}
impl indexmap::Equivalent<Atom> for str {
fn equivalent(&self, key: &Atom) -> bool {
&*key.as_str() == self
}
}
impl PartialEq<str> for Atom {
fn eq(&self, other: &str) -> bool {
self.as_str().deref() == other
}
}
impl PartialEq<&str> for Atom {
fn eq(&self, &other: &&str) -> bool {
self.as_str().deref() == other
}
}
const ATOM_TABLE_INIT_SIZE: usize = 1 << 16;
const ATOM_TABLE_ALIGN: usize = 8;
#[inline(always)]
fn global_atom_table() -> &'static RwLock<Weak<AtomTable>> {
#[cfg(feature = "rust_beta_channel")]
{
// const Weak::new will be stabilized in 1.73 which is currently in beta,
// till then we need a OnceLock for initialization
static GLOBAL_ATOM_TABLE: RwLock<Weak<AtomTable>> = RwLock::const_new(Weak::new());
&GLOBAL_ATOM_TABLE
}
#[cfg(not(feature = "rust_beta_channel"))]
{
use std::sync::OnceLock;
static GLOBAL_ATOM_TABLE: OnceLock<RwLock<Weak<AtomTable>>> = OnceLock::new();
GLOBAL_ATOM_TABLE.get_or_init(|| RwLock::new(Weak::new()))
}
static GLOBAL_ATOM_TABLE: RwLock<Weak<AtomTable>> = RwLock::new(Weak::new());
&GLOBAL_ATOM_TABLE
}
#[inline(always)]
@@ -99,6 +201,12 @@ struct AtomHeader {
padding: B13,
}
#[repr(C)]
pub struct AtomData {
header: AtomHeader,
data: str,
}
impl AtomHeader {
fn build_with(len: u64) -> Self {
AtomHeader::new().with_len(len).with_m(false)
@@ -109,32 +217,23 @@ impl Hash for Atom {
#[inline]
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.as_str().hash(hasher)
// hasher.write_usize(self.index)
}
}
#[macro_export]
macro_rules! is_char {
($s:expr) => {
!$s.is_empty() && $s.chars().nth(1).is_none()
};
}
pub enum AtomString<'a> {
Static(&'a str),
Inlined([u8; 8]),
Dynamic(AtomTableRef<str>),
}
impl AtomString<'_> {
pub fn map<F>(self, f: F) -> Self
where
for<'a> F: FnOnce(&'a str) -> &'a str,
{
match self {
Self::Static(reference) => Self::Static(f(reference)),
Self::Dynamic(guard) => Self::Dynamic(AtomTableRef::map(guard, f)),
}
}
#[inline(always)]
fn inlined_to_str(bytes: &[u8; 8]) -> &str {
let slice_len = bytes
.iter()
.position(|&b| b == 0u8)
.unwrap_or(INLINED_ATOM_MAX_LEN);
unsafe { str::from_utf8_unchecked(&bytes[..slice_len]) }
}
impl std::fmt::Debug for AtomString<'_> {
@@ -154,6 +253,7 @@ impl std::ops::Deref for AtomString<'_> {
fn deref(&self) -> &Self::Target {
match self {
Self::Static(reference) => reference,
Self::Inlined(inlined) => inlined_to_str(inlined),
Self::Dynamic(guard) => guard.deref(),
}
}
@@ -171,25 +271,47 @@ impl rustyline::completion::Candidate for AtomString<'_> {
}
impl Atom {
#[inline(always)]
pub fn is_static(self) -> bool {
(self.index as usize) < STRINGS.len() << 3
#[inline]
fn new_inlined(string: &str) -> Self {
AtomCell::new_inlined(string, 0).get_name()
}
#[inline(always)]
pub fn as_ptr(self) -> Option<AtomTableRef<u8>> {
fn is_static(self) -> bool {
if self.is_inlined() {
true
} else {
(self.flat_index() as usize) < STRINGS.len()
}
}
#[inline]
pub(crate) fn flat_index(self) -> u64 {
self.index >> 1
}
#[inline(always)]
pub(crate) fn is_inlined(self) -> bool {
self.index & 1 == 1
}
#[inline(always)]
fn as_ptr(self) -> Option<AtomTableRef<AtomData>> {
if self.is_static() {
None
} else {
let atom_table =
arc_atom_table().expect("We should only have an Atom while there is an AtomTable");
unsafe {
AtomTableRef::try_map(atom_table.buf(), |buf| {
(buf as *const u8)
.add((self.index as usize) - (STRINGS.len() << 3))
.as_ref()
})
}
AtomTableRef::try_map(atom_table.inner.read(), |buf| unsafe {
let ptr = buf
.block
.get_unchecked(self.flat_index() as usize - STRINGS.len());
// TODO use std::ptr::from_raw_parts instead when feature ptr_metadata is stable rust-lang/rust#81513
let atom_data = &*(std::ptr::slice_from_raw_parts(ptr, 0) as *const AtomData);
let len = atom_data.header.len();
Some(&*(std::ptr::slice_from_raw_parts(ptr, len as usize) as *const AtomData))
})
}
}
@@ -200,12 +322,14 @@ impl Atom {
#[inline(always)]
pub fn len(self) -> usize {
if self.is_static() {
STRINGS[(self.index >> 3) as usize].len()
if let Some(s) = self.inlined_str() {
s.len()
} else if self.is_static() {
let index = self.flat_index();
STRINGS[index as usize].len()
} else {
let ptr = self.as_ptr().unwrap();
let ptr = ptr.deref() as *const u8 as *const AtomHeader;
unsafe { ptr::read(ptr) }.len() as _
let len: u64 = self.as_ptr().unwrap().header.len();
len as usize
}
}
@@ -213,11 +337,6 @@ impl Atom {
self.len() == 0
}
#[inline(always)]
pub fn flat_index(self) -> u64 {
self.index >> 3
}
pub fn as_char(self) -> Option<char> {
let s = self.as_str();
let mut it = s.chars();
@@ -225,8 +344,13 @@ impl Atom {
let c1 = it.next();
let c2 = it.next();
if c2.is_none() {
c1
if c2.is_none() { c1 } else { None }
}
#[inline]
fn inlined_str<'a>(&self) -> Option<AtomString<'a>> {
if self.is_inlined() {
Some(AtomString::Inlined(self.flat_index().to_le_bytes()))
} else {
None
}
@@ -234,20 +358,15 @@ impl Atom {
#[inline]
pub fn as_str(&self) -> AtomString<'static> {
if self.is_static() {
AtomString::Static(STRINGS[(self.index >> 3) as usize])
if let Some(s) = self.inlined_str() {
s
} else if self.is_static() {
let index = self.flat_index() as usize;
AtomString::Static(STRINGS[index])
} else if let Some(ptr) = self.as_ptr() {
AtomString::Dynamic(AtomTableRef::map(ptr, |ptr| {
let header =
// Miri seems to hit this line a lot
unsafe { ptr::read::<AtomHeader>(ptr as *const u8 as *const AtomHeader) };
let len = header.len() as usize;
let buf = unsafe { (ptr as *const u8).add(mem::size_of::<AtomHeader>()) };
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(buf, len)) }
}))
AtomString::Dynamic(AtomTableRef::map(ptr, |ptr| &ptr.data))
} else {
AtomString::Static(STRINGS[(self.index >> 3) as usize])
AtomString::Static(STRINGS[(self.index >> 1) as usize])
}
}
@@ -265,9 +384,11 @@ impl Atom {
}
unsafe fn write_to_ptr(string: &str, ptr: *mut u8) {
ptr::write(ptr as *mut _, AtomHeader::build_with(string.len() as u64));
let str_ptr = ptr.add(mem::size_of::<AtomHeader>());
ptr::copy_nonoverlapping(string.as_ptr(), str_ptr, string.len());
unsafe {
ptr::write(ptr as *mut _, AtomHeader::build_with(string.len() as u64));
let str_ptr = ptr.add(mem::size_of::<AtomHeader>());
ptr::copy_nonoverlapping(string.as_ptr(), str_ptr, string.len());
}
}
impl PartialOrd for Atom {
@@ -287,17 +408,17 @@ impl Ord for Atom {
#[derive(Debug)]
pub struct InnerAtomTable {
block: RawBlock<AtomTable>,
pub table: Rcu<IndexSet<Atom>>,
pub table: Arcu<IndexSet<Atom>, GlobalEpochCounterPool>,
}
#[derive(Debug)]
pub struct AtomTable {
inner: Rcu<InnerAtomTable>,
inner: Arcu<InnerAtomTable, GlobalEpochCounterPool>,
// this lock is taking during resizing
update: Mutex<()>,
}
pub type AtomTableRef<M> = RcuRef<InnerAtomTable, M>;
pub type AtomTableRef<M> = arcu::rcu_ref::RcuRef<InnerAtomTable, M>;
impl InnerAtomTable {
#[inline(always)]
@@ -305,51 +426,56 @@ impl InnerAtomTable {
STATIC_ATOMS_MAP
.get(string)
.cloned()
.or_else(|| self.table.active_epoch().get(string).cloned())
.or_else(|| self.table.read().get(string).cloned())
}
}
impl AtomTable {
#[inline]
pub fn new() -> Arc<Self> {
pub fn new() -> Result<Arc<Self>, AllocError> {
let upgraded = global_atom_table().read().unwrap().upgrade();
// don't inline upgraded, otherwise temporary will be dropped too late in case of None
if let Some(atom_table) = upgraded {
atom_table
Ok(atom_table)
} else {
let mut guard = global_atom_table().write().unwrap();
// try to upgrade again in case we lost the race on the write lock
if let Some(atom_table) = guard.upgrade() {
atom_table
Ok(atom_table)
} else {
let atom_table = Arc::new(Self {
inner: Rcu::new(InnerAtomTable {
block: RawBlock::new(),
table: Rcu::new(IndexSet::new()),
}),
inner: Arcu::new(
InnerAtomTable {
block: RawBlock::new()?,
table: Arcu::new(IndexSet::new(), GlobalEpochCounterPool),
},
GlobalEpochCounterPool,
),
update: Mutex::new(()),
});
*guard = Arc::downgrade(&atom_table);
atom_table
Ok(atom_table)
}
}
}
#[inline]
pub fn buf(&self) -> AtomTableRef<u8> {
AtomTableRef::<InnerAtomTable>::map(self.inner.active_epoch(), |inner| {
unsafe { inner.block.base.as_ref() }.unwrap()
})
pub fn retrieve() -> Arc<Self> {
global_atom_table().read().unwrap().upgrade().unwrap()
}
pub fn active_table(&self) -> RcuRef<IndexSet<Atom>, IndexSet<Atom>> {
self.inner.active_epoch().table.active_epoch()
self.inner.read().table.read()
}
pub fn build_with(atom_table: &AtomTable, string: &str) -> Atom {
if !string.is_empty() && string.len() <= INLINED_ATOM_MAX_LEN && !string.contains('\u{0}') {
return Atom::new_inlined(string);
}
loop {
let mut block_epoch = atom_table.inner.active_epoch();
let mut table_epoch = block_epoch.table.active_epoch();
let mut block_epoch = atom_table.inner.read();
let mut table_epoch = block_epoch.table.read();
if let Some(atom) = block_epoch.lookup_str(string) {
return atom;
@@ -358,10 +484,8 @@ impl AtomTable {
// take a lock to prevent concurrent updates
let update_guard = atom_table.update.lock().unwrap();
let is_same_allocation =
RcuRef::same_epoch(&block_epoch, &atom_table.inner.active_epoch());
let is_same_atom_list =
RcuRef::same_epoch(&table_epoch, &block_epoch.table.active_epoch());
let is_same_allocation = RcuRef::same_epoch(&block_epoch, &atom_table.inner.read());
let is_same_atom_list = RcuRef::same_epoch(&table_epoch, &block_epoch.table.read());
if !(is_same_allocation && is_same_atom_list) {
// some other thread raced us between our lookup and
@@ -371,8 +495,7 @@ impl AtomTable {
}
let size = mem::size_of::<AtomHeader>() + string.len();
let align_offset = 8 * mem::align_of::<AtomHeader>();
let size = (size & !(align_offset - 1)) + align_offset;
let size = size.next_multiple_of(AtomTable::align());
unsafe {
let len_ptr = loop {
@@ -381,32 +504,38 @@ impl AtomTable {
if ptr.is_null() {
// garbage collection would go here
let new_block = block_epoch.block.grow_new().unwrap();
let new_table = Rcu::new(table_epoch.clone());
let new_table = Arcu::new(table_epoch.clone(), GlobalEpochCounterPool);
let new_alloc = InnerAtomTable {
block: new_block,
table: new_table,
};
atom_table.inner.replace(new_alloc);
block_epoch = atom_table.inner.active_epoch();
table_epoch = block_epoch.table.active_epoch();
block_epoch = atom_table.inner.read();
table_epoch = block_epoch.table.read();
} else {
break ptr;
}
};
let ptr_base = block_epoch.block.base as usize;
// SAFETY: `len_ptr` was obtained from `block_epoch.block.alloc()`
let len_offset = block_epoch.block.get_offset(len_ptr);
write_to_ptr(string, len_ptr);
let atom = Atom {
index: ((STRINGS.len() << 3) + len_ptr as usize - ptr_base) as u64,
};
let atom = AtomCell::new()
.with_name((STRINGS.len() + len_offset) as u64)
.with_arity(0)
.with_f(false)
.with_m(false)
.with_is_inlined(false)
.with_tag(HeapCellValueTag::Atom as u8)
.get_name();
let mut table = table_epoch.clone();
table.insert(atom);
block_epoch.table.replace(table);
// expicit drop to ensure we don't accidentally drop it early
// explicit drop to ensure we don't accidentally drop it early
drop(update_guard);
return atom;
@@ -417,57 +546,3 @@ impl AtomTable {
unsafe impl Send for AtomTable {}
unsafe impl Sync for AtomTable {}
#[bitfield]
#[repr(u64)]
#[derive(Copy, Clone, Debug)]
pub struct AtomCell {
name: B46,
arity: B10,
#[allow(unused)]
f: bool,
#[allow(unused)]
m: bool,
#[allow(unused)]
tag: B6,
}
impl AtomCell {
#[inline]
pub fn build_with(name: u64, arity: u16, tag: HeapCellValueTag) -> Self {
if arity > 0 {
debug_assert!(arity as usize <= MAX_ARITY);
AtomCell::new()
.with_name(name)
.with_arity(arity)
.with_f(false)
.with_tag(tag as u8)
} else {
AtomCell::new()
.with_name(name)
.with_f(false)
.with_tag(tag as u8)
}
}
#[inline]
pub fn get_index(self) -> usize {
self.name() as usize
}
#[inline]
pub fn get_name(self) -> Atom {
Atom::from((self.get_index() as u64) << 3)
}
#[inline]
pub fn get_arity(self) -> usize {
self.arity() as usize
}
#[inline]
pub fn get_name_and_arity(self) -> (Atom, usize) {
(Atom::from((self.get_index() as u64) << 3), self.get_arity())
}
}

View File

@@ -1,27 +1,7 @@
fn main() -> std::process::ExitCode {
use scryer_prolog::atom_table::Atom;
use scryer_prolog::*;
#[cfg(feature = "repl")]
ctrlc::set_handler(move || {
scryer_prolog::machine::INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
})
.unwrap();
#[cfg(target_arch = "wasm32")]
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
return std::process::ExitCode::SUCCESS;
#[cfg(not(target_arch = "wasm32"))]
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut wam = machine::Machine::new(Default::default());
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
})
return scryer_prolog::run_binary();
}

View File

@@ -8,11 +8,9 @@ use crate::instructions::*;
use crate::iterators::*;
use crate::parser::ast::*;
use crate::targets::*;
use crate::temp_v;
use crate::types::*;
use crate::variable_records::*;
use crate::instr;
use crate::machine::disjuncts::*;
use crate::machine::machine_errors::*;
@@ -147,7 +145,7 @@ impl BranchCodeStack {
settings.trust_me()
});
combined_code.extend(code.into_iter());
combined_code.extend(code);
}
}
@@ -271,8 +269,7 @@ impl CodeGenSettings {
}
#[derive(Debug)]
pub(crate) struct CodeGenerator<'a> {
pub(crate) atom_tbl: &'a AtomTable,
pub(crate) struct CodeGenerator {
marker: DebrayAllocator,
settings: CodeGenSettings,
pub(crate) skeleton: PredicateSkeleton,
@@ -321,33 +318,12 @@ impl DebrayAllocator {
}
}
// if the final argument of the structure is a Literal::Index,
// decrement the arity of the PutStructure instruction by 1.
fn trim_structure_by_last_arg(instr: &mut Instruction, last_arg: &Term) {
match instr {
Instruction::PutStructure(_, ref mut arity, _)
| Instruction::GetStructure(.., ref mut arity, _) => {
if let Term::Literal(_, Literal::CodeIndex(_)) = last_arg {
// it is acceptable if arity == 0 is the result of
// this decrement. call/N will have to read the index
// constant for '$call_inline' to succeed. to find it,
// it must know the heap location of the index.
// self.store must stop before reading the atom into a
// register.
*arity -= 1;
}
}
_ => {}
}
}
trait AddToFreeList<'a, Target: CompilationTarget<'a>> {
fn add_term_to_free_list(&mut self, r: RegType);
fn add_subterm_to_free_list(&mut self, term: &Term);
}
impl<'a, 'b> AddToFreeList<'a, FactInstruction> for CodeGenerator<'b> {
impl<'a> AddToFreeList<'a, FactInstruction> for CodeGenerator {
fn add_term_to_free_list(&mut self, r: RegType) {
self.marker.add_reg_to_free_list(r);
}
@@ -355,7 +331,7 @@ impl<'a, 'b> AddToFreeList<'a, FactInstruction> for CodeGenerator<'b> {
fn add_subterm_to_free_list(&mut self, _term: &Term) {}
}
impl<'a, 'b> AddToFreeList<'a, QueryInstruction> for CodeGenerator<'b> {
impl<'a> AddToFreeList<'a, QueryInstruction> for CodeGenerator {
#[inline(always)]
fn add_term_to_free_list(&mut self, _r: RegType) {}
@@ -369,18 +345,17 @@ impl<'a, 'b> AddToFreeList<'a, QueryInstruction> for CodeGenerator<'b> {
fn structure_cell(term: &Term) -> Option<&Cell<RegType>> {
match term {
&Term::Cons(ref cell, ..)
| &Term::Clause(ref cell, ..)
| Term::PartialString(ref cell, ..)
| Term::CompleteString(ref cell, ..) => Some(cell),
Term::Cons(cell, ..)
| Term::Clause(cell, ..)
| Term::PartialString(cell, ..)
| Term::CompleteString(cell, ..) => Some(cell),
_ => None,
}
}
impl<'b> CodeGenerator<'b> {
pub(crate) fn new(atom_tbl: &'b AtomTable, settings: CodeGenSettings) -> Self {
impl CodeGenerator {
pub(crate) fn new(settings: CodeGenSettings) -> Self {
CodeGenerator {
atom_tbl,
marker: DebrayAllocator::new(),
settings,
skeleton: PredicateSkeleton::new(),
@@ -426,18 +401,18 @@ impl<'b> CodeGenerator<'b> {
&Term::AnonVar => {
Self::add_or_increment_void_instr::<Target>(target);
}
&Term::Cons(ref cell, ..)
| &Term::Clause(ref cell, ..)
| Term::PartialString(ref cell, ..)
| Term::CompleteString(ref cell, ..) => {
Term::Cons(cell, ..)
| Term::Clause(cell, ..)
| Term::PartialString(cell, ..)
| Term::CompleteString(cell, ..) => {
self.marker
.mark_non_var::<Target>(Level::Deep, term_loc, cell, target);
target.push_back(Target::clause_arg_to_instr(cell.get()));
}
Term::Literal(_, ref constant) => {
Term::Literal(_, constant) => {
target.push_back(Target::constant_subterm(*constant));
}
Term::Var(ref cell, ref var_ptr) => {
Term::Var(cell, var_ptr) => {
self.deep_var_instr::<Target>(
cell,
var_ptr.to_var_num().unwrap(),
@@ -448,99 +423,99 @@ impl<'b> CodeGenerator<'b> {
};
}
fn compile_target<'a, Target, Iter>(&mut self, iter: Iter, term_loc: GenContext) -> CodeDeque
fn compile_target<'a, Target, Iter>(&mut self, iter: Iter, context: GenContext) -> CodeDeque
where
Target: crate::targets::CompilationTarget<'a>,
Iter: Iterator<Item = TermRef<'a>>,
CodeGenerator<'b>: AddToFreeList<'a, Target>,
CodeGenerator: AddToFreeList<'a, Target>,
{
let mut target = CodeDeque::new();
for term in iter {
match term {
TermRef::AnonVar(lvl @ Level::Shallow) => {
if let GenContext::Head = term_loc {
if let GenContext::Head = context {
self.marker.advance_arg();
} else {
self.marker
.mark_anon_var::<Target>(lvl, term_loc, &mut target);
.mark_anon_var::<Target>(lvl, context, &mut target);
}
}
TermRef::Clause(lvl, cell, name, terms) => {
self.marker
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
target.push_back(Target::to_structure(lvl, name, terms.len(), cell.get()));
let terms_range =
if let Some(subterm @ Term::Literal(_, Literal::CodeIndexOffset(_))) =
terms.last()
{
self.subterm_to_instr::<Target>(subterm, context, &mut target);
0..terms.len() - 1
} else {
0..terms.len()
};
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_term_to_free_list(
self.marker
.mark_non_var::<Target>(lvl, context, cell, &mut target);
target.push_back(Target::to_structure(lvl, name, terms_range.end, cell.get()));
<CodeGenerator as AddToFreeList<'a, Target>>::add_term_to_free_list(
self,
cell.get(),
);
if let Some(instr) = target.back_mut() {
if let Some(term) = terms.last() {
trim_structure_by_last_arg(instr, term);
}
for subterm in &terms[terms_range.clone()] {
self.subterm_to_instr::<Target>(subterm, context, &mut target);
}
for subterm in terms {
self.subterm_to_instr::<Target>(subterm, term_loc, &mut target);
}
for subterm in terms {
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
for subterm in &terms[terms_range] {
<CodeGenerator as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
self, subterm,
);
}
}
TermRef::Cons(lvl, cell, head, tail) => {
self.marker
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
.mark_non_var::<Target>(lvl, context, cell, &mut target);
target.push_back(Target::to_list(lvl, cell.get()));
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_term_to_free_list(
<CodeGenerator as AddToFreeList<'a, Target>>::add_term_to_free_list(
self,
cell.get(),
);
self.subterm_to_instr::<Target>(head, term_loc, &mut target);
self.subterm_to_instr::<Target>(tail, term_loc, &mut target);
self.subterm_to_instr::<Target>(head, context, &mut target);
self.subterm_to_instr::<Target>(tail, context, &mut target);
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
<CodeGenerator as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
self, head,
);
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
<CodeGenerator as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
self, tail,
);
}
TermRef::Literal(lvl @ Level::Shallow, cell, Literal::String(ref string)) => {
self.marker
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
target.push_back(Target::to_pstr(lvl, *string, cell.get(), false));
}
TermRef::Literal(lvl @ Level::Shallow, cell, constant) => {
self.marker
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
.mark_non_var::<Target>(lvl, context, cell, &mut target);
target.push_back(Target::to_constant(lvl, *constant, cell.get()));
}
TermRef::PartialString(lvl, cell, string, tail) => {
self.marker
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
let atom = AtomTable::build_with(self.atom_tbl, string);
.mark_non_var::<Target>(lvl, context, cell, &mut target);
target.push_back(Target::to_pstr(lvl, atom, cell.get(), true));
self.subterm_to_instr::<Target>(tail, term_loc, &mut target);
target.push_back(Target::to_pstr(lvl, string.clone(), cell.get()));
self.subterm_to_instr::<Target>(tail, context, &mut target);
}
TermRef::CompleteString(lvl, cell, atom) => {
TermRef::CompleteString(lvl, cell, string) => {
self.marker
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
target.push_back(Target::to_pstr(lvl, atom, cell.get(), false));
.mark_non_var::<Target>(lvl, context, cell, &mut target);
target.push_back(Target::to_pstr(lvl, string.clone(), cell.get()));
target.push_back(Target::constant_subterm(Literal::Atom(atom!("[]"))));
}
TermRef::Var(lvl @ Level::Shallow, cell, var) => {
self.marker.mark_var::<Target>(
var.to_var_num().unwrap(),
lvl,
cell,
term_loc,
context,
&mut target,
);
}
@@ -559,14 +534,14 @@ impl<'b> CodeGenerator<'b> {
match call_policy {
CallPolicy::Default => {
if self.marker.in_tail_position {
code.push_back(call_instr.to_execute().to_default());
code.push_back(call_instr.into_execute().into_default());
} else {
code.push_back(call_instr.to_default())
code.push_back(call_instr.into_default())
}
}
CallPolicy::Counted => {
if self.marker.in_tail_position {
code.push_back(call_instr.to_execute());
code.push_back(call_instr.into_execute());
} else {
code.push_back(call_instr)
}
@@ -585,29 +560,19 @@ impl<'b> CodeGenerator<'b> {
&InlinedClauseType::CompareNumber(mut cmp) => {
self.marker.reset_arg(2);
let (mut lcode, at_1) = self.compile_arith_expr(&terms[0], 1, term_loc, 1)?;
if !matches!(terms[0], Term::Var(..)) {
self.marker.advance_arg();
}
let (mut rcode, at_2) = self.compile_arith_expr(&terms[1], 2, term_loc, 2)?;
let (mut lcode, at_1) = self.compile_arith_expr(&terms[0], term_loc, 1)?;
let (mut rcode, at_2) = self.compile_arith_expr(&terms[1], term_loc, 2)?;
code.append(&mut lcode);
code.append(&mut rcode);
let at_1 = at_1.unwrap_or(interm!(1));
let at_2 = at_2.unwrap_or(interm!(2));
compare_number_instr!(cmp, at_1, at_2)
}
InlinedClauseType::IsAtom(..) => match &terms[0] {
Term::Literal(_, Literal::Char(_))
| Term::Literal(_, Literal::Atom(atom!("[]")))
| Term::Literal(_, Literal::Atom(..)) => {
Term::Literal(_, Literal::Atom(..)) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -632,13 +597,10 @@ impl<'b> CodeGenerator<'b> {
| Term::CompleteString(..) => {
instr!("$fail")
}
Term::Literal(_, Literal::String(_)) => {
instr!("$fail")
}
Term::Literal(..) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -656,11 +618,10 @@ impl<'b> CodeGenerator<'b> {
Term::Clause(..)
| Term::Cons(..)
| Term::PartialString(..)
| Term::CompleteString(..)
| Term::Literal(_, Literal::String(..)) => {
| Term::CompleteString(..) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -678,7 +639,10 @@ impl<'b> CodeGenerator<'b> {
}
},
InlinedClauseType::IsRational(..) => match terms[0] {
Term::Literal(_, Literal::Rational(_)) => {
Term::Literal(
_,
Literal::Rational(_) | Literal::Fixnum(_) | Literal::Integer(_),
) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
@@ -697,7 +661,7 @@ impl<'b> CodeGenerator<'b> {
}
},
InlinedClauseType::IsFloat(..) => match terms[0] {
Term::Literal(_, Literal::Float(_)) => {
Term::Literal(_, Literal::F64(..)) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
@@ -718,10 +682,13 @@ impl<'b> CodeGenerator<'b> {
}
},
InlinedClauseType::IsNumber(..) => match terms[0] {
Term::Literal(_, Literal::Float(_))
| Term::Literal(_, Literal::Rational(_))
| Term::Literal(_, Literal::Integer(_))
| Term::Literal(_, Literal::Fixnum(_)) => {
Term::Literal(
_,
Literal::F64(..)
| Literal::Rational(_)
| Literal::Integer(_)
| Literal::Fixnum(_),
) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
@@ -766,7 +733,7 @@ impl<'b> CodeGenerator<'b> {
Term::Literal(_, Literal::Integer(_)) | Term::Literal(_, Literal::Fixnum(_)) => {
instr!("$succeed")
}
Term::Var(ref vr, name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -812,18 +779,16 @@ impl<'b> CodeGenerator<'b> {
// inlined predicates are never counted, so this overrides nothing.
self.add_call(code, call_instr, CallPolicy::Counted);
Ok(())
}
fn compile_arith_expr(
&mut self,
term: &Term,
target_int: usize,
term_loc: GenContext,
arg: usize,
) -> Result<ArithCont, ArithmeticError> {
let mut evaluator = ArithmeticEvaluator::new(&mut self.marker, target_int);
let mut evaluator = ArithmeticEvaluator::new(&mut self.marker);
evaluator.compile_is(term, term_loc, arg)
}
@@ -836,7 +801,7 @@ impl<'b> CodeGenerator<'b> {
) -> Result<(), CompilationError> {
macro_rules! compile_expr {
($self:expr, $terms:expr, $term_loc:expr, $code:expr) => {{
let (acode, at) = $self.compile_arith_expr($terms, 1, $term_loc, 2)?;
let (acode, at) = $self.compile_arith_expr($terms, $term_loc, 2)?;
$code.extend(acode.into_iter());
at
}};
@@ -863,7 +828,7 @@ impl<'b> CodeGenerator<'b> {
self.marker
.mark_anon_var::<QueryInstruction>(Level::Shallow, term_loc, code);
if let Term::Var(ref vr, ref var) = &terms[1] {
if let Term::Var(vr, var) = &terms[1] {
let var_num = var.to_var_num().unwrap();
// if var is an anonymous variable, insert
@@ -891,16 +856,18 @@ impl<'b> CodeGenerator<'b> {
}
}
Term::Literal(
_,
ref cell,
c @ Literal::Integer(_)
| c @ Literal::Float(_)
| c @ Literal::F64(..)
| c @ Literal::Rational(_)
| c @ Literal::Fixnum(_),
) => {
let v = HeapCellValue::from(c);
code.push_back(instr!("put_constant", Level::Shallow, v, temp_v!(1)));
self.marker.advance_arg();
self.marker
.mark_non_var::<QueryInstruction>(Level::Shallow, term_loc, cell, code);
code.push_back(instr!("put_constant", Level::Shallow, v, temp_v!(1)));
compile_expr!(self, &terms[1], term_loc, code)
}
_ => {
@@ -909,9 +876,7 @@ impl<'b> CodeGenerator<'b> {
}
};
let at = at.unwrap_or(interm!(1));
self.add_call(code, instr!("is", temp_v!(1), at), call_policy);
Ok(())
}
@@ -926,9 +891,9 @@ impl<'b> CodeGenerator<'b> {
while let Some(clause_item) = clause_iter.next() {
match clause_item {
ClauseItem::Chunk(chunk) => {
for (idx, term) in chunk.iter().enumerate() {
let term_loc = if idx + 1 < chunk.len() {
ClauseItem::Chunk { terms } => {
for (idx, term) in terms.iter().enumerate() {
let term_loc = if idx + 1 < terms.len() {
GenContext::Mid(chunk_num)
} else {
self.marker.in_tail_position = clause_iter.in_tail_position();
@@ -1029,20 +994,27 @@ impl<'b> CodeGenerator<'b> {
self.marker.in_tail_position = false;
self.marker.reset_contents();
}
ClauseItem::FirstBranch(num_branches) => {
ClauseItem::FirstBranch {
branch_num,
num_branches,
} => {
branch_code_stack.add_new_branch_stack();
branch_code_stack.add_new_branch();
self.marker.branch_stack.add_branch_stack(num_branches);
self.marker
.branch_stack
.add_branch_stack(branch_num.clone(), num_branches);
self.marker.add_branch();
}
ClauseItem::NextBranch => {
ClauseItem::NextBranch { branch_num } => {
branch_code_stack.add_new_branch();
self.marker.add_branch();
self.marker.branch_stack.incr_current_branch();
self.marker
.branch_stack
.incr_current_branch(branch_num.clone());
}
ClauseItem::BranchEnd(depth) => {
ClauseItem::BranchEnd { depth } => {
if !clause_iter.in_tail_position() {
let subsumed_hits =
branch_code_stack.push_missing_vars(depth, &mut self.marker);
@@ -1149,27 +1121,24 @@ impl<'b> CodeGenerator<'b> {
'outer: for (right, clause) in clauses.iter().enumerate() {
if let Some(args) = clause.args() {
for (instantiated_arg_index, arg) in args.iter().enumerate() {
match arg {
Term::Var(..) | Term::AnonVar => {}
_ => {
if optimal_index != instantiated_arg_index {
if left >= right {
optimal_index = instantiated_arg_index;
continue 'outer;
}
subseqs.push(ClauseSpan {
left,
right,
instantiated_arg_index: optimal_index,
});
if !matches!(arg, Term::Var(..) | Term::AnonVar) {
if optimal_index != instantiated_arg_index {
if left >= right {
optimal_index = instantiated_arg_index;
left = right;
continue 'outer;
}
continue 'outer;
subseqs.push(ClauseSpan {
left,
right,
instantiated_arg_index: optimal_index,
});
optimal_index = instantiated_arg_index;
left = right;
}
continue 'outer;
}
}
}
@@ -1262,12 +1231,12 @@ impl<'b> CodeGenerator<'b> {
let index = code.len();
if clauses_len > 1 || self.settings.is_extensible {
code_offsets.index_term(arg, index, &mut clause_index_info, self.atom_tbl);
code_offsets.index_term(arg, index, &mut clause_index_info);
}
}
self.skeleton.clauses.push_back(clause_index_info);
code.extend(clause_code.into_iter());
code.extend(clause_code);
}
let index_code = if clauses_len > 1 || self.settings.is_extensible {
@@ -1344,7 +1313,7 @@ impl<'b> CodeGenerator<'b> {
}
}
code.extend(code_segment.into_iter());
code.extend(code_segment);
}
Ok(code)

View File

@@ -1,6 +1,6 @@
use crate::allocator::*;
use crate::codegen::SubsumedBranchHits;
use crate::forms::Level;
use crate::forms::{BranchNumber, GenContext, Level};
use crate::instructions::*;
use crate::machine::disjuncts::VarData;
use crate::parser::ast::*;
@@ -15,6 +15,7 @@ use indexmap::IndexMap;
use std::cell::Cell;
use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
pub type BranchHits = IndexMap<usize, BitVec, FxBuildHasher>; // key: var_num, value: branch arm occurrences.
@@ -24,24 +25,26 @@ pub struct BranchOccurrences {
pub shallow_safety: BitSet<usize>, // unset means safe, set means unsafe (after the branch merge)
pub deep_safety: BitSet<usize>,
pub num_branches: usize,
pub current_branch: usize,
pub current_branch_idx: usize,
pub current_branch_num: Arc<BranchNumber>,
pub subsumed_hits: SubsumedBranchHits,
}
impl BranchOccurrences {
fn new(num_branches: usize) -> Self {
fn new(current_branch_num: Arc<BranchNumber>, num_branches: usize) -> Self {
Self {
hits: BranchHits::with_hasher(FxBuildHasher::default()),
shallow_safety: BitSet::default(),
deep_safety: BitSet::default(),
num_branches,
current_branch: 0,
current_branch_idx: 0,
current_branch_num,
subsumed_hits: SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
}
}
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
debug_assert!(self.current_branch < self.num_branches);
debug_assert!(self.current_branch_idx < self.num_branches);
let num_branches = self.num_branches;
let entry = self
@@ -49,7 +52,7 @@ impl BranchOccurrences {
.entry(var_num)
.or_insert_with(|| BitVec::repeat(false, num_branches));
entry.set(self.current_branch, true);
entry.set(self.current_branch_idx, true);
self.subsumed_hits.insert(var_num);
}
}
@@ -76,19 +79,6 @@ impl DerefMut for BranchStack {
}
impl BranchStack {
fn branch_subsumes(&self, branch: &BranchDesignator, sub_branch: &BranchDesignator) -> bool {
if branch.branch_stack_num < sub_branch.branch_stack_num {
if branch.branch_stack_num == 0 {
true
} else {
let idx = branch.branch_stack_num - 1;
self[idx].current_branch == branch.branch_num
}
} else {
branch == sub_branch
}
}
fn safety_unneeded_in_branch(
&self,
safety: &VarSafetyStatus,
@@ -96,9 +86,9 @@ impl BranchStack {
) -> bool {
match safety {
VarSafetyStatus::Needed => false,
VarSafetyStatus::LocallyUnneeded(planter_branch) => {
self.branch_subsumes(planter_branch, branch)
}
VarSafetyStatus::LocallyUnneeded(planter_branch) => planter_branch
.branch_num
.has_as_subbranch(&branch.branch_num),
VarSafetyStatus::GloballyUnneeded => true,
}
}
@@ -109,31 +99,31 @@ impl BranchStack {
}
}
pub(crate) fn add_branch_stack(&mut self, num_branches: usize) {
self.push(BranchOccurrences::new(num_branches));
pub(crate) fn add_branch_stack(&mut self, branch_num: Arc<BranchNumber>, num_branches: usize) {
self.push(BranchOccurrences::new(branch_num, num_branches));
}
pub(crate) fn current_branch_designator(&self) -> BranchDesignator {
let branch_stack_num = self.len();
let branch_num = self
.last()
.map(|occurrences| occurrences.current_branch)
.unwrap_or(0);
.map(|occurrences| occurrences.current_branch_num.clone())
.unwrap_or_default();
BranchDesignator {
branch_stack_num,
branch_num,
}
BranchDesignator { branch_num }
}
#[inline]
pub(crate) fn incr_current_branch(&mut self) {
pub(crate) fn incr_current_branch(&mut self, branch_num: Arc<BranchNumber>) {
let branch_occurrences = self.last_mut().unwrap();
branch_occurrences.current_branch += 1;
branch_occurrences.current_branch_idx += 1;
branch_occurrences.current_branch_num = branch_num;
}
#[inline]
pub(crate) fn drain_branches(&mut self, depth: usize) -> std::vec::Drain<BranchOccurrences> {
pub(crate) fn drain_branches(
&mut self,
depth: usize,
) -> std::vec::Drain<'_, BranchOccurrences> {
let start_idx = self.len() - depth;
self.drain(start_idx..)
}
@@ -168,7 +158,7 @@ impl DebrayAllocator {
for var_num in subsumed_hits {
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(_, ref mut allocation) => {
VarAlloc::Perm(_, allocation) => {
if let PermVarAllocation::Done {
shallow_safety,
deep_safety,
@@ -212,7 +202,7 @@ impl DebrayAllocator {
},
);
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
let (deep_safety, shallow_safety) = match self.branch_stack.last_mut() {
Some(latest_branch) => {
@@ -232,12 +222,12 @@ impl DebrayAllocator {
VarAlloc::Perm(_, allocation) => {
let shallow_safety = VarSafetyStatus::needed_if(
shallow_safety.contains(var_num),
branch_designator,
&branch_designator,
);
let deep_safety = VarSafetyStatus::needed_if(
deep_safety.contains(var_num),
branch_designator,
&branch_designator,
);
if running_count < num_occurrences {
@@ -251,7 +241,7 @@ impl DebrayAllocator {
}
}
if self.branch_stack.len() > 0 {
if !self.branch_stack.is_empty() {
for var_num in subsumed_hits {
self.branch_stack.add_branch_occurrence(var_num);
}
@@ -517,7 +507,7 @@ impl DebrayAllocator {
}
pub(crate) fn mark_safe_var_unconditionally(&mut self, var_num: usize) {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
@@ -528,18 +518,20 @@ impl DebrayAllocator {
..
},
) => {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
*deep_safety = VarSafetyStatus::unneeded(&branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(&branch_designator);
}
VarAlloc::Temp { safety, .. } => {
*safety = VarSafetyStatus::unneeded(branch_designator);
*safety = VarSafetyStatus::unneeded(&branch_designator);
}
_ => {
unreachable!()
}
_ => unreachable!(),
}
}
fn mark_safe_var(&mut self, var_num: usize, lvl: Level, term_loc: GenContext) {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
@@ -552,15 +544,14 @@ impl DebrayAllocator {
) => {
// GetVariable in head chunk is considered safe.
if lvl == Level::Deep {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
*deep_safety = VarSafetyStatus::unneeded(&branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(&branch_designator);
} else if term_loc == GenContext::Head {
*shallow_safety = VarSafetyStatus::GloballyUnneeded;
} else if let Some(&temp_var_num) = self.shallow_temp_mappings.get(&self.arg_c) {
match &mut self.var_data.records[temp_var_num].allocation {
VarAlloc::Temp {
ref mut to_perm_var_num,
..
to_perm_var_num, ..
} => {
*to_perm_var_num = Some(var_num);
}
@@ -568,7 +559,7 @@ impl DebrayAllocator {
}
}
}
VarAlloc::Temp { ref mut safety, .. } => {
VarAlloc::Temp { safety, .. } => {
*safety = VarSafetyStatus::GloballyUnneeded;
}
_ => {
@@ -583,16 +574,10 @@ impl DebrayAllocator {
r: RegType,
arg_c: usize,
) -> Instruction {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
ref mut shallow_safety,
..
},
) => {
VarAlloc::Perm(_, PermVarAllocation::Done { shallow_safety, .. }) => {
if !self.in_tail_position
|| self
.branch_stack
@@ -600,7 +585,7 @@ impl DebrayAllocator {
{
Target::argument_to_value(r, arg_c)
} else {
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(&branch_designator);
Target::unsafe_argument_to_value(r, arg_c)
}
}
@@ -619,34 +604,28 @@ impl DebrayAllocator {
var_num: usize,
r: RegType,
) -> Instruction {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
ref mut deep_safety,
..
},
) => {
VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, .. }) => {
if self
.branch_stack
.safety_unneeded_in_branch(deep_safety, &branch_designator)
{
Target::subterm_to_value(r)
} else {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*deep_safety = VarSafetyStatus::unneeded(&branch_designator);
Target::unsafe_subterm_to_value(r)
}
}
VarAlloc::Temp { ref mut safety, .. } => {
VarAlloc::Temp { safety, .. } => {
if self
.branch_stack
.safety_unneeded_in_branch(safety, &branch_designator)
{
Target::subterm_to_value(r)
} else {
*safety = VarSafetyStatus::unneeded(branch_designator);
*safety = VarSafetyStatus::unneeded(&branch_designator);
Target::unsafe_subterm_to_value(r)
}
}
@@ -679,7 +658,7 @@ impl Allocator for DebrayAllocator {
lvl: Level,
term_loc: GenContext,
code: &mut CodeDeque,
) {
) -> RegType {
let r = RegType::Temp(self.alloc_reg_to_non_var());
match lvl {
@@ -696,6 +675,8 @@ impl Allocator for DebrayAllocator {
code.push_back(Target::argument_to_variable(r, k));
}
};
r
}
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
@@ -880,7 +861,7 @@ impl Allocator for DebrayAllocator {
self.arity = args.len();
for (idx, arg) in args.iter().enumerate() {
if let Term::Var(_, ref var) = arg {
if let Term::Var(_, var) = arg {
let var_num = var.to_var_num().unwrap();
let r = self.get_binding(var_num);

View File

@@ -1,5 +1,7 @@
:- use_module(library(charsio)).
:- use_module(library(lists)).
:- use_module(library(pio)).
:- use_module(library(dcgs)).
:- initialization(unit_test).
@@ -10,22 +12,12 @@ unit_test :-
Cs = "a£\x2124\".
write_f :-
open('x.txt', write, Stream, [type(binary)]),
F = put_byte(Stream),
chars_utf8bytes("£\x2124\\x2764\\x1F496\\n", Bs),
maplist(F, Bs),
close(Stream).
get_bytes(Stream, Res) :- get_bytes(Stream, [], Res).
get_bytes(Stream, Acc, Res) :-
get_byte(Stream, B),
(B =:= -1 ->
reverse(Acc, Res)
; get_bytes(Stream, [B|Acc], Res)).
maplist(char_code, Cs, Bs),
phrase_to_file(Cs, "x.txt", [type(binary)]).
read_f :-
open('x.txt', read, Stream, [type(binary)]),
get_bytes(Stream, Bs),
chars_utf8bytes(Cs, Bs),
write(Cs),
close(Stream).
phrase_from_file(seq(Cs), "x.txt", [type(binary)]),
maplist(char_code, Cs, Bs),
chars_utf8bytes(Chars, Bs),
write(Chars).

1282
src/ffi.rs

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
use crate::arena::*;
use crate::atom_table::*;
use crate::functor_macro::*;
use crate::instructions::*;
use crate::machine::disjuncts::VarData;
use crate::machine::heap::*;
use crate::machine::loader::PredicateQueue;
use crate::machine::machine_errors::*;
use crate::machine::machine_indices::*;
@@ -18,28 +18,17 @@ use indexmap::{IndexMap, IndexSet};
use ordered_float::OrderedFloat;
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{AddAssign, Deref, DerefMut};
use std::path::PathBuf;
use crate::{is_infix, is_postfix};
use std::sync::Arc;
pub type PredicateKey = (Atom, usize); // name, arity.
/*
// vars of predicate, toplevel offset. Vec<Term> is always a vector
// of vars (we get their adjoining cells this way).
pub type JumpStub = Vec<Term>;
*/
#[derive(Debug)]
pub enum TopLevel {
Fact(Fact, VarData), // Term, line_num, col_num
Rule(Rule, VarData), // Rule, line_num, col_num
}
#[derive(Debug, Clone, Copy)]
pub enum AppendOrPrepend {
Append,
@@ -56,12 +45,6 @@ impl AppendOrPrepend {
}
}
#[derive(Debug, Clone, Copy)]
pub enum VarComparison {
Indistinct,
Distinct,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Level {
Deep,
@@ -84,6 +67,28 @@ pub enum CallPolicy {
Counted,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum GenContext {
Head,
Mid(usize),
Last(usize), // Mid & Last: chunk_num
}
impl GenContext {
#[inline]
pub fn chunk_num(&self) -> usize {
match self {
GenContext::Head => 0,
&GenContext::Mid(cn) | &GenContext::Last(cn) => cn,
}
}
#[inline]
pub fn is_last(self) -> bool {
matches!(self, GenContext::Last(_))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChunkType {
Head,
@@ -120,10 +125,82 @@ impl ChunkType {
}
}
#[derive(Debug, Clone)] //, PartialOrd, PartialEq, Eq, Hash)]
pub(crate) struct BranchNumber {
pub(crate) branch_num: Rational,
pub(crate) delta: Rational,
}
impl Default for BranchNumber {
fn default() -> Self {
Self {
branch_num: Rational::from(0),
delta: Rational::from(1u64 << 31),
}
}
}
impl PartialEq<BranchNumber> for BranchNumber {
#[inline]
fn eq(&self, rhs: &BranchNumber) -> bool {
self.branch_num == rhs.branch_num
}
}
impl Eq for BranchNumber {}
impl Hash for BranchNumber {
#[inline(always)]
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.branch_num.hash(hasher)
}
}
impl PartialOrd<BranchNumber> for BranchNumber {
#[inline]
fn partial_cmp(&self, rhs: &BranchNumber) -> Option<Ordering> {
self.branch_num.partial_cmp(&rhs.branch_num)
}
}
impl BranchNumber {
pub(crate) fn has_as_subbranch(&self, other: &Self) -> bool {
other.delta <= self.delta
&& other.branch_num >= self.branch_num
&& other.branch_num < &self.branch_num + &self.delta
}
pub(crate) fn split(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta / Rational::from(2),
delta: &self.delta / Rational::from(4),
}
}
pub(crate) fn incr_by_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta,
delta: self.delta.clone(),
}
}
pub(crate) fn halve_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone(),
delta: &self.delta / Rational::from(2),
}
}
}
#[derive(Debug)]
pub enum ChunkedTerms {
Branch(Vec<VecDeque<ChunkedTerms>>),
Chunk(VecDeque<QueryTerm>),
Branch {
branch_nums: Vec<Arc<BranchNumber>>,
arms: Vec<VecDeque<ChunkedTerms>>,
},
Chunk {
terms: VecDeque<QueryTerm>,
},
}
#[derive(Debug)]
@@ -157,39 +234,37 @@ impl ChunkedTermVec {
}
pub fn reserve_branch(&mut self, capacity: usize) {
self.chunk_vec
.push_back(ChunkedTerms::Branch(Vec::with_capacity(capacity)));
}
pub fn push_branch_arm(&mut self, branch: VecDeque<ChunkedTerms>) {
match self.chunk_vec.back_mut().unwrap() {
ChunkedTerms::Branch(branches) => {
branches.push(branch);
}
ChunkedTerms::Chunk(_) => {
self.chunk_vec.push_back(ChunkedTerms::Branch(vec![branch]));
}
}
self.chunk_vec.push_back(ChunkedTerms::Branch {
branch_nums: Vec::with_capacity(capacity),
arms: Vec::with_capacity(capacity),
});
}
#[inline]
pub fn add_chunk(&mut self) {
self.chunk_vec
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![])));
self.chunk_vec.push_back(ChunkedTerms::Chunk {
terms: VecDeque::from(vec![]),
});
}
pub fn push_chunk_term(&mut self, term: QueryTerm) {
match self.chunk_vec.back_mut() {
Some(ChunkedTerms::Branch(_)) => {
self.chunk_vec
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
Some(ChunkedTerms::Branch { .. }) => {
let chunk = ChunkedTerms::Chunk {
terms: VecDeque::from(vec![term]),
};
self.chunk_vec.push_back(chunk);
}
Some(ChunkedTerms::Chunk(chunk)) => {
chunk.push_back(term);
Some(ChunkedTerms::Chunk { terms, .. }) => {
terms.push_back(term);
}
None => {
self.chunk_vec
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
let chunk = ChunkedTerms::Chunk {
terms: VecDeque::from(vec![term]),
};
self.chunk_vec.push_back(chunk);
}
}
}
@@ -314,15 +389,15 @@ impl ClauseInfo for Rule {
impl ClauseInfo for PredicateClause {
fn name(&self) -> Option<Atom> {
match self {
PredicateClause::Fact(ref term, ..) => term.head.name(),
PredicateClause::Rule(ref rule, ..) => rule.name(),
PredicateClause::Fact(term, ..) => term.head.name(),
PredicateClause::Rule(rule, ..) => rule.name(),
}
}
fn arity(&self) -> usize {
match self {
PredicateClause::Fact(ref term, ..) => term.head.arity(),
PredicateClause::Rule(ref rule, ..) => rule.arity(),
PredicateClause::Fact(term, ..) => term.head.arity(),
PredicateClause::Rule(rule, ..) => rule.arity(),
}
}
}
@@ -366,9 +441,9 @@ pub enum ModuleSource {
impl ModuleSource {
pub(crate) fn as_functor_stub(&self) -> MachineStub {
match self {
match *self {
ModuleSource::Library(name) => {
functor!(atom!("library"), [atom(name)])
functor!(atom!("library"), [atom_as_cell(name)])
}
ModuleSource::File(name) => {
functor!(name)
@@ -403,16 +478,6 @@ pub struct OpDecl {
pub(crate) name: Atom,
}
#[inline(always)]
pub(crate) fn fixity(spec: u32) -> Fixity {
match spec {
XFY | XFX | YFX => Fixity::In,
XF | YF => Fixity::Post,
FX | FY => Fixity::Pre,
_ => unreachable!(),
}
}
impl OpDecl {
#[inline]
pub(crate) fn new(op_desc: OpDesc, name: Atom) -> Self {
@@ -429,7 +494,7 @@ impl OpDecl {
}
pub(crate) fn insert_into_op_dir(&self, op_dir: &mut OpDir) -> Option<OpDesc> {
let key = (self.name, fixity(self.op_desc.get_spec() as u32));
let key = (self.name, self.op_desc.get_spec().fixity());
if let Some(cell) = op_dir.get_mut(&key) {
let (old_prec, old_spec) = cell.get();
@@ -447,7 +512,7 @@ impl OpDecl {
) -> Result<(), SessionError> {
let (spec, name) = (self.op_desc.get_spec(), self.name);
if is_infix!(spec as u32) {
if spec.is_infix() {
if let Some(desc) = existing_desc {
if desc.post > 0 {
return Err(SessionError::OpIsInfixAndPostFix(name));
@@ -455,7 +520,7 @@ impl OpDecl {
}
}
if is_postfix!(spec as u32) {
if spec.is_postfix() {
if let Some(desc) = existing_desc {
if desc.inf > 0 {
return Err(SessionError::OpIsInfixAndPostFix(name));
@@ -517,7 +582,7 @@ pub(crate) fn fetch_op_spec_from_existing(
op_desc: Option<OpDesc>,
op_dir: &OpDir,
) -> Option<OpDesc> {
if let Some(ref op_desc) = &op_desc {
if let Some(op_desc) = &op_desc {
if op_desc.arity() != arity {
/* it's possible to extend operator functors with
* additional terms. When that happens,
@@ -631,9 +696,9 @@ impl Default for Number {
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Number::Float(fl) => write!(f, "{}", fl),
Number::Integer(n) => write!(f, "{}", n),
Number::Rational(r) => write!(f, "{}", r),
Number::Float(fl) => write!(f, "{fl}"),
Number::Integer(n) => write!(f, "{n}"),
Number::Rational(r) => write!(f, "{r}"),
Number::Fixnum(n) => write!(f, "{}", n.get_num()),
}
}
@@ -702,17 +767,18 @@ impl ArenaFrom<isize> for Number {
impl ArenaFrom<u32> for Number {
#[inline]
fn arena_from(value: u32, _arena: &mut Arena) -> Number {
Number::Fixnum(Fixnum::build_with(value as i64))
Number::Fixnum(Fixnum::build_with(value))
}
}
impl ArenaFrom<i32> for Number {
#[inline]
fn arena_from(value: i32, _arena: &mut Arena) -> Number {
Number::Fixnum(Fixnum::build_with(value as i64))
Number::Fixnum(Fixnum::build_with(value))
}
}
/*
impl ArenaFrom<Number> for Literal {
#[inline]
fn arena_from(value: Number, arena: &mut Arena) -> Literal {
@@ -724,6 +790,21 @@ impl ArenaFrom<Number> for Literal {
}
}
}
*/
impl ArenaFrom<u64> for HeapCellValue {
#[inline]
fn arena_from(value: u64, arena: &mut Arena) -> HeapCellValue {
HeapCellValue::from(fixnum!(Literal, value as i64, arena))
}
}
impl ArenaFrom<usize> for HeapCellValue {
#[inline]
fn arena_from(value: usize, arena: &mut Arena) -> HeapCellValue {
HeapCellValue::arena_from(value as u64, arena)
}
}
impl ArenaFrom<Number> for HeapCellValue {
#[inline]
@@ -731,7 +812,7 @@ impl ArenaFrom<Number> for HeapCellValue {
match value {
Number::Fixnum(n) => fixnum_as_cell!(n),
Number::Integer(n) => typed_arena_ptr_as_cell!(n),
Number::Float(OrderedFloat(n)) => HeapCellValue::from(float_alloc!(n, arena)),
Number::Float(n) => HeapCellValue::from(arena.f64_tbl.build_with(n)),
Number::Rational(n) => typed_arena_ptr_as_cell!(n),
}
}
@@ -762,9 +843,9 @@ impl Number {
pub(crate) fn is_positive(&self) -> bool {
match self {
Number::Fixnum(n) => n.get_num() > 0,
Number::Integer(ref n) => n.is_positive(),
Number::Integer(n) => n.is_positive(),
Number::Float(f) => f.is_sign_positive(),
Number::Rational(ref r) => r.is_positive(),
Number::Rational(r) => r.is_positive(),
}
}
@@ -772,9 +853,9 @@ impl Number {
pub(crate) fn is_negative(&self) -> bool {
match self {
Number::Fixnum(n) => n.get_num() < 0,
Number::Integer(ref n) => n.is_negative(),
Number::Integer(n) => n.is_negative(),
&Number::Float(OrderedFloat(f)) => f.is_sign_negative() && f != -0f64,
Number::Rational(ref r) => r.is_negative(),
Number::Rational(r) => r.is_negative(),
}
}
@@ -782,9 +863,9 @@ impl Number {
pub(crate) fn is_zero(&self) -> bool {
match self {
Number::Fixnum(n) => n.get_num() == 0,
Number::Integer(ref n) => n.is_zero(),
Number::Integer(n) => n.is_zero(),
&Number::Float(OrderedFloat(f)) => f == 0.0 || f == -0.0,
Number::Rational(ref r) => r.is_zero(),
Number::Rational(r) => r.is_zero(),
}
}
@@ -794,10 +875,10 @@ impl Number {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Copy, Clone)]
pub(crate) enum OptArgIndexKey {
Literal(usize, usize, Literal, Vec<Literal>), // index, IndexingCode location, opt arg, alternatives
List(usize, usize), // index, IndexingCode location
Literal(usize, usize, Literal, Option<Literal>), // index, IndexingCode location, opt arg, alternatives
List(usize, usize), // index, IndexingCode location
None,
Structure(usize, usize, Atom, usize), // index, IndexingCode location, name, arity
}
@@ -839,9 +920,9 @@ impl OptArgIndexKey {
#[inline]
pub(crate) fn set_switch_on_term_loc(&mut self, value: usize) {
match self {
OptArgIndexKey::Literal(_, ref mut loc, ..)
| OptArgIndexKey::Structure(_, ref mut loc, ..)
| OptArgIndexKey::List(_, ref mut loc) => {
OptArgIndexKey::Literal(_, loc, ..)
| OptArgIndexKey::Structure(_, loc, ..)
| OptArgIndexKey::List(_, loc) => {
*loc = value;
}
OptArgIndexKey::None => {}
@@ -853,9 +934,9 @@ impl AddAssign<usize> for OptArgIndexKey {
#[inline]
fn add_assign(&mut self, n: usize) {
match self {
OptArgIndexKey::Literal(_, ref mut o, ..)
| OptArgIndexKey::List(_, ref mut o)
| OptArgIndexKey::Structure(_, ref mut o, ..) => {
OptArgIndexKey::Literal(_, o, ..)
| OptArgIndexKey::List(_, o)
| OptArgIndexKey::Structure(_, o, ..) => {
*o += n;
}
OptArgIndexKey::None => {}

798
src/functor_macro.rs Normal file
View File

@@ -0,0 +1,798 @@
//! A macro to construct functor terms ready to be written to the WAM
//! heap.
use crate::atom_table::*;
use crate::instructions::IndexingCodePtr;
use crate::machine::heap::Heap;
use crate::parser::ast::Fixnum;
use crate::types::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum FunctorElement {
AbsoluteCell(HeapCellValue),
Cell(HeapCellValue),
InnerFunctor(u64, Vec<FunctorElement>),
String(u64, String),
}
// helper macros
macro_rules! count {
() => (0);
( $x:tt $($xs:tt)* ) => (1 + count!($($xs)*));
}
// core macros
/*
* functor! is more declarative now, with fewer effects and more
* work done at compile time using const functions. With these
* advantages come new quirks: expressions must generally be wrapped
* in round parentheses for rustc to parse them. See the tests module
* below for examples, especially those involving atom!
* subexpressions.
*/
macro_rules! functor {
($name:expr) => ({
vec![FunctorElement::Cell(atom_as_cell!($name))]
});
($name:expr, [$($dt:ident($($value:tt),*)),+]) => ({
build_functor!([$($dt($($value),*)),*],
[FunctorElement::Cell(atom_as_cell!($name, count!($($dt) *)))],
1,
[])
});
}
macro_rules! inner_functor {
($name:expr, $res_len:expr, [$($dt:ident($($value:tt),*)),+]) => ({
build_functor!([$($dt($($value),*)),*],
[FunctorElement::Cell(atom_as_cell!($name, count!($($dt) *)))],
1 + $res_len,
[])
});
}
macro_rules! build_functor {
([], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
vec![$($res,)* $($subfunctor),*]
});
([indexing_code_ptr($e:expr) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
let (inner_functor, cell_size) = indexing_code_ptr($e);
let referent = if cell_size == 1 {
heap_loc_as_cell!(1u64 + count!($($dt)*) + $res_len)
} else {
str_loc_as_cell!(1u64 + count!($($dt)*) + $res_len)
};
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(referent)],
1 + cell_size + $res_len,
[$($subfunctor, )* FunctorElement::InnerFunctor(cell_size, inner_functor)])
});
([fixnum($e:expr) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(fixnum_as_cell!(/*FIXME this is not safe*/ unsafe{Fixnum::build_with_unchecked($e as i64)}))],
1 + $res_len,
[$($subfunctor),*])
});
([cell($e:expr) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::AbsoluteCell($e)],
1 + $res_len,
[$($subfunctor),*])
});
([literal($e:expr) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::AbsoluteCell(HeapCellValue::from($e))],
1 + $res_len,
[$($subfunctor),*])
});
([number($n:expr, $arena:expr) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
let number_cell = HeapCellValue::arena_from($n, $arena);
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(number_cell)],
1 + $res_len,
[$($subfunctor),*])
});
([list([]) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(empty_list_as_cell!())],
1 + $res_len,
[$($subfunctor),*])
});
([list([$id:ident($($id_value:tt),*) $(, $in_dt:ident($($in_value:tt),*))*]) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
build_functor!([functor((atom!(".")), [$id($($id_value),*), list([$($in_dt($($in_value),*)),*])])
$(, $dt($($value),*))*],
[$($res),*],
$res_len,
[$($subfunctor),*])
});
([string($s:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
#[allow(unused_parens)]
let string = $s;
let pstr_len = cell_index!(Heap::compute_pstr_size(&string)) as u64;
let result_len = 1 + count!($($dt)*) + $res_len;
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(pstr_loc_as_cell!(heap_index!(result_len as usize) as u64))],
1 + $res_len + pstr_len,
[$($subfunctor, )* FunctorElement::String(pstr_len, string)])
});
([atom_as_cell($n:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(atom_as_cell!($n))],
1 + $res_len,
[$($subfunctor),*])
});
([functor($stub:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
let result_len = 1u64 + count!($($dt)*) + $res_len;
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&$stub)) as u64;
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(str_loc_as_cell!(result_len))],
1 + $res_len + inner_functor_size,
[$($subfunctor, )*
FunctorElement::InnerFunctor(inner_functor_size, $stub)])
});
([$id:ident($n:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell($id!($n))],
1 + $res_len,
[$($subfunctor),*])
});
([functor($name:expr, [$($in_dt:ident($($in_value:tt),*)),+]) $(, $dt:ident($($value:tt),*))*],
[$($res:expr),*],
$res_len:expr,
[$($subfunctor:expr),*]) => ({
let result_len = 1u64 + count!($($dt)*) + $res_len;
let inner_functor = inner_functor!($name, 0, [$($in_dt($($in_value),*)),*]);
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&inner_functor)) as u64;
build_functor!([$($dt($($value),*)),*],
[$($res, )* FunctorElement::Cell(str_loc_as_cell!(result_len))],
1 + $res_len + inner_functor_size,
[$($subfunctor, )*
FunctorElement::InnerFunctor(inner_functor_size, inner_functor)])
});
}
pub(crate) fn indexing_code_ptr(code_ptr: IndexingCodePtr) -> (Vec<FunctorElement>, u64) {
match code_ptr {
IndexingCodePtr::DynamicExternal(o) => {
(functor!(atom!("dynamic_external"), [fixnum(o)]), 2)
}
IndexingCodePtr::External(o) => (functor!(atom!("external"), [fixnum(o)]), 2),
IndexingCodePtr::Internal(o) => (functor!(atom!("internal"), [fixnum(o)]), 2),
IndexingCodePtr::Fail => (vec![FunctorElement::Cell(atom_as_cell!(atom!("fail")))], 1),
}
}
pub(crate) fn variadic_functor(
name: Atom,
arity: usize,
iter: impl Iterator<Item = Vec<FunctorElement>>,
) -> Vec<FunctorElement> {
let mut arg_vec = vec![
FunctorElement::Cell(atom_as_cell!(name, arity)),
FunctorElement::Cell(list_loc_as_cell!(2)),
];
let key_value_pairs: Vec<_> = iter.collect();
let num_items = key_value_pairs.len();
let mut functor_offset = 0;
for (idx, kv_func) in key_value_pairs.iter().enumerate() {
let functor_size = cell_index!(Heap::compute_functor_byte_size(kv_func));
arg_vec.push(FunctorElement::Cell(str_loc_as_cell!(
2 + num_items * 2 + functor_offset
)));
arg_vec.push(FunctorElement::Cell(list_loc_as_cell!(4 + 2 * idx)));
functor_offset += functor_size;
}
arg_vec.pop();
arg_vec.push(FunctorElement::Cell(empty_list_as_cell!()));
arg_vec.extend(key_value_pairs.into_iter().map(|kv_func| {
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&kv_func));
FunctorElement::InnerFunctor(inner_functor_size as u64, kv_func)
}));
arg_vec
}
#[cfg(test)]
#[allow(unused_parens)]
mod tests {
use super::*;
use FunctorElement::*;
use indexmap::indexmap;
use std::string::String;
#[test]
fn basic_terms() {
let functor = functor!(
atom!("first"),
[atom_as_cell((atom!("a"))), char_as_cell('c')]
);
assert_eq!(functor.len(), 3);
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("first"), 2)));
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
assert_eq!(functor[2], Cell(char_as_cell!('c')));
let functor = functor!(
atom!("second"),
[
atom_as_cell((atom!("a"))),
functor((atom!("b")), [fixnum(1), fixnum(2)]),
char_as_cell('c')
]
);
assert_eq!(functor.len(), 5);
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("second"), 3)));
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
assert_eq!(functor[2], Cell(str_loc_as_cell!(4)));
assert_eq!(functor[3], Cell(char_as_cell!('c')));
assert_eq!(
functor[4],
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
);
let functor = functor!(
atom!("third"),
[
atom_as_cell((atom!("a"))),
functor((atom!("b")), [fixnum(1), fixnum(2)]),
functor((atom!("c")), [fixnum(1), fixnum(2)]),
char_as_cell('c')
]
);
assert_eq!(functor.len(), 7);
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("third"), 4)));
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
assert_eq!(functor[2], Cell(str_loc_as_cell!(5)));
assert_eq!(functor[3], Cell(str_loc_as_cell!(8)));
assert_eq!(functor[4], Cell(char_as_cell!('c')));
assert_eq!(
functor[5],
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
);
assert_eq!(
functor[6],
InnerFunctor(3, functor!(atom!("c"), [fixnum(1), fixnum(2)]))
);
let functor = functor!(
atom!("fourth"),
[
atom_as_cell((atom!("a"))),
functor((atom!("b")), [fixnum(1), fixnum(2)]),
functor((atom!("c")), [fixnum(1)]),
functor((atom!("d")), [fixnum(453), fixnum(2)]),
char_as_cell('c')
]
);
assert_eq!(functor.len(), 9);
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("fourth"), 5)));
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
assert_eq!(functor[2], Cell(str_loc_as_cell!(6)));
assert_eq!(functor[3], Cell(str_loc_as_cell!(9)));
assert_eq!(functor[4], Cell(str_loc_as_cell!(11)));
assert_eq!(functor[5], Cell(char_as_cell!('c')));
assert_eq!(
functor[6],
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
);
assert_eq!(
functor[7],
InnerFunctor(2, functor!(atom!("c"), [fixnum(1)]))
);
assert_eq!(
functor[8],
InnerFunctor(3, functor!(atom!("d"), [fixnum(453), fixnum(2)]))
);
}
#[test]
fn basic_terms_in_heap() {
let functor = functor!(
atom!("first"),
[atom_as_cell((atom!("a"))), char_as_cell('b')]
);
assert_eq!(functor.len(), 3);
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(functor);
let loc = functor_writer(&mut heap).unwrap();
assert_eq!(loc, str_loc_as_cell!(0));
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 2));
assert_eq!(heap[1], atom_as_cell!(atom!("a")));
assert_eq!(heap[2], char_as_cell!('b'));
heap.truncate(2);
let functor = functor!(
atom!("second"),
[
atom_as_cell((atom!("a"))),
functor((atom!("b")), [fixnum(1), fixnum(2)]),
functor((atom!("c")), [fixnum(1), fixnum(2)]),
char_as_cell('b')
]
);
assert_eq!(functor.len(), 7);
let mut functor_writer = Heap::functor_writer(functor);
let loc = functor_writer(&mut heap).unwrap();
assert_eq!(loc, str_loc_as_cell!(2));
assert_eq!(heap[2], atom_as_cell!(atom!("second"), 4));
assert_eq!(heap[3], atom_as_cell!(atom!("a")));
assert_eq!(heap[4], str_loc_as_cell!(7));
assert_eq!(heap[5], str_loc_as_cell!(10));
assert_eq!(heap[6], char_as_cell!('b'));
assert_eq!(heap[7], atom_as_cell!(atom!("b"), 2));
assert_eq!(heap[8], fixnum_as_cell!(Fixnum::build_with(1)));
assert_eq!(heap[9], fixnum_as_cell!(Fixnum::build_with(2)));
assert_eq!(heap[10], atom_as_cell!(atom!("c"), 2));
assert_eq!(heap[11], fixnum_as_cell!(Fixnum::build_with(1)));
assert_eq!(heap[12], fixnum_as_cell!(Fixnum::build_with(2)));
}
#[test]
fn nested_functors() {
let functor = functor!(
atom!("first"),
[
atom_as_cell((atom!("a"))),
functor(
(atom!("d")),
[
fixnum(1),
functor(
(atom!("b")),
[atom_as_cell((atom!("c"))), char_as_cell('c')]
)
]
),
functor((atom!("e")), [fixnum(453), fixnum(2)]),
char_as_cell('b')
]
);
assert_eq!(functor.len(), 7);
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("first"), 4)));
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
assert_eq!(functor[2], Cell(str_loc_as_cell!(5)));
assert_eq!(functor[3], Cell(str_loc_as_cell!(11)));
assert_eq!(functor[4], Cell(char_as_cell!('b')));
assert_eq!(
functor[5],
InnerFunctor(
6,
vec![
Cell(atom_as_cell!(atom!("d"), 2)),
Cell(fixnum_as_cell!(Fixnum::build_with(1))),
Cell(str_loc_as_cell!(3)),
InnerFunctor(
3,
functor!(atom!("b"), [atom_as_cell((atom!("c"))), char_as_cell('c')])
)
]
)
);
assert_eq!(
functor[6],
InnerFunctor(3, functor!(atom!("e"), [fixnum(453), fixnum(2)]))
);
}
#[test]
fn nested_functors_in_heap() {
let functor = functor!(
atom!("first"),
[
atom_as_cell((atom!("a"))),
functor(
(atom!("second")),
[
fixnum(1),
functor(
(atom!("third")),
[atom_as_cell((atom!("b"))), char_as_cell('c')]
)
]
),
functor((atom!("fourth")), [fixnum(453), fixnum(2)]),
char_as_cell('b')
]
);
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(functor);
let loc = functor_writer(&mut heap).unwrap();
assert_eq!(loc, str_loc_as_cell!(0));
assert_eq!(heap.cell_len(), 14);
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 4));
assert_eq!(heap[1], atom_as_cell!(atom!("a")));
assert_eq!(heap[2], str_loc_as_cell!(5));
assert_eq!(heap[3], str_loc_as_cell!(11));
assert_eq!(heap[4], char_as_cell!('b'));
assert_eq!(heap[5], atom_as_cell!(atom!("second"), 2));
assert_eq!(heap[6], fixnum_as_cell!(Fixnum::build_with(1)));
assert_eq!(heap[7], str_loc_as_cell!(8));
assert_eq!(heap[8], atom_as_cell!(atom!("third"), 2));
assert_eq!(heap[9], atom_as_cell!(atom!("b")));
assert_eq!(heap[10], char_as_cell!('c'));
assert_eq!(heap[11], atom_as_cell!(atom!("fourth"), 2));
assert_eq!(heap[12], fixnum_as_cell!(Fixnum::build_with(453)));
assert_eq!(heap[13], fixnum_as_cell!(Fixnum::build_with(2)));
}
#[test]
fn functors_with_strings_in_heap() {
let functor = functor!(atom!("first"), [string((String::from("a string")))]);
assert_eq!(functor.len(), 3);
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(functor);
let loc = functor_writer(&mut heap).unwrap();
assert_eq!(loc, str_loc_as_cell!(0));
assert_eq!(heap.cell_len(), 5);
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 1));
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(2)));
assert_eq!(
heap.slice_to_str(heap_index!(2), "a string".len()),
"a string"
);
assert_eq!(heap[4], empty_list_as_cell!());
heap.truncate(0);
let functor = functor!(
atom!("second"),
[string((String::from("a stuttered\0 string")))]
);
let mut functor_writer = Heap::functor_writer(functor);
functor_writer(&mut heap).unwrap();
assert_eq!(heap.cell_len(), 10);
assert_eq!(heap[0], atom_as_cell!(atom!("second"), 1));
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(2)));
assert_eq!(
heap.slice_to_str(heap_index!(2), "a stuttered".len()),
"a stuttered"
);
assert_eq!(heap[4], list_loc_as_cell!(5));
assert_eq!(heap[5], char_as_cell!('\u{0}'));
assert_eq!(heap[6], pstr_loc_as_cell!(heap_index!(7)));
assert_eq!(
heap.slice_to_str(heap_index!(7), " string".len()),
" string"
);
assert_eq!(heap[9], empty_list_as_cell!());
}
#[test]
fn functors_with_lists_in_heap() {
let functor = functor!(
atom!("first"),
[list([fixnum(1), atom_as_cell((atom!("a"))), fixnum(2)])]
);
assert_eq!(functor.len(), 3);
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(functor);
functor_writer(&mut heap).unwrap();
assert_eq!(heap.cell_len(), 11);
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 1));
assert_eq!(heap[1], str_loc_as_cell!(2));
assert_eq!(heap[2], atom_as_cell!(atom!("."), 2));
assert_eq!(heap[3], fixnum_as_cell!(Fixnum::build_with(1)));
assert_eq!(heap[4], str_loc_as_cell!(5));
assert_eq!(heap[5], atom_as_cell!(atom!("."), 2));
assert_eq!(heap[6], atom_as_cell!(atom!("a")));
assert_eq!(heap[7], str_loc_as_cell!(8));
assert_eq!(heap[8], atom_as_cell!(atom!("."), 2));
assert_eq!(heap[9], fixnum_as_cell!(Fixnum::build_with(2)));
assert_eq!(heap[10], empty_list_as_cell!());
}
#[test]
fn inlined_atoms() {
let atom_table = AtomTable::new().unwrap();
let inlined = AtomTable::build_with(&atom_table, "inline");
assert!(inlined.is_inlined());
assert_eq!(&*inlined.as_str(), "inline");
let non_inlined = AtomTable::build_with(&atom_table, "longer non-inlined atom");
assert!(!non_inlined.is_inlined());
assert_eq!(&*non_inlined.as_str(), "longer non-inlined atom");
}
#[test]
fn functors_with_indexing_code_ptr() {
let code_ptr = IndexingCodePtr::Internal(0);
let functor = functor!(
atom!("first"),
[
string((String::from("a string"))),
indexing_code_ptr(code_ptr)
]
);
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(functor);
functor_writer(&mut heap).unwrap();
assert_eq!(heap.cell_len(), 8);
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 2));
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
assert_eq!(heap[2], str_loc_as_cell!(6));
assert_eq!(
heap.slice_to_str(heap_index!(3), "a string".len()),
"a string"
);
assert_eq!(heap[5], empty_list_as_cell!());
assert_eq!(heap[6], atom_as_cell!(atom!("internal"), 1));
assert_eq!(heap[7], fixnum_as_cell!(Fixnum::build_with(0)));
heap.truncate(0);
let functor = functor!(
atom!("second"),
[
string((String::from("a string"))),
functor(
(atom!("third")),
[
atom_as_cell((atom!("a"))),
string((String::from("another string"))),
indexing_code_ptr(code_ptr)
]
)
]
);
let mut functor_writer = Heap::functor_writer(functor);
functor_writer(&mut heap).unwrap();
assert_eq!(heap.cell_len(), 15);
assert_eq!(heap[0], atom_as_cell!(atom!("second"), 2));
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
assert_eq!(heap[2], str_loc_as_cell!(6));
assert_eq!(
heap.slice_to_str(heap_index!(3), "a string".len()),
"a string"
);
assert_eq!(heap[5], empty_list_as_cell!());
assert_eq!(heap[6], atom_as_cell!(atom!("third"), 3));
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
assert_eq!(heap[8], pstr_loc_as_cell!(heap_index!(10)));
assert_eq!(heap[9], str_loc_as_cell!(13));
assert_eq!(
heap.slice_to_str(heap_index!(10), "another string".len()),
"another string"
);
assert_eq!(heap[12], empty_list_as_cell!());
assert_eq!(heap[13], atom_as_cell!(atom!("internal"), 1));
assert_eq!(heap[14], fixnum_as_cell!(Fixnum::build_with(0)));
let functor = functor!(
atom!("fourth"),
[
string((String::from("a string"))),
functor(
(atom!("a")),
[
functor(
(atom!("fifth")),
[
fixnum(5),
string((String::from("another string"))),
indexing_code_ptr(code_ptr)
]
),
string((String::from("and another")))
]
)
]
);
heap.truncate(0);
let mut functor_writer = Heap::functor_writer(functor);
functor_writer(&mut heap).unwrap();
assert_eq!(heap.cell_len(), 21);
assert_eq!(heap[0], atom_as_cell!(atom!("fourth"), 2));
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
assert_eq!(heap[2], str_loc_as_cell!(6));
assert_eq!(
heap.slice_to_str(heap_index!(3), "a string".len()),
"a string"
);
assert_eq!(heap[5], empty_list_as_cell!());
assert_eq!(heap[6], atom_as_cell!(atom!("a"), 2));
assert_eq!(heap[7], str_loc_as_cell!(9));
assert_eq!(heap[8], pstr_loc_as_cell!(heap_index!(18))); // <-- wrong!
assert_eq!(heap[9], atom_as_cell!(atom!("fifth"), 3));
assert_eq!(heap[10], fixnum_as_cell!(Fixnum::build_with(5)));
assert_eq!(heap[11], pstr_loc_as_cell!(heap_index!(13)));
assert_eq!(heap[12], str_loc_as_cell!(16));
assert_eq!(
heap.slice_to_str(heap_index!(13), "another string".len()),
"another string"
);
assert_eq!(heap[15], empty_list_as_cell!());
assert_eq!(heap[16], atom_as_cell!(atom!("internal"), 1));
assert_eq!(heap[17], fixnum_as_cell!(Fixnum::build_with(0)));
assert_eq!(
heap.slice_to_str(heap_index!(18), "and another".len()),
"and another"
);
assert_eq!(heap[20], empty_list_as_cell!());
let constants = indexmap![
atom_as_cell!(atom!("a")) => IndexingCodePtr::External(2),
atom_as_cell!(atom!("d")) => IndexingCodePtr::External(7),
];
let functor = variadic_functor(
atom!("switch_on_constants"),
1,
constants
.iter()
.map(|(c, ptr)| functor!(atom!(":"), [cell((*c)), indexing_code_ptr((*ptr))])),
);
heap.truncate(0);
let mut functor_writer = Heap::functor_writer(functor);
functor_writer(&mut heap).unwrap();
assert_eq!(heap[0], atom_as_cell!(atom!("switch_on_constants"), 1));
assert_eq!(heap[1], list_loc_as_cell!(2));
assert_eq!(heap[2], str_loc_as_cell!(6));
assert_eq!(heap[3], list_loc_as_cell!(4));
assert_eq!(heap[4], str_loc_as_cell!(11));
assert_eq!(heap[5], empty_list_as_cell!());
assert_eq!(heap[6], atom_as_cell!(atom!(":"), 2));
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
assert_eq!(heap[8], str_loc_as_cell!(9));
assert_eq!(heap[9], atom_as_cell!(atom!("external"), 1));
assert_eq!(heap[10], fixnum_as_cell!(Fixnum::build_with(2)));
assert_eq!(heap[11], atom_as_cell!(atom!(":"), 2));
assert_eq!(heap[12], atom_as_cell!(atom!("d")));
assert_eq!(heap[13], str_loc_as_cell!(14));
assert_eq!(heap[14], atom_as_cell!(atom!("external"), 1));
assert_eq!(heap[15], fixnum_as_cell!(Fixnum::build_with(7)));
}
#[test]
fn undefined_procedure_functor() {
// existence_error
let culprit = functor!(atom!("/"), [atom_as_cell((atom!("a"))), fixnum(1)]);
let stub = functor!(
atom!("existence_error"),
[
atom_as_cell((atom!("procedure"))),
functor((culprit.clone()))
]
);
println!("{stub:?}");
// now the error form
let lineless_error_form = functor!(atom!("error"), [functor(stub), functor(culprit)]);
println!("{lineless_error_form:?}");
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(lineless_error_form);
functor_writer(&mut heap).unwrap();
assert_eq!(heap[0], atom_as_cell!(atom!("error"), 2));
assert_eq!(heap[1], str_loc_as_cell!(3));
assert_eq!(heap[2], str_loc_as_cell!(9));
assert_eq!(heap[3], atom_as_cell!(atom!("existence_error"), 2));
assert_eq!(heap[4], atom_as_cell!(atom!("procedure")));
assert_eq!(heap[5], str_loc_as_cell!(6)); // is str_loc_as_cell!(3)
assert_eq!(heap[6], atom_as_cell!(atom!("/"), 2));
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
assert_eq!(heap[8], fixnum_as_cell!(Fixnum::build_with(1)));
assert_eq!(heap[9], atom_as_cell!(atom!("/"), 2));
assert_eq!(heap[10], atom_as_cell!(atom!("a")));
assert_eq!(heap[11], fixnum_as_cell!(Fixnum::build_with(1)));
}
#[test]
fn argless_functor() {
let name = functor!(atom!("[]"));
assert_eq!(name.len(), 1);
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(name);
let loc = functor_writer(&mut heap).unwrap();
assert_eq!(loc, heap_loc_as_cell!(0));
}
#[test]
fn predefined_subfunctors() {
let stub = functor!(atom!("sub"), [atom_as_cell((atom!("[]")))]);
let name = functor!(atom!("super"), [functor(stub)]);
let mut heap = Heap::new();
let mut functor_writer = Heap::functor_writer(name);
functor_writer(&mut heap).unwrap();
assert_eq!(heap.cell_len(), 4);
assert_eq!(heap[0], atom_as_cell!(atom!("super"), 1));
assert_eq!(heap[1], str_loc_as_cell!(2));
assert_eq!(heap[2], atom_as_cell!(atom!("sub"), 1));
assert_eq!(heap[3], empty_list_as_cell!());
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,12 @@
use std::io::BufRead;
use bytes::{Bytes, buf::Reader};
use std::sync::{Arc, Condvar, Mutex};
use tokio::sync::Notify;
use warp::http;
pub struct HttpListener {
pub incoming: std::sync::mpsc::Receiver<HttpRequest>,
pub warp_shutdown: Arc<Notify>,
}
pub struct HttpRequest {
@@ -19,5 +21,5 @@ pub struct HttpRequestData {
pub headers: http::HeaderMap,
pub path: String,
pub query: String,
pub body: Box<dyn BufRead + Send>,
pub body: Reader<Bytes>,
}

View File

@@ -3,6 +3,7 @@ use crate::parser::ast::*;
use crate::forms::*;
use crate::instructions::*;
use crate::types::HeapCellValue;
use fxhash::FxBuildHasher;
use indexmap::IndexMap;
@@ -112,15 +113,15 @@ impl<'a> IndexingCodeMergingPtr<'a> {
match constant_key {
Some(OptArgIndexKey::Literal(_, _, constant, _)) => {
constants.insert(*constant, constant_ptr);
constants.insert(HeapCellValue::from(*constant), constant_ptr);
}
_ if constant_ptr.is_external() => {
// this must be a defunct clause, because it's been deleted
// from the skeleton.
debug_assert!(constant_key.is_none());
}
_ => {
if let IndexingCodePtr::DynamicExternal(_) = constant_ptr {
// this must be a defunct clause, because it's been deleted
// from the skeleton.
} else {
unreachable!()
}
unreachable!()
}
}
@@ -144,7 +145,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
fn add_static_indexed_choice_for_constant(
&mut self,
external: usize,
constant: Literal,
constant: HeapCellValue,
index: usize,
) {
let third_level_index = if self.append_or_prepend.is_append() {
@@ -166,7 +167,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::IndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref mut constants)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants)) => {
constants.insert(
constant,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -181,7 +182,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
fn add_dynamic_indexed_choice_for_constant(
&mut self,
external: usize,
constant: Literal,
constant: HeapCellValue,
index: usize,
) {
let third_level_index = if self.append_or_prepend.is_append() {
@@ -195,7 +196,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref mut constants)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants)) => {
constants.insert(
constant,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -209,22 +210,22 @@ impl<'a> IndexingCodeMergingPtr<'a> {
fn extend_indexed_choice(&mut self, index: usize) {
match &mut self.indexing_code[self.offset] {
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs)
IndexingLine::IndexedChoice(indexed_choice_instrs)
if self.append_or_prepend.is_append() =>
{
uncap_choice_seq_with_trust(indexed_choice_instrs.make_contiguous());
indexed_choice_instrs.push_back(IndexedChoiceInstruction::Trust(index));
}
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
uncap_choice_seq_with_try(indexed_choice_instrs.make_contiguous());
indexed_choice_instrs.push_front(IndexedChoiceInstruction::Try(index));
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs)
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs)
if self.append_or_prepend.is_append() =>
{
indexed_choice_instrs.push_back(index);
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
indexed_choice_instrs.push_front(index);
}
_ => {
@@ -235,15 +236,15 @@ impl<'a> IndexingCodeMergingPtr<'a> {
fn index_overlapping_constant(
&mut self,
orig_constant: Literal,
overlapping_constant: Literal,
orig_constant: HeapCellValue,
overlapping_constant: HeapCellValue,
index: usize,
) {
loop {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => {
match *c {
IndexingCodePtr::Fail if self.is_dynamic => {
*c = IndexingCodePtr::DynamicExternal(index);
@@ -316,12 +317,12 @@ impl<'a> IndexingCodeMergingPtr<'a> {
}
}
fn index_constant(&mut self, constant: Literal, index: usize) {
fn index_constant(&mut self, constant: HeapCellValue, index: usize) {
loop {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => {
match *c {
IndexingCodePtr::Fail if self.is_dynamic => {
*c = IndexingCodePtr::DynamicExternal(index);
@@ -444,7 +445,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::IndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
structures.insert(
key,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -473,7 +474,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
structures.insert(
key,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -490,33 +491,29 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
_,
_,
ref mut s,
)) => match *s {
IndexingCodePtr::Fail if self.is_dynamic => {
*s = IndexingCodePtr::DynamicExternal(index);
break;
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, s)) => {
match *s {
IndexingCodePtr::Fail if self.is_dynamic => {
*s = IndexingCodePtr::DynamicExternal(index);
break;
}
IndexingCodePtr::Fail => {
*s = IndexingCodePtr::External(index);
break;
}
IndexingCodePtr::DynamicExternal(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::DynamicExternal(o));
}
IndexingCodePtr::External(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::External(o));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
}
}
IndexingCodePtr::Fail => {
*s = IndexingCodePtr::External(index);
break;
}
IndexingCodePtr::DynamicExternal(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::DynamicExternal(o));
}
IndexingCodePtr::External(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::External(o));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
}
},
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
match structures.get(&key).cloned() {
None | Some(IndexingCodePtr::Fail) if self.is_dynamic => {
@@ -558,52 +555,50 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, ref mut l, _)) => {
match *l {
IndexingCodePtr::Fail if self.is_dynamic => {
*l = IndexingCodePtr::DynamicExternal(index);
}
IndexingCodePtr::Fail => {
*l = IndexingCodePtr::External(index);
}
IndexingCodePtr::DynamicExternal(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![o, index].into()
} else {
vec![index, o].into()
};
self.indexing_code
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
}
IndexingCodePtr::External(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![
IndexedChoiceInstruction::Try(o),
IndexedChoiceInstruction::Trust(index),
]
.into()
} else {
vec![
IndexedChoiceInstruction::Try(index),
IndexedChoiceInstruction::Trust(o),
]
.into()
};
self.indexing_code
.push(IndexingLine::IndexedChoice(third_level_index));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
self.extend_indexed_choice(index);
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, l, _)) => match *l {
IndexingCodePtr::Fail if self.is_dynamic => {
*l = IndexingCodePtr::DynamicExternal(index);
}
}
IndexingCodePtr::Fail => {
*l = IndexingCodePtr::External(index);
}
IndexingCodePtr::DynamicExternal(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![o, index].into()
} else {
vec![index, o].into()
};
self.indexing_code
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
}
IndexingCodePtr::External(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![
IndexedChoiceInstruction::Try(o),
IndexedChoiceInstruction::Trust(index),
]
.into()
} else {
vec![
IndexedChoiceInstruction::Try(index),
IndexedChoiceInstruction::Trust(o),
]
.into()
};
self.indexing_code
.push(IndexingLine::IndexedChoice(third_level_index));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
self.extend_indexed_choice(index);
}
},
_ => {
unreachable!()
}
@@ -631,14 +626,17 @@ pub(crate) fn merge_clause_index(
);
match &opt_arg_index_key {
OptArgIndexKey::Literal(_, index_loc, constant, ref overlapping_constants) => {
OptArgIndexKey::Literal(_, index_loc, constant, overlapping_constants) => {
let offset = new_clause_loc - index_loc + 1;
merging_ptr.index_constant(*constant, offset);
merging_ptr.index_constant(HeapCellValue::from(*constant), offset);
for overlapping_constant in overlapping_constants {
if let Some(overlapping_constant) = overlapping_constants {
merging_ptr.offset = 0;
merging_ptr.index_overlapping_constant(*constant, *overlapping_constant, offset);
merging_ptr.index_overlapping_constant(
HeapCellValue::from(*constant),
HeapCellValue::from(*overlapping_constant),
offset,
);
}
}
OptArgIndexKey::Structure(_, index_loc, name, arity) => {
@@ -664,7 +662,7 @@ pub(crate) fn merge_clause_index(
pub(crate) fn remove_constant_indices(
constant: Literal,
overlapping_constants: &[Literal],
overlapping_constants: Option<Literal>,
indexing_code: &mut [IndexingLine],
offset: usize,
) {
@@ -672,20 +670,18 @@ pub(crate) fn remove_constant_indices(
let iter = once(&constant).chain(overlapping_constants.iter());
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
match *c {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*c = IndexingCodePtr::Fail;
return;
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => match *c {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*c = IndexingCodePtr::Fail;
return;
}
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
},
_ => {
unreachable!()
}
@@ -693,21 +689,17 @@ pub(crate) fn remove_constant_indices(
let mut constants_index = 0;
for constant in iter {
for constant in iter.map(|l| HeapCellValue::from(*l)) {
loop {
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants)) => {
constants_index = index;
let constant = *constant;
match constants.get(&constant).cloned() {
Some(IndexingCodePtr::DynamicExternal(_))
| Some(IndexingCodePtr::External(_))
| Some(IndexingCodePtr::Fail) => {
constants.remove(&constant);
constants.swap_remove(&constant);
break;
}
Some(IndexingCodePtr::Internal(o)) => {
@@ -718,7 +710,7 @@ pub(crate) fn remove_constant_indices(
}
}
}
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
StaticCodeIndices::remove_instruction_with_offset(
indexed_choice_instrs,
offset,
@@ -732,15 +724,15 @@ pub(crate) fn remove_constant_indices(
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
ref mut c,
c,
..,
)) => {
*c = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
constants,
)) => {
constants.insert(*constant, ext);
constants.insert(constant, ext);
}
_ => {
unreachable!()
@@ -751,7 +743,7 @@ pub(crate) fn remove_constant_indices(
break;
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
DynamicCodeIndices::remove_instruction_with_offset(
indexed_choice_instrs,
offset,
@@ -765,15 +757,15 @@ pub(crate) fn remove_constant_indices(
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
ref mut c,
c,
..,
)) => {
*c = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
constants,
)) => {
constants.insert(*constant, ext);
constants.insert(constant, ext);
}
_ => {
unreachable!()
@@ -792,11 +784,11 @@ pub(crate) fn remove_constant_indices(
}
match &indexing_code[constants_index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref constants))
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants))
if constants.is_empty() =>
{
match &mut indexing_code[0] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => {
*c = IndexingCodePtr::Fail;
}
_ => {
@@ -817,20 +809,18 @@ pub(crate) fn remove_structure_index(
let mut index = 0;
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, ref mut s)) => {
match *s {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*s = IndexingCodePtr::Fail;
return;
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, s)) => match *s {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*s = IndexingCodePtr::Fail;
return;
}
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
},
_ => {
unreachable!()
}
@@ -840,13 +830,13 @@ pub(crate) fn remove_structure_index(
loop {
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
structures_index = index;
match structures.get(&(name, arity)).cloned() {
Some(IndexingCodePtr::DynamicExternal(_))
| Some(IndexingCodePtr::External(_)) => {
structures.remove(&(name, arity));
structures.swap_remove(&(name, arity));
break;
}
Some(IndexingCodePtr::Internal(o)) => {
@@ -857,7 +847,7 @@ pub(crate) fn remove_structure_index(
}
}
}
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
StaticCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -870,12 +860,12 @@ pub(crate) fn remove_structure_index(
_,
_,
_,
ref mut s,
s,
)) => {
*s = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
ref mut structures,
structures,
)) => {
structures.insert((name, arity), ext);
}
@@ -888,7 +878,7 @@ pub(crate) fn remove_structure_index(
break;
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
DynamicCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -901,12 +891,12 @@ pub(crate) fn remove_structure_index(
_,
_,
_,
ref mut s,
s,
)) => {
*s = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
ref mut structures,
structures,
)) => {
structures.insert((name, arity), ext);
}
@@ -926,17 +916,11 @@ pub(crate) fn remove_structure_index(
}
match &indexing_code[structures_index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref structures))
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures))
if structures.is_empty() =>
{
match &mut indexing_code[0] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
_,
_,
ref mut s,
)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, s)) => {
*s = IndexingCodePtr::Fail;
}
_ => {
@@ -952,27 +936,25 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
let mut index = 0;
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, ref mut l, _)) => {
match *l {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*l = IndexingCodePtr::Fail;
return;
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, l, _)) => match *l {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*l = IndexingCodePtr::Fail;
return;
}
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
},
_ => {
unreachable!()
}
}
match &mut indexing_code[index] {
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
StaticCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -984,7 +966,7 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
_,
_,
_,
ref mut l,
l,
_,
)) => {
*l = ext;
@@ -996,7 +978,7 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
}
}
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
DynamicCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -1008,7 +990,7 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
_,
_,
_,
ref mut l,
l,
_,
)) => {
*l = ext;
@@ -1032,8 +1014,8 @@ pub(crate) fn remove_index(
clause_loc: usize,
) {
match opt_arg_index_key {
OptArgIndexKey::Literal(_, _, constant, ref overlapping_constants) => {
remove_constant_indices(*constant, overlapping_constants, indexing_code, clause_loc);
OptArgIndexKey::Literal(_, _, constant, overlapping_constants) => {
remove_constant_indices(*constant, *overlapping_constants, indexing_code, clause_loc);
}
OptArgIndexKey::Structure(_, _, name, arity) => {
remove_structure_index(*name, *arity, indexing_code, clause_loc);
@@ -1095,62 +1077,26 @@ fn uncap_choice_seq_with_try(prelude: &mut [IndexedChoiceInstruction]) {
}
}
pub(crate) fn constant_key_alternatives(
constant: Literal,
atom_tbl: &AtomTable,
// arena: &mut Arena,
) -> Vec<Literal> {
let mut constants = vec![];
pub(crate) fn constant_key_alternatives(constant: Literal) -> Option<Literal> {
let n = match &constant {
Literal::Rational(n) if n.denominator().is_one() => n.numerator(),
Literal::Integer(n) => n,
_ => return None,
};
match constant {
Literal::Atom(ref name) => {
if let Some(c) = name.as_char() {
constants.push(Literal::Char(c));
}
}
Literal::Char(c) => {
let atom = AtomTable::build_with(atom_tbl, &c.to_string());
constants.push(Literal::Atom(atom));
}
/*
Literal::Fixnum(ref n) => {
constants.push(Literal::Integer(arena_alloc!(n, arena))); //Rc::new(Integer::from(*n))));
/*
if *n >= 0 {
if let Ok(n) = usize::try_from(*n) {
constants.push(Literal::Usize(n));
}
}
*/
}
*/
Literal::Integer(ref n) => {
let result = (&**n).try_into();
if let Ok(value) = result {
Fixnum::build_with_checked(value)
.map(|n| {
constants.push(Literal::Fixnum(n));
})
.unwrap();
}
}
_ => {}
}
constants
Fixnum::build_with_checked(n).map(Literal::Fixnum).ok()
}
#[derive(Debug)]
pub(crate) struct StaticCodeIndices {
constants: IndexMap<Literal, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
constants: IndexMap<HeapCellValue, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
lists: VecDeque<IndexedChoiceInstruction>,
structures: IndexMap<(Atom, usize), VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
}
#[derive(Debug)]
pub(crate) struct DynamicCodeIndices {
constants: IndexMap<Literal, VecDeque<usize>, FxBuildHasher>,
constants: IndexMap<HeapCellValue, VecDeque<usize>, FxBuildHasher>,
lists: VecDeque<usize>,
structures: IndexMap<(Atom, usize), VecDeque<usize>, FxBuildHasher>,
}
@@ -1162,7 +1108,7 @@ pub(crate) trait Indexer {
fn constants(
&mut self,
) -> &mut IndexMap<Literal, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
) -> &mut IndexMap<HeapCellValue, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
fn lists(&mut self) -> &mut VecDeque<Self::ThirdLevelIndex>;
fn structures(
&mut self,
@@ -1210,7 +1156,7 @@ impl Indexer for StaticCodeIndices {
#[inline]
fn constants(
&mut self,
) -> &mut IndexMap<Literal, VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
) -> &mut IndexMap<HeapCellValue, VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
&mut self.constants
}
@@ -1334,7 +1280,7 @@ impl Indexer for DynamicCodeIndices {
}
#[inline]
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<usize>, FxBuildHasher> {
fn constants(&mut self) -> &mut IndexMap<HeapCellValue, VecDeque<usize>, FxBuildHasher> {
&mut self.constants
}
@@ -1362,9 +1308,7 @@ impl Indexer for DynamicCodeIndices {
for (key, code) in indices.into_iter() {
if code.len() > 1 {
index_locs.insert(key, IndexingCodePtr::Internal(prelude.len() + 1));
prelude.push_back(IndexingLine::DynamicIndexedChoice(
code.into_iter().collect(),
));
prelude.push_back(IndexingLine::DynamicIndexedChoice(code));
} else if let Some(i) = code.front() {
index_locs.insert(key, IndexingCodePtr::DynamicExternal(*i));
}
@@ -1403,9 +1347,7 @@ impl Indexer for DynamicCodeIndices {
) -> IndexingCodePtr {
if lists.len() > 1 {
let lists = std::mem::take(lists);
prelude.push_back(IndexingLine::DynamicIndexedChoice(
lists.into_iter().collect(),
));
prelude.push_back(IndexingLine::DynamicIndexedChoice(lists));
IndexingCodePtr::Internal(1)
} else {
lists
@@ -1453,14 +1395,13 @@ impl<I: Indexer> CodeOffsets<I> {
self.indices.lists().push_back(index);
}
fn index_constant(
&mut self,
atom_tbl: &AtomTable,
constant: Literal,
index: usize,
) -> Vec<Literal> {
let overlapping_constants = constant_key_alternatives(constant, atom_tbl);
let code = self.indices.constants().entry(constant).or_default();
fn index_constant(&mut self, constant: Literal, index: usize) -> Option<Literal> {
let overlapping_constant_opt = constant_key_alternatives(constant);
let code = self
.indices
.constants()
.entry(HeapCellValue::from(constant))
.or_default();
let is_initial_index = code.is_empty();
code.push_back(I::compute_index(
@@ -1469,8 +1410,8 @@ impl<I: Indexer> CodeOffsets<I> {
self.non_counted_bt,
));
for constant in &overlapping_constants {
let code = self.indices.constants().entry(*constant).or_default();
if let Some(constant) = overlapping_constant_opt.map(HeapCellValue::from) {
let code = self.indices.constants().entry(constant).or_default();
let is_initial_index = code.is_empty();
let index = I::compute_index(is_initial_index, index, self.non_counted_bt);
@@ -1478,7 +1419,7 @@ impl<I: Indexer> CodeOffsets<I> {
code.push_back(index);
}
overlapping_constants
overlapping_constant_opt
}
fn index_structure(&mut self, name: Atom, arity: usize, index: usize) -> usize {
@@ -1500,14 +1441,13 @@ impl<I: Indexer> CodeOffsets<I> {
optimal_arg: &Term,
index: usize,
clause_index_info: &mut ClauseIndexInfo,
atom_tbl: &AtomTable,
) {
match optimal_arg {
&Term::Clause(_, atom!("."), ref terms) if terms.len() == 2 => {
clause_index_info.opt_arg_index_key = OptArgIndexKey::List(self.optimal_index, 0);
self.index_list(index);
}
&Term::Cons(..) | &Term::Literal(_, Literal::String(_)) | &Term::PartialString(..) => {
&Term::Cons(..) | &Term::PartialString(..) | &Term::CompleteString(..) => {
clause_index_info.opt_arg_index_key = OptArgIndexKey::List(self.optimal_index, 0);
self.index_list(index);
}
@@ -1518,7 +1458,7 @@ impl<I: Indexer> CodeOffsets<I> {
self.index_structure(name, terms.len(), index);
}
&Term::Literal(_, constant) => {
let overlapping_constants = self.index_constant(atom_tbl, constant, index);
let overlapping_constants = self.index_constant(constant, index);
clause_index_info.opt_arg_index_key =
OptArgIndexKey::Literal(self.optimal_index, 0, constant, overlapping_constants);
@@ -1565,11 +1505,11 @@ impl<I: Indexer> CodeOffsets<I> {
&mut prelude,
);
if let IndexingCodePtr::Internal(ref mut i) = &mut str_loc {
if let IndexingCodePtr::Internal(i) = &mut str_loc {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
}
if let IndexingCodePtr::Internal(ref mut i) = &mut lst_loc {
if let IndexingCodePtr::Internal(i) = &mut lst_loc {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
*i += emitted_switch_on_structure as usize; // str_loc.is_internal() as usize;
}
@@ -1584,6 +1524,6 @@ impl<I: Indexer> CodeOffsets<I> {
str_loc,
)));
prelude.into_iter().collect()
prelude.into()
}
}

1386
src/instructions.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,8 @@ use crate::parser::ast::*;
use std::cell::Cell;
use std::collections::VecDeque;
use std::iter::*;
use std::rc::Rc;
use std::sync::Arc;
use std::vec::Vec;
#[allow(clippy::borrowed_box)]
@@ -15,27 +17,11 @@ pub(crate) enum TermRef<'a> {
Cons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
Literal(Level, &'a Cell<RegType>, &'a Literal),
Clause(Level, &'a Cell<RegType>, Atom, &'a Vec<Term>),
PartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
CompleteString(Level, &'a Cell<RegType>, Atom),
PartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
CompleteString(Level, &'a Cell<RegType>, Rc<String>),
Var(Level, &'a Cell<VarReg>, VarPtr),
}
/*
impl<'a> TermRef<'a> {
pub(crate) fn level(&self) -> Level {
match self {
TermRef::AnonVar(lvl) |
TermRef::Cons(lvl, ..) |
TermRef::Literal(lvl, ..) |
TermRef::Var(lvl, ..) |
TermRef::Clause(lvl, ..) |
TermRef::CompleteString(lvl, ..) |
TermRef::PartialString(lvl, ..) => *lvl,
}
}
}
*/
#[allow(clippy::borrowed_box)]
#[derive(Debug)]
pub(crate) enum TermIterState<'a> {
@@ -44,9 +30,9 @@ pub(crate) enum TermIterState<'a> {
Literal(Level, &'a Cell<RegType>, &'a Literal),
InitialCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
FinalCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
InitialPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
FinalPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
CompleteString(Level, &'a Cell<RegType>, Atom),
InitialPartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
FinalPartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
CompleteString(Level, &'a Cell<RegType>, Rc<String>),
Var(Level, &'a Cell<VarReg>, VarPtr),
}
@@ -62,9 +48,11 @@ impl<'a> TermIterState<'a> {
}
Term::Literal(cell, constant) => TermIterState::Literal(lvl, cell, constant),
Term::PartialString(cell, string_buf, tail) => {
TermIterState::InitialPartialString(lvl, cell, string_buf, tail)
TermIterState::InitialPartialString(lvl, cell, string_buf.clone(), tail)
}
Term::CompleteString(cell, string) => {
TermIterState::CompleteString(lvl, cell, string.clone())
}
Term::CompleteString(cell, atom) => TermIterState::CompleteString(lvl, cell, *atom),
Term::Var(cell, var_ptr) => TermIterState::Var(lvl, cell, var_ptr.clone()),
}
}
@@ -102,7 +90,7 @@ impl<'a> QueryIterator<'a> {
| Term::CompleteString(..) => {
return QueryIterator {
state_stack: vec![],
}
};
}
Term::Clause(r, name, terms) => TermIterState::Clause(Level::Root, 0, r, *name, terms),
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Root, cell, var_ptr.clone()),
@@ -115,11 +103,11 @@ impl<'a> QueryIterator<'a> {
fn extend_state(&mut self, lvl: Level, term: &'a QueryTerm) {
match term {
QueryTerm::Clause(ref cell, ClauseType::CallN(_), ref terms, _) => {
QueryTerm::Clause(cell, ClauseType::CallN(_), terms, _) => {
self.state_stack
.push(TermIterState::Clause(lvl, 1, cell, atom!("$call"), terms));
}
QueryTerm::Clause(ref cell, ref ct, ref terms, _) => {
QueryTerm::Clause(cell, ct, terms, _) => {
self.state_stack
.push(TermIterState::Clause(lvl, 0, cell, ct.name(), terms));
}
@@ -155,7 +143,7 @@ impl<'a> Iterator for QueryIterator<'a> {
return match lvl {
Level::Root => None,
lvl => Some(TermRef::Clause(lvl, cell, name, child_terms)),
}
};
}
};
} else {
@@ -182,11 +170,11 @@ impl<'a> Iterator for QueryIterator<'a> {
.push(TermIterState::FinalPartialString(lvl, cell, string, tail));
self.push_subterm(lvl.child_level(), tail);
}
TermIterState::FinalPartialString(lvl, cell, atom, tail) => {
return Some(TermRef::PartialString(lvl, cell, atom, tail));
TermIterState::FinalPartialString(lvl, cell, string, tail) => {
return Some(TermRef::PartialString(lvl, cell, string, tail));
}
TermIterState::CompleteString(lvl, cell, atom) => {
return Some(TermRef::CompleteString(lvl, cell, atom));
TermIterState::CompleteString(lvl, cell, string) => {
return Some(TermRef::CompleteString(lvl, cell, string));
}
TermIterState::FinalCons(lvl, cell, head, tail) => {
return Some(TermRef::Cons(lvl, cell, head, tail));
@@ -242,16 +230,20 @@ impl<'a> FactIterator<'a> {
head.as_ref(),
tail.as_ref(),
)],
Term::PartialString(cell, string_buf, tail) => {
Term::PartialString(cell, string, tail) => {
vec![TermIterState::InitialPartialString(
Level::Root,
cell,
string_buf,
string.clone(),
tail,
)]
}
Term::CompleteString(cell, atom) => {
vec![TermIterState::CompleteString(Level::Root, cell, *atom)]
Term::CompleteString(cell, string) => {
vec![TermIterState::CompleteString(
Level::Root,
cell,
string.clone(),
)]
}
Term::Literal(cell, constant) => {
vec![TermIterState::Literal(Level::Root, cell, constant)]
@@ -301,7 +293,7 @@ impl<'a> Iterator for FactIterator<'a> {
return Some(TermRef::CompleteString(lvl, cell, atom));
}
TermIterState::Literal(lvl, cell, constant) => {
return Some(TermRef::Literal(lvl, cell, constant))
return Some(TermRef::Literal(lvl, cell, constant));
}
TermIterState::Var(lvl, cell, var_ptr) => {
return Some(TermRef::Var(lvl, cell, var_ptr));
@@ -314,29 +306,42 @@ impl<'a> Iterator for FactIterator<'a> {
}
}
pub(crate) fn post_order_iter(term: &'_ Term) -> QueryIterator {
pub(crate) fn post_order_iter(term: &Term) -> QueryIterator<'_> {
QueryIterator::from_term(term)
}
pub(crate) fn breadth_first_iter(
term: &'_ Term,
term: &Term,
iterable_root: RootIterationPolicy,
) -> FactIterator {
) -> FactIterator<'_> {
FactIterator::new(term, iterable_root)
}
#[derive(Debug, Copy, Clone)]
enum ClauseIteratorState<'a> {
RemainingChunks(&'a VecDeque<ChunkedTerms>, usize),
RemainingBranches(&'a Vec<VecDeque<ChunkedTerms>>, usize),
RemainingBranches(
&'a Vec<Arc<BranchNumber>>,
&'a Vec<VecDeque<ChunkedTerms>>,
usize,
),
}
#[derive(Debug, Clone)]
pub(crate) enum ClauseItem<'a> {
FirstBranch(usize),
NextBranch,
BranchEnd(usize),
Chunk(&'a VecDeque<QueryTerm>),
FirstBranch {
branch_num: &'a Arc<BranchNumber>,
num_branches: usize,
},
NextBranch {
branch_num: &'a Arc<BranchNumber>,
},
BranchEnd {
depth: usize,
},
Chunk {
terms: &'a VecDeque<QueryTerm>,
},
}
#[derive(Debug)]
@@ -345,10 +350,10 @@ pub(crate) struct ClauseIterator<'a> {
remaining_chunks_on_stack: usize,
}
fn state_from_chunked_terms(chunk_vec: &'_ VecDeque<ChunkedTerms>) -> ClauseIteratorState {
fn state_from_chunked_terms(chunk_vec: &VecDeque<ChunkedTerms>) -> ClauseIteratorState<'_> {
if chunk_vec.len() == 1 {
if let Some(ChunkedTerms::Branch(ref branches)) = chunk_vec.front() {
return ClauseIteratorState::RemainingBranches(branches, 0);
if let Some(ChunkedTerms::Branch { branch_nums, arms }) = chunk_vec.front() {
return ClauseIteratorState::RemainingBranches(branch_nums, arms, 0);
}
}
@@ -379,7 +384,9 @@ impl<'a> ClauseIterator<'a> {
while let Some(state) = self.state_stack.pop() {
match state {
ClauseIteratorState::RemainingBranches(terms, focus) if terms.len() == focus => {
ClauseIteratorState::RemainingBranches(_branch_nums, terms, focus)
if terms.len() == focus =>
{
depth += 1;
}
_ => {
@@ -408,23 +415,27 @@ impl<'a> Iterator for ClauseIterator<'a> {
}
match &chunks[focus] {
ChunkedTerms::Branch(branches) => {
ChunkedTerms::Branch { branch_nums, arms } => {
self.state_stack
.push(ClauseIteratorState::RemainingBranches(branches, 0));
.push(ClauseIteratorState::RemainingBranches(branch_nums, arms, 0));
}
ChunkedTerms::Chunk(chunk) => {
return Some(ClauseItem::Chunk(chunk));
ChunkedTerms::Chunk { terms } => {
return Some(ClauseItem::Chunk { terms });
}
}
}
ClauseIteratorState::RemainingChunks(chunks, focus) => {
debug_assert_eq!(chunks.len(), focus);
}
ClauseIteratorState::RemainingBranches(branches, focus)
ClauseIteratorState::RemainingBranches(branch_nums, branches, focus)
if focus < branches.len() =>
{
self.state_stack
.push(ClauseIteratorState::RemainingBranches(branches, focus + 1));
.push(ClauseIteratorState::RemainingBranches(
branch_nums,
branches,
focus + 1,
));
let state = state_from_chunked_terms(&branches[focus]);
if let ClauseIteratorState::RemainingChunks(..) = &state {
@@ -434,14 +445,21 @@ impl<'a> Iterator for ClauseIterator<'a> {
self.state_stack.push(state);
return if focus == 0 {
Some(ClauseItem::FirstBranch(branches.len()))
Some(ClauseItem::FirstBranch {
branch_num: &branch_nums[0],
num_branches: branches.len(),
})
} else {
Some(ClauseItem::NextBranch)
Some(ClauseItem::NextBranch {
branch_num: &branch_nums[focus],
})
};
}
ClauseIteratorState::RemainingBranches(branches, focus) => {
ClauseIteratorState::RemainingBranches(_branch_nums, branches, focus) => {
debug_assert_eq!(branches.len(), focus);
return Some(ClauseItem::BranchEnd(self.branch_end_depth()));
return Some(ClauseItem::BranchEnd {
depth: self.branch_end_depth(),
});
}
}
}

View File

@@ -1,60 +1,85 @@
//! A free software ISO Prolog system.
#![recursion_limit = "4112"]
#![deny(missing_docs)]
#[macro_use]
extern crate static_assertions;
#[cfg(test)]
#[macro_use]
extern crate maplit;
#[macro_use]
pub mod macros;
pub(crate) mod macros;
#[macro_use]
pub mod atom_table;
pub(crate) mod atom_table;
#[macro_use]
pub mod arena;
pub(crate) mod arena;
pub(crate) mod offset_table;
#[macro_use]
pub mod parser;
pub(crate) mod parser;
#[macro_use]
pub(crate) mod functor_macro;
mod allocator;
mod arithmetic;
pub mod codegen;
pub(crate) mod codegen;
mod debray_allocator;
#[cfg(feature = "ffi")]
mod ffi;
mod forms;
mod heap_iter;
pub mod heap_print;
pub(crate) mod heap_print;
#[cfg(feature = "http")]
mod http;
mod indexing;
mod variable_records;
#[macro_use]
pub mod instructions {
include!(concat!(env!("OUT_DIR"), "/instructions.rs"));
}
pub(crate) mod instructions;
mod iterators;
pub mod machine;
pub(crate) mod machine;
mod raw_block;
pub mod read;
pub(crate) mod read;
#[cfg(feature = "repl")]
mod repl_helper;
mod targets;
pub mod types;
pub(crate) mod types;
use instructions::instr;
mod rcu;
// Re-exports
pub use machine::Machine;
pub use machine::config::*;
pub use machine::lib_machine::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
pub mod wasm;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn eval_code(s: &str) -> String {
use machine::mock_wam::*;
#[cfg(not(target_arch = "wasm32"))]
/// The entry point for the Scryer Prolog CLI.
pub fn run_binary() -> std::process::ExitCode {
use crate::atom_table::Atom;
console_error_panic_hook::set_once();
#[cfg(feature = "repl")]
use crate::machine::INTERRUPT;
let mut wam = Machine::with_test_streams();
let bytes = wam.test_load_string(s);
String::from_utf8_lossy(&bytes).to_string()
#[cfg(feature = "repl")]
ctrlc::set_handler(move || {
INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
})
.unwrap();
#[cfg(target_arch = "wasm32")]
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
#[cfg(not(target_arch = "wasm32"))]
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut wam = MachineBuilder::default()
.with_streams(StreamConfig::stdio())
.build();
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
})
}

View File

@@ -62,8 +62,8 @@ Assocs are Key-Value associations implemented as a balanced binary tree
Authors: R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
*/
:- meta_predicate map_assoc(1, ?).
:- meta_predicate map_assoc(2, ?, ?).
:- meta_predicate(map_assoc(1, ?)).
:- meta_predicate(map_assoc(2, ?, ?)).
%% empty_assoc(?Assoc) is semidet.
%
@@ -299,7 +299,7 @@ map_assoc_(t(Key,Val,B,L0,R0), Pred, t(Key,Ans,B,L1,R1)) :-
map_assoc_(R0, Pred, R1).
%% max_assoc(+Assoc, -Key, -Value) is semidet.
%% max_assoc(+Assoc, -Key, -Value) is semidet.
%
% True if Key-Value is in Assoc and Key is the largest key.

View File

@@ -59,7 +59,7 @@ get_attrs_var_check(Module) -->
!,
'$get_attr_list'(Var, Ls),
nonvar(Ls),
atts:'$copy_attr_list'(Ls, Module, Attr))].
atts:'$copy_attr_list'(Ls, Module, Attr))].
put_attrs(Name/Arity, Module) -->
put_attr(Name, Arity, Module),

View File

@@ -12,7 +12,7 @@ integers that may also be interesting.
:- use_module(library(lists), [length/2]).
:- use_module(library(error)).
%% between(+Lower, +Upper, -X).
%% between(+Lower, +Upper, ?X).
%
% Given Lower and Upper are both integer numbers, true iff X is an integer so that _Lower =< X =< Upper_.
% Can be used both to check if X is between Lower and Upper or to generate an integer between

View File

@@ -113,7 +113,7 @@ call(_, _, _, _, _, _, _, _).
call(_, _, _, _, _, _, _, _, _).
:- meta_predicate catch(0, ?, 0).
:- meta_predicate(catch(0, ?, 0)).
% flags.
@@ -125,12 +125,12 @@ call(_, _, _, _, _, _, _, _, _).
%
% The flags that Scryer Prolog support are:
%
% * `max_arity`: The max arity a predicate can have in Prolog. On Scryer is set to 1023. Read only.
% * `max_arity`: The max arity a predicate can have in Prolog. On Scryer is set to 255. Read only.
% * `bounded`: `true` if integer arithmethic is bounded between some min/max values. On Scryer is always set
% to `false` since it supports unbounded integer arithmethic. Read only.
% * `integer_rounding_function`: Describes the rounding donde by `//` and `rem` functions. On Scryer is
% always set to `toward_zero`. Read only
% * `double_quotes`: Determines how double quoted strings are red by Prolog. Scryer uses `chars` by default
% * `double_quotes`: Determines how double quoted strings are read by Prolog. Scryer uses `chars` by default
% which is a list of one-character atoms. Other values are codes (list of integers representing characters),
% and atom which creates a whole atom for the string value. Read and write.
% * `max_integer`: Maximum integer supported by the system. As Scryer Prolog has unbounded integer arithmethic,
@@ -145,8 +145,8 @@ call(_, _, _, _, _, _, _, _, _).
% `fail` (the call silently fails) and `warn` (the call fails and a warning about the undefined predicate is printed).
% * `answer_write_options`: Additional write options used by the top level for writing answers.
%
current_prolog_flag(Flag, Value) :- Flag == max_arity, !, Value = 1023.
current_prolog_flag(max_arity, 1023).
current_prolog_flag(Flag, Value) :- Flag == max_arity, !, Value = 255.
current_prolog_flag(max_arity, 255).
current_prolog_flag(Flag, Value) :- Flag == bounded, !, Value = false.
current_prolog_flag(bounded, false).
current_prolog_flag(Flag, Value) :- Flag == integer_rounding_function, !, Value == toward_zero.
@@ -248,7 +248,7 @@ flag_domain_error(Flag, Value) :-
fail :- '$fail'.
:- meta_predicate \+(0).
:- meta_predicate(\+(0)).
%% \+(Goal)
%
@@ -263,7 +263,7 @@ X \= X :- !, '$fail'.
_ \= _.
:- meta_predicate once(0).
:- meta_predicate(once(0)).
%% once(Goal)
%
@@ -278,11 +278,11 @@ repeat.
repeat :- repeat.
:- meta_predicate ','(0,0).
:- meta_predicate(','(0,0)).
:- meta_predicate ;(0,0).
:- meta_predicate(;(0,0)).
:- meta_predicate ->(0,0).
:- meta_predicate(->(0,0)).
%% ->(G1, G2)
%
@@ -364,7 +364,7 @@ cont_list_goal(Conts, '$call'(builtins:dispatch_call_list(Conts))).
dispatch_prep(Gs, B, Conts) :-
( callable(Gs) ->
strip_module(Gs, M, Gs0),
loader:strip_module(Gs, M, Gs0),
( nonvar(Gs0),
dispatch_prep_(Gs0, B, Conts) ->
true
@@ -553,7 +553,7 @@ get_args([Arg|Args], Func, I0, N) :-
get_args(Args, Func, I1, N).
:- meta_predicate parse_options_list(?, 2, ?, ?, ?).
:- meta_predicate(parse_options_list(?, 2, ?, ?, ?)).
parse_options_list(Options, Selector, DefaultPairs, OptionValues, Stub) :-
'$skip_max_list'(_, _, Options, Tail),
@@ -661,7 +661,7 @@ write_term(Term, Options) :-
%% write_term(+Stream, +Term, +Options).
%
% Write Term to the stream Stream according to some output syntax options. The options avaibale are:
% Write Term to the stream Stream according to some output syntax options. The options available are:
%
% * `ignore_ops(+Boolean)` if `true`, the generic term representation is used everywhere. In `false`
% (default), operators do not use that generic term representation.
@@ -766,6 +766,9 @@ read(Term) :-
read_term(Stream, Term, []).
% read(Stream, Term).
%% read(+Stream, -Term).
%
% Same as read_term/3 with all default options.
read(Stream, Term) :-
read_term(Stream, Term, []).
@@ -813,7 +816,7 @@ catch(G,C,R) :-
'$get_current_block'(Bb),
catch(G,C,R,Bb).
:- meta_predicate catch(0, ?, 0, ?).
:- meta_predicate(catch(0, ?, 0, ?)).
:- non_counted_backtracking catch/4.
@@ -881,7 +884,7 @@ throw(Ball) :-
truncate_lh_to(LhLength) :- '$truncate_lh_to'(LhLength).
:- meta_predicate findall(?, 0, ?).
:- meta_predicate(findall(?, 0, ?)).
:- non_counted_backtracking findall_cleanup/2.
@@ -924,7 +927,7 @@ findall(Template, Goal, Solutions) :-
'$get_lh_from_offset_diff'(LhOffset, Solutions0, Solutions1).
:- meta_predicate findall(?, 0, ?, ?).
:- meta_predicate(findall(?, 0, ?, ?)).
:- non_counted_backtracking findall/4.
@@ -941,65 +944,6 @@ findall(Template, Goal, Solutions0, Solutions1) :-
builtins:findall_cleanup(LhLength, Error)
).
:- non_counted_backtracking set_difference/3.
set_difference([X|Xs], [Y|Ys], Zs) :-
X == Y, !, set_difference(Xs, [Y|Ys], Zs).
set_difference([X|Xs], [Y|Ys], [X|Zs]) :-
X @< Y, !, set_difference(Xs, [Y|Ys], Zs).
set_difference([X|Xs], [Y|Ys], Zs) :-
X @> Y, !, set_difference([X|Xs], Ys, Zs).
set_difference([], _, []) :- !.
set_difference(Xs, [], Xs).
% variant/2 checks whether X is a variant of Y per the definition in
% 7.1.6.1 of the ISO standard.
:- non_counted_backtracking variant/4.
variant(X,Y,VPs,VPs0) :-
( var(X) ->
var(Y),
VPs = [X-Y|VPs0]
; var(Y) ->
false
; X =.. [FX | XArgs],
Y =.. [FX | YArgs],
lists:foldl('$call'(builtins:variant), XArgs, YArgs, VPs, VPs0)
).
:- non_counted_backtracking variant/2.
singleton([_]).
variant(X, Y) :-
variant(X,Y, VPs, []),
keysort(VPs, SVPs),
pairs:group_pairs_by_key(SVPs, SVPKs),
pairs:pairs_values(SVPKs, Vals),
lists:maplist('$call'(builtins:term_variables), Vals, Vs),
lists:maplist('$call'(builtins:singleton), Vs),
term_variables(Vs, YVars),
lists:length(SVPKs, N),
lists:length(YVars, N).
:- non_counted_backtracking group_by_variant/4.
group_by_variant([V2-S2 | Pairs], V1-S1, [S2 | Solutions], Pairs0) :-
variant(V1, V2),
!,
V1 = V2,
group_by_variant(Pairs, V2-S2, Solutions, Pairs0).
group_by_variant(Pairs, _, [], Pairs).
:- non_counted_backtracking group_by_variants/2.
group_by_variants([V-S|Pairs], [V-Solution|Solutions]) :-
group_by_variant([V-S|Pairs], V-S, Solution, Pairs0),
group_by_variants(Pairs0, Solutions).
group_by_variants([], []).
:- non_counted_backtracking iterate_variants/3.
@@ -1032,9 +976,7 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses)
( Goal1 = _ ^ _ ) ->
rightmost_power(Goal1, Goal2, ExistentialVars0),
term_variables(ExistentialVars0, ExistentialVars),
sort(Witnesses0, Witnesses1),
sort(ExistentialVars, ExistentialVars1),
set_difference(Witnesses1, ExistentialVars1, Witnesses),
lists:append(Witnesses0, Witnesses, ExistentialVars),
expand_goal(M:Goal2, M, Goal3),
findall(Witnesses-Template, Goal3, PairedSolutions)
; Witnesses = Witnesses0,
@@ -1042,7 +984,35 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses)
).
:- meta_predicate bagof(?, 0, ?).
:- non_counted_backtracking split_by_variant/4.
:- non_counted_backtracking split_by_variant/3.
:- non_counted_backtracking unify_variant_variables/2.
split_by_variant([V2-S2 | Pairs], V1-S1, Solutions, Rest) :-
( V1 == V2 ->
Solutions = [S2 | Solutions1],
split_by_variant(Pairs, V1-S1, Solutions1, Rest)
; Solutions = [],
Rest = [V2-S2 | Pairs]
).
split_by_variant([], _, [], []).
split_by_variant([V-S|Pairs], Ws, Solutions) :-
split_by_variant(Pairs, V-S, Solutions0, Rest),
( Rest == [] -> V = Ws, Solutions = [S|Solutions0]
; V = Ws, Solutions = [S|Solutions0]
; split_by_variant(Rest, Ws, Solutions)
).
unify_variant_variables([], _Dict).
unify_variant_variables([V-_S|Pairs], Dict) :-
term_variables(V, VVars),
lists:append(VVars, _, Dict),
unify_variant_variables(Pairs, Dict).
:- meta_predicate(bagof(?, 0, ?)).
:- non_counted_backtracking bagof/3.
@@ -1071,24 +1041,12 @@ bagof(Template, Goal, Solution) :-
term_variables(Goal, GoalVars),
term_variables(TemplateVars+GoalVars, TGVs),
lists:append(TemplateVars, Witnesses0, TGVs),
findall_with_existential(Template, Goal, PairedSolutions0, Witnesses0, Witnesses),
keysort(PairedSolutions0, PairedSolutions),
group_by_variants(PairedSolutions, GroupedSolutions),
iterate_variants(GroupedSolutions, Witnesses, Solution).
findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses),
unify_variant_variables(PairedSolutions, _Dict),
keysort(PairedSolutions, PairedSolutions1),
split_by_variant(PairedSolutions1, Witnesses, Solution).
:- non_counted_backtracking iterate_variants_and_sort/3.
iterate_variants_and_sort([V-Solution0|GroupSolutions], V, Solution) :-
sort(Solution0, Solution1),
Solution1 = Solution,
( GroupSolutions == [] -> !
; true
).
iterate_variants_and_sort([_|GroupSolutions], Ws, Solution) :-
iterate_variants_and_sort(GroupSolutions, Ws, Solution).
:- meta_predicate setof(?, 0, ?).
:- meta_predicate(setof(?, 0, ?)).
:- non_counted_backtracking setof/3.
@@ -1109,10 +1067,10 @@ setof(Template, Goal, Solution) :-
term_variables(Goal, GoalVars),
term_variables(TemplateVars+GoalVars, TGVs),
lists:append(TemplateVars, Witnesses0, TGVs),
findall_with_existential(Template, Goal, PairedSolutions0, Witnesses0, Witnesses),
'$keysort_with_constant_var_ordering'(PairedSolutions0, PairedSolutions), % see 7.2.1
group_by_variants(PairedSolutions, GroupedSolutions),
iterate_variants_and_sort(GroupedSolutions, Witnesses, Solution).
findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses),
unify_variant_variables(PairedSolutions, _Dict),
sort(PairedSolutions, PairedSolutions1),
split_by_variant(PairedSolutions1, Witnesses, Solution).
% Clause retrieval and information.
@@ -1165,14 +1123,14 @@ clause(H, B) :-
).
:- meta_predicate asserta(:).
:- meta_predicate(asserta(:)).
%% asserta(Clause).
%
% Asserts (inserts) a new clause (rule or fact) into the current module.
% The clause will be inserted at the beginning of the module.
asserta(Clause0) :-
loader:strip_subst_module(Clause0, user, Module, Clause),
loader:strip_module(Clause0, Module, Clause),
asserta_(Module, Clause).
asserta_(Module, (Head :- Body)) :-
@@ -1181,14 +1139,14 @@ asserta_(Module, (Head :- Body)) :-
asserta_(Module, Fact) :-
'$asserta'(Module, Fact, true).
:- meta_predicate assertz(:).
:- meta_predicate(assertz(:)).
%% assertz(Clause).
%
% Asserts (inserts) a new clause (rule or fact) into the current module.
% The clase will be inserted at the end of the module.
assertz(Clause0) :-
loader:strip_subst_module(Clause0, user, Module, Clause),
loader:strip_module(Clause0, Module, Clause),
assertz_(Module, Clause).
assertz_(Module, (Head :- Body)) :-
@@ -1198,7 +1156,7 @@ assertz_(Module, Fact) :-
'$assertz'(Module, Fact, true).
:- meta_predicate retract(:).
:- meta_predicate(retract(:)).
%% retract(Clause)
%
@@ -1207,16 +1165,10 @@ assertz_(Module, Fact) :-
retract(Clause0) :-
loader:strip_module(Clause0, Module, Clause),
( Clause \= (_ :- _) ->
loader:strip_module(Clause, Module, Head),
( var(Module) -> Module = user
; true
),
loader:strip_subst_module(Clause, Module, InnerModule, Head),
Body = true,
retract_module_clause(Head, Body, Module)
retract_module_clause(Head, Body, InnerModule)
; Clause = (Head :- Body) ->
( var(Module) -> Module = user
; true
),
retract_module_clause(Head, Body, Module)
).
@@ -1281,7 +1233,7 @@ retract_module_clause(Head, Body, Module) :-
; throw(error(type_error(callable, Head), retract/1))
).
:- meta_predicate retractall(:).
:- meta_predicate(retractall(:)).
%% retractall(Head)
%
@@ -1321,7 +1273,7 @@ module_abolish(Pred, Module) :-
; throw(error(type_error(predicate_indicator, Module:Pred), abolish/1))
).
:- meta_predicate abolish(:).
:- meta_predicate(abolish(:)).
%% abolish(Pred).
%
@@ -1370,11 +1322,7 @@ current_predicate(Pred) :-
( var(Pred) ->
'$get_db_refs'(_, _, _, PIs),
lists:member(Pred, PIs)
; '$strip_module'(Pred, Module, UnqualifiedPred),
( var(Module),
\+ functor(Pred, (:), 2)
; atom(Module)
),
; loader:strip_module(Pred, Module, UnqualifiedPred),
UnqualifiedPred = Name/Arity ->
( ( nonvar(Name), \+ atom(Name)
; nonvar(Arity), \+ integer(Arity)
@@ -1558,7 +1506,7 @@ atom_chars(Atom, List) :-
%% atom_codes(?Atom, ?Codes).
%
% Relates an atom with a string in codes representation. It can be used to convert
% between atoms and strings. However, codes is not the default representation of double quoutes
% between atoms and strings. However, codes is not the default representation of double quoted
% strings in Scryer Prolog. Examples:
%
% ```
@@ -1726,20 +1674,27 @@ must_be_number(N, PI) :-
).
chars_or_vars(Cs, _) :-
chars_or_vars(Cs, PI) :-
( '$is_partial_string'(Cs) ->
% use a fast test for the expected case
true
; chars_or_vars_(Cs, PI)
).
chars_or_vars_(Cs, _) :-
( var(Cs) ->
!
; Cs == [] ->
!
).
chars_or_vars([C|Cs], PI) :-
chars_or_vars_([C|Cs], PI) :-
( nonvar(C) ->
( atom(C),
atom_length(C, 1) ->
chars_or_vars(Cs, PI)
chars_or_vars_(Cs, PI)
; throw(error(type_error(character, C), PI))
)
; chars_or_vars(Cs, PI)
; chars_or_vars_(Cs, PI)
).
@@ -2195,12 +2150,10 @@ stream_property(S, P) :-
%% at_end_of_stream(+Stream).
%
% True iff the stream Stream has ended
at_end_of_stream(S_or_a) :-
( var(S_or_a) ->
at_end_of_stream(S) :-
( var(S) ->
throw(error(instantiation_error, at_end_of_stream/1))
; atom(S_or_a) ->
stream_property(S, alias(S_or_a))
; S = S_or_a
; true
),
stream_property(S, end_of_stream(E)),
( E = at -> true ; E = past ).
@@ -2212,7 +2165,7 @@ at_end_of_stream :-
current_input(S),
stream_property(S, end_of_stream(E)),
!,
( E = at ; E = past ).
( E = at -> true ; E = past ).
%% set_stream_position(+Stream, +Position).
%
@@ -2228,7 +2181,7 @@ set_stream_position(S_or_a, Position) :-
%% callable(X).
%
% True iff X is bound o an atom or a compund term.
% True iff X is bound to an atom or a compound term.
:- non_counted_backtracking callable/1.

View File

@@ -195,7 +195,7 @@ get_single_char(C) :-
; type_error(in_character, C, get_single_char/1)
).
%% read_from_chars(+Chars, -Term).
%% read_from_chars(+Chars, ?Term).
%
% Given a string made of chars which contains a representation of
% a Prolog term, Term is the Prolog term represented. Example:
@@ -206,10 +206,10 @@ get_single_char(C) :-
% ```
read_from_chars(Chars, Term) :-
must_be(chars, Chars),
must_be(var, Term),
'$read_from_chars'(Chars, Term).
'$read_from_chars'(Chars, Term0),
Term = Term0.
%% read_term_from_chars(+Chars, -Term, +Options).
%% read_term_from_chars(+Chars, ?Term, +Options).
%
% Like `read_from_chars`, except the reader is configured according to
% `Options` which are those of `read_term`.
@@ -220,9 +220,9 @@ read_from_chars(Chars, Term) :-
% ```
read_term_from_chars(Chars, Term, Options) :-
must_be(chars, Chars),
must_be(var, Term),
builtins:parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term_from_chars/3),
'$read_term_from_chars'(Chars, Term, Singletons, Variables, VariableNames).
'$read_term_from_chars'(Chars, Term0, Singletons, Variables, VariableNames),
Term = Term0.
%% write_term_to_chars(+Term, +Options, -Chars).
%

View File

@@ -3,7 +3,7 @@
Author: Markus Triska
E-mail: triska@metalevel.at
WWW: https://www.metalevel.at
Copyright (C): 2019-2023 Markus Triska
Copyright (C): 2019-2025 Markus Triska
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@@ -1150,8 +1150,9 @@ verify_attributes(Var, Other, Gs) :-
( integer(Other) ->
( between(0, 1, Other) ->
root_get_formula_bdd(Root, Sat, BDD0),
bdd_restriction(BDD0, I, Other, BDD),
root_put_formula_bdd(Root, Sat, BDD),
Gs = [bdd_restriction(BDD0,I,Other,BDD),satisfiable_bdd(BDD)]
Gs = [satisfiable_bdd(BDD)]
; no_truth_value(Other)
)
; atom(Other) ->
@@ -1550,11 +1551,15 @@ random_bindings(VNum, Node) -->
{ node_var_low_high(Node, Var, Low, High),
bdd_count(Node, VNum, Total),
bdd_count(Low, VNum, LCount) },
( { maybe(LCount, Total) } ->
( { weighted_maybe(LCount, Total) } ->
[Var=0], random_bindings(VNum, Low)
; [Var=1], random_bindings(VNum, High)
).
weighted_maybe(K, N) :-
random_integer(0, N, X),
X < K.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Find solutions with maximum weight.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

View File

@@ -3,7 +3,7 @@
Author: Markus Triska
E-mail: triska@metalevel.at
WWW: https://www.metalevel.at
Copyright (C): 2016-2024 Markus Triska
Copyright (C): 2016-2026 Markus Triska
This library provides CLP():
@@ -458,7 +458,7 @@ replacements of these low-level arithmetic predicates, often yielding
more general programs. See [`n_factorial/2`](#clpz-factorial) for an
example.
This library uses goal_expansion/2 to automatically rewrite
This library uses `goal_expansion/2` to automatically rewrite
constraints at compilation time so that low-level arithmetic
predicates are _automatically_ used whenever possible. For example,
the predicate:
@@ -5088,11 +5088,21 @@ run_propagator(pmod(X,Y,Z), MState) -->
; nonvar(Z), nonvar(X) ->
( Z > 0 ->
( X < 0 -> true
; X >= Z
; X >= Z,
% due to X = Z+Y*_ and Y > Z
( X-Z > 0 ->
X-Z > Z
; true
)
)
; Z < 0 ->
( X > 0 -> true
; X =< Z
; X =< Z,
% due to X = Z+Y*_ and Y < Z
( X-Z < 0 ->
X-Z < Z
; true
)
)
; Z =:= 0 % Multiple solutions so do nothing special.
),
@@ -5100,7 +5110,23 @@ run_propagator(pmod(X,Y,Z), MState) -->
YU < X, X =< 0 } -> kill(MState), Z =:= X
; { fd_get(Y, _, n(YL), _, _),
YL > X, X >= 0 } -> kill(MState), Z =:= X
; ( Z > 0 ->
; ( Z > 0, X < 0 ->
{ fd_get(Y, YD, YPs),
YMin is Z+1,
YMax is Z-X,
domain_remove_smaller_than(YD, YMin, YD1),
domain_remove_greater_than(YD1, YMax, YD2) },
fd_put(Y, YD2, YPs)
% queue_goal((Y #> Z, Y #=< Z-X))
; Z < 0, X > 0 ->
{ fd_get(Y, YD, YPs),
YMax is Z-1,
YMin is Z-X,
domain_remove_greater_than(YD, YMax, YD1),
domain_remove_smaller_than(YD1, YMin, YD2) },
fd_put(Y, YD2, YPs)
% queue_goal((Y #< Z, Y #>= Z-X))
; Z > 0 ->
{ fd_get(Y, YD, YPs),
YMin is Z + 1,
domain_remove_smaller_than(YD, YMin, YD1) },
@@ -5112,7 +5138,16 @@ run_propagator(pmod(X,Y,Z), MState) -->
domain_remove_greater_than(YD, YMax, YD1) },
fd_put(Y, YD1, YPs)
% queue_goal(Y #< Z)
; true
; Z =:= 0,
( X =:= 0 ->
kill(MState) % trivial
; % only 4 solutions {-abs(X),-1,1,abs(X)}
{ YL is -abs(X), YU is abs(X),
fd_get(Y, YD0, YPs),
domain_remove_smaller_than(YD0, YL, YD1),
domain_remove_greater_than(YD1, YU, YD) },
fd_put(Y, YD, YPs)
)
)
)
; run_propagator(pmodz(X,Y,Z), MState),
@@ -6138,7 +6173,7 @@ difference_arcs([V|Vs], FL0) -->
writeln(T) :- write(T), nl.
:- meta_predicate must_succeed(0).
:- meta_predicate(must_succeed(0)).
must_succeed(G) :-
( G -> true
@@ -6262,7 +6297,7 @@ put_free(F) :- put_attr(F, free, true).
free_node(F) :- get_attr(F, free, true).
:- meta_predicate with_local_attributes(?, 0, ?).
:- meta_predicate(with_local_attributes(?, 0, ?)).
:- dynamic(nat_copy/1).
@@ -6809,6 +6844,22 @@ gcc_global(Vs, KNs) -->
% the propagator of tuples_in/2). Hence: We need this only if
% an example shows it, ideally found by a systematic search
% that can be used to test the implementation.
%
% UPDATE: A case that requires gcc_check//1 was recently found,
% see: https://github.com/mthom/scryer-prolog/issues/3369.
%
% We need to study carefully what exactly happens in this case.
% The example involves reified constraints, which post constraints
% in their propagators. The example also involves multiple queues,
% because not all reified constraints use variables_same_queue/1
% (for instance (#==>)/2 does not), and variables_same_queue/1 would
% in fact need changes to handle cases where variables no longer
% have a clpz attribute attached. Such cases arise for variables
% involved in entailed reified constraints.
gcc_check(KNs), % see comment above.
do_queue, % is this needed? this (also) needs more analysis.
{ with_local_attributes(Vs,
(gcc_arcs(KNs, S, Vals),
variables_with_num_occurrences(Vs, VNs),

View File

@@ -1,6 +1,6 @@
:- module(cont, [reset/3, shift/1]).
:- meta_predicate reset(0, ?, ?).
:- meta_predicate(reset(0, ?, ?)).
reset(Goal, Ball, Cont) :-
call(Goal),

View File

@@ -1,5 +1,5 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2020-2024 by Markus Triska (triska@metalevel.at)
Written 2020-2025 by Markus Triska (triska@metalevel.at)
Part of Scryer Prolog.
/** Predicates for cryptographic applications.
@@ -10,10 +10,6 @@
Especially for cryptographic applications, it is an advantage that
using strings leaves little trace of what was processed in the system.
For predicates that accept an `encoding/1` option to specify the encoding
of the input data, if `encoding(octet)` is used, then the input can also
be specified as a list of _bytes_, i.e., integers between 0 and 255.
*/
:- module(crypto,
@@ -91,9 +87,36 @@ bytes_hex([B|Bs]) --> [C0,C1],
},
bytes_hex(Bs).
char_hexval(C, H) :-
integer(H),
!,
hexval_char(H, C).
char_hexval(C, H) :- nth0(H, "0123456789abcdef", C), !.
char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
We specialize char_hexval/2 for use if the value is given,
so that it works in constant time in this case.
The security of HMAC verification depends on this property.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
hexval_char(0, '0').
hexval_char(1, '1').
hexval_char(2, '2').
hexval_char(3, '3').
hexval_char(4, '4').
hexval_char(5, '5').
hexval_char(6, '6').
hexval_char(7, '7').
hexval_char(8, '8').
hexval_char(9, '9').
hexval_char(0xa, a).
hexval_char(0xb, b).
hexval_char(0xc, c).
hexval_char(0xd, d).
hexval_char(0xe, e).
hexval_char(0xf, f).
must_be_bytes(Bytes, Context) :-
must_be(list, Bytes),
@@ -190,12 +213,19 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
%
% - `encoding(+Encoding)`
% The default encoding is `utf8`. The alternative is `octet`, to
% treat the input as a list of raw bytes.
% use the character code of each character in Data as a byte
% value.
%
% - `hmac(+Key)`
% Compute a hash-based message authentication code (HMAC) using
% Key, a list of bytes. This option is currently supported for
% algorithms `sha256`, `sha384` and `sha512`.
% algorithms `sha256`, `sha384` and `sha512`. If `Hash` is
% instantiated, then it is compared with the computed HMAC
% in such a way that no information about the expected HMAC
% is revealed, using a comparison of strings that always takes
% the same time independent of whether and where the strings
% differ. This option can therefore also be used to safely
% _verify_ a given HMAC.
%
% Example:
%
@@ -222,10 +252,26 @@ crypto_data_hash(Data0, Hash, Options0) :-
( member(HMAC, Options0), nonvar(HMAC), HMAC = hmac(Ks) ->
must_be_bytes(Ks, crypto_data_hash/3),
hmac_algorithm(A),
'$crypto_hmac'(Data, Encoding, Ks, HashBytes, A)
; '$crypto_data_hash'(Data, Encoding, HashBytes, A)
),
hex_bytes(Hash, HashBytes).
'$crypto_hmac'(Data, Encoding, Ks, HashBytes, A),
( var(Hash) ->
hex_bytes(Hash, HashBytes)
; must_be(chars, Hash),
hex_bytes(HashMAC, HashBytes),
chars_equal_constant_time(Hash, HashMAC)
)
; '$crypto_data_hash'(Data, Encoding, HashBytes, A),
hex_bytes(Hash, HashBytes)
).
chars_equal_constant_time(As, Bs) :-
maplist(chars_xor, As, Bs, Xs),
sum_list(Xs, Sum),
Sum =:= 0.
chars_xor(A, B, Xor) :-
char_code(A, CA),
char_code(B, CB),
Xor is xor(CA,CB).
hmac_algorithm(sha256).
hmac_algorithm(sha384).
@@ -285,7 +331,8 @@ hash_algorithm(blake2b512).
% default is all zeroes.
% - `encoding(+Encoding)`
% The default encoding is `utf8`. The alternative is `octet`,
% to treat the input as a list of raw bytes.
% to use the character code of each character in Data as a byte
% value.
%
% The `info/1` option can be used to generate multiple keys from a
% single master key, using for example values such as "key" and
@@ -392,7 +439,7 @@ dollar_segments(Ls, Segments) :-
% storing passwords, store these hashes. Later, you can verify the
% validity of a password with `crypto_password_hash/2`, comparing the
% then entered password to the stored hash. If you need to export this
% atom, you should treat it as opaque ASCII data with up to 255 bytes
% string, you should treat it as opaque ASCII data with up to 255 bytes
% of length. The maximal length may increase in the future.
%
% Admissible options are:
@@ -491,8 +538,9 @@ bytes_base64(Bytes, Base64) :-
% Options:
%
% - `encoding(+Encoding)`
% Encoding to use for PlainText. Default is utf8. The alternative
% is octet to treat PlainText as raw bytes.
% Encoding to use for PlainText. The default is `utf8`. The
% alternative is `octet`, to use the character code of each
% character in PlainText as a byte value.
%
% - `tag(-List)`
% For authenticated encryption schemes, List is unified with a
@@ -578,8 +626,9 @@ algorithm_key_iv('chacha20-poly1305', Key, IV) :-
% Options is a list of:
%
% - `encoding(+Encoding)`
% Encoding to use for PlainText. The default is utf8. The
% alternative is octet, which is used if the data are raw bytes.
% Encoding to use for PlainText. The default is `utf8`. The
% alternative is `octet`, to obtain a list of characters where each
% character code corresponds to a decrypted octet of CipherText.
%
% - `tag(+Tag)`
% For authenticated encryption schemes, the tag must be specified as
@@ -615,6 +664,8 @@ crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :-
encoding_chars(octet, Bs, Cs) :-
must_be(list, Bs),
( maplist(integer, Bs) ->
% the ability to use integers is deprecated and a
% candidate for removal in the future!
maplist(char_code, Cs, Bs)
; Bs = Cs
),
@@ -701,6 +752,146 @@ ed25519_keypair_public_key(Pair, PublicKey) :-
% PKCS#8 v2 format as generated by `ed25519_new_keypair/1`. Sign Data
% with Key, yielding Signature as a list of hexadecimal characters.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Side-channel attacks on Ed25519 predicates
==========================================
Ed25519 predicates where the private key occurs as part of the
arguments are potentially subject to side-channel attacks, since
key pairs are represented as strings in the context of Ed25519.
The compact string representation used by Scryer Prolog means that
different characters may occupy different numbers of bytes: Due
to UTF-8 encoding, characters with codes 1..127 occupy exactly 1 byte,
characters with codes 128..2047 occupy exactly 2 bytes, and '0'
is represented as a list element occupying an entire cell and
dedicated list constructor in addition to string termination and
possibly padding.
This difference is located at the level of the Rust engine. To
Prolog code, any two characters look conceptually the same (i.e.,
they are both atoms of length 1), and the internal difference in
representation cannot be observed at all.
Very precise timing information or other measurements about
operations that reason about such strings may yield information
that is meant to stay secret. For example, if Ks is a secret key
stored as a list of characters, then the time it takes to run
phrase(..., Ks) may reveal the number of bytes in Ks that are 0 or
greater than 127.
To test whether it is possible to detect such differences, I use
exp(N) which succeeds exactly 2^N times:
exp(E) :-
N is 2^E,
between(1, N, _).
Here is an example query that uses partial_string/1 to traverse
various strings consisting uniformly of characters with the same
code, such as 0, 32, 255 and others, in the hope to detect
differences in timing if only in such extreme cases:
?- length(Ls, 256),
member(Code, [12,0,55,0,0,32,255,10,0,127,64]),
portray_clause(byte=Code),
maplist(=(Code), Ls),
atom_codes(A, Ls),
atom_chars(A, Cs),
time((exp(21),partial_string(Cs),false)).
%@ byte=12.
%@ % CPU time: 1.537s, 14_680_107 inferences
%@ byte=0.
%@ % CPU time: 1.526s, 14_680_107 inferences
%@ byte=55.
%@ % CPU time: 1.534s, 14_680_107 inferences
%@ byte=0.
%@ % CPU time: 1.556s, 14_680_107 inferences
%@ byte=0.
%@ % CPU time: 1.520s, 14_680_107 inferences
%@ byte=32.
%@ % CPU time: 1.524s, 14_680_107 inferences
%@ byte=255.
%@ % CPU time: 1.522s, 14_680_107 inferences
%@ byte=10.
%@ % CPU time: 1.526s, 14_680_107 inferences
%@ byte=0.
%@ % CPU time: 1.522s, 14_680_107 inferences
%@ byte=127.
%@ % CPU time: 1.522s, 14_680_107 inferences
%@ byte=64.
%@ % CPU time: 1.517s, 14_680_107 inferences
%@ false.
This shows that there is enough variety between runs that
traversing a list with 256 elements that are all '\x0\' may even,
and unexpectedly, be faster than traversing a list consisting
entirely of characters with character code 32, which in turn may be
slower than processing a list with 256 characters that all have
code 255 and thus occupy twice as much space. This holds even over
millions of runs. Reasons for such variety can include CPU power
saving mechanisms, dynamic optimizations, prefetching heuristics,
branch prediction algorithms, varying system loads etc.
This gives rise to the suspicion that any such timing differences
would be extremely hard to exploit, at least on the architecture I
tested it on, also since partial_string/1 is a very low-level
operation and any actual processing (using phrase/2 etc.) would
introduce additional overheads that in all likelihood far outweigh
any differences that can be measured with partial_string/1.
Any resulting differences in timing and resource use, if they are
measurable at all in any way, can at most reveal one bit per byte.
Note also that Ed25519 private keys are chosen randomly, and hence
half of their bytes are expected to be in 128..255. The predicates
remain completely safe to use in all scenarios where no information
about the private key can be gathered by unauthorized parties.
Still, the concern remains: We know that different keys may occupy
different numbers of bytes in the internal compact representation
of strings used by Scryer Prolog, and it may be possible to exploit
these differences to obtain information that is meant to be kept
secret. We must therefore keep an eye on this issue. For example,
it may become a concern on very slow devices such as ID-cards where
Scryer Prolog may be deployed in the future and where such timing
differences may be detectable, or if Scryer Prolog itself becomes
so fast that the relative overhead of such low-level operations
becomes greater and thus more easily measurable.
Possible mitigations in such situations would be to:
1. use lists of integers to represent Ed25519 key pairs, resulting
in a 24-fold space increase. This may be prohibitive in
applications that manage a great number of keys. The Rust code
would not be affected by this change, since it already operates
on bytes. A Prolog application may implement this privately, and
also encourage an API change of this library.
2. introduce a compact internal representation for lists of bytes,
which appear to Prolog programs as lists of characters.
(2) seems to be the better solution despite the implementation
overhead: In addition to the improved security properties due to
the elimination of side-channel attacks when reasoning about keys,
all applications that reason about binary data would benefit from a
more compact representation of binary data. Such an additional
compact representation should only be attempted if the amount of
Rust code it impacts is kept to the absolute minimum, certainly
much smaller than what the compact string representation as it is
currently implemented affects.
*No* solution would be to:
- eliminate the compact string representation from the engine and
use plain lists of characters to represent Ed25519 key pairs,
- continue to use lists of characters for Ed25519 key pairs,
and ensure that they are never coalesced into compact strings
(a future GC compaction step may need to be adapted for this)
This is because atom names are still represented in UTF-8 encoding,
and are hence also susceptible to side-channel attacks due to their
using different numbers of bytes for different codes.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
ed25519_sign(KeyPair, Data0, Signature, Options) :-
must_be_octet_chars(KeyPair, ed25519_sign/4),
length(Prefix, 16),
@@ -722,7 +913,8 @@ ed25519_sign(KeyPair, Data0, Signature, Options) :-
%
% - `encoding(+Encoding)`
% The default encoding of Data is `utf8`. The alternative is `octet`,
% which treats Data as a list of raw bytes.
% to use the character code of each character in Data as a byte
% value.
ed25519_verify(Key, Data0, Signature0, Options) :-
must_be_octet_chars(Key, ed25519_verify/4),

View File

@@ -12,8 +12,8 @@ to learn more about them.
[op(1105, xfy, '|'),
phrase/2,
phrase/3,
phrase/4,
phrase/5,
phrase//2,
phrase//3,
seq//1,
seqq//1,
... //0,
@@ -25,13 +25,17 @@ to learn more about them.
:- use_module(library(lists), [append/3, member/2]).
:- use_module(library(loader), [strip_module/3]).
:- meta_predicate phrase(2, ?).
:- meta_predicate(phrase(2, ?)).
:- meta_predicate phrase(2, ?, ?).
:- meta_predicate(phrase(2, ?, ?)).
:- meta_predicate phrase(2, ?, ?, ?).
:- meta_predicate(phrase(3, ?, ?, ?)).
:- meta_predicate phrase(2, ?, ?, ?, ?).
:- meta_predicate(phrase(4, ?, ?, ?, ?)).
:- meta_predicate(','(2, 2, ?, ?)).
:- meta_predicate(;(2, 2, ?, ?)).
%% phrase(+Body, ?Ls).
%
@@ -210,10 +214,13 @@ dcg_cbody(( GRIf -> GRThen ), S0, S, ( If -> Then )) :-
dcg_body(GRIf, S0, S1, If),
dcg_body(GRThen, S1, S, Then).
% When DCG expansion throws an exception remove offending term and rethrow.
user:term_expansion(throw_dcg_expansion_error(E), _) :-
throw(E).
user:term_expansion(Term0, Term) :-
nonvar(Term0),
dcg_rule(Term0, Term).
catch(dcg_rule(Term0, Term), E, Term = throw_dcg_expansion_error(E)).
%% seq(Seq)//
%
@@ -242,6 +249,9 @@ seqq([Es|Ess]) --> seq(Es), seqq(Ess).
Cs0 = Cs.
... --> [] | [_], ... .
% defer instantiation errors until runtime. instantiations may be made
% then.
error_goal(error(instantiation_error, _Context), _).
error_goal(error(E, must_be/2), error(E, must_be/2)).
error_goal(error(E, (=..)/2), error(E, (=..)/2)).
error_goal(error(representation_error(dcg_body), Context),
@@ -255,7 +265,11 @@ user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
E,
dcgs:error_goal(E, GRBody1)
),
( GRBody = (_:_) ->
( E = error(instantiation_error, _),
GRBody0 = [T|Ts] ->
GRBody2 = (error:must_be(list, [T|Ts]),
lists:append([T|Ts], S0, S))
; GRBody = (_:_) ->
GRBody2 = M:GRBody1
; GRBody2 = GRBody1
).

View File

@@ -29,9 +29,9 @@
:- use_module(library(format), [portray_clause/1]).
:- meta_predicate *(0).
:- meta_predicate $(0).
:- meta_predicate $-(0).
:- meta_predicate(*(0)).
:- meta_predicate($(0)).
:- meta_predicate($-(0)).
%% $-(Goal)
%

View File

@@ -1,44 +1,49 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2018-2023 by Markus Triska (triska@metalevel.at)
Written 2018-2025 by Markus Triska (triska@metalevel.at)
I place this code in the public domain. Use it in any way you want.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- module(error, [must_be/2,
can_be/2,
instantiation_error/0,
domain_error/2,
type_error/2,
representation_error/1,
resource_error/1,
instantiation_error/1,
domain_error/3,
type_error/3
type_error/3,
call_with_error_context/2
]).
:- meta_predicate check_(1, ?, ?).
:- meta_predicate(check_(1, ?, ?)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
must_be(Type, Term)
This predicate is intended for type-checks of built-in predicates.
It asserts that Term is:
1) instantiated *and*
2) instantiated to an instance of the given Type.
It corresponds to usage mode +Term.
Currently, the following types are supported:
- atom
- boolean
- character
- chars
- in_character
- integer
- list
- octet_character
- octet_chars
- term
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% must_be(Type, Term)
%
% This predicate is intended for type-checks of built-in predicates.
%
% It asserts that Term is:
%
% 1) instantiated *and*
% 2) instantiated to an instance of the given Type.
%
% It corresponds to usage mode +Term.
%
% Currently, the following types are supported:
%
% - atom
% - boolean
% - character
% - chars
% - in_character
% - integer
% - list
% - octet_character
% - octet_chars
% - pair
% - term
must_be(Type, Term) :-
must_be_(type, Type),
@@ -84,6 +89,7 @@ must_be_(octet_chars, Cs) :-
must_be_(list, Term) :- check_(error:ilist, list, Term).
must_be_(type, Term) :- check_(error:type, type, Term).
must_be_(boolean, Term) :- check_(error:boolean, boolean, Term).
must_be_(pair, Term) :- check_(error:pair, pair, Term).
must_be_(term, Term) :-
( acyclic_term(Term) ->
( ground(Term) -> true
@@ -106,6 +112,9 @@ check_(Pred, Type, Term) :-
; type_error(Type, Term, must_be/2)
).
pair(_-_).
boolean(B) :- ( B == true ; B == false ).
character(C) :-
@@ -141,20 +150,18 @@ type(var).
type(boolean).
type(term).
type(not_less_than_zero).
type(pair).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
can_be(Type, Term)
This predicate is intended for type-checks of built-in predicates.
It asserts that there is a substitution which, if applied to Term,
makes it an instance of Type.
It corresponds to usage mode ?Term.
It supports the same types as must_be/2.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% can_be(Type, Term)
%
% This predicate is intended for type-checks of built-in predicates.
%
% It asserts that there is a substitution which, if applied to Term,
% makes it an instance of Type.
%
% It corresponds to usage mode ?Term.
%
% It supports the same types as must_be/2.
can_be(Type, Term) :-
must_be(type, Type),
@@ -192,6 +199,7 @@ can_(octet_chars, Cs) :-
).
can_(list, Term) :- list_or_partial_list(Term).
can_(boolean, Term) :- boolean(Term).
can_(pair, Term) :- pair(Term).
can_(term, Term) :-
( acyclic_term(Term) ->
true
@@ -212,6 +220,30 @@ list_or_partial_list(Ls) :-
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Shorthands for throwing ISO errors.
The variants without context promote the use of
call_with_error_context/2.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
instantiation_error :-
throw(error(instantiation_error, [])).
domain_error(Type, Term) :-
throw(error(domain_error(Type, Term), [])).
type_error(Type, Term) :-
throw(error(type_error(Type, Term), [])).
representation_error(Flag) :-
throw(error(representation_error(Flag), [])).
resource_error(Resource) :-
throw(error(resource_error(Resource), [])).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The variants *with* context would not have been needed if
call_with_error_context/2 had been found earlier. In the future,
we may be able to remove them.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
instantiation_error(Context) :-
@@ -222,3 +254,20 @@ domain_error(Type, Term, Context) :-
type_error(Type, Term, Context) :-
throw(error(type_error(Type, Term), Context)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call_with_error_context/2
See https://github.com/mthom/scryer-prolog/discussions/2839 .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% call_with_error_context(+Goal, +Pair)
%
% Call _Goal_ with error context _Pair_.
%
% Examples of error contexts: `predicate-PI`, `file-Filename` etc.
:- meta_predicate(call_with_error_context(0,+)).
call_with_error_context(G_0, Pair) :-
must_be(pair, Pair),
catch(G_0, error(E,Pairs), throw(error(E,[Pair|Pairs]))).

View File

@@ -1,4 +1,4 @@
:- module(ffi, [use_foreign_module/2, foreign_struct/2]).
:- module(ffi, [use_foreign_module/2, foreign_struct/2, with_locals/2, allocate/4, deallocate/3, read_ptr/3, array_type/3]).
/** Foreign Function Interface
@@ -11,28 +11,75 @@ The main predicate is `use_foreign_module/2`. It takes a library name (which dep
operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each
function is defined by its name, a list of the type of the arguments, and the return argument.
Types available are: `sint8`, `uint8`, `sint16`, `uint16`, `sint32`, `uint32`, `sint64`,
`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
with `foreign_struct/2`.
After that, each function on the lists maps to a predicate created in the ffi module which
are used to call the native code.
The predicate takes the functor name after the function name. Then, the arguments are the input
arguments followed by a return argument. However, functions with return type `void` or `bool`
don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend
on the return value on the native side.
For each function in the list a predicate of the same name is generated in the ffi module which
can then be used to call the native code.
The predicates arguments are the input arguments of the foreign function and depending on the return type an extra argument for the return value.
Functions with return type `void` or `bool` don't have this extra argument.
Predicates for functions with return type `void` always succeed.
Predicates for functions with retun type `bool` succeed iff the return value is 1.
```
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
```
## Available types are
### Basic C Types
[C Types Reference](https://en.cppreference.com/w/c/language/types.html)
- `void`,
- `char`, `uchar`, `schar`
- `short`, `ushort`
- `int`, `uint`
- `long`, `ulong`,
- `longlong`, `ulonglong`,
- `float`, `double`
### Fixed Width Integer Types
[C Fixed With Integer Types Reference](https://en.cppreference.com/w/c/types/integer.html)
- `sint8`/`i8`, `uint8`/`u8`,
- `sint16`/`i16`, `uint16`/`u16`,
- `sint32`/`i32`, `uint32`/`u32`,
- `sint64`/`i64`, `uint64`/`u64`,
### Fixed Width Floating-Point Types
[C++ Fixed Width Floating-Point Types Reference](https://en.cppreference.com/w/cpp/types/floating-point.html)
- `f32`, `f64`
### Other Types
- `cstr`,
- `ptr`,
- `bool` and,
- custom structs, which can be defined with `foreign_struct/2`.
### Notes regarding bool
- Not necessarily compatible with the fundamental C type bool.
- Same as `i8` but only values 0 and 1 are valid values.
### Notes regarding cstr
- When using `cstr` as an argument type the string will be deallocated once the function returns.
- When using `cstr` as a return type the string will be copied and won't be deallocated.
- When an ffi function returns bytes that are not a valid utf8-string the bytes will be turned into a list of `codes` (integers)
instead of a string (list of `chars`). Note: passing a list of `codes` is not accepted in argument position.
- In argument position you can also pass a pointer directly instead of a string,
e.g. to pass a null-pointer one can provide the integer 0 as the argument.
- In return position a null-pointer will be returned as the integer 0
## Example
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
```
?- use_foreign_module("./libraylib.so", ['InitWindow'([sint32, sint32, cstr], void)]).
?- use_foreign_module("./libraylib.so", ['InitWindow'([int, int, cstr], void)]).
```
This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it:
@@ -46,6 +93,9 @@ And a new window should pop up!
:- use_module(library(lists)).
:- use_module(library(error)).
:- use_module(library(format)).
:- use_module(library(dcgs)).
:- use_module(library(iso_ext)).
%% foreign_struct(+Name, +Elements).
%
@@ -63,6 +113,21 @@ And a new window should pop up!
foreign_struct(Name, Elements) :-
'$define_foreign_struct'(Name, Elements).
%% use_foreign_module(+LibName, +Predicates)
%
% - LibName the path to the shared library to load/bind
% - Predicates list of function definitions
%
% Each function definition is a functor of arity 2.
% The functor name is the name of the function to bind,
% the first argument is the list of arguments of the function,
% the second argument is the return type of the function.
%
% This will define a predicate in the ffi module with the defined name,
% for void and bool return type functions the arity will match the length of the arguments list,
% for other return types there will be an additional out parameter.
%
use_foreign_module(LibName, Predicates) :-
'$load_foreign_lib'(LibName, Predicates),
maplist(assert_predicate, Predicates).
@@ -102,3 +167,93 @@ assert_predicate(PredicateDefinition) :-
),
Predicate = (Head:-Body),
assertz(ffi:Predicate).
%% allocate(+Allocator, +Type, +Args, -Ptr)
%
% Using the Allocator allocate Type initialized with Args and
% unify Ptr with a pointer to that allocation.
%
allocate(Allocator, Type, Args, Ptr) :-
must_be(var, Ptr),
must_be(atom, Type),
must_be(atom, Allocator),
'$ffi_allocate'(Allocator, Type, Args, Ptr).
%% read_ptr(+Type, +Ptr, -Value)
%
% Read a value of Type from the pointer Ptr and unify the read value with Value
%
% For type cstr read a nul-terminated utf-8 string starting at Ptr.
%
read_ptr(Type, Ptr, Value) :-
must_be(atom, Type),
must_be(integer, Ptr),
'$ffi_read_ptr'(Type, Ptr, Value).
%% deallocate(+Allocator, +Type, +Ptr)
%
% Deallocate the allocation at Ptr of Type allocated with Allocator
%
deallocate(Allocator, Type, Ptr) :-
must_be(atom, Allocator),
must_be(integer, Ptr),
'$ffi_deallocate'(Allocator, Type, Ptr).
:- dynamic(is_array_type_defined/1).
%% array_type(+ElemType, +Len, -ArrayType)
%
% unify the ffi type for an array of length Len with element type ElemType with ArrayType
%
array_type(ElemType, Len, ArrayType) :-
(Len =< 0 -> domain_error(greater_than_zero, Len, array_type/3); true),
phrase(format_("$[~a;~d]", [ElemType, Len]), ArrayTypeName),
atom_chars(ArrayType, ArrayTypeName),
(is_array_type_defined(ArrayType) -> true
; length(Fields, Len),
maplist(=(ElemType), Fields),
foreign_struct(ArrayType, Fields),
assertz(is_array_type_defined(ArrayType))
).
:- meta_predicate(with_locals(?, 0)).
%% with_locals(+Locals, :Goal)
%
% Allocate the Locals, evaluate the Goal and deallocate the Locals.
% The Locals will also be cleandup when Goal fails or throws an error.
%
% Locals is a list of local variable definitions `let(-Ptr, +Type, +Args)`.
% Ptr will be unified with the pointer to the local of Type initialized with Args.
%
with_locals(Locals, Goal) :-
verify_locals(Locals),
setup_call_cleanup(
allocate_locals(Locals),
Goal,
deallocate_locals(Locals)
).
verify_locals(Locals) :-
must_be(list, Locals),
( maplist(verify_local, Locals) -> true
; domain_error(locals_decl_list, Locals, [verify_locals/1])
).
verify_local(let(Var, Type, Init)) :-
must_be(var, Var),
must_be(atom, Type),
ground(Init).
allocate_locals([]).
allocate_locals([let(Var, Type, Init) | Ls]) :-
allocate(rust, Type, Init , Var),
(catch(allocate_locals(Ls), E, (deallocate_locals([let(Var, Type, Init)]), throw(E))) -> true
; deallocate_locals([let(Var, Type, Init)]), false
).
deallocate_locals([]).
deallocate_locals([let(Var, Type, _) | Ls]) :-
deallocate(rust, Type, Var),
deallocate_locals(Ls).

View File

@@ -18,7 +18,7 @@ _lists of characters_. This is an ideal representation:
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2020, 2022 by Markus Triska (triska@metalevel.at)
Written 2020-2025 by Markus Triska (triska@metalevel.at)
Part of Scryer Prolog.
Predicates for reasoning about files and directories.
@@ -89,17 +89,17 @@ _lists of characters_. This is an ideal representation:
%% directory_files(+Directory, -Files).
%
% Returns the list of files *and* directories available at a specific
% directory in the current system.
% True if `Files` are the files *and* directories available at a specific
% `Directory` in the current system.
directory_files(Directory, Files) :-
must_be(chars, Directory),
can_be(list, Files),
'$directory_files'(Directory, Files).
%% file_size(+File, -Size).
%% file_size(+File, ?Size).
%
% Returns the size (in bytes) of a file. The file must exist.
% True iff `Size` is the size (in bytes) of `File`. The file must exist.
file_size(File, Size) :-
file_must_exist(File, file_size/2),
@@ -108,22 +108,25 @@ file_size(File, Size) :-
%% file_exists(+File).
%
% Succeeds if File is a file that exists in the current system.
% True iff `File` is a file that exists in the current system.
file_exists(File) :-
must_be(chars, File),
'$file_exists'(File).
%% directory_exists(+Directory).
%
% Succeeds if Directory is a directory that exists in the current system.
% True iff `Directory` is a directory that exists in the current system.
directory_exists(Directory) :-
must_be(chars, Directory),
'$directory_exists'(Directory).
%% make_directory(+Directory).
%
% Succeeds if it creates a new directory named Directory in the current system.
% Creates a new directory named `Directory`.
% If you want to create a nested directory, use `make_directory_path/1`.
make_directory(Directory) :-
must_be(chars, Directory),
'$make_directory'(Directory).
@@ -216,25 +219,26 @@ path_canonical(Ps, Cs) :-
For two time stamps A and B, if A precedes B, then A @< B holds.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% file_modification_time(+File, -T).
%% file_modification_time(+File, ?T).
%
% For a file File that must exist, it returns a time stamp T with the modification time
% For a file `File` that must exist, `T` is the modification time.
%
% T is a time stamp compatible with `library(time)`.
% T is a time stamp compatible with `library(time)`.
file_modification_time(File, T) :-
file_time_(File, modification, T).
%% file_access_time(+File, -T).
%% file_access_time(+File, ?T).
%
% For a file File that must exist, it returns a time stamp T with the access time
% For a file `File` that must exist, `T` is the access time.
%
% T is a time stamp compatible with `library(time)`.
file_access_time(File, T) :-
file_time_(File, access, T).
%% file_creation_time(+File, -T).
%% file_creation_time(+File, ?T).
%
% For a file File that must exist, it returns a time stamp T with the creation time
% For a file `File` that must exist, `T` is the creation time.
%
% T is a time stamp compatible with `library(time)`.
file_creation_time(File, T) :-
@@ -246,7 +250,7 @@ file_time_(File, Which, T) :-
read_from_chars(T0, T).
%% path_segments(Ps, Segments).
%% path_segments(?Ps, ?Segments).
%
% True iff Segments are the segments of Ps.
%

View File

@@ -1,5 +1,5 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2020-2024 by Markus Triska (triska@metalevel.at)
Written 2020-2025 by Markus Triska (triska@metalevel.at)
Part of Scryer Prolog.
I place this code in the public domain. Use it in any way you want.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -42,7 +42,6 @@
% FormatString are used literally, except for the following tokens
% with special meaning:
%
% | `~w` | use the next available argument from Arguments here |
% | `~q` | use the next argument here, formatted as by `writeq/1` |
% | `~a` | use the next argument here, which must be an atom |
% | `~s` | use the next argument here, which must be a string |
@@ -72,6 +71,7 @@
% | `~Nn` | N newlines |
% | `~i` | ignore the next argument |
% | `~~` | the literal ~ |
% | `~w` | format like `write/1` would; consider using `~q`, `~d`, etc. |
%
% Instead of `~N`, you can write `~*` to use the next argument from
% Arguments as the numeric argument.
@@ -84,12 +84,66 @@
% ```
format_(Fs, Args) -->
{ must_be(list, Fs),
must_be(list, Args),
unique_variable_names(Args, VNs),
phrase(cells(Fs,Args,0,[],VNs), Cells) },
{ format_args_cells(Fs, Args, Cells) },
format_cells(Cells).
format_args_cells(Fs, Args, Cells) :-
must_be(chars, Fs),
must_be(list, Args),
unique_variable_names(fabricated, Args, VNs),
phrase(cells(Fs,Args,0,[],VNs), Cells).
unique_variable_names(Type, Term, VNs) :-
term_variables(Term, Vs),
foldl(var_name(Type), Vs, VNs, 0, _).
var_name(Type, V, Name=V, Num0, Num) :-
charsio:fabricate_var_name(Type, Name, Num0),
Num is Num0 + 1.
user:goal_expansion(format_(Fs,Args,Cs0,Cs),
format:format_cells(Cells, Cs0, Cs)) :-
catch(format_args_cells(Fs,Args,Cells),
E,
% no partial evaluation for uses of format_//2 that
% cannot be compiled statically, for example those where
% the argument list is a variable, or where ~*n occurs
% in the format string, or a domain error occurs
( ( E = error(instantiation_error,_)
; E = error(domain_error(_,_), _)
) ->
false
; throw(E)
)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Partial evaluation of goals involving conditions that can be
checked at compilation time. This is especially useful for the
common case of conditions that test a numeric argument against 0.
It is currently used for the goals of ~d.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
goal_pe(G0, G) :- var(G0), !, G = G0.
goal_pe((A0,B0), (A,B)) :- !, goal_pe(A0, A), goal_pe(B0, B).
goal_pe((Body0 ; Else0), Body) :-
nonvar(Body0),
Body0 = ( If -> Then0 ),
!,
( ground(If) ->
( If ->
goal_pe(Then0, Body)
; goal_pe(Else0, Body)
)
; goal_pe(Then0, Then),
goal_pe(Else0, Else),
Body = ( If -> Then ; Else )
).
goal_pe(Goal, Goal).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
format_cells//1 is an interpreter for cells, describing a string.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
format_cells([]) --> [].
format_cells([Cell|Cells]) -->
format_cell(Cell),
@@ -125,6 +179,7 @@ format_element(glue(Fill,Num)) -->
{ length(Ls, Num),
maplist(=(Fill), Ls) },
seq(Ls).
format_element(goal(_)) --> [].
elements_gluevars([], N, N) --> [].
elements_gluevars([E|Es], N0, N) -->
@@ -132,9 +187,11 @@ elements_gluevars([E|Es], N0, N) -->
elements_gluevars(Es, N1, N).
element_gluevar(chars(Cs), N0, N) -->
{ length(Cs, L),
{ must_be(chars, Cs),
length(Cs, L),
N is N0 + L }.
element_gluevar(glue(_,V), N, N) --> [V].
element_gluevar(goal(G), N, N) --> { G }.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Our key datastructure is a list of cells and newlines.
@@ -142,12 +199,16 @@ element_gluevar(glue(_,V), N, N) --> [V].
From and To denote the positions of surrounding tab stops.
Elements is a list of elements that occur in a cell,
namely terms of the form chars(Cs) and glue(Char, Var).
namely terms of the form chars(Cs), glue(Char, Var)
and goal(G).
"glue" elements (TeX terminology) are evenly stretched
to fill the remaining whitespace in the cell. For each
glue element, the character Char is used for filling,
and Var is a free variable that is used when the
available space is distributed.
available space is distributed. Goals are dynamically
executed to obtain characters. In this way, format strings
can be parsed and compiled statically when possible.
newline is used if ~n occurs in a format string.
It is used because a newline character does not
@@ -161,54 +222,55 @@ cells([], Args, Tab, Es, _) --> !,
cells([~,~|Fs], Args, Tab, Es, VNs) --> !,
cells(Fs, Args, Tab, [chars("~")|Es], VNs).
cells([~,w|Fs], [Arg|Args], Tab, Es, VNs) --> !,
{ write_term_to_chars(Arg, [numbervars(true),variable_names(VNs)], Chars) },
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
{ G = write_term_to_chars(Arg, [numbervars(true),variable_names(VNs)], Chars) },
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
cells([~,q|Fs], [Arg|Args], Tab, Es, VNs) --> !,
{ write_term_to_chars(Arg, [quoted(true),numbervars(true),variable_names(VNs)], Chars) },
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
{ G = write_term_to_chars(Arg, [quoted(true),numbervars(true),variable_names(VNs)], Chars) },
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
cells([~,a|Fs], [Arg|Args], Tab, Es, VNs) --> !,
{ atom_chars(Arg, Chars) },
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
{ G = atom_chars(Arg, Chars) },
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num, [d|Fs], Args0, [Arg0|Args]) },
!,
{ Arg is Arg0, % evaluate compound expression
must_be(integer, Arg),
number_chars(Arg, Cs0) },
( { Num =:= 0 } -> { Cs = Cs0 }
; { length(Cs0, L),
( L =< Num ->
Delta is Num - L,
length(Zs, Delta),
maplist(=('0'), Zs),
phrase(("0.",seq(Zs),seq(Cs0)), Cs)
; BeforeComma is L - Num,
length(Bs, BeforeComma),
append(Bs, Ds, Cs0),
phrase((seq(Bs),".",seq(Ds)), Cs)
) }
),
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
{ G0 = ( Arg is Arg0, % evaluate compound expression
must_be(integer, Arg),
number_chars(Arg, Cs0),
( Num =:= 0 -> Cs = Cs0
; length(Cs0, L),
( L =< Num ->
Delta is Num - L,
length(Zs, Delta),
maplist(=('0'), Zs),
phrase(("0.",seq(Zs),seq(Cs0)), Cs)
; BeforeComma is L - Num,
length(Bs, BeforeComma),
append(Bs, Ds, Cs0),
phrase((seq(Bs),".",seq(Ds)), Cs)
)
)),
goal_pe(G0, G) },
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num, ['D'|Fs], Args0, [Arg|Args]) },
!,
{ separate_digits_fractional(Arg, ',', Num, Cs) },
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
{ G = separate_digits_fractional(Arg, ',', Num, Cs) },
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num, ['U'|Fs], Args0, [Arg|Args]) },
!,
{ separate_digits_fractional(Arg, '_', Num, Cs) },
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
{ G = separate_digits_fractional(Arg, '_', Num, Cs) },
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num0, ['L'|Fs], Args0, [Arg|Args]) },
!,
{ ( Num0 =:= 0 ->
Num = 72
; Num = Num0
),
phrase(format_("~d", [Arg]), Cs0),
phrase(split_lines_width(Cs0, Num), Cs) },
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
{ G = (( Num0 =:= 0 ->
Num = 72
; Num = Num0
),
phrase(format_("~d", [Arg]), Cs0),
phrase(split_lines_width(Cs0, Num), Cs) ) },
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
cells([~,i|Fs], [_|Args], Tab, Es, VNs) --> !,
cells(Fs, Args, Tab, Es, VNs).
cells([~,n|Fs], Args, Tab, Es, VNs) --> !,
@@ -223,59 +285,40 @@ cells([~|Fs0], Args0, Tab, Es, VNs) -->
cells(Fs, Args, 0, [], VNs).
cells([~,s|Fs], [Arg|Args], Tab, Es, VNs) --> !,
cells(Fs, Args, Tab, [chars(Arg)|Es], VNs).
cells([~,f|Fs], [Arg|Args], Tab, Es, VNs) --> !,
{ format_number_chars(Arg, Chars) },
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
cells([~,f|Fs], Args, Tab, Es, VNs) --> !,
cells([~,'6',f|Fs], Args, Tab, Es, VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num, [f|Fs], Args0, [Arg|Args]) },
!,
{ format_number_chars(Arg, Cs0),
phrase(upto_what(Bs, .), Cs0, Cs),
( Num =:= 0 -> Chars = Bs
; ( Cs = ['.'|Rest] ->
length(Rest, L),
( Num < L ->
length(Ds, Num),
append(Ds, _, Rest)
; Num =:= L ->
Ds = Rest
; Num > L,
Delta is Num - L,
% we should look into the float with
% greater accuracy here, and use the
% actual digits instead of 0.
length(Zs, Delta),
maplist(=('0'), Zs),
append(Rest, Zs, Ds)
)
; length(Ds, Num),
maplist(=('0'), Ds)
),
append(Bs, ['.'|Ds], Chars)
) },
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
{ G = phrase(float_with_n_decimal_digits(Arg, Num), Chars) },
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
cells([~,r|Fs], Args, Tab, Es, VNs) --> !,
cells([~,'8',r|Fs], Args, Tab, Es, VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num, [r|Fs], Args0, [Arg|Args]) },
!,
{ integer_to_radix(Arg, Num, lowercase, Cs) },
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
{ G = integer_to_radix(Arg, Num, lowercase, Cs) },
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
cells([~,'R'|Fs], Args, Tab, Es, VNs) --> !,
cells([~,'8','R'|Fs], Args, Tab, Es, VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num, ['R'|Fs], Args0, [Arg|Args]) },
!,
{ integer_to_radix(Arg, Num, uppercase, Cs) },
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
{ G = integer_to_radix(Arg, Num, uppercase, Cs) },
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
cells([~,'`',Char,t|Fs], Args, Tab, Es, VNs) --> !,
cells(Fs, Args, Tab, [glue(Char,_)|Es], VNs).
cells([~,t|Fs], Args, Tab, Es, VNs) --> !,
cells(Fs, Args, Tab, [glue(' ',_)|Es], VNs).
cells([~,'|'|Fs], Args, Tab0, Es, VNs) --> !,
{ phrase(elements_gluevars(Es, 0, Width), _),
Tab is Tab0 + Width },
cell(Tab0, Tab, Es),
( { ground(Tab0), Es = [chars(Cs)], ground(Cs) } ->
{ length(Cs, Width),
Tab is Tab0 + Width },
cell(Tab0, Tab, Es)
; { G = (phrase(elements_gluevars(Es, 0, Width), _),
Tab is Tab0 + Width) },
cell(Tab0, Tab, [goal(G)|Es])
),
cells(Fs, Args, Tab, [], VNs).
cells([~|Fs0], Args0, Tab, Es, VNs) -->
{ numeric_argument(Fs0, Num, ['|'|Fs], Args0, Args) },
@@ -285,8 +328,12 @@ cells([~|Fs0], Args0, Tab, Es, VNs) -->
cells([~|Fs0], Args0, Tab0, Es, VNs) -->
{ numeric_argument(Fs0, Num, [+|Fs], Args0, Args) },
!,
{ Tab is Tab0 + Num },
cell(Tab0, Tab, Es),
( { ground(Tab0+Num) } ->
{ Tab is Tab0 + Num },
cell(Tab0, Tab, Es)
; { G = (Tab is Tab0 + Num) },
cell(Tab0, Tab, [goal(G)|Es])
),
cells(Fs, Args, Tab, [], VNs).
cells([~|Cs], Args, _, _, _) -->
( { Args == [] } ->
@@ -298,9 +345,18 @@ cells(Fs0, Args, Tab, Es, VNs) -->
Fs1 = [_|_] },
cells(Fs, Args, Tab, [chars(Fs1)|Es], VNs).
format_number_chars(N0, Chars) :-
N is N0, % evaluate compound expression
number_chars(N, Chars).
float_with_n_decimal_digits(F, N) -->
{ Fr is abs(float_fractional_part(F)),
FrR0 is round(Fr*10^N),
I0 is truncate(F),
( FrR0 >= 10^N
-> I is I0+truncate(sign(F)), FrR = FrR0
; I = I0, FrR is FrR0+10^N
)
},
( { I=0, F<0, FrR>10^N } -> "-0" ; { number_chars(I, Is) }, seq(Is) ),
".",
( { FrR = 1 } -> "0" ; { number_chars(FrR, ['1'|FrRs]) }, seq(FrRs) ).
n_newlines(N0) --> { N0 > 0, N is N0 - 1 }, [newline], n_newlines(N).
n_newlines(0) --> [].
@@ -419,9 +475,11 @@ digits(uppercase, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").
% advantage of this is that an ideal implementation writes the
% characters as they become known, without manifesting the list.
format(Fs, Args) :-
current_output(Stream),
format(Stream, Fs, Args).
format(_, _) :- not_used.
user:goal_expansion(format(Fs, Args),
( current_output(Stream),
format(Stream, Fs, Args))).
%% format(Stream, FormatString, Arguments)
%
@@ -429,9 +487,11 @@ format(Fs, Args) :-
% binary stream, then the code of each emitted character must be in
% 0..255.
format(Stream, Fs, Args) :-
phrase_to_stream(format_(Fs, Args), Stream),
flush_output(Stream).
format(_, _, _) :- not_used.
user:goal_expansion(format(Stream, Fs, Args),
( pio:phrase_to_stream(format:format_(Fs, Args), Stream),
flush_output(Stream))).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
?- phrase(format:cells("hello", [], 0, [], []), Cs).
@@ -514,20 +574,29 @@ portray_clause(Stream, Term) :-
flush_output(Stream).
portray_clause_(Term) -->
{ unique_variable_names(Term, VNs) },
{ unique_variable_names(numbervars, Term, VNs) },
portray_(Term, VNs), ".\n".
unique_variable_names(Term, VNs) :-
term_variables(Term, Vs),
foldl(var_name, Vs, VNs, 0, _).
var_name(V, Name=V, Num0, Num) :-
charsio:fabricate_var_name(numbervars, Name, Num0),
Num is Num0 + 1.
literal(Lit, VNs) -->
{ write_term_to_chars(Lit, [quoted(true),variable_names(VNs),double_quotes(true)], Ls) },
seq(Ls).
( { nonvar(Lit),
\+ number(Lit),
functor(Lit, F, A),
current_op(Pri, _, F),
( A =:= 0
; Pri >= 1000
) } ->
"(", seq(Ls), ")"
; seq(Ls)
).
literal_(Lit, VNs) -->
{ phrase(literal(Lit, VNs), Ls) },
seq(Ls),
( { phrase((...,[Last]), Ls), char_type(Last, graphic_token) } ->
" "
; ""
).
portray_(Var, VNs) --> { var(Var) }, !, literal(Var, VNs).
portray_((Head :- Body), VNs) --> !,
@@ -536,7 +605,7 @@ portray_((Head :- Body), VNs) --> !,
portray_((Head --> Body), VNs) --> !,
literal(Head, VNs), " -->\n",
body_(Body, 0, 3, VNs).
portray_(Any, VNs) --> literal(Any, VNs).
portray_(Any, VNs) --> literal_(Any, VNs).
body_(Var, C, I, VNs) --> { var(Var) }, !,
@@ -561,7 +630,7 @@ body_((A;B), C, I, VNs) --> !,
body_(A, C1, C1, VNs), "\n",
else_branch(B, I, VNs).
body_(Goal, C, I, VNs) -->
indent_to(C, I), literal(Goal, VNs).
indent_to(C, I), literal_(Goal, VNs).
% True iff Body has the shape ( If -> Then ; Else ).

View File

@@ -6,7 +6,7 @@
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- meta_predicate freeze(-, 0).
:- meta_predicate(freeze(-, 0)).
:- attribute frozen/1.

View File

@@ -17,6 +17,8 @@ as one HTTP method (in lowercase) and followed by a Route Match and a predicate
which will handle the call.
```
:- use_module(library(http/http_server)).
text_handler(Request, Response) :-
http_status_code(Response, 200),
http_body(Response, text("Welcome to Scryer Prolog!")).
@@ -24,10 +26,11 @@ text_handler(Request, Response) :-
parameter_handler(User, Request, Response) :-
http_body(Response, text(User)).
http_listen(7890, [
get(echo, text_handler), % GET /echo
post(user/User, parameter_handler(User)) % POST /user/<User>
]).
run:-
http_listen(7890, [
get(echo, text_handler), % GET /echo
post(user/User, parameter_handler(User)) % POST /user/<User>
]).
```
Every handler predicate will have at least 2-arity, with Request and Response.
@@ -62,10 +65,10 @@ Some things that are still missing:
http_basic_auth/4
]).
:- meta_predicate http_listen(?, :).
:- meta_predicate http_listen(?, :, ?).
:- meta_predicate(http_listen(?, :)).
:- meta_predicate(http_listen(?, :, ?)).
:- meta_predicate http_basic_auth(:, :, ?, ?).
:- meta_predicate(http_basic_auth(:, :, ?, ?)).
:- use_module(library(charsio)).
:- use_module(library(crypto)).
@@ -109,12 +112,29 @@ module_qualification(M, H0, H) :-
H0 =.. [Method, Path, Goal],
H =.. [Method, Path, M:Goal].
http_listen__(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit) :-
'$http_listen'(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit).
http_listen_stop_(HttpListener) :-
'$http_listen_stop'(HttpListener).
http_accept_(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle) :-
'$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle).
http_answer_(ResponseHandle, Code, Headers, ResponseStream) :-
'$http_answer'(ResponseHandle, Code, Headers, ResponseStream).
http_listen_(Port, Handlers, Options) :-
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit),
phrase(format_("0.0.0.0:~d", [Port]), Addr),
'$http_listen'(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit),!,
format("Listening at ~s\n", [Addr]),
http_loop(HttpListener, Handlers).
setup_call_cleanup(
(
http_listen__(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit),
format("Listening at http://~s\n", [Addr])
),
http_loop(HttpListener, Handlers),
http_listen_stop_(HttpListener)
).
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit) :-
member_option_default(tls_key, Options, "", TLSKey),
@@ -128,37 +148,56 @@ member_option_default(Key, List, _Default, Value) :-
member_option_default(Key, List, Default, Default) :-
X =.. [Key, _],
\+ member(X, List).
http_loop(HttpListener, Handlers) :-
'$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
current_time(Time),
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
format("~s ~w ~s\n", [TimeString, RequestMethod, RequestPath]),
maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
phrase(parse_queries(RequestQueries), RequestQuery),
(
match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
(
HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
HttpResponse = http_response(_, _, _),
(call(Handler, HttpRequest, HttpResponse) ->
send_response(ResponseHandle, HttpResponse)
; (
'$http_answer'(ResponseHandle, 500, [], ResponseStream),
call_cleanup(format(ResponseStream, "Internal Server Error", []), close(ResponseStream)))
)
)
; (
'$http_answer'(ResponseHandle, 404, [], ResponseStream),
call_cleanup(format(ResponseStream, "Not Found", []), close(ResponseStream)))
),
time((
http_accept_(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
current_time(Time),
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
format("~s ~w ~s", [TimeString, RequestMethod, RequestPath]),
maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
phrase(parse_queries(RequestQueries), RequestQuery),
(
match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
(
HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
HttpResponse = http_response(_, _, _),
catch(
(call(Handler, HttpRequest, HttpResponse) ->
send_response(ResponseHandle, HttpResponse)
;
setup_call_cleanup(
http_answer_(ResponseHandle, 500, [], ResponseStream),
format(ResponseStream, "Internal Server Error", []),
close(ResponseStream)
),
throw(handler_not_available(Handler, RequestMethod, RequestPath, RequestQuery, RequestHeaders))
),
HandlerError,
(
setup_call_cleanup(
http_answer_(ResponseHandle, 500, [], ResponseStream),
format(ResponseStream, "Internal Server Error", []),
close(ResponseStream)
),
throw(HandlerError)
)
)
)
;
setup_call_cleanup(
http_answer_(ResponseHandle, 404, [], ResponseStream),
format(ResponseStream, "Not Found", []),
close(ResponseStream)
)
)
)),
http_loop(HttpListener, Handlers).
send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
open(stream(ResponseStream0), write, ResponseStream, [type(text)]),
catch(
call_cleanup(format(ResponseStream, "~s", [ResponseText]),close(ResponseStream)),
@@ -169,9 +208,9 @@ send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), Res
send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
catch(
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
error(existence_error(stream, _), _),
true
).
@@ -179,7 +218,7 @@ send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), R
send_response(ResponseHandle, http_response(StatusCode0, file(Filename), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
catch(
call_cleanup(
setup_call_cleanup(

View File

@@ -22,6 +22,7 @@ but they're not part of the ISO Prolog standard at the moment.
copy_term/3]).
:- use_module(library(error), [can_be/2,
must_be/2,
domain_error/3,
instantiation_error/1,
type_error/3]).
@@ -223,6 +224,7 @@ run_cleaners_without_handling(Cp) :-
%% call_with_inference_limit(Goal, Limit, Result).
%
% Similar to `call(Goal)` but it limits the number of inferences for each solution of Goal.
% Calls to it may be nested, but only the last limit will be in power.
call_with_inference_limit(G, L, R) :-
( integer(L) ->
( L < 0 ->
@@ -274,17 +276,15 @@ call_with_inference_limit(_, _, R, Bb, B) :-
; nonvar(R)
).
%% partial_string(String, L, L0)
%% partial_string(String, Ls0, Ls)
%
% Explicitly construct a partial string "manually". It can be used as an optimized append/3.
% It's not recommended to use this predicate in application code.
partial_string(String, L, L0) :-
partial_string(String, Ls0, Ls) :-
must_be(chars, String),
( String == [] ->
L = L0
; catch(atom_chars(Atom, String),
error(E, _),
throw(error(E, partial_string/3))),
'$create_partial_string'(Atom, L, L0)
Ls0 = Ls
; '$create_partial_string'(String, Ls0, Ls)
).
%% partial_string(+String)
@@ -399,7 +399,7 @@ copy_term(Term, Copy, Gs) :-
; true
).
:- meta_predicate call_residue_vars(0, ?).
:- meta_predicate(call_residue_vars(0, ?)).
call_residue_vars(Goal, Vars) :-
can_be(list, Vars),

View File

@@ -117,32 +117,32 @@ Further discussions
@author Ulrich Neumerkel
*/
:- meta_predicate ^(?,0,?).
:- meta_predicate ^(?,1,?,?).
:- meta_predicate ^(?,2,?,?,?).
:- meta_predicate ^(?,3,?,?,?,?).
:- meta_predicate ^(?,4,?,?,?,?,?).
:- meta_predicate ^(?,5,?,?,?,?,?,?).
:- meta_predicate ^(?,6,?,?,?,?,?,?,?).
:- meta_predicate ^(?,7,?,?,?,?,?,?,?,?).
:- meta_predicate \(0).
:- meta_predicate \(1,?).
:- meta_predicate \(2,?,?).
:- meta_predicate \(3,?,?,?).
:- meta_predicate \(4,?,?,?,?).
:- meta_predicate \(5,?,?,?,?,?).
:- meta_predicate \(6,?,?,?,?,?,?).
:- meta_predicate \(7,?,?,?,?,?,?,?).
:- meta_predicate +\(?,0).
:- meta_predicate +\(?,1,?).
:- meta_predicate +\(?,2,?,?).
:- meta_predicate +\(?,3,?,?,?).
:- meta_predicate +\(?,4,?,?,?,?).
:- meta_predicate +\(?,5,?,?,?,?,?).
:- meta_predicate +\(?,6,?,?,?,?,?,?).
:- meta_predicate +\(?,7,?,?,?,?,?,?,?).
:- meta_predicate(^(?,0,?)).
:- meta_predicate(^(?,1,?,?)).
:- meta_predicate(^(?,2,?,?,?)).
:- meta_predicate(^(?,3,?,?,?,?)).
:- meta_predicate(^(?,4,?,?,?,?,?)).
:- meta_predicate(^(?,5,?,?,?,?,?,?)).
:- meta_predicate(^(?,6,?,?,?,?,?,?,?)).
:- meta_predicate(^(?,7,?,?,?,?,?,?,?,?)).
:- meta_predicate(\(0)).
:- meta_predicate(\(1,?)).
:- meta_predicate(\(2,?,?)).
:- meta_predicate(\(3,?,?,?)).
:- meta_predicate(\(4,?,?,?,?)).
:- meta_predicate(\(5,?,?,?,?,?)).
:- meta_predicate(\(6,?,?,?,?,?,?)).
:- meta_predicate(\(7,?,?,?,?,?,?,?)).
:- meta_predicate(+\(?,0)).
:- meta_predicate(+\(?,1,?)).
:- meta_predicate(+\(?,2,?,?)).
:- meta_predicate(+\(?,3,?,?,?)).
:- meta_predicate(+\(?,4,?,?,?,?)).
:- meta_predicate(+\(?,5,?,?,?,?,?)).
:- meta_predicate(+\(?,6,?,?,?,?,?,?)).
:- meta_predicate(+\(?,7,?,?,?,?,?,?,?)).
:- meta_predicate no_hat_call(0).
:- meta_predicate(no_hat_call(0)).
^(V1,C_0,V1) :-
no_hat_call(C_0).

View File

@@ -2,7 +2,7 @@
List manipulation predicates
*/
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5,
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5, foldl/6,
memberchk/2, reverse/2, length/2, maplist/2,
maplist/3, maplist/4, maplist/5, maplist/6,
maplist/7, maplist/8, maplist/9, same_length/2, nth0/3, nth0/4, nth1/3, nth1/4,
@@ -42,17 +42,17 @@ List manipulation predicates
:- use_module(library(error)).
:- meta_predicate maplist(1, ?).
:- meta_predicate maplist(2, ?, ?).
:- meta_predicate maplist(3, ?, ?, ?).
:- meta_predicate maplist(4, ?, ?, ?, ?).
:- meta_predicate maplist(5, ?, ?, ?, ?, ?).
:- meta_predicate maplist(6, ?, ?, ?, ?, ?, ?).
:- meta_predicate maplist(7, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate maplist(8, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate(maplist(1, ?)).
:- meta_predicate(maplist(2, ?, ?)).
:- meta_predicate(maplist(3, ?, ?, ?)).
:- meta_predicate(maplist(4, ?, ?, ?, ?)).
:- meta_predicate(maplist(5, ?, ?, ?, ?, ?)).
:- meta_predicate(maplist(6, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(maplist(7, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(maplist(8, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate foldl(3, ?, ?, ?).
:- meta_predicate foldl(4, ?, ?, ?, ?).
:- meta_predicate(foldl(3, ?, ?, ?)).
:- meta_predicate(foldl(4, ?, ?, ?, ?)).
:- use_module(library(error)).
@@ -314,6 +314,16 @@ foldl(G_4, [X|Xs], [Y|Ys], A0, A) :-
call(G_4, X, Y, A0, A1),
foldl(G_4, Xs, Ys, A1, A).
%% foldl(+Goal, ?Ls0, ?Ls1, ?Ls2, +A0, ?A).
%
% Like `foldl/4`, with 2 additional lists.
foldl(_, [], [], [], A, A).
foldl(G_5, [X|Xs], [Y|Ys], [Z|Zs], A0, A) :-
call(G_5, X, Y, Z, A0, A1),
foldl(G_5, Xs, Ys, Zs, A1, A).
%% transpose(?Ls, ?Ts).
%
% If Ls is a list of lists, Ts contains the transposition

View File

@@ -0,0 +1,190 @@
% Efforts toward literate tests with quads
:- module(quadtests, [check_module_quads/2]).
:- use_module(library(iso_ext)).
:- use_module(library(pio)).
:- use_module(library(lists)).
:- use_module(library(dcgs)).
:- use_module(library(format)).
:- use_module(library(reif)).
:- use_module(library(debug)).
:- use_module(library(lambda)).
:- use_module(library(error)).
:- use_module(library(time)).
:- use_module(library('numerics/testutils')).
:- use_module(library('numerics/special_functions')).
portray_term(Stream) :-
read_term(Stream, Term, []),
portray_clause(Term).
?- check_module_quads(special_functions, _).
% Checking 11 quads ..
% CHECKING.. (?-A=0.6174468790806071,erf(A,A),B is-A,erf(B,B)).
% CHECKING.. (?-try_falsify(odd_t(erf,real(A)))).
% CHECKING.. (?-witness(odd_t(erf,real(A),false))).
% CHECKING.. (?-witness((real(A),erf(A,B),erf(-A,C),abs(B+C)>0))).
% CHECKING.. (?-length(A,B)).
% CHECKING.. (?-real(A),erf(A,B),erfc(A,C),abs(B+C-1)>epsilon).
% CHECKING.. (?-try_falsify(δ_inverses_t(40*epsilon,erf,inverf,interval(-2,2,A)))).
% CHECKING.. (?-try_falsify(δ_inverses_t(40*epsilon,erfc,inverfc,interval(-2,2,A)))).
% CHECKING.. (?-A=10,B is A+1,gamma(B,C),int_realfact(A,D)).
% CHECKING.. (?-gamma_P_Q(1.2,2.3,A,B),abs(A+B-1)<epsilon).
% CHECKING.. (?-A=1.5,B=0.7,invgammp(A,B,C),gamma_P_Q(A,C,D,E),abs(B-D)<epsilon).
true.
check_module_quads(Module, Quads) :-
use_module(Module),
read_quads(Module, Quads),
zip(Qs, ADs, Quads),
length(Qs, NQ),
format("% Checking ~d quads ..~n", [NQ]),
maplist(check_qu_ad(Module), Qs, ADs).
read_quads(Module, Quads) :-
module_terms(Module, Terms),
terms_quads(Terms, Quads).
module_terms(Module, Terms) :-
module_file(Module, File),
setup_call_cleanup(
open(File, read, Stream, [type(text)]),
read_terms(Stream, Terms),
close(Stream)
).
module_file(Module, File) :- atom_concat(Module, '.pl', File).
% Given a list of terms, filter out the predicate clauses.
% TODO: Arg 1 is really a list of Term-VarNames _pairs_;
% it would be very nice to find a less unsightly
% name than 'TermVN' for these!
terms_quads([Term|Terms], Quads) :-
( term_type(Term, clause) -> terms_quads(Terms, Quads)
; Quads = [Term|Quads_],
terms_quads(Terms, Quads_)
).
terms_quads([], []).
term_type(Term-_, Type) :-
( Term = (?- _) -> Type = query
; Term = (_,_) -> Type = answer_description
; Term = (_;_) -> Type = answer_description
; Term = (_ = _) -> Type = answer_description
; Term == true -> Type = answer_description
; Term == false -> Type = answer_description
; Type = clause
).
?- term_type(test("erf is odd",try_falsify(odd_t(erf,real(_L))))-_, Type).
Type = clause.
read_terms(Stream, Terms) :-
read_terms_(Stream, [], Terms).
read_terms_(Stream, Terms0, Terms) :-
Options = [variable_names(VarNames)],
read_term(Stream, Term, Options),
( Term = end_of_file -> reverse(Terms0, Terms)
; read_terms_(Stream, [Term-VarNames|Terms0], Terms)
).
%% zip(+Xs, +Ys, ?XYs)
%% zip(?Xs, ?Ys, +XYs)
%
% List XYs interleaves same-length lists Xs and Ys.
zip([X|Xs], [Y|Ys], [X,Y|XYs]) :-
zip(Xs, Ys, XYs).
zip([], [], []).
?- zip(Xs, Ys, XYs). % MGQ does not terminate
error('$interrupt_thrown',repl/0).
% The following suggested by Ulrich via Quad Works chat
?- zip(Xs, Ys, XYs), false. % loops
?- zip(X, [4,5,6], [1,4,2,5,3,6]).
X = [1,2,3].
?- zip([1,2,3], Y, [1,4,2,5,3,6]).
Y = [4,5,6].
?- zip([1,2,3], [4,5,6], Z).
Z = [1,4,2,5,3,6].
?- zip(Xs, Ys, [1,4,2,5,3,6]).
Xs = [1,2,3], Ys = [4,5,6].
% 3. Demonstrate checking 1 quad, the top two elements of a QAs list.
check_qu_ad(Module, Q-QVN, A-AVN) :-
Q = ?-(G),
phrase(portray_clause_(Q), LitQ), % NB: LitQ terminates w/ newline
format("% CHECKING.. ",[]),
( A == true -> call(Module:G)
; A == false -> ( call(Module:G) -> false
; true
)
; phrase(unconj(A), As) ->
( length(As, N),
n_answers(N, A, AVN, ADs),
n_answers(N, Module:G, QVN, Answers),
maplist(contains, ADs, Answers)
)
; % Otherwise, we have the ',' case of a solitary answer
call(Module:G),
call(A),
QVN == AVN
),
format("~s", [LitQ]).
% Answer-description AD (qua set-of-bindings) contains Answer.
contains(AD, Answer) :- append(Answer, _, AD).
?- contains(['Xs'=[C],'L'=1,'_A'=C,'_B'=D], ['Xs'=[A],'L'=1]).
C = A.
?- check_qu_ad(quadtests, (?-length(_F,_G))-['Xs'=_F,'L'=_G],(_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)-['Xs'=_H,'L'=_I,'_A'=_J,'_B'=_K]).
% CHECKING.. (?-length(A,B)).
_F = [_A,_B], _G = 2, _H = [_J,_K], _I = 2.
% Unravel the nested (;)/1 applications of multiple-AD structures.
unconj(Conj) --> { Conj = (Elt;Conj_) },
[Elt],
unconj(Conj_).
unconj(...) --> [].
?- phrase(unconj((_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)), List).
List = [(_H=[],_I=0),(_H=[_J],_I=1),(_H=[_J,_K],_I=2)].
empty_anstack :-
( retract('$anstack'(_)), fail
; asserta('$anstack'([]))
).
push(VN) :-
retract('$anstack'(As)),
asserta('$anstack'([VN|As])).
backtrack(N) :-
( '$anstack'(Ans),
length(Ans, N) -> true
; fail
).
n_answers(N, G, VN, ADs) :-
must_be(integer, N),
( N > 0 -> n_answers_(N, G, VN, ADs)
; domain_error(not_less_than_zero, N, n_answers/4)
).
n_answers_(N, G, VN, ADs) :-
empty_anstack,
call(G), push(VN),
backtrack(N),
!,
retract('$anstack'(As)),
reverse(As, ADs).
?- n_answers(3, length(Xs, L), ('Xs'=Xs,'Len'=L), ADs).
Xs = [_A,_B], L = 2, ADs = [('Xs'=[],'Len'=0),('Xs'=[_C],'Len'=1),('Xs'=[_D,_E],'Len'=2)].

View File

@@ -0,0 +1,257 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2025 by David C. Norris (david@precisionmethods.guru)
As with all things floating-point, use at your own risk.
Part of Scryer Prolog.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Special math functions in the Error, Gamma and Beta families
The underlying Rust implementations come from the
[puruspe](https://docs.rs/puruspe/latest/puruspe/) crate.
*/
:- module(special_functions, [
erf/2
,erfc/2
,inverf/2
,inverfc/2
,gamma/2
,gamma/3
,gamma_P_Q/4
,invgammp/3
,log_gamma/2
,beta/3
,betai/4
,invbetai/4
,test/2
,test_special_functions/0
,try_falsify/1
,witness/1
]).
:- use_module(library(numerics/testutils)).
%% erf(+Xexpr, -Erf)
%
% X is Xexpr , Erf is erf(X).
%
% [DLMF §7.2.1](https://dlmf.nist.gov/7.2#E1),
% [`puruspe::error::erf`](https://docs.rs/puruspe/latest/puruspe/error/fn.erf.html)
erf(Xexpr, Erf) :-
X is Xexpr,
builtins:must_be_number(X, erf/2),
'$erf'(X, Erf).
% Demonstrate the roots of x - erf(x))
?- X0 = 0.6174468790806071, erf(X0, X0), _X0 is -X0, erf(_X0, _X0).
X0 = 0.6174468790806071, _X0 = -0.6174468790806071.
% erf is an odd function:
?- try_falsify(odd_t(erf, real(_))).
false.
% Another way to say the same thing..
?- witness(odd_t(erf, real(_), false)).
false.
% ..and yet one more:
?- witness((real(X), erf(X,Erf), erf(-X,_Erf), abs(Erf+_Erf) > 0)).
false.
%% erfc(+X, -Erfc)
%
% Erfc is erfc(X) for X ∈ .
%
% [DLMF §7.2.2](https://dlmf.nist.gov/7.2#E2),
% [`puruspe::error::erfc`](https://docs.rs/puruspe/latest/puruspe/error/fn.erfc.html)
erfc(X, Erfc) :-
builtins:must_be_number(X, erfc/2),
'$erfc'(X, Erfc).
?- real(X), erf(X, Erf), erfc(X, Erfc), abs(Erf+Erfc-1) > epsilon.
false.
%% inverf(+ErfX, -X)
%
% X is erf⁻¹(ErfX) for ErfX ∈ (-1,1).
inverf(ErfX, X) :-
builtins:must_be_number(ErfX, inverf/2),
'$inverf'(ErfX, X).
?- try_falsify(δ_inverses_t(40*epsilon, erf, inverf, interval(-2,2,_))).
false.
%% inverfc(+ErfcX, -X)
%
% X is erfc⁻¹(ErfcX) for ErfcX ∈ (0,2).
inverfc(ErfcX, X) :-
builtins:must_be_number(ErfcX, inverfc/2),
'$inverfc'(ErfcX, X).
?- try_falsify(δ_inverses_t(40*epsilon, erfc, inverfc, interval(-2,2,_))).
false.
%% gamma(+X, -Gamma)
%
% Gamma is Γ(X), the [ordinary] gamma function evaluated at X ∈ .
%
% [DLMF §5.2.1](https://dlmf.nist.gov/5.2#E1)
% [`puruspe::gamma::gamma`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gamma.html)
gamma(X, Gamma) :-
builtins:must_be_number(X, gamma/2),
'$gamma'(X, Gamma).
% Γ(n+1) ≡ n!
?- N = 10, N1 is N+1, gamma(N1, ΓN1), int_realfact(N, Γ11).
N = 10, N1 = 11, ΓN1 = 3628800.0, Γ11 = 3628800.0.
%% gamma(+A, +X, -Gamma)
%
% Gamma is Γ(A,X), the upper incomplete gamma function, where A > 0
% is the shape parameter and X ≥ 0 is the lower limit of integration.
%
% [DLMF §8.2.2](https://dlmf.nist.gov/8.2#E2),
gamma(A, X, Gamma) :-
builtins:must_be_number(A, gammq/3),
builtins:must_be_number(X, gammq/3),
'$gammq'(A, X, Q),
gamma(A, GammaA),
Gamma is Q*GammaA.
%% gamma_P_Q(+A, +X, -P, -Q)
%
% For shape parameter A > 0 and lower limit of integration X ≥ 0,
%
% * P is γ(A,X)/Γ(X), the regularized _lower_ incomplete gamma function, and
%
% * Q is Γ(A,X)/Γ(X), the regularized _upper_ incomplete gamma function.
%
% [DLMF §8.2.4](https://dlmf.nist.gov/8.2#E4),
% [`puruspe::gammp::gammp`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gammp.html),
% [`puruspe::gammp::gammq`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gammq.html)
gamma_P_Q(A, X, P, Q) :-
builtins:must_be_number(A, gamma_P_Q/4),
builtins:must_be_number(X, gamma_P_Q/4),
'$gammp'(A, X, P),
'$gammq'(A, X, Q).
% P + Q ≈ 1
?- gamma_P_Q(1.2, 2.3, P, Q), abs(P + Q - 1) < epsilon.
P = 0.8621845438106976, Q = 0.1378154561893024.
%% invgammp(+A, +P, -X)
%
% Given shape parameter A > 0 and probability P ∈ [0,1),
%
% X is the unique solution of P = P(A,X), where P(-,-) is the
% regularized lower incomplete gamma function.
%
% [`puruspe::gamma::invgammp`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.invgammp.html)
invgammp(A, P, X) :-
builtins:must_be_number(A, invgammp/3),
builtins:must_be_number(P, invgammp/3),
'$invgammp'(P, A, X).
?- A = 1.5, P = 0.7, invgammp(A, P, X), gamma_P_Q(A, X, P_, _), abs(P-P_) < epsilon.
A = 1.5, P = 0.7, X = 1.8324353915624363, P_ = 0.7000000000000001.
%% log_gamma(+X, -LogGamma)
%
% LogGamma is ln(Γ(X)), the natural logarithm of Γ(X).
%
% [`puruspe::gamma::ln_gamma`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.ln_gamma.html)
log_gamma(X, LnGamma) :-
builtins:must_be_number(X, log_gamma/2),
'$ln_gamma'(X, LnGamma).
%% beta(+X, +Y, -B)
%
% B is B(X,Y) ≡ Γ(X)*Γ(Y)/Γ(X+Y)
%
% [DLMF §5.12.1](https://dlmf.nist.gov/5.12#E1)
% [`puruspe::beta::beta`](https://docs.rs/puruspe/latest/puruspe/beta/fn.beta.html)
beta(X, Y, B) :-
builtins:must_be_number(X, beta/3),
builtins:must_be_number(Y, beta/3),
'$beta'(X, Y, B).
%% betai(+A, +B, +X, -Ix)
%
% Given:
%
% * shape parameters A > 0 and B > 0,
% * upper limit of integration X ∈ [0,1],
%
% Ix is Iₓ(A,B) ≡ B(X;A,B)/B(A,B), the regularized incomplete beta function;
%
% [DLMF §8.17.2](https://dlmf.nist.gov/8.17#E2),
% [`puruspe::beta::betai`](https://docs.rs/puruspe/latest/puruspe/beta/fn.betai.html)
betai(A, B, X, Ix) :-
builtins:must_be_number(A, betai/4),
builtins:must_be_number(B, betai/4),
builtins:must_be_number(X, betai/4),
'$betai'(A, B, X, Ix).
%% invbetai(+A, +B, +P, -X)
%
% Given:
%
% * shape parameters A > 0 and B > 0,
% * probability P ∈ [0,1],
%
% X ∈ [0,1] is the unique solution of P = Iₓ(A,B) ≡ B(X;A,B)/B(A,B).
%
% [`puruspe::beta::invbetai`](https://docs.rs/puruspe/latest/puruspe/beta/fn.invbetai.html)
invbetai(A, B, P, X) :-
builtins:must_be_number(A, invbetai/4),
builtins:must_be_number(B, invbetai/4),
builtins:must_be_number(P, invbetai/4),
'$invbetai'(P, A, B, X).
% ============================== TESTS ==============================
%% test_special_functions
%
% Run all tests defined in this module. (These tests _succeed_ when
% they find counterexamples, so the 'desirable' result is `false`.)
test_special_functions :-
format("Seeking counterexamples to assertions:~n", []),
test(T, G), format("% ~s ~n", [T]),
call(G).
% We default to 1M falsification attempts per assertion, and -- more
% importantly -- use the (unexported) testutils:try_falsify_/2, to
% avoid testutils:reproducibly/0 fixing an RNG seed. Thus we obtain
% truly pseudorandom tests untainted by seed-hacking impropriety.
:- meta_predicate(try_falsify(1)).
try_falsify(G) :- testutils:try_falsify_(10^6, G).
% A unary witness/1 predicate similarly renders queries more concise.
:- meta_predicate(witness(0)).
witness(G) :- testutils:witness(10^6, G).
:- discontiguous(test/2).
%% test(+Name, ?Goal)
%
% Tests have the signature established by @bakaq's test_framework,
% each with a user-facing string Name, and a Goal which serves as an
% _assertion_ by succeeding iff the Name'd desirable property holds.
test("erf is odd", try_falsify(odd_t(erf, real(_)))).
test("pos root of erf(x)-x", \+ (X0 = 0.6174468790806071, erf(X0, X0))).
test("erfc ≈ 1 - erf", try_falsify(erf_plus_erfc_unity_t(real(_)))).
erf_plus_erfc_unity_t(Any, T) :-
call_free(Any, X), erf(X, Erf), erfc(X, Erfc),
( abs(Erf + Erfc - 1) < epsilon -> T = true
; T = false
).
test("inverf ≈ erf⁻¹",
try_falsify(δ_inverses_t(40*epsilon, erf, inverf, interval(-2,2,_)))).
test("('false' is good)", false).

View File

@@ -0,0 +1,147 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2025 by David C. Norris (david@precisionmethods.guru)
As with all things floating-point, use at your own risk.
Part of Scryer Prolog.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Utility predicates for testing numerics
*/
:- module(testutils, [
try_falsify/2
,witness/2
,real/1
,posreal/1
,interval/3
,call_free/2
,odd_t/3
,δ_inverses_t/5
,int_realfact/2
]).
:- use_module(library(lists)).
:- use_module(library(random)).
:- use_module(library(error)).
:- use_module(library(format)).
%% real(-X)
%
% X = tan(U) for uniform U ~ U[-π,π).
real(X) :-
random(U),
X is tan(pi*(U - 0.5)).
%% posreal(-X)
%
% X = 1/U for uniform U ~ U[0,1).
posreal(X) :-
random(U),
X is 1/U - 1.
%% interval(+A, +B, -X)
%
% X ~ U[A,B).
interval(A, B, X) :-
random(U),
X is A + (B-A)*U.
:- meta_predicate(try_falsify(+, 1)).
% NOTE: NON-reproducible tests would in fact be MORE STRINGENT,
% creating opportunities to detect rare error cases over time.
reproducibly :- set_random(seed(2025)).
%% try_falsify(+IntExpr, ?G_1)
%
% Make (N is IntExpr) attempts to falsify the partial goal G/1,
% reporting the first counterexample found.
try_falsify(N, G_1) :- reproducibly -> try_falsify_(N, G_1).
try_falsify_(IntExpr, G_1) :- N is IntExpr, must_be(integer, N), N > 0,
( call(G_1, false) -> counterexample(G_1)
; N_ is N - 1,
try_falsify_(N_, G_1)
).
%% call_free(?G, -V)
%
% Call goal G, the single free variable of which is bound to V.
call_free(G, V) :- term_variables(G, [V]), call(G).
:- meta_predicate(odd_t(2, 0, ?)).
:- meta_predicate(witness(+, 0)).
%% witness(+N, ?OhNo)
%
% Make N attempts to satisfy the goal OhNo, reporting the first
% counterexample found.
witness(N, OhNo) :- N > 0,
( call(OhNo) -> counterexample(OhNo)
; N_ is N - 1,
witness(N_, OhNo)
).
% The goal below is obviously _designed_ to succeed 10% of the time,
% giving us plenty of chances to see 'counterexamples':
?- witness(10, (random(X), format("% X = ~f~n", [X]), X > 0.9)).
% X = 0.8084510175379878
% X = 0.3173976152322231
% X = 0.6016479707924276
% X = 0.2782828276608216
% X = 0.5737059731916494
% X = 0.4300520177737992
% X = 0.5411038305190763
% X = 0.6901983097300066
% X = 0.05013429563996663
% X = 0.7321078902421636
false.
% X = 0.6401599325865659
% X = 0.7024957773981413
% X = 0.5798797162672757
% X = 0.3107013295892864
% X = 0.3792925200660049
% X = 0.3424593598278811
% X = 0.0589899862019303
% X = 0.9692529829158145
% COUNTEREXAMPLE: user:random(0.9692529829158145),(current_output(user_output),pio:phrase_to_stream(format:format_([%, ,X, ,=, ,~,f,~,n],[0.9692529829158145]),user_output),flush_output(user_output)),0.9692529829158145>0.9
X = 0.9692529829158145.
%% odd_t(+F_2, +Any, ?T)
%
% T is the truth-value from testing that function F_2 is
% [odd](https://en.wikipedia.org/wiki/Even_and_odd_functions) at a
% value X obtained via call_free(Any, X).
odd_t(F, Any, T) :-
( call_free(Any, X),
_X is -X,
call(F, X, Fx),
call(F, _X, _Fx),
_Fx is -Fx -> T = true
; T = false
).
:- meta_predicate(δ_inverses_t(?, 2, 2, 0, ?)).
%% δ_inverses_t(+Δ, +F_2, +Finv_2, +Any, ?T)
%
% For given functions F_2 and Finv_2, T is the truth-value from
% testing that Finv_2 F_2 is within Δ of the identity at a value X
% obtained via call_free(Any, X).
δ_inverses_t(Δ, F, Finv, Any, T) :-
( call_free(Any, X),
call(F, X, Fx),
call(Finv, Fx, X_),
( abs(X - X_) < Δ -> T = true
; T = false
)
).
%% int_realfact(+N, -FactN)
%
% FactN is floating-point N!
int_realfact(N, FactN) :-
N > 0, N_ is N - 1, int_realfact(N_, FactN_), FactN is N*FactN_.
int_realfact(0, 1.0).
counterexample(G) :- format("% COUNTEREXAMPLE: ~w~n", [G]).

View File

@@ -3,11 +3,16 @@
% module resolution operator.
:- op(600, xfy, :).
:- op(1199, fx, meta_predicate).
/* this is an implementation specific declarative operator used to implement call_with_inference_limit/3
and setup_call_cleanup/3. switches to the default trust_me and retry_me_else. Indexing choice
instructions are unchanged. */
% Implementation specific declarative operator used to implement
% call_with_inference_limit/3 and setup_call_cleanup/3. Compiler switches
% to the default trust_me, retry_me_else and some other instructions for all
% predicates that are marked with it. Indexing choice instructions are unchanged.
%
% Implementation details:
% Default instructions are not subject to inference counting, so their
% execution will not be considered if they happen to be called by
% call_with_inference_limit/3.
%
:- op(700, fx, non_counted_backtracking).
% arithmetic operators.
@@ -19,7 +24,6 @@
:- op(200, xfy, ^).
:- op(500, yfx, /\).
:- op(500, yfx, \/).
:- op(500, yfx, xor).
:- op(400, yfx, div).
:- op(400, yfx, //).
:- op(400, yfx, rdiv).
@@ -60,70 +64,70 @@
:- op(1200, xfx, -->).
% meta_predicate declarations for call/{1, 66}.
:- meta_predicate call(0).
:- meta_predicate call(1, ?).
:- meta_predicate call(2, ?, ?).
:- meta_predicate call(3, ?, ?, ?).
:- meta_predicate call(4, ?, ?, ?, ?).
:- meta_predicate call(5, ?, ?, ?, ?, ?).
:- meta_predicate call(6, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(7, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(8, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(9, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(10, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(11, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(12, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(13, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(14, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(15, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(16, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(17, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(18, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(19, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(20, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(21, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(22, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(23, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(24, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(25, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(26, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(27, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(28, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(29, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(30, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(31, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(32, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(33, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(34, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(35, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(36, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(37, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(38, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(39, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(40, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(41, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(42, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(43, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(44, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(45, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(46, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(47, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(48, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(49, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(50, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(51, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(52, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(53, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(54, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(55, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(56, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(57, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(58, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(59, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(61, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(62, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(63, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(64, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate call(65, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
:- meta_predicate(call(0)).
:- meta_predicate(call(1, ?)).
:- meta_predicate(call(2, ?, ?)).
:- meta_predicate(call(3, ?, ?, ?)).
:- meta_predicate(call(4, ?, ?, ?, ?)).
:- meta_predicate(call(5, ?, ?, ?, ?, ?)).
:- meta_predicate(call(6, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(7, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(8, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(9, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(10, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(11, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(12, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(13, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(14, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(15, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(16, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(17, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(18, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(19, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(20, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(21, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(22, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(23, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(24, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(25, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(26, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(27, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(28, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(29, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(30, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(31, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(32, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(33, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(34, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(35, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(36, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(37, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(38, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(39, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(40, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(41, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(42, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(43, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(44, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(45, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(46, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(47, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(48, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(49, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(50, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(51, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(52, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(53, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(54, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(55, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(56, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(57, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(58, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(59, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(61, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(62, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(63, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(64, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
:- meta_predicate(call(65, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).

View File

@@ -12,7 +12,7 @@
map_list_to_pairs/3]).
:- meta_predicate map_list_to_pairs(2, ?, ?).
:- meta_predicate(map_list_to_pairs(2, ?, ?)).
%% pairs_keys_values(?Pairs, ?Keys, ?Values)
%

View File

@@ -39,7 +39,8 @@
% represented as a list of characters.
phrase_from_stream(GRBody, Stream) :-
stream_to_lazy_list(Stream, Ls),
stream_property(Stream, reposition(Reposition)),
stream_to_lazy_list(Reposition, Stream, Ls),
phrase(GRBody, Ls).
%% phrase_from_file(+GRBody, +File)
@@ -64,7 +65,7 @@ phrase_from_file(NT, File, Options) :-
; Type = text
),
setup_call_cleanup(
open(File, read, Stream, Options),
open(File, read, Stream, [reposition(true)|Options]),
phrase_from_stream(NT, Stream),
close(Stream)
)
@@ -73,34 +74,41 @@ phrase_from_file(NT, File, Options) :-
% How many chars to read from stream and buffer in each step
chars_to_read(4096).
stream_to_lazy_list(Stream, Ls) :-
get_stream_buffer_position(Stream, Pos),
freeze(Ls, render_step(Stream, Pos, Ls)).
stream_to_lazy_list(Reposition, Stream, Ls) :-
get_stream_buffer_position(Reposition, Stream, Pos),
freeze(Ls, render_step(Reposition, Stream, Pos, Ls)).
render_step(Stream, Pos, Ls) :-
set_stream_buffer_position(Stream, Pos),
( buffer_at_end_of_stream(Stream) ->
render_step(Reposition, Stream, Pos, Ls) :-
set_stream_buffer_position(Reposition, Stream, Pos),
( buffer_at_end_of_stream(Reposition, Stream) ->
Ls = []
; chars_to_read(CharsToRead),
buffer_get_n_chars(Stream, CharsToRead, Chars),
buffer_get_n_chars(Reposition, Stream, CharsToRead, Chars),
partial_string(Chars, Ls, Ls0),
stream_to_lazy_list(Stream, Ls0)
stream_to_lazy_list(Reposition, Stream, Ls0)
).
buffer_at_end_of_stream(Stream) :-
buffer_at_end_of_stream(true, Stream) :- at_end_of_stream(Stream).
buffer_at_end_of_stream(false, Stream) :-
stream_bufferids(Stream, _, BufferPosId, _),
bb_get(BufferPosId, Pos),
Pos = eof.
get_stream_buffer_position(Stream, Pos) :-
get_stream_buffer_position(true, Stream, Pos) :-
stream_property(Stream, position(Pos)).
get_stream_buffer_position(false, Stream, Pos) :-
stream_bufferids(Stream, _, BufferPosId, _),
bb_get(BufferPosId, Pos).
set_stream_buffer_position(Stream, Pos) :-
set_stream_buffer_position(true, Stream, Pos) :-
set_stream_position(Stream, Pos).
set_stream_buffer_position(false, Stream, Pos) :-
stream_bufferids(Stream, _, BufferPosId, _),
bb_put(BufferPosId, Pos).
buffer_get_n_chars(Stream, N, Chars) :-
buffer_get_n_chars(true, Stream, N, Chars) :-
get_n_chars(Stream, N, Chars).
buffer_get_n_chars(false, Stream, N, Chars) :-
stream_bufferids(Stream, BufferId, BufferPosId, BufferLenId),
buffer_prepare_for_n(Stream, BufferId, BufferPosId, BufferLenId, N),
bb_get(BufferId, Buffer),

237
src/lib/process.pl Normal file
View File

@@ -0,0 +1,237 @@
:- module(process, [
process_create/3,
process_id/2,
process_release/1,
process_wait/2,
process_wait/3,
process_kill/1
]).
:- use_module(library(error)).
:- use_module(library(iso_ext)).
:- use_module(library(lists), [member/2, maplist/2, maplist/3, append/2]).
:- use_module(library(reif), [tfilter/3, memberd_t/3]).
%% process_create(+Exe, +Args:list, +Options).
%
% Create a new process by executing the executable Exe and passing it the Arguments Args.
%
% Note: On windows please take note of [windows argument splitting](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting).
%
% Options is a list consisting of the following options:
%
% * `cwd(+Path)` Set the processes working directory to `Path`
% * `process(-Process)` `Process` will be assigned a process handle for the spawned process
% * `env(+List)` Don't inherit environment variables and set the variables defined in `List`
% * `environment(+List)` Inherit environment variables and set/override the variables defined in `List`
% * `stdin(Spec)`, `stdout(Spec)` or `stderr(Spec)` defines how to redirect the spawned processes io streams
%
% The elements of `List` in `env(List)`/`environment(List)` List must be string pairs using `=/2`.
% `env/1` and `environment/1` may not be both specified.
%
% The following stdio `Spec` are available:
%
% * `std` inherit the current processes original stdio streams (does currently not account for stdio being changed by `set_input` or `set_output`)
% * `file(+Path)` attach the strea to the file at `Path`
% * `null` discards writes and behaves as eof for read. Equivalent to using `file(/dev/null)`
% * `pipe(-Steam)` create a new pipe and assigne one end to the created process and the other end to `Stream`
%
% Specifying an option multiple times is an error, when an option is not specified the following defaults apply:
%
% - `cwd(".")`
% - `environment([])`
% - `stdin(std)`, `stdout(std)`, `stderr(std)`
%
process_create(Exe, Args, Options) :- call_with_error_context(process_create_(Exe, Args, Options), predicate-process_create/3).
process_create_(Exe, Args, Options) :-
must_be(chars, Exe),
must_be(list, Args),
maplist(must_be(chars), Args),
must_be(list, Options),
check_options(
[
option([stdin], valid_stdio, stdin(std), stdin(Stdin)),
option([stdout], valid_stdio, stdout(std), stdout(Stdout)),
option([stderr], valid_stdio, stderr(std), stderr(Stderr)),
option([env, environment], valid_env, environment([]), Env),
option([process], valid_uninit_process, process(_), process(Process)),
option([cwd], valid_cwd, cwd("."), cwd(Cwd))
],
Options,
process_create_option
),
Stdin =.. Stdin1,
Stdout =.. Stdout1,
Stderr =.. Stderr1,
simplify_env(Env, Env1),
'$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Process).
%% process_id(+Process, -Pid).
%
process_id(Process, Pid) :- call_with_error_context(process_id_(Process, Pid), predicate-process_id/2).
process_id_(Process, Pid) :-
valid_process(Process),
must_be(var, Pid),
'$process_id'(Process, Pid).
%% process_wait(+Process, Status).
%
% See `process_create/3` with `Options = []`
%
process_wait(Process, Status) :- call_with_error_context(process_wait(Process, Status, []), predicate-process_wait/2).
%% process_wait(+Process, Status, Options).
%
% Wait for the process behind the process handle `Process` to exit.
%
% When the process exits regulary `Status` will be unified with `exit(Exit)` where `Exit` is the processes exit code.
% When the process exits was killed `Status` will be unified with `killed(Signal)` where `Signal` is the signal number that killed the process.
% When the process doesn't exit before the timeout `Status` will be unified with `timeout`.
%
% `Options` is a a list of the following options
%
% * timeout(Timeout) supported values for `Timeout` are 0 or `infinite`
% * release(Bool) supported values for `Bool` are `true` or `false`
%
% Each options may be specified at most once, when an option is not specified the following defaults apply:
%
% - timeout(infinite)
% - release(true)
%
process_wait(Process, Status, Options) :- call_with_error_context(process_wait_(Process, Status, Options), predicate-process_wait/3).
process_wait_(Process, Status, Options) :-
valid_process(Process),
check_options(
[
option([timeout], valid_timeout, timeout(infinite), timeout(Timeout)),
option([release], valid_release, release(true), release(Release))
],
Options,
process_wait_option
),
'$process_wait'(Process, Exit, Timeout),
((true = Release) -> '$process_release'(Process) ; true),
Exit = Status.
valid_timeout(timeout(infinite)).
valid_timeout(timeout(0)).
valid_release(release(Arg)) :-
( var(Arg) -> instantiation_error([])
; valid_bool(Arg) -> true
; domain_error(boolean, Arg, [])
).
valid_bool(true).
valid_bool(false).
%% process_kill(+Process).
%
% Kill the process using the process handle `Process`.
% On Unix this sends SIGKILL.
%
% Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1`
%
process_kill(Process) :- call_with_error_context(process_kill_(Process), predicate-process_kill/1).
process_kill_(Process) :-
valid_process(Process),
'$process_kill'(Process).
%% process_release(+Process)
%
% wait for the process to exit (if not already) and release process handle `Process`
%
% It's an error if `Process` is not a valid process handle
%
process_release(Process) :- call_with_error_context(process_release_(Process), predicate-process_release/1).
process_release_(Process) :-
valid_process(Process),
process_wait(Process, _).
must_be_known_options(Valid, Options, Domain) :- call_with_error_context(must_be_known_options_(Valid, [], Options, Domain),predicate-must_be_known_options/3).
must_be_known_options_(_, _, [], _).
must_be_known_options_(Valid, Found, [X|XS], Domain) :-
( functor(X, Option, 1) -> true
; domain_error(Domain, X, [])
) ,
( member(Option, Found) -> domain_error(non_duplicate_options, Option , [])
; member(Option, Valid) -> true
; domain_error(Domain, Option, [])
),
must_be_known_options_(Valid, [Option | Found], XS, Domain).
check_options(KnownOptions, Options, Domain) :- call_with_error_context(check_options_(KnownOptions, Options, Domain), predicate-check_options/3).
check_options_(KnownOptions, Options, Domain) :-
maplist(option_names, KnownOptions, Namess),
append(Namess, Names),
must_be_known_options(Names, Options, Domain),
extract_options(KnownOptions, Options).
option_names(option(Names,_,_,_), Names).
extract_options(KnownOptions, Options) :- call_with_error_context(extract_options_(KnownOptions, Options), predicate-extract_options/2).
extract_options_([], _).
extract_options_([X | XS], Options) :-
option(Kinds, Pred, Default, Choice) = X,
tfilter(find_option(Kinds), Options, Solutions),
( Solutions = [] -> Choice = Default
; Solutions = [Provided] -> functor(Pred, Name, Arity), ArityP1 is Arity+1, call_with_error_context(call(Pred, Provided),predicate-Name/ArityP1), Choice = Provided
; domain_error(non_conflicting_options, Solutions, [])
),
extract_options_(XS, Options).
find_option(Names, Found, T) :-
functor(Found, Name, 1),
memberd_t(Name, Names, T).
valid_stdio(IO) :- arg(1, IO, Arg),
( var(Arg) -> instantiation_error([])
; valid_stdio_(Arg) -> true
; domain_error(stdio_spec, Arg, [])
).
valid_stdio_(std).
valid_stdio_(null).
valid_stdio_(pipe(Stream)) :- must_be(var, Stream).
valid_stdio_(file(Path)) :- must_be(chars, Path).
valid_env(env(E)) :-
must_be(list, E),
( valid_env_(E) -> true
; domain_error(process_create_option, env(E), [])
).
valid_env(environment(E)) :-
must_be(list, E),
( valid_env_(E) -> true
; domain_error(process_create_option, environment(E), [])
).
valid_env_([]).
valid_env_([N=V|ES]) :-
must_be(chars, N),
must_be(chars, V),
valid_env_(ES).
valid_uninit_process(process(Process)) :- must_be(var, Process).
valid_process(Process) :- var(Process) -> instantiation_error([]) ; true.
valid_cwd(cwd(Cwd)) :- must_be(chars, Cwd).
simplify_env(E, [Kind, Envs1]) :- E =.. [Kind, Envs], simplify_env_(Envs, Envs1).
simplify_env_([],[]).
simplify_env_([N=V|E],[[N, V]|E1]) :- simplify_env_(E, E1).

View File

@@ -15,16 +15,14 @@ the random seed. This makes everything completely reproducible.
% Succeeds with probability 0.5.
maybe :- '$maybe'.
% The higher the precision, the slower it gets.
random_number_precision(64).
%% random(-R).
%
% Generates a random floating number between 0 (inclusive) and 1 (exclusive).
random(R) :-
var(R),
random_number_precision(N),
rnd(N, R).
N is 2^50,
'$random_integer'(0, N, K),
R is K/N.
%% random_integer(+Lower, +Upper, -R).
%
@@ -41,25 +39,10 @@ random_integer(Lower, Upper, R) :-
type_error(integer, Lower, random_integer/3)
; \+ integer(Upper) ->
type_error(integer, Upper, random_integer/3)
; Upper > Lower,
random(R0),
R is floor((Upper - Lower) * R0 + Lower)
; Lower < Upper,
'$random_integer'(Lower, Upper, R)
).
rnd(N, R) :-
rnd_(N, 0, R).
rnd_(0, R, R) :- !.
rnd_(N, R0, R) :-
maybe,
!,
N1 is N - 1,
rnd_(N1, R0, R).
rnd_(N, R0, R) :-
N1 is N - 1,
R1 is R0 + 1.0 / 2.0 ^ N,
rnd_(N1, R1, R).
%% set_random(+Seed).
%
% Sets a seed that will be used for subsequent random generations in this library.

View File

@@ -96,14 +96,14 @@ is_sgml_source([]).
is_sgml_source([C|Cs]) :- must_be(chars, [C|Cs]).
load_structure_([], [], _, _).
load_structure_([C|Cs], [E], Options, What) :-
load_(What, [C|Cs], E, Options).
load_structure_(file(Fs), [E], Options, What) :-
load_structure_([C|Cs], [E|Es], Options, What) :-
load_(What, [C|Cs], [E|Es], Options).
load_structure_(file(Fs), [E|Es], Options, What) :-
once(phrase_from_file(seq(Cs), Fs)),
load_(What, Cs, E, Options).
load_structure_(stream(Stream), [E], Options, What) :-
load_(What, Cs, [E|Es], Options).
load_structure_(stream(Stream), [E|Es], Options, What) :-
get_n_chars(Stream, _, Cs),
load_(What, Cs, E, Options).
load_(What, Cs, [E|Es], Options).
load_(html, Cs, E, Options) :- '$load_html'(Cs, E, Options).
load_(xml, Cs, E, Options) :- '$load_xml'(Cs, E, Options).

View File

@@ -40,6 +40,7 @@
term_si/1,
chars_si/1,
dif_si/2,
not_si/1,
when_si/2]).
:- use_module(library(lists)).
@@ -100,6 +101,17 @@ dif_si(X, Y) :-
; throw(error(instantiation_error,dif_si/2))
).
%% not_si(+Goal).
%
% True if Goal is not provable. Instantiation error if Goal is not
% ground.
:- meta_predicate(not_si(0)).
not_si(Goal) :-
term_si(Goal),
\+ Goal.
:- meta_predicate(when_si(+, 0)).
%% when_si(Condition, Goal).

View File

@@ -1,7 +1,7 @@
/** Tabling, also called SLG resolution.
SLG resolution is an alternative execution strategy that sometimes
helps to improve termination and performance characters of Prolog
helps to improve termination and performance characteristics of Prolog
predicates.
To enable this execution strategy for a Prolog predicate, add a
@@ -93,7 +93,7 @@ table_and_status_for_variant(V,T,S) :-
tbd_table_status(T,S).
:- meta_predicate start_tabling(?, :).
:- meta_predicate(start_tabling(?, :)).
start_tabling(Wrapper,Worker) :-
put_new_trie_table_link,

View File

@@ -1,12 +1,18 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2020-2023 by Markus Triska (triska@metalevel.at)
Written 2020-2024 by Markus Triska (triska@metalevel.at)
Part of Scryer Prolog.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** This library provides predicates for reasoning about time.
*/
:- module(time, [max_sleep_time/1, sleep/1, time/1, current_time/1, format_time//2]).
:- module(time, [max_sleep_time/1,
sleep/1,
time/1,
current_time/1,
format_time//2,
statistics/2
]).
:- use_module(library(format)).
:- use_module(library(iso_ext)).
@@ -91,9 +97,18 @@ sleep(T) :-
).
% '$cpu_now' can be replaced by statistics/2 once that is implemented.
%% statistics(?Keyword, ?List)
%
% Preliminary support for statistics/2, yielding timing information.
% The only supported `Keyword` is `runtime`. The first element of
% `List` is the CPU time in milliseconds, the second element is
% currently not supported.
:- meta_predicate time(0).
statistics(runtime, [T,unsupported]) :-
'$cpu_now'(T0),
T is T0*1000.
:- meta_predicate(time(0)).
:- dynamic(time_id/1).
:- dynamic(time_state/3).

View File

@@ -19,6 +19,7 @@ Provides the predicate `when/2`.
%% when(Condition, Goal).
%
% Executes Goal when Condition becomes true.
% Condition may consist of `ground(T)`, `nonvar(T)`, `C1,C2`, `C1;C2`.
when(Condition, Goal) :-
( when_condition(Condition) ->
( Condition ->

View File

@@ -19,10 +19,9 @@ write_error(Error) :-
% '$fetch_global_var' is the core system call of bb_get/2, but
% bb_get may not exist when write_error is first called, so fall
% back on '$fetch_global_var'.
( '$fetch_global_var'('$first_answer', false) ->
( '$fetch_global_var'('$answer_count', C), C =\= 0 ->
true
; write(' ') % if '$first_answer' isn't defined yet or true,
% print indentation.
; write(' ') % if still in the first answer print indentation.
),
( current_prolog_flag(double_quotes, chars) ->
DQ = true
@@ -106,7 +105,7 @@ run_initialization_goals(Module) :-
; true
).
:- meta_predicate success_or_warning(0).
:- meta_predicate(success_or_warning(0)).
success_or_warning(Goal) :-
( call(Goal) ->
@@ -200,22 +199,61 @@ warn_about_singletons([Singleton|Singletons], LinesRead) :-
).
load_loop(Stream, Evacuable) :-
stream_next_term(Stream, Term, LinesRead, Singletons) :-
( '$devour_whitespace'(Stream) ->
stream_property(Stream, position(position_and_lines_read(_, LinesRead))),
read_term(Stream, Term, [singletons(Singletons)])
; Term = end_of_file
),
).
load_loop(Stream, Evacuable) :-
stream_next_term(Stream, Term, LinesRead, Singletons),
( Term == end_of_file ->
close(Stream),
'$conclude_load'(Evacuable)
; var(Term) ->
instantiation_error(load/1)
; Term = (?- _Query) ->
devour_answer_descriptions(Stream, Evacuable)
; LineNum is LinesRead + 1,
warn_about_singletons(Singletons, LineNum),
compile_term(Term, Evacuable),
load_loop(Stream, Evacuable)
).
devour_answer_descriptions(Stream, Evacuable) :-
stream_next_term(Stream, Term, LinesRead, Singletons),
( Term == end_of_file ->
close(Stream),
'$conclude_load'(Evacuable)
; var(Term) ->
instantiation_error(load/1)
; Term = (?- _Query) ->
devour_answer_descriptions(Stream, Evacuable)
; answer_description(Term) ->
devour_answer_descriptions(Stream, Evacuable)
; warn_about_singletons(Singletons, LinesRead),
compile_term(Term, Evacuable),
load_loop(Stream, Evacuable)
).
answer_description(true).
answer_description(false).
answer_description(_V=_T).
answer_description((_A,_As)).
answer_description((_Answer;_Answers)).
answer_description(...).
answer_description(loops).
answer_description(throw(_Ball)).
answer_description(error(_Error_Term, _Impdef)).
answer_description(instantiation_error).
answer_description(type_error(_Type,_Culprit)).
answer_description(domain_error(_Domain, _Culprit)).
answer_description(syntax_error(_Error)).
answer_description(representation_error(_Flag)).
answer_description(resource_error(_Ressource)).
answer_description(uninstantiation_error(_Culprit)).
answer_description('|'(_AD,_ADs)).
compile_term(Term, Evacuable) :-
expand_terms_and_goals(Term, Terms),
@@ -223,8 +261,8 @@ compile_term(Term, Evacuable) :-
( var(Terms) ->
instantiation_error(load/1)
; Terms = [_|_] ->
compile_dispatch_or_clause_on_list(Terms, Evacuable)
; compile_dispatch_or_clause(Terms, Evacuable)
compile_dispatch_or_clause_on_list(list(Term), Terms, Evacuable)
; compile_dispatch_or_clause(term(Term), Terms, Evacuable)
).
complete_partial_goal(N, HeadArg, InnerHeadArgs, SuppArgs, CompleteHeadArg) :-
@@ -299,7 +337,7 @@ expand_term_goals(Terms0, Terms) :-
( atom(Module) ->
prolog_load_context(module, Target),
module_expanded_head_variables(Head2, HeadVars),
catch(expand_goal(Body0, Target, Body1, HeadVars, []),
catch('$call'(loader:expand_goal(Body0, Target, Body1, HeadVars, [])),
error(type_error(callable, Pred), _),
( loader:print_goal_expansion_warning(Pred),
builtins:(Body1 = Body0)
@@ -309,7 +347,7 @@ expand_term_goals(Terms0, Terms) :-
)
; module_expanded_head_variables(Head1, HeadVars),
prolog_load_context(module, Target),
catch(expand_goal(Body0, Target, Body1, HeadVars, []),
catch('$call'(loader:expand_goal(Body0, Target, Body1, HeadVars, [])),
error(type_error(callable, Pred), _),
( loader:print_goal_expansion_warning(Pred),
builtins:(Body1 = Body0)
@@ -330,18 +368,18 @@ expand_terms_and_goals(Term, Terms) :-
).
compile_dispatch_or_clause_on_list([], Evacuable).
compile_dispatch_or_clause_on_list([Term | Terms], Evacuable) :-
compile_dispatch_or_clause(Term, Evacuable),
compile_dispatch_or_clause_on_list(Terms, Evacuable).
compile_dispatch_or_clause_on_list(OrigTerm, [], Evacuable).
compile_dispatch_or_clause_on_list(OrigTerm, [Term | Terms], Evacuable) :-
compile_dispatch_or_clause(OrigTerm, Term, Evacuable),
compile_dispatch_or_clause_on_list(OrigTerm, Terms, Evacuable).
compile_dispatch_or_clause(Term, Evacuable) :-
compile_dispatch_or_clause(OrigTerm, Term, Evacuable) :-
( var(Term) ->
instantiation_error(load/1)
; compile_dispatch(Term, Evacuable) ->
'$flush_term_queue'(Evacuable)
; compile_clause(Term, Evacuable)
; compile_clause(OrigTerm, Term, Evacuable)
).
@@ -403,7 +441,7 @@ predicate_indicator_sequence(PI_Seq) :-
)
).
:- meta_predicate add_predicate_declaration(3, ?).
:- meta_predicate(add_predicate_declaration(3, ?)).
add_predicate_declaration(Handler, Name/Arity) :-
predicate_indicator(Name/Arity),
@@ -467,39 +505,46 @@ compile_declaration(non_counted_backtracking(Name/Arity), Evacuable) :-
; domain_error(not_less_than_zero, Arity, load/1)
).
recompile_term(list(OrigTerm), Term, Evacuable) :-
% since OrigTerm expanded to a list, its contents are considered a
% unit to be compiled simultaneously, and so its clauses are not
% re-expanded when their predecessors are compiled.
compile_clause(list(OrigTerm), Term, Evacuable).
recompile_term(term(OrigTerm), _Term, Evacuable) :-
compile_term(OrigTerm, Evacuable).
compile_clause((Target:Head :- Body), Evacuable) :-
compile_clause(OrigTerm, (Target:Head :- Body), Evacuable) :-
!,
functor(Head, Name, Arity),
( '$is_consistent_with_term_queue'(Target, Name, Arity, Evacuable) ->
'$scoped_clause_to_evacuable'(Target, (Head :- Body), Evacuable)
; '$flush_term_queue'(Evacuable),
compile_term((Target:Head :- Body), Evacuable)
recompile_term(OrigTerm, (Target:Head :- Body), Evacuable)
).
compile_clause(Target:Head, Evacuable) :-
compile_clause(OrigTerm, Target:Head, Evacuable) :-
!,
functor(Head, Name, Arity),
( '$is_consistent_with_term_queue'(Target, Name, Arity, Evacuable) ->
'$scoped_clause_to_evacuable'(Target, Head, Evacuable)
; '$flush_term_queue'(Evacuable),
compile_term(Target:Head, Evacuable)
recompile_term(OrigTerm, Target:Head, Evacuable)
).
compile_clause((Head :- Body), Evacuable) :-
compile_clause(OrigTerm, (Head :- Body), Evacuable) :-
!,
prolog_load_context(module, Target),
functor(Head, Name, Arity),
( '$is_consistent_with_term_queue'(Target, Name, Arity, Evacuable) ->
'$clause_to_evacuable'((Head :- Body), Evacuable)
; '$flush_term_queue'(Evacuable),
compile_term((Head :- Body), Evacuable)
recompile_term(OrigTerm, (Head :- Body), Evacuable)
).
compile_clause(Head, Evacuable) :-
compile_clause(OrigTerm, Head, Evacuable) :-
prolog_load_context(module, Target),
functor(Head, Name, Arity),
( '$is_consistent_with_term_queue'(Target, Name, Arity, Evacuable) ->
'$clause_to_evacuable'(Head, Evacuable)
; '$flush_term_queue'(Evacuable),
compile_term(Head, Evacuable)
recompile_term(OrigTerm, Head, Evacuable)
).
@@ -685,16 +730,21 @@ predicate_property(Callable, Property) :-
).
strip_module(Goal, M, G) :-
'$strip_module'(Goal, M, G).
'$strip_module'(Goal, MQ, G),
( MQ = specified(M) ->
true
; MQ = unspecified,
load_context(M)
).
:- non_counted_backtracking strip_subst_module/4.
strip_subst_module(Goal, M1, M2, G) :-
'$strip_module'(Goal, M2, G),
( var(M2), \+ functor(Goal, (:), 2) ->
M2 = M1
; true
'$strip_module'(Goal, MQ, G),
( MQ = specified(M2) ->
true
; MQ = unspecified,
M1 = M2
).
/*
@@ -783,7 +833,7 @@ qualified_spec((:)).
qualified_spec(MS) :- integer(MS), MS >= 0.
:- non_counted_backtracking expand_meta_predicate_subgoals/5.
:- non_counted_backtracking expand_meta_predicate_subgoals/6.
expand_meta_predicate_subgoals([SG | SGs], [MS | MSs], M, [ESG | ESGs], HeadVars, TGs) :-
( var(SG) ->
@@ -880,9 +930,15 @@ expand_call_goal_(UnexpandedGoals, Module, ExpandedGoals) :-
UnexpandedGoals = ExpandedGoals
; goal_expansion(UnexpandedGoals, Module, UnexpandedGoals1),
( Module \== user ->
goal_expansion(UnexpandedGoals1, user, ExpandedGoals)
; ExpandedGoals = UnexpandedGoals1
goal_expansion(UnexpandedGoals1, user, Goals)
; Goals = UnexpandedGoals1
),
( predicate_property(Module:Goals, meta_predicate(MetaSpecs0)),
MetaSpecs0 =.. [_ | MetaSpecs] ->
expand_module_names(Goals, MetaSpecs, Module, ExpandedGoals, [], [])
; ExpandedGoals = Goals
)
; ExpandedGoals = UnexpandedGoals1
).
:- non_counted_backtracking transitive_goal/3.
@@ -962,17 +1018,16 @@ thread_goals(Goals0, Goals1, Hole, Functor) :-
% length(Args, N),
% Head =.. [call, G | Args],
% N1 is N + 1,
% StripModule =.. ['$strip_module', G, M1, G1],
% CallModule = call_module(G, M0, G0),
% FastCall =.. ['$fast_call', G | Args],
% PrepareCallClause =.. [ '$prepare_call_clause', G2, G1 | Args],
% ModuleCall =.. ['$module_call', M2, G4],
% PrepareCallClause =.. [ '$prepare_call_clause', G1, G0 | Args],
% ModuleCall =.. ['$module_call', M2, G3],
% Clauses = [(Head :- var(G),
% instantiation_error(call/N1)),
% (Head :- FastCall),
% (Head :- StripModule,
% PrepareCallClause,
% expand_call_goal(G2, M1, G3),
% strip_subst_module(G3, M1, M2, G4),
% (Head :- PrepareCallClause,
% expand_call_goal(G1, M1, G2),
% strip_subst_module(G2, M1, M2, G3),
% ModuleCall)].
%
% generate_call_forms :-
@@ -989,15 +1044,21 @@ thread_goals(Goals0, Goals1, Hole, Functor) :-
% The '$call' functor is an escape hatch from goal expansion. So far,
% it is used only to avoid infinite recursion into expand_call_goal/3.
:- non_counted_backtracking call/1.
call_module(G0, M, G1) :-
'$strip_module'(G0, MQ, G1),
( MQ = specified(M) ->
true
; load_context(M)
).
:- non_counted_backtracking call/1.
call(G) :-
var(G),
instantiation_error(call/1).
call(G) :-
'$fast_call'(G).
call(G0) :-
'$strip_module'(G0, M0, G1),
call_module(G0, M0, G1),
expand_call_goal(G1, M0, G2),
strip_subst_module(G2, M0, M1, G3),
'$module_call'(M1, G3).
@@ -1009,7 +1070,7 @@ call(A,B) :-
call(A,B) :-
'$fast_call'(A,B).
call(A,B) :-
'$strip_module'(A,C,D),
call_module(A,C,D),
'$prepare_call_clause'(E,D,B),
expand_call_goal(E,C,F),
strip_subst_module(F,C,G,H),
@@ -1022,7 +1083,7 @@ call(A,B,C) :-
call(A,B,C) :-
'$fast_call'(A,B,C).
call(A,B,C) :-
'$strip_module'(A,D,E),
call_module(A,D,E),
'$prepare_call_clause'(F,E,B,C),
expand_call_goal(F,D,G),
strip_subst_module(G,D,H,I),
@@ -1035,7 +1096,7 @@ call(A,B,C,D) :-
call(A,B,C,D) :-
'$fast_call'(A,B,C,D).
call(A,B,C,D) :-
'$strip_module'(A,E,F),
call_module(A,E,F),
'$prepare_call_clause'(G,F,B,C,D),
expand_call_goal(G,E,H),
strip_subst_module(H,E,I,J),
@@ -1048,7 +1109,7 @@ call(A,B,C,D,E) :-
call(A,B,C,D,E) :-
'$fast_call'(A,B,C,D,E).
call(A,B,C,D,E) :-
'$strip_module'(A,F,G),
call_module(A,F,G),
'$prepare_call_clause'(H,G,B,C,D,E),
expand_call_goal(H,F,I),
strip_subst_module(I,F,J,K),
@@ -1061,7 +1122,7 @@ call(A,B,C,D,E,F) :-
call(A,B,C,D,E,F) :-
'$fast_call'(A,B,C,D,E,F).
call(A,B,C,D,E,F) :-
'$strip_module'(A,G,H),
call_module(A,G,H),
'$prepare_call_clause'(I,H,B,C,D,E,F),
expand_call_goal(I,G,J),
strip_subst_module(J,G,K,L),
@@ -1074,7 +1135,7 @@ call(A,B,C,D,E,F,G) :-
call(A,B,C,D,E,F,G) :-
'$fast_call'(A,B,C,D,E,F,G).
call(A,B,C,D,E,F,G) :-
'$strip_module'(A,H,I),
call_module(A,H,I),
'$prepare_call_clause'(J,I,B,C,D,E,F,G),
expand_call_goal(J,H,K),
strip_subst_module(K,H,L,M),
@@ -1087,7 +1148,7 @@ call(A,B,C,D,E,F,G,H) :-
call(A,B,C,D,E,F,G,H) :-
'$fast_call'(A,B,C,D,E,F,G,H).
call(A,B,C,D,E,F,G,H) :-
'$strip_module'(A,I,J),
call_module(A,I,J),
'$prepare_call_clause'(K,J,B,C,D,E,F,G,H),
expand_call_goal(K,I,L),
strip_subst_module(L,I,M,N),
@@ -1100,7 +1161,7 @@ call(A,B,C,D,E,F,G,H,I) :-
call(A,B,C,D,E,F,G,H,I) :-
'$fast_call'(A,B,C,D,E,F,G,H,I).
call(A,B,C,D,E,F,G,H,I) :-
'$strip_module'(A,J,K),
call_module(A,J,K),
'$prepare_call_clause'(L,K,B,C,D,E,F,G,H,I),
expand_call_goal(L,J,M),
strip_subst_module(M,J,N,O),
@@ -1113,7 +1174,7 @@ call(A,B,C,D,E,F,G,H,I,J) :-
call(A,B,C,D,E,F,G,H,I,J) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J).
call(A,B,C,D,E,F,G,H,I,J) :-
'$strip_module'(A,K,L),
call_module(A,K,L),
'$prepare_call_clause'(M,L,B,C,D,E,F,G,H,I,J),
expand_call_goal(M,K,N),
strip_subst_module(N,K,O,P),
@@ -1126,7 +1187,7 @@ call(A,B,C,D,E,F,G,H,I,J,K) :-
call(A,B,C,D,E,F,G,H,I,J,K) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K).
call(A,B,C,D,E,F,G,H,I,J,K) :-
'$strip_module'(A,L,M),
call_module(A,L,M),
'$prepare_call_clause'(N,M,B,C,D,E,F,G,H,I,J,K),
expand_call_goal(N,L,O),
strip_subst_module(O,L,P,Q),
@@ -1139,7 +1200,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L) :-
call(A,B,C,D,E,F,G,H,I,J,K,L) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L).
call(A,B,C,D,E,F,G,H,I,J,K,L) :-
'$strip_module'(A,M,N),
call_module(A,M,N),
'$prepare_call_clause'(O,N,B,C,D,E,F,G,H,I,J,K,L),
expand_call_goal(O,M,P),
strip_subst_module(P,M,Q,R),
@@ -1152,7 +1213,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M).
call(A,B,C,D,E,F,G,H,I,J,K,L,M) :-
'$strip_module'(A,N,O),
call_module(A,N,O),
'$prepare_call_clause'(P,O,B,C,D,E,F,G,H,I,J,K,L,M),
expand_call_goal(P,N,Q),
strip_subst_module(Q,N,R,S),
@@ -1165,7 +1226,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N) :-
'$strip_module'(A,O,P),
call_module(A,O,P),
'$prepare_call_clause'(Q,P,B,C,D,E,F,G,H,I,J,K,L,M,N),
expand_call_goal(Q,O,R),
strip_subst_module(R,O,S,T),
@@ -1178,7 +1239,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O) :-
'$strip_module'(A,P,Q),
call_module(A,P,Q),
'$prepare_call_clause'(R,Q,B,C,D,E,F,G,H,I,J,K,L,M,N,O),
expand_call_goal(R,P,S),
strip_subst_module(S,P,T,U),
@@ -1191,7 +1252,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) :-
'$strip_module'(A,Q,R),
call_module(A,Q,R),
'$prepare_call_clause'(S,R,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P),
expand_call_goal(S,Q,T),
strip_subst_module(T,Q,U,V),
@@ -1204,7 +1265,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q) :-
'$strip_module'(A,R,S),
call_module(A,R,S),
'$prepare_call_clause'(T,S,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q),
expand_call_goal(T,R,U),
strip_subst_module(U,R,V,W),
@@ -1217,7 +1278,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) :-
'$strip_module'(A,S,T),
call_module(A,S,T),
'$prepare_call_clause'(U,T,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R),
expand_call_goal(U,S,V),
strip_subst_module(V,S,W,X),
@@ -1230,7 +1291,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S) :-
'$strip_module'(A,T,U),
call_module(A,T,U),
'$prepare_call_clause'(V,U,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S),
expand_call_goal(V,T,W),
strip_subst_module(W,T,X,Y),
@@ -1243,7 +1304,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T) :-
'$strip_module'(A,U,V),
call_module(A,U,V),
'$prepare_call_clause'(W,V,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T),
expand_call_goal(W,U,X),
strip_subst_module(X,U,Y,Z),
@@ -1256,7 +1317,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U) :-
'$strip_module'(A,V,W),
call_module(A,V,W),
'$prepare_call_clause'(X,W,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U),
expand_call_goal(X,V,Y),
strip_subst_module(Y,V,Z,A1),
@@ -1269,7 +1330,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V) :-
'$strip_module'(A,W,X),
call_module(A,W,X),
'$prepare_call_clause'(Y,X,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V),
expand_call_goal(Y,W,Z),
strip_subst_module(Z,W,A1,B1),
@@ -1282,7 +1343,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W) :-
'$strip_module'(A,X,Y),
call_module(A,X,Y),
'$prepare_call_clause'(Z,Y,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W),
expand_call_goal(Z,X,A1),
strip_subst_module(A1,X,B1,C1),
@@ -1295,7 +1356,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X) :-
'$strip_module'(A,Y,Z),
call_module(A,Y,Z),
'$prepare_call_clause'(A1,Z,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X),
expand_call_goal(A1,Y,B1),
strip_subst_module(B1,Y,C1,D1),
@@ -1308,7 +1369,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y) :-
'$strip_module'(A,Z,A1),
call_module(A,Z,A1),
'$prepare_call_clause'(B1,A1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y),
expand_call_goal(B1,Z,C1),
strip_subst_module(C1,Z,D1,E1),
@@ -1321,7 +1382,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z) :-
'$strip_module'(A,A1,B1),
call_module(A,A1,B1),
'$prepare_call_clause'(C1,B1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z),
expand_call_goal(C1,A1,D1),
strip_subst_module(D1,A1,E1,F1),
@@ -1334,7 +1395,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1) :-
'$strip_module'(A,B1,C1),
call_module(A,B1,C1),
'$prepare_call_clause'(D1,C1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1),
expand_call_goal(D1,B1,E1),
strip_subst_module(E1,B1,F1,G1),
@@ -1347,7 +1408,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1) :-
'$strip_module'(A,C1,D1),
call_module(A,C1,D1),
'$prepare_call_clause'(E1,D1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1),
expand_call_goal(E1,C1,F1),
strip_subst_module(F1,C1,G1,H1),
@@ -1360,7 +1421,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1) :-
'$strip_module'(A,D1,E1),
call_module(A,D1,E1),
'$prepare_call_clause'(F1,E1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1),
expand_call_goal(F1,D1,G1),
strip_subst_module(G1,D1,H1,I1),
@@ -1373,7 +1434,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1) :-
'$strip_module'(A,E1,F1),
call_module(A,E1,F1),
'$prepare_call_clause'(G1,F1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1),
expand_call_goal(G1,E1,H1),
strip_subst_module(H1,E1,I1,J1),
@@ -1386,7 +1447,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1) :-
'$strip_module'(A,F1,G1),
call_module(A,F1,G1),
'$prepare_call_clause'(H1,G1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1),
expand_call_goal(H1,F1,I1),
strip_subst_module(I1,F1,J1,K1),
@@ -1399,7 +1460,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1) :-
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1) :-
'$strip_module'(A,G1,H1),
call_module(A,G1,H1),
'$prepare_call_clause'(I1,H1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1),
expand_call_goal(I1,G1,J1),
strip_subst_module(J1,G1,K1,L1),
@@ -1412,7 +1473,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1) :
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1) :-
'$strip_module'(A,H1,I1),
call_module(A,H1,I1),
'$prepare_call_clause'(J1,I1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1),
expand_call_goal(J1,H1,K1),
strip_subst_module(K1,H1,L1,M1),
@@ -1425,7 +1486,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1) :-
'$strip_module'(A,I1,J1),
call_module(A,I1,J1),
'$prepare_call_clause'(K1,J1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1),
expand_call_goal(K1,I1,L1),
strip_subst_module(L1,I1,M1,N1),
@@ -1438,7 +1499,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1) :-
'$strip_module'(A,J1,K1),
call_module(A,J1,K1),
'$prepare_call_clause'(L1,K1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1),
expand_call_goal(L1,J1,M1),
strip_subst_module(M1,J1,N1,O1),
@@ -1451,7 +1512,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1) :-
'$strip_module'(A,K1,L1),
call_module(A,K1,L1),
'$prepare_call_clause'(M1,L1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1),
expand_call_goal(M1,K1,N1),
strip_subst_module(N1,K1,O1,P1),
@@ -1464,7 +1525,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1) :-
'$strip_module'(A,L1,M1),
call_module(A,L1,M1),
'$prepare_call_clause'(N1,M1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1),
expand_call_goal(N1,L1,O1),
strip_subst_module(O1,L1,P1,Q1),
@@ -1477,7 +1538,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1) :-
'$strip_module'(A,M1,N1),
call_module(A,M1,N1),
'$prepare_call_clause'(O1,N1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1),
expand_call_goal(O1,M1,P1),
strip_subst_module(P1,M1,Q1,R1),
@@ -1490,7 +1551,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1) :-
'$strip_module'(A,N1,O1),
call_module(A,N1,O1),
'$prepare_call_clause'(P1,O1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1),
expand_call_goal(P1,N1,Q1),
strip_subst_module(Q1,N1,R1,S1),
@@ -1503,7 +1564,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1) :-
'$strip_module'(A,O1,P1),
call_module(A,O1,P1),
'$prepare_call_clause'(Q1,P1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1),
expand_call_goal(Q1,O1,R1),
strip_subst_module(R1,O1,S1,T1),
@@ -1516,7 +1577,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1) :-
'$strip_module'(A,P1,Q1),
call_module(A,P1,Q1),
'$prepare_call_clause'(R1,Q1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1),
expand_call_goal(R1,P1,S1),
strip_subst_module(S1,P1,T1,U1),
@@ -1529,7 +1590,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1) :-
'$strip_module'(A,Q1,R1),
call_module(A,Q1,R1),
'$prepare_call_clause'(S1,R1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1),
expand_call_goal(S1,Q1,T1),
strip_subst_module(T1,Q1,U1,V1),
@@ -1542,7 +1603,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1) :-
'$strip_module'(A,R1,S1),
call_module(A,R1,S1),
'$prepare_call_clause'(T1,S1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1),
expand_call_goal(T1,R1,U1),
strip_subst_module(U1,R1,V1,W1),
@@ -1555,7 +1616,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1) :-
'$strip_module'(A,S1,T1),
call_module(A,S1,T1),
'$prepare_call_clause'(U1,T1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1),
expand_call_goal(U1,S1,V1),
strip_subst_module(V1,S1,W1,X1),
@@ -1568,7 +1629,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1) :-
'$strip_module'(A,T1,U1),
call_module(A,T1,U1),
'$prepare_call_clause'(V1,U1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1),
expand_call_goal(V1,T1,W1),
strip_subst_module(W1,T1,X1,Y1),
@@ -1581,7 +1642,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1) :-
'$strip_module'(A,U1,V1),
call_module(A,U1,V1),
'$prepare_call_clause'(W1,V1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1),
expand_call_goal(W1,U1,X1),
strip_subst_module(X1,U1,Y1,Z1),
@@ -1594,7 +1655,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1) :-
'$strip_module'(A,V1,W1),
call_module(A,V1,W1),
'$prepare_call_clause'(X1,W1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1),
expand_call_goal(X1,V1,Y1),
strip_subst_module(Y1,V1,Z1,A2),
@@ -1607,7 +1668,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1) :-
'$strip_module'(A,W1,X1),
call_module(A,W1,X1),
'$prepare_call_clause'(Y1,X1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1),
expand_call_goal(Y1,W1,Z1),
strip_subst_module(Z1,W1,A2,B2),
@@ -1620,7 +1681,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1) :-
'$strip_module'(A,X1,Y1),
call_module(A,X1,Y1),
'$prepare_call_clause'(Z1,Y1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1),
expand_call_goal(Z1,X1,A2),
strip_subst_module(A2,X1,B2,C2),
@@ -1633,7 +1694,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1) :-
'$strip_module'(A,Y1,Z1),
call_module(A,Y1,Z1),
'$prepare_call_clause'(A2,Z1,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1),
expand_call_goal(A2,Y1,B2),
strip_subst_module(B2,Y1,C2,D2),
@@ -1646,7 +1707,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1) :-
'$strip_module'(A,Z1,A2),
call_module(A,Z1,A2),
'$prepare_call_clause'(B2,A2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1),
expand_call_goal(B2,Z1,C2),
strip_subst_module(C2,Z1,D2,E2),
@@ -1659,7 +1720,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1) :-
'$strip_module'(A,A2,B2),
call_module(A,A2,B2),
'$prepare_call_clause'(C2,B2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1),
expand_call_goal(C2,A2,D2),
strip_subst_module(D2,A2,E2,F2),
@@ -1672,7 +1733,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2) :-
'$strip_module'(A,B2,C2),
call_module(A,B2,C2),
'$prepare_call_clause'(D2,C2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2),
expand_call_goal(D2,B2,E2),
strip_subst_module(E2,B2,F2,G2),
@@ -1685,7 +1746,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2) :-
'$strip_module'(A,C2,D2),
call_module(A,C2,D2),
'$prepare_call_clause'(E2,D2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2),
expand_call_goal(E2,C2,F2),
strip_subst_module(F2,C2,G2,H2),
@@ -1698,7 +1759,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2) :-
'$strip_module'(A,D2,E2),
call_module(A,D2,E2),
'$prepare_call_clause'(F2,E2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2),
expand_call_goal(F2,D2,G2),
strip_subst_module(G2,D2,H2,I2),
@@ -1711,7 +1772,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2) :-
'$strip_module'(A,E2,F2),
call_module(A,E2,F2),
'$prepare_call_clause'(G2,F2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2),
expand_call_goal(G2,E2,H2),
strip_subst_module(H2,E2,I2,J2),
@@ -1724,7 +1785,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2) :-
'$strip_module'(A,F2,G2),
call_module(A,F2,G2),
'$prepare_call_clause'(H2,G2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2),
expand_call_goal(H2,F2,I2),
strip_subst_module(I2,F2,J2,K2),
@@ -1737,7 +1798,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2) :-
'$strip_module'(A,G2,H2),
call_module(A,G2,H2),
'$prepare_call_clause'(I2,H2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2),
expand_call_goal(I2,G2,J2),
strip_subst_module(J2,G2,K2,L2),
@@ -1750,7 +1811,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2) :-
'$strip_module'(A,H2,I2),
call_module(A,H2,I2),
'$prepare_call_clause'(J2,I2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2),
expand_call_goal(J2,H2,K2),
strip_subst_module(K2,H2,L2,M2),
@@ -1763,7 +1824,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2) :-
'$strip_module'(A,I2,J2),
call_module(A,I2,J2),
'$prepare_call_clause'(K2,J2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2),
expand_call_goal(K2,I2,L2),
strip_subst_module(L2,I2,M2,N2),
@@ -1776,7 +1837,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2) :-
'$strip_module'(A,J2,K2),
call_module(A,J2,K2),
'$prepare_call_clause'(L2,K2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2),
expand_call_goal(L2,J2,M2),
strip_subst_module(M2,J2,N2,O2),
@@ -1789,7 +1850,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2) :-
'$strip_module'(A,K2,L2),
call_module(A,K2,L2),
'$prepare_call_clause'(M2,L2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2),
expand_call_goal(M2,K2,N2),
strip_subst_module(N2,K2,O2,P2),
@@ -1802,7 +1863,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2) :-
'$strip_module'(A,L2,M2),
call_module(A,L2,M2),
'$prepare_call_clause'(N2,M2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2),
expand_call_goal(N2,L2,O2),
strip_subst_module(O2,L2,P2,Q2),
@@ -1815,7 +1876,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2) :-
'$strip_module'(A,M2,N2),
call_module(A,M2,N2),
'$prepare_call_clause'(O2,N2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2),
expand_call_goal(O2,M2,P2),
strip_subst_module(P2,M2,Q2,R2),
@@ -1828,7 +1889,7 @@ call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2,M2) :-
'$fast_call'(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2,M2).
call(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2,M2) :-
'$strip_module'(A,N2,O2),
call_module(A,N2,O2),
'$prepare_call_clause'(P2,O2,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1,K1,L1,M1,N1,O1,P1,Q1,R1,S1,T1,U1,V1,W1,X1,Y1,Z1,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2,K2,L2,M2),
expand_call_goal(P2,N2,Q2),
strip_subst_module(Q2,N2,R2,S2),

View File

@@ -1,4 +1,3 @@
use std::collections::BTreeSet;
use std::env;
#[derive(Debug)]
@@ -8,9 +7,8 @@ pub struct MachineArgs {
impl MachineArgs {
pub fn new() -> Self {
let args: BTreeSet<String> = env::args().collect();
Self {
add_history: !args.contains("--no-add-history"),
add_history: env::args().all(|arg| arg != "--no-add-history"),
}
}
}

View File

@@ -1,6 +1,6 @@
use dashu::base::{Abs, Gcd, Signed, UnsignedAbs};
use dashu::integer::fast_div::ConstDivisor;
use dashu::integer::IBig;
use dashu::integer::fast_div::ConstDivisor;
use divrem::*;
use num_order::NumOrd;
@@ -11,20 +11,17 @@ use crate::forms::*;
use crate::heap_iter::*;
use crate::machine::machine_errors::*;
use crate::machine::machine_state::*;
use crate::offset_table::*;
use crate::parser::ast::*;
use crate::parser::dashu::{Integer, Rational};
use crate::types::*;
use crate::fixnum;
use ordered_float::*;
use ordered_float::{Float, OrderedFloat};
use std::cmp;
use std::convert::TryFrom;
use std::f64;
use std::mem;
#[macro_export]
macro_rules! try_numeric_result {
($e: expr, $stub_gen: expr) => {
match $e {
@@ -51,7 +48,7 @@ macro_rules! drop_iter_on_err {
};
}
fn zero_divisor_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> MachineStubGen {
fn zero_divisor_eval_error(stub_gen: impl Fn() -> MachineStub + 'static) -> MachineStubGen {
Box::new(move |machine_st| {
let eval_error = machine_st.evaluation_error(EvalError::ZeroDivisor);
let stub = stub_gen();
@@ -60,7 +57,7 @@ fn zero_divisor_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> Mach
})
}
fn undefined_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> MachineStubGen {
fn undefined_eval_error(stub_gen: impl Fn() -> MachineStub + 'static) -> MachineStubGen {
Box::new(move |machine_st| {
let eval_error = machine_st.evaluation_error(EvalError::Undefined);
let stub = stub_gen();
@@ -72,7 +69,7 @@ fn undefined_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> Machine
fn numerical_type_error(
valid_type: ValidType,
n: Number,
stub_gen: impl Fn() -> FunctorStub + 'static,
stub_gen: impl Fn() -> MachineStub + 'static,
) -> MachineStubGen {
Box::new(move |machine_st| {
let type_error = machine_st.type_error(valid_type, n);
@@ -200,11 +197,10 @@ pub(crate) fn neg(n: Number, arena: &mut Arena) -> Number {
pub(crate) fn abs(n: Number, arena: &mut Arena) -> Number {
match n {
Number::Fixnum(n) => {
if let Some(n) = n.get_num().checked_abs() {
fixnum!(Number, n, arena)
if let Some(n) = n.checked_abs() {
Number::Fixnum(n)
} else {
let arena_int = Integer::from(n.get_num());
Number::arena_from(arena_int.abs(), arena)
Number::arena_from(Integer::from(Fixnum::MAX + 1), arena)
}
}
Number::Integer(n) => {
@@ -458,13 +454,8 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
Ok(Number::Fixnum(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => {
if n1 > n2 {
Ok(Number::Integer(n1))
} else {
Ok(Number::Integer(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => Ok(Number::Integer(cmp::max(n1, n2))),
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::Rational(cmp::max(r1, r2))),
(n1, n2) => {
let stub_gen = || {
let max_atom = atom!("max");
@@ -474,7 +465,15 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;
Ok(Number::Float(cmp::max(OrderedFloat(f1), OrderedFloat(f2))))
match OrderedFloat(f1).cmp(&OrderedFloat(f2)) {
cmp::Ordering::Less => Ok(n2),
cmp::Ordering::Equal => {
// Note: n1 and n2 were compared as floats,
// so we return the second argument as a floating point value.
Ok(Number::Float(OrderedFloat(f2)))
}
cmp::Ordering::Greater => Ok(n1),
}
}
}
}
@@ -502,13 +501,8 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
Ok(Number::Fixnum(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => {
if n1 < n2 {
Ok(Number::Integer(n1))
} else {
Ok(Number::Integer(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => Ok(Number::Integer(cmp::min(n1, n2))),
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::Rational(cmp::min(r1, r2))),
(n1, n2) => {
let stub_gen = || {
let min_atom = atom!("min");
@@ -518,20 +512,28 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;
Ok(Number::Float(cmp::min(OrderedFloat(f1), OrderedFloat(f2))))
match OrderedFloat(f1).cmp(&OrderedFloat(f2)) {
cmp::Ordering::Less => Ok(n1),
cmp::Ordering::Equal => {
// Note: n1 and n2 were compared as floats,
// so we return the first argument as a floating point value.
Ok(Number::Float(OrderedFloat(f1)))
}
cmp::Ordering::Greater => Ok(n2),
}
}
}
}
pub fn rational_from_number(
n: Number,
stub_gen: impl Fn() -> FunctorStub + 'static,
stub_gen: impl Fn() -> MachineStub + 'static,
arena: &mut Arena,
) -> Result<TypedArenaPtr<Rational>, MachineStubGen> {
match n {
Number::Fixnum(n) => Ok(arena_alloc!(Rational::from(n.get_num()), arena)),
Number::Rational(r) => Ok(r),
Number::Float(OrderedFloat(f)) => match Rational::simplest_from_f64(f) {
Number::Float(OrderedFloat(f)) => match Rational::try_from(f).ok() {
Some(r) => Ok(arena_alloc!(r, arena)),
None => Err(Box::new(move |machine_st| {
let instantiation_error = machine_st.instantiation_error();
@@ -626,111 +628,110 @@ pub(crate) fn int_floor_div(
idiv(n1, n2, arena)
}
pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
pub(crate) fn shr(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let stub_gen = || {
let shr_atom = atom!(">>");
functor_stub(shr_atom, 2)
};
if n2.is_integer() && n2.is_negative() {
return shl(n1, neg(n2, arena), arena);
if rhs.is_integer() && rhs.is_negative() {
return shl(lhs, neg(rhs, arena), arena);
}
match (n1, n2) {
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
let n1_i = n1.get_num();
let n2_i = n2.get_num();
match lhs {
Number::Fixnum(lhs) => {
let rhs = match rhs {
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(u32::MAX),
Number::Integer(int) => (&*int).try_into().unwrap_or(u32::MAX),
other => {
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
}
};
let n1 = Integer::from(n1_i);
if let Ok(n2) = usize::try_from(n2_i) {
Ok(Number::arena_from(n1 >> n2, arena))
} else {
Ok(Number::arena_from(n1 >> usize::max_value(), arena))
}
let res = lhs.get_num().checked_shr(rhs).unwrap_or(0);
Ok(Number::arena_from(res, arena))
}
(Number::Fixnum(n1), Number::Integer(n2)) => {
let n1 = Integer::from(n1.get_num());
Number::Integer(lhs) => {
// Note: bigints require `log(n)` bits of space. If `rhs > usize::MAX`,
// then this clamping only becomes an issue when `lhs < 2 ^ (usize::MAX)`:
// - on 32-bit systems, `lhs` would need to be 512MiB big (1/8th of the addressable memory)
// - on 64-bit systems, `lhs` would need to be 2EiB big (!!!)
let rhs = match rhs {
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(usize::MAX),
Number::Integer(int) => (&*int).try_into().unwrap_or(usize::MAX),
other => {
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
}
};
let result: Result<usize, _> = (&*n2).try_into();
match result {
Ok(n2) => Ok(Number::arena_from(n1 >> n2, arena)),
Err(_) => Ok(Number::arena_from(n1 >> usize::max_value(), arena)),
}
Ok(Number::arena_from(Integer::from(&*lhs >> rhs), arena))
}
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
_ => Ok(Number::arena_from(
Integer::from(&*n1 >> usize::max_value()),
arena,
)),
},
(Number::Integer(n1), Number::Integer(n2)) => {
let result: Result<usize, _> = (&*n2).try_into();
match result {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
Err(_) => Ok(Number::arena_from(
Integer::from(&*n1 >> usize::max_value()),
arena,
)),
}
}
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(n1, _) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
other => Err(numerical_type_error(ValidType::Integer, other, stub_gen)),
}
}
pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
pub(crate) fn shl(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let stub_gen = || {
let shl_atom = atom!("<<");
functor_stub(shl_atom, 2)
};
if n2.is_integer() && n2.is_negative() {
return shr(n1, neg(n2, arena), arena);
if rhs.is_integer() && rhs.is_negative() {
return shr(lhs, neg(rhs, arena), arena);
}
match (n1, n2) {
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
let n1_i = n1.get_num();
let n2_i = n2.get_num();
let rhs = match rhs {
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(usize::MAX),
Number::Integer(int) => (&*int).try_into().unwrap_or(usize::MAX),
other => {
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
}
};
let n1 = Integer::from(n1_i);
match lhs {
Number::Fixnum(lhs) => {
let lhs = lhs.get_num();
if let Ok(n2) = usize::try_from(n2_i) {
Ok(Number::arena_from(n1 << n2, arena))
if let Some(res) = checked_signed_shl(lhs, rhs) {
Ok(Number::arena_from(res, arena))
} else {
Ok(Number::arena_from(n1 << usize::max_value(), arena))
let lhs = Integer::from(lhs);
Ok(Number::arena_from(
Integer::from(lhs << (rhs as usize)),
arena,
))
}
}
(Number::Fixnum(n1), Number::Integer(n2)) => {
let n1 = Integer::from(n1.get_num());
Number::Integer(lhs) => Ok(Number::arena_from(
Integer::from(&*lhs << (rhs as usize)),
arena,
)),
other => Err(numerical_type_error(ValidType::Integer, other, stub_gen)),
}
}
match (&*n2).try_into() as Result<usize, _> {
Ok(n2) => Ok(Number::arena_from(n1 << n2, arena)),
_ => Ok(Number::arena_from(n1 << usize::max_value(), arena)),
}
/// Returns `x << shift`, checking for overflow and for values of `shift` that are too big.
#[inline]
fn checked_signed_shl(x: i64, shift: usize) -> Option<i64> {
if shift == 0 {
return Some(x);
}
if x >= 0 {
// Note: for unsigned integers, the condition would usually be spelled
// `shift <= x.leading_zeros()`, but since the MSB for signed integers
// controls the sign, we need to make sure that `shift` is at most
// `x.leading_zeros() - 1`.
if shift < x.leading_zeros() as usize {
Some(x << shift)
} else {
None
}
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
_ => Ok(Number::arena_from(
Integer::from(&*n1 << usize::max_value()),
arena,
)),
},
(Number::Integer(n1), Number::Integer(n2)) => match (&*n2).try_into() as Result<usize, _> {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
_ => Ok(Number::arena_from(
Integer::from(&*n1 << usize::max_value()),
arena,
)),
},
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(n1, _) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
} else {
let y = x.checked_neg()?;
// FIXME: incorrectly rejects `-2 ^ 62 << 1`. This is currently a non-issue,
// since the bitshift is then done as a `Number::Integer`
checked_signed_shl(y, shift).and_then(|res| res.checked_neg())
}
}
@@ -958,8 +959,7 @@ pub(crate) fn gcd(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
Ok(Number::arena_from(Integer::from(n2_clone.gcd(&n1)), arena))
}
(Number::Integer(n1), Number::Integer(n2)) => {
let n2: isize = (&*n2).try_into().unwrap();
let value: Integer = (&*n1).gcd(&Integer::from(n2)).into();
let value: Integer = (&*n1).gcd(&*n2).into();
Ok(Number::arena_from(value, arena))
}
(Number::Float(f), _) | (_, Number::Float(f)) => {
@@ -1055,7 +1055,12 @@ pub(crate) fn sqrt(n1: Number) -> Result<f64, MachineStubGen> {
#[inline]
pub(crate) fn floor(n1: Number, arena: &mut Arena) -> Number {
rnd_i(&n1, arena)
rnd_i(&n1, arena).unwrap_or_else(|_err| {
// FIXME: Currently floor/1 (and several call sites) are infallible,
// but the failing cases (when `n1` is `Number::Float(NAN)` or `Number::Float(INFINITY)`)
// are not reachable with standard is/2 operations.
todo!("Make floor/1 fallible");
})
}
#[inline]
@@ -1078,21 +1083,27 @@ pub(crate) fn truncate(n: Number, arena: &mut Arena) -> Number {
}
}
pub(crate) fn round(n: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let stub_gen = || {
let is_atom = atom!("is");
functor_stub(is_atom, 2)
pub(crate) fn round(num: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let res = match num {
Number::Fixnum(_) | Number::Integer(_) => num,
Number::Rational(rat) => Number::arena_from(rat.round(), arena),
Number::Float(f) => Number::Float(OrderedFloat((*f).round())),
};
let result = add(n, Number::Float(OrderedFloat(0.5f64)), arena);
let result = try_numeric_result!(result, stub_gen)?;
// FIXME: make round/1 return EvalError
rnd_i(&res, arena).map_err(|err| -> MachineStubGen {
Box::new(move |machine_st| {
let eval_error = machine_st.evaluation_error(err);
let stub = functor_stub(atom!("round"), 1);
Ok(floor(result, arena))
machine_st.error_form(eval_error, stub)
})
})
}
pub(crate) fn bitwise_complement(n1: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
match n1 {
Number::Fixnum(n) => Ok(Number::Fixnum(Fixnum::build_with(!n.get_num()))),
Number::Fixnum(n) => Ok(Number::Fixnum(!n)),
Number::Integer(n1) => Ok(Number::arena_from(Integer::from(!&*n1), arena)),
_ => {
let stub_gen = || {
@@ -1108,27 +1119,25 @@ pub(crate) fn bitwise_complement(n1: Number, arena: &mut Arena) -> Result<Number
impl MachineState {
#[inline]
pub fn get_number(&mut self, at: &ArithmeticTerm) -> Result<Number, MachineStub> {
match at {
&ArithmeticTerm::Reg(r) => {
let value = self.store(self.deref(self[r]));
let value = match at {
&ArithmeticTerm::Reg(r) => self.store(self.deref(self[r])),
&ArithmeticTerm::IntermReg(i) => self.registers[i],
ArithmeticTerm::Number(n) => return Ok(*n),
};
match Number::try_from(value) {
Ok(n) => Ok(n),
Err(_) => self.arith_eval_by_metacall(value),
}
match Number::try_from((value, &self.arena.f64_tbl)) {
Ok(n) => Ok(n),
Err(_) => {
self.heap[0] = value;
self.arith_eval_by_metacall(0)
}
&ArithmeticTerm::Interm(i) => Ok(mem::replace(
&mut self.interms[i - 1],
Number::Fixnum(Fixnum::build_with(0)),
)),
ArithmeticTerm::Number(n) => Ok(*n),
}
}
pub fn get_rational(
&mut self,
at: &ArithmeticTerm,
caller: impl Fn() -> FunctorStub + 'static,
caller: impl Fn() -> MachineStub + 'static,
) -> Result<TypedArenaPtr<Rational>, MachineStub> {
let n = self.get_number(at)?;
@@ -1140,11 +1149,12 @@ impl MachineState {
pub(crate) fn arith_eval_by_metacall(
&mut self,
value: HeapCellValue,
term_loc: usize,
) -> Result<Number, MachineStub> {
let stub_gen = || functor_stub(atom!("is"), 2);
let mut interms = vec![];
let mut iter =
stackful_post_order_iter::<NonListElider>(&mut self.heap, &mut self.stack, value);
stackful_post_order_iter::<NonListElider>(&mut self.heap, &mut self.stack, term_loc);
while let Some(value) = iter.next() {
if value.get_forwarding_bit() {
@@ -1157,7 +1167,7 @@ impl MachineState {
(HeapCellValueTag::Str, s) => {
cell_as_atom_cell!(self.heap[s]).get_name_and_arity()
}
(HeapCellValueTag::Lis | HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset |
(HeapCellValueTag::Lis | // HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset |
HeapCellValueTag::PStrLoc) => {
(atom!("."), 2)
}
@@ -1179,38 +1189,38 @@ impl MachineState {
read_heap_cell!(value,
(HeapCellValueTag::Atom, (name, arity)) => {
if arity == 2 {
let a2 = self.interms.pop().unwrap();
let a1 = self.interms.pop().unwrap();
let a2 = interms.pop().unwrap();
let a1 = interms.pop().unwrap();
match name {
atom!("+") => self.interms.push(drop_iter_on_err!(
atom!("+") => interms.push(drop_iter_on_err!(
self,
iter,
try_numeric_result!(add(a1, a2, &mut self.arena), stub_gen)
)),
atom!("-") => self.interms.push(drop_iter_on_err!(
atom!("-") => interms.push(drop_iter_on_err!(
self,
iter,
try_numeric_result!(sub(a1, a2, &mut self.arena), stub_gen)
)),
atom!("*") => self.interms.push(drop_iter_on_err!(
atom!("*") => interms.push(drop_iter_on_err!(
self,
iter,
try_numeric_result!(mul(a1, a2, &mut self.arena), stub_gen)
)),
atom!("/") => self.interms.push(
atom!("/") => interms.push(
drop_iter_on_err!(self, iter, div(a1, a2))
),
atom!("**") => self.interms.push(
atom!("**") => interms.push(
drop_iter_on_err!(self, iter, pow(a1, a2, atom!("is")))
),
atom!("^") => self.interms.push(
atom!("^") => interms.push(
drop_iter_on_err!(self, iter, int_pow(a1, a2, &mut self.arena))
),
atom!("max") => self.interms.push(
atom!("max") => interms.push(
drop_iter_on_err!(self, iter, max(a1, a2))
),
atom!("min") => self.interms.push(
atom!("min") => interms.push(
drop_iter_on_err!(self, iter, min(a1, a2))
),
atom!("rdiv") => {
@@ -1231,39 +1241,39 @@ impl MachineState {
&mut self.arena
);
self.interms.push(Number::Rational(result));
interms.push(Number::Rational(result));
}
atom!("//") => self.interms.push(
atom!("//") => interms.push(
drop_iter_on_err!(self, iter, idiv(a1, a2, &mut self.arena))
),
atom!("div") => self.interms.push(
atom!("div") => interms.push(
drop_iter_on_err!(self, iter, int_floor_div(a1, a2, &mut self.arena))
),
atom!(">>") => self.interms.push(
atom!(">>") => interms.push(
drop_iter_on_err!(self, iter, shr(a1, a2, &mut self.arena))
),
atom!("<<") => self.interms.push(
atom!("<<") => interms.push(
drop_iter_on_err!(self, iter, shl(a1, a2, &mut self.arena))
),
atom!("/\\") => self.interms.push(
atom!("/\\") => interms.push(
drop_iter_on_err!(self, iter, and(a1, a2, &mut self.arena))
),
atom!("\\/") => self.interms.push(
atom!("\\/") => interms.push(
drop_iter_on_err!(self, iter, or(a1, a2, &mut self.arena))
),
atom!("xor") => self.interms.push(
atom!("xor") => interms.push(
drop_iter_on_err!(self, iter, xor(a1, a2, &mut self.arena))
),
atom!("mod") => self.interms.push(
atom!("mod") => interms.push(
drop_iter_on_err!(self, iter, modulus(a1, a2, &mut self.arena))
),
atom!("rem") => self.interms.push(
atom!("rem") => interms.push(
drop_iter_on_err!(self, iter, remainder(a1, a2, &mut self.arena))
),
atom!("atan2") => self.interms.push(Number::Float(OrderedFloat(
atom!("atan2") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, atan2(a1, a2))
))),
atom!("gcd") => self.interms.push(
atom!("gcd") => interms.push(
drop_iter_on_err!(self, iter, gcd(a1, a2, &mut self.arena))
),
_ => {
@@ -1279,56 +1289,56 @@ impl MachineState {
continue;
} else if arity == 1 {
let a1 = self.interms.pop().unwrap();
let a1 = interms.pop().unwrap();
match name {
atom!("-") => self.interms.push(neg(a1, &mut self.arena)),
atom!("+") => self.interms.push(a1),
atom!("cos") => self.interms.push(Number::Float(OrderedFloat(
atom!("-") => interms.push(neg(a1, &mut self.arena)),
atom!("+") => interms.push(a1),
atom!("cos") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, cos(a1))
))),
atom!("sin") => self.interms.push(Number::Float(OrderedFloat(
atom!("sin") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, sin(a1))
))),
atom!("tan") => self.interms.push(Number::Float(OrderedFloat(
atom!("tan") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, tan(a1))
))),
atom!("float_fractional_part") => self.interms.push(Number::Float(OrderedFloat(
atom!("float_fractional_part") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, float_fractional_part(a1))
))),
atom!("float_integer_part") => self.interms.push(Number::Float(OrderedFloat(
atom!("float_integer_part") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, float_integer_part(a1))
))),
atom!("sqrt") => self.interms.push(Number::Float(OrderedFloat(
atom!("sqrt") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, sqrt(a1))
))),
atom!("log") => self.interms.push(Number::Float(OrderedFloat(
atom!("log") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, log(a1))
))),
atom!("exp") => self.interms.push(Number::Float(OrderedFloat(
atom!("exp") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, exp(a1))
))),
atom!("acos") => self.interms.push(Number::Float(OrderedFloat(
atom!("acos") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, acos(a1))
))),
atom!("asin") => self.interms.push(Number::Float(OrderedFloat(
atom!("asin") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, asin(a1))
))),
atom!("atan") => self.interms.push(Number::Float(OrderedFloat(
atom!("atan") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, atan(a1))
))),
atom!("abs") => self.interms.push(abs(a1, &mut self.arena)),
atom!("float") => self.interms.push(Number::Float(OrderedFloat(
atom!("abs") => interms.push(abs(a1, &mut self.arena)),
atom!("float") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, float(a1))
))),
atom!("truncate") => self.interms.push(truncate(a1, &mut self.arena)),
atom!("round") => self.interms.push(drop_iter_on_err!(self, iter, round(a1, &mut self.arena))),
atom!("ceiling") => self.interms.push(ceiling(a1, &mut self.arena)),
atom!("floor") => self.interms.push(floor(a1, &mut self.arena)),
atom!("\\") => self.interms.push(
atom!("truncate") => interms.push(truncate(a1, &mut self.arena)),
atom!("round") => interms.push(drop_iter_on_err!(self, iter, round(a1, &mut self.arena))),
atom!("ceiling") => interms.push(ceiling(a1, &mut self.arena)),
atom!("floor") => interms.push(floor(a1, &mut self.arena)),
atom!("\\") => interms.push(
drop_iter_on_err!(self, iter, bitwise_complement(a1, &mut self.arena))
),
atom!("sign") => self.interms.push(a1.sign()),
atom!("sign") => interms.push(a1.sign()),
_ => {
let evaluable_stub = functor_stub(name, 1);
std::mem::drop(iter);
@@ -1347,15 +1357,15 @@ impl MachineState {
} else if arity == 0 {
match name {
atom!("pi") => {
self.interms.push(Number::Float(OrderedFloat(f64::consts::PI)));
interms.push(Number::Float(OrderedFloat(f64::consts::PI)));
continue;
}
atom!("e") => {
self.interms.push(Number::Float(OrderedFloat(f64::consts::E)));
interms.push(Number::Float(OrderedFloat(f64::consts::E)));
continue;
}
atom!("epsilon") => {
self.interms.push(Number::Float(OrderedFloat(f64::EPSILON)));
interms.push(Number::Float(OrderedFloat(f64::EPSILON)));
continue;
}
_ => {
@@ -1371,18 +1381,19 @@ impl MachineState {
return Err(self.error_form(evaluable_error, stub));
}
(HeapCellValueTag::Fixnum, n) => {
self.interms.push(Number::Fixnum(n));
interms.push(Number::Fixnum(n));
}
(HeapCellValueTag::F64, fl) => {
self.interms.push(Number::Float(*fl));
(HeapCellValueTag::F64Offset, offset) => {
let fl = self.arena.f64_tbl.get_entry(offset);
interms.push(Number::Float(fl));
}
(HeapCellValueTag::Cons, ptr) => {
match_untyped_arena_ptr!(ptr,
(ArenaHeaderTag::Integer, n) => {
self.interms.push(Number::Integer(n));
interms.push(Number::Integer(n));
}
(ArenaHeaderTag::Rational, r) => {
self.interms.push(Number::Rational(r));
interms.push(Number::Rational(r));
}
_ => {
std::mem::drop(iter);
@@ -1413,7 +1424,7 @@ impl MachineState {
)
}
Ok(self.interms.pop().unwrap())
Ok(interms.pop().unwrap())
}
}
@@ -1423,22 +1434,21 @@ mod tests {
use crate::machine::mock_wam::*;
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn arith_eval_by_metacall_tests() {
let mut wam = MachineState::new();
let mut op_dir = default_op_dir();
op_dir.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX as u8));
op_dir.insert((atom!("-"), Fixity::In), OpDesc::build_with(500, YFX as u8));
op_dir.insert((atom!("-"), Fixity::Pre), OpDesc::build_with(200, FY as u8));
op_dir.insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX as u8));
op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX as u8));
op_dir.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX));
op_dir.insert((atom!("-"), Fixity::In), OpDesc::build_with(500, YFX));
op_dir.insert((atom!("-"), Fixity::Pre), OpDesc::build_with(200, FY));
op_dir.insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX));
op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX));
let term_write_result =
parse_and_write_parsed_term_to_heap(&mut wam, "3 + 4 - 1 + 2.", &op_dir).unwrap();
assert_eq!(
wam.arith_eval_by_metacall(heap_loc_as_cell!(term_write_result.heap_loc)),
wam.arith_eval_by_metacall(term_write_result.heap_loc),
Ok(Number::Fixnum(Fixnum::build_with(8))),
);
@@ -1448,7 +1458,7 @@ mod tests {
parse_and_write_parsed_term_to_heap(&mut wam, "5 * 4 - 1.", &op_dir).unwrap();
assert_eq!(
wam.arith_eval_by_metacall(heap_loc_as_cell!(term_write_result.heap_loc)),
wam.arith_eval_by_metacall(term_write_result.heap_loc),
Ok(Number::Fixnum(Fixnum::build_with(19))),
);
@@ -1458,7 +1468,7 @@ mod tests {
parse_and_write_parsed_term_to_heap(&mut wam, "sign(-1).", &op_dir).unwrap();
assert_eq!(
wam.arith_eval_by_metacall(heap_loc_as_cell!(term_write_result.heap_loc)),
wam.arith_eval_by_metacall(term_write_result.heap_loc),
Ok(Number::Fixnum(Fixnum::build_with(-1)))
);
}

View File

@@ -1,13 +1,11 @@
use crate::heap_iter::*;
use crate::machine::*;
use crate::parser::ast::*;
use crate::temp_v;
use crate::types::*;
use indexmap::IndexSet;
use std::cmp::Ordering;
use std::vec::IntoIter;
pub(super) type Bindings = Vec<(usize, HeapCellValue)>;
@@ -17,7 +15,6 @@ pub(super) struct AttrVarInitializer {
pub(super) bindings: Bindings,
pub(super) p: usize,
pub(super) cp: usize,
// pub(super) instigating_p: usize,
pub(super) verify_attrs_loc: usize,
}
@@ -43,45 +40,51 @@ impl MachineState {
pub(super) fn push_attr_var_binding(&mut self, h: usize, addr: HeapCellValue) {
if self.attr_var_init.bindings.is_empty() {
// save self.p and self.cp and ensure that the next
// instruction is InstallVerifyAttrInterrupt.
// instruction is RunVerifyAttrInterrupt.
self.attr_var_init.p = self.p;
self.attr_var_init.cp = self.cp;
self.p = INSTALL_VERIFY_ATTR_INTERRUPT - 1;
self.cp = INSTALL_VERIFY_ATTR_INTERRUPT;
self.p = VERIFY_ATTR_INTERRUPT_LOC - 1;
self.cp = VERIFY_ATTR_INTERRUPT_LOC;
}
debug_assert_eq!(self.heap[h].get_tag(), HeapCellValueTag::AttrVar);
self.attr_var_init.bindings.push((h, addr));
}
fn populate_var_and_value_lists(&mut self) -> (HeapCellValue, HeapCellValue) {
fn populate_var_and_value_lists(
&mut self,
) -> Result<(HeapCellValue, HeapCellValue), AllocError> {
let size = self.attr_var_init.bindings.len();
let iter = self
.attr_var_init
.bindings
.iter()
.map(|(ref h, _)| attr_var_as_cell!(*h));
.map(|(h, _)| attr_var_as_cell!(*h));
let var_list_addr = heap_loc_as_cell!(iter_to_heap_list(&mut self.heap, iter));
let var_list_addr = sized_iter_to_heap_list(&mut self.heap, size, iter)?;
let iter = self.attr_var_init.bindings.drain(0..).map(|(_, ref v)| *v);
let value_list_addr = heap_loc_as_cell!(iter_to_heap_list(&mut self.heap, iter));
let value_list_addr = sized_iter_to_heap_list(&mut self.heap, size, iter)?;
(var_list_addr, value_list_addr)
Ok((var_list_addr, value_list_addr))
}
fn verify_attributes(&mut self) {
fn verify_attributes(&mut self) -> Result<(), AllocError> {
for (h, _) in &self.attr_var_init.bindings {
self.heap[*h] = attr_var_as_cell!(*h);
}
let (var_list_addr, value_list_addr) = self.populate_var_and_value_lists();
let (var_list_addr, value_list_addr) = self.populate_var_and_value_lists()?;
self[temp_v!(1)] = var_list_addr;
self[temp_v!(2)] = value_list_addr;
Ok(())
}
pub(super) fn gather_attr_vars_created_since(&mut self, b: usize) -> IntoIter<HeapCellValue> {
pub(super) fn gather_attr_vars_created_since(&mut self, b: usize) -> Vec<HeapCellValue> {
let mut attr_vars: Vec<_> = if b >= self.attr_var_init.attr_var_queue.len() {
vec![]
} else {
@@ -100,41 +103,61 @@ impl MachineState {
.collect()
};
attr_vars.sort_unstable_by(|a1, a2| {
compare_term_test!(self, *a1, *a2).unwrap_or(Ordering::Less)
});
attr_vars
.sort_unstable_by(|a1, a2| self.compare_term_test(*a1, *a2).unwrap_or(Ordering::Less));
attr_vars.dedup();
attr_vars.into_iter()
attr_vars
}
pub(super) fn verify_attr_interrupt(&mut self, p: usize, arity: usize) {
self.allocate(arity + 3);
pub(super) fn verify_attr_interrupt(
&mut self,
p: usize,
arity: usize,
) -> Result<(), AllocError> {
self.allocate(arity + 3)?;
let e = self.e;
let and_frame = self.stack.index_and_frame_mut(e);
for i in 1..arity + 1 {
for i in 1..=arity {
and_frame[i] = self.registers[i];
}
and_frame[arity + 1] = fixnum_as_cell!(Fixnum::build_with(self.b0 as i64));
and_frame[arity + 2] = fixnum_as_cell!(Fixnum::build_with(self.num_of_args as i64));
and_frame[arity + 3] = fixnum_as_cell!(Fixnum::build_with(self.attr_var_init.cp as i64));
and_frame[arity + 1] =
fixnum_as_cell!(
/* FIXME this is not safe */
unsafe { Fixnum::build_with_unchecked(self.b0 as i64) }
);
and_frame[arity + 2] =
fixnum_as_cell!(
/* FIXME this is not safe */
unsafe { Fixnum::build_with_unchecked(self.num_of_args as i64) }
);
and_frame[arity + 3] =
fixnum_as_cell!(
/* FIXME this is not safe */
unsafe { Fixnum::build_with_unchecked(self.attr_var_init.cp as i64) }
);
self.verify_attributes();
self.verify_attributes()?;
self.num_of_args = 3;
self.b0 = self.b;
self.p = p;
Ok(())
}
pub(super) fn attr_vars_of_term(&mut self, cell: HeapCellValue) -> Vec<HeapCellValue> {
debug_assert!(cell.is_ref());
let mut seen_set = IndexSet::new();
let mut seen_vars = vec![];
let mut iter =
stackful_preorder_iter::<NonListElider>(&mut self.heap, &mut self.stack, cell);
self.heap[0] = cell;
let mut iter = stackful_preorder_iter::<NonListElider>(&mut self.heap, &mut self.stack, 0);
while let Some(value) = iter.next() {
read_heap_cell!(value,

View File

@@ -41,7 +41,7 @@ fn capture_offset(line: &Instruction, index: usize, stack: &mut Vec<usize>) -> b
/* This function walks the code of a single predicate, supposed to
* begin in code at the offset p. Each instruction is passed to the
* walker function.
*/
*/
pub(crate) fn walk_code(code: &Code, p: usize, mut walker: impl FnMut(&Instruction)) {
let mut stack = vec![p];
let mut visited_indices = IndexSet::with_hasher(FxBuildHasher::default());

View File

@@ -80,19 +80,19 @@ fn derelictize_try_me_else(
) -> Option<usize> {
match &mut code[index] {
Instruction::DynamicElse(_, _, NextOrFail::Next(0)) => None,
Instruction::DynamicElse(_, _, NextOrFail::Next(ref mut o)) => {
Instruction::DynamicElse(_, _, NextOrFail::Next(o)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(index, *o));
Some(mem::replace(o, 0))
}
Instruction::DynamicInternalElse(_, _, NextOrFail::Next(0)) => None,
Instruction::DynamicInternalElse(_, _, NextOrFail::Next(ref mut o)) => {
Instruction::DynamicInternalElse(_, _, NextOrFail::Next(o)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(index, *o));
Some(mem::replace(o, 0))
}
Instruction::DynamicElse(_, _, NextOrFail::Fail(_))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Fail(_)) => None,
Instruction::TryMeElse(0) => None,
Instruction::TryMeElse(ref mut o) => {
Instruction::TryMeElse(o) => {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(index, *o));
Some(mem::replace(o, 0))
}
@@ -133,7 +133,7 @@ fn merge_indices(
);
retraction_info.push_record(RetractionRecord::AddedIndex(
skeleton[clause_index].opt_arg_index_key.clone(),
skeleton[clause_index].opt_arg_index_key,
clause_loc,
));
} else {
@@ -246,7 +246,7 @@ fn remove_index_from_subsequence(
// appear anywhere inside an Internal record.
retraction_info.push_record(RetractionRecord::RemovedIndex(
index_loc,
opt_arg_index_key.clone(),
*opt_arg_index_key,
offset,
));
}
@@ -275,7 +275,7 @@ fn merge_indexed_subsequences(
.unwrap(),
);
if let Instruction::TryMeElse(ref mut o) = &mut code[inner_try_me_else_loc] {
if let Instruction::TryMeElse(o) = &mut code[inner_try_me_else_loc] {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
inner_try_me_else_loc,
*o,
@@ -324,7 +324,7 @@ fn merge_indexed_subsequences(
);
}
None => {
if let Instruction::TryMeElse(ref mut o) = &mut code[outer_threaded_choice_instr_loc] {
if let Instruction::TryMeElse(o) = &mut code[outer_threaded_choice_instr_loc] {
retraction_info
.push_record(RetractionRecord::ModifiedTryMeElse(inner_trust_me_loc, *o));
@@ -472,7 +472,7 @@ fn set_switch_var_offset(
let target_indexing_line = code[index_loc].to_indexing_line_mut().unwrap();
let old_v = match &mut target_indexing_line[0] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, ref mut v, ..)) => match *v {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, v, ..)) => match *v {
IndexingCodePtr::DynamicExternal(_) => {
mem::replace(v, IndexingCodePtr::DynamicExternal(offset))
}
@@ -497,7 +497,7 @@ fn internalize_choice_instr_at(
match &mut code[instr_loc] {
Instruction::DynamicElse(_, _, NextOrFail::Fail(_))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Fail(_)) => {}
Instruction::DynamicElse(_, _, ref mut o @ NextOrFail::Next(0)) => {
Instruction::DynamicElse(_, _, o @ NextOrFail::Next(0)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(instr_loc, 0));
*o = NextOrFail::Fail(0);
}
@@ -516,7 +516,7 @@ fn internalize_choice_instr_at(
}
}
}
Instruction::DynamicInternalElse(_, _, ref mut o @ NextOrFail::Next(0)) => {
Instruction::DynamicInternalElse(_, _, o @ NextOrFail::Next(0)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(instr_loc, 0));
*o = NextOrFail::Fail(0);
}
@@ -564,16 +564,14 @@ fn thread_choice_instr_at_to(
) {
loop {
match &mut code[instr_loc] {
Instruction::TryMeElse(ref mut o) | Instruction::RetryMeElse(ref mut o)
if target_loc >= instr_loc =>
{
Instruction::TryMeElse(o) | Instruction::RetryMeElse(o) if target_loc >= instr_loc => {
retraction_info.push_record(RetractionRecord::ReplacedChoiceOffset(instr_loc, *o));
*o = target_loc - instr_loc;
return;
}
Instruction::DynamicElse(_, _, NextOrFail::Next(ref mut o))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Next(ref mut o))
Instruction::DynamicElse(_, _, NextOrFail::Next(o))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Next(o))
if target_loc >= instr_loc =>
{
retraction_info
@@ -588,7 +586,7 @@ fn thread_choice_instr_at_to(
Instruction::TryMeElse(o) | Instruction::RetryMeElse(o) => {
instr_loc += *o;
}
Instruction::RevJmpBy(ref mut o) if instr_loc >= target_loc => {
Instruction::RevJmpBy(o) if instr_loc >= target_loc => {
retraction_info.push_record(RetractionRecord::ModifiedRevJmpBy(instr_loc, *o));
*o = instr_loc - target_loc;
@@ -631,7 +629,7 @@ fn thread_choice_instr_at_to(
Instruction::DynamicInternalElse(_, _, NextOrFail::Fail(o)) if *o > 0 => {
instr_loc += *o;
}
Instruction::TrustMe(ref mut o) if target_loc >= instr_loc => {
Instruction::TrustMe(o) if target_loc >= instr_loc => {
retraction_info.push_record(
RetractionRecord::AppendedTrustMe(instr_loc, *o, false),
//choice_instr.is_default()),
@@ -657,7 +655,7 @@ fn remove_non_leading_clause(
retraction_info: &mut RetractionInfo,
) -> Option<IndexPtr> {
match &mut code[non_indexed_choice_instr_loc] {
Instruction::RetryMeElse(ref mut o) => {
Instruction::RetryMeElse(o) => {
let o = *o;
thread_choice_instr_at_to(
@@ -680,7 +678,7 @@ fn remove_non_leading_clause(
None
}
Instruction::TryMeElse(ref mut o) => {
Instruction::TryMeElse(o) => {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
preceding_choice_instr_loc,
*o,
@@ -700,31 +698,25 @@ fn remove_non_leading_clause(
}
}
fn finalize_retract(
fn finalize_retract<'a, LS: LoadState<'a>>(
payload: &mut <LS as LoadState<'a>>::LoaderFieldType,
key: PredicateKey,
compilation_target: CompilationTarget,
skeleton: &mut PredicateSkeleton,
code_index: CodeIndex,
target_pos: usize,
index_ptr_opt: Option<IndexPtr>,
retraction_info: &mut RetractionInfo,
) -> usize {
let clause_clause_loc = delete_from_skeleton(
compilation_target,
key,
skeleton,
target_pos,
retraction_info,
&mut payload.retraction_info,
);
if let Some(index_ptr) = index_ptr_opt {
set_code_index(
retraction_info,
&compilation_target,
key,
code_index,
index_ptr,
);
set_code_index::<LS>(payload, &compilation_target, key, code_index, index_ptr);
}
clause_clause_loc
@@ -736,7 +728,7 @@ fn remove_leading_unindexed_clause(
retraction_info: &mut RetractionInfo,
) -> Option<IndexPtr> {
match &mut code[non_indexed_choice_instr_loc] {
Instruction::TryMeElse(ref mut o) => {
Instruction::TryMeElse(o) => {
if *o > 0 {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
non_indexed_choice_instr_loc,
@@ -814,7 +806,7 @@ fn prepend_compiled_clause(
skeleton.clauses[0].clause_start = clause_loc + 2;
retraction_info.push_record(RetractionRecord::AddedIndex(
skeleton.clauses[0].opt_arg_index_key.clone(),
skeleton.clauses[0].opt_arg_index_key,
skeleton.clauses[0].clause_start,
));
@@ -941,10 +933,10 @@ fn prepend_compiled_clause(
let prepend_queue_len = prepend_queue.len();
match &mut prepend_queue[1] {
Instruction::TryMeElse(ref mut o) if *o == 0 => {
Instruction::TryMeElse(o) if *o == 0 => {
*o = prepend_queue_len - 2;
}
Instruction::DynamicInternalElse(_, _, ref mut o @ NextOrFail::Next(0)) => {
Instruction::DynamicInternalElse(_, _, o @ NextOrFail::Next(0)) => {
*o = NextOrFail::Fail(prepend_queue_len - 2);
}
_ => {
@@ -1076,7 +1068,7 @@ fn append_compiled_clause(
skeleton.clauses[target_pos].opt_arg_index_key += index_loc - 1;
retraction_info.push_record(RetractionRecord::AddedIndex(
skeleton.clauses[target_pos].opt_arg_index_key.clone(),
skeleton.clauses[target_pos].opt_arg_index_key,
skeleton.clauses[target_pos].clause_start,
));
@@ -1194,11 +1186,8 @@ fn print_overwrite_warning(
key: PredicateKey,
is_dynamic: bool,
) {
if let CompilationTarget::Module(module_name) = compilation_target {
match module_name {
atom!("builtins") | atom!("loader") => return,
_ => {}
}
if let CompilationTarget::Module(atom!("builtins") | atom!("loader")) = compilation_target {
return;
}
match code_ptr.tag() {
@@ -1240,12 +1229,9 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
settings: CodeGenSettings,
) -> Result<StandaloneCompileResult, SessionError> {
let mut preprocessor = Preprocessor::new(settings);
let clause = preprocessor.try_term_to_tl(self, term)?;
let clause = self.try_term_to_tl(term, &mut preprocessor)?;
// let queue = preprocessor.parse_queue(self)?;
let mut cg = CodeGenerator::new(&LS::machine_st(&mut self.payload).atom_tbl, settings);
let mut cg = CodeGenerator::new(settings);
let clause_code = cg.compile_predicate(vec![clause])?;
Ok(StandaloneCompileResult {
@@ -1260,7 +1246,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
mut predicates: PredicateQueue,
settings: CodeGenSettings,
) -> Result<CodeIndex, SessionError> {
let code_index = self.get_or_insert_code_index(key, predicates.compilation_target);
let code_idx = self.get_or_insert_code_index(key, predicates.compilation_target);
LS::err_on_builtin_overwrite(self, key)?;
@@ -1271,11 +1257,10 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
let mut preprocessor = Preprocessor::new(settings);
for term in predicates.predicates.drain(0..) {
clauses.push(self.try_term_to_tl(term, &mut preprocessor)?);
clauses.push(preprocessor.try_term_to_tl(self, term)?);
}
let mut cg = CodeGenerator::new(&LS::machine_st(&mut self.payload).atom_tbl, settings);
let mut cg = CodeGenerator::new(settings);
let mut code = cg.compile_predicate(clauses)?;
if settings.is_extensible {
@@ -1334,9 +1319,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
);
}
let index_ptr = LS::machine_st(&mut self.payload)
.arena
.code_index_tbl
.get_entry(code_idx.into());
print_overwrite_warning(
&predicates.compilation_target,
code_index.get(),
index_ptr,
key,
settings.is_dynamic(),
);
@@ -1347,16 +1337,16 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
IndexPtr::index(code_ptr)
};
set_code_index(
&mut self.payload.retraction_info,
set_code_index::<LS>(
&mut self.payload,
&predicates.compilation_target,
key,
code_index,
code_idx,
index_ptr,
);
self.wam_prelude.code.extend(code);
Ok(code_index)
Ok(code_idx)
}
fn extend_local_predicate_skeleton(
@@ -1558,19 +1548,19 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
self.push_back_to_local_predicate_skeleton(&compilation_target, &key, code_len);
let code_index = self.get_or_insert_code_index(key, compilation_target);
let code_idx = self.get_or_insert_code_index(key, compilation_target);
if let Some(new_code_ptr) = result {
set_code_index(
&mut self.payload.retraction_info,
set_code_index::<LS>(
&mut self.payload,
&compilation_target,
key,
code_index,
code_idx,
new_code_ptr,
);
}
Ok(code_index)
Ok(code_idx)
}
AppendOrPrepend::Prepend => {
let clause_index_info = standalone_skeleton.clauses.pop_back().unwrap();
@@ -1600,17 +1590,17 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
self.push_front_to_local_predicate_skeleton(&compilation_target, &key, code_len);
let code_index = self.get_or_insert_code_index(key, compilation_target);
let code_idx = self.get_or_insert_code_index(key, compilation_target);
set_code_index(
&mut self.payload.retraction_info,
set_code_index::<LS>(
&mut self.payload,
&compilation_target,
key,
code_index,
code_idx,
new_code_ptr,
);
Ok(code_index)
Ok(code_idx)
}
}
}
@@ -1640,8 +1630,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
};
match &mut self.wam_prelude.code[clause_loc] {
Instruction::DynamicElse(_, ref mut d, _)
| Instruction::DynamicInternalElse(_, ref mut d, _) => {
Instruction::DynamicElse(_, d, _) | Instruction::DynamicInternalElse(_, d, _) => {
*d = Death::Finite(LS::machine_st(&mut self.payload).global_clock);
}
_ => unreachable!(),
@@ -1658,7 +1647,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
pub(super) fn retract_clause(&mut self, key: PredicateKey, target_pos: usize) -> usize {
let payload_compilation_target = self.payload.compilation_target;
let code_index = self.get_or_insert_code_index(key, payload_compilation_target);
let code_idx_offset = self.get_or_insert_code_index(key, payload_compilation_target);
let skeleton = self
.wam_prelude
@@ -1736,14 +1725,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
None
};
return finalize_retract(
return finalize_retract::<LS>(
&mut self.payload,
key,
payload_compilation_target,
skeleton,
code_index,
code_idx_offset,
target_pos,
index_ptr_opt,
&mut self.payload.retraction_info,
);
}
None => {
@@ -1765,14 +1754,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
)
};
return finalize_retract(
return finalize_retract::<LS>(
&mut self.payload,
key,
payload_compilation_target,
skeleton,
code_index,
code_idx_offset,
target_pos,
index_ptr_opt,
&mut self.payload.retraction_info,
);
}
}
@@ -1995,14 +1984,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
}
};
finalize_retract(
finalize_retract::<LS>(
&mut self.payload,
key,
payload_compilation_target,
skeleton,
code_index,
code_idx_offset,
target_pos,
index_ptr_opt,
&mut self.payload.retraction_info,
)
}
}
@@ -2229,18 +2218,22 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
};
let predicates = self.payload.predicates.take();
let code_index = self.compile(key, predicates, settings)?;
let offset = self.compile(key, predicates, settings)?;
if let Some(filename) = self.listing_src_file_name() {
if let Some(ref mut module) = self.wam_prelude.indices.modules.get_mut(&filename) {
let index_ptr = code_index.get();
let code_index = *module.code_dir.entry(key).or_insert(code_index);
let index_ptr = LS::machine_st(&mut self.payload)
.arena
.code_index_tbl
.get_entry(offset.into());
set_code_index(
&mut self.payload.retraction_info,
let offset = *module.code_dir.entry(key).or_insert(offset);
set_code_index::<LS>(
&mut self.payload,
&CompilationTarget::Module(filename),
key,
code_index,
offset,
index_ptr,
);
}

View File

@@ -1,32 +1,368 @@
pub struct MachineConfig {
pub streams: StreamConfig,
pub toplevel: &'static str,
}
use std::borrow::Cow;
use std::io::Write;
use std::sync::mpsc::{Receiver, Sender, channel};
pub enum StreamConfig {
Stdio,
use rand::{SeedableRng, rngs::StdRng};
use crate::Machine;
use super::{
Arena, Atom, Callback, CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState,
Stream, bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module,
};
#[derive(Default)]
enum OutputStreamConfigInner {
#[default]
Memory,
Stdout,
Stderr,
Callback(Callback),
}
impl Default for MachineConfig {
impl std::fmt::Debug for OutputStreamConfigInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Memory => write!(f, "Memory"),
Self::Stdout => write!(f, "Stdout"),
Self::Stderr => write!(f, "Stderr"),
Self::Callback(_) => f.debug_tuple("Callback").field(&"<callback>").finish(),
}
}
}
/// Configuration for an output stream.
#[derive(Debug, Default)]
pub struct OutputStreamConfig {
inner: OutputStreamConfigInner,
}
impl OutputStreamConfig {
/// Sends output to stdout.
pub fn stdout() -> Self {
Self {
inner: OutputStreamConfigInner::Stdout,
}
}
/// Sends output to stderr.
pub fn stderr() -> Self {
Self {
inner: OutputStreamConfigInner::Stderr,
}
}
/// Keeps output in a memory buffer.
pub fn memory() -> Self {
Self {
inner: OutputStreamConfigInner::Memory,
}
}
/// Calls a callback with the output whenever the stream is written to.
pub fn callback(callback: Callback) -> Self {
Self {
inner: OutputStreamConfigInner::Callback(callback),
}
}
fn into_stream(self, arena: &mut Arena) -> Stream {
match self.inner {
OutputStreamConfigInner::Memory => Stream::from_owned_string("".to_owned(), arena),
OutputStreamConfigInner::Stdout => Stream::stdout(arena),
OutputStreamConfigInner::Stderr => Stream::stderr(arena),
OutputStreamConfigInner::Callback(callback) => Stream::from_callback(callback, arena),
}
}
}
#[derive(Debug)]
enum InputStreamConfigInner {
String(Cow<'static, str>),
Stdin,
Channel(Receiver<Vec<u8>>),
}
impl Default for InputStreamConfigInner {
fn default() -> Self {
MachineConfig {
streams: StreamConfig::Stdio,
toplevel: include_str!("../toplevel.pl"),
Self::String("".into())
}
}
/// Configuration for an input stream;
#[derive(Debug, Default)]
pub struct InputStreamConfig {
inner: InputStreamConfigInner,
}
impl InputStreamConfig {
/// Gets input from string.
pub fn string(s: impl Into<Cow<'static, str>>) -> Self {
Self {
inner: InputStreamConfigInner::String(s.into()),
}
}
/// Gets input from stdin.
pub fn stdin() -> Self {
Self {
inner: InputStreamConfigInner::Stdin,
}
}
/// Connects the input to the receiving end of a channel.
pub fn channel() -> (UserInput, Self) {
let (sender, receiver) = channel();
(
UserInput { inner: sender },
Self {
inner: InputStreamConfigInner::Channel(receiver),
},
)
}
fn into_stream(self, arena: &mut Arena, add_history: bool) -> Stream {
match self.inner {
InputStreamConfigInner::String(s) => match s {
Cow::Owned(s) => Stream::from_owned_string(s, arena),
Cow::Borrowed(s) => Stream::from_static_string(s, arena),
},
InputStreamConfigInner::Stdin => Stream::stdin(arena, add_history),
InputStreamConfigInner::Channel(channel) => Stream::input_channel(channel, arena),
}
}
}
impl MachineConfig {
pub fn in_memory() -> Self {
MachineConfig {
streams: StreamConfig::Memory,
..Default::default()
/// Describes how the streams of a [`Machine`](crate::Machine) will be handled.
pub struct StreamConfig {
user_input: InputStreamConfig,
user_output: OutputStreamConfig,
user_error: OutputStreamConfig,
}
impl Default for StreamConfig {
fn default() -> Self {
Self::in_memory()
}
}
impl StreamConfig {
/// Binds the input, output and error streams to stdin, stdout and stderr.
pub fn stdio() -> Self {
StreamConfig {
user_input: InputStreamConfig::stdin(),
user_output: OutputStreamConfig::stdout(),
user_error: OutputStreamConfig::stderr(),
}
}
pub fn with_toplevel(mut self, toplevel: &'static str) -> Self {
self.toplevel = toplevel;
/// Binds the output and error streams to memory buffers and has an empty input.
pub fn in_memory() -> Self {
StreamConfig {
user_input: InputStreamConfig::string(String::new()),
user_output: OutputStreamConfig::memory(),
user_error: OutputStreamConfig::memory(),
}
}
/// Calls the given callbacks when the respective streams are written to.
///
/// This also returns a handler to the stdin of the [`Machine`](crate::Machine).
pub fn from_callbacks(stdout: Option<Callback>, stderr: Option<Callback>) -> (UserInput, Self) {
let (user_input, channel_stream) = InputStreamConfig::channel();
(
user_input,
StreamConfig {
user_input: channel_stream,
user_output: stdout
.map_or_else(OutputStreamConfig::memory, OutputStreamConfig::callback),
user_error: stderr
.map_or_else(OutputStreamConfig::memory, OutputStreamConfig::callback),
},
)
}
/// Configures the `user_input` stream.
pub fn with_user_input(self, user_input: InputStreamConfig) -> Self {
Self { user_input, ..self }
}
/// Configures the `user_output` stream.
pub fn with_user_output(self, user_output: OutputStreamConfig) -> Self {
Self {
user_output,
..self
}
}
/// Configures the `user_error` stream.
pub fn with_user_error(self, user_error: OutputStreamConfig) -> Self {
Self { user_error, ..self }
}
fn into_streams(self, arena: &mut Arena, add_history: bool) -> (Stream, Stream, Stream) {
(
self.user_input.into_stream(arena, add_history),
self.user_output.into_stream(arena),
self.user_error.into_stream(arena),
)
}
}
/// A handler for the stdin of the [`Machine`](crate::Machine).
#[derive(Debug)]
pub struct UserInput {
inner: Sender<Vec<u8>>,
}
impl Write for UserInput {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner
.send(buf.into())
.map(|_| buf.len())
.map_err(|_| std::io::ErrorKind::BrokenPipe.into())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
/// Describes how a [`Machine`](crate::Machine) will be configured.
pub struct MachineBuilder {
pub(crate) streams: StreamConfig,
pub(crate) toplevel: Cow<'static, str>,
}
impl Default for MachineBuilder {
/// Defaults to using in-memory streams.
fn default() -> Self {
MachineBuilder {
streams: Default::default(),
toplevel: default_toplevel().into(),
}
}
}
impl MachineBuilder {
/// Creates a default configuration.
pub fn new() -> Self {
Default::default()
}
/// Uses the given `crate::StreamConfig` in this configuration.
pub fn with_streams(mut self, streams: StreamConfig) -> Self {
self.streams = streams;
self
}
/// Uses the given toplevel in this configuration.
pub fn with_toplevel(mut self, toplevel: impl Into<Cow<'static, str>>) -> Self {
self.toplevel = toplevel.into();
self
}
/// Builds the [`Machine`](crate::Machine) from this configuration.
pub fn build(self) -> Machine {
let args = MachineArgs::new();
let mut machine_st = MachineState::new();
let (user_input, user_output, user_error) = self
.streams
.into_streams(&mut machine_st.arena, args.add_history);
let mut wam = Machine {
machine_st,
indices: IndexStore::new(),
code: vec![],
user_input,
user_output,
user_error,
load_contexts: vec![],
#[cfg(feature = "ffi")]
foreign_function_table: Default::default(),
rng: StdRng::from_entropy(),
};
let mut lib_path = current_dir();
lib_path.pop();
lib_path.push("lib");
wam.add_impls_to_indices();
bootstrapping_compile(
Stream::from_static_string(
libraries::get("ops_and_meta_predicates")
.expect("library ops_and_meta_predicates should exist"),
&mut wam.machine_st.arena,
),
&mut wam,
ListingSource::from_file_and_path(
atom!("ops_and_meta_predicates.pl"),
lib_path.clone(),
),
)
.unwrap();
bootstrapping_compile(
Stream::from_static_string(
libraries::get("builtins").expect("library builtins should exist"),
&mut wam.machine_st.arena,
),
&mut wam,
ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()),
)
.unwrap();
if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) {
load_module(
&mut wam.machine_st,
&mut wam.indices.code_dir,
&mut wam.indices.op_dir,
&mut wam.indices.meta_predicates,
&CompilationTarget::User,
builtins,
);
import_builtin_impls(&wam.indices.code_dir, builtins);
} else {
unreachable!()
}
lib_path.pop(); // remove the "lib" at the end
bootstrapping_compile(
Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena),
&mut wam,
ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()),
)
.unwrap();
wam.configure_modules();
if let Some(loader) = wam.indices.modules.get(&atom!("loader")) {
load_module(
&mut wam.machine_st,
&mut wam.indices.code_dir,
&mut wam.indices.op_dir,
&mut wam.indices.meta_predicates,
&CompilationTarget::User,
loader,
);
} else {
unreachable!()
}
wam.load_special_forms();
wam.load_top_level(self.toplevel);
wam.configure_streams();
wam
}
}
/// Returns a static string slice to the default toplevel
pub fn default_toplevel() -> &'static str {
include_str!("../toplevel.pl")
}

View File

@@ -1,12 +1,71 @@
use fxhash::FxBuildHasher;
use indexmap::IndexSet;
use crate::atom_table::*;
use crate::machine::get_structure_index;
use crate::machine::heap::*;
use crate::machine::stack::*;
use crate::types::*;
use std::mem;
use std::ops::IndexMut;
use modular_bitfield::specifiers::*;
use modular_bitfield::*;
type Trail = Vec<(Ref, HeapCellValue)>;
use std::collections::BTreeMap;
use std::mem;
use std::ops::{IndexMut, Range};
#[derive(Specifier, Copy, Clone, Debug)]
#[bits = 6]
enum TrailRefTag {
HeapCell = 0b001011,
StackCell = 0b001101,
AttrVar = 0b010001,
PStrLoc = 0b001111,
}
#[bitfield]
#[repr(u64)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
struct TrailRef {
val: B56,
#[allow(unused)]
m: bool,
#[allow(unused)]
f: bool,
tag: TrailRefTag,
}
impl TrailRef {
#[inline(always)]
fn heap_cell(h: usize) -> Self {
TrailRef::new()
.with_tag(TrailRefTag::HeapCell)
.with_val(h as u64)
}
#[inline(always)]
fn stack_cell(h: usize) -> Self {
TrailRef::new()
.with_tag(TrailRefTag::StackCell)
.with_val(h as u64)
}
#[inline(always)]
fn attr_var(h: usize) -> Self {
TrailRef::new()
.with_tag(TrailRefTag::AttrVar)
.with_val(h as u64)
}
#[inline(always)]
fn pstr_loc(h: usize) -> Self {
TrailRef::new()
.with_tag(TrailRefTag::PStrLoc)
.with_val(h as u64)
}
}
type Trail = Vec<(TrailRef, HeapCellValue)>;
#[derive(Debug, Clone, Copy)]
pub enum AttrVarPolicy {
@@ -17,22 +76,39 @@ pub enum AttrVarPolicy {
pub trait CopierTarget: IndexMut<usize, Output = HeapCellValue> {
fn store(&self, value: HeapCellValue) -> HeapCellValue;
fn deref(&self, value: HeapCellValue) -> HeapCellValue;
fn push(&mut self, value: HeapCellValue);
fn push_attr_var_queue(&mut self, attr_var_loc: usize);
fn stack(&mut self) -> &mut Stack;
fn threshold(&self) -> usize;
// returns the tail location of the pstr on success
fn as_slice_from<'a>(&'a self, from: usize) -> Box<dyn Iterator<Item = u8> + 'a>;
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, AllocError>;
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, AllocError>;
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), AllocError>;
}
pub(crate) fn copy_term<T: CopierTarget>(
target: T,
addr: HeapCellValue,
attr_var_policy: AttrVarPolicy,
) {
) -> Result<usize, AllocError> {
let mut copy_term_state = CopyTermState::new(target, attr_var_policy);
let old_threshold = copy_term_state.target.threshold();
copy_term_state.copy_term_impl(addr);
copy_term_state.copy_attr_var_lists();
copy_term_state.copy_term_impl(addr)?;
copy_term_state.copy_attr_var_lists()?;
copy_term_state.unwind_trail();
let new_threshold = copy_term_state.target.threshold();
copy_term_state.copy_pstrs()?;
Ok(new_threshold - old_threshold)
}
#[derive(Debug)]
pub struct PStrData {
pre_old_h_tail_loc: usize,
post_old_h_tail_loc: usize,
post_old_h_pstr_loc_locs: IndexSet<usize, FxBuildHasher>,
}
#[derive(Debug)]
@@ -43,6 +119,9 @@ struct CopyTermState<T: CopierTarget> {
target: T,
attr_var_policy: AttrVarPolicy,
attr_var_list_locs: Vec<(usize, HeapCellValue)>,
// keys of pstr_loc_locs are byte indices rounded down to the
// nearest cell boundary
pstr_loc_locs: BTreeMap<usize, PStrData>,
}
impl<T: CopierTarget> CopyTermState<T> {
@@ -54,6 +133,7 @@ impl<T: CopierTarget> CopyTermState<T> {
target,
attr_var_policy,
attr_var_list_locs: vec![],
pstr_loc_locs: BTreeMap::new(),
}
}
@@ -64,17 +144,17 @@ impl<T: CopierTarget> CopyTermState<T> {
fn trail_list_cell(&mut self, addr: usize, threshold: usize) {
let trail_item = mem::replace(&mut self.target[addr], list_loc_as_cell!(threshold));
self.trail.push((Ref::heap_cell(addr), trail_item));
self.trail.push((TrailRef::heap_cell(addr), trail_item));
}
fn copy_list(&mut self, addr: usize) {
fn copy_list(&mut self, addr: usize) -> Result<(), AllocError> {
for offset in 0..2 {
read_heap_cell!(self.target[addr + offset],
(HeapCellValueTag::Lis, h) => {
if h >= self.old_h {
*self.value_at_scan() = list_loc_as_cell!(h);
self.scan += 1;
return;
return Ok(());
}
}
_ => {
@@ -83,14 +163,10 @@ impl<T: CopierTarget> CopyTermState<T> {
}
let threshold = self.target.threshold();
self.target.copy_slice_to_end(addr..addr + 2)?;
*self.value_at_scan() = list_loc_as_cell!(threshold);
for i in 0..2 {
let hcv = self.target[addr + i];
self.target.push(hcv);
}
let cdr = self
.target
.store(self.target.deref(heap_loc_as_cell!(addr + 1)));
@@ -113,80 +189,109 @@ impl<T: CopierTarget> CopyTermState<T> {
}
self.scan += 1;
Ok(())
}
fn copy_partial_string(&mut self, scan_tag: HeapCellValueTag, pstr_loc: usize) {
read_heap_cell!(self.target[pstr_loc],
(HeapCellValueTag::PStrLoc, h) => {
debug_assert!(h >= self.old_h);
*self.value_at_scan() = match scan_tag {
HeapCellValueTag::PStrLoc => {
pstr_loc_as_cell!(h)
}
tag => {
debug_assert_eq!(tag, HeapCellValueTag::PStrOffset);
pstr_offset_as_cell!(h)
}
};
fn copy_partial_string(&mut self, pstr_loc: usize) -> Result<(), AllocError> {
match self.pstr_loc_locs.range_mut(..=pstr_loc).next_back() {
Some((
_prev_pstr_loc,
&mut PStrData {
pre_old_h_tail_loc,
ref mut post_old_h_pstr_loc_locs,
..
},
)) if pre_old_h_tail_loc >= cell_index!(pstr_loc) => {
post_old_h_pstr_loc_locs.insert(self.scan);
self.scan += 1;
return;
}
(HeapCellValueTag::Var, h) => {
debug_assert!(h >= self.old_h);
debug_assert_eq!(scan_tag, HeapCellValueTag::PStrOffset);
*self.value_at_scan() = pstr_offset_as_cell!(h);
self.scan += 1;
return;
return Ok(());
}
_ => {}
);
}
let threshold = self.target.threshold();
let offset = self
.target
.as_slice_from(pstr_loc)
.take_while(|b| *b != 0u8)
.count();
let replacement = read_heap_cell!(self.target[pstr_loc],
(HeapCellValueTag::CStr) => {
debug_assert_eq!(scan_tag, HeapCellValueTag::PStrOffset);
let left_pstr_boundary = cell_index!(pstr_loc + offset);
let flag = u64::from_be_bytes(self.target[left_pstr_boundary].into_bytes());
let pstr_loc_idx = cell_index!(pstr_loc);
*self.value_at_scan() = pstr_offset_as_cell!(threshold);
self.target.push(self.target[pstr_loc]);
if flag == 1 {
if left_pstr_boundary != pstr_loc_idx {
let mut pstr_data = self
.pstr_loc_locs
.remove(&heap_index!(left_pstr_boundary))
.unwrap();
heap_loc_as_cell!(threshold)
pstr_data.post_old_h_pstr_loc_locs.insert(self.scan);
self.pstr_loc_locs
.insert(heap_index!(cell_index!(pstr_loc)), pstr_data);
let old_cell = self.target[pstr_loc_idx];
self.target[pstr_loc_idx] = HeapCellValue::from_bytes(u64::to_be_bytes(1));
self.trail
.push((TrailRef::pstr_loc(pstr_loc_idx), old_cell));
} else {
let pstr_data = self
.pstr_loc_locs
.get_mut(&heap_index!(left_pstr_boundary))
.unwrap();
pstr_data.post_old_h_pstr_loc_locs.insert(self.scan);
}
_ => {
*self.value_at_scan() = if scan_tag == HeapCellValueTag::PStrLoc {
pstr_loc_as_cell!(threshold)
} else {
let old_cell = self.target[pstr_loc_idx];
self.target[pstr_loc_idx] = HeapCellValue::from_bytes(u64::to_be_bytes(1));
self.trail
.push((TrailRef::pstr_loc(pstr_loc_idx), old_cell));
let old_tail_idx =
if (pstr_loc + offset + 1).is_multiple_of(Heap::heap_cell_alignment()) {
cell_index!(pstr_loc + offset) + 2
} else {
debug_assert_eq!(scan_tag, HeapCellValueTag::PStrOffset);
pstr_offset_as_cell!(threshold)
cell_index!(pstr_loc + offset) + 1
};
self.target.push(self.target[pstr_loc]);
self.target.push(self.target[pstr_loc + 1]);
let tail_cell = self.target[old_tail_idx];
pstr_loc_as_cell!(threshold)
}
);
let new_tail_idx = self.target.threshold();
let mut writer = self.target.reserve(1)?;
writer.write_with(|section| {
section.push_cell(tail_cell);
});
let mut post_old_h_pstr_loc_locs = IndexSet::with_hasher(FxBuildHasher::default());
post_old_h_pstr_loc_locs.insert(self.scan);
let pstr_data = PStrData {
pre_old_h_tail_loc: old_tail_idx,
post_old_h_tail_loc: new_tail_idx,
post_old_h_pstr_loc_locs,
};
self.pstr_loc_locs
.insert(heap_index!(pstr_loc_idx), pstr_data);
}
self.scan += 1;
let trail_item = mem::replace(&mut self.target[pstr_loc], replacement);
self.trail.push((Ref::heap_cell(pstr_loc), trail_item));
Ok(())
}
fn copy_attr_var_lists(&mut self) {
fn copy_attr_var_lists(&mut self) -> Result<(), AllocError> {
while !self.attr_var_list_locs.is_empty() {
let iter = std::mem::take(&mut self.attr_var_list_locs);
let mut list_loc_vec = std::mem::take(&mut self.attr_var_list_locs);
for (threshold, list_loc) in iter {
while let Some((threshold, list_loc)) = list_loc_vec.pop() {
self.target[threshold] = list_loc_as_cell!(self.target.threshold());
self.target.push_attr_var_queue(threshold - 1);
self.copy_attr_var_list(list_loc);
self.copy_attr_var_list(list_loc)?;
}
}
Ok(())
}
/*
@@ -194,48 +299,53 @@ impl<T: CopierTarget> CopyTermState<T> {
* structure which is ensured by this function and not at all by
* the vanilla copier.
*/
fn copy_attr_var_list(&mut self, mut list_addr: HeapCellValue) {
fn copy_attr_var_list(&mut self, mut list_addr: HeapCellValue) -> Result<(), AllocError> {
while let HeapCellValueTag::Lis = list_addr.get_tag() {
let threshold = self.target.threshold();
let heap_loc = list_addr.get_value() as usize;
let str_loc = self.target[heap_loc].get_value() as usize;
let str_cell = self.target[str_loc];
let mut writer = self.target.reserve(3)?;
self.target.push(heap_loc_as_cell!(threshold + 2));
self.target.push(heap_loc_as_cell!(threshold + 1));
writer.write_with(|section| {
section.push_cell(heap_loc_as_cell!(threshold + 2));
section.push_cell(heap_loc_as_cell!(threshold + 1));
read_heap_cell!(self.target[str_loc],
(HeapCellValueTag::Atom) => {
self.target.push(self.target[str_loc]);
if str_cell.to_atom().is_some() {
section.push_cell(str_cell);
}
(HeapCellValueTag::Str) => {
self.copy_term_impl(self.target[str_loc]);
}
_ => {
unreachable!();
}
);
});
debug_assert_eq!(str_cell.get_tag(), HeapCellValueTag::Str);
self.copy_term_impl(str_cell)?;
list_addr = self.target[heap_loc + 1];
if HeapCellValueTag::Lis == list_addr.get_tag() {
self.target[threshold + 1] = list_loc_as_cell!(self.target.threshold());
}
}
Ok(())
}
fn reinstantiate_var(&mut self, addr: HeapCellValue, frontier: usize) {
fn reinstantiate_var(
&mut self,
addr: HeapCellValue,
frontier: usize,
) -> Result<(), AllocError> {
read_heap_cell!(addr,
(HeapCellValueTag::Var, h) => {
self.target[frontier] = heap_loc_as_cell!(frontier);
self.target[h] = heap_loc_as_cell!(frontier);
self.trail.push((Ref::heap_cell(h), heap_loc_as_cell!(h)));
self.trail.push((TrailRef::heap_cell(h), heap_loc_as_cell!(h)));
}
(HeapCellValueTag::StackVar, s) => {
self.target[frontier] = heap_loc_as_cell!(frontier);
self.target.stack()[s] = heap_loc_as_cell!(frontier);
self.trail.push((Ref::stack_cell(s), stack_loc_as_cell!(s)));
self.trail.push((TrailRef::stack_cell(s), stack_loc_as_cell!(s)));
}
(HeapCellValueTag::AttrVar, h) => {
let threshold = if let AttrVarPolicy::DeepCopy = self.attr_var_policy {
@@ -247,14 +357,18 @@ impl<T: CopierTarget> CopyTermState<T> {
self.target[frontier] = heap_loc_as_cell!(threshold);
self.target[h] = heap_loc_as_cell!(threshold);
self.trail.push((Ref::attr_var(h), attr_var_as_cell!(h)));
self.trail.push((TrailRef::attr_var(h), attr_var_as_cell!(h)));
if let AttrVarPolicy::DeepCopy = self.attr_var_policy {
self.target.push(attr_var_as_cell!(threshold));
self.target.push(heap_loc_as_cell!(threshold + 1));
let mut writer = self.target.reserve(2)?;
writer.write_with(|section| {
section.push_cell(attr_var_as_cell!(threshold));
section.push_cell(heap_loc_as_cell!(threshold + 1));
});
let old_list_link = self.target[h + 1];
self.trail.push((Ref::heap_cell(h + 1), old_list_link));
self.trail.push((TrailRef::heap_cell(h + 1), old_list_link));
self.target[h + 1] = heap_loc_as_cell!(threshold + 1);
if old_list_link.get_tag() == HeapCellValueTag::Lis {
@@ -266,10 +380,11 @@ impl<T: CopierTarget> CopyTermState<T> {
unreachable!()
}
);
Ok(())
}
fn copy_var(&mut self, addr: HeapCellValue) {
let index = addr.get_value() as usize;
fn copy_var(&mut self, addr: HeapCellValue) -> Result<(), AllocError> {
let rd = self.target.deref(addr);
let ra = self.target.store(rd);
@@ -278,10 +393,11 @@ impl<T: CopierTarget> CopyTermState<T> {
if h >= self.old_h {
*self.value_at_scan() = ra;
self.scan += 1;
return;
return Ok(());
}
}
(HeapCellValueTag::Lis, h) => {
let index = rd.get_value() as usize;
if h >= self.old_h && self.target[index].get_mark_bit() {
*self.value_at_scan() = heap_loc_as_cell!(
if ra.get_forwarding_bit() {
@@ -292,47 +408,52 @@ impl<T: CopierTarget> CopyTermState<T> {
);
self.scan += 1;
return;
return Ok(());
}
}
_ => {}
);
if rd == ra {
self.reinstantiate_var(ra, self.scan);
self.reinstantiate_var(ra, self.scan)?;
self.scan += 1;
} else {
*self.value_at_scan() = ra;
}
Ok(())
}
fn copy_structure(&mut self, addr: usize) {
fn copy_structure(&mut self, addr: usize) -> Result<(), AllocError> {
read_heap_cell!(self.target[addr],
(HeapCellValueTag::Atom, (name, arity)) => {
(HeapCellValueTag::Atom, (_name, arity)) => {
let threshold = self.target.threshold();
*self.value_at_scan() = str_loc_as_cell!(threshold);
let index_cell = self.target[addr.saturating_sub(1)];
let str_cell = if get_structure_index(index_cell).is_some() {
// copy the index pointer trailing this
// inlined or expanded goal.
let mut writer = self.target.reserve(1)?;
writer.write_with(|section| {
section.push_cell(index_cell);
});
str_loc_as_cell!(threshold + 1)
} else {
str_loc_as_cell!(threshold)
};
*self.value_at_scan() = str_cell;
self.target.copy_slice_to_end(addr .. addr + 1 + arity)?;
let trail_item = mem::replace(
&mut self.target[addr],
str_loc_as_cell!(threshold),
str_cell,
);
self.trail.push((Ref::heap_cell(addr), trail_item));
self.target.push(atom_as_cell!(name, arity));
for i in 0..arity {
let hcv = self.target[addr + 1 + i];
self.target.push(hcv);
}
let index_cell = self.target[addr + 1 + arity];
if get_structure_index(index_cell).is_some() {
// copy the index pointer trailing this
// inlined or expanded goal.
self.target.push(index_cell);
}
self.trail.push((TrailRef::heap_cell(addr), trail_item));
}
(HeapCellValueTag::Str, h) => {
*self.value_at_scan() = str_loc_as_cell!(h);
@@ -343,11 +464,16 @@ impl<T: CopierTarget> CopyTermState<T> {
);
self.scan += 1;
Ok(())
}
fn copy_term_impl(&mut self, addr: HeapCellValue) {
fn copy_term_impl(&mut self, addr: HeapCellValue) -> Result<(), AllocError> {
self.scan = self.target.threshold();
self.target.push(addr);
let mut writer = self.target.reserve(1)?;
writer.write_with(|section| {
section.push_cell(addr);
});
while self.scan < self.target.threshold() {
let addr = *self.value_at_scan();
@@ -356,37 +482,64 @@ impl<T: CopierTarget> CopyTermState<T> {
(HeapCellValueTag::Lis, h) => {
if h >= self.old_h {
self.scan += 1;
continue;
} else {
self.copy_list(h);
self.copy_list(h)
}
}
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var) => {
self.copy_var(addr);
self.copy_var(addr)
}
(HeapCellValueTag::Str, h) => {
self.copy_structure(h);
self.copy_structure(h)
}
(HeapCellValueTag::PStrLoc | HeapCellValueTag::PStrOffset, pstr_loc) => {
self.copy_partial_string(addr.get_tag(), pstr_loc);
(HeapCellValueTag::PStrLoc, pstr_loc) => {
self.copy_partial_string(pstr_loc)
}
_ => {
self.scan += 1;
continue;
}
);
)?;
}
Ok(())
}
fn unwind_trail(mut self) {
for (r, value) in self.trail {
let index = r.get_value() as usize;
fn copy_pstrs(&mut self) -> Result<(), AllocError> {
while let Some((least_pstr_loc, pstr_data)) = self.pstr_loc_locs.pop_first() {
let threshold = heap_index!(self.target.threshold());
match r.get_tag() {
RefTag::AttrVar | RefTag::HeapCell => {
for pstr_loc_loc in pstr_data.post_old_h_pstr_loc_locs {
let pstr_loc = self.target[pstr_loc_loc].get_value() as usize;
self.target[pstr_loc_loc] =
pstr_loc_as_cell!(threshold + pstr_loc - least_pstr_loc);
}
self.target.copy_pstr_to_threshold(least_pstr_loc)?;
let mut writer = self.target.reserve(1)?;
writer.write_with(|section| {
section.push_cell(heap_loc_as_cell!(pstr_data.post_old_h_tail_loc));
});
}
Ok(())
}
fn unwind_trail(&mut self) {
for (r, value) in self.trail.drain(..) {
let index = r.val() as usize;
match r.tag() {
TrailRefTag::AttrVar | TrailRefTag::HeapCell => {
self.target[index] = value;
self.target[index].set_mark_bit(false);
self.target[index].set_forwarding_bit(false);
}
RefTag::StackCell => self.target.stack()[index] = value,
TrailRefTag::StackCell => self.target.stack()[index] = value,
TrailRefTag::PStrLoc => self.target[index] = value,
}
}
}
@@ -395,20 +548,26 @@ impl<T: CopierTarget> CopyTermState<T> {
#[cfg(test)]
mod tests {
use super::*;
use crate::functor_macro::*;
use crate::machine::mock_wam::*;
#[test]
#[cfg_attr(miri, ignore = "blocked on atom_table.rs UB")]
fn copier_tests() {
let mut wam = MockWAM::new();
// clear the heap of resource error data etc
wam.machine_st.heap.clear();
let f_atom = atom!("f");
let a_atom = atom!("a");
let b_atom = atom!("b");
wam.machine_st
.heap
.extend(functor!(f_atom, [atom(a_atom), atom(b_atom)]));
let mut functor_writer = Heap::functor_writer(functor!(
f_atom,
[atom_as_cell(a_atom), atom_as_cell(b_atom)]
));
functor_writer(&mut wam.machine_st.heap).unwrap();
assert_eq!(wam.machine_st.heap[0], atom_as_cell!(f_atom, 2));
assert_eq!(wam.machine_st.heap[1], atom_as_cell!(a_atom));
@@ -416,7 +575,7 @@ mod tests {
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy);
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
}
// check that the original heap state is still intact.
@@ -431,69 +590,266 @@ mod tests {
wam.machine_st.heap.clear();
let pstr_var_cell =
put_partial_string(&mut wam.machine_st.heap, "abc ", &wam.machine_st.atom_tbl);
let pstr_cell = wam.machine_st.heap[pstr_var_cell.get_value() as usize];
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
wam.machine_st.heap.pop();
wam.machine_st.heap.push(pstr_loc_as_cell!(2));
writer.write_with(|section| {
section.push_pstr("abc ");
section.push_cell(pstr_loc_as_cell!(heap_index!(2)));
let pstr_second_var_cell =
put_partial_string(&mut wam.machine_st.heap, "def", &wam.machine_st.atom_tbl);
let pstr_second_cell = wam.machine_st.heap[pstr_second_var_cell.get_value() as usize];
wam.machine_st.heap.pop();
wam.machine_st
.heap
.push(pstr_loc_as_cell!(wam.machine_st.heap.len() + 1));
wam.machine_st.heap.push(pstr_offset_as_cell!(0));
wam.machine_st
.heap
.push(fixnum_as_cell!(Fixnum::build_with(0i64)));
section.push_pstr("def");
section.push_cell(pstr_loc_as_cell!(0));
});
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy);
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
}
print_heap_terms(wam.machine_st.heap[6..].iter(), 6);
assert_eq!(wam.machine_st.heap[0], pstr_cell);
assert_eq!(wam.machine_st.heap[1], pstr_loc_as_cell!(2));
assert_eq!(wam.machine_st.heap[2], pstr_second_cell);
assert_eq!(wam.machine_st.heap[3], pstr_loc_as_cell!(4));
assert_eq!(wam.machine_st.heap[4], pstr_offset_as_cell!(0));
assert_eq!(wam.machine_st.heap.slice_to_str(0, "abc ".len()), "abc ");
assert_eq!(wam.machine_st.heap[1], pstr_loc_as_cell!(heap_index!(2)));
assert_eq!(
wam.machine_st.heap[5],
fixnum_as_cell!(Fixnum::build_with(0i64))
wam.machine_st
.heap
.slice_to_str(heap_index!(2), "def".len()),
"def"
);
assert_eq!(wam.machine_st.heap[3], pstr_loc_as_cell!(0));
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(heap_index!(7)));
assert_eq!(wam.machine_st.heap[5], pstr_loc_as_cell!(heap_index!(9)));
assert_eq!(wam.machine_st.heap[6], pstr_loc_as_cell!(heap_index!(7)));
assert_eq!(wam.machine_st.heap[7], pstr_cell);
assert_eq!(wam.machine_st.heap[8], pstr_loc_as_cell!(9));
assert_eq!(wam.machine_st.heap[9], pstr_second_cell);
assert_eq!(wam.machine_st.heap[10], pstr_loc_as_cell!(11));
assert_eq!(wam.machine_st.heap[11], pstr_offset_as_cell!(7));
assert_eq!(
wam.machine_st.heap[12],
fixnum_as_cell!(Fixnum::build_with(0i64))
wam.machine_st
.heap
.slice_to_str(heap_index!(7), "abc ".len()),
"abc "
);
assert_eq!(wam.machine_st.heap[8], heap_loc_as_cell!(5));
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(9), "def".len()),
"def"
);
assert_eq!(wam.machine_st.heap[10], heap_loc_as_cell!(6));
wam.machine_st.heap.clear();
wam.machine_st.heap.extend(functor!(
f_atom,
[
atom(a_atom),
atom(b_atom),
atom(a_atom),
cell(str_loc_as_cell!(0))
]
));
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
writer.write_with(|section| {
section.push_pstr("abc ");
section.push_cell(pstr_loc_as_cell!(heap_index!(2) + 9));
section.push_pstr("defdefdefdef");
section.push_cell(pstr_loc_as_cell!(0));
});
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy);
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
}
assert_eq!(wam.machine_st.heap.slice_to_str(0, "abc ".len()), "abc ");
assert_eq!(
wam.machine_st.heap[1],
pstr_loc_as_cell!(heap_index!(2) + 9)
);
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(2), "defdefdefdef".len()),
"defdefdefdef"
);
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(0));
assert_eq!(wam.machine_st.heap[5], pstr_loc_as_cell!(heap_index!(8)));
assert_eq!(
wam.machine_st.heap[6],
pstr_loc_as_cell!(heap_index!(10) + 1)
);
assert_eq!(wam.machine_st.heap[7], pstr_loc_as_cell!(heap_index!(8)));
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(8), "abc ".len()),
"abc "
);
assert_eq!(wam.machine_st.heap[9], heap_loc_as_cell!(6));
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(10), "fdef".len()),
"fdef"
);
assert_eq!(wam.machine_st.heap[11], heap_loc_as_cell!(7));
wam.machine_st.heap.clear();
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
writer.write_with(|section| {
section.push_pstr("012345678912345");
section.push_cell(pstr_loc_as_cell!(heap_index!(0)));
});
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
}
assert_eq!(
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
"012345678912345"
);
assert_eq!(wam.machine_st.heap[3], pstr_loc_as_cell!(heap_index!(0)));
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(heap_index!(6)));
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(6), "012345678912345".len()),
"012345678912345"
);
assert_eq!(wam.machine_st.heap[5], pstr_loc_as_cell!(heap_index!(6)));
wam.machine_st.heap.clear();
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
writer.write_with(|section| {
section.push_pstr("012345678912345");
section.push_cell(pstr_loc_as_cell!(heap_index!(0) + 9));
});
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
}
assert_eq!(
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
"012345678912345"
);
assert_eq!(
wam.machine_st.heap[3],
pstr_loc_as_cell!(heap_index!(0) + 9)
);
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(heap_index!(6)));
assert_eq!(
wam.machine_st.heap[5],
pstr_loc_as_cell!(heap_index!(6) + 9)
);
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(6), "012345678912345".len()),
"012345678912345"
);
assert_eq!(wam.machine_st.heap[9], heap_loc_as_cell!(5));
wam.machine_st.heap.clear();
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
writer.write_with(|section| {
section.push_pstr("012345678912345");
section.push_cell(pstr_loc_as_cell!(heap_index!(0) + 7));
});
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, pstr_loc_as_cell!(11), AttrVarPolicy::DeepCopy).unwrap();
}
assert_eq!(
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
"012345678912345"
);
assert_eq!(
wam.machine_st.heap[3],
pstr_loc_as_cell!(heap_index!(0) + 7)
);
assert_eq!(
wam.machine_st.heap[4],
pstr_loc_as_cell!(heap_index!(6) + 11)
);
assert_eq!(
wam.machine_st.heap[5],
pstr_loc_as_cell!(heap_index!(6) + 7)
);
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(6), "012345678912345".len()),
"012345678912345"
);
assert_eq!(wam.machine_st.heap[9], heap_loc_as_cell!(5));
wam.machine_st.heap.clear();
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
writer.write_with(|section| {
section.push_pstr("012345678912345");
section.push_cell(pstr_loc_as_cell!(heap_index!(0) + 12));
});
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, pstr_loc_as_cell!(11), AttrVarPolicy::DeepCopy).unwrap();
}
assert_eq!(
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
"012345678912345"
);
assert_eq!(
wam.machine_st.heap[3],
pstr_loc_as_cell!(heap_index!(0) + 12)
);
assert_eq!(
wam.machine_st.heap[4],
pstr_loc_as_cell!(heap_index!(6) + 3)
);
assert_eq!(
wam.machine_st.heap[5],
pstr_loc_as_cell!(heap_index!(6) + 4)
);
assert_eq!(
wam.machine_st
.heap
.slice_to_str(heap_index!(6), "8912345".len()),
"8912345"
);
assert_eq!(wam.machine_st.heap[8], heap_loc_as_cell!(5));
wam.machine_st.heap.clear();
let mut functor_writer = Heap::functor_writer(functor!(
f_atom,
[
atom_as_cell(a_atom),
atom_as_cell(b_atom),
atom_as_cell(a_atom),
str_loc_as_cell(0)
]
));
functor_writer(&mut wam.machine_st.heap).unwrap();
{
let wam = TermCopyingMockWAM { wam: &mut wam };
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
}
assert_eq!(wam.machine_st.heap[0], atom_as_cell!(f_atom, 4));
@@ -501,7 +857,6 @@ mod tests {
assert_eq!(wam.machine_st.heap[2], atom_as_cell!(b_atom));
assert_eq!(wam.machine_st.heap[3], atom_as_cell!(a_atom));
assert_eq!(wam.machine_st.heap[4], str_loc_as_cell!(0));
assert_eq!(wam.machine_st.heap[5], str_loc_as_cell!(6));
assert_eq!(wam.machine_st.heap[6], atom_as_cell!(f_atom, 4));
assert_eq!(wam.machine_st.heap[7], atom_as_cell!(a_atom));

View File

@@ -1,4 +1,5 @@
use crate::atom_table::*;
use crate::machine::heap::*;
use crate::types::*;
/* Use the pointer reversal technique of the Deutsch-Schorr-Waite
@@ -11,18 +12,18 @@ use crate::types::*;
* - Cells are only marked during the backward phase
* - Visiting subterms of a visited compound does not immediately shift to the backward phase
* - The heads of LIS structures are both marked and forwarded rather
* than just forwarded to distinguish them from tails;
* than just forwarded to distinguish them from tails
* continue_forwarding() checks for this before entering the forward
* phase
*
* Commonalities with the GC marking algorithm:
* - The contents of forwarded cells are modified only when they are unforwarded
* - Marked (but unforwarded!) cells immediately shift to the backward phase
*/
*/
#[derive(Debug)]
pub(crate) struct CycleDetectingIter<'a, const STOP_AT_CYCLES: bool> {
pub(crate) heap: &'a mut [HeapCellValue],
pub(crate) heap: &'a mut Heap,
start: usize,
current: usize,
next: u64,
@@ -31,7 +32,7 @@ pub(crate) struct CycleDetectingIter<'a, const STOP_AT_CYCLES: bool> {
}
impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
pub(crate) fn new(heap: &'a mut [HeapCellValue], start: usize) -> Self {
pub(crate) fn new(heap: &'a mut Heap, start: usize) -> Self {
heap[start].set_forwarding_bit(true);
let next = heap[start].get_value();
@@ -127,7 +128,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
self.current = next;
self.next = temp;
if self.next < self.heap.len() as u64 {
if self.next < self.heap.cell_len() as u64 {
return Some(HeapCellValue::build_with(tag, next as u64));
}
}
@@ -136,10 +137,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
let cell = self.heap[h];
let arity = cell_as_atom_cell!(self.heap[h]).get_arity();
let last_cell_loc = match self.traverse_subterm(h + 1, arity) {
Some(last_cell_loc) => last_cell_loc,
None => return None,
};
let last_cell_loc = self.traverse_subterm(h + 1, arity)?;
if last_cell_loc == h {
if self.backward() {
@@ -170,10 +168,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
let mut cell = self.heap[self.current];
cell.set_value(self.next);
let last_cell_loc = match self.traverse_subterm(self.next as usize, 2) {
Some(last_cell_loc) => last_cell_loc,
None => return None,
};
let last_cell_loc = self.traverse_subterm(self.next as usize, 2)?;
if self.cycle_detection_active() {
for idx in (self.next as usize..last_cell_loc).rev() {
@@ -205,10 +200,9 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
}
HeapCellValueTag::PStrLoc => {
let h = self.next as usize;
let cell = self.heap[h];
let last_cell_loc = h + 1;
let tail_idx = self.heap.scan_slice_to_str(h).tail_idx;
if self.heap[last_cell_loc].get_forwarding_bit() {
if self.heap[tail_idx].get_forwarding_bit() {
if self.cycle_detection_active() {
self.cycle_found = true;
return None;
@@ -219,45 +213,13 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
continue;
}
self.heap[last_cell_loc].set_forwarding_bit(true);
self.heap[tail_idx].set_forwarding_bit(true);
self.next = self.heap[last_cell_loc].get_value();
self.heap[last_cell_loc].set_value(self.current as u64);
self.current = last_cell_loc;
self.next = self.heap[tail_idx].get_value();
self.heap[tail_idx].set_value(self.current as u64);
self.current = tail_idx;
return Some(cell);
}
HeapCellValueTag::PStrOffset => {
let h = self.next as usize;
let cell = self.heap[h];
let last_cell_loc = h + 1;
if self.heap[h].get_tag() == HeapCellValueTag::PStr {
if self.heap[last_cell_loc].get_forwarding_bit() {
if self.cycle_detection_active() {
self.cycle_found = true;
return None;
} else if self.backward() {
return None;
}
continue;
}
self.heap[last_cell_loc].set_forwarding_bit(true);
self.next = self.heap[last_cell_loc].get_value();
self.heap[last_cell_loc].set_value(self.current as u64);
self.current = last_cell_loc;
} else {
debug_assert!(self.heap[h].get_tag() == HeapCellValueTag::CStr);
self.next = self.heap[h].get_value();
self.heap[h].set_value(self.current as u64);
self.current = h;
}
return Some(cell);
return Some(pstr_loc_as_cell!(h));
}
tag @ HeapCellValueTag::Atom => {
let cell = HeapCellValue::build_with(tag, self.next);
@@ -269,11 +231,6 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
return None;
}
}
HeapCellValueTag::PStr => {
if self.backward() {
return None;
}
}
_ => {
return Some(self.backward_and_return());
}

View File

@@ -6,78 +6,16 @@ use crate::machine::loader::*;
use crate::machine::machine_errors::CompilationError;
use crate::machine::preprocessor::*;
use crate::parser::ast::*;
use crate::parser::dashu::Rational;
use crate::variable_records::*;
use dashu::Integer;
use indexmap::{IndexMap, IndexSet};
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::hash::{Hash, Hasher};
use std::hash::Hash;
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone)] //, PartialOrd, PartialEq, Eq, Hash)]
pub struct BranchNumber {
branch_num: Rational,
delta: Rational,
}
impl Default for BranchNumber {
fn default() -> Self {
Self {
branch_num: Rational::from(1u64 << 63),
delta: Rational::from(1),
}
}
}
impl PartialEq<BranchNumber> for BranchNumber {
#[inline]
fn eq(&self, rhs: &BranchNumber) -> bool {
self.branch_num == rhs.branch_num
}
}
impl Eq for BranchNumber {}
impl Hash for BranchNumber {
#[inline(always)]
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.branch_num.hash(hasher)
}
}
impl PartialOrd<BranchNumber> for BranchNumber {
#[inline]
fn partial_cmp(&self, rhs: &BranchNumber) -> Option<Ordering> {
self.branch_num.partial_cmp(&rhs.branch_num)
}
}
impl BranchNumber {
fn split(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta / Rational::from(2),
delta: &self.delta / Rational::from(4),
}
}
fn incr_by_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta,
delta: self.delta.clone(),
}
}
fn halve_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone(),
delta: &self.delta / Rational::from(2),
}
}
}
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VarInfo {
@@ -95,19 +33,14 @@ pub struct ChunkInfo {
vars: Vec<VarInfo>,
}
#[derive(Debug)]
pub struct BranchArm {
pub arm_terms: Vec<QueryTerm>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BranchInfo {
branch_num: BranchNumber,
branch_num: Arc<BranchNumber>,
chunks: Vec<ChunkInfo>,
}
impl BranchInfo {
fn new(branch_num: BranchNumber) -> Self {
fn new(branch_num: Arc<BranchNumber>) -> Self {
Self {
branch_num,
chunks: vec![],
@@ -136,7 +69,7 @@ impl DerefMut for BranchMap {
}
}
type RootSet = IndexSet<BranchNumber>;
type RootSet = IndexSet<Arc<BranchNumber>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClassifyInfo {
@@ -145,12 +78,13 @@ pub struct ClassifyInfo {
}
enum TraversalState {
// construct a QueryTerm::Branch with number of disjuncts, reset
// the chunk type to that of the chunk preceding the disjunct and the chunk_num.
// pop the latest branch number from the root set and use it to construct a QueryTerm::Branch
// with number of disjuncts, reset the chunk type to that of the chunk preceding the disjunct
// and the chunk_num.
BuildDisjunct(usize),
BuildFinalDisjunct(usize),
// add the last disjunct to a QueryTerm::Branch, continuing from
// where it leaves off.
BuildFinalDisjunct(usize),
Fail,
GetCutPoint { var_num: usize, prev_b: bool },
Cut { var_num: usize, is_global: bool },
@@ -159,15 +93,14 @@ enum TraversalState {
Term(Term),
OverrideGlobalCutVar(usize),
ResetGlobalCutVarOverride(Option<usize>),
RemoveBranchNum, // pop the current_branch_num and from the root set.
AddBranchNum(BranchNumber), // set current_branch_num, add it to the root set
RepBranchNum(BranchNumber), // replace current_branch_num and the latest in the root set
AddBranchNum(Arc<BranchNumber>), // set current_branch_num, add it to the root set
RepBranchNum(Arc<BranchNumber>), // replace current_branch_num and the latest in the root set
}
#[derive(Debug)]
pub struct VariableClassifier {
call_policy: CallPolicy,
current_branch_num: BranchNumber,
current_branch_num: Arc<BranchNumber>,
current_chunk_num: usize,
current_chunk_type: ChunkType,
branch_map: BranchMap,
@@ -204,11 +137,13 @@ impl VarData {
VarAlloc::Perm(0, PermVarAllocation::Pending);
match build_stack.front_mut() {
Some(ChunkedTerms::Branch(_)) => {
build_stack.push_front(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
Some(ChunkedTerms::Branch { .. }) => {
build_stack.push_front(ChunkedTerms::Chunk {
terms: VecDeque::from(vec![term]),
});
}
Some(ChunkedTerms::Chunk(chunk)) => {
chunk.push_front(term);
Some(ChunkedTerms::Chunk { terms, .. }) => {
terms.push_front(term);
}
None => {
unreachable!()
@@ -222,24 +157,33 @@ pub type ClassifyFactResult = (Term, VarData);
pub type ClassifyRuleResult = (Term, ChunkedTermVec, VarData);
fn merge_branch_seq(branches: impl Iterator<Item = BranchInfo>) -> BranchInfo {
let mut branch_info = BranchInfo::new(BranchNumber::default());
let mut branch_info = BranchInfo::new(Arc::new(BranchNumber::default()));
for mut branch in branches {
branch_info.branch_num = branch.branch_num;
branch_info.chunks.append(&mut branch.chunks);
}
branch_info.branch_num.delta = branch_info.branch_num.delta * Integer::from(2);
branch_info.branch_num.branch_num -= &branch_info.branch_num.delta;
let new_delta = branch_info.branch_num.delta.clone() * Integer::from(2);
branch_info.branch_num = Arc::new(BranchNumber {
branch_num: branch_info.branch_num.branch_num.clone() - &new_delta,
delta: new_delta,
});
branch_info
}
fn flatten_into_disjunct(build_stack: &mut ChunkedTermVec, preceding_len: usize) {
fn flatten_into_disjunct(
build_stack: &mut ChunkedTermVec,
branch_num: Arc<BranchNumber>,
preceding_len: usize,
) {
let branch_vec = build_stack.drain(preceding_len + 1..).collect();
if let ChunkedTerms::Branch(ref mut disjuncts) = &mut build_stack[preceding_len] {
disjuncts.push(branch_vec);
if let ChunkedTerms::Branch { branch_nums, arms } = &mut build_stack[preceding_len] {
branch_nums.push(branch_num);
arms.push(branch_vec);
} else {
unreachable!();
}
@@ -249,7 +193,7 @@ impl VariableClassifier {
pub fn new(call_policy: CallPolicy) -> Self {
Self {
call_policy,
current_branch_num: BranchNumber::default(),
current_branch_num: Arc::new(BranchNumber::default()),
current_chunk_num: 0,
current_chunk_type: ChunkType::Head,
branch_map: BranchMap(BranchMapInt::new()),
@@ -428,7 +372,6 @@ impl VariableClassifier {
// "probe_head_var". note the difference between it
// and "probe_body_var".
let branch_info_v = self.branch_map.entry(var_ptr.clone()).or_default();
let needs_new_branch = branch_info_v.is_empty();
if needs_new_branch {
@@ -481,9 +424,6 @@ impl VariableClassifier {
self.root_set.insert(branch_num.clone());
self.current_branch_num = branch_num;
}
TraversalState::RemoveBranchNum => {
self.root_set.pop();
}
TraversalState::RepBranchNum(branch_num) => {
self.root_set.pop();
self.root_set.insert(branch_num.clone());
@@ -492,14 +432,10 @@ impl VariableClassifier {
TraversalState::ResetCallPolicy(call_policy) => {
self.call_policy = call_policy;
}
TraversalState::BuildDisjunct(preceding_len) => {
flatten_into_disjunct(&mut build_stack, preceding_len);
self.current_chunk_type = ChunkType::Mid;
self.current_chunk_num += 1;
}
TraversalState::BuildFinalDisjunct(preceding_len) => {
flatten_into_disjunct(&mut build_stack, preceding_len);
TraversalState::BuildDisjunct(preceding_len)
| TraversalState::BuildFinalDisjunct(preceding_len) => {
let branch_num = self.root_set.pop().unwrap();
flatten_into_disjunct(&mut build_stack, branch_num, preceding_len);
self.current_chunk_type = ChunkType::Mid;
self.current_chunk_num += 1;
@@ -583,7 +519,7 @@ impl VariableClassifier {
mut terms,
) if terms.len() == 3 => {
if let Some(last_arg) = terms.last() {
if let Term::Literal(_, Literal::CodeIndex(_)) = last_arg {
if let Term::Literal(_, Literal::CodeIndexOffset(_)) = last_arg {
terms.pop();
state_stack.push(TraversalState::Term(Term::Clause(
Cell::default(),
@@ -611,9 +547,9 @@ impl VariableClassifier {
let tail = terms.pop().unwrap();
let head = terms.pop().unwrap();
let first_branch_num = self.current_branch_num.split();
let first_branch_num = Arc::new(self.current_branch_num.split());
let branches: Vec<_> = std::iter::once(head)
.chain(unfold_by_str(tail, atom!(";")).into_iter())
.chain(unfold_by_str(tail, atom!(";")))
.collect();
let mut branch_numbers = vec![first_branch_num];
@@ -622,25 +558,24 @@ impl VariableClassifier {
let succ_branch_number = branch_numbers[idx - 1].incr_by_delta();
branch_numbers.push(if idx + 1 < branches.len() {
succ_branch_number.split()
Arc::new(succ_branch_number.split())
} else {
succ_branch_number
Arc::new(succ_branch_number)
});
}
let build_stack_len = build_stack.len();
build_stack.reserve_branch(branches.len());
state_stack.push(TraversalState::RepBranchNum(
state_stack.push(TraversalState::RepBranchNum(Arc::new(
self.current_branch_num.halve_delta(),
));
)));
let iter = branches.into_iter().zip(branch_numbers.into_iter());
let iter = branches.into_iter().zip(branch_numbers);
let final_disjunct_loc = state_stack.len();
for (term, branch_num) in iter.rev() {
state_stack.push(TraversalState::BuildDisjunct(build_stack_len));
state_stack.push(TraversalState::RemoveBranchNum);
state_stack.push(TraversalState::Term(term));
state_stack.push(TraversalState::AddBranchNum(branch_num));
}
@@ -659,23 +594,18 @@ impl VariableClassifier {
let then_term = terms.pop().unwrap();
let if_term = terms.pop().unwrap();
let prev_b = if matches!(
state_stack.last(),
Some(TraversalState::RemoveBranchNum)
) {
// check if the second-to-last element
// is a regular BuildDisjunct, as we
// don't want to add GetPrevLevel in
// case of a TrustMe.
match state_stack.iter().rev().nth(1) {
Some(&TraversalState::BuildDisjunct(preceding_len)) => {
preceding_len + 1 == build_stack.len()
}
_ => false,
}
} else {
false
};
let prev_b =
if let Some(&TraversalState::BuildDisjunct(preceding_len)) =
state_stack.last()
{
// check if the second-to-last element
// is a regular BuildDisjunct, as we
// don't want to add GetPrevLevel in
// case of a TrustMe.
preceding_len + 1 == build_stack.len()
} else {
false
};
state_stack.push(TraversalState::Term(then_term));
state_stack.push(TraversalState::Cut {
@@ -694,14 +624,21 @@ impl VariableClassifier {
let not_term = terms.pop().unwrap();
let build_stack_len = build_stack.len();
let first_branch_num = Arc::new(self.current_branch_num.split());
let second_branch_num = Arc::new(first_branch_num.incr_by_delta());
build_stack.reserve_branch(2);
state_stack.push(TraversalState::RepBranchNum(Arc::new(
self.current_branch_num.halve_delta(),
)));
state_stack.push(TraversalState::BuildFinalDisjunct(build_stack_len));
state_stack.push(TraversalState::Term(Term::Clause(
Cell::default(),
atom!("$succeed"),
vec![],
)));
state_stack.push(TraversalState::AddBranchNum(second_branch_num));
state_stack.push(TraversalState::BuildDisjunct(build_stack_len));
state_stack.push(TraversalState::Fail);
state_stack.push(TraversalState::CutPrev(self.var_num));
@@ -714,6 +651,7 @@ impl VariableClassifier {
var_num: self.var_num,
prev_b: false,
});
state_stack.push(TraversalState::AddBranchNum(first_branch_num));
self.current_chunk_type = ChunkType::Mid;
self.current_chunk_num += 1;
@@ -806,7 +744,7 @@ impl VariableClassifier {
self.call_policy,
));
}
Term::Literal(_, Literal::Atom(atom!("!")) | Literal::Char('!')) => {
Term::Literal(_, Literal::Atom(atom!("!"))) => {
let (var_num, is_global) =
if let Some(var_num) = self.global_cut_var_num_override {
(var_num, false)
@@ -884,10 +822,10 @@ impl BranchMap {
for var_info in chunk.vars.iter_mut() {
if var_info.lvl == Level::Shallow {
let term_loc = var_info.chunk_type.to_gen_context(chunk.chunk_num);
let context = var_info.chunk_type.to_gen_context(chunk.chunk_num);
temp_var_data
.use_set
.insert((term_loc, var_info.classify_info.arg_c));
.insert((context, var_info.classify_info.arg_c));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,612 +0,0 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use crate::atom_table;
use crate::heap_print::{HCPrinter, HCValueOutputter, PrinterOutputter};
use crate::machine::machine_indices::VarKey;
use crate::machine::mock_wam::CompositeOpDir;
use crate::machine::{BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS};
use crate::parser::ast::{Var, VarPtr};
use crate::parser::parser::{Parser, Tokens};
use crate::read::write_term_to_heap;
use indexmap::IndexMap;
use super::{
streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, MachineConfig,
QueryResolution, QueryResolutionLine, QueryResult, Value,
};
impl Machine {
pub fn new_lib() -> Self {
Machine::new(MachineConfig::in_memory())
}
pub fn load_module_string(&mut self, module_name: &str, program: String) {
let stream = Stream::from_owned_string(program, &mut self.machine_st.arena);
self.load_file(module_name, stream);
}
pub fn consult_module_string(&mut self, module_name: &str, program: String) {
let stream = Stream::from_owned_string(program, &mut self.machine_st.arena);
self.machine_st.registers[1] = stream_as_cell!(stream);
self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(
&self.machine_st.atom_tbl,
module_name
));
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
}
fn allocate_stub_choice_point(&mut self) {
// NOTE: create a choice point to terminate the dispatch_loop
// if an exception is thrown.
let stub_b = self.machine_st.stack.allocate_or_frame(0);
let or_frame = self.machine_st.stack.index_or_frame_mut(stub_b);
or_frame.prelude.num_cells = 0;
or_frame.prelude.e = 0;
or_frame.prelude.cp = 0;
or_frame.prelude.b = 0;
or_frame.prelude.bp = BREAK_FROM_DISPATCH_LOOP_LOC;
or_frame.prelude.boip = 0;
or_frame.prelude.biip = 0;
or_frame.prelude.tr = 0;
or_frame.prelude.h = 0;
or_frame.prelude.b0 = 0;
or_frame.prelude.attr_var_queue_len = 0;
self.machine_st.b = stub_b;
self.machine_st.hb = self.machine_st.heap.len();
self.machine_st.block = stub_b;
}
pub fn run_query(&mut self, query: String) -> QueryResult {
// println!("Query: {}", query);
// Parse the query so we can analyze and then call the term
let mut parser = Parser::new(
Stream::from_owned_string(query, &mut self.machine_st.arena),
&mut self.machine_st,
);
let op_dir = CompositeOpDir::new(&self.indices.op_dir, None);
let term = parser
.read_term(&op_dir, Tokens::Default)
.expect("Failed to parse query");
self.allocate_stub_choice_point();
// Write parsed term to heap
let term_write_result =
write_term_to_heap(&term, &mut self.machine_st.heap, &self.machine_st.atom_tbl)
.expect("couldn't write term to heap");
let var_names: IndexMap<_, _> = term_write_result
.var_dict
.iter()
.map(|(var_key, cell)| match var_key {
// NOTE: not the intention behind Var::InSitu here but
// we can hijack it to store anonymous variables
// without creating problems.
VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))),
VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()),
})
.collect();
// Write term to heap
self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
let call_index_p = self
.indices
.code_dir
.get(&(atom!("call"), 1))
.expect("couldn't get code index")
.local()
.unwrap();
self.machine_st.execute_at_index(1, call_index_p);
let stub_b = self.machine_st.b;
let mut matches: Vec<QueryResolutionLine> = Vec::new();
// Call the term
loop {
self.dispatch_loop();
//println!("b: {}", self.machine_st.b);
//println!("stub_b: {}", stub_b);
//println!("fail: {}", self.machine_st.fail);
if !self.machine_st.ball.stub.is_empty() {
// NOTE: this means an exception was thrown, at which
// point we backtracked to the stub choice point.
// this should halt the search for solutions as it
// does in the Scryer top-level. the exception term is
// contained in self.machine_st.ball.
let error_string = self
.machine_st
.ball
.stub
.iter()
.filter(|h| {
matches!(
h.get_tag(),
HeapCellValueTag::Atom | HeapCellValueTag::Fixnum
)
})
.map(|h| match h.get_tag() {
HeapCellValueTag::Atom => {
let (name, _) = cell_as_atom_cell!(h).get_name_and_arity();
name.as_str().to_string()
}
HeapCellValueTag::Fixnum => h.get_value().clone().to_string(),
_ => unreachable!(),
})
.collect::<Vec<String>>()
.join(" ");
return Err(error_string);
}
/*
if self.machine_st.fail {
// NOTE: only print results on success
self.machine_st.fail = false;
println!("fail!");
matches.push(QueryResolutionLine::False);
break;
};
*/
if self.machine_st.p == LIB_QUERY_SUCCESS {
if term_write_result.var_dict.is_empty() {
matches.push(QueryResolutionLine::True);
break;
}
} else if self.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
// NOTE: only print results on success
// self.machine_st.fail = false;
// println!("b == stub_b");
matches.push(QueryResolutionLine::False);
break;
}
let mut bindings: BTreeMap<String, Value> = BTreeMap::new();
for (var_key, term_to_be_printed) in &term_write_result.var_dict {
if var_key.to_string().starts_with('_') {
continue;
}
let mut printer = HCPrinter::new(
&mut self.machine_st.heap,
Arc::clone(&self.machine_st.atom_tbl),
&mut self.machine_st.stack,
&self.indices.op_dir,
PrinterOutputter::new(),
*term_to_be_printed,
);
printer.ignore_ops = false;
printer.numbervars = true;
printer.quoted = true;
printer.max_depth = 1000; // NOTE: set this to 0 for unbounded depth
printer.double_quotes = true;
printer.var_names = var_names.clone();
let outputter = printer.print();
let output: String = outputter.result();
// println!("Result: {} = {}", var_key.to_string(), output);
if var_key.to_string() != output {
bindings.insert(
var_key.to_string(),
Value::try_from(output).expect("Couldn't convert Houtput to Value"),
);
}
}
matches.push(QueryResolutionLine::Match(bindings));
// NOTE: there are outstanding choicepoints, backtrack
// through them for further solutions. if
// self.machine_st.b == stub_b we've backtracked to the stub
// choice point, so we should break.
self.machine_st.backtrack();
if self.machine_st.b <= stub_b {
// NOTE: out of choicepoints to backtrack through, no
// more solutions to gather.
break;
}
}
// NOTE: deallocate stub choice point
if self.machine_st.b == stub_b {
self.trust_me();
}
Ok(QueryResolution::from(matches))
}
}
#[cfg(test)]
mod tests {
use ordered_float::OrderedFloat;
use super::*;
use crate::machine::{QueryMatch, QueryResolution, Value};
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn programatic_query() {
let mut machine = Machine::new_lib();
machine.load_module_string(
"facts",
String::from(
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
),
);
let query = String::from(r#"triple("a",P,"b")."#);
let output = machine.run_query(query);
assert_eq!(
output,
Ok(QueryResolution::Matches(vec![
QueryMatch::from(btreemap! {
"P" => Value::from("p1"),
}),
QueryMatch::from(btreemap! {
"P" => Value::from("p2"),
}),
]))
);
assert_eq!(
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
Ok(QueryResolution::True)
);
assert_eq!(
machine.run_query(String::from(r#"triple("x","y","z")."#)),
Ok(QueryResolution::False)
);
}
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn failing_query() {
let mut machine = Machine::new_lib();
let query = String::from(r#"triple("a",P,"b")."#);
let output = machine.run_query(query);
assert_eq!(
output,
Err(String::from(
"error existence_error procedure / triple 3 / triple 3"
))
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn complex_results() {
let mut machine = Machine::new_lib();
machine.load_module_string(
"facts",
r#"
:- discontiguous(subject_class/2).
:- discontiguous(constructor/2).
subject_class("Todo", c).
constructor(c, '[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]').
subject_class("Recipe", xyz).
constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]').
"#.to_string());
let result = machine.run_query(String::from(
"subject_class(\"Todo\", C), constructor(C, Actions).",
));
assert_eq!(
result,
Ok(QueryResolution::Matches(vec![QueryMatch::from(
btreemap! {
"C" => Value::from("c"),
"Actions" => Value::from("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]"),
}
),]))
);
let result = machine.run_query(String::from(
"subject_class(\"Recipe\", C), constructor(C, Actions).",
));
assert_eq!(
result,
Ok(QueryResolution::Matches(vec![QueryMatch::from(
btreemap! {
"C" => Value::from("xyz"),
"Actions" => Value::from("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]"),
}
),]))
);
let result = machine.run_query(String::from("subject_class(Class, _)."));
assert_eq!(
result,
Ok(QueryResolution::Matches(vec![
QueryMatch::from(btreemap! {
"Class" => Value::from("Todo")
}),
QueryMatch::from(btreemap! {
"Class" => Value::from("Recipe")
}),
]))
);
}
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn empty_predicate() {
let mut machine = Machine::new_lib();
machine.load_module_string(
"facts",
r#"
:- discontiguous(subject_class/2).
"#
.to_string(),
);
let result = machine.run_query(String::from("subject_class(X, _)."));
assert_eq!(result, Ok(QueryResolution::False));
}
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn list_results() {
let mut machine = Machine::new_lib();
machine.load_module_string(
"facts",
r#"
list([1,2,3]).
"#
.to_string(),
);
let result = machine.run_query(String::from("list(X)."));
assert_eq!(
result,
Ok(QueryResolution::Matches(vec![QueryMatch::from(
btreemap! {
"X" => Value::List(
Vec::from([
Value::Float(OrderedFloat::from(1.0)),
Value::Float(OrderedFloat::from(2.0)),
Value::Float(OrderedFloat::from(3.0))
])
)
}
),]))
);
}
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn consult() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
),
);
let query = String::from(r#"triple("a",P,"b")."#);
let output = machine.run_query(query);
assert_eq!(
output,
Ok(QueryResolution::Matches(vec![
QueryMatch::from(btreemap! {
"P" => Value::from("p1"),
}),
QueryMatch::from(btreemap! {
"P" => Value::from("p2"),
}),
]))
);
assert_eq!(
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
Ok(QueryResolution::True)
);
assert_eq!(
machine.run_query(String::from(r#"triple("x","y","z")."#)),
Ok(QueryResolution::False)
);
machine.consult_module_string(
"facts",
String::from(
r#"
triple("a", "new", "b").
"#,
),
);
assert_eq!(
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
Ok(QueryResolution::False)
);
assert_eq!(
machine.run_query(String::from(r#"triple("a","new","b")."#)),
Ok(QueryResolution::True)
);
}
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn integration_test() {
let mut machine = Machine::new_lib();
// File with test commands, i.e. program code to consult and queries to run
let code = include_str!("./lib_integration_test_commands.txt");
// Split the code into blocks
let blocks = code.split("=====");
let mut i = 0;
let mut last_result: Option<_> = None;
// Iterate over the blocks
for block in blocks {
// Trim the block to remove any leading or trailing whitespace
let block = block.trim();
// Skip empty blocks
if block.is_empty() {
continue;
}
// Check if the block is a query
if let Some(query) = block.strip_prefix("query") {
// Parse and execute the query
let result = machine.run_query(query.to_string());
assert!(result.is_ok());
last_result = Some(result);
} else if let Some(code) = block.strip_prefix("consult") {
// Load the code into the machine
machine.consult_module_string("facts", code.to_string());
} else if let Some(result) = block.strip_prefix("result") {
i += 1;
if let Some(Ok(ref last_result)) = last_result {
println!(
"\n\n=====Result No. {}=======\n{}\n===============",
i,
last_result.to_string().trim()
);
assert_eq!(last_result.to_string().trim(), result.to_string().trim(),)
}
}
}
}
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn findall() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
),
);
let query =
String::from(r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#);
let output = machine.run_query(query);
assert_eq!(
output,
Ok(QueryResolution::Matches(vec![QueryMatch::from(
btreemap! {
"Result" => Value::List(
Vec::from([
Value::List([Value::from("p1"), Value::from("b")].into()),
Value::List([Value::from("p2"), Value::from("b")].into()),
])
),
}
),]))
);
}
#[test]
fn dont_return_partial_matches() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
:- discontiguous(property_resolve/2).
subject_class("Todo", c).
"#,
),
);
let query = String::from(r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
let query = String::from(r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
}
#[test]
fn dont_return_partial_matches_without_discountiguous() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
a("true for a").
b("true for b").
"#,
),
);
let query = String::from(r#"a("true for a")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::True));
let query = String::from(r#"a("true for a"), b("true for b")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::True));
let query = String::from(r#"a("true for b"), b("true for b")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
let query = String::from(r#"a("true for a"), b("true for a")."#);
let output = machine.run_query(query);
assert_eq!(output, Ok(QueryResolution::False));
}
#[test]
fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defined() {
let mut machine = Machine::new_lib();
machine.consult_module_string(
"facts",
String::from(
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
),
);
let query = String::from("non_existent_predicate(\"a\",\"p1\",\"b\").");
let result = machine.run_query(query);
assert_eq!(
result,
Err(String::from("error existence_error procedure / non_existent_predicate 3 / non_existent_predicate 3"))
);
}
}

View File

@@ -0,0 +1,651 @@
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::rc::Rc;
use crate::atom_table;
use crate::heap_iter::{NonListElider, stackful_post_order_iter};
use crate::machine::heap::AllocError;
use crate::machine::machine_indices::VarKey;
use crate::machine::mock_wam::CompositeOpDir;
use crate::machine::{
ArenaHeaderTag, BREAK_FROM_DISPATCH_LOOP_LOC, Fixnum, LIB_QUERY_SUCCESS, Number,
};
use crate::offset_table::*;
use crate::parser::ast::{Var, VarPtr};
use crate::parser::parser::{Parser, Tokens};
use crate::read::{TermWriteResult, write_term_to_heap};
use crate::types::UntypedArenaPtr;
use dashu::{Integer, Rational};
use indexmap::IndexMap;
use super::{Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, streams::Stream};
#[cfg(test)]
mod tests;
/// Represents a leaf answer from a query.
#[derive(Debug, Clone, PartialEq)]
pub enum LeafAnswer {
/// A `true` leaf answer.
True,
/// A `false` leaf answer.
///
/// This means that there are no more answers for the query.
False,
/// An exception leaf answer.
Exception(Term),
/// A leaf answer with bindings.
#[non_exhaustive]
LeafAnswer {
/// The bindings of variables in the query.
bindings: BTreeMap<String, Term>,
//residual_goals: Vec<Term>,
},
}
impl LeafAnswer {
/// Creates a leaf answer with no residual goals.
pub fn from_bindings<S: Into<String>>(bindings: impl IntoIterator<Item = (S, Term)>) -> Self {
LeafAnswer::LeafAnswer {
bindings: bindings.into_iter().map(|(k, v)| (k.into(), v)).collect(),
}
}
}
/// Represents a Prolog term.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum Term {
/// An arbitrary precision integer.
Integer(Integer),
/// An arbitrary precision rational.
Rational(Rational),
/// A float.
Float(f64),
/// A Prolog atom.
Atom(String),
/// A Prolog string.
///
/// In particular, this represents Prolog lists of characters.
String(String),
/// A Prolog list.
List(Vec<Term>),
/// A Prolog compound term.
Compound(String, Vec<Term>),
/// A Prolog variable.
Var(String),
}
impl Term {
/// Creates an integer term.
pub fn integer(value: impl Into<Integer>) -> Self {
Term::Integer(value.into())
}
/// Creates a rational term.
pub fn rational(value: impl Into<Rational>) -> Self {
Term::Rational(value.into())
}
/// Creates a float term.
pub fn float(value: impl Into<f64>) -> Self {
Term::Float(value.into())
}
/// Creates an atom term.
pub fn atom(value: impl Into<String>) -> Self {
Term::Atom(value.into())
}
/// Creates a string term.
///
/// In specific, this represents a list of chars in Prolog.
pub fn string(value: impl Into<String>) -> Self {
Term::String(value.into())
}
/// Creates a list term.
pub fn list(value: impl IntoIterator<Item = Term>) -> Self {
Term::List(value.into_iter().collect())
}
/// Creates a compound term.
pub fn compound(functor: impl Into<String>, args: impl IntoIterator<Item = Term>) -> Self {
Term::Compound(functor.into(), args.into_iter().collect())
}
/// Creates a variable.
pub fn variable(value: impl Into<String>) -> Self {
Term::Var(value.into())
}
/// Creates a conjunction, giving the atom `true` if empty.
pub fn conjunction(value: impl IntoIterator<Item = Term>) -> Self {
Term::try_conjunction(value).unwrap_or(Term::atom("true"))
}
/// Creates a conjunction, giving `None` if empty.
pub fn try_conjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
let mut iter = value.into_iter();
iter.next().map(|first| {
if let Some(rest) = Term::try_conjunction(iter) {
Term::compound(",", [first, rest])
} else {
first
}
})
}
/// Creates a disjunction, giving the atom `false` if empty.
pub fn disjunction(value: impl IntoIterator<Item = Term>) -> Self {
Term::try_disjunction(value).unwrap_or(Term::atom("false"))
}
/// Creates a disjunction, giving `None` if empty.
pub fn try_disjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
let mut iter = value.into_iter();
iter.next().map(|first| {
if let Some(rest) = Term::try_disjunction(iter) {
Term::compound(";", [first, rest])
} else {
first
}
})
}
}
/// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B,
/// _AB, etc...
fn count_to_letter_code(mut count: usize) -> String {
let mut letters = Vec::new();
// +2 rather than +1 to account for the _ at the end
let length = count.checked_ilog(26).unwrap_or(0) as usize + 2;
letters.reserve(length);
loop {
let letter_idx = (count % 26) as u8;
letters.push(b'A' + letter_idx);
count /= 26;
if count == 0 {
break;
}
}
letters.push(b'_');
debug_assert_eq!(length, letters.len());
letters.reverse();
// Safety: we only push ascii chars A-Z and _
// an ascii only byte sequence is always valid utf-8
unsafe { String::from_utf8_unchecked(letters) }
}
impl Term {
pub(crate) fn from_heapcell(
machine: &mut Machine,
heap_cell: HeapCellValue,
var_names: &mut IndexMap<HeapCellValue, VarPtr>,
) -> Self {
// Adapted from MachineState::read_term_from_heap
let mut term_stack = vec![];
machine.machine_st.heap[0] = heap_cell;
let mut iter = stackful_post_order_iter::<NonListElider>(
&mut machine.machine_st.heap,
&mut machine.machine_st.stack,
0,
);
let mut anon_count: usize = 0;
let var_ptr_cmp = |a, b| match a {
Var::Named(name_a) => match b {
Var::Named(name_b) => name_a.cmp(&name_b),
_ => Ordering::Less,
},
_ => match b {
Var::Named(_) => Ordering::Greater,
_ => Ordering::Equal,
},
};
while let Some(addr) = iter.next() {
let addr = unmark_cell_bits!(addr);
read_heap_cell!(addr,
(HeapCellValueTag::Lis) => {
let tail = term_stack.pop().unwrap();
let head = term_stack.pop().unwrap();
let list = match tail {
Term::Atom(atom) if atom == "[]" => match head {
Term::Atom(ref a) if a.chars().count() == 1 => {
// Handle lists of char as strings
Term::String(a.to_string())
}
_ => Term::List(vec![head]),
},
Term::List(elems) if elems.is_empty() => match head {
Term::Atom(ref a) if a.chars().count() == 1 => {
// Handle lists of char as strings
Term::String(a.to_string())
},
_ => Term::List(vec![head]),
},
Term::List(mut elems) => {
elems.insert(0, head);
Term::List(elems)
},
Term::String(mut elems) => match head {
Term::Atom(ref a) if a.chars().count() == 1 => {
// Handle lists of char as strings
elems.insert(0, a.chars().next().unwrap());
Term::String(elems)
},
_ => {
let mut elems: Vec<Term> = elems
.chars()
.map(|x| Term::Atom(x.into()))
.collect();
elems.insert(0, head);
Term::List(elems)
}
},
_ => {
Term::Compound(".".into(), vec![head, tail])
}
};
term_stack.push(list);
}
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => {
let var = var_names.get(&addr).map(|x| x.borrow().clone());
match var {
Some(Var::Named(name)) => term_stack.push(Term::Var(name.as_ref().to_owned())),
_ => {
let anon_name = loop {
// Generate a name for the anonymous variable
let anon_name = Rc::new(count_to_letter_code(anon_count));
// Find if this name is already being used
var_names.sort_by(|_, a, _, b| {
var_ptr_cmp(a.borrow().clone(), b.borrow().clone())
});
let binary_result = var_names.binary_search_by(|_,a| {
let var_ptr = Var::Named(anon_name.clone());
var_ptr_cmp(a.borrow().clone(), var_ptr.clone())
});
match binary_result {
Ok(_) => anon_count += 1, // Name already used
Err(_) => {
// Name not used, assign it to this variable
let var_ptr = VarPtr::from(Var::Named(anon_name.clone()));
var_names.insert(addr, var_ptr);
break anon_name;
},
}
};
term_stack.push(Term::Var(anon_name.as_ref().to_owned()));
},
}
}
(HeapCellValueTag::F64Offset, offset) => {
let f = machine.machine_st.arena.f64_tbl.get_entry(offset);
term_stack.push(Term::Float(f.into()));
}
(HeapCellValueTag::Fixnum, n) => {
term_stack.push(Term::Integer(n.into()));
}
(HeapCellValueTag::Cons, ptr) => {
if let Ok(n) = Number::try_from((addr, &machine.machine_st.arena.f64_tbl)) {
match n {
Number::Integer(i) => term_stack.push(Term::Integer((*i).clone())),
Number::Rational(r) => term_stack.push(Term::Rational((*r).clone())),
_ => { unreachable!() },
}
} else {
match_untyped_arena_ptr!(ptr,
(ArenaHeaderTag::Stream, stream) => {
let stream_term = if let Some(alias) = stream.options().get_alias() {
Term::atom(alias.as_str().to_string())
} else {
Term::compound("$stream", [
Term::integer(stream.as_ptr().addr())
])
};
term_stack.push(stream_term);
}
(ArenaHeaderTag::Dropped, _stream) => {
term_stack.push(Term::atom("$dropped_value"));
}
_ => {
unreachable!();
}
);
}
}
(HeapCellValueTag::Atom, (name, arity)) => {
if arity == 0 {
let atom_name = name.as_str().to_string();
if atom_name == "[]" {
term_stack.push(Term::List(vec![]));
} else {
term_stack.push(Term::Atom(atom_name));
}
} else {
let subterms = term_stack
.drain(term_stack.len() - arity ..)
.collect();
term_stack.push(Term::Compound(name.as_str().to_string(), subterms));
}
}
(HeapCellValueTag::PStrLoc, pstr_loc) => {
let tail = term_stack.pop().unwrap();
let char_iter = iter.base_iter.heap.char_iter(pstr_loc);
match tail {
Term::Atom(atom) => {
if atom == "[]" {
term_stack.push(Term::String(atom.as_str().to_string()));
}
},
Term::List(l) if l.is_empty() => {
term_stack.push(Term::String(char_iter.collect()));
}
Term::List(l) => {
let mut list: Vec<Term> = char_iter
.map(|x| Term::Atom(x.to_string()))
.collect();
list.extend(l.into_iter());
term_stack.push(Term::List(list));
},
_ => {
let mut list: Vec<Term> = char_iter
.map(|x| Term::Atom(x.to_string()))
.collect();
let mut partial_list = Term::Compound(
".".into(),
vec![
list.pop().unwrap(),
tail,
],
);
while let Some(last) = list.pop() {
partial_list = Term::Compound(
".".into(),
vec![
last,
partial_list,
],
);
}
term_stack.push(partial_list);
}
}
}
_ => {
unreachable!();
}
);
}
debug_assert_eq!(term_stack.len(), 1);
term_stack.pop().unwrap()
}
}
/// An iterator though the leaf answers of a query.
pub struct QueryState<'a> {
machine: &'a mut Machine,
term: TermWriteResult,
stub_b: usize,
var_names: IndexMap<HeapCellValue, VarPtr>,
called: bool,
}
impl Drop for QueryState<'_> {
fn drop(&mut self) {
// FIXME: This may be wrong if the iterator is not fully consumed, but from testing it
// seems fine. Is this really ok?
self.machine.trust_me();
}
}
impl Iterator for QueryState<'_> {
type Item = Result<LeafAnswer, Term>;
fn next(&mut self) -> Option<Self::Item> {
let var_names = &mut self.var_names;
let term_write_result = &self.term;
let machine = &mut self.machine;
// No more choicepoints, end iteration
if self.called && machine.machine_st.b <= self.stub_b {
return None;
}
machine.dispatch_loop();
self.called = true;
if !machine.machine_st.ball.stub.is_empty() {
// NOTE: this means an exception was thrown, at which
// point we backtracked to the stub choice point.
// this should halt the search for solutions as it
// does in the Scryer top-level. the exception term is
// contained in self.machine_st.ball.
let h = machine.machine_st.heap.cell_len();
if let Err(err) = machine
.machine_st
.heap
.append(&machine.machine_st.ball.stub)
{
let resource_error_offset = err.resource_error_offset(&mut machine.machine_st.heap);
return Some(Err(Term::from_heapcell(
machine,
machine.machine_st.heap[resource_error_offset],
&mut IndexMap::new(),
)));
}
let exception_term =
Term::from_heapcell(machine, machine.machine_st.heap[h], &mut var_names.clone());
if let Term::Compound(functor, args) = &exception_term {
if functor == "error" && args.len() == 2 {
// We have an error
return Some(Err(exception_term));
}
}
// We have an exception that is not an error
return Some(Ok(LeafAnswer::Exception(exception_term)));
}
if machine.machine_st.p == LIB_QUERY_SUCCESS {
if term_write_result.var_dict.is_empty() {
self.machine.machine_st.backtrack();
return Some(Ok(LeafAnswer::True));
}
} else if machine.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
return Some(Ok(LeafAnswer::False));
}
let mut bindings: BTreeMap<String, Term> = BTreeMap::new();
let var_dict = &term_write_result.var_dict;
for (var_key, term_to_be_printed) in var_dict.iter() {
let mut var_name = var_key.to_string();
if var_name.starts_with('_') {
let should_print = var_names.values().any(|x| match x.borrow().clone() {
Var::Named(v) => *v == *var_name,
_ => false,
});
if !should_print {
continue;
}
}
let mut term =
Term::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone());
if let Term::Var(ref term_str) = term {
if *term_str == var_name {
continue;
}
// Var dict is in the order things appear in the query. If var_name appears
// after term in the query, switch their places.
let var_name_idx = var_dict
.get_index_of(&VarKey::VarPtr(Var::from(var_name.clone()).into()))
.unwrap();
let term_idx =
var_dict.get_index_of(&VarKey::VarPtr(Var::from(term_str.clone()).into()));
if let Some(idx) = term_idx {
if idx < var_name_idx {
let new_term = Term::Var(var_name);
let new_var_name = term_str.into();
term = new_term;
var_name = new_var_name;
}
}
}
bindings.insert(var_name, term);
}
// NOTE: there are outstanding choicepoints, backtrack
// through them for further solutions. if
// self.machine_st.b == stub_b we've backtracked to the stub
// choice point, so we should break.
self.machine.machine_st.backtrack();
Some(Ok(LeafAnswer::LeafAnswer { bindings }))
}
}
impl Machine {
/// Loads a module into the [`Machine`] from a string.
pub fn load_module_string(&mut self, module_name: &str, program: impl Into<String>) {
let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
self.load_file(module_name, stream);
}
/// Consults a module into the [`Machine`] from a string.
pub fn consult_module_string(&mut self, module_name: &str, program: impl Into<String>) {
let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
self.machine_st.registers[1] = stream_as_cell!(stream);
self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(
&self.machine_st.atom_tbl,
module_name
));
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
}
pub(crate) fn allocate_stub_choice_point(&mut self) -> Result<(), AllocError> {
// NOTE: create a choice point to terminate the dispatch_loop
// if an exception is thrown.
let stub_b = self.machine_st.stack.allocate_or_frame(0)?;
let or_frame = self.machine_st.stack.index_or_frame_mut(stub_b);
or_frame.prelude.num_cells = 0;
or_frame.prelude.e = 0;
or_frame.prelude.cp = 0;
or_frame.prelude.b = 0;
or_frame.prelude.bp = BREAK_FROM_DISPATCH_LOOP_LOC;
or_frame.prelude.boip = 0;
or_frame.prelude.biip = 0;
or_frame.prelude.tr = 0;
or_frame.prelude.h = 0;
or_frame.prelude.b0 = 0;
or_frame.prelude.attr_var_queue_len = 0;
self.machine_st.b = stub_b;
self.machine_st.hb = self.machine_st.heap.cell_len();
self.machine_st.block = stub_b;
Ok(())
}
/// Runs a query.
pub fn run_query(&mut self, query: impl Into<String>) -> QueryState<'_> {
let mut parser = Parser::new(
Stream::from_owned_string(query.into(), &mut self.machine_st.arena),
&mut self.machine_st,
);
let op_dir = CompositeOpDir::new(&self.indices.op_dir, None);
let term = parser
.read_term(&op_dir, Tokens::Default)
.expect("Failed to parse query");
self.allocate_stub_choice_point()
.expect("failed to allocate stub choice point");
// Write parsed term to heap
let term_write_result = write_term_to_heap(&term, &mut self.machine_st.heap)
.expect("couldn't write term to heap");
let var_names: IndexMap<_, _> = term_write_result
.var_dict
.iter()
.map(|(var_key, cell)| match var_key {
// NOTE: not the intention behind Var::InSitu here but
// we can hijack it to store anonymous variables
// without creating problems.
VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))),
VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()),
})
.collect();
// Write term to heap
self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
let call_index_p = self
.indices
.code_dir
.get(&(atom!("call"), 1))
.cloned()
.map(|offset| {
self.machine_st
.arena
.code_index_tbl
.get_entry(offset.into())
.p() as usize
})
.expect("couldn't get code index");
self.machine_st.execute_at_index(1, call_index_p);
let stub_b = self.machine_st.b;
QueryState {
machine: self,
term: term_write_result,
stub_b,
var_names,
called: false,
}
}
}
#[test]
fn test_count_to_letter_code() {
for idx in 0..1000 {
// ensure the debug assert doesn't trigger
count_to_letter_code(idx);
}
}

View File

@@ -0,0 +1,610 @@
use super::*;
use crate::MachineBuilder;
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn programmatic_query() {
let mut machine = MachineBuilder::default().build();
machine.load_module_string(
"facts",
String::from(
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
),
);
let query = r#"triple("a",P,"b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(
complete_answer,
[
LeafAnswer::from_bindings([("P", Term::string("p1")),]),
LeafAnswer::from_bindings([("P", Term::string("p2")),]),
],
);
let query = r#"triple("a","p1","b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::True],);
let query = r#"triple("x","y","z")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False],);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn failing_query() {
let mut machine = MachineBuilder::default().build();
let query = r#"triple("a",P,"b")."#;
let complete_answer: Result<Vec<_>, _> = machine.run_query(query).collect();
assert_eq!(
complete_answer,
Err(Term::compound(
"error",
[
Term::compound(
"existence_error",
[
Term::atom("procedure"),
Term::compound("/", [Term::atom("triple"), Term::integer(3)]),
]
),
Term::compound("/", [Term::atom("triple"), Term::integer(3)]),
],
))
);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn complex_results() {
let mut machine = MachineBuilder::default().build();
machine.load_module_string(
"facts",
r#"
:- discontiguous(subject_class/2).
:- discontiguous(constructor/2).
subject_class("Todo", c).
constructor(c, '[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]').
subject_class("Recipe", xyz).
constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]').
"#,
);
let complete_answer: Vec<_> = machine
.run_query(r#"subject_class("Todo", C), constructor(C, Actions)."#)
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([
("C", Term::atom("c")),
(
"Actions",
Term::atom(
r#"[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]"#
)
),
])],
);
let complete_answer: Vec<_> = machine
.run_query(r#"subject_class("Recipe", C), constructor(C, Actions)."#)
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([
("C", Term::atom("xyz")),
(
"Actions",
Term::atom(
r#"[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]"#
)
),
])],
);
let complete_answer: Vec<_> = machine
.run_query("subject_class(Class, _).")
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
complete_answer,
[
LeafAnswer::from_bindings([("Class", Term::string("Todo"))]),
LeafAnswer::from_bindings([("Class", Term::string("Recipe"))]),
],
);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn empty_predicate() {
let mut machine = MachineBuilder::default().build();
machine.load_module_string(
"facts",
r#"
:- discontiguous(subject_class/2).
"#,
);
let complete_answer: Vec<_> = machine
.run_query("subject_class(X, _).")
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(complete_answer, [LeafAnswer::False]);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn list_results() {
let mut machine = MachineBuilder::default().build();
machine.load_module_string(
"facts",
r#"
list([1,2,3]).
"#,
);
let complete_answer: Vec<_> = machine
.run_query("list(X).")
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([(
"X",
Term::list([Term::integer(1), Term::integer(2), Term::integer(3)]),
)])],
);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn consult() {
let mut machine = MachineBuilder::default().build();
machine.consult_module_string(
"facts",
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
);
let query = r#"triple("a",P,"b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(
complete_answer,
[
LeafAnswer::from_bindings([("P", Term::string("p1"))]),
LeafAnswer::from_bindings([("P", Term::string("p2"))]),
],
);
let query = r#"triple("a","p1","b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::True],);
let query = r#"triple("x","y","z")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False],);
machine.consult_module_string(
"facts",
r#"
triple("a", "new", "b").
"#,
);
let query = r#"triple("a","p1","b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False],);
let query = r#"triple("a","new","b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::True]);
}
/*
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
#[ignore = "uses old flawed interface"]
fn integration_test() {
let mut machine = MachineBuilder::default().build();
// File with test commands, i.e. program code to consult and queries to run
let code = include_str!("./lib_integration_test_commands.txt");
// Split the code into blocks
let blocks = code.split("=====");
let mut i = 0;
let mut last_result: Option<_> = None;
// Iterate over the blocks
for block in blocks {
// Trim the block to remove any leading or trailing whitespace
let block = block.trim();
// Skip empty blocks
if block.is_empty() {
continue;
}
// Check if the block is a query
if let Some(query) = block.strip_prefix("query") {
// Parse and execute the query
let result = machine.run_query(query.to_string());
assert!(result.is_ok());
last_result = Some(result);
} else if let Some(code) = block.strip_prefix("consult") {
// Load the code into the machine
machine.consult_module_string("facts", code.to_string());
} else if let Some(result) = block.strip_prefix("result") {
i += 1;
if let Some(Ok(ref last_result)) = last_result {
println!("\n\n=====Result No. {i}=======\n{last_result}\n===============");
assert_eq!(last_result.to_string(), result.to_string().trim(),)
}
}
}
}
*/
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn findall() {
let mut machine = MachineBuilder::default().build();
machine.consult_module_string(
"facts",
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
);
let query = r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([(
"Result",
Term::list([
Term::list([Term::string("p1"), Term::string("b")]),
Term::list([Term::string("p2"), Term::string("b")]),
])
)])]
);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn dont_return_partial_matches() {
let mut machine = MachineBuilder::default().build();
machine.consult_module_string(
"facts",
r#"
:- discontiguous(property_resolve/2).
subject_class("Todo", c).
"#,
);
let query = r#"property_resolve(C, "isLiked"), subject_class("Todo", C)."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False]);
let query = r#"subject_class("Todo", C), property_resolve(C, "isLiked")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False]);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn dont_return_partial_matches_without_discountiguous() {
let mut machine = MachineBuilder::default().build();
machine.consult_module_string(
"facts",
r#"
a("true for a").
b("true for b").
"#,
);
let query = r#"a("true for a")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::True]);
let query = r#"a("true for a"), b("true for b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::True]);
let query = r#"a("true for b"), b("true for b")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False]);
let query = r#"a("true for a"), b("true for a")."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False]);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn non_existent_predicate_should_not_cause_panic_when_other_predicates_are_defined() {
let mut machine = MachineBuilder::default().build();
machine.consult_module_string(
"facts",
r#"
triple("a", "p1", "b").
triple("a", "p2", "b").
"#,
);
let query = r#"non_existent_predicate("a","p1","b")."#;
let complete_answer: Result<Vec<_>, _> = machine.run_query(query).collect();
assert_eq!(
complete_answer,
Err(Term::compound(
"error",
[
Term::compound(
"existence_error",
[
Term::atom("procedure"),
Term::compound(
"/",
[Term::atom("non_existent_predicate"), Term::integer(3)],
),
],
),
Term::compound(
"/",
[Term::atom("non_existent_predicate"), Term::integer(3)]
),
],
))
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn atom_quoting() {
let mut machine = MachineBuilder::default().build();
let query = "X = '.'.";
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([("X", Term::atom("."))])]
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn rational_number() {
use crate::parser::dashu::rational::RBig;
let mut machine = MachineBuilder::default().build();
let query = "X is 1 rdiv 2.";
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([(
"X",
Term::rational(RBig::from_parts(1.into(), 2u32.into()))
)])]
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn big_integer() {
use crate::parser::dashu::integer::IBig;
let mut machine = MachineBuilder::default().build();
let query = "X is 10^100.";
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([(
"X",
Term::integer(IBig::from(10).pow(100))
)])],
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn complicated_term() {
let mut machine = MachineBuilder::default().build();
let query = r#"X = a("asdf", [42, 2.54, asdf, a, [a,b|_], Z])."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
let expected = Term::Compound(
// Compound term
"a".into(),
vec![
Term::String("asdf".into()), // String
Term::List(vec![
Term::Integer(42.into()), // Fixnum
Term::Float(2.54), // Float
Term::Atom("asdf".into()), // Atom
Term::Atom("a".into()), // Char
Term::Compound(
// Partial string
".".into(),
vec![
Term::Atom("a".into()),
Term::Compound(
".".into(),
vec![
Term::Atom("b".into()),
Term::Var("_A".into()), // Anonymous variable
],
),
],
),
Term::Var("Z".into()), // Named variable
]),
],
);
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([("X", expected)])]
);
}
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn issue_2341() {
let mut machine = MachineBuilder::default().build();
machine.load_module_string(
"facts",
r#"
male(stephen).
parent(albert,edward).
father(F,C):-parent(F,C),male(F).
"#,
);
let query = r#"father(F,C)."#;
let complete_answer: Vec<_> = machine.run_query(query).collect::<Result<_, _>>().unwrap();
assert_eq!(complete_answer, [LeafAnswer::False]);
}
#[test]
#[cfg_attr(miri, ignore)]
fn query_iterator_determinism() {
let mut machine = MachineBuilder::default().build();
{
let mut iterator = machine.run_query("X = 1.");
iterator.next();
assert_eq!(iterator.next(), None);
}
{
let mut iterator = machine.run_query("X = 1 ; false.");
iterator.next();
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
assert_eq!(iterator.next(), None);
}
{
let mut iterator = machine.run_query("false.");
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
assert_eq!(iterator.next(), None);
}
}
#[test]
#[cfg_attr(miri, ignore)]
fn query_iterator_backtracking_when_no_variables() {
let mut machine = MachineBuilder::default().build();
let mut iterator = machine.run_query("true;false.");
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::True)));
assert_eq!(iterator.next(), Some(Ok(LeafAnswer::False)));
assert_eq!(iterator.next(), None);
}
#[test]
#[cfg_attr(miri, ignore)]
fn differentiate_anonymous_variables() {
let mut machine = MachineBuilder::default().build();
let complete_answer: Vec<_> = machine
.run_query("A = [_,_], _B = 1 ; B = [_,_].")
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
complete_answer,
[
LeafAnswer::from_bindings([
(
"A",
Term::list([Term::variable("_A"), Term::variable("_C")])
),
("_B", Term::integer(1)),
]),
LeafAnswer::from_bindings([(
"B",
Term::list([Term::variable("_A"), Term::variable("_C")])
),]),
]
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn order_of_variables_in_binding() {
let mut machine = MachineBuilder::default().build();
let complete_answer: Vec<_> = machine
.run_query("X = Y, Z = W.")
.collect::<Result<_, _>>()
.unwrap();
assert_eq!(
complete_answer,
[LeafAnswer::from_bindings([
("X", Term::variable("Y")),
("Z", Term::variable("W")),
])]
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn errors_and_exceptions() {
let mut machine = MachineBuilder::default().build();
let complete_answer: Vec<_> = machine.run_query("functor(_,_,_).").collect();
assert_eq!(
complete_answer,
[Err(Term::compound(
"error",
[
Term::atom("instantiation_error"),
Term::compound("/", [Term::atom("functor"), Term::integer(3)]),
],
))]
);
let complete_answer: Vec<_> = machine.run_query("throw(a).").collect();
assert_eq!(
complete_answer,
[Ok(LeafAnswer::Exception(Term::atom("a")))]
);
}

View File

@@ -2,14 +2,12 @@ use crate::forms::*;
use crate::machine::loader::*;
use crate::machine::machine_errors::*;
use crate::machine::machine_indices::*;
use crate::machine::preprocessor::*;
use crate::machine::term_stream::*;
use crate::machine::*;
use crate::parser::ast::*;
use fxhash::FxBuildHasher;
use indexmap::IndexSet;
pub use ref_thread_local::RefThreadLocal;
use std::collections::VecDeque;
use std::fs::File;
@@ -17,35 +15,38 @@ use std::mem;
pub(super) type ModuleOpExports = Vec<(OpDecl, Option<OpDesc>)>;
pub(super) fn set_code_index(
retraction_info: &mut RetractionInfo,
pub(super) fn set_code_index<'a, LS: LoadState<'a>>(
payload: &mut <LS as LoadState<'a>>::LoaderFieldType,
compilation_target: &CompilationTarget,
key: PredicateKey,
mut code_index: CodeIndex,
code_idx: CodeIndex,
code_ptr: IndexPtr,
) {
let record = match compilation_target {
CompilationTarget::User => {
if IndexPtrTag::Undefined == code_index.get().tag() {
code_index.set(code_ptr);
RetractionRecord::AddedUserPredicate(key)
} else {
let replaced = code_index.replace(code_ptr);
RetractionRecord::ReplacedUserPredicate(key, replaced)
let record = LS::machine_st(payload).arena.code_index_tbl.with_entry_mut(
code_idx.into(),
|code_idx_ptr| match compilation_target {
CompilationTarget::User => {
if IndexPtrTag::Undefined == code_idx_ptr.tag() {
*code_idx_ptr = code_ptr;
RetractionRecord::AddedUserPredicate(key)
} else {
let replaced = mem::replace(code_idx_ptr, code_ptr);
RetractionRecord::ReplacedUserPredicate(key, replaced)
}
}
}
CompilationTarget::Module(ref module_name) => {
if IndexPtrTag::Undefined == code_index.get().tag() {
code_index.set(code_ptr);
RetractionRecord::AddedModulePredicate(*module_name, key)
} else {
let replaced = code_index.replace(code_ptr);
RetractionRecord::ReplacedModulePredicate(*module_name, key, replaced)
CompilationTarget::Module(module_name) => {
if IndexPtrTag::Undefined == code_idx_ptr.tag() {
*code_idx_ptr = code_ptr;
RetractionRecord::AddedModulePredicate(*module_name, key)
} else {
let replaced = mem::replace(code_idx_ptr, code_ptr);
RetractionRecord::ReplacedModulePredicate(*module_name, key, replaced)
}
}
}
};
},
);
retraction_info.push_record(record);
payload.retraction_info.push_record(record);
}
fn add_op_decl_as_module_export<'a, LS: LoadState<'a>>(
@@ -97,7 +98,7 @@ pub(super) fn add_op_decl(
CompilationTarget::User => {
retraction_info.push_record(RetractionRecord::ReplacedUserOp(*op_decl, op_desc));
}
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
retraction_info.push_record(RetractionRecord::ReplacedModuleOp(
*module_name,
*op_decl,
@@ -109,7 +110,7 @@ pub(super) fn add_op_decl(
CompilationTarget::User => {
retraction_info.push_record(RetractionRecord::AddedUserOp(*op_decl));
}
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
retraction_info
.push_record(RetractionRecord::AddedModuleOp(*module_name, *op_decl));
}
@@ -135,21 +136,28 @@ pub(super) fn import_module_exports<'a, LS: LoadState<'a>>(
}
if let Some(src_code_index) = imported_module.code_dir.get(&key).cloned() {
let arena = &mut LS::machine_st(payload).arena;
let code_idx_tbl = &mut LS::machine_st(payload).arena.code_index_tbl;
let target_code_index = *code_dir
.entry(key)
.or_insert_with(|| CodeIndex::default(arena));
.or_insert_with(|| CodeIndex::default(code_idx_tbl));
set_code_index(
&mut payload.retraction_info,
let src_code_index_ptr = code_idx_tbl.get_entry(src_code_index.into());
set_code_index::<LS>(
payload,
compilation_target,
key,
target_code_index,
src_code_index.get(),
src_code_index_ptr,
);
if src_code_index.is_dynamic_undefined() {
if LS::machine_st(payload)
.arena
.code_index_tbl
.get_entry(src_code_index.into())
.is_dynamic_undefined()
{
code_dir.insert(key, src_code_index);
}
} else {
@@ -159,7 +167,7 @@ pub(super) fn import_module_exports<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl(
&mut payload.retraction_info,
compilation_target,
@@ -191,19 +199,20 @@ fn import_module_exports_into_module<'a, LS: LoadState<'a>>(
meta_predicates.insert(key, meta_specs.clone());
}
if let Some(src_code_index) = imported_module.code_dir.get(&key) {
let arena = &mut LS::machine_st(payload).arena;
if let Some(src_code_index) = imported_module.code_dir.get(&key).cloned() {
let code_index_tbl = &mut LS::machine_st(payload).arena.code_index_tbl;
let src_code_ptr = code_index_tbl.get_entry(src_code_index.into());
let target_code_index = *code_dir
.entry(key)
.or_insert_with(|| CodeIndex::default(arena));
.or_insert_with(|| CodeIndex::default(code_index_tbl));
set_code_index(
&mut payload.retraction_info,
set_code_index::<LS>(
payload,
compilation_target,
key,
target_code_index,
src_code_index.get(),
src_code_ptr,
);
} else {
return Err(SessionError::ModuleDoesNotContainExport(
@@ -212,7 +221,7 @@ fn import_module_exports_into_module<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl_as_module_export::<LS>(payload, op_dir, wam_op_dir, op_decl);
}
}
@@ -244,21 +253,21 @@ fn import_qualified_module_exports<'a, LS: LoadState<'a>>(
.insert(key, meta_specs.clone());
}
if let Some(src_code_index) = imported_module.code_dir.get(&key) {
let arena = &mut LS::machine_st(payload).arena;
if let Some(src_code_index) = imported_module.code_dir.get(&key).cloned() {
let code_index_tbl = &mut LS::machine_st(payload).arena.code_index_tbl;
let target_code_index = *wam_prelude
.indices
.code_dir
.entry(key)
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), arena));
let src_code_ptr = code_index_tbl.get_entry(src_code_index.into());
let target_code_index =
*wam_prelude.indices.code_dir.entry(key).or_insert_with(|| {
CodeIndex::new(IndexPtr::undefined(), code_index_tbl)
});
set_code_index(
&mut payload.retraction_info,
set_code_index::<LS>(
payload,
compilation_target,
key,
target_code_index,
src_code_index.get(),
src_code_ptr,
);
} else {
return Err(SessionError::ModuleDoesNotContainExport(
@@ -267,7 +276,7 @@ fn import_qualified_module_exports<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl(
&mut payload.retraction_info,
compilation_target,
@@ -305,19 +314,20 @@ fn import_qualified_module_exports_into_module<'a, LS: LoadState<'a>>(
meta_predicates.insert(key, meta_specs.clone());
}
if let Some(src_code_index) = imported_module.code_dir.get(&key) {
let arena = &mut LS::machine_st(payload).arena;
if let Some(src_code_index) = imported_module.code_dir.get(&key).cloned() {
let code_index_tbl = &mut LS::machine_st(payload).arena.code_index_tbl;
let src_code_ptr = code_index_tbl.get_entry(src_code_index.into());
let target_code_index = *code_dir
.entry(key)
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), arena));
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), code_index_tbl));
set_code_index(
&mut payload.retraction_info,
set_code_index::<LS>(
payload,
&payload_compilation_target,
key,
target_code_index,
src_code_index.get(),
src_code_ptr,
);
} else {
return Err(SessionError::ModuleDoesNotContainExport(
@@ -326,7 +336,7 @@ fn import_qualified_module_exports_into_module<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl_as_module_export::<LS>(payload, op_dir, wam_op_dir, op_decl);
}
}
@@ -435,19 +445,6 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
self.retract_local_clauses_impl(clause_clause_compilation_target, key, clause_locs);
}
pub(super) fn try_term_to_tl(
&mut self,
term: Term,
preprocessor: &mut Preprocessor,
) -> Result<PredicateClause, SessionError> {
let tl = preprocessor.try_term_to_tl(self, term)?;
Ok(match tl {
TopLevel::Fact(fact, var_data) => PredicateClause::Fact(fact, var_data),
TopLevel::Rule(rule, var_data) => PredicateClause::Rule(rule, var_data),
})
}
#[inline]
pub(super) fn remove_module_op_exports(&mut self) {
for (mut op_decl, record) in self.payload.module_op_exports.drain(0..) {
@@ -461,7 +458,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
}
pub(super) fn remove_replaced_in_situ_module(&mut self, module_name: Atom) {
let mut removed_module = match self.wam_prelude.indices.modules.remove(&module_name) {
let mut removed_module = match self.wam_prelude.indices.modules.swap_remove(&module_name) {
Some(module) => module,
None => return,
};
@@ -483,11 +480,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
.indices
.get_predicate_skeleton(local_compilation_target, key)
{
let old_index_ptr = code_index.replace(if global_skeleton.core.is_dynamic {
IndexPtr::dynamic_undefined()
} else {
IndexPtr::undefined()
});
let old_index_ptr = code_index.replace(
&mut LS::machine_st(&mut self.payload).arena.code_index_tbl,
if global_skeleton.core.is_dynamic {
IndexPtr::dynamic_undefined()
} else {
IndexPtr::undefined()
},
);
self.payload.retraction_info.push_record(
RetractionRecord::ReplacedModulePredicate(module_name, *key, old_index_ptr),
@@ -501,8 +501,11 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
continue;
}
if !code_index.is_undefined() && !code_index.is_dynamic_undefined() {
let old_index_ptr = code_index.replace(IndexPtr::undefined());
let code_index_tbl = &mut LS::machine_st(&mut self.payload).arena.code_index_tbl;
let code_ptr = code_index_tbl.get_entry((*code_index).into());
if !code_ptr.is_undefined() && !code_ptr.is_dynamic_undefined() {
let old_index_ptr = code_index.replace(code_index_tbl, IndexPtr::undefined());
self.payload.retraction_info.push_record(
RetractionRecord::ReplacedModulePredicate(module_name, *key, old_index_ptr),
@@ -527,40 +530,47 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
}
pub(super) fn remove_module_exports(&mut self, module_name: Atom) {
let removed_module = match self.wam_prelude.indices.modules.remove(&module_name) {
let removed_module = match self.wam_prelude.indices.modules.swap_remove(&module_name) {
Some(module) => module,
None => return,
};
fn remove_module_exports(
fn remove_module_exports<'b, LS: LoadState<'b>>(
payload: &mut <LS as LoadState<'b>>::LoaderFieldType,
removed_module: &Module,
code_dir: &mut CodeDir,
op_dir: &mut OpDir,
retraction_info: &mut RetractionInfo,
predicate_retractor: impl Fn(PredicateKey, IndexPtr) -> RetractionRecord,
op_retractor: impl Fn(OpDecl, OpDesc) -> RetractionRecord,
) {
for export in removed_module.module_decl.exports.iter() {
match export {
ModuleExport::PredicateKey(ref key) => {
match (removed_module.code_dir.get(key), code_dir.get_mut(key)) {
(Some(module_code_index), Some(target_code_index))
if module_code_index.get() == target_code_index.get() =>
{
ModuleExport::PredicateKey(key) => {
if let (Some(module_code_idx), Some(target_code_idx)) = (
removed_module.code_dir.get(key).cloned(),
code_dir.get_mut(key).cloned(),
) {
let code_index_tbl = &mut LS::machine_st(payload).arena.code_index_tbl;
let module_code_ptr = code_index_tbl.get_entry(module_code_idx.into());
let target_code_ptr = code_index_tbl.get_entry(target_code_idx.into());
if module_code_ptr == target_code_ptr {
let old_index_ptr =
target_code_index.replace(IndexPtr::undefined());
retraction_info
target_code_idx.replace(code_index_tbl, IndexPtr::undefined());
payload
.retraction_info
.push_record(predicate_retractor(*key, old_index_ptr));
}
_ => {}
}
}
ModuleExport::OpDecl(op_decl) => {
let op_dir_value_opt = op_dir
.remove(&(op_decl.name, fixity(op_decl.op_desc.get_spec() as u32)));
.swap_remove(&(op_decl.name, op_decl.op_desc.get_spec().fixity()));
if let Some(op_desc) = op_dir_value_opt {
retraction_info.push_record(op_retractor(*op_decl, op_desc));
payload
.retraction_info
.push_record(op_retractor(*op_decl, op_desc));
}
}
}
@@ -569,11 +579,11 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
match self.payload.compilation_target {
CompilationTarget::User => {
remove_module_exports(
remove_module_exports::<LS>(
&mut self.payload,
&removed_module,
&mut self.wam_prelude.indices.code_dir,
&mut self.wam_prelude.indices.op_dir,
&mut self.payload.retraction_info,
RetractionRecord::ReplacedUserPredicate,
RetractionRecord::ReplacedUserOp,
);
@@ -593,11 +603,11 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
.modules
.get_mut(&target_module_name)
{
remove_module_exports(
remove_module_exports::<LS>(
&mut self.payload,
&removed_module,
&mut module.code_dir,
&mut module.op_dir,
&mut self.payload.retraction_info,
predicate_retractor,
op_retractor,
);
@@ -623,7 +633,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
Some(ref mut module) => *module.code_dir.entry(key).or_insert_with(|| {
CodeIndex::new(
IndexPtr::undefined(),
&mut LS::machine_st(&mut self.payload).arena,
&mut LS::machine_st(&mut self.payload).arena.code_index_tbl,
)
}),
None => {
@@ -633,7 +643,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
Some(ref mut module) => *module.code_dir.entry(key).or_insert_with(|| {
CodeIndex::new(
IndexPtr::undefined(),
&mut LS::machine_st(&mut self.payload).arena,
&mut LS::machine_st(&mut self.payload).arena.code_index_tbl,
)
}),
None => {
@@ -649,7 +659,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
key: PredicateKey,
compilation_target: CompilationTarget,
) -> CodeIndex {
let arena = &mut LS::machine_st(&mut self.payload).arena;
let code_index_tbl = &mut LS::machine_st(&mut self.payload).arena.code_index_tbl;
match compilation_target {
CompilationTarget::User => *self
@@ -657,7 +667,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
.indices
.code_dir
.entry(key)
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), arena)),
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), code_index_tbl)),
CompilationTarget::Module(module_name) => {
self.get_or_insert_local_code_index(module_name, key)
}
@@ -669,15 +679,15 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
module_name: Atom,
key: PredicateKey,
) -> CodeIndex {
let arena = &mut LS::machine_st(&mut self.payload).arena;
let code_index_tbl = &mut LS::machine_st(&mut self.payload).arena.code_index_tbl;
if module_name == atom!("user") {
return *self
*self
.wam_prelude
.indices
.code_dir
.entry(key)
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), arena));
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), code_index_tbl))
} else {
self.get_or_insert_local_code_index(module_name, key)
}
@@ -1018,7 +1028,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
self.reset_in_situ_module(module_decl.clone(), &listing_src);
let mut module = match self.wam_prelude.indices.modules.remove(&module_name) {
let mut module = match self.wam_prelude.indices.modules.swap_remove(&module_name) {
Some(mut module) => {
module.listing_src = listing_src;
module
@@ -1040,7 +1050,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
);
for export in &module.module_decl.exports {
if let ModuleExport::OpDecl(ref op_decl) = export {
if let ModuleExport::OpDecl(op_decl) = export {
add_op_decl_as_module_export::<LS>(
&mut self.payload,
&mut module.op_dir,
@@ -1060,7 +1070,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
}
pub(super) fn import_module(&mut self, module_name: Atom) -> Result<(), SessionError> {
if let Some(module) = self.wam_prelude.indices.modules.remove(&module_name) {
if let Some(module) = self.wam_prelude.indices.modules.swap_remove(&module_name) {
let payload_compilation_target = self.payload.compilation_target;
match &payload_compilation_target {
@@ -1074,7 +1084,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
&mut self.wam_prelude.indices.meta_predicates,
)?;
}
CompilationTarget::Module(ref defining_module_name) => {
CompilationTarget::Module(defining_module_name) => {
match self
.wam_prelude
.indices
@@ -1116,7 +1126,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
module_name: Atom,
exports: IndexSet<ModuleExport>,
) -> Result<(), SessionError> {
if let Some(module) = self.wam_prelude.indices.modules.remove(&module_name) {
if let Some(module) = self.wam_prelude.indices.modules.swap_remove(&module_name) {
let payload_compilation_target = self.payload.compilation_target;
let result = match &payload_compilation_target {
@@ -1127,7 +1137,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
&exports,
&mut self.wam_prelude,
),
CompilationTarget::Module(ref defining_module_name) => {
CompilationTarget::Module(defining_module_name) => {
match self
.wam_prelude
.indices
@@ -1176,7 +1186,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
ListingSource::File(filename, path_buf),
)
}
ModuleSource::Library(library) => match LIBRARIES.borrow().get(&*library.as_str()) {
ModuleSource::Library(library) => match libraries::get(&library.as_str()) {
Some(code) => {
if let Some(module) = self.wam_prelude.indices.modules.get(&library) {
if let ListingSource::DynamicallyGenerated = &module.listing_src {
@@ -1257,7 +1267,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
ListingSource::File(filename, path_buf),
)
}
ModuleSource::Library(library) => match LIBRARIES.borrow().get(&*library.as_str()) {
ModuleSource::Library(library) => match libraries::get(&library.as_str()) {
Some(code) => {
if self.wam_prelude.indices.modules.contains_key(&library) {
return self.import_qualified_module(library, exports);

View File

@@ -20,6 +20,7 @@ use std::collections::VecDeque;
use std::convert::TryFrom;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
/*
* The loader compiles Prolog terms read from a TermStream instance,
@@ -45,7 +46,7 @@ use std::ops::{Deref, DerefMut};
* loader fail later, all changes must be rolled back, restoring the
* WAM to its prior state. Retraction records describe individual changes
* made by the loader, and they may be used later.
*/
*/
#[derive(Debug)]
pub(crate) enum RetractionRecord {
@@ -107,7 +108,7 @@ pub(crate) enum RetractionRecord {
* "extent" of a code vector is its length prior to an attempted
* module load. The only code vector of the WAM's IndexStore, "code",
* is shared by all modules, including the default "user" module.
*/
*/
pub(super) struct RetractionInfo {
orig_code_extent: usize,
@@ -159,7 +160,7 @@ impl fmt::Display for CompilationTarget {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CompilationTarget::User => write!(f, "user"),
CompilationTarget::Module(ref module_name) => write!(f, "{}", module_name.as_str()),
CompilationTarget::Module(module_name) => write!(f, "{}", module_name.as_str()),
}
}
}
@@ -211,7 +212,6 @@ impl PredicateQueue {
}
}
#[macro_export]
macro_rules! predicate_queue {
[$($v:expr),*] => (
PredicateQueue {
@@ -304,11 +304,15 @@ impl<'a> LoadState<'a> for LiveLoadAndMachineState<'a> {
#[inline(always)]
fn evacuate(mut loader: Loader<'a, Self>) -> Result<Self::Evacuable, SessionError> {
loader
.payload
.load_state
.set_tag(ArenaHeaderTag::InactiveLoadState);
Ok(loader.payload.load_state)
if loader.payload.load_state.get_tag() != ArenaHeaderTag::Dropped {
loader
.payload
.load_state
.set_tag(ArenaHeaderTag::InactiveLoadState);
Ok(loader.payload.load_state)
} else {
unreachable!("we never evacuate after dropping")
}
}
#[inline(always)]
@@ -319,8 +323,8 @@ impl<'a> LoadState<'a> for LiveLoadAndMachineState<'a> {
#[inline(always)]
fn reset_machine(loader: &mut Loader<'a, Self>) {
if loader.payload.load_state.get_tag() != ArenaHeaderTag::Dropped {
loader.payload.load_state.set_tag(ArenaHeaderTag::Dropped);
loader.reset_machine();
loader.payload.load_state.drop_payload();
}
}
@@ -353,7 +357,7 @@ impl<'a> LoadState<'a> for LiveLoadAndMachineState<'a> {
#[inline]
fn err_on_builtin_module_overwrite(module_name: Atom) -> Result<(), SessionError> {
if LIBRARIES.borrow().contains_key(&*module_name.as_str()) {
if libraries::contains(&module_name.as_str()) {
Err(SessionError::CannotOverwriteBuiltInModule(module_name))
} else {
Ok(())
@@ -379,7 +383,6 @@ impl<'a> LoadState<'a> for BootstrappingLoadState<'a> {
let repo_len = loader.wam_prelude.code.len();
loader.payload.retraction_info.reset(repo_len);
loader.remove_module_op_exports();
Ok(loader.payload.compilation_target)
@@ -570,7 +573,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
RetractionRecord::AddedMetaPredicate(target_module_name, key) => {
match target_module_name {
atom!("user") => {
self.wam_prelude.indices.meta_predicates.remove(&key);
self.wam_prelude.indices.meta_predicates.swap_remove(&key);
}
_ => match self
.wam_prelude
@@ -579,7 +582,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
.get_mut(&target_module_name)
{
Some(ref mut module) => {
module.meta_predicates.remove(&key);
module.meta_predicates.swap_remove(&key);
}
_ => {
unreachable!()
@@ -613,7 +616,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
}
}
RetractionRecord::AddedModule(module_name) => {
self.wam_prelude.indices.modules.remove(&module_name);
self.wam_prelude.indices.modules.swap_remove(&module_name);
}
RetractionRecord::ReplacedModule(
module_decl,
@@ -709,7 +712,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
if let Some(ref mut module) =
self.wam_prelude.indices.modules.get_mut(&module_name)
{
module.code_dir.remove(&key);
module.code_dir.swap_remove(&key);
}
}
RetractionRecord::ReplacedModulePredicate(module_name, key, old_code_idx) => {
@@ -717,7 +720,9 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
self.wam_prelude.indices.modules.get_mut(&module_name)
{
if let Some(code_idx) = module.code_dir.get_mut(&key) {
code_idx.set(old_code_idx)
let code_index_tbl =
&mut LS::machine_st(&mut self.payload).arena.code_index_tbl;
code_idx.set(code_index_tbl, old_code_idx);
}
}
}
@@ -734,11 +739,13 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
op_decl.insert_into_op_dir(&mut self.wam_prelude.indices.op_dir);
}
RetractionRecord::AddedUserPredicate(key) => {
self.wam_prelude.indices.code_dir.remove(&key);
self.wam_prelude.indices.code_dir.swap_remove(&key);
}
RetractionRecord::ReplacedUserPredicate(key, old_code_idx) => {
if let Some(code_idx) = self.wam_prelude.indices.code_dir.get_mut(&key) {
code_idx.set(old_code_idx)
let code_index_tbl =
&mut LS::machine_st(&mut self.payload).arena.code_index_tbl;
code_idx.set(code_index_tbl, old_code_idx)
}
}
RetractionRecord::AddedIndex(index_key, clause_loc) => {
@@ -759,7 +766,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
) => {
remove_constant_indices(
constant,
&overlapping_constants,
overlapping_constants,
indexing_code,
clause_loc - index_loc, // WAS: &inner_index_locs,
);
@@ -812,11 +819,8 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
if let Instruction::IndexingCode(ref mut indexing_code) =
self.wam_prelude.code[index_loc]
{
if let IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
ref mut v,
..,
)) = &mut indexing_code[0]
if let IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, v, ..)) =
&mut indexing_code[0]
{
*v = old_v;
}
@@ -1043,8 +1047,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
let cell = machine_st[r];
let export_list = machine_st.read_term_from_heap(cell);
let atom_tbl = &mut LS::machine_st(&mut self.payload).atom_tbl;
let export_list = setup_module_export_list(export_list, atom_tbl)?;
let export_list = setup_module_export_list(export_list)?;
Ok(export_list.into_iter().collect())
}
@@ -1057,7 +1060,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
self.payload.clause_clauses.push((head, body));
}
head @ Term::Literal(_, Literal::Atom(..)) | head @ Term::Clause(..) => {
head @ (Term::Clause(..) | Term::Literal(_, Literal::Atom(_))) => {
let body = Term::Literal(Cell::default(), Literal::Atom(atom!("true")));
self.payload.clause_clauses.push((head, body));
}
@@ -1104,7 +1107,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
}
}
}
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
match self.wam_prelude.indices.modules.get_mut(module_name) {
Some(ref mut module) => match module.extensible_predicates.get_mut(&key) {
Some(ref mut skeleton) => {
@@ -1215,14 +1218,18 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
* but to multifile and discontiguous predicates as well.
*/
let code_index = self.get_or_insert_code_index(key, compilation_target);
let offset = self.get_or_insert_code_index(key, compilation_target);
let code_idx_ptr = LS::machine_st(&mut self.payload)
.arena
.code_index_tbl
.get_entry(offset.into());
if code_index.is_undefined() {
set_code_index(
&mut self.payload.retraction_info,
if code_idx_ptr.is_undefined() {
set_code_index::<LS>(
&mut self.payload,
&compilation_target,
key,
code_index,
offset,
IndexPtr::dynamic_undefined(),
);
}
@@ -1286,13 +1293,16 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
fn add_clause_clause_if_dynamic(&mut self, term: &Term) -> Result<(), SessionError> {
if let Some(predicate_name) = ClauseInfo::name(term) {
let arity = ClauseInfo::arity(term);
let predicate_arity = ClauseInfo::arity(term);
let predicates_compilation_target = self.payload.predicates.compilation_target;
let is_dynamic = self
.wam_prelude
.indices
.get_predicate_skeleton(&predicates_compilation_target, &(predicate_name, arity))
.get_predicate_skeleton(
&predicates_compilation_target,
&(predicate_name, predicate_arity),
)
.map(|skeleton| skeleton.core.is_dynamic)
.unwrap_or(false);
@@ -1348,17 +1358,15 @@ impl<'a> MachinePreludeView<'a> {
pub(super) fn composite_op_dir(
&self,
compilation_target: &CompilationTarget,
) -> CompositeOpDir {
) -> CompositeOpDir<'_, '_> {
match compilation_target {
CompilationTarget::User => CompositeOpDir::new(&self.indices.op_dir, None),
CompilationTarget::Module(ref module_name) => {
match self.indices.modules.get(module_name) {
Some(module) => CompositeOpDir::new(&self.indices.op_dir, Some(&module.op_dir)),
None => {
unreachable!()
}
CompilationTarget::Module(module_name) => match self.indices.modules.get(module_name) {
Some(module) => CompositeOpDir::new(&self.indices.op_dir, Some(&module.op_dir)),
None => {
unreachable!()
}
}
},
}
}
}
@@ -1366,12 +1374,22 @@ impl<'a> MachinePreludeView<'a> {
impl MachineState {
pub(super) fn read_term_from_heap(&mut self, term_addr: HeapCellValue) -> Term {
let mut term_stack = vec![];
self.heap[0] = term_addr;
let mut iter =
stackful_post_order_iter::<NonListElider>(&mut self.heap, &mut self.stack, term_addr);
stackful_post_order_iter::<NonListElider>(&mut self.heap, &mut self.stack, 0);
while let Some(addr) = iter.next() {
let addr = unmark_cell_bits!(addr);
let addr = if addr.is_var() {
unmark_cell_bits!(heap_bound_store(
iter.base_iter.heap,
heap_bound_deref(iter.base_iter.heap, addr)
))
} else {
addr
};
read_heap_cell!(addr,
(HeapCellValueTag::Lis) => {
use crate::parser::parser::as_partial_string;
@@ -1381,42 +1399,31 @@ impl MachineState {
match as_partial_string(head, tail) {
Ok((string, Some(tail))) => {
term_stack.push(Term::PartialString(Cell::default(), string, tail));
term_stack.push(Term::PartialString(Cell::default(), Rc::new(string), tail));
}
Ok((string, None)) => {
let atom = AtomTable::build_with(&self.atom_tbl, &string);
term_stack.push(Term::CompleteString(Cell::default(), atom));
term_stack.push(Term::CompleteString(Cell::default(), Rc::new(string)));
}
Err(cons_term) => term_stack.push(cons_term),
}
}
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar, h) => {
term_stack.push(Term::Var(Cell::default(), VarPtr::from(format!("_{}", h))));
(HeapCellValueTag::Cons | HeapCellValueTag::Fixnum | HeapCellValueTag::F64Offset) => {
term_stack.push(Term::Literal(Cell::default(), Literal::try_from((addr, &self.arena.f64_tbl)).unwrap()));
}
(HeapCellValueTag::Cons | HeapCellValueTag::CStr | HeapCellValueTag::Fixnum |
HeapCellValueTag::Char | HeapCellValueTag::F64) => {
term_stack.push(Term::Literal(Cell::default(), Literal::try_from(addr).unwrap()));
(HeapCellValueTag::StackVar, h) => {
term_stack.push(Term::Var(Cell::default(), VarPtr::from(format!("s_{h}"))));
}
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar, h) => {
term_stack.push(Term::Var(Cell::default(), VarPtr::from(format!("_{h}"))));
}
(HeapCellValueTag::Atom, (name, arity)) => {
let h = iter.focus().value() as usize;
let mut arity = arity;
let value = iter.heap[h.saturating_sub(1)];
if iter.heap.len() > h + arity + 1 {
let value = iter.heap[h + arity + 1];
if let Some(idx) = get_structure_index(value) {
// in the second condition, arity == 0,
// meaning idx cannot pertain to this atom
// if it is the direct subterm of a larger
// structure.
if arity > 0 || !iter.direct_subterm_of_str(h) {
term_stack.push(
Term::Literal(Cell::default(), Literal::CodeIndex(idx))
);
arity += 1;
}
}
if let Some(idx) = get_structure_index(value) {
term_stack.push(Term::Literal(Cell::default(), Literal::CodeIndexOffset(idx.into())));
arity += 1;
}
if arity == 0 {
@@ -1429,28 +1436,22 @@ impl MachineState {
term_stack.push(Term::Clause(Cell::default(), name, subterms));
}
}
(HeapCellValueTag::PStr, atom) => {
let tail = term_stack.pop().unwrap();
if let Term::Literal(_, Literal::Atom(atom!("[]"))) = &tail {
term_stack.push(Term::CompleteString(Cell::default(), atom));
} else {
term_stack.push(Term::PartialString(
Cell::default(),
atom.as_str().to_owned(),
Box::new(tail),
));
}
}
(HeapCellValueTag::PStrLoc, h) => {
let atom = cell_as_atom_cell!(iter.heap[h]).get_name();
let HeapStringScan { string, .. } = iter.heap.scan_slice_to_str(h);
let tail = term_stack.pop().unwrap();
term_stack.push(Term::PartialString(
Cell::default(),
atom.as_str().to_owned(),
Box::new(tail),
));
term_stack.push(if matches!(tail, Term::Literal(_, Literal::Atom(atom!("[]")))) {
Term::CompleteString(
Cell::default(),
Rc::new(string.to_owned()),
)
} else {
Term::PartialString(
Cell::default(),
Rc::new(string.to_owned()),
Box::new(tail),
)
});
}
_ => {
}
@@ -1498,9 +1499,10 @@ impl Machine {
}
pub(crate) fn load_compiled_library(&mut self) -> CallResult {
let library = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let library = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
if let Some(module) = self.indices.modules.get(&library) {
if let ListingSource::DynamicallyGenerated = module.listing_src {
@@ -1531,9 +1533,10 @@ impl Machine {
}
pub(crate) fn declare_module(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let mut loader = self.loader_from_heap_evacuable(temp_v!(3));
@@ -1599,7 +1602,7 @@ impl Machine {
let predicate_name = cell_as_atom!(self.deref_register(2));
let arity = self.deref_register(3);
let arity = match Number::try_from(arity) {
let arity = match Number::try_from((arity, &self.machine_st.arena.f64_tbl)) {
Ok(Number::Integer(n)) if *n >= Integer::ZERO && *n <= Integer::from(MAX_ARITY) => {
let value: usize = (&*n).try_into().unwrap();
Ok(value)
@@ -1643,9 +1646,10 @@ impl Machine {
}
pub(crate) fn add_goal_expansion_clause(&mut self) -> CallResult {
let target_module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let target_module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let mut loader = self.loader_from_heap_evacuable(temp_v!(3));
@@ -1738,9 +1742,10 @@ impl Machine {
&mut self,
r: RegType,
) -> Loader<'_, LiveLoadAndMachineState<'_>> {
let mut load_state = cell_as_load_state_payload!(self
.machine_st
.store(self.machine_st.deref(self.machine_st[r])));
let mut load_state = cell_as_load_state_payload!(
self.machine_st
.store(self.machine_st.deref(self.machine_st[r]))
);
load_state.set_tag(ArenaHeaderTag::LiveLoadState);
@@ -1757,7 +1762,7 @@ impl Machine {
#[inline]
pub(crate) fn push_load_state_payload(&mut self) {
let payload = arena_alloc!(
let payload: TypedArenaPtr<LiveLoadState> = arena_alloc!(
LoadStatePayload::new(self.code.len(), LiveTermStream::new(ListingSource::User),),
&mut self.machine_st.arena
);
@@ -1784,11 +1789,8 @@ impl Machine {
(HeapCellValueTag::Cons, cons_ptr) => {
match_untyped_arena_ptr!(cons_ptr,
(ArenaHeaderTag::LiveLoadState, payload) => {
unsafe {
std::ptr::drop_in_place(
payload.as_ptr() as *mut LiveLoadState,
);
}
let mut payload = payload;
payload.drop_payload()
}
_ => {}
);
@@ -1805,7 +1807,7 @@ impl Machine {
pub(crate) fn push_load_context(&mut self) -> CallResult {
let stream = self.machine_st.get_stream_or_alias(
self.machine_st.registers[1],
&self.indices.stream_aliases,
&self.indices,
atom!("$push_load_context"),
2,
)?;
@@ -1839,9 +1841,7 @@ impl Machine {
let err = self.machine_st.permission_error(
Permission::Modify,
atom!("static_procedure"),
functor_stub(atom!(":"), 2)
.into_iter()
.collect::<MachineStub>(),
functor_stub(atom!(":"), 2),
);
self.machine_st
@@ -1978,7 +1978,6 @@ impl Machine {
};
let stub_gen = || functor_stub(key.0, key.1);
let head = self.deref_register(2);
if head.is_var() {
@@ -2014,7 +2013,14 @@ impl Machine {
.wam_prelude
.indices
.get_predicate_code_index(name, arity, module_name)
.map(|code_idx| code_idx.get_tag())
.map(|offset| {
loader
.payload
.machine_st
.arena
.code_index_tbl
.with_entry(offset.into(), |idx| idx.tag())
})
.unwrap_or(IndexPtrTag::DynamicUndefined);
if idx_tag == IndexPtrTag::Index {
@@ -2091,9 +2097,10 @@ impl Machine {
}
pub(crate) fn abolish_clause(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2120,14 +2127,16 @@ impl Machine {
.indices
.remove_predicate_skeleton(&compilation_target, &key)
.map(|skeleton| {
let mut clause_clause_skeleton = loader
.wam_prelude
.indices
.remove_predicate_skeleton(
let mut clause_clause_skeleton =
match loader.wam_prelude.indices.remove_predicate_skeleton(
&clause_clause_compilation_target,
&(atom!("$clause"), 2),
)
.unwrap();
) {
Some(skeleton) => skeleton,
None => {
return vec![];
}
};
let result = skeleton
.core
@@ -2157,9 +2166,16 @@ impl Machine {
.indices
.remove_predicate_skeleton(&compilation_target, &key);
let mut code_index = loader.get_or_insert_code_index(key, compilation_target);
let offset = loader.get_or_insert_code_index(key, compilation_target);
code_index.set(IndexPtr::undefined());
loader
.payload
.machine_st
.arena
.code_index_tbl
.with_entry_mut(offset.into(), |code_idx| {
*code_idx = IndexPtr::undefined();
});
loader.payload.compilation_target = clause_clause_compilation_target;
@@ -2189,7 +2205,7 @@ impl Machine {
.machine_st
.store(self.machine_st.deref(self.machine_st[temp_v!(3)]));
let target_pos = match Number::try_from(target_pos) {
let target_pos = match Number::try_from((target_pos, &self.machine_st.arena.f64_tbl)) {
Ok(Number::Integer(n)) => {
let value: usize = (&*n).try_into().unwrap();
value
@@ -2198,9 +2214,10 @@ impl Machine {
_ => unreachable!(),
};
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[4])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[4]))
);
let compilation_target = match module_name {
atom!("user") => CompilationTarget::User,
@@ -2253,9 +2270,10 @@ impl Machine {
}
pub(crate) fn is_consistent_with_term_queue(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2293,9 +2311,10 @@ impl Machine {
}
pub(crate) fn remove_module_exports(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let mut loader = self.loader_from_heap_evacuable(temp_v!(2));
@@ -2321,9 +2340,10 @@ impl Machine {
}
pub(crate) fn meta_predicate_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let (predicate_name, arity) = self
.machine_st
@@ -2339,29 +2359,38 @@ impl Machine {
.get_meta_predicate_spec(predicate_name, arity, &compilation_target)
{
Some(meta_specs) => {
let term_loc = self.machine_st.heap.len();
let term_loc = self.machine_st.heap.cell_len();
self.machine_st
.heap
.push(atom_as_cell!(predicate_name, arity));
self.machine_st
.heap
.extend(meta_specs.iter().map(|meta_spec| match meta_spec {
MetaSpec::Minus => atom_as_cell!(atom!("+")),
MetaSpec::Plus => atom_as_cell!(atom!("-")),
MetaSpec::Either => atom_as_cell!(atom!("?")),
MetaSpec::Colon => atom_as_cell!(atom!(":")),
MetaSpec::RequiresExpansionWithArgument(ref arg_num) => {
fixnum_as_cell!(Fixnum::build_with(*arg_num as i64))
}
}));
let mut writer = match self.machine_st.heap.reserve(3 + meta_specs.len()) {
Ok(writer) => writer,
Err(err) => {
self.machine_st.throw_resource_error(err);
return;
}
};
let heap_loc = self.machine_st.heap.len();
writer.write_with(|section| {
section.push_cell(atom_as_cell!(predicate_name, arity));
self.machine_st
.heap
.push(atom_as_cell!(atom!("meta_predicate"), 1));
self.machine_st.heap.push(str_loc_as_cell!(term_loc));
for meta_spec in meta_specs.iter() {
section.push_cell(match meta_spec {
MetaSpec::Minus => atom_as_cell!(atom!("+")),
MetaSpec::Plus => atom_as_cell!(atom!("-")),
MetaSpec::Either => atom_as_cell!(atom!("?")),
MetaSpec::Colon => atom_as_cell!(atom!(":")),
MetaSpec::RequiresExpansionWithArgument(arg_num) => {
fixnum_as_cell!(/* FIXME this is not safe */ unsafe {
Fixnum::build_with_unchecked(*arg_num as i64)
})
}
});
}
section.push_cell(atom_as_cell!(atom!("meta_predicate"), 1));
section.push_cell(str_loc_as_cell!(term_loc));
});
let heap_loc = self.machine_st.heap.cell_len() - 2;
unify!(
self.machine_st,
@@ -2376,9 +2405,10 @@ impl Machine {
}
pub(crate) fn dynamic_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2403,9 +2433,10 @@ impl Machine {
}
pub(crate) fn multifile_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2430,9 +2461,10 @@ impl Machine {
}
pub(crate) fn discontiguous_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More