1726 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
Mark
b70f121e46 use scryer-modular-bitfield on github, version bump
Some checks failed
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 / style (push) Has been cancelled
CI / build-test (--no-default-features, ubuntu-22.04, true, nightly, wasm32-unknown-unknown, --no-run --no-default-features) (push) Has been cancelled
CI / build-test (macos-11, true, stable, x86_64-apple-darwin) (push) Has been cancelled
CI / build-test (ubuntu-20.04, true, stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, 1.70, 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, nightly, 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 / report (push) Has been cancelled
CI / release (push) Has been cancelled
Docker Publish / build (push) Has been cancelled
2024-02-29 10:08:14 -07:00
Mark
641765858f fix misuse of TypeError trait while fixing #2345 2024-02-28 20:47:36 -07:00
Mark
ba362e2fe0 issue callable type error from dynamic_module_resolution if module is not an atom (#2345) 2024-02-28 20:35:38 -07:00
Mark Thom
84d5ce0d1e Merge pull request #2309 from coasys/library-use-case
Iron-out edge cases for library use-case, adding extensive real-world test assertions
2024-02-28 14:34:54 -07:00
Mark Thom
419623d158 Merge pull request #2342 from triska/curve_doc
DOC: Add DocLog comments for reasoning about elliptic curves.
2024-02-24 14:53:18 -07:00
Markus Triska
1dd0c599c6 DOC: Add DocLog comments for reasoning about elliptic curves. 2024-02-24 20:43:57 +01:00
Mark Thom
c7934ca246 Merge pull request #2338 from triska/hmac
ADDED: Hash-based message authentication code (HMAC), using hmac(Key).
2024-02-22 14:48:12 -07:00
Markus Triska
27852eafd7 ADDED: Hash-based message authentication code (HMAC), using hmac(Key). 2024-02-22 20:53:27 +01:00
Mark Thom
d4bde5008d Merge pull request #2337 from triska/master
link to newly available paper on analysis of dose-escalation protocols
2024-02-18 10:05:09 -07:00
Markus Triska
74a525a672 link to newly available paper on analysis of dose-escalation protocols
Many thanks to @dcnorris for this great application and cooperation!
2024-02-18 09:25:29 +01:00
Bennet Bleßmann
e9982dc447 fix build of run_iai bench for wasm32 2024-02-17 00:15:03 +01:00
Bennet Bleßmann
7028baaf8f fix benchmarks being broken for every target except wam32 2024-02-17 00:01:10 +01:00
Bennet Bleßmann
7837c76c74 cfg out benches for wasm32 2024-02-16 23:43:22 +01:00
Bennet Bleßmann
e5ad70c093 fix compilation of wasm32 test and skip to run wasm32 tests 2024-02-16 23:18:21 +01:00
Bennet Bleßmann
7fce688769 undo continue-on-error
- currently  `continue-on-error` is not shown in a usefull way on failiure see <https://github.com/orgs/community/discussions/15452>
2024-02-16 22:23:22 +01:00
Bennet Bleßmann
90c874777c bump ahash lock to 0.8.7
- fix nightly build
- not bumping to latest aka. 0.8.8 as that has a msrv of 1.72.0 and we are only at 1.70.0
2024-02-16 22:15:32 +01:00
Mark Thom
70795134af Merge pull request #2327 from triska/char_type
FIXED: char_type/2 for unbound first argument.
2024-02-11 17:33:07 -07:00
Markus Triska
8f2e9c6b94 FIXED: char_type/2 for unbound first argument.
Surrogate pairs form a gap in valid character codes, see:

    https://github.com/mthom/scryer-prolog/issues/2326#issuecomment-1937864665

Many thanks to @Skgland for the pointer, and to @librarianmage for the
question that spawned this!

This addresses #2326.
2024-02-11 22:19:19 +01:00
Nicolas Luck
ce34ca8f1f continue-on-error if target=wasm32 or rust=nightly 2024-02-09 13:48:45 +01:00
Nicolas Luck
7c632cf165 Reactivate nightly test job with continue-on-error set 2024-02-09 13:44:37 +01:00
Nicolas Luck
b3c5a8db80 Use stable Rust for style/report and deactivate nightly x86_64 target in CI 2024-02-09 13:14:06 +01:00
Nicolas Luck
1d2961e047 Merge branch 'master' into library-use-case 2024-02-09 12:41:01 +01:00
Mark Thom
b43f0979d9 Update src/machine/parsed_results.rs
Co-authored-by: Bennet Bleßmann <bennet.blessmann+github@googlemail.com>
2024-02-08 19:10:29 -07:00
Mark
539a1aee2c fix tests broken by singleton 2024-02-07 21:46:32 -07:00
Mark Thom
8e323b9dfa Merge pull request #2322 from triska/char_code
ENHANCED: Bi-directional char_code/2, addressing #2321.
2024-02-07 18:49:13 -07:00
Markus Triska
53b7d9eec9 ENHANCED: Bidirectional char_type/2, addressing #2321.
Suggested by @librarianmage, many thanks!
2024-02-07 20:57:28 +01:00
Mark Thom
89d3451767 Merge pull request #2319 from triska/clpz_t
ADDED: clpz_t/2, generalizing support for use with library(reif)
2024-02-06 18:11:40 -07:00
Markus Triska
aa98a7e7d6 ADDED: clpz_t/2, generalizing support for use with library(reif)
This was suggested and contributed by @librarianmage in:

    https://github.com/mthom/scryer-prolog/issues/2225#issuecomment-1890801923

Many thanks!

If anyone can find a better predicate name, please let us know any time!
2024-02-06 19:05:29 +01:00
Mark
614850ab1d fmt 2024-02-05 18:34:57 -07:00
Mark Thom
140a15f9cb Merge pull request #2317 from aarroyoc/all-modules-test
Add all_mdoules test and fix library(csv)
2024-02-04 15:09:13 -07:00
Adrián Arroyo Calle
ec4a8745e7 Add all_mdoules test and fix library(csv) 2024-02-04 21:34:01 +01:00
Mark
673329ddb7 fix reverted tests, ensure files are loaded before goals (#2315) 2024-02-03 11:49:26 -07:00
Mark
6a421dd8b0 fix broken tests 2024-02-02 17:04:47 -07:00
Mark
de6c460a51 treat consultation of command line modules as regular goals (#2314) 2024-02-02 15:35:00 -07:00
Mark
75a94fd0b3 fmt 2024-02-02 11:49:02 -07:00
Mark
7573c64087 load .scryerrc before files and goals (#1775, #2313) 2024-02-02 10:54:28 -07:00
Mark
cbb422f69d record stub choice point as block 2024-02-01 09:26:58 -07:00
Nicolas Luck
06f198bc57 Test show problem with nonexistent predicate 2024-02-01 14:14:50 +01:00
Nicolas Luck
6586657658 fmt 2024-02-01 13:53:21 +01:00
Nicolas Luck
a0e598b97e clippy 2024-02-01 13:51:37 +01:00
Nicolas Luck
8a6ea29c45 Fix all orderings in integration assertions 2024-02-01 13:43:51 +01:00
Nicolas Luck
e1b0ba466b Remove test with long program literals, not needed 2024-02-01 13:00:49 +01:00
Nicolas Luck
f35d6287ab More ordering adjustments 2024-02-01 12:59:10 +01:00
Mark
53028a9c2a fix style errors 2024-01-31 17:32:32 -07:00
Mark
33fc2ed10c index stub choice point correctly 2024-01-31 17:30:13 -07:00
Nicolas Luck
bc02fb3754 Extract failing assertion as single test case 2024-01-31 15:03:28 +01:00
Nicolas Luck
48b2379fe5 Adjust some first result orderings in integration assertions 2024-01-30 12:54:12 +01:00
Mark
7de693eb23 check for True or False Query Resolution unconditionally 2024-01-29 15:06:56 -07:00
Nicolas Luck
7bb9c00356 fmt 2024-01-29 18:44:20 +01:00
Nicolas Luck
9aacfff35d Add more test cases to differentiate usage of discontiguous 2024-01-29 16:49:03 +01:00
Nicolas Luck
2e728c7051 Minimal reproduction of faulty behaviour seen in integration tests 2024-01-29 15:26:06 +01:00
Nicolas Luck
7e973a6da6 Merge branch 'master' into library-use-case 2024-01-26 17:21:47 +01:00
Nicolas Luck
cb014095ad Add expected results to integration test
Results are logs of what we get with old toplevel-based version of lib_machine. These are also congruent with what our tests logged out based on SWI.
2024-01-26 17:18:57 +01:00
Mark
6fe8f64835 scan registers of instructions leading to verify_attributes interrupt to find the number of registers to save (#2307) 2024-01-25 23:59:54 -07:00
Mark
6111f72b24 add non chunk traversing cut family instructions to is_head_instr (#2307) 2024-01-25 23:25:06 -07:00
Mark Thom
43041971b7 Merge pull request #2308 from triska/small_format_improvements
Small format improvements
2024-01-25 14:17:18 -07:00
Markus Triska
3505cc3ba0 update answers using latest toplevel output 2024-01-25 21:33:27 +01:00
Markus Triska
05ba5f4358 rely on recently improved argument indexing for determinism
This is possible as of 4fd37335f5.
2024-01-25 21:31:08 +01:00
Mark
bc616ca7d8 move call_residue_vars/2 from atts.pl to iso_ext.pl 2024-01-25 12:40:24 -07:00
Mark
99131131af don't mark temp variables as safe through argument_to_value (#2307) 2024-01-25 12:34:21 -07:00
Mark Thom
44b945a5f3 Merge pull request #2306 from triska/dcg_representation
No longer use (->)//2 in library(clpb)
2024-01-23 15:22:17 -07:00
Mark Thom
3bff923331 Merge pull request #2305 from mthom/dependabot/cargo/h2-0.3.24
Bump h2 from 0.3.22 to 0.3.24
2024-01-23 13:50:17 -07:00
Markus Triska
a6ccf95076 improve error context
Source: https://github.com/mthom/scryer-prolog/issues/2304#issuecomment-1906434756
2024-01-23 21:43:34 +01:00
Markus Triska
665f319a0e no longer use (->)//2, since it is implementation defined
This addresses #2304.
2024-01-23 21:42:36 +01:00
dependabot[bot]
fe31afce6b Bump h2 from 0.3.22 to 0.3.24
Bumps [h2](https://github.com/hyperium/h2) from 0.3.22 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.22...v0.3.24)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-23 18:20:39 +00:00
Mark Thom
ac07d2dfc8 Merge pull request #2303 from mthom/dependabot/cargo/shlex-1.3.0
Bump shlex from 1.2.0 to 1.3.0
2024-01-23 11:19:53 -07:00
dependabot[bot]
71165c6984 Bump shlex from 1.2.0 to 1.3.0
Bumps [shlex](https://github.com/comex/rust-shlex) from 1.2.0 to 1.3.0.
- [Changelog](https://github.com/comex/rust-shlex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/comex/rust-shlex/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-22 21:53:43 +00:00
Mark Thom
eab3bff78b Merge pull request #2302 from triska/dcg_representation
Throw representation errors for unsupported DCG grammar control constructs
2024-01-22 00:26:23 -07:00
Markus Triska
81dba11ab1 use newly available false//0 2024-01-21 22:53:22 +01:00
Markus Triska
388fa5baa9 no longer use (->)//2, since it is implementation dependent 2024-01-21 22:50:36 +01:00
Markus Triska
fe3241c07c remove checks that are now no longer needed due to representation errors 2024-01-21 10:28:19 +01:00
Markus Triska
0559ddca2a ENHANCED: Throw representation errors for DCG bodies that use unsupported constructs.
This addresses #2285.
2024-01-21 10:28:19 +01:00
Markus Triska
98a046500f MODIFIED: (->)/2 in DCGs is now only supported in the context of if-then-else.
That is, ( If -> Then ; Else ) is still supported, but ( If -> Then ) not.
2024-01-21 10:28:19 +01:00
Mark Thom
58cd0d1669 Merge pull request #2301 from aarroyoc/docs-copy-term-3
Move copy_term/3 to library(iso_ext)
2024-01-19 10:18:59 -07:00
Mark Thom
6421fe10f8 Merge pull request #2300 from aarroyoc/fix-2298
Throw error in call_residue_vars/2
2024-01-19 10:15:35 -07:00
Adrián Arroyo Calle
99c85459a7 Move copy_term/3 to library(iso_ext) 2024-01-19 17:33:45 +01:00
Adrián Arroyo Calle
958bf51648 Throw error in call_residue_vars/2 2024-01-19 16:58:26 +01:00
Mark
e3aa85e2a2 add better no op detection in char_reader.rs (#2297) 2024-01-16 21:50:53 -07:00
Mark
29ced36a79 add #2293 tests, advance continuable HeapPStrIters away from unpacked compounds (#2293) 2024-01-14 15:07:33 -07:00
Mark
f9a5c2d341 fix additional tests, make better use of existing code (#2293) 2024-01-13 12:01:54 -07:00
Mark
dde03718e1 fix additional tests (#2293) 2024-01-12 22:01:01 -07:00
Mark
60d34bea70 add missing CStr branch to unify_partial_string (#2293) 2024-01-12 13:58:46 -07:00
Mark Thom
6fb3b61441 Merge pull request #2276 from triska/hophrase
ADDED: Support for higher-order non-terminals phrase//[2,3]
2024-01-11 11:56:10 -07:00
Mark Thom
11ca168175 Merge pull request #2281 from bakaq/miri
Initial Miri support
2024-01-10 12:13:14 -07:00
bakaq
f02c0eacd8 Make "cargo miri test" actually run 2024-01-09 04:38:53 -03:00
Mark
72a566d2f8 fix cargo fmt --check 2024-01-08 18:12:31 -07:00
Mark Thom
b56ae28c45 Merge pull request #2280 from notoria/clpz
Special case for `(^)/2`
2024-01-08 17:39:18 -07:00
Mark
cd89d71e0c check for PStr in eager stackful preorder iterator before adding tail of PStrOffset (#2272) 2024-01-08 17:36:41 -07:00
notoria
902cd5c3ea Special case for (^)/2
The only solutions for `Z #= X^Z` are `X = Z, X in -1\/1`. Also helps
avoid big numbers.
2024-01-08 21:50:02 +01:00
Mark Thom
f9eadc8e6a Merge pull request #2278 from notoria/clpz
Small fixes for `CLP(ℤ)`
2024-01-07 18:59:38 -07:00
Mark Thom
7b8c8fdda7 Merge pull request #2277 from triska/master
Correct arithmetic disequalities accidentally broken in f5d9a67f36
2024-01-07 18:59:18 -07:00
notoria
1f3de74cbd Fix bound computation in (^)/2
?- A in 0..1, B in-1..0, A^2 #= B.
   false. % unexpected.
?- A^2 #= B, A in 0..1, B in-1..0.
   A = 0, B = 0.
2024-01-07 20:19:40 +01:00
notoria
770ead9c05 Remove min/2 and max/2 when possible
?- A #= min(A,B).
   clpz:(B#>=A), clpz:(A#=min(A,B)). % unexpected.
?- A #= min(A,0).
   clpz:(A in inf..0), clpz:(A#=min(A,0)). % unexpected.
2024-01-07 20:15:28 +01:00
notoria
fe371ff1d1 Fix min/2 and max/2
?- X in 0..1, Y in 2..4, Z #= max(X,Y).
   clpz:(X in 0..1), clpz:(Z#=max(X,Y)), clpz:(Z in 2..4), clpz:(Z#>=Y), clpz:(Y in 2..4). % unexpected.
?- X in 0..1, Y in 2..4, Z #= max(Y,X).
   Y = Z, clpz:(X in 0..1), clpz:(Y in 2..4).
2024-01-07 20:14:32 +01:00
Markus Triska
2f783f0aef Correct arithmetic inequalities accidentally broken in f5d9a67f36.
Example:

    ?- X #>= Y.
    %@    clpz:(X#>=Y).
2024-01-07 09:33:54 +01:00
Mark Thom
92b85d4ba6 Merge pull request #2264 from Skgland/fix-os-argv-1
Fix os:argv/1 in case the raw args contain multiple `--`
2024-01-06 15:52:53 -07:00
Markus Triska
e702fe5c68 ADDED: Support for higher-order non-terminals phrase//[2,3]
These non-terminals take a grammar rule body and additional arguments
as arguments. These arguments are appended to the first argument.

A key motivation for the introduction of these non-terminals is found
in the discussion and sample code provided by @bakaq in:

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

In this way, portable higher-order DCG programming is possible while
keeping the logical grammar rule expansion implementation dependent.

Example:

    ?- phrase(phrase('.', a, []), Cs).
       Cs = "a".
2024-01-06 14:02:13 +01:00
Mark Thom
ed92e1b4f4 Merge pull request #2275 from triska/master
Prevent assertion of clauses for (-->)/2 when DCGs are enabled.
2024-01-05 12:33:05 -07:00
Markus Triska
f2b63d1689 Prevent assertion of clauses for (-->)/2 when DCGs are enabled.
This addresses #1508.
2024-01-05 19:04:53 +01:00
Mark
d51defed06 fix cargo --fmt check error 2024-01-04 13:56:57 -07:00
Mark
851ea2c45b add missing permission error in compile_assert (#2271) 2024-01-04 13:53:46 -07:00
Mark
ec97ee5d41 fix acceptance of invalid syntax, update iso_conformity_tests.pl (#2270) 2024-01-04 13:14:58 -07:00
Skgland
78b83ca1e1 simplify os:argv/1 fix 2024-01-04 19:26:10 +01:00
Skgland
ea0130d114 use once to limit solutions instead of cut 2024-01-04 18:14:32 +01:00
Skgland
e66e7c5034 add another test case for os:argv/1 2024-01-04 18:12:38 +01:00
Mark Thom
ff010678c3 Merge pull request #2269 from aarroyoc/fix-2267
Use reqwest async and use futures::executor
2024-01-03 15:40:48 -07:00
Mark Thom
5abc72cc8b Merge pull request #2268 from aarroyoc/docs-toplevel
Small fixes in documentation and toplevel.pl
2024-01-03 14:52:34 -07:00
Adrián Arroyo Calle
fac8866fb4 Use reqwest async and use futures::executor 2024-01-03 22:43:22 +01:00
Adrián Arroyo Calle
d58e91303b Small fixes in documentation and toplevel.pl 2024-01-03 19:52:50 +01:00
Skgland
8bd0317e9c fix os:argv(V) not completing 2024-01-02 21:43:38 +01:00
Skgland
76b1167842 fix os:argv 2024-01-02 20:54:37 +01:00
Skgland
7537686d8f cover both args ending in -- and args not ending in -- for os:argv/1 test 2024-01-02 20:48:38 +01:00
Skgland
6fc340c4db remove unnecessary true 2024-01-02 20:27:14 +01:00
Skgland
38f25af47d cover trailing -- and os:argv([]) in os_argv test 2024-01-02 20:23:53 +01:00
Skgland
91df533834 fix os:argv/1 2024-01-02 19:55:27 +01:00
Skgland
9a05ca0667 add failing test for os:argv issue
See https://github.com/mthom/scryer-prolog/pull/2263#issuecomment-1874400820
2024-01-02 19:51:09 +01:00
Mark
ccf581d86f expand module names in transitive goals (#2255) 2024-01-02 11:34:38 -07:00
Mark
1dc546ac36 fix cargo fmt --check 2024-01-02 10:08:00 -07:00
Mark
372b4bca46 mark anonymous variables in compile_is_call (#2257) 2024-01-02 10:06:07 -07:00
Mark Thom
5c89029462 Merge pull request #2263 from aarroyoc/docs-toplevel
Move argv/1 to library(os)
2024-01-02 09:19:10 -07:00
Mark Thom
a475a8a899 Merge pull request #2262 from triska/faster_global_cardinality
ENHANCED: Suspend propagation during filtering in global_cardinality/2.
2024-01-02 09:18:42 -07:00
Adrián Arroyo Calle
47b5ae7984 Move argv/1 to library(os) 2024-01-02 13:16:11 +01:00
Markus Triska
f5d9a67f36 ENHANCED: Suspend propagation during filtering in global_cardinality/2.
This allows subsequently invoked constraints to take the entire
filtering results into account, instead of being invoked when the
obtained information is not yet entirely used.

The SICStus-style attributed variables mechanism of Scryer Prolog
automatically prevents very subtle interaction problems that can arise
in systems that do not give all variables that are involved in a
unification an opportunity to schedule their propagators.

An example of such a subtle interaction is:

    ?- tuples_in([[A,C,B]], [[3,1,3],[4,2,4]]),
       global_cardinality([A,B,D], [3-1,4-2]),
       A = 4.

A = 4 causes pgcc_check/1 and pgcc/2 to be queued in the fast and slow
queue, respectively. In the fast queue, there is also rel_tuple/2,
which is worked off after gcc_check/1 and simultaneously instantiates
both C and B (to 2 and 4, respectively). Instantiation of C schedules
do_queue//0 from verify_attributes/3. Note that C does not participate
in the global_cardinality/2 constraint.

Critically, B also gets an opportunity to schedule its propagators in
this case, so another gcc_check/1 is run before gcc_global/2!
2024-01-01 10:50:06 +01:00
Mark Thom
1f90dbec20 Merge pull request #2259 from bakaq/comment_warnings
Emit warnings as Prolog comments
2024-01-01 00:33:58 -07:00
Mark Thom
d713456e29 Merge pull request #2258 from triska/faster_scalar_product
ENHANCED: Suspend propagation during filtering in scalar product constraints.
2024-01-01 00:33:36 -07:00
bakaq
d9d90d1ae8 Emit warnings as Prolog comments 2023-12-30 17:39:20 -03:00
Markus Triska
62b61107e0 ENHANCED: Suspend propagation during filtering in scalar product constraints.
This allows subsequently invoked constraints to take the entire
filtering results into account, instead of being invoked when the
obtained information is not yet entirely used.

It speeds up programs such as the one in:

    https://github.com/triska/clpz/issues/26
2023-12-30 10:30:25 +01:00
Mark Thom
1e0fa56786 Merge pull request #2252 from aarroyoc/bump-versions
Update ring and bump versions in Cargo.lock
2023-12-29 13:10:19 -07:00
Mark Thom
97d5bf7e97 Merge pull request #2254 from bakaq/issue_1404
Parse as much of a list as possible as a partial string
2023-12-29 10:40:19 -07:00
Mark
75302ab716 check that Target is an atom in scoped_clause_to_evacuable (#2248) 2023-12-29 10:12:08 -07:00
Adrián Arroyo Calle
742de9e77c Update ring and bump versions in Cargo.lock 2023-12-28 20:23:01 +01:00
bakaq
921046e886 Parse as much of a list as possible as a partial string #1404 2023-12-28 15:12:32 -03:00
Mark Thom
781f4afe25 Merge pull request #2251 from triska/crrl_ed25519
improve structure, naming and explanation of PKCS#8v2 encoding
2023-12-27 17:37:42 -07:00
Markus Triska
10158f62e0 improve structure, naming and explanation of PKCS#8v2 encoding 2023-12-27 21:12:52 +01:00
Mark
42d6749501 throw errors from char_reader.rs and get_n_chars when reading bad UTF8 data (#2244) 2023-12-26 12:31:04 -07:00
Mark Thom
2fa46e6b6c Merge pull request #2245 from triska/crrl_ed25519
ENHANCED: Use crrl for Ed25519 signing and signature verification.
2023-12-26 10:09:08 -07:00
Markus Triska
47ec5eb6c6 ENHANCED: Use crrl for Ed25519 signing and signature verification.
The main motivation for this change is the introduction of the newly
available predicate ed25519_seed_keypair/2, allowing to generate a key
pair from a given seed. In this way, a key pair can be dynamically
generated from (for example) a password, using crypto_password_hash/3
in combination with crypto_data_hkdf/4 to generate the seed. The
advantage of this method is that the private key need not be stored at
all anywhere.

It is not possible to add a corresponding feature to ring, since it is
closed as "not planned": https://github.com/briansmith/ring/issues/1003

I also used this opportunity to move more of the logic to Prolog. We
now have total control of the key pair representation, and I also
changed the representation to conform to the PKCS#8 v2 standard,
something that only later ring versions do, while still being
backwards compatible with tools that produce a wrong representation
including earlier ring versions.

Another great advantage we get from this change is that the Ed25519
predicates now also run on the 32-bit and WASM versions of Scryer.
2023-12-26 07:33:44 +01:00
Mark Thom
799035c692 Merge pull request #2243 from bakaq/issue_2241
Fix anonymous attributed variables not showing in toplevel
2023-12-22 09:53:40 -07:00
Mark Thom
6c07256dc5 Merge pull request #2242 from triska/starting
explain starting Scryer Prolog
2023-12-22 09:52:56 -07:00
bakaq
20c6a327ea Fix anonymous attributed variables not showing in toplevel #2241 2023-12-22 13:07:09 -03:00
Markus Triska
bcfdc812f9 explain starting Scryer Prolog 2023-12-22 08:36:29 +01:00
Mark
373b08a91e fix off-by-one error caused logtalk test to crash 2023-12-21 12:14:12 -07:00
Mark
a7154968d8 fix cargo fmt --check 2023-12-21 10:24:01 -07:00
Mark
46e432c4d9 restore [o|i]ip zeroing to trust and fix '$get_clause_p'/3 (#2238) 2023-12-20 20:45:15 -07:00
Mark Thom
44a61fa72c Merge pull request #2218 from bakaq/when
Add when/2 and when_si/2
2023-12-20 13:42:46 -07:00
Mark
299df50066 clamp lower_bound_of_target_clause index (#840) 2023-12-18 15:21:30 -07:00
Mark
335330690f don't replace undefined module predicates (#2232) 2023-12-18 14:57:22 -07:00
Mark
f30cee7655 fix retraction of local dynamic predicates (#2215, #2232) 2023-12-18 13:28:45 -07:00
Mark
b943afb7c7 fix '$get_clause_p' whose old implementation was broken by the introduction
of lookahead indexing
2023-12-18 13:28:45 -07:00
Mark
30f3818c66 improve performance of lower_bound_of_target_clause (#1598) 2023-12-18 13:28:45 -07:00
Mark Thom
13624568b5 Merge pull request #2233 from aarroyoc/fix-functor-2214
Throw ValidType::Atom error in functor
2023-12-17 20:20:50 -07:00
Mark Thom
e4f64581ec Merge pull request #2229 from aarroyoc/fix-backslash-docs
Fix backslash docs
2023-12-17 20:20:41 -07:00
Adrián Arroyo Calle
d132227860 Throw ValidType::Atom error in functor 2023-12-17 12:38:46 +01:00
Adrián Arroyo Calle
45abb4703d Fix backslash docs 2023-12-16 19:01:39 +01:00
Mark Thom
a66b93c96f Merge pull request #2226 from mthom/dependabot/cargo/zerocopy-0.7.31
Bump zerocopy from 0.7.25 to 0.7.31
2023-12-15 11:33:03 -07:00
Mark
e5ea95db8d Revert "improve performance of lower_bound_of_target_clause (#1598)"
This reverts commit 8938331915.
2023-12-15 00:42:58 -07:00
Mark
8938331915 improve performance of lower_bound_of_target_clause (#1598) 2023-12-15 00:39:36 -07:00
dependabot[bot]
749767539a Bump zerocopy from 0.7.25 to 0.7.31
Bumps [zerocopy](https://github.com/google/zerocopy) from 0.7.25 to 0.7.31.
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.7.25...v0.7.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-15 03:52:00 +00:00
Mark
399b50b4d7 fix use of copy_term/3 for attribute goals in toplevel (#888, #2057, #2217) 2023-12-13 12:27:59 -07:00
Mark
82f3731c97 fix cargo style failure 2023-12-11 15:17:02 -07:00
Mark
658258c8c3 dereference port address in socket_server_open (#2222) 2023-12-11 14:38:20 -07:00
Mark
d14cce2374 restore print_tcp_listener in printer (#2221) 2023-12-11 13:29:27 -07:00
bakaq
f411fb1eb3 Tests for when/2 and solved bug 2023-12-11 12:28:59 -03:00
bakaq
6a30ccc907 Add when/2 and when_si/2 2023-12-10 20:55:50 -03:00
Mark Thom
1e1426cab0 Merge pull request #2207 from infogulch/ci-build-before-test
CI: Make test step output easier to read
2023-12-06 13:45:46 -07:00
Mark Thom
2e750671ca Merge pull request #2211 from Skgland/file-based-cli-tests
fix missing .pl file for singleton test case and move .pl files under `<testcase>.in/`
2023-12-06 13:27:14 -07:00
Skgland
147e47c8eb fix missing .pl file for singleton test case and move .pl files under <testcase>.in/
follow up to #2205
2023-12-06 21:21:28 +01:00
infogulch
7208916230 Clean up test step by compiling in build step 2023-12-05 20:51:04 -06:00
Mark Thom
43556f6df0 Merge pull request #2205 from Skgland/file-based-cli-tests
File based cli tests using trycmd
2023-12-05 14:45:00 -07:00
Mark Thom
c0c33392f1 Merge pull request #2203 from coasys/library-use-case
Don’t include unbound variables in results returned from run_query()
2023-12-05 13:14:30 -07:00
Nicolas Luck
77ce5a9586 Result with one empty match should be false 2023-12-05 15:00:07 +01:00
Mark Thom
8665722367 Merge pull request #2204 from mthom/remove_not_as_non_terminal
remove (\+)//1 (#1511)
2023-12-04 15:49:26 -07:00
Skgland
57b52fbe90 fix windows test by not disabling normalization
- this re-enables line ending and file path normalization
  - the latter might be unwanted, but there is only a shared flag
  - this might result in too loose matching of / and \ in stdout and stderr as prolog syntax may be mistaken for a path
2023-12-04 22:59:52 +01:00
Skgland
b052b62b16 fix formatting 2023-12-04 22:41:25 +01:00
Skgland
4df7e94d56 rename to match toml file
- goals.pl is used by both compound_goal.toml and multiple_goals.toml therefor renamed the later to share a common prefix with goals.pl, to keep them together
2023-12-04 22:23:04 +01:00
Skgland
fcf2e2db05 move .pl files from tests-pl next to the .toml of the test that uses them 2023-12-04 22:16:04 +01:00
Skgland
1c52de9ab1 File-oriented testing
inspired by #2191 but for the `run_top_level_test_with_args`s and  `run_top_level_test_no_args instead of the `load_module_test`s
2023-12-04 22:02:46 +01:00
Mark
9cd762d88a remove (\+)//1 (#1511) 2023-12-04 13:51:44 -07:00
Mark
eddcdaabef correct cargo fmt errors 2023-12-04 12:20:54 -07:00
Mark
2c76943d35 emit callable type errors from call/1 and phrase/3 when appropriate (#2202) 2023-12-04 12:20:14 -07:00
Nicolas Luck
24e3e1794e cargo fmt 2023-12-04 20:18:41 +01:00
Nicolas Luck
99055b553a Don’t include unbound variables in results returned from run_query 2023-12-04 20:10:06 +01:00
Nicolas Luck
576f13df60 Merge branch 'master' into library-use-case 2023-12-04 20:06:14 +01:00
Mark
004201abda add module name and missing predicate key to module_does_not_contain_claimed_export error (#2195) 2023-12-04 11:19:44 -07:00
Mark Thom
6899c6b63c Merge pull request #2201 from wkral/revert-2200-percent-graphic-char
Revert "Add '%' to the list of graphic_chars (#2199)"
2023-12-04 10:46:37 -07:00
William Kral
7f5c56a2a8 Revert "Add '%' to the list of graphic_chars (#2199)" 2023-12-03 22:32:49 -08:00
Mark Thom
92e07d2be1 Merge pull request #2200 from wkral/percent-graphic-char
Add '%' to the list of graphic_chars (#2119)
2023-12-03 23:02:05 -07:00
William Kral
aff1fdc6aa Add '%' to the list of graphic_chars (#2199) 2023-12-03 21:30:05 -08:00
Mark Thom
b0b1efcc49 Merge pull request #2194 from triska/clpz_monotonic
support (#=)/3 and (#<)/3 also in the monotonic execution mode
2023-12-02 20:16:59 -07:00
Markus Triska
eb575b9882 support (#=)/3 and (#<)/3 also in the monotonic execution mode 2023-12-02 22:33:56 +01:00
Mark
e667abb143 move propagated_var_nums insertion to proper scope (#2183) 2023-12-01 22:35:36 -07:00
Mark
347ee0211b propagate variable hits to parent branches in push_missing_vars (#2183) 2023-11-30 19:46:32 -07:00
Mark Thom
4f4feba6fc Merge pull request #2187 from triska/term_si
ADDED: term_si/1, true for terms
2023-11-29 13:52:38 -07:00
Markus Triska
8cb4dfef62 ADDED: term_si/1, true for terms
One use case is to ensure that once/1 is safe to use:

    term_si(Goal),
    once(Goal)

In such cases, Goal is ground and can yield at most one solution,
therefore once/1 does not remove any solutions.
2023-11-29 21:25:09 +01:00
Mark
de452bb2c2 fix overeager detection of (->)/2 in if position of (;)/2 (#2183) 2023-11-29 11:05:04 -07:00
Mark Thom
e8d8b09e52 Merge pull request #2186 from aarroyoc/cpu-now-wasm32
Implement $cpu_now in wasm32
2023-11-28 16:17:44 -07:00
Mark
631674db29 open pl files instead of directories (#2014) 2023-11-28 15:32:06 -07:00
Mark Thom
bc3ae52c7a Merge pull request #2184 from mthom/dependabot/cargo/openssl-0.10.60
Bump openssl from 0.10.57 to 0.10.60
2023-11-28 15:31:53 -07:00
Adrián Arroyo Calle
4c795018b4 Implement $cpu_now in wasm32 2023-11-28 23:30:02 +01:00
dependabot[bot]
651b7b2f59 Bump openssl from 0.10.57 to 0.10.60
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.57 to 0.10.60.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.57...openssl-v0.10.60)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 21:43:23 +00:00
Mark
5cb207d8b3 allocate cut variables properly (#2183) 2023-11-28 14:38:48 -07:00
Mark
de5de0cfaf allocate cut variables as temporaries when in non-allocating clause (#2183) 2023-11-28 12:58:54 -07:00
Mark
b8ef367824 bind missing module atom in retract/1 (#2182) 2023-11-25 20:59:04 -07:00
Mark Thom
f1458b772e Merge pull request #2169 from infogulch/flame
Benchmark improvements
2023-11-24 18:57:13 +01:00
Mark Thom
4181c9fab2 Merge branch 'master' into flame 2023-11-24 18:00:55 +01:00
Mark
1fbc7f9842 remove redundant running count increment in mark_non_callable (#2176) 2023-11-23 22:05:31 -07:00
Mark Thom
73ed08a412 Merge pull request #2179 from mthom/fix_stress_integration_test
Fix stress integration test
2023-11-23 21:31:12 +01:00
Mark
7cac13aefa set b0 to b for run_query call 2023-11-23 12:55:55 -07:00
Mark Thom
2e114df4c7 Merge pull request #2173 from aarroyoc/js-eval
Add js_eval/2 for wasm32
2023-11-22 21:52:49 +01:00
Adrián Arroyo Calle
36ab590c4e Add js_eval/2 for wasm32 2023-11-22 21:21:19 +01:00
Mark Thom
20da3427c1 Merge pull request #2177 from mthom/remove_assertx
Remove assert(a,z)/2 (#1975)
2023-11-22 17:54:55 +01:00
Mark
2811660fa7 remove assert(a,z)/2 (#1975) 2023-11-22 09:24:07 -07:00
Mark
ef8eb935d4 add CutPoint to select_switch_on_term_index (#2176) 2023-11-22 08:57:23 -07:00
infogulch
11832b524b Expose inference counts on Machine; publish to CI 2023-11-21 21:53:29 -06:00
infogulch
503e45eae1 Upgrade to latest iai for summary output 2023-11-21 21:53:19 -06:00
Mark Thom
041ec06dbc Merge pull request #1999 from triska/time_inferences
ENHANCED: time/1 now shows the number of inferences
2023-11-21 23:04:26 +01:00
Mark
3841b29db8 fix copy_term/2 variable copying bug in lists (#923, #2127) 2023-11-21 14:52:35 -07:00
infogulch
1c94624958 Benchmark improvements
* CI: Clean offline; display cleaned bytes; one artifact upload
* Add missing csv bench to iai benchmarks
* Generate flamegraphs for benchmarks
* Validate benchmark results separately
2023-11-21 13:54:16 -06:00
infogulch
c6f3700de1 Remove accidentally committed files 2023-11-21 13:54:16 -06:00
Markus Triska
186bba9d75 ENHANCED: time/1 now shows the number of inferences
Example:

    ?- time(member(X, "abc")).
       % CPU time: 0.000s, 1 inference
       X = a
    ;  % CPU time: 0.000s, 3 inferences
       X = b
    ;  % CPU time: 0.000s, 3 inferences
       X = c.

This is an initial step towards addressing #1039.
2023-11-21 20:36:44 +01:00
Mark
a3e83d59be fix error detection in int_pow (#2161) 2023-11-21 08:32:26 -07:00
Mark
54166b91eb add global_count inference counter that is always incremented (#2009) 2023-11-20 16:24:31 -07:00
Mark
1e5bb2d3da fix dif:vars_remove_goal/2 (#2175), untabify forms.rs to appease format check 2023-11-20 14:21:18 -07:00
Mark
8d9a759a7d remove unnecessary spaces around (|) (#2164) 2023-11-19 08:46:20 -07:00
Mark Thom
f32b035287 Merge pull request #2168 from aarroyoc/update-web-2023-11-15
Remove Scryer Prolog meetup banner :(
2023-11-16 21:07:25 +01:00
Mark Thom
a8e6930d19 Merge pull request #2159 from mthom/dependabot/cargo/rustix-0.38.21
Bump rustix from 0.38.14 to 0.38.21
2023-11-16 21:07:03 +01:00
dependabot[bot]
7fa4cb7ee8 Bump rustix from 0.38.14 to 0.38.21
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.38.14 to 0.38.21.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.14...v0.38.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-16 19:27:40 +00:00
Mark Thom
df5855dbda Merge pull request #2167 from infogulch/ci-checks-permission
Add checks permission to ci workflow
2023-11-16 09:30:27 +01:00
Adrián Arroyo Calle
e7f5ba6ca1 Remove Scryer Prolog meetup banner :( 2023-11-15 22:42:18 +01:00
Mark Thom
bcb68fcc3d fix sign/1 (#2157) 2023-11-15 20:38:24 +01:00
infogulch
e5ee918063 Add checks permission to ci workflow
This should allow dependabot builds to complete
2023-11-15 13:07:45 -06:00
Mark Thom
4be320abc8 fix shr crash (#2158) 2023-11-14 08:15:19 +01:00
Mark Thom
300f5f817f Merge pull request #2165 from infogulch/bench-csv
Bench csv
2023-11-14 07:32:07 +01:00
Mark Thom
574a4d0758 Merge pull request #2166 from triska/more_tests
mention more passing tests
2023-11-14 07:31:52 +01:00
Markus Triska
a02bd46094 mention more passing tests 2023-11-14 04:09:03 +01:00
infogulch
cab80d3fa2 Fix lint 2023-11-13 15:41:15 -06:00
infogulch
98d8b00d7a Add csv parsing benchmark #1403 2023-11-13 15:41:06 -06:00
infogulch
40608dc8d4 Simplify benchmark setup; support mutating machine 2023-11-13 15:40:27 -06:00
Mark Thom
3b7d4a7b36 Merge pull request #2153 from infogulch/benchmark
Add benchmarks
2023-11-12 07:21:25 +01:00
Joe Taber
f704fcb41d Fix formatting issues 2023-11-11 13:33:19 -06:00
infogulch
8570f119c0 Add benchmarks using library interface 2023-11-11 13:32:34 -06:00
infogulch
1656d9d7ad CI: Setup rust in new action; split style & report actions 2023-11-11 13:32:34 -06:00
Mark Thom
50c64b8512 correct style failures 2023-11-11 18:57:47 +01:00
Mark Thom
83b9c6184c fix permanent variable handling over branches and cells (#2154) 2023-11-11 17:20:22 +01:00
Nicolas Luck
0ad479498c Activate run_query() integration test 2023-11-07 10:28:36 +01:00
Mark
2dd1f6e880 fix compare_pstr_to_string (#2148) 2023-11-04 19:32:23 -06:00
Mark Thom
da0018efdf Merge pull request #2147 from infogulch/lint-format
Resolve all lints and formating; split style checks into separate job
2023-11-04 11:34:11 -06:00
infogulch
16f768197b Split style checks, test reports into separate job 2023-11-04 02:22:24 -05:00
infogulch
d6fe5b5aad Bump Cargo.lock to reflect new scryer version 2023-11-04 02:20:55 -05:00
infogulch
dfe4fbfdc5 Add previous commit to .git-blame-ignore-revs 2023-11-04 02:20:37 -05:00
infogulch
9444e62df9 Resolve lints and format 2023-11-04 02:16:54 -05:00
Mark
dddffb01a6 update release version linked on website 2023-11-02 17:34:49 -06:00
Mark
4520bedf8d update release link 2023-11-02 17:32:33 -06:00
Mark
126d7bba57 bump version number to v0.9.3
Some checks failed
CI / build-test (--no-default-features, ubuntu-22.04, true, nightly, wasm32-unknown-unknown) (push) Has been cancelled
CI / build-test (macos-11, true, stable, x86_64-apple-darwin) (push) Has been cancelled
CI / build-test (true, ubuntu-22.04, true, stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-20.04, true, stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, 1.70, 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, nightly, 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 (windows-latest, true, stable, x86_64-pc-windows-msvc) (push) Has been cancelled
CI / logtalk-test (push) Has been cancelled
CI / release (push) Has been cancelled
Docker Publish / build (push) Has been cancelled
2023-11-02 17:01:54 -06:00
Mark Thom
575245c62f Merge pull request #1880 from coasys/library-use-case
Programatic use of Machine / Scryer as library
2023-11-02 16:50:01 -06:00
Nicolas Luck
59264c0aa5 Check for target_arch = “wasm32”
instead of target_os = “wasi”
2023-11-02 19:07:55 +01:00
Nicolas Luck
ee1bd9e006 Merge branch 'master' into library-use-case 2023-11-02 11:12:54 +01:00
Mark
dfd9e43405 correct (\+)/1 (#2141) 2023-10-31 23:58:59 -06:00
Mark
8f06ef965a remove non-conforming hyperbolic and log10 evaluable functors 2023-10-28 18:17:43 -06:00
Mark
ae7cf15c70 fix signed modulus bug (#2134) 2023-10-28 18:13:42 -06:00
Mark Thom
469d2bd104 Merge pull request #2140 from aarroyoc/rename-to-upper
Rename `to_upper` to `upper` and `to_lower` to `lower`
2023-10-28 14:48:10 -06:00
Adrián Arroyo Calle
682b2ada4c Rename to_upper to upper and to_lower to lower 2023-10-28 19:16:07 +02:00
Mark Thom
f4e5426e97 Merge pull request #2137 from infogulch/master
CI: compile wasm separately; default 22.04; matrix
2023-10-26 14:07:18 -06:00
infogulch
6bae789bd6 CI: compile wasm separately; default 22.04; matrix 2023-10-26 14:23:32 -05:00
Mark
ff19db0084 qualify dif/2 call in reinforce_goal (#2135) 2023-10-25 12:26:01 -06:00
Mark
f10d7c05f9 correct char_type/2 (#2132) 2023-10-24 15:35:37 -06:00
Mark
f80dff851b remove unnecessary macros inclusion in mock_wam.rs 2023-10-24 15:15:29 -06:00
Mark
63cbeb9a3c correct modulus bug (#2118, #2119) 2023-10-24 14:20:26 -06:00
Mark
773d3f81fd remove list remnants from stack iteration in printer when cyclic (#2131) 2023-10-24 14:10:11 -06:00
Mark
4ab3e23b1f fix cycle detection in lists by looking backward analogously to the same process for structures (#2130) 2023-10-24 12:23:05 -06:00
Mark
d588b18c39 correct LIS backward bug in cycle_detection.rs 2023-10-23 13:28:44 -06:00
Mark
97b899f4a3 add STOP_AT_CYCLES const parameter for CycleDetectingIter 2023-10-23 12:05:13 -06:00
Mark
6a913bc4cc use Deutsch-Schorr-Waite to implement acyclic_term/1 (#2128) 2023-10-21 21:47:29 -06:00
Mark
bf46c4b5c1 use topo_sort to correct acyclic_term (#2124, #2125) 2023-10-18 12:00:40 -06:00
Nicolas Luck
69a0725c30 Cleanup Cargo.toml 2023-10-17 22:09:34 +02:00
Nicolas Luck
0c9740fe52 Fix wasm warnings 2023-10-17 22:07:38 +02:00
Mark
43f0b6c28d fix cyclicity detection through variables (#2122, #2123) 2023-10-16 15:19:43 -06:00
Nicolas Luck
2776beb842 Use std::sync::RwLock instead of tokio::sync::RwLock (by @aarroyoc) 2023-10-16 14:59:06 +02:00
Nicolas Luck
d7fa6c0ade Fix signalling of success/failure (by @mthom) 2023-10-16 14:56:17 +02:00
Nicolas Luck
025412aac0 Revert "Remove unuse import"
This reverts commit bfa7d3cf41.
2023-10-16 14:55:38 +02:00
Nicolas Luck
bfa7d3cf41 Remove unuse import 2023-10-16 13:44:51 +02:00
Nicolas Luck
9990780b82 Merge branch 'master' into library-use-case 2023-10-16 13:43:26 +02:00
Mark
dff2e73842 more eagerly check for cyclicity of variables in cycle detecting stackless iterator (#2121) 2023-10-15 17:43:29 -06:00
Mark
e2000859b6 add backward looking cyclicity check for variables in cycle detecting stackless iterator (#2111, #2117) 2023-10-15 13:07:41 -06:00
Mark
3a6aee72a3 correct is_cyclic again for non-variable ref cells (#2116) 2023-10-15 01:28:42 -06:00
Mark
3fb2e451a2 improve cycle detection in detect_list_cycles (#2111) 2023-10-15 00:58:19 -06:00
Mark
7875b96956 simplify stackless iterator is_cyclic (#2111) 2023-10-14 23:56:46 -06:00
Mark
d96c9e00b7 correct more acyclic_term/1 issues (#2111, #2114), add acyclic_term tests 2023-10-14 18:38:10 -06:00
Mark
379c252b89 correct cyclic variable check in cycle detecting stackless iterator (#2111, #2113) 2023-10-14 14:03:47 -06:00
Mark
307cb56ef5 fix bugs & incompleteness of cycle-detecting stackless iterator (#2111) 2023-10-14 13:00:58 -06:00
notoria
62e6ca02f9 Reify (^)/2
Like (/)/2, (^)/2 can fail in cases such as 0 #==> X #= 2^(-1), where success is expected.
2023-10-14 12:11:24 -06:00
notoria
c6fcbe20e1 Trigger propagator for popcount/1 2023-10-14 12:11:24 -06:00
notoria
a1b71f0440 Trigger propagator for sign/1 2023-10-14 12:11:24 -06:00
notoria
dc08c26d9f Trigger propagator for (xor)/2 2023-10-14 12:11:24 -06:00
Mark
669023914a add bounds checks for stackless iterator (#2110) 2023-10-14 12:11:22 -06:00
Mark
e4a677ceea detect all cycles in roughly linear time and constant space (#2102) 2023-10-13 18:05:52 -06:00
Mark
8aadc99f1d fix bugs in marker algorithm iterator 2023-10-13 14:38:50 -06:00
Mark
1ea397a807 correct (mod)/2 (#2103, #2107) 2023-10-13 14:33:50 -06:00
Mark Thom
b348f54c33 Merge pull request #2105 from triska/morphing_propagators
ENHANCED: Omit unnecessary residual constraints in disentailed reified (//)/2
2023-10-12 19:10:54 -06:00
Markus Triska
721cf20cf7 shift morphing to the more general p/2 case 2023-10-12 23:32:04 +02:00
Markus Triska
6ed9a99832 ENHANCED: Omit unnecessary residual constraints in disentailed reified (//)/2
Example:

    ?- #\ 0#=(Y// -1)/0.
    %@    clpz:(Y in inf..sup).

This addresses #2104.
2023-10-12 23:32:04 +02:00
Mark
1163d14ea1 fix control construct bugs, iter indentation (#947) 2023-10-11 12:41:45 -06:00
Mark
b83631fb20 Merge branch 'master' of ssh://github.com/mthom/scryer-prolog 2023-10-11 10:11:50 -06:00
Mark
902b08e657 follow marked variables to end in eager_stackful_iter (#2100, #2101) 2023-10-10 20:45:08 -06:00
Mark
b5fdde08aa follow marked variables to end in eager_stackful_iter (#2100, #2101) 2023-10-10 16:03:12 -06:00
Mark Thom
4962c3df11 Merge pull request #2098 from triska/morphing_propagators
Queue morphed propagators to give them a chance for propagation
2023-10-10 14:57:00 -06:00
Markus Triska
8de3498e07 use round brackets around operators to form valid Prolog terms 2023-10-10 21:32:45 +02:00
Markus Triska
26fdf83a48 update comment to reflect the used propagators 2023-10-10 21:32:45 +02:00
Markus Triska
32af047925 remove definition and calls of do_queue/0, which has become a NOP 2023-10-10 21:32:45 +02:00
Markus Triska
282633c877 ENHANCED: Queue morphed propagators to give them a chance for propagation.
This addresses #2096:

    ?- B in -2..0, 0#<==>0#=0/(B*B),labeling([],[B]).
       B = 0.

    ?- A#<==>A#=A/A^2,A=0.
       A = 0.
2023-10-10 21:32:45 +02:00
Mark
77de570aa4 report pre-marked values from eager stackful iterator (#2097) 2023-10-10 12:56:27 -06:00
Mark Thom
c9df19ca30 Merge pull request #2094 from triska/unary_minus
ENHANCED: use (+)/2 to express unary minus
2023-10-09 15:06:09 -06:00
Markus Triska
7c1cd18a06 Z --> Y 2023-10-09 23:13:09 +02:00
Mark Thom
0ca2356be5 Merge pull request #2092 from triska/morphing_pexp
attach the propagator to Y
2023-10-09 15:05:59 -06:00
Markus Triska
8329d222cb ENHANCED: use (+)/2 to express unary minus
This makes answers a bit shorter and more readable.

Example:

    ?- X #= -Y.
       clpz:(X+Y#=0).

This addresses #2058.
2023-10-09 22:59:51 +02:00
Markus Triska
1c3df1cdd7 attach the propagator to Y 2023-10-09 21:55:39 +02:00
Mark Thom
b149805b9e Merge pull request #2091 from triska/morphing_pexp
ENHANCED: Omit projection of morphed (^)/2 in disentailed constraints.
2023-10-09 13:43:25 -06:00
Markus Triska
99348ec309 ENHANCED: Omit projection of morphed (^)/2 in disentailed constraints.
Example:

    ?- B #<==> (0^Y/0) #= Z.
       B = 0, clpz:(Y in 0..sup), clpz:(Z in inf..sup).
2023-10-09 21:33:04 +02:00
Mark Thom
c6976c0f92 Merge pull request #2090 from triska/morphing_propagators
FIXED: Queue triggered propagators to be processed after pexp/3 finishes
2023-10-09 13:20:44 -06:00
Markus Triska
cacc7f3193 FIXED: Queue triggered propagators to be processed after pexp/3 finishes
If reification constraints (such as reified equality) are triggered
here, then they may wish to disable this propagator and remove
attributes from auxiliary variables. If the pexp/3 propagation is
interrupted for that purpose, then the attributes will be
unintentionally reattached by the following fd_put/3 calls in this
propagator. We must ensure that this propagator completely finishes,
so we queue the triggered propagators for later processing.

geq/2 implements propagator activation outside the queue, and thus
should not be used in propagators in the way it was used here.

pexp/3 by itself may not seem particularly important. However, it can
arise by metamorphosis from Var*Var. Example:

    ?- A#<==> -1#=C*C, C in 0..1.
       A = 0, clpz:(C in 0..1).

This addresses #2089.
2023-10-09 20:42:28 +02:00
Mark
4b9cf0952e do not push stack variables to the heap in term_variables (#2087) 2023-10-09 11:38:17 -06:00
Mark Thom
6d37684e9c Merge pull request #2088 from triska/morphing_propagators
ENHANCED: Remove no longer needed morphed propagators.
2023-10-08 12:45:45 -06:00
Markus Triska
743412de33 ENHANCED: Remove no longer needed morphed propagators.
This addresses all remaining cases from #2083, excepting (//)/2:

    ?- #\ 1#=(X*X)/0.
       clpz:(X in inf..sup).

    ?- #\ 1#=(X+X)/0.
       clpz:(X in inf..sup).

Still remaining:

    ?- #\ 0#=(Y// -1)/0.
       clpz:(-1*Y#=_A).
2023-10-08 20:15:56 +02:00
Mark Thom
11c1ee4481 Merge pull request #2085 from triska/reification_same_queue
Use the same queue for variables in reified constraints
2023-10-08 12:01:26 -06:00
Markus Triska
f34703a279 remove no longer needed goal projection for pdiv 2023-10-08 12:07:17 +02:00
Markus Triska
5cce8ddd7d ENHANCED: avoid pending residual constraints in disentailed reified (div)/2
This addresses #2083:

    ?- #\0#=0//0 div 2.
       true.
2023-10-08 11:53:06 +02:00
Markus Triska
ff63eacf2c replace list//1 by seq//1 from library(dcgs) 2023-10-08 11:28:52 +02:00
Markus Triska
8121dce2a4 instead of prdiv, use ptimes directly 2023-10-08 09:46:41 +02:00
Markus Triska
4d910f6bfe FIXED: variables in reified propagators must share the same queue
Otherwise, propagation steps may be inadvertently omitted, if
propagators are scheduled in a different queue.

This addresses #2084.
2023-10-08 09:45:20 +02:00
Markus Triska
51c00fce57 adapt query to Scryer Prolog 2023-10-08 09:26:22 +02:00
Mark
1ab14ea519 mark both components of a PStrLoc (#2082) 2023-10-07 18:48:49 -06:00
Mark Thom
c5c7c1913a Merge pull request #2080 from triska/reified_slash
Corrections to reified (/)/2
2023-10-06 15:31:13 -06:00
Markus Triska
3fc969b38b reorder and realign entries to form a contiguous group starting with d(D) 2023-10-06 22:58:56 +02:00
Markus Triska
7ed38d6c6c FIXED: reification of (/)/2 for undefined subexpressions
This addresses #2078 and #2079.
2023-10-06 22:55:46 +02:00
Mark
fa68fa211c replace eager_stackful_iter's mark stack with a second unmark phase 2023-10-05 20:21:28 -06:00
Nicolas Luck
69cf2c36bc Deactivate tokio runtime respawning on interrupt 2023-10-05 12:11:28 +02:00
Nicolas Luck
640c637ca8 Merge branch 'master' into library-use-case
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/http.rs
#	src/machine/mock_wam.rs
#	src/machine/mod.rs
#	src/machine/system_calls.rs
2023-10-05 12:06:47 +02:00
Mark
0ad4427f83 use eager_stackful_preorder_iter in variable_set, add term_variables/1 tests 2023-10-04 15:12:25 -06:00
Mark
1bfdea7527 rewrite ground_test, add tests for ground/1 (#2075) 2023-10-04 14:17:03 -06:00
Mark
6c36d067d7 Revert "consider Str, PStrLoc in ElideLists of StackfulHeapIterator (#2075)"
This reverts commit 1e60eeef34.
2023-10-04 11:57:09 -06:00
Mark
1e60eeef34 consider Str, PStrLoc in ElideLists of StackfulHeapIterator (#2075) 2023-10-04 00:27:01 -06:00
Mark Thom
005570c90a Merge pull request #2076 from infogulch/master
Pin logtalk to version before scryer support was removed
2023-10-03 21:24:19 -06:00
infogulch
c070fbec62 Pin logtalk to version before scryer support was removed 2023-10-03 21:14:45 -05:00
Mark
f9d44c93fd check for free variables in locations removed from iterator stack in ground_test (#2075) 2023-10-03 15:31:01 -06:00
Nicolas Luck
fab5ca9440 Fix nested List parsing 2023-10-03 20:19:57 +02:00
Nicolas Luck
7b128a9f00 Handle stub_b = b as false 2023-10-03 20:12:11 +02:00
Mark
fd14869ddc correct cycle detection in ground/1 (#2073) 2023-10-03 12:07:36 -06:00
Mark Thom
07d7d3b13b Merge pull request #2074 from triska/expansion_simpler
Revert "FIXED: corrections to expansion_simpler/2"
2023-10-03 11:54:04 -06:00
Markus Triska
7c10683e46 Revert "FIXED: corrections to expansion_simpler/2"
This reverts commit f3b848537a.

The root cause of this problem is a mistake in ground/1. See #2073.
2023-10-03 19:16:04 +02:00
Mark Thom
3ffea2d987 Merge pull request #2072 from aarroyoc/fix-urldecode
Improved version of url_decode
2023-10-02 16:06:35 -06:00
Mark Thom
770665a7b8 Merge pull request #2070 from triska/expansion_simpler
FIXED: corrections to expansion_simpler/2
2023-10-02 16:06:10 -06:00
Adrián Arroyo Calle
93ff049e54 Improved version of url_decode 2023-10-02 23:49:09 +02:00
Markus Triska
f3b848537a FIXED: corrections to expansion_simpler/2
Example:

    ?- X = 0, Y = 0, Z #= X-1 + Y-1.
       X = 0, Y = 0, Z = -2.

This addresses #2064.

See ca5a5b4392 for a previous issue in
this logic.
2023-10-02 23:17:06 +02:00
Mark
27b971cbfa add registers to inlined instruction functors 2023-10-01 23:50:07 -06:00
Mark
a1ceeb697a consider an '$aux' a relation of the unexpanded goal's variables in compile_inline_or_expanded_goal (#2062) 2023-10-01 23:49:55 -06:00
Mark
4a8aa0acbd throw instantiation_error from error/2 if Error_term uninstantiated (#2060) 2023-09-30 22:35:46 -06:00
Mark
6fa00b5b55 get rid of inference_limit_exceeded(B) as an error term (#2023) 2023-09-30 22:35:46 -06:00
Mark Thom
c2218faf47 Merge pull request #2066 from bakaq/dif_tests
Add tests for #2056
2023-09-30 18:41:50 -06:00
bakaq
21c61b6e3a Add tests for #2056 2023-09-30 21:33:42 -03:00
Mark
b065e1cd53 correct depth calculation for lists that are their own car (#1876) 2023-09-30 17:15:20 -06:00
Mark
7d6ce119f5 substitute names for cyclic variables permitted by max_depth > 0 in check_for_seen using a loop (#2057) 2023-09-30 16:47:13 -06:00
Mark
62c23166fa implement ListElisionPolicy to restore previous printer behavior 2023-09-30 16:00:15 -06:00
Mark
25afc11168 correct skipping of not fully visited lists in stackful heap iterator (#2056, #2063, #2065) 2023-09-30 15:24:30 -06:00
Mark
9e713406d3 correct max_depth marking for lists 2023-09-30 11:44:20 -06:00
Mark
f33f641f11 further max_depth improvements 2023-09-29 12:07:12 -06:00
Mark
ca2ddfeed0 improve max_depth write option (#1876, #2053) 2023-09-29 01:00:14 -06:00
Mark
11cd42379b do not deref AttrVar binding in redo_attr_var_binding (#2059) 2023-09-28 15:25:57 -06:00
Mark
40d3345cd5 revert throwing domain errors for unexpected forms of read-options (#2015) 2023-09-27 18:58:45 -06:00
Mark
035e214ef5 check for ChildCloseList in print_struct on [] (#2039) 2023-09-27 18:16:23 -06:00
Mark
f630a8cc1a fix add_predicate_declaration bug not correctly identifying lists of predicate indicators (#2049, #2050, #2051, #2052) 2023-09-27 17:55:15 -06:00
Mark
0bfb08e464 remove debug symbols from release builds (#2054) 2023-09-27 13:53:33 -06:00
Mark Thom
5f6ae3857b Merge pull request #1998 from aarroyoc/warp-http-server
Replace Hyper with Warp for HTTP server
2023-09-27 13:35:46 -06:00
Mark
750544dd2a fix max_depth settings for partial strings on lists (#1876) 2023-09-27 13:34:24 -06:00
Adrián Arroyo Calle
660860bccf Replace Hyper with Warp for HTTP server
- Use Warp
- Optimize clones
- HTTPS server
- Content-Length limit
- HTTP Basic Auth
- Stop server with Ctrl-C
2023-09-27 18:50:19 +02:00
Mark Thom
841aaa78b4 Merge pull request #2048 from infogulch/master
Build windows with msvc #1998; add ubuntu 22.04 target #2047
2023-09-27 10:43:54 -06:00
infogulch
93f46a4142 Tidy ci.yaml 2023-09-27 02:58:48 -05:00
infogulch
a86db1bde8 Build windows with msvc 2023-09-27 02:57:02 -05:00
infogulch
4163437d5a Add ubuntu 22.04 2023-09-27 00:31:08 -05:00
Mark
a6535c28ea remove module_resolution_error (#2035) 2023-09-26 14:06:21 -06:00
Mark
2efe95f2fb filter our builtins from current_predicate/1 (#153) 2023-09-26 13:46:21 -06:00
Mark
56992570d8 check for predicate_indicator list and sequence types in add_predicate_declaration (#1586) 2023-09-26 12:54:16 -06:00
Mark
39934208c3 throw instantiation_error when appropriate from parse_write_options_ (5.5.12 of the standard, #1965) 2023-09-26 12:08:51 -06:00
Mark
193bb313fd correct OutOfFiles resource error (#1375) 2023-09-26 11:22:56 -06:00
Mark
e8334f9b67 add predicate indicator sequences to loader:add_predicate_declaration (#1586) 2023-09-26 11:21:47 -06:00
Mark
280ff8b5d0 throw an error instead of allowing builtin modules to be overwritten (#2042) 2023-09-25 19:21:34 -06:00
Mark
172bb9e96a Merge branch 'master' of ssh://github.com/mthom/scryer-prolog 2023-09-25 16:09:20 -06:00
Mark
c04f1dea48 fix off-by-1 bug in '$skip_max_list'/4 (#2037) 2023-09-25 16:09:04 -06:00
Mark Thom
9114c982e0 Merge pull request #2032 from bakaq/phrase_from_stream
Add phrase_from_stream/2 to library(pio)
2023-09-25 12:04:33 -06:00
bakaq
f644a76281 Remove reposition option from phrase_from_file/2 2023-09-25 02:18:05 -03:00
bakaq
63bb993c02 Inline string_get_n_chars_/3 2023-09-24 19:29:51 -03:00
bakaq
c50291cec8 Better string_get_n_chars_/3 2023-09-24 19:21:03 -03:00
bakaq
2fe79b5fc3 Fixed bug with '$skip_max_list'/4 2023-09-24 15:08:24 -03:00
bakaq
b4fab5a806 Use '$skip_max_list'/4 in string_get_n_chars/4 2023-09-24 14:32:44 -03:00
bakaq
35d0042be1 Add phrase_from_stream/2 to library(pio) 2023-09-24 14:32:44 -03:00
Mark Thom
75dcc3f276 Merge pull request #2040 from rujialiu/allow-cr
Solves CRLF/CR issue by considering'\r' a `layout_char` #553 #2028
2023-09-24 11:11:15 -06:00
Rujia Liu
9c43974747 Solves CRLF/CR issue by considering'\r' a layout_char #553 #2028 2023-09-24 19:10:40 +08:00
Mark Thom
4be5fc6d43 Merge pull request #2038 from infogulch/patch-1
Allow all matrix ci jobs to run to completion even if one fails
2023-09-24 00:44:33 -06:00
Joe Taber
f03336b3a2 Allow all jobs to run to completion even if one fails
See: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast
2023-09-23 21:43:51 -05:00
Mark
a5db117ef6 fix off-by-1 bug in ''/4 (#2037) 2023-09-23 18:32:32 -06:00
Mark
142e0c2c3a don't parse bracketed non-operators as functor terms (#2033) 2023-09-23 14:26:28 -06:00
Mark
1c8cd85f6c record compaction depth after reduce_op if '|' an operator (#1905) 2023-09-23 00:19:54 -06:00
Mark
c547f67c54 add (now failing) test 317 to iso_conformity_tests.pl 2023-09-21 16:49:45 -06:00
Mark
c26e9436b4 generalize multifile/dynamic/discontiguous declarations over lists of predicate indicators (#1586) 2023-09-21 16:22:41 -06:00
Mark
b3239abea1 throw resource error if OpenOptions raises an error of uncategorized kind (#1375) 2023-09-21 14:36:53 -06:00
Mark Thom
81edd4592f Merge pull request #2027 from bakaq/dif
Improvements to dif/2
2023-09-21 11:32:38 -06:00
Mark Thom
7484433e2b Merge pull request #2026 from infogulch/master
Simplify fn maybe
2023-09-21 11:31:35 -06:00
bakaq
cb79e83510 Avoid dif/1 attribute with empty list
Closes #1956
2023-09-21 14:00:37 -03:00
bakaq
cac52c0537 Run dif tests on cargo test 2023-09-21 12:14:27 -03:00
bakaq
f5c23fbb16 Tests for dif/2 2023-09-20 20:56:25 -03:00
bakaq
a64a765f32 Improved dif/2 2023-09-20 20:56:04 -03:00
infogulch
eecfeb2d03 Simplify maybe
Fix whitespace
2023-09-20 18:23:07 -05:00
Mark Thom
54a0313d72 Merge pull request #2025 from aarroyoc/random-seed
Use a SeedableRng to generate random numbers
2023-09-20 15:03:21 -06:00
Adrián Arroyo Calle
7cf6e77f4d Use a SeedableRng to generate random numbers 2023-09-20 22:07:46 +02:00
Nicolas Luck
4968fa0024 Remove debugging println! 2023-09-18 19:31:56 +02:00
Nicolas Luck
5e55625733 Remove some debug println!s 2023-09-18 19:12:50 +02:00
Nicolas Luck
65f64e428e Construct and return exception string 2023-09-18 13:25:27 +02:00
Nicolas Luck
d2f5291412 mthom's changes fixing the panic 2023-09-18 13:01:27 +02:00
Mark
9dc1c339ef remove unnecessary Result return type from read_term_from_heap 2023-09-15 14:10:42 -06:00
Nicolas Luck
c86304b18e Remove lib_toplevel.pl 2023-09-15 11:01:16 +02:00
Nicolas Luck
7c83a1fb8e Cleanup code that's not needed anymore 2023-09-15 10:48:41 +02:00
Nicolas Luck
f02728aab3 Construct QueryResult from printer output 2023-09-15 10:44:59 +02:00
Nicolas Luck
48283c4dbc mthom's changes to run_query with backtracking 2023-09-15 10:04:11 +02:00
Nicolas Luck
6bdd7f3a3f mthom's revised run_query with manually created printer and some cleanup 2023-09-14 22:00:50 +02:00
Mark Thom
6e0dd371a0 Merge pull request #2018 from triska/libffi
use version from crates.io
2023-09-13 15:05:31 -06:00
Markus Triska
5b1df8c4b3 use version from crates.io 2023-09-13 20:45:38 +02:00
Nicolas Luck
5fa68e253c lib_machine tests should not be tokio/async 2023-09-13 19:51:57 +02:00
Mark Thom
3e14ef634f Merge pull request #2017 from cmpute/master
Upgrade dashu and make the related code more idomatic.
2023-09-13 10:41:34 -06:00
Jacob
adb5fcf708 Upgrade dashu and some changes 2023-09-13 12:29:12 -04:00
Nicolas Luck
136463c92e Merge branch 'master' into library-use-case
# Conflicts:
#	Cargo.toml
#	src/atom_table.rs
#	src/bin/scryer-prolog.rs
#	src/http.rs
#	src/machine/mock_wam.rs
#	src/machine/mod.rs
#	src/machine/system_calls.rs
2023-09-13 18:13:14 +02:00
Nicolas Luck
ef56193c44 Use write_term_to_heap as suggest, clean up, and include error in comment 2023-09-13 17:17:02 +02:00
Mark
4077040d03 update syn crates 2023-09-12 14:52:42 -06:00
Mark
de440a8c92 get rid of OssifiedOpDir, DBRef, simplify current_op/3 2023-09-11 15:36:15 -06:00
Mark Thom
c75f74f681 Merge pull request #1993 from coasys/dashu-fixes
Dashu fixes
2023-09-11 11:33:16 -06:00
Mark Thom
46c78ad338 Merge pull request #2013 from gruhn/fix-docker-debian-bullseye
fix Docker image
2023-09-11 11:23:10 -06:00
Niklas Gruhn
2b7a8875c0 fix Docker image
The Docker image is broken with the error message:

    scryer-prolog: error while loading shared libraries:
    libssl.so.1.1: cannot open shared object file: No such
    file or directory

We use the base base image `debian:stable-slim` and in the
background a new Debian version was marked stable (bookworm),
which has libssl3 instead of libssl1.1 installed.

I downgraded the base image to the previous stable version
(bullseye), which seems to fix the issue.
2023-09-11 01:46:59 +02:00
Mark Thom
2ee131c52c Merge pull request #2010 from aarroyoc/update-website-0.9.2
Update INDEX.dj
2023-09-10 17:45:45 -06:00
Adrián Arroyo Calle
f3901cd8b8 Update website to mention 0.9.2 and playground 2023-09-10 23:12:10 +02:00
Fayeed Pawaskar
7f0536b51d Merge branch 'master' into dashu-fixes 2023-09-10 21:51:02 +05:30
Fayeed Pawaskar
af76659830 Removed value method and use deref to get type 2023-09-10 21:40:22 +05:30
Mark Thom
fb658c4918 Merge pull request #2008 from triska/clpb_improvements
Various CLP(B) improvements
2023-09-09 00:40:30 -06:00
Markus Triska
85cc4a80d0 remove code that is not needed in Scryer Prolog 2023-09-09 07:46:01 +02:00
Markus Triska
6fe85c5779 remove more attributes so that they do not appear in residual goals 2023-09-09 07:46:01 +02:00
Markus Triska
b38a56e7d3 move compatibility predicates and meta_predicate/1 declarations so that they are correctly taken into account 2023-09-09 07:46:01 +02:00
Mark Thom
cbf18e5c3a Merge pull request #2006 from infogulch/wasm-ci
Add wasm build steps
2023-09-08 23:34:59 -06:00
Mark Thom
96bd0d267c Merge pull request #2007 from triska/rust_1_70
update required Rust version
2023-09-08 23:34:36 -06:00
Markus Triska
ec67752db4 shorter partition/5, relying on first instantiated argument indexing 2023-09-09 07:32:06 +02:00
Markus Triska
ad4c17fbb6 add meta_predicate/1 declaration for include/3 and exclude/3 2023-09-09 07:32:06 +02:00
Markus Triska
672979c515 shorter include/3 and exclude/3, relying on improved indexing 2023-09-09 07:32:06 +02:00
Markus Triska
efcc2b81cd add licensing information, addressing part of #1798 2023-09-09 07:32:06 +02:00
Markus Triska
640f29fe0f update required Rust version 2023-09-09 07:30:23 +02:00
Joe Taber
13a0085408 Add wasm build steps 2023-09-08 22:47:06 -05:00
Mark Thom
74440d503e Merge pull request #2004 from triska/generalize_description
this holds for all architectures (also 32-bit systems)
2023-09-08 16:11:06 -06:00
Mark Thom
f4769de5c4 Merge pull request #2003 from triska/clpz_comment
update comment
2023-09-08 15:22:49 -06:00
Markus Triska
b843b76b7a this holds for all architectures (also 32-bit systems) 2023-09-08 23:22:36 +02:00
Markus Triska
182afe3b7d update comment 2023-09-08 22:34:24 +02:00
Mark Thom
920e0c6b56 Merge pull request #1986 from rujialiu/wasm32-support
Basic WebAssembly support with minimal Javascript API #615
2023-09-08 11:15:51 -06:00
Rujia Liu
b6ce6b7cdb Basic WebAssembly support with minimal Javascript API #615. Currently 6/12 crypto functions supported. Tested in browser with all default features disabled. 2023-09-08 17:27:22 +08:00
Mark
a0b5a24853 check atom == ',' in ambiguity_check mirroring logic in print_op_addendum (#2000) 2023-09-05 14:56:26 -06:00
Mark
fa11d6bdd4 remove error printing redundancy and inconsistent state resulting from file_load_cleanup (#1995) 2023-09-05 13:53:57 -06:00
Mark Thom
a9aef2bc84 Merge pull request #1980 from Skgland/atomtable
make AtomTable consurrency safe
2023-09-05 12:32:20 -06:00
Mark Thom
bf2b73706a Merge pull request #1997 from triska/countall
Corrections to countall/2
2023-09-05 12:04:52 -06:00
Mark
83ebce86b6 call_with_inference_limit/3 should fail if inference limit is exceeded and R term is not inference_limit_exceeded (#1977) 2023-09-05 11:51:06 -06:00
Markus Triska
1967518fa2 FIXED: documentation of countall/2 (#1976) 2023-09-05 19:42:55 +02:00
Bennet Bleßmann
5928d64d7a make F64Table concurency safe 2023-09-05 19:39:59 +02:00
Bennet Bleßmann
c7ec5a13a5 remove locks from AtomTable
there are technically still two locks
- the Mutex to serialize consurrent updates to the AtomTable
- the RwLock as part of GLOBAL_ATOM_TABLE

the former is the age lock of RESIZE ATOM TABLE in <https://arxiv.org/pdf/1608.00989.pdf>, though we use it for the whole update as we don't match the whole structure and can't reserve an atom slot as is, so we need to also lock concurrent updates without resizing

the later should be uncontendet as we only write AtomTable::new iff the current value is a dangling Weak
2023-09-05 19:39:59 +02:00
Bennet Bleßmann
01aeb7515d [WIP] move towards lockless AtomTable 2023-09-05 19:39:46 +02:00
Markus Triska
d54c3369b3 FIXED: countall/2 for zero solutions (#1996)
Example:

    ?- countall(false, 0).
       true.
2023-09-05 19:39:05 +02:00
Skgland
d24e6100a7 fix rust_beta_channel feature 2023-09-05 19:12:23 +02:00
Skgland
cd0e45b70e fix accidentally left in println!() 2023-09-05 19:12:23 +02:00
Skgland
0f0018abe4 raise msrv for OnceLock support 2023-09-05 19:12:23 +02:00
Skgland
a95c5e26e6 add comment why blocking_write is not used 2023-09-05 19:12:23 +02:00
Skgland
70b6cc8e55 fix deadlock in load_library_as_stream 2023-09-05 19:12:22 +02:00
Bennet Bleßmann
13cbff7eab [WIP] fix deadlock in AtomTable::build_with 2023-09-05 19:12:22 +02:00
Skgland
86166dbf25 [WIP] make AtomTable concurrentcy ready 2023-09-05 19:12:22 +02:00
Skgland
b2130c2a48 run cargo fmt 2023-09-05 19:12:17 +02:00
Mark
5585e83fd6 correct inference counting over index backtracking, built-ins (#1977, #1987) 2023-09-04 17:15:16 -06:00
Fayeed Pawaskar
7b921fc767 Implemented num_order methods on Integer & Rational numbers 2023-09-04 14:12:47 +05:30
Fayeed Pawaskar
cfd67c8337 Added new unify methods 2023-09-04 14:12:11 +05:30
Fayeed Pawaskar
dc498d4de8 Fixed warning 2023-09-04 14:11:54 +05:30
Fayeed Pawaskar
2c67106f26 Added value method to get the generic type from TypedArenaPtr 2023-09-04 14:11:30 +05:30
Fayeed Pawaskar
b105c33b79 Added latest dashu from github 2023-09-04 14:09:45 +05:30
Mark Thom
2ccc238119 Merge pull request #1991 from triska/discoverable_flags
FIXED: make occurs_check and answer_write_options flags discoverable
2023-09-03 18:39:24 -06:00
Mark Thom
e693b7d33b Merge pull request #1990 from triska/clpb_max
remove clpb_max/1 attribute for residual goal projection
2023-09-03 18:39:12 -06:00
Markus Triska
aca0de06cd FIXED: make occurs_check and answer_write_options flags discoverable
Quoting from 7.11 Flags:

   NOTE - A built-in predicate current_prolog_flag(Flag, Value)
   (8.17.2) enables a program to discover all the flags supported
   by a processor and their current values.
2023-09-04 00:56:09 +02:00
Markus Triska
85f4bdbe0b update answer 2023-09-03 22:01:38 +02:00
Markus Triska
1257ba165f untabify 2023-09-03 21:57:51 +02:00
Markus Triska
1c33d2a2ed remove clpb_max/1 attribute for residual goal projection
Example:

    ?- sat(A+B), weighted_maximum([1,1], [A,B], Max).
       A = 1, B = 1, Max = 2.
2023-09-03 21:56:15 +02:00
Mark Thom
99bd3d3b1a Merge pull request #1988 from triska/answer_write_options
ADDED: New Prolog flag, answer_write_options.
2023-09-03 11:36:40 -06:00
Mark Thom
d8aed0ac4f Merge pull request #1989 from triska/mention_binaries
mention precompiled binaries
2023-09-03 11:36:07 -06:00
Markus Triska
5ebd4bb2a4 mention precompiled binaries 2023-09-03 14:12:46 +02:00
Markus Triska
c934e06171 ADDED: New Prolog flag, answer_write_options.
This lets us specify additional write options used by the top level
for writing answers.
2023-09-03 10:49:07 +02:00
Nicolas Luck
8a0685a3e9 Debug print value of register 6 which leads to failing heap dereference 2023-08-30 20:01:56 +02:00
Nicolas Luck
2b018be392 Add back needed dep. hyper-util and upgrade hyper to rc4 2023-08-30 20:00:56 +02:00
Nicolas Luck
4d19c437e0 Merge branch 'master' into library-use-case
# Conflicts:
#	Cargo.toml
#	src/machine/mock_wam.rs
#	src/machine/mod.rs
2023-08-30 18:12:26 +02:00
Nicolas Luck
44c274b9e9 WIP: Pure Rust impl. of run_query 2023-08-30 17:35:36 +02:00
Mark
66f6399b8a refine is/2 compilation so errors are thrown when expected without needlessly allocating (#1974, #1984) 2023-08-29 16:42:23 -06:00
Mark
53a1be78cc don't compile is instruction if bound to anonymous variable (#1974) 2023-08-29 15:50:04 -06:00
Mark
78b35dc57d update Cargo.toml version numbers and bump version number
Some checks failed
CI / build-test (macos-11, stable, bash, x86_64-apple-darwin) (push) Has been cancelled
CI / build-test (true, ubuntu-20.04, stable, bash, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-20.04, 1.65, bash, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-20.04, beta, bash, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-20.04, nightly, bash, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-20.04, stable, bash, i686-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (windows-latest, stable, msys2 {0}, x86_64-pc-windows-gnu) (push) Has been cancelled
CI / logtalk-test (push) Has been cancelled
CI / release (push) Has been cancelled
Docker Publish / build (push) Has been cancelled
2023-08-28 13:23:56 -06:00
Mark Thom
c65d1204af Merge pull request #1978 from aarroyoc/ci-32-bits
32 bit CI for Linux
2023-08-27 12:49:20 -06:00
Mark Thom
3bfed50d81 Merge pull request #1979 from triska/normalize_space
ENHANCED: rudimentary implementation of normalize_space
2023-08-27 12:48:48 -06:00
Adrián Arroyo Calle
4523bb5b81 32 bit CI for Linux 2023-08-27 19:25:54 +02:00
Markus Triska
75aec69de1 replace adjacent whitespace characters with a single SPACE character 2023-08-27 13:01:13 +02:00
Markus Triska
9b96735615 ENHANCED: rudimentary implementation of normalize_space
This removes leading and trailing whitespace from the text. Whitespace
within the string is not yet considered.
2023-08-27 09:56:30 +02:00
Mark Thom
f74d74fe6e Merge pull request #1972 from rujialiu/32-bit-support
32-bit system support #615 #1509 (WIP)
2023-08-25 10:51:15 -06:00
Nicolas Luck
77394ba914 Remove debug println!s 2023-08-24 19:15:41 +02:00
Nicolas Luck
d13173942d Cargo feature "multi_thread" for thread-local ATOM_TABLE_BUF_BASE 2023-08-24 19:13:11 +02:00
Nicolas Luck
afbadd9ea2 Revert "Enable multiple Machines per process by having ATOM_TABLE_BUF_BASE always be thread_local, not only in cfg(test) mode."
This reverts commit bfb3164a0d.
2023-08-24 19:13:11 +02:00
Nicolas Luck
c25888288a Fix parsing of floats 2023-08-24 18:10:47 +02:00
Rujia Liu
4fd059be5f 32-bit system support, addressing all (at most) 4GB addresses of RAM. 2023-08-24 20:05:40 +08:00
Nicolas Luck
bfb3164a0d Enable multiple Machines per process by having ATOM_TABLE_BUF_BASE always be thread_local, not only in cfg(test) mode. 2023-08-23 14:36:07 +02:00
Mark Thom
81712f4c82 Merge pull request #1973 from triska/clpz
CLP(ℤ) improvements, notably related to queue handling
2023-08-22 15:04:33 -06:00
Nicolas Luck
7e97f16f41 Fix list result parsing 2023-08-22 22:46:00 +02:00
Markus Triska
98086de77e attach more propagators before starting propagation
This prevents repeated triggering of already attached propagators
while new propagators are being attached to involved variables.
2023-08-22 21:52:55 +02:00
Markus Triska
24456e9703 remove optional (+)/1 prefix in get_atts/2 and put_atts/2 calls
The (+)/1 prefix in get_atts/2 at line 4219 by itself already causes a
greater than 15% slowdown for the benchmark shown in #1730:

    ?- N #= 2^14,
       time(((between(1, N, _),
              X #\= Y,
              false)
            ; true)).

The performance impact is not a good reason to remove the optional
(+)/1 prefix! Performance issues should be addressed at the root, in
this case get_atts/2 (#1962). We should never manually work around
performance issues in built-in predicates.

In contrast, readability is a good argument, and I find the calls
slightly easier to read without the optional (+)/1 prefix.

The prefix is now consistently omitted when possible.
2023-08-22 21:52:55 +02:00
Markus Triska
7f024f3b8d use pneq/2 2023-08-22 21:52:55 +02:00
Markus Triska
67c1b171c7 enforce same queue in more constraints 2023-08-22 21:52:55 +02:00
Markus Triska
04fbb0c1ce append the entire queues instead of inserting each element individually 2023-08-22 21:52:55 +02:00
Markus Triska
327423ab84 adapt query to Scryer Prolog 2023-08-21 21:19:29 +02:00
Markus Triska
20cfffdff5 update answers 2023-08-21 21:19:29 +02:00
Markus Triska
d8e126044f use can_be/2 2023-08-21 21:19:29 +02:00
Mark Thom
61f975ea18 Merge pull request #1967 from rujialiu/optional-features
Allow users to disable optional features
2023-08-21 11:42:03 -06:00
Rujia Liu
25d4950216 Allow users to disable optional features. Needed for wasm32 support (see #615). 2023-08-21 10:01:24 +08:00
Mark Thom
5aa1521819 Merge pull request #1968 from rujialiu/atom-64bit
Make Atom 64-bit regardless of architecture
2023-08-20 11:50:58 -06:00
Mark Thom
dd030fa18b Merge pull request #1970 from triska/crrl
switch to using crrl for scalar multiplication on Curve25519
2023-08-20 11:24:37 -06:00
Markus Triska
013df58fea ENHANCED: more extensive type checking in curve25519_scalar_mult/3 2023-08-20 14:43:14 +02:00
Markus Triska
745ddc2c87 switch to using crrl for scalar multiplication on Curve25519
This is to facilitate WASM compilation as currently worked on
by @rujialiu in #615. Many thanks, and many thanks to @pornin
for crrl which makes this possible!
2023-08-20 14:43:14 +02:00
Rujia Liu
f6d3b2f896 Make Atom 64-bit regardless of architecture 2023-08-20 19:43:13 +08:00
Mark Thom
7da321ab4f Merge pull request #1964 from triska/master
use newly available double_quotes/1 write option in portray_clause_//1
2023-08-17 15:18:25 -06:00
Markus Triska
92fdc7e380 use newly available double_quotes/1 write option in portray_clause_//1 2023-08-17 23:05:21 +02:00
Mark Thom
2e811de0f5 Merge pull request #1963 from triska/master
ENHANCED: domain error in zcompare/3 (#1723)
2023-08-14 12:18:45 -06:00
Markus Triska
f02cd0ad3c ENHANCED: domain error in zcompare/3 (#1723) 2023-08-14 17:54:18 +02:00
Mark Thom
a36df33a67 Merge pull request #1961 from triska/succ
ADDED: succ/2, part of the Prologue for Prolog
2023-08-12 14:35:21 -06:00
Markus Triska
4a6bf5fd5f reorder tests, per https://github.com/mthom/scryer-prolog/pull/1961#pullrequestreview-1574380588 2023-08-11 23:41:54 +02:00
Markus Triska
26c0b4fc75 ADDED: succ/2, part of the Prologue for Prolog
Specification:

   https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue#succ
2023-08-11 22:46:46 +02:00
Fayeed Pawaskar
ce1c8aac4c Fixed libffi dep 2023-08-08 16:22:08 +05:30
Fayeed Pawaskar
72ceceb7ae Updated hyper to 1.0.0-rc.4 2023-08-08 16:20:49 +05:30
Nicolas Luck
cf63b588bc Ignore stress test because it fails on windows 2023-08-07 18:41:08 +02:00
Nicolas Luck
4e8f7f0a1b Remove unused toplevel predicate 2023-08-07 18:38:34 +02:00
Nicolas Luck
579816a04f Use new double_quotes write-option 2023-08-07 18:29:05 +02:00
Nicolas Luck
cc04872933 Merge branch 'master' into library-use-case 2023-08-07 16:42:48 +02:00
Mark
fad363e64a shed CodeIndex for control predicates in disjuncts.rs (#1791) 2023-08-06 01:26:01 -06:00
Mark
094cf2ac5d retract discontiguous non-multifile predicates between consultations (#1202, #1058, #1585) 2023-08-05 17:42:33 -06:00
Mark Thom
cc82727d20 Merge pull request #1950 from triska/chars_si
ENHANCED: more efficient chars_si/1, using specialized predicates of Scryer
2023-08-04 15:25:42 -06:00
Markus Triska
924750f826 ENHANCED: more efficient chars_si/1, using specialized predicates of Scryer
Source:

    https://github.com/mthom/scryer-prolog/issues/1947#issuecomment-1665113488
2023-08-04 21:18:37 +02:00
Mark Thom
ffbf630b5c Merge pull request #1948 from triska/chars_si
add character_si/1 and use it to correct chars_si/1
2023-08-03 18:34:19 -06:00
Markus Triska
8073a4ba87 add character_si/1 and use it to correct chars_si/1
This addresses #1947.
2023-08-03 22:27:13 +02:00
Nicolas Luck
48a4835819 Merge branch 'master' into library-use-case
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/bin/scryer-prolog.rs
#	src/loader.pl
#	src/machine/mock_wam.rs
#	src/machine/mod.rs
#	src/machine/system_calls.rs
2023-08-03 20:16:32 +02:00
Nicolas Luck
3cf3c0ea99 Integration stress test showing Machine blocking on query 2023-08-03 19:51:12 +02:00
Nicolas Luck
ac61055d43 Remove some unused code from lib_toplevel.pl 2023-08-03 17:41:20 +02:00
Joshua Parkin
65eb93793c dont spawn a runtime in machine; inherit from outside with runtime::handle::Current 2023-08-03 16:22:03 +01:00
Mark
ec450fc567 allocate negator results in arena (#1898) 2023-08-02 19:50:36 -06:00
Mark Thom
2c05ebbded Merge pull request #1943 from triska/dead_skeleton
FIXED: do not attach constraint if the propagator is already entailed and killed
2023-08-02 16:32:27 -06:00
Markus Triska
0ddda0a864 FIXED: do not attach constraint if the propagator is already entailed and killed
Example:

    ?- A#=A//A#==>B,A-B=1-1.
       A = 1, B = 1.

This addresses #1941.
2023-08-03 00:26:40 +02:00
Nicolas Luck
3ff02da314 Deactivate some debugging outputs 2023-08-02 16:25:53 +02:00
Nicolas Luck
df048a4f42 Switch back to run_input_once and use duplicated write_eqs/2 without any input handling 2023-08-02 14:52:49 +02:00
Nicolas Luck
21c36880f1 Fix build 2023-08-02 14:51:41 +02:00
Mark Thom
02328d818c Merge pull request #1938 from triska/remove_auxiliary_constraints
ENHANCED: forget auxiliary constraints set up by the propagator for multiplication
2023-07-31 14:52:34 -06:00
Mark Thom
0f55ba7218 Merge pull request #1939 from triska/oncology_trial_design
better wording, applying the feedback of @dcnorris. Thank you a lot!
2023-07-31 14:52:21 -06:00
Markus Triska
1c089a2bbb better wording, applying the feedback from @dcnorris. Thank you a lot! 2023-07-31 22:07:13 +02:00
Markus Triska
54a887cdc3 ENHANCED: forget auxiliary constraints set up by the propagator for multiplication
This addresses the issue raised in https://github.com/mthom/scryer-prolog/discussions/1937.
2023-07-31 21:57:25 +02:00
Mark Thom
ca28c76e52 Merge pull request #1929 from Skgland/master
detect and prevent concurrent AtomTable use
2023-07-29 10:52:36 -06:00
Bennet Bleßmann
a70157003b fix spelling 2023-07-29 13:11:00 +02:00
Bennet Bleßmann
6aa3c7d5d6 handle atom table resize
* bumping serial_test dev-dependency due to broken should_panic handling in old version
2023-07-29 11:22:25 +02:00
Bennet Bleßmann
4ef8c5c47d detect and prevent² concurrent AtomTable use
²in the case of `#[cfg(not(test))]` there is still a toctou race as I am not sufficently familiar with Atomics
2023-07-27 23:37:03 +02:00
Mark Thom
31d17f135a Merge pull request #1927 from triska/ascii_punctuation
FIXED: correct handling of ascii_punctuation in char_type/2 (#1926)
2023-07-27 14:51:01 -06:00
Markus Triska
03f7b01109 FIXED: correct handling of ascii_punctuation in char_type/2 (#1926) 2023-07-27 20:22:05 +02:00
Nicolas Luck
c2658dc6da Try triska's toplevel and add some debugging println!s. 2023-07-27 14:46:00 +02:00
Nicolas Luck
30dac8ea41 HashSet -> BTreeSet: Make parsing or results and thus tests deterministic. Add comments. 2023-07-27 12:06:03 +02:00
Nicolas Luck
0d28404aad Add special case when parsing 2023-07-27 11:50:38 +02:00
Nicolas Luck
2f99bb025c Add consult that works with streams / strings in library use-case 2023-07-27 11:41:52 +02:00
Mark
bff48e7c7f simplify and correct prefix-bracket spacing in heap_print.rs (#1914, #1918) 2023-07-26 09:32:54 -06:00
Mark
3b67ffa814 overwrite code indices of dynamic_undefined predicates (dynamic, multifile, discontiguous) on export 2023-07-25 13:48:34 -06:00
Mark Thom
49b4e4cbcb Merge pull request #1907 from coasys/dashu
Replace rug with dashu
2023-07-25 11:35:10 -06:00
Mark
287c308bc3 track the parent operator of the current operator in heap_print to emit space if necessary (#1906) 2023-07-24 20:05:28 -06:00
Mark
af44d91568 Merge branch 'dashu' of https://github.com/coasys/scryer-prolog into coasys-dashu 2023-07-24 11:47:57 -06:00
Fayeed Pawaskar
40b6890c54 updated cargo to use git 2023-07-24 19:11:41 +05:30
Fayeed Pawaskar
7248425a76 Fixed warnings 2023-07-24 12:41:56 +05:30
Fayeed Pawaskar
1dcc1ca524 Fixed stackoverflow error 2023-07-24 12:19:41 +05:30
Fayeed Pawaskar
0e17d6acd7 remove rug completely 2023-07-24 10:12:22 +05:30
Fayeed Pawaskar
c89217903a fix all the move errors 2023-07-24 10:12:14 +05:30
Mark
762e6d3ba4 pop both pending redirections in format_bar_separator_op when max depth exceeded (#1903) 2023-07-23 15:02:27 -06:00
Mark
e9ae80e250 fix list abbreviation (#1901) 2023-07-23 14:43:13 -06:00
Mark
fd7f24e265 remove EMIT_NEWLINE (#1900) 2023-07-22 21:42:40 -06:00
Mark
24450a8827 use ExitCode when halting so Drop is called, close terminal stream in rustyline Drop 2023-07-22 19:06:13 -06:00
Mark Thom
57e8ed65b0 Merge pull request #1050 from pmoura/add_float_integer_part_and_float_fractional_part_standard_functions
WIP: Add float_integer_part/1 and float_fractional_part/1 standard arithmetic functions
2023-07-22 13:48:16 -06:00
Mark
3f819e2dfd additional write fixes, use rustyline 12.0.0 (#1876, #1901) 2023-07-22 11:53:09 -06:00
Mark
9a7862c322 correct max_depth option (#1876) 2023-07-22 11:11:23 -06:00
Mark Thom
92c77cdff5 Merge pull request #1899 from triska/master
remove another !/0 which is now no longer necessary due to improved indexing
2023-07-21 23:26:20 -06:00
Markus Triska
554e956ef5 remove another !/0 which is now no longer necessary due to improved indexing 2023-07-22 06:52:29 +02:00
Nicolas Luck
9fd6e18d59 Dedupe machine results 2023-07-22 00:32:24 +02:00
Nicolas Luck
d8a9475460 Add missing list_last_item to lib_toplevel.pl and increase MaxDepth of write_eq to avoid truncation of results 2023-07-22 00:31:49 +02:00
Mark
60d9d01a55 Merge branch 'add_float_integer_part_and_float_fractional_part_standard_functions' of https://github.com/pmoura/scryer-prolog into pmoura-add_float_integer_part_and_float_fractional_part_standard_functions 2023-07-21 15:35:28 -06:00
Mark
cb79552dd0 correct max_depth option (#1876) 2023-07-21 15:05:27 -06:00
Mark
f310ff24a5 remove now unnecessary cut in lists.pl 2023-07-21 11:35:57 -06:00
Nicolas Luck
b1963864d2 Explicitly dereference pointer to avoid calling neg() on reference 2023-07-21 17:07:40 +02:00
Nicolas Luck
9e85be11fe Fix result parsing for complex string results 2023-07-21 14:35:44 +02:00
Fayeed Pawaskar
b4e7000eb2 Fixed missing functionality in dashu with their methods still has some issue with move 2023-07-21 14:21:32 +05:30
Nicolas Luck
0b833bd2f3 Add missing write_goal/3 to lib_toplevel.pl 2023-07-21 00:23:35 +02:00
Nicolas Luck
7c93450aa7 type QueryResult = Result<QueryResolution, String> 2023-07-20 22:31:20 +02:00
Mark
e529e7ba21 improve goal expansion and (',') interpretation error handling 2023-07-20 14:27:10 -06:00
Nicolas Luck
cae32d6a00 Error handling 2023-07-20 22:23:51 +02:00
Nicolas Luck
836f6c1d5b Don't panic when parsing results fails 2023-07-20 21:34:43 +02:00
Mark
1697cd5c7f add log10, hyperbolic tan and inverse hyperbolic tan functions (#1898) 2023-07-20 12:33:51 -06:00
Mark
dcd7360b17 add EMIT_NEWLINE to add newlines to readline input only after query terms begin to be read (#1074, #1897) 2023-07-20 09:39:06 -06:00
Mark
4fd247f881 check for unexpected EOF in get_to_eof (#1897) 2023-07-19 17:13:47 -06:00
Mark
85bc544fb9 dereference TermWriteResult variables sooner in read_term_body (#1894) 2023-07-18 15:39:51 -06:00
Mark
14646074be remove failing append choicepoint in atom_concat/3 special case (#1893) 2023-07-18 14:44:02 -06:00
Mark Thom
1ba040c24d Merge pull request #1885 from mthom/setof_bagof_fixes
Fix group_by_variants/4 and keysort in setof/3
2023-07-18 12:35:10 -06:00
Mark
56f6772422 call write_read_term_options if read_term_from_chars/3 succeeds by unifying Term to end_of_file (#1892) 2023-07-18 12:15:20 -06:00
Mark
db43d461b9 catch errors thrown from tabling Worker (#1526, #1888) 2023-07-18 12:10:27 -06:00
Mark
42a50474da remove read/{1,2} as a builtin, write read options upon EOF, throw better domain errors in parse_read_term_options/2 2023-07-17 16:45:03 -06:00
Mark
cf367024fd add specialized EOF handling for user input (#1892) 2023-07-17 14:38:26 -06:00
Nicolas Luck
644559b7f7 Add back newline at end of toplevel.pl 2023-07-17 21:52:17 +02:00
Nicolas Luck
bb95ed3ad0 Add back all needed predicates to lib_toplevel.pl 2023-07-17 21:48:05 +02:00
Nicolas Luck
3947390877 Use lib constructor in lib tests 2023-07-17 21:47:43 +02:00
Nicolas Luck
e7f1e32ee3 fmt machine/parsed_results.rs 2023-07-17 21:35:00 +02:00
Nicolas Luck
5f8cc3c64b WIP: refactor to generalize Machine::run_top_level() 2023-07-17 21:34:26 +02:00
Mark
67d198ac77 Merge branch 'read_term_eof_changes' 2023-07-17 13:22:51 -06:00
Mark
a154a34f87 omit anonymous variables from read_term variable_names and singletons lists 2023-07-17 13:20:10 -06:00
Mark
86c90d77dd do a better job handling EOF in read_term (#1887) 2023-07-17 13:19:30 -06:00
Mark
4e1a4dae6c print strings in tails of lists (#1890) 2023-07-17 11:38:50 -06:00
Fayeed Pawaskar
cf345d8174 wip dashu move 2023-07-17 20:40:41 +05:30
Mark Thom
a18da368a1 Merge pull request #1891 from triska/initialization_goals
ENHANCED: use newly available read_term_from_chars/3 for better errors
2023-07-16 10:26:13 -06:00
Markus Triska
5d3295c40c ENHANCED: use newly available read_term_from_chars/3 for better errors
Examples, previously:

    $ scryer-prolog -g "member(X,Ls"
    ?-
    $ scryer-prolog -g "member(X,Ls)"
    member(_542,_543) causes: error(existence_error(procedure,member/2),member/2)
    ?-

Now:

    $ scryer-prolog -g "member(X,Ls"
    "member(X,Ls" cannot be read: error(syntax_error(incomplete_reduction),read_term_from_chars/3:0)
    $ scryer-prolog -g "member(X,Ls)"
    member(X,Ls) causes: error(existence_error(procedure,member/2),member/2)
    ?-

This also addresses #1185.
2023-07-16 14:16:07 +02:00
Mark
de10ccfdee re-factor options handling of read_term into read_term_body (#1887) 2023-07-15 21:52:54 -06:00
Mark
65a8ce8e22 generalize simple goal detection to fix call/N test failures in logtalk test suite 2023-07-15 19:29:06 -06:00
Mark
5a7da721cd add read_term_from_chars/3 (#637) 2023-07-15 11:20:20 -06:00
Mark
b234ef7ea3 use double_quotes in write_error (#1886) 2023-07-15 10:58:25 -06:00
Mark
617c961f88 add is_inbuilt check to err_on_builtin_overwrite (#1872) 2023-07-15 10:44:00 -06:00
Mark
e95355e56e eliminate call_with_inference_limit/3 leaks (#1300) 2023-07-15 10:22:23 -06:00
Mark
ff5e9a793b add unknown flag to set_prolog_flag and current_prolog_flag 2023-07-14 19:10:10 -06:00
Mark
101d0548db fix group_by_variants/4 and keysort in setof/3 (#1440, #1856) 2023-07-14 18:52:29 -06:00
Mark
b6a81c51ab add (:-)/1 and (:-)/2 to ClauseType::is_inbuilt (#1872) 2023-07-14 12:44:28 -06:00
Mark
ba2cd43144 fix assert(a|z)/1 errors 2023-07-13 17:12:17 -06:00
Mark Thom
bd720b49f3 Merge pull request #1884 from triska/countall
ADDED: countall/2, for compatibility with GNU Prolog.
2023-07-13 16:09:29 -06:00
Markus Triska
9590d5200c ADDED: countall/2, for compatibility with GNU Prolog.
Example:

    ?- countall(member(X, "abc"), N).
       N = 3.
2023-07-13 23:08:13 +02:00
Mark
12f890e4a2 throw permission_error in compile_assert when attempting to assert a built-in (#1872) 2023-07-13 15:03:26 -06:00
Mark
a9cb826bf3 arith_eval_by_metacall may receive a stack variable 2023-07-13 13:58:51 -06:00
Mark
4163cb038d correct peek_code/2, don't set stream position in peek functions 2023-07-13 13:37:34 -06:00
Mark
b051f39145 correct peek_byte/2 bugs (#1882) 2023-07-13 12:23:20 -06:00
Mark
cfc49243c8 improve ground/1 performance (#1389) 2023-07-12 18:11:56 -06:00
Mark
29430ec88b fix peek_byte/2 crash (#1882) 2023-07-12 16:34:11 -06:00
Mark Thom
d520046a4f Merge pull request #1878 from mthom/lookahead_indexing
use lookahead to skip inapplicable clauses (#1028, #1502)
2023-07-12 16:21:06 -06:00
Markus Triska
814ce2d672 ENHANCED: improved determinism of member/2
Example:

    ?- member(X, "abc").
    %@    X = a
    %@ ;  X = b
    %@ ;  X = c.

This addresses #750.
2023-07-12 14:39:08 -06:00
Mark
4fd37335f5 use lookahead to skip inapplicable clauses (#1028, #1502) 2023-07-12 14:39:02 -06:00
Mark
1791bd8626 remove unsafe unwrap in put_char (#1881) 2023-07-12 14:24:20 -06:00
Mark
3f5dbc1680 emit stream aliases as permission error culprits whenever possible 2023-07-12 12:17:06 -06:00
Mark
44052cb373 Revert "Allow comparisons with stream terms"
This reverts commit 076a75d138.
2023-07-12 11:44:45 -06:00
Mark
8c33da11ce Revert "add stream alias check to atom/1 (#1855)"
This reverts commit b746a8f9ab.
2023-07-12 11:44:44 -06:00
Mark
ab80c84714 Revert "add stream alias processing to atom_chars/2, atom_codes/2"
This reverts commit 811ff65209.
2023-07-12 11:44:41 -06:00
Nicolas Luck
c0dd94c8a3 Add test for programatic queries 2023-07-12 11:57:53 +02:00
Nicolas Luck
f65675836c Fix build warnings 2023-07-12 11:38:53 +02:00
Nicolas Luck
f324c9591d Refactor result parsing to idiomatic Rust and extract into parsed_results.rs 2023-07-12 10:34:07 +02:00
Nicolas Luck
3347f830c7 Remove debug println!s 2023-07-12 01:26:54 +02:00
Nicolas Luck
568abef5b8 Parsed QueryResult 2023-07-12 01:24:39 +02:00
Nicolas Luck
703efdb22d Make run_input_once/0 match and print all results 2023-07-12 00:03:47 +02:00
Mark
bb420e9347 use indexing functions to set num_cells in allocate_and_frame/or_frame (#1877) 2023-07-11 13:43:34 -06:00
Nicolas Luck
2f45f0cfed Add convenience methods Machine::load_module_string() and Machine::run_query() 2023-07-11 14:51:58 +02:00
Nicolas Luck
112d398175 Add Machine::run_input_once() which reads one goal from user input and runs it 2023-07-11 14:24:30 +02:00
Nicolas Luck
95b31146b5 Add Machine::set_user_input(&mut self, input: String) and get_user_output() -> String. Make read_term_from_user_input() handle Stream::Byte. 2023-07-11 14:22:27 +02:00
Mark
55a1f8d3da clean commented code from disjuncts.rs 2023-07-10 10:32:15 -06:00
Mark
fba7790637 remove errant debug_hook from read_and_match 2023-07-09 10:30:30 -06:00
Markus Triska
918dfca409 DOC: new section on applications of Scryer Prolog
This addresses an important aspect of #1777.
2023-07-09 10:29:53 -06:00
Markus Triska
d18f128a3c correct \\ to \, addressing #1865 2023-07-09 10:29:53 -06:00
Mark
c8b9059289 refine EOF handling more (#1873) 2023-07-09 10:29:47 -06:00
Mark
b8a6882a27 refine EOF handling 2023-07-08 22:25:30 -06:00
Mark
fb8e3071f2 follow EOF action after open_parsing_stream in get_char if stream at EOF 2023-07-08 19:27:32 -06:00
Mark
067b5998ee clarify EOF error across stream types and predicates (#1867, #1870) 2023-07-08 13:38:52 -06:00
Mark Thom
2e26f37f5e Merge pull request #1869 from triska/master
print version more readably, addressing #1868
2023-07-08 01:24:04 -06:00
Markus Triska
6525c1f543 print version more readably, addressing #1868 2023-07-08 08:19:05 +02:00
Mark
4db0b385f3 treat unexpected EOF as incomplete reduction in bracketed_comment 2023-07-07 13:04:27 -06:00
Mark
b7f77d1747 interpret '\u{0}' as end_of_file in get_char/1 2023-07-07 11:05:44 -06:00
Mark
811ff65209 add stream alias processing to atom_chars/2, atom_codes/2 2023-07-07 10:38:05 -06:00
Mark Thom
fd70d8975b Merge pull request #1832 from aarroyoc/http-fixes
Multiple fixes for http libraries
2023-07-06 16:30:56 -06:00
Mark
b746a8f9ab add stream alias check to atom/1 (#1855) 2023-07-06 11:38:12 -06:00
Mark
483e4568a2 add scc_block to MachineState to avoid SCC cleanup terms being deallocated too early (#1427) 2023-07-06 11:20:49 -06:00
Mark
9ff1b660f1 correct heap_print.rs tests 2023-07-05 18:11:09 -06:00
Mark
5ffdd2d91a shrink scope of control_entry_point catch, add CutPoint tag to printer 2023-07-05 14:50:49 -06:00
Mark Thom
5b81bee941 Merge pull request #1864 from triska/double_quotes
make double_quotes write option not dependent on double_quotes flag
2023-07-05 13:49:29 -06:00
Markus Triska
3b9b9e75c4 make double_quotes write option not dependent on double_quotes flag
This gives consistent results without depending on another flag.
2023-07-05 21:37:11 +02:00
Mark Thom
faef45fd68 Merge pull request #1862 from mthom/iso_conformity_tests
Revise iso_conformity_tests.pl to remove unnecessary spaces
2023-07-05 10:49:51 -06:00
Mark
b5b45dde9d add missing self.pos to peek_char slices (#1726) 2023-07-04 17:44:27 -06:00
Mark
5ab087bc1e revise iso_conformity_tests.pl in response to new ambiguity check of #1860 2023-07-04 16:12:03 -06:00
Mark
7683367c0e throw lexer errors from devour_whitespace (#1778) 2023-07-04 16:09:54 -06:00
Mark Thom
d2db66b9f7 Merge pull request #1861 from aarroyoc/fix-1855
Allow comparisons with stream terms
2023-07-04 15:11:59 -06:00
Mark
c58d8804a1 widen CharReader buffer (#1859) 2023-07-04 15:08:46 -06:00
Mark
5d09449c95 widen CharReader buffer (#1859) 2023-07-04 11:22:22 -06:00
Adrián Arroyo Calle
076a75d138 Allow comparisons with stream terms 2023-07-04 17:33:48 +02:00
Mark Thom
7f69ac3f4a Merge pull request #1857 from triska/master
advertise newly achieved strong syntactic conformance: all current tests pass
2023-07-03 14:05:16 -06:00
Markus Triska
7f159a7ed2 advertise newly achieved strong syntactic conformance: all current tests pass
This addresses an important aspect of #1777.
2023-07-03 22:04:26 +02:00
Mark Thom
42274ef3ae Merge pull request #1850 from mthom/iso-conformity-tests
Automate ISO syntax conformity tests
2023-07-03 13:53:41 -06:00
Mark
ab893be418 update tests 2023-07-03 13:18:53 -06:00
Mark
f5e7573bd6 correct tests #171 and #300 2023-07-03 12:09:26 -06:00
Mark
9cdad087ef add double_quotes write option for printing to strings, enable it at toplevel 2023-07-03 12:09:05 -06:00
Mark
e36f96fd47 correct ISO conformity test #185 2023-07-03 11:34:41 -06:00
Mark
cb25a90250 add iso-conformity-tests.pl to test suite 2023-07-02 11:10:33 -06:00
Mark
38a9d23174 correct initialization_goals misnomer in iso-conformity-tests.pl 2023-07-02 11:10:33 -06:00
Mark
521118265a make setup of test_166_167 pass 2023-07-02 11:10:33 -06:00
Mark
a09306c585 check ambiguity of "'" against tail if atom token is about to be quoted 2023-07-02 11:10:33 -06:00
Mark
8140ff9154 always print a space between prefix operator and its operand 2023-07-02 11:10:33 -06:00
Mark
3cbe78cb9b correct tests 259 and 304 of tests-pl/iso-conformity-tests.pl 2023-07-02 11:10:33 -06:00
Mark
699afb2c00 introduce tests-pl/iso-conformity-tests.pl 2023-07-02 11:10:33 -06:00
Mark
d079a18459 removing residual debugging comments from format.pl 2023-07-02 11:10:25 -06:00
Mark
b0566e4150 use lexer to detect remaining layout in parse_number_from_string (#1773) 2023-06-30 17:18:10 -06:00
Mark Thom
c38a26e6cd Merge pull request #1854 from triska/needs_bracketing
remove now unneeded check
2023-06-30 15:43:45 -06:00
Markus Triska
31030738a4 remove now unneeded check 2023-06-30 22:14:00 +02:00
Mark
caf84a259e check that F in needs_bracketing/2 is an atom 2023-06-30 13:45:45 -06:00
Mark
1620824d3a do not enclose '(' as atom in brackets (#1487) 2023-06-30 12:06:07 -06:00
Mark Thom
1d2f5053d6 Merge pull request #1852 from triska/needs_bracketing
Improvements to needs_bracketing/2
2023-06-30 11:13:24 -06:00
Markus Triska
42282c6e6e remove unneeded case: only operator definitions should count, not quoting 2023-06-30 18:03:21 +02:00
Markus Triska
a3f8ddd24a remove subsumed case: F == Value means Arity =:= 0, now considered above 2023-06-30 18:02:43 +02:00
Markus Triska
bfe808a779 shorten needs_bracketing/2 2023-06-30 18:01:54 +02:00
Markus Triska
28065b0565 constrain bracketing to operators with pertaining arity
Example:

    ?- X = -->(a,b,c).
       X = -->(a,b,c).
2023-06-30 18:01:17 +02:00
Mark Thom
4953cfd10e Merge pull request #1851 from triska/operator_bracketing
bracket all operators that are direct operands of (=)/2
2023-06-30 00:26:18 -06:00
Markus Triska
db972de40c bracket all operators that are direct operands of (=)/2
This addresses #804.
2023-06-30 08:03:38 +02:00
Adrián Arroyo Calle
330e9ba4ef Multiple fixes for http libraries
* use reqwest for http_open (still uses Hyper underneath)
* use Hyper 1.0.0-rc3 for server
* Modify all internal handling of server
2023-06-29 23:43:37 +02:00
Mark
bb09de1805 fix ReadlineStream peek_char using CharReader 2023-06-29 14:58:15 -06:00
Mark
a6a0cef9fc read the cell written to by mark_var when needed in compile_is (#1846) 2023-06-29 12:58:15 -06:00
Mark
c36bd4dc07 introduce CutPoint heap tag so that they can be offset by call_continuation/1 2023-06-29 12:14:15 -06:00
Mark Thom
83f352b95e Merge pull request #1849 from triska/showterm
remove unneeded single quotes
2023-06-29 12:11:12 -06:00
Markus Triska
c84a5c3282 remove unneeded single quotes 2023-06-29 19:25:23 +02:00
Mark
58af615dd4 correct and generalize current_predicate/1 2023-06-28 17:31:43 -06:00
Mark
c4b13a2176 unify stack variables to streams in unify_constant (#1845) 2023-06-27 17:35:10 -06:00
Mark
039fffb339 better detect syntax errors in lexer.rs (#1771) 2023-06-27 16:39:20 -06:00
Mark Thom
8613513b9c Merge pull request #1844 from triska/copy_term_nat
use copy_term_nat/2
2023-06-27 15:26:33 -06:00
Markus Triska
ceb276b249 use copy_term_nat/2 2023-06-27 22:37:23 +02:00
Mark
16f281e3d1 enable unification of streams to alias atoms (#1823) 2023-06-27 12:23:40 -06:00
Mark
b593fffc7d support module resolution in current_predicate/1 (#1817) 2023-06-27 11:08:29 -06:00
Mark
ce890799bc fix builtin_predicate (#1819) 2023-06-27 10:41:15 -06:00
Mark
aa65287c3b remove tabling attributes in each module's attribute_goals//1 (#1825) 2023-06-27 10:31:10 -06:00
Mark
a6522d6317 properly account for partial string offsets in '$skip_max_list' (#1827) 2023-06-26 16:36:04 -06:00
Mark
0b45d42912 mark chunk boundary at beginning of disjunct in disjuncts.rs (#1843) 2023-06-24 17:18:00 -06:00
Mark
d9829a3606 fix string incompleteness (#1828) 2023-06-24 14:12:08 -06:00
Mark Thom
59766e2db4 Merge pull request #1842 from infogulch/release-on-tag
Fix tags trigger; bump MSRV to 1.65
2023-06-24 13:13:07 -06:00
infogulch
e52a4fbfc0 Bump msrv to 1.65 due to bumping rug to 1.19
https://gitlab.com/tspiteri/rug#version-1190-news-2023-01-06
2023-06-24 13:21:42 -05:00
infogulch
c05afb4705 Fix tags trigger 2023-06-24 13:05:57 -05:00
Mark
9f209dadd9 fix branch subsumption bug (#1840, #1841) 2023-06-24 10:59:29 -06:00
Mark Thom
2ec45b7413 Merge pull request #1838 from triska/get_line_to_chars
MODIFIED: read_line_to_chars/3 is now called get_line_to_chars/3
2023-06-24 09:19:22 -06:00
Mark
bf581879e6 update README 2023-06-24 09:11:30 -06:00
Markus Triska
9bc3757a9e another case of "read" --> "get", for an only internally used predicate 2023-06-24 11:48:28 +02:00
Markus Triska
18d0a74f23 MODIFIED: read_line_to_chars/3 is now called get_line_to_chars/3
This is for consistency with other I/O predicates, where "read" always
indicates Prolog terms. Please adjust your programs accordingly.
2023-06-24 11:47:32 +02:00
Mark
7a188744da correct code_walker.rs in light of compilation improvements 2023-06-23 16:53:18 -06:00
Mark
7f45ac3f7a Merge branch 'compiling_disj' 2023-06-23 15:45:04 -06:00
Mark
612861e010 correct reversions after rebase 2023-06-23 14:32:37 -06:00
Mark
fcae0d9fcf polish perm free list management 2023-06-23 14:11:31 -06:00
infogulch
f446939770 Add steps to publish binaries when releases are tagged 2023-06-23 14:11:31 -06:00
Mark
92853a6a12 free local cut variables after cut 2023-06-23 14:11:31 -06:00
Mark
4ad113a6f8 mark is/2 allocated permanent variables as safe, add CompareNumber terms to ClauseType::is_inlined 2023-06-23 14:11:31 -06:00
dependabot[bot]
89ed1aa8de Bump openssl from 0.10.48 to 0.10.55
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.48 to 0.10.55.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.48...openssl-v0.10.55)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-23 14:11:31 -06:00
Mark
d7f5675727 improve call/N implementation (#1829) 2023-06-23 14:11:31 -06:00
Mark
4d982d22c1 set_local_value does not make values safe (#1812) 2023-06-23 14:11:31 -06:00
Mark
5ed1802f0f read set_value args from temp regs of put_unsafe_value (#1812) 2023-06-23 14:11:31 -06:00
Markus Triska
2716381e7b FIXED: correct dereferencing in atom_codes/2 and number_codes/2.
This addresses #1818.

Test case:

    run :-
	    length(Ls, L),
	    portray_clause(L),
	    maplist(=(X), Ls),
	    X = Y,
	    Y = 12,
	    atom_codes(_, Ls),
	    false.
2023-06-23 14:11:31 -06:00
Adrián Arroyo Calle
c5c83d724a Rename INDEX.md to INDEX.dj and add banner about Scryer Prolog Meetup 2023-06-23 14:11:31 -06:00
Mark
749dedf477 read from machine stack in stackful pre-order iterator (#1812) 2023-06-23 14:11:31 -06:00
notoria
911c49c43f Compute correctly the domain of the remainder 2023-06-23 14:11:31 -06:00
notoria
770a682d8b Don't add variable
?- Z #= 0, Z #= X mod Y.
   Z = 0, clpz:(_A*Y#=X), clpz:(Y in inf.. -1\/1..sup) % Unexpected.

The expected result:
   Z = 0, clpz:(X mod Y#=0), clpz:(Y in inf.. -1\/1..sup).
2023-06-23 14:11:31 -06:00
notoria
73ca37ecca Remove and move comments 2023-06-23 14:11:31 -06:00
Mark
e0f49e8f43 optionally read from machine stack in stackful pre-order iterator (#1812) 2023-06-23 14:11:31 -06:00
Mark
dae34b6009 affirm integers as rational/1 (#1810) 2023-06-23 14:11:31 -06:00
Adrián Arroyo Calle
5850125d97 Update select crate to 0.6.0 and remove warning 2023-06-23 14:11:31 -06:00
Markus Triska
97bd778745 FIXED: correctly reset counter in reset_gensym/2 (#1807)
Many thanks to @infradig for detecting this issue and suggesting this
correction!
2023-06-23 14:11:31 -06:00
Markus Triska
43df2e2649 shorten gensym/2 2023-06-23 14:11:31 -06:00
Markus Triska
c2f2623471 extend logic to all control and whitespace characters
This addresses #1802.
2023-06-23 14:11:31 -06:00
Mark
8e4465315f use same logic to print Chars and Atoms (#1804) 2023-06-23 14:11:31 -06:00
Markus Triska
86beb222ae rely on first instantiated argument indexing in the definitions of foldl/N
This allows shorter and more natural definitions.
2023-06-23 14:11:31 -06:00
Markus Triska
5e124ccf44 ENHANCED: allow Roman numerals in strings
Example:

    ?- X = "ↁ".
       X = "ↁ".

This addresses #1790.
2023-06-23 14:11:31 -06:00
Markus Triska
47d4e6d2f9 FIXED: consistent read/write of further control characters, and non-breaking space
Example:

    ?- X = '\xa0\'.
       X = '\xa0\'.

This addresses #1768.
2023-06-23 14:11:31 -06:00
Mark
33f65210ee make tests compatible 2023-06-23 14:11:31 -06:00
Mark
9ea6cb4cab backtrack on emission of unsafe register instructions on internal branches 2023-06-23 14:11:31 -06:00
Mark
0e583d620a implement new disjunction compilation 2023-06-23 14:11:31 -06:00
Mark Thom
b205abe949 remove BuildIf, BuildNot, BuildThen TermIterState variants 2023-06-23 14:11:31 -06:00
Mark Thom
cb59c3003a correct chunk type labeling 2023-06-23 14:11:30 -06:00
Mark Thom
942095baa7 remove GetLevelAndUnify and replace it with GetCutPoint 2023-06-23 14:11:30 -06:00
Mark Thom
097849385e add QueryTerm::ChunkTypeBoundary 2023-06-23 14:11:30 -06:00
Mark Thom
c4783062ff delete ChunkedTerm, chunked iteration 2023-06-23 14:11:30 -06:00
Mark Thom
063cf0c608 new TermIterState variants 2023-06-23 14:11:30 -06:00
Mark Thom
a66d666bed variable classification al a carte 2023-06-23 14:11:22 -06:00
Mark Thom
170818759d add more variable probing, chunk type labeling 2023-06-23 14:11:07 -06:00
Mark Thom
e41d1b319b adapt code generation 2023-06-23 14:11:04 -06:00
Mark Thom
b9c9de5222 add classifications and occurrence counting 2023-06-23 13:54:46 -06:00
Mark Thom
d565f5901b milestone marker for surgery 2023-06-23 13:54:04 -06:00
Mark Thom
46317c3a39 begin adapting the techniques of "Compiling Large Disjunctions" 2023-06-23 13:52:24 -06:00
Mark Thom
bcc5bff376 Merge pull request #1834 from infogulch/ci-publish-releases
Add steps to publish binaries when releases are tagged
2023-06-23 12:01:32 -06:00
infogulch
5244d71570 Add steps to publish binaries when releases are tagged 2023-06-22 23:36:04 -05:00
Mark Thom
9fac289d9a Merge pull request #1833 from mthom/dependabot/cargo/openssl-0.10.55
Bump openssl from 0.10.48 to 0.10.55
2023-06-22 11:30:56 -06:00
dependabot[bot]
de665c05d8 Bump openssl from 0.10.48 to 0.10.55
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.48 to 0.10.55.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.48...openssl-v0.10.55)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-21 22:54:36 +00:00
Mark
98b0ab3409 improve call/N implementation (#1829) 2023-06-10 17:32:18 -06:00
Mark
3c344b176b set_local_value does not make values safe (#1812) 2023-06-06 00:58:44 -06:00
Mark
6093c2858d read set_value args from temp regs of put_unsafe_value (#1812) 2023-05-29 20:49:54 -06:00
Mark Thom
e06ab1ca1c Merge pull request #1822 from triska/master
FIXED: correct dereferencing in atom_codes/2 and number_codes/2.
2023-05-29 10:15:43 -06:00
Markus Triska
5154314786 FIXED: correct dereferencing in atom_codes/2 and number_codes/2.
This addresses #1818.

Test case:

    run :-
	    length(Ls, L),
	    portray_clause(L),
	    maplist(=(X), Ls),
	    X = Y,
	    Y = 12,
	    atom_codes(_, Ls),
	    false.
2023-05-29 11:43:56 +02:00
Mark Thom
fa07306f09 Merge pull request #1820 from aarroyoc/banner-meetup
Rename INDEX.md to INDEX.dj and add banner about Scryer Prolog Meetup
2023-05-28 16:39:05 -06:00
Adrián Arroyo Calle
07115ce4f5 Rename INDEX.md to INDEX.dj and add banner about Scryer Prolog Meetup 2023-05-29 00:29:53 +02:00
Mark
b656700294 read from machine stack in stackful pre-order iterator (#1812) 2023-05-28 12:59:01 -06:00
Mark Thom
7bc7f0ad06 Merge pull request #1816 from notoria/clpz
Small fixes for `CLP(ℤ)`
2023-05-27 11:07:50 -06:00
notoria
495df8846a Compute correctly the domain of the remainder 2023-05-27 13:47:14 +02:00
notoria
05d48cdcc3 Don't add variable
?- Z #= 0, Z #= X mod Y.
   Z = 0, clpz:(_A*Y#=X), clpz:(Y in inf.. -1\/1..sup) % Unexpected.

The expected result:
   Z = 0, clpz:(X mod Y#=0), clpz:(Y in inf.. -1\/1..sup).
2023-05-27 13:41:04 +02:00
notoria
dc02be4944 Remove and move comments 2023-05-27 13:08:25 +02:00
Mark
462097d956 optionally read from machine stack in stackful pre-order iterator (#1812) 2023-05-26 15:19:07 -06:00
Mark
0e374c2e96 affirm integers as rational/1 (#1810) 2023-05-24 13:58:43 -06:00
Mark Thom
94a1313916 Merge pull request #1809 from aarroyoc/update-select
Update select crate to 0.6.0 and remove warning
2023-05-18 12:31:44 -06:00
Adrián Arroyo Calle
b54a4afad6 Update select crate to 0.6.0 and remove warning 2023-05-17 18:19:19 +02:00
Mark Thom
c71b8e05f0 Merge pull request #1808 from triska/gensym
gensym-related improvement and correction
2023-05-16 15:55:39 -06:00
Markus Triska
021c01dfd0 FIXED: correctly reset counter in reset_gensym/2 (#1807)
Many thanks to @infradig for detecting this issue and suggesting this
correction!
2023-05-16 22:42:57 +02:00
Markus Triska
30f222b837 shorten gensym/2 2023-05-16 22:41:20 +02:00
Mark Thom
f7d9237bb9 Merge pull request #1805 from triska/master
extend logic to all control and whitespace characters
2023-05-14 23:27:49 -06:00
Markus Triska
49addc7b04 extend logic to all control and whitespace characters
This addresses #1802.
2023-05-14 09:25:08 +02:00
Mark
2f9996f9ac use same logic to print Chars and Atoms (#1804) 2023-05-10 00:04:35 -06:00
Mark Thom
c1218fc986 Merge pull request #1801 from triska/foldl
rely on first instantiated argument indexing in the definitions of foldl/N
2023-05-04 21:25:16 -06:00
Mark Thom
57ef706eb0 Merge pull request #1799 from triska/roman_numerals
ENHANCED: allow Roman numerals in strings
2023-05-04 21:25:03 -06:00
Mark Thom
e4b19dc1dd Merge pull request #1800 from triska/nbsp
FIXED: consistent read/write of further control characters, and non-breaking space
2023-05-04 21:24:45 -06:00
Markus Triska
4e60cc46a2 rely on first instantiated argument indexing in the definitions of foldl/N
This allows shorter and more natural definitions.
2023-05-04 00:50:27 +02:00
Markus Triska
d8edf7bfff FIXED: consistent read/write of further control characters, and non-breaking space
Example:

    ?- X = '\xa0\'.
       X = '\xa0\'.

This addresses #1768.
2023-05-03 23:29:22 +02:00
Markus Triska
e951db662d ENHANCED: allow Roman numerals in strings
Example:

    ?- X = "ↁ".
       X = "ↁ".

This addresses #1790.
2023-05-03 22:02:47 +02:00
Mark
c5a3ec3ba8 fix current_predicate/1 (#1761) 2023-04-24 23:23:27 -06:00
Mark Thom
f12a90351a Merge pull request #1794 from aarroyoc/uppercase-2
Fix to_upper/to_lower when string is instantiated
2023-04-23 17:08:21 -06:00
Adrián Arroyo Calle
b162c40007 Fix to_upper/to_lower when string is instantiated 2023-04-23 23:01:29 +02:00
Mark Thom
402100fd52 Merge pull request #1797 from triska/clpb_residuals
remove residual goal for ground BDD
2023-04-23 10:31:40 -06:00
Markus Triska
2a1b8f37ec remove residual goal for ground BDD
Example:

    ?- sat(X).
       X = 1.
2023-04-23 09:23:00 +02:00
Mark
7d2e59ab64 discard CodeIndex literals from unfolded control operators in preprocessor (#1791) 2023-04-22 18:38:50 -06:00
Mark
f35298a227 add and document inlined_instructions/2 to/in diag.pl (#1791) 2023-04-22 18:38:49 -06:00
Mark Thom
198e925430 Merge pull request #1796 from triska/dif_si
ADDED: dif_si/2
2023-04-22 17:21:03 -06:00
Markus Triska
8b7281fad0 ADDED: dif_si/2
Source: https://stackoverflow.com/questions/20223390/prolog-a-person-is-a-sibling-of-himself

In Scryer Prolog, this is actually not needed, since Scryer Prolog provides
dif/2 in library(dif). However, it is still useful to provide dif_si/2 for two reasons:

  1) to more easily port code from systems where only dif_si/2 is available
  2) to provide correct disequality in other systems that adopt this library
2023-04-23 00:37:14 +02:00
Mark Thom
502574dfc3 Merge pull request #1795 from infogulch/fix-ci-install
Use --force to install cargo2junit
2023-04-22 16:03:39 -06:00
infogulch
7a0f4e5787 Use --force to install cargo2junit
This probably appeared now because it's the first time this tool was cached
from a previous run.
2023-04-22 15:42:10 -05:00
Mark Thom
f277f660f3 Merge pull request #1793 from infogulch/fix-action-ref
Change ref for `publish-unit-test-result-action`
2023-04-22 11:26:45 -06:00
infogulch
9c037d6028 Change ref for publish-unit-test-result-action 2023-04-22 10:18:10 -05:00
Mark Thom
396528189d Merge pull request #1789 from aarroyoc/uppercase
library(charsio): add to_upper and to_lower
2023-04-20 07:17:09 +02:00
Adrián Arroyo Calle
4b882c465c library(charsio): add to_upper and to_lower 2023-04-18 18:58:09 +02:00
Mark Thom
8937cac47d Merge pull request #1783 from infogulch/test
Refactor CI Workflow
2023-04-18 08:35:45 +02:00
infogulch
5a3e2899dd Refactor CI Workflow
* Rename the workflow from Test to CI, since it does more than tests
* Run logtalk tests in a separate job to improve isolation
* Publish all xunit/junit test files as build artifacts to be consumed
  by a separate publishing workflow.
* Add "job summary" feature to show a formatted summary of the test
  results on the job summary status page
* Run the CI job once every Wed to ensure that there are always some
  recent builds on master that haven't expired.
2023-04-14 14:55:49 -05:00
Mark Thom
3a5ed4723b Merge pull request #1785 from mthom/dependabot/cargo/h2-0.3.17
Bump h2 from 0.3.15 to 0.3.17
2023-04-14 06:22:32 +02:00
Mark Thom
dcf4c44173 Merge pull request #1786 from triska/tuples_in
FIXED: propagation for ground tuples
2023-04-14 06:22:18 +02:00
Markus Triska
5763a4b9df FIXED: propagation for ground tuples
Example:

    ?- tuples_in([[A,A]],[[0,1],[2,0]]).
       false.

See https://github.com/triska/clpz/issues/22.
2023-04-13 23:39:45 +02:00
dependabot[bot]
82200a21eb Bump h2 from 0.3.15 to 0.3.17
Bumps [h2](https://github.com/hyperium/h2) from 0.3.15 to 0.3.17.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.15...v0.3.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-13 17:03:50 +00:00
Mark Thom
fc7d98d748 Merge pull request #1780 from triska/tuples_in
Various improvements to tuples_in/2
2023-04-12 19:10:50 +02:00
Markus Triska
5dce7d9075 FIXED: enforce equality also for ground elements in tuples
Example:

    ?- tuples_in([[A,B]], [[1,2],[3,4]]),
       tuples_in([[A,B]], [[3,2]]).
       false.

See https://github.com/SWI-Prolog/swipl-devel/issues/1160.
2023-04-11 22:33:19 +02:00
Markus Triska
f08f539768 do not create attributed variables for ground tuples 2023-04-11 21:26:18 +02:00
Markus Triska
be45672e22 actually disable and reenable the queue 2023-04-11 21:26:18 +02:00
Markus Triska
94efb9ffe3 remove no longer needed clpz_relation attributes 2023-04-11 21:26:18 +02:00
Markus Triska
fd1e902492 do not leave an attribute when (re-)enabling a queue 2023-04-11 21:14:18 +02:00
Mark Thom
8d6e3d7d56 Merge pull request #1776 from infogulch/master
Add iso prolog / logtalk tests to test runner
2023-04-07 06:09:08 +02:00
infogulch
a197bb6815 Allow the test suite to fail without failing the build 2023-04-06 09:40:03 -05:00
infogulch
9de8456c7f Add logtalk test suite 2023-04-02 17:31:20 -05:00
infogulch
ebf8091dba Test workflow cleanup; switch to macos-11 2023-04-02 14:47:52 -05:00
Mark Thom
47ce84b273 Merge pull request #1772 from triska/number_chars
FIXED: number_chars(N, "0' "), addressing #1580.
2023-04-02 19:24:59 +02:00
Mark Thom
449e097f4a Merge pull request #1774 from infogulch/master
Optimize Test GH action
2023-04-02 19:24:36 +02:00
Markus Triska
fe27605497 FIXED: number_chars(N, "0' "), addressing #1580.
There may be a more elegant way to solve this.
2023-04-02 10:04:52 +02:00
infogulch
8792ee438c Add cache step to test workflow 2023-04-02 02:09:41 -05:00
Mark Thom
7279062d3c Merge pull request #1770 from triska/doc
Smaller documentation improvements to explain compilation and target directory
2023-04-01 23:28:19 +02:00
Markus Triska
b87fe1e21f DOC: link to "Indexing dif/2" 2023-04-01 11:04:37 +02:00
Markus Triska
d6ac125425 DOC: explain location of scryer-prolog after compilation
This question was recently raised on the #scryer IRC channel.
2023-04-01 11:04:37 +02:00
Markus Triska
b79d8732ea use the release flag so that the instructions can be used verbatim
Also, the location of the executable depends on this flag.
2023-04-01 11:04:33 +02:00
Markus Triska
3df0806017 change "run" to "build", since "run" leads to a Scryer prompt which can be unexpected 2023-04-01 11:03:31 +02:00
Mark Thom
c7759aa737 Merge pull request #1769 from triska/show_pending_constraints
ENHANCED: use call_residue_vars/2 to show all pending constraints
2023-03-31 04:51:34 +02:00
Markus Triska
8ab1155fc5 ENHANCED: use call_residue_vars/2 to show all pending constraints
Example:

    ?- freeze(_, false).
       freeze:freeze(_A,false).

This was originally added in 04ba9bc11a,
then reverted, and is now restored.
2023-03-30 23:49:33 +02:00
Mark Thom
adc77985d7 broaden the definition of alpha_char! (#1749, #1515, #1591) 2023-03-26 11:10:46 -06:00
Mark Thom
d85fc7c9f8 Merge pull request #1765 from mthom/dependabot/cargo/openssl-0.10.48
Bump openssl from 0.10.42 to 0.10.48
2023-03-25 06:20:00 +01:00
dependabot[bot]
41b083c962 Bump openssl from 0.10.42 to 0.10.48
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.42 to 0.10.48.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.42...openssl-v0.10.48)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-25 01:27:40 +00:00
Mark Thom
4ee6a7bfb8 add '$unattributed_var' builtin (#1758) 2023-03-18 23:08:38 -06:00
Mark Thom
6e53d08d40 Merge pull request #1759 from triska/better_queue
Better queue
2023-03-16 04:34:33 +01:00
Markus Triska
01285f12c3 remove no longer needed queue attributes after propagation 2023-03-15 21:17:06 +01:00
Markus Triska
cd586aab8c show remaining queue/2 attributes as residual goals
This lets us verify that all attributes are correctly removed earlier.
2023-03-15 19:56:02 +01:00
Mark Thom
4da646252b Revert "correct call_residue_vars/3 using new copy_term_3 (#1239)"
This reverts commit 9b35a316c9.
2023-03-14 21:34:49 -06:00
Mark Thom
cc8bb38abc Revert "use new call_residue_vars/2 in toplevel.pl (#847)"
This reverts commit 04ba9bc11a.
2023-03-14 21:34:48 -06:00
Mark Thom
04ba9bc11a use new call_residue_vars/2 in toplevel.pl (#847) 2023-03-12 17:56:54 -06:00
Mark Thom
9b35a316c9 correct call_residue_vars/3 using new copy_term_3 (#1239) 2023-03-12 17:56:54 -06:00
Mark Thom
3a522f3c98 Merge pull request #1757 from aarroyoc/fix-docs-1756
DOCS: Fixes #1756
2023-03-12 18:26:54 +01:00
Adrián Arroyo Calle
4c44859132 Fixes #1756 2023-03-12 16:47:20 +01:00
Mark Thom
ecd77f7512 Merge pull request #1755 from triska/doc
various smaller documentation improvements
2023-03-10 02:40:29 +01:00
Markus Triska
56bd596af3 use actual toplevel answers 2023-03-08 23:35:11 +01:00
Markus Triska
21acb9361a use string notation as discussed on #scryer IRC 2023-03-08 23:32:05 +01:00
Markus Triska
c3477d8476 items --> elements
This addresses #1740.
2023-03-08 23:31:02 +01:00
Mark Thom
cba09d4ea1 Merge pull request #1754 from triska/dif
ENHANCED: more compact definition of dif/2
2023-03-08 21:48:34 +01:00
Markus Triska
884b0ca10e FIXED: Take all variables into account during goal projection.
This addresses #1751.
2023-03-08 21:16:54 +01:00
Markus Triska
c9ecfb11d9 ENHANCED: more compact definition of dif/2
As outlined in #1753.
2023-03-08 20:50:33 +01:00
Mark Thom
9288dcabe9 Merge pull request #1744 from aarroyoc/ffi
Foreign Function Interface - library(ffi)
2023-03-02 21:57:56 +01:00
Adrián Arroyo Calle
73df96244d Fill more cases 2023-03-01 22:13:04 +01:00
Adrián Arroyo Calle
7396630627 Merge branch 'master' into ffi 2023-02-28 22:10:40 +01:00
Adrián Arroyo Calle
0ac93751d0 FFI: Documentation 2023-02-28 22:09:35 +01:00
Mark Thom
400ca21213 invoke '$default_attr_list' in project_attributes.pl (#1748) 2023-02-26 22:42:02 -07:00
Mark Thom
3286e78cd2 third argument of copy_term should be instantiated as a list (#1747) 2023-02-26 22:42:02 -07:00
Mark Thom
24eb9ce483 Merge pull request #1746 from triska/type_over_instantiation_error
ENHANCED: must_be/2: prefer type error over instantiation error
2023-02-26 23:46:23 +01:00
Markus Triska
3dc6ed79d2 ENHANCED: must_be/2: prefer type error over instantiation error
This addresses #1594.
2023-02-26 22:28:18 +01:00
Adrián Arroyo Calle
f94294dbd9 FFI: Nested structs 2023-02-26 20:48:00 +01:00
Mark Thom
04ba58067a add, implement and use the Unifier trait 2023-02-26 12:21:22 -07:00
Adrián Arroyo Calle
92b262d599 Macroization of the code 2023-02-25 22:27:28 +01:00
Mark Thom
7ffb40e0ad Merge pull request #1743 from triska/rely_on_indexing
rely on first instantiated argument indexing
2023-02-25 19:23:59 +01:00
Markus Triska
997161c740 rely on first instantiated argument indexing
This great improvement to indexing allows much more natural
definitions of virtually all meta-predicates. Many thanks to @notoria!
2023-02-25 10:17:55 +01:00
Mark Thom
84c95c59e9 Merge pull request #1741 from triska/clpz_residuals
in projection of residual goals, mark considered propagators as processed
2023-02-23 01:51:22 +01:00
Markus Triska
669242a8ce DOC: update residual goals 2023-02-23 00:10:36 +01:00
Markus Triska
2a04d5e799 in projection of residual goals, mark considered propagators as processed
This is to avoid duplicated goals with the new projection mechanism.
2023-02-23 00:05:27 +01:00
Adrián Arroyo Calle
9d52d2a653 MVP of Foreign Function Interface 2023-02-22 23:10:03 +01:00
Mark Thom
95f6ebc000 assign responsibility for emitting dif goal to the first variable of the left-hand term (#1739) 2023-02-22 10:28:29 -07:00
Mark Thom
6e9cd072c5 catch attribute_goals errors in copy_term/3, don't discard variable module qualifiers in dcg_body/3 (#1738) 2023-02-21 00:50:46 -07:00
Mark Thom
729f2b1cb9 Merge pull request #1737 from triska/link_to_homepage
add link to newly available homepage
2023-02-20 21:10:01 +01:00
Markus Triska
1a01438064 add link to newly available homepage
Many thanks to @aarroyoc for the documentation system, and for hosting the page!
2023-02-20 20:09:47 +01:00
Robert Jacobson
34ec6d3167 Changed the links for the delimited continuations papers and the precise garbage collection paper. 2023-02-20 01:11:52 -07:00
Robert Jacobson
c9295323f6 Added links to referenced research papers in the Phase 2 and Nice to Have Features sections. 2023-02-20 01:11:52 -07:00
Mark Thom
92d543b8a8 change '$delete_all_attributes' to '$delete_all_attributes_from_var' 2023-02-20 01:11:52 -07:00
Mark Thom
3f445c76be add '$delete_all_attributes', use copy_term/3 as defined in #1272 2023-02-20 01:11:51 -07:00
Mark Thom
a6e416f13d compile '$atts' and '$project_atts' modules using loader.pl 2023-02-20 01:11:35 -07:00
Mark Thom
601ff567e3 keep phrase goal qualified even if qualifier is a variable 2023-02-17 00:20:15 -07:00
Mark Thom
56783b8e4b correct incremental compilation bugs 2023-02-12 23:41:25 -07:00
Mark Thom
326f18ea75 copy attributed variable attribute lists specially via copy_attr_var_list 2023-02-12 17:30:04 -07:00
Mark Thom
491472a8c5 retire TrailedAttrVarHeapLink TrailEntry tag 2023-02-10 22:52:27 -07:00
Mark Thom
359619e035 simplify and optimize attributed variables (#1590, #1634, #1730) 2023-02-10 21:56:42 -07:00
Mark Thom
9454d670c7 port '$get_from_list' to '$get_from_attr_list' in Rust 2023-02-06 01:23:29 -07:00
Mark Thom
2fcec4fff9 include wambook errata 2023-02-04 18:14:16 -07:00
Mark Thom
2c2a9fe01e correct shl stub_gen 2023-02-03 22:27:15 -07:00
Mark Thom
165a55dac6 Merge pull request #1734 from triska/clpz_corrections
DOC: teletype font for reification
2023-02-04 05:49:11 +01:00
Markus Triska
196e9c1e47 DOC: teletype font for reification 2023-02-03 20:47:08 +01:00
Mark Thom
17450520ba shift by usize instead of u32 in shl and shr (#1718, #1719) 2023-02-02 20:49:39 -07:00
Mark Thom
97702f3071 Merge pull request #1732 from aarroyoc/docs-minor-fixes
Minor fixes to docs
2023-02-03 01:58:52 +01:00
Adrián Arroyo Calle
e8408ca93f Minor fixes to docs 2023-02-02 21:35:35 +01:00
Mark Thom
e0dcf88b68 Merge pull request #1731 from triska/clpz_corrections
correct DocLog ~/.scryerrc rendering
2023-02-02 06:26:08 +01:00
Mark Thom
cc38cf15a4 Merge pull request #1729 from aarroyoc/negative-shifts
Negative shifts (fixes #1719 and #1718)
2023-02-02 06:25:52 +01:00
Markus Triska
da4c0a359b correct DocLog ~/.scryerrc rendering 2023-02-01 23:26:52 +01:00
Mark Thom
ce56a7303e avoid arena allocation of stream in read_term_from_chars (#1266) 2023-01-31 00:15:57 -07:00
Mark Thom
5667ec8699 update tokio version 2023-01-30 23:27:07 -07:00
Adrián Arroyo Calle
36b3150225 Negative shifts (fixes #1719 and #1718) 2023-01-30 18:56:37 +01:00
Mark Thom
28c338416f Merge pull request #1728 from aarroyoc/docs-csv
Compatible Doclog docs for library(csv)
2023-01-30 04:24:04 +01:00
Mark Thom
26a8fc1a37 Merge pull request #1727 from triska/clpz_corrections
DOC: use valid Prolog terms as predicate indicators
2023-01-30 04:23:52 +01:00
Adrián Arroyo Calle
f347baafa3 Compatible Doclog docs for library(csv) 2023-01-29 22:36:16 +01:00
Markus Triska
95278c221b DOC: use valid Prolog terms as predicate indicators 2023-01-29 21:48:22 +01:00
Mark Thom
5a28366158 Merge pull request #1725 from aarroyoc/fix-docs-http-server
Fix library(http/http_server) docs. Other minor fixes
2023-01-28 20:12:09 +01:00
Mark Thom
ac0cea8a73 Merge pull request #1724 from triska/clpz_corrections
CLP(ℤ) corrections
2023-01-28 20:11:48 +01:00
Markus Triska
814b631543 use DocLog syntax for section anchors and links within the document 2023-01-28 17:37:43 +01:00
Markus Triska
0d8c7f8785 small documentation adjustments 2023-01-28 17:37:43 +01:00
Markus Triska
5bae8fcaf8 FIXED: use lsb/2 and msb/2 from library(arithmetic)
This addresses #1720.
2023-01-28 17:37:43 +01:00
Adrián Arroyo Calle
4110cfcfcb Fix library(http/http_server) docs. Other minor fixes 2023-01-28 16:00:27 +01:00
Mark Thom
2bdba261ea Merge pull request #1717 from triska/format_doc
correct table layout for entries that themselves contain |
2023-01-27 07:47:53 +01:00
Markus Triska
58fb851717 correct table layout for entries that themselves contain | 2023-01-27 00:22:48 +01:00
Mark Thom
1118b37c92 Merge pull request #1674 from aarroyoc/docs-builtins
Compatible Doclog docs for builtins
2023-01-26 05:10:20 +01:00
Mark Thom
39606a2277 Merge pull request #1705 from triska/format_doc
DOC: convert library(format) documentation to DocLog format
2023-01-26 02:05:23 +01:00
Mark Thom
275d306b69 Merge pull request #1706 from triska/si_doc
DOC: convert library(si) documentation to DocLog format
2023-01-26 02:03:24 +01:00
Mark Thom
e948413c09 Merge pull request #1707 from triska/reif_doc
DOC: add link to "Indexing dif/2" in DocLog format
2023-01-26 02:03:14 +01:00
Mark Thom
f8bb3148d0 Merge pull request #1708 from triska/freeze_doc
DOC: add documentation for library(freeze) in DocLog format
2023-01-26 02:02:58 +01:00
Mark Thom
e7cf70936a Merge pull request #1709 from triska/pio_doc
DOC: convert library(pio) documentation to DocLog format
2023-01-26 02:02:47 +01:00
Mark Thom
409287e8f8 Merge pull request #1710 from triska/tabling_doc
DOC: initial documentation for library(tabling) in DocLog format
2023-01-26 02:02:35 +01:00
Mark Thom
87d6ef16f7 Merge pull request #1711 from triska/pairs_doc
DOC: initial documentation for library(pairs) in DocLog format
2023-01-26 02:02:22 +01:00
Mark Thom
3c08eba559 Merge pull request #1712 from triska/lambda_doc
DOC: convert code samples in library(lambda) to DocLog format
2023-01-26 02:02:12 +01:00
Mark Thom
d399b68d01 Merge pull request #1713 from triska/simplex_doc
DOC: convert library(simplex) documentation to DocLog format
2023-01-26 02:02:01 +01:00
Markus Triska
2e9ec653a8 DOC: convert library(simplex) documentation to DocLog format 2023-01-26 00:46:06 +01:00
Markus Triska
7ca782b92d DOC: convert code samples in library(lambda) to DocLog format 2023-01-26 00:40:08 +01:00
Markus Triska
996496c3f5 DOC: initial documentation for library(pairs) in DocLog format 2023-01-26 00:36:05 +01:00
Markus Triska
90cf713186 add link to Desouter et al., "Tabling as a Library with Delimited Control" 2023-01-26 00:25:26 +01:00
Markus Triska
ca4aaf44de DOC: initial documentation for library(tabling) in DocLog format 2023-01-26 00:20:53 +01:00
Markus Triska
b04d845ec0 DOC: convert library(pio) documentation to DocLog format 2023-01-26 00:09:41 +01:00
Markus Triska
a8ea2b0f97 DOC: add documentation for library(freeze) in DocLog format 2023-01-25 23:54:30 +01:00
Markus Triska
a29227d0d4 DOC: add link to "Indexing dif/2" in DocLog format 2023-01-25 23:40:55 +01:00
Markus Triska
7f8f137aa0 DOC: convert library(si) documentation to DocLog format 2023-01-25 23:27:45 +01:00
Markus Triska
cc7e721611 DOC: convert library(format) documentation to DocLog format 2023-01-25 23:18:39 +01:00
Mark Thom
058cbcf19a Merge pull request #1704 from triska/time_doc
DOC: correctly format the table using DocLog syntax
2023-01-25 22:51:05 +01:00
Markus Triska
22b815dc5c DOC: correctly format the table using DocLog syntax 2023-01-25 22:29:14 +01:00
Mark Thom
c709853aa7 Merge pull request #1703 from aarroyoc/docs-arithmetic
Compatible Doclog docs for library(arithmetic) and small fixes on INDEX.md
2023-01-25 21:40:15 +01:00
Adrián Arroyo Calle
d755bb7e12 Apply feedback on builtins 2023-01-25 21:04:10 +01:00
Adrián Arroyo Calle
6d99912b7b Compatible Doclog docs for library(arithmetic) and small fixes on INDEX.md 2023-01-25 19:48:01 +01:00
Mark Thom
82dd9e0596 Merge pull request #1701 from triska/diag_doc
DOC: add DocLog documentation for library(diag)
2023-01-25 00:16:53 +01:00
Mark Thom
6ba3d49034 Merge pull request #1702 from triska/debug_doc
DOC: add DocLog documentation for library(debug)
2023-01-25 00:16:34 +01:00
Markus Triska
64be8e0fba DOC: add DocLog documentation for library(debug) 2023-01-25 00:19:14 +01:00
Markus Triska
43a297b691 DOC: add DocLog documentation for library(diag) 2023-01-25 00:09:44 +01:00
Mark Thom
df266378c4 Merge pull request #1689 from triska/clpb_doc
DOC: preliminary CLP(B) documentation in DocLog format
2023-01-24 22:51:35 +01:00
Mark Thom
ec55765cb2 Merge pull request #1697 from triska/crypto_doc
DOC: convert library(crypto) documentation to DocLog format
2023-01-24 22:46:14 +01:00
Mark Thom
de2ff8118f Merge pull request #1696 from triska/clpz_doc
DOC: improve CLP(ℤ) DocLog documentation
2023-01-24 22:45:51 +01:00
Mark Thom
c30c9dc98f Merge pull request #1698 from triska/time_doc
DOC: convert library(time) documentation to DocLog format
2023-01-24 22:43:53 +01:00
Mark Thom
1f872c3f0c Merge pull request #1694 from aarroyoc/docs-dcgs
Compatible Doclog docs for library(dcgs)
2023-01-24 22:43:43 +01:00
Mark Thom
b6204eb6f1 Merge pull request #1699 from triska/sgml_doc
DOC: convert library(sgml) documentation to DocLog format
2023-01-24 22:43:22 +01:00
Markus Triska
af9f0f81d8 DOC: convert library(sgml) documentation to DocLog format 2023-01-24 22:42:58 +01:00
Markus Triska
1c08b56e05 strengthen reified division for divisor == 1 2023-01-24 22:15:05 +01:00
Markus Triska
6cb8020f62 DOC: add CLP(B) documentation in DocLog format 2023-01-24 22:15:05 +01:00
Markus Triska
d742d4cde9 DOC: convert library(time) documentation to DocLog format 2023-01-24 22:14:15 +01:00
Markus Triska
b7d06540e6 DOC: convert library(crypto) documentation to DocLog format 2023-01-24 22:13:38 +01:00
Markus Triska
909f2e1058 DOC: improve CLP(ℤ) DocLog documentation 2023-01-24 20:39:47 +01:00
Mark Thom
26d39c3617 Merge pull request #1695 from aarroyoc/website
Add website frontpage
2023-01-24 20:29:48 +01:00
Mark Thom
371bc3b231 Merge pull request #1676 from aarroyoc/docs-http-server
Compatible Doclog docs for library(http/http_server)
2023-01-24 04:09:39 +01:00
Adrián Arroyo Calle
bdeabcdd89 Website page 2023-01-23 23:51:27 +01:00
Adrián Arroyo Calle
7f177c3d03 Compatible Doclog docs for library(dcgs) 2023-01-23 21:03:11 +01:00
Mark Thom
4ed36a12e9 Merge branch 'retract_improvements' 2023-01-22 21:04:55 -07:00
Mark Thom
a7e93db363 improve retract/1 and related predicates (#1598) 2023-01-22 21:04:41 -07:00
Mark Thom
789715f31a Merge pull request #1693 from aarroyoc/docs-xpath
Compatible Doclog docs for library(xpath).
2023-01-23 04:04:00 +01:00
Mark Thom
7cc900d437 Merge pull request #1691 from aarroyoc/docs-os-3
Compatible Doclog docs for library(os)
2023-01-23 04:02:57 +01:00
Mark Thom
ff0f6f4fc2 Merge pull request #1690 from aarroyoc/docs-charsio
Compatible Doclog docs for library(charsio)
2023-01-23 04:02:02 +01:00
Mark Thom
5698e8f21a Merge pull request #1692 from aarroyoc/docs-between
Compatible Doclog docs for library(between).
2023-01-23 04:00:11 +01:00
Adrián Arroyo Calle
ddcae2c906 Compatible Doclog docs for library(xpath). 2023-01-22 21:22:54 +01:00
Adrián Arroyo Calle
468d096ccb Compatible Doclog docs for library(between). 2023-01-22 20:26:30 +01:00
Adrián Arroyo Calle
bca79d12c0 Compatible Doclog docs for library(os) 2023-01-22 17:45:50 +01:00
Adrián Arroyo Calle
03eba9594b Compatible Doclog docs for library(charsio) 2023-01-21 21:05:13 +01:00
Mark Thom
5e0e3e2754 Merge pull request #1687 from gruhn/master
Fix failing Docker build
2023-01-21 05:02:33 +01:00
Mark Thom
04fd835267 Merge pull request #1688 from aarroyoc/djot-migration
Migrate from Markdown to Djot
2023-01-21 04:48:26 +01:00
Adrián Arroyo Calle
f072761150 Fix assoc.pl file 2023-01-19 21:18:40 +01:00
Adrián Arroyo Calle
46d1e3bee3 Migrate from Markdown to Djot 2023-01-19 21:15:25 +01:00
Niklas Gruhn
f8e6e0252d Use lastest 1.xx Rust version in Docker build
With the previous Rust version 1.61, the build fails with

> error[E0658]: use of unstable library feature 'scoped_threads'

This has been "stabilized" in Rust 1.63. To avoid the hassle
of manually updating the version, we can just default to the
latest minor release.
2023-01-18 19:08:19 +01:00
Mark Thom
f9e3bdb6b0 restore free list usage on structures in facts without crashing lgtunit loader 2023-01-13 18:35:13 -07:00
Mark Thom
a80aab48cd Merge pull request #1677 from aarroyoc/docs-dif
Compatible Doclog docs for library(dif)
2023-01-13 07:50:17 +01:00
Mark Thom
3a4aa2a541 tighten deallocate truncation of stack (#1686) 2023-01-12 23:47:07 -07:00
Mark Thom
84583da5b8 Merge pull request #1685 from triska/reify_sign
ADDED: sign/1 is now reifiable.
2023-01-11 17:41:14 +01:00
Markus Triska
f213956ceb use (#)/1 2023-01-11 17:23:53 +01:00
Markus Triska
c5caa9d311 ADDED: sign/1 is now reifiable.
This addresses #1500.
2023-01-11 17:22:38 +01:00
Mark Thom
0c4d93f01f remove add_term_to_free_list from AddToList (#1684) 2023-01-10 18:24:48 -07:00
Mark Thom
a4e8bfc1ba Merge pull request #1682 from triska/reify_slash
FIXED: correctly reify (/)/2.
2023-01-10 23:44:48 +01:00
Markus Triska
542b9e1976 rely on newly available operator notation for (#)/1 2023-01-10 22:59:11 +01:00
Markus Triska
73a1ee59fa replace several more instances of ?/1 by (#)/1 2023-01-10 22:57:14 +01:00
Markus Triska
2771109427 use (#)/1 already internally for describing constraint projections 2023-01-10 22:50:38 +01:00
Markus Triska
cc420bd31a FIXED: reification of (xor)/2.
Example:

    ?- A #= 1 xor 0 #<==> R.
    %@    clpz:(A#=1#<==>R), clpz:(R in 0..1).
2023-01-10 22:09:41 +01:00
Markus Triska
2fe1d2ef53 FIXED: correctly reify (/)/2.
Example:

    ?- 0 #==> X #= 1/2.
    %@    clpz:(X in inf..sup)
    %@ ;  false.

This addresses #1501.
2023-01-10 17:35:43 +01:00
Mark Thom
bb624cc971 use free lists to allow register re-use (#1612) 2023-01-08 12:11:04 -07:00
Mark Thom
aa7b8e52f3 use '$enqueue_attr_var' when adding attributes only 2022-12-24 00:31:47 -07:00
Adrián Arroyo Calle
07f358c91b Compatible Doclog docs for library(dif) 2022-12-22 23:23:07 +01:00
Adrián Arroyo Calle
8ae0a1a4af Compatible Doclog docs for library(http/http_server) 2022-12-22 22:36:33 +01:00
Adrián Arroyo Calle
7647ad14b8 More feedback applied 2022-12-21 23:19:38 +01:00
Adrián Arroyo Calle
21e4347b3e Apply some feedback 2022-12-21 22:32:36 +01:00
Adrián Arroyo Calle
f2d041ea23 Merge branch 'master' into docs-builtins 2022-12-21 21:29:22 +01:00
Adrián Arroyo Calle
56c1c4e43c Compatible Doclog docs for builtins 2022-12-21 21:28:16 +01:00
Mark Thom
820011412b Merge pull request #1658 from aarroyoc/copy_file
Add predicate file_copy/2 in library(files)
2022-12-18 21:22:25 +01:00
Mark Thom
81afa6942f Merge pull request #1672 from triska/deref_register
dereference more registers, analogous to d660e4244f
2022-12-18 21:21:58 +01:00
Markus Triska
6cb8d7596a dereference more registers, analogous to d660e4244f
See also #1654 for a nice test case by @notoria which this corrects.
2022-12-18 17:43:33 +01:00
Mark Thom
a2d46af5ac Merge pull request #1671 from aarroyoc/docs-iso-ext
Compatible Doclog docs for library(iso_ext)
2022-12-18 00:38:22 +01:00
Mark Thom
6d94b8ab75 Merge pull request #1669 from aarroyoc/docs-http-open
Compatible Doclog docs for library(http/http_open).
2022-12-18 00:38:13 +01:00
Adrián Arroyo Calle
0166c3bf0f Compatible Doclog docs for library(iso_ext) 2022-12-17 22:46:44 +01:00
Adrián Arroyo Calle
6a995fb62b Compatible Doclog docs for library(http/http_open). 2022-12-17 20:43:43 +01:00
Mark Thom
d804d8a92e use proper dynamic arities in JmpByCall and JmpByExecute (#1605, #1606) 2022-12-17 11:54:20 -07:00
Mark Thom
cf63e8375d track attr_var_init lengths on the stack (#1667) 2022-12-16 20:10:17 -07:00
Mark Thom
1eff758751 update README to point to local wambook (#1668) 2022-12-16 00:45:00 -07:00
Mark Thom
0a8fc70ba9 detect cyclic bindings in attr_vars_of_term (#1666) 2022-12-16 00:42:38 -07:00
Mark Thom
b4b72a3166 Merge pull request #1662 from mthom/interpreting_disj
Fix mishandled if-then-else interpretation (#1659)
2022-12-16 04:36:02 +01:00
Mark Thom
a5054c0064 use append/3 rather than set_difference/3 to gather witnesses in bagof/3 and setof/3 (#1663, #1664) 2022-12-12 23:55:55 -07:00
Mark Thom
705f421d53 Merge pull request #1661 from aarroyoc/lcm2
Add predicate lcm/2 to library(arithmetic)
2022-12-12 00:38:40 +01:00
Mark Thom
4dc0114c52 fix mishandled if-then-else interpretation (#1659) 2022-12-11 16:35:32 -07:00
Adrián Arroyo Calle
5f2c77fa74 Add predicate lcm/2 to library(arithmetic) 2022-12-11 21:49:41 +01:00
Adrián Arroyo Calle
5f703afed1 Rename copy_file/2 to file_copy/2 2022-12-11 00:08:41 +01:00
Adrián Arroyo Calle
c6aa2068e2 Add predicate copy_file/2 in library(files) 2022-12-11 00:06:56 +01:00
Mark Thom
209f7a239a Merge pull request #1657 from aarroyoc/docs-files
Compatible Doclog docs for library(files)
2022-12-10 18:09:01 +01:00
Mark Thom
0d404ad374 Merge pull request #1656 from aarroyoc/docs-ordsets
Compatible Doclog docs for library(ordsets)
2022-12-10 18:08:45 +01:00
Mark Thom
d5db0c641c Merge pull request #1655 from triska/deref_register
more uses of newly available deref_register(n)
2022-12-10 18:08:26 +01:00
Adrián Arroyo Calle
21d6220f3f Compatible Doclog docs for library(files) 2022-12-08 23:41:28 +01:00
Adrián Arroyo Calle
e0464d5447 Compatible Doclog docs for library(ordsets) 2022-12-08 22:30:48 +01:00
Markus Triska
309e5b320e more uses of newly available deref_register(n) 2022-12-07 23:19:31 +01:00
Mark Thom
afa9703eb5 Merge pull request #1653 from triska/deref_register
introduce and use deref_register(n)
2022-12-07 05:52:07 +01:00
Markus Triska
56e5da6680 introduce and use deref_register(n) 2022-12-06 21:07:50 +01:00
Mark Thom
a8810d73e4 Merge pull request #1652 from aarroyoc/docs-lists
Compatible Doclog docs for library(lists)
2022-12-06 17:57:46 +01:00
Mark Thom
4b31c30cab Merge pull request #1651 from aarroyoc/docs-sockets
Compatible Doclog docs for library(sockets)
2022-12-06 17:57:36 +01:00
Adrián Arroyo Calle
df4b56148f Compatible Doclog docs for library(lists) 2022-12-06 13:19:10 +01:00
Adrián Arroyo Calle
f22a48f576 Compatible Doclog docs for library(sockets) 2022-12-06 11:58:26 +01:00
Mark Thom
d660e4244f dereference encoding register in crypto_data_decrypt (#1650) 2022-12-05 23:28:47 -07:00
Mark Thom
d383e5eb8b avoid pushing stack variables to the heap in get_continuation_chunk (#1644) 2022-12-05 23:17:15 -07:00
Mark Thom
e1dc114517 Merge pull request #1649 from aarroyoc/docs-ugraphs
Compatible Doclog docs for library(ugraphs)
2022-12-05 06:05:13 +01:00
Mark Thom
73780ebf63 Merge pull request #1648 from aarroyoc/docs-random
Compatible Doclog docs for library(random)
2022-12-05 06:05:02 +01:00
Mark Thom
a19f7a0b9f Merge pull request #1647 from aarroyoc/docs-uuid
Compatible Doclog docs for library(uuid)
2022-12-05 06:04:48 +01:00
Mark Thom
e07473d155 Merge pull request #1646 from aarroyoc/docs-assoc
Compatible Doclog docs for library(assoc)
2022-12-05 06:04:30 +01:00
Adrián Arroyo Calle
d429b263eb Compatible Doclog docs for library(ugraphs) 2022-12-05 00:09:32 +01:00
Adrián Arroyo Calle
b2a0d0c1c7 Compatible Doclog docs for library(random) 2022-12-02 23:43:24 +01:00
Adrián Arroyo Calle
8bafd7adb1 Compatible Doclog docs for library(uuid) 2022-12-02 23:23:16 +01:00
Adrián Arroyo Calle
76d24fe4e3 Compatible Doclog docs for library(assoc) 2022-12-02 23:05:03 +01:00
Mark Thom
3bdcc3aba9 return -1 from get_code to indicate end of file (#1622) 2022-11-18 18:19:55 -07:00
Mark Thom
cb25b27963 Merge pull request #1640 from gruhn/master
Adjust Github Action: Docker Publish
2022-11-16 20:57:35 +01:00
Niklas Gruhn
b3df81c143 Adjust Github Action: Docker Publish
1. Not only publish Docker images when new release tags are created but
   on every push to master, since release frequency is so low.

2. Use newer versions of the various actions (setup-buildx-action,
   login-action, metadata-action, ...) to suppress some deprecation
   warnings see:
   https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
2022-11-15 18:55:15 +01:00
Mark Thom
68b3c480c9 update Dockerfile to use rustc 1.61 2022-11-14 21:18:03 -07:00
Mark Thom
9fd1bf5574 mention #scryer in README 2022-11-14 21:16:46 -07:00
Mark Thom
b491a06c6d README rustc version bump 2022-11-14 21:13:52 -07:00
Mark Thom
d16312a314 use existing bindings in compile_is (#1545) 2022-11-14 20:58:07 -07:00
Mark Thom
d19a8d6b98 begin to mark registers as safe from built-in predicates like is/2 (#1545) 2022-11-14 20:58:07 -07:00
Mark Thom
c90dd80ece revise UnsafeVarMarker (#1545) 2022-11-14 20:57:51 -07:00
Paulo Moura
495025dafb Add support for the float_integer_part/1 and float_fractional_part/1 standard arithmetic functions 2021-10-24 09:10:38 +01:00
341 changed files with 65451 additions and 28071 deletions

0
.dockerignore Executable file → Normal file
View File

1
.envrc Normal file
View File

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

2
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,2 @@
# Resolved all lints and formatted the codebase
9444e62df9820d6bfd96dbd8849e177bc5cecc2e

52
.github/actions/setup-rust/action.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: 'Setup Rust'
inputs:
rust-version:
required: true
type: string
targets:
required: true
type: string
components:
required: false
default:
cache-context:
required: true
type: string
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@master
id: toolchain
with:
toolchain: ${{ inputs.rust-version }}
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}
- name: Install i686 dependencies
if: "contains(inputs.targets,'i686')"
shell: bash
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libssl-dev:i386 gcc-multilib clang -y
echo "CC=clang" >> $GITHUB_ENV
echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ inputs.cache-context }}_${{ inputs.targets }}_rustc-${{ steps.toolchain.outputs.cachekey }}_cargo-${{ hashFiles('**/Cargo.lock') }}
# 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@v4.1.0
with:
main: bash ./.github/actions/setup-rust/cleanup.sh
post: bash ./.github/actions/setup-rust/cleanup.sh

13
.github/actions/setup-rust/cleanup.sh vendored Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
echo Cleanup workspace build artifacts and extra target output
# clean just the direct members of the current workspace, use cargo metadata to generalize to all rust projects
cargo clean -p `cargo metadata --no-deps --offline --format-version 1 | jq -r '[.workspace_members[]|split(" ")|.[0]]|join(" ")'`
# remove directories in /target/ that are not named `debug` or `release`
before=`du -s target | awk '{print $1}'`
find ./target -maxdepth 1 -type d ! -name debug ! -name release ! -name target -exec rm -r {} \;
after=`du -s target | awk '{print $1}'`
echo Deleted $(($before - $after)) bytes from target directory

238
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,238 @@
name: CI
on:
push:
branches:
- master
- rebis-dev
tags:
- "v**"
pull_request:
schedule:
- cron: '0 0 * * 3' # At 12:00 AM, only on Wednesday
workflow_dispatch:
permissions:
checks: write
jobs:
style:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
rust-version: stable
targets: x86_64-unknown-linux-gnu
components: clippy, rustfmt
cache-context: style
- name: Check formatting
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-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', use_swap: true }
# Cargo.toml rust-version
- { os: ubuntu-22.04, rust-version: "${{ needs.read-msrv.outputs.msrv }}" , target: 'x86_64-unknown-linux-gnu'}
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu'}
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri"}
defaults:
run:
shell: bash
steps:
- 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
run: cargo build --all-targets --target ${{ matrix.target }} ${{ matrix.args }} --verbose
- name: Test
run: cargo test --target ${{ matrix.target }} ${{ matrix.test-args }} --all
- name: Check 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.
- name: Build release binary
if: matrix.publish
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@v4
with:
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-22.04
needs: [build-test]
steps:
# Download prebuilt ubuntu binary from build-test job, setup logtalk
- uses: actions/download-artifact@v4
with:
name: scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu
- run: |
chmod +x release/scryer-prolog
echo "$PWD/release" >> "$GITHUB_PATH"
- name: Install Logtalk
uses: logtalk-actions/setup-logtalk@master
with:
logtalk-version: "3.70.0"
logtalk-tool-dependencies: false
# Run logtalk tests.
- name: Run Logtalk's prolog compliance test suite
working-directory: ${{ env.LOGTALKUSER }}/tests/prolog/
run: |
pwd
scryerlgt -g '{ack(tester)},halt.'
logtalk_tester -p scryer -g "set_logtalk_flag(clean,off)" -w -t 360 \
-f xunit \
-s "$LOGTALKUSER/tests/prolog" \
|| 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@v4
with:
name: logtalk-test-logs
path: '${{ env.LOGTALKUSER }}/tests/prolog/logtalk_tester_logs'
- name: Publish Logtalk test results artifact
uses: actions/upload-artifact@v4
with:
name: logtalk-test-results
path: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
- name: Publish Logtalk test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
with:
check_name: Logtalk test summary
files: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
fail_on: nothing
comment_mode: off
report:
runs-on: ubuntu-22.04
steps:
- 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
- name: Install CLI tools
run: |
cargo install cargo2junit --force
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@v4
with:
name: cargo-test-results
path: cargo_test_results.xml
- name: Publish cargo test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
with:
check_name: Cargo test summary
files: cargo_test_results.xml
fail_on: nothing
comment_mode: off
- run: cargo build --all-targets --release
- run: cargo test --bench setup --release
- run: cargo bench --bench run_iai -- --save-summary=json
- run: cargo bench --bench run_criterion
- run: cargo bench --bench run_criterion -- --profile-time 60
- name: Publish benchmark results
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: |
target/criterion/*
target/iai/*
target/benchmark_inference_counts.json
# Publish binaries when building for a tag
release:
runs-on: ubuntu-20.04
needs: [build-test]
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/download-artifact@v4
- name: Zip binaries for release
run: |
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@v2
with:
files: |
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

@@ -2,10 +2,10 @@ name: Docker Publish
on:
push:
tags: [ 'v*.*.*' ]
env:
IMAGE_NAME: mjt128/scryer-prolog
branches:
- 'master'
tags:
- 'v*.*.*'
jobs:
build:
@@ -14,37 +14,46 @@ 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
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
# https://github.com/docker/setup-buildx-action
uses: docker/setup-buildx-action@v3
# Login against Docker registry
# https://github.com/docker/login-action
- name: Log into registry
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
# https://github.com/docker/login-action
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Extract Docker image tag from git tag. E.g. if git tag is "v0.19.1" then use
# Docker image tag "0.19.1". Tag "latest" is automatically synced with newest
# version.
# https://github.com/docker/metadata-action
# Docker image tag "0.19.1". The "latest" tag reflects the most recent build on
# master.
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
# https://github.com/docker/metadata-action
uses: docker/metadata-action@v5
with:
images: docker.io/${{ env.IMAGE_NAME }}
images: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/scryer-prolog
tags: |
type=semver,pattern={{version}}
type=raw,value=latest,enable={{is_default_branch}}
# type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
# Build and push Docker image with Buildx
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
# https://github.com/docker/build-push-action
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

View File

@@ -1,71 +0,0 @@
name: Test
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-10.15]
rust-version: [stable, beta]
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust-version }}
override: true
- name: Build lib
uses: actions-rs/cargo@v1
with:
command: rustc
args: --verbose --lib -- -D warnings
- name: Build bin
uses: actions-rs/cargo@v1
with:
command: rustc
args: --verbose --bin scryer-prolog -- -D warnings
- name: Test
uses: actions-rs/cargo@v1
with:
command: test
args: --verbose --all
- name: Num tests
uses: actions-rs/cargo@v1
continue-on-error: true
with:
command: test
args: --verbose --all --no-default-features --features num
msrv:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, macos-10.15]
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install cargo-msrv
uses: baptiste0928/cargo-install@v1.1.0
with:
crate: cargo-msrv
- name: Verify MSRV
run: cargo msrv --verify
windows:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
update: true
install: >-
base-devel
mingw-w64-x86_64-rust
- name: Checkout sources
uses: actions/checkout@v3
- name: Test on Windows
run: cargo test --verbose --all

2
.gitignore vendored
View File

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

3802
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.1"
version = "0.10.0"
authors = ["Mark Thom <markjordanthom@gmail.com>"]
edition = "2021"
description = "A modern Prolog implementation written mostly in Rust."
@@ -10,67 +10,159 @@ license = "BSD-3-Clause"
keywords = ["prolog", "prolog-interpreter", "prolog-system"]
categories = ["command-line-utilities"]
build = "build/main.rs"
rust-version = "1.61"
# Remember to check CI
rust-version = "1.85"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["rug"]
default = ["ffi", "repl", "hostname", "tls", "http", "crypto-full"]
ffi = ["dep:libffi"]
repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
hostname = ["dep:hostname"]
tls = ["dep:native-tls"]
http = ["dep:warp", "dep:reqwest"]
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 = "1.0.88", features = ['full', 'visit', 'extra-traits'] }
to-syn-value = "0.1.0"
to-syn-value_derive = "0.1.0"
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]
arcu = { version = "0.1.2", features = ["thread_local_counter"] }
base64 = "0.22.1"
bit-set = "0.8.0"
bitvec = "1"
blake2 = "0.10.6"
bytes = "1"
chrono = "0.4.38"
cpu-time = "1.0.0"
crossterm = "0.20.0"
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"
fxhash = "0.2.1"
git-version = "0.3.4"
hostname = "0.3.1"
indexmap = "1.0.2"
lazy_static = "1.4.0"
lexical = "5.2.2"
libc = "0.2.62"
modular-bitfield = "0.11.2"
ctrlc = "3.2.2"
ordered-float = "2.6.0"
phf = { version = "0.9", features = ["macros"] }
ref_thread_local = "0.0.0"
rug = { version = "1.15.0", optional = true }
rustyline = "9.0.0"
ring = "0.16.13"
ripemd160 = "0.8.0"
sha3 = "0.8.2"
blake2 = "0.8.1"
crrl ="0.2.0"
native-tls = "0.2.4"
chrono = "0.4.11"
select = "0.4.3"
roxmltree = "0.11.0"
base64 = "0.12.3"
smallvec = "1.8.0"
sodiumoxide = "0.2.6"
static_assertions = "1.1.0"
ryu = "1.0.9"
hyper = { version = "0.14", features = ["full"] }
hyper-tls = "0.5.0"
tokio = { version = "1", features = ["full"] }
divrem = "1.0.0"
futures = "0.3"
fxhash = "0.2.1"
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 = "5.0.0"
phf = { version = "0.11", features = ["macros"] }
puruspe = "0.4.1"
rand = "0.8.5"
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"
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.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.15", features = ["js"] }
tokio = { version = "1.39.2", features = [
"sync",
"macros",
"io-util",
"rt",
"time",
] }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
console_error_panic_hook = "0.1"
wasm-bindgen = "0.2.92"
wasm-bindgen-futures = "0.4"
serde-wasm-bindgen = "0.6"
web-sys = { version = "0.3", features = [
"Document",
"Window",
"Element",
"Performance",
] }
js-sys = "0.3"
ouroboros = "0.18"
[dev-dependencies]
assert_cmd = "1.0.3"
predicates-core = "1.0.2"
serial_test = "0.5.1"
current_platform = "0.2.0"
maplit = "1.0.2"
serial_test = "3.1.1"
[patch.crates-io]
modular-bitfield = { git = "https://github.com/mthom/modular-bitfield" }
[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies]
criterion = "0.5.1"
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"] }
[profile.bench]
lto = true
opt-level = 3
[profile.release]
debug = true
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"
harness = false
[[bench]]
name = "run_iai"
harness = false

18
Dockerfile Executable file → Normal file
View File

@@ -1,18 +1,19 @@
# See https://github.com/LukeMathWalker/cargo-chef
ARG RUST_VERSION=1.60-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,7 +21,14 @@ COPY --from=cacher /scryer-prolog/target target
COPY --from=cacher $CARGO_HOME $CARGO_HOME
RUN cargo build --release --bin scryer-prolog
FROM debian:stable-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)
# then fail the build
RUN scryer-prolog --version
ENTRYPOINT ["/usr/local/bin/scryer-prolog"]

80
INDEX.dj Normal file
View File

@@ -0,0 +1,80 @@
# Scryer Prolog
```
?- append("Hello, ", X, "Hello, Scryer Prolog!").
X = "Scryer Prolog!".
```
![scryer](scryer.png){width=128 style=float:right;} [Scryer Prolog](https://github.com/mthom/scryer-prolog) is a free software ISO Prolog system intended to be an industrial
strength production environment *and* a testbed for bleeding edge research in
logic and constraint programming.
Some of the Scryer Prolog features are:
* ISO standard compliant
* Integrated constraint programming libraries: [clp(B)](/clpb.html), [clp(Z)](/clpz.html).
* [Definite Clause Grammars](/dcgs.html)
* Coroutining support ([`dif/2`](/dif.html), [`freeze/2`](/freeze.html), ...)
* [Tabling and SLG resolution](/tabling.html)
* Compact string representation
* Network libraries ([TCP sockets](/sockets.html), [HTTP server](/http/http_server.html), [HTTP client](/http/http_open.html), ...)
* [Cryptographical predicates](/crypto.html)
* [Foreign Function Interface](/ffi.html)
* WebAssembly support
* Usable as a library
* WAM based engine, cross-platform made in Rust
* _and more..._
Try Scryer Prolog without any installation! Use [Scryer Playground](https://play.scryer.pl), which uses the WASM version of Scryer Prolog.
## What is Prolog?
Prolog is a logic programming language created by [Alain Colmerauer](https://en.wikipedia.org/wiki/Alain_Colmerauer) and [Robert Kowalski](https://en.wikipedia.org/wiki/Robert_Kowalski) in 1972.
The idea behind Prolog is try to express a task in language similar to First Order Logic.
Prolog systems include _unification_ and _non-determinism_ as key concepts upon which we build programs.
A Prolog program is made up of predicates which define a relation between its arguments. A predicate
is made from clauses. A clause can be either a fact or a rule. There's also a toplevel, which we
can use to ask and reason about our task.
It's still to this day one of the best examples and one of the most popular languages in the field
of logic programming. That's because Prolog allows us to elegantly solve many tasks with short and
general programs.
If you want a more detailed description of Prolog, check [A Tour of Prolog](https://www.youtube.com/watch?v=8XUutFBbUrg).
If you want to learn more about Prolog history, check the videos [l'Aventure Prolog](https://www.youtube.com/watch?v=74Ig_QKndvE) and [50 years of Prolog and beyond](https://prologyear.logicprogramming.org/videos/PrologDay_Session_1_talk.mp4).
## Where can I learn Prolog?
There are a lot of classical Prolog books. Those books can teach you the basics of Prolog. Some
examples are: _The Art of Prolog (Shapiro)_, _Programming in Prolog (Clocksin, Mellish)_ and _The Craft
of Prolog (O'Keefe)_. However, most of them are not updated to _modern_ Prolog.
We recommend _[The Power of Prolog (Markus Triska)](https://www.metalevel.at/prolog)_ for modern Prolog. For reference about
the builtin Prolog modules and libraries in Scryer, check the documentation site. It's this!
## Downloads
The latest version of Scryer Prolog is *0.9.4*. And it's already useful for lots of tasks.
| 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.
If you're in Linux, maybe your distribution already has an Scryer Prolog package.
There's also a [Docker image](https://github.com/mthom/scryer-prolog#docker-install) available.
## Support and discussions
If Scryer Prolog crashes or yields unexpected errors, consider filing
an [issue](https://github.com/mthom/scryer-prolog/issues).
To get in touch with the Scryer Prolog community, participate in
[discussions](https://github.com/mthom/scryer-prolog/discussions)
or visit our #scryer IRC channel on [Libera](https://libera.chat)!

294
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
@@ -6,14 +5,20 @@ source industrial strength production environment that is also a
testbed for bleeding edge research in logic and constraint
programming, which is itself written in a high-level language.
**Scryer Prolog passes all tests** of
[syntactic&nbsp;conformity](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing),
[`variable_names/1`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/variable_names) and
[`dif/2`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/dif).
The homepage of the project is: [**https://www.scryer.pl**](https://www.scryer.pl)
![Scryer Logo: Cryer](logo/scryer.png)
## Phase 1
Produce an implementation of the Warren Abstract Machine in Rust, done
according to the progression of languages in [Warren's Abstract
Machine: A Tutorial
Reconstruction](http://wambook.sourceforge.net/wambook.pdf).
Machine: A Tutorial Reconstruction](https://github.com/mthom/scryer-prolog/blob/master/wambook/wambook.pdf).
Phase 1 has been completed in that Scryer Prolog implements in some form
all of the WAM book, including lists, cuts, Debray allocation, first
@@ -44,7 +49,7 @@ Extend Scryer Prolog to include the following, among other features:
- [x] Support for `attribute_goals/2` and `project_attributes/2`
- [x] `call_residue_vars/2`
- [x] `if_/3` and related predicates, following the developments of the
paper "Indexing `dif/2`".
paper "[Indexing `dif/2`](https://arxiv.org/abs/1607.01590)".
- [x] All-solutions predicates (`findall/{3,4}`, `bagof/3`, `setof/3`, `forall/2`).
- [x] Clause creation and destruction (`asserta/1`, `assertz/1`,
`retract/1`, `abolish/1`) with logical update semantics.
@@ -52,9 +57,9 @@ Extend Scryer Prolog to include the following, among other features:
`bb_put/2` (non-backtrackable) and `bb_b_put/2`
(backtrackable).
- [x] Delimited continuations based on reset/3, shift/1 (documented in
"Delimited Continuations for Prolog").
"[Delimited Continuations for Prolog](https://biblio.ugent.be/publication/5646080/file/5646081)").
- [x] Tabling library based on delimited continuations
(documented in "Tabling as a Library with Delimited Control").
(documented in "[Tabling as a Library with Delimited Control](https://biblio.ugent.be/publication/6880648/file/6885145.pdf)").
- [x] A _redone_ representation of strings as difference lists of
characters, using a packed internal representation.
- [x] clp(B) and clp() as builtin libraries.
@@ -66,10 +71,10 @@ Extend Scryer Prolog to include the following, among other features:
- [ ] Replacing choice points pivoting on inlined semi-deterministic predicates
(`atom`, `var`, etc) with if/else ladders. (_in progress_)
- [ ] Inlining all built-ins and system call instructions.
- [ ] Greatly reducing the number of instructions used to compile disjunctives.
- [ ] Storing short atoms to heap cells without writing them to the atom table.
- [x] Greatly reducing the number of instructions used to compile disjunctives.
- [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." (_in progress_)
"[Precise Garbage Collection in Prolog](https://www.complang.tuwien.ac.at/ulrich/papers/PDF/2008-ciclops.pdf)." (_in progress_)
- [ ] Mode declarations.
## Phase 3
@@ -88,12 +93,12 @@ nice to have in the future. They'd make a good project for anyone wanting
to contribute code to Scryer Prolog.
1. Implement the global analysis techniques described in Peter van
Roy's thesis, "Can Logic Programming Execute as Fast as Imperative
Programming?"
Roy's thesis, "[Can Logic Programming Execute as Fast as Imperative
Programming?](https://www.info.ucl.ac.be/~pvr/Peter.thesis/Peter.thesis.html)"
2. Add unum representation and arithmetic, using either an existing
unum implementation or an ad hoc one. Unums are described in
Gustafson's book "The End of Error."
Gustafson's book "[The End of Error](http://www.johngustafson.net/unums.html)."
3. Add concurrent tables to manage shared references to atoms and
strings.
@@ -102,10 +107,17 @@ strings.
## Installing Scryer Prolog
### Native Install
### Binaries
Precompiled binaries for several platforms are available for download
at:
**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
@@ -113,17 +125,51 @@ 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, which 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
$> cd scryer-prolog
$> cargo run [--release]
$> cargo build --release
```
The optional `--release` flag will perform various optimizations,
producing a faster executable.
The `--release` flag performs various optimizations, producing a
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,
@@ -136,10 +182,119 @@ 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.57 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.
However, none of the [default features](https://doc.rust-lang.org/cargo/reference/features.html#the-default-feature) are currently supported. The preferred way of disabling them is passing [extra options](https://rustwasm.github.io/wasm-pack/book/commands/build.html#extra-options) to `wasm-pack`.
For example, if you want a minimal working package without using any bundler like `webpack`, you can do this:
```
wasm-pack build --target web -- --no-default-features
```
Then a `pkg` directory will be created, containing everything you need for a webapp. You can test whether the package is successfully built by creating an html file, adapted from `wasm-bindgen`'s [official example](https://rustwasm.github.io/wasm-bindgen/examples/without-a-bundler.html) like this:
```html
<!DOCTYPE html>
<html>
<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);
}
// 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>
```
Then you can serve it with your favorite http server like `python -m http.server` or `npx serve`, and access the page with your browser.
### 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.
@@ -228,6 +383,35 @@ To quit Scryer Prolog, use the standard predicate `halt/0`:
?- halt.
```
### Starting Scryer Prolog
Scryer Prolog can be started from the command line by specifying
options, files and additional arguments. All components are optional:
<pre>
scryer-prolog [OPTIONS] [FILES] [-- ARGUMENTS]
</pre>
The supported options are:
```
-h, --help Display help message
-v, --version Print version information and exit
-g, --goal GOAL Run the query GOAL after consulting files
-f Fast startup. Do not load initialization file (~/.scryerrc)
--no-add-history Prevent adding input to history file (~/.scryer_history)
```
All specified Prolog files are consulted.
After Prolog files, application-specific arguments can be specified on
the command line. These arguments can be accessed from within Prolog
applications with the predicate&nbsp;`argv/1`, which yields the list
of arguments represented as strings.
Prolog files can also be turned into *shell&nbsp;scripts* as explained in
https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993.
### Dynamic operators
Scryer supports dynamic operators. Using the built-in
@@ -278,14 +462,14 @@ innovations of Scryer Prolog. This means that terms which appear as
lists of characters to Prolog programs are stored in packed
UTF-8&nbsp;encoding by the engine.
Without this innovation, storing a list of characters in memory
would use one memory&nbsp;cell per character, one memory&nbsp;cell per
list constructor, and one memory&nbsp;cell for each tail that occurs
in the list. Since one memory&nbsp;cell takes 8&nbsp;bytes on 64-bit
machines, the packed representation used by Scryer&nbsp;Prolog yields
an up&nbsp;to **24-fold&nbsp;reduction** of memory usage, and
corresponding reduction of memory&nbsp;accesses when creating and
processing strings.
Without this innovation, storing a list of characters in memory would
use one WAM memory&nbsp;cell per character, one cell per list
constructor, and one cell for each tail that occurs in the list. Since
one cell takes 8&nbsp;bytes in the WAM as implemented by
Scryer&nbsp;Prolog, the packed representation yields an up&nbsp;to
**24-fold&nbsp;reduction** of memory usage, and corresponding
reduction of memory&nbsp;accesses when creating and processing
strings.
Scryer Prolog's compact internal string representation makes it
ideally suited for the use case Prolog was originally developed for:
@@ -345,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:
@@ -539,7 +723,7 @@ The modules that ship with Scryer&nbsp;Prolog are also called
Probabilistic predicates and random number generators.
* [`http/http_open`](src/lib/http/http_open.pl) Open a stream to
read answers from web&nbsp;servers. HTTPS is also supported.
* [`http/http_server`](src/lib/http/http_server.pl) Runs a HTTP/1.1 and HTTP/2.0 web server. Uses [Hyper](https://hyper.rs) as a backend. Supports some query and form handling.
* [`http/http_server`](src/lib/http/http_server.pl) Runs a HTTP/1.1 and HTTP/2.0 web server. Uses [Warp](https://github.com/seanmonstar/warp) as a backend. Supports some query and form handling.
* [`sgml`](src/lib/sgml.pl)
`load_html/3` and `load_xml/3` represent HTML and XML&nbsp;documents
as Prolog&nbsp;terms for convenient and efficient reasoning. Use
@@ -577,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
@@ -673,10 +861,54 @@ not need additional tools and formalisms for its application, and
further, it encourages declarative reasoning that can in principle
also be performed automatically.
## Applications
Scryer Prolog's strong commitment to the Prolog ISO standard makes it
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:
- [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
an&nbsp;[issue](https://github.com/mthom/scryer-prolog/issues).
To get in touch with the Scryer Prolog community, participate in
[discussions](https://github.com/mthom/scryer-prolog/discussions)!
[discussions](https://github.com/mthom/scryer-prolog/discussions)
or visit our #scryer IRC channel on [Libera](https://libera.chat)!

95
benches/README.md Normal file
View File

@@ -0,0 +1,95 @@
# About benches
The `benches` directory contains benchmarks that test scryer-prolog performance.
Benchmarks are run via two harnesses:
* `criterion` - criterion performs statistical analysis of benchmark runs and is
great for benchmarking locally.
* `iai-callgrind` - this runs the benchmark with callgrind, which is able to
precisely track the number of instructions executed during the run. This is
especially helpful in a public CI runner context where neighboring VMs can
cause a very high wall time variance. While instructions executed is only
correlated with the desired metric (wall time), this is a good tradeoff for CI
where that metric is unreliable.
Run benchmarks with the following commands:
```
cargo bench --bench run_criterion
# run a particular criterion benchmark
cargo bench --bench run_criterion -- <benchmark_name>
# run in profiling mode which outputs flamegraphs. Set profile time in seconds:
cargo bench --bench run_criterion -- --profile-time <time>
# to run iai, you need valgrind installed and to install iai-callgrind-runner
# at the same version as is in Cargo.toml:
cargo install iai-callgrind-runner --version 0.7.3
cargo bench --bench run_iai
```
For consistency, both runners -- `run_iai.rs` and `run_criterion.rs` -- import
the same setup code from `setup.rs`.
## Setup
`setup.rs` contains the setup code to run benchmarks. `fn prolog_benches()` at
the top of the file is where the benchmarks are defined.
Benchmarks are organized around running queries against a prolog module file.
Before a benchmark starts, `benchmark.setup()` is called which reads the module
file and initializes a new `scryer_prolog::machine::Machine`.
Each benchmark measurement is done by running a query against the machine. In
the case of criterion each query is run many times, in the case of iai it's run
once.
## Adding benchmarks
This design is meant to suppoort defining lots of benchmarks.
To add a new benchmark:
* Add a new file `benches/[module].pl` that contains setup prolog code. Import
libraries, define predicates, etc.
* Add a new section in `setup.rs::prolog_benchmarks()` that refers to to the
file and write a query to be benchmarked.
* If the query mutates the machine, then use `Strategy::Fresh` so the criterion
benchmark will recreate a new machine for each benchmark run, otherwise use
`Strategy::Reuse` which has lower overhead. (This is not used by the iai
benchmark because it only runs once anyway.)
Some tips:
* The goal of benchmarking is to know if a library or engine change improved
performance or not.
* Once a benchmark is defined and named, avoid changing it's definition. If a
benchmark needs to change to be more useful, give the new definition a new
name instead. This will prevent charts from showing wild changes in
performance just because the definition changed (see previous).
* Aim for queries to execute in less than 0.5s realtime. Longer runtimes make it
easier for humans to see big differences, but benchmarks either run 10x slower
(iai) or execute repeatedly to attain statistical significance (criterion) and
in both cases benchmarking queries that take longer than about 0.5s are
cumbersome to run.
* Consider that the library runtime actually parses the text output of the top
level. So don't use custom outputs or it will fail to parse. Also keep the
output small so it doesn't just benchmark the ouput parsing code.
* DO test the output of the benchmark run, we don't want to count broken
benchmarks.
## CI
Both benchmark harnesses are run in `.github/workflows/ci.yaml` in the `report`
job, and the results are published as build artifacts.
## Todo
- [ ] Currently, the execution time to load a module is not benchmarked. It
would be nice to have at least one benchmark for loading a module (probably a
big one).
- [ ] Write a new action that downloads the test and benchmark results
artifacts, plots them over time, and publishes a report to github pages.

41
benches/csv.pl Normal file

File diff suppressed because one or more lines are too long

130
benches/edges.pl Normal file
View File

@@ -0,0 +1,130 @@
:- use_module(library(clpb)).
:- use_module(library(assoc)).
:- use_module(library(lists)).
:- use_module(library(pairs)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Contiguous United States and DC as they appear in SGB:
http://www-cs-faculty.stanford.edu/~uno/sgb.html
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
edge(al, fl).
edge(al, ga).
edge(al, ms).
edge(al, tn).
edge(ar, la).
edge(ar, mo).
edge(ar, ms).
edge(ar, ok).
edge(ar, tn).
edge(ar, tx).
edge(az, ca).
edge(az, nm).
edge(az, nv).
edge(az, ut).
edge(ca, nv).
edge(ca, or).
edge(co, ks).
edge(co, ne).
edge(co, nm).
edge(co, ok).
edge(co, ut).
edge(co, wy).
edge(ct, ma).
edge(ct, ny).
edge(ct, ri).
edge(dc, md).
edge(dc, va).
edge(de, md).
edge(de, nj).
edge(de, pa).
edge(fl, ga).
edge(ga, nc).
edge(ga, sc).
edge(ga, tn).
edge(ia, il).
edge(ia, mn).
edge(ia, mo).
edge(ia, ne).
edge(ia, sd).
edge(ia, wi).
edge(id, mt).
edge(id, nv).
edge(id, or).
edge(id, ut).
edge(id, wa).
edge(id, wy).
edge(il, in).
edge(il, ky).
edge(il, mo).
edge(il, wi).
edge(in, ky).
edge(in, mi).
edge(in, oh).
edge(ks, mo).
edge(ks, ne).
edge(ks, ok).
edge(ky, mo).
edge(ky, oh).
edge(ky, tn).
edge(ky, va).
edge(ky, wv).
edge(la, ms).
edge(la, tx).
edge(ma, nh).
edge(ma, ny).
edge(ma, ri).
edge(ma, vt).
edge(md, pa).
edge(md, va).
edge(md, wv).
edge(me, nh).
edge(mi, oh).
edge(mi, wi).
edge(mn, nd).
edge(mn, sd).
edge(mn, wi).
edge(mo, ne).
edge(mo, ok).
edge(mo, tn).
edge(ms, tn).
edge(mt, nd).
edge(mt, sd).
edge(mt, wy).
edge(nc, sc).
edge(nc, tn).
edge(nc, va).
edge(nd, sd).
edge(ne, sd).
edge(ne, wy).
edge(nh, vt).
edge(nj, ny).
edge(nj, pa).
edge(nm, ok).
edge(nm, tx).
edge(nv, or).
edge(nv, ut).
edge(ny, pa).
edge(ny, vt).
edge(oh, pa).
edge(oh, wv).
edge(ok, tx).
edge(or, wa).
edge(pa, wv).
edge(sd, wy).
edge(tn, va).
edge(ut, wy).
edge(va, wv).
independent_set(G, *(NBs)) :-
findall(U-V, (edge(U, V),G@<U), Edges),
setof(U, V^(member(U-V, Edges);member(V-U, Edges)), Nodes),
pairs_keys_values(Pairs, Nodes, _),
list_to_assoc(Pairs, Assoc),
maplist(not_both(Assoc), Edges, NBs).
not_both(Assoc, U-V, ~BU + ~BV) :-
get_assoc(U, Assoc, BU),
get_assoc(V, Assoc, BV).
independent_set_count(G, Count) :- independent_set(G, Sat), sat_count(Sat, Count).

2
benches/numlist.pl Normal file
View File

@@ -0,0 +1,2 @@
:- use_module(library(between)).
run_numlist(Upper, Head) :- numlist(1, Upper, L), L = [Head|_].

46
benches/run_criterion.rs Normal file
View File

@@ -0,0 +1,46 @@
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[cfg(not(target_os = "windows"))]
use pprof::criterion::{Output, PProfProfiler};
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
mod setup;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
fn bench_criterion(c: &mut Criterion) {
for (&name, bench) in setup::prolog_benches().iter() {
match bench.strategy {
setup::Strategy::Fresh => c.bench_function(name, |b| {
b.iter_batched(|| bench.setup(), |mut r| r(), BatchSize::LargeInput)
}),
setup::Strategy::Reuse => c.bench_function(name, |b| b.iter(bench.setup())),
};
}
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[cfg(not(target_os = "windows"))]
fn config() -> Criterion {
Criterion::default()
.sample_size(20)
.with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)))
}
#[cfg(target_os = "windows")]
fn config() -> Criterion {
Criterion::default().sample_size(20)
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
criterion_group!(
name = benches;
config = config();
targets = bench_criterion
);
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
criterion_main!(benches);
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
fn main() {}

38
benches/run_iai.rs Normal file
View File

@@ -0,0 +1,38 @@
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
mod setup;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
mod iai {
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
use scryer_prolog::LeafAnswer;
use super::setup;
#[library_benchmark]
#[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() -> Vec<LeafAnswer>) -> Vec<LeafAnswer> {
run()
}
library_benchmark_group!(
name = benches;
benchmarks = bench
);
main!(library_benchmark_groups = benches);
pub fn call_main() {
main()
}
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
fn main() {
iai::call_main();
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
fn main() {}

140
benches/setup.rs Normal file
View File

@@ -0,0 +1,140 @@
use std::{collections::BTreeMap, fs, path::Path};
use maplit::btreemap;
use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term};
pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
[
(
"count_edges", // name of the benchmark
"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" => Term::integer(2869176) },
),
(
"numlist",
"benches/numlist.pl",
"run_numlist(1000000, Head).",
Strategy::Reuse,
btreemap! { "Head" => Term::integer(1) },
),
(
"csv_codename",
"benches/csv.pl",
"get_codename(\"0020\",Name).",
Strategy::Reuse,
btreemap! { "Name" => Term::string("SPACE") },
),
]
.map(|b| {
(
b.0,
PrologBenchmark {
name: b.0,
filename: b.1,
query: b.2,
strategy: b.3,
bindings: b.4,
},
)
})
.into()
}
pub enum Strategy {
#[allow(dead_code)]
Fresh,
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, Term>,
}
impl PrologBenchmark {
pub fn make_machine(&self) -> Machine {
let program = fs::read_to_string(self.filename).unwrap();
let module_name = Path::new(self.filename)
.file_stem()
.and_then(|s| s.to_str())
.unwrap();
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() -> 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))
.collect::<Result<Vec<_>, _>>()
.unwrap(),
)
}
}
}
#[cfg(test)]
mod test {
#[test]
fn validate_benchmarks() {
use super::prolog_benches;
use scryer_prolog::LeafAnswer;
use std::{fmt::Write, fs};
struct BenchResult {
pub name: &'static str,
pub setup_inference_count: u64,
pub query_inference_count: u64,
}
let mut results: Vec<BenchResult> = vec![];
for (_, r) in prolog_benches() {
let mut machine = r.make_machine();
let setup_inference_count = machine.get_inference_count();
let result: Vec<_> = machine
.run_query(r.query)
.collect::<Result<_, _>>()
.unwrap();
let query_inference_count = machine.get_inference_count() - setup_inference_count;
let expected = [LeafAnswer::from_bindings(r.bindings.clone())];
assert_eq!(result, expected, "validating benchmark {}", r.name);
results.push(BenchResult {
name: r.name,
setup_inference_count,
query_inference_count,
})
}
let mut json: String = Default::default();
json.push('[');
for r in results {
json.push('\n');
write!(
json,
r#"{{"name":"{}","setup_inference_count":{},"query_inference_count":{}}},"#,
r.name, r.setup_inference_count, r.query_inference_count
)
.unwrap();
}
json.pop(); // trailing comma
json.push_str("\n]");
fs::write("target/benchmark_inference_counts.json", json).expect("Unable to write file");
}
}

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())
@@ -55,19 +60,23 @@ fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
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 mut libraries = File::create(dest_path).unwrap();
let lib_path = Path::new("src").join("lib");
libraries
.write_all(
b"ref_thread_local::ref_thread_local! {
pub(crate) static managed LIBRARIES: IndexMap<&'static str, &'static str> = {
let mut m = IndexMap::new();\n",
let constants = find_prolog_files("", &lib_path);
let out_dir = std::env::var("OUT_DIR").unwrap();
writeln!(libraries, "{{").unwrap();
for (name, lib_path) in constants {
let path = format!("{}{}", MAIN_SEPARATOR_STR, lib_path.display());
writeln!(
libraries,
"m.insert(\"{name}\", include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), {path:?})));"
)
.unwrap();
find_prolog_files(&mut libraries, "", &lib_path);
libraries.write_all(b"\n m\n };\n}\n").unwrap();
}
writeln!(libraries, "}}").unwrap();
let instructions_path = Path::new(&out_dir).join("instructions.rs");
let mut instructions_file = File::create(&instructions_path).unwrap();

View File

@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use syn::*;
use syn::parse::*;
use syn::visit::*;
use syn::*;
use indexmap::IndexSet;
@@ -11,7 +11,9 @@ struct StaticStrVisitor {
impl StaticStrVisitor {
fn new() -> Self {
Self { static_strs: IndexSet::new() }
Self {
static_strs: IndexSet::new(),
}
}
}
@@ -33,7 +35,7 @@ impl Parse for ReadHeapCellExprAndArms {
arms.push(input.parse()?);
while !input.is_empty() {
if let Ok(_) = input.parse::<Token![,]>() {}
let _ = input.parse::<Token![,]>();
arms.push(input.parse()?);
}
@@ -50,7 +52,7 @@ impl Parse for MacroFnArgs {
}
while !input.is_empty() {
if let Ok(_) = input.parse::<Token![,]>() {}
let _ = input.parse::<Token![,]>();
args.push(input.parse()?);
}
@@ -63,27 +65,37 @@ impl<'ast> Visit<'ast> for StaticStrVisitor {
let Macro { path, .. } = m;
if path.is_ident("atom") {
if let Some(Lit::Str(string)) = m.parse_body::<Lit>().ok() {
if let Ok(Lit::Str(string)) = m.parse_body::<Lit>() {
self.static_strs.insert(string.value());
}
} else if path.is_ident("read_heap_cell") || path.is_ident("match_untyped_arena_ptr") {
if let Some(m) = m.parse_body::<ReadHeapCellExprAndArms>().ok() {
if let Ok(m) = m.parse_body::<ReadHeapCellExprAndArms>() {
self.visit_expr(&m.expr);
for e in m.arms {
self.visit_arm(&e);
}
}
} else {
if let Some(m) = m.parse_body::<MacroFnArgs>().ok() {
for e in m.args {
self.visit_expr(&e);
}
} else if let Ok(m) = m.parse_body::<MacroFnArgs>() {
for e in m.args {
self.visit_expr(&e);
}
}
}
}
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)
@@ -145,33 +162,48 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
visitor.visit_file(&syntax);
}
match process_filepath(instruction_rs_path) {
Ok(syntax) => visitor.visit_file(&syntax),
Err(_) => {}
if let Ok(syntax) = process_filepath(instruction_rs_path) {
visitor.visit_file(&syntax)
}
let indices = (0..visitor.static_strs.len()).map(|i| i << 3);
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: [&'static str; #static_strs_len] = [
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

@@ -4,7 +4,6 @@
<Package Description="An open source industrial strength production environment for ISO Prolog that is also a testbed for bleeding edge research in logic and constraint programming, which is itself written in a high-level language." Platform="x64" Keywords="prolog" Id="*" Compressed="yes" InstallScope="perMachine" InstallerVersion="300" Languages="1033" SummaryCodepage="1252" Manufacturer="Scryer Prolog contributors"/>
<Property Id="APPHELPLINK" Value="https://github.com/mthom/scryer-prolog"/>
<Media Id="1" Cabinet="scryer.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder" Name="PFiles">
<Directory Id="INSTALLDIR" Name="Scryer Prolog">
@@ -14,18 +13,16 @@
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Component Id="ApplicationShortcut" Guid="8c9b14a3-e7b1-4d30-a892-61d7371dcae2">
<Component Id="ApplicationShortcut" Guid="8c9b14a3-e7b1-4d30-a892-61d7371dcae2">
<Shortcut Id="ApplicationStarMenuShortcut" Name="Scryer Prolog" Description="Launch Scryer Prolog" Target="[#ScryerPrologEXE]" WorkingDirectory="INSTALLDIR"/>
<RemoveFolder Id="ApplicationShortcut" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\Microsoft\ScryerProlog" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
</Component>
<RemoveFolder Id="ApplicationShortcut" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\Microsoft\ScryerProlog" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Feature Id="Complete" Level="1" Display="expand" ConfigurableDirectory="INSTALLDIR">
<ComponentRef Id="MainExecutable"/>
<ComponentRef Id="ApplicationShortcut"/>
</Feature>
</Product>
</Wix>

View File

@@ -1,14 +1,10 @@
use crate::parser::ast::*;
use crate::temp_v;
use crate::fixtures::*;
use crate::forms::*;
use crate::instructions::*;
use crate::machine::machine_indices::*;
use crate::targets::*;
use std::cell::Cell;
use std::rc::Rc;
pub(crate) trait Allocator {
fn new() -> Self;
@@ -17,91 +13,45 @@ pub(crate) trait Allocator {
&mut self,
lvl: Level,
context: GenContext,
code: &mut Code,
);
code: &mut CodeDeque,
) -> RegType;
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
&mut self,
lvl: Level,
context: GenContext,
cell: &'a Cell<RegType>,
code: &mut Code,
code: &mut CodeDeque,
);
#[allow(clippy::too_many_arguments)]
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
&mut self,
var_name: Rc<String>,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
term_loc: GenContext,
code: &mut Code,
code: &mut CodeDeque,
r: RegType,
is_new_var: bool,
);
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType;
fn mark_var<'a, Target: CompilationTarget<'a>>(
&mut self,
var_name: Rc<String>,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
context: GenContext,
code: &mut Code,
code: &mut CodeDeque,
);
fn reset(&mut self);
fn reset_contents(&mut self) {}
fn reset_arg(&mut self, arg_num: usize);
fn reset_at_head(&mut self, args: &Vec<Term>);
fn reset_at_head(&mut self, args: &[Term]);
fn reset_contents(&mut self);
fn advance_arg(&mut self);
fn bindings(&self) -> &AllocVarDict;
fn bindings_mut(&mut self) -> &mut AllocVarDict;
fn take_bindings(self) -> AllocVarDict;
fn max_reg_allocated(&self) -> usize;
fn drain_var_data<'a>(
&mut self,
vs: VariableFixtures<'a>,
num_of_chunks: usize,
) -> VariableFixtures<'a> {
let mut perm_vs = VariableFixtures::new();
for (var, (var_status, cells)) in vs.into_iter() {
match var_status {
VarStatus::Temp(chunk_num, tvd) => {
self.bindings_mut()
.insert(var.clone(), VarData::Temp(chunk_num, 0, tvd));
if chunk_num + 1 == num_of_chunks {
perm_vs.insert_last_chunk_temp_var(var);
}
}
VarStatus::Perm(_) => {
self.bindings_mut().insert(var.clone(), VarData::Perm(0));
perm_vs.insert(var, (var_status, cells));
}
};
}
perm_vs
}
fn get(&self, var: Rc<String>) -> RegType {
self.bindings()
.get(&var)
.map_or(temp_v!(0), |v| v.as_reg_type())
}
fn is_unbound(&self, var: Rc<String>) -> bool {
self.get(var).reg_num() == 0
}
fn record_register(&mut self, var: Rc<String>, r: RegType) {
match self.bindings_mut().get_mut(&var).unwrap() {
&mut VarData::Temp(_, ref mut s, _) => *s = r.reg_num(),
&mut VarData::Perm(ref mut s) => *s = r.reg_num(),
}
}
}

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,16 +7,19 @@ use crate::debray_allocator::*;
use crate::forms::*;
use crate::instructions::*;
use crate::iterators::*;
use crate::offset_table::*;
use crate::targets::QueryInstruction;
use crate::types::*;
use crate::parser::ast::*;
use crate::parser::rug::ops::PowAssign;
use crate::parser::rug::{Assign, Integer, Rational};
use crate::parser::dashu::{Integer, Rational};
use crate::machine::machine_errors::*;
use ordered_float::*;
use dashu::base::Abs;
use dashu::base::BitTest;
use num_order::NumOrd;
use ordered_float::{Float, OrderedFloat};
use std::cell::Cell;
use std::cmp::{max, min, Ordering};
@@ -22,7 +27,6 @@ use std::convert::TryFrom;
use std::f64;
use std::num::FpCategory;
use std::ops::Div;
use std::rc::Rc;
use std::vec::Vec;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -53,7 +57,7 @@ pub(crate) struct ArithInstructionIterator<'a> {
state_stack: Vec<TermIterState<'a>>,
}
pub(crate) type ArithCont = (Code, Option<ArithmeticTerm>);
pub(crate) type ArithCont = (CodeDeque, Option<ArithmeticTerm>);
impl<'a> ArithInstructionIterator<'a> {
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
@@ -67,19 +71,6 @@ impl<'a> ArithInstructionIterator<'a> {
Term::Clause(cell, name, terms) => {
TermIterState::Clause(Level::Shallow, 0, cell, *name, terms)
}
/* match ClauseType::from(*name, terms.len()) {
ct @ ClauseType::Named(..) => {
Ok(TermIterState::Clause(Level::Shallow, 0, cell, ct, terms))
}
ct @ ClauseType::Inlined(InlinedClauseType::IsFloat(_)) => {
// let ct = ClauseType::Named(1, atom!("float"), CodeIndex::default());
Ok(TermIterState::Clause(Level::Shallow, 0, cell, ct, terms))
}
_ => Err(ArithmeticError::NonEvaluableFunctor(
Literal::Atom(*name),
terms.len(),
)),
}?,*/
Term::Literal(cell, cons) => TermIterState::Literal(Level::Shallow, cell, cons),
Term::Cons(..) | Term::PartialString(..) | Term::CompleteString(..) => {
return Err(ArithmeticError::NonEvaluableFunctor(
@@ -87,7 +78,7 @@ impl<'a> ArithInstructionIterator<'a> {
2,
))
}
Term::Var(cell, var) => TermIterState::Var(Level::Shallow, cell, var.clone()),
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Shallow, cell, var_ptr.clone()),
};
Ok(ArithInstructionIterator {
@@ -98,9 +89,9 @@ 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>, Rc<String>),
Var(Level, &'a Cell<VarReg>, VarPtr),
}
impl<'a> Iterator for ArithInstructionIterator<'a> {
@@ -127,9 +118,9 @@ 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::Var(lvl, cell, var) => {
return Some(Ok(ArithTermRef::Var(lvl, cell, var.clone())));
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(*c))),
TermIterState::Var(lvl, cell, var_ptr) => {
return Some(Ok(ArithTermRef::Var(lvl, cell, var_ptr)));
}
_ => {
return Some(Err(ArithmeticError::NonEvaluableFunctor(
@@ -169,16 +160,18 @@ 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))
Number::Float(OrderedFloat(std::f64::consts::E)),
)),
Literal::Atom(name) if name == &atom!("pi") => interm.push(ArithmeticTerm::Number(
Number::Float(OrderedFloat(std::f64::consts::PI))
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)),
}
@@ -219,6 +212,8 @@ impl<'a> ArithmeticEvaluator<'a> {
atom!("round") => Ok(Instruction::Round(a1, t)),
atom!("ceiling") => Ok(Instruction::Ceiling(a1, t)),
atom!("floor") => Ok(Instruction::Floor(a1, t)),
atom!("float_integer_part") => Ok(Instruction::FloatIntegerPart(a1, t)),
atom!("float_fractional_part") => Ok(Instruction::FloatFractionalPart(a1, t)),
atom!("sign") => Ok(Instruction::Sign(a1, t)),
atom!("\\") => Ok(Instruction::BitwiseComplement(a1, t)),
_ => Err(ArithmeticError::NonEvaluableFunctor(Literal::Atom(name), 1)),
@@ -278,7 +273,7 @@ impl<'a> ArithmeticEvaluator<'a> {
let ninterm = if a1.interm_or(0) == 0 {
self.incr_interm()
} else {
self.interm.push(a1.clone());
self.interm.push(a1);
a1.interm_or(0)
};
@@ -320,41 +315,39 @@ impl<'a> ArithmeticEvaluator<'a> {
src: &'a Term,
term_loc: GenContext,
arg: usize,
) -> Result<ArithCont, ArithmeticError>
{
let mut code = vec![];
let mut iter = src.iter()?;
) -> Result<ArithCont, ArithmeticError> {
let mut code = CodeDeque::new();
while let Some(term_ref) = iter.next() {
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 r = if lvl == Level::Shallow {
self.marker.mark_non_callable(
name.clone(),
arg,
term_loc,
cell,
&mut code,
)
} else if term_loc.is_last() || cell.get().norm().reg_num() == 0 {
self.marker.mark_var::<QueryInstruction>(
name.clone(),
lvl,
cell,
term_loc,
&mut code,
);
let var_num = name.to_var_num().unwrap();
self.marker.get_binding(&name).unwrap()
let r = if lvl == Level::Shallow {
self.marker
.mark_non_callable(var_num, arg, term_loc, cell, &mut code)
} else if term_loc.is_last() || cell.get().norm().reg_num() == 0 {
let r = self.marker.get_binding(var_num);
if r.reg_num() == 0 {
self.marker.mark_var::<QueryInstruction>(
var_num, lvl, cell, term_loc, &mut code,
);
cell.get().norm()
} else {
self.marker.increment_running_count(var_num);
r
}
} else {
self.marker.increment_running_count(var_num);
cell.get().norm()
};
self.interm.push(ArithmeticTerm::Reg(r));
}
ArithTermRef::Op(name, arity) => {
code.push(self.instr_from_clause(name, arity)?);
code.push_back(self.instr_from_clause(name, arity)?);
}
}
}
@@ -364,37 +357,43 @@ impl<'a> ArithmeticEvaluator<'a> {
}
// integer division rounding function -- 9.1.3.1.
pub(crate) fn rnd_i<'a>(n: &'a Number, arena: &mut Arena) -> Number {
pub(crate) fn rnd_i(n: &'_ Number, arena: &mut Arena) -> Result<Number, EvalError> {
match n {
&Number::Integer(i) => {
if let Some(n) = i.to_i64() {
fixnum!(Number, n, 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_f64(f.into_inner()).unwrap(), arena))
Ok(Number::Integer(arena_alloc!(
Integer::try_from(classify_float(f.0)?).unwrap_or_else(|_| {
unreachable!();
}),
arena
)))
}
}
&Number::Rational(ref r) => {
let r_ref = r.fract_floor_ref();
let (mut fract, mut floor) = (Rational::new(), Integer::new());
(&mut fract, &mut floor).assign(r_ref);
Number::Rational(ref r) => {
let floor = r.floor();
if let Some(floor) = floor.to_i64() {
fixnum!(Number, floor, 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)))
}
}
}
@@ -411,9 +410,9 @@ impl From<Fixnum> for Integer {
pub(crate) fn rnd_f(n: &Number) -> f64 {
match n {
&Number::Fixnum(n) => n.get_num() as f64,
&Number::Integer(ref n) => n.to_f64(),
Number::Integer(ref n) => n.to_f64().value(),
&Number::Float(OrderedFloat(f)) => f,
&Number::Rational(ref r) => r.to_f64(),
Number::Rational(ref r) => r.to_f64().value(),
}
}
@@ -433,7 +432,7 @@ fn classify_float(f: f64) -> Result<f64, EvalError> {
}
}
FpCategory::Nan => Err(EvalError::Undefined),
_ => Ok(f)
_ => Ok(f),
}
}
@@ -444,12 +443,12 @@ pub(crate) fn float_fn_to_f(n: i64) -> Result<f64, EvalError> {
#[inline]
pub(crate) fn float_i_to_f(n: &Integer) -> Result<f64, EvalError> {
classify_float(n.to_f64())
classify_float(n.to_f64().value())
}
#[inline]
pub(crate) fn float_r_to_f(r: &Rational) -> Result<f64, EvalError> {
classify_float(r.to_f64())
classify_float(r.to_f64().value())
}
#[inline]
@@ -541,39 +540,33 @@ impl PartialEq for Number {
fn eq(&self, rhs: &Self) -> bool {
match (self, rhs) {
(&Number::Fixnum(n1), &Number::Fixnum(n2)) => n1.eq(&n2),
(&Number::Fixnum(n1), &Number::Integer(ref n2)) => n1.get_num().eq(&**n2),
(&Number::Integer(ref n1), &Number::Fixnum(n2)) => (&**n1).eq(&n2.get_num()),
(&Number::Fixnum(n1), &Number::Rational(ref n2)) => n1.get_num().eq(&**n2),
(&Number::Rational(ref n1), &Number::Fixnum(n2)) => (&**n1).eq(&n2.get_num()),
(&Number::Fixnum(n1), Number::Integer(ref n2)) => n1.get_num().num_eq(&**n2),
(Number::Integer(ref n1), &Number::Fixnum(n2)) => n1.num_eq(&n2.get_num()),
(&Number::Fixnum(n1), Number::Rational(ref n2)) => {
Integer::from(n1.get_num()).num_eq(&**n2)
}
(Number::Rational(ref n1), &Number::Fixnum(n2)) => {
n1.num_eq(&Integer::from(n2.get_num()))
}
(&Number::Fixnum(n1), &Number::Float(n2)) => OrderedFloat(n1.get_num() as f64).eq(&n2),
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.eq(&OrderedFloat(n2.get_num() as f64)),
(&Number::Integer(ref n1), &Number::Integer(ref n2)) => n1.eq(n2),
(&Number::Integer(ref n1), Number::Float(n2)) => OrderedFloat(n1.to_f64()).eq(n2),
(&Number::Float(n1), &Number::Integer(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64())),
(&Number::Integer(ref n1), &Number::Rational(ref n2)) => {
#[cfg(feature = "num")]
{
&Rational::from(&**n1) == &**n2
}
#[cfg(not(feature = "num"))]
{
&**n1 == &**n2
}
(Number::Integer(ref n1), Number::Integer(ref n2)) => n1.eq(n2),
(Number::Integer(ref n1), Number::Float(n2)) => {
OrderedFloat(n1.to_f64().value()).eq(n2)
}
(&Number::Rational(ref n1), &Number::Integer(ref n2)) => {
#[cfg(feature = "num")]
{
&**n1 == &Rational::from(&**n2)
}
#[cfg(not(feature = "num"))]
{
&**n1 == &**n2
}
(&Number::Float(n1), Number::Integer(ref n2)) => {
n1.eq(&OrderedFloat(n2.to_f64().value()))
}
(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)
}
(&Number::Float(n1), Number::Rational(ref n2)) => {
n1.eq(&OrderedFloat(n2.to_f64().value()))
}
(&Number::Rational(ref n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64()).eq(&n2),
(&Number::Float(n1), &Number::Rational(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64())),
(&Number::Float(f1), &Number::Float(f2)) => f1.eq(&f2),
(&Number::Rational(ref r1), &Number::Rational(ref r2)) => r1.eq(&r2),
(Number::Rational(ref r1), Number::Rational(ref r2)) => r1.eq(r2),
}
}
}
@@ -593,8 +586,8 @@ impl PartialOrd<usize> for Number {
(n as usize).partial_cmp(rhs)
}
}
Number::Integer(n) => (&**n).partial_cmp(rhs),
Number::Rational(r) => (&**r).partial_cmp(rhs),
Number::Integer(n) => Some((n).num_cmp(rhs)),
Number::Rational(r) => Some((r).num_cmp(&Integer::from(*rhs))),
Number::Float(f) => f.partial_cmp(&OrderedFloat(*rhs as f64)),
}
}
@@ -613,8 +606,8 @@ impl PartialEq<usize> for Number {
(n as usize).eq(rhs)
}
}
Number::Integer(n) => (&**n).eq(rhs),
Number::Rational(r) => (&**r).eq(rhs),
Number::Integer(n) => (n).num_eq(rhs),
Number::Rational(r) => (r).num_eq(&Integer::from(*rhs)),
Number::Float(f) => f.eq(&OrderedFloat(*rhs as f64)),
}
}
@@ -630,50 +623,42 @@ impl Ord for Number {
fn cmp(&self, rhs: &Number) -> Ordering {
match (self, rhs) {
(&Number::Fixnum(n1), &Number::Fixnum(n2)) => n1.get_num().cmp(&n2.get_num()),
(&Number::Fixnum(n1), Number::Integer(n2)) => Integer::from(n1.get_num()).cmp(&*n2),
(Number::Integer(n1), &Number::Fixnum(n2)) => (&**n1).cmp(&Integer::from(n2.get_num())),
(&Number::Fixnum(n1), Number::Rational(n2)) => Rational::from(n1.get_num()).cmp(&*n2),
(&Number::Fixnum(n1), Number::Integer(n2)) => Integer::from(n1.get_num()).cmp(n2),
(Number::Integer(n1), &Number::Fixnum(n2)) => (**n1).cmp(&Integer::from(n2.get_num())),
(&Number::Fixnum(n1), Number::Rational(n2)) => Rational::from(n1.get_num()).cmp(n2),
(Number::Rational(n1), &Number::Fixnum(n2)) => {
(&**n1).cmp(&Rational::from(n2.get_num()))
(**n1).cmp(&Rational::from(n2.get_num()))
}
(&Number::Fixnum(n1), &Number::Float(n2)) => OrderedFloat(n1.get_num() as f64).cmp(&n2),
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.cmp(&OrderedFloat(n2.get_num() as f64)),
(&Number::Integer(n1), &Number::Integer(n2)) => (*n1).cmp(&*n2),
(&Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64()).cmp(n2),
(&Number::Float(n1), &Number::Integer(ref n2)) => n1.cmp(&OrderedFloat(n2.to_f64())),
(&Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).cmp(n2),
(&Number::Float(n1), Number::Integer(ref n2)) => {
n1.cmp(&OrderedFloat(n2.to_f64().value()))
}
(&Number::Integer(n1), &Number::Rational(n2)) => {
#[cfg(feature = "num")]
{
Rational::from(&**n1).cmp(n2)
}
#[cfg(not(feature = "num"))]
{
(&*n1).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).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)
}
(&Number::Float(n1), &Number::Rational(n2)) => {
n1.cmp(&OrderedFloat(n2.to_f64().value()))
}
(&Number::Rational(n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64()).cmp(&n2),
(&Number::Float(n1), &Number::Rational(n2)) => n1.cmp(&OrderedFloat(n2.to_f64())),
(&Number::Float(f1), &Number::Float(f2)) => f1.cmp(&f2),
(&Number::Rational(r1), &Number::Rational(r2)) => (*r1).cmp(&*r2),
}
}
}
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,
@@ -688,10 +673,11 @@ 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, n) => {
(HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
Ok(Number::Fixnum(n))
}
_ => {
@@ -703,20 +689,20 @@ impl TryFrom<HeapCellValue> for Number {
// Computes n ^ power. Ignores the sign of power.
pub(crate) fn binary_pow(mut n: Integer, power: &Integer) -> Integer {
let mut power = Integer::from(power.abs_ref());
let mut power = power.abs();
if power == 0 {
return Integer::from(1);
if power.is_zero() {
return Integer::ONE;
}
let mut oddand = Integer::from(1);
let mut oddand = Integer::ONE;
while power > 1 {
if power.is_odd() {
while power.num_gt(&1) {
if power.bit(0) {
oddand *= &n;
}
n.pow_assign(2);
n = n.pow(2);
power >>= 1;
}

View File

@@ -1,28 +1,146 @@
#![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::types::*;
use std::borrow::Borrow;
use std::cmp::Ordering;
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 modular_bitfield::prelude::*;
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: usize,
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 {
@@ -30,49 +148,36 @@ 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
}
}
#[cfg(test)]
use std::cell::RefCell;
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;
#[cfg(test)]
thread_local! {
static ATOM_TABLE_BUF_BASE: RefCell<*const u8> = RefCell::new(ptr::null_mut());
#[inline(always)]
fn global_atom_table() -> &'static RwLock<Weak<AtomTable>> {
static GLOBAL_ATOM_TABLE: RwLock<Weak<AtomTable>> = RwLock::new(Weak::new());
&GLOBAL_ATOM_TABLE
}
#[cfg(not(test))]
static mut ATOM_TABLE_BUF_BASE: *const u8 = ptr::null_mut();
#[cfg(test)]
fn set_atom_tbl_buf_base(ptr: *const u8) {
ATOM_TABLE_BUF_BASE.with(|atom_table_buf_base| {
*atom_table_buf_base.borrow_mut() = ptr;
});
}
#[cfg(test)]
pub(crate) fn get_atom_tbl_buf_base() -> *const u8 {
ATOM_TABLE_BUF_BASE.with(|atom_table_buf_base| *atom_table_buf_base.borrow())
}
#[cfg(not(test))]
fn set_atom_tbl_buf_base(ptr: *const u8) {
unsafe {
ATOM_TABLE_BUF_BASE = ptr;
}
}
#[cfg(not(test))]
pub(crate) fn get_atom_tbl_buf_base() -> *const u8 {
unsafe { ATOM_TABLE_BUF_BASE }
#[inline(always)]
fn arc_atom_table() -> Option<Arc<AtomTable>> {
global_atom_table().read().unwrap().upgrade()
}
impl RawBlockTraits for AtomTable {
@@ -90,9 +195,17 @@ impl RawBlockTraits for AtomTable {
#[bitfield]
#[derive(Copy, Clone, Debug)]
struct AtomHeader {
#[allow(unused)] m: bool,
#[allow(unused)]
m: bool,
len: B50,
#[allow(unused)] padding: B13,
#[allow(unused)]
padding: B13,
}
#[repr(C)]
pub struct AtomData {
header: AtomHeader,
data: str,
}
impl AtomHeader {
@@ -101,71 +214,129 @@ impl AtomHeader {
}
}
impl Borrow<str> for Atom {
#[inline]
fn borrow(&self) -> &str {
self.as_str()
}
}
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>),
}
#[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<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Debug::fmt(self.deref(), f)
}
}
impl std::fmt::Display for AtomString<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self.deref(), f)
}
}
impl std::ops::Deref for AtomString<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
match self {
Self::Static(reference) => reference,
Self::Inlined(inlined) => inlined_to_str(inlined),
Self::Dynamic(guard) => guard.deref(),
}
}
}
#[cfg(feature = "repl")]
impl rustyline::completion::Candidate for AtomString<'_> {
fn display(&self) -> &str {
self.deref()
}
fn replacement(&self) -> &str {
self.deref()
}
}
impl Atom {
#[inline]
pub fn buf(self) -> *const u8 {
let ptr = self.as_ptr();
if ptr.is_null() {
return ptr::null();
}
(ptr as usize + mem::size_of::<AtomHeader>()) as *const u8
fn new_inlined(string: &str) -> Self {
AtomCell::new_inlined(string, 0).get_name()
}
#[inline(always)]
pub fn is_static(self) -> bool {
self.index < STRINGS.len() << 3
}
#[inline(always)]
pub fn as_ptr(self) -> *const u8 {
if self.is_static() {
ptr::null()
fn is_static(self) -> bool {
if self.is_inlined() {
true
} else {
(get_atom_tbl_buf_base() as usize + self.index - (STRINGS.len() << 3)) as *const u8
(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");
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))
})
}
}
#[inline(always)]
pub fn from(index: usize) -> Self {
pub fn from(index: u64) -> Self {
Self { index }
}
#[inline(always)]
pub fn len(self) -> usize {
if self.is_static() {
STRINGS[self.index >> 3].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 {
unsafe { ptr::read(self.as_ptr() as *const AtomHeader).len() as _ }
let len: u64 = self.as_ptr().unwrap().header.len();
len as usize
}
}
#[inline(always)]
pub fn flat_index(self) -> u64 {
(self.index >> 3) as u64
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn as_char(self) -> Option<char> {
@@ -175,48 +346,53 @@ impl Atom {
let c1 = it.next();
let c2 = it.next();
if c2.is_none() { c1 } else { None }
}
#[inline]
pub fn chars(&self) -> str::Chars {
self.as_str().chars()
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe {
let ptr = self.as_ptr();
if ptr.is_null() {
return STRINGS[self.index >> 3];
}
let header = ptr::read::<AtomHeader>(ptr as *const _);
let len = header.len() as usize;
let buf = (ptr as usize + mem::size_of::<AtomHeader>()) as *mut u8;
str::from_utf8_unchecked(slice::from_raw_parts(buf, len))
if c2.is_none() {
c1
} else {
None
}
}
pub fn defrock_brackets(&self, atom_tbl: &mut AtomTable) -> Self {
#[inline]
fn inlined_str<'a>(&self) -> Option<AtomString<'a>> {
if self.is_inlined() {
Some(AtomString::Inlined(self.flat_index().to_le_bytes()))
} else {
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])
}
}
pub fn defrock_brackets(&self, atom_tbl: &AtomTable) -> Self {
let s = self.as_str();
let s = if s.starts_with('(') && s.ends_with(')') {
let sub_str = if s.starts_with('(') && s.ends_with(')') {
&s['('.len_utf8()..s.len() - ')'.len_utf8()]
} else {
return *self;
};
atom_tbl.build_with(s)
AtomTable::build_with(atom_tbl, sub_str)
}
}
unsafe fn write_to_ptr(string: &str, ptr: *mut u8) {
ptr::write(ptr as *mut _, AtomHeader::build_with(string.len() as u64));
let str_ptr = (ptr as usize + mem::size_of::<AtomHeader>()) as *mut u8;
ptr::copy_nonoverlapping(string.as_ptr(), str_ptr as *mut u8, string.len());
let str_ptr = ptr.add(mem::size_of::<AtomHeader>());
ptr::copy_nonoverlapping(string.as_ptr(), str_ptr, string.len());
}
impl PartialOrd for Atom {
@@ -229,138 +405,142 @@ impl PartialOrd for Atom {
impl Ord for Atom {
#[inline]
fn cmp(&self, other: &Atom) -> Ordering {
self.as_str().cmp(other.as_str())
self.as_str().cmp(&*other.as_str())
}
}
#[derive(Debug)]
pub struct AtomTable {
pub struct InnerAtomTable {
block: RawBlock<AtomTable>,
pub table: IndexSet<Atom>,
pub table: Arcu<IndexSet<Atom>, GlobalEpochCounterPool>,
}
impl Drop for AtomTable {
fn drop(&mut self) {
self.block.deallocate();
#[derive(Debug)]
pub struct AtomTable {
inner: Arcu<InnerAtomTable, GlobalEpochCounterPool>,
// this lock is taking during resizing
update: Mutex<()>,
}
pub type AtomTableRef<M> = arcu::rcu_ref::RcuRef<InnerAtomTable, M>;
impl InnerAtomTable {
#[inline(always)]
fn lookup_str(self: &InnerAtomTable, string: &str) -> Option<Atom> {
STATIC_ATOMS_MAP
.get(string)
.cloned()
.or_else(|| self.table.read().get(string).cloned())
}
}
impl AtomTable {
#[inline]
pub fn new() -> Self {
let table = Self {
block: RawBlock::new(),
table: IndexSet::new(),
};
set_atom_tbl_buf_base(table.block.base);
table
pub fn new() -> Arc<Self> {
let upgraded = global_atom_table().read().unwrap().upgrade();
// don't inline upgraded, otherwise temporary will be dropped too late in case of None
if let Some(atom_table) = upgraded {
atom_table
} else {
let mut guard = global_atom_table().write().unwrap();
// try to upgrade again in case we lost the race on the write lock
if let Some(atom_table) = guard.upgrade() {
atom_table
} else {
let atom_table = Arc::new(Self {
inner: Arcu::new(
InnerAtomTable {
block: RawBlock::new(),
table: Arcu::new(IndexSet::new(), GlobalEpochCounterPool),
},
GlobalEpochCounterPool,
),
update: Mutex::new(()),
});
*guard = Arc::downgrade(&atom_table);
atom_table
}
}
}
#[inline]
pub fn buf(&self) -> *const u8 {
self.block.base as *const u8
pub fn active_table(&self) -> RcuRef<IndexSet<Atom>, IndexSet<Atom>> {
self.inner.read().table.read()
}
#[inline]
pub fn top(&self) -> *const u8 {
self.block.top
}
#[inline(always)]
fn lookup_str(&self, string: &str) -> Option<Atom> {
STATIC_ATOMS_MAP.get(string).or_else(|| self.table.get(string)).cloned()
}
pub fn build_with(&mut self, string: &str) -> Atom {
if let Some(atom) = self.lookup_str(string) {
return atom;
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);
}
unsafe {
loop {
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;
}
// 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.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
// us aquring the update lock,
// try again
continue;
}
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());
let len_ptr = {
let mut ptr;
loop {
ptr = self.block.alloc(size);
unsafe {
let len_ptr = loop {
let ptr = block_epoch.block.alloc(size);
if ptr.is_null() {
self.block.grow();
set_atom_tbl_buf_base(self.block.base);
// garbage collection would go here
let new_block = block_epoch.block.grow_new().unwrap();
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.read();
table_epoch = block_epoch.table.read();
} else {
break;
break ptr;
}
}
};
ptr
};
let ptr_base = block_epoch.block.base.addr();
let ptr_base = self.block.base as usize;
write_to_ptr(string, len_ptr);
write_to_ptr(string, len_ptr);
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 atom = Atom {
index: (STRINGS.len() << 3) + len_ptr as usize - ptr_base,
};
let mut table = table_epoch.clone();
table.insert(atom);
block_epoch.table.replace(table);
self.table.insert(atom);
// expicit drop to ensure we don't accidentally drop it early
drop(update_guard);
atom
return atom;
}
}
}
}
#[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() << 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() << 3), self.get_arity())
}
}
unsafe impl Send for AtomTable {}
unsafe impl Sync for AtomTable {}

View File

@@ -1,11 +1,7 @@
fn main() {
use std::sync::atomic::Ordering;
use scryer_prolog::*;
fn main() -> std::process::ExitCode {
#[cfg(target_arch = "wasm32")]
return std::process::ExitCode::SUCCESS;
ctrlc::set_handler(move || {
scryer_prolog::machine::INTERRUPT.store(true, Ordering::Relaxed);
}).unwrap();
let mut wam = machine::Machine::new();
wam.run_top_level();
#[cfg(not(target_arch = "wasm32"))]
return scryer_prolog::run_binary();
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +1,280 @@
use crate::allocator::*;
use crate::codegen::SubsumedBranchHits;
use crate::forms::{GenContext, Level};
use crate::instructions::*;
use crate::machine::disjuncts::VarData;
use crate::parser::ast::*;
use crate::targets::*;
use crate::variable_records::*;
use bit_set::*;
use bitvec::prelude::*;
use fxhash::FxBuildHasher;
use indexmap::IndexMap;
use crate::allocator::*;
use crate::fixtures::*;
use crate::forms::Level;
use crate::instructions::*;
use crate::machine::machine_indices::*;
use crate::parser::ast::*;
use crate::targets::CompilationTarget;
use crate::temp_v;
use fxhash::FxBuildHasher;
use std::cell::Cell;
use std::collections::BTreeSet;
use std::rc::Rc;
use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
pub type BranchHits = IndexMap<usize, BitVec, FxBuildHasher>; // key: var_num, value: branch arm occurrences.
#[derive(Debug, Default)]
pub struct BranchOccurrences {
pub hits: BranchHits,
pub shallow_safety: BitSet<usize>, // unset means safe, set means unsafe (after the branch merge)
pub deep_safety: BitSet<usize>,
pub num_branches: usize,
pub current_branch: usize,
pub subsumed_hits: SubsumedBranchHits,
}
impl BranchOccurrences {
fn new(num_branches: usize) -> Self {
Self {
hits: BranchHits::with_hasher(FxBuildHasher::default()),
shallow_safety: BitSet::default(),
deep_safety: BitSet::default(),
num_branches,
current_branch: 0,
subsumed_hits: SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
}
}
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
debug_assert!(self.current_branch < self.num_branches);
let num_branches = self.num_branches;
let entry = self
.hits
.entry(var_num)
.or_insert_with(|| BitVec::repeat(false, num_branches));
entry.set(self.current_branch, true);
self.subsumed_hits.insert(var_num);
}
}
#[derive(Debug)]
pub(crate) struct BranchStack {
stack: Vec<BranchOccurrences>,
}
impl Deref for BranchStack {
type Target = Vec<BranchOccurrences>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.stack
}
}
impl DerefMut for BranchStack {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.stack
}
}
impl BranchStack {
fn branch_subsumes(&self, branch: &BranchDesignator, sub_branch: &BranchDesignator) -> bool {
if branch.branch_stack_num < sub_branch.branch_stack_num {
if branch.branch_stack_num == 0 {
true
} else {
let idx = branch.branch_stack_num - 1;
self[idx].current_branch == branch.branch_num
}
} else {
branch == sub_branch
}
}
fn safety_unneeded_in_branch(
&self,
safety: &VarSafetyStatus,
branch: &BranchDesignator,
) -> bool {
match safety {
VarSafetyStatus::Needed => false,
VarSafetyStatus::LocallyUnneeded(planter_branch) => {
self.branch_subsumes(planter_branch, branch)
}
VarSafetyStatus::GloballyUnneeded => true,
}
}
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
if let Some(occurrences) = self.last_mut() {
occurrences.add_branch_occurrence(var_num);
}
}
pub(crate) fn add_branch_stack(&mut self, num_branches: usize) {
self.push(BranchOccurrences::new(num_branches));
}
pub(crate) fn current_branch_designator(&self) -> BranchDesignator {
let branch_stack_num = self.len();
let branch_num = self
.last()
.map(|occurrences| occurrences.current_branch)
.unwrap_or(0);
BranchDesignator {
branch_stack_num,
branch_num,
}
}
#[inline]
pub(crate) fn incr_current_branch(&mut self) {
let branch_occurrences = self.last_mut().unwrap();
branch_occurrences.current_branch += 1;
}
#[inline]
pub(crate) fn drain_branches(
&mut self,
depth: usize,
) -> std::vec::Drain<'_, BranchOccurrences> {
let start_idx = self.len() - depth;
self.drain(start_idx..)
}
}
#[derive(Debug)]
pub(crate) struct DebrayAllocator {
bindings: IndexMap<Rc<String>, VarData, FxBuildHasher>,
pub(crate) var_data: VarData, // var_data replaces bindings.
pub(crate) branch_stack: BranchStack,
pub(crate) in_tail_position: bool,
arg_c: usize,
temp_lb: usize,
perm_lb: usize,
arity: usize, // 0 if not at head.
contents: IndexMap<usize, Rc<String>, FxBuildHasher>,
in_use: BTreeSet<usize>,
shallow_temp_mappings: IndexMap<usize, usize, FxBuildHasher>,
in_use: BitSet<usize>, // deep and non-var allocations
temp_free_list: Vec<usize>,
perm_free_list: VecDeque<(usize, usize)>, // chunk_num, var_num
}
impl DebrayAllocator {
fn is_curr_arg_distinct_from(&self, var: &String) -> bool {
match self.contents.get(&self.arg_c) {
Some(t_var) if **t_var != *var => true,
pub(crate) fn add_branch(&mut self) {
let branch_designator = self.branch_stack.current_branch_designator();
let subsumed_hits = {
let branch_occurrences = self.branch_stack.last_mut().unwrap();
std::mem::replace(
&mut branch_occurrences.subsumed_hits,
SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
)
};
for var_num in subsumed_hits {
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(_, ref mut allocation) => {
if let PermVarAllocation::Done {
shallow_safety,
deep_safety,
..
} = allocation
{
if !self
.branch_stack
.safety_unneeded_in_branch(shallow_safety, &branch_designator)
{
let branch_occurrences = self.branch_stack.last_mut().unwrap();
branch_occurrences.shallow_safety.insert(var_num);
}
if !self
.branch_stack
.safety_unneeded_in_branch(deep_safety, &branch_designator)
{
let branch_occurrences = self.branch_stack.last_mut().unwrap();
branch_occurrences.deep_safety.insert(var_num);
}
}
*allocation = PermVarAllocation::Pending;
}
_ => unreachable!(),
}
}
}
pub(crate) fn pop_branch(&mut self, depth: usize, subsumed_hits: SubsumedBranchHits) {
let removed_branches = self.branch_stack.drain_branches(depth);
let (deep_safety, shallow_safety) = removed_branches.into_iter().fold(
(BitSet::default(), BitSet::default()),
|(mut deep_safety, mut shallow_safety), branch_occurrences| {
deep_safety.union_with(&branch_occurrences.deep_safety);
shallow_safety.union_with(&branch_occurrences.shallow_safety);
(deep_safety, shallow_safety)
},
);
let branch_designator = self.branch_stack.current_branch_designator();
let (deep_safety, shallow_safety) = match self.branch_stack.last_mut() {
Some(latest_branch) => {
latest_branch.deep_safety.union_with(&deep_safety);
latest_branch.shallow_safety.union_with(&shallow_safety);
(&latest_branch.deep_safety, &latest_branch.shallow_safety)
}
None => (&deep_safety, &shallow_safety),
};
for var_num in subsumed_hits.iter().cloned() {
let running_count = self.var_data.records[var_num].running_count;
let num_occurrences = self.var_data.records[var_num].num_occurrences;
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(_, allocation) => {
let shallow_safety = VarSafetyStatus::needed_if(
shallow_safety.contains(var_num),
branch_designator,
);
let deep_safety = VarSafetyStatus::needed_if(
deep_safety.contains(var_num),
branch_designator,
);
if running_count < num_occurrences {
*allocation = PermVarAllocation::Done {
shallow_safety,
deep_safety,
};
}
}
_ => unreachable!(),
}
}
if !self.branch_stack.is_empty() {
for var_num in subsumed_hits {
self.branch_stack.add_branch_occurrence(var_num);
}
}
}
fn is_curr_arg_distinct_from(&self, var_num: usize) -> bool {
match self.shallow_temp_mappings.get(&self.arg_c).cloned() {
Some(t_var) => t_var != var_num,
_ => false,
}
}
fn occurs_shallowly_in_head(&self, var: &String, r: usize) -> bool {
match self.bindings.get(var).unwrap() {
&VarData::Temp(_, _, ref tvd) => tvd.use_set.contains(&(GenContext::Head, r)),
fn occurs_shallowly_in_head(&self, var_num: usize, r: usize) -> bool {
match &self.var_data.records[var_num].allocation {
VarAlloc::Temp {
temp_var_data,
term_loc: GenContext::Head,
..
} => temp_var_data.use_set.contains(&(GenContext::Head, r)),
_ => false,
}
}
@@ -44,13 +282,13 @@ impl DebrayAllocator {
#[inline]
fn is_in_use(&self, r: usize) -> bool {
let in_use_range = r <= self.arity && r >= self.arg_c;
in_use_range || self.in_use.contains(&r)
in_use_range || self.in_use.contains(r)
}
fn alloc_with_cr(&self, var: &String) -> usize {
match self.bindings.get(var) {
Some(&VarData::Temp(_, _, ref tvd)) => {
for &(_, reg) in tvd.use_set.iter() {
fn alloc_with_cr(&self, var_num: usize) -> usize {
match &self.var_data.records[var_num].allocation {
VarAlloc::Temp { temp_var_data, .. } => {
for &(_, reg) in temp_var_data.use_set.iter() {
if !self.is_in_use(reg) {
return reg;
}
@@ -59,11 +297,9 @@ impl DebrayAllocator {
let mut result = 0;
for reg in self.temp_lb.. {
if !self.is_in_use(reg) {
if !tvd.no_use_set.contains(&reg) {
result = reg;
break;
}
if !self.is_in_use(reg) && !temp_var_data.no_use_set.contains(reg) {
result = reg;
break;
}
}
@@ -73,10 +309,10 @@ impl DebrayAllocator {
}
}
fn alloc_with_ca(&self, var: &String) -> usize {
match self.bindings.get(var) {
Some(&VarData::Temp(_, _, ref tvd)) => {
for &(_, reg) in tvd.use_set.iter() {
fn alloc_with_ca(&self, var_num: usize) -> usize {
match &self.var_data.records[var_num].allocation {
VarAlloc::Temp { temp_var_data, .. } => {
for &(_, reg) in temp_var_data.use_set.iter() {
if !self.is_in_use(reg) {
return reg;
}
@@ -85,13 +321,12 @@ impl DebrayAllocator {
let mut result = 0;
for reg in self.temp_lb.. {
if !self.is_in_use(reg) {
if !tvd.no_use_set.contains(&reg) {
if !tvd.conflict_set.contains(&reg) {
result = reg;
break;
}
}
if !self.is_in_use(reg)
&& !temp_var_data.no_use_set.contains(reg)
&& !temp_var_data.conflict_set.contains(reg)
{
result = reg;
break;
}
}
@@ -101,22 +336,26 @@ impl DebrayAllocator {
}
}
fn alloc_in_last_goal_hint(&self, chunk_num: usize) -> Option<(Rc<String>, usize)> {
fn alloc_in_last_goal_hint(&self, chunk_num: usize) -> Option<(usize, usize)> {
// we want to allocate a register to the k^{th} parameter, par_k.
// par_k may not be a temporary variable.
let k = self.arg_c;
match self.contents.get(&k) {
match self.shallow_temp_mappings.get(&k).cloned() {
Some(t_var) => {
// suppose this branch fires. then t_var is a
// temp. var. belonging to the current chunk.
// consider its use set. T == par_k iff
// (GenContext::Last(_), k) is in t_var.use_set.
let tvd = self.bindings.get(t_var).unwrap();
if let &VarData::Temp(_, _, ref tvd) = tvd {
if !tvd.use_set.contains(&(GenContext::Last(chunk_num), k)) {
return Some((t_var.clone(), self.alloc_with_ca(t_var)));
if let VarAlloc::Temp { temp_var_data, .. } =
&self.var_data.records[t_var].allocation
{
if !temp_var_data
.use_set
.contains(&(GenContext::Last(chunk_num), k))
{
return Some((t_var, self.alloc_with_ca(t_var)));
}
}
@@ -129,51 +368,50 @@ impl DebrayAllocator {
fn evacuate_arg<'a, Target: CompilationTarget<'a>>(
&mut self,
chunk_num: usize,
code: &mut Code,
code: &mut CodeDeque,
) {
match self.alloc_in_last_goal_hint(chunk_num) {
Some((var, r)) => {
let k = self.arg_c;
if let Some((var_num, r)) = self.alloc_in_last_goal_hint(chunk_num) {
let k = self.arg_c;
if r != k {
let r = RegType::Temp(r);
if r != k {
let r = RegType::Temp(r);
code.push(Target::move_to_register(r, k));
code.push_back(Target::move_to_register(r, k));
self.contents.swap_remove(&k);
self.contents.insert(r.reg_num(), var.clone());
self.shallow_temp_mappings.swap_remove(&k);
self.shallow_temp_mappings.insert(r.reg_num(), var_num);
self.record_register(var, r);
self.in_use.insert(r.reg_num());
}
self.var_data.records[var_num]
.allocation
.set_register(r.reg_num());
self.in_use.insert(r.reg_num());
}
_ => {}
};
}
fn alloc_reg_to_var<'a, Target: CompilationTarget<'a>>(
&mut self,
var: &String,
var_num: usize,
lvl: Level,
term_loc: GenContext,
target: &mut Vec<Instruction>,
target: &mut CodeDeque,
) -> usize {
match term_loc {
GenContext::Head => {
if let Level::Shallow = lvl {
self.evacuate_arg::<Target>(0, target);
self.alloc_with_cr(var)
self.alloc_with_cr(var_num)
} else {
self.alloc_with_ca(var)
self.alloc_with_ca(var_num)
}
}
GenContext::Mid(_) => self.alloc_with_ca(var),
GenContext::Mid(_) => self.alloc_with_ca(var_num),
GenContext::Last(chunk_num) => {
if let Level::Shallow = lvl {
self.evacuate_arg::<Target>(chunk_num, target);
self.alloc_with_cr(var)
self.alloc_with_cr(var_num)
} else {
self.alloc_with_ca(var)
self.alloc_with_ca(var_num)
}
}
}
@@ -182,38 +420,262 @@ impl DebrayAllocator {
fn alloc_reg_to_non_var(&mut self) -> usize {
let mut final_index = 0;
while let Some(r) = self.temp_free_list.pop() {
if !self.is_in_use(r) {
self.in_use.insert(r);
return r;
}
}
for index in self.temp_lb.. {
if !self.in_use.contains(&index) {
if !self.in_use.contains(index) {
final_index = index;
self.in_use.insert(final_index);
break;
}
}
self.in_use.insert(final_index);
self.temp_lb = final_index + 1;
final_index
}
fn in_place(&self, var: &String, term_loc: GenContext, r: RegType, k: usize) -> bool {
fn in_place(&self, var_num: usize, term_loc: GenContext, r: RegType, k: usize) -> bool {
match term_loc {
GenContext::Head if !r.is_perm() => r.reg_num() == k,
_ => match self.bindings().get(var).unwrap() {
&VarData::Temp(_, o, _) if r.reg_num() == k => o == k,
_ => match &self.var_data.records[var_num].allocation {
&VarAlloc::Temp { temp_reg, .. } if r.reg_num() == k => temp_reg == k,
_ => false,
},
}
}
fn alloc_perm_var(&mut self, var_num: usize, chunk_num: usize) -> usize {
let p = if let Some(p) = self.pop_free_perm(chunk_num) {
p
} else {
let p = self.perm_lb;
self.perm_lb += 1;
p
};
self.var_data.records[var_num].allocation = VarAlloc::Perm(p, PermVarAllocation::done());
p
}
pub(crate) fn add_reg_to_free_list(&mut self, r: RegType) {
if let RegType::Temp(r) = r {
self.in_use.remove(r);
self.temp_free_list.push(r);
}
}
pub fn reset_free_list(&mut self) {
self.temp_free_list.clear();
}
#[inline(always)]
pub fn get_binding(&self, var_num: usize) -> RegType {
self.var_data.records[var_num].allocation.as_reg_type()
}
pub fn num_perm_vars(&self) -> usize {
self.perm_lb - 1
}
pub fn increment_running_count(&mut self, var_num: usize) {
self.var_data.records[var_num].running_count += 1;
}
fn add_perm_to_free_list(&mut self, chunk_num: usize, var_num: usize) {
if let VarAlloc::Perm(..) = &self.var_data.records[var_num].allocation {
self.perm_free_list.push_back((chunk_num, var_num));
}
}
fn pop_free_perm(&mut self, chunk_num: usize) -> Option<usize> {
while let Some((perm_chunk_num, var_num)) = self.perm_free_list.front().cloned() {
if chunk_num > perm_chunk_num {
self.perm_free_list.pop_front();
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(p, PermVarAllocation::Pending) if *p > 0 => {
return Some(std::mem::replace(p, 0));
}
_ => {}
}
} else {
return None;
}
}
None
}
pub(crate) fn free_var(&mut self, chunk_num: usize, var_num: usize) {
if let VarAlloc::Perm(_, allocation) = &mut self.var_data.records[var_num].allocation {
*allocation = PermVarAllocation::Pending;
self.add_perm_to_free_list(chunk_num, var_num);
}
}
pub(crate) fn mark_safe_var_unconditionally(&mut self, var_num: usize) {
let branch_designator = self.branch_stack.current_branch_designator();
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
deep_safety,
shallow_safety,
..
},
) => {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
}
VarAlloc::Temp { safety, .. } => {
*safety = VarSafetyStatus::unneeded(branch_designator);
}
_ => {
unreachable!()
}
}
}
fn mark_safe_var(&mut self, var_num: usize, lvl: Level, term_loc: GenContext) {
let branch_designator = self.branch_stack.current_branch_designator();
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
deep_safety,
shallow_safety,
..
},
) => {
// GetVariable in head chunk is considered safe.
if lvl == Level::Deep {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
} else if term_loc == GenContext::Head {
*shallow_safety = VarSafetyStatus::GloballyUnneeded;
} else if let Some(&temp_var_num) = self.shallow_temp_mappings.get(&self.arg_c) {
match &mut self.var_data.records[temp_var_num].allocation {
VarAlloc::Temp {
ref mut to_perm_var_num,
..
} => {
*to_perm_var_num = Some(var_num);
}
_ => unreachable!(),
}
}
}
VarAlloc::Temp { ref mut safety, .. } => {
*safety = VarSafetyStatus::GloballyUnneeded;
}
_ => {
unreachable!()
}
}
}
fn argument_to_value<'a, Target: CompilationTarget<'a>>(
&mut self,
var_num: usize,
r: RegType,
arg_c: usize,
) -> Instruction {
let branch_designator = self.branch_stack.current_branch_designator();
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
ref mut shallow_safety,
..
},
) => {
if !self.in_tail_position
|| self
.branch_stack
.safety_unneeded_in_branch(shallow_safety, &branch_designator)
{
Target::argument_to_value(r, arg_c)
} else {
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
Target::unsafe_argument_to_value(r, arg_c)
}
}
VarAlloc::Temp { .. } => {
debug_assert!(matches!(r, RegType::Temp(_)));
Target::argument_to_value(r, arg_c)
}
_ => {
unreachable!()
}
}
}
fn subterm_to_value<'a, Target: CompilationTarget<'a>>(
&mut self,
var_num: usize,
r: RegType,
) -> Instruction {
let branch_designator = self.branch_stack.current_branch_designator();
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
ref mut deep_safety,
..
},
) => {
if self
.branch_stack
.safety_unneeded_in_branch(deep_safety, &branch_designator)
{
Target::subterm_to_value(r)
} else {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
Target::unsafe_subterm_to_value(r)
}
}
VarAlloc::Temp { ref mut safety, .. } => {
if self
.branch_stack
.safety_unneeded_in_branch(safety, &branch_designator)
{
Target::subterm_to_value(r)
} else {
*safety = VarSafetyStatus::unneeded(branch_designator);
Target::unsafe_subterm_to_value(r)
}
}
_ => {
unreachable!()
}
}
}
}
impl Allocator for DebrayAllocator {
fn new() -> DebrayAllocator {
DebrayAllocator {
Self {
var_data: VarData::default(),
in_tail_position: false,
arity: 0,
arg_c: 1,
temp_lb: 1,
bindings: IndexMap::with_hasher(FxBuildHasher::default()),
contents: IndexMap::with_hasher(FxBuildHasher::default()),
in_use: BTreeSet::new(),
perm_lb: 1,
shallow_temp_mappings: IndexMap::with_hasher(FxBuildHasher::default()),
in_use: BitSet::default(),
temp_free_list: vec![],
perm_free_list: VecDeque::new(),
branch_stack: BranchStack { stack: vec![] },
}
}
@@ -221,12 +683,12 @@ impl Allocator for DebrayAllocator {
&mut self,
lvl: Level,
term_loc: GenContext,
code: &mut Code,
) {
code: &mut CodeDeque,
) -> RegType {
let r = RegType::Temp(self.alloc_reg_to_non_var());
match lvl {
Level::Deep => code.push(Target::subterm_to_variable(r)),
Level::Deep => code.push_back(Target::subterm_to_variable(r)),
Level::Root | Level::Shallow => {
let k = self.arg_c;
@@ -236,9 +698,11 @@ impl Allocator for DebrayAllocator {
self.arg_c += 1;
code.push(Target::argument_to_variable(r, k));
code.push_back(Target::argument_to_variable(r, k));
}
};
r
}
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
@@ -246,7 +710,7 @@ impl Allocator for DebrayAllocator {
lvl: Level,
term_loc: GenContext,
cell: &'a Cell<RegType>,
code: &mut Code,
code: &mut CodeDeque,
) {
let r = cell.get();
@@ -273,39 +737,51 @@ impl Allocator for DebrayAllocator {
fn mark_var<'a, Target: CompilationTarget<'a>>(
&mut self,
var: Rc<String>,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
term_loc: GenContext,
code: &mut Code,
code: &mut CodeDeque,
) {
let (r, is_new_var) = match self.get(var.clone()) {
let (r, is_new_var) = match self.get_binding(var_num) {
RegType::Temp(0) => {
// here, r is temporary *and* unassigned.
let o = self.alloc_reg_to_var::<Target>(&var, lvl, term_loc, code);
let o = self.alloc_reg_to_var::<Target>(var_num, lvl, term_loc, code);
cell.set(VarReg::Norm(RegType::Temp(o)));
(RegType::Temp(o), true)
}
RegType::Perm(0) => {
let pr = cell.get().norm();
self.record_register(var.clone(), pr);
let p = self.alloc_perm_var(var_num, term_loc.chunk_num());
cell.set(VarReg::Norm(RegType::Perm(p)));
(RegType::Perm(p), true)
}
r @ RegType::Perm(_) => {
let is_new_var = match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(_, allocation) => {
if allocation.pending() {
*allocation = PermVarAllocation::done();
true
} else {
false
}
}
_ => unreachable!(),
};
(pr, true)
(r, is_new_var)
}
r => (r, false),
};
self.mark_reserved_var::<Target>(var, lvl, cell, term_loc, code, r, is_new_var);
self.mark_reserved_var::<Target>(var_num, lvl, cell, term_loc, code, r, is_new_var);
}
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
&mut self,
var: Rc<String>,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
term_loc: GenContext,
code: &mut Code,
code: &mut CodeDeque,
r: RegType,
is_new_var: bool,
) {
@@ -313,84 +789,114 @@ impl Allocator for DebrayAllocator {
Level::Root | Level::Shallow => {
let k = self.arg_c;
if self.is_curr_arg_distinct_from(&var) {
if self.is_curr_arg_distinct_from(var_num) {
self.evacuate_arg::<Target>(term_loc.chunk_num(), code);
}
self.arg_c += 1;
cell.set(VarReg::ArgAndNorm(r, k));
if !self.in_place(&var, term_loc, r, k) {
if !self.in_place(var_num, term_loc, r, k) {
if is_new_var {
code.push(Target::argument_to_variable(r, k));
self.mark_safe_var(var_num, lvl, term_loc);
code.push_back(Target::argument_to_variable(r, k));
} else {
code.push(Target::argument_to_value(r, k));
code.push_back(self.argument_to_value::<Target>(var_num, r, k));
}
}
self.arg_c += 1;
}
Level::Deep if is_new_var => {
if let GenContext::Head = term_loc {
if self.occurs_shallowly_in_head(&var, r.reg_num()) {
code.push(Target::subterm_to_value(r));
if self.occurs_shallowly_in_head(var_num, r.reg_num()) {
code.push_back(self.subterm_to_value::<Target>(var_num, r));
} else {
code.push(Target::subterm_to_variable(r));
self.mark_safe_var(var_num, lvl, term_loc);
code.push_back(Target::subterm_to_variable(r));
}
} else {
code.push(Target::subterm_to_variable(r));
self.mark_safe_var(var_num, lvl, term_loc);
code.push_back(Target::subterm_to_variable(r));
}
}
Level::Deep => code.push(Target::subterm_to_value(r)),
};
Level::Deep => code.push_back(self.subterm_to_value::<Target>(var_num, r)),
}
let o = r.reg_num();
if !r.is_perm() {
let o = r.reg_num();
self.shallow_temp_mappings.insert(o, var_num);
} else if r.is_perm() && is_new_var {
self.branch_stack.add_branch_occurrence(var_num);
}
self.contents.insert(o, var.clone());
self.record_register(var.clone(), r);
self.in_use.insert(o);
let record = &mut self.var_data.records[var_num];
record.allocation.set_register(o);
if record.running_count < record.num_occurrences {
record.running_count += 1;
} else {
self.free_var(term_loc.chunk_num(), var_num);
}
self.in_use.insert(o);
}
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType {
match self.get_binding(var_num) {
RegType::Perm(0) => RegType::Perm(self.alloc_perm_var(var_num, chunk_num)),
RegType::Temp(0) => {
let t = self.alloc_reg_to_non_var();
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Temp {
temp_reg, safety, ..
} => {
*temp_reg = t;
*safety = VarSafetyStatus::GloballyUnneeded;
}
_ => unreachable!(),
};
RegType::Temp(t)
}
r => r,
}
}
fn reset(&mut self) {
self.bindings.clear();
self.contents.clear();
self.perm_lb = 1;
self.shallow_temp_mappings.clear();
self.in_use.clear();
self.temp_free_list.clear();
}
fn reset_contents(&mut self) {
self.contents.clear();
self.in_use.clear();
self.shallow_temp_mappings.clear();
self.temp_free_list.clear();
}
fn advance_arg(&mut self) {
self.arg_c += 1;
}
fn bindings(&self) -> &AllocVarDict {
&self.bindings
}
fn bindings_mut(&mut self) -> &mut AllocVarDict {
&mut self.bindings
}
fn take_bindings(self) -> AllocVarDict {
self.bindings
}
fn reset_at_head(&mut self, args: &Vec<Term>) {
fn reset_at_head(&mut self, args: &[Term]) {
self.reset_arg(args.len());
self.arity = args.len();
for (idx, arg) in args.iter().enumerate() {
if let &Term::Var(_, ref var) = arg {
let r = self.get(var.clone());
if let Term::Var(_, ref var) = arg {
let var_num = var.to_var_num().unwrap();
let r = self.get_binding(var_num);
if !r.is_perm() && r.reg_num() == 0 {
self.in_use.insert(idx + 1);
self.contents.insert(idx + 1, var.clone());
self.record_register(var.clone(), temp_v!(idx + 1));
self.shallow_temp_mappings.insert(idx + 1, var_num);
self.var_data.records[var_num]
.allocation
.set_register(idx + 1);
}
}
}

936
src/ffi.rs Normal file
View File

@@ -0,0 +1,936 @@
/* How does FFI work?
Each WAM machine has a ForeignFunctionTable instance that contains a table of functions and structs.
Structs are defined via foreign_struct/2. Basic types are defined by libffi, but struct types need to
be manually defined to get an ffi_type. Additionally, to recover structs from return arguments, we store
fields and atom_fields, as a way to lookup the content of the struct (fields) and the nested structs (atom_fields).
Functions are defined via use_foreign_module/2. It opens a library and leaks the memory of the library,
to prevent Rust freeing the memory. There's no way to recover that memory at the moment. We get a pointer for
each function and we build a CIF for each one, with the input arguments and the return argument.
Exec happens via '$foreign_call', we find the function, we try to cast the values that we have to the definition
of the function, we reserve memory for them and we build an array of pointers. To get the return argument, we
reserve enough memory for the return and we build the Scryer values from them.
Structs are a bit tricky as they need to be aligned. For that, we reserve enough memory (libffi calculates that)
and for each field: we add to the pointer until we're aligned to the next data type we're going to write, we write it,
and finally we add the pointer the size of what we've written.
*/
use crate::arena::Arena;
use crate::atom_table::Atom;
use crate::forms::Number;
use crate::machine::machine_errors::DomainErrorType;
use crate::parser::ast::{Fixnum, MightNotFitInFixnum};
use dashu::Integer;
use libffi::middle::{Arg, Cif, CodePtr, Type};
use libloading::{Library, Symbol};
use ordered_float::OrderedFloat;
use std::alloc::{self, Layout};
use std::collections::HashMap;
use std::error::Error;
use std::ffi::{c_char, c_void, CStr, CString};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ptr::NonNull;
pub struct FunctionDefinition {
pub name: Atom,
pub return_value: Atom,
pub args: Vec<Atom>,
}
#[derive(Debug)]
pub struct FunctionImpl {
cif: Cif,
args: Vec<FfiType>,
code_ptr: CodePtr,
return_type: FfiType,
}
impl FunctionImpl {
unsafe fn call_void(&self, args: &[Arg], _: &mut Arena) -> Result<Value, FfiError> {
self.cif.call::<()>(self.code_ptr, args);
Ok(Value::Number(Number::Fixnum(Fixnum::build_with(0))))
}
unsafe fn call_int<T>(&self, args: &[Arg], arena: &mut Arena) -> Result<Value, FfiError>
where
Integer: From<T>,
T: Copy + TryInto<i64> + MightNotFitInFixnum,
{
let n = self.cif.call::<T>(self.code_ptr, args);
Ok(Value::Number(fixnum!(Number, n, arena)))
}
unsafe fn call_float<T>(&self, args: &[Arg], _: &mut Arena) -> Result<Value, FfiError>
where
T: Into<f64>,
{
let n = self.cif.call::<T>(self.code_ptr, args);
Ok(Value::Number(Number::Float(OrderedFloat(n.into()))))
}
unsafe fn call_ptr(&self, args: &[Arg], arena: &mut Arena) -> Result<Value, FfiError> {
let ptr = unsafe { self.cif.call::<*mut c_void>(self.code_ptr, args) };
Ok(Value::Number(fixnum!(Number, ptr as isize, arena)))
}
unsafe fn call_cstr(&self, args: &[Arg], _: &mut Arena) -> Result<Value, FfiError> {
let ptr = unsafe {
self.cif
.call::<Option<NonNull<c_char>>>(self.code_ptr, args)
};
if let Some(cstr) = ptr {
Ok(Value::CString(
unsafe { CStr::from_ptr(cstr.as_ptr()) }.to_owned(),
))
} else {
Ok(Value::Number(Number::Fixnum(Fixnum::build_with(0))))
}
}
unsafe fn call_struct(
&self,
return_type_name: Atom,
args: &[Arg],
arena: &mut Arena,
structs_table: &HashMap<Atom, StructImpl>,
) -> Result<Value, FfiError> {
let struct_type = structs_table
.get(&return_type_name)
.ok_or(FfiError::StructNotFound(return_type_name))?;
let ffi_type = unsafe { *struct_type.ffi_type.as_raw_ptr() };
let layout = Layout::from_size_align(ffi_type.size, ffi_type.alignment.into())
.map_err(|_| FfiError::LayoutError)?;
let alloc = FfiStruct::new(layout, FfiAllocator::Rust)?;
unsafe {
libffi::raw::ffi_call(
self.cif.as_raw_ptr(),
Some(*self.code_ptr.as_safe_fun()),
alloc.ptr.as_ptr(),
args.as_ptr() as *mut *mut c_void,
)
};
let struct_val =
struct_type.read(alloc.ptr.as_ptr(), return_type_name, structs_table, arena);
drop(alloc);
struct_val
}
fn call(
&self,
args: &[Arg],
arena: &mut Arena,
structs_table: &HashMap<Atom, StructImpl>,
) -> Result<Value, FfiError> {
let call_fn: unsafe fn(&Self, &[Arg], &mut Arena) -> Result<Value, FfiError> =
match self.return_type {
FfiType::Void => FunctionImpl::call_void,
FfiType::U8 => FunctionImpl::call_int::<u8>,
FfiType::I8 | FfiType::Bool => FunctionImpl::call_int::<i8>,
FfiType::U16 => FunctionImpl::call_int::<u16>,
FfiType::I16 => FunctionImpl::call_int::<i16>,
FfiType::U32 => FunctionImpl::call_int::<u32>,
FfiType::I32 => FunctionImpl::call_int::<i32>,
FfiType::U64 => FunctionImpl::call_int::<u64>,
FfiType::I64 => FunctionImpl::call_int::<i64>,
FfiType::F32 => FunctionImpl::call_float::<f32>,
FfiType::F64 => FunctionImpl::call_float::<f64>,
FfiType::Ptr => FunctionImpl::call_ptr,
FfiType::CStr => FunctionImpl::call_cstr,
FfiType::Struct(name) => {
return unsafe { self.call_struct(name, args, arena, structs_table) }
}
};
unsafe { call_fn(self, args, arena) }
}
}
#[derive(Debug, Default)]
pub struct ForeignFunctionTable {
table: HashMap<Atom, FunctionImpl>,
structs: HashMap<Atom, StructImpl>,
}
#[derive(Clone, Debug)]
struct StructImpl {
ffi_type: Type,
fields: Vec<FfiType>,
}
impl StructImpl {
fn layout(&self) -> Result<Layout, FfiError> {
let ffi_type = unsafe { *self.ffi_type.as_raw_ptr() };
Layout::from_size_align(ffi_type.size, ffi_type.alignment.into())
.map_err(|_| FfiError::LayoutError)
}
fn build(
&self,
structs_table: &HashMap<Atom, StructImpl>,
struct_args: &mut [Value],
) -> Result<FfiStruct, FfiError> {
let args = ArgValue::build_args(struct_args, &self.fields, structs_table)?;
let alloc = FfiStruct::new(self.layout()?, FfiAllocator::Rust)?;
let Ok(mut current_layout) = Layout::from_size_align(0, 1) else {
return Err(FfiError::LayoutError);
};
unsafe fn write_primitive<T>(
ptr: NonNull<c_void>,
layout: &mut Layout,
val: T,
) -> Result<(), FfiError> {
let (new_layout, offset) = layout
.extend(Layout::new::<T>())
.map_err(|_| FfiError::LayoutError)?;
*layout = new_layout;
ptr.byte_offset(offset as isize).cast::<T>().write(val);
Ok(())
}
for arg in args {
unsafe {
match arg {
ArgValue::U8(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::I8(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::U16(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::I16(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::U32(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::I32(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::U64(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::I64(i) => write_primitive(alloc.ptr, &mut current_layout, i)?,
ArgValue::F32(f) => write_primitive(alloc.ptr, &mut current_layout, f)?,
ArgValue::F64(f) => write_primitive(alloc.ptr, &mut current_layout, f)?,
ArgValue::Ptr(p, _) => write_primitive(alloc.ptr, &mut current_layout, p)?,
ArgValue::Struct(arg) => {
let Ok((new_layout, offset)) = current_layout.extend(arg.layout) else {
return Err(FfiError::LayoutError);
};
current_layout = new_layout;
std::ptr::copy(
arg.ptr.as_ptr(),
alloc.ptr.byte_offset(offset as isize).as_ptr(),
arg.layout.size(),
);
}
}
}
}
if alloc.layout != current_layout.pad_to_align() {
// sanity check
return Err(FfiError::LayoutError);
}
Ok(alloc)
}
fn read(
&self,
ptr: *mut c_void,
struct_name: Atom,
struct_table: &HashMap<Atom, StructImpl>,
arena: &mut Arena,
) -> Result<Value, FfiError> {
unsafe {
let mut returns = Vec::new();
unsafe fn read_primitive<T>(
ptr: *mut c_void,
layout: &mut Layout,
) -> Result<T, FfiError> {
let (new_layout, offset) = layout
.extend(Layout::new::<T>())
.map_err(|_| FfiError::LayoutError)?;
*layout = new_layout;
let n = std::ptr::read::<T>(ptr.byte_offset(offset as isize).cast());
Ok(n)
}
unsafe fn read_int<T>(
ptr: *mut c_void,
layout: &mut Layout,
arena: &mut Arena,
) -> Result<Value, FfiError>
where
T: Copy + TryInto<i64> + MightNotFitInFixnum,
Integer: From<T>,
{
let n = read_primitive::<T>(ptr, layout)?;
Ok(Value::Number(fixnum!(Number, n, arena)))
}
unsafe fn read_float<T>(
ptr: *mut c_void,
layout: &mut Layout,
) -> Result<Value, FfiError>
where
T: Into<f64>,
{
let n = read_primitive::<T>(ptr, layout)?;
Ok(Value::Number(Number::Float(OrderedFloat(n.into()))))
}
let mut layout = Layout::from_size_align(0, 1).map_err(|_| FfiError::LayoutError)?;
for field_type in &self.fields {
let val = match field_type {
FfiType::U8 => read_int::<u8>(ptr, &mut layout, arena),
FfiType::I8 | FfiType::Bool => read_int::<i8>(ptr, &mut layout, arena),
FfiType::U16 => read_int::<u16>(ptr, &mut layout, arena),
FfiType::I16 => read_int::<i16>(ptr, &mut layout, arena),
FfiType::U32 => read_int::<u32>(ptr, &mut layout, arena),
FfiType::I32 => read_int::<i32>(ptr, &mut layout, arena),
FfiType::U64 => read_int::<u64>(ptr, &mut layout, arena),
FfiType::I64 => read_int::<i64>(ptr, &mut layout, arena),
FfiType::Ptr => {
let ptr = read_primitive::<*mut c_void>(ptr, &mut layout)?;
Ok(Value::Number(fixnum!(Number, ptr as isize, arena)))
}
FfiType::CStr => {
let ptr = read_primitive::<*mut c_void>(ptr, &mut layout)?;
Ok(Value::CString(CStr::from_ptr(ptr.cast()).to_owned()))
}
FfiType::F32 => read_float::<f32>(ptr, &mut layout),
FfiType::F64 => read_float::<f64>(ptr, &mut layout),
FfiType::Struct(substruct) => {
let Some(substruct_type) = struct_table.get(substruct) else {
return Err(FfiError::StructNotFound(*substruct));
};
let ffi_type = *substruct_type.ffi_type.as_raw_ptr();
let field_layout =
Layout::from_size_align(ffi_type.size, ffi_type.alignment as usize)
.map_err(|_| FfiError::LayoutError)?;
let (new_layout, offset) = layout
.extend(field_layout)
.map_err(|_| FfiError::LayoutError)?;
layout = new_layout;
let field_ptr = ptr.byte_offset(offset as isize);
let struct_val =
substruct_type.read(field_ptr, *substruct, struct_table, arena)?;
Ok(struct_val)
}
FfiType::Void => return Err(FfiError::VoidArgumentType),
};
returns.push(val?);
}
Ok(Value::Struct(struct_name, returns))
}
}
}
struct PointerArgs<'a, 'val> {
memory: Vec<Arg>,
phantom: PhantomData<&'a mut ArgValue<'val>>,
}
impl<'args, 'val> PointerArgs<'args, 'val> {
fn new(args: &'args [ArgValue<'val>]) -> Self {
let args = args
.iter()
.map(|arg| match arg {
ArgValue::U8(a) => libffi::middle::arg(a),
ArgValue::I8(a) => libffi::middle::arg(a),
ArgValue::U16(a) => libffi::middle::arg(a),
ArgValue::I16(a) => libffi::middle::arg(a),
ArgValue::U32(a) => libffi::middle::arg(a),
ArgValue::I32(a) => libffi::middle::arg(a),
ArgValue::U64(a) => libffi::middle::arg(a),
ArgValue::I64(a) => libffi::middle::arg(a),
ArgValue::F32(a) => libffi::middle::arg(a),
ArgValue::F64(a) => libffi::middle::arg(a),
ArgValue::Ptr(ptr, _) => Arg::new(ptr),
ArgValue::Struct(s) => unsafe {
std::mem::transmute::<*mut c_void, Arg>(s.ptr.as_ptr())
},
})
.collect();
PointerArgs {
memory: args,
phantom: PhantomData,
}
}
}
impl Deref for PointerArgs<'_, '_> {
type Target = [Arg];
fn deref(&self) -> &Self::Target {
&self.memory
}
}
#[derive(Debug, Clone, Copy)]
enum FfiType {
Void,
Bool,
U8,
I8,
U16,
I16,
U32,
I32,
U64,
I64,
F32,
F64,
Ptr,
CStr,
Struct(Atom),
}
impl FfiType {
fn from_atom(atom: &Atom) -> Self {
match atom {
atom!("sint64") | atom!("i64") => Self::I64,
atom!("sint32") | atom!("i32") => Self::I32,
atom!("sint16") | atom!("i16") => Self::I16,
atom!("sint8") | atom!("i8") => Self::I8,
atom!("uint64") | atom!("u64") => Self::U64,
atom!("uint32") | atom!("u32") => Self::U32,
atom!("uint16") | atom!("u16") => Self::U16,
atom!("uint8") | atom!("u8") => Self::U8,
atom!("bool") => Self::Bool,
atom!("void") => Self::Void,
atom!("cstr") => Self::CStr,
atom!("ptr") => Self::Ptr,
atom!("f32") => Self::F32,
atom!("f64") => Self::F64,
struct_name => Self::Struct(*struct_name),
}
}
fn to_type(self, structs_table: &HashMap<Atom, StructImpl>) -> Result<Type, FfiError> {
Ok(match self {
Self::I64 => libffi::middle::Type::i64(),
Self::I32 => libffi::middle::Type::i32(),
Self::I16 => libffi::middle::Type::i16(),
Self::I8 => libffi::middle::Type::i8(),
Self::U64 => libffi::middle::Type::u64(),
Self::U32 => libffi::middle::Type::u32(),
Self::U16 => libffi::middle::Type::u16(),
Self::U8 => libffi::middle::Type::u8(),
Self::Bool => libffi::middle::Type::i8(),
Self::Void => libffi::middle::Type::void(),
Self::CStr => libffi::middle::Type::pointer(),
Self::Ptr => libffi::middle::Type::pointer(),
Self::F32 => libffi::middle::Type::f32(),
Self::F64 => libffi::middle::Type::f64(),
Self::Struct(struct_name) => structs_table
.get(&struct_name)
.ok_or(FfiError::StructNotFound(struct_name))?
.ffi_type
.clone(),
})
}
}
enum ArgValue<'a> {
U8(u8),
I8(i8),
U16(u16),
I16(i16),
U32(u32),
I32(i32),
U64(u64),
I64(i64),
F32(f32),
F64(f64),
Ptr(*mut c_void, PhantomData<&'a CString>),
Struct(FfiStruct),
}
impl<'val> ArgValue<'val> {
fn new(
val: &'val mut Value,
arg_type: &FfiType,
structs_table: &HashMap<Atom, StructImpl>,
) -> Result<Self, FfiError> {
match arg_type {
FfiType::U8 => Ok(Self::U8(val.as_int()?)),
FfiType::I8 | FfiType::Bool => Ok(Self::I8(val.as_int()?)),
FfiType::U16 => Ok(Self::U16(val.as_int()?)),
FfiType::I16 => Ok(Self::I16(val.as_int()?)),
FfiType::U32 => Ok(Self::U32(val.as_int()?)),
FfiType::I32 => Ok(Self::I32(val.as_int()?)),
FfiType::U64 => Ok(Self::U64(val.as_int()?)),
FfiType::I64 => Ok(Self::I64(val.as_int()?)),
FfiType::F32 => Ok(Self::F32(val.as_float()? as f32)),
FfiType::F64 => Ok(Self::F64(val.as_float()?)),
FfiType::Ptr => Ok(Self::Ptr(val.as_ptr()?, PhantomData)),
FfiType::CStr => Ok(Self::Ptr(val.as_ptr()?, PhantomData)),
FfiType::Struct(arg_type_name) => {
let (val_type_name, args) = val.as_struct()?;
if *arg_type_name != val_type_name {
return Err(FfiError::ValueCast(*arg_type_name, val_type_name));
}
let Some(struct_type) = structs_table.get(&val_type_name) else {
return Err(FfiError::StructNotFound(*arg_type_name));
};
Ok(Self::Struct(struct_type.build(structs_table, args)?))
}
FfiType::Void => Err(FfiError::VoidArgumentType),
}
}
fn build_args(
args: &'val mut [Value],
types: &[FfiType],
structs_table: &HashMap<Atom, StructImpl>,
) -> Result<Vec<Self>, FfiError> {
if types.len() != args.len() {
return Err(FfiError::ArgCountMismatch);
}
args.iter_mut()
.zip(types)
.map(|(arg, arg_type)| ArgValue::new(arg, arg_type, structs_table))
.collect::<Result<Vec<_>, _>>()
}
}
struct FfiStruct {
ptr: NonNull<c_void>,
layout: Layout,
allocator: FfiAllocator,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum FfiAllocator {
Rust,
C,
}
impl TryFrom<Atom> for FfiAllocator {
type Error = ();
fn try_from(value: Atom) -> Result<Self, Self::Error> {
match value {
atom!("rust") => Ok(Self::Rust),
atom!("c") => Ok(Self::C),
_ => Err(()),
}
}
}
impl FfiAllocator {
/// # Safety
///
/// - layout must not have a size of 0
unsafe fn alloc(self, layout: Layout) -> Result<NonNull<c_void>, FfiError> {
let ptr = match self {
FfiAllocator::Rust => unsafe { alloc::alloc(layout).cast() },
FfiAllocator::C => unsafe { libc::malloc(layout.size()) },
};
NonNull::new(ptr).ok_or(FfiError::AllocationFailed)
}
/// # Safety
///
/// - ptr must point to an allocation currently allocated by this allocator
/// - layout must match the layout that was used to allocate the allocation pointed to by ptr
unsafe fn dealloc(self, layout: Layout, ptr: NonNull<c_void>) {
match self {
FfiAllocator::Rust => unsafe { alloc::dealloc(ptr.as_ptr().cast(), layout) },
FfiAllocator::C => unsafe { libc::free(ptr.as_ptr()) },
}
}
}
impl FfiStruct {
fn new(layout: Layout, allocator: FfiAllocator) -> Result<Self, FfiError> {
assert_ne!(layout.size(), 0);
Ok(FfiStruct {
ptr: unsafe { allocator.alloc(layout) }?,
layout,
allocator,
})
}
}
impl Drop for FfiStruct {
fn drop(&mut self) {
unsafe { self.allocator.dealloc(self.layout, self.ptr) };
}
}
impl ForeignFunctionTable {
pub fn merge(&mut self, other: ForeignFunctionTable) {
self.table.extend(other.table);
}
pub fn define_struct(&mut self, name: Atom, atom_fields: Vec<Atom>) -> Result<(), FfiError> {
let fields: Vec<_> = atom_fields.iter().map(FfiType::from_atom).collect();
let struct_type = libffi::middle::Type::structure(
fields
.iter()
.map(|field| field.to_type(&self.structs))
.collect::<Result<Vec<_>, _>>()?,
);
unsafe {
// ensure that size and alignment of struct_type are set properly
use libffi::low::{ffi_abi_FFI_DEFAULT_ABI, prep_cif};
prep_cif(
&mut Default::default(),
ffi_abi_FFI_DEFAULT_ABI,
1,
struct_type.as_raw_ptr(),
[struct_type.as_raw_ptr()].as_mut_ptr(),
)?;
};
self.structs.insert(
name,
StructImpl {
ffi_type: struct_type,
fields,
},
);
Ok(())
}
pub(crate) fn load_library(
&mut self,
library_name: &str,
functions: &Vec<FunctionDefinition>,
) -> Result<(), Box<dyn Error>> {
let mut ff_table: ForeignFunctionTable = Default::default();
let library = unsafe { Library::new(library_name) }?;
for function in functions {
let symbol_name: CString = CString::new(&*function.name.as_str())?;
let code_ptr: Symbol<*mut c_void> =
unsafe { library.get(symbol_name.as_bytes_with_nul()) }?;
let args: Vec<_> = function.args.iter().map(FfiType::from_atom).collect();
let return_type = FfiType::from_atom(&function.return_value);
let cif = libffi::middle::Cif::new(
args.iter()
.map(|arg| arg.to_type(&self.structs))
.collect::<Result<Vec<_>, _>>()?,
return_type.to_type(&self.structs)?,
);
ff_table.table.insert(
function.name,
FunctionImpl {
cif,
args,
code_ptr: CodePtr(unsafe { code_ptr.into_raw() }.as_raw_ptr()),
return_type,
},
);
}
std::mem::forget(library);
self.merge(ff_table);
Ok(())
}
pub fn exec(
&mut self,
fn_name: Atom,
mut args: Vec<Value>,
arena: &mut Arena,
) -> Result<Value, FfiError> {
let fn_impl = self
.table
.get(&fn_name)
.ok_or(FfiError::FunctionNotFound(fn_name))?;
let args = ArgValue::build_args(&mut args, &fn_impl.args, &self.structs)?;
let args = PointerArgs::new(&args);
fn_impl.call(&args, arena, &self.structs)
}
pub fn allocate(
&mut self,
allocator: FfiAllocator,
kind: Atom,
mut args: Value,
arena: &mut Arena,
) -> Result<Value, FfiError> {
fn allocate_primitive<T: Copy>(
allocator: FfiAllocator,
initial_value: T,
arena: &mut Arena,
) -> Result<Value, FfiError> {
const { assert!(std::mem::size_of::<T>() != 0) };
let ptr = unsafe { allocator.alloc(Layout::new::<T>()) }?;
unsafe { ptr.cast::<T>().write(initial_value) };
Ok(Value::Number(fixnum!(
Number,
ptr.as_ptr().expose_provenance(),
arena
)))
}
match FfiType::from_atom(&kind) {
FfiType::Void => Err(FfiError::VoidArgumentType),
FfiType::Bool => {
let val = args.as_int::<u8>()?;
let init = match val {
0 => false,
1 => true,
_ => return Err(FfiError::ValueOutOfRange(DomainErrorType::ZeroOrOne, args)),
};
allocate_primitive::<bool>(allocator, init, arena)
}
FfiType::U8 => allocate_primitive::<u8>(allocator, args.as_int()?, arena),
FfiType::I8 => allocate_primitive::<i8>(allocator, args.as_int()?, arena),
FfiType::U16 => allocate_primitive::<u16>(allocator, args.as_int()?, arena),
FfiType::I16 => allocate_primitive::<i16>(allocator, args.as_int()?, arena),
FfiType::U32 => allocate_primitive::<u32>(allocator, args.as_int()?, arena),
FfiType::I32 => allocate_primitive::<i32>(allocator, args.as_int()?, arena),
FfiType::U64 => allocate_primitive::<u64>(allocator, args.as_int()?, arena),
FfiType::I64 => allocate_primitive::<i64>(allocator, args.as_int()?, arena),
FfiType::F32 => allocate_primitive::<f32>(allocator, args.as_float()? as f32, arena),
FfiType::F64 => allocate_primitive::<f64>(allocator, args.as_float()?, arena),
FfiType::Ptr => allocate_primitive::<*mut c_void>(allocator, args.as_ptr()?, arena),
FfiType::CStr => Err(FfiError::CStrFieldType),
FfiType::Struct(_) => {
let Some(struct_impl) = self.structs.get(&kind) else {
return Err(FfiError::StructNotFound(kind));
};
let (_, args) = args.as_struct()?;
let ffi_struct = struct_impl.build(&self.structs, args)?;
let ptr = ManuallyDrop::new(ffi_struct).ptr;
Ok(Value::Number(fixnum!(
Number,
ptr.as_ptr().expose_provenance(),
arena
)))
}
}
}
pub fn read_ptr(
&mut self,
kind: Atom,
mut ptr: Value,
arena: &mut Arena,
) -> Result<Value, FfiError> {
unsafe fn read_int<T>(ptr: NonNull<c_void>, arena: &mut Arena) -> Value
where
T: Copy + TryInto<i64> + MightNotFitInFixnum,
Integer: From<T>,
{
let n = ptr.cast::<T>().read();
Value::Number(fixnum!(Number, n, arena))
}
let ptr = ptr.as_ptr()?;
let Some(ptr) = NonNull::new(ptr) else {
return Err(FfiError::NullPtr);
};
match FfiType::from_atom(&kind) {
FfiType::Void => Err(FfiError::VoidArgumentType),
FfiType::Bool | FfiType::U8 => Ok(unsafe { read_int::<u8>(ptr, arena) }),
FfiType::I8 => Ok(unsafe { read_int::<i8>(ptr, arena) }),
FfiType::U16 => Ok(unsafe { read_int::<u16>(ptr, arena) }),
FfiType::I16 => Ok(unsafe { read_int::<i16>(ptr, arena) }),
FfiType::U32 => Ok(unsafe { read_int::<u32>(ptr, arena) }),
FfiType::I32 => Ok(unsafe { read_int::<i32>(ptr, arena) }),
FfiType::U64 => Ok(unsafe { read_int::<u64>(ptr, arena) }),
FfiType::I64 => Ok(unsafe { read_int::<i64>(ptr, arena) }),
FfiType::F32 => Ok(Value::Number(Number::Float(
(unsafe { ptr.cast::<f32>().read() } as f64).into(),
))),
FfiType::F64 => Ok(Value::Number(Number::Float(
unsafe { ptr.cast::<f64>().read() }.into(),
))),
FfiType::Ptr => {
let addr = unsafe { ptr.cast::<*mut c_void>().read() }.expose_provenance();
Ok(Value::Number(fixnum!(Number, addr, arena)))
}
FfiType::CStr => Ok(Value::CString(
unsafe { CStr::from_ptr(ptr.as_ptr().cast()) }.to_owned(),
)),
FfiType::Struct(_) => {
let Some(struct_impl) = self.structs.get(&kind) else {
return Err(FfiError::StructNotFound(kind));
};
struct_impl.read(ptr.as_ptr(), kind, &self.structs, arena)
}
}
}
pub fn deallocate(
&mut self,
allocator: FfiAllocator,
kind: Atom,
mut ptr: Value,
) -> Result<(), FfiError> {
fn deallocate_primitive<T: Copy>(allocator: FfiAllocator, ptr: NonNull<c_void>) {
const { assert!(std::mem::size_of::<T>() != 0) };
unsafe { allocator.dealloc(Layout::new::<T>(), ptr) };
}
let ptr = ptr.as_ptr()?;
let Some(ptr) = NonNull::new(ptr) else {
return Err(FfiError::NullPtr);
};
match FfiType::from_atom(&kind) {
FfiType::Void => return Err(FfiError::VoidArgumentType),
FfiType::Bool => deallocate_primitive::<bool>(allocator, ptr),
FfiType::U8 => deallocate_primitive::<u8>(allocator, ptr),
FfiType::I8 => deallocate_primitive::<i8>(allocator, ptr),
FfiType::U16 => deallocate_primitive::<u16>(allocator, ptr),
FfiType::I16 => deallocate_primitive::<i16>(allocator, ptr),
FfiType::U32 => deallocate_primitive::<u32>(allocator, ptr),
FfiType::I32 => deallocate_primitive::<i32>(allocator, ptr),
FfiType::U64 => deallocate_primitive::<u64>(allocator, ptr),
FfiType::I64 => deallocate_primitive::<i64>(allocator, ptr),
FfiType::F32 => deallocate_primitive::<f32>(allocator, ptr),
FfiType::F64 => deallocate_primitive::<f64>(allocator, ptr),
FfiType::Ptr => deallocate_primitive::<*mut c_void>(allocator, ptr),
FfiType::CStr => return Err(FfiError::CStrFieldType),
FfiType::Struct(_) => {
let Some(struct_impl) = self.structs.get(&kind) else {
return Err(FfiError::StructNotFound(kind));
};
let layout = struct_impl.layout()?;
drop(FfiStruct {
ptr,
layout,
allocator,
})
}
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub enum Value {
Number(Number),
CString(CString),
Struct(Atom, Vec<Value>),
}
impl Value {
fn as_int<I>(&self) -> Result<I, FfiError>
where
Integer: TryInto<I>,
i64: TryInto<I>,
{
match self {
Value::Number(Number::Integer(ibig_ptr)) => {
let ibig: &Integer = ibig_ptr;
ibig.clone().try_into().map_err(|_| {
FfiError::ValueOutOfRange(DomainErrorType::FixedSizedInt, self.clone())
})
}
Value::Number(Number::Fixnum(fixnum)) => fixnum.get_num().try_into().map_err(|_| {
FfiError::ValueOutOfRange(DomainErrorType::FixedSizedInt, self.clone())
}),
_ => Err(FfiError::ValueOutOfRange(
DomainErrorType::FixedSizedInt,
self.clone(),
)),
}
}
fn as_float(&self) -> Result<f64, FfiError> {
match self {
&Value::Number(Number::Float(OrderedFloat(f))) => Ok(f),
_ => Err(FfiError::ValueOutOfRange(
DomainErrorType::F64,
self.clone(),
)),
}
}
fn as_ptr(&mut self) -> Result<*mut c_void, FfiError> {
match self {
Value::CString(ref mut cstr) => Ok(cstr.as_ptr().cast_mut().cast()),
Value::Number(Number::Fixnum(fixnum)) => Ok(std::ptr::with_exposed_provenance_mut(
fixnum.get_num() as usize,
)),
_ => Err(FfiError::ValueOutOfRange(
DomainErrorType::PtrLike,
self.clone(),
)),
}
}
fn as_struct(&mut self) -> Result<(Atom, &mut [Self]), FfiError> {
match self {
Value::Struct(name, values) => Ok((*name, values)),
_ => Err(FfiError::ValueOutOfRange(
DomainErrorType::FfiStruct,
self.clone(),
)),
}
}
}
#[derive(Debug)]
pub enum FfiError {
ValueCast(Atom, Atom),
ValueOutOfRange(DomainErrorType, Value),
VoidArgumentType,
FunctionNotFound(Atom),
StructNotFound(Atom),
ArgCountMismatch,
AllocationFailed,
// LayoutError should never occour
LayoutError,
UnsupportedTypedef,
UnsupportedAbi,
CStrFieldType,
NullPtr,
}
impl std::fmt::Display for FfiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}
impl Error for FfiError {}
impl From<libffi::low::Error> for FfiError {
fn from(value: libffi::low::Error) -> Self {
match value {
libffi::low::Error::Typedef => FfiError::UnsupportedTypedef,
libffi::low::Error::Abi => FfiError::UnsupportedAbi,
}
}
}

View File

@@ -1,320 +0,0 @@
use crate::parser::ast::*;
use crate::forms::*;
use crate::instructions::*;
use crate::iterators::*;
use indexmap::{IndexMap, IndexSet};
use std::cell::Cell;
use std::collections::BTreeSet;
use std::mem::swap;
use std::rc::Rc;
use std::vec::Vec;
// labeled with chunk numbers.
#[derive(Debug)]
pub(crate) enum VarStatus {
Perm(usize),
Temp(usize, TempVarData), // Perm(chunk_num) | Temp(chunk_num, _)
}
pub(crate) type OccurrenceSet = BTreeSet<(GenContext, usize)>;
// Perm: 0 initially, a stack register once processed.
// Temp: labeled with chunk_num and temp offset (unassigned if 0).
#[derive(Debug)]
pub(crate) enum VarData {
Perm(usize),
Temp(usize, usize, TempVarData),
}
impl VarData {
pub(crate) fn as_reg_type(&self) -> RegType {
match self {
&VarData::Temp(_, r, _) => RegType::Temp(r),
&VarData::Perm(r) => RegType::Perm(r),
}
}
}
#[derive(Debug)]
pub(crate) struct TempVarData {
pub(crate) last_term_arity: usize,
pub(crate) use_set: OccurrenceSet,
pub(crate) no_use_set: BTreeSet<usize>,
pub(crate) conflict_set: BTreeSet<usize>,
}
impl TempVarData {
pub(crate) fn new(last_term_arity: usize) -> Self {
TempVarData {
last_term_arity: last_term_arity,
use_set: BTreeSet::new(),
no_use_set: BTreeSet::new(),
conflict_set: BTreeSet::new(),
}
}
pub(crate) fn uses_reg(&self, reg: usize) -> bool {
for &(_, nreg) in self.use_set.iter() {
if reg == nreg {
return true;
}
}
return false;
}
pub(crate) fn populate_conflict_set(&mut self) {
if self.last_term_arity > 0 {
let arity = self.last_term_arity;
let mut conflict_set: BTreeSet<usize> = (1..arity).collect();
for &(_, reg) in self.use_set.iter() {
conflict_set.remove(&reg);
}
self.conflict_set = conflict_set;
}
}
}
type VariableFixture<'a> = (VarStatus, Vec<&'a Cell<VarReg>>);
#[derive(Debug)]
pub(crate) struct VariableFixtures<'a> {
perm_vars: IndexMap<Rc<String>, VariableFixture<'a>>,
last_chunk_temp_vars: IndexSet<Rc<String>>,
}
impl<'a> VariableFixtures<'a> {
pub(crate) fn new() -> Self {
VariableFixtures {
perm_vars: IndexMap::new(),
last_chunk_temp_vars: IndexSet::new(),
}
}
pub(crate) fn insert(&mut self, var: Rc<String>, vs: VariableFixture<'a>) {
self.perm_vars.insert(var, vs);
}
pub(crate) fn insert_last_chunk_temp_var(&mut self, var: Rc<String>) {
self.last_chunk_temp_vars.insert(var);
}
// computes no_use and conflict sets for all temp vars.
pub(crate) fn populate_restricting_sets(&mut self) {
// three stages:
// 1. move the use sets of each variable to a local IndexMap, use_set
// (iterate mutably, swap mutable refs).
// 2. drain use_set. For each use set of U, add into the
// no-use sets of appropriate variables T =/= U.
// 3. Move the use sets back to their original locations in the fixture.
// Compute the conflict set of u.
// 1.
let mut use_sets: IndexMap<Rc<String>, OccurrenceSet> = IndexMap::new();
for (var, &mut (ref mut var_status, _)) in self.iter_mut() {
if let &mut VarStatus::Temp(_, ref mut var_data) = var_status {
let mut use_set = OccurrenceSet::new();
swap(&mut var_data.use_set, &mut use_set);
use_sets.insert((*var).clone(), use_set);
}
}
for (u, use_set) in use_sets.drain(..) {
// 2.
for &(term_loc, reg) in use_set.iter() {
if let GenContext::Last(cn_u) = term_loc {
for (ref t, &mut (ref mut var_status, _)) in self.iter_mut() {
if let &mut VarStatus::Temp(cn_t, ref mut t_data) = var_status {
if cn_u == cn_t && *u != ***t {
if !t_data.uses_reg(reg) {
t_data.no_use_set.insert(reg);
}
}
}
}
}
}
// 3.
match self.get_mut(u).unwrap() {
&mut (VarStatus::Temp(_, ref mut u_data), _) => {
u_data.use_set = use_set;
u_data.populate_conflict_set();
}
_ => {}
};
}
}
fn get_mut(&mut self, u: Rc<String>) -> Option<&mut VariableFixture<'a>> {
self.perm_vars.get_mut(&u)
}
fn iter_mut(&mut self) -> indexmap::map::IterMut<Rc<String>, VariableFixture<'a>> {
self.perm_vars.iter_mut()
}
fn record_temp_info(&mut self, tvd: &mut TempVarData, arg_c: usize, term_loc: GenContext) {
match term_loc {
GenContext::Head | GenContext::Last(_) => {
tvd.use_set.insert((term_loc, arg_c));
}
_ => {}
};
}
pub(crate) fn vars_above_threshold(&self, index: usize) -> usize {
let mut var_count = 0;
for &(ref var_status, _) in self.values() {
if let &VarStatus::Perm(i) = var_status {
if i > index {
var_count += 1;
}
}
}
var_count
}
pub(crate) fn mark_vars_in_chunk<I>(&mut self, iter: I, lt_arity: usize, term_loc: GenContext)
where
I: Iterator<Item = TermRef<'a>>,
{
let chunk_num = term_loc.chunk_num();
let mut arg_c = 1;
for term_ref in iter {
if let &TermRef::Var(lvl, cell, ref var) = &term_ref {
let mut status = self.perm_vars.swap_remove(var).unwrap_or((
VarStatus::Temp(chunk_num, TempVarData::new(lt_arity)),
Vec::new(),
));
status.1.push(cell);
match status.0 {
VarStatus::Temp(cn, ref mut tvd) if cn == chunk_num => {
if let Level::Shallow = lvl {
self.record_temp_info(tvd, arg_c, term_loc);
}
}
_ => status.0 = VarStatus::Perm(chunk_num),
};
self.perm_vars.insert(var.clone(), status);
}
if let Level::Shallow = term_ref.level() {
arg_c += 1;
}
}
}
pub(crate) fn into_iter(self) -> indexmap::map::IntoIter<Rc<String>, VariableFixture<'a>> {
self.perm_vars.into_iter()
}
fn values(&self) -> indexmap::map::Values<Rc<String>, VariableFixture<'a>> {
self.perm_vars.values()
}
pub(crate) fn size(&self) -> usize {
self.perm_vars.len()
}
pub(crate) fn set_perm_vals(&self, has_deep_cuts: bool) {
let mut values_vec: Vec<_> = self
.values()
.filter_map(|ref v| match &v.0 {
&VarStatus::Perm(i) => Some((i, &v.1)),
_ => None,
})
.collect();
values_vec.sort_by_key(|ref v| v.0);
let offset = has_deep_cuts as usize;
for (i, (_, cells)) in values_vec.into_iter().rev().enumerate() {
for cell in cells {
cell.set(VarReg::Norm(RegType::Perm(i + 1 + offset)));
}
}
}
}
#[derive(Debug)]
pub(crate) struct UnsafeVarMarker {
pub(crate) unsafe_vars: IndexMap<RegType, usize>,
pub(crate) safe_vars: IndexSet<RegType>,
}
impl UnsafeVarMarker {
pub(crate) fn new() -> Self {
UnsafeVarMarker {
unsafe_vars: IndexMap::new(),
safe_vars: IndexSet::new(),
}
}
pub(crate) fn from_safe_vars(safe_vars: IndexSet<RegType>) -> Self {
UnsafeVarMarker {
unsafe_vars: IndexMap::new(),
safe_vars,
}
}
pub(crate) fn mark_safe_vars(&mut self, query_instr: &Instruction) -> bool {
match query_instr {
&Instruction::PutVariable(r @ RegType::Temp(_), _) |
&Instruction::SetVariable(r) => {
self.safe_vars.insert(r);
true
}
_ => false,
}
}
pub(crate) fn mark_phase(&mut self, query_instr: &Instruction, phase: usize) {
match query_instr {
&Instruction::PutValue(r @ RegType::Perm(_), _) |
&Instruction::SetValue(r) => {
let p = self.unsafe_vars.entry(r).or_insert(0);
*p = phase;
}
_ => {}
}
}
pub(crate) fn mark_unsafe_vars(&mut self, query_instr: &mut Instruction, phase: usize) {
match query_instr {
&mut Instruction::PutValue(RegType::Perm(i), arg) => {
if let Some(p) = self.unsafe_vars.swap_remove(&RegType::Perm(i)) {
if p == phase {
*query_instr = Instruction::PutUnsafeValue(i, arg);
self.safe_vars.insert(RegType::Perm(i));
} else {
self.unsafe_vars.insert(RegType::Perm(i), p);
}
}
}
&mut Instruction::SetValue(r) => {
if !self.safe_vars.contains(&r) {
*query_instr = Instruction::SetLocalValue(r);
self.safe_vars.insert(r);
self.unsafe_vars.remove(&r);
}
}
_ => {}
}
}
}

View File

@@ -1,15 +1,17 @@
use crate::arena::*;
use crate::atom_table::*;
use crate::functor_macro::*;
use crate::instructions::*;
use crate::machine::heap::*;
use crate::machine::disjuncts::VarData;
use crate::machine::loader::PredicateQueue;
use crate::machine::machine_errors::*;
use crate::machine::machine_indices::*;
use crate::parser::ast::*;
use crate::parser::dashu::{Integer, Rational};
use crate::parser::parser::CompositeOpDesc;
use crate::parser::rug::{Integer, Rational};
use crate::types::*;
use dashu::base::Signed;
use fxhash::FxBuildHasher;
use indexmap::{IndexMap, IndexSet};
@@ -19,28 +21,11 @@ use std::cell::Cell;
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::fmt;
use std::ops::AddAssign;
use std::ops::{AddAssign, Deref, DerefMut};
use std::path::PathBuf;
use std::rc::Rc;
use crate::{is_infix, is_postfix};
pub type PredicateKey = (Atom, usize); // name, arity.
pub type Predicate = Vec<PredicateClause>;
// 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, Clone)]
pub enum TopLevel {
Fact(Term), // Term, line_num, col_num
Predicate(Predicate),
Query(Vec<QueryTerm>),
Rule(Rule), // Rule, line_num, col_num
}
#[derive(Debug, Clone, Copy)]
pub enum AppendOrPrepend {
Append,
@@ -57,7 +42,13 @@ impl AppendOrPrepend {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy)]
pub enum VarComparison {
Indistinct,
Distinct,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Level {
Deep,
Root,
@@ -79,38 +70,166 @@ pub enum CallPolicy {
Counted,
}
#[derive(Debug, Clone)]
#[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,
Mid,
Last,
}
#[derive(Debug)]
pub enum RootIterationPolicy {
Iterated,
NotIterated,
}
impl RootIterationPolicy {
#[inline(always)]
pub fn iterable(&self) -> bool {
matches!(self, RootIterationPolicy::Iterated)
}
}
impl ChunkType {
#[inline(always)]
pub fn to_gen_context(self, chunk_num: usize) -> GenContext {
match self {
ChunkType::Head => GenContext::Head,
ChunkType::Mid => GenContext::Mid(chunk_num),
ChunkType::Last => GenContext::Last(chunk_num),
}
}
#[inline(always)]
pub fn is_last(self) -> bool {
self == ChunkType::Last
}
}
#[derive(Debug)]
pub enum ChunkedTerms {
Branch(Vec<VecDeque<ChunkedTerms>>),
Chunk { terms: VecDeque<QueryTerm> },
}
#[derive(Debug)]
pub struct ChunkedTermVec {
pub chunk_vec: VecDeque<ChunkedTerms>,
}
impl Deref for ChunkedTermVec {
type Target = VecDeque<ChunkedTerms>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.chunk_vec
}
}
impl DerefMut for ChunkedTermVec {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.chunk_vec
}
}
impl ChunkedTermVec {
#[allow(clippy::new_without_default)]
#[inline]
pub fn new() -> Self {
Self {
chunk_vec: VecDeque::new(),
}
}
pub fn reserve_branch(&mut self, capacity: usize) {
self.chunk_vec
.push_back(ChunkedTerms::Branch(Vec::with_capacity(capacity)));
}
#[inline]
pub fn add_chunk(&mut self) {
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(_)) => {
let chunk = ChunkedTerms::Chunk {
terms: VecDeque::from(vec![term]),
};
self.chunk_vec.push_back(chunk);
}
Some(ChunkedTerms::Chunk { terms, .. }) => {
terms.push_back(term);
}
None => {
let chunk = ChunkedTerms::Chunk {
terms: VecDeque::from(vec![term]),
};
self.chunk_vec.push_back(chunk);
}
}
}
}
#[derive(Debug)]
pub enum QueryTerm {
// register, clause type, subterms, clause call policy.
Clause(Cell<RegType>, ClauseType, Vec<Term>, CallPolicy),
BlockedCut, // a cut which is 'blocked by letters', like the P term in P -> Q.
UnblockedCut(Cell<VarReg>),
GetLevelAndUnify(Cell<VarReg>, Rc<String>),
Jump(JumpStub),
Fail,
LocalCut { var_num: usize, cut_prev: bool }, // var_num
GlobalCut(usize), // var_num
GetCutPoint { var_num: usize, prev_b: bool },
GetLevel(usize), // var_num
}
impl QueryTerm {
pub(crate) fn set_call_policy(&mut self, cp: CallPolicy) {
match self {
&mut QueryTerm::Clause(_, _, _, ref mut clause_cp) => *clause_cp = cp,
_ => {}
}
}
pub(crate) fn arity(&self) -> usize {
match self {
&QueryTerm::Clause(_, _, ref subterms, ..) => subterms.len(),
&QueryTerm::BlockedCut | &QueryTerm::UnblockedCut(..) => 0,
&QueryTerm::Jump(ref vars) => vars.len(),
&QueryTerm::GetLevelAndUnify(..) => 1,
QueryTerm::Clause(_, _, subterms, ..) => subterms.len(),
&QueryTerm::GetLevel(_) | &QueryTerm::GetCutPoint { .. } => 1,
_ => 0,
}
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Fact {
pub(crate) head: Term,
}
#[derive(Debug)]
pub struct Rule {
pub(crate) head: (Atom, Vec<Term>, QueryTerm),
pub(crate) clauses: Vec<QueryTerm>,
pub(crate) head: (Atom, Vec<Term>),
pub(crate) clauses: ChunkedTermVec,
}
#[derive(Clone, Debug, Hash)]
@@ -156,11 +275,10 @@ impl ClauseInfo for Term {
fn name(&self) -> Option<Atom> {
match self {
Term::Clause(_, name, terms) => {
match name {
atom!(":-") => {
match terms.len() {
1 => None, // a declaration.
1 => None, // a declaration.
2 => terms[0].name(),
_ => Some(*name),
}
@@ -175,7 +293,7 @@ impl ClauseInfo for Term {
fn arity(&self) -> usize {
match self {
Term::Clause(_, name, terms) => match name.as_str() {
Term::Clause(_, name, terms) => match &*name.as_str() {
":-" => match terms.len() {
1 => 0,
2 => terms[0].arity(),
@@ -201,30 +319,30 @@ impl ClauseInfo for Rule {
impl ClauseInfo for PredicateClause {
fn name(&self) -> Option<Atom> {
match self {
&PredicateClause::Fact(ref term, ..) => term.name(),
&PredicateClause::Rule(ref rule, ..) => rule.name(),
PredicateClause::Fact(ref term, ..) => term.head.name(),
PredicateClause::Rule(ref rule, ..) => rule.name(),
}
}
fn arity(&self) -> usize {
match self {
&PredicateClause::Fact(ref term, ..) => term.arity(),
&PredicateClause::Rule(ref rule, ..) => rule.arity(),
PredicateClause::Fact(ref term, ..) => term.head.arity(),
PredicateClause::Rule(ref rule, ..) => rule.arity(),
}
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum PredicateClause {
Fact(Term),
Rule(Rule),
Fact(Fact, VarData),
Rule(Rule, VarData),
}
impl PredicateClause {
pub(crate) fn args(&self) -> Option<&[Term]> {
match self {
PredicateClause::Fact(term, ..) => match term {
Term::Clause(_, _, args) => Some(&args),
PredicateClause::Fact(term, ..) => match &term.head {
Term::Clause(_, _, args) => Some(args),
_ => None,
},
PredicateClause::Rule(rule, ..) => {
@@ -253,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)
@@ -290,17 +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 {
@@ -317,15 +424,12 @@ 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());
match op_dir.get_mut(&key) {
Some(cell) => {
let (old_prec, old_spec) = cell.get();
cell.set(self.op_desc.get_prec(), self.op_desc.get_spec());
return Some(OpDesc::build_with(old_prec, old_spec));
}
None => {}
if let Some(cell) = op_dir.get_mut(&key) {
let (old_prec, old_spec) = cell.get();
cell.set(self.op_desc.get_prec(), self.op_desc.get_spec());
return Some(OpDesc::build_with(old_prec, old_spec));
}
op_dir.insert(key, self.op_desc)
@@ -336,9 +440,9 @@ impl OpDecl {
existing_desc: Option<CompositeOpDesc>,
op_dir: &mut OpDir,
) -> Result<(), SessionError> {
let (spec, name) = (self.op_desc.get_spec(), self.name.clone());
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));
@@ -346,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));
@@ -367,35 +471,28 @@ pub enum AtomOrString {
impl AtomOrString {
#[inline]
pub fn as_atom(&self, atom_tbl: &mut AtomTable) -> Atom {
pub fn as_atom(&self, atom_tbl: &AtomTable) -> Atom {
match self {
&AtomOrString::Atom(atom) => {
atom
}
AtomOrString::String(string) => {
atom_tbl.build_with(&string)
}
&AtomOrString::Atom(atom) => atom,
AtomOrString::String(string) => AtomTable::build_with(atom_tbl, string),
}
}
#[inline]
pub fn as_str(&self) -> &str {
pub fn as_str(&self) -> AtomString<'_> {
match self {
AtomOrString::Atom(atom) if atom == &atom!("[]") => "",
AtomOrString::Atom(atom) if atom == &atom!("[]") => AtomString::Static(""),
AtomOrString::Atom(atom) => atom.as_str(),
AtomOrString::String(string) => string.as_str(),
AtomOrString::String(string) => AtomString::Static(string.as_str()),
}
}
}
#[inline]
pub fn to_string(self) -> String {
match self {
AtomOrString::Atom(atom) => {
atom.as_str().to_owned()
}
AtomOrString::String(string) => {
string
}
impl From<AtomOrString> for String {
fn from(val: AtomOrString) -> Self {
match val {
AtomOrString::Atom(atom) => atom.as_str().to_owned(),
AtomOrString::String(string) => string,
}
}
}
@@ -437,7 +534,7 @@ pub(crate) fn fetch_op_spec(name: Atom, arity: usize, op_dir: &OpDir) -> Option<
}
}),
1 => {
if let Some(op_desc) = op_dir.get(&(name.clone(), Fixity::Pre)) {
if let Some(op_desc) = op_dir.get(&(name, Fixity::Pre)) {
if op_desc.get_prec() > 0 {
return Some(*op_desc);
}
@@ -451,9 +548,7 @@ pub(crate) fn fetch_op_spec(name: Atom, arity: usize, op_dir: &OpDir) -> Option<
}
})
}
0 => {
fetch_atom_op_spec(name, None, op_dir)
}
0 => fetch_atom_op_spec(name, None, op_dir),
_ => None,
}
}
@@ -485,10 +580,7 @@ pub struct Module {
// Module's and related types are defined in forms.
impl Module {
pub(crate) fn new(
module_decl: ModuleDecl,
listing_src: ListingSource,
) -> Self {
pub(crate) fn new(module_decl: ModuleDecl, listing_src: ListingSource) -> Self {
Module {
module_decl,
code_dir: CodeDir::with_hasher(FxBuildHasher::default()),
@@ -510,7 +602,7 @@ impl Module {
meta_predicates: MetaPredicateDir::with_hasher(FxBuildHasher::default()),
extensible_predicates: ExtensiblePredicates::with_hasher(FxBuildHasher::default()),
local_extensible_predicates: LocalExtensiblePredicates::with_hasher(
FxBuildHasher::default()
FxBuildHasher::default(),
),
listing_src: ListingSource::DynamicallyGenerated,
}
@@ -534,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()),
}
}
@@ -605,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 {
@@ -627,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]
@@ -634,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),
}
}
@@ -643,11 +751,15 @@ impl ArenaFrom<Number> for HeapCellValue {
impl Number {
pub(crate) fn sign(&self) -> Number {
match self {
&Number::Float(f) if f == 0.0 => Number::Float(OrderedFloat(0f64)),
&Number::Float(f) => Number::Float(OrderedFloat(f.signum())),
Number::Float(f) if *f == 0.0 => Number::Float(OrderedFloat(0f64)),
Number::Float(f) => Number::Float(OrderedFloat(f.signum())),
_ => {
if self.is_positive() {
Number::Fixnum(Fixnum::build_with(1))
if self.is_zero() {
Number::Fixnum(Fixnum::build_with(0))
} else {
Number::Fixnum(Fixnum::build_with(1))
}
} else if self.is_negative() {
Number::Fixnum(Fixnum::build_with(-1))
} else {
@@ -660,46 +772,43 @@ impl Number {
#[inline]
pub(crate) fn is_positive(&self) -> bool {
match self {
&Number::Fixnum(n) => n.get_num() > 0,
&Number::Integer(ref n) => &**n > &0,
&Number::Float(f) => f.is_sign_positive(),
&Number::Rational(ref r) => &**r > &0,
Number::Fixnum(n) => n.get_num() > 0,
Number::Integer(ref n) => n.is_positive(),
Number::Float(f) => f.is_sign_positive(),
Number::Rational(ref r) => r.is_positive(),
}
}
#[inline]
pub(crate) fn is_negative(&self) -> bool {
match self {
&Number::Fixnum(n) => n.get_num() < 0,
&Number::Integer(ref n) => &**n < &0,
&Number::Float(OrderedFloat(f)) => f.is_sign_negative() && OrderedFloat(f) != -0f64,
&Number::Rational(ref r) => &**r < &0,
Number::Fixnum(n) => n.get_num() < 0,
Number::Integer(ref n) => n.is_negative(),
&Number::Float(OrderedFloat(f)) => f.is_sign_negative() && f != -0f64,
Number::Rational(ref r) => r.is_negative(),
}
}
#[inline]
pub(crate) fn is_zero(&self) -> bool {
match self {
&Number::Fixnum(n) => n.get_num() == 0,
&Number::Integer(ref n) => &**n == &0,
&Number::Float(f) => f == OrderedFloat(0f64) || f == OrderedFloat(-0f64),
&Number::Rational(ref r) => &**r == &0,
Number::Fixnum(n) => n.get_num() == 0,
Number::Integer(ref n) => n.is_zero(),
&Number::Float(OrderedFloat(f)) => f == 0.0 || f == -0.0,
Number::Rational(ref r) => r.is_zero(),
}
}
#[inline]
pub(crate) fn is_integer(&self) -> bool {
match self {
Number::Fixnum(_) | Number::Integer(_) => true,
_ => false,
}
matches!(self, Number::Fixnum(_) | Number::Integer(_))
}
}
#[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
}
@@ -782,7 +891,7 @@ impl ClauseIndexInfo {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct PredicateInfo {
pub(crate) is_extensible: bool,
pub(crate) is_discontiguous: bool,
@@ -791,19 +900,6 @@ pub(crate) struct PredicateInfo {
pub(crate) has_clauses: bool,
}
impl Default for PredicateInfo {
#[inline]
fn default() -> Self {
PredicateInfo {
is_extensible: false,
is_discontiguous: false,
is_dynamic: false,
is_multifile: false,
has_clauses: false,
}
}
}
impl PredicateInfo {
#[inline]
pub(crate) fn compile_incrementally(&self) -> bool {
@@ -812,8 +908,11 @@ impl PredicateInfo {
}
#[inline]
pub(crate) fn must_retract_local_clauses(&self) -> bool {
self.is_extensible && self.has_clauses && !self.is_discontiguous
pub(crate) fn must_retract_local_clauses(&self, is_cross_module_clause: bool) -> bool {
self.is_extensible
&& self.has_clauses
&& !self.is_discontiguous
&& !(self.is_multifile && is_cross_module_clause)
}
}
@@ -859,7 +958,7 @@ impl LocalPredicateSkeleton {
#[inline]
pub(crate) fn add_retracted_dynamic_clause_info(&mut self, clause_info: ClauseIndexInfo) {
debug_assert_eq!(self.is_dynamic, true);
debug_assert!(self.is_dynamic);
if self.retracted_dynamic_clauses.is_none() {
self.retracted_dynamic_clauses = Some(vec![]);
@@ -902,19 +1001,17 @@ impl PredicateSkeleton {
&mut self,
clause_clause_loc: usize,
) -> Option<usize> {
let search_result = self.core.clause_clause_locs
.make_contiguous()[0..self.core.clause_assert_margin]
.binary_search_by(|loc| clause_clause_loc.cmp(&loc));
let search_result = self.core.clause_clause_locs.make_contiguous()
[0..self.core.clause_assert_margin]
.binary_search_by(|loc| clause_clause_loc.cmp(loc));
match search_result {
Ok(loc) => Some(loc),
Err(_) => {
self.core.clause_clause_locs
.make_contiguous()[self.core.clause_assert_margin..]
.binary_search_by(|loc| loc.cmp(&clause_clause_loc))
.map(|loc| loc + self.core.clause_assert_margin)
.ok()
}
Err(_) => self.core.clause_clause_locs.make_contiguous()
[self.core.clause_assert_margin..]
.binary_search_by(|loc| loc.cmp(&clause_clause_loc))
.map(|loc| loc + self.core.clause_assert_margin)
.ok(),
}
}
}

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,25 +1,23 @@
use std::sync::Arc;
use std::convert::Infallible;
use bytes::{buf::Reader, Bytes};
use std::sync::{Arc, Condvar, Mutex};
use hyper::{Response, Request, Body};
use tokio::sync::Mutex;
use tokio::sync::mpsc::{channel, Receiver, Sender};
use warp::http;
pub struct HttpListener {
pub incoming: Receiver<HttpRequest>
pub incoming: std::sync::mpsc::Receiver<HttpRequest>,
}
#[derive(Debug)]
pub struct HttpRequest {
pub request: Request<Body>,
pub request_data: HttpRequestData,
pub response: HttpResponse,
}
pub type HttpResponse = Sender<Response<Body>>;
pub type HttpResponse = Arc<(Mutex<bool>, Mutex<Option<warp::reply::Response>>, Condvar)>;
pub async fn serve_req(req: Request<Body>, tx: Arc<Mutex<Sender<HttpRequest>>>) -> Result<Response<Body>, Infallible> {
let (response_tx, mut rx) = channel(1);
let http_request = HttpRequest { request: req, response: response_tx };
tx.lock().await.send(http_request).await.unwrap();
Ok(rx.recv().await.unwrap())
pub struct HttpRequestData {
pub method: http::Method,
pub headers: http::HeaderMap,
pub path: String,
pub query: String,
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,23 +145,26 @@ 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() {
vec![
IndexedChoiceInstruction::Try(external),
IndexedChoiceInstruction::Trust(index)
].into()
IndexedChoiceInstruction::Trust(index),
]
.into()
} else {
vec![
IndexedChoiceInstruction::Try(index),
IndexedChoiceInstruction::Trust(external)
].into()
IndexedChoiceInstruction::Trust(external),
]
.into()
};
let indexing_code_len = self.indexing_code.len();
self.indexing_code.push(IndexingLine::IndexedChoice(third_level_index));
self.indexing_code
.push(IndexingLine::IndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref mut constants)) => {
@@ -178,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() {
@@ -188,7 +192,8 @@ impl<'a> IndexingCodeMergingPtr<'a> {
};
let indexing_code_len = self.indexing_code.len();
self.indexing_code.push(IndexingLine::DynamicIndexedChoice(third_level_index));
self.indexing_code
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref mut constants)) => {
@@ -231,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 {
@@ -275,10 +280,8 @@ impl<'a> IndexingCodeMergingPtr<'a> {
);
}
None | Some(IndexingCodePtr::Fail) => {
constants.insert(
overlapping_constant,
IndexingCodePtr::External(index),
);
constants
.insert(overlapping_constant, IndexingCodePtr::External(index));
}
Some(IndexingCodePtr::DynamicExternal(o)) => {
self.add_dynamic_indexed_choice_for_constant(
@@ -314,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();
@@ -345,16 +348,10 @@ impl<'a> IndexingCodeMergingPtr<'a> {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants)) => {
match constants.get(&constant).cloned() {
None | Some(IndexingCodePtr::Fail) if self.is_dynamic => {
constants.insert(
constant,
IndexingCodePtr::DynamicExternal(index),
);
constants.insert(constant, IndexingCodePtr::DynamicExternal(index));
}
None | Some(IndexingCodePtr::Fail) => {
constants.insert(
constant,
IndexingCodePtr::External(index),
);
constants.insert(constant, IndexingCodePtr::External(index));
}
Some(IndexingCodePtr::DynamicExternal(o)) => {
self.add_dynamic_indexed_choice_for_constant(o, constant, index);
@@ -432,17 +429,20 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let third_level_index = if self.append_or_prepend.is_append() {
vec![
IndexedChoiceInstruction::Try(external),
IndexedChoiceInstruction::Trust(index)
].into()
IndexedChoiceInstruction::Trust(index),
]
.into()
} else {
vec![
IndexedChoiceInstruction::Try(index),
IndexedChoiceInstruction::Trust(external)
].into()
IndexedChoiceInstruction::Trust(external),
]
.into()
};
let indexing_code_len = self.indexing_code.len();
self.indexing_code.push(IndexingLine::IndexedChoice(third_level_index));
self.indexing_code
.push(IndexingLine::IndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
@@ -470,7 +470,8 @@ impl<'a> IndexingCodeMergingPtr<'a> {
};
let indexing_code_len = self.indexing_code.len();
self.indexing_code.push(IndexingLine::DynamicIndexedChoice(third_level_index));
self.indexing_code
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
@@ -584,13 +585,15 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let third_level_index = if self.append_or_prepend.is_append() {
vec![
IndexedChoiceInstruction::Try(o),
IndexedChoiceInstruction::Trust(index)
].into()
IndexedChoiceInstruction::Trust(index),
]
.into()
} else {
vec![
IndexedChoiceInstruction::Try(index),
IndexedChoiceInstruction::Trust(o)
].into()
IndexedChoiceInstruction::Trust(o),
]
.into()
};
self.indexing_code
@@ -613,7 +616,7 @@ pub(crate) fn merge_clause_index(
target_indexing_code: &mut Vec<IndexingLine>,
skeleton: &mut [ClauseIndexInfo], // the clause to be merged is the last element in the skeleton.
retracted_clauses: &Option<Vec<ClauseIndexInfo>>,
new_clause_loc: usize, // the absolute location of the new clause in the code vector.
new_clause_loc: usize, // the absolute location of the new clause in the code vector.
append_or_prepend: AppendOrPrepend,
) {
let opt_arg_index_key = match append_or_prepend {
@@ -631,14 +634,13 @@ 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,
HeapCellValue::from(*constant),
HeapCellValue::from(*overlapping_constant),
offset,
);
}
@@ -666,8 +668,8 @@ pub(crate) fn merge_clause_index(
pub(crate) fn remove_constant_indices(
constant: Literal,
overlapping_constants: &[Literal],
indexing_code: &mut Vec<IndexingLine>,
overlapping_constants: Option<Literal>,
indexing_code: &mut [IndexingLine],
offset: usize,
) {
let mut index = 0;
@@ -695,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(
@@ -703,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)) => {
@@ -742,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!()
@@ -775,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!()
@@ -813,7 +813,7 @@ pub(crate) fn remove_constant_indices(
pub(crate) fn remove_structure_index(
name: Atom,
arity: usize,
indexing_code: &mut Vec<IndexingLine>,
indexing_code: &mut [IndexingLine],
offset: usize,
) {
let mut index = 0;
@@ -845,10 +845,10 @@ pub(crate) fn remove_structure_index(
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
structures_index = index;
match structures.get(&(name.clone(), arity)).cloned() {
match structures.get(&(name, arity)).cloned() {
Some(IndexingCodePtr::DynamicExternal(_))
| Some(IndexingCodePtr::External(_)) => {
structures.remove(&(name.clone(), arity));
structures.swap_remove(&(name, arity));
break;
}
Some(IndexingCodePtr::Internal(o)) => {
@@ -879,7 +879,7 @@ pub(crate) fn remove_structure_index(
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
ref mut structures,
)) => {
structures.insert((name.clone(), arity), ext);
structures.insert((name, arity), ext);
}
_ => {
unreachable!()
@@ -910,7 +910,7 @@ pub(crate) fn remove_structure_index(
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
ref mut structures,
)) => {
structures.insert((name.clone(), arity), ext);
structures.insert((name, arity), ext);
}
_ => {
unreachable!()
@@ -950,7 +950,7 @@ pub(crate) fn remove_structure_index(
}
}
pub(crate) fn remove_list_index(indexing_code: &mut Vec<IndexingLine>, offset: usize) {
pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usize) {
let mut index = 0;
match &mut indexing_code[index] {
@@ -1030,12 +1030,12 @@ pub(crate) fn remove_list_index(indexing_code: &mut Vec<IndexingLine>, offset: u
pub(crate) fn remove_index(
opt_arg_index_key: &OptArgIndexKey,
indexing_code: &mut Vec<IndexingLine>,
indexing_code: &mut [IndexingLine],
clause_loc: usize,
) {
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);
@@ -1051,93 +1051,72 @@ pub(crate) fn remove_index(
#[inline]
fn cap_choice_seq(prelude: &mut [IndexedChoiceInstruction]) {
prelude.first_mut().map(|instr| {
if let Some(instr) = prelude.first_mut() {
*instr = IndexedChoiceInstruction::Try(instr.offset());
});
}
cap_choice_seq_with_trust(prelude);
}
#[inline]
fn cap_choice_seq_with_trust(prelude: &mut [IndexedChoiceInstruction]) {
prelude.last_mut().map(|instr| {
if let IndexedChoiceInstruction::Retry(i) = instr {
*instr = IndexedChoiceInstruction::Trust(*i);
if let Some(instr) = prelude.last_mut() {
match instr {
IndexedChoiceInstruction::Retry(i) => {
*instr = IndexedChoiceInstruction::Trust(*i);
}
IndexedChoiceInstruction::DefaultRetry(i) => {
*instr = IndexedChoiceInstruction::DefaultTrust(*i);
}
_ => {}
}
});
}
}
#[inline]
fn uncap_choice_seq_with_trust(prelude: &mut [IndexedChoiceInstruction]) {
prelude.last_mut().map(|instr| {
if let IndexedChoiceInstruction::Trust(i) = instr {
*instr = IndexedChoiceInstruction::Retry(*i);
if let Some(instr) = prelude.last_mut() {
match instr {
IndexedChoiceInstruction::Trust(i) => {
*instr = IndexedChoiceInstruction::Retry(*i);
}
IndexedChoiceInstruction::DefaultTrust(i) => {
*instr = IndexedChoiceInstruction::DefaultRetry(*i);
}
_ => {}
}
});
}
}
#[inline]
fn uncap_choice_seq_with_try(prelude: &mut [IndexedChoiceInstruction]) {
prelude.first_mut().map(|instr| {
if let Some(instr) = prelude.first_mut() {
if let IndexedChoiceInstruction::Try(i) = instr {
*instr = IndexedChoiceInstruction::Retry(*i);
}
});
}
}
pub(crate) fn constant_key_alternatives(
constant: Literal,
atom_tbl: &mut 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 = atom_tbl.build_with(&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) => {
if let Some(n) = n.to_isize() {
Fixnum::build_with_checked(n as i64).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>,
}
@@ -1147,11 +1126,19 @@ pub(crate) trait Indexer {
fn new() -> Self;
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
fn constants(
&mut self,
) -> &mut IndexMap<HeapCellValue, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
fn lists(&mut self) -> &mut VecDeque<Self::ThirdLevelIndex>;
fn structures(&mut self) -> &mut IndexMap<(Atom, usize), VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
fn structures(
&mut self,
) -> &mut IndexMap<(Atom, usize), VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
fn compute_index(is_initial_index: bool, index: usize) -> Self::ThirdLevelIndex;
fn compute_index(
is_initial_index: bool,
index: usize,
non_counted_bt: bool,
) -> Self::ThirdLevelIndex;
fn second_level_index<IndexKey: Eq + Hash>(
indices: IndexMap<IndexKey, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>,
@@ -1187,7 +1174,9 @@ impl Indexer for StaticCodeIndices {
}
#[inline]
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
fn constants(
&mut self,
) -> &mut IndexMap<HeapCellValue, VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
&mut self.constants
}
@@ -1197,13 +1186,21 @@ impl Indexer for StaticCodeIndices {
}
#[inline]
fn structures(&mut self) -> &mut IndexMap<(Atom, usize), VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
fn structures(
&mut self,
) -> &mut IndexMap<(Atom, usize), VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
&mut self.structures
}
fn compute_index(is_initial_index: bool, index: usize) -> IndexedChoiceInstruction {
fn compute_index(
is_initial_index: bool,
index: usize,
non_counted_bt: bool,
) -> IndexedChoiceInstruction {
if is_initial_index {
IndexedChoiceInstruction::Try(index + 1)
} else if non_counted_bt {
IndexedChoiceInstruction::DefaultRetry(index + 1)
} else {
IndexedChoiceInstruction::Retry(index + 1)
}
@@ -1220,10 +1217,8 @@ impl Indexer for StaticCodeIndices {
index_locs.insert(key, IndexingCodePtr::Internal(prelude.len() + 1));
cap_choice_seq_with_trust(code.make_contiguous());
prelude.push_back(IndexingLine::from(code));
} else {
code.front().map(|i| {
index_locs.insert(key, IndexingCodePtr::External(i.offset()));
});
} else if let Some(i) = code.front() {
index_locs.insert(key, IndexingCodePtr::External(i.offset()));
}
}
@@ -1231,7 +1226,9 @@ impl Indexer for StaticCodeIndices {
}
fn switch_on<IndexKey: Eq + Hash>(
mut instr_fn: impl FnMut(IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>) -> IndexingInstruction,
mut instr_fn: impl FnMut(
IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>,
) -> IndexingInstruction,
index: &mut IndexMap<IndexKey, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
prelude: &mut VecDeque<IndexingLine>,
) -> IndexingCodePtr {
@@ -1258,7 +1255,7 @@ impl Indexer for StaticCodeIndices {
) -> IndexingCodePtr {
if lists.len() > 1 {
cap_choice_seq_with_trust(lists.make_contiguous());
let lists = mem::replace(lists, VecDeque::new());
let lists = std::mem::take(lists);
prelude.push_back(IndexingLine::from(lists));
IndexingCodePtr::Internal(1)
@@ -1303,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
}
@@ -1318,7 +1315,7 @@ impl Indexer for DynamicCodeIndices {
}
#[inline]
fn compute_index(_: bool, index: usize) -> usize {
fn compute_index(_: bool, index: usize, _: bool) -> usize {
index + 1
}
@@ -1331,11 +1328,11 @@ impl Indexer for DynamicCodeIndices {
for (key, code) in indices.into_iter() {
if code.len() > 1 {
index_locs.insert(key, IndexingCodePtr::Internal(prelude.len() + 1));
prelude.push_back(IndexingLine::DynamicIndexedChoice(code.into_iter().collect()));
} else {
code.front().map(|i| {
index_locs.insert(key, IndexingCodePtr::DynamicExternal(*i));
});
prelude.push_back(IndexingLine::DynamicIndexedChoice(
code.into_iter().collect(),
));
} else if let Some(i) = code.front() {
index_locs.insert(key, IndexingCodePtr::DynamicExternal(*i));
}
}
@@ -1343,7 +1340,9 @@ impl Indexer for DynamicCodeIndices {
}
fn switch_on<IndexKey: Eq + Hash>(
mut instr_fn: impl FnMut(IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>) -> IndexingInstruction,
mut instr_fn: impl FnMut(
IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>,
) -> IndexingInstruction,
index: &mut IndexMap<IndexKey, VecDeque<usize>, FxBuildHasher>,
prelude: &mut VecDeque<IndexingLine>,
) -> IndexingCodePtr {
@@ -1369,8 +1368,10 @@ impl Indexer for DynamicCodeIndices {
prelude: &mut VecDeque<IndexingLine>,
) -> IndexingCodePtr {
if lists.len() > 1 {
let lists = mem::replace(lists, VecDeque::new());
prelude.push_back(IndexingLine::DynamicIndexedChoice(lists.into_iter().collect()));
let lists = std::mem::take(lists);
prelude.push_back(IndexingLine::DynamicIndexedChoice(
lists.into_iter().collect(),
));
IndexingCodePtr::Internal(1)
} else {
lists
@@ -1400,61 +1401,62 @@ impl Indexer for DynamicCodeIndices {
pub(crate) struct CodeOffsets<I: Indexer> {
indices: I,
optimal_index: usize,
non_counted_bt: bool,
}
impl<I: Indexer> CodeOffsets<I> {
pub(crate) fn new(indices: I, optimal_index: usize) -> Self {
pub(crate) fn new(indices: I, optimal_index: usize, non_counted_bt: bool) -> Self {
CodeOffsets {
indices,
optimal_index,
non_counted_bt,
}
}
fn index_list(&mut self, index: usize) {
let is_initial_index = self.indices.lists().is_empty();
let index = I::compute_index(is_initial_index, index);
let index = I::compute_index(is_initial_index, index, self.non_counted_bt);
self.indices.lists().push_back(index);
}
fn index_constant(
&mut self,
atom_tbl: &mut AtomTable,
constant: Literal,
index: usize,
) -> Vec<Literal> {
let overlapping_constants = constant_key_alternatives(constant, atom_tbl);
let code = self.indices.constants().entry(constant).or_insert(VecDeque::new());
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(is_initial_index, index));
code.push_back(I::compute_index(
is_initial_index,
index,
self.non_counted_bt,
));
for constant in &overlapping_constants {
let code = self
.indices
.constants()
.entry(*constant)
.or_insert(VecDeque::new());
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);
let index = I::compute_index(is_initial_index, index, self.non_counted_bt);
code.push_back(index);
}
overlapping_constants
overlapping_constant_opt
}
fn index_structure(&mut self, name: Atom, arity: usize, index: usize) -> usize {
let code = self
.indices
.structures()
.entry((name.clone(), arity))
.or_insert(VecDeque::new());
let code = self.indices.structures().entry((name, arity)).or_default();
let code_len = code.len();
let is_initial_index = code.is_empty();
code.push_back(I::compute_index(is_initial_index, index));
code.push_back(I::compute_index(
is_initial_index,
index,
self.non_counted_bt,
));
code_len
}
@@ -1463,25 +1465,24 @@ impl<I: Indexer> CodeOffsets<I> {
optimal_arg: &Term,
index: usize,
clause_index_info: &mut ClauseIndexInfo,
atom_tbl: &mut 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);
}
&Term::Clause(_, name, ref terms) => {
clause_index_info.opt_arg_index_key =
OptArgIndexKey::Structure(self.optimal_index, 0, name.clone(), terms.len());
OptArgIndexKey::Structure(self.optimal_index, 0, name, terms.len());
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);
@@ -1528,20 +1529,14 @@ impl<I: Indexer> CodeOffsets<I> {
&mut prelude,
);
match &mut str_loc {
IndexingCodePtr::Internal(ref mut i) => {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
}
_ => {}
};
if let IndexingCodePtr::Internal(ref mut i) = &mut str_loc {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
}
match &mut lst_loc {
IndexingCodePtr::Internal(ref mut i) => {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
*i += emitted_switch_on_structure as usize; // str_loc.is_internal() as usize;
}
_ => {}
};
if let IndexingCodePtr::Internal(ref mut i) = &mut lst_loc {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
*i += emitted_switch_on_structure as usize; // str_loc.is_internal() as usize;
}
let var_offset = 1 + skip_stub_try_me_else as usize;

View File

@@ -5,47 +5,34 @@ use crate::parser::ast::*;
use std::cell::Cell;
use std::collections::VecDeque;
use std::fmt;
use std::iter::*;
use std::rc::Rc;
use std::vec::Vec;
#[allow(clippy::borrowed_box)]
#[derive(Debug, Clone)]
pub(crate) enum TermRef<'a> {
AnonVar(Level),
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),
Var(Level, &'a Cell<VarReg>, Rc<String>),
}
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,
}
}
PartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
CompleteString(Level, &'a Cell<RegType>, Rc<String>),
Var(Level, &'a Cell<VarReg>, VarPtr),
}
#[allow(clippy::borrowed_box)]
#[derive(Debug)]
pub(crate) enum TermIterState<'a> {
AnonVar(Level),
Literal(Level, &'a Cell<RegType>, &'a Literal),
Clause(Level, usize, &'a Cell<RegType>, Atom, &'a Vec<Term>),
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),
Var(Level, &'a Cell<VarReg>, Rc<String>),
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),
}
impl<'a> TermIterState<'a> {
@@ -60,12 +47,12 @@ 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, atom) => {
TermIterState::CompleteString(lvl, cell, *atom)
Term::CompleteString(cell, string) => {
TermIterState::CompleteString(lvl, cell, string.clone())
}
Term::Var(cell, var) => TermIterState::Var(lvl, cell, var.clone()),
Term::Var(cell, var_ptr) => TermIterState::Var(lvl, cell, var_ptr.clone()),
}
}
}
@@ -81,6 +68,7 @@ impl<'a> QueryIterator<'a> {
.push(TermIterState::subterm_to_state(lvl, term));
}
/*
fn from_rule_head_clause(terms: &'a Vec<Term>) -> Self {
let state_stack = terms
.iter()
@@ -90,23 +78,21 @@ impl<'a> QueryIterator<'a> {
QueryIterator { state_stack }
}
*/
fn from_term(term: &'a Term) -> Self {
let state = match term {
Term::AnonVar | Term::Cons(..) | Term::Literal(..) |
Term::PartialString(..) | Term::CompleteString(..) => {
Term::AnonVar
| Term::Cons(..)
| Term::Literal(..)
| Term::PartialString(..)
| Term::CompleteString(..) => {
return QueryIterator {
state_stack: vec![],
}
}
Term::Clause(r, name, terms) => TermIterState::Clause(
Level::Root,
0,
r,
*name,
terms,
),
Term::Var(cell, var) => TermIterState::Var(Level::Root, cell, var.clone()),
Term::Clause(r, name, terms) => TermIterState::Clause(Level::Root, 0, r, *name, terms),
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Root, cell, var_ptr.clone()),
};
QueryIterator {
@@ -114,46 +100,27 @@ impl<'a> QueryIterator<'a> {
}
}
fn new(term: &'a QueryTerm) -> Self {
fn extend_state(&mut self, lvl: Level, term: &'a QueryTerm) {
match term {
&QueryTerm::Clause(ref cell, ClauseType::CallN(_), ref terms, _) => {
let state = TermIterState::Clause(Level::Root, 1, cell, atom!("$call"), terms);
QueryIterator {
state_stack: vec![state],
}
QueryTerm::Clause(ref cell, ClauseType::CallN(_), ref terms, _) => {
self.state_stack
.push(TermIterState::Clause(lvl, 1, cell, atom!("$call"), terms));
}
&QueryTerm::Clause(ref cell, ref ct, ref terms, _) => {
let state = TermIterState::Clause(Level::Root, 0, cell, ct.name(), terms);
QueryIterator {
state_stack: vec![state],
}
QueryTerm::Clause(ref cell, ref ct, ref terms, _) => {
self.state_stack
.push(TermIterState::Clause(lvl, 0, cell, ct.name(), terms));
}
&QueryTerm::UnblockedCut(ref cell) => {
let state = TermIterState::Var(Level::Root, cell, Rc::new("!".to_string()));
QueryIterator {
state_stack: vec![state],
}
}
&QueryTerm::GetLevelAndUnify(ref cell, ref var) => {
let state = TermIterState::Var(Level::Root, cell, var.clone());
QueryIterator {
state_stack: vec![state],
}
}
&QueryTerm::Jump(ref vars) => {
let state_stack = vars
.iter()
.rev()
.map(|t| TermIterState::subterm_to_state(Level::Shallow, t))
.collect();
QueryIterator { state_stack }
}
&QueryTerm::BlockedCut => QueryIterator {
state_stack: vec![],
},
_ => {}
}
}
pub fn new(term: &'a QueryTerm) -> Self {
let mut iter = QueryIterator {
state_stack: vec![],
};
iter.extend_state(Level::Root, term);
iter
}
}
impl<'a> Iterator for QueryIterator<'a> {
@@ -191,20 +158,22 @@ impl<'a> Iterator for QueryIterator<'a> {
}
}
TermIterState::InitialCons(lvl, cell, head, tail) => {
self.state_stack.push(TermIterState::FinalCons(lvl, cell, head, tail));
self.state_stack
.push(TermIterState::FinalCons(lvl, cell, head, tail));
self.push_subterm(lvl.child_level(), tail);
self.push_subterm(lvl.child_level(), head);
}
TermIterState::InitialPartialString(lvl, cell, string, tail) => {
self.state_stack.push(TermIterState::FinalPartialString(lvl, cell, string, tail));
self.state_stack
.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));
@@ -212,8 +181,8 @@ impl<'a> Iterator for QueryIterator<'a> {
TermIterState::Literal(lvl, cell, constant) => {
return Some(TermRef::Literal(lvl, cell, constant));
}
TermIterState::Var(lvl, cell, var) => {
return Some(TermRef::Var(lvl, cell, var));
TermIterState::Var(lvl, cell, var_ptr) => {
return Some(TermRef::Var(lvl, cell, var_ptr));
}
};
}
@@ -225,7 +194,7 @@ impl<'a> Iterator for QueryIterator<'a> {
#[derive(Debug)]
pub(crate) struct FactIterator<'a> {
state_queue: VecDeque<TermIterState<'a>>,
iterable_root: bool,
iterable_root: RootIterationPolicy,
}
impl<'a> FactIterator<'a> {
@@ -234,7 +203,7 @@ impl<'a> FactIterator<'a> {
.push_back(TermIterState::subterm_to_state(lvl, term));
}
pub(crate) fn from_rule_head_clause(terms: &'a Vec<Term>) -> Self {
pub(crate) fn from_rule_head_clause(terms: &'a [Term]) -> Self {
let state_queue = terms
.iter()
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt))
@@ -242,11 +211,11 @@ impl<'a> FactIterator<'a> {
FactIterator {
state_queue,
iterable_root: false,
iterable_root: RootIterationPolicy::NotIterated,
}
}
fn new(term: &'a Term, iterable_root: bool) -> Self {
fn new(term: &'a Term, iterable_root: RootIterationPolicy) -> Self {
let states = match term {
Term::AnonVar => {
vec![TermIterState::AnonVar(Level::Root)]
@@ -260,26 +229,26 @@ 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) => {
Term::CompleteString(cell, string) => {
vec![TermIterState::CompleteString(
Level::Root,
cell,
*atom,
string.clone(),
)]
}
Term::Literal(cell, constant) => {
vec![TermIterState::Literal(Level::Root, cell, constant)]
}
Term::Var(cell, var) => {
vec![TermIterState::Var(Level::Root, cell, var.clone())]
Term::Var(cell, var_ptr) => {
vec![TermIterState::Var(Level::Root, cell, var_ptr.clone())]
}
};
@@ -305,7 +274,7 @@ impl<'a> Iterator for FactIterator<'a> {
}
match lvl {
Level::Root if !self.iterable_root => continue,
Level::Root if !self.iterable_root.iterable() => continue,
_ => return Some(TermRef::Clause(lvl, cell, name, child_terms)),
};
}
@@ -325,8 +294,8 @@ impl<'a> Iterator for FactIterator<'a> {
TermIterState::Literal(lvl, cell, constant) => {
return Some(TermRef::Literal(lvl, cell, constant))
}
TermIterState::Var(lvl, cell, var) => {
return Some(TermRef::Var(lvl, cell, var));
TermIterState::Var(lvl, cell, var_ptr) => {
return Some(TermRef::Var(lvl, cell, var_ptr));
}
_ => {}
}
@@ -336,197 +305,138 @@ impl<'a> Iterator for FactIterator<'a> {
}
}
pub(crate) fn post_order_iter<'a>(term: &'a Term) -> QueryIterator<'a> {
pub(crate) fn post_order_iter(term: &Term) -> QueryIterator<'_> {
QueryIterator::from_term(term)
}
pub(crate) fn breadth_first_iter<'a>(term: &'a Term, iterable_root: bool) -> FactIterator<'a> {
pub(crate) fn breadth_first_iter(
term: &Term,
iterable_root: RootIterationPolicy,
) -> FactIterator<'_> {
FactIterator::new(term, iterable_root)
}
#[derive(Debug, Copy, Clone)]
enum ClauseIteratorState<'a> {
RemainingChunks(&'a VecDeque<ChunkedTerms>, usize),
RemainingBranches(&'a Vec<VecDeque<ChunkedTerms>>, usize),
}
#[derive(Debug, Clone)]
pub(crate) enum ClauseItem<'a> {
FirstBranch(usize),
NextBranch,
BranchEnd(usize),
Chunk { terms: &'a VecDeque<QueryTerm> },
}
#[derive(Debug)]
pub(crate) enum ChunkedTerm<'a> {
HeadClause(Atom, &'a Vec<Term>),
BodyTerm(&'a QueryTerm),
pub(crate) struct ClauseIterator<'a> {
state_stack: Vec<ClauseIteratorState<'a>>,
remaining_chunks_on_stack: usize,
}
pub(crate) fn query_term_post_order_iter<'a>(query_term: &'a QueryTerm) -> QueryIterator<'a> {
QueryIterator::new(query_term)
}
impl<'a> ChunkedTerm<'a> {
pub(crate) fn post_order_iter(&self) -> QueryIterator<'a> {
match self {
&ChunkedTerm::BodyTerm(qt) => QueryIterator::new(qt),
&ChunkedTerm::HeadClause(_, terms) => QueryIterator::from_rule_head_clause(terms),
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);
}
}
ClauseIteratorState::RemainingChunks(chunk_vec, 0)
}
fn contains_cut_var<'a, Iter: Iterator<Item = &'a Term>>(terms: Iter) -> bool {
for term in terms {
if let &Term::Var(_, ref var) = term {
if var.as_str() == "!" {
return true;
impl<'a> ClauseIterator<'a> {
pub fn new(clauses: &'a ChunkedTermVec) -> Self {
match state_from_chunked_terms(&clauses.chunk_vec) {
state @ ClauseIteratorState::RemainingBranches(..) => Self {
state_stack: vec![state],
remaining_chunks_on_stack: 0,
},
state @ ClauseIteratorState::RemainingChunks(..) => Self {
state_stack: vec![state],
remaining_chunks_on_stack: 1,
},
}
}
#[inline(always)]
pub fn in_tail_position(&self) -> bool {
self.remaining_chunks_on_stack == 0
}
fn branch_end_depth(&mut self) -> usize {
let mut depth = 1;
while let Some(state) = self.state_stack.pop() {
match state {
ClauseIteratorState::RemainingBranches(terms, focus) if terms.len() == focus => {
depth += 1;
}
_ => {
self.state_stack.push(state);
break;
}
}
}
}
false
}
pub(crate) struct ChunkedIterator<'a> {
pub(crate) chunk_num: usize,
iter: Box<dyn Iterator<Item = ChunkedTerm<'a>> + 'a>,
deep_cut_encountered: bool,
cut_var_in_head: bool,
}
impl<'a> fmt::Debug for ChunkedIterator<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("ChunkedIterator")
.field("chunk_num", &self.chunk_num)
// Hacky solution.
.field("iter", &"Box<dyn Iterator<Item = ChunkedTerm<'a>> + 'a>")
.field("deep_cut_encountered", &self.deep_cut_encountered)
.field("cut_var_in_head", &self.cut_var_in_head)
.finish()
depth
}
}
type ChunkedIteratorItem<'a> = (usize, usize, Vec<ChunkedTerm<'a>>);
type RuleBodyIteratorItem<'a> = (usize, usize, Vec<&'a QueryTerm>);
impl<'a> ChunkedIterator<'a> {
pub(crate) fn rule_body_iter(self) -> Box<dyn Iterator<Item = RuleBodyIteratorItem<'a>> + 'a> {
Box::new(self.filter_map(|(cn, lt_arity, terms)| {
let filtered_terms: Vec<_> = terms
.into_iter()
.filter_map(|ct| match ct {
ChunkedTerm::BodyTerm(qt) => Some(qt),
_ => None,
})
.collect();
if filtered_terms.is_empty() {
None
} else {
Some((cn, lt_arity, filtered_terms))
}
}))
}
pub(crate) fn from_rule_body(p1: &'a QueryTerm, clauses: &'a Vec<QueryTerm>) -> Self {
let inner_iter = Box::new(once(ChunkedTerm::BodyTerm(p1)));
let iter = inner_iter.chain(clauses.iter().map(|t| ChunkedTerm::BodyTerm(t)));
ChunkedIterator {
chunk_num: 0,
iter: Box::new(iter),
deep_cut_encountered: false,
cut_var_in_head: false,
}
}
pub(crate) fn from_rule(rule: &'a Rule) -> Self {
let &Rule {
head: (ref name, ref args, ref p1),
ref clauses,
} = rule;
let iter = once(ChunkedTerm::HeadClause(name.clone(), args));
let inner_iter = Box::new(once(ChunkedTerm::BodyTerm(p1)));
let iter = iter.chain(inner_iter.chain(clauses.iter().map(|t| ChunkedTerm::BodyTerm(t))));
ChunkedIterator {
chunk_num: 0,
iter: Box::new(iter),
deep_cut_encountered: false,
cut_var_in_head: false,
}
}
pub(crate) fn encountered_deep_cut(&self) -> bool {
self.deep_cut_encountered
}
fn take_chunk(&mut self, term: ChunkedTerm<'a>) -> (usize, usize, Vec<ChunkedTerm<'a>>) {
let mut arity = 0;
let mut item = Some(term);
let mut result = Vec::new();
while let Some(term) = item {
match term {
ChunkedTerm::HeadClause(_, terms) => {
if contains_cut_var(terms.iter()) {
self.cut_var_in_head = true;
}
result.push(term);
}
ChunkedTerm::BodyTerm(&QueryTerm::Jump(ref vars)) => {
result.push(term);
arity = vars.len();
if contains_cut_var(vars.iter()) && !self.cut_var_in_head {
self.deep_cut_encountered = true;
}
break;
}
ChunkedTerm::BodyTerm(&QueryTerm::BlockedCut) => {
result.push(term);
if self.chunk_num > 0 {
self.deep_cut_encountered = true;
}
}
ChunkedTerm::BodyTerm(&QueryTerm::GetLevelAndUnify(..)) => {
self.deep_cut_encountered = true;
result.push(term);
arity = 1;
break;
}
ChunkedTerm::BodyTerm(&QueryTerm::UnblockedCut(..)) => {
self.deep_cut_encountered = true;
result.push(term);
}
ChunkedTerm::BodyTerm(&QueryTerm::Clause(_, ClauseType::Inlined(_), ..)) => {
result.push(term)
}
ChunkedTerm::BodyTerm(&QueryTerm::Clause(
_,
ClauseType::CallN(_),
ref subterms,
_,
)) => {
result.push(term);
arity = subterms.len() + 1;
break;
}
ChunkedTerm::BodyTerm(qt) => {
result.push(term);
arity = qt.arity();
break;
}
};
item = self.iter.next();
}
let chunk_num = self.chunk_num;
self.chunk_num += 1;
(chunk_num, arity, result)
}
}
impl<'a> Iterator for ChunkedIterator<'a> {
// the chunk number, last term arity, and vector of references.
type Item = ChunkedIteratorItem<'a>;
impl<'a> Iterator for ClauseIterator<'a> {
type Item = ClauseItem<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|term| self.take_chunk(term))
while let Some(state) = self.state_stack.pop() {
match state {
ClauseIteratorState::RemainingChunks(chunks, focus) if focus < chunks.len() => {
if focus + 1 < chunks.len() {
self.state_stack
.push(ClauseIteratorState::RemainingChunks(chunks, focus + 1));
} else {
self.remaining_chunks_on_stack -= 1;
}
match &chunks[focus] {
ChunkedTerms::Branch(branches) => {
self.state_stack
.push(ClauseIteratorState::RemainingBranches(branches, 0));
}
ChunkedTerms::Chunk { ref terms } => {
return Some(ClauseItem::Chunk { terms });
}
}
}
ClauseIteratorState::RemainingChunks(chunks, focus) => {
debug_assert_eq!(chunks.len(), focus);
}
ClauseIteratorState::RemainingBranches(branches, focus)
if focus < branches.len() =>
{
self.state_stack
.push(ClauseIteratorState::RemainingBranches(branches, focus + 1));
let state = state_from_chunked_terms(&branches[focus]);
if let ClauseIteratorState::RemainingChunks(..) = &state {
self.remaining_chunks_on_stack += 1;
}
self.state_stack.push(state);
return if focus == 0 {
Some(ClauseItem::FirstBranch(branches.len()))
} else {
Some(ClauseItem::NextBranch)
};
}
ClauseIteratorState::RemainingBranches(branches, focus) => {
debug_assert_eq!(branches.len(), focus);
return Some(ClauseItem::BranchEnd(self.branch_end_depth()));
}
}
}
None
}
}

View File

@@ -1,36 +1,83 @@
//! A free software ISO Prolog system.
#![recursion_limit = "4112"]
#![deny(missing_docs)]
#[macro_use]
extern crate static_assertions;
#[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;
mod fixtures;
#[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;
// Re-exports
pub use machine::config::*;
pub use machine::lib_machine::*;
pub use machine::Machine;
#[cfg(target_arch = "wasm32")]
pub mod wasm;
#[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;
#[cfg(feature = "repl")]
ctrlc::set_handler(move || {
INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
})
.unwrap();
#[cfg(target_arch = "wasm32")]
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
#[cfg(not(target_arch = "wasm32"))]
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
let mut wam = MachineBuilder::default()
.with_streams(StreamConfig::stdio())
.build();
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
})
}

View File

@@ -1,4 +1,9 @@
:- module(arithmetic, [expmod/4, lsb/2, msb/2, number_to_rational/2,
/** Arithmetic predicates
These predicates are additions to standard the arithmetic functions provided by `is/2`.
*/
:- module(arithmetic, [expmod/4, lcm/3, lsb/2, msb/2, number_to_rational/2,
number_to_rational/3, popcount/2,
rational_numerator_denominator/3]).
@@ -6,6 +11,10 @@
:- use_module(library(error)).
:- use_module(library(lists), [append/3, member/2]).
%% expmod(+Base, +Expo, +Mod, -R).
%
% Modular exponentiation. Base, Expo and Mod must be integers.
expmod(Base, Expo, Mod, R) :-
( member(N, [Base, Expo, Mod]), var(N) -> instantiation_error(expmod/4)
; member(N, [Base, Expo, Mod]), \+ integer(N) ->
@@ -28,6 +37,25 @@ expmod_(Base0, Expo0, Mod, C, R) :-
Base is (Base0 * Base0) mod Mod,
expmod_(Base, Expo, Mod, C, R).
%% lcm(+A, +B, -Lcm) is det.
%
% Calculates the Least common multiple for A and B: the smallest positive integer
% that is divisible by both A and B.
%
% A and B need to be integers.
lcm(A, B, X) :-
builtins:must_be_number(A, lcm/2),
builtins:must_be_number(B, lcm/2),
( \+ integer(A) -> type_error(integer, A, lcm/2)
; \+ integer(B) -> type_error(integer, B, lcm/2)
; (A = 0, B = 0) -> X = 0
; builtins:can_be_number(X, lcm/2),
X is abs(B) // gcd(A,B) * abs(A)
).
%% lsb(+X, -N).
%
% True iff N is the least significat bit of integer X
lsb(X, N) :-
builtins:must_be_number(X, lsb/2),
( \+ integer(X) -> type_error(integer, X, lsb/2)
@@ -37,6 +65,9 @@ lsb(X, N) :-
msb_(X1, -1, N)
).
%% msb(+X, -N).
%
% True iff N is the most significant bit of integer X
msb(X, N) :-
builtins:must_be_number(X, msb/2),
( \+ integer(X) -> type_error(integer, X, msb/2)
@@ -52,6 +83,9 @@ msb_(X, M, N) :-
M1 is M + 1,
msb_(X1, M1, N).
%% number_to_rational(+Real, -Fraction).
%
% True iff given a number Real, Fraction is the same number represented as a fraction.
number_to_rational(Real, Fraction) :-
( var(Real) -> instantiation_error(number_to_rational/2)
; integer(Real) -> Fraction is Real rdiv 1
@@ -110,12 +144,20 @@ simplify_fraction(A0/B0, A/B) :-
A is A0 // G,
B is B0 // G.
%% rational_numerator_denominator(+Fraction, -Numerator, -Denominator).
%
% True iff given a fraction Fraction, Numerator is the numerator of that fraction
% and Denominator the denominator.
rational_numerator_denominator(R, N, D) :-
write_term_to_chars(R, [], Cs),
append(Ns, [' ', r, d, i, v, ' '|Ds], Cs),
number_chars(N, Ns),
number_chars(D, Ds).
%% popcount(+Number, -Bits1).
%
% True iff given an integer Number, Bits1 is the amount of 1 bits the binary representation
% of that number has.
popcount(X, N) :-
must_be(integer, X),
'$popcount'(X, N).

View File

@@ -54,28 +54,27 @@
:- use_module(library(lists)).
/** <module> Binary associations
/** Binary associations
Assocs are Key-Value associations implemented as a balanced binary tree
(AVL tree).
@see library(pairs), library(rbtrees)
@author R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
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.
%% empty_assoc(?Assoc) is semidet.
%
% Is true if Assoc is the empty association list.
% Is true if Assoc is the empty association list.
empty_assoc(t).
%! assoc_to_list(+Assoc, -Pairs) is det.
%% assoc_to_list(+Assoc, -Pairs) is det.
%
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
% in Pairs are sorted in ascending order.
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
% in Pairs are sorted in ascending order.
assoc_to_list(Assoc, List) :-
assoc_to_list(Assoc, List, []).
@@ -86,10 +85,10 @@ assoc_to_list(t(Key,Val,_,L,R), List, Rest) :-
assoc_to_list(t, List, List).
%! assoc_to_keys(+Assoc, -Keys) is det.
%% assoc_to_keys(+Assoc, -Keys) is det.
%
% True if Keys is the list of keys in Assoc. The keys are sorted
% in ascending order.
% True if Keys is the list of keys in Assoc. The keys are sorted
% in ascending order.
assoc_to_keys(Assoc, List) :-
assoc_to_keys(Assoc, List, []).
@@ -100,11 +99,11 @@ assoc_to_keys(t(Key,_,_,L,R), List, Rest) :-
assoc_to_keys(t, List, List).
%! assoc_to_values(+Assoc, -Values) is det.
%% assoc_to_values(+Assoc, -Values) is det.
%
% True if Values is the list of values in Assoc. Values are
% ordered in ascending order of the key to which they were
% associated. Values may contain duplicates.
% True if Values is the list of values in Assoc. Values are
% ordered in ascending order of the key to which they were
% associated. Values may contain duplicates.
assoc_to_values(Assoc, List) :-
assoc_to_values(Assoc, List, []).
@@ -114,12 +113,12 @@ assoc_to_values(t(_,Value,_,L,R), List, Rest) :-
assoc_to_values(R, More, Rest).
assoc_to_values(t, List, List).
%! is_assoc(+Assoc) is semidet.
%% is_assoc(+Assoc) is semidet.
%
% True if Assoc is an association list. This predicate checks
% that the structure is valid, elements are in order, and tree
% is balanced to the extent guaranteed by AVL trees. I.e.,
% branches of each subtree differ in depth by at most 1.
% True if Assoc is an association list. This predicate checks
% that the structure is valid, elements are in order, and tree
% is balanced to the extent guaranteed by AVL trees. I.e.,
% branches of each subtree differ in depth by at most 1.
is_assoc(Assoc) :-
is_assoc(Assoc, _Min, _Max, _Depth).
@@ -151,12 +150,10 @@ balance(=,-).
balance(<,<).
balance(>,>).
%! gen_assoc(?Key, +Assoc, ?Value) is nondet.
%% gen_assoc(?Key, +Assoc, ?Value) is nondet.
%
% True if Key-Value is an association in Assoc. Enumerates keys in
% ascending order on backtracking.
%
% @see get_assoc/3.
% True if Key-Value is an association in Assoc. Enumerates keys in
% ascending order on backtracking.
gen_assoc(Key, Assoc, Value) :-
( ground(Key)
@@ -171,11 +168,11 @@ gen_assoc_(Key, t(_,_,_,_,R), Val) :-
gen_assoc_(Key, R, Val).
%! get_assoc(+Key, +Assoc, -Value) is semidet.
%% get_assoc(+Key, +Assoc, -Value) is semidet.
%
% True if Key-Value is an association in Assoc.
% True if Key-Value is an association in Assoc.
%
% @error type_error(assoc, Assoc) if Assoc is not an association list.
% Throws error: `type_error(assoc, Assoc)` if Assoc is not an association list.
get_assoc(Key, Assoc, Val) :-
must_be(assoc, Assoc),
@@ -201,9 +198,9 @@ get_assoc(>, Key, _, _, Tree, Val) :-
% :- endif.
%! get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
%% get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
%
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
get_assoc(Key, t(K,V,B,L,R), Val, t(K,NV,B,NL,NR), NVal) :-
compare(Rel, Key, K),
@@ -216,12 +213,12 @@ get_assoc(>, Key, V, L, R, Val, V, L, NR, NVal) :-
get_assoc(Key, R, Val, NR, NVal).
%! list_to_assoc(+Pairs, -Assoc) is det.
%% list_to_assoc(+Pairs, -Assoc) is det.
%
% Create an association from a list Pairs of Key-Value pairs. List
% must not contain duplicate keys.
% Create an association from a list Pairs of Key-Value pairs. List
% must not contain duplicate keys.
%
% @error domain_error(unique_key_pairs, List) if List contains duplicate keys
% Throws error: `domain_error(unique_key_pairs, List)` if List contains duplicate keys
list_to_assoc(List, Assoc) :-
( List = [] -> Assoc = t
@@ -246,13 +243,13 @@ list_to_assoc(N, List, More, Depth, t(K,V,Balance,L,R)) :-
compare(B, RDepth, LDepth),
balance(B, Balance).
%! ord_list_to_assoc(+Pairs, -Assoc) is det.
%% ord_list_to_assoc(+Pairs, -Assoc) is det.
%
% Assoc is created from an ordered list Pairs of Key-Value
% pairs. The pairs must occur in strictly ascending order of
% their keys.
% Assoc is created from an ordered list Pairs of Key-Value
% pairs. The pairs must occur in strictly ascending order of
% their keys.
%
% @error domain_error(key_ordered_pairs, List) if pairs are not ordered.
% Throws error: `domain_error(key_ordered_pairs, List)` if pairs are not ordered.
ord_list_to_assoc(Sorted, Assoc) :-
( Sorted = [] -> Assoc = t
@@ -263,9 +260,9 @@ ord_list_to_assoc(Sorted, Assoc) :-
)
).
%! ord_pairs(+Pairs) is semidet
%% ord_pairs(+Pairs) is semidet
%
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
ord_pairs([K-_V|Rest]) :-
ord_pairs(Rest, K).
@@ -274,9 +271,9 @@ ord_pairs([K-_V|Rest], K0) :-
K0 @< K,
ord_pairs(Rest, K).
%! map_assoc(:Pred, +Assoc) is semidet.
%% map_assoc(:Pred, +Assoc) is semidet.
%
% True if Pred(Value) is true for all values in Assoc.
% True if Pred(Value) is true for all values in Assoc.
map_assoc(Pred, T) :-
map_assoc_(T, Pred).
@@ -287,10 +284,10 @@ map_assoc_(t(_,Val,_,L,R), Pred) :-
call(Pred, Val),
map_assoc_(R, Pred).
%! map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
%% map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
%
% Map corresponding values. True if Assoc is Assoc0 with Pred
% applied to all corresponding pairs of of values.
% Map corresponding values. True if Assoc is Assoc0 with Pred
% applied to all corresponding pairs of of values.
map_assoc(Pred, T0, T) :-
map_assoc_(T0, Pred, T).
@@ -302,9 +299,9 @@ 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.
% True if Key-Value is in Assoc and Key is the largest key.
max_assoc(t(K,V,_,_,R), Key, Val) :-
max_assoc(R, K, V, Key, Val).
@@ -314,9 +311,9 @@ max_assoc(t(K,V,_,_,R), _, _, Key, Val) :-
max_assoc(R, K, V, Key, Val).
%! min_assoc(+Assoc, -Key, -Value) is semidet.
%% min_assoc(+Assoc, -Key, -Value) is semidet.
%
% True if Key-Value is in assoc and Key is the smallest key.
% True if Key-Value is in assoc and Key is the smallest key.
min_assoc(t(K,V,_,L,_), Key, Val) :-
min_assoc(L, K, V, Key, Val).
@@ -326,10 +323,10 @@ min_assoc(t(K,V,_,L,_), _, _, Key, Val) :-
min_assoc(L, K, V, Key, Val).
%! put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
%% put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
%
% Assoc is Assoc0, except that Key is associated with
% Value. This can be used to insert and change associations.
% Assoc is Assoc0, except that Key is associated with
% Value. This can be used to insert and change associations.
put_assoc(Key, A0, Value, A) :-
insert(A0, Key, Value, A, _).
@@ -361,11 +358,11 @@ table(< , right , - , no , no ) :- !.
table(> , left , - , no , no ) :- !.
table(> , right , - , no , yes ) :- !.
%! del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
%% del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
%
% True if Key-Value is in Assoc0 and Key is the smallest key.
% Assoc is Assoc0 with Key-Value removed. Warning: This will
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
% True if Key-Value is in Assoc0 and Key is the smallest key.
% Assoc is Assoc0 with Key-Value removed. Warning: This will
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
del_min_assoc(Tree, Key, Val, NewTree) :-
del_min_assoc(Tree, Key, Val, NewTree, _DepthChanged).
@@ -375,11 +372,11 @@ del_min_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
del_min_assoc(L, Key, Val, NewL, LeftChanged),
deladjust(LeftChanged, t(K,V,B,NewL,R), left, NewTree, Changed).
%! del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
%% del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
%
% True if Key-Value is in Assoc0 and Key is the greatest key.
% Assoc is Assoc0 with Key-Value removed. Warning: This will
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
% True if Key-Value is in Assoc0 and Key is the greatest key.
% Assoc is Assoc0 with Key-Value removed. Warning: This will
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
del_max_assoc(Tree, Key, Val, NewTree) :-
del_max_assoc(Tree, Key, Val, NewTree, _DepthChanged).
@@ -389,10 +386,10 @@ del_max_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
del_max_assoc(R, Key, Val, NewR, RightChanged),
deladjust(RightChanged, t(K,V,B,L,NewR), right, NewTree, Changed).
%! del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
%% del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
%
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
% Key-Value removed.
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
% Key-Value removed.
del_assoc(Key, A0, Value, A) :-
delete(A0, Key, Value, A, _).

View File

@@ -1,8 +1,8 @@
:- module(atts, [op(1199, fx, attribute),
call_residue_vars/2,
term_attributed_variables/2]).
:- use_module(library(dcgs)).
:- use_module(library(error)).
:- use_module(library(terms)).
/* represent the list of attributes belonging to a variable,
@@ -19,77 +19,12 @@
'$default_attr_list'(PGs, Module, AttrVar).
'$default_attr_list'([], _, _) --> [].
'$absent_attr'(V, Attr) :-
'$get_attr_list'(V, Ls),
'$absent_from_list'(Ls, Attr).
'$absent_from_list'(X, Attr) :-
( var(X) ->
true
; X = [L|Ls],
L \= Attr ->
'$absent_from_list'(Ls, Attr)
).
'$get_attr'(V, Attr) :-
'$get_attr_list'(V, Ls),
nonvar(Ls),
'$get_from_list'(Ls, V, Attr).
'$get_from_list'([L|Ls], V, Attr) :-
nonvar(L),
( L \= Attr ->
nonvar(Ls),
'$get_from_list'(Ls, V, Attr)
; L = Attr,
'$enqueue_attr_var'(V)
).
'$put_attr'(V, Attr) :-
'$get_attr_list'(V, Ls),
'$add_to_list'(Ls, V, Attr).
'$add_to_list'(Ls, V, Attr) :-
( var(Ls) ->
Ls = [Attr | _],
'$enqueue_attr_var'(V)
; Ls = [_ | Ls0],
'$add_to_list'(Ls0, V, Attr)
).
'$del_attr'(Ls0, _, _) :-
var(Ls0),
!.
'$del_attr'(Ls0, V, Attr) :-
Ls0 = [Att | Ls1],
nonvar(Att),
( Att \= Attr ->
'$del_attr_buried'(Ls0, Ls1, V, Attr)
; '$enqueue_attr_var'(V),
'$del_attr_head'(V),
'$del_attr'(Ls1, V, Attr)
).
'$del_attr_step'(Ls1, V, Attr) :-
( nonvar(Ls1) ->
Ls1 = [_ | Ls2],
'$del_attr_buried'(Ls1, Ls2, V, Attr)
'$absent_attr'(V, Module, Attr) :-
( '$get_from_attr_list'(V, Module, Attr) ->
false
; true
).
%% assumptions: Ls0 is a list, Ls1 is its tail;
%% the head of Ls0 can be ignored.
'$del_attr_buried'(Ls0, Ls1, V, Attr) :-
( var(Ls1) -> true
; Ls1 = [Att | Ls2] ->
( Att \= Attr ->
'$del_attr_buried'(Ls1, Ls2, V, Attr)
; '$enqueue_attr_var'(V),
'$del_attr_non_head'(Ls0), %% set tail of Ls0 = tail of Ls1. can be undone by backtracking.
'$del_attr_step'(Ls1, V, Attr)
)
).
'$copy_attr_list'(L, _Module, []) :- var(L), !.
'$copy_attr_list'([Module0:Att|Atts], Module, CopiedAtts) :-
( Module0 == Module ->
@@ -145,38 +80,28 @@ put_attr(Name, Arity, Module) -->
{ functor(Attr, Name, Arity) },
[(put_atts(V, +Attr) :-
!,
functor(Attr, Head, Arity),
functor(AttrForm, Head, Arity),
'$get_attr_list'(V, Ls),
atts:'$del_attr'(Ls, V, Module:AttrForm),
atts:'$put_attr'(V, Module:Attr)),
(put_atts(V, Attr) :-
'$put_to_attr_list'(V, Module, Attr)),
(put_atts(V, Attr) :-
!,
functor(Attr, Head, Arity),
functor(AttrForm, Head, Arity),
'$get_attr_list'(V, Ls),
atts:'$del_attr'(Ls, V, Module:AttrForm),
atts:'$put_attr'(V, Module:Attr)),
'$put_to_attr_list'(V, Module, Attr)),
(put_atts(V, -Attr) :-
!,
functor(Attr, _, _),
'$get_attr_list'(V, Ls),
atts:'$del_attr'(Ls, V, Module:Attr))].
'$del_from_attr_list'(V, Module, Attr))].
get_attr(Name, Arity, Module) -->
{ functor(Attr, Name, Arity) },
[(get_atts(V, +Attr) :-
!,
functor(Attr, _, _),
atts:'$get_attr'(V, Module:Attr)),
atts:'$get_from_attr_list'(V, Module, Attr)),
(get_atts(V, Attr) :-
!,
functor(Attr, _, _),
atts:'$get_attr'(V, Module:Attr)),
atts:'$get_from_attr_list'(V, Module, Attr)),
(get_atts(V, -Attr) :-
!,
functor(Attr, _, _),
atts:'$absent_attr'(V, Module:Attr))].
atts:'$absent_attr'(V, Module, Attr))].
user:goal_expansion(Term, M:put_atts(Var, Attr)) :-
nonvar(Term),
@@ -185,12 +110,5 @@ user:goal_expansion(Term, M:get_atts(Var, Attr)) :-
nonvar(Term),
Term = get_atts(Var, M, Attr).
:- meta_predicate call_residue_vars(0, ?).
call_residue_vars(Goal, Vars) :-
'$get_attr_var_queue_delim'(B),
call(Goal),
'$get_attr_var_queue_beyond'(B, Vars).
term_attributed_variables(Term, Vars) :-
'$term_attributed_variables'(Term, Vars).

View File

@@ -1,3 +1,10 @@
/** Predicates that generate integers
These predicates can be used to reason about integers in a reduced domain that
follow some property. `library(clpz)` provides another way of reasoning about
integers that may also be interesting.
*/
:- module(between, [between/3, gen_int/1, gen_nat/1, numlist/2, numlist/3, repeat/1]).
%% TODO: numlist/5.
@@ -5,6 +12,24 @@
:- use_module(library(lists), [length/2]).
:- use_module(library(error)).
%% between(+Lower, +Upper, -X).
%
% Given Lower and Upper are both integer numbers, true iff X is an integer so that _Lower =< X =< Upper_.
% Can be used both to check if X is between Lower and Upper or to generate an integer between
% Lower and Upper.
%
% Examples:
%
% ```
% ?- between(10, 20, 15).
% true.
% ?- between(10, 20, 25).
% false.
% ?- between(3, 5, X).
% X = 3
% ; X = 4
% ; X = 5.
% ```
between(Lower, Upper, X) :-
must_be(integer, Lower),
must_be(integer, Upper),
@@ -30,6 +55,9 @@ enumerate_nats(I0, N) :-
I1 is I0 + 1,
enumerate_nats(I1, N).
%% gen_nat(?N)
%
% True iff N is a natural number.
gen_nat(N) :-
can_be(integer, N),
( var(N) -> enumerate_nats(0, N)
@@ -44,6 +72,9 @@ enumerate_ints(I0, N) :-
I1 is I0 + 1,
enumerate_ints(I1, N).
%% gen_int(?N)
%
% True iff N is an integer.
gen_int(N) :-
can_be(integer, N),
( var(N) -> enumerate_ints(0, N)
@@ -55,9 +86,24 @@ repeat_integer(N) :-
repeat_integer(N0) :-
N0 > 0, N1 is N0 - 1, repeat_integer(N1).
%% repeat(+N)
%
% Succeeds N times. This predicate is only included for compatibility and *should not be used*
% because it lacks a declarative interpretation.
repeat(N) :-
must_be(integer, N), repeat_integer(N).
%% numlist(?Upper, ?List)
%
% True iff List is the list of integers _[1, ..., Upper]_. Example:
%
% ```
% ?- numlist(X, Y).
% X = 1, Y = [1],
% ; X = 2, Y = [1,2]
% ; X = 3, Y = [1,2,3]
% ; ... .
% ```
numlist(Upper, List) :-
( integer(Upper) -> findall(X, between(1, Upper, X), List)
; List = [_|_], length(List, Upper), findall(X, between(1, Upper, X), List)
@@ -106,5 +152,14 @@ gen_ints(L, U) :-
),
L =< U.
%% numlist(?Lower, ?Upper, ?List).
%
% True iff List is a list of the form _[Lower, ..., Upper]_.
% Example:
%
% ```
% ?- numlist(5, 10, X).
% X = [5,6,7,8,9,10].
% ```
numlist(Lower, Upper, List) :-
gen_ints(Lower, Upper), findall(X, between(Lower, Upper, X), List).

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,18 @@
/** High-level predicates to work with chars and strings
This module contains predicates that relates strings of chars
to other representations, as well as high-level predicates to
read and write chars.
*/
:- module(charsio, [char_type/2,
chars_utf8bytes/2,
get_single_char/1,
get_n_chars/3,
read_line_to_chars/3,
get_line_to_chars/3,
read_from_chars/2,
read_term_from_chars/3,
write_term_to_chars/3,
chars_base64/3]).
@@ -11,6 +20,7 @@
:- use_module(library(iso_ext)).
:- use_module(library(error)).
:- use_module(library(lists)).
:- use_module(library(between)).
:- use_module(library(iso_ext), [partial_string/1,partial_string/3]).
fabricate_var_name(VarType, VarName, N) :-
@@ -65,18 +75,86 @@ extend_var_list_([V|Vs], N, VarList, NewVarList, VarType) :-
).
%% char_type(?Char, ?Type).
%
% Type is one of the categories that Char fits in.
% At least one of the arguments must be ground.
% Possible categories are:
%
% - `alnum`
% - `alpha`
% - `alphabetic`
% - `alphanumeric`
% - `ascii`
% - `ascii_graphic`
% - `ascii_punctuation`
% - `binary_digit`
% - `control`
% - `decimal_digit`
% - `exponent`
% - `graphic`
% - `graphic_token`
% - `hexadecimal_digit`
% - `layout`
% - `lower`
% - `meta`
% - `numeric`
% - `octal_digit`
% - `octet`
% - `prolog`
% - `sign`
% - `solo`
% - `symbolic_control`
% - `symbolic_hexadecimal`
% - `upper`
% - `lower(Lower)`
% - `upper(Upper)`
% - `whitespace`
%
% An example:
%
% ```
% ?- char_type(a, Type).
% Type = alnum
% ; Type = alpha
% ; Type = alphabetic
% ; Type = alphanumeric
% ; Type = ascii
% ; Type = ascii_graphic
% ; Type = hexadecimal_digit
% ; Type = lower
% ; Type = octet
% ; Type = prolog
% ; Type = symbolic_control
% ; Type = lower("a")
% ; Type = upper("A")
% ; false.
% ```
%
% Note that uppercase and lowercase transformations use a string. This is because
% some characters do not map 1:1 between lowercase and uppercase.
char_type(Char, Type) :-
must_be(character, Char),
( ground(Type) ->
( ctype(Type) ->
'$char_type'(Char, Type)
; domain_error(char_type, Type, char_type/2)
)
; ctype(Type),
can_be(character, Char),
( \+ ctype(Type) ->
domain_error(char_type, Type, char_type/2)
; true
),
( ground(Char) ->
ctype(Type),
'$char_type'(Char, Type)
; ground(Type) ->
ccode(Code),
char_code(Char, Code),
'$char_type'(Char, Type)
; must_be(character, Char)
).
% 0xD800 to 0xDFFF are surrogate code points used by UTF-16.
ccode(Code) :- between(0, 0xD7FF, Code).
ccode(Code) :- between(0xE000, 0x10FFFF, Code).
ctype(alnum).
ctype(alpha).
ctype(alphabetic).
@@ -102,27 +180,68 @@ ctype(sign).
ctype(solo).
ctype(symbolic_control).
ctype(symbolic_hexadecimal).
ctype(lower(_)).
ctype(upper(_)).
ctype(upper).
ctype(whitespace).
%% get_single_char(-Char).
%
% Gets a single char from the current input stream.
get_single_char(C) :-
( var(C) -> '$get_single_char'(C)
; atom_length(C, 1) -> '$get_single_char'(C)
; type_error(in_character, C, get_single_char/1)
).
%% 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:
%
% ```
% ?- read_from_chars("f(x,y).", X).
% X = f(x,y).
% ```
read_from_chars(Chars, Term) :-
must_be(chars, Chars),
'$read_term_from_chars'(Chars, Term).
'$read_from_chars'(Chars, Term0),
Term = Term0.
%% 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`.
%
% ```
% ?- read_term_from_chars("f(X,y).", T, [variable_names(['X'=X])]).
% T = f(X,y).
% ```
read_term_from_chars(Chars, Term, Options) :-
must_be(chars, Chars),
builtins:parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term_from_chars/3),
'$read_term_from_chars'(Chars, Term0, Singletons, Variables, VariableNames),
Term = Term0.
%% write_term_to_chars(+Term, +Options, -Chars).
%
% Given a Term which is a Prolog term and a set of options, Chars is
% string representation of that term. 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.
% * `max_depth(+N)` if the term is nested deeper than N, print the reminder as ellipses.
% If N = 0 (default), there's no limit.
% * `numbervars(+Boolean)` if true, replaces `$VAR(N)` variables with letters, in order. Default is false.
% * `quoted(+Boolean)` if true, strings and atoms that need quotes to be valid Prolog syntax, are quoted. Default is false.
% * `variable_names(+List)` assign names to variables in term. List should be a list of terms of format `Name=Var`.
% * `double_quotes(+Boolean)` if true, strings are printed in double quotes rather than with list notation. Default is false.
write_term_to_chars(_, Options, _) :-
var(Options), instantiation_error(write_term_to_chars/3).
write_term_to_chars(Term, Options, Chars) :-
builtins:parse_write_options(Options,
[IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames],
[DoubleQuotes, IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames],
write_term_to_chars/3),
( nonvar(Chars) ->
throw(error(uninstantiation_error(Chars), write_term_to_chars/3))
@@ -131,7 +250,7 @@ write_term_to_chars(Term, Options, Chars) :-
),
term_variables(Term, Vars),
extend_var_list(Vars, VNNames, NewVarNames, numbervars),
'$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth).
'$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth, DoubleQuotes).
% Encodes Ch character to list of Bytes.
char_utf8bytes(Ch, Bytes) :-
@@ -151,6 +270,17 @@ encode(Code, Prefix, Nb) -->
% Maps characters and UTF-8 bytes.
% If Cs is a variable, parses Bs as a list of UTF-8 bytes.
% Otherwise, transform the list of characters Cs to UTF-8 bytes.
%% chars_utf8bytes(?Chars, ?Bytes).
%
% Maps a string made of chars with a list of UTF-8 bytes. Some examples:
%
% ```
% ?- chars_utf8bytes("Prolog", X).
% X = [80,114,111,108,111,103].
% ?- chars_utf8bytes(X, [226, 136, 145]).
% X = "∑".
% ```
chars_utf8bytes(Cs, Bs) :-
var(Cs), must_be(list, Bs) ->
once(phrase(decode_utf8(Cs), Bs))
@@ -177,58 +307,66 @@ continuation(Code, Chars, Nb) --> [Byte],
% each remaining continuation byte (if any) will raise 0xFFFD too
continuation(_, ['\xFFFD\'|T], _) --> [_], decode_utf8(T).
read_line_to_chars(Stream, Cs0, Cs) :-
%% get_line_to_chars(+Stream, -Chars, +InitialChars).
%
% Reads chars from stream Stream until it finds a `\n` character.
% InitialChars will be appended at the end of Chars
get_line_to_chars(Stream, Cs0, Cs) :-
'$get_n_chars'(Stream, 1, Char), % this also works for binary streams
( Char == [] -> Cs0 = Cs
; Char = [C],
Cs0 = [C|Rest],
( C == '\n' -> Rest = Cs
; read_line_to_chars(Stream, Rest, Cs)
; get_line_to_chars(Stream, Rest, Cs)
)
).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Read N characters from Stream.
If N is a variable, read until EOF, unifying N with the number of
characters read.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% get_n_chars(+Stream, ?N, -Chars).
%
% Read N chars from stream Stream. N can be an integer, in that case
% only N chars are read, or a variable, unifying N with the number of chars
% read until it found EOF.
get_n_chars(Stream, N, Cs) :-
can_be(integer, N),
( var(N) ->
read_to_eof(Stream, Cs),
get_to_eof(Stream, Cs),
length(Cs, N)
; N >= 0,
'$get_n_chars'(Stream, N, Cs)
).
read_to_eof(Stream, Cs) :-
'$get_n_chars'(Stream, 512, Cs0),
get_n_chars_wrapper(Stream, N, Cs) :-
'$get_n_chars'(Stream, N, Cs).
get_to_eof(Stream, Cs) :-
catch(get_n_chars_wrapper(Stream, 512, Cs0),
error(syntax_error(unexpected_end_of_file), _),
Cs0 = []),
( Cs0 == [] -> Cs = []
; partial_string(Cs0, Cs, Rest),
read_to_eof(Stream, Rest)
get_to_eof(Stream, Rest)
).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Relation between a list of characters Cs and its Base64 encoding Bs,
also a list of characters.
At least one of the arguments must be instantiated.
Options are:
- padding(Boolean)
Whether to use padding: true (the default) or false.
- charset(C)
Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5).
Example:
?- chars_base64("hello", Bs, []).
Bs = "aGVsbG8=".
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% chars_base64(?Chars, ?Base64, +Options).
%
% Relation between a list of characters Cs and its Base64 encoding Bs,
% also a list of characters.
%
% At least one of the arguments must be instantiated.
%
% Options are:
%
% - `padding(Boolean)`
% Whether to use padding: true (the default) or false.
% - `charset(C)`
% Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5).
%
% Example:
%
% ```
% ?- chars_base64("hello", Bs, []).
% Bs = "aGVsbG8=".
% ```
chars_base64(Cs, Bs, Options) :-
must_be(list, Options),

View File

@@ -1,10 +1,29 @@
/* CLP(B): Constraint Logic Programming over Boolean Variables
Copyright (C): 2019 Markus Triska
All rights reserved.
Author: Markus Triska
E-mail: triska@metalevel.at
WWW: http://www.metalevel.at
WWW: https://www.metalevel.at
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
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
@@ -17,8 +36,8 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- module(clpb, [op(300, fy, ~),
op(500, yfx, #),
sat/1,
op(500, yfx, #),
sat/1,
taut/2,
labeling/1,
sat_count/2,
@@ -91,6 +110,46 @@ domain_error(Expectation, Term) :-
type_error(Expectation, Term) :-
type_error(Expectation, Term, unknown(Term)-1).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Compatibility predicates.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- meta_predicate(include(1, ?, ?)).
include(_, [], []).
include(Goal, [L|Ls0], Ls) :-
( call(Goal, L) ->
Ls = [L|Rest]
; Ls = Rest
),
include(Goal, Ls0, Rest).
:- meta_predicate(exclude(1, ?, ?)).
exclude(_, [], []).
exclude(Goal, [L|Ls0], Ls) :-
( call(Goal, L) ->
Ls = Rest
; Ls = [L|Rest]
),
exclude(Goal, Ls0, Rest).
:- meta_predicate(partition(2,?,?,?,?)).
partition(_, [], [], [], []).
partition(Pred, [H|T], L, E, G) :-
call(Pred, H, Diff),
partition_(Diff, H, Pred, T, L, E, G).
partition_(<, H, Pred, T, [H|Rest], E, G) :-
partition(Pred, T, Rest, E, G).
partition_(=, H, Pred, T, L, [H|Rest], G) :-
partition(Pred, T, L, Rest, G).
partition_(>, H, Pred, T, L, E, [H|Rest]) :-
partition(Pred, T, L, E, Rest).
:- meta_predicate(partition(1,?,?,?)).
partition(Pred, Ls0, As, Bs) :-
include(Pred, Ls0, As),
exclude(Pred, Ls0, Bs).
@@ -105,6 +164,262 @@ goal_expansion(del_attr(Var, Module), (var(Var) -> put_atts(Var, -Access);true))
Access =.. [Module,_].
/** Constraint Logic Programming over Boolean variables
## Introduction
This library provides CLP(B), Constraint Logic Programming over
Boolean variables. It can be used to model and solve combinatorial
problems such as verification, allocation and covering tasks.
CLP(B) is an instance of the general CLP(_X_) scheme,
extending logic programming with reasoning over specialised domains.
The implementation is based on reduced and ordered Binary Decision
Diagrams (BDDs).
Benchmarks and usage examples of this library are available from:
[*https://www.metalevel.at/clpb/*](https://www.metalevel.at/clpb/)
## Boolean expressions
A _Boolean expression_ is one of:
| `0` | false |
| `1` | true |
| _variable_ | unknown truth value |
| _atom_ | universally quantified variable |
| `~` _Expr_ | logical NOT |
| _Expr_ `+` _Expr_ | logical OR |
| _Expr_ `*` _Expr_ | logical AND |
| _Expr_ `#` _Expr_ | exclusive OR |
| _Var_ `^` _Expr_ | existential quantification |
| _Expr_ `=:=` _Expr_ | equality |
| _Expr_ `=\=` _Expr_ | disequality (same as #) |
| _Expr_ `=<` _Expr_ | less or equal (implication) |
| _Expr_ `>=` _Expr_ | greater or equal |
| _Expr_ `<` _Expr_ | less than |
| _Expr_ `>` _Expr_ | greater than |
| `card(Is,Exprs)` | cardinality constraint (_see below_) |
| `+(Exprs)` | n-fold disjunction (_see below_) |
| `*(Exprs)` | n-fold conjunction (_see below_) |
where _Expr_ again denotes a Boolean expression.
The Boolean expression `card(Is,Exprs)` is true iff the number of true
expressions in the list `Exprs` is a member of the list `Is` of
integers and integer ranges of the form `From-To`. For example, to
state that precisely two of the three variables `X`, `Y` and `Z` are
`true`, you can use `sat(card([2],[X,Y,Z]))`.
`+(Exprs)` and `*(Exprs)` denote, respectively, the disjunction and
conjunction of all elements in the list `Exprs` of Boolean
expressions.
Atoms denote parametric values that are universally quantified. All
universal quantifiers appear implicitly in front of the entire
expression. In residual goals, universally quantified variables always
appear on the right-hand side of equations. Therefore, they can be
used to express functional dependencies on input variables.
## Interface predicates
The most frequently used CLP(B) predicates are:
* `sat(+Expr)`
True iff the Boolean expression Expr is satisfiable.
* `taut(+Expr, -T)`
If Expr is a tautology with respect to the posted constraints, succeeds
with *T = 1*. If Expr cannot be satisfied, succeeds with *T = 0*.
Otherwise, it fails.
* `labeling(+Vs)`
Assigns truth values to the variables Vs such that all constraints
are satisfied.
The unification of a CLP(B) variable _X_ with a term _T_ is equivalent
to posting the constraint sat(X=:=T).
## Examples
Here is an example session with a few queries and their answers:
```
?- use_module(library(clpb)).
true.
?- sat(X*Y).
X = 1, Y = 1.
?- sat(X * ~X).
false.
?- taut(X * ~X, T).
T = 0, clpb:sat(X=:=X).
?- sat(X^Y^(X+Y)).
clpb:sat(X=:=X), clpb:sat(Y=:=Y).
?- sat(X*Y + X*Z), labeling([X,Y,Z]).
X = 1, Y = 0, Z = 1
; X = 1, Y = 1, Z = 0
; X = 1, Y = 1, Z = 1.
?- sat(X =< Y), sat(Y =< Z), taut(X =< Z, T).
T = 1, clpb:sat(X=:=X*Y), clpb:sat(Y=:=Y*Z).
?- sat(1#X#a#b).
clpb:sat(X=:=a#b).
```
The pending residual goals constrain remaining variables to Boolean
expressions and are declaratively equivalent to the original query.
The last example illustrates that when applicable, remaining variables
are expressed as functions of universally quantified variables.
## Obtaining BDDs
By default, CLP(B) residual goals appear in (approximately) algebraic
normal form (ANF). This projection is often computationally expensive.
We can assert `clpb:clpb_residuals(bdd)` to see the BDD representation
of all constraints. This results in faster projection to residual
goals, and is also useful for learning more about BDDs. For example:
```
?- asserta(clpb:clpb_residuals(bdd)).
true.
?- sat(X#Y).
node(3)- (v(X, 0)->node(2);node(1)),
node(1)- (v(Y, 1)->true;false),
node(2)- (v(Y, 1)->false;true).
```
Note that this representation cannot be pasted back on the toplevel,
and its details are subject to change. Use copy_term/3 to obtain
such answers as Prolog terms.
The variable order of the BDD is determined by the order in which the
variables first appear in constraints. To obtain different orders,
we can for example use:
```
?- sat(+[1,Y,X]), sat(X#Y).
node(3)- (v(Y, 0)->node(2);node(1)),
node(1)- (v(X, 1)->true;false),
node(2)- (v(X, 1)->false;true).
```
## Enabling monotonic CLP(B)
In the default execution mode, CLP(B) constraints are _not_ monotonic.
This means that _adding_ constraints can yield new solutions. For
example:
```
?- sat(X=:=1), X = 1+0.
false.
?- X = 1+0, sat(X=:=1), X = 1+0.
X = 1+0.
```
This behaviour is highly problematic from a logical point of view, and
it may render [*declarative
debugging*](https://www.metalevel.at/prolog/debugging)
techniques inapplicable.
Assert `clpb:monotonic` to make CLP(B) *monotonic*. If this mode is
enabled, then you must wrap CLP(B) variables with the functor
`v/1`. For example:
```
?- asserta(clpb:monotonic).
true.
?- sat(v(X)=:=1#1).
X = 0.
```
## Example: Pigeons
In this example, we are attempting to place _I_ pigeons into _J_ holes
in such a way that each hole contains at most one pigeon. One
interesting property of this task is that it can be formulated using
only _cardinality constraints_ (`card/2`). Another interesting aspect
is that this task has no short resolution refutations in general.
In the following, we use [*Prolog DCG
notation*](https://www.metalevel.at/prolog/dcg) to describe a
list `Cs` of CLP(B) constraints that must all be satisfied.
```
:- use_module(library(clpb)).
:- use_module(library(clpz)).
:- use_module(library(lists)).
:- use_module(library(dcgs)).
pigeon(I, J, Rows, Cs) :-
length(Rows, I), length(Row, J),
maplist(same_length(Row), Rows),
transpose(Rows, TRows),
phrase((all_cards(Rows,[1]),all_cards(TRows,[0,1])), Cs).
all_cards([], _) --> [].
all_cards([Ls|Lss], Cs) --> [card(Cs,Ls)], all_cards(Lss, Cs).
```
Example queries:
```
?- pigeon(9, 8, Rows, Cs), sat(*(Cs)).
false.
?- pigeon(2, 3, Rows, Cs), sat(*(Cs)),
append(Rows, Vs), labeling(Vs),
maplist(portray_clause, Rows).
[0,0,1].
[0,1,0].
etc.
```
## Example: Boolean circuit
Consider a Boolean circuit that express the Boolean function =|XOR|=
with 4 =|NAND|= gates. We can model such a circuit with CLP(B)
constraints as follows:
```
:- use_module(library(clpb)).
nand_gate(X, Y, Z) :- sat(Z =:= ~(X*Y)).
xor(X, Y, Z) :-
nand_gate(X, Y, T1),
nand_gate(X, T1, T2),
nand_gate(Y, T1, T3),
nand_gate(T2, T3, Z).
```
Using universally quantified variables, we can show that the circuit
does compute =|XOR|= as intended:
```
?- xor(x, y, Z).
clpb:sat(Z=:=x#y).
```
## Acknowledgments
The interface predicates of this library follow the example of
[*SICStus Prolog*](https://sicstus.sics.se).
Use SICStus Prolog for higher performance in many cases.
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Each CLP(B) variable belongs to exactly one BDD. Each CLP(B)
variable gets an attribute (in module "clpb") of the form:
@@ -191,6 +506,10 @@ non_monotonic(X) :-
; true
).
:- meta_predicate(bdd_nodes(1, ?, ?)).
:- meta_predicate(bdd_nodes_(1, ?, ?, ?)).
:- meta_predicate(with_aux(1, ?)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Rewriting to canonical expressions.
Atoms are converted to variables with a special attribute.
@@ -831,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) ->
@@ -932,7 +1252,7 @@ bdd_restriction_(Node, VI, Value, Res) -->
node_id(Node, ID) },
( { I0 =:= VI } ->
( { Value =:= 0 } -> { Res = Low }
; { Value =:= 1 } -> { Res = High }
; { Res = High }
)
; { I0 > VI } -> { Res = Node }
; state(G0), { get_assoc(ID, G0, Res) } -> []
@@ -1108,19 +1428,17 @@ indomain(1).
%
% Examples:
%
% ==
% ```
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
% Vs = [A, B],
% Count = 3,
% sat(A=:=A*B).
% Vs = [A,B], Count = 3, clpb:sat(A=:=A*B).
%
% ?- length(Vs, 120),
% sat_count(+Vs, CountOr),
% sat_count(*(Vs), CountAnd).
% Vs = [...],
% CountOr = 1329227995784915872903807060280344575,
% CountAnd = 1.
% ==
% Vs = [...],
% CountOr = 1329227995784915872903807060280344575,
% CountAnd = 1.
% ```
@@ -1233,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.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -1248,7 +1570,7 @@ random_bindings(VNum, Node) -->
% linear objective function over Boolean variables Vs with integer
% coefficients Weights. This predicate assigns 0 and 1 to the
% variables in Vs such that all stated constraints are satisfied, and
% Maximum is the maximum of sum(Weight_i*V_i) over all admissible
% Maximum is the maximum of `sum(Weight_i*V_i)` over all admissible
% assignments. On backtracking, all admissible assignments that
% attain the optimum are generated.
%
@@ -1257,10 +1579,10 @@ random_bindings(VNum, Node) -->
%
% Example:
%
% ==
% ```
% ?- sat(A#B), weighted_maximum([1,2,1], [A,B,C], Maximum).
% A = 0, B = 1, C = 1, Maximum = 3.
% ==
% A = 0, B = 1, C = 1, Maximum = 3.
% ```
weighted_maximum(Ws, Vars, Max) :-
must_be(list(integer), Ws),
@@ -1373,14 +1695,14 @@ skip_to_var_(Var, Weight, [Var0-Weight0|VWs0], VWs) -->
attribute_goals(Var) -->
{ var_index_root(Var, _, Root) },
!,
( { root_get_formula_bdd(Root, Formula, BDD) } ->
{ del_bdd(Root) },
( { clpb_residuals(bdd) } ->
{ bdd_nodes(BDD, Nodes),
phrase(nodes(Nodes), Ns) },
[clpb:'$clpb_bdd'(Ns)]
; { prepare_global_variables(BDD),
phrase(sat_ands(Formula), Ands0),
; { phrase(sat_ands(Formula), Ands0),
ands_fusion(Ands0, Ands),
maplist(formula_anf, Ands, ANFs0),
sort(ANFs0, ANFs1),
@@ -1400,39 +1722,24 @@ attribute_goals(Var) -->
booleans(RestVs)
; boolean(Var) % the variable may have occurred only in taut/2
).
attribute_goals(Var) -->
{ get_atts(Var, clpb_max(_)),
!,
put_atts(Var, -clpb_max(_)) }.
attribute_goals(Var) -->
{ get_atts(Var, clpb_bdd(BDD)),
ground(BDD),
put_atts(Var, -clpb_bdd(_)) }.
del_clpb(Var) :-
del_attr(Var, clpb),
del_attr(Var, clpb_hash).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
To make residual projection work with recorded constraints, the
global counters must be adjusted so that new variables and nodes
also get new IDs. Also, clpb_next_id/2 is used to actually create
these counters, because creating them with b_setval/2 would make
them [] on backtracking, which is quite unfortunate in itself.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
del_attr(Var, clpb_hash),
del_attr(Var, clpb_atom).
b_setval(K, T) :- bb_b_put(K, T).
nb_setval(K, T) :- bb_put(K, T).
b_getval(K, T) :- bb_get(K, T).
prepare_global_variables(BDD) :-
clpb_next_id('$clpb_next_var', V0),
clpb_next_id('$clpb_next_node', N0),
bdd_nodes(BDD, Nodes),
foldl(max_variable_node, Nodes, V0-N0, MaxV0-MaxN0),
MaxV is MaxV0 + 1,
MaxN is MaxN0 + 1,
b_setval('$clpb_next_var', MaxV),
b_setval('$clpb_next_node', MaxN).
max_variable_node(Node, V0-N0, V-N) :-
node_id(Node, N1),
node_varindex(Node, V1),
N is max(N0,N1),
V is max(V0,V1).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Fuse formulas that share the same variables into single conjunctions.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
@@ -1557,7 +1864,8 @@ booleans([B|Bs]) --> boolean(B), booleans(Bs).
boolean(Var) -->
{ del_clpb(Var) },
( { get_attr(Var, clpb_omit_boolean, true) } -> []
( { get_attr(Var, clpb_omit_boolean, true) } ->
{ put_atts(Var, -clpb_omit_boolean(_)) }
; [clpb:sat(Var =:= Var)]
).
@@ -1659,49 +1967,3 @@ clpb_atom_var(Atom, Var) :-
put_assoc(Atom, A0, Var, A),
b_setval('$clpb_atoms', A)
).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Compatibility predicates.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
include(Goal, List, Is) :-
include_(List, Goal, Is).
include_([], _, []).
include_([X1|Xs1], P, Is) :-
( call(P, X1)
-> Is = [X1|Is1]
; Is = Is1
),
include_(Xs1, P, Is1).
exclude(Goal, List, Is) :-
exclude_(List, Goal, Is).
exclude_([], _, []).
exclude_([X1|Xs1], P, Is) :-
( call(P, X1)
-> Is = Is1
; Is = [X1|Is1]
),
exclude_(Xs1, P, Is1).
partition(Pred, List, Less, Equal, Greater) :-
partition_(List, Pred, Less, Equal, Greater).
partition_([], _, [], [], []).
partition_([H|T], Pred, L, E, G) :-
call(Pred, H, Diff),
partition_(Diff, H, Pred, T, L, E, G).
partition_(<, H, Pred, T, [H|Rest], E, G) :-
partition_(T, Pred, Rest, E, G).
partition_(=, H, Pred, T, L, [H|Rest], G) :-
partition_(T, Pred, L, Rest, G).
partition_(>, H, Pred, T, L, E, [H|Rest]) :-
partition_(T, Pred, L, E, Rest).

File diff suppressed because it is too large Load Diff

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),

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,67 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Predicates for parsing CSV data
/** Predicates for parsing CSV data
## Read CSV files.
Read csv files
Only two options with default values:
Only two options with default values :
- token_separator(',')
- with_header(true)
- `token_separator(',')`
- `with_header(true)`
Examples
### Examples:
* parsing a csv string:
Parsing a CSV string:
?- use_module(library(csv)).
?- use_module(library(dcgs)).
?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three").
Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]).
```
?- use_module(library(csv)).
?- use_module(library(dcgs)).
?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three").
Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]).
```
* with some options:
With some options:
?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three").
Data = frame([],[["one",2,[],"three"]]).
```
?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three").
Data = frame([],[["one",2,[],"three"]]).
```
* parsing a csv file:
Parsing a CSV file:
?- use_module(library(csv)).
?- use_module(library(pio)).
?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv').
```
?- use_module(library(csv)).
?- use_module(library(pio)).
?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv').
```
## Write CSV files
Write csv files
Four options with default values :
Four options with default values :
- line_separator('\n')
- token_separator(',')
- with_header(true)
- null_value(empty)
- `line_separator('\n')`
- `token_separator(',')`
- `with_header(true)`
- `null_value(empty)`
Examples
### Examples
* writing a csv file:
Writing a CSV file:
?- use_module(library(csv)).
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])).
```
?- use_module(library(csv)).
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])).
```
* with some options
With some options
?- use_module(library(csv)).
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]]), [with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
```
?- use_module(library(csv)).
?- write_csv('./test.csv', frame(
["col1","col2","col3","col4"],
[["one",2,[],"three"]]
),
[with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]).
```
*/
:- module(csv, [
parse_csv//1,
@@ -208,7 +221,7 @@ row([X | Y], Opt) -->
!,
( separator(Opt) ->
row(Y, Opt)
; end_token ->
; end_token,
{ Y = [] }).

View File

@@ -1,10 +1,23 @@
/** Support for Definite Clause Grammars.
A Prolog definite clause grammar (DCG) describes a sequence. Operationally, DCGs
can be used to parse, generate, complete and check sequences manifested as lists.
Check [The Power of Prolog chapter on DCGs](https://www.metalevel.at/prolog/dcg)
to learn more about them.
*/
:- module(dcgs,
[op(1105, xfy, '|'),
phrase/2,
phrase/3,
phrase//2,
phrase//3,
seq//1,
seqq//1,
... //0
... //0,
(-->)/2
]).
:- use_module(library(error)).
@@ -12,13 +25,56 @@
:- 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(3, ?, ?, ?)).
:- meta_predicate(phrase(4, ?, ?, ?, ?)).
:- meta_predicate(','(2, 2, ?, ?)).
:- meta_predicate(;(2, 2, ?, ?)).
%% phrase(+Body, ?Ls).
%
% True iff Body describes the list Ls. Body must be a DCG body.
% It is equivalent to `phrase(Body, Ls, [])`.
%
% Examples:
%
% ```
% as --> [].
% as --> [a], as.
%
% ?- phrase(as, Ls).
% Ls = []
% ; Ls = "a"
% ; Ls = "aa"
% ; Ls = "aaa"
% ; ... .
%
% ?- phrase(as, "aaa").
% true.
% ```
phrase(GRBody, S0) :-
phrase(GRBody, S0, []).
%% phrase(+Body, ?Ls, ?Ls0).
%
% True iff Body describes part of the list Ls and the rest of Ls is Ls0.
%
% Example:
%
% ```
% ?- phrase(seq(X), "aaa", Y).
% X = [], Y = "aaa"
% ; X = "a", Y = "aa"
% ; X = "aa", Y = "a"
% ; X = "aaa", Y = [].
% ```
phrase(GRBody, S0, S) :-
strip_module(GRBody, M, GRBody1),
( var(GRBody) ->
@@ -30,12 +86,33 @@ phrase(GRBody, S0, S) :-
; call(M:GRBody1, S0, S)
).
module_call_qualified(M, Call, Call1) :-
( nonvar(M) -> Call1 = M:Call
; Call = Call1
phrase(GRBody, Arg, S0, S) :-
strip_module(GRBody, M, GRBody1),
( var(GRBody) ->
instantiation_error(phrase/4)
; nonvar(GRBody1),
GRBody1 =.. GRBodys1,
append(GRBodys1, [Arg], GRBodys2),
GRBody2 =.. GRBodys2,
dcg_constr(GRBody2),
dcg_body(GRBody2, S0, S, GRBody3) ->
call(M:GRBody3)
; call(M:GRBody1, Arg, S0, S)
).
phrase(GRBody, Arg1, Arg2, S0, S) :-
strip_module(GRBody, M, GRBody1),
( var(GRBody) ->
instantiation_error(phrase/5)
; nonvar(GRBody1),
GRBody1 =.. GRBodys1,
append(GRBodys1, [Arg1,Arg2], GRBodys2),
GRBody2 =.. GRBodys2,
dcg_constr(GRBody2),
dcg_body(GRBody2, S0, S, GRBody3) ->
call(M:GRBody3)
; call(M:GRBody1, Arg1, Arg2, S0, S)
).
% The same version of the below two dcg_rule clauses, but with module scoping.
dcg_rule(( M:NonTerminal, Terminals --> GRBody ), ( M:Head :- Body )) :-
@@ -63,7 +140,10 @@ dcg_rule(( NonTerminal --> GRBody ), ( Head :- Body )) :-
dcg_non_terminal(NonTerminal, S0, S, Goal) :-
NonTerminal =.. NonTerminalUniv,
append(NonTerminalUniv, [S0, S], GoalUniv),
Goal =.. GoalUniv.
( callable(NonTerminal) ->
Goal =.. GoalUniv
; Goal = NonTerminal % let call/N throw an error instead of throwing one here.
).
dcg_terminals(Terminals, S0, S, S0 = List) :-
append(Terminals, S, List).
@@ -78,11 +158,12 @@ dcg_body(GRBody, S0, S, Body) :-
dcg_body(NonTerminal, S0, S, Goal1) :-
nonvar(NonTerminal),
\+ dcg_constr(NonTerminal),
NonTerminal \= ( _ -> _ ),
NonTerminal \= ( \+ _ ),
loader:strip_module(NonTerminal, M, NonTerminal0),
dcg_non_terminal(NonTerminal0, S0, S, Goal0),
module_call_qualified(M, Goal0, Goal1).
( functor(NonTerminal, (:), 2) ->
Goal1 = M:Goal0
; Goal1 = Goal0
).
% The following constructs in a grammar rule body
% are defined in the corresponding subclauses.
@@ -94,9 +175,13 @@ dcg_constr(( _'|'_ )). % 7.14.6 - alternative
dcg_constr({_}). % 7.14.7
dcg_constr(call(_)). % 7.14.8
dcg_constr(phrase(_)). % 7.14.9
dcg_constr(phrase(_,_)). % extension of 7.14.9
dcg_constr(phrase(_,_,_)). % extension of 7.14.9
dcg_constr(!). % 7.14.10
%% dcg_constr(\+ _). % 7.14.11 - not (existence implementation dep.)
dcg_constr((_->_)). % 7.14.12 - if-then (existence implementation dep.)
dcg_constr(\+ G_0) :- % 7.14.11 - not (existence implementation def.)
throw(error(representation_error(dcg_body), [culprit- (\+ G_0)])).
dcg_constr((If->Then)) :- % 7.14.12 - if-then (existence implementation def.)
throw(error(representation_error(dcg_body), [culprit- (If->Then)])).
% The principal functor of the first argument indicates
% the construct to be expanded.
@@ -121,16 +206,24 @@ dcg_cbody(( GREither '|' GROr ), S0, S, ( Either ; Or )) :-
dcg_cbody({Goal}, S0, S, ( Goal, S0 = S )).
dcg_cbody(call(Cont), S0, S, call(Cont, S0, S)).
dcg_cbody(phrase(Body), S0, S, phrase(Body, S0, S)).
dcg_cbody(phrase(Body, Arg), S0, S, phrase(Body, Arg, S0, S)).
dcg_cbody(phrase(Body, Arg1, Arg2), S0, S, phrase(Body, Arg1, Arg2, S0, S)).
dcg_cbody(!, S0, S, ( !, S0 = S )).
dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )).
% dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )).
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)//
%
% Describes a sequence
seq(Xs, Cs0,Cs) :-
var(Xs),
@@ -141,10 +234,14 @@ seq(Xs, Cs0,Cs) :-
seq([]) --> [].
seq([E|Es]) --> [E], seq(Es).
%% seqq(SeqOfSeqs)//
%
% Describes a sequence of sequences
seqq([]) --> [].
seqq([Es|Ess]) --> seq(Es), seqq(Ess).
%% ...//
%
% Describes an arbitrary number of elements
...(Cs0,Cs) :-
Cs0 == [],
@@ -152,8 +249,13 @@ 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),
error(representation_error(dcg_body), Context)).
error_goal(E, _) :- throw(E).
user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
@@ -163,6 +265,20 @@ user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
E,
dcgs:error_goal(E, GRBody1)
),
module_call_qualified(M, GRBody1, GRBody2).
( 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
).
user:goal_expansion(phrase(GRBody, S), phrase(GRBody, S, [])).
% (-->)/2 behaves as if it didn't exist. We export (and define) it
% only so that clauses for (-->)/2 cannot be asserted when
% library(dcgs) is loaded.
(_-->_) :- throw(error(existence_error(procedure,(-->)/2),(-->)/2)).

View File

@@ -1,4 +1,22 @@
% Source: https://stackoverflow.com/a/30791637
/** Declarative debugging.
This library provides three predicates with associated operators.
The operators can be placed in front of goals to debug Prolog
programs.
Of these predicates, the most frequently used is `(*)/1`, with
associated prefix operator `*` (star). Placing `*` in front of a
goal means to _generalize away_ the goal. `* Goal` acts as if `Goal`
did not appear at all in the source code. It is declaratively
equivalent to _commenting out_ the goal, and easier to write,
because `*` can also be placed in front of the last goal in a clause
without any additional changes.
Source: [https://stackoverflow.com/a/30791637](https://stackoverflow.com/a/30791637)
*/
:- module(debug, [
op(900, fx, $),
@@ -11,16 +29,28 @@
:- 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)
%
% Portray exceptions thrown by Goal.
$-(G_0) :-
catch(G_0, Ex, ( portray_clause(exception:Ex:G_0), throw(Ex) ) ).
%% $(Goal)
%
% Provide a _trace_ for calls of Goal.
$(G_0) :-
portray_clause(call:G_0),
$-G_0,
portray_clause(exit:G_0).
%% *(Goal)
%
% Generalize away Goal.
*(_).

View File

@@ -1,7 +1,160 @@
:- module(diag, [wam_instructions/2]).
:- module(diag, [wam_instructions/2, inlined_instructions/2]).
/** Diagnostics library
The predicate `wam_instructions/2` _decompiles_ a predicate so that
we can inspect its Warren Abstract Machine (WAM) instructions.
In this way, we can verify and reason about compiled programs,
and detect opportunities for optimization.
For example, we have:
```
?- use_module(library(lists)).
true.
?- use_module(library(diag)).
true.
?- use_module(library(format)).
true.
?- wam_instructions(append/3, Is),
maplist(portray_clause, Is).
switch_on_term(1,external(1),external(2),external(6),fail).
try_me_else(4).
get_constant(level(shallow),[],x(1)).
get_value(x(2),3).
proceed.
trust_me(0).
get_list(level(shallow),x(1)).
unify_variable(x(4)).
unify_variable(x(1)).
get_list(level(shallow),x(3)).
unify_value(x(4)).
unify_variable(x(3)).
execute(append,3).
Is = [switch_on_term(1,external(1),external(2),external(6),fail)|...].
```
`inlined_instructions/2` decompiles predicates at the code offset in
its first argument.
For example, given the program
```
?- [user].
:- use_module(library(clpz)).
all_eq(Vs, E) :- maplist(#=(E), Vs).
```
we inspect the code of `all_eqs/2` using `wam_instructions/2`,
revealing:
```
?- wam_instructions(all_eq/2, Is),
maplist(portray_clause, Is).
put_structure('$aux',2,x(3)).
set_local_value(x(2)).
set_void(1).
set_constant('$index_ptr'(115334)).
get_variable(x(4),1).
put_structure(:,2,x(1)).
set_constant(user).
set_local_value(x(3)).
get_variable(x(5),2).
put_value(x(4),2).
execute(maplist,2).
Is = [put_structure('$aux',2,x(3)),set_local_value(x(2)),set_void(1),set_constant('$index_ptr'(115334)),get_variable(x(4),1),put_structure(:,2,x(1)),set_constant(user),set_local_value(x(3)),get_variable(x(5),2),put_value(x(4),2),execute(maplist,2)].
```
The `'$index_ptr(115334)` functor gives a code offset to an inlined
predicate compiled for the use of maplist/2. `inlined_instructions/2`
can be used to decompile its source code:
```
?- inlined_instructions(115334, Is),
maplist(portray_clause, Is).
allocate(1).
get_level(y(1)).
get_variable(x(5),2).
put_value(x(3),2).
get_variable(x(6),3).
put_value(x(5),3).
put_unsafe_value(1,4).
deallocate.
jmp_by_execute(1).
try_me_else(8).
call(integer,1).
neck_cut.
get_variable(x(5),1).
put_value(x(2),1).
get_variable(x(6),2).
put_value(x(5),2).
jmp_by_execute(7).
try_me_else(12).
allocate(3).
get_level(y(1)).
get_variable(y(3),1).
get_variable(y(2),2).
call_default(true,0).
call(var,1).
cut(y(1)).
put_unsafe_value(3,1).
put_unsafe_value(2,2).
deallocate.
execute_default(is,2).
default_retry_me_else(4).
call(integer,1).
neck_cut.
execute(=:=,2).
default_trust_me(0).
allocate(2).
get_variable(y(1),1).
get_variable(y(2),3).
put_value(y(2),1).
call_default(is,2).
put_unsafe_value(2,1).
put_unsafe_value(1,2).
deallocate.
execute_default(clpz_equal,2).
default_retry_me_else(4).
call(integer,1).
neck_cut.
jmp_by_execute(29).
try_me_else(12).
allocate(3).
get_level(y(1)).
get_variable(y(3),1).
get_variable(y(2),2).
call_default(true,0).
call(var,1).
cut(y(1)).
put_unsafe_value(3,1).
put_unsafe_value(2,2).
deallocate.
execute_default(is,2).
default_trust_me(0).
allocate(2).
get_variable(y(2),1).
get_variable(y(1),3).
put_value(y(1),1).
call_default(is,2).
put_unsafe_value(2,1).
put_unsafe_value(1,2).
deallocate.
execute_default(clpz_equal,2).
default_trust_me(0).
execute_default(clpz_equal,2).
Is = [allocate(1),get_level(y(1)),get_variable(x(5),2),put_value(x(3),2),get_variable(x(6),3),put_value(x(5),3),put_unsafe_value(1,4),deallocate,jmp_by_execute(1),try_me_else(8),call(integer,1),neck_cut,get_variable(x(5),1),put_value(x(2),1),get_variable(x(6),2),put_value(x(5),2),jmp_by_execute(7),try_me_else(12),allocate(3),get_level(...),...].
```
*/
:- use_module(library(error)).
%% wam_instructions(+PI, -Instrs)
%
% _Instrs_ are the WAM instructions corresponding to predicate indicator _PI_.
wam_instructions(Clause, Listing) :-
( nonvar(Clause) ->
@@ -13,6 +166,16 @@ wam_instructions(Clause, Listing) :-
; throw(error(instantiation_error, wam_instructions/2))
).
%% inlined_instructions(+IndexPtr, -Instrs)
%
% _Instrs_ are the WAM instructions corresponding to code offset _IndexPtr_.
inlined_instructions(IndexPtr, Listing) :-
must_be(integer, IndexPtr),
( IndexPtr >= 0 ->
'$inlined_instructions'(IndexPtr, Listing)
; throw(error(domain_error(not_less_than_zero, IndexPtr), inlined_instructions/2))
).
fetch_instructions(Module, Name, Arity, Listing) :-
must_be(atom, Module),

View File

@@ -1,8 +1,13 @@
/**
Provides predicate `dif/2`. `dif/2` is a constraint that is true only if both of its
arguments are different terms.
*/
:- module(dif, [dif/2]).
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- use_module(library(lists), [append/3]).
:- use_module(library(lists), [append/3, maplist/3]).
:- attribute dif/1.
@@ -18,6 +23,34 @@ dif_set_variables([Var|Vars], X, Y) :-
put_dif_att(Var, X, Y),
dif_set_variables(Vars, X, Y).
remove_goal([], _, []).
remove_goal([G0|G0s], Goal0, Goals) :-
( G0 == Goal0 ->
remove_goal(G0s, Goal0, Goals)
; Goals = [G0|Goals1],
remove_goal(G0s, Goal0, Goals1)
).
vars_remove_goal([], _).
vars_remove_goal([Var|Vars], Goal0) :-
( get_atts(Var, +dif(Goals0)) ->
remove_goal(Goals0, Goal0, Goals),
( Goals = [] ->
put_atts(Var, -dif(_))
; put_atts(Var, +dif(Goals))
)
; true
),
vars_remove_goal(Vars, Goal0).
reinforce_goal(Goal0, Goal) :-
Goal = (
term_variables(Goal0, Vars),
dif:vars_remove_goal(Vars, Goal0),
Goal0 = (L \== R),
dif:dif(L, R)
).
append_goals([], _).
append_goals([Var|Vars], Goals) :-
( get_atts(Var, +dif(VarGoals)) ->
@@ -29,31 +62,46 @@ append_goals([Var|Vars], Goals) :-
append_goals(Vars, Goals).
verify_attributes(Var, Value, Goals) :-
( get_atts(Var, +dif(Goals)) ->
( get_atts(Var, +dif(Goals0)) ->
term_variables(Value, ValueVars),
append_goals(ValueVars, Goals)
append_goals(ValueVars, Goals0),
maplist(reinforce_goal, Goals0, Goals)
; Goals = []
).
% Probably the world's worst dif/2 implementation. I'm open to
% suggestions for improvement.
%% dif(?X, ?Y).
%
% True iff X and Y are different terms. Unlike `\=/2`, `dif/2` is more declarative because if X and Y can
% unify but they're not yet equal, the decision is delayed, and prevents X and Y to become equal later.
% Examples:
%
% ```
% ?- dif(a, a).
% false.
% ?- dif(a, b).
% true.
% ?- dif(X, b).
% dif:dif(X,b).
% ?- dif(X, b), X = b.
% false.
% ```
dif(X, Y) :-
X \== Y,
( X \= Y -> true
; ( term_variables(X, XVars),
term_variables(Y, YVars),
dif_set_variables(XVars, X, Y),
dif_set_variables(YVars, X, Y)
)
; term_variables(dif(X,Y), Vars),
dif_set_variables(Vars, X, Y)
).
gather_dif_goals([]) --> [].
gather_dif_goals([(X \== Y) | Goals]) -->
[dif:dif(X, Y)],
gather_dif_goals(Goals).
gather_dif_goals(_, []) --> [].
gather_dif_goals(V, [(X \== Y) | Goals]) -->
( { term_variables(X-Y, [V0 | _]),
V == V0 } ->
[dif:dif(X, Y)]
; []
),
gather_dif_goals(V, Goals).
attribute_goals(X) -->
{ get_atts(X, +dif(Goals)) },
gather_dif_goals(Goals),
gather_dif_goals(X, Goals),
{ put_atts(X, -dif(_)) }.

View File

@@ -1,44 +1,49 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2018-2022 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,12 +89,13 @@ 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) :-
( \+ ground(Term) ->
instantiation_error(must_be/2)
; \+ acyclic_term(Term) ->
type_error(term, Term, must_be/2)
; true
( acyclic_term(Term) ->
( ground(Term) -> true
; instantiation_error(must_be/2)
)
; type_error(term, Term, must_be/2)
).
% We cannot use maplist(must_be(character), Cs), because library(lists)
@@ -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]))).

218
src/lib/ffi.pl Normal file
View File

@@ -0,0 +1,218 @@
:- 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
This module contains predicates used to call native code (exposed by the C ABI).
It uses [libffi](https://sourceware.org/libffi/) under the hood. The bridge is very simple
and is very unsafe and should be used with care. FFI isn't the only way to communicate with
the outside world in Prolog: sockets, pipes and HTTP may be good enough for your use case.
The main predicate is `use_foreign_module/2`. It takes a library name (which depending on the
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`/`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
are used to call the native code.
The predicate takes the functor name after the function name. Then, the arguments are the input
arguments followed by a return argument. However, functions with return type `void` or `bool`
don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend
on the return value on the native side.
```
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
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.
```
?- use_foreign_module("./libraylib.so", ['InitWindow'([sint32, sint32, cstr], void)]).
```
This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it:
```
?- ffi:'InitWindow'(800, 600, "Scryer Prolog + Raylib").
```
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).
%
% Defines a new struct type with name Name, composed of the elements Elements, which is a list
% of other types.
%
% The name of the types doesn't matter, but the order of Elements must match the ones in the
% native code.
%
% Example:
%
% ```
% ?- foreign_struct(color, [uint8, uint8, uint8, uint8]).
% ```
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).
assert_predicate(PredicateDefinition) :-
PredicateDefinition =.. [Name, Inputs, void],
length(Inputs, NumInputs),
functor(Head, Name, NumInputs),
term_variables(Head, TermList),
Body = (
'$foreign_call'(Name, TermList, _),!
),
Predicate = (Head:-Body),
assertz(ffi:Predicate).
assert_predicate(PredicateDefinition) :-
PredicateDefinition =.. [Name, Inputs, bool],
length(Inputs, NumInputs),
functor(Head, Name, NumInputs),
term_variables(Head, TermList),
Body = (
'$foreign_call'(Name, TermList, 1),!
),
Predicate = (Head:-Body),
assertz(ffi:Predicate).
assert_predicate(PredicateDefinition) :-
PredicateDefinition =.. [Name, Inputs, Return],
\+ member(Return, [void, bool]),
length(Inputs, NumInputs),
NumArgs is NumInputs + 1,
functor(Head, Name, NumArgs),
term_variables(Head, TermList),
Body = (
lists:append(TermListInputs, [TermListReturn], TermList),
'$foreign_call'(Name, TermListInputs, TermListReturn),!
),
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

@@ -1,5 +1,24 @@
/** Predicates for reasoning about files and directories.
In this library, directories and files are represented as
_lists of characters_. This is an ideal representation:
* Lists of characters can be conveniently reasoned about with DCGs
and built-in Prolog predicates from `library(lists)`. This alone
is already a very compelling argument to use them.
* Other Scryer libraries such as `library(http/http_open)` also already
use lists of characters to represent paths.
* File names are mostly ephemeral, so it is good for efficiency
that they can quickly allocated transiently on the heap, leaving the
atom table mostly unaffected. Indexing is almost never needed
for file names. If needed, it should be added to the engine.
* The previous point is also good for security, since the system
leaves little trace of which files were even accessed.
* Scryer Prolog represents lists of characters extremely compactly.
*/
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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.
@@ -51,8 +70,9 @@
file_exists/1,
directory_exists/1,
delete_file/1,
rename_file/2,
delete_directory/1,
rename_file/2,
file_copy/2,
delete_directory/1,
make_directory/1,
make_directory_path/1,
working_directory/2,
@@ -67,41 +87,85 @@
:- use_module(library(charsio)).
:- use_module(library(dcgs)).
%% directory_files(+Directory, -Files).
%
% 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).
%
% 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),
can_be(integer, Size),
'$file_size'(File, Size).
%% file_exists(+File).
%
% 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).
%
% 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).
%
% 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).
%% make_directory_path(+Directory).
%
% Similar to `make_directory/1` but recursively creates directories if they're missing.
% Equivalent to mkdir -p in Unix.
make_directory_path(Directory) :-
must_be(chars, Directory),
'$make_directory_path'(Directory).
%% delete_file(+File).
%
% Succeeds if deletes File from the current system.
delete_file(File) :-
file_must_exist(File, delete_file/1),
'$delete_file'(File).
%% rename_file(+File, +Renamed).
%
% Succeeds if File is renamed to Renamed
rename_file(File, Renamed) :-
file_must_exist(File, rename_file/2),
must_be(chars, Renamed),
'$rename_file'(File, Renamed).
%% file_copy(+File, +Copied).
%
% Succeeds if File is copied to Copied
file_copy(File, Copied) :-
file_must_exist(File, file_copy/2),
must_be(chars, Copied),
'$file_copy'(File, Copied).
%% delete_directory(+Directory).
%
% Succeeds if Directory is deleted from the current system.
% Directory must be empty.
delete_directory(Directory) :-
directory_must_exist(Directory, delete_directory/1),
must_be(chars, Directory),
@@ -117,31 +181,31 @@ directory_must_exist(Directory, Context) :-
; throw(error(existence_error(directory, Directory), Context))
).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Dir0 is the current working directory, and the working directory
is changed to Dir.
Use working_directory(Ds, Ds) to determine the current working directory,
and leave it as is.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% working_directory(Dir0, Dir).
%
% Dir0 is the current working directory, and the working directory
% is changed to Dir.
%
% Use `working_directory/2` to determine the current working directory,
% and leave it as is.
working_directory(Dir0, Dir) :-
can_be(list, Dir0),
can_be(list, Dir),
'$working_directory'(Dir0, Dir).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
True iff Cs is the canonical, absolute path of Ps.
All intermediate components are normalized, and all symbolic links
are resolved.
The predicate fails in the following situations, though not
necessarily *only* in these cases:
1. Ps is a path that does not exist.
2. A non-final component in Ps is not a directory.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% path_canonical(Ps, Cs).
%
% True iff Cs is the canonical, absolute path of Ps.
%
% All intermediate components are normalized, and all symbolic links
% are resolved.
%
% The predicate fails in the following situations, though not
% necessarily *only* in these cases:
%
% 1. Ps is a path that does not exist.
% 2. A non-final component in Ps is not a directory.
path_canonical(Ps, Cs) :-
must_be(chars, Ps),
@@ -155,12 +219,28 @@ path_canonical(Ps, Cs) :-
For two time stamps A and B, if A precedes B, then A @< B holds.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% file_modification_time(+File, ?T).
%
% For a file `File` that must exist, `T` is the modification 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).
%
% 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).
%
% 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) :-
file_time_(File, creation, T).
@@ -170,29 +250,31 @@ file_time_(File, Which, T) :-
read_from_chars(T0, T).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
path_segments(Ps, Segments): True iff Segments are the segments of Ps.
Segments is the list of components of the path Ps that are
separated by the platform-specific directory separator. Each
segment is a list of characters.
At least one of the arguments must be instantiated.
Examples:
?- path_segments("/hello/there", Segments).
Segments = [[],"hello","there"].
?- path_segments(Path, ["hello","there"]).
Path = "hello/there".
To obtain the platform-specific directory separator, you can use:
?- path_segments(Separator, ["",""]).
Separator = "/".
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% path_segments(?Ps, ?Segments).
%
% True iff Segments are the segments of Ps.
%
% Segments is the list of components of the path Ps that are
% separated by the platform-specific directory separator. Each
% segment is a list of characters.
%
% At least one of the arguments must be instantiated.
%
% Examples:
%
% ```
% ?- path_segments("/hello/there", Segments).
% Segments = [[],"hello","there"].
% ?- path_segments(Path, ["hello","there"]).
% Path = "hello/there".
% ```
%
% To obtain the platform-specific directory separator, you can use:
%
% ```
% ?- path_segments(Separator, ["",""]).
% Separator = "/".
% ```
path_segments(Path, Segments) :-
'$directory_separator'(Sep),

View File

@@ -1,83 +1,17 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2020, 2021, 2022 by Markus Triska (triska@metalevel.at)
Written 2020-2025 by Markus Triska (triska@metalevel.at)
Part of Scryer Prolog.
This library provides the nonterminal format_//2 to describe
formatted strings. format/[2,3] are provided for impure output.
Usage:
======
phrase(format_(FormatString, Arguments), Ls)
format_//2 describes a list of characters Ls that are formatted
according to FormatString. FormatString is a string (i.e.,
a list of characters) that specifies the layout of Ls.
The characters in 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
~d use the next argument here, which must be an integer
~f use the next argument here, a floating point number
~Nf where N is an integer: format the float argument
using N digits after the decimal point
~Nd like ~d, placing the last N digits after a decimal point;
if N is 0 or omitted, no decimal point is used.
~ND like ~Nd, separating digits to the left of the decimal point
in groups of three, using the character "," (comma)
~NU like ~ND, using "_" (underscore) to separate groups of digits
~NL format an integer so that at most N digits appear on a line.
If N is 0 or omitted, it defaults to 72.
~Nr where N is an integer between 2 and 36: format the
next argument, which must be an integer, in radix N.
The characters "a" to "z" are used for radices 10 to 36.
If N is omitted, it defaults to 8 (octal).
~NR like ~Nr, except that "A" to "Z" are used for radices > 9
~| place a tab stop at this position
~N| where N is an integer: place a tab stop at text column N
~N+ where N is an integer: place a tab stop N characters
after the previous tab stop (or start of line)
~t distribute spaces evenly between the two closest tab stops
~`Ct like ~t, use character C instead of spaces to fill the space
~n newline
~Nn N newlines
~i ignore the next argument
~~ the literal ~
Instead of ~N, you can write ~* to use the next argument from Arguments
as the numeric argument.
The predicate format/2 is like format_//2, except that it outputs
the text on the terminal instead of describing it declaratively.
format/3, used as format(Stream, FormatString, Arguments), outputs
the described string to the given Stream. If Stream is a binary
stream, then the code of each emitted character must be in 0..255.
If at all possible, format_//2 should be used, to stress pure parts
that enable easy testing etc. If necessary, you can emit the list Ls
with maplist(put_char, Ls) or, much faster, with format("~s", [Ls]).
Ideally, however, you use phrase_to_file/[2,3] or phrase_to_stream/2
from library(pio) to write the described list directly to a file
or stream, respectively: phrase_to_stream(format_(..., [...]), S).
The advantage of this is that an ideal implementation writes
the characters as they become known, without manifesting the list.
The entire library only works if the Prolog flag double_quotes
is set to chars, the default value in Scryer Prolog. This should
also stay that way, to encourage a sensible environment.
Example:
?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs).
%@ Cs = "hello\n......there!".
I place this code in the public domain. Use it in any way you want.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** This library provides the nonterminal `format_//2` to describe
formatted strings. `format/[2,3]` are provided for _impure_ output.
The entire library only works if the Prolog flag `double_quotes`
is set to `chars`, the default value in Scryer Prolog. This should
also stay that way, to encourage a sensible environment.
*/
:- module(format, [format_//2,
format/2,
format/3,
@@ -94,13 +28,122 @@
:- use_module(library(between)).
:- use_module(library(pio)).
%% format_(+FormatString, +Arguments)//
%
% Usage:
%
% ```
% phrase(format_(FormatString, Arguments), Ls)
% ```
%
% `format_//2` describes a list of characters Ls that are formatted
% according to FormatString. FormatString is a string (i.e., a list of
% characters) that specifies the layout of Ls. The characters in
% FormatString are used literally, except for the following tokens
% with special meaning:
%
% | `~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 |
% | `~d` | use the next argument here, which must be an integer |
% | `~f` | use the next argument here, a floating point number |
% | `~Nf` | where N is an integer: format the float argument |
% | | using N digits after the decimal point |
% | `~Nd` | like ~d, placing the last N digits after a decimal point; |
% | | if N is 0 or omitted, no decimal point is used. |
% | `~ND` | like ~Nd, separating digits to the left of the decimal point |
% | | in groups of three, using the character "," (comma) |
% | `~NU` | like ~ND, using "_" (underscore) to separate groups of digits |
% | `~NL` | format an integer so that at most N digits appear on a line. |
% | | If N is 0 or omitted, it defaults to 72. |
% | `~Nr` | where N is an integer between 2 and 36: format the |
% | | next argument, which must be an integer, in radix N. |
% | | The characters "a" to "z" are used for radices 10 to 36. |
% | | If N is omitted, it defaults to 8 (octal). |
% | `~NR` | like ~Nr, except that "A" to "Z" are used for radices > 9 |
% | `~|` | place a tab stop at this position |
% | `~N|` | where N is an integer: place a tab stop at text column N |
% | `~N+` | where N is an integer: place a tab stop N characters |
% | | after the previous tab stop (or start of line) |
% | `~t` | distribute spaces evenly between the two closest tab stops |
% | ``~`Ct`` | like ~t, use character C instead of spaces to fill the space |
% | `~n` | newline |
% | `~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.
%
% Example:
%
% ```
% ?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs).
% Cs = "hello\n......there!".
% ```
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),
@@ -136,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) -->
@@ -143,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.
@@ -153,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
@@ -172,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) --> !,
@@ -234,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) },
@@ -296,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 == [] } ->
@@ -309,18 +345,27 @@ 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(0) --> !.
n_newlines(N0) --> { N0 > 0, N is N0 - 1 }, [newline], n_newlines(N).
n_newlines(0) --> [].
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
?- phrase(upto_what(Cs, ~), "abc~test", Rest).
Cs = [a,b,c], Rest = [~,t,e,s,t].
?- phrase(upto_what(Cs, ~), "abc", Rest).
Cs = [a,b,c], Rest = [].
?- phrase(format:upto_what(Cs, ~), "abc~test", Rest).
Cs = "abc", Rest = "~test".
?- phrase(format:upto_what(Cs, ~), "abc", Rest).
Cs = "abc", Rest = [].
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
separate_digits_fractional(Arg, Sep, Num, Cs) :-
@@ -414,13 +459,39 @@ digits(uppercase, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").
Impure I/O, implemented as a small wrapper over format_//2.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
format(Fs, Args) :-
current_output(Stream),
format(Stream, Fs, Args).
%% format(+Fs, +Args)
%
% The predicate `format/2` is like `format_//2`, except that it
% outputs the text on the terminal instead of describing it
% declaratively as a list of characters.
%
% If at all possible, `format_//2` should be used, to stress pure
% parts that enable easy testing etc. If necessary, you can emit the
% described list of characters `Ls` with `maplist(put_char, Ls)` or,
% much faster, with `format("~s", [Ls])`. Ideally, however, you use
% `phrase_to_file/[2,3]` or `phrase_to_stream/2` from `library(pio)`
% to write the described list directly to a file or stream,
% respectively: `phrase_to_stream(format_(..., [...]), S)`. The
% advantage of this is that an ideal implementation writes the
% characters as they become known, without manifesting the list.
format(Stream, Fs, Args) :-
phrase_to_stream(format_(Fs, Args), Stream),
flush_output(Stream).
format(_, _) :- not_used.
user:goal_expansion(format(Fs, Args),
( current_output(Stream),
format(Stream, Fs, Args))).
%% format(Stream, FormatString, Arguments)
%
% Output the described string to the given Stream. If Stream is a
% binary stream, then the code of each emitted character must be in
% 0..255.
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).
@@ -433,9 +504,9 @@ format(Stream, Fs, Args) :-
?- phrase(format:cells("~`at~50|", [], 0, [], []), Cs),
phrase(format:format_cells(Cs), Ls).
?- phrase(format:cells("~ta~t~tb~tc~21|", [], 0, [], []), Cs).
Cs = [cell(0,21,[glue(' ',_A),chars("a"),glue(' ',_B),glue(' ',_C),chars("b"),glue(' ',_D),chars("c ...")])]
Cs = [cell(0,21,[glue(' ',_A),chars("a"),glue(' ',_B),glue(' ',_C),chars("b"),glue(' ',_D),chars("c")])].
?- phrase(format:cells("~ta~t~4|", [], 0, [], []), Cs).
Cs = [cell(0,4,[glue(' ',_A),chars("a"),glue(' ',_B)])]
Cs = [cell(0,4,[glue(' ',_A),chars("a"),glue(' ',_B)])].
?- phrase(format:format_cell(cell(0,1,[glue(a,_94)])), Ls).
@@ -486,11 +557,14 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
In the eventual library organization, portray_clause/1 and
related predicates may be placed in their own dedicated library.
portray_clause/1 is useful for printing solutions in such a way
that they can be read back with read/1.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% portray_clause(+Term)
%
% `portray_clause/1` is useful for printing solutions in such a way
% that they can be read back with `read/1`.
portray_clause(Term) :-
current_output(Out),
portray_clause(Out, Term).
@@ -500,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)], Ls) },
seq(Ls).
{ write_term_to_chars(Lit, [quoted(true),variable_names(VNs),double_quotes(true)], 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) --> !,
@@ -522,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) }, !,
@@ -547,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

@@ -1,9 +1,12 @@
:- module(freeze, [freeze/2]).
/** Provides the constraint `freeze/2`.
*/
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- meta_predicate freeze(-, 0).
:- meta_predicate(freeze(-, 0)).
:- attribute frozen/1.
@@ -19,6 +22,15 @@ verify_attributes(Var, Other, Goals) :-
).
verify_attributes(_, _, []).
%% freeze(Var, Goal)
%
% Schedules Goal to be executed when Var is instantiated. This can
% be useful to observe the exact moment a variable becomes bound to a
% more concrete term, for example when creating animations of search
% processes. Higher-level constructs such as `phrase_from_file/2` can
% also be implemented with `freeze/2`, by scheduling a goal that
% reads additional data from a file as soon as it is needed.
freeze(X, Goal) :-
put_atts(Fresh, frozen(Goal)),
Fresh = X.
@@ -26,5 +38,5 @@ freeze(X, Goal) :-
attribute_goals(Var) -->
{ get_atts(Var, frozen(Goals)),
put_atts(Var, -frozen(_)) },
[freeze(Var, Goals)].
[freeze:freeze(Var, Goals)].

View File

@@ -19,14 +19,14 @@ gensym(Base, Unique) :-
must_be(var, Unique),
atom_si(Base),
gensym_key(Base, BaseKey),
( bb_get(BaseKey, UniqueID0) ->
UniqueID is UniqueID0 + 1,
bb_put(BaseKey, UniqueID),
append_id(Base, UniqueID, Unique)
; bb_put(BaseKey, 1),
append_id(Base, 1, Unique)
).
( bb_get(BaseKey, UniqueID0) -> true
; UniqueID0 = 0
),
UniqueID is UniqueID0 + 1,
append_id(Base, UniqueID, Unique),
bb_put(BaseKey, UniqueID).
reset_gensym(Base) :-
atom_si(Base),
bb_put(Base, 0).
gensym_key(Base, BaseKey),
bb_put(BaseKey, 0).

View File

@@ -1,34 +1,39 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2022 by Adrián Arroyo Calle (adrian.arroyocalle@gmail.com)
Part of Scryer Prolog.
*/
http_open(+Address, -Stream, +Options)
======================================
/** Make HTTP requests.
Yields Stream to read the body of an HTTP reply from Address.
Address is a list of characters, and includes the method. Both HTTP
and HTTPS are supported.
Options supported:
* method(+Method): Sets the HTTP method of the call. Method can be get (default), head, delete, post, put or patch.
* data(+Data): Data to be sent in the request. Useful for POST, PUT and PATCH operations.
* size(-Size): Unifies with the value of the Content-Length header
* request_headers(+RequestHeaders): Headers to be used in the request
* headers(-ListHeaders): Unifies with a list with all headers returned in the response
* status_code(-Code): Unifies with the status code of the request (200, 201, 404, ...)
Example:
?- http_open("https://github.com/mthom/scryer-prolog", S, []).
%@ S = '$stream'(0x7fcfc9e00f00).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
This library contains the predicate `http_open/3` which allows you to perform HTTP(S) calls.
Useful for making API calls, or parsing websites. It uses Hyper underneath.
*/
:- module(http_open, [http_open/3]).
:- use_module(library(lists)).
%% http_open(+Address, -Stream, +Options).
%
% Yields Stream to read the body of an HTTP reply from Address.
% Address is a list of characters, and includes the method. Both HTTP
% and HTTPS are supported.
%
% Options supported:
%
% * `method(+Method)`: Sets the HTTP method of the call. Method can be `get` (default), `head`, `delete`, `post`, `put` or `patch`.
% * `data(+Data)`: Data to be sent in the request. Useful for POST, PUT and PATCH operations.
% * `size(-Size)`: Unifies with the value of the Content-Length header
% * `request_headers(+RequestHeaders)`: Headers to be used in the request
% * `headers(-ListHeaders)`: Unifies with a list with all headers returned in the response
% * `status_code(-Code)`: Unifies with the status code of the request (200, 201, 404, ...)
%
% Example:
%
% ```
% ?- http_open("https://www.example.com", S, []), get_n_chars(S, N, HTML).
% S = '$stream'(0x7fb548001be8), N = 1256, HTML = "<!doctype html>\n<ht ...".
% ```
http_open(Address, Response, Options) :-
parse_http_options(Options, OptionValues),
( member(method(Method), OptionValues) -> true; Method = get),

View File

@@ -1,63 +1,74 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written in December 2020 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
Updated in March 2022 by Adrián Arroyo to use the Hyper backend
Part of Scryer Prolog
This library provides an starting point to build HTTP server based applications.
It is based on Hyper, which allows for HTTP/1.0, HTTP/1.1 and HTTP/2. However,
some advanced features that Hyper provides are still not accesible.
Usage
==========
The main predicate of the library is http_listen/2, which needs a port number
(usually 80) and a list of handlers. A handler is a compound term with the functor
as one HTTP method (in lowercase) and followed by a Route Match and a predicate
which will handle the call.
text_handler(Request, Response) :-
http_status_code(Response, 200),
http_body(Response, text("Welcome to Scryer Prolog!")).
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>
]).
Every handler predicate will have at least 2-arity, with Request and Response.
Although you can work directly with http_request and http_response terms, it is
recommeded to use the helper predicates, which are easier to understand and cleaner:
- http_headers(Response/Request, Headers)
- http_status_code(Responde, StatusCode)
- http_body(Response/Request, text(Body))
- http_body(Response/Request, binary(Body))
- http_body(Request, form(Form))
- http_body(Response, file(Filename))
- http_redirect(Response, Url)
- http_query(Request, QueryName, QueryValue)
Some things that are still missing:
- Read forms in multipart format
- HTTP Basic Auth
- Session handling via cookies
- HTML Templating
Part of Scryer Prolog.
I place this code in the public domain. Use it in any way you want.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
*/
/** This library provides an starting point to build HTTP server based applications.
It is based on [Warp](https://github.com/seanmonstar/warp), which allows for HTTP/1.0, HTTP/1.1 and HTTP/2. However,
some advanced features that Warp provides are still not accesible.
## Usage
The main predicate of the library is `http_listen/2`, which needs a port number
(usually 80) and a list of handlers. A handler is a compound term with the functor
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!")).
parameter_handler(User, Request, Response) :-
http_body(Response, text(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.
Although you can work directly with `http_request` and `http_response` terms, it is
recommeded to use the helper predicates, which are easier to understand and cleaner:
- `http_headers(Response/Request, Headers)`
- `http_status_code(Responde, StatusCode)`
- `http_body(Response/Request, text(Body))`
- `http_body(Response/Request, binary(Body))`
- `http_body(Request, form(Form))`
- `http_body(Response, file(Filename))`
- `http_redirect(Response, Url)`
- `http_query(Request, QueryName, QueryValue)`
Some things that are still missing:
- Read forms in multipart format
- Session handling via cookies
- HTML Templating (but you can use [Teruel](https://github.com/aarroyoc/teruel/), [Marquete](https://github.com/aarroyoc/marquete/) or [Djota](https://github.com/aarroyoc/djota) for that)
*/
:- module(http_server, [
http_listen/2,
http_listen/3,
http_headers/2,
http_status_code/2,
http_body/2,
http_redirect/2,
http_query/3
http_query/3,
http_basic_auth/4
]).
:- meta_predicate http_listen(?, :).
:- meta_predicate(http_listen(?, :)).
:- meta_predicate(http_listen(?, :, ?)).
:- meta_predicate(http_basic_auth(:, :, ?, ?)).
:- use_module(library(charsio)).
:- use_module(library(crypto)).
@@ -68,22 +79,60 @@
:- use_module(library(pio)).
:- use_module(library(time)).
%% http_listen(+Port, +Handlers).
%
% Equivalent to `http_listen(Port, Handlers, [])`.
http_listen(Port, Module:Handlers0) :-
must_be(integer, Port),
must_be(list, Handlers0),
maplist(module_qualification(Module), Handlers0, Handlers),
http_listen_(Port, Handlers).
http_listen_(Port, Handlers, []).
%% http_listen(+Port, +Handlers, +Options).
%
% Listens for HTTP connections on port Port. Each handler on the list Handlers should be of the form: `HttpVerb(PathUnification, Predicate)`.
% For example: `get(user/User, get_info(User))` will match an HTTP request that is a GET, the path unifies with /user/User (where User is a variable)
% and it will call `get_info` with three arguments: an `http_request` term, an `http_response` term and User.
%
% The following options are supported:
%
% - `tls_key(+Key)` - a TLS key for HTTPS (string)
% - `tls_cert(+Cert)` - a TLS cert for HTTPS (string)
% - `content_length_limit(+Limit)` - maximum length (in bytes) for the incoming bodies. By default, 32KB.
%
% In order to have a HTTPS server (instead of plain HTTP), both `tls_key` and `tls_cert` options must be provided.
http_listen(Port, Module:Handlers0, Options) :-
must_be(integer, Port),
must_be(list, Handlers0),
must_be(list, Options),
maplist(module_qualification(Module), Handlers0, Handlers),
http_listen_(Port, Handlers, Options).
module_qualification(M, H0, H) :-
H0 =.. [Method, Path, Goal],
H =.. [Method, Path, M:Goal].
http_listen_(Port, Handlers) :-
http_listen_(Port, Handlers, Options) :-
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit),
phrase(format_("0.0.0.0:~d", [Port]), Addr),
'$http_listen'(Addr, HttpListener),!,
'$http_listen'(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit),!,
format("Listening at ~s\n", [Addr]),
http_loop(HttpListener, Handlers).
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit) :-
member_option_default(tls_key, Options, "", TLSKey),
member_option_default(tls_cert, Options, "", TLSCert),
member_option_default(content_length_limit, Options, 32768, ContentLengthLimit),
must_be(integer, ContentLengthLimit).
member_option_default(Key, List, _Default, Value) :-
X =.. [Key, Value],
member(X, List).
member_option_default(Key, List, Default, Default) :-
X =.. [Key, _],
\+ member(X, List).
http_loop(HttpListener, Handlers) :-
'$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
current_time(Time),
@@ -105,42 +154,49 @@ http_loop(HttpListener, Handlers) :-
)
; (
'$http_answer'(ResponseHandle, 404, [], ResponseStream),
call_cleanup(format(ResponseStream, "Not Found"), close(ResponseStream)))
call_cleanup(format(ResponseStream, "Not Found", []), close(ResponseStream)))
),
http_loop(HttpListener, Handlers).
send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
call_cleanup(
format(ResponseStream, "~s", [ResponseText]),
close(ResponseStream)
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
open(stream(ResponseStream0), write, ResponseStream, [type(text)]),
catch(
call_cleanup(format(ResponseStream, "~s", [ResponseText]),close(ResponseStream)),
error(existence_error(stream, _), _),
true
).
send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
call_cleanup(
format(ResponseStream, "~s", [ResponseBytes]),
close(ResponseStream)
catch(
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
error(existence_error(stream, _), _),
true
).
send_response(ResponseHandle, http_response(StatusCode0, file(Filename), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
call_cleanup(
setup_call_cleanup(
open(Filename, read, FileStream, [type(binary)]),
(
get_n_chars(FileStream, _, FileCs),
format(ResponseStream, "~s", [FileCs])
catch(
call_cleanup(
setup_call_cleanup(
open(Filename, read, FileStream, [type(binary)]),
(
get_n_chars(FileStream, _, FileCs),
format(ResponseStream, "~s", [FileCs])
),
close(FileStream)
),
close(FileStream)
close(ResponseStream)
),
close(ResponseStream)
error(existence_error(stream, _), _),
true
).
@@ -206,9 +262,21 @@ string_without(Not, [Char|String]) -->
string_without(_, []) -->
[].
%% http_headers(?Request_Response, ?Headers).
%
% True iff `Request_Response` is a request or response with headers Headers. Can be used both to get headers (usually in from a request)
% and to add headers (usually in a response).
http_headers(http_request(Headers, _, _), Headers).
http_headers(http_response(_, _, Headers), Headers).
%% http_body(?Request_Response, ?Body).
%
% True iff Body is the body of the request or response. A body can be of the following types:
%
% * `bytes(Bytes)` for both requests and responses, interprets the body as bytes
% * `text(Bytes)` for both requests and responses, interprets the body as text
% * `form(Form)` only for requests, interprets the body as an `application/x-www-form-urlencoded` form.
% * `file(File)` only for responses, interprets the body as the content of a file (useful to send static files).
http_body(http_request(_, stream(StreamBody), _), bytes(BytesBody)) :- get_n_chars(StreamBody, _, BytesBody).
http_body(http_request(_, stream(StreamBody), _), text(TextBody)) :- get_n_chars(StreamBody, _, TextBody).
http_body(http_request(Headers, stream(StreamBody), _), form(FormBody)) :-
@@ -218,31 +286,38 @@ http_body(http_request(Headers, stream(StreamBody), _), form(FormBody)) :-
http_body(http_request(_, Body, _), Body).
http_body(http_response(_, Body, _), Body).
%% http_status_code(?Response, ?StatusCode).
%
% True iff the status code of the response Response unifies with StatusCode.
http_status_code(http_response(StatusCode, _, _), StatusCode).
%% http_redirect(-Response, +Uri).
%
% True iff Response is a response that redirects the user to the uri Uri.
http_redirect(http_response(307, text("Moved Temporarily"), ["Location"-Uri]), Uri).
%% http_query(+Request, ?Key, ?Value).
%
% True iff there's a query in request Request with key Key and value Value.
http_query(http_request(_, _, Queries), Key, Value) :- member(Key-Value, Queries).
parse_queries([Key-Value|Queries]) -->
string_without("=", Key0),
{
phrase(url_decode(Key), Key0)
},
"=",
string_without("&", Value0),
{
phrase(url_decode(Value), Value0)
},
"&",
parse_queries(Queries).
parse_queries(Queries),
{
phrase(url_decode(Key), Key0),
phrase(url_decode(Value), Value0)
}.
parse_queries([Key-Value]) -->
string_without("=", Key0),
{
phrase(url_decode(Key), Key0)
},
"=",
string_without(" ", Value0),
{
phrase(url_decode(Key), Key0),
phrase(url_decode(Value), Value0)
}.
@@ -253,9 +328,13 @@ parse_queries([]) -->
url_decode([Char|Chars]) -->
[Char],
{
Char \= '%'
Char \= '%',
Char \= (+)
},
url_decode(Chars).
url_decode([' '|Chars]) -->
"+",
url_decode(Chars).
url_decode([Char|Chars]) -->
"%",
[A],
@@ -313,3 +392,49 @@ url_decode([Char|Chars]) -->
url_decode(Chars).
url_decode([]) --> [].
%% http_basic_auth(+LoginPredicate, +Handler, +Request, -Response)
%
% Metapredicate that wraps an existing Handler with an HTTP Basic Auth flow.
% Checks if a given user + password is authorized to execute that handler, returning 401
% if it's not satisfied.
%
% `LoginPredicate` must be a predicate of arity 2 that takes a User and a Password.
% `Handler` will have, in addition to the Request and Response arguments, a User argument
% containing the User given in the authentication.
%
% Example:
%
% ```
% main :-
% http_listen(8800,[get('/', http_basic_auth(login, inside_handler("data")))]).
%
% login(User, Pass) :-
% User = "aarroyoc",
% Pass = "123456".
%
% inside_handler(Data, User, Request, Response) :-
% http_body(Response, text(User)).
% ```
http_basic_auth(LoginPredicate, Handler, Request, Response) :-
http_headers(Request, Headers),
member("authorization"-AuthorizationStr, Headers),
append("Basic ", Coded, AuthorizationStr),
chars_base64(UserPass, Coded, []),
append(User, [':'|Password], UserPass),
(
call(LoginPredicate, User, Password) ->
call(Handler, User, Request, Response)
; http_basic_auth_unauthorized_response(Response)
).
http_basic_auth(_LoginPredicate, _Handler, Request, Response) :-
http_headers(Request, Headers),
\+ member("authorization"-_, Headers),
http_basic_auth_unauthorized_response(Response).
http_basic_auth_unauthorized_response(Response) :-
http_status_code(Response, 401),
http_headers(Response, ["www-authenticate"-"Basic realm=\"Scryer Prolog\", charset=\"UTF-8\""]),
http_body(Response, text("Unauthorized")).

View File

@@ -1,46 +1,114 @@
/** Useful general predicates that are not ISO standard yet
Predicates available here are similar to the ones defined in builtin.pl,
but they're not part of the ISO Prolog standard at the moment.
*/
:- module(iso_ext, [bb_b_put/2,
bb_get/2,
bb_put/2,
call_cleanup/2,
call_with_inference_limit/3,
call_residue_vars/2,
forall/2,
partial_string/1,
partial_string/3,
partial_string_tail/2,
setup_call_cleanup/3,
succ/2,
call_nth/2,
countall/2,
copy_term_nat/2,
asserta/2,
assertz/2]).
copy_term/3]).
:- use_module(library(error), [can_be/2,
must_be/2,
domain_error/3,
instantiation_error/1,
type_error/3]).
:- use_module(library(lists), [maplist/3]).
:- use_module(library('$project_atts')).
:- meta_predicate(forall(0, 0)).
%% forall(Generate, Test).
%
% For all bindings possible by Generate, Test must be true.
%
% In this example, it checks that all numbers are even:
%
% ```
% ?- Ns = [2,4,6], forall(member(N, Ns), 0 is N mod 2).
% Ns = [2,4,6].
% ```
forall(Generate, Test) :-
\+ (Generate, \+ Test).
%% (non-)backtrackable global variables.
% (non-)backtrackable global variables.
%% bb_put(+Key, +Value).
%
% Sets a global variable named Key (must be an atom) with value Value.
% The global variable isn't backtrackable. Check `bb_b_put/2` for the
% backtrackable version.
%
% ```
% ?- bb_put(city, "Valladolid").
% true.
% ?- bb_get(city, X).
% X = "Valladolid".
% ```
%
% In this example one can understand the difference between `bb_put/2` and
% `bb_b_put/2`:
%
% ```
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
% X = "Salamanca".
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
% X = "Valladolid".
% ```
bb_put(Key, Value) :-
( atom(Key) ->
'$store_global_var'(Key, Value)
; type_error(atom, Key, bb_put/2)
).
%% backtrackable global variables.
% backtrackable global variables.
%% bb_b_put(+Key, +Value).
%
% Sets a global variable named Key (must be an atom) with value Value.
% The global variable is backtrackable. Check `bb_put/2` for the
% non-backtrackable version.
%
% ```
% ?- bb_b_put(city, "Valladolid").
% true.
% ?- bb_get(city, X).
% X = "Valladolid".
% ```
%
% In this example one can understand the difference between `bb_put/2` and
% `bb_b_put/2`:
%
% ```
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
% X = "Salamanca".
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
% X = "Valladolid".
% ```
bb_b_put(Key, Value) :-
( atom(Key) ->
'$store_backtrackable_global_var'(Key, Value)
; type_error(atom, Key, bb_b_put/2)
).
%% bb_get(+Key, -Value).
%
% Gets the value Value of a global variable named Key (must be an atom)
bb_get(Key, Value) :-
( atom(Key) ->
'$fetch_global_var'(Key, Value)
@@ -48,21 +116,51 @@ bb_get(Key, Value) :-
).
%% succ(?I, ?S).
%
% True iff S is the successor of the non-negative integer I.
% At least one of the arguments must be instantiated.
succ(I, S) :-
can_be(not_less_than_zero, I),
can_be(not_less_than_zero, S),
( integer(S) ->
S > 0,
I is S-1
; integer(I) ->
S is I+1
; instantiation_error(succ/2)
).
% setup_call_cleanup.
:- meta_predicate(call_cleanup(0, 0)).
%% call_cleanup(Goal, Cleanup).
%
% Executes Goal and then, either on success or failure, executes Cleanup.
% The success or failure of Cleanup is ignored and choice points created inside are destroyed.
call_cleanup(G, C) :- setup_call_cleanup(true, G, C).
:- meta_predicate(setup_call_cleanup(0, 0, 0)).
:- non_counted_backtracking setup_call_cleanup/3.
%% setup_call_cleanup(Setup, Goal, Cleanup).
%
% If Setup succeeds, Cleanup will be called after the execution of Goal. Goal itself can succeed or not.
%
% In this example, we use the predicate to always close an open file:
%
% ```
% ?- setup_call_cleanup(open(File, read, Stream), do_something_with_stream(Stream), close(Stream)).
% ```
setup_call_cleanup(S, G, C) :-
'$get_b_value'(B),
'$call_with_inference_counting'(call(S)),
'$set_cp_by_default'(B),
'$get_current_block'(Bb),
'$get_current_scc_block'(Bb),
( C = _:CC,
var(CC) ->
instantiation_error(setup_call_cleanup/3)
@@ -75,17 +173,16 @@ setup_call_cleanup(S, G, C) :-
scc_helper(C, G, Bb) :-
'$get_cp'(Cp),
'$install_scc_cleaner'(C, NBb),
'$install_scc_cleaner'(C),
'$call_with_inference_counting'(call(G)),
( '$check_cp'(Cp) ->
'$reset_block'(Bb),
'$reset_scc_block'(Bb),
run_cleaners_without_handling(Cp)
; true
; '$reset_block'(NBb),
'$fail'
; '$fail'
).
scc_helper(_, _, Bb) :-
'$reset_block'(Bb),
'$reset_scc_block'(Bb),
'$push_ball_stack',
run_cleaners_with_handling,
'$pop_from_ball_stack',
@@ -99,7 +196,7 @@ scc_helper(_, _, _) :-
run_cleaners_with_handling :-
'$get_scc_cleaner'(C),
'$get_level'(B),
'$get_cp'(B),
catch(C, _, true),
'$set_cp_by_default'(B),
run_cleaners_with_handling.
@@ -110,7 +207,7 @@ run_cleaners_with_handling :-
run_cleaners_without_handling(Cp) :-
'$get_scc_cleaner'(C),
'$get_level'(B),
'$get_cp'(B),
call(C),
'$set_cp_by_default'(B),
run_cleaners_without_handling(Cp).
@@ -120,30 +217,14 @@ run_cleaners_without_handling(Cp) :-
% call_with_inference_limit
:- non_counted_backtracking end_block/4.
end_block(_, Bb, NBb, _L) :-
'$clean_up_block'(NBb),
'$reset_block'(Bb).
end_block(B, _Bb, NBb, L) :-
'$install_inference_counter'(B, L, _),
'$reset_block'(NBb),
'$fail'.
:- non_counted_backtracking handle_ile/3.
handle_ile(B, inference_limit_exceeded(B), inference_limit_exceeded) :-
!,
'$pop_ball_stack'.
handle_ile(B, _, _) :-
'$remove_call_policy_check'(B),
'$pop_from_ball_stack',
'$unwind_stack'.
:- meta_predicate(call_with_inference_limit(0, ?, ?)).
:- non_counted_backtracking call_with_inference_limit/3.
%% 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 ->
@@ -159,8 +240,6 @@ call_with_inference_limit(G, L, R) :-
call_with_inference_limit(G, L, R, Bb, B),
'$remove_call_policy_check'(B).
install_inference_counter(B, L, Count0) :-
'$install_inference_counter'(B, L, Count0).
:- meta_predicate(call_with_inference_limit(0,?,?,?,?)).
@@ -168,36 +247,57 @@ install_inference_counter(B, L, Count0) :-
call_with_inference_limit(G, L, R, Bb, B) :-
'$install_new_block'(NBb),
'$install_inference_counter'(B, L, Count0),
'$install_inference_counter'(NBb, L, Count0),
'$call_with_inference_counting'(call(G)),
'$inference_level'(R, B),
'$remove_inference_counter'(B, Count1),
'$remove_inference_counter'(NBb, Count1),
Diff is L - (Count1 - Count0),
end_block(B, Bb, NBb, Diff).
call_with_inference_limit(_, _, R, Bb, B) :-
'$reset_block'(Bb),
'$remove_inference_counter'(B, _),
( '$get_ball'(Ball),
'$push_ball_stack',
'$get_level'(Cp),
'$set_cp_by_default'(Cp)
; '$remove_call_policy_check'(B),
( '$clean_up_block'(NBb),
'$reset_block'(Bb)
; '$install_inference_counter'(NBb, Diff, _),
'$reset_block'(NBb),
'$fail'
).
call_with_inference_limit(_, _, R, Bb, B) :-
( '$inference_limit_exceeded' ->
R = inference_limit_exceeded
; true
),
handle_ile(B, Ball, R).
partial_string(String, L, L0) :-
( String == [] ->
L = L0
; catch(atom_chars(Atom, String),
error(E, _),
throw(error(E, partial_string/3))),
'$create_partial_string'(Atom, L, L0)
'$get_current_block'(NBb),
'$remove_inference_counter'(NBb, _),
'$reset_block'(Bb),
'$remove_call_policy_check'(B),
( '$get_ball'(_),
'$push_ball_stack',
'$get_cp'(Cp),
'$set_cp_by_default'(Cp),
'$pop_from_ball_stack',
'$unwind_stack'
; nonvar(R)
).
%% 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, Ls0, Ls) :-
must_be(chars, String),
( String == [] ->
Ls0 = Ls
; '$create_partial_string'(String, Ls0, Ls)
).
%% partial_string(+String)
%
% Succeeds if String is a _partial string_. A partial string is a string composed of several smaller
% strings, even just one. That means all strings in Scryer are partial strings.
partial_string(String) :-
'$is_partial_string'(String).
%% partial_string_tail(+String, -Tail).
%
% Unifies Tail with the last section of the partial string.
% It's not recommended to use this predicate in application code.
partial_string_tail(String, Tail) :-
( partial_string(String) ->
'$partial_string_tail'(String, Tail)
@@ -209,6 +309,9 @@ partial_string_tail(String, Tail) :-
:- meta_predicate(call_nth(0, ?)).
%% call_nth(Goal, N).
%
% Succeeds when Goal succeeded for the Nth time (there are at least N solutions)
call_nth(Goal, N) :-
can_be(integer, N),
( integer(N) ->
@@ -246,20 +349,60 @@ call_nth_nesting(C, ID) :-
bb_put(ID, 0),
bb_put(i_call_nth_counter, C).
%% countall(G_0, N).
%
% countall(G_0, N) is true iff N unifies with the total number of
% answers of call(G_0).
:- meta_predicate(countall(0, ?)).
countall(Goal, N) :-
can_be(integer, N),
( integer(N) ->
( N < 0 ->
domain_error(not_less_than_zero, N, countall/2)
; true
)
; true
),
setup_call_cleanup(call_nth_nesting(C, ID),
( ( Goal,
bb_get(ID, N0),
N1 is N0 + 1,
bb_put(ID, N1),
false
; bb_get(ID, N)
)
),
( bb_get(i_call_nth_counter, C) ->
C1 is C - 1,
bb_put(i_call_nth_counter, C1)
; true
)).
%% copy_term_nat(Source, Dest)
%
% Similar to `copy_term/2` but without attribute variables
copy_term_nat(Source, Dest) :-
'$copy_term_without_attr_vars'(Source, Dest).
%% copy_term(+Term, -Copy, -Gs).
%
% Produce a deep copy of Term and unify it to Copy, without attributes.
% Unify Gs with a list of goals that represent the attributes of Term.
% Similar to `copy_term/2` but splitting the attributes.
copy_term(Term, Copy, Gs) :-
can_be(list, Gs),
findall(Term-Rs, '$project_atts':term_residual_goals(Term,Rs), [Copy-Gs]),
( var(Gs) ->
Gs = []
; true
).
asserta(Module, (Head :- Body)) :-
!,
'$asserta'(Module, Head, Body).
asserta(Module, Fact) :-
'$asserta'(Module, Fact, true).
assertz(Module, (Head :- Body)) :-
!,
'$assertz'(Module, Head, Body).
assertz(Module, Fact) :-
'$assertz'(Module, Fact, true).
:- meta_predicate(call_residue_vars(0, ?)).
call_residue_vars(Goal, Vars) :-
can_be(list, Vars),
'$get_attr_var_queue_delim'(B),
call(Goal),
'$get_attr_var_queue_beyond'(B, Vars).

View File

@@ -50,11 +50,13 @@ programming based on call/N.
Lambda expressions are represented by ordinary Prolog terms.
There are two kinds of lambda expressions:
```
Free+\X1^X2^ ..^XN^Goal
\X1^X2^ ..^XN^Goal
```
The second is a shorthand for t+\X1^X2^..^XN^Goal.
The second is a shorthand for `t+\X1^X2^..^XN^Goal`.
Xi are the parameters.
@@ -70,20 +72,20 @@ currently not checked. Violations may lead to unexpected bindings.
In the following example the parentheses around X>3 are necessary.
==
```
?- use_module(library(lambda)).
?- use_module(library(lists)).
?- maplist(\X^(X>3),[4,5,9]).
true.
==
```
In the following X is a variable that is shared by both instances of
the lambda expression. The second query illustrates the cooperation of
continuations and lambdas. The lambda expression is in this case a
continuation expecting a further argument.
==
```
?- use_module(library(dif)).
true.
@@ -92,11 +94,12 @@ continuation expecting a further argument.
?- Xs = [A,B], maplist(X+\dif(X), Xs).
Xs = [A,B], dif:dif(X,A), dif:dif(X,B).
==
```
The following queries are all equivalent. To see this, use
the fact f(x,y).
==
the fact `f(x,y)`.
```
?- call(f,A1,A2).
?- call(\X^f(X),A1,A2).
?- call(\X^Y^f(X,Y), A1,A2).
@@ -105,41 +108,41 @@ the fact f(x,y).
?- call(f(A1),A2).
?- f(A1,A2).
A1 = x, A2 = y.
==
```
Further discussions
http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord
[http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord](http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord)
@tbd Static expansion similar to apply_macros.
@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

@@ -1,3 +1,7 @@
/**
List manipulation predicates
*/
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5,
memberchk/2, reverse/2, length/2, maplist/2,
maplist/3, maplist/4, maplist/5, maplist/6,
@@ -38,17 +42,17 @@
:- 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)).
@@ -57,6 +61,20 @@
resource_error(Resource, Context) :-
throw(error(resource_error(Resource), Context)).
%% length(?Xs, ?N).
%
% Relates a list to its length (number of elements). It can be used to count the elements of a current list or
% to create a list full of free variables with N length.
%
% ```
% ?- length("abc", 3).
% true.
% ?- length("abc", N).
% N = 3.
% ?- length(Xs, 3).
% Xs = [_A,_B,_C].
% ```
length(Xs0, N) :-
'$skip_max_list'(M, N, Xs0,Xs),
!,
@@ -74,7 +92,7 @@ length(_, N) :-
length_rundown(Xs, 0) :- !, Xs = [].
length_rundown(Vs, N) :-
\+ \+ '$project_atts':copy_term(Vs,Vs,[]), % unconstrained
'$unattributed_var'(Vs), % unconstrained
!,
'$det_length_rundown'(Vs, N).
length_rundown([_|Xs], N) :- % force unification
@@ -82,7 +100,7 @@ length_rundown([_|Xs], N) :- % force unification
length(Xs, N1). % maybe some new info on Xs
failingvarskip(Xs) :-
\+ \+ '$project_atts':copy_term(Xs,Xs,[]), % unconstrained
'$unattributed_var'(Xs), % unconstrained
!.
failingvarskip([_|Xs0]) :- % force unification
'$skip_max_list'(_, _, Xs0,Xs),
@@ -95,28 +113,71 @@ length_addendum([_|Xs], N, M) :-
M1 is M + 1,
length_addendum(Xs, N, M1).
%% member(?X, ?Xs).
%
% Succeeds when X unifies with an item of the list Xs, which can be at any position.
%
% ```
% ?- member(X, "hello world").
% X = h
% ; ... .
% ```
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).
member(X, [L|Ls]) :-
member_(Ls, L, X).
member_(_, X, X).
member_([L|Ls], _, X) :-
member_(Ls, L, X).
%% select(X, Xs0, Xs1).
%
% Succeeds when the list Xs1 is the list Xs0 without the item X
%
% ```
% ?- select(c, "abcd", X).
% X = "abd"
% ; false.
% ```
select(X, [X|Xs], Xs).
select(X, [Y|Xs], [Y|Ys]) :- select(X, Xs, Ys).
%% append(+XsXs, ?Xs).
%
% Concatenates a list of lists
%
% ```
% ?- append([[1, 2], [3]], Xs).
% Xs = [1,2,3].
% ```
append([], []).
append([L0|Ls0], Ls) :-
append(L0, Rest, Ls),
append(Ls0, Rest).
%% append(Xs0, Xs1, Xs).
%
% List Xs is the concatenation of Xs0 and Xs1
%
% ```
% ?- append([1,2,3], [4,5,6], Xs).
% Xs = [1,2,3,4,5,6].
% ```
append([], R, R).
append([X|L], R, [X|S]) :- append(L, R, S).
%% memberchk(?X, +Xs).
%
% This predicate is similar to `member/2`, but it only provides a single answer
memberchk(X, Xs) :- member(X, Xs), !.
%% reverse(?Xs, ?Ys).
%
% Xs is the Ys list in reverse order
%
% ?- reverse([1,2,3], [3,2,1]).
% true.
%
reverse(Xs, Ys) :-
( nonvar(Xs) -> reverse(Xs, Ys, [], Xs)
; reverse(Ys, Xs, [], Ys)
@@ -126,81 +187,141 @@ reverse([], [], YsRev, YsRev).
reverse([_|Xs], [Y1|Ys], YsPreludeRev, Xss) :-
reverse(Xs, Ys, [Y1|YsPreludeRev], Xss).
%% maplist(+Predicate, ?Xs0).
%
% This is a metapredicate that applies predicate to each element of the list Xs0
%
% ```
% ?- maplist(write, [1,2,3]).
% 123 true.
% ```
maplist(_, []).
maplist(Cont1, [E1|E1s]) :-
call(Cont1, E1),
maplist(Cont1, E1s).
%% maplist(+Predicate, ?Xs0, ?Xs1).
%
% This is a metapredicate that applies predicate to each element of the lists Xs0 and Xs1.
%
% ```
% ?- maplist(length, ["hello", "prolog", "marseille"], Xs1).
% Xs1 = [5,6,9].
% ```
maplist(_, [], []).
maplist(Cont2, [E1|E1s], [E2|E2s]) :-
call(Cont2, E1, E2),
maplist(Cont2, E1s, E2s).
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2).
%
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1 and Xs2.
maplist(_, [], [], []).
maplist(Cont3, [E1|E1s], [E2|E2s], [E3|E3s]) :-
call(Cont3, E1, E2, E3),
maplist(Cont3, E1s, E2s, E3s).
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3).
%
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2 and Xs3.
maplist(_, [], [], [], []).
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s]) :-
call(Cont, E1, E2, E3, E4),
maplist(Cont, E1s, E2s, E3s, E4s).
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4).
%
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3 and Xs4.
maplist(_, [], [], [], [], []).
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s]) :-
call(Cont, E1, E2, E3, E4, E5),
maplist(Cont, E1s, E2s, E3s, E4s, E5s).
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5).
%
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4 and Xs5.
maplist(_, [], [], [], [], [], []).
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s]) :-
call(Cont, E1, E2, E3, E4, E5, E6),
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s).
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6).
%
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5 and Xs6.
maplist(_, [], [], [], [], [], [], []).
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s]) :-
call(Cont, E1, E2, E3, E4, E5, E6, E7),
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s).
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6, ?Xs7).
%
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5, Xs6 and Xs7.
maplist(_, [], [], [], [], [], [], [], []).
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s], [E8|E8s]) :-
call(Cont, E1, E2, E3, E4, E5, E6, E7, E8),
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s, E8s).
%% sum_list(+Xs, -Sum).
%
% Takes a lists of numbers and unifies Sum with the result of summing all the elements of the list.
%
% ```
% ?- sum_list([2,2,2], 6).
% true.
% ```
sum_list(Ls, S) :-
foldl(lists:sum_, Ls, 0, S).
sum_(L, S0, S) :- S is S0 + L.
%% same_length(?Xs, ?Ys).
%
% Succeeds if Xs and Ys are lists of the same length
same_length([], []).
same_length([_|As], [_|Bs]) :-
same_length(As, Bs).
%% foldl(+Predicate, ?Ls, +A0, ?A).
%
% foldl, sometimes called reduce, is a metapredicate that takes a predicate, a list of items
% and a starting value, and outputs a single value. The predicate _Predicate_ must be able to take the current
% element of the list, the previous value of the computation and the next value of the computation.
%
% For example, if we define sum_ as:
%
% ```
% sum_(L, S0, S) :- S is S0 + L.
% ```
%
% Then we can define `sum_list/2` as the following:
%
% ```
% sum_list(Ls, S) :- foldl(sum_, Ls, 0, S).
% ```
foldl(Goal_3, Ls, A0, A) :-
foldl_(Ls, Goal_3, A0, A).
foldl_([], _, A, A).
foldl_([L|Ls], G_3, A0, A) :-
foldl(_, [], A, A).
foldl(G_3, [L|Ls], A0, A) :-
call(G_3, L, A0, A1),
foldl_(Ls, G_3, A1, A).
foldl(G_3, Ls, A1, A).
%% foldl(+Predicate, ?Ls0, ?Ls1, +A0, ?A).
%
% Same as `foldl/4` but with an extra list
foldl(Goal_4, Xs, Ys, A0, A) :-
foldl_(Xs, Ys, Goal_4, A0, A).
foldl_([], [], _, A, A).
foldl_([X|Xs], [Y|Ys], G_4, A0, A) :-
foldl(_, [], [], A, A).
foldl(G_4, [X|Xs], [Y|Ys], A0, A) :-
call(G_4, X, Y, A0, A1),
foldl_(Xs, Ys, G_4, A1, A).
foldl(G_4, Xs, Ys, A1, A).
%% transpose(?Ls, ?Ts).
%
% If Ls is a list of lists, Ts contains the transposition
%
% ```
% ?- transpose([[1,1],[2,2]], Ts).
% Ts = [[1,2],[1,2]].
% ```
transpose(Ls, Ts) :-
lists_transpose(Ls, Ts).
@@ -214,7 +335,14 @@ transpose_(_, Fs, Lists0, Lists) :-
list_first_rest([L|Ls], L, Ls).
%% list_to_set(+Ls0, -Set).
%
% Takes a list Ls0 and returns a list Set that doesn't contain any repeated element
%
% ```
% ?- list_to_set([2,3,4,4,1,2], Set).
% Set = [2,3,4,1].
% ```
list_to_set(Ls0, Ls) :-
maplist(lists:with_var, Ls0, LVs0),
keysort(LVs0, LVs),
@@ -242,7 +370,14 @@ unify_same(E-V, Prev-Var, E-V) :-
; true
).
%% nth0(?N, ?Ls, ?E).
%
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from zero.
%
% ```
% ?- nth0(2, [1,2,3,4], 3).
% true.
% ```
nth0(N, Es0, E) :-
nonvar(N),
'$skip_max_list'(Skip, N, Es0,Es1),
@@ -261,7 +396,6 @@ nth0(N, Es0, E) :-
skipn(N0, Es0,Es) :-
N0>0,
!, % should not be necessary #1028
N1 is N0-1,
Es0 = [_|Es1],
skipn(N1, Es1,Es).
@@ -277,6 +411,14 @@ nth0_el(N0,N, _,E, [E0|Es0]) :-
N1 is N0+1,
nth0_el(N1,N, E0,E, Es0).
%% nth1(?N, ?Ls, ?E).
%
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from one.
%
% ```
% ?- nth1(2, [1,2,3,4], 2).
% true.
% ```
nth1(N, Es0, E) :-
N \== 0,
nth0(N, [_|Es0], E),
@@ -284,13 +426,20 @@ nth1(N, Es0, E) :-
skipn(N0, Es0,Es, Xs0,Xs) :-
N0>0,
!, % should not be necessary #1028
N1 is N0-1,
Es0 = [E|Es1],
Xs0 = [E|Xs1],
skipn(N1, Es1,Es, Xs1,Xs).
skipn(0, Es,Es, Xs,Xs).
%% nth0(?N, ?Ls, ?E, ?Rs).
%
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from zero.
%
% ```
% ?- nth0(2, [1,2,3,4], 3, [1,2,4]).
% true.
% ```
nth0(N, Es0, E, Es) :-
integer(N),
N >= 0,
@@ -315,45 +464,58 @@ nth0_elx(N0,N, E0,E, [E1|Es0], [E0|Es]) :-
% p.p.8.5
%% nth1(?N, ?Ls, ?E, ?Rs).
%
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from one.
%
% ```
% ?- nth1(2, [1,2,3,4], 2, [1,3,4]).
% true.
% ```
nth1(N, Es0, E, Es) :-
N \== 0,
nth0(N, [_|Es0], E, [_|Es]),
N \== 0.
%% list_max(+Xs, -Max).
%
% Takes a list Xs and unifies with the maximum value of the list
list_max([N|Ns], Max) :-
foldl(lists:list_max_, Ns, N, Max).
list_max_(N, Max0, Max) :-
Max is max(N, Max0).
%% list_min(+Xs, -Min).
%
% Takes a list Xs and unifies with the minimum value of the list
list_min([N|Ns], Min) :-
foldl(lists:list_min_, Ns, N, Min).
list_min_(N, Min0, Min) :-
Min is min(N, Min0).
%! permutation(?Xs, ?Ys) is nondet.
%% permutation(?Xs, ?Ys) is nondet.
%
% True when Xs is a permutation of Ys. This can solve for Ys given
% Xs or Xs given Ys, or even enumerate Xs and Ys together. The
% predicate permutation/2 is primarily intended to generate
% permutations. Note that a list of length N has N! permutations,
% and unbounded permutation generation becomes prohibitively
% expensive, even for rather short lists (10! = 3,628,800).
% True when Xs is a permutation of Ys. This can solve for Ys given
% Xs or Xs given Ys, or even enumerate Xs and Ys together. The
% predicate `permutation/2` is primarily intended to generate
% permutations. Note that a list of length N has N! permutations,
% and unbounded permutation generation becomes prohibitively
% expensive, even for rather short lists (10! = 3,628,800).
%
% The example below illustrates that Xs and Ys being proper lists
% is not a sufficient condition to use the above replacement.
% The example below illustrates that Xs and Ys being proper lists
% is not a sufficient condition to use the above replacement.
%
% ==
% ?- permutation([1,2], [X,Y]).
% X = 1, Y = 2 ;
% X = 2, Y = 1 ;
% false.
% ==
% ```
% ?- permutation([1,2], [X,Y]).
% X = 1, Y = 2
% ; X = 2, Y = 1
% ; false.
% ```
%
% @error type_error(list, Arg) if either argument is not a proper
% or partial list.
% Throws `type_error(list, Arg)` if either argument is not a proper
% or partial list.
permutation(Xs, Ys) :-
'$skip_max_list'(Xlen, _, Xs, XTail),

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

@@ -54,39 +54,38 @@
:- use_module(library(lists)).
/** <module> Ordered set manipulation
/** Ordered set manipulation
Ordered sets are lists with unique elements sorted to the standard order
of terms (see sort/2). Exploiting ordering, many of the set operations
of terms (see `sort/2`). Exploiting ordering, many of the set operations
can be expressed in order N rather than N^2 when dealing with unordered
sets that may contain duplicates. The library(ordsets) is available in a
number of Prolog implementations. Our predicates are designed to be
compatible with common practice in the Prolog community. The
implementation is incomplete and relies partly on library(oset), an
older ordered set library distributed with SWI-Prolog. New applications
are advised to use library(ordsets).
compatible with common practice in the Prolog community.
Some of these predicates match directly to corresponding list
operations. It is advised to use the versions from this library to make
clear you are operating on ordered sets. An exception is member/2. See
ord_memberchk/2.
clear you are operating on ordered sets. An exception is `member/2`. See
`ord_memberchk/2`.
The ordsets library is based on the standard order of terms. This
implies it can handle all Prolog terms, including variables. Note
however, that the ordering is not stable if a term inside the set is
further instantiated. Also note that variable ordering changes if
variables in the set are unified with each other or a variable in the
set is unified with a variable that is `older' than the newest variable
set is unified with a variable that is _older_ than the newest variable
in the set. In practice, this implies that it is allowed to use
member(X, OrdSet) on an ordered set that holds variables only if X is a
fresh variable. In other cases one should cease using it as an ordset
because the order it relies on may have been changed.
*/
%! is_ordset(@Term) is semidet.
%% is_ordset(@Term) is semidet.
%
% True if Term is an ordered set. All predicates in this library
% expect ordered sets as input arguments. Failing to fullfil this
% assumption results in undefined behaviour. Typically, ordered
% sets are created by predicates from this library, sort/2 or
% setof/3.
% True if Term is an ordered set. All predicates in this library
% expect ordered sets as input arguments. Failing to fullfil this
% assumption results in undefined behaviour. Typically, ordered
% sets are created by predicates from this library, `sort/2` or
% `setof/3`.
is_ordset(Term) :-
'$skip_max_list'(_, _, Term, Tail), Tail == [], %% is_list(Term),
@@ -102,37 +101,35 @@ is_ordset3([H2|T], H) :-
is_ordset3(T, H2).
%! ord_empty(?List) is semidet.
%% ord_empty(?List) is semidet.
%
% True when List is the empty ordered set. Simply unifies list
% with the empty list. Not part of Quintus.
% True when List is the empty ordered set. Simply unifies list
% with the empty list. Not part of Quintus.
ord_empty([]).
%! ord_seteq(+Set1, +Set2) is semidet.
%% ord_seteq(+Set1, +Set2) is semidet.
%
% True if Set1 and Set2 have the same elements. As both are
% canonical sorted lists, this is the same as ==/2.
%
% @compat sicstus
% True if Set1 and Set2 have the same elements. As both are
% canonical sorted lists, this is the same as `==/2`.
ord_seteq(Set1, Set2) :-
Set1 == Set2.
%! list_to_ord_set(+List, -OrdSet) is det.
%% list_to_ord_set(+List, -OrdSet) is det.
%
% Transform a list into an ordered set. This is the same as
% sorting the list.
% Transform a list into an ordered set. This is the same as
% sorting the list.
list_to_ord_set(List, Set) :-
sort(List, Set).
%! ord_intersect(+Set1, +Set2) is semidet.
%% ord_intersect(+Set1, +Set2) is semidet.
%
% True if both ordered sets have a non-empty intersection.
% True if both ordered sets have a non-empty intersection.
ord_intersect([H1|T1], L2) :-
ord_intersect_(L2, H1, T1).
@@ -148,31 +145,29 @@ ord_intersect__(>, H1, T1, _H2, T2) :-
ord_intersect_(T2, H1, T1).
%! ord_disjoint(+Set1, +Set2) is semidet.
%% ord_disjoint(+Set1, +Set2) is semidet.
%
% True if Set1 and Set2 have no common elements. This is the
% negation of ord_intersect/2.
% True if Set1 and Set2 have no common elements. This is the
% negation of `ord_intersect/2`.
ord_disjoint(Set1, Set2) :-
\+ ord_intersect(Set1, Set2).
%! ord_intersect(+Set1, +Set2, -Intersection)
%% ord_intersect(+Set1, +Set2, -Intersection)
%
% Intersection holds the common elements of Set1 and Set2.
% Intersection holds the common elements of Set1 and Set2.
%
% @deprecated Use ord_intersection/3
% This predicate is *deprecated*. Use `ord_intersection/3`
ord_intersect(Set1, Set2, Intersection) :-
oset_int(Set1, Set2, Intersection).
%! ord_intersection(+PowerSet, -Intersection)
%% ord_intersection(+PowerSet, -Intersection)
%
% Intersection of a powerset. True when Intersection is an ordered
% set holding all elements common to all sets in PowerSet.
%
% @compat sicstus
% Intersection of a powerset. True when Intersection is an ordered
% set holding all elements common to all sets in PowerSet.
ord_intersection(PowerSet, Intersection) :-
key_by_length(PowerSet, Pairs),
@@ -190,10 +185,10 @@ l_int([_-H|T], S0, S) :-
l_int(T, S1, S).
%! ord_intersection(+Set1, +Set2, -Intersection) is det.
%% ord_intersection(+Set1, +Set2, -Intersection) is det.
%
% Intersection holds the common elements of Set1 and Set2. Uses
% ord_disjoint/2 if Intersection is bound to `[]` on entry.
% Intersection holds the common elements of Set1 and Set2. Uses
% `ord_disjoint/2` if Intersection is bound to `[]` on entry.
ord_intersection(Set1, Set2, Intersection) :-
( Intersection == []
@@ -202,13 +197,11 @@ ord_intersection(Set1, Set2, Intersection) :-
).
%! ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
%% ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
%
% Intersection and difference between two ordered sets.
% Intersection is the intersection between Set1 and Set2, while
% Difference is defined by ord_subtract(Set2, Set1, Difference).
%
% @see ord_intersection/3 and ord_subtract/3.
% Intersection and difference between two ordered sets.
% Intersection is the intersection between Set1 and Set2, while
% Difference is defined by `ord_subtract(Set2, Set1, Difference)`.
ord_intersection([], L, [], L) :- !.
ord_intersection([_|_], [], [], []) :- !.
@@ -224,35 +217,35 @@ ord_intersection2(>, H1, T1, H2, T2, Intersection, [H2|HDiff]) :-
ord_intersection([H1|T1], T2, Intersection, HDiff).
%! ord_add_element(+Set1, +Element, ?Set2) is det.
%% ord_add_element(+Set1, +Element, ?Set2) is det.
%
% Insert an element into the set. This is the same as
% ord_union(Set1, [Element], Set2).
% Insert an element into the set. This is the same as
% `ord_union(Set1, [Element], Set2)`.
ord_add_element(Set1, Element, Set2) :-
oset_addel(Set1, Element, Set2).
%! ord_del_element(+Set, +Element, -NewSet) is det.
%% ord_del_element(+Set, +Element, -NewSet) is det.
%
% Delete an element from an ordered set. This is the same as
% ord_subtract(Set, [Element], NewSet).
% Delete an element from an ordered set. This is the same as
% `ord_subtract(Set, [Element], NewSet)`.
ord_del_element(Set, Element, NewSet) :-
oset_delel(Set, Element, NewSet).
%! ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
%% ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
%
% Selectchk/3, specialised for ordered sets. Is true when
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
% without duplicates. This implementation is only expected to work
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
% is meant to remind you of memberchk/2, which also expects its
% first argument to be ground. ord_selectchk(X, S, T) =>
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).
% `selectchk/3`, specialised for ordered sets. Is true when
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
% without duplicates. This implementation is only expected to work
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
% is meant to remind you of `memberchk/2`, which also expects its
% first argument to be ground. `ord_selectchk(X, S, T) =>
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).`
%
% @author Richard O'Keefe
% Author: Richard O'Keefe
ord_selectchk(Item, [X|Set1], [X|Set2]) :-
X @< Item,
@@ -266,19 +259,19 @@ ord_selectchk(Item, [Item|Set1], Set1) :-
).
%! ord_memberchk(+Element, +OrdSet) is semidet.
%% ord_memberchk(+Element, +OrdSet) is semidet.
%
% True if Element is a member of OrdSet, compared using ==. Note
% that _enumerating_ elements of an ordered set can be done using
% member/2.
% True if Element is a member of OrdSet, compared using ==. Note
% that _enumerating_ elements of an ordered set can be done using
% `member/2`.
%
% Some Prolog implementations also provide ord_member/2, with the
% same semantics as ord_memberchk/2. We believe that having a
% semidet ord_member/2 is unacceptably inconsistent with the *_chk
% convention. Portable code should use ord_memberchk/2 or
% member/2.
% Some Prolog implementations also provide `ord_member/2`, with the
% same semantics as `ord_memberchk/2`. We believe that having a
% semidet `ord_member/2` is unacceptably inconsistent with the \*\_chk
% convention. Portable code should use `ord_memberchk/2` or
% `member/2`.
%
% @author Richard O'Keefe
% Author: Richard O'Keefe
ord_memberchk(Item, [X1,X2,X3,X4|Xs]) :-
!,
@@ -303,9 +296,9 @@ ord_memberchk(Item, [X1]) :-
Item == X1.
%! ord_subset(+Sub, +Super) is semidet.
%% ord_subset(+Sub, +Super) is semidet.
%
% Is true if all elements of Sub are in Super
% Is true if all elements of Sub are in Super
ord_subset([], _).
ord_subset([H1|T1], [H2|T2]) :-
@@ -319,22 +312,20 @@ ord_subset_(=, _, T1, T2) :-
ord_subset(T1, T2).
%! ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
%% ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
%
% Diff is the set holding all elements of InOSet that are not in
% NotInOSet.
% Diff is the set holding all elements of InOSet that are not in
% NotInOSet.
ord_subtract(InOSet, NotInOSet, Diff) :-
oset_diff(InOSet, NotInOSet, Diff).
%! ord_union(+SetOfSets, -Union) is det.
%% ord_union(+SetOfSets, -Union) is det.
%
% True if Union is the union of all elements in the superset
% SetOfSets. Each member of SetOfSets must be an ordered set, the
% sets need not be ordered in any way.
%
% @author Copied from YAP, probably originally by Richard O'Keefe.
% True if Union is the union of all elements in the superset
% SetOfSets. Each member of SetOfSets must be an ordered set, the
% sets need not be ordered in any way.
ord_union([], []).
ord_union([Set|Sets], Union) :-
@@ -355,18 +346,18 @@ ord_union_all(N, Sets0, Union, Sets) :-
).
%! ord_union(+Set1, +Set2, ?Union) is det.
%% ord_union(+Set1, +Set2, ?Union) is det.
%
% Union is the union of Set1 and Set2
% Union is the union of Set1 and Set2
ord_union(Set1, Set2, Union) :-
oset_union(Set1, Set2, Union).
%! ord_union(+Set1, +Set2, -Union, -New) is det.
%% ord_union(+Set1, +Set2, -Union, -New) is det.
%
% True iff ord_union(Set1, Set2, Union) and
% ord_subtract(Set2, Set1, New).
% True iff `ord_union(Set1, Set2, Union)` and
% `ord_subtract(Set2, Set1, New)`.
ord_union([], Set2, Set2, Set2).
ord_union([H|T], Set2, Union, New) :-
@@ -390,26 +381,26 @@ ord_union_2([H|T], H2, T2, Union, New) :-
ord_union(Order, H, T, H2, T2, Union, New).
%! ord_symdiff(+Set1, +Set2, ?Difference) is det.
%% ord_symdiff(+Set1, +Set2, ?Difference) is det.
%
% Is true when Difference is the symmetric difference of Set1 and
% Set2. I.e., Difference contains all elements that are not in the
% intersection of Set1 and Set2. The semantics is the same as the
% sequence below (but the actual implementation requires only a
% single scan).
% Is true when Difference is the symmetric difference of Set1 and
% Set2. I.e., Difference contains all elements that are not in the
% intersection of Set1 and Set2. The semantics is the same as the
% sequence below (but the actual implementation requires only a
% single scan).
%
% ==
% ord_union(Set1, Set2, Union),
% ord_intersection(Set1, Set2, Intersection),
% ord_subtract(Union, Intersection, Difference).
% ==
% ```
% ord_union(Set1, Set2, Union),
% ord_intersection(Set1, Set2, Intersection),
% ord_subtract(Union, Intersection, Difference).
% ```
%
% For example:
% For example:
%
% ==
% ?- ord_symdiff([1,2], [2,3], X).
% X = [1,3].
% ==
% ```
% ?- ord_symdiff([1,2], [2,3], X).
% X = [1,3].
% ```
ord_symdiff([], Set2, Set2).
ord_symdiff([H1|T1], Set2, Difference) :-
@@ -457,7 +448,7 @@ ord_symdiff(>, H1, T1, H2, Set2, [H2|Difference]) :-
*/
/** <module> Ordered set manipulation
/* Ordered set manipulation
This library defines set operations on sets represented as ordered
lists.

View File

@@ -12,37 +12,80 @@
Public domain code.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Predicates for reasoning about the operating system (OS) environment.
This includes predicates about environment variables, calls to shell and
finding out the PID of the running system.
*/
:- module(os, [getenv/2,
setenv/2,
unsetenv/1,
shell/1,
shell/2,
pid/1]).
pid/1,
raw_argv/1,
argv/1]).
:- use_module(library(error)).
:- use_module(library(charsio)).
:- use_module(library(lists)).
:- use_module(library(si)).
%% getenv(+Key, -Value).
%
% True iff Value contains the value of the environment variable Key.
% Example:
%
% ```
% ?- getenv("LANG", Ls).
% Ls = "en_US.UTF-8".
% ```
getenv(Key, Value) :-
must_be_env_var(Key),
'$getenv'(Key, Value).
%% setenv(+Key, +Value).
%
% Sets the environment variable Key to Value
setenv(Key, Value) :-
must_be_env_var(Key),
must_be_chars(Value),
'$setenv'(Key, Value).
%% unsetenv(+Key).
%
% Unsets the environment variable Key
unsetenv(Key) :-
must_be_env_var(Key),
'$unsetenv'(Key).
%% shell(+Command)
%
% Equivalent to `shell(Command, 0)`.
shell(Command) :- shell(Command, 0).
%% shell(+Command, -Status).
%
% True iff executes Command in a shell of the operating system and the exit code is Status.
% Keep in mind the shell syntax is dependant on the operating system, so it should be
% used very carefully.
%
% Example (using Linux and fish shell):
%
% ```
% ?- shell("echo $SHELL", Status).
% /bin/fish
% Status = 0.
% ```
shell(Command, Status) :-
must_be_chars(Command),
can_be(integer, Status),
'$shell'(Command, Status).
%% pid(-PID).
%
% True iff PID is the process identification number of current Scryer Prolog instance.
pid(PID) :-
can_be(integer, PID),
'$pid'(PID).
@@ -69,3 +112,34 @@ permitted('_').
must_be_chars(Cs) :-
must_be(list, Cs),
maplist(must_be(character), Cs).
%% raw_argv(-Argv)
%
% True iff Argv is the list of arguments that this program was started with (usually passed via command line).
% In contrast to `argv/1`, this version includes every argument, without any postprocessing, just as the operating
% system reports it to the system. This includes-flags of Scryer itself, which are not needed in general.
raw_argv(Argv) :-
can_be(list, Argv),
'$argv'(Argv).
%% argv(-Argv)
%
% True if Argv is the list of arguments that this program was started with (usually passed via command line).
% In this version, only arguments specific to the program are passed. To differentiate between the system
% arguments and the program arguments, we use `--` as a separator.
%
% Example:
%
% ```
% % Call with scryer-prolog -f -- -t hello
% ?- argv(X).
% X = ["-t", "hello"].
% ```
argv(Argv) :-
can_be(list, Argv),
'$argv'(Argv0),
( append(_, ["--"|Argv1], Argv0) ->
Argv = Argv1
;
Argv = []
).

View File

@@ -1,3 +1,10 @@
/** Reasoning about pairs.
Pairs are Prolog terms with principal functor `(-)/2`. A pair
often has the form `Key-Value`. The predicates of this library
relate pairs to keys and values.
*/
:- module(pairs, [pairs_keys_values/3,
pairs_keys/2,
pairs_values/2,
@@ -5,14 +12,27 @@
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)
%
% The first argument is a list of Pairs, the second the corresponding
% Keys, and the third argument the corresponding values.
pairs_keys_values([], [], []).
pairs_keys_values([A-B|ABs], [A|As], [B|Bs]) :-
pairs_keys_values(ABs, As, Bs).
%% pairs_keys(?Pairs, ?Keys)
%
% Same as `pairs_keys_values(Pairs, Keys, _)`.
pairs_keys(Ps, Ks) :- pairs_keys_values(Ps, Ks, _).
%% pairs_values(?Pairs, ?Values)
%
% Same as `pairs_keys_values(Pairs, _, Values)`.
pairs_values(Ps, Vs) :- pairs_keys_values(Ps, _, Vs).
map_list_to_pairs(Pred, Ls, Ps) :-

View File

@@ -1,16 +1,15 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pure I/O
========
/** Pure I/O.
Our goal is to encourage the use of definite clause grammars (DCGs)
for describing strings. The predicates phrase_from_file/[2,3],
phrase_to_file/[2,3] and phrase_to_stream/2 let us apply DCGs
for describing strings. The predicates `phrase_from_file/[2,3]`,
`phrase_to_file/[2,3]` and `phrase_to_stream/2` let us apply DCGs
transparently to files and streams, and therefore decouple side-effects
from declarative descriptions.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
*/
:- module(pio, [phrase_from_file/2,
phrase_from_file/3,
phrase_from_stream/2,
phrase_to_file/2,
phrase_to_file/3,
phrase_to_stream/2
@@ -19,26 +18,43 @@
:- use_module(library(dcgs)).
:- use_module(library(error)).
:- use_module(library(freeze)).
:- use_module(library(iso_ext), [setup_call_cleanup/3, partial_string/3]).
:- use_module(library(lists), [member/2, maplist/2]).
:- use_module(library(gensym)).
:- use_module(library(iso_ext), [
bb_get/2, bb_put/2, setup_call_cleanup/3, partial_string/3, partial_string_tail/2
]).
:- use_module(library(lists), [append/3, length/2, member/2, maplist/2]).
:- use_module(library(charsio), [get_n_chars/3]).
:- meta_predicate(phrase_from_file(2, ?)).
:- meta_predicate(phrase_from_file(2, ?, ?)).
:- meta_predicate(phrase_from_stream(2, ?)).
:- meta_predicate(phrase_to_file(2, ?)).
:- meta_predicate(phrase_to_file(2, ?, ?)).
:- meta_predicate(phrase_to_stream(2, ?)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
phrase_from_file(GRBody, File)
True if grammar rule body GRBody covers the contents of File,
represented as a list of characters.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% phrase_from_stream(+GRBody, +Stream)
%
% True if grammar rule body GRBody covers the contents of the stream,
% represented as a list of characters.
phrase_from_stream(GRBody, Stream) :-
stream_property(Stream, reposition(Reposition)),
stream_to_lazy_list(Reposition, Stream, Ls),
phrase(GRBody, Ls).
%% phrase_from_file(+GRBody, +File)
%
% True if grammar rule body GRBody covers the contents of File,
% represented as a list of characters.
phrase_from_file(NT, File) :-
phrase_from_file(NT, File, []).
%% phrase_from_file(+GRBody, +File, +Options)
%
% Like `phrase_from_file/2`, using Options to open the file.
phrase_from_file(NT, File, Options) :-
( var(File) -> instantiation_error(phrase_from_file/3)
; must_be(list, Options),
@@ -48,43 +64,145 @@ phrase_from_file(NT, File, Options) :-
member(Type, [text,binary])
; Type = text
),
setup_call_cleanup(open(File, read, Stream, [reposition(true)|Options]),
( stream_to_lazy_list(Stream, Xs),
phrase(NT, Xs) ),
close(Stream))
).
setup_call_cleanup(
open(File, read, Stream, [reposition(true)|Options]),
phrase_from_stream(NT, Stream),
close(Stream)
)
).
% How many chars to read from stream and buffer in each step
chars_to_read(4096).
stream_to_lazy_list(Stream, Xs) :-
stream_property(Stream, position(Pos)),
freeze(Xs, reader_step(Stream, Pos, Xs)).
stream_to_lazy_list(Reposition, Stream, Ls) :-
get_stream_buffer_position(Reposition, Stream, Pos),
freeze(Ls, render_step(Reposition, Stream, Pos, Ls)).
reader_step(Stream, Pos, Xs0) :-
set_stream_position(Stream, Pos),
( at_end_of_stream(Stream)
-> Xs0 = []
; get_n_chars(Stream, 4096, Cs),
partial_string(Cs, Xs0, Xs),
stream_to_lazy_list(Stream, Xs)
).
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(Reposition, Stream, CharsToRead, Chars),
partial_string(Chars, Ls, Ls0),
stream_to_lazy_list(Reposition, Stream, Ls0)
).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
phrase_to_stream(+GRBody, +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.
Emit the list of characters described by the grammar rule body
GRBody to Stream.
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).
An ideal implementation of phrase_to_stream/2 writes each character
as soon as it becomes known and no choice-points remain, and thus
avoids the manifestation of the entire string in memory. See #691
for more information.
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).
The current preliminary implementation is provided so that Prolog
programmers can already get used to describing output with DCGs,
and then writing it to a file when necessary. This simple
implementation suffices as long as the entire contents can be
represented in memory, and thus covers a large number of use cases.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
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),
bb_get(BufferPosId, BufferPos),
( BufferPos = eof ->
Chars = []
; string_get_n_chars(Buffer, BufferPos, N, Chars),
length(Chars, NChars),
( NChars = 0 ->
BufferPos1 = eof
; BufferPos1 is BufferPos + NChars
),
bb_put(BufferPosId, BufferPos1)
).
buffer_prepare_for_n(Stream, BufferId, BufferPosId, BufferLenId, N) :-
bb_get(BufferPosId, BufferPos),
bb_get(BufferLenId, BufferLen),
( BufferLen < BufferPos + N ->
bb_get(BufferId, Buffer),
(
( var(Buffer) ->
BufferTail = Buffer
; partial_string_last_tail(Buffer, BufferTail)
) ->
( at_end_of_stream(Stream) ->
BufferTail = [],
bb_put(BufferId, Buffer)
; chars_to_read(CharsToRead),
get_n_chars(Stream, CharsToRead, Chars),
length(Chars, NChars),
partial_string(Chars, BufferTail, _),
bb_put(BufferId, Buffer),
BufferLen1 is BufferLen + NChars,
bb_put(BufferLenId, BufferLen1),
buffer_prepare_for_n(Stream, BufferId, BufferPosId, BufferLenId, N)
)
; true
)
; true
).
partial_string_last_tail(PartialString, PartialStringTail) :-
partial_string_tail(PartialString, PartialStringTail0),
( var(PartialStringTail0) ->
PartialStringTail = PartialStringTail0
; partial_string_last_tail(PartialStringTail0, PartialStringTail)
).
string_get_n_chars(String, Pos, N, Chars) :-
'$skip_max_list'(_, Pos, String, String1),
'$skip_max_list'(N1, N, String1, _),
length(Chars, N1),
append(Chars, _, String1).
stream_bufferids(Stream, BufferId, BufferPosId, BufferLenId) :-
( bb_get(streams_buffers, _) ->
true
; bb_put(streams_buffers, [])
),
bb_get(streams_buffers, StreamsBuffers),
( member(
stream_buffer(Stream, BufferId, BufferPosId, BufferLenId),
StreamsBuffers
) ->
true
; gensym(buffer, BufferId),
gensym(buffer_pos, BufferPosId),
gensym(buffer_len, BufferLenId),
bb_put(
streams_buffers,
[stream_buffer(Stream, BufferId, BufferPosId, BufferLenId)|StreamsBuffers]
),
bb_put(BufferId, _),
bb_put(BufferPosId, 0),
bb_put(BufferLenId, 0)
).
%% phrase_to_stream(+GRBody, +Stream)
%
% Emit the list of characters described by the grammar rule body
% GRBody to Stream.
%
% An ideal implementation of `phrase_to_stream/2` writes each
% character as soon as it becomes known and no choice-points remain,
% and thus avoids the manifestation of the entire string in memory.
% See [#691](https://github.com/mthom/scryer-prolog/issues/691) for
% more information.
%
% The current preliminary implementation is provided so that Prolog
% programmers can already get used to describing output with DCGs,
% and then writing it to a file when necessary. This simple
% implementation suffices as long as the entire contents can be
% represented in memory, and thus covers a large number of use cases.
phrase_to_stream(GRBody, Stream) :-
phrase(GRBody, Cs),
@@ -101,14 +219,18 @@ phrase_to_stream(GRBody, Stream) :-
% maplist(put_char(Stream), Cs). It also works for binary streams.
'$put_chars'(Stream, Cs).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
phrase_to_file(+GRBody, +File), writing the string described
by GRBody to File.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
%% phrase_to_file(+GRBody, +File)
%
% Write the string described by GRBody to File.
phrase_to_file(GRBody, File) :-
phrase_to_file(GRBody, File, []).
%% phrase_to_file(+GRBody, +File, +Options)
%
% Like `phrase_to_file/2`, using Options to open the file.
phrase_to_file(GRBody, File, Options) :-
setup_call_cleanup(open(File, write, Stream, Options),
phrase_to_stream(GRBody, Stream),

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

@@ -1,24 +1,36 @@
:- module(random, [maybe/0, random/1, random_integer/3, set_random/1]).
/**
This library provides probabilistic predicates and random number generators.
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
To retain desirable declarative properties, predicates that internally
use random numbers should be equipped with an argument that specifies
the random seed. This makes everything completely reproducible.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
To retain desirable declarative properties, predicates that internally
use random numbers should be equipped with an argument that specifies
the random seed. This makes everything completely reproducible.
*/
:- module(random, [maybe/0, random/1, random_integer/3, set_random/1]).
:- use_module(library(error)).
% succeeds with probability 0.5.
%% maybe.
%
% 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).
%
% Generates a random integer number between Lower (inclusive) and Upper (exclusive).
%
% Throws `instantiation_error` if Lower or Upper are variables.
%
% Throws `type_error` if Lower or Upper aren't integers.
random_integer(Lower, Upper, R) :-
var(R),
( (var(Lower) ; var(Upper)) ->
@@ -27,25 +39,14 @@ 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.
% It's necessary to set a seed to provide reproducible executions using this library.
set_random(Seed) :-
( nonvar(Seed) ->
( Seed = seed(S) ->

View File

@@ -1,3 +1,16 @@
/** Predicates from [*Indexing dif/2*](https://arxiv.org/abs/1607.01590).
Example:
```
?- tfilter(=(a), [X,Y], Es).
X = a, Y = a, Es = "aa"
; X = a, Es = "a", dif:dif(a,Y)
; Y = a, Es = "a", dif:dif(a,X)
; Es = [], dif:dif(a,X), dif:dif(a,Y).
```
*/
:- module(reif, [if_/3, (=)/3, (',')/3, (;)/3, cond_t/3, dif/3,
memberd_t/3, tfilter/3, tmember/2, tmember_t/3,
tpartition/4]).

View File

@@ -2,57 +2,70 @@
Predicates for parsing HTML and XML documents.
Written 2020-2022 by Markus Triska (triska@metalevel.at)
Part of Scryer Prolog.
Currently, two predicates are provided:
- load_html(+Source, -Es, +Options)
- load_xml(+Source, -Es, +Options)
These predicates parse HTML and XML documents, respectively.
Source must be one of:
- a list of characters with the document contents
- stream(S), specifying a stream S from which to read the content
- file(Name), where Name is a list of characters specifying a file name.
Es is unified with the abstract syntax tree of the parsed document,
represented as a list of elements where each is of the form:
* a list of characters, representing text
* element(Name, Attrs, Children)
- Name, an atom, is the name of the tag
- Attrs is a list of Key=Value pairs:
Key is an atom, and Value is a list of characters
- Children is a list of elements as specified here.
Currently, Options are ignored. In the future, more options may be
provided to control parsing.
Example:
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []).
Yielding:
Es = [element(html,[],
[element(head,[],
[element(title,[],
["Hello!"])]),
element(body,[],[])])].
library(xpath) provides convenient reasoning about parsed documents.
For example, to fetch the title of the document above, we can use:
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []),
xpath(Es, //title(text), T).
Yielding T = "Hello!".
Use http_open/3 from library(http/http_open) to read answers from
web servers via streams.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/** Predicates for parsing HTML and XML documents.
Currently, two predicates are provided:
- `load_html(+Source, -Es, +Options)`
- `load_xml(+Source, -Es, +Options)`
These predicates parse HTML and XML documents, respectively.
Source must be one of:
- a list of characters with the document contents
- `stream(S)`, specifying a stream S from which to read the content
- `file(Name)`, where Name is a list of characters specifying a file name.
Es is unified with the abstract syntax tree of the parsed document,
represented as a list of elements where each is of the form:
* a list of characters, representing text
* `element(Name, Attrs, Children)`
- `Name`, an atom, is the name of the tag
- `Attrs` is a list of `Key=Value` pairs:
`Key` is an atom, and `Value` is a list of characters
- `Children` is a list of elements as specified here.
Currently, Options are ignored. In the future, more options may be
provided to control parsing.
Example:
```
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []).
```
Yielding:
```
Es = [element(html,[],
[element(head,[],
[element(title,[],
["Hello!"])]),
element(body,[],[])])].
```
`library(xpath)` provides convenient reasoning about parsed documents.
For example, to fetch the title of the document above, we can use:
```
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []),
xpath(Es, //title(text), T).
```
Yielding `T = "Hello!"`.
Use `http_open/3` from `library(http/http_open)` to read answers from
web servers via streams.
*/
:- module(sgml, [load_html/3,
load_xml/3]).
@@ -83,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,34 +1,46 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Safe type tests
===============
/** Safe type tests.
"si" stands for "sufficiently instantiated".
"si" stands for "sufficiently instantiated". It can also be read as
"safe inference", so possibly also other predicates are candidates
for this library.
These predicates:
A safe type test:
- throw instantiation errors if the argument is
- throws an *instantiation error* if the argument is
not sufficiently instantiated to make a sound decision
- succeed if the argument is of the specified type
- fail otherwise.
- *succeeds* if the argument is of the specified type
- *fails* otherwise.
For instance, atom_si(A) yields an *instantiation error* if A is a
For instance, `atom_si(A)` yields an *instantiation error* if `A` is a
variable. This is logically sound, since in that case the argument
is not sufficiently instantiated to make any decision.
The definitions are taken from:
The definitions are taken from [Safer type tests in Prolog](https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog).
https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog
Examples:
"si" can also be read as "safe inference", so possibly also other
predicates are candidates for this library.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
```
?- chars_si(Cs).
error(instantiation_error,list_si/1).
?- chars_si([h|Cs]).
error(instantiation_error,list_si/1).
?- chars_si("hello").
true.
?- chars_si(hello).
false.
```
*/
:- module(si, [atom_si/1,
integer_si/1,
atomic_si/1,
list_si/1,
chars_si/1]).
character_si/1,
term_si/1,
chars_si/1,
dif_si/2,
when_si/2]).
:- use_module(library(lists)).
@@ -53,6 +65,65 @@ list_si(L0) :-
; throw(error(instantiation_error, list_si/1))
).
chars_si(Cs) :-
list_si(Cs),
'$is_partial_string'(Cs).
character_si(Ch) :-
functor(Ch,Ch,0),
atom(Ch),
atom_length(Ch,1).
term_si(Term) :-
( ground(Term) -> acyclic_term(Term)
; throw(error(instantiation_error, term_si/1))
).
chars_si(Chs0) :-
'$skip_max_list'(_,_, Chs0,Chs),
( nonvar(Chs) -> Chs == [] ; true ), % fails for infinite lists too
failnochars(Chs0, Uninstantiated),
( nonvar(Uninstantiated)
-> throw(error(instantiation_error, chars_si/1))
; true
).
failnochars(Chs0, U) :-
( var(Chs0) -> U = true
; Chs0 == [] -> true
; Chs0 = [Ch|Chs1],
( nonvar(Ch) -> atom(Ch), atom_length(Ch,1)
; U = true
),
failnochars(Chs1, U)
).
dif_si(X, Y) :-
X \== Y,
( X \= Y -> true
; throw(error(instantiation_error,dif_si/2))
).
:- meta_predicate(when_si(+, 0)).
%% when_si(Condition, Goal).
%
% Executes Goal when Condition becomes true. Throws an instantiation error if
% it can't decide.
when_si(Condition, Goal) :-
% Taken from https://stackoverflow.com/a/40449516
( when_condition_si(Condition) ->
( Condition ->
Goal
; throw(error(instantiation_error,when_si/2))
)
; throw(error(domain_error(when_condition_si, Condition),_))
).
when_condition_si(Cond) :-
var(Cond), !, throw(error(instantiation_error,when_condition_si/2)).
when_condition_si(ground(_)).
when_condition_si(nonvar(_)).
when_condition_si((A, B)) :-
when_condition_si(A),
when_condition_si(B).
when_condition_si((A ; B)) :-
when_condition_si(A),
when_condition_si(B).

View File

@@ -77,9 +77,9 @@ thesis project, for example.
A *linear programming problem* or simply *linear program* (LP)
consists of:
- a set of _linear_ **constraints**
- a set of **variables**
- a _linear_ **objective function**.
- a set of _linear_ *constraints*
- a set of *variables*
- a _linear_ *objective function*.
The goal is to assign values to the variables so as to _maximize_ (or
minimize) the value of the objective function while satisfying all
@@ -107,10 +107,10 @@ non-negativity constraints should therefore be stated explicitly.
This is the "radiation therapy" example, taken from _Introduction to
Operations Research_ by Hillier and Lieberman.
[**Prolog DCG notation**](https://www.metalevel.at/prolog/dcg) is
[*Prolog DCG notation*](https://www.metalevel.at/prolog/dcg) is
used to _implicitly_ thread the state through posting the constraints:
==
```
:- use_module(library(simplex)).
:- use_module(library(dcgs)).
@@ -125,15 +125,15 @@ post_constraints -->
constraint([0.6*x1, 0.4*x2] >= 6),
constraint([x1] >= 0),
constraint([x2] >= 0).
==
```
An example query:
==
```
?- radiation(S), variable_value(S, x1, Val1),
variable_value(S, x2, Val2).
S = solved(...), Val1 = 15 rdiv 2, Val2 = 9 rdiv 2.
==
```
## Example 2 {#simplex-ex-2}
@@ -143,7 +143,7 @@ Here is an instance of the knapsack problem described above, where `C
variables, `x(1)` and `x(2)` that denote how many items to take of
each type.
==
```
:- use_module(library(simplex)).
knapsack(S) :-
@@ -155,15 +155,15 @@ knapsack_constraints(S) :-
constraint([6*x(1), 4*x(2)] =< 8, S0, S1),
constraint([x(1)] =< 1, S1, S2),
constraint([x(2)] =< 2, S2, S).
==
```
An example query yields:
==
```
?- knapsack(S), variable_value(S, x(1), X1),
variable_value(S, x(2), X2).
S = solved(...), X1 = 1 rdiv 1, X2 = 1 rdiv 2.
==
```
That is, we are to take the one item of the first type, and half of one of
the items of the other type to maximize the total value of items in the
@@ -171,23 +171,23 @@ knapsack.
If items can not be split, integrality constraints have to be imposed:
==
```
knapsack_integral(S) :-
knapsack_constraints(S0),
constraint(integral(x(1)), S0, S1),
constraint(integral(x(2)), S1, S2),
maximize([7*x(1), 4*x(2)], S2, S).
==
```
Now the result is different:
==
```
?- knapsack_integral(S), variable_value(S, x(1), X1),
variable_value(S, x(2), X2).
X1 = 0
X2 = 2
==
```
That is, we are to take only the _two_ items of the second type.
Notice in particular that always choosing the remaining item with best
@@ -207,7 +207,7 @@ The task is to find a _minimal_ number of these coins that amount to
111 units in total. We introduce variables `c(1)`, `c(5)` and `c(20)`
denoting how many coins to take of the respective type:
==
```
:- use_module(library(simplex)).
coins(S) :-
@@ -226,16 +226,16 @@ coins -->
constraint(integral(c(5))),
constraint(integral(c(20))),
minimize([c(1), c(5), c(20)]).
==
```
An example query:
==
```
?- coins(S), variable_value(S, c(1), C1),
variable_value(S, c(5), C5),
variable_value(S, c(20), C20).
S = solved(...), C1 = 1 rdiv 1, C5 = 2 rdiv 1, C20 = 5 rdiv 1.
==
```
@author [Markus Triska](https://www.metalevel.at)
*/

View File

@@ -1,4 +1,9 @@
/**
Predicates for handling network sockets, both as a server and as a client.
As a server, you should open a socket an call `socket_server_accept/4` to get a stream for each connection.
As a client, you should just open a socket and you will receive a stream.
In both cases, with a stream, you can use the usual predicates to read and write to the stream.
*/
:- module(sockets, [socket_client_open/3,
socket_server_open/2,
socket_server_accept/4,
@@ -7,6 +12,18 @@
:- use_module(library(error)).
%% socket_client_open(+Addr, -Stream, +Options).
%
% Open a socket to a server, returning a stream. Addr must satisfy `Addr = Address:Port`.
%
% The following options are available:
%
% * `alias(+Alias)`: Set an alias to the stream
% * `eof_action(+Action)`: Defined what happens if the end of the stream is reached. Values: `error`, `eof_code` and `reset`.
% * `reposition(+Boolean)`: Specifies whether repositioning is required for the stream. `false` is the default.
% * `type(+Type)`: Type can be `text` or `binary`. Defines the type of the stream, if it's optimized for plain text
% or just binary
%
socket_client_open(Addr, Stream, Options) :-
( var(Addr) ->
throw(error(instantiation_error, socket_client_open/3))
@@ -27,7 +44,11 @@ socket_client_open(Addr, Stream, Options) :-
socket_client_open/3),
'$socket_client_open'(Address, Port, Stream, Alias, EOFAction, Reposition, Type).
%% socket_server_open(+Addr, -ServerSocket).
%
% Open a server socket, returning a ServerSocket. Use that ServerSocket to accept incoming connections in
% `socket_server_accept/4`. Addr must satisfy `Addr = Address:Port`. Depending on the operating system
% configuration, some ports might be reserved for superusers.
socket_server_open(Addr, ServerSocket) :-
must_be(var, ServerSocket),
( ( integer(Addr) ; var(Addr) ) ->
@@ -39,7 +60,19 @@ socket_server_open(Addr, ServerSocket) :-
'$socket_server_open'(Address, Port, ServerSocket)
).
%% socket_server_accept(+ServerSocket, -Client, -Stream, +Options).
%
% Given a ServerSocket and a list of Options, accepts a incoming connection, returning data from the Client and
% a Stream to read or write data.
%
% The following options are available:
%
% * `alias(+Alias)`: Set an alias to the stream
% * `eof_action(+Action)`: Defined what happens if the end of the stream is reached. Values: `error`, `eof_code` and `reset`.
% * `reposition(+Boolean)`: Specifies whether repositioning is required for the stream. `false` is the default.
% * `type(+Type)`: Type can be `text` or `binary`. Defines the type of the stream, if it's optimized for plain text
% or just binary
%
socket_server_accept(ServerSocket, Client, Stream, Options) :-
must_be(var, Client),
must_be(var, Stream),
@@ -48,10 +81,14 @@ socket_server_accept(ServerSocket, Client, Stream, Options) :-
socket_server_accept/4),
'$socket_server_accept'(ServerSocket, Client, Stream, Alias, EOFAction, Reposition, Type).
%% socket_server_close(+ServerSocket).
%
% Stops listening on that ServerSocket. It's recommended to always close a ServerSocket once it's no longer needed
socket_server_close(ServerSocket) :-
'$socket_server_close'(ServerSocket).
%% current_hostname(-HostName).
%
% Returns the current hostname of the computer in which Scryer Prolog is executing right now
current_hostname(HostName) :-
'$current_hostname'(HostName).

View File

@@ -1,3 +1,29 @@
/** Tabling, also called SLG resolution.
SLG resolution is an alternative execution strategy that sometimes
helps to improve termination and performance characteristics of Prolog
predicates.
To enable this execution strategy for a Prolog predicate, add a
`(table)/1` directive, using the prefix operator `table` that this
module defines. For example, to enable tabling for the predicate
`p/2`, use:
```
:- use_module(library(tabling)).
:- table p/2.
...
```
The possibility to apply different execution strategies is one of
the greatest attractions of pure Prolog code, and one of the
strongest arguments for keeping to the pure core of Prolog as far
as possible.
Scryer Prolog implements tabling as described by Desouter et al. in [*Tabling as a Library with Delimited Control*](https://www.ijcai.org/Proceedings/16/Papers/619.pdf).
*/
:- module(tabling,
[ start_tabling/2, % +Wrapper, :Worker.
@@ -67,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,
@@ -138,7 +164,9 @@ activate(Wrapper,Worker,T) :-
delim(Wrapper,Worker,Table) :-
% debug(tabling, 'ACT: ~p on ~p', [Wrapper, Table]),
reset(Worker,SourceCall,Continuation),
catch(reset(Worker,SourceCall,Continuation),
_,
fail),
( Continuation = none ->
( add_answer(Table,Wrapper)
-> true %debug(tabling, 'ADD: ~p', [Wrapper])

View File

@@ -49,12 +49,20 @@
:- use_module(library(tabling/double_linked_list)).
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- use_module(library(lists)).
:- attribute executing_all_work/1, worklist_presence/1, wkl_answer_cluster/1, wkl_suspension_cluster/1, wkl_answer_cluster_pointer_flag/1.
verify_attributes(_, _, []).
attribute_goals(X) -->
{ put_atts(X, -executing_all_work(_)),
put_atts(X, -worklist_presence(_)),
put_atts(X, -wkl_answer_cluster(_)),
put_atts(X, -wkl_suspension_cluster(_)),
put_atts(X, -wkl_answer_cluster_pointer_flag(_)) }.
/** <module> Tabling Worklist management
A batched worklist: a worklist that clusters suspensions and answers as

View File

@@ -49,9 +49,15 @@
]).
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- attribute dll_element/1, dll_next/1, dll_prev/1.
attribute_goals(X) -->
{ put_atts(X, -dll_element(_)),
put_atts(X, -dll_next(_)),
put_atts(X, -dll_prev(_)) }.
% A circular double linked list
% =============================

View File

@@ -9,12 +9,15 @@
]).
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- use_module(library(iso_ext)).
:- attribute table_global_worklist/1.
verify_attributes(_, _, []).
attribute_goals(X) --> { put_atts(X, -table_global_worklist(_)) }.
put_new_global_worklist :-
( bb_get(table_global_worklist_initialized, _) ->
true

View File

@@ -56,6 +56,7 @@
:- use_module(library(tabling/batched_worklist)).
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- use_module(library(gensym)).
:- use_module(library(iso_ext)).
@@ -63,6 +64,10 @@
verify_attributes(_, _, []).
attribute_goals(X) -->
{ put_atts(X, -table_status(_)),
put_atts(X, -newly_created_table_identifiers(_)) }.
% This file defines the table datastructure.
%
% The table datastructure contains the following sub-structures:

View File

@@ -43,6 +43,7 @@
]).
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- use_module(library(lists)).
:- use_module(library(iso_ext)).
:- use_module(library(terms)).
@@ -53,6 +54,9 @@
verify_attributes(_, _, []).
attribute_goals(X) -->
{ put_atts(X, -trie_table_link(_)) }.
% This file defines a call pattern trie.
%
% This data structure keeps the relation between a variant and the

View File

@@ -45,12 +45,17 @@
:- use_module(library(assoc)).
:- use_module(library(atts)).
:- use_module(library(dcgs)).
:- use_module(library(lists)).
:- attribute maybe_just/1, children/1.
verify_attributes(_, _, []).
attribute_goals(X) -->
{ put_atts(X, -maybe_just(_)),
put_atts(X, -children(_)) }.
% Implementation of a prefix tree, a.k.a. trie %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@@ -1,48 +1,18 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written 2020, 2021 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.
current_time(T) yields the current system time in an opaque form,
called a time stamp. Use format_time//2 to describe strings that
contain attributes of the time stamp.
The nonterminal format_time//2 describes a list of characters that
are formatted according to a format string. Usage:
phrase(format_time(FormatString, TimeStamp), Cs)
TimeStamp represents a moment in time in an opaque form, as for
example obtained by current_time/1.
FormatString is a list of characters that are interpreted literally,
except for the following specifiers (and possibly more in the future):
%Y year of the time stamp. Example: 2020.
%m month number (01-12), zero-padded to 2 digits
%d day number (01-31), zero-padded to 2 digits
%H hour number (00-24), zero-padded to 2 digits
%M minute number (00-59), zero-padded to 2 digits
%S second number (00-60), zero-padded to 2 digits
%b abbreviated month name, always 3 letters
%a abbreviated weekday name, always 3 letters
%A full weekday name
%j day of the year (001-366), zero-padded to 3 digits
%% the literal %
Example:
?- current_time(T), phrase(format_time("%d.%m.%Y (%H:%M:%S)", T), Cs).
T = [...], Cs = "11.06.2020 (00:24:32)".
sleep(S) sleeps for S seconds (a floating point number).
time(Goal) reports the execution time of Goal.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
:- module(time, [max_sleep_time/1, sleep/1, time/1, current_time/1, format_time//2]).
/** This library provides predicates for reasoning about time.
*/
:- 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)).
@@ -51,10 +21,51 @@
:- use_module(library(lists)).
:- use_module(library(charsio), [read_from_chars/2]).
%% current_time(-T)
%
% Yields the current system time _T_ in an opaque form, called a
% _time stamp_. Use `format_time//2` to describe strings that contain
% attributes of the time stamp.
current_time(T) :-
'$current_time'(T0),
read_from_chars(T0, T).
%% format_time(FormatString, TimeStamp)//
%
% The nonterminal format_time//2 describes a list of characters that
% are formatted according to a format string. Usage:
%
% ```
% phrase(format_time(FormatString, TimeStamp), Cs)
% ```
%
% TimeStamp represents a moment in time in an opaque form, as for
% example obtained by `current_time/1`.
%
% FormatString is a list of characters that are interpreted literally,
% except for the following specifiers (and possibly more in the future):
%
% | `%Y` | year of the time stamp. Example: 2020. |
% | `%m` | month number (01-12), zero-padded to 2 digits |
% | `%d` | day number (01-31), zero-padded to 2 digits |
% | `%H` | hour number (00-24), zero-padded to 2 digits |
% | `%M` | minute number (00-59), zero-padded to 2 digits |
% | `%S` | second number (00-60), zero-padded to 2 digits |
% | `%b` | abbreviated month name, always 3 letters |
% | `%a` | abbreviated weekday name, always 3 letters |
% | `%A` | full weekday name |
% | `%j` | day of the year (001-366), zero-padded to 3 digits |
% | `%%` | the literal `%` |
%
% Example:
%
% ```
% ?- current_time(T), phrase(format_time("%d.%m.%Y (%H:%M:%S)", T), Cs).
% T = [...], Cs = "11.06.2020 (00:24:32)".
% ```
format_time([], _) --> [].
format_time(['%','%'|Fs], T) --> !, "%", format_time(Fs, T).
format_time(['%',Spec|Fs], T) --> !,
@@ -65,8 +76,17 @@ format_time(['%',Spec|Fs], T) --> !,
format_time(Fs, T).
format_time([F|Fs], T) --> [F], format_time(Fs, T).
%% max_sleep_time(T)
%
% The maximum admissible time span for `sleep/1`.
max_sleep_time(0xfffffffffffffbff).
%% sleep(S)
%
% Sleeps for S seconds (a floating point number or integer).
sleep(T) :-
builtins:must_be_number(T, sleep),
( T < 0 ->
@@ -77,12 +97,21 @@ 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/2).
:- dynamic(time_state/3).
time_next_id(N) :-
( retract(time_id(N0)) ->
@@ -91,10 +120,15 @@ time_next_id(N) :-
),
asserta(time_id(N)).
%% time(Goal)
%
% Reports the execution time of Goal.
time(Goal) :-
'$cpu_now'(T0),
cputime_inferences(T0, I0),
time_next_id(ID),
setup_call_cleanup(asserta(time_state(ID, T0)),
setup_call_cleanup(asserta(time_state(ID, T0, I0)),
( call_cleanup(catch(Goal, E, (report_time(ID),throw(E))),
Det = true),
time_true(ID),
@@ -104,49 +138,72 @@ time(Goal) :-
; report_time(ID),
false
),
retract(time_state(ID, _))).
retract(time_state(ID, _, _))).
cputime_inferences(T, I) :-
'$cpu_now'(T),
'$inference_count'(I).
time_true(ID) :-
report_time(ID).
time_true(ID) :-
% on backtracking, update the stored CPU time for this ID
retract(time_state(ID, _)),
'$cpu_now'(T0),
asserta(time_state(ID, T0)),
retract(time_state(ID, _, _)),
cputime_inferences(T0, I0),
asserta(time_state(ID, T0, I0)),
false.
report_time(ID) :-
time_state(ID, T0),
'$cpu_now'(T),
time_state(ID, T0, I0),
cputime_inferences(T, I),
Time is T - T0,
Inferences0 is I - I0,
% we must subtract the number of inferences that time/1 itself takes;
% this may have to be adapted if the implementation changes,
% so that (for example) true/1 takes exactly 1 inference.
( bb_get('$answer_count', 0) ->
Inferences is Inferences0 - 60,
Pre = " ", Post = ""
; Pre = "", Post = " "
; Inferences is Inferences0 - 9,
Pre = "", Post = " "
),
format("~s% CPU time: ~3fs~n~s", [Pre,Time,Post]).
phrase((Pre,"% CPU time: ", format_("~3f", [Time]), "s, ",
format_("~U", [Inferences])," inference",s_if_necessary(Inferences),"\n",
Post), Cs),
format("~s", [Cs]).
s_if_necessary(Inferences) -->
{ compare(C, 1, Inferences) },
s_(C).
s_(=) --> "".
s_(<) --> "s".
s_(>) --> " (exception?)".
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
?- time((true;false)).
%@ % CPU time: 0.006s
%@ true
%@ ; % CPU time: 0.001s
%@ false.
% CPU time: 0.000s, 1 inference
true
; % CPU time: 0.000s, 0 inference (exception?)
false.
:- time(use_module(library(clpz))).
%@ % CPU time: 3.711s
%@ true.
% CPU time: 0.343s, 409_874 inferences
true.
:- time(use_module(library(lists))).
%@ % CPU time: 0.006s
%@ true.
% CPU time: 0.000s, 19 inferences
true.
?- time(member(X, "abc")).
%@ % CPU time: 0.005s
%@ X = a
%@ ; % CPU time: 0.000s
%@ X = b
%@ ; % CPU time: 0.000s
%@ X = c
%@ ; % CPU time: 0.000s
%@ false.
% CPU time: 0.000s, 1 inference
X = a
; % CPU time: 0.000s, 3 inferences
X = b
; % CPU time: 0.000s, 3 inferences
X = c.
?- time((repeat,false)).
% CPU time: 2.726s, 53_330_502 inferences
error('$interrupt_thrown',repl/0).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

View File

@@ -53,7 +53,7 @@
connect_ugraph/3 % +Graph1, -Start, -Graph
]).
/** <module> Graph manipulation library
/** Graph manipulation library
The S-representation of a graph is a list of (vertex-neighbours) pairs,
where the pairs are in standard order (as produced by keysort) and the
@@ -61,55 +61,56 @@ neighbours of each vertex are also in standard order (as produced by
sort). This form is convenient for many calculations.
A new UGraph from raw data can be created using
vertices_edges_to_ugraph/3.
`vertices_edges_to_ugraph/3`.
Adapted to support some of the functionality of the SICStus ugraphs
library by Vitor Santos Costa.
Ported from YAP 5.0.1 to SWI-Prolog by Jan Wielemaker.
@author R.A.O'Keefe
@author Vitor Santos Costa
@author Jan Wielemaker
@license BSD-2 or Artistic 2.0
Ported from SWI-Prolog to Scryer by [Adrián Arroyo Calle](https://adrianistan.eu)
License: BSD-2 or Artistic 2.0
*/
:- use_module(library(lists)).
:- use_module(library(pairs)).
:- use_module(library(ordsets)).
%! vertices(+Graph, -Vertices)
%% vertices(+Graph, -Vertices)
%
% Unify Vertices with all vertices appearing in Graph. Example:
% Unify Vertices with all vertices appearing in Graph. Example:
%
% ?- vertices([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
% L = [1, 2, 3, 4, 5]
% ```
% ?- vertices([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
% L = [1, 2, 3, 4, 5]
% ```
vertices([], []) :- !.
vertices([Vertex-_|Graph], [Vertex|Vertices]) :-
vertices(Graph, Vertices).
%! vertices_edges_to_ugraph(+Vertices, +Edges, -UGraph) is det.
%% vertices_edges_to_ugraph(+Vertices, +Edges, -UGraph) is det.
%
% Create a UGraph from Vertices and edges. Given a graph with a
% set of Vertices and a set of Edges, Graph must unify with the
% corresponding S-representation. Note that the vertices without
% edges will appear in Vertices but not in Edges. Moreover, it is
% sufficient for a vertice to appear in Edges.
% Create a UGraph from Vertices and edges. Given a graph with a
% set of Vertices and a set of Edges, Graph must unify with the
% corresponding S-representation. Note that the vertices without
% edges will appear in Vertices but not in Edges. Moreover, it is
% sufficient for a vertice to appear in Edges.
%
% ==
% ?- vertices_edges_to_ugraph([],[1-3,2-4,4-5,1-5], L).
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[]]
% ==
% ```
% ?- vertices_edges_to_ugraph([],[1-3,2-4,4-5,1-5], L).
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[]]
% ```
%
% In this case all vertices are defined implicitly. The next
% example shows three unconnected vertices:
% In this case all vertices are defined implicitly. The next
% example shows three unconnected vertices:
%
% ==
% ?- vertices_edges_to_ugraph([6,7,8],[1-3,2-4,4-5,1-5], L).
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[], 6-[], 7-[], 8-[]]
% ==
% ```
% ?- vertices_edges_to_ugraph([6,7,8],[1-3,2-4,4-5,1-5], L).
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[], 6-[], 7-[], 8-[]]
% ```
vertices_edges_to_ugraph(Vertices, Edges, Graph) :-
sort(Edges, EdgeSet),
@@ -119,15 +120,15 @@ vertices_edges_to_ugraph(Vertices, Edges, Graph) :-
p_to_s_group(VertexSet, EdgeSet, Graph).
%! add_vertices(+Graph, +Vertices, -NewGraph)
%% add_vertices(+Graph, +Vertices, -NewGraph)
%
% Unify NewGraph with a new graph obtained by adding the list of
% Vertices to Graph. Example:
% Unify NewGraph with a new graph obtained by adding the list of
% Vertices to Graph. Example:
%
% ```
% ?- add_vertices([1-[3,5],2-[]], [0,1,2,9], NG).
% NG = [0-[], 1-[3,5], 2-[], 9-[]]
% ```
% ```
% ?- add_vertices([1-[3,5],2-[]], [0,1,2,9], NG).
% NG = [0-[], 1-[3,5], 2-[], 9-[]]
% ```
% replace with real msort/2 when available
msort_(List, Sorted) :-
@@ -159,23 +160,18 @@ add_empty_vertices([], []).
add_empty_vertices([V|G], [V-[]|NG]) :-
add_empty_vertices(G, NG).
%! del_vertices(+Graph, +Vertices, -NewGraph) is det.
%% del_vertices(+Graph, +Vertices, -NewGraph) is det.
%
% Unify NewGraph with a new graph obtained by deleting the list of
% Vertices and all the edges that start from or go to a vertex in
% Vertices to the Graph. Example:
% Unify NewGraph with a new graph obtained by deleting the list of
% Vertices and all the edges that start from or go to a vertex in
% Vertices to the Graph. Example:
%
% ==
% ?- del_vertices([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[2,6],8-[]],
% [2,1],
% NL).
% NL = [3-[],4-[5],5-[],6-[],7-[6],8-[]]
% ==
%
% @compat Upto 5.6.48 the argument order was (+Vertices, +Graph,
% -NewGraph). Both YAP and SWI-Prolog have changed the argument
% order for compatibility with recent SICStus as well as
% consistency with del_edges/3.
% ```
% ?- del_vertices([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[2,6],8-[]],
% [2,1],
% NL).
% NL = [3-[],4-[5],5-[],6-[],7-[6],8-[]]
% ```
del_vertices(Graph, Vertices, NewGraph) :-
sort(Vertices, V1), % JW: was msort
@@ -204,32 +200,32 @@ split_on_del_vertices(>, V, Edges, [_|Vs], Vs, V1, [V-NEdges|NG], NG) :-
ord_subtract(Edges, V1, NEdges).
split_on_del_vertices(=, _, _, [_|Vs], Vs, _, NG, NG).
%! add_edges(+Graph, +Edges, -NewGraph)
%% add_edges(+Graph, +Edges, -NewGraph)
%
% Unify NewGraph with a new graph obtained by adding the list of Edges
% to Graph. Example:
% Unify NewGraph with a new graph obtained by adding the list of Edges
% to Graph. Example:
%
% ```
% ?- add_edges([1-[3,5],2-[4],3-[],4-[5],
% 5-[],6-[],7-[],8-[]],
% [1-6,2-3,3-2,5-7,3-2,4-5],
% NL).
% NL = [1-[3,5,6], 2-[3,4], 3-[2], 4-[5],
% 5-[7], 6-[], 7-[], 8-[]]
% ```
% ```
% ?- add_edges([1-[3,5],2-[4],3-[],4-[5],
% 5-[],6-[],7-[],8-[]],
% [1-6,2-3,3-2,5-7,3-2,4-5],
% NL).
% NL = [1-[3,5,6], 2-[3,4], 3-[2], 4-[5],
% 5-[7], 6-[], 7-[], 8-[]]
% ```
add_edges(Graph, Edges, NewGraph) :-
p_to_s_graph(Edges, G1),
ugraph_union(Graph, G1, NewGraph).
%! ugraph_union(+Graph1, +Graph2, -NewGraph)
%% ugraph_union(+Graph1, +Graph2, -NewGraph)
%
% NewGraph is the union of Graph1 and Graph2. Example:
% NewGraph is the union of Graph1 and Graph2. Example:
%
% ```
% ?- ugraph_union([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
% L = [1-[2], 2-[3,4], 3-[1,2,4]]
% ```
% ```
% ?- ugraph_union([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
% L = [1-[2], 2-[3,4], 3-[1,2,4]]
% ```
ugraph_union(Set1, [], Set1) :- !.
ugraph_union([], Set2, Set2) :- !.
@@ -245,25 +241,25 @@ ugraph_union(<, Head1, Tail1, Head2, Tail2, [Head1|Union]) :-
ugraph_union(>, Head1, Tail1, Head2, Tail2, [Head2|Union]) :-
ugraph_union([Head1|Tail1], Tail2, Union).
%! del_edges(+Graph, +Edges, -NewGraph)
%% del_edges(+Graph, +Edges, -NewGraph)
%
% Unify NewGraph with a new graph obtained by removing the list of
% Edges from Graph. Notice that no vertices are deleted. Example:
% Unify NewGraph with a new graph obtained by removing the list of
% Edges from Graph. Notice that no vertices are deleted. Example:
%
% ```
% ?- del_edges([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[],8-[]],
% [1-6,2-3,3-2,5-7,3-2,4-5,1-3],
% NL).
% NL = [1-[5],2-[4],3-[],4-[],5-[],6-[],7-[],8-[]]
% ```
% ```
% ?- del_edges([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[],8-[]],
% [1-6,2-3,3-2,5-7,3-2,4-5,1-3],
% NL).
% NL = [1-[5],2-[4],3-[],4-[],5-[],6-[],7-[],8-[]]
% ```
del_edges(Graph, Edges, NewGraph) :-
p_to_s_graph(Edges, G1),
graph_subtract(Graph, G1, NewGraph).
%! graph_subtract(+Set1, +Set2, ?Difference)
%% graph_subtract(+Set1, +Set2, ?Difference)
%
% Is based on ord_subtract
% Is based on `ord_subtract/3`
graph_subtract(Set1, [], Set1) :- !.
graph_subtract([], _, []).
@@ -279,12 +275,14 @@ graph_subtract(<, Head1, Tail1, Head2, Tail2, [Head1|Difference]) :-
graph_subtract(>, Head1, Tail1, _, Tail2, Difference) :-
graph_subtract([Head1|Tail1], Tail2, Difference).
%! edges(+Graph, -Edges)
%% edges(+Graph, -Edges)
%
% Unify Edges with all edges appearing in Graph. Example:
% Unify Edges with all edges appearing in Graph. Example:
%
% ?- edges([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
% L = [1-3, 1-5, 2-4, 4-5]
% ```
% ?- edges([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
% L = [1-3, 1-5, 2-4, 4-5]
% ```
edges(Graph, Edges) :-
s_to_p_graph(Graph, Edges).
@@ -324,15 +322,15 @@ s_to_p_graph([], _, P_Graph, P_Graph) :- !.
s_to_p_graph([Neib|Neibs], Vertex, [Vertex-Neib|P], Rest_P) :-
s_to_p_graph(Neibs, Vertex, P, Rest_P).
%! transitive_closure(+Graph, -Closure)
%% transitive_closure(+Graph, -Closure)
%
% Generate the graph Closure as the transitive closure of Graph.
% Example:
% Generate the graph Closure as the transitive closure of Graph.
% Example:
%
% ```
% ?- transitive_closure([1-[2,3],2-[4,5],4-[6]],L).
% L = [1-[2,3,4,5,6], 2-[4,5,6], 4-[6]]
% ```
% ```
% ?- transitive_closure([1-[2,3],2-[4,5],4-[6]],L).
% L = [1-[2,3,4,5,6], 2-[4,5,6], 4-[6]]
% ```
transitive_closure(Graph, Closure) :-
warshall(Graph, Graph, Closure).
@@ -354,23 +352,18 @@ warshall([X-Neibs|G], V, Y, [X-Neibs|NewG]) :-
warshall(G, V, Y, NewG).
warshall([], _, _, []).
%! transpose_ugraph(Graph, NewGraph) is det.
%% transpose_ugraph(Graph, NewGraph) is det.
%
% Unify NewGraph with a new graph obtained from Graph by replacing
% all edges of the form V1-V2 by edges of the form V2-V1. The cost
% is O(|V|*log(|V|)). Notice that an undirected graph is its own
% transpose. Example:
% Unify NewGraph with a new graph obtained from Graph by replacing
% all edges of the form V1-V2 by edges of the form V2-V1. The cost
% is O(|V|\*log(|V|)). Notice that an undirected graph is its own
% transpose. Example:
%
% ==
% ?- transpose([1-[3,5],2-[4],3-[],4-[5],
% 5-[],6-[],7-[],8-[]], NL).
% NL = [1-[],2-[],3-[1],4-[2],5-[1,4],6-[],7-[],8-[]]
% ==
%
% @compat This predicate used to be known as transpose/2.
% Following SICStus 4, we reserve transpose/2 for matrix
% transposition and renamed ugraph transposition to
% transpose_ugraph/2.
% ```
% ?- transpose([1-[3,5],2-[4],3-[],4-[5],
% 5-[],6-[],7-[],8-[]], NL).
% NL = [1-[],2-[],3-[1],4-[2],5-[1,4],6-[],7-[],8-[]]
% ```
transpose_ugraph(Graph, NewGraph) :-
edges(Graph, Edges),
@@ -382,13 +375,15 @@ flip_edges([], []).
flip_edges([Key-Val|Pairs], [Val-Key|Flipped]) :-
flip_edges(Pairs, Flipped).
%! compose(+LeftGraph, +RightGraph, -NewGraph)
%% compose(+LeftGraph, +RightGraph, -NewGraph)
%
% Compose NewGraph by connecting the _drains_ of LeftGraph to the
% _sources_ of RightGraph. Example:
% Compose NewGraph by connecting the _drains_ of LeftGraph to the
% _sources_ of RightGraph. Example:
%
% ?- compose([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
% L = [1-[4], 2-[1,2,4], 3-[]]
% ```
% ?- compose([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
% L = [1-[4], 2-[1,2,4], 3-[]]
% ```
compose(G1, G2, Composition) :-
vertices(G1, V1),
@@ -423,21 +418,17 @@ compose1(=, V1, Vs1, V1, N2, G2, SoFar, Comp) :-
ord_union(N2, SoFar, Next),
compose1(Vs1, G2, Next, Comp).
%! top_sort(+Graph, -Sorted) is semidet.
%! top_sort(+Graph, -Sorted, ?Tail) is semidet.
%% top_sort(+Graph, -Sorted) is semidet.
%
% Sorted is a topological sorted list of nodes in Graph. A
% toplogical sort is possible if the graph is connected and
% acyclic. In the example we show how topological sorting works
% for a linear graph:
% Sorted is a topological sorted list of nodes in Graph. A
% toplogical sort is possible if the graph is connected and
% acyclic. In the example we show how topological sorting works
% for a linear graph:
%
% ==
% ?- top_sort([1-[2], 2-[3], 3-[]], L).
% L = [1, 2, 3]
% ==
%
% The predicate top_sort/3 is a difference list version of
% top_sort/2.
% ```
% ?- top_sort([1-[2], 2-[3], 3-[]], L).
% L = [1, 2, 3]
% ```
top_sort(Graph, Sorted) :-
vertices_and_zeros(Graph, Vertices, Counts0),
@@ -445,6 +436,11 @@ top_sort(Graph, Sorted) :-
select_zeros(Counts1, Vertices, Zeros),
top_sort(Zeros, Sorted, Graph, Vertices, Counts1).
%% top_sort(+Graph, -Sorted, ?Tail) is semidet.
%
% The predicate `top_sort/3` is a difference list version of
% `top_sort/2`.
top_sort(Graph, Sorted0, Sorted) :-
vertices_and_zeros(Graph, Vertices, Counts0),
count_edges(Graph, Vertices, Counts0, Counts1),
@@ -520,17 +516,21 @@ decr_list(Neibs, [_|Vertices], [N|Counts1], [N|Counts2], Zi, Zo) :-
decr_list(Neibs, Vertices, Counts1, Counts2, Zi, Zo).
%! neighbors(+Vertex, +Graph, -Neigbours) is det.
%! neighbours(+Vertex, +Graph, -Neigbours) is det.
%% neighbours(+Vertex, +Graph, -Neigbours) is det.
%
% Neigbours is a sorted list of the neighbours of Vertex in Graph.
% Example:
% Neigbours is a sorted list of the neighbours of Vertex in Graph.
% Example:
%
% ```
% ?- neighbours(4,[1-[3,5],2-[4],3-[],
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
% NL = [1,2,7,5]
% ```
% ```
% ?- neighbours(4,[1-[3,5],2-[4],3-[],
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
% NL = [1,2,7,5]
% ```
%% neighbors(+Vertex, +Graph, -Neigbours) is det.
%
% Same as `neighbours/3`.
neighbors(Vertex, Graph, Neig) :-
neighbours(Vertex, Graph, Neig).
@@ -542,24 +542,24 @@ neighbours(V,[_|G],Neig) :-
neighbours(V,G,Neig).
%! connect_ugraph(+UGraphIn, -Start, -UGraphOut) is det.
%% connect_ugraph(+UGraphIn, -Start, -UGraphOut) is det.
%
% Adds Start as an additional vertex that is connected to all vertices
% in UGraphIn. This can be used to create an topological sort for a
% not connected graph. Start is before any vertex in UGraphIn in the
% standard order of terms. No vertex in UGraphIn can be a variable.
% Adds Start as an additional vertex that is connected to all vertices
% in UGraphIn. This can be used to create an topological sort for a
% not connected graph. Start is before any vertex in UGraphIn in the
% standard order of terms. No vertex in UGraphIn can be a variable.
%
% Can be used to order a not-connected graph as follows:
% Can be used to order a not-connected graph as follows:
%
% ```
% top_sort_unconnected(Graph, Vertices) :-
% ( top_sort(Graph, Vertices)
% -> true
% ; connect_ugraph(Graph, Start, Connected),
% top_sort(Connected, Ordered0),
% Ordered0 = [Start|Vertices]
% ).
% ```
% ```
% top_sort_unconnected(Graph, Vertices) :-
% ( top_sort(Graph, Vertices)
% -> true
% ; connect_ugraph(Graph, Start, Connected),
% top_sort(Connected, Ordered0),
% Ordered0 = [Start|Vertices]
% ).
% ```
connect_ugraph([], 0, []) :- !.
connect_ugraph(Graph, Start, [Start-Vertices|Graph]) :-
@@ -567,12 +567,12 @@ connect_ugraph(Graph, Start, [Start-Vertices|Graph]) :-
Vertices = [First|_],
before(First, Start).
%! before(+Term, -Before) is det.
%% before(+Term, -Before) is det.
%
% Unify Before to a term that comes before Term in the standard
% order of terms.
% Unify Before to a term that comes before Term in the standard
% order of terms.
%
% @error instantiation_error if Term is unbound.
% Throws `instantiation_error` if Term is unbound.
before(X, _) :-
var(X),
@@ -585,21 +585,22 @@ before(Number, Start) :-
before(_, 0).
%! complement(+UGraphIn, -UGraphOut)
%% complement(+UGraphIn, -UGraphOut)
%
% UGraphOut is a ugraph with an edge between all vertices that are
% _not_ connected in UGraphIn and all edges from UGraphIn removed.
% Example:
% UGraphOut is a ugraph with an edge between all vertices that are
% _not_ connected in UGraphIn and all edges from UGraphIn removed.
% Example:
%
% ```
% ?- complement([1-[3,5],2-[4],3-[],
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
% NL = [1-[2,4,6,7,8],2-[1,3,5,6,7,8],3-[1,2,4,5,6,7,8],
% 4-[3,5,6,8],5-[1,2,3,4,6,7,8],6-[1,2,3,4,5,7,8],
% 7-[1,2,3,4,5,6,8],8-[1,2,3,4,5,6,7]]
% ```
%
% @tbd Simple two-step algorithm. You could be smarter, I suppose.
% ```
% ?- complement([1-[3,5],2-[4],3-[],
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
% NL = [1-[2,4,6,7,8],2-[1,3,5,6,7,8],3-[1,2,4,5,6,7,8],
% 4-[3,5,6,8],5-[1,2,3,4,6,7,8],6-[1,2,3,4,5,7,8],
% 7-[1,2,3,4,5,6,8],8-[1,2,3,4,5,6,7]]
% ```
% TODO: Simple two-step algorithm. You could be smarter, I suppose.
complement(G, NG) :-
vertices(G,Vs),
@@ -611,13 +612,15 @@ complement([V-Ns|G], Vs, [V-INs|NG]) :-
ord_subtract(Vs,Ns1,INs),
complement(G, Vs, NG).
%! reachable(+Vertex, +UGraph, -Vertices)
%% reachable(+Vertex, +UGraph, -Vertices)
%
% True when Vertices is an ordered set of vertices reachable in
% UGraph, including Vertex. Example:
% True when Vertices is an ordered set of vertices reachable in
% UGraph, including Vertex. Example:
%
% ?- reachable(1,[1-[3,5],2-[4],3-[],4-[5],5-[]],V).
% V = [1, 3, 5]
% ```
% ?- reachable(1,[1-[3,5],2-[4],3-[],4-[5],5-[]],V).
% V = [1, 3, 5]
% ```
reachable(N, G, Rs) :-
reachable([N], G, [N], Rs).

View File

@@ -1,25 +1,32 @@
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Written in February 2021 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
Part of Scryer-Prolog
This library provides reasoning about UUID (only version 4 right now).
There are three predicates:
* uuidv4/1, to generate a new UUIDv4
* uuidv4_string/1, to generate a new UUIDv4 in string hex representation
* uuid_string/2, to converte between UUID list of bytes and UUID hex representation
Examples:
?- uuidv4(X).
X = [42,147,248,242,117,196,79,2,129,159|...].
?- uuidv4_string(X).
X = "428499fc-76e3-4240- ...".
?- uuidv4(X), uuid_string(X, S).
X = [173,12,244,152,139,118,64,139,137,4|...], S = "ad0cf498-8b76-408b- ...".
?- uuid_string(X, "61ae692e-eaf6-4199-8dd3-9f01db70a20b").
X = [97,174,105,46,234,246,65,153,141,211|...].
I place this code in the public domain. Use it in any way you want.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
This library provides reasoning and working with [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)
(only version 4 right now).
There are three predicates:
* `uuidv4/1`, to generate a new UUIDv4
* `uuidv4_string/1`, to generate a new UUIDv4 in string hex representation
* `uuid_string/2`, to converte between UUID list of bytes and UUID hex representation
Examples:
```
?- uuidv4(X).
X = [42,147,248,242,117,196,79,2,129,159|...].
?- uuidv4_string(X).
X = "428499fc-76e3-4240- ...".
?- uuidv4(X), uuid_string(X, S).
X = [173,12,244,152,139,118,64,139,137,4|...], S = "ad0cf498-8b76-408b- ...".
?- uuid_string(X, "61ae692e-eaf6-4199-8dd3-9f01db70a20b").
X = [97,174,105,46,234,246,65,153,141,211|...].
*/
:- module(uuid, [
uuidv4/1,
uuidv4_string/1,
@@ -39,6 +46,10 @@ clock_seq_hi_and_res_clock_seq_low - 2
node - 6
UUID v4 can be generated from a set of 16 random bytes: https://www.rfc-archive.org/getrfc.php?rfc=4122#gsc.tab=0 (section 4.4)
*/
%% uuidv4(-Uuid).
%
% Generates a new UUID v4 (random). It unifies with a list of bytes.
uuidv4(Uuid) :-
crypto_n_random_bytes(16, Bytes),
Bytes = [B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16],
@@ -52,8 +63,15 @@ uuidv4(Uuid) :-
byte_bits(NewTimeHi, NewBitsTimeHi),
Uuid = [B1, B2, B3, B4, B5, B6, NewTimeHi, B8, NewClockSeqHi0, B10, B11, B12, B13, B14, B15, B16].
%% uuidv4_string(-UuidString).
%
% Generates a new UUID v4 (random). It unifies with a string representation of the UUID.
% It is equivalent of calling `uuidv4/1` followed by `uuid_string/2`.
uuidv4_string(String) :- uuidv4(Uuid), uuid_string(Uuid, String).
%% uuid_string(?UuidBytes, ?UuidString).
%
% Translates between the bytes representation and the string representation of the same UUID.
uuid_string(Uuid, String) :-
Uuid = [B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16],
phrase(uuid_([S1, S2, S3, S4, S5]), String),

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