661 Commits

Author SHA1 Message Date
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
231 changed files with 24045 additions and 14166 deletions

0
.dockerignore Executable file → Normal file
View File

1
.envrc Normal file
View File

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

View File

@@ -33,7 +33,7 @@ runs:
echo "CC=clang" >> $GITHUB_ENV
echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
@@ -46,7 +46,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@v4.1.0
with:
main: bash ./.github/actions/setup-rust/cleanup.sh
post: bash ./.github/actions/setup-rust/cleanup.sh

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@v4
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
@@ -30,37 +32,54 @@ 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@v4
- 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: macos-latest, rust-version: stable, target: 'x86_64-apple-darwin', publish: 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: '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: 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'}
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri"}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actionhippie/swap-space@v1
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
@@ -68,6 +87,10 @@ jobs:
- name: Test
run: cargo test --target ${{ matrix.target }} ${{ matrix.test-args }} --all
- name: Check miri
if: matrix.miri
run: cargo miri test
# 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,25 +99,36 @@ 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@v4
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@v4
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
with:
@@ -113,12 +147,12 @@ 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@v4
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@v4
with:
name: logtalk-test-results
path: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
@@ -133,24 +167,26 @@ jobs:
report:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- 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
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 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@v4
with:
name: cargo-test-results
path: cargo_test_results.xml
@@ -169,7 +205,7 @@ jobs:
- run: cargo bench --bench run_criterion -- --profile-time 60
- name: Publish benchmark results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: |
@@ -183,20 +219,20 @@ jobs:
needs: [build-test]
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
- 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@v2
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@v4
# 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@v3
# 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@v3
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@v5
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@v6
# 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

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
src/static_atoms.rs
target/
.direnv/

2601
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "scryer-prolog"
version = "0.9.4"
version = "0.10.0"
authors = ["Mark Thom <markjordanthom@gmail.com>"]
edition = "2021"
description = "A modern Prolog implementation written mostly in Rust."
@@ -10,7 +10,8 @@ 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.85"
[lib]
crate-type = ["cdylib", "rlib"]
@@ -22,74 +23,88 @@ repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
hostname = ["dep:hostname"]
tls = ["dep:native-tls"]
http = ["dep:warp", "dep:reqwest"]
rust_beta_channel = []
crypto-full = []
[lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = [
'cfg(rust_version, values("1.87.0"))',
] }
[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'] }
indexmap = "2.3.0"
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"
version_check = "0.9.5"
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"
git-version = "0.3.9"
indexmap = "2.3.0"
lazy_static = "1.5.0"
lexical = "7.0.4"
libc = "0.2.155"
libloading = "0.8"
scryer-modular-bitfield = "0.11.4"
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"] }
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 = "4.0.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 = "14.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 +114,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 +124,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 +146,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,14 @@ 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.9.4*. 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-094/scryer-prolog_windows-latest_x86_64-pc-windows-msvc.zip) |
| macOS (Intel) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_macos-11_x86_64-apple-darwin.zip) |
| macOS (ARM) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog-macos-arm.zip) |
| Linux (Ubuntu 20.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu.zip) |
| Linux (Ubuntu 22.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/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-094/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.

230
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,7 +72,7 @@ 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.
- [ ] 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.
@@ -113,12 +112,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 +125,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 +145,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 lates 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 referece to 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 +182,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 +197,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 +289,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 +529,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 +761,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 +868,42 @@ 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)
- 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).
- **Save the date:** The [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/)
will take place on Nov.&nbsp;13th and 14th&nbsp;2025 in Düsseldorf, Germany.
Its [announcement](https://github.com/mthom/scryer-prolog/discussions/2948).
## Support and discussions
If Scryer Prolog crashes or yields unexpected errors, consider filing

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> {
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

@@ -5,43 +5,48 @@ use instructions_template::generate_instructions_rs;
use static_string_indexing::index_static_strings;
use std::env;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR_STR;
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)> {
let mut libraries = vec![];
let entries = match current_dir.read_dir() {
Ok(entries) => entries,
Err(_) => return,
Err(_) => return libraries,
};
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.push((lib_name, entry));
}
}
}
libraries
}
fn main() {
if version_check::is_min_version("1.87.0").unwrap_or(false) {
println!(r#"cargo:rustc-cfg=rust_version="1.87.0""#);
}
let has_rustfmt = Command::new("rustfmt")
.arg("--version")
.stdin(Stdio::inherit())
@@ -56,18 +61,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

@@ -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,44 @@ 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 mut static_str_keys = vec![];
let mut static_strs = vec![];
let mut static_str_indices = vec![];
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());
static_str_keys.push(string);
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 } },)*
};
}
}

14
doclog.config.pl Normal file
View File

@@ -0,0 +1,14 @@
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"),
page("Test page", "First steps", "test-page.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 compund 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 arithmethic system of Prolog, but it's valid with clpz. clpz allows us to have a more declarative arithmethic, 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. Aditionally 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)

3
learn/test-page.dj Normal file
View File

@@ -0,0 +1,3 @@
# Test page
This is a page about Scryer Prolog

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

@@ -1,3 +1,5 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::allocator::*;
use crate::arena::*;
use crate::atom_table::*;
@@ -5,6 +7,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,7 +19,7 @@ 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};
@@ -86,7 +89,7 @@ impl<'a> ArithInstructionIterator<'a> {
#[derive(Debug)]
pub(crate) enum ArithTermRef<'a> {
Literal(&'a Literal),
Literal(Literal),
Op(Atom, usize), // name, arity.
Var(Level, &'a Cell<VarReg>, VarPtr),
}
@@ -115,7 +118,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)));
}
@@ -157,7 +160,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 +171,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)),
}
@@ -315,7 +320,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();
@@ -352,36 +357,43 @@ impl<'a> ArithmeticEvaluator<'a> {
}
// 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());
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)))
}
}
}
@@ -545,26 +557,8 @@ impl PartialEq for Number {
(&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::Integer(ref n1), Number::Rational(ref n2)) => n1.num_eq(&**n2),
(Number::Rational(ref n1), Number::Integer(ref n2)) => n1.num_eq(&**n2),
(Number::Rational(ref n1), &Number::Float(n2)) => {
OrderedFloat(n1.to_f64().value()).eq(&n2)
}
@@ -643,24 +637,10 @@ impl Ord for Number {
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 +654,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 +673,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,7 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::parser::ast::MAX_ARITY;
use crate::raw_block::*;
use crate::rcu::{Rcu, RcuRef};
use crate::types::*;
use std::cmp::Ordering;
@@ -8,26 +9,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::atomic::Arcu;
use arcu::epoch_counters::GlobalEpochCounterPool;
use arcu::rcu_ref::RcuRef;
use arcu::Rcu;
use indexmap::IndexSet;
use scryer_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 +148,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 +202,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 +218,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 +254,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 +272,48 @@ 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
.base
.add(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 +324,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 +339,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();
@@ -233,21 +354,25 @@ impl Atom {
}
#[inline]
pub fn as_str(&self) -> AtomString<'static> {
if self.is_static() {
AtomString::Static(STRINGS[(self.index >> 3) as usize])
} 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)) }
}))
fn inlined_str<'a>(&self) -> Option<AtomString<'a>> {
if self.is_inlined() {
Some(AtomString::Inlined(self.flat_index().to_le_bytes()))
} else {
AtomString::Static(STRINGS[(self.index >> 3) as usize])
None
}
}
#[inline]
pub fn as_str(&self) -> AtomString<'static> {
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| &ptr.data))
} else {
AtomString::Static(STRINGS[(self.index >> 1) as usize])
}
}
@@ -287,17 +412,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,7 +430,7 @@ 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())
}
}
@@ -323,10 +448,13 @@ impl AtomTable {
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);
@@ -335,21 +463,18 @@ impl AtomTable {
}
}
#[inline]
pub fn buf(&self) -> AtomTableRef<u8> {
AtomTableRef::<InnerAtomTable>::map(self.inner.active_epoch(), |inner| {
unsafe { inner.block.base.as_ref() }.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 +483,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 +494,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,26 +503,31 @@ 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;
let ptr_base = block_epoch.block.base.addr();
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_ptr.addr() - ptr_base) 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);
@@ -417,57 +544,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::*;
@@ -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) {}
@@ -377,10 +353,9 @@ fn structure_cell(term: &Term) -> Option<&Cell<RegType>> {
}
}
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(),
@@ -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)
}
@@ -602,9 +577,7 @@ impl<'b> CodeGenerator<'b> {
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) => {
@@ -632,9 +605,6 @@ impl<'b> CodeGenerator<'b> {
| Term::CompleteString(..) => {
instr!("$fail")
}
Term::Literal(_, Literal::String(_)) => {
instr!("$fail")
}
Term::Literal(..) => {
instr!("$succeed")
}
@@ -656,8 +626,7 @@ 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) => {
@@ -697,7 +666,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,7 +687,7 @@ impl<'b> CodeGenerator<'b> {
}
},
InlinedClauseType::IsNumber(..) => match terms[0] {
Term::Literal(_, Literal::Float(_))
Term::Literal(_, Literal::F64(..))
| Term::Literal(_, Literal::Rational(_))
| Term::Literal(_, Literal::Integer(_))
| Term::Literal(_, Literal::Fixnum(_)) => {
@@ -812,7 +781,6 @@ impl<'b> CodeGenerator<'b> {
// inlined predicates are never counted, so this overrides nothing.
self.add_call(code, call_instr, CallPolicy::Counted);
Ok(())
}
@@ -893,7 +861,7 @@ impl<'b> CodeGenerator<'b> {
Term::Literal(
_,
c @ Literal::Integer(_)
| c @ Literal::Float(_)
| c @ Literal::F64(..)
| c @ Literal::Rational(_)
| c @ Literal::Fixnum(_),
) => {
@@ -911,7 +879,6 @@ 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 +893,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();
@@ -1149,27 +1116,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,7 +1226,7 @@ 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);
}
}

View File

@@ -1,6 +1,6 @@
use crate::allocator::*;
use crate::codegen::SubsumedBranchHits;
use crate::forms::Level;
use crate::forms::{GenContext, Level};
use crate::instructions::*;
use crate::machine::disjuncts::VarData;
use crate::parser::ast::*;
@@ -133,7 +133,10 @@ impl BranchStack {
}
#[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..)
}
@@ -251,7 +254,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);
}
@@ -534,7 +537,9 @@ impl DebrayAllocator {
VarAlloc::Temp { safety, .. } => {
*safety = VarSafetyStatus::unneeded(branch_designator);
}
_ => unreachable!(),
_ => {
unreachable!()
}
}
}
@@ -679,7 +684,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 +701,8 @@ impl Allocator for DebrayAllocator {
code.push_back(Target::argument_to_variable(r, k));
}
};
r
}
fn mark_non_var<'a, Target: CompilationTarget<'a>>(

1214
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::*;
@@ -24,22 +24,8 @@ use std::fmt;
use std::ops::{AddAssign, Deref, DerefMut};
use std::path::PathBuf;
use crate::{is_infix, is_postfix};
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,
@@ -84,6 +70,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,
@@ -123,7 +131,7 @@ impl ChunkType {
#[derive(Debug)]
pub enum ChunkedTerms {
Branch(Vec<VecDeque<ChunkedTerms>>),
Chunk(VecDeque<QueryTerm>),
Chunk { terms: VecDeque<QueryTerm> },
}
#[derive(Debug)]
@@ -161,35 +169,32 @@ impl ChunkedTermVec {
.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]));
}
}
}
#[inline]
pub fn add_chunk(&mut self) {
self.chunk_vec
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![])));
let chunk = ChunkedTerms::Chunk {
terms: VecDeque::from(vec![]),
};
self.chunk_vec.push_back(chunk);
}
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])));
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);
}
}
}
@@ -366,9 +371,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 +408,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 +424,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 +442,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 +450,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));
@@ -631,9 +626,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 +697,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 +720,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 +742,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),
}
}
@@ -794,10 +805,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
}

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 indexmap::indexmap;
use std::string::String;
use FunctorElement::*;
#[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();
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,4 +1,4 @@
use std::io::BufRead;
use bytes::{buf::Reader, Bytes};
use std::sync::{Arc, Condvar, Mutex};
use warp::http;
@@ -19,5 +19,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() {
@@ -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() {
@@ -235,8 +236,8 @@ 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 {
@@ -316,7 +317,7 @@ 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();
@@ -633,12 +634,15 @@ pub(crate) fn merge_clause_index(
match &opt_arg_index_key {
OptArgIndexKey::Literal(_, index_loc, constant, ref 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 +668,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,
) {
@@ -693,7 +697,7 @@ 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(
@@ -701,13 +705,11 @@ pub(crate) fn remove_constant_indices(
)) => {
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)) => {
@@ -740,7 +742,7 @@ pub(crate) fn remove_constant_indices(
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
)) => {
constants.insert(*constant, ext);
constants.insert(constant, ext);
}
_ => {
unreachable!()
@@ -773,7 +775,7 @@ pub(crate) fn remove_constant_indices(
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
)) => {
constants.insert(*constant, ext);
constants.insert(constant, ext);
}
_ => {
unreachable!()
@@ -846,7 +848,7 @@ pub(crate) fn remove_structure_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)) => {
@@ -1033,7 +1035,7 @@ pub(crate) fn remove_index(
) {
match opt_arg_index_key {
OptArgIndexKey::Literal(_, _, constant, ref overlapping_constants) => {
remove_constant_indices(*constant, overlapping_constants, indexing_code, clause_loc);
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 +1097,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 +1128,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 +1176,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 +1300,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
}
@@ -1453,14 +1419,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 +1434,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 +1443,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 +1465,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 +1482,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);

View File

@@ -6,6 +6,7 @@ use crate::parser::ast::*;
use std::cell::Cell;
use std::collections::VecDeque;
use std::iter::*;
use std::rc::Rc;
use std::vec::Vec;
#[allow(clippy::borrowed_box)]
@@ -15,27 +16,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 +29,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 +47,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()),
}
}
@@ -182,11 +169,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 +229,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)]
@@ -314,14 +305,14 @@ 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)
}
@@ -336,7 +327,7 @@ pub(crate) enum ClauseItem<'a> {
FirstBranch(usize),
NextBranch,
BranchEnd(usize),
Chunk(&'a VecDeque<QueryTerm>),
Chunk { terms: &'a VecDeque<QueryTerm> },
}
#[derive(Debug)]
@@ -345,7 +336,7 @@ 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);
@@ -412,8 +403,8 @@ impl<'a> Iterator for ClauseIterator<'a> {
self.state_stack
.push(ClauseIteratorState::RemainingBranches(branches, 0));
}
ChunkedTerms::Chunk(chunk) => {
return Some(ClauseItem::Chunk(chunk));
ChunkedTerms::Chunk { ref terms } => {
return Some(ClauseItem::Chunk { terms });
}
}
}

View File

@@ -1,60 +1,83 @@
//! 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 {
pub(crate) mod instructions {
include!(concat!(env!("OUT_DIR"), "/instructions.rs"));
}
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::config::*;
pub use machine::lib_machine::*;
pub use machine::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;
use crate::machine::INTERRUPT;
console_error_panic_hook::set_once();
#[cfg(feature = "repl")]
ctrlc::set_handler(move || {
INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
})
.unwrap();
let mut wam = Machine::with_test_streams();
let bytes = wam.test_load_string(s);
String::from_utf8_lossy(&bytes).to_string()
#[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

@@ -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.
@@ -1042,7 +1045,7 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses)
).
:- meta_predicate bagof(?, 0, ?).
:- meta_predicate(bagof(?, 0, ?)).
:- non_counted_backtracking bagof/3.
@@ -1088,7 +1091,7 @@ 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.
@@ -1165,14 +1168,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 +1184,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 +1201,7 @@ assertz_(Module, Fact) :-
'$assertz'(Module, Fact, true).
:- meta_predicate retract(:).
:- meta_predicate(retract(:)).
%% retract(Clause)
%
@@ -1207,16 +1210,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 +1278,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 +1318,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 +1367,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)
@@ -1726,20 +1719,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 +2195,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 +2210,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 +2226,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

@@ -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).

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,8 +11,8 @@ 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
Types available are: `sint8`/`i8`, `uint8`/`u8`, `sint16`/`i16`, `uint16`/`u16`, `sint32`/`i32`, `uint32`/`u32`, `sint64`/`i64`,
`uint64`/`u64`, `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
@@ -27,6 +27,12 @@ ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return typ
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
```
## 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.
## Example
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
@@ -46,6 +52,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 +72,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 +126,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 take 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 lenth 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)).

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

@@ -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)).

View File

@@ -0,0 +1,187 @@
% Efforts toward literate tests with quads
:- 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(testutils).
:- use_module(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, 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 == 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(Q-QVN, A-AVN) :-
Q = ?-(G),
phrase(portray_clause_(Q), LitQ), % NB: LitQ terminates w/ newline
format("% CHECKING.. ",[]),
( A == true -> call(G)
; A == false -> ( call(G) -> false
; true
)
; phrase(unconj(A), As) ->
( length(As, N),
n_answers(N, A, AVN, ADs),
n_answers(N, G, QVN, Answers),
maplist(contains, ADs, Answers)
)
; % Otherwise, we have the ',' case of a solitary answer
call(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((?-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),

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

@@ -0,0 +1,224 @@
:- 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`
%
% Each options may be specified at most once, when an option is not specified the following defaults apply:
%
% - timeout(infinite)
%
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))
],
Options,
process_wait_option
),
'$process_wait'(Process, Exit, Timeout),
Exit = Status.
valid_timeout(timeout(infinite)).
valid_timeout(timeout(0)).
%% 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, _),
'$process_release'(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

@@ -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

@@ -11,20 +11,18 @@ 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 +49,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 +58,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 +70,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 +198,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 +455,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 +466,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 +502,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 +513,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 +629,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 +960,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 +1056,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 +1084,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 = || {
@@ -1112,9 +1124,12 @@ impl MachineState {
&ArithmeticTerm::Reg(r) => {
let value = self.store(self.deref(self[r]));
match Number::try_from(value) {
match Number::try_from((value, &self.arena.f64_tbl)) {
Ok(n) => Ok(n),
Err(_) => self.arith_eval_by_metacall(value),
Err(_) => {
self.heap[0] = value;
self.arith_eval_by_metacall(0)
}
}
}
&ArithmeticTerm::Interm(i) => Ok(mem::replace(
@@ -1128,7 +1143,7 @@ impl MachineState {
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 +1155,11 @@ 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 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 +1172,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)
}
@@ -1373,8 +1388,9 @@ impl MachineState {
(HeapCellValueTag::Fixnum, n) => {
self.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);
self.interms.push(Number::Float(fl));
}
(HeapCellValueTag::Cons, ptr) => {
match_untyped_arena_ptr!(ptr,
@@ -1423,22 +1439,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 +1463,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 +1473,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)>;
@@ -56,32 +54,36 @@ impl MachineState {
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), usize> {
let size = self.attr_var_init.bindings.len();
let iter = self
.attr_var_init
.bindings
.iter()
.map(|(ref 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<(), usize> {
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 {
@@ -105,10 +107,10 @@ impl MachineState {
});
attr_vars.dedup();
attr_vars.into_iter()
attr_vars
}
pub(super) fn verify_attr_interrupt(&mut self, p: usize, arity: usize) {
pub(super) fn verify_attr_interrupt(&mut self, p: usize, arity: usize) -> Result<(), usize> {
self.allocate(arity + 3);
let e = self.e;
@@ -118,23 +120,40 @@ impl MachineState {
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

@@ -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,
));
}
@@ -700,31 +700,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
@@ -814,7 +808,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,
));
@@ -1076,7 +1070,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 +1188,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 +1231,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 +1248,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 +1259,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 +1321,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 +1339,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 +1550,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 +1592,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)
}
}
}
@@ -1658,7 +1650,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 +1728,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 +1757,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 +1987,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 +2221,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::{channel, Receiver, Sender};
pub enum StreamConfig {
Stdio,
use rand::{rngs::StdRng, SeedableRng};
use crate::Machine;
use super::{
bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Arena, Atom,
Callback, CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState, Stream,
};
#[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 scryer_modular_bitfield::specifiers::*;
use scryer_modular_bitfield::*;
type Trail = Vec<(Ref, HeapCellValue)>;
use std::collections::BTreeMap;
use std::mem;
use std::ops::{IndexMut, Range};
#[derive(BitfieldSpecifier, 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, usize>;
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize>;
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize>;
}
pub(crate) fn copy_term<T: CopierTarget>(
target: T,
addr: HeapCellValue,
attr_var_policy: AttrVarPolicy,
) {
) -> Result<usize, usize> {
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<(), usize> {
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,108 @@ 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<(), usize> {
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 {
debug_assert_eq!(scan_tag, HeapCellValueTag::PStrOffset);
pstr_offset_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));
self.target.push(self.target[pstr_loc]);
self.target.push(self.target[pstr_loc + 1]);
let old_tail_idx = if (pstr_loc + offset + 1) % Heap::heap_cell_alignment() == 0 {
cell_index!(pstr_loc + offset) + 2
} else {
cell_index!(pstr_loc + offset) + 1
};
pstr_loc_as_cell!(threshold)
}
);
let tail_cell = self.target[old_tail_idx];
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<(), usize> {
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 +298,49 @@ 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<(), usize> {
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).unwrap();
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<(), usize> {
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 +352,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,9 +375,11 @@ impl<T: CopierTarget> CopyTermState<T> {
unreachable!()
}
);
Ok(())
}
fn copy_var(&mut self, addr: HeapCellValue) {
fn copy_var(&mut self, addr: HeapCellValue) -> Result<(), usize> {
let index = addr.get_value() as usize;
let rd = self.target.deref(addr);
let ra = self.target.store(rd);
@@ -278,7 +389,7 @@ impl<T: CopierTarget> CopyTermState<T> {
if h >= self.old_h {
*self.value_at_scan() = ra;
self.scan += 1;
return;
return Ok(());
}
}
(HeapCellValueTag::Lis, h) => {
@@ -292,47 +403,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<(), usize> {
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).unwrap();
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 +459,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<(), usize> {
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 +477,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<(), usize> {
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 +543,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 +570,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 +585,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 +852,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

@@ -95,11 +95,6 @@ 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,
@@ -205,10 +200,12 @@ impl VarData {
match build_stack.front_mut() {
Some(ChunkedTerms::Branch(_)) => {
build_stack.push_front(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
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!()
@@ -428,7 +425,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 {
@@ -583,7 +579,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(),
@@ -806,7 +802,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 +880,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,621 @@
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::rc::Rc;
use crate::atom_table;
use crate::heap_iter::{stackful_post_order_iter, NonListElider};
use crate::machine::machine_indices::VarKey;
use crate::machine::mock_wam::CompositeOpDir;
use crate::machine::{
ArenaHeaderTag, Fixnum, Number, BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS,
};
use crate::offset_table::*;
use crate::parser::ast::{Var, VarPtr};
use crate::parser::parser::{Parser, Tokens};
use crate::read::{write_term_to_heap, TermWriteResult};
use crate::types::UntypedArenaPtr;
use dashu::{Integer, Rational};
use indexmap::IndexMap;
use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine};
#[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| {
Term::try_conjunction(iter)
.map(|rest| Term::compound(",", [first.clone(), rest]))
.unwrap_or(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| {
Term::try_disjunction(iter)
.map(|rest| Term::compound(";", [first.clone(), rest]))
.unwrap_or(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();
loop {
let letter_idx = (count % 26) as u32;
letters.push(char::from_u32('A' as u32 + letter_idx).unwrap());
count /= 26;
if count == 0 {
break;
}
}
letters.into_iter().chain("_".chars()).rev().collect()
}
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().collect::<Vec<_>>().len() == 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().collect::<Vec<_>>().len() == 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().collect::<Vec<_>>().len() == 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 consumend, 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(resource_err_loc) = machine
.machine_st
.heap
.append(&machine.machine_st.ball.stub)
{
return Some(Err(Term::from_heapcell(
machine,
machine.machine_st.heap[resource_err_loc],
&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) {
// 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;
}
/// 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();
// 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,
}
}
}

View File

@@ -0,0 +1,610 @@
use super::*;
use crate::MachineBuilder;
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn programatic_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(ref 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>>(
@@ -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 {
@@ -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(
@@ -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(
@@ -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(
@@ -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() =>
{
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
@@ -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 {
@@ -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 {
@@ -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,
@@ -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,
);
@@ -1043,8 +1050,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 +1063,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));
}
@@ -1215,14 +1221,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 +1296,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,7 +1361,7 @@ 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) => {
@@ -1366,12 +1379,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 +1404,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 +1441,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),
)
});
}
_ => {
}
@@ -1599,7 +1605,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)
@@ -1757,7 +1763,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 +1790,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 +1808,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,
)?;
@@ -1978,7 +1981,6 @@ impl Machine {
};
let stub_gen = || functor_stub(key.0, key.1);
let head = self.deref_register(2);
if head.is_var() {
@@ -2014,7 +2016,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 {
@@ -2120,14 +2129,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 +2168,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 +2207,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
@@ -2339,29 +2357,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_loc) => {
self.machine_st.throw_resource_error(err_loc);
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(ref 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,

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,15 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::parser::ast::*;
use crate::arena::*;
use crate::atom_table::*;
use crate::forms::*;
use crate::machine::loader::*;
use crate::machine::machine_state::*;
use crate::machine::streams::Stream;
use crate::machine::streams::{Stream, StreamOptions};
use crate::machine::ClauseType;
use crate::machine::MachineStubGen;
use crate::offset_table::*;
use fxhash::FxBuildHasher;
use indexmap::{IndexMap, IndexSet};
@@ -15,11 +18,8 @@ use scryer_modular_bitfield::{bitfield, BitfieldSpecifier};
use std::cmp::Ordering;
use std::collections::BTreeSet;
use std::ops::{Deref, DerefMut};
use crate::types::*;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct OrderedOpDirKey(pub(crate) Atom, pub(crate) Fixity);
// 7.2
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -125,10 +125,19 @@ impl IndexPtr {
pub(crate) fn is_dynamic_undefined(&self) -> bool {
matches!(self.tag(), IndexPtrTag::DynamicUndefined)
}
#[inline]
pub(crate) fn local(&self) -> Option<usize> {
match self.tag() {
IndexPtrTag::Index => Some(self.p() as usize),
IndexPtrTag::DynamicIndex => Some(self.p() as usize),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, Ord, Hash, PartialOrd, Eq, PartialEq)]
pub struct CodeIndex(TypedArenaPtr<IndexPtr>);
#[derive(Debug, Clone, Copy)] // , Ord, Hash, PartialOrd, Eq, PartialEq)]
pub struct CodeIndex(CodeIndexOffset);
#[cfg(target_pointer_width = "32")]
const_assert!(std::mem::align_of::<CodeIndex>() == 4);
@@ -136,85 +145,53 @@ const_assert!(std::mem::align_of::<CodeIndex>() == 4);
#[cfg(target_pointer_width = "64")]
const_assert!(std::mem::align_of::<CodeIndex>() == 8);
impl Deref for CodeIndex {
type Target = TypedArenaPtr<IndexPtr>;
impl From<CodeIndex> for HeapCellValue {
#[inline(always)]
fn deref(&self) -> &TypedArenaPtr<IndexPtr> {
&self.0
fn from(idx: CodeIndex) -> HeapCellValue {
HeapCellValue::from(idx.0)
}
}
impl DerefMut for CodeIndex {
impl From<CodeIndexOffset> for CodeIndex {
#[inline(always)]
fn deref_mut(&mut self) -> &mut TypedArenaPtr<IndexPtr> {
&mut self.0
fn from(offset: CodeIndexOffset) -> CodeIndex {
CodeIndex(offset)
}
}
impl From<CodeIndex> for UntypedArenaPtr {
impl From<CodeIndex> for CodeIndexOffset {
#[inline(always)]
fn from(ptr: CodeIndex) -> UntypedArenaPtr {
UntypedArenaPtr::build_with(ptr.0.as_ptr() as usize)
fn from(value: CodeIndex) -> CodeIndexOffset {
value.0
}
}
impl From<UntypedArenaPtr> for CodeIndex {
impl From<&'_ CodeIndex> for CodeIndexOffset {
#[inline(always)]
fn from(ptr: UntypedArenaPtr) -> CodeIndex {
CodeIndex(TypedArenaPtr::new(ptr.get_ptr() as *mut IndexPtr))
}
}
impl From<TypedArenaPtr<IndexPtr>> for CodeIndex {
#[inline(always)]
fn from(ptr: TypedArenaPtr<IndexPtr>) -> CodeIndex {
CodeIndex(ptr)
fn from(value: &'_ CodeIndex) -> CodeIndexOffset {
value.0
}
}
impl CodeIndex {
#[inline]
pub(crate) fn new(ptr: IndexPtr, arena: &mut Arena) -> Self {
CodeIndex(arena_alloc!(ptr, arena))
pub(crate) fn new(ptr: IndexPtr, code_index_tbl: &mut CodeIndexTable) -> Self {
CodeIndex(code_index_tbl.build_with(ptr))
}
#[inline(always)]
pub(crate) fn default(arena: &mut Arena) -> Self {
CodeIndex::new(IndexPtr::undefined(), arena)
}
pub(crate) fn local(&self) -> Option<usize> {
match self.0.tag() {
IndexPtrTag::Index => Some(self.0.p() as usize),
IndexPtrTag::DynamicIndex => Some(self.0.p() as usize),
_ => None,
}
pub(crate) fn default(code_index_tbl: &mut CodeIndexTable) -> Self {
CodeIndex::new(IndexPtr::undefined(), code_index_tbl)
}
#[inline(always)]
pub(crate) fn get(&self) -> IndexPtr {
*self.0.deref()
pub(crate) fn set(&self, code_index_tbl: &mut CodeIndexTable, value: IndexPtr) {
code_index_tbl.with_entry_mut(self.0, |idx| *idx = value);
}
#[inline(always)]
pub(crate) fn set(&mut self, value: IndexPtr) {
*self.0.deref_mut() = value;
}
#[inline(always)]
pub(crate) fn get_tag(self) -> IndexPtrTag {
self.0.tag()
}
#[inline(always)]
pub(crate) fn replace(&mut self, value: IndexPtr) -> IndexPtr {
std::mem::replace(self.0.deref_mut(), value)
}
#[inline(always)]
pub(crate) fn as_ptr(&self) -> *const IndexPtr {
self.0.as_ptr()
pub(crate) fn replace(&self, code_index_tbl: &mut CodeIndexTable, value: IndexPtr) -> IndexPtr {
code_index_tbl.with_entry_mut(self.0, |idx| std::mem::replace(idx, value))
}
}
@@ -229,7 +206,7 @@ impl VarKey {
#[inline]
pub(crate) fn to_string(&self) -> String {
match self {
VarKey::AnonVar(h) => format!("_{}", h),
VarKey::AnonVar(h) => format!("_{h}"),
VarKey::VarPtr(var) => var.borrow().to_string(),
}
}
@@ -268,8 +245,8 @@ pub struct IndexStore {
pub(super) meta_predicates: MetaPredicateDir,
pub(super) modules: ModuleDir,
pub(super) op_dir: OpDir,
pub(super) streams: StreamDir,
pub(super) stream_aliases: StreamAliasDir,
streams: StreamDir,
stream_aliases: StreamAliasDir,
}
impl IndexStore {
@@ -287,8 +264,27 @@ impl IndexStore {
}
#[inline(always)]
pub(crate) fn goal_expansion_defined(&self, key: PredicateKey) -> bool {
self.goal_expansion_indices.contains(&key)
pub(crate) fn goal_expansion_defined(&self, key: PredicateKey, module_name: Atom) -> bool {
let compilation_target = match module_name {
atom!("user") => CompilationTarget::User,
_ => CompilationTarget::Module(module_name),
};
match key {
_ if self.goal_expansion_indices.contains(&key) => true,
_ => self
.get_meta_predicate_spec(key.0, key.1, &compilation_target)
.map(|meta_specs| {
meta_specs.iter().find(|meta_spec| {
matches!(
meta_spec,
MetaSpec::Colon | MetaSpec::RequiresExpansionWithArgument(_)
)
})
})
.map(|meta_spec_opt| meta_spec_opt.is_some())
.unwrap_or(false),
}
}
pub(crate) fn get_predicate_skeleton_mut(
@@ -385,10 +381,10 @@ impl IndexStore {
key: &PredicateKey,
) -> Option<PredicateSkeleton> {
match compilation_target {
CompilationTarget::User => self.extensible_predicates.remove(key),
CompilationTarget::User => self.extensible_predicates.swap_remove(key),
CompilationTarget::Module(ref module_name) => {
if let Some(module) = self.modules.get_mut(module_name) {
module.extensible_predicates.remove(key)
module.extensible_predicates.swap_remove(key)
} else {
None
}
@@ -447,6 +443,113 @@ impl IndexStore {
}
}
pub(crate) fn add_stream(
&mut self,
stream: Stream,
stub_name: Atom,
stub_arity: usize,
) -> Result<(), MachineStubGen> {
if let Some(alias) = stream.options().get_alias() {
if self.stream_aliases.contains_key(&alias) {
return Err(Box::new(move |machine_st| {
machine_st.occupied_alias_permission_error(alias, stub_name, stub_arity)
}));
}
self.stream_aliases.insert(alias, stream);
}
self.streams.insert(stream);
Ok(())
}
pub(crate) fn remove_stream(&mut self, stream: Stream) {
if let Some(alias) = stream.options().get_alias() {
debug_assert_eq!(self.stream_aliases.get(&alias), Some(&stream));
assert!(!is_protected_alias(alias));
self.stream_aliases.swap_remove(&alias);
}
self.streams.remove(&stream);
}
pub(crate) fn update_stream_options<F: Fn(&mut StreamOptions)>(
&mut self,
mut stream: Stream,
callback: F,
) {
if let Some(prev_alias) = stream.options().get_alias() {
debug_assert_eq!(self.stream_aliases.get(&prev_alias), Some(&stream));
}
let options = stream.options_mut();
let prev_alias = options.get_alias();
callback(options);
if options.get_alias() != prev_alias {
if prev_alias.map(is_protected_alias).unwrap_or(false)
|| options
.get_alias()
.map(|alias| self.has_stream(alias))
.unwrap_or(false)
{
// user_input, user_output and user_error cannot be realiased,
// and realiasing cannot shadow an existing stream.
options.set_alias_to_atom_opt(prev_alias);
return;
}
if let Some(prev_alias) = prev_alias {
self.stream_aliases.swap_remove(&prev_alias);
}
if let Some(new_alias) = options.get_alias() {
self.stream_aliases.insert(new_alias, stream);
}
}
}
pub(crate) fn has_stream(&self, alias: Atom) -> bool {
self.stream_aliases.contains_key(&alias)
}
/// ## Warning
///
/// The returned stream's options should only be modified through
/// [`IndexStore::update_stream_options`], to avoid breaking the
/// invariants of [`IndexStore`].
pub(crate) fn get_stream(&self, alias: Atom) -> Option<Stream> {
self.stream_aliases.get(&alias).copied()
}
pub(crate) fn iter_streams<'a, R: std::ops::RangeBounds<Stream>>(
&'a self,
range: R,
) -> impl Iterator<Item = Stream> + 'a {
self.streams.range(range).copied()
}
/// Forcibly sets `alias` to `stream`.
/// If there was a previous stream with that alias, it will lose that alias.
///
/// Consider using [`add_stream`](Self::add_stream) if you wish to instead
/// return an error when stream aliases conflict.
pub(crate) fn set_stream(&mut self, alias: Atom, mut stream: Stream) {
if let Some(mut prev_stream) = self.get_stream(alias) {
if prev_stream == stream {
// Nothing to do, as the stream is already present
return;
}
prev_stream.options_mut().set_alias_to_atom_opt(None);
}
stream.options_mut().set_alias_to_atom_opt(Some(alias));
self.stream_aliases.insert(alias, stream);
self.streams.insert(stream);
}
#[inline]
pub(super) fn new() -> Self {
index_store!(
@@ -456,3 +559,13 @@ impl IndexStore {
)
}
}
/// A stream is said to have a "protected" alias if modifying its
/// alias would cause breakage in other parts of the code.
///
/// A stream with a protected alias cannot be realiased through
/// [`IndexStore::update_stream_options`]. Instead, one has to use
/// [`IndexStore::set_stream`] to do so.
fn is_protected_alias(alias: Atom) -> bool {
alias == atom!("user_input") || alias == atom!("user_output") || alias == atom!("user_error")
}

View File

@@ -21,7 +21,7 @@ use indexmap::IndexMap;
use std::convert::TryFrom;
use std::fmt;
use std::ops::{Index, IndexMut};
use std::ops::{Index, IndexMut, Range};
use std::sync::Arc;
pub(crate) type Registers = [HeapCellValue; MAX_ARITY + 1];
@@ -35,8 +35,8 @@ pub(super) enum MachineMode {
#[derive(Debug, Clone)]
pub(super) enum HeapPtr {
HeapCell(usize),
PStrChar(usize, usize),
PStrLocation(usize, usize),
PStr(usize), // Char(usize),
// PStrLocation(usize),
}
impl Default for HeapPtr {
@@ -183,47 +183,62 @@ impl IndexMut<RegType> for MachineState {
}
}
pub type CallResult = Result<(), Vec<HeapCellValue>>;
#[inline(always)]
pub fn pstr_loc_and_offset(heap: &[HeapCellValue], index: usize) -> (usize, Fixnum) {
read_heap_cell!(heap[index],
(HeapCellValueTag::PStr | HeapCellValueTag::CStr) => {
(index, Fixnum::build_with(0))
}
(HeapCellValueTag::PStrOffset, h) => {
(h, cell_as_fixnum!(heap[index+1]))
}
_ => {
unreachable!()
}
)
}
pub type CallResult<Ok = ()> = Result<Ok, MachineStub>;
// size may be an upper bound.
// true_size is calculated to compute the exact offset.
fn push_var_eq_functors<'a>(
heap: &mut Heap,
size: usize,
iter: impl Iterator<Item = (&'a VarKey, &'a HeapCellValue)>,
atom_tbl: &AtomTable,
) -> Vec<HeapCellValue> {
let mut list_of_var_eqs = vec![];
) -> Result<HeapCellValue, usize> {
let src_h = heap.cell_len();
for (var, binding) in iter {
let var_atom = AtomTable::build_with(atom_tbl, &var.to_string());
let h = heap.len();
let true_size = if size > 0 {
let mut writer = heap.reserve(2 + 5 * size)?;
heap.push(atom_as_cell!(atom!("="), 2));
heap.push(atom_as_cell!(var_atom));
heap.push(*binding);
writer
.write_with(|section| {
let mut size = 0;
list_of_var_eqs.push(str_loc_as_cell!(h));
}
for (var, binding) in iter {
let var_atom = AtomTable::build_with(atom_tbl, &var.to_string());
list_of_var_eqs
section.push_cell(atom_as_cell!(atom!("="), 2));
section.push_cell(atom_as_cell!(var_atom));
section.push_cell(*binding);
size += 1;
}
for idx in 0..size {
section.push_cell(list_loc_as_cell!(section.cell_len() + 1));
section.push_cell(str_loc_as_cell!(src_h + 3 * idx));
}
if size > 0 {
section.push_cell(empty_list_as_cell!());
}
size
})
.result
} else {
size
};
Ok(if true_size > 0 {
heap_loc_as_cell!(src_h + 3 * true_size)
} else {
empty_list_as_cell!()
})
}
#[derive(Debug)]
pub struct Ball {
pub(super) boundary: usize,
pub(super) pstr_boundary: usize,
pub(super) stub: Heap,
}
@@ -231,23 +246,44 @@ impl Ball {
pub(super) fn new() -> Self {
Ball {
boundary: 0,
pstr_boundary: 0,
stub: Heap::new(),
}
}
pub(super) fn reset(&mut self) {
self.boundary = 0;
self.pstr_boundary = 0;
self.stub.clear();
}
pub(super) fn copy_and_align(&self, h: usize) -> Heap {
pub(super) fn copy_and_align_to(&self, dest: &mut Heap) -> Result<usize, usize> {
let h = dest.cell_len();
let diff = self.boundary as i64 - h as i64;
self.stub
.iter()
.cloned()
.map(|heap_value| heap_value - diff)
.collect()
let mut dest_writer = dest.reserve(self.stub.cell_len())?;
dest_writer.write_with(|section| {
for idx in 0..self.pstr_boundary {
section.push_cell(self.stub[idx] - diff);
}
});
let mut pstr_threshold = heap_index!(self.pstr_boundary);
while pstr_threshold < heap_index!(self.stub.cell_len()) {
let HeapStringScan { string, tail_idx } = self.stub.scan_slice_to_str(pstr_threshold);
pstr_threshold += dest_writer
.write_with(|section| {
if section.push_pstr(string).is_some() {
section.push_cell(self.stub[tail_idx] - diff);
}
})
.bytes_written;
}
Ok(h)
}
}
@@ -279,21 +315,6 @@ impl<'a> IndexMut<usize> for CopyTerm<'a> {
}
impl<'a> CopierTarget for CopyTerm<'a> {
#[inline(always)]
fn threshold(&self) -> usize {
self.state.heap.len()
}
#[inline(always)]
fn push(&mut self, hcv: HeapCellValue) {
self.state.heap.push(hcv);
}
#[inline(always)]
fn push_attr_var_queue(&mut self, attr_var_loc: usize) {
self.state.attr_var_init.attr_var_queue.push(attr_var_loc);
}
#[inline(always)]
fn store(&self, value: HeapCellValue) -> HeapCellValue {
self.state.store(value)
@@ -304,10 +325,40 @@ impl<'a> CopierTarget for CopyTerm<'a> {
self.state.deref(value)
}
#[inline(always)]
fn push_attr_var_queue(&mut self, attr_var_loc: usize) {
self.state.attr_var_init.attr_var_queue.push(attr_var_loc);
}
#[inline(always)]
fn stack(&mut self) -> &mut Stack {
&mut self.state.stack
}
#[inline(always)]
fn threshold(&self) -> usize {
self.state.heap.cell_len()
}
#[inline(always)]
fn as_slice_from<'b>(&'b self, from: usize) -> Box<dyn Iterator<Item = u8> + 'b> {
Box::new(self.state.heap.as_slice()[from..].iter().cloned())
}
#[inline(always)]
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize> {
self.state.heap.copy_pstr_within(pstr_loc)
}
#[inline(always)]
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize> {
self.state.heap.reserve(num_cells)
}
#[inline(always)]
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize> {
self.state.heap.copy_slice_to_end(bounds)
}
}
#[derive(Debug)]
@@ -315,7 +366,6 @@ pub(super) struct CopyBallTerm<'a> {
attr_var_queue: &'a mut Vec<usize>,
stack: &'a mut Stack,
heap: &'a mut Heap,
heap_boundary: usize,
stub: &'a mut Heap,
}
@@ -326,13 +376,10 @@ impl<'a> CopyBallTerm<'a> {
heap: &'a mut Heap,
stub: &'a mut Heap,
) -> Self {
let hb = heap.len();
CopyBallTerm {
attr_var_queue,
stack,
heap,
heap_boundary: hb,
stub,
}
}
@@ -342,10 +389,10 @@ impl<'a> Index<usize> for CopyBallTerm<'a> {
type Output = HeapCellValue;
fn index(&self, index: usize) -> &Self::Output {
if index < self.heap_boundary {
if index < self.heap.cell_len() {
&self.heap[index]
} else {
let index = index - self.heap_boundary;
let index = index - self.heap.cell_len();
&self.stub[index]
}
}
@@ -353,10 +400,10 @@ impl<'a> Index<usize> for CopyBallTerm<'a> {
impl<'a> IndexMut<usize> for CopyBallTerm<'a> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
if index < self.heap_boundary {
if index < self.heap.cell_len() {
&mut self.heap[index]
} else {
let index = index - self.heap_boundary;
let index = index - self.heap.cell_len();
&mut self.stub[index]
}
}
@@ -364,11 +411,7 @@ impl<'a> IndexMut<usize> for CopyBallTerm<'a> {
impl<'a> CopierTarget for CopyBallTerm<'a> {
fn threshold(&self) -> usize {
self.heap_boundary + self.stub.len()
}
fn push(&mut self, value: HeapCellValue) {
self.stub.push(value);
self.heap.cell_len() + self.stub.cell_len()
}
#[inline(always)]
@@ -379,10 +422,10 @@ impl<'a> CopierTarget for CopyBallTerm<'a> {
fn store(&self, value: HeapCellValue) -> HeapCellValue {
read_heap_cell!(value,
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar, h) => {
if h < self.heap_boundary {
if h < self.heap.cell_len() {
self.heap[h]
} else {
let index = h - self.heap_boundary;
let index = h - self.heap.cell_len();
self.stub[index]
}
}
@@ -411,6 +454,45 @@ impl<'a> CopierTarget for CopyBallTerm<'a> {
fn stack(&mut self) -> &mut Stack {
self.stack
}
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize> {
debug_assert!(pstr_loc < self.heap.byte_len());
let HeapStringScan { string, tail_idx } = self.heap.scan_slice_to_str(pstr_loc);
self.stub.allocate_pstr(string)?;
Ok(tail_idx)
}
fn as_slice_from<'b>(&'b self, from: usize) -> Box<dyn Iterator<Item = u8> + 'b> {
if from < self.heap.byte_len() {
Box::new(
self.heap.as_slice()[from..]
.iter()
.cloned()
.chain(self.stub.as_slice().iter().cloned()),
)
} else {
Box::new(self.stub.as_slice()[from..].iter().cloned())
}
}
#[inline]
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize> {
self.stub.reserve(num_cells)
}
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize> {
let len = bounds.end - bounds.start;
let mut stub_writer = self.stub.reserve(len)?;
stub_writer.write_with(|section| {
for idx in bounds {
section.push_cell(self.heap[idx]);
}
});
Ok(())
}
}
impl MachineState {
@@ -461,10 +543,6 @@ impl MachineState {
let addr = self.store(self.deref(addr));
read_heap_cell!(addr,
(HeapCellValueTag::Char, c) => {
chars.push(c);
continue;
}
(HeapCellValueTag::Atom, (name, arity)) => {
if arity == 0 {
if let Some(c) = name.as_char() {
@@ -537,39 +615,26 @@ impl MachineState {
pub fn write_read_term_options(
&mut self,
mut var_list: Vec<(VarKey, HeapCellValue, usize)>,
singleton_var_list: Vec<HeapCellValue>,
singleton_heap_list: HeapCellValue,
) -> CallResult {
var_list.sort_by(|(_, _, idx_1), (_, _, idx_2)| idx_1.cmp(idx_2));
let list_of_var_eqs = push_var_eq_functors(
&mut self.heap,
var_list.iter().filter_map(|(var_name, var, _)| {
if var_name.is_anon() {
None
} else {
Some((var_name, var))
}
}),
&self.atom_tbl,
);
let singleton_addr = self.registers[3];
let singletons_offset = heap_loc_as_cell!(iter_to_heap_list(
&mut self.heap,
singleton_var_list.into_iter()
));
unify_fn!(*self, singletons_offset, singleton_addr);
unify_fn!(*self, singleton_heap_list, singleton_addr);
if self.fail {
return Ok(());
}
let vars_addr = self.registers[4];
let vars_offset = heap_loc_as_cell!(iter_to_heap_list(
&mut self.heap,
var_list.into_iter().map(|(_, cell, _)| cell)
));
let vars_offset = resource_error_call_result!(
self,
sized_iter_to_heap_list(
&mut self.heap,
var_list.len(),
var_list.iter().map(|(_, cell, _)| *cell),
)
);
unify_fn!(*self, vars_offset, vars_addr);
@@ -578,24 +643,27 @@ impl MachineState {
}
let var_names_addr = self.registers[5];
let var_names_offset = heap_loc_as_cell!(iter_to_heap_list(
&mut self.heap,
list_of_var_eqs.into_iter()
));
Ok(unify_fn!(*self, var_names_offset, var_names_addr))
let var_names_offset = resource_error_call_result!(
self,
push_var_eq_functors(
&mut self.heap,
var_list.len(),
var_list.iter().filter_map(|(var_name, var, _)| {
if var_name.is_anon() {
None
} else {
Some((var_name, var))
}
}),
&self.atom_tbl,
)
);
unify_fn!(*self, var_names_offset, var_names_addr);
Ok(())
}
pub fn read_term_body(&mut self, mut term_write_result: TermWriteResult) -> CallResult {
let heap_loc = read_heap_cell!(self.heap[term_write_result.heap_loc],
(HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset) => {
pstr_loc_as_cell!(term_write_result.heap_loc)
}
_ => {
heap_loc_as_cell!(term_write_result.heap_loc)
}
);
let heap_loc = heap_loc_as_cell!(term_write_result.heap_loc);
unify_fn!(*self, heap_loc, self.registers[2]);
if self.fail {
@@ -607,10 +675,9 @@ impl MachineState {
}
let mut singleton_var_set: IndexMap<Ref, bool> = IndexMap::new();
self.heap[0] = heap_loc;
for cell in
stackful_preorder_iter::<NonListElider>(&mut self.heap, &mut self.stack, heap_loc)
{
for cell in stackful_preorder_iter::<NonListElider>(&mut self.heap, &mut self.stack, 0) {
let cell = unmark_cell_bits!(cell);
if let Some(var) = cell.as_var() {
@@ -622,29 +689,29 @@ impl MachineState {
}
}
let singleton_var_list = push_var_eq_functors(
&mut self.heap,
term_write_result
.var_dict
.iter()
.filter(|(var_name, binding)| {
if var_name.is_anon() {
return false;
}
let singleton_var_list = resource_error_call_result!(
self,
push_var_eq_functors(
&mut self.heap,
term_write_result.var_dict.len(),
term_write_result
.var_dict
.iter()
.filter(|(var_name, binding)| {
if var_name.is_anon() {
return false;
}
if let Some(r) = binding.as_var() {
*singleton_var_set.get(&r).unwrap_or(&false)
} else {
false
}
}),
&self.atom_tbl,
if let Some(r) = binding.as_var() {
*singleton_var_set.get(&r).unwrap_or(&false)
} else {
false
}
}),
&self.atom_tbl,
)
);
for var in term_write_result.var_dict.values_mut() {
*var = heap_bound_deref(&self.heap, *var);
}
let mut var_list = Vec::with_capacity(singleton_var_set.len());
for (var_name, addr) in term_write_result.var_dict {
@@ -678,27 +745,50 @@ impl MachineState {
stream: Stream,
indices: &mut IndexStore,
) -> CallResult {
if let Stream::Readline(ptr) = stream {
unsafe {
let readline = ptr.as_ptr().as_mut().unwrap();
readline.set_atoms_for_completion(&self.atom_tbl);
return self.read_term(
stream,
indices,
MachineState::read_term_from_user_input_eof_handler,
);
}
}
if let Stream::Byte(_) = stream {
return self.read_term(
match stream {
#[cfg(feature = "http")]
Stream::HttpRead(_) => self.read_term(
stream,
indices,
MachineState::read_term_from_user_input_eof_handler,
);
}
),
#[cfg(feature = "tls")]
Stream::NamedTls(_) => self.read_term(
stream,
indices,
MachineState::read_term_from_user_input_eof_handler,
),
Stream::Readline(ptr) => {
let readline = unsafe { ptr.as_ptr().as_mut() }.unwrap();
readline.set_atoms_for_completion(&self.atom_tbl);
self.read_term(
stream,
indices,
MachineState::read_term_from_user_input_eof_handler,
)
}
Stream::Byte(_)
| Stream::InputChannel(_)
| Stream::InputFile(_)
| Stream::NamedTcp(_)
| Stream::Null(_)
| Stream::PipeReader(_)
| Stream::StaticString(_) => self.read_term(
stream,
indices,
MachineState::read_term_from_user_input_eof_handler,
),
_ => {
let stub = functor_stub(atom!("read_term_from_user_input"), 3);
let err = self.permission_error(
Permission::InputStream,
atom!("stream"),
atom_as_cell!(atom!("user_input")),
);
unreachable!("Stream must be a Stream::Readline(_)")
Err(self.error_form(err, stub))
}
}
}
pub fn read_term_eof_handler(&mut self, mut stream: Stream) -> Result<OnEOF, MachineStub> {
@@ -743,7 +833,8 @@ impl MachineState {
CompilationError::ParserError(e) if e.is_unexpected_eof() => {
match eof_handler(self, stream)? {
OnEOF::Return => {
return self.write_read_term_options(vec![], vec![])
return self
.write_read_term_options(vec![], empty_list_as_cell!());
}
OnEOF::Continue => continue,
}
@@ -792,19 +883,16 @@ impl MachineState {
}
read_heap_cell!(atom,
(HeapCellValueTag::Char, c) => {
var_names.insert(var, VarPtr::from(c.to_string()));
}
(HeapCellValueTag::Atom, (name, _arity)) => {
debug_assert_eq!(_arity, 0);
var_names.insert(var, VarPtr::from(&*name.as_str()));
var_names.insert(var, VarPtr::from(name.as_str().to_owned()));
}
(HeapCellValueTag::Str, s) => {
let (name, arity) = cell_as_atom_cell!(self.heap[s])
.get_name_and_arity();
debug_assert_eq!(arity, 0);
var_names.insert(var, VarPtr::from(&*name.as_str()));
var_names.insert(var, VarPtr::from(name.as_str().to_owned()));
}
_ => {
unreachable!();
@@ -883,13 +971,15 @@ impl MachineState {
}
);
self.heap[0] = term_to_be_printed;
let mut printer = HCPrinter::new(
&mut self.heap,
Arc::clone(&self.atom_tbl),
&mut self.stack,
&self.arena,
op_dir,
PrinterOutputter::new(),
term_to_be_printed,
0,
);
printer.ignore_ops = ignore_ops;
@@ -897,7 +987,7 @@ impl MachineState {
printer.quoted = quoted;
printer.double_quotes = double_quotes;
match Number::try_from(max_depth) {
match Number::try_from((max_depth, &self.arena.f64_tbl)) {
Ok(Number::Fixnum(n)) => {
if let Ok(n) = usize::try_from(n.get_num()) {
printer.max_depth = n;
@@ -922,7 +1012,6 @@ impl MachineState {
}
printer.var_names = var_names;
printer
}
Err(err) => {
@@ -939,7 +1028,10 @@ impl MachineState {
arity: HeapCellValue,
) -> (Atom, usize) {
let name = cell_as_atom!(self.store(self.deref(name)));
let arity = cell_as_fixnum!(self.store(self.deref(arity)));
let arity = unsafe {
self.store(self.deref(arity))
.to_fixnum_or_cut_point_unchecked()
};
(name, usize::try_from(arity.get_num()).unwrap())
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,26 @@
pub use crate::arena::*;
pub use crate::atom_table::*;
use crate::heap_print::*;
pub use crate::machine::heap::*;
pub use crate::machine::machine_state::*;
pub use crate::machine::stack::*;
pub use crate::machine::streams::*;
pub use crate::machine::*;
pub use crate::parser::ast::*;
use crate::read::*;
pub use crate::types::*;
use std::sync::Arc;
#[cfg(test)]
use crate::machine::copier::CopierTarget;
use crate::read::TermWriteResult;
#[cfg(test)]
use std::ops::{Deref, DerefMut, Index, IndexMut};
use std::ops::{Deref, DerefMut, Index, IndexMut, Range};
// a mini-WAM for test purposes.
pub struct MockWAM {
pub machine_st: MachineState,
pub op_dir: OpDir,
pub flags: MachineFlags,
//pub flags: MachineFlags,
}
#[allow(dead_code)]
impl MockWAM {
pub fn new() -> Self {
let op_dir = default_op_dir();
@@ -33,7 +28,6 @@ impl MockWAM {
Self {
machine_st: MachineState::new(),
op_dir,
flags: MachineFlags::default(),
}
}
@@ -57,16 +51,15 @@ impl MockWAM {
term_string: &'static str,
) -> Result<String, CompilationError> {
let term_write_result = self.parse_and_write_parsed_term_to_heap(term_string)?;
print_heap_terms(self.machine_st.heap.iter(), term_write_result.heap_loc);
print_heap_terms(&self.machine_st.heap, term_write_result.heap_loc);
let mut printer = HCPrinter::new(
&mut self.machine_st.heap,
Arc::clone(&self.machine_st.atom_tbl),
&mut self.machine_st.stack,
&self.machine_st.arena,
&self.op_dir,
PrinterOutputter::new(),
heap_loc_as_cell!(term_write_result.heap_loc),
term_write_result.heap_loc,
);
printer.var_names = term_write_result
@@ -97,6 +90,7 @@ pub struct TermCopyingMockWAM<'a> {
impl<'a> Index<usize> for TermCopyingMockWAM<'a> {
type Output = HeapCellValue;
#[inline]
fn index(&self, index: usize) -> &HeapCellValue {
&self.wam.machine_st.heap[index]
}
@@ -114,6 +108,7 @@ impl<'a> IndexMut<usize> for TermCopyingMockWAM<'a> {
impl<'a> Deref for TermCopyingMockWAM<'a> {
type Target = MockWAM;
#[inline]
fn deref(&self) -> &Self::Target {
self.wam
}
@@ -121,6 +116,7 @@ impl<'a> Deref for TermCopyingMockWAM<'a> {
#[cfg(test)]
impl<'a> DerefMut for TermCopyingMockWAM<'a> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.wam
}
@@ -155,10 +151,6 @@ impl<'a> CopierTarget for TermCopyingMockWAM<'a> {
}
}
fn push(&mut self, val: HeapCellValue) {
self.wam.machine_st.heap.push(val);
}
fn push_attr_var_queue(&mut self, attr_var_loc: usize) {
self.wam
.machine_st
@@ -172,43 +164,66 @@ impl<'a> CopierTarget for TermCopyingMockWAM<'a> {
}
fn threshold(&self) -> usize {
self.wam.machine_st.heap.len()
self.wam.machine_st.heap.cell_len()
}
#[inline(always)]
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize> {
self.wam.machine_st.heap.copy_pstr_within(pstr_loc)
}
#[inline(always)]
fn as_slice_from<'b>(&'b self, from: usize) -> Box<dyn Iterator<Item = u8> + 'b> {
Box::new(self.wam.machine_st.heap.as_slice()[from..].iter().cloned())
}
#[inline(always)]
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize> {
self.wam.machine_st.heap.reserve(num_cells)
}
#[inline(always)]
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize> {
self.wam.machine_st.heap.copy_slice_to_end(bounds)
}
}
#[cfg(test)]
pub fn all_cells_marked_and_unforwarded(heap: &[HeapCellValue]) {
for (idx, cell) in heap.iter().enumerate() {
pub fn all_cells_marked_and_unforwarded(heap: &Heap, offset: usize) {
for curr_idx in offset..heap.cell_len() {
let cell = heap[curr_idx];
assert!(
cell.get_mark_bit(),
"cell {:?} at index {} is not marked",
cell,
idx
"cell {cell:?} at index {curr_idx} is not marked"
);
assert!(
!cell.get_forwarding_bit(),
"cell {:?} at index {} is forwarded",
cell,
idx
"cell {cell:?} at index {curr_idx} is forwarded"
);
}
}
#[cfg(test)]
pub fn all_cells_unmarked(heap: &Heap) {
for (idx, cell) in heap.iter().enumerate() {
assert!(
!cell.get_mark_bit(),
"cell {:?} at index {} is still marked",
cell,
idx
);
pub fn unmark_all_cells(heap: &mut Heap, offset: usize) {
for idx in offset..heap.cell_len() {
heap[idx].set_mark_bit(false);
}
}
#[cfg(test)]
pub fn all_cells_unmarked(iter: &impl SizedHeap) {
let mut idx = 0;
let cell_len = iter.cell_len();
while idx < cell_len {
let curr_idx = idx;
idx += 1;
let cell = iter[curr_idx];
assert!(
!cell.get_forwarding_bit(),
"cell {:?} at index {} is still forwarded",
cell,
idx
!cell.get_mark_bit(),
"cell {cell:?} at index {curr_idx} is still marked"
);
}
}
@@ -233,10 +248,8 @@ pub(crate) fn parse_and_write_parsed_term_to_heap(
}
impl Machine {
pub fn with_test_streams() -> Self {
Machine::new(MachineConfig::in_memory())
}
/// For use in tests.
#[allow(clippy::unbuffered_bytes)]
pub fn test_load_file(&mut self, file: &str) -> Vec<u8> {
let stream = Stream::from_owned_string(
std::fs::read_to_string(AsRef::<std::path::Path>::as_ref(file)).unwrap(),
@@ -247,6 +260,8 @@ impl Machine {
self.user_output.bytes().map(|b| b.unwrap()).collect()
}
/// For use in tests.
#[allow(clippy::unbuffered_bytes)]
pub fn test_load_string(&mut self, code: &str) -> Vec<u8> {
let stream = Stream::from_owned_string(code.to_owned(), &mut self.machine_st.arena);
@@ -259,17 +274,18 @@ impl Machine {
mod tests {
use super::*;
use crate::functor_macro::FunctorElement;
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn unify_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::In), OpDesc::build_with(500, 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(700, XFX 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::In), OpDesc::build_with(500, YFX));
op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX));
op_dir.insert((atom!("="), Fixity::In), OpDesc::build_with(700, XFX));
{
parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap();
@@ -392,36 +408,66 @@ mod tests {
wam.heap.clear();
wam.heap.push(pstr_as_cell!(atom!("this is a string")));
wam.heap.push(heap_loc_as_cell!(1));
let mut writer = wam.heap.reserve(96).unwrap();
wam.heap.push(pstr_as_cell!(atom!("this is a string")));
wam.heap.push(pstr_loc_as_cell!(4));
writer.write_with(|section| {
section.push_pstr("this is a string"); // 0
wam.heap.push(pstr_offset_as_cell!(0));
wam.heap.push(fixnum_as_cell!(Fixnum::build_with(6)));
let h = section.cell_len();
assert_eq!(h, 3);
unify!(wam, pstr_loc_as_cell!(0), pstr_loc_as_cell!(2));
section.push_cell(heap_loc_as_cell!(h)); // 3
section.push_pstr("this is a string"); // 4
let h = section.cell_len();
assert_eq!(h + 1, 8);
section.push_cell(pstr_loc_as_cell!(heap_index!(h + 1))); // 7
section.push_pstr("this is a string"); // 8
section.push_cell(pstr_loc_as_cell!(heap_index!(h + 1)));
});
unify!(wam, pstr_loc_as_cell!(0), pstr_loc_as_cell!(heap_index!(4)));
assert!(!wam.fail);
assert_eq!(wam.heap[1], pstr_loc_as_cell!(4));
all_cells_unmarked(&wam.heap);
assert_eq!(
wam.heap
.slice_to_str(heap_index!(0), "this is a string".len()),
"this is a string"
);
assert_eq!(wam.heap[3], pstr_loc_as_cell!(heap_index!(8)));
assert_eq!(
wam.heap
.slice_to_str(heap_index!(4), "this is a string".len()),
"this is a string"
);
assert_eq!(wam.heap[7], pstr_loc_as_cell!(heap_index!(8)));
assert_eq!(
wam.heap
.slice_to_str(heap_index!(8), "this is a string".len()),
"this is a string"
);
assert_eq!(wam.heap[11], pstr_loc_as_cell!(heap_index!(8)));
wam.heap.clear();
wam.heap.push(list_loc_as_cell!(1));
wam.heap.push(atom_as_cell!(atom!("a")));
wam.heap.push(list_loc_as_cell!(3));
wam.heap.push(atom_as_cell!(atom!("b")));
wam.heap.push(heap_loc_as_cell!(0));
let mut writer = wam.heap.reserve(96).unwrap();
wam.heap.push(list_loc_as_cell!(6));
wam.heap.push(atom_as_cell!(atom!("a")));
wam.heap.push(list_loc_as_cell!(8));
wam.heap.push(atom_as_cell!(atom!("b")));
wam.heap.push(heap_loc_as_cell!(5));
writer.write_with(|section| {
section.push_cell(list_loc_as_cell!(1));
section.push_cell(atom_as_cell!(atom!("a")));
section.push_cell(list_loc_as_cell!(3));
section.push_cell(atom_as_cell!(atom!("b")));
section.push_cell(heap_loc_as_cell!(0));
section.push_cell(list_loc_as_cell!(6));
section.push_cell(atom_as_cell!(atom!("a")));
section.push_cell(list_loc_as_cell!(8));
section.push_cell(atom_as_cell!(atom!("b")));
section.push_cell(heap_loc_as_cell!(5));
});
unify!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(5));
@@ -431,17 +477,21 @@ mod tests {
wam.heap.clear();
wam.heap.push(list_loc_as_cell!(1));
wam.heap.push(atom_as_cell!(atom!("a")));
wam.heap.push(list_loc_as_cell!(3));
wam.heap.push(atom_as_cell!(atom!("b")));
wam.heap.push(heap_loc_as_cell!(0));
let mut writer = wam.heap.reserve(96).unwrap();
wam.heap.push(list_loc_as_cell!(6));
wam.heap.push(atom_as_cell!(atom!("a")));
wam.heap.push(list_loc_as_cell!(8));
wam.heap.push(atom_as_cell!(atom!("c")));
wam.heap.push(heap_loc_as_cell!(5));
writer.write_with(|section| {
section.push_cell(list_loc_as_cell!(1));
section.push_cell(atom_as_cell!(atom!("a")));
section.push_cell(list_loc_as_cell!(3));
section.push_cell(atom_as_cell!(atom!("b")));
section.push_cell(heap_loc_as_cell!(0));
section.push_cell(list_loc_as_cell!(6));
section.push_cell(atom_as_cell!(atom!("a")));
section.push_cell(list_loc_as_cell!(8));
section.push_cell(atom_as_cell!(atom!("c")));
section.push_cell(heap_loc_as_cell!(5));
});
unify!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(5));
@@ -449,19 +499,24 @@ mod tests {
wam.fail = false;
all_cells_unmarked(&wam.heap);
wam.heap.clear();
wam.heap.push(list_loc_as_cell!(1));
wam.heap.push(atom_as_cell!(atom!("a")));
wam.heap.push(list_loc_as_cell!(3));
wam.heap.push(atom_as_cell!(atom!("b")));
wam.heap.push(heap_loc_as_cell!(5));
let mut writer = wam.heap.reserve(96).unwrap();
wam.heap.push(list_loc_as_cell!(6));
wam.heap.push(atom_as_cell!(atom!("a")));
wam.heap.push(list_loc_as_cell!(8));
wam.heap.push(atom_as_cell!(atom!("b")));
wam.heap.push(heap_loc_as_cell!(0));
writer.write_with(|section| {
section.push_cell(list_loc_as_cell!(1));
section.push_cell(atom_as_cell!(atom!("a")));
section.push_cell(list_loc_as_cell!(3));
section.push_cell(atom_as_cell!(atom!("b")));
section.push_cell(heap_loc_as_cell!(5));
section.push_cell(list_loc_as_cell!(6));
section.push_cell(atom_as_cell!(atom!("a")));
section.push_cell(list_loc_as_cell!(8));
section.push_cell(atom_as_cell!(atom!("b")));
section.push_cell(heap_loc_as_cell!(0));
});
unify!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(5));
@@ -473,7 +528,7 @@ mod tests {
let term_write_result_1 =
parse_and_write_parsed_term_to_heap(&mut wam, "X = g(X,y).", &op_dir).unwrap();
print_heap_terms(wam.heap.iter(), term_write_result_1.heap_loc);
print_heap_terms(&wam.heap, term_write_result_1.heap_loc);
unify!(wam, heap_loc_as_cell!(2), str_loc_as_cell!(4));
@@ -482,15 +537,14 @@ mod tests {
}
#[test]
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
fn test_unify_with_occurs_check() {
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::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::In), OpDesc::build_with(400, YFX));
op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX));
{
parse_and_write_parsed_term_to_heap(&mut wam, "f(X,X).", &op_dir).unwrap();
@@ -516,8 +570,15 @@ mod tests {
let mut wam = MachineState::new();
wam.heap.push(heap_loc_as_cell!(0));
wam.heap.push(heap_loc_as_cell!(1));
// clear the heap of resource error data etc
wam.heap.clear();
let mut writer = wam.heap.reserve(96).unwrap();
writer.write_with(|section| {
section.push_cell(heap_loc_as_cell!(0));
section.push_cell(heap_loc_as_cell!(1));
});
assert_eq!(
compare_term_test!(wam, wam.heap[0], wam.heap[1]),
@@ -539,12 +600,10 @@ mod tests {
Some(Ordering::Equal)
);
let cstr_cell = wam.heap.allocate_cstr("string").unwrap();
assert_eq!(
compare_term_test!(
wam,
atom_as_cell!(atom!("atom")),
atom_as_cstr_cell!(atom!("string"))
),
compare_term_test!(wam, atom_as_cell!(atom!("atom")), cstr_cell),
Some(Ordering::Less)
);
@@ -577,8 +636,12 @@ mod tests {
wam.heap.clear();
wam.heap.push(atom_as_cell!(atom!("f"), 1));
wam.heap.push(heap_loc_as_cell!(1));
let mut writer = wam.heap.reserve(96).unwrap();
writer.write_with(|section| {
section.push_cell(atom_as_cell!(atom!("f"), 1));
section.push_cell(heap_loc_as_cell!(1));
});
assert_eq!(
compare_term_test!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(0)),
@@ -592,21 +655,25 @@ mod tests {
wam.heap.clear();
// [1,2,3]
wam.heap.push(list_loc_as_cell!(1));
wam.heap.push(fixnum_as_cell!(Fixnum::build_with(1)));
wam.heap.push(list_loc_as_cell!(3));
wam.heap.push(fixnum_as_cell!(Fixnum::build_with(2)));
wam.heap.push(list_loc_as_cell!(5));
wam.heap.push(fixnum_as_cell!(Fixnum::build_with(3)));
wam.heap.push(empty_list_as_cell!());
let mut writer = wam.heap.reserve(96).unwrap();
// [1,2]
wam.heap.push(list_loc_as_cell!(8));
wam.heap.push(fixnum_as_cell!(Fixnum::build_with(1)));
wam.heap.push(list_loc_as_cell!(10));
wam.heap.push(fixnum_as_cell!(Fixnum::build_with(2)));
wam.heap.push(empty_list_as_cell!());
writer.write_with(|section| {
// [1,2,3]
section.push_cell(list_loc_as_cell!(1));
section.push_cell(fixnum_as_cell!(Fixnum::build_with(1)));
section.push_cell(list_loc_as_cell!(3));
section.push_cell(fixnum_as_cell!(Fixnum::build_with(2)));
section.push_cell(list_loc_as_cell!(5));
section.push_cell(fixnum_as_cell!(Fixnum::build_with(3)));
section.push_cell(empty_list_as_cell!());
// [1,2]
section.push_cell(list_loc_as_cell!(8));
section.push_cell(fixnum_as_cell!(Fixnum::build_with(1)));
section.push_cell(list_loc_as_cell!(10));
section.push_cell(fixnum_as_cell!(Fixnum::build_with(2)));
section.push_cell(empty_list_as_cell!());
});
assert_eq!(
compare_term_test!(wam, heap_loc_as_cell!(7), heap_loc_as_cell!(7)),
@@ -632,12 +699,10 @@ mod tests {
Some(Ordering::Greater)
);
let cstr_cell = wam.heap.allocate_cstr("string").unwrap();
assert_eq!(
compare_term_test!(
wam,
empty_list_as_cell!(),
atom_as_cstr_cell!(atom!("string"))
),
compare_term_test!(wam, empty_list_as_cell!(), cstr_cell),
Some(Ordering::Less)
);
@@ -668,55 +733,63 @@ mod tests {
fn is_cyclic_term_tests() {
let mut wam = MachineState::new();
assert!(!wam.is_cyclic_term(atom_as_cell!(atom!("f"))));
assert!(!wam.is_cyclic_term(fixnum_as_cell!(Fixnum::build_with(555))));
let mut writer = wam.heap.reserve(96).unwrap();
wam.heap.push(heap_loc_as_cell!(0));
writer.write_with(|section| {
section.push_cell(atom_as_cell!(atom!("f")));
section.push_cell(fixnum_as_cell!(Fixnum::build_with(555)));
section.push_cell(heap_loc_as_cell!(0));
});
assert!(!wam.is_cyclic_term(heap_loc_as_cell!(0)));
assert!(!wam.is_cyclic_term(0));
assert!(!wam.is_cyclic_term(1));
assert!(!wam.is_cyclic_term(2));
all_cells_unmarked(&wam.heap);
wam.heap.clear();
wam.heap
.extend(functor!(atom!("f"), [atom(atom!("a")), atom(atom!("b"))]));
let mut functor_writer = Heap::functor_writer(functor!(
atom!("f"),
[atom_as_cell((atom!("a"))), atom_as_cell((atom!("b")))]
));
assert!(!wam.is_cyclic_term(str_loc_as_cell!(0)));
functor_writer(&mut wam.heap).unwrap();
let h = wam.heap.cell_len();
wam.heap.push_cell(str_loc_as_cell!(0)).unwrap();
assert!(!wam.is_cyclic_term(h));
all_cells_unmarked(&wam.heap);
assert!(!wam.is_cyclic_term(heap_loc_as_cell!(1)));
assert!(!wam.is_cyclic_term(1));
all_cells_unmarked(&wam.heap);
assert!(!wam.is_cyclic_term(heap_loc_as_cell!(2)));
assert!(!wam.is_cyclic_term(2));
all_cells_unmarked(&wam.heap);
wam.heap[2] = str_loc_as_cell!(0);
print_heap_terms(wam.heap.iter(), 0);
print_heap_terms(&wam.heap, 0);
assert!(wam.is_cyclic_term(str_loc_as_cell!(0)));
assert!(wam.is_cyclic_term(2));
all_cells_unmarked(&wam.heap);
wam.heap[2] = atom_as_cell!(atom!("b"));
wam.heap[1] = str_loc_as_cell!(0);
assert!(wam.is_cyclic_term(str_loc_as_cell!(0)));
all_cells_unmarked(&wam.heap);
assert!(wam.is_cyclic_term(heap_loc_as_cell!(1)));
assert!(wam.is_cyclic_term(1));
all_cells_unmarked(&wam.heap);
wam.heap.clear();
wam.heap.push(pstr_as_cell!(atom!("a string")));
wam.heap.push(empty_list_as_cell!());
let h = wam.heap.cell_len();
wam.heap.allocate_cstr("a string").unwrap();
assert!(!wam.is_cyclic_term(pstr_loc_as_cell!(0)));
assert!(!wam.is_cyclic_term(h));
}
}

View File

@@ -1,4 +1,5 @@
pub mod args;
#[macro_use]
pub mod arithmetic_ops;
pub mod attributed_variables;
pub mod code_walker;
@@ -19,7 +20,6 @@ pub mod machine_indices;
pub mod machine_state;
pub mod machine_state_impl;
pub mod mock_wam;
pub mod parsed_results;
pub mod partial_string;
pub mod preprocessor;
pub mod stack;
@@ -45,6 +45,7 @@ use crate::machine::machine_indices::*;
use crate::machine::machine_state::*;
use crate::machine::stack::*;
use crate::machine::streams::*;
use crate::offset_table::*;
use crate::parser::ast::*;
use crate::parser::dashu::{Integer, Rational};
use crate::types::*;
@@ -54,20 +55,21 @@ use lazy_static::lazy_static;
use ordered_float::OrderedFloat;
use rand::rngs::StdRng;
use rand::SeedableRng;
use std::borrow::Cow;
use std::cmp::Ordering;
use std::env;
use std::io::Read;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use self::config::MachineConfig;
use self::parsed_results::*;
use std::sync::OnceLock;
lazy_static! {
pub static ref INTERRUPT: AtomicBool = AtomicBool::new(false);
}
/// An instance of Scryer Prolog.
///
/// Created with [`MachineBuilder::build`](crate::machine::config::MachineBuilder::build).
#[derive(Debug)]
pub struct Machine {
pub(super) machine_st: MachineState,
@@ -110,10 +112,33 @@ impl LoadContext {
#[inline]
fn current_dir() -> PathBuf {
env::current_dir().unwrap_or(PathBuf::from("./"))
if !cfg!(miri) {
env::current_dir().unwrap_or(PathBuf::from("./"))
} else {
PathBuf::from("./")
}
}
include!(concat!(env!("OUT_DIR"), "/libraries.rs"));
mod libraries {
use indexmap::IndexMap;
use std::sync::LazyLock;
static LIBRARIES: LazyLock<IndexMap<&'static str, &'static str>> = LazyLock::new(|| {
let mut m = IndexMap::new();
include!(concat!(env!("OUT_DIR"), "/libraries.rs"));
m
});
pub(crate) fn contains(name: &str) -> bool {
LIBRARIES.contains_key(name)
}
pub(crate) fn get(name: &str) -> Option<&'static str> {
LIBRARIES.get(name).copied()
}
}
pub static BREAK_FROM_DISPATCH_LOOP_LOC: usize = 0;
pub static INSTALL_VERIFY_ATTR_INTERRUPT: usize = 1;
@@ -183,13 +208,8 @@ pub(crate) fn import_builtin_impls(code_dir: &CodeDir, builtins: &mut Module) {
#[inline]
pub(crate) fn get_structure_index(value: HeapCellValue) -> Option<CodeIndex> {
read_heap_cell!(value,
(HeapCellValueTag::Cons, cons_ptr) => {
match_untyped_arena_ptr!(cons_ptr,
(ArenaHeaderTag::IndexPtr, ip) => {
return Some(CodeIndex::from(ip));
}
_ => {}
);
(HeapCellValueTag::CodeIndexOffset, offset) => {
return Some(CodeIndex::from(offset));
}
_ => {
}
@@ -200,7 +220,7 @@ pub(crate) fn get_structure_index(value: HeapCellValue) -> Option<CodeIndex> {
impl Machine {
#[inline]
pub fn prelude_view_and_machine_st(&mut self) -> (MachinePreludeView, &mut MachineState) {
fn prelude_view_and_machine_st(&mut self) -> (MachinePreludeView<'_>, &mut MachineState) {
(
MachinePreludeView {
indices: &mut self.indices,
@@ -211,6 +231,7 @@ impl Machine {
)
}
/// Gets the current inference count.
pub fn get_inference_count(&mut self) -> u64 {
self.machine_st
.cwil
@@ -220,22 +241,27 @@ impl Machine {
.unwrap()
}
pub fn throw_session_error(&mut self, err: SessionError, key: PredicateKey) {
let err = self.machine_st.session_error(err);
let stub = functor_stub(key.0, key.1);
let err = self.machine_st.error_form(err, stub);
self.machine_st.throw_exception(err);
}
pub fn run_module_predicate(
/// Runs the predicate `key` in `module_name` until completion.
/// Silently ignores failure, thrown errors and choice points.
///
/// Consider using [`Machine::run_query`] if you wish to handle
/// predicates that may fail, leave a choice point or throw.
pub(crate) fn run_module_predicate(
&mut self,
module_name: Atom,
key: PredicateKey,
) -> std::process::ExitCode {
if let Some(module) = self.indices.modules.get(&module_name) {
if let Some(code_index) = module.code_dir.get(&key) {
let p = code_index.local().unwrap();
if let Some(code_idx) = module.code_dir.get(&key) {
let index_ptr = self
.machine_st
.arena
.code_index_tbl
.get_entry(code_idx.into());
let p = index_ptr.local().unwrap();
// Leave a halting choice point to backtrack to in case the predicate fails or throws.
self.allocate_stub_choice_point();
self.machine_st.cp = BREAK_FROM_DISPATCH_LOOP_LOC;
self.machine_st.p = p;
@@ -247,21 +273,24 @@ impl Machine {
unreachable!();
}
pub fn load_file(&mut self, path: &str, stream: Stream) {
self.machine_st.registers[1] = stream_as_cell!(stream);
fn load_file(&mut self, path: &str, stream: Stream) {
self.machine_st.registers[1] = stream.into();
self.machine_st.registers[2] =
atom_as_cell!(AtomTable::build_with(&self.machine_st.atom_tbl, path));
self.run_module_predicate(atom!("loader"), (atom!("file_load"), 2));
}
fn load_top_level(&mut self, program: &'static str) {
fn load_top_level(&mut self, program: Cow<'static, str>) {
let mut path_buf = current_dir();
path_buf.push("src/toplevel.pl");
let path = path_buf.to_str().unwrap();
let toplevel_stream = Stream::from_static_string(program, &mut self.machine_st.arena);
let toplevel_stream = match program {
Cow::Borrowed(s) => Stream::from_static_string(s, &mut self.machine_st.arena),
Cow::Owned(s) => Stream::from_owned_string(s, &mut self.machine_st.arena),
};
self.load_file(path, toplevel_stream);
@@ -301,21 +330,17 @@ impl Machine {
self.load_file(path_buf.to_str().unwrap(), stream);
if let Some(module) = self.indices.modules.get(&atom!("$atts")) {
if let Some(code_index) = module.code_dir.get(&(atom!("driver"), 2)) {
self.machine_st.attr_var_init.verify_attrs_loc = code_index.local().unwrap();
if let Some(code_idx) = module.code_dir.get(&(atom!("driver"), 2)) {
let index_ptr = self
.machine_st
.arena
.code_index_tbl
.get_entry(code_idx.into());
self.machine_st.attr_var_init.verify_attrs_loc = index_ptr.local().unwrap();
}
}
}
pub fn set_user_input(&mut self, input: String) {
self.user_input = Stream::from_owned_string(input, &mut self.machine_st.arena);
}
pub fn get_user_output(&self) -> String {
let output_bytes: Vec<_> = self.user_output.bytes().map(|b| b.unwrap()).collect();
String::from_utf8(output_bytes).unwrap()
}
pub(crate) fn configure_modules(&mut self) {
fn update_call_n_indices(
loader: &Module,
@@ -325,13 +350,16 @@ impl Machine {
for arity in 1..66 {
let key = (atom!("call"), arity);
match loader.code_dir.get(&key) {
match loader.code_dir.get(&key).cloned() {
Some(src_code_index) => {
let target_code_index = target_code_dir
.entry(key)
.or_insert_with(|| CodeIndex::new(IndexPtr::undefined(), arena));
let code_index_tbl = &mut arena.code_index_tbl;
target_code_index.set(src_code_index.get());
let target_code_index = target_code_dir.entry(key).or_insert_with(|| {
CodeIndex::new(IndexPtr::undefined(), code_index_tbl)
});
let src_code_ptr = code_index_tbl.get_entry(src_code_index.into());
target_code_index.set(code_index_tbl, src_code_ptr);
}
None => {
unreachable!();
@@ -440,150 +468,37 @@ impl Machine {
key,
CodeIndex::new(
IndexPtr::index(p + impls_offset),
&mut self.machine_st.arena,
&mut self.machine_st.arena.code_index_tbl,
),
);
}
}
#[allow(clippy::new_without_default)]
pub fn new(config: MachineConfig) -> Self {
use ref_thread_local::RefThreadLocal;
let args = MachineArgs::new();
let mut machine_st = MachineState::new();
let (user_input, user_output, user_error) = match config.streams {
config::StreamConfig::Stdio => (
Stream::stdin(&mut machine_st.arena, args.add_history),
Stream::stdout(&mut machine_st.arena),
Stream::stderr(&mut machine_st.arena),
),
config::StreamConfig::Memory => (
Stream::Null(StreamOptions::default()),
Stream::from_owned_string("".to_owned(), &mut machine_st.arena),
Stream::stderr(&mut machine_st.arena),
),
};
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.borrow()["ops_and_meta_predicates"],
&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.borrow()["builtins"], &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(config.toplevel);
wam.configure_streams();
wam
}
/// Ensures that [`Machine::indices`] properly reflects
/// the streams stored in [`Machine::user_input`], [`Machine::user_output`]
/// and [`Machine::user_error`].
pub(crate) fn configure_streams(&mut self) {
self.user_input
.options_mut()
.set_alias_to_atom_opt(Some(atom!("user_input")));
self.indices
.set_stream(atom!("user_input"), self.user_input);
self.indices
.set_stream(atom!("user_output"), self.user_output);
self.indices
.set_stream(atom!("user_error"), self.user_error);
let mut null_options = StreamOptions::default();
null_options.set_alias_to_atom_opt(Some(atom!("null_stream")));
self.indices
.stream_aliases
.insert(atom!("user_input"), self.user_input);
self.indices.streams.insert(self.user_input);
self.user_output
.options_mut()
.set_alias_to_atom_opt(Some(atom!("user_output")));
self.indices
.stream_aliases
.insert(atom!("user_output"), self.user_output);
self.indices.streams.insert(self.user_output);
self.indices
.stream_aliases
.insert(atom!("user_error"), self.user_error);
self.indices.streams.insert(self.user_error);
.set_stream(atom!("null_stream"), Stream::Null(null_options));
}
#[inline(always)]
pub(crate) fn run_verify_attr_interrupt(&mut self, arity: usize) {
let p = self.machine_st.attr_var_init.verify_attrs_loc;
self.machine_st.verify_attr_interrupt(p, arity);
step_or_resource_error!(
self.machine_st,
self.machine_st.verify_attr_interrupt(p, arity)
);
}
fn next_clause_applicable(&mut self, mut offset: usize) -> bool {
@@ -607,8 +522,8 @@ impl Machine {
.select_switch_on_term_index(cell, v, c, l, s)
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(hm)) => {
let lit = self.machine_st.constant_to_literal(cell);
hm.get(&lit).cloned().unwrap_or(IndexingCodePtr::Fail)
// let lit = self.machine_st.constant_to_literal(cell);
hm.get(&cell).cloned().unwrap_or(IndexingCodePtr::Fail)
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(hm)) => {
self.machine_st.select_switch_on_structure_index(cell, hm)
@@ -634,34 +549,8 @@ impl Machine {
if cell.is_var() {
offset += 1;
} else if lit.get_tag() == HeapCellValueTag::CStr {
read_heap_cell!(cell,
(HeapCellValueTag::CStr) => {
if cell == lit {
offset += 1;
} else {
return false;
}
}
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc) => {
offset += 1;
}
(HeapCellValueTag::Str, s) => {
let (name, arity) = cell_as_atom_cell!(self.machine_st.heap[s])
.get_name_and_arity();
if name == atom!(".") && arity == 2 {
offset += 1;
} else {
return false;
}
}
_ => {
return false;
}
);
} else {
self.machine_st.write_literal_to_var(cell, lit);
unify!(self.machine_st, cell, lit);
if self.machine_st.fail {
self.machine_st.fail = false;
@@ -675,7 +564,7 @@ impl Machine {
let cell = self.deref_register(t);
read_heap_cell!(cell,
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc | HeapCellValueTag::CStr) => {
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc) => {// | HeapCellValueTag::CStr) => {
offset += 1;
}
(HeapCellValueTag::Str, s) => {
@@ -714,27 +603,24 @@ impl Machine {
}
);
}
&Instruction::GetPartialString(
Level::Shallow,
string,
RegType::Temp(t),
has_tail,
) => {
&Instruction::GetPartialString(Level::Shallow, ref string, RegType::Temp(t)) => {
let cell = self.deref_register(t);
read_heap_cell!(cell,
(HeapCellValueTag::CStr, cstr) => {
if !has_tail && string != cstr {
return false;
}
(HeapCellValueTag::PStrLoc, pstr_loc) => {
let heap_slice = &self.machine_st.heap.as_slice()[pstr_loc ..];
offset += 1;
match compare_pstr_slices(heap_slice, string.as_bytes()) {
PStrSegmentCmpResult::Continue(..) => offset += 1,
_ => return false,
}
}
(HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc) => {
(HeapCellValueTag::Lis) => {
offset += 1;
}
(HeapCellValueTag::Str, s) => {
let (name, arity) = cell_as_atom_cell!(self.machine_st.heap[s]).get_name_and_arity();
let (name, arity) = cell_as_atom_cell!(self.machine_st.heap[s])
.get_name_and_arity();
if name == atom!(".") && arity == 2 {
offset += 1;
@@ -857,7 +743,7 @@ impl Machine {
or_frame.prelude.boip = 0;
or_frame.prelude.biip = 0;
or_frame.prelude.tr = self.machine_st.tr;
or_frame.prelude.h = self.machine_st.heap.len();
or_frame.prelude.h = self.machine_st.heap.cell_len();
or_frame.prelude.b0 = self.machine_st.b0;
or_frame.prelude.attr_var_queue_len =
self.machine_st.attr_var_init.attr_var_queue.len();
@@ -868,7 +754,7 @@ impl Machine {
or_frame[i] = self.machine_st.registers[i + 1];
}
self.machine_st.hb = self.machine_st.heap.len();
self.machine_st.hb = self.machine_st.heap.cell_len();
}
self.machine_st.p += 1;
@@ -889,7 +775,7 @@ impl Machine {
or_frame.prelude.boip = self.machine_st.oip;
or_frame.prelude.biip = self.machine_st.iip + iip_offset; // 1
or_frame.prelude.tr = self.machine_st.tr;
or_frame.prelude.h = self.machine_st.heap.len();
or_frame.prelude.h = self.machine_st.heap.cell_len();
or_frame.prelude.b0 = self.machine_st.b0;
or_frame.prelude.attr_var_queue_len =
self.machine_st.attr_var_init.attr_var_queue.len();
@@ -900,7 +786,7 @@ impl Machine {
or_frame[i] = self.machine_st.registers[i + 1];
}
self.machine_st.hb = self.machine_st.heap.len();
self.machine_st.hb = self.machine_st.heap.cell_len();
// self.machine_st.oip = 0;
// self.machine_st.iip = 0;
@@ -1167,13 +1053,15 @@ impl Machine {
if module_name == atom!("user") {
if let Some(idx) = self.indices.code_dir.get(&(name, arity)).cloned() {
self.try_call(name, arity, idx.get())
let index_ptr = self.machine_st.arena.code_index_tbl.get_entry(idx.into());
self.try_call(name, arity, index_ptr)
} else {
Err(self.machine_st.throw_undefined_error(name, arity))
}
} else if let Some(module) = self.indices.modules.get(&module_name) {
if let Some(idx) = module.code_dir.get(&(name, arity)).cloned() {
self.try_call(name, arity, idx.get())
let index_ptr = self.machine_st.arena.code_index_tbl.get_entry(idx.into());
self.try_call(name, arity, index_ptr)
} else {
self.undefined_procedure(name, arity)
}
@@ -1196,14 +1084,24 @@ impl Machine {
let (name, arity) = key;
if module_name == atom!("user") {
if let Some(idx) = self.indices.code_dir.get(&(name, arity)).cloned() {
self.try_execute(name, arity, idx.get())
if let Some(offset) = self.indices.code_dir.get(&(name, arity)).cloned() {
let index_ptr = self
.machine_st
.arena
.code_index_tbl
.get_entry(offset.into());
self.try_execute(name, arity, index_ptr)
} else {
self.undefined_procedure(name, arity)
}
} else if let Some(module) = self.indices.modules.get(&module_name) {
if let Some(idx) = module.code_dir.get(&(name, arity)).cloned() {
self.try_execute(name, arity, idx.get())
if let Some(offset) = module.code_dir.get(&(name, arity)).cloned() {
let index_ptr = self
.machine_st
.arena
.code_index_tbl
.get_entry(offset.into());
self.try_execute(name, arity, index_ptr)
} else {
self.undefined_procedure(name, arity)
}
@@ -1235,41 +1133,47 @@ impl Machine {
#[inline(always)]
fn run_cleaners(&mut self) -> bool {
use std::sync::Once;
static CLEANER_INIT: OnceLock<(usize, usize)> = OnceLock::new();
static CLEANER_INIT: Once = Once::new();
let (r_c_w_h, r_c_wo_h) = *CLEANER_INIT.get_or_init(|| {
let r_c_w_h_atom = atom!("run_cleaners_with_handling");
let r_c_wo_h_atom = atom!("run_cleaners_without_handling");
let iso_ext = atom!("iso_ext");
static mut RCWH: usize = 0;
static mut RCWOH: usize = 0;
let (r_c_w_h, r_c_wo_h) = unsafe {
CLEANER_INIT.call_once(|| {
let r_c_w_h_atom = atom!("run_cleaners_with_handling");
let r_c_wo_h_atom = atom!("run_cleaners_without_handling");
let iso_ext = atom!("iso_ext");
RCWH = self
.indices
.get_predicate_code_index(r_c_w_h_atom, 0, iso_ext)
.and_then(|item| item.local())
.unwrap();
RCWOH = self
.indices
.get_predicate_code_index(r_c_wo_h_atom, 1, iso_ext)
.and_then(|item| item.local())
.unwrap();
});
(RCWH, RCWOH)
};
let r_c_w_h = self
.indices
.get_predicate_code_index(r_c_w_h_atom, 0, iso_ext)
.and_then(|code_idx| {
self.machine_st
.arena
.code_index_tbl
.get_entry(code_idx.into())
.local()
})
.unwrap();
let r_c_wo_h = self
.indices
.get_predicate_code_index(r_c_wo_h_atom, 1, iso_ext)
.and_then(|code_idx| {
self.machine_st
.arena
.code_index_tbl
.get_entry(code_idx.into())
.local()
})
.unwrap();
(r_c_w_h, r_c_wo_h)
});
if let Some(&(_, b_cutoff, prev_block)) = self.machine_st.cont_pts.last() {
if self.machine_st.b < b_cutoff {
let (idx, arity) = if self.machine_st.effective_block() > prev_block {
(r_c_w_h, 0)
} else {
self.machine_st.registers[1] =
fixnum_as_cell!(Fixnum::build_with(b_cutoff as i64));
self.machine_st.registers[1] = fixnum_as_cell!(
/* FIXME this is not safe */
unsafe { Fixnum::build_with_unchecked(b_cutoff as i64) }
);
(r_c_wo_h, 1)
};
@@ -1341,3 +1245,24 @@ impl Machine {
}
}
}
#[cfg(test)]
mod tests {
use super::config::*;
use super::*;
#[test]
#[cfg_attr(miri, ignore)]
fn test_run_module_predicate_throw() {
let mut machine = MachineBuilder::default()
.with_toplevel(
r#"
:- module('$toplevel', []).
repl :- throw(kaboom).
"#,
)
.build();
machine.run_module_predicate(atom!("$toplevel"), (atom!("repl"), 0));
}
}

View File

@@ -1,353 +0,0 @@
use crate::atom_table::*;
use dashu::*;
use ordered_float::OrderedFloat;
use std::collections::BTreeMap;
use std::collections::HashMap;
pub type QueryResult = Result<QueryResolution, String>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryResolution {
True,
False,
Matches(Vec<QueryMatch>),
}
pub fn prolog_value_to_json_string(value: Value) -> String {
match value {
Value::Integer(i) => format!("{}", i),
Value::Float(f) => format!("{}", f),
Value::Rational(r) => format!("{}", r),
Value::Atom(a) => format!("{}", a.as_str()),
Value::String(s) => {
if let Err(_e) = serde_json::from_str::<serde_json::Value>(s.as_str()) {
//treat as string literal
//escape double quotes
format!(
"\"{}\"",
s.replace('\"', "\\\"")
.replace('\n', "\\n")
.replace('\t', "\\t")
.replace('\r', "\\r")
)
} else {
//return valid json string
s
}
}
Value::List(l) => {
let mut string_result = "[".to_string();
for (i, v) in l.iter().enumerate() {
if i > 0 {
string_result.push(',');
}
string_result.push_str(&prolog_value_to_json_string(v.clone()));
}
string_result.push(']');
string_result
}
Value::Structure(s, l) => {
let mut string_result = format!("\"{}\":[", s.as_str());
for (i, v) in l.iter().enumerate() {
if i > 0 {
string_result.push(',');
}
string_result.push_str(&prolog_value_to_json_string(v.clone()));
}
string_result.push(']');
string_result
}
_ => "null".to_string(),
}
}
fn prolog_match_to_json_string(query_match: &QueryMatch) -> String {
let mut string_result = "{".to_string();
for (i, (k, v)) in query_match.bindings.iter().enumerate() {
if i > 0 {
string_result.push(',');
}
string_result.push_str(&format!(
"\"{}\":{}",
k,
prolog_value_to_json_string(v.clone())
));
}
string_result.push('}');
string_result
}
impl ToString for QueryResolution {
fn to_string(&self) -> String {
match self {
QueryResolution::True => "true".to_string(),
QueryResolution::False => "false".to_string(),
QueryResolution::Matches(matches) => {
let matches_json: Vec<String> =
matches.iter().map(prolog_match_to_json_string).collect();
format!("[{}]", matches_json.join(","))
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QueryMatch {
pub bindings: BTreeMap<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueryResolutionLine {
True,
False,
Match(BTreeMap<String, Value>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Value {
Integer(Integer),
Rational(Rational),
Float(OrderedFloat<f64>),
Atom(Atom),
String(String),
List(Vec<Value>),
Structure(Atom, Vec<Value>),
Var,
}
impl From<BTreeMap<&str, Value>> for QueryMatch {
fn from(bindings: BTreeMap<&str, Value>) -> Self {
QueryMatch {
bindings: bindings
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect::<BTreeMap<_, _>>(),
}
}
}
impl From<BTreeMap<String, Value>> for QueryMatch {
fn from(bindings: BTreeMap<String, Value>) -> Self {
QueryMatch { bindings }
}
}
impl From<Vec<QueryResolutionLine>> for QueryResolution {
fn from(query_result_lines: Vec<QueryResolutionLine>) -> Self {
// If there is only one line, and it is true or false, return that.
if query_result_lines.len() == 1 {
match query_result_lines[0].clone() {
QueryResolutionLine::True => return QueryResolution::True,
QueryResolutionLine::False => return QueryResolution::False,
_ => {}
}
}
// If there is only one line, and it is an empty match, return false.
if query_result_lines.len() == 1 {
if let QueryResolutionLine::Match(m) = query_result_lines[0].clone() {
if m.is_empty() {
return QueryResolution::False;
}
}
}
// If there is at least one line with true and no matches, return true.
if query_result_lines
.iter()
.any(|l| l == &QueryResolutionLine::True)
&& !query_result_lines
.iter()
.any(|l| matches!(l, QueryResolutionLine::Match(_)))
{
return QueryResolution::True;
}
// If there is at least one match, return all matches.
let all_matches = query_result_lines
.into_iter()
.filter(|l| matches!(l, QueryResolutionLine::Match(_)))
.map(|l| match l {
QueryResolutionLine::Match(m) => QueryMatch::from(m),
_ => unreachable!(),
})
.collect::<Vec<_>>();
if !all_matches.is_empty() {
return QueryResolution::Matches(all_matches);
}
QueryResolution::False
}
}
fn split_response_string(input: &str) -> Vec<String> {
let mut level_bracket = 0;
let mut level_parenthesis = 0;
let mut in_double_quotes = false;
let mut in_single_quotes = false;
let mut start = 0;
let mut result = Vec::new();
for (i, c) in input.chars().enumerate() {
match c {
'[' => level_bracket += 1,
']' => level_bracket -= 1,
'(' => level_parenthesis += 1,
')' => level_parenthesis -= 1,
'"' => in_double_quotes = !in_double_quotes,
'\'' => in_single_quotes = !in_single_quotes,
',' if level_bracket == 0
&& level_parenthesis == 0
&& !in_double_quotes
&& !in_single_quotes =>
{
result.push(input[start..i].trim().to_string());
start = i + 1;
}
_ => {}
}
}
result.push(input[start..].trim().to_string());
result
}
fn split_key_value_pairs(input: &str) -> Vec<(String, String)> {
let items = split_response_string(input);
let mut result = Vec::new();
for item in items {
let parts: Vec<&str> = item.splitn(2, '=').collect();
if parts.len() == 2 {
let key = parts[0].trim().to_string();
let value = parts[1].trim().to_string();
result.push((key, value));
}
}
result
}
fn parse_prolog_response(input: &str) -> HashMap<String, String> {
let mut map: HashMap<String, String> = HashMap::new();
// Use regex to match strings including commas inside them
for result in split_key_value_pairs(input) {
let key = result.0;
let value = result.1;
// cut off at given characters/strings:
let value = value.split('\n').next().unwrap().to_string();
let value = value.split(' ').next().unwrap().to_string();
let value = value.split('\t').next().unwrap().to_string();
let value = value.split("error").next().unwrap().to_string();
map.insert(key, value);
}
map
}
impl TryFrom<String> for QueryResolutionLine {
type Error = ();
fn try_from(string: String) -> Result<Self, Self::Error> {
match string.as_str() {
"true" => Ok(QueryResolutionLine::True),
"false" => Ok(QueryResolutionLine::False),
_ => Ok(QueryResolutionLine::Match(
parse_prolog_response(&string)
.iter()
.map(|(k, v)| -> Result<(String, Value), ()> {
let key = k.to_string();
let value = v.to_string();
Ok((key, Value::try_from(value)?))
})
.filter_map(Result::ok)
.collect::<BTreeMap<_, _>>(),
)),
}
}
}
fn split_nested_list(input: &str) -> Vec<String> {
let mut level = 0;
let mut start = 0;
let mut result = Vec::new();
for (i, c) in input.chars().enumerate() {
match c {
'[' => level += 1,
']' => level -= 1,
',' if level == 0 => {
result.push(input[start..i].trim().to_string());
start = i + 1;
}
_ => {}
}
}
result.push(input[start..].trim().to_string());
result
}
impl TryFrom<String> for Value {
type Error = ();
fn try_from(string: String) -> Result<Self, Self::Error> {
let trimmed = string.trim();
if let Ok(float_value) = string.parse::<f64>() {
Ok(Value::Float(OrderedFloat(float_value)))
} else if let Ok(int_value) = string.parse::<i128>() {
Ok(Value::Integer(int_value.into()))
} else if trimmed.starts_with('\'') && trimmed.ends_with('\'')
|| trimmed.starts_with('"') && trimmed.ends_with('"')
{
Ok(Value::String(trimmed[1..trimmed.len() - 1].into()))
} else if trimmed.starts_with('[') && trimmed.ends_with(']') {
let split = split_nested_list(&trimmed[1..trimmed.len() - 1]);
let values = split
.into_iter()
.map(Value::try_from)
.collect::<Result<Vec<_>, _>>()?;
Ok(Value::List(values))
} else if trimmed.starts_with('{') && trimmed.ends_with('}') {
let iter = trimmed[1..trimmed.len() - 1].split(',');
let mut values = vec![];
for value in iter {
let items: Vec<_> = value.split(':').collect();
if items.len() == 2 {
let _key = items[0].to_string();
let value = items[1].to_string();
values.push(Value::try_from(value)?);
}
}
Ok(Value::Structure(atom!("{}"), values))
} else if trimmed.starts_with("<<") && trimmed.ends_with(">>") {
let iter = trimmed[2..trimmed.len() - 2].split(',');
let mut values = vec![];
for value in iter {
let items: Vec<_> = value.split(':').collect();
if items.len() == 2 {
let _key = items[0].to_string();
let value = items[1].to_string();
values.push(Value::try_from(value)?);
}
}
Ok(Value::Structure(atom!("<<>>"), values))
} else if !trimmed.contains(',') && !trimmed.contains('\'') && !trimmed.contains('"') {
Ok(Value::String(trimmed.into()))
} else {
Err(())
}
}
}
impl From<&str> for Value {
fn from(str: &str) -> Self {
Value::String(str.to_string())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,42 +11,73 @@ use indexmap::IndexSet;
use std::cell::Cell;
use std::convert::TryFrom;
pub(crate) fn to_op_decl(prec: u16, spec: Atom, name: Atom) -> Result<OpDecl, CompilationError> {
match spec {
atom!("xfx") => Ok(OpDecl::new(OpDesc::build_with(prec, XFX as u8), name)),
atom!("xfy") => Ok(OpDecl::new(OpDesc::build_with(prec, XFY as u8), name)),
atom!("yfx") => Ok(OpDecl::new(OpDesc::build_with(prec, YFX as u8), name)),
atom!("fx") => Ok(OpDecl::new(OpDesc::build_with(prec, FX as u8), name)),
atom!("fy") => Ok(OpDecl::new(OpDesc::build_with(prec, FY as u8), name)),
atom!("xf") => Ok(OpDecl::new(OpDesc::build_with(prec, XF as u8), name)),
atom!("yf") => Ok(OpDecl::new(OpDesc::build_with(prec, YF as u8), name)),
_ => Err(CompilationError::InconsistentEntry),
}
pub(crate) fn to_op_decl(prec: u16, spec: OpDeclSpec, name: Atom) -> OpDecl {
OpDecl::new(OpDesc::build_with(prec, spec), name)
}
fn setup_op_decl(mut terms: Vec<Term>, atom_tbl: &AtomTable) -> Result<OpDecl, CompilationError> {
pub(crate) fn to_op_decl_spec(spec: Atom) -> Result<OpDeclSpec, CompilationError> {
OpDeclSpec::try_from(spec).map_err(|_err| {
CompilationError::InvalidDirective(DirectiveError::InvalidOpDeclSpecValue(spec))
})
}
fn setup_op_decl(mut terms: Vec<Term>) -> Result<OpDecl, CompilationError> {
// should allow non-partial lists?
let name = match terms.pop().unwrap() {
Term::Literal(_, Literal::Atom(name)) => name,
Term::Literal(_, Literal::Char(c)) => AtomTable::build_with(atom_tbl, &c.to_string()),
_ => return Err(CompilationError::InconsistentEntry),
other => {
return Err(CompilationError::InvalidDirective(
DirectiveError::InvalidOpDeclNameType(other),
));
}
};
let spec = match terms.pop().unwrap() {
Term::Literal(_, Literal::Atom(name)) => name,
Term::Literal(_, Literal::Char(c)) => AtomTable::build_with(atom_tbl, &c.to_string()),
_ => return Err(CompilationError::InconsistentEntry),
other => {
return Err(CompilationError::InvalidDirective(
DirectiveError::InvalidOpDeclSpecDomain(other),
))
}
};
let spec = to_op_decl_spec(spec)?;
let prec = match terms.pop().unwrap() {
Term::Literal(_, Literal::Fixnum(bi)) => match u16::try_from(bi.get_num()) {
Ok(n) if n <= 1200 => n,
_ => return Err(CompilationError::InconsistentEntry),
_ => {
return Err(CompilationError::InvalidDirective(
DirectiveError::InvalidOpDeclPrecDomain(bi),
));
}
},
_ => return Err(CompilationError::InconsistentEntry),
other => {
return Err(CompilationError::InvalidDirective(
DirectiveError::InvalidOpDeclPrecType(other),
));
}
};
to_op_decl(prec, spec, name)
if name == "[]" || name == "{}" {
return Err(CompilationError::InvalidDirective(
DirectiveError::ShallNotCreate(name),
));
}
if name == "," {
return Err(CompilationError::InvalidDirective(
DirectiveError::ShallNotModify(name),
));
}
if name == "|" && (prec < 1001 || !spec.is_infix()) {
return Err(CompilationError::InvalidDirective(
DirectiveError::ShallNotCreate(name),
));
}
Ok(to_op_decl(prec, spec, name))
}
fn setup_predicate_indicator(term: &mut Term) -> Result<PredicateKey, CompilationError> {
@@ -80,16 +111,13 @@ fn setup_predicate_indicator(term: &mut Term) -> Result<PredicateKey, Compilatio
}
}
fn setup_module_export(
mut term: Term,
atom_tbl: &AtomTable,
) -> Result<ModuleExport, CompilationError> {
fn setup_module_export(mut term: Term) -> Result<ModuleExport, CompilationError> {
setup_predicate_indicator(&mut term)
.map(ModuleExport::PredicateKey)
.or_else(|_| {
if let Term::Clause(_, name, terms) = term {
if terms.len() == 3 && name == atom!("op") {
Ok(ModuleExport::OpDecl(setup_op_decl(terms, atom_tbl)?))
Ok(ModuleExport::OpDecl(setup_op_decl(terms)?))
} else {
Err(CompilationError::InvalidModuleDecl)
}
@@ -108,12 +136,11 @@ pub(crate) fn build_rule_body(vars: &[Term], body_term: Term) -> Term {
pub(super) fn setup_module_export_list(
mut export_list: Term,
atom_tbl: &AtomTable,
) -> Result<Vec<ModuleExport>, CompilationError> {
let mut exports = vec![];
while let Term::Cons(_, t1, t2) = export_list {
let module_export = setup_module_export(*t1, atom_tbl)?;
let module_export = setup_module_export(*t1)?;
exports.push(module_export);
export_list = *t2;
@@ -126,10 +153,7 @@ pub(super) fn setup_module_export_list(
}
}
fn setup_module_decl(
mut terms: Vec<Term>,
atom_tbl: &AtomTable,
) -> Result<ModuleDecl, CompilationError> {
fn setup_module_decl(mut terms: Vec<Term>) -> Result<ModuleDecl, CompilationError> {
let export_list = terms.pop().unwrap();
let name = terms.pop().unwrap();
@@ -139,8 +163,7 @@ fn setup_module_decl(
}
.ok_or(CompilationError::InvalidModuleDecl)?;
let exports = setup_module_export_list(export_list, atom_tbl)?;
let exports = setup_module_export_list(export_list)?;
Ok(ModuleDecl { name, exports })
}
@@ -159,10 +182,7 @@ fn setup_use_module_decl(mut terms: Vec<Term>) -> Result<ModuleSource, Compilati
type UseModuleExport = (ModuleSource, IndexSet<ModuleExport>);
fn setup_qualified_import(
mut terms: Vec<Term>,
atom_tbl: &AtomTable,
) -> Result<UseModuleExport, CompilationError> {
fn setup_qualified_import(mut terms: Vec<Term>) -> Result<UseModuleExport, CompilationError> {
let mut export_list = terms.pop().unwrap();
let module_src = match terms.pop().unwrap() {
Term::Clause(_, name, mut terms) if name == atom!("library") && terms.len() == 1 => {
@@ -178,7 +198,7 @@ fn setup_qualified_import(
let mut exports = IndexSet::new();
while let Term::Cons(_, t1, t2) = export_list {
exports.insert(setup_module_export(*t1, atom_tbl)?);
exports.insert(setup_module_export(*t1)?);
export_list = *t2;
}
@@ -193,7 +213,7 @@ fn setup_qualified_import(
* setup_meta_predicate tries to extract meta-predicate information
* from an appropriately formed declaration
*
* :- meta_predicate maplist(:, ?, ?).
* :- meta_predicate(maplist(:, ?, ?)).
*
* indicating that, for each QueryTerm call to maplist/3, the first
* argument is to be expanded with the call resolution ((:)/2)
@@ -220,13 +240,13 @@ fn setup_qualified_import(
* contained in src/lib/ops_and_meta_predicates.pl, which is loaded before
* src/lib/builtins.pl.
*
* Meta-specs have three forms:
* Meta-specs have four forms:
*
* (:) (the argument should be expanded with (:)/2 as described above)
* + (mode declarations under the mode syntax, which currently have no effect)
* -
* ?
*/
*/
fn setup_meta_predicate<'a, LS: LoadState<'a>>(
mut terms: Vec<Term>,
@@ -308,32 +328,28 @@ pub(super) fn setup_declaration<'a, LS: LoadState<'a>>(
let (name, arity) = setup_predicate_indicator(&mut terms.pop().unwrap())?;
Ok(Declaration::Dynamic(name, arity))
}
(atom!("module"), 2) => {
let atom_tbl = &mut LS::machine_st(&mut loader.payload).atom_tbl;
Ok(Declaration::Module(setup_module_decl(terms, atom_tbl)?))
}
(atom!("op"), 3) => {
let atom_tbl = &mut LS::machine_st(&mut loader.payload).atom_tbl;
Ok(Declaration::Op(setup_op_decl(terms, atom_tbl)?))
}
(atom!("module"), 2) => Ok(Declaration::Module(setup_module_decl(terms)?)),
(atom!("op"), 3) => Ok(Declaration::Op(setup_op_decl(terms)?)),
(atom!("non_counted_backtracking"), 1) => {
let (name, arity) = setup_predicate_indicator(&mut terms.pop().unwrap())?;
Ok(Declaration::NonCountedBacktracking(name, arity))
}
(atom!("use_module"), 1) => Ok(Declaration::UseModule(setup_use_module_decl(terms)?)),
(atom!("use_module"), 2) => {
let atom_tbl = &mut LS::machine_st(&mut loader.payload).atom_tbl;
let (name, exports) = setup_qualified_import(terms, atom_tbl)?;
let (name, exports) = setup_qualified_import(terms)?;
Ok(Declaration::UseQualifiedModule(name, exports))
}
(atom!("meta_predicate"), 1) => {
let (module_name, name, meta_specs) = setup_meta_predicate(terms, loader)?;
Ok(Declaration::MetaPredicate(module_name, name, meta_specs))
}
_ => Err(CompilationError::InconsistentEntry),
_ => Err(CompilationError::InvalidDirective(
DirectiveError::InvalidDirective(name, terms.len()),
)),
},
_ => Err(CompilationError::InconsistentEntry),
other => Err(CompilationError::InvalidDirective(
DirectiveError::ExpectedDirective(other),
)),
}
}
@@ -408,7 +424,7 @@ fn build_meta_predicate_clause<'a, LS: LoadState<'a>>(
let term = match term {
Term::Clause(cell, name, mut terms) => {
if let Some(Term::Literal(_, Literal::CodeIndex(_))) = terms.last() {
if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = terms.last() {
arg_terms
.push(process_term(module_name, Term::Clause(cell, name, terms)));
@@ -417,7 +433,10 @@ fn build_meta_predicate_clause<'a, LS: LoadState<'a>>(
let idx = loader.get_or_insert_qualified_code_index(module_name, key);
terms.push(Term::Literal(Cell::default(), Literal::CodeIndex(idx)));
terms.push(Term::Literal(
Cell::default(),
Literal::CodeIndexOffset(idx.into()),
));
process_term(module_name, Term::Clause(cell, name, terms))
}
Term::Literal(cell, Literal::Atom(name)) => {
@@ -428,7 +447,10 @@ fn build_meta_predicate_clause<'a, LS: LoadState<'a>>(
Term::Clause(
cell,
name,
vec![Term::Literal(Cell::default(), Literal::CodeIndex(idx))],
vec![Term::Literal(
Cell::default(),
Literal::CodeIndexOffset(idx.into()),
)],
),
)
}
@@ -453,7 +475,7 @@ pub(super) fn clause_to_query_term<'a, LS: LoadState<'a>>(
mut terms: Vec<Term>,
call_policy: CallPolicy,
) -> QueryTerm {
if let Some(Term::Literal(_, Literal::CodeIndex(_))) = terms.last() {
if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = terms.last() {
// supplementary code vector indices are unnecessary for
// root-level clauses.
terms.pop();
@@ -488,7 +510,7 @@ pub(super) fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
mut terms: Vec<Term>,
call_policy: CallPolicy,
) -> QueryTerm {
if let Some(Term::Literal(_, Literal::CodeIndex(_))) = terms.last() {
if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = terms.last() {
// supplementary code vector indices are unnecessary for
// root-level clauses.
terms.pop();
@@ -569,7 +591,7 @@ impl Preprocessor {
&mut self,
loader: &mut Loader<'a, LS>,
term: Term,
) -> Result<TopLevel, CompilationError> {
) -> Result<PredicateClause, CompilationError> {
match term {
Term::Clause(r, name, mut terms) => {
let is_rule = name == atom!(":-") && terms.len() == 2;
@@ -579,16 +601,16 @@ impl Preprocessor {
let head = terms.pop().unwrap();
let (rule, var_data) = self.setup_rule(loader, head, tail)?;
Ok(TopLevel::Rule(rule, var_data))
Ok(PredicateClause::Rule(rule, var_data))
} else {
let term = Term::Clause(r, name, terms);
let (fact, var_data) = self.setup_fact(term)?;
Ok(TopLevel::Fact(fact, var_data))
Ok(PredicateClause::Fact(fact, var_data))
}
}
term => {
let (fact, var_data) = self.setup_fact(term)?;
Ok(TopLevel::Fact(fact, var_data))
Ok(PredicateClause::Fact(fact, var_data))
}
}
}

View File

@@ -15,7 +15,9 @@ impl RawBlockTraits for Stack {
#[inline]
fn align() -> usize {
mem::align_of::<HeapCellValue>()
mem::align_of::<OrFrame>()
.max(mem::align_of::<AndFrame>())
.max(mem::align_of::<HeapCellValue>())
}
}
@@ -57,9 +59,10 @@ impl Index<usize> for AndFrame {
unsafe {
let ptr = self as *const crate::machine::stack::AndFrame as *const u8;
let ptr = ptr as usize + prelude_offset + index_offset;
&*(ptr as *const HeapCellValue)
// This address falls outside the provenance for self, therefore we have to get it
// from exposed provenance.
&*std::ptr::with_exposed_provenance(ptr.addr() + prelude_offset + index_offset)
}
}
}
@@ -70,10 +73,11 @@ impl IndexMut<usize> for AndFrame {
let index_offset = (index - 1) * mem::size_of::<HeapCellValue>();
unsafe {
let ptr = self as *mut crate::machine::stack::AndFrame as *const u8;
let ptr = ptr as usize + prelude_offset + index_offset;
let ptr = self as *mut crate::machine::stack::AndFrame as *mut u8;
&mut *(ptr as *mut HeapCellValue)
// This address falls outside the provenance for self, therefore we have to get it
// from exposed provenance.
&mut *std::ptr::with_exposed_provenance_mut(ptr.addr() + prelude_offset + index_offset)
}
}
}
@@ -83,20 +87,14 @@ impl Index<usize> for Stack {
#[inline]
fn index(&self, index: usize) -> &Self::Output {
unsafe {
let ptr = self.buf.base as usize + index;
&*(ptr as *const HeapCellValue)
}
unsafe { &*self.buf.base.add(index).cast() }
}
}
impl IndexMut<usize> for Stack {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
unsafe {
let ptr = self.buf.base as usize + index;
&mut *(ptr as *mut HeapCellValue)
}
unsafe { &mut *self.buf.base.add(index).cast_mut().cast() }
}
}
@@ -130,9 +128,10 @@ impl Index<usize> for OrFrame {
unsafe {
let ptr = self as *const crate::machine::stack::OrFrame as *const u8;
let ptr = ptr as usize + prelude_offset + index_offset;
&*(ptr as *const HeapCellValue)
// This address falls outside the provenance for self, therefore we have to get it
// from exposed provenance.
&*std::ptr::with_exposed_provenance(ptr.addr() + prelude_offset + index_offset)
}
}
}
@@ -144,10 +143,11 @@ impl IndexMut<usize> for OrFrame {
let index_offset = index * mem::size_of::<HeapCellValue>();
unsafe {
let ptr = self as *mut crate::machine::stack::OrFrame as *const u8;
let ptr = ptr as usize + prelude_offset + index_offset;
let ptr = self as *mut crate::machine::stack::OrFrame as *mut u8;
&mut *(ptr as *mut HeapCellValue)
// This address falls outside the provenance for self, therefore we have to get it
// from exposed provenance.
&mut *std::ptr::with_exposed_provenance_mut(ptr.addr() + prelude_offset + index_offset)
}
}
}
@@ -172,7 +172,9 @@ impl Stack {
let ptr = self.buf.alloc(frame_size);
if ptr.is_null() {
self.buf.grow();
if !self.buf.grow() {
panic!("growing the stack failed")
}
} else {
return ptr;
}
@@ -183,15 +185,19 @@ impl Stack {
let frame_size = AndFrame::size_of(num_cells);
unsafe {
let e = (*self.buf.ptr.get_mut()) as usize - self.buf.base as usize;
let e = (*self.buf.ptr.get_mut()).addr() - self.buf.base.addr();
let new_ptr = self.alloc(frame_size);
let mut offset = prelude_size::<AndFramePrelude>();
for idx in 0..num_cells {
ptr::write(
new_ptr.add(offset) as *mut HeapCellValue,
stack_loc_as_cell!(AndFrame, e, idx + 1),
);
let cell_ptr = new_ptr.add(offset) as *mut HeapCellValue;
ptr::write(cell_ptr, stack_loc_as_cell!(AndFrame, e, idx + 1));
// Because in the Index and IndexMut inplementations we need to get this from
// exposed provenance, we need to expose the provenance here, even though we don't
// actually use the value for anything. This is a reminder that `expose_provenance`
// isn't just a cast from a pointer to an integer but has actual side effects.
cell_ptr.expose_provenance();
offset += mem::size_of::<HeapCellValue>();
}
@@ -204,22 +210,26 @@ impl Stack {
}
pub(crate) fn top(&self) -> usize {
unsafe { (*self.buf.ptr.get()) as usize - self.buf.base as usize }
unsafe { (*self.buf.ptr.get()).addr() - self.buf.base.addr() }
}
pub(crate) fn allocate_or_frame(&mut self, num_cells: usize) -> usize {
let frame_size = OrFrame::size_of(num_cells);
unsafe {
let b = (*self.buf.ptr.get_mut()) as usize - self.buf.base as usize;
let b = (*self.buf.ptr.get_mut()).addr() - self.buf.base.addr();
let new_ptr = self.alloc(frame_size);
let mut offset = prelude_size::<OrFramePrelude>();
for idx in 0..num_cells {
ptr::write(
(new_ptr as usize + offset) as *mut HeapCellValue,
stack_loc_as_cell!(OrFrame, b, idx),
);
let cell_ptr = new_ptr.byte_add(offset) as *mut HeapCellValue;
ptr::write(cell_ptr, stack_loc_as_cell!(OrFrame, b, idx));
// Because in the Index and IndexMut inplementations we need to get this from
// exposed provenance, we need to expose the provenance here, even though we don't
// actually use the value for anything. This is a reminder that `expose_provenance`
// isn't just a cast from a pointer to an integer but has actual side effects.
cell_ptr.expose_provenance();
offset += mem::size_of::<HeapCellValue>();
}
@@ -233,10 +243,7 @@ impl Stack {
#[inline(always)]
pub(crate) fn index_and_frame(&self, e: usize) -> &AndFrame {
unsafe {
let ptr = self.buf.base as usize + e;
&*(ptr as *const AndFrame)
}
unsafe { &*self.buf.base.add(e).cast() }
}
#[inline(always)]
@@ -250,26 +257,20 @@ impl Stack {
#[inline(always)]
pub(crate) fn index_or_frame(&self, b: usize) -> &OrFrame {
unsafe {
let ptr = self.buf.base as usize + b;
&*(ptr as *const OrFrame)
}
unsafe { &*self.buf.base.add(b).cast() }
}
#[inline(always)]
pub(crate) fn index_or_frame_mut(&mut self, b: usize) -> &mut OrFrame {
unsafe {
let ptr = self.buf.base as usize + b;
&mut *(ptr as *mut OrFrame)
}
unsafe { &mut *self.buf.base.add(b).cast_mut().cast() }
}
#[inline(always)]
pub(crate) fn truncate(&mut self, b: usize) {
let base = self.buf.base as usize + b;
let base = unsafe { self.buf.base.add(b) };
if base < (*self.buf.ptr.get_mut()) as usize {
*self.buf.ptr.get_mut() = base as *mut _;
if base < (*self.buf.ptr.get_mut()) {
*self.buf.ptr.get_mut() = base.cast_mut();
}
}
}
@@ -281,7 +282,6 @@ mod tests {
use crate::machine::mock_wam::*;
#[test]
#[cfg_attr(miri, ignore)]
fn stack_tests() {
let mut wam = MockWAM::new();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
#[cfg(rust_version = "1.87.0")]
pub use ge_1_87_0::{PipeReader, PipeWriter};
#[cfg(not(rust_version = "1.87.0"))]
pub use lt_1_87_0::{PipeReader, PipeWriter};
#[cfg(not(rust_version = "1.87.0"))]
pub(crate) use lt_1_87_0::PipeReaderInner;
#[cfg(not(rust_version = "1.87.0"))]
mod lt_1_87_0 {
use std::process::{ChildStderr, ChildStdout};
pub type PipeWriter = std::process::ChildStdin;
#[derive(Debug)]
pub struct PipeReader(pub(crate) PipeReaderInner);
#[derive(Debug)]
pub(crate) enum PipeReaderInner {
Stdout(ChildStdout),
Stderr(ChildStderr),
}
impl std::io::Read for PipeReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read(buf),
}
}
fn read_vectored(
&mut self,
bufs: &mut [std::io::IoSliceMut<'_>],
) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_vectored(bufs),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_vectored(bufs),
}
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_to_end(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_to_end(buf),
}
}
fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_to_string(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_to_string(buf),
}
}
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_exact(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_exact(buf),
}
}
}
}
#[cfg(rust_version = "1.87.0")]
mod ge_1_87_0 {
pub type PipeReader = std::io::PipeReader;
pub type PipeWriter = std::io::PipeWriter;
}

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