Compare commits
1303 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b70f121e46 | ||
|
|
641765858f | ||
|
|
ba362e2fe0 | ||
|
|
84d5ce0d1e | ||
|
|
419623d158 | ||
|
|
1dd0c599c6 | ||
|
|
c7934ca246 | ||
|
|
27852eafd7 | ||
|
|
d4bde5008d | ||
|
|
74a525a672 | ||
|
|
e9982dc447 | ||
|
|
7028baaf8f | ||
|
|
7837c76c74 | ||
|
|
e5ad70c093 | ||
|
|
7fce688769 | ||
|
|
90c874777c | ||
|
|
70795134af | ||
|
|
8f2e9c6b94 | ||
|
|
ce34ca8f1f | ||
|
|
7c632cf165 | ||
|
|
b3c5a8db80 | ||
|
|
1d2961e047 | ||
|
|
b43f0979d9 | ||
|
|
539a1aee2c | ||
|
|
8e323b9dfa | ||
|
|
53b7d9eec9 | ||
|
|
89d3451767 | ||
|
|
aa98a7e7d6 | ||
|
|
614850ab1d | ||
|
|
140a15f9cb | ||
|
|
ec4a8745e7 | ||
|
|
673329ddb7 | ||
|
|
6a421dd8b0 | ||
|
|
de6c460a51 | ||
|
|
75a94fd0b3 | ||
|
|
7573c64087 | ||
|
|
cbb422f69d | ||
|
|
06f198bc57 | ||
|
|
6586657658 | ||
|
|
a0e598b97e | ||
|
|
8a6ea29c45 | ||
|
|
e1b0ba466b | ||
|
|
f35d6287ab | ||
|
|
53028a9c2a | ||
|
|
33fc2ed10c | ||
|
|
bc02fb3754 | ||
|
|
48b2379fe5 | ||
|
|
7de693eb23 | ||
|
|
7bb9c00356 | ||
|
|
9aacfff35d | ||
|
|
2e728c7051 | ||
|
|
7e973a6da6 | ||
|
|
cb014095ad | ||
|
|
6fe8f64835 | ||
|
|
6111f72b24 | ||
|
|
43041971b7 | ||
|
|
3505cc3ba0 | ||
|
|
05ba5f4358 | ||
|
|
bc616ca7d8 | ||
|
|
99131131af | ||
|
|
44b945a5f3 | ||
|
|
3bff923331 | ||
|
|
a6ccf95076 | ||
|
|
665f319a0e | ||
|
|
fe31afce6b | ||
|
|
ac07d2dfc8 | ||
|
|
71165c6984 | ||
|
|
eab3bff78b | ||
|
|
81dba11ab1 | ||
|
|
388fa5baa9 | ||
|
|
fe3241c07c | ||
|
|
0559ddca2a | ||
|
|
98a046500f | ||
|
|
58cd0d1669 | ||
|
|
6421fe10f8 | ||
|
|
99c85459a7 | ||
|
|
958bf51648 | ||
|
|
e3aa85e2a2 | ||
|
|
29ced36a79 | ||
|
|
f9a5c2d341 | ||
|
|
dde03718e1 | ||
|
|
60d34bea70 | ||
|
|
6fb3b61441 | ||
|
|
11ca168175 | ||
|
|
f02c0eacd8 | ||
|
|
72a566d2f8 | ||
|
|
b56ae28c45 | ||
|
|
cd89d71e0c | ||
|
|
902cd5c3ea | ||
|
|
f9eadc8e6a | ||
|
|
7b8c8fdda7 | ||
|
|
1f3de74cbd | ||
|
|
770ead9c05 | ||
|
|
fe371ff1d1 | ||
|
|
2f783f0aef | ||
|
|
92b85d4ba6 | ||
|
|
e702fe5c68 | ||
|
|
ed92e1b4f4 | ||
|
|
f2b63d1689 | ||
|
|
d51defed06 | ||
|
|
851ea2c45b | ||
|
|
ec97ee5d41 | ||
|
|
78b83ca1e1 | ||
|
|
ea0130d114 | ||
|
|
e66e7c5034 | ||
|
|
ff010678c3 | ||
|
|
5abc72cc8b | ||
|
|
fac8866fb4 | ||
|
|
d58e91303b | ||
|
|
8bd0317e9c | ||
|
|
76b1167842 | ||
|
|
7537686d8f | ||
|
|
6fc340c4db | ||
|
|
38f25af47d | ||
|
|
91df533834 | ||
|
|
9a05ca0667 | ||
|
|
ccf581d86f | ||
|
|
1dc546ac36 | ||
|
|
372b4bca46 | ||
|
|
5c89029462 | ||
|
|
a475a8a899 | ||
|
|
47b5ae7984 | ||
|
|
f5d9a67f36 | ||
|
|
1f90dbec20 | ||
|
|
d713456e29 | ||
|
|
d9d90d1ae8 | ||
|
|
62b61107e0 | ||
|
|
1e0fa56786 | ||
|
|
97d5bf7e97 | ||
|
|
75302ab716 | ||
|
|
742de9e77c | ||
|
|
921046e886 | ||
|
|
781f4afe25 | ||
|
|
10158f62e0 | ||
|
|
42d6749501 | ||
|
|
2fa46e6b6c | ||
|
|
47ec5eb6c6 | ||
|
|
799035c692 | ||
|
|
6c07256dc5 | ||
|
|
20c6a327ea | ||
|
|
bcfdc812f9 | ||
|
|
373b08a91e | ||
|
|
a7154968d8 | ||
|
|
46e432c4d9 | ||
|
|
44a61fa72c | ||
|
|
299df50066 | ||
|
|
335330690f | ||
|
|
f30cee7655 | ||
|
|
b943afb7c7 | ||
|
|
30f3818c66 | ||
|
|
13624568b5 | ||
|
|
e4f64581ec | ||
|
|
d132227860 | ||
|
|
45abb4703d | ||
|
|
a66b93c96f | ||
|
|
e5ea95db8d | ||
|
|
8938331915 | ||
|
|
749767539a | ||
|
|
399b50b4d7 | ||
|
|
82f3731c97 | ||
|
|
658258c8c3 | ||
|
|
d14cce2374 | ||
|
|
f411fb1eb3 | ||
|
|
6a30ccc907 | ||
|
|
1e1426cab0 | ||
|
|
2e750671ca | ||
|
|
147e47c8eb | ||
|
|
7208916230 | ||
|
|
43556f6df0 | ||
|
|
c0c33392f1 | ||
|
|
77ce5a9586 | ||
|
|
8665722367 | ||
|
|
57b52fbe90 | ||
|
|
b052b62b16 | ||
|
|
4df7e94d56 | ||
|
|
fcf2e2db05 | ||
|
|
1c52de9ab1 | ||
|
|
9cd762d88a | ||
|
|
eddcdaabef | ||
|
|
2c76943d35 | ||
|
|
24e3e1794e | ||
|
|
99055b553a | ||
|
|
576f13df60 | ||
|
|
004201abda | ||
|
|
6899c6b63c | ||
|
|
7f5c56a2a8 | ||
|
|
92e07d2be1 | ||
|
|
aff1fdc6aa | ||
|
|
b0b1efcc49 | ||
|
|
eb575b9882 | ||
|
|
e667abb143 | ||
|
|
347ee0211b | ||
|
|
4f4feba6fc | ||
|
|
8cb4dfef62 | ||
|
|
de452bb2c2 | ||
|
|
e8d8b09e52 | ||
|
|
631674db29 | ||
|
|
bc3ae52c7a | ||
|
|
4c795018b4 | ||
|
|
651b7b2f59 | ||
|
|
5cb207d8b3 | ||
|
|
de5de0cfaf | ||
|
|
b8ef367824 | ||
|
|
f1458b772e | ||
|
|
4181c9fab2 | ||
|
|
1fbc7f9842 | ||
|
|
73ed08a412 | ||
|
|
7cac13aefa | ||
|
|
2e114df4c7 | ||
|
|
36ab590c4e | ||
|
|
20da3427c1 | ||
|
|
2811660fa7 | ||
|
|
ef8eb935d4 | ||
|
|
11832b524b | ||
|
|
503e45eae1 | ||
|
|
041ec06dbc | ||
|
|
3841b29db8 | ||
|
|
1c94624958 | ||
|
|
c6f3700de1 | ||
|
|
186bba9d75 | ||
|
|
a3e83d59be | ||
|
|
54166b91eb | ||
|
|
1e5bb2d3da | ||
|
|
8d9a759a7d | ||
|
|
f32b035287 | ||
|
|
a8e6930d19 | ||
|
|
7fa4cb7ee8 | ||
|
|
df5855dbda | ||
|
|
e7f5ba6ca1 | ||
|
|
bcb68fcc3d | ||
|
|
e5ee918063 | ||
|
|
4be320abc8 | ||
|
|
300f5f817f | ||
|
|
574a4d0758 | ||
|
|
a02bd46094 | ||
|
|
cab80d3fa2 | ||
|
|
98d8b00d7a | ||
|
|
40608dc8d4 | ||
|
|
3b7d4a7b36 | ||
|
|
f704fcb41d | ||
|
|
8570f119c0 | ||
|
|
1656d9d7ad | ||
|
|
50c64b8512 | ||
|
|
83b9c6184c | ||
|
|
0ad479498c | ||
|
|
2dd1f6e880 | ||
|
|
da0018efdf | ||
|
|
16f768197b | ||
|
|
d6fe5b5aad | ||
|
|
dfe4fbfdc5 | ||
|
|
9444e62df9 | ||
|
|
dddffb01a6 | ||
|
|
4520bedf8d | ||
|
|
126d7bba57 | ||
|
|
575245c62f | ||
|
|
59264c0aa5 | ||
|
|
ee1bd9e006 | ||
|
|
dfd9e43405 | ||
|
|
8f06ef965a | ||
|
|
ae7cf15c70 | ||
|
|
469d2bd104 | ||
|
|
682b2ada4c | ||
|
|
f4e5426e97 | ||
|
|
6bae789bd6 | ||
|
|
ff19db0084 | ||
|
|
f10d7c05f9 | ||
|
|
f80dff851b | ||
|
|
63cbeb9a3c | ||
|
|
773d3f81fd | ||
|
|
4ab3e23b1f | ||
|
|
d588b18c39 | ||
|
|
97b899f4a3 | ||
|
|
6a913bc4cc | ||
|
|
bf46c4b5c1 | ||
|
|
69a0725c30 | ||
|
|
0c9740fe52 | ||
|
|
43f0b6c28d | ||
|
|
2776beb842 | ||
|
|
d7fa6c0ade | ||
|
|
025412aac0 | ||
|
|
bfa7d3cf41 | ||
|
|
9990780b82 | ||
|
|
dff2e73842 | ||
|
|
e2000859b6 | ||
|
|
3a6aee72a3 | ||
|
|
3fb2e451a2 | ||
|
|
7875b96956 | ||
|
|
d96c9e00b7 | ||
|
|
379c252b89 | ||
|
|
307cb56ef5 | ||
|
|
62e6ca02f9 | ||
|
|
c6fcbe20e1 | ||
|
|
a1b71f0440 | ||
|
|
dc08c26d9f | ||
|
|
669023914a | ||
|
|
e4a677ceea | ||
|
|
8aadc99f1d | ||
|
|
1ea397a807 | ||
|
|
b348f54c33 | ||
|
|
721cf20cf7 | ||
|
|
6ed9a99832 | ||
|
|
1163d14ea1 | ||
|
|
b83631fb20 | ||
|
|
902b08e657 | ||
|
|
b5fdde08aa | ||
|
|
4962c3df11 | ||
|
|
8de3498e07 | ||
|
|
26fdf83a48 | ||
|
|
32af047925 | ||
|
|
282633c877 | ||
|
|
77de570aa4 | ||
|
|
c9df19ca30 | ||
|
|
7c1cd18a06 | ||
|
|
0ca2356be5 | ||
|
|
8329d222cb | ||
|
|
1c3df1cdd7 | ||
|
|
b149805b9e | ||
|
|
99348ec309 | ||
|
|
c6976c0f92 | ||
|
|
cacc7f3193 | ||
|
|
4b9cf0952e | ||
|
|
6d37684e9c | ||
|
|
743412de33 | ||
|
|
11c1ee4481 | ||
|
|
f34703a279 | ||
|
|
5cce8ddd7d | ||
|
|
ff63eacf2c | ||
|
|
8121dce2a4 | ||
|
|
4d910f6bfe | ||
|
|
51c00fce57 | ||
|
|
1ab14ea519 | ||
|
|
c5c7c1913a | ||
|
|
3fc969b38b | ||
|
|
7ed38d6c6c | ||
|
|
fa68fa211c | ||
|
|
69cf2c36bc | ||
|
|
640c637ca8 | ||
|
|
0ad4427f83 | ||
|
|
1bfdea7527 | ||
|
|
6c36d067d7 | ||
|
|
1e60eeef34 | ||
|
|
005570c90a | ||
|
|
c070fbec62 | ||
|
|
f9d44c93fd | ||
|
|
fab5ca9440 | ||
|
|
7b128a9f00 | ||
|
|
fd14869ddc | ||
|
|
07d7d3b13b | ||
|
|
7c10683e46 | ||
|
|
3ffea2d987 | ||
|
|
770665a7b8 | ||
|
|
93ff049e54 | ||
|
|
f3b848537a | ||
|
|
27b971cbfa | ||
|
|
a1ceeb697a | ||
|
|
4a8aa0acbd | ||
|
|
6fa00b5b55 | ||
|
|
c2218faf47 | ||
|
|
21c61b6e3a | ||
|
|
b065e1cd53 | ||
|
|
7d6ce119f5 | ||
|
|
62c23166fa | ||
|
|
25afc11168 | ||
|
|
9e713406d3 | ||
|
|
f33f641f11 | ||
|
|
ca2ddfeed0 | ||
|
|
11cd42379b | ||
|
|
40d3345cd5 | ||
|
|
035e214ef5 | ||
|
|
f630a8cc1a | ||
|
|
0bfb08e464 | ||
|
|
5f6ae3857b | ||
|
|
750544dd2a | ||
|
|
660860bccf | ||
|
|
841aaa78b4 | ||
|
|
93f46a4142 | ||
|
|
a86db1bde8 | ||
|
|
4163437d5a | ||
|
|
a6535c28ea | ||
|
|
2efe95f2fb | ||
|
|
56992570d8 | ||
|
|
39934208c3 | ||
|
|
193bb313fd | ||
|
|
e8334f9b67 | ||
|
|
280ff8b5d0 | ||
|
|
172bb9e96a | ||
|
|
c04f1dea48 | ||
|
|
9114c982e0 | ||
|
|
f644a76281 | ||
|
|
63bb993c02 | ||
|
|
c50291cec8 | ||
|
|
2fe79b5fc3 | ||
|
|
b4fab5a806 | ||
|
|
35d0042be1 | ||
|
|
75dcc3f276 | ||
|
|
9c43974747 | ||
|
|
4be5fc6d43 | ||
|
|
f03336b3a2 | ||
|
|
a5db117ef6 | ||
|
|
142e0c2c3a | ||
|
|
1c8cd85f6c | ||
|
|
c547f67c54 | ||
|
|
c26e9436b4 | ||
|
|
b3239abea1 | ||
|
|
81edd4592f | ||
|
|
7484433e2b | ||
|
|
cb79e83510 | ||
|
|
cac52c0537 | ||
|
|
f5c23fbb16 | ||
|
|
a64a765f32 | ||
|
|
eecfeb2d03 | ||
|
|
54a0313d72 | ||
|
|
7cf6e77f4d | ||
|
|
4968fa0024 | ||
|
|
5e55625733 | ||
|
|
65f64e428e | ||
|
|
d2f5291412 | ||
|
|
9dc1c339ef | ||
|
|
c86304b18e | ||
|
|
7c83a1fb8e | ||
|
|
f02728aab3 | ||
|
|
48283c4dbc | ||
|
|
6bdd7f3a3f | ||
|
|
6e0dd371a0 | ||
|
|
5b1df8c4b3 | ||
|
|
5fa68e253c | ||
|
|
3e14ef634f | ||
|
|
adb5fcf708 | ||
|
|
136463c92e | ||
|
|
ef56193c44 | ||
|
|
4077040d03 | ||
|
|
de440a8c92 | ||
|
|
c75f74f681 | ||
|
|
46c78ad338 | ||
|
|
2b7a8875c0 | ||
|
|
2ee131c52c | ||
|
|
f3901cd8b8 | ||
|
|
7f0536b51d | ||
|
|
af76659830 | ||
|
|
fb658c4918 | ||
|
|
85cc4a80d0 | ||
|
|
6fe85c5779 | ||
|
|
b38a56e7d3 | ||
|
|
cbf18e5c3a | ||
|
|
96bd0d267c | ||
|
|
ec67752db4 | ||
|
|
ad4c17fbb6 | ||
|
|
672979c515 | ||
|
|
efcc2b81cd | ||
|
|
640f29fe0f | ||
|
|
13a0085408 | ||
|
|
74440d503e | ||
|
|
f4769de5c4 | ||
|
|
b843b76b7a | ||
|
|
182afe3b7d | ||
|
|
920e0c6b56 | ||
|
|
b6ce6b7cdb | ||
|
|
a0b5a24853 | ||
|
|
fa11d6bdd4 | ||
|
|
a9aef2bc84 | ||
|
|
bf2b73706a | ||
|
|
83ebce86b6 | ||
|
|
1967518fa2 | ||
|
|
5928d64d7a | ||
|
|
c7ec5a13a5 | ||
|
|
01aeb7515d | ||
|
|
d54c3369b3 | ||
|
|
d24e6100a7 | ||
|
|
cd0e45b70e | ||
|
|
0f0018abe4 | ||
|
|
a95c5e26e6 | ||
|
|
70b6cc8e55 | ||
|
|
13cbff7eab | ||
|
|
86166dbf25 | ||
|
|
b2130c2a48 | ||
|
|
5585e83fd6 | ||
|
|
7b921fc767 | ||
|
|
cfd67c8337 | ||
|
|
dc498d4de8 | ||
|
|
2c67106f26 | ||
|
|
b105c33b79 | ||
|
|
2ccc238119 | ||
|
|
e693b7d33b | ||
|
|
aca0de06cd | ||
|
|
85f4bdbe0b | ||
|
|
1257ba165f | ||
|
|
1c33d2a2ed | ||
|
|
99bd3d3b1a | ||
|
|
d8aed0ac4f | ||
|
|
5ebd4bb2a4 | ||
|
|
c934e06171 | ||
|
|
8a0685a3e9 | ||
|
|
2b018be392 | ||
|
|
4d19c437e0 | ||
|
|
44c274b9e9 | ||
|
|
66f6399b8a | ||
|
|
53a1be78cc | ||
|
|
78b35dc57d | ||
|
|
c65d1204af | ||
|
|
3bfed50d81 | ||
|
|
4523bb5b81 | ||
|
|
75aec69de1 | ||
|
|
9b96735615 | ||
|
|
f74d74fe6e | ||
|
|
77394ba914 | ||
|
|
d13173942d | ||
|
|
afbadd9ea2 | ||
|
|
c25888288a | ||
|
|
4fd059be5f | ||
|
|
bfb3164a0d | ||
|
|
81712f4c82 | ||
|
|
7e97f16f41 | ||
|
|
98086de77e | ||
|
|
24456e9703 | ||
|
|
7f024f3b8d | ||
|
|
67c1b171c7 | ||
|
|
04fbb0c1ce | ||
|
|
327423ab84 | ||
|
|
20cfffdff5 | ||
|
|
d8e126044f | ||
|
|
61f975ea18 | ||
|
|
25d4950216 | ||
|
|
5aa1521819 | ||
|
|
dd030fa18b | ||
|
|
013df58fea | ||
|
|
745ddc2c87 | ||
|
|
f6d3b2f896 | ||
|
|
7da321ab4f | ||
|
|
92fdc7e380 | ||
|
|
2e811de0f5 | ||
|
|
f02cd0ad3c | ||
|
|
a36df33a67 | ||
|
|
4a6bf5fd5f | ||
|
|
26c0b4fc75 | ||
|
|
ce1c8aac4c | ||
|
|
72ceceb7ae | ||
|
|
cf63b588bc | ||
|
|
4e8f7f0a1b | ||
|
|
579816a04f | ||
|
|
cc04872933 | ||
|
|
fad363e64a | ||
|
|
094cf2ac5d | ||
|
|
cc82727d20 | ||
|
|
924750f826 | ||
|
|
ffbf630b5c | ||
|
|
8073a4ba87 | ||
|
|
48a4835819 | ||
|
|
3cf3c0ea99 | ||
|
|
ac61055d43 | ||
|
|
65eb93793c | ||
|
|
ec450fc567 | ||
|
|
2c05ebbded | ||
|
|
0ddda0a864 | ||
|
|
3ff02da314 | ||
|
|
df048a4f42 | ||
|
|
21c36880f1 | ||
|
|
02328d818c | ||
|
|
0f55ba7218 | ||
|
|
1c089a2bbb | ||
|
|
54a887cdc3 | ||
|
|
ca28c76e52 | ||
|
|
a70157003b | ||
|
|
6aa3c7d5d6 | ||
|
|
4ef8c5c47d | ||
|
|
31d17f135a | ||
|
|
03f7b01109 | ||
|
|
c2658dc6da | ||
|
|
30dac8ea41 | ||
|
|
0d28404aad | ||
|
|
2f99bb025c | ||
|
|
bff48e7c7f | ||
|
|
3b67ffa814 | ||
|
|
49b4e4cbcb | ||
|
|
287c308bc3 | ||
|
|
af44d91568 | ||
|
|
40b6890c54 | ||
|
|
7248425a76 | ||
|
|
1dcc1ca524 | ||
|
|
0e17d6acd7 | ||
|
|
c89217903a | ||
|
|
762e6d3ba4 | ||
|
|
e9ae80e250 | ||
|
|
fd7f24e265 | ||
|
|
24450a8827 | ||
|
|
57e8ed65b0 | ||
|
|
3f819e2dfd | ||
|
|
9a7862c322 | ||
|
|
92c77cdff5 | ||
|
|
554e956ef5 | ||
|
|
9fd6e18d59 | ||
|
|
d8a9475460 | ||
|
|
60d9d01a55 | ||
|
|
cb79552dd0 | ||
|
|
f310ff24a5 | ||
|
|
b1963864d2 | ||
|
|
9e85be11fe | ||
|
|
b4e7000eb2 | ||
|
|
0b833bd2f3 | ||
|
|
7c93450aa7 | ||
|
|
e529e7ba21 | ||
|
|
cae32d6a00 | ||
|
|
836f6c1d5b | ||
|
|
1697cd5c7f | ||
|
|
dcd7360b17 | ||
|
|
4fd247f881 | ||
|
|
85bc544fb9 | ||
|
|
14646074be | ||
|
|
1ba040c24d | ||
|
|
56f6772422 | ||
|
|
db43d461b9 | ||
|
|
42a50474da | ||
|
|
cf367024fd | ||
|
|
644559b7f7 | ||
|
|
bb95ed3ad0 | ||
|
|
3947390877 | ||
|
|
e7f1e32ee3 | ||
|
|
5f8cc3c64b | ||
|
|
67d198ac77 | ||
|
|
a154a34f87 | ||
|
|
86c90d77dd | ||
|
|
4e1a4dae6c | ||
|
|
cf345d8174 | ||
|
|
a18da368a1 | ||
|
|
5d3295c40c | ||
|
|
de10ccfdee | ||
|
|
65a8ce8e22 | ||
|
|
5a7da721cd | ||
|
|
b234ef7ea3 | ||
|
|
617c961f88 | ||
|
|
e95355e56e | ||
|
|
ff5e9a793b | ||
|
|
101d0548db | ||
|
|
b6a81c51ab | ||
|
|
ba2cd43144 | ||
|
|
bd720b49f3 | ||
|
|
9590d5200c | ||
|
|
12f890e4a2 | ||
|
|
a9cb826bf3 | ||
|
|
4163cb038d | ||
|
|
b051f39145 | ||
|
|
cfc49243c8 | ||
|
|
29430ec88b | ||
|
|
d520046a4f | ||
|
|
814ce2d672 | ||
|
|
4fd37335f5 | ||
|
|
1791bd8626 | ||
|
|
3f5dbc1680 | ||
|
|
44052cb373 | ||
|
|
8c33da11ce | ||
|
|
ab80c84714 | ||
|
|
c0dd94c8a3 | ||
|
|
f65675836c | ||
|
|
f324c9591d | ||
|
|
3347f830c7 | ||
|
|
568abef5b8 | ||
|
|
703efdb22d | ||
|
|
bb420e9347 | ||
|
|
2f45f0cfed | ||
|
|
112d398175 | ||
|
|
95b31146b5 | ||
|
|
55a1f8d3da | ||
|
|
fba7790637 | ||
|
|
918dfca409 | ||
|
|
d18f128a3c | ||
|
|
c8b9059289 | ||
|
|
b8a6882a27 | ||
|
|
fb8e3071f2 | ||
|
|
067b5998ee | ||
|
|
2e26f37f5e | ||
|
|
6525c1f543 | ||
|
|
4db0b385f3 | ||
|
|
b7f77d1747 | ||
|
|
811ff65209 | ||
|
|
fd70d8975b | ||
|
|
b746a8f9ab | ||
|
|
483e4568a2 | ||
|
|
9ff1b660f1 | ||
|
|
5ffdd2d91a | ||
|
|
5b81bee941 | ||
|
|
3b9b9e75c4 | ||
|
|
faef45fd68 | ||
|
|
b5b45dde9d | ||
|
|
5ab087bc1e | ||
|
|
7683367c0e | ||
|
|
d2db66b9f7 | ||
|
|
c58d8804a1 | ||
|
|
5d09449c95 | ||
|
|
076a75d138 | ||
|
|
7f69ac3f4a | ||
|
|
7f159a7ed2 | ||
|
|
42274ef3ae | ||
|
|
ab893be418 | ||
|
|
f5e7573bd6 | ||
|
|
9cdad087ef | ||
|
|
e36f96fd47 | ||
|
|
cb25a90250 | ||
|
|
38a9d23174 | ||
|
|
521118265a | ||
|
|
a09306c585 | ||
|
|
8140ff9154 | ||
|
|
3cbe78cb9b | ||
|
|
699afb2c00 | ||
|
|
d079a18459 | ||
|
|
b0566e4150 | ||
|
|
c38a26e6cd | ||
|
|
31030738a4 | ||
|
|
caf84a259e | ||
|
|
1620824d3a | ||
|
|
1d2f5053d6 | ||
|
|
42282c6e6e | ||
|
|
a3f8ddd24a | ||
|
|
bfe808a779 | ||
|
|
28065b0565 | ||
|
|
4953cfd10e | ||
|
|
db972de40c | ||
|
|
330e9ba4ef | ||
|
|
bb09de1805 | ||
|
|
a6a0cef9fc | ||
|
|
c36bd4dc07 | ||
|
|
83f352b95e | ||
|
|
c84a5c3282 | ||
|
|
58af615dd4 | ||
|
|
c4b13a2176 | ||
|
|
039fffb339 | ||
|
|
8613513b9c | ||
|
|
ceb276b249 | ||
|
|
16f281e3d1 | ||
|
|
b593fffc7d | ||
|
|
ce890799bc | ||
|
|
aa65287c3b | ||
|
|
a6522d6317 | ||
|
|
0b45d42912 | ||
|
|
d9829a3606 | ||
|
|
59766e2db4 | ||
|
|
e52a4fbfc0 | ||
|
|
c05afb4705 | ||
|
|
9f209dadd9 | ||
|
|
2ec45b7413 | ||
|
|
bf581879e6 | ||
|
|
9bc3757a9e | ||
|
|
18d0a74f23 | ||
|
|
7a188744da | ||
|
|
7f45ac3f7a | ||
|
|
612861e010 | ||
|
|
fcae0d9fcf | ||
|
|
f446939770 | ||
|
|
92853a6a12 | ||
|
|
4ad113a6f8 | ||
|
|
89ed1aa8de | ||
|
|
d7f5675727 | ||
|
|
4d982d22c1 | ||
|
|
5ed1802f0f | ||
|
|
2716381e7b | ||
|
|
c5c83d724a | ||
|
|
749dedf477 | ||
|
|
911c49c43f | ||
|
|
770a682d8b | ||
|
|
73ca37ecca | ||
|
|
e0f49e8f43 | ||
|
|
dae34b6009 | ||
|
|
5850125d97 | ||
|
|
97bd778745 | ||
|
|
43df2e2649 | ||
|
|
c2f2623471 | ||
|
|
8e4465315f | ||
|
|
86beb222ae | ||
|
|
5e124ccf44 | ||
|
|
47d4e6d2f9 | ||
|
|
33f65210ee | ||
|
|
9ea6cb4cab | ||
|
|
0e583d620a | ||
|
|
b205abe949 | ||
|
|
cb59c3003a | ||
|
|
942095baa7 | ||
|
|
097849385e | ||
|
|
c4783062ff | ||
|
|
063cf0c608 | ||
|
|
a66d666bed | ||
|
|
170818759d | ||
|
|
e41d1b319b | ||
|
|
b9c9de5222 | ||
|
|
d565f5901b | ||
|
|
46317c3a39 | ||
|
|
bcc5bff376 | ||
|
|
5244d71570 | ||
|
|
9fac289d9a | ||
|
|
de665c05d8 | ||
|
|
98b0ab3409 | ||
|
|
3c344b176b | ||
|
|
6093c2858d | ||
|
|
e06ab1ca1c | ||
|
|
5154314786 | ||
|
|
fa07306f09 | ||
|
|
07115ce4f5 | ||
|
|
b656700294 | ||
|
|
7bc7f0ad06 | ||
|
|
495df8846a | ||
|
|
05d48cdcc3 | ||
|
|
dc02be4944 | ||
|
|
462097d956 | ||
|
|
0e374c2e96 | ||
|
|
94a1313916 | ||
|
|
b54a4afad6 | ||
|
|
c71b8e05f0 | ||
|
|
021c01dfd0 | ||
|
|
30f222b837 | ||
|
|
f7d9237bb9 | ||
|
|
49addc7b04 | ||
|
|
2f9996f9ac | ||
|
|
c1218fc986 | ||
|
|
57ef706eb0 | ||
|
|
e4b19dc1dd | ||
|
|
4e60cc46a2 | ||
|
|
d8edf7bfff | ||
|
|
e951db662d | ||
|
|
c5a3ec3ba8 | ||
|
|
f12a90351a | ||
|
|
b162c40007 | ||
|
|
402100fd52 | ||
|
|
2a1b8f37ec | ||
|
|
7d2e59ab64 | ||
|
|
f35298a227 | ||
|
|
198e925430 | ||
|
|
8b7281fad0 | ||
|
|
502574dfc3 | ||
|
|
7a0f4e5787 | ||
|
|
f277f660f3 | ||
|
|
9c037d6028 | ||
|
|
396528189d | ||
|
|
4b882c465c | ||
|
|
8937cac47d | ||
|
|
5a3e2899dd | ||
|
|
3a5ed4723b | ||
|
|
dcf4c44173 | ||
|
|
5763a4b9df | ||
|
|
82200a21eb | ||
|
|
fc7d98d748 | ||
|
|
5dce7d9075 | ||
|
|
f08f539768 | ||
|
|
be45672e22 | ||
|
|
94efb9ffe3 | ||
|
|
fd1e902492 | ||
|
|
8d6e3d7d56 | ||
|
|
a197bb6815 | ||
|
|
9de8456c7f | ||
|
|
ebf8091dba | ||
|
|
47ce84b273 | ||
|
|
449e097f4a | ||
|
|
fe27605497 | ||
|
|
8792ee438c | ||
|
|
7279062d3c | ||
|
|
b87fe1e21f | ||
|
|
d6ac125425 | ||
|
|
b79d8732ea | ||
|
|
3df0806017 | ||
|
|
c7759aa737 | ||
|
|
8ab1155fc5 | ||
|
|
adc77985d7 | ||
|
|
d85fc7c9f8 | ||
|
|
41b083c962 | ||
|
|
4ee6a7bfb8 | ||
|
|
6e53d08d40 | ||
|
|
01285f12c3 | ||
|
|
cd586aab8c | ||
|
|
4da646252b | ||
|
|
cc8bb38abc | ||
|
|
04ba9bc11a | ||
|
|
9b35a316c9 | ||
|
|
3a522f3c98 | ||
|
|
4c44859132 | ||
|
|
ecd77f7512 | ||
|
|
56bd596af3 | ||
|
|
21acb9361a | ||
|
|
c3477d8476 | ||
|
|
cba09d4ea1 | ||
|
|
884b0ca10e | ||
|
|
c9ecfb11d9 | ||
|
|
9288dcabe9 | ||
|
|
73df96244d | ||
|
|
7396630627 | ||
|
|
0ac93751d0 | ||
|
|
400ca21213 | ||
|
|
3286e78cd2 | ||
|
|
24eb9ce483 | ||
|
|
3dc6ed79d2 | ||
|
|
f94294dbd9 | ||
|
|
04ba58067a | ||
|
|
92b262d599 | ||
|
|
7ffb40e0ad | ||
|
|
997161c740 | ||
|
|
84c95c59e9 | ||
|
|
669242a8ce | ||
|
|
2a04d5e799 | ||
|
|
9d52d2a653 | ||
|
|
95f6ebc000 | ||
|
|
6e9cd072c5 | ||
|
|
729f2b1cb9 | ||
|
|
1a01438064 | ||
|
|
34ec6d3167 | ||
|
|
c9295323f6 | ||
|
|
92d543b8a8 | ||
|
|
3f445c76be | ||
|
|
a6e416f13d | ||
|
|
601ff567e3 | ||
|
|
56783b8e4b | ||
|
|
326f18ea75 | ||
|
|
491472a8c5 | ||
|
|
359619e035 | ||
|
|
9454d670c7 | ||
|
|
2fcec4fff9 | ||
|
|
2c2a9fe01e | ||
|
|
165a55dac6 | ||
|
|
196e9c1e47 | ||
|
|
17450520ba | ||
|
|
97702f3071 | ||
|
|
e8408ca93f | ||
|
|
e0dcf88b68 | ||
|
|
cc38cf15a4 | ||
|
|
da4c0a359b | ||
|
|
ce56a7303e | ||
|
|
5667ec8699 | ||
|
|
36b3150225 | ||
|
|
28c338416f | ||
|
|
26a8fc1a37 | ||
|
|
f347baafa3 | ||
|
|
95278c221b | ||
|
|
5a28366158 | ||
|
|
ac0cea8a73 | ||
|
|
814b631543 | ||
|
|
0d8c7f8785 | ||
|
|
5bae8fcaf8 | ||
|
|
4110cfcfcb | ||
|
|
2bdba261ea | ||
|
|
58fb851717 | ||
|
|
1118b37c92 | ||
|
|
39606a2277 | ||
|
|
275d306b69 | ||
|
|
e948413c09 | ||
|
|
f8bb3148d0 | ||
|
|
e7cf70936a | ||
|
|
409287e8f8 | ||
|
|
87d6ef16f7 | ||
|
|
3c08eba559 | ||
|
|
d399b68d01 | ||
|
|
2e9ec653a8 | ||
|
|
7ca782b92d | ||
|
|
996496c3f5 | ||
|
|
90cf713186 | ||
|
|
ca4aaf44de | ||
|
|
b04d845ec0 | ||
|
|
a8ea2b0f97 | ||
|
|
a29227d0d4 | ||
|
|
7f8f137aa0 | ||
|
|
cc7e721611 | ||
|
|
058cbcf19a | ||
|
|
22b815dc5c | ||
|
|
c709853aa7 | ||
|
|
d755bb7e12 | ||
|
|
6d99912b7b | ||
|
|
82dd9e0596 | ||
|
|
6ba3d49034 | ||
|
|
64be8e0fba | ||
|
|
43a297b691 | ||
|
|
df266378c4 | ||
|
|
ec55765cb2 | ||
|
|
de2ff8118f | ||
|
|
c30c9dc98f | ||
|
|
1f872c3f0c | ||
|
|
b6204eb6f1 | ||
|
|
af9f0f81d8 | ||
|
|
1c08b56e05 | ||
|
|
6cb8020f62 | ||
|
|
d742d4cde9 | ||
|
|
b7d06540e6 | ||
|
|
909f2e1058 | ||
|
|
26d39c3617 | ||
|
|
371bc3b231 | ||
|
|
bdeabcdd89 | ||
|
|
7f177c3d03 | ||
|
|
4ed36a12e9 | ||
|
|
a7e93db363 | ||
|
|
789715f31a | ||
|
|
7cc900d437 | ||
|
|
ff0f6f4fc2 | ||
|
|
5698e8f21a | ||
|
|
ddcae2c906 | ||
|
|
468d096ccb | ||
|
|
bca79d12c0 | ||
|
|
03eba9594b | ||
|
|
5e0e3e2754 | ||
|
|
04fd835267 | ||
|
|
f072761150 | ||
|
|
46d1e3bee3 | ||
|
|
f8e6e0252d | ||
|
|
f9e3bdb6b0 | ||
|
|
a80aab48cd | ||
|
|
3a4aa2a541 | ||
|
|
84583da5b8 | ||
|
|
f213956ceb | ||
|
|
c5caa9d311 | ||
|
|
0c4d93f01f | ||
|
|
a4e8bfc1ba | ||
|
|
542b9e1976 | ||
|
|
73a1ee59fa | ||
|
|
2771109427 | ||
|
|
cc420bd31a | ||
|
|
2fe1d2ef53 | ||
|
|
bb624cc971 | ||
|
|
aa7b8e52f3 | ||
|
|
07f358c91b | ||
|
|
8ae0a1a4af | ||
|
|
7647ad14b8 | ||
|
|
21e4347b3e | ||
|
|
f2d041ea23 | ||
|
|
56c1c4e43c | ||
|
|
820011412b | ||
|
|
81afa6942f | ||
|
|
6cb8d7596a | ||
|
|
a2d46af5ac | ||
|
|
6d94b8ab75 | ||
|
|
0166c3bf0f | ||
|
|
6a995fb62b | ||
|
|
d804d8a92e | ||
|
|
cf63e8375d | ||
|
|
1eff758751 | ||
|
|
0a8fc70ba9 | ||
|
|
b4b72a3166 | ||
|
|
a5054c0064 | ||
|
|
705f421d53 | ||
|
|
4dc0114c52 | ||
|
|
5f2c77fa74 | ||
|
|
5f703afed1 | ||
|
|
c6aa2068e2 | ||
|
|
209f7a239a | ||
|
|
0d404ad374 | ||
|
|
d5db0c641c | ||
|
|
21d6220f3f | ||
|
|
e0464d5447 | ||
|
|
309e5b320e | ||
|
|
afa9703eb5 | ||
|
|
56e5da6680 | ||
|
|
a8810d73e4 | ||
|
|
4b31c30cab | ||
|
|
df4b56148f | ||
|
|
f22a48f576 | ||
|
|
d660e4244f | ||
|
|
d383e5eb8b | ||
|
|
e1dc114517 | ||
|
|
73780ebf63 | ||
|
|
a19f7a0b9f | ||
|
|
e07473d155 | ||
|
|
d429b263eb | ||
|
|
b2a0d0c1c7 | ||
|
|
8bafd7adb1 | ||
|
|
76d24fe4e3 | ||
|
|
3bdcc3aba9 | ||
|
|
cb25b27963 | ||
|
|
b3df81c143 | ||
|
|
68b3c480c9 | ||
|
|
9fd1bf5574 | ||
|
|
b491a06c6d | ||
|
|
d16312a314 | ||
|
|
d19a8d6b98 | ||
|
|
c90dd80ece | ||
|
|
eb9d865635 | ||
|
|
694c87cb61 | ||
|
|
c41aba6b90 | ||
|
|
28ea672e36 | ||
|
|
9366a48d6d | ||
|
|
c7e1f5d568 | ||
|
|
d91ee5b77c | ||
|
|
fd97b84916 | ||
|
|
23f59970cb | ||
|
|
8781e03863 | ||
|
|
f2940ddfcf | ||
|
|
140149f051 | ||
|
|
9055370326 | ||
|
|
1af62c8d11 | ||
|
|
ec5430826e | ||
|
|
b2cccab768 | ||
|
|
579aa8acd4 | ||
|
|
9c2cb144b5 | ||
|
|
3d04689660 | ||
|
|
a891cc4edf | ||
|
|
706ab2ae5b | ||
|
|
9c1de8e00b | ||
|
|
1109e05e06 | ||
|
|
ce313b8a6b | ||
|
|
7c96b91663 | ||
|
|
87e966e185 | ||
|
|
1e018681de | ||
|
|
5b324680ba | ||
|
|
abca5fc405 | ||
|
|
6a455d2866 | ||
|
|
70da818101 | ||
|
|
5ec837acb4 | ||
|
|
76e33d051c | ||
|
|
7c5318b784 | ||
|
|
47892bf24a | ||
|
|
8a9cd7779c | ||
|
|
d4c0277065 | ||
|
|
069e132c0e | ||
|
|
4e6c138099 | ||
|
|
0ab355eada | ||
|
|
44825826df | ||
|
|
612f09893c | ||
|
|
e9f507b868 | ||
|
|
78278c804f | ||
|
|
b51460a59a | ||
|
|
181be5be3f | ||
|
|
91d4e91f53 | ||
|
|
ad3ae7991b | ||
|
|
c7caf6b7a9 | ||
|
|
4422ffe39f | ||
|
|
1ff52f70aa | ||
|
|
fce45167a5 | ||
|
|
b8f384045c | ||
|
|
ea95a7900c | ||
|
|
edea1273c8 | ||
|
|
ec9c763211 | ||
|
|
5a08117e75 | ||
|
|
0aec980aa8 | ||
|
|
6b05ee5130 | ||
|
|
1ffbf63d20 | ||
|
|
b905d2758d | ||
|
|
311d985145 | ||
|
|
a9a06b5297 | ||
|
|
6b8e620495 | ||
|
|
577f85099d | ||
|
|
607c84a79b | ||
|
|
2eec6499ff | ||
|
|
cd1150c11d | ||
|
|
987bbdecf5 | ||
|
|
a68394b6f2 | ||
|
|
3d6fbabc86 | ||
|
|
1479b5d38c | ||
|
|
336311ecc8 | ||
|
|
e9bb35c895 | ||
|
|
ab4f93dcee | ||
|
|
140a199805 | ||
|
|
232b66cfc3 | ||
|
|
11ba7e47fd | ||
|
|
c36d4a9c9a | ||
|
|
4b5c22864e | ||
|
|
5e1faeb5d2 | ||
|
|
4b7c2ba6c8 | ||
|
|
b9285f8de1 | ||
|
|
084fc84590 | ||
|
|
7a9b71cc03 | ||
|
|
6b49653754 | ||
|
|
d5fa8cc211 | ||
|
|
10a11c293e | ||
|
|
e486862db8 | ||
|
|
1c8aae0839 | ||
|
|
6feb1f8b82 | ||
|
|
bef8eb538c | ||
|
|
4e3b066555 | ||
|
|
4d23542ef3 | ||
|
|
47a6b498e3 | ||
|
|
b6f77f4e6f | ||
|
|
b96cd06129 | ||
|
|
e8eb6765bd | ||
|
|
8df552e952 | ||
|
|
18c52e076f | ||
|
|
1810dabc14 | ||
|
|
609a3a229f | ||
|
|
dff56643f0 | ||
|
|
77f8d52271 | ||
|
|
1e9821ec0c | ||
|
|
66e047083a | ||
|
|
e6438e79d8 | ||
|
|
a5e72679bc | ||
|
|
15cc916333 | ||
|
|
2f3de51e55 | ||
|
|
cea1353fbb | ||
|
|
bfbafd4168 | ||
|
|
27a15a2464 | ||
|
|
c2a87f898e | ||
|
|
aaa75c3ffc | ||
|
|
2dfa108034 | ||
|
|
4895cb7b22 | ||
|
|
2d3eb8483f | ||
|
|
36ef988514 | ||
|
|
d58dd7cc91 | ||
|
|
7e06abebd5 | ||
|
|
8dd7f51ae3 | ||
|
|
6030fae685 | ||
|
|
0f502fff84 | ||
|
|
789f662ec8 | ||
|
|
72536037ca | ||
|
|
e4eefc92f4 | ||
|
|
23c4e935b4 | ||
|
|
68b4951bc9 | ||
|
|
ed89c43e48 | ||
|
|
16137ace0e | ||
|
|
b4ef7556da | ||
|
|
eea83d9474 | ||
|
|
9d5264c5a3 | ||
|
|
66075bf45b | ||
|
|
35b8f69f92 | ||
|
|
2d5fe21b05 | ||
|
|
b448681872 | ||
|
|
05c14d5780 | ||
|
|
bb9de52a53 | ||
|
|
7595ec16e5 | ||
|
|
d8cf0f320d | ||
|
|
d644a3996e | ||
|
|
525050c407 | ||
|
|
468088fd6b | ||
|
|
95c192a988 | ||
|
|
d6287fae06 | ||
|
|
9e98f75495 | ||
|
|
56681af1b6 | ||
|
|
8bf3d4ea71 | ||
|
|
00eab4d415 | ||
|
|
0876d46880 | ||
|
|
ed803d30cd | ||
|
|
b6504bf377 | ||
|
|
85b4f235ef | ||
|
|
064bfa5e0d | ||
|
|
4a6ffb6b9e | ||
|
|
d952b7ace8 | ||
|
|
3b6138eaaa | ||
|
|
f3167f6b5f | ||
|
|
8c99d748e2 | ||
|
|
0ab928063e | ||
|
|
c9a34bde3f | ||
|
|
7f85c682b8 | ||
|
|
181185bad6 | ||
|
|
01a9fd9e25 | ||
|
|
3be00045cf | ||
|
|
86176dce8d | ||
|
|
1fa8a0a969 | ||
|
|
d5fe2e88d1 | ||
|
|
9177dbce7f | ||
|
|
55a896367d | ||
|
|
f2e49c84cc | ||
|
|
afa0bea1b1 | ||
|
|
f7e31fceac | ||
|
|
c490d818fb | ||
|
|
32bdedde6d | ||
|
|
48960f1beb | ||
|
|
cf65cbe2f1 | ||
|
|
45c35eca3e | ||
|
|
bb2a8c9090 | ||
|
|
6798b22b49 | ||
|
|
3acbe2a418 | ||
|
|
c45cdd6ea0 | ||
|
|
f520c38b60 | ||
|
|
046a2175ab | ||
|
|
83def95383 | ||
|
|
012fefa37e | ||
|
|
4878e42e07 | ||
|
|
1a7992e524 | ||
|
|
94d7c6dc5f | ||
|
|
23bfb4da50 | ||
|
|
1f21a5fe62 | ||
|
|
2173a8e3cb | ||
|
|
067e2633af | ||
|
|
d916da9ba8 | ||
|
|
c2fc33afec | ||
|
|
3d4e400ed2 | ||
|
|
75d719d204 | ||
|
|
172e3e9f34 | ||
|
|
8e32bdd8db | ||
|
|
895b02b641 | ||
|
|
d06b5f7ab5 | ||
|
|
e88ec6736c | ||
|
|
922abf21e7 | ||
|
|
c55bfa2c6e | ||
|
|
5463c1d47f | ||
|
|
3513a9c7ba | ||
|
|
45db236ec7 | ||
|
|
5848ff5735 | ||
|
|
64c9ac4071 | ||
|
|
75fc922898 | ||
|
|
59e2c3fd5e | ||
|
|
5bd73741cf | ||
|
|
fcfa3beaa3 | ||
|
|
dfbdaa9dab | ||
|
|
5f1f07e5a1 | ||
|
|
eace0d9b37 | ||
|
|
d83b32a200 | ||
|
|
9cefb33388 | ||
|
|
bd5d6d0686 | ||
|
|
ecebaf8216 | ||
|
|
8762647b55 | ||
|
|
664c0ee77c | ||
|
|
7a9c3d351d | ||
|
|
69e52d1ed8 | ||
|
|
bd594e0c05 | ||
|
|
3bf42860e2 | ||
|
|
0532f51cc2 | ||
|
|
1808597258 | ||
|
|
2744989cf1 | ||
|
|
0d04abb5c7 | ||
|
|
495168e7bb | ||
|
|
475fbf9561 | ||
|
|
c6264cf098 | ||
|
|
bc9123affb | ||
|
|
7fd12972ad | ||
|
|
8ad4f188f2 | ||
|
|
495025dafb |
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Resolved all lints and formatted the codebase
|
||||
9444e62df9820d6bfd96dbd8849e177bc5cecc2e
|
||||
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.png binary
|
||||
*.pl text eol=lf
|
||||
*.rs text eol=lf diff=rust
|
||||
*.md text eol=lf diff=markdown
|
||||
52
.github/actions/setup-rust/action.yml
vendored
Normal file
52
.github/actions/setup-rust/action.yml
vendored
Normal 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@v3
|
||||
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@v0.4.6
|
||||
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
13
.github/actions/setup-rust/cleanup.sh
vendored
Executable 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
|
||||
202
.github/workflows/ci.yml
vendored
Normal file
202
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
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@v3
|
||||
- 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
|
||||
|
||||
build-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# operating systems
|
||||
- { os: windows-latest, rust-version: stable, target: 'x86_64-pc-windows-msvc', publish: true }
|
||||
- { os: macos-11, rust-version: stable, target: 'x86_64-apple-darwin', publish: true }
|
||||
- { os: ubuntu-20.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true }
|
||||
# architectures
|
||||
- { os: ubuntu-22.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true }
|
||||
- { os: ubuntu-22.04, rust-version: stable, target: 'i686-unknown-linux-gnu', publish: true }
|
||||
# FIXME(issue #2138): run wasm tests, failing to run since https://github.com/mthom/scryer-prolog/pull/2137 removed wasm-pack
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features' }
|
||||
# rust versions
|
||||
- { os: ubuntu-22.04, rust-version: "1.70", target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu'}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: ${{ matrix.rust-version }}
|
||||
targets: ${{ matrix.target }}
|
||||
cache-context: ${{ matrix.os }}
|
||||
|
||||
# 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
|
||||
|
||||
# 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: Publish release binary artifact
|
||||
if: matrix.publish
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: target/${{ matrix.target }}/release/scryer-prolog*
|
||||
name: scryer-prolog_${{ matrix.os }}_${{ matrix.target }}
|
||||
|
||||
logtalk-test:
|
||||
# if: false # uncomment to disable job
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [build-test]
|
||||
steps:
|
||||
# Download prebuilt ubuntu binary from build-test job, setup logtalk
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu
|
||||
- run: |
|
||||
chmod +x scryer-prolog
|
||||
echo "$PWD" >> "$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@v3
|
||||
with:
|
||||
name: logtalk-test-logs
|
||||
path: '${{ env.LOGTALKUSER }}/tests/prolog/logtalk_tester_logs'
|
||||
- name: Publish Logtalk test results artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
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@v3
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: stable
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
cache-context: report
|
||||
- run: |
|
||||
cargo install cargo2junit --force
|
||||
# cargo install iai-callgrind-runner --force --version `cargo metadata --format-version 1 | jq -r '.resolve.nodes[].id|split(" ")|select(.[0]=="iai-callgrind")|.[1]'`
|
||||
cargo install iai-callgrind-runner --force --git https://github.com/iai-callgrind/iai-callgrind --rev c77bc3c83d7f4e976cc42d4597236a8db259e772
|
||||
sudo apt install valgrind -y
|
||||
|
||||
- name: Test and report
|
||||
run: |
|
||||
RUSTC_BOOTSTRAP=1 cargo test --all -- -Z unstable-options --format json --report-time | cargo2junit > cargo_test_results.xml
|
||||
- name: Publish cargo test results artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
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@v3
|
||||
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@v3
|
||||
- name: Zip binaries for release
|
||||
run: |
|
||||
zip scryer-prolog_macos-11.zip ./scryer-prolog_macos-11_x86_64-apple-darwin/scryer-prolog
|
||||
zip scryer-prolog_ubuntu-20.04.zip ./scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu/scryer-prolog
|
||||
zip scryer-prolog_ubuntu-22.04.zip ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/scryer-prolog
|
||||
zip scryer-prolog_windows-latest.zip ./scryer-prolog_windows-latest_x86_64-pc-windows-msvc/scryer-prolog.exe
|
||||
zip scryer-prolog_wasm32.zip ./scryer-prolog_ubuntu-22.04_wasm32-unknown-unknown/scryer-prolog.wasm
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
scryer-prolog_macos-11.zip
|
||||
scryer-prolog_ubuntu-20.04.zip
|
||||
scryer-prolog_ubuntu-22.04.zip
|
||||
scryer-prolog_windows-latest.zip
|
||||
scryer-prolog_wasm32.zip
|
||||
55
.github/workflows/docker-publish.yml
vendored
Normal file
55
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Docker Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Workaround: https://github.com/docker/build-push-action/issues/461
|
||||
- name: Setup Docker buildx
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
|
||||
# Login against Docker registry
|
||||
- name: Log into registry
|
||||
# https://github.com/docker/login-action
|
||||
uses: docker/login-action@v2.1.0
|
||||
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". The "latest" tag reflects the most recent build on
|
||||
# master.
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
# https://github.com/docker/metadata-action
|
||||
uses: docker/metadata-action@v4.1.1
|
||||
with:
|
||||
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
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
# https://github.com/docker/build-push-action
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
40
.github/workflows/test.yml
vendored
40
.github/workflows/test.yml
vendored
@@ -1,40 +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
|
||||
3386
Cargo.lock
generated
3386
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
144
Cargo.toml
144
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "scryer-prolog"
|
||||
version = "0.9.0"
|
||||
version = "0.9.4"
|
||||
authors = ["Mark Thom <markjordanthom@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A modern Prolog implementation written mostly in Rust."
|
||||
@@ -9,67 +9,133 @@ repository = "https://github.com/mthom/scryer-prolog"
|
||||
license = "BSD-3-Clause"
|
||||
keywords = ["prolog", "prolog-interpreter", "prolog-system"]
|
||||
categories = ["command-line-utilities"]
|
||||
build = "build.rs"
|
||||
build = "build/main.rs"
|
||||
rust-version = "1.70"
|
||||
|
||||
[workspace]
|
||||
members = ["crates/num-rug-adapter",
|
||||
"crates/static-string-indexing",
|
||||
"crates/instructions-template",
|
||||
"crates/to-syn-value",
|
||||
"crates/to-syn-value_derive"]
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
num = ["num-rug-adapter"]
|
||||
# no default features to make num tests work
|
||||
# workaround for --no-default-features and --features not working intuitively for workspaces with a root package
|
||||
# see rust-lang/cargo#7160
|
||||
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"]
|
||||
rust_beta_channel = []
|
||||
crypto-full = []
|
||||
|
||||
[build-dependencies]
|
||||
indexmap = "1.0.2"
|
||||
static-string-indexing = { path = "./crates/static-string-indexing" }
|
||||
instructions-template = { path = "./crates/instructions-template" }
|
||||
proc-macro2 = "*"
|
||||
proc-macro2 = "1.0.36"
|
||||
quote = "1.0.15"
|
||||
strum = "0.23"
|
||||
strum_macros = "0.23"
|
||||
syn = { version = "2.0.32", features = ['full', 'visit', 'extra-traits'] }
|
||||
to-syn-value = "0.1.1"
|
||||
to-syn-value_derive = "0.1.1"
|
||||
walkdir = "2"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.12.3"
|
||||
bit-set = "0.5.3"
|
||||
bitvec = "1"
|
||||
blake2 = "0.8.1"
|
||||
bytes = "1"
|
||||
chrono = "0.4.11"
|
||||
cpu-time = "1.0.0"
|
||||
crossterm = "0.16.0"
|
||||
crrl = "0.6.0"
|
||||
dashu = "0.4.0"
|
||||
derive_deref = "1.1.1"
|
||||
dirs-next = "2.0.0"
|
||||
divrem = "0.1.0"
|
||||
futures = "0.3"
|
||||
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 = { git = "https://github.com/mthom/modular-bitfield" } # modular-bitfield = "0.11.2"
|
||||
nix = "0.15.0"
|
||||
num-rug-adapter = { optional = true, path = "./crates/num-rug-adapter" }
|
||||
ordered-float = "2.1.1"
|
||||
phf = { version = "0.9", features = ["macros"] }
|
||||
libloading = "0.7"
|
||||
scryer-modular-bitfield = "0.11.4"
|
||||
num-order = { version = "1.2.0" }
|
||||
ordered-float = "2.6.0"
|
||||
phf = { version = "0.9", features = ["macros"] }
|
||||
rand = "0.8.5"
|
||||
ref_thread_local = "0.0.0"
|
||||
rug = { version = "1.12.0", optional = true }
|
||||
rustyline = "9.0.0"
|
||||
ring = "0.16.13"
|
||||
regex = "1.9.1"
|
||||
ring = { version = "0.17.5", features = ["wasm32_unknown_unknown_js"] }
|
||||
ripemd160 = "0.8.0"
|
||||
sha3 = "0.8.2"
|
||||
blake2 = "0.8.1"
|
||||
openssl = { version = "0.10.29", features = ["vendored"] }
|
||||
native-tls = "0.2.4"
|
||||
chrono = "0.4.11"
|
||||
select = "0.4.3"
|
||||
roxmltree = "0.11.0"
|
||||
base64 = "0.12.3"
|
||||
smallvec = "*"
|
||||
sodiumoxide = "0.2.6"
|
||||
ryu = "1.0.9"
|
||||
select = "0.6.0"
|
||||
sha3 = "0.8.2"
|
||||
smallvec = "1.8.0"
|
||||
static_assertions = "1.1.0"
|
||||
slice-deque = "0.3.0"
|
||||
|
||||
serde_json = "1.0.95"
|
||||
serde = "1.0.159"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
crossterm = { version = "0.20.0", optional = true }
|
||||
ctrlc = { version = "3.2.2", optional = true }
|
||||
hostname = { version = "0.3.1", optional = true }
|
||||
libffi = { version = "3.2.0", optional = true }
|
||||
native-tls = { version = "0.2.4", optional = true }
|
||||
reqwest = { version = "0.11.18", optional = true }
|
||||
rustyline = { version = "12.0.0", optional = true }
|
||||
tokio = { version = "1.28.2", features = ["full"] }
|
||||
warp = { version = "=0.3.5", features = ["tls"], optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.10", features = ["js"] }
|
||||
tokio = { version = "1.28.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.87"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Document",
|
||||
"Window",
|
||||
"Element",
|
||||
"Performance",
|
||||
] }
|
||||
js-sys = "0.3"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.3"
|
||||
maplit = "1.0.2"
|
||||
predicates-core = "1.0.2"
|
||||
serial_test = "0.5.1"
|
||||
serial_test = "2.0.0"
|
||||
|
||||
[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies]
|
||||
assert_cmd = "1.0.3"
|
||||
criterion = "0.5.1"
|
||||
iai-callgrind = "0.9.0"
|
||||
trycmd = "0.14.19"
|
||||
|
||||
[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
|
||||
|
||||
[[bench]]
|
||||
name = "run_criterion"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "run_iai"
|
||||
harness = false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# See https://github.com/LukeMathWalker/cargo-chef
|
||||
ARG RUST_VERSION=1.52.1-buster
|
||||
ARG RUST_VERSION=1-buster
|
||||
FROM rust:${RUST_VERSION} as planner
|
||||
WORKDIR /scryer-prolog
|
||||
RUN cargo install cargo-chef
|
||||
@@ -20,7 +20,12 @@ 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
|
||||
# Newer versions of Debian (i.e. bookworm) contain libssl3 instead of libssl1.1
|
||||
# which we depend on.
|
||||
FROM debian:bullseye-slim
|
||||
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"]
|
||||
|
||||
77
INDEX.dj
Normal file
77
INDEX.dj
Normal file
@@ -0,0 +1,77 @@
|
||||
# Scryer Prolog
|
||||
|
||||
```
|
||||
?- append("Hello, ", X, "Hello, Scryer Prolog!").
|
||||
X = "Scryer Prolog!".
|
||||
```
|
||||
|
||||
{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.3*. And it's already useful for lots of tasks.
|
||||
|
||||
| Windows | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.3/scryer-prolog_windows-latest.zip) |
|
||||
| macOS (Intel) | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.3/scryer-prolog_macos-11.zip) |
|
||||
| Linux | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.3/scryer-prolog_ubuntu-20.04.zip) |
|
||||
|
||||
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)!
|
||||
266
README.md
266
README.md
@@ -1,3 +1,4 @@
|
||||
|
||||
# Scryer Prolog
|
||||
|
||||
Scryer Prolog aims to become to ISO Prolog what GHC is to Haskell: an open
|
||||
@@ -5,14 +6,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 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)
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
@@ -43,7 +50,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.
|
||||
@@ -51,24 +58,24 @@ 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.
|
||||
- [x] Streams and predicates for stream control.
|
||||
- [x] A simple sockets library representing TCP connections as streams.
|
||||
- [x] A simple sockets library representing TCP connections as streams.
|
||||
- [x] Incremental compilation and loading process, newly written,
|
||||
primarily in Prolog.
|
||||
- [ ] Improvements to the WAM compiler and heap representation:
|
||||
- [ ] 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.
|
||||
- [x] Greatly reducing the number of instructions used to compile disjunctives.
|
||||
- [ ] 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
|
||||
@@ -87,12 +94,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.
|
||||
@@ -101,7 +108,14 @@ strings.
|
||||
|
||||
## Installing Scryer Prolog
|
||||
|
||||
### Native Install (Unix Only)
|
||||
### Binaries
|
||||
|
||||
Precompiled binaries for several platforms are available for download
|
||||
at:
|
||||
|
||||
**https://github.com/mthom/scryer-prolog/releases/tag/v0.9.3**
|
||||
|
||||
### Native Compilation
|
||||
|
||||
First, install the latest stable version of
|
||||
[Rust](https://www.rust-lang.org/en-US/install.html) using your
|
||||
@@ -112,33 +126,107 @@ Rust updated to the latest stable release; any existing Rust
|
||||
distribution should be uninstalled from your system before rustup is
|
||||
used.
|
||||
|
||||
Scryer Prolog can be installed with cargo, like so:
|
||||
|
||||
```
|
||||
$> cargo install scryer-prolog
|
||||
```
|
||||
|
||||
cargo will download and install the libraries Scryer Prolog uses
|
||||
automatically from crates.io. You can find the `scryer-prolog`
|
||||
executable in `~/.cargo/bin`.
|
||||
|
||||
Publishing Rust crates to crates.io and pushing to git are entirely
|
||||
distinct, independent processes, so to be sure you have the latest
|
||||
commit, it is recommended to clone directly from this git repository,
|
||||
which can be done as follows:
|
||||
Currently the only way to install the latest version of Scryer is to
|
||||
clone directly from this git repository, and compile the system. This
|
||||
can be done as follows:
|
||||
|
||||
```
|
||||
$> 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.
|
||||
|
||||
Scryer Prolog must be built with **Rust 1.57 and up**.
|
||||
After compilation, the executable `scryer-prolog` is available in the
|
||||
directory `target/release` and can be invoked to run the system.
|
||||
|
||||
### Docker Install (All Platforms)
|
||||
On Windows, Scryer Prolog is easier to build inside a [MSYS2](https://www.msys2.org/)
|
||||
environment as some crates may require native C compilation. However,
|
||||
the resulting binary does not need MSYS2 to run. When executing Scryer in a shell, it is recommended to use a more advanced shell than mintty (the default MSYS2 shell). The [Windows Terminal](https://github.com/microsoft/terminal) works correctly.
|
||||
|
||||
To build a Windows Installer, you'll need first Scryer Prolog compiled in release mode, then, with WiX Toolset installed, execute:
|
||||
```
|
||||
candle.exe scryer-prolog.wxs
|
||||
light.exe scryer-prolog.wixobj
|
||||
```
|
||||
It will generate a very basic MSI file which installs the main executable and a shortcut in the Start Menu. It can be installed with a double-click. To uninstall, go to the Control Panel and uninstall as usual.
|
||||
|
||||
Scryer Prolog must be built with **Rust 1.70 and up**.
|
||||
|
||||
### Building WebAssembly
|
||||
|
||||
Scryer Prolog has basic WebAssembly support. You can follow `wasm-pack`'s [official instructions](https://rustwasm.github.io/docs/wasm-pack/quickstart.html) to install `wasm-pack` and build it in any way you like.
|
||||
|
||||
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 init, { eval_code } from './pkg/scryer_prolog.js';
|
||||
|
||||
const run = async () => {
|
||||
await init("./pkg/scryer_prolog_bg.wasm");
|
||||
let code = `
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
sudoku(Rows) :-
|
||||
length(Rows, 9), maplist(same_length(Rows), Rows),
|
||||
append(Rows, Vs), Vs ins 1..9,
|
||||
maplist(all_distinct, Rows),
|
||||
transpose(Rows, Columns),
|
||||
maplist(all_distinct, Columns),
|
||||
Rows = [As,Bs,Cs,Ds,Es,Fs,Gs,Hs,Is],
|
||||
blocks(As, Bs, Cs),
|
||||
blocks(Ds, Es, Fs),
|
||||
blocks(Gs, Hs, Is).
|
||||
|
||||
blocks([], [], []).
|
||||
blocks([N1,N2,N3|Ns1], [N4,N5,N6|Ns2], [N7,N8,N9|Ns3]) :-
|
||||
all_distinct([N1,N2,N3,N4,N5,N6,N7,N8,N9]),
|
||||
blocks(Ns1, Ns2, Ns3).
|
||||
|
||||
problem(1, [[_,_,_,_,_,_,_,_,_],
|
||||
[_,_,_,_,_,3,_,8,5],
|
||||
[_,_,1,_,2,_,_,_,_],
|
||||
[_,_,_,5,_,7,_,_,_],
|
||||
[_,_,4,_,_,_,1,_,_],
|
||||
[_,9,_,_,_,_,_,_,_],
|
||||
[5,_,_,_,_,_,_,7,3],
|
||||
[_,_,2,_,1,_,_,_,_],
|
||||
[_,_,_,_,4,_,_,_,9]]).
|
||||
|
||||
main :-
|
||||
problem(1, Rows), sudoku(Rows), maplist(portray_clause, Rows).
|
||||
|
||||
:- initialization(main).
|
||||
`;
|
||||
const result = eval_code(code);
|
||||
document.write(`<p>Sudoku solver returns:</p><pre>${result}</pre>`);
|
||||
}
|
||||
run();
|
||||
</script>
|
||||
</head>
|
||||
<body></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
|
||||
|
||||
First, install [Docker](https://docs.docker.com/get-docker/) on Linux,
|
||||
Windows, or Mac.
|
||||
@@ -213,8 +301,14 @@ predicates it defines. For example, with the program shown above:
|
||||
|
||||
Press `SPACE` to show further answers, if any exist. Press `RETURN`
|
||||
or `.` to abort the search and return to the
|
||||
toplevel prompt. Press `f` to see the next 5 answers, and
|
||||
`a` to see all answers. Press `h` to show a help message.
|
||||
toplevel prompt. Press `f` to see up to the next multiple of
|
||||
5 answers, and `a` to see all answers. Press `h` to show a help
|
||||
message.
|
||||
|
||||
Use `TAB` to complete atoms and predicate names in queries. For
|
||||
instance, after consulting the program above, typing `decl` followed
|
||||
by `TAB` yields `declarative_world`. Press `TAB` repeatedly
|
||||
to cycle through alternative completions.
|
||||
|
||||
To quit Scryer Prolog, use the standard predicate `halt/0`:
|
||||
|
||||
@@ -222,6 +316,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 `argv/1`, which yields the list
|
||||
of arguments represented as strings.
|
||||
|
||||
Prolog files can also be turned into *shell scripts* as explained in
|
||||
https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993.
|
||||
|
||||
### Dynamic operators
|
||||
|
||||
Scryer supports dynamic operators. Using the built-in
|
||||
@@ -267,9 +390,30 @@ in any clause of a predicate's definition.
|
||||
|
||||
### Strings and partial strings
|
||||
|
||||
A very compact internal representation of *strings* is one of the key
|
||||
innovations of Scryer Prolog. This means that terms which appear as
|
||||
lists of characters to Prolog programs are stored in packed
|
||||
UTF-8 encoding by the engine.
|
||||
|
||||
Without this innovation, storing a list of characters in memory would
|
||||
use one WAM memory cell per character, one cell per list
|
||||
constructor, and one cell for each tail that occurs in the list. Since
|
||||
one cell takes 8 bytes in the WAM as implemented by
|
||||
Scryer Prolog, the packed representation yields an up to
|
||||
**24-fold reduction** of memory usage, and corresponding
|
||||
reduction of memory 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:
|
||||
efficient and convenient text processing, especially with definite
|
||||
clause grammars (DCGs) as provided by
|
||||
[`library(dcgs)`](src/lib/dcgs.pl) and
|
||||
[`library(pio)`](src/lib/pio.pl) to transparently apply DCGs to files.
|
||||
|
||||
In Scryer Prolog, the default value of the Prolog flag `double_quotes`
|
||||
is `chars`, which is also the recommended setting. This means that
|
||||
double-quoted strings are interpreted as lists of *characters*, in the
|
||||
lists of characters can be written as double-quoted strings, in the
|
||||
tradition of Marseille Prolog.
|
||||
|
||||
For example, the following query succeeds:
|
||||
@@ -279,15 +423,9 @@ For example, the following query succeeds:
|
||||
true.
|
||||
```
|
||||
|
||||
Internally, strings are represented very compactly in packed
|
||||
UTF-8 encoding. A naive representation of strings as lists of
|
||||
characters would use one memory cell per character, one
|
||||
memory cell per list constructor, and one memory cell for
|
||||
each tail that occurs in the list. Since one memory cell takes
|
||||
8 bytes on 64-bit machines, the packed representation used by
|
||||
Scryer Prolog yields an up to **24-fold reduction** of
|
||||
memory usage, and corresponding reduction of memory accesses when
|
||||
creating and processing strings.
|
||||
This shows that the string `"abc"`, which is represented as a sequence
|
||||
of 3 bytes internally, appears to Prolog programs as a list of
|
||||
characters.
|
||||
|
||||
Scryer Prolog uses the same efficient encoding for *partial* strings,
|
||||
which appear to Prolog code as partial lists of characters. The
|
||||
@@ -310,13 +448,11 @@ the above example, posting <tt>Ls0 = [a,b,c|Ls]</tt> yields
|
||||
the exact same internal representation, and has the advantage that
|
||||
only the standard predicate `(=)/2` is used.
|
||||
|
||||
Definite clause grammars as provided by
|
||||
[`library(dcgs)`](src/lib/dcgs.pl), and the predicates from
|
||||
[`library(lists)`](src/lib/lists.pl), are ideally suited for reasoning
|
||||
about strings.
|
||||
|
||||
Partial strings were first proposed by Ulrich Neumerkel in issue
|
||||
[#95](https://github.com/mthom/scryer-prolog/issues/95).
|
||||
The efficient internal representation of strings and partial strings
|
||||
was first proposed and explained by Ulrich Neumerkel in
|
||||
issues [#24](https://github.com/mthom/scryer-prolog/issues/24)
|
||||
and [#95](https://github.com/mthom/scryer-prolog/issues/95), and
|
||||
Scryer Prolog is the first Prolog system that implements it.
|
||||
|
||||
### Occurs check and cyclic terms
|
||||
|
||||
@@ -520,7 +656,7 @@ The modules that ship with Scryer 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 servers. HTTPS is also supported.
|
||||
* [`http/http_server`](src/lib/http/http_server.pl) Runs a HTTP/1.0 web server.
|
||||
* [`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 documents
|
||||
as Prolog terms for convenient and efficient reasoning. Use
|
||||
@@ -562,6 +698,9 @@ The modules that ship with Scryer Prolog are also called
|
||||
* [`tls`](src/lib/tls.pl)
|
||||
Predicates for negotiating TLS connections explicitly.
|
||||
* [`ugraphs`](src/lib/ugraphs.pl) Graph manipulation library
|
||||
* [`simplex`](src/lib/simplex.pl) Providing `assignment/2`,
|
||||
`transportation/4` and other predicates for solving linear
|
||||
programming problems.
|
||||
|
||||
To use predicates provided by the `lists` library, write:
|
||||
|
||||
@@ -651,10 +790,33 @@ 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 agencies
|
||||
that are subject to strict regulations pertaining to interoperability,
|
||||
standards compliance and warranty.
|
||||
|
||||
Successful existing applications of Scryer Prolog include the
|
||||
[DocLog](https://github.com/aarroyoc/doclog) system which
|
||||
generates Scryer's own documentation and homepage, [Symbolic
|
||||
Analysis of Grants](https://www.brz.gv.at/en/BRZ-Tech-Blog/Tech-Blog-7-Symbolic-Analysis-of-Grants.html)
|
||||
by the Austrian Federal Computing Center, and parts of the
|
||||
[precautionary](https://github.com/dcnorris/precautionary/tree/main/exec/prolog)
|
||||
package for the analysis of dose-escalation trials in the
|
||||
safety-critical and highly regulated domain of oncology
|
||||
trial design, described in [*An Executable Specification of
|
||||
Oncology Dose-Escalation Protocols with Prolog*](https://arxiv.org/abs/2402.08334).
|
||||
|
||||
Scryer Prolog is also very well suited for teaching and learning
|
||||
Prolog, and for testing syntactic conformance and hence portability of
|
||||
existing Prolog programs.
|
||||
|
||||
## 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)!
|
||||
[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
95
benches/README.md
Normal 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
41
benches/csv.pl
Normal file
File diff suppressed because one or more lines are too long
130
benches/edges.pl
Normal file
130
benches/edges.pl
Normal 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
2
benches/numlist.pl
Normal 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
46
benches/run_criterion.rs
Normal 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
38
benches/run_iai.rs
Normal 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::machine::parsed_results::QueryResolution;
|
||||
|
||||
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() -> QueryResolution) -> QueryResolution {
|
||||
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() {}
|
||||
133
benches/setup.rs
Normal file
133
benches/setup.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
|
||||
use maplit::btreemap;
|
||||
use scryer_prolog::machine::{
|
||||
parsed_results::{QueryResolution, Value},
|
||||
Machine,
|
||||
};
|
||||
|
||||
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" => Value::try_from("2869176".to_string()).unwrap() },
|
||||
),
|
||||
(
|
||||
"numlist",
|
||||
"benches/numlist.pl",
|
||||
"run_numlist(1000000, Head).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Head" => Value::try_from("1".to_string()).unwrap()},
|
||||
),
|
||||
(
|
||||
"csv_codename",
|
||||
"benches/csv.pl",
|
||||
"get_codename(\"0020\",Name).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Name" => Value::try_from("SPACE".to_string()).unwrap()},
|
||||
),
|
||||
]
|
||||
.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,
|
||||
}
|
||||
|
||||
pub struct PrologBenchmark {
|
||||
pub name: &'static str,
|
||||
pub filename: &'static str,
|
||||
pub query: &'static str,
|
||||
pub strategy: Strategy,
|
||||
pub bindings: BTreeMap<&'static str, Value>,
|
||||
}
|
||||
|
||||
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 = Machine::new_lib();
|
||||
machine.load_module_string(module_name, program);
|
||||
machine
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
pub fn setup(&self) -> impl FnMut() -> QueryResolution {
|
||||
let mut machine = self.make_machine();
|
||||
let query = self.query;
|
||||
move || {
|
||||
use criterion::black_box;
|
||||
black_box(machine.run_query(black_box(query.to_string()))).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn validate_benchmarks() {
|
||||
use super::prolog_benches;
|
||||
use scryer_prolog::machine::parsed_results::{QueryMatch, QueryResolution};
|
||||
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 = machine.run_query(r.query.to_string()).unwrap();
|
||||
let query_inference_count = machine.get_inference_count() - setup_inference_count;
|
||||
|
||||
let expected = QueryResolution::Matches(vec![QueryMatch::from(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
@@ -1,12 +1,15 @@
|
||||
use static_string_indexing::index_static_strings;
|
||||
mod instructions_template;
|
||||
mod static_string_indexing;
|
||||
|
||||
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::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn find_prolog_files(libraries: &mut File, prefix: &str, current_dir: &Path) {
|
||||
let entries = match current_dir.read_dir() {
|
||||
@@ -39,10 +42,20 @@ fn find_prolog_files(libraries: &mut File, prefix: &str, current_dir: &Path) {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let has_rustfmt = Command::new("rustfmt")
|
||||
.arg("--version")
|
||||
.stdin(Stdio::inherit())
|
||||
.status()
|
||||
.is_ok();
|
||||
|
||||
if !has_rustfmt {
|
||||
println!("Failed to run rustfmt, will skip formatting generated files.")
|
||||
}
|
||||
|
||||
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 mut libraries = File::create(dest_path).unwrap();
|
||||
let lib_path = Path::new("src/lib");
|
||||
|
||||
libraries
|
||||
@@ -53,7 +66,7 @@ fn main() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
find_prolog_files(&mut libraries, "", &lib_path);
|
||||
find_prolog_files(&mut libraries, "", lib_path);
|
||||
libraries.write_all(b"\n m\n };\n}\n").unwrap();
|
||||
|
||||
let instructions_path = Path::new(&out_dir).join("instructions.rs");
|
||||
@@ -65,10 +78,9 @@ fn main() {
|
||||
.write_all(quoted_output.to_string().as_bytes())
|
||||
.unwrap();
|
||||
|
||||
Command::new("rustfmt")
|
||||
.arg(instructions_path.as_os_str())
|
||||
.spawn().unwrap()
|
||||
.wait().unwrap();
|
||||
if has_rustfmt {
|
||||
format_generated_file(instructions_path.as_path());
|
||||
}
|
||||
|
||||
let static_atoms_path = Path::new(&out_dir).join("static_atoms.rs");
|
||||
let mut static_atoms_file = File::create(&static_atoms_path).unwrap();
|
||||
@@ -79,10 +91,24 @@ fn main() {
|
||||
.write_all(quoted_output.to_string().as_bytes())
|
||||
.unwrap();
|
||||
|
||||
Command::new("rustfmt")
|
||||
.arg(static_atoms_path.as_os_str())
|
||||
.spawn().unwrap()
|
||||
.wait().unwrap();
|
||||
if has_rustfmt {
|
||||
format_generated_file(static_atoms_path.as_path());
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=src/");
|
||||
}
|
||||
|
||||
fn format_generated_file(path: &Path) {
|
||||
Command::new("rustfmt")
|
||||
.arg(path.as_os_str())
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"{}: rustfmt was detected as available, but failed to format generated file '{}'",
|
||||
err,
|
||||
path.display()
|
||||
);
|
||||
})
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
@@ -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,22 +65,20 @@ 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") {
|
||||
if let Some(m) = m.parse_body::<ReadHeapCellExprAndArms>().ok() {
|
||||
} else if path.is_ident("read_heap_cell") || path.is_ident("match_untyped_arena_ptr") {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,12 +145,11 @@ 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 = (0..visitor.static_strs.len()).map(|i| (i << 3) as u64);
|
||||
let indices_iter = indices.clone();
|
||||
|
||||
let static_strs_len = visitor.static_strs.len();
|
||||
@@ -159,7 +158,7 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
|
||||
quote! {
|
||||
use phf;
|
||||
|
||||
static STRINGS: [&'static str; #static_strs_len] = [
|
||||
static STRINGS: [&str; #static_strs_len] = [
|
||||
#(
|
||||
#static_strs,
|
||||
)*
|
||||
@@ -170,7 +169,7 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
|
||||
#((#static_strs) => { Atom { index: #indices_iter } };)*
|
||||
}
|
||||
|
||||
static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {
|
||||
pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {
|
||||
#(#static_strs => { Atom { index: #indices } },)*
|
||||
};
|
||||
}
|
||||
129
crates/instructions-template/Cargo.lock
generated
129
crates/instructions-template/Cargo.lock
generated
@@ -1,129 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instructions-template"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "392a54546fda6b7cc663379d0e6ce8b324cf88aecc5a499838e1be9781bdce2e"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.84"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "to-syn-value"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"syn",
|
||||
"to-syn-value_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "to-syn-value_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "instructions-template"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
indexmap = "*"
|
||||
proc-macro2 = "*"
|
||||
quote = "*"
|
||||
strum = "0.23"
|
||||
strum_macros = "0.23"
|
||||
syn = { version = "*", features = ['full', 'visit', 'extra-traits'] }
|
||||
to-syn-value = { path = "../to-syn-value" }
|
||||
to-syn-value_derive = { path = "../to-syn-value_derive" }
|
||||
@@ -1,18 +0,0 @@
|
||||
[package]
|
||||
name = "num-rug-adapter"
|
||||
version = "0.1.5"
|
||||
authors = ["Marco A L Barbosa <malbarbo@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "An adapter to use num crate where rug is needed."
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/malbarbo/num-rug-adapter"
|
||||
keywords = ["mathematics", "numerics", "bignum"]
|
||||
categories = ["api-bindings", "science"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
num-bigint = "0.2"
|
||||
num-integer = "0.1.41"
|
||||
num-rational = "0.2"
|
||||
num-traits = "0.2"
|
||||
@@ -1,888 +0,0 @@
|
||||
use num_bigint::{BigInt, ParseBigIntError};
|
||||
use num_integer::Integer as _;
|
||||
use num_rational::BigRational;
|
||||
use num_traits::{FromPrimitive, Num, Signed, ToPrimitive};
|
||||
use num_traits::identities::One;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::*;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Integer(BigInt);
|
||||
|
||||
impl Integer {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Integer(BigInt::default())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseBigIntError> {
|
||||
BigInt::from_str_radix(s, radix).map(Integer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_u8(&self) -> Option<u8> {
|
||||
self.0.to_u8()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_u32(&self) -> Option<u32> {
|
||||
self.0.to_u32()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_u64(&self) -> Option<u64> {
|
||||
self.0.to_u64()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_usize(&self) -> Option<usize> {
|
||||
self.0.to_usize()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_i32(&self) -> Option<i32> {
|
||||
self.0.to_i32()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_isize(&self) -> Option<isize> {
|
||||
self.0.to_isize()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_f64(&self) -> f64 {
|
||||
self.0.to_f64().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn abs(&self) -> Self {
|
||||
Integer(self.0.abs())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn abs_ref(&self) -> Self {
|
||||
Integer(self.0.abs())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn div_rem(&self, other: Self) -> (Self, Self) {
|
||||
let (a, b) = num_integer::Integer::div_rem(&self.0, &other.0);
|
||||
(Integer(a), Integer(b))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn div_rem_ref(&self, other: &Self) -> (Self, Self) {
|
||||
let (a, b) = num_integer::Integer::div_rem(&self.0, &other.0);
|
||||
(Integer(a), Integer(b))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn div_rem_floor(&self, other: Self) -> (Self, Self) {
|
||||
let (a, b) = num_integer::Integer::div_mod_floor(&self.0, &other.0);
|
||||
(Integer(a), Integer(b))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn div_rem_floor_ref(&self, other: &Self) -> (Self, Self) {
|
||||
let (a, b) = num_integer::Integer::div_mod_floor(&self.0, &other.0);
|
||||
(Integer(a), Integer(b))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn mod_u(&self, modulo: u32) -> u32 {
|
||||
(self.0.abs() % modulo).to_u32().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_odd(&self) -> bool {
|
||||
num_integer::Integer::is_odd(&self.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_f64(v: f64) -> Option<Self> {
|
||||
BigInt::from_f64(v).map(Integer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn gcd_ref(&self, other: &Self) -> Self {
|
||||
Integer(num_integer::Integer::gcd(&self.0, &other.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn gcd(&self, other: &Self) -> Self {
|
||||
Integer(num_integer::Integer::gcd(&self.0, &other.0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn count_ones(&self) -> Option<u32> {
|
||||
Some(self.0.to_u32_digits().1.iter().map(|&d| d.count_ones()).sum())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Integer> for Integer {
|
||||
#[inline]
|
||||
fn from(s: &Integer) -> Self {
|
||||
s.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Integer {
|
||||
#[inline]
|
||||
fn from(s: i32) -> Self {
|
||||
Integer(BigInt::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for Integer {
|
||||
#[inline]
|
||||
fn from(s: isize) -> Self {
|
||||
Integer(BigInt::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Integer {
|
||||
#[inline]
|
||||
fn from(s: u8) -> Self {
|
||||
Integer(BigInt::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Integer {
|
||||
#[inline]
|
||||
fn from(s: u32) -> Self {
|
||||
Integer(BigInt::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Integer {
|
||||
#[inline]
|
||||
fn from(s: u64) -> Self {
|
||||
Integer(BigInt::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for Integer {
|
||||
#[inline]
|
||||
fn from(s: usize) -> Self {
|
||||
Integer(BigInt::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, other: Integer) -> Self::Output {
|
||||
Integer(self.0 * other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<u32> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, other: u32) -> Self::Output {
|
||||
Integer(self.0 * other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<&Integer> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
fn mul(self, other: &Integer) -> Self::Output {
|
||||
Integer(self.0 * &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<&Integer> for Integer {
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, other: &Integer) {
|
||||
self.0 *= &other.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: Integer) -> Self::Output {
|
||||
Integer(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: Integer) -> Self::Output {
|
||||
Integer(&self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Integer> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: &Integer) -> Self::Output {
|
||||
Integer(self.0 + &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: &Integer) -> Self::Output {
|
||||
Integer(&self.0 + &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<i64> for Integer {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: i64) {
|
||||
self.0 += other;
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<&Integer> for Integer {
|
||||
#[inline]
|
||||
fn add_assign(&mut self, other: &Integer) {
|
||||
self.0 += &other.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn div(self, other: Integer) -> Integer {
|
||||
Integer(self.0 / other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<&Integer> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn div(self, other: &Integer) -> Integer {
|
||||
Integer(self.0 / &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn div(self, other: Integer) -> Integer {
|
||||
Integer(&self.0 / &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shr<u32> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn shr(self, rhs: u32) -> Self::Output {
|
||||
Integer(self.0 >> rhs as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shr<u32> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn shr(self, rhs: u32) -> Self::Output {
|
||||
Integer(&self.0 >> rhs as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShrAssign<u32> for Integer {
|
||||
#[inline]
|
||||
fn shr_assign(&mut self, rhs: u32) {
|
||||
self.0 >>= rhs as usize;
|
||||
}
|
||||
}
|
||||
|
||||
impl Shl<u32> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn shl(self, rhs: u32) -> Self::Output {
|
||||
Integer(self.0 << rhs as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Shl<u32> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn shl(self, rhs: u32) -> Self::Output {
|
||||
Integer(&self.0 << rhs as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn not(self) -> Self::Output {
|
||||
Integer(!self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn not(self) -> Self::Output {
|
||||
Integer(!&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn rem(self, other: Integer) -> Self::Output {
|
||||
Integer(self.0.mod_floor(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem<&Integer> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn rem(self, other: &Integer) -> Self::Output {
|
||||
Integer(self.0.mod_floor(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem<&Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn rem(self, other: &Integer) -> Self::Output {
|
||||
Integer(self.0.mod_floor(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem<Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn rem(self, other: Integer) -> Self::Output {
|
||||
Integer(self.0.mod_floor(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitand(self, other: Integer) -> Self::Output {
|
||||
Integer(self.0 & &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd<&Integer> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitand(self, other: &Integer) -> Self::Output {
|
||||
Integer(self.0 & &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd<Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitand(self, other: Integer) -> Self::Output {
|
||||
Integer(&self.0 & other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitand(self, other: &Integer) -> Self::Output {
|
||||
Integer(&self.0 & &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitor(self, other: Integer) -> Self::Output {
|
||||
Integer(self.0 | other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<&Integer> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitor(self, other: &Integer) -> Self::Output {
|
||||
Integer(self.0 | &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitor(self, other: Integer) -> Self::Output {
|
||||
Integer(&self.0 | other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitor(self, other: &Integer) -> Self::Output {
|
||||
Integer(&self.0 | &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitXor for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitxor(self, other: Integer) -> Self::Output {
|
||||
Integer(self.0 ^ other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitXor<&Integer> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitxor(self, other: &Integer) -> Self::Output {
|
||||
Integer(self.0 ^ &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitXor<Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitxor(self, other: Integer) -> Self::Output {
|
||||
Integer(&self.0 ^ other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitXor<&Integer> for &Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn bitxor(self, other: &Integer) -> Self::Output {
|
||||
Integer(&self.0 ^ &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i32> for Integer {
|
||||
#[inline]
|
||||
fn eq(&self, other: &i32) -> bool {
|
||||
self.0 == BigInt::from(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i64> for Integer {
|
||||
#[inline]
|
||||
fn eq(&self, other: &i64) -> bool {
|
||||
self.0 == BigInt::from(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<isize> for Integer {
|
||||
#[inline]
|
||||
fn eq(&self, other: &isize) -> bool {
|
||||
self.0 == BigInt::from(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<usize> for Integer {
|
||||
#[inline]
|
||||
fn eq(&self, other: &usize) -> bool {
|
||||
self.0 == BigInt::from(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Integer> for isize {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Integer) -> bool {
|
||||
other.0 == BigInt::from(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<i32> for Integer {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&BigInt::from(*other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<i64> for Integer {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &i64) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&BigInt::from(*other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<isize> for Integer {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &isize) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&BigInt::from(*other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<usize> for Integer {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&BigInt::from(*other))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Integer {
|
||||
type Err = <BigInt as FromStr>::Err;
|
||||
|
||||
#[inline]
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Integer(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self {
|
||||
Integer(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Integer {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Rational
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Rational(BigRational);
|
||||
|
||||
impl Rational {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Rational(BigRational::from(BigInt::default()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_f64(v: f64) -> Option<Self> {
|
||||
BigRational::from_f64(v).map(Rational)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_f64(&self) -> f64 {
|
||||
self.0.numer().to_f64().unwrap() / self.0.denom().to_f64().unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn numer(&self) -> &Integer {
|
||||
unsafe { ::std::mem::transmute(self.0.numer()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn denom(&self) -> &Integer {
|
||||
unsafe { ::std::mem::transmute(self.0.denom()) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn abs(self) -> Self {
|
||||
Rational(self.0.abs())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn abs_ref(&self) -> Self {
|
||||
Rational(self.0.abs())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fract_floor_ref(&self) -> &Self {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<isize> for Rational {
|
||||
#[inline]
|
||||
fn from(s: isize) -> Self {
|
||||
Rational(BigRational::new_raw(BigInt::from(s), One::one()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Integer> for Rational {
|
||||
#[inline]
|
||||
fn from(s: &Integer) -> Self {
|
||||
Rational::from(s.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Rational> for Rational {
|
||||
#[inline]
|
||||
fn from(s: &Rational) -> Self {
|
||||
s.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Integer> for Rational {
|
||||
#[inline]
|
||||
fn from(i: Integer) -> Self {
|
||||
Rational(BigRational::from(i.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Rational {
|
||||
type Output = Rational;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: Rational) -> Self::Output {
|
||||
Rational(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<&Rational> for Rational {
|
||||
type Output = Rational;
|
||||
|
||||
#[inline]
|
||||
fn add(self, other: &Rational) -> Self::Output {
|
||||
Rational(self.0 + &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i32> for Rational {
|
||||
#[inline]
|
||||
fn eq(&self, other: &i32) -> bool {
|
||||
self.0 == BigRational::from(BigInt::from(*other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<i64> for Rational {
|
||||
#[inline]
|
||||
fn eq(&self, other: &i64) -> bool {
|
||||
self.0 == BigRational::from(BigInt::from(*other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<isize> for Rational {
|
||||
#[inline]
|
||||
fn eq(&self, other: &isize) -> bool {
|
||||
self == &Rational::from(*other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Rational> for isize {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Rational) -> bool {
|
||||
other == &Rational::from(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<isize> for Rational {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &isize) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&BigRational::from(BigInt::from(*other)))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<i64> for Rational {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &i64) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&BigRational::from(BigInt::from(*other)))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<i32> for Rational {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
|
||||
self.0.partial_cmp(&BigRational::from(BigInt::from(*other)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Rational {
|
||||
type Output = Rational;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self {
|
||||
Rational(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Rational {
|
||||
type Output = Rational;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, other: Rational) -> Self::Output {
|
||||
Rational(self.0 * other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<&Rational> for Rational {
|
||||
type Output = Rational;
|
||||
|
||||
fn mul(self, other: &Rational) -> Self::Output {
|
||||
Rational(self.0 * &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Rational {
|
||||
type Output = Rational;
|
||||
|
||||
#[inline]
|
||||
fn div(self, other: Rational) -> Self::Output {
|
||||
Rational(self.0 / other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<&Rational> for &Rational {
|
||||
type Output = Rational;
|
||||
|
||||
#[inline]
|
||||
fn div(self, other: &Rational) -> Self::Output {
|
||||
Rational(&self.0 / &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Rational {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Assign<Src = Self> {
|
||||
fn assign(&mut self, src: Src);
|
||||
}
|
||||
|
||||
impl Assign<&Rational> for (&mut Rational, &mut Integer) {
|
||||
fn assign(&mut self, _src: &Rational) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod ops {
|
||||
use super::{Integer, Rational};
|
||||
|
||||
pub trait Pow<Rhs> {
|
||||
type Output;
|
||||
fn pow(self, rhs: Rhs) -> Self::Output;
|
||||
}
|
||||
|
||||
impl Pow<u32> for Integer {
|
||||
type Output = Integer;
|
||||
|
||||
fn pow(self, rhs: u32) -> Self::Output {
|
||||
Integer(num_traits::Pow::pow(&self.0, rhs))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PowAssign<Rhs> {
|
||||
fn pow_assign(&mut self, rhs: Rhs);
|
||||
}
|
||||
|
||||
impl PowAssign<u32> for Integer {
|
||||
fn pow_assign(&mut self, rhs: u32) {
|
||||
// FIXME: make it efficient
|
||||
self.0 = num_traits::Pow::pow(&self.0, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NegAssign {
|
||||
fn neg_assign(&mut self);
|
||||
}
|
||||
|
||||
impl NegAssign for Integer {
|
||||
fn neg_assign(&mut self) {
|
||||
self.0 = -std::mem::replace(self, Integer::new()).0;
|
||||
}
|
||||
}
|
||||
|
||||
impl NegAssign for Rational {
|
||||
#[inline]
|
||||
fn neg_assign(&mut self) {
|
||||
self.0 = -std::mem::replace(self, Rational::new()).0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod rand {
|
||||
use super::Integer;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct RandState<'a>{
|
||||
_marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> RandState<'a> {
|
||||
pub fn new() -> Self {
|
||||
unsafe { libc::srand(libc::time(std::ptr::null_mut()) as _) };
|
||||
RandState { _marker: PhantomData }
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn bits(&mut self, bits: u32) -> u32 {
|
||||
assert!(bits <= 32);
|
||||
(unsafe { libc::rand() } as u32) & (u32::max_value() >> (32 - bits))
|
||||
}
|
||||
|
||||
pub fn seed(&mut self, seed: &Integer) {
|
||||
unsafe { libc::srand(seed.to_f64() as _)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::ops::NegAssign;
|
||||
|
||||
#[test]
|
||||
fn bits() {
|
||||
let mut rand = rand::RandState::new();
|
||||
for bits in 1..32 {
|
||||
for _ in 0..100 {
|
||||
let r = rand.bits(bits);
|
||||
let max = 1 << bits;
|
||||
assert!(max > r, "{} > {}", max, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neg_rational() {
|
||||
let mut x = Rational::from_f64(5.0).unwrap();
|
||||
let x_neg = Rational::from_f64(-5.0).unwrap();
|
||||
x.neg_assign();
|
||||
assert_eq!(x, x_neg);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neg_integer() {
|
||||
let mut x = Integer::from(5);
|
||||
let x_neg = Integer::from(-5);
|
||||
x.neg_assign();
|
||||
assert_eq!(x, x_neg);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "static-string-indexing"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "*"
|
||||
syn = { version = "*", features = ['full', 'visit', 'extra-traits'] }
|
||||
indexmap = "*"
|
||||
walkdir = "2"
|
||||
quote = "*"
|
||||
@@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "to-syn-value"
|
||||
version = "0.1.0"
|
||||
authors = ["Mark Thom <markjordanthom@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "*", features = ['full', 'visit', 'extra-traits'] }
|
||||
to-syn-value_derive = { path = "../to-syn-value_derive" }
|
||||
@@ -1,3 +0,0 @@
|
||||
pub trait ToDeriveInput {
|
||||
fn to_derive_input() -> syn::DeriveInput;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "to-syn-value_derive"
|
||||
version = "0.1.0"
|
||||
authors = ["Mark Thom <markjordanthom@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "*"
|
||||
syn = { version = "*", features = ['full', 'visit', 'extra-traits'] }
|
||||
quote = "*"
|
||||
@@ -1,20 +0,0 @@
|
||||
use syn::*;
|
||||
use quote::*;
|
||||
|
||||
#[proc_macro_derive(ToDeriveInput)]
|
||||
pub fn derive_to_derive_input(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let derive_input = parse_macro_input!(input as DeriveInput);
|
||||
let ty_name = derive_input.ident.clone();
|
||||
|
||||
quote! {
|
||||
use to_syn_value::*;
|
||||
|
||||
impl ToDeriveInput for #ty_name {
|
||||
fn to_derive_input() -> syn::DeriveInput {
|
||||
syn::parse_quote! {
|
||||
#derive_input
|
||||
}
|
||||
}
|
||||
}
|
||||
}.into()
|
||||
}
|
||||
28
scryer-prolog.wxs
Normal file
28
scryer-prolog.wxs
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Name="Scryer Prolog" Manufacturer="Scryer Prolog contributors" Id="*" UpgradeCode="cfb2dee4-5dd5-4d7d-b426-cd7340810559" Language="1033" Codepage="1252" Version="0.9.0">
|
||||
<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">
|
||||
<Component Id="MainExecutable" Guid="1b41ceda-ba18-47f9-911b-ee41b4f20921">
|
||||
<File Id="ScryerPrologEXE" Name="scryer-prolog.exe" DiskId="1" Source="target/release/scryer-prolog.exe" KeyPath="yes" Checksum="yes"/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<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>
|
||||
</Directory>
|
||||
</Directory>
|
||||
<Feature Id="Complete" Level="1" Display="expand" ConfigurableDirectory="INSTALLDIR">
|
||||
<ComponentRef Id="MainExecutable"/>
|
||||
<ComponentRef Id="ApplicationShortcut"/>
|
||||
</Feature>
|
||||
</Product>
|
||||
</Wix>
|
||||
@@ -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,7 +13,7 @@ pub(crate) trait Allocator {
|
||||
&mut self,
|
||||
lvl: Level,
|
||||
context: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
|
||||
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
|
||||
@@ -25,82 +21,37 @@ pub(crate) trait Allocator {
|
||||
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 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(),
|
||||
}
|
||||
}
|
||||
fn max_reg_allocated(&self) -> usize;
|
||||
}
|
||||
|
||||
693
src/arena.rs
693
src/arena.rs
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,21 @@
|
||||
use crate::allocator::*;
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::fixtures::*;
|
||||
use crate::debray_allocator::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::iterators::*;
|
||||
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 crate::machine::machine_indices::*;
|
||||
|
||||
use dashu::base::Abs;
|
||||
use dashu::base::BitTest;
|
||||
use num_order::NumOrd;
|
||||
use ordered_float::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
@@ -22,7 +24,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 +54,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) {
|
||||
@@ -64,27 +65,17 @@ impl<'a> ArithInstructionIterator<'a> {
|
||||
fn from(term: &'a Term) -> Result<Self, ArithmeticError> {
|
||||
let state = match term {
|
||||
Term::AnonVar => return Err(ArithmeticError::UninstantiatedVar),
|
||||
Term::Clause(cell, name, terms) => match ClauseType::from(*name, terms.len()) {
|
||||
ct @ ClauseType::Named(..) => {
|
||||
Ok(TermIterState::Clause(Level::Shallow, 0, cell, ct, terms))
|
||||
}
|
||||
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::Clause(cell, name, terms) => {
|
||||
TermIterState::Clause(Level::Shallow, 0, cell, *name, terms)
|
||||
}
|
||||
Term::Literal(cell, cons) => TermIterState::Literal(Level::Shallow, cell, cons),
|
||||
Term::Cons(..) | Term::PartialString(..) => {
|
||||
Term::Cons(..) | Term::PartialString(..) | Term::CompleteString(..) => {
|
||||
return Err(ArithmeticError::NonEvaluableFunctor(
|
||||
Literal::Atom(atom!(".")),
|
||||
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 {
|
||||
@@ -97,7 +88,7 @@ impl<'a> ArithInstructionIterator<'a> {
|
||||
pub(crate) enum ArithTermRef<'a> {
|
||||
Literal(&'a 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> {
|
||||
@@ -107,29 +98,26 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
|
||||
while let Some(iter_state) = self.state_stack.pop() {
|
||||
match iter_state {
|
||||
TermIterState::AnonVar(_) => return Some(Err(ArithmeticError::UninstantiatedVar)),
|
||||
TermIterState::Clause(lvl, child_num, cell, ct, subterms) => {
|
||||
TermIterState::Clause(lvl, child_num, cell, name, subterms) => {
|
||||
let arity = subterms.len();
|
||||
|
||||
if child_num == arity {
|
||||
return Some(Ok(ArithTermRef::Op(ct.name(), arity)));
|
||||
return Some(Ok(ArithTermRef::Op(name, arity)));
|
||||
} else {
|
||||
self.state_stack.push(TermIterState::Clause(
|
||||
lvl,
|
||||
child_num + 1,
|
||||
cell,
|
||||
ct,
|
||||
name,
|
||||
subterms,
|
||||
));
|
||||
|
||||
self.push_subterm(lvl, &subterms[child_num]);
|
||||
self.push_subterm(lvl.child_level(), &subterms[child_num]);
|
||||
}
|
||||
}
|
||||
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(c))),
|
||||
TermIterState::Var(lvl, cell, var) => {
|
||||
// the expression is the second argument of an
|
||||
// is/2 but the iterator can't see that, so the
|
||||
// level needs to be demoted manually.
|
||||
return Some(Ok(ArithTermRef::Var(lvl.child_level(), cell, var.clone())));
|
||||
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||
return Some(Ok(ArithTermRef::Var(lvl, cell, var_ptr)));
|
||||
}
|
||||
_ => {
|
||||
return Some(Err(ArithmeticError::NonEvaluableFunctor(
|
||||
@@ -145,8 +133,8 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ArithmeticEvaluator<'a, TermMarker> {
|
||||
marker: &'a mut TermMarker,
|
||||
pub(crate) struct ArithmeticEvaluator<'a> {
|
||||
marker: &'a mut DebrayAllocator,
|
||||
interm: Vec<ArithmeticTerm>,
|
||||
interm_c: usize,
|
||||
}
|
||||
@@ -169,16 +157,16 @@ 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))),
|
||||
Literal::Float(n) => interm.push(ArithmeticTerm::Number(Number::Float(*n.as_ptr()))),
|
||||
Literal::Rational(n) => interm.push(ArithmeticTerm::Number(Number::Rational(*n))),
|
||||
Literal::Atom(name) if name == &atom!("e") => interm.push(ArithmeticTerm::Number(
|
||||
Number::Float(OrderedFloat(f64::consts::E)),
|
||||
Number::Float(OrderedFloat(std::f64::consts::E)),
|
||||
)),
|
||||
Literal::Atom(name) if name == &atom!("pi") => interm.push(ArithmeticTerm::Number(
|
||||
Number::Float(OrderedFloat(f64::consts::PI)),
|
||||
Number::Float(OrderedFloat(std::f64::consts::PI)),
|
||||
)),
|
||||
Literal::Atom(name) if name == &atom!("epsilon") => interm.push(ArithmeticTerm::Number(
|
||||
Number::Float(OrderedFloat(f64::EPSILON)),
|
||||
Number::Float(OrderedFloat(std::f64::EPSILON)),
|
||||
)),
|
||||
_ => return Err(ArithmeticError::NonEvaluableFunctor(*c, 0)),
|
||||
}
|
||||
@@ -186,8 +174,8 @@ fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), Ari
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a, TermMarker: Allocator> ArithmeticEvaluator<'a, TermMarker> {
|
||||
pub(crate) fn new(marker: &'a mut TermMarker, target_int: usize) -> Self {
|
||||
impl<'a> ArithmeticEvaluator<'a> {
|
||||
pub(crate) fn new(marker: &'a mut DebrayAllocator, target_int: usize) -> Self {
|
||||
ArithmeticEvaluator {
|
||||
marker,
|
||||
interm: Vec::new(),
|
||||
@@ -219,6 +207,8 @@ impl<'a, TermMarker: Allocator> ArithmeticEvaluator<'a, TermMarker> {
|
||||
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 +268,7 @@ impl<'a, TermMarker: Allocator> ArithmeticEvaluator<'a, TermMarker> {
|
||||
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)
|
||||
};
|
||||
|
||||
@@ -315,54 +305,44 @@ impl<'a, TermMarker: Allocator> ArithmeticEvaluator<'a, TermMarker> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eval(
|
||||
pub(crate) fn compile_is(
|
||||
&mut self,
|
||||
src: &'a Term,
|
||||
term_loc: GenContext,
|
||||
) -> Result<ArithCont, ArithmeticError>
|
||||
{
|
||||
let mut code = vec![];
|
||||
let mut iter = src.iter()?;
|
||||
arg: usize,
|
||||
) -> 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::Var(lvl, cell, name) => {
|
||||
let r = if cell.get().norm().reg_num() == 0 {
|
||||
let mut getter = || {
|
||||
use crate::targets::QueryInstruction;
|
||||
let var_num = name.to_var_num().unwrap();
|
||||
|
||||
loop {
|
||||
match self.marker.bindings().get(&name) {
|
||||
Some(&VarData::Temp(_, t, _)) if t != 0 =>
|
||||
return RegType::Temp(t),
|
||||
Some(&VarData::Perm(p)) if p != 0 =>
|
||||
return RegType::Perm(p),
|
||||
_ => {
|
||||
self.marker.mark_var::<QueryInstruction>(
|
||||
name.clone(),
|
||||
lvl,
|
||||
cell,
|
||||
term_loc,
|
||||
&mut code,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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);
|
||||
|
||||
getter()
|
||||
/*
|
||||
_ => return Err(ArithmeticError::UninstantiatedVar),
|
||||
*/
|
||||
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)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,16 +352,37 @@ impl<'a, TermMarker: Allocator> ArithmeticEvaluator<'a, TermMarker> {
|
||||
}
|
||||
|
||||
// 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) -> Number {
|
||||
match n {
|
||||
&Number::Integer(_) | &Number::Fixnum(_) => *n,
|
||||
&Number::Float(OrderedFloat(f)) => fixnum!(Number, f.floor() as i64, 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::Integer(i) => {
|
||||
let result = (&*i).try_into();
|
||||
if let Ok(value) = result {
|
||||
fixnum!(Number, value, arena)
|
||||
} else {
|
||||
*n
|
||||
}
|
||||
}
|
||||
Number::Fixnum(_) => *n,
|
||||
&Number::Float(f) => {
|
||||
let f = f.floor();
|
||||
|
||||
Number::Integer(arena_alloc!(floor, arena))
|
||||
const I64_MIN_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MIN as f64);
|
||||
const I64_MAX_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MAX as f64);
|
||||
|
||||
if I64_MIN_TO_F <= f && f <= I64_MAX_TO_F {
|
||||
fixnum!(Number, f.into_inner() as i64, arena)
|
||||
} else {
|
||||
Number::Integer(arena_alloc!(Integer::from(f.0 as i64), arena))
|
||||
}
|
||||
}
|
||||
Number::Rational(ref r) => {
|
||||
let (_, floor) = (r.fract(), r.floor());
|
||||
|
||||
if let Ok(value) = (&floor).try_into() {
|
||||
fixnum!(Number, value, arena)
|
||||
} else {
|
||||
Number::Integer(arena_alloc!(floor, arena))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,30 +398,21 @@ 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(),
|
||||
}
|
||||
}
|
||||
|
||||
// floating point result function -- 9.1.4.2.
|
||||
pub(crate) fn result_f<Round>(n: &Number, round: Round) -> Result<f64, EvalError>
|
||||
where
|
||||
Round: Fn(&Number) -> f64,
|
||||
{
|
||||
let f = rnd_f(n);
|
||||
classify_float(f, round)
|
||||
pub(crate) fn result_f(n: &Number) -> Result<f64, EvalError> {
|
||||
classify_float(rnd_f(n))
|
||||
}
|
||||
|
||||
fn classify_float<Round>(f: f64, round: Round) -> Result<f64, EvalError>
|
||||
where
|
||||
Round: Fn(&Number) -> f64,
|
||||
{
|
||||
fn classify_float(f: f64) -> Result<f64, EvalError> {
|
||||
match f.classify() {
|
||||
FpCategory::Normal | FpCategory::Zero => Ok(round(&Number::Float(OrderedFloat(f)))),
|
||||
FpCategory::Normal | FpCategory::Zero => Ok(f),
|
||||
FpCategory::Infinite => {
|
||||
let f = round(&Number::Float(OrderedFloat(f)));
|
||||
|
||||
if OrderedFloat(f) == OrderedFloat(f64::MAX) {
|
||||
Ok(f)
|
||||
} else {
|
||||
@@ -428,33 +420,33 @@ where
|
||||
}
|
||||
}
|
||||
FpCategory::Nan => Err(EvalError::Undefined),
|
||||
_ => Ok(round(&Number::Float(OrderedFloat(f)))),
|
||||
_ => Ok(f),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_fn_to_f(n: i64) -> Result<f64, EvalError> {
|
||||
classify_float(n as f64, rnd_f)
|
||||
classify_float(n as f64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_i_to_f(n: &Integer) -> Result<f64, EvalError> {
|
||||
classify_float(n.to_f64(), rnd_f)
|
||||
classify_float(n.to_f64().value())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_r_to_f(r: &Rational) -> Result<f64, EvalError> {
|
||||
classify_float(r.to_f64(), rnd_f)
|
||||
classify_float(r.to_f64().value())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn add_f(f1: f64, f2: f64) -> Result<OrderedFloat<f64>, EvalError> {
|
||||
Ok(OrderedFloat(classify_float(f1 + f2, rnd_f)?))
|
||||
Ok(OrderedFloat(classify_float(f1 + f2)?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn mul_f(f1: f64, f2: f64) -> Result<OrderedFloat<f64>, EvalError> {
|
||||
Ok(OrderedFloat(classify_float(f1 * f2, rnd_f)?))
|
||||
Ok(OrderedFloat(classify_float(f1 * f2)?))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -462,7 +454,7 @@ fn div_f(f1: f64, f2: f64) -> Result<OrderedFloat<f64>, EvalError> {
|
||||
if FpCategory::Zero == f2.classify() {
|
||||
Err(EvalError::ZeroDivisor)
|
||||
} else {
|
||||
Ok(OrderedFloat(classify_float(f1 / f2, rnd_f)?))
|
||||
Ok(OrderedFloat(classify_float(f1 / f2)?))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,39 +528,51 @@ 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)) => {
|
||||
(Number::Integer(ref n1), Number::Integer(ref n2)) => n1.eq(n2),
|
||||
(Number::Integer(ref n1), Number::Float(n2)) => {
|
||||
OrderedFloat(n1.to_f64().value()).eq(n2)
|
||||
}
|
||||
(&Number::Float(n1), Number::Integer(ref n2)) => {
|
||||
n1.eq(&OrderedFloat(n2.to_f64().value()))
|
||||
}
|
||||
(Number::Integer(ref n1), Number::Rational(ref n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
&Rational::from(&**n1) == &**n2
|
||||
}
|
||||
#[cfg(not(feature = "num"))]
|
||||
{
|
||||
&**n1 == &**n2
|
||||
n1.num_eq(&**n2)
|
||||
}
|
||||
}
|
||||
(&Number::Rational(ref n1), &Number::Integer(ref n2)) => {
|
||||
(Number::Rational(ref n1), Number::Integer(ref n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
&**n1 == &Rational::from(&**n2)
|
||||
n1 == &Rational::from(&**n2)
|
||||
}
|
||||
#[cfg(not(feature = "num"))]
|
||||
{
|
||||
&**n1 == &**n2
|
||||
n1.num_eq(&**n2)
|
||||
}
|
||||
}
|
||||
(&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::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::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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,8 +592,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)),
|
||||
}
|
||||
}
|
||||
@@ -608,8 +612,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)),
|
||||
}
|
||||
}
|
||||
@@ -625,17 +629,19 @@ 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")]
|
||||
{
|
||||
@@ -643,7 +649,7 @@ impl Ord for Number {
|
||||
}
|
||||
#[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)) => {
|
||||
@@ -653,11 +659,15 @@ impl Ord for Number {
|
||||
}
|
||||
#[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()).cmp(&n2),
|
||||
(&Number::Float(n1), &Number::Rational(n2)) => n1.cmp(&OrderedFloat(n2.to_f64())),
|
||||
(&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::Float(f1), &Number::Float(f2)) => f1.cmp(&f2),
|
||||
(&Number::Rational(r1), &Number::Rational(r2)) => (*r1).cmp(&*r2),
|
||||
}
|
||||
@@ -672,9 +682,6 @@ impl TryFrom<HeapCellValue> for Number {
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Cons, c) => {
|
||||
match_untyped_arena_ptr!(c,
|
||||
(ArenaHeaderTag::F64, n) => {
|
||||
Ok(Number::Float(*n))
|
||||
}
|
||||
(ArenaHeaderTag::Integer, n) => {
|
||||
Ok(Number::Integer(n))
|
||||
}
|
||||
@@ -687,9 +694,9 @@ impl TryFrom<HeapCellValue> for Number {
|
||||
)
|
||||
}
|
||||
(HeapCellValueTag::F64, n) => {
|
||||
Ok(Number::Float(**n))
|
||||
Ok(Number::Float(*n))
|
||||
}
|
||||
(HeapCellValueTag::Fixnum, n) => {
|
||||
(HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
|
||||
Ok(Number::Fixnum(n))
|
||||
}
|
||||
_ => {
|
||||
@@ -701,20 +708,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
use crate::parser::ast::MAX_ARITY;
|
||||
use crate::raw_block::*;
|
||||
use crate::rcu::{Rcu, RcuRef};
|
||||
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 indexmap::IndexSet;
|
||||
|
||||
use modular_bitfield::prelude::*;
|
||||
use scryer_modular_bitfield::prelude::*;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Atom {
|
||||
pub index: usize,
|
||||
pub index: u64,
|
||||
}
|
||||
|
||||
const_assert!(mem::size_of::<Atom>() == 8);
|
||||
@@ -33,46 +38,43 @@ 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") }
|
||||
if value {
|
||||
atom!("true")
|
||||
} else {
|
||||
atom!("false")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
use std::cell::RefCell;
|
||||
impl indexmap::Equivalent<Atom> for str {
|
||||
fn equivalent(&self, key: &Atom) -> bool {
|
||||
&*key.as_str() == self
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
#[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;
|
||||
#[inline(always)]
|
||||
fn global_atom_table() -> &'static RwLock<Weak<AtomTable>> {
|
||||
#[cfg(feature = "rust_beta_channel")]
|
||||
{
|
||||
// const Weak::new will be stabilized in 1.73 which is currently in beta,
|
||||
// till then we need a OnceLock for initialization
|
||||
static GLOBAL_ATOM_TABLE: RwLock<Weak<AtomTable>> = RwLock::const_new(Weak::new());
|
||||
&GLOBAL_ATOM_TABLE
|
||||
}
|
||||
#[cfg(not(feature = "rust_beta_channel"))]
|
||||
{
|
||||
use std::sync::OnceLock;
|
||||
static GLOBAL_ATOM_TABLE: OnceLock<RwLock<Weak<AtomTable>>> = OnceLock::new();
|
||||
GLOBAL_ATOM_TABLE.get_or_init(|| RwLock::new(Weak::new()))
|
||||
}
|
||||
}
|
||||
|
||||
#[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 +92,11 @@ 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,
|
||||
}
|
||||
|
||||
impl AtomHeader {
|
||||
@@ -101,13 +105,6 @@ 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) {
|
||||
@@ -123,49 +120,102 @@ macro_rules! is_char {
|
||||
};
|
||||
}
|
||||
|
||||
impl Atom {
|
||||
#[inline]
|
||||
pub fn buf(self) -> *const u8 {
|
||||
let ptr = self.as_ptr();
|
||||
pub enum AtomString<'a> {
|
||||
Static(&'a str),
|
||||
Dynamic(AtomTableRef<str>),
|
||||
}
|
||||
|
||||
if ptr.is_null() {
|
||||
return ptr::null();
|
||||
impl AtomString<'_> {
|
||||
pub fn map<F>(self, f: F) -> Self
|
||||
where
|
||||
for<'a> F: FnOnce(&'a str) -> &'a str,
|
||||
{
|
||||
match self {
|
||||
Self::Static(reference) => Self::Static(f(reference)),
|
||||
Self::Dynamic(guard) => Self::Dynamic(AtomTableRef::map(guard, f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(ptr as usize + mem::size_of::<AtomHeader>()) as *const u8
|
||||
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::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(always)]
|
||||
pub fn is_static(self) -> bool {
|
||||
self.index < STRINGS.len() << 3
|
||||
(self.index as usize) < STRINGS.len() << 3
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_ptr(self) -> *const u8 {
|
||||
pub fn as_ptr(self) -> Option<AtomTableRef<u8>> {
|
||||
if self.is_static() {
|
||||
ptr::null()
|
||||
None
|
||||
} else {
|
||||
(get_atom_tbl_buf_base() as usize + self.index - (STRINGS.len() << 3)) as *const u8
|
||||
let atom_table =
|
||||
arc_atom_table().expect("We should only have an Atom while there is an AtomTable");
|
||||
unsafe {
|
||||
AtomTableRef::try_map(atom_table.buf(), |buf| {
|
||||
(buf as *const u8)
|
||||
.add((self.index as usize) - (STRINGS.len() << 3))
|
||||
.as_ref()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
STRINGS[(self.index >> 3) as usize].len()
|
||||
} else {
|
||||
unsafe { ptr::read(self.as_ptr() as *const AtomHeader).len() as _ }
|
||||
let ptr = self.as_ptr().unwrap();
|
||||
let ptr = ptr.deref() as *const u8 as *const AtomHeader;
|
||||
unsafe { ptr::read(ptr) }.len() as _
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn flat_index(self) -> u64 {
|
||||
(self.index >> 3) as u64
|
||||
self.index >> 3
|
||||
}
|
||||
|
||||
pub fn as_char(self) -> Option<char> {
|
||||
@@ -175,48 +225,49 @@ 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]
|
||||
pub fn as_str(&self) -> AtomString<'static> {
|
||||
if self.is_static() {
|
||||
AtomString::Static(STRINGS[(self.index >> 3) as usize])
|
||||
} else if let Some(ptr) = self.as_ptr() {
|
||||
AtomString::Dynamic(AtomTableRef::map(ptr, |ptr| {
|
||||
let header =
|
||||
// Miri seems to hit this line a lot
|
||||
unsafe { ptr::read::<AtomHeader>(ptr as *const u8 as *const AtomHeader) };
|
||||
let len = header.len() as usize;
|
||||
let buf = unsafe { (ptr as *const u8).add(mem::size_of::<AtomHeader>()) };
|
||||
|
||||
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(buf, len)) }
|
||||
}))
|
||||
} else {
|
||||
AtomString::Static(STRINGS[(self.index >> 3) 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,100 +280,156 @@ 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: Rcu<IndexSet<Atom>>,
|
||||
}
|
||||
|
||||
impl Drop for AtomTable {
|
||||
fn drop(&mut self) {
|
||||
self.block.deallocate();
|
||||
#[derive(Debug)]
|
||||
pub struct AtomTable {
|
||||
inner: Rcu<InnerAtomTable>,
|
||||
// this lock is taking during resizing
|
||||
update: Mutex<()>,
|
||||
}
|
||||
|
||||
pub type AtomTableRef<M> = 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.active_epoch().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
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn buf(&self) -> *const u8 {
|
||||
self.block.base as *const u8
|
||||
}
|
||||
|
||||
#[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 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: Rcu::new(InnerAtomTable {
|
||||
block: RawBlock::new(),
|
||||
table: Rcu::new(IndexSet::new()),
|
||||
}),
|
||||
update: Mutex::new(()),
|
||||
});
|
||||
*guard = Arc::downgrade(&atom_table);
|
||||
atom_table
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn buf(&self) -> AtomTableRef<u8> {
|
||||
AtomTableRef::<InnerAtomTable>::map(self.inner.active_epoch(), |inner| {
|
||||
unsafe { inner.block.base.as_ref() }.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn active_table(&self) -> RcuRef<IndexSet<Atom>, IndexSet<Atom>> {
|
||||
self.inner.active_epoch().table.active_epoch()
|
||||
}
|
||||
|
||||
pub fn build_with(atom_table: &AtomTable, string: &str) -> Atom {
|
||||
loop {
|
||||
let mut block_epoch = atom_table.inner.active_epoch();
|
||||
let mut table_epoch = block_epoch.table.active_epoch();
|
||||
|
||||
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.active_epoch());
|
||||
let is_same_atom_list =
|
||||
RcuRef::same_epoch(&table_epoch, &block_epoch.table.active_epoch());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
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 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 = Rcu::new(table_epoch.clone());
|
||||
let new_alloc = InnerAtomTable {
|
||||
block: new_block,
|
||||
table: new_table,
|
||||
};
|
||||
atom_table.inner.replace(new_alloc);
|
||||
block_epoch = atom_table.inner.active_epoch();
|
||||
table_epoch = block_epoch.table.active_epoch();
|
||||
} else {
|
||||
break;
|
||||
break ptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ptr
|
||||
};
|
||||
let ptr_base = block_epoch.block.base as usize;
|
||||
|
||||
let ptr_base = self.block.base as usize;
|
||||
write_to_ptr(string, len_ptr);
|
||||
|
||||
write_to_ptr(string, len_ptr);
|
||||
let atom = Atom {
|
||||
index: ((STRINGS.len() << 3) + len_ptr as usize - ptr_base) as u64,
|
||||
};
|
||||
|
||||
let atom = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for AtomTable {}
|
||||
unsafe impl Sync for AtomTable {}
|
||||
|
||||
#[bitfield]
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtomCell {
|
||||
name: B46,
|
||||
arity: B10,
|
||||
#[allow(unused)] f: bool,
|
||||
#[allow(unused)] m: bool,
|
||||
#[allow(unused)] tag: B6,
|
||||
#[allow(unused)]
|
||||
f: bool,
|
||||
#[allow(unused)]
|
||||
m: bool,
|
||||
#[allow(unused)]
|
||||
tag: B6,
|
||||
}
|
||||
|
||||
impl AtomCell {
|
||||
@@ -351,7 +458,7 @@ impl AtomCell {
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(self) -> Atom {
|
||||
Atom::from(self.get_index() << 3)
|
||||
Atom::from((self.get_index() as u64) << 3)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -361,6 +468,6 @@ impl AtomCell {
|
||||
|
||||
#[inline]
|
||||
pub fn get_name_and_arity(self) -> (Atom, usize) {
|
||||
(Atom::from(self.get_index() << 3), self.get_arity())
|
||||
(Atom::from((self.get_index() as u64) << 3), self.get_arity())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
fn main() {
|
||||
use nix::sys::signal;
|
||||
fn main() -> std::process::ExitCode {
|
||||
use scryer_prolog::atom_table::Atom;
|
||||
use scryer_prolog::*;
|
||||
|
||||
let handler = signal::SigHandler::Handler(handle_sigint);
|
||||
unsafe { signal::signal(signal::Signal::SIGINT, handler) }.unwrap();
|
||||
#[cfg(feature = "repl")]
|
||||
ctrlc::set_handler(move || {
|
||||
scryer_prolog::machine::INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut wam = machine::Machine::new();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
wam.run_top_level();
|
||||
}
|
||||
|
||||
pub extern "C" fn handle_sigint(signal: libc::c_int) {
|
||||
use nix::sys::signal;
|
||||
use std::sync::atomic::Ordering;
|
||||
let signal = signal::Signal::from_c_int(signal).unwrap();
|
||||
if signal == signal::Signal::SIGINT {
|
||||
scryer_prolog::machine::INTERRUPT.store(true, Ordering::Relaxed);
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut wam = machine::Machine::new(Default::default());
|
||||
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
|
||||
})
|
||||
}
|
||||
|
||||
1387
src/codegen.rs
1387
src/codegen.rs
File diff suppressed because it is too large
Load Diff
@@ -1,42 +1,277 @@
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::allocator::*;
|
||||
use crate::fixtures::*;
|
||||
use crate::codegen::SubsumedBranchHits;
|
||||
use crate::forms::Level;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
use crate::machine::disjuncts::VarData;
|
||||
use crate::parser::ast::*;
|
||||
use crate::targets::CompilationTarget;
|
||||
|
||||
use crate::temp_v;
|
||||
use crate::targets::*;
|
||||
use crate::variable_records::*;
|
||||
|
||||
use bit_set::*;
|
||||
use bitvec::prelude::*;
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
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.len() > 0 {
|
||||
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 +279,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 +294,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(®) {
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
if !self.is_in_use(reg) && !temp_var_data.no_use_set.contains(reg) {
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +306,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 +318,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(®) {
|
||||
if !tvd.conflict_set.contains(®) {
|
||||
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 +333,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,54 +365,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>(
|
||||
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>,
|
||||
) -> usize
|
||||
where
|
||||
Target: CompilationTarget<'a>,
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,38 +417,260 @@ 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![] },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,12 +678,12 @@ impl Allocator for DebrayAllocator {
|
||||
&mut self,
|
||||
lvl: Level,
|
||||
term_loc: GenContext,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
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;
|
||||
|
||||
@@ -239,7 +693,7 @@ 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));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -249,7 +703,7 @@ impl Allocator for DebrayAllocator {
|
||||
lvl: Level,
|
||||
term_loc: GenContext,
|
||||
cell: &'a Cell<RegType>,
|
||||
code: &mut Code,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
let r = cell.get();
|
||||
|
||||
@@ -276,39 +730,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,
|
||||
) {
|
||||
@@ -316,84 +782,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,4 +900,9 @@ impl Allocator for DebrayAllocator {
|
||||
self.arg_c = 1;
|
||||
self.temp_lb = arity + 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn max_reg_allocated(&self) -> usize {
|
||||
std::cmp::max(self.temp_lb, self.arg_c)
|
||||
}
|
||||
}
|
||||
|
||||
502
src/ffi.rs
Normal file
502
src/ffi.rs
Normal file
@@ -0,0 +1,502 @@
|
||||
/* 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::atom_table::Atom;
|
||||
|
||||
use std::alloc::{alloc, Layout};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::ffi::{c_void, CString};
|
||||
use std::ptr::addr_of_mut;
|
||||
|
||||
use libffi::low::type_tag::STRUCT;
|
||||
use libffi::low::{ffi_abi_FFI_DEFAULT_ABI, ffi_cif, ffi_type, prep_cif, types, CodePtr};
|
||||
use libloading::{Library, Symbol};
|
||||
|
||||
pub struct FunctionDefinition {
|
||||
pub name: String,
|
||||
pub return_value: Atom,
|
||||
pub args: Vec<Atom>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionImpl {
|
||||
cif: ffi_cif,
|
||||
args: Vec<*mut ffi_type>,
|
||||
code_ptr: CodePtr,
|
||||
return_struct_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ForeignFunctionTable {
|
||||
table: HashMap<String, FunctionImpl>,
|
||||
structs: HashMap<String, StructImpl>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct StructImpl {
|
||||
ffi_type: ffi_type,
|
||||
fields: Vec<*mut ffi_type>,
|
||||
atom_fields: Vec<Atom>,
|
||||
}
|
||||
|
||||
struct PointerArgs {
|
||||
pointers: Vec<*mut c_void>,
|
||||
_memory: Vec<Box<dyn Any>>,
|
||||
}
|
||||
|
||||
impl ForeignFunctionTable {
|
||||
pub fn merge(&mut self, other: ForeignFunctionTable) {
|
||||
self.table.extend(other.table);
|
||||
}
|
||||
|
||||
pub fn define_struct(&mut self, name: &str, atom_fields: Vec<Atom>) {
|
||||
let mut fields: Vec<_> = atom_fields.iter().map(|x| self.map_type_ffi(x)).collect();
|
||||
fields.push(std::ptr::null_mut::<ffi_type>());
|
||||
let struct_type = ffi_type {
|
||||
type_: STRUCT,
|
||||
elements: fields.as_mut_ptr(),
|
||||
..Default::default()
|
||||
};
|
||||
self.structs.insert(
|
||||
name.to_string(),
|
||||
StructImpl {
|
||||
ffi_type: struct_type,
|
||||
fields,
|
||||
atom_fields,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn map_type_ffi(&mut self, source: &Atom) -> *mut ffi_type {
|
||||
unsafe {
|
||||
match source {
|
||||
atom!("sint64") => addr_of_mut!(types::sint64),
|
||||
atom!("sint32") => addr_of_mut!(types::sint32),
|
||||
atom!("sint16") => addr_of_mut!(types::sint16),
|
||||
atom!("sint8") => addr_of_mut!(types::sint8),
|
||||
atom!("uint64") => addr_of_mut!(types::uint64),
|
||||
atom!("uint32") => addr_of_mut!(types::uint32),
|
||||
atom!("uint16") => addr_of_mut!(types::uint16),
|
||||
atom!("uint8") => addr_of_mut!(types::uint8),
|
||||
atom!("bool") => addr_of_mut!(types::sint8),
|
||||
atom!("void") => addr_of_mut!(types::void),
|
||||
atom!("cstr") => addr_of_mut!(types::pointer),
|
||||
atom!("ptr") => addr_of_mut!(types::pointer),
|
||||
atom!("f32") => addr_of_mut!(types::float),
|
||||
atom!("f64") => addr_of_mut!(types::double),
|
||||
struct_name => match self.structs.get_mut(&*struct_name.as_str()) {
|
||||
Some(ref mut struct_type) => &mut struct_type.ffi_type,
|
||||
None => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_library(
|
||||
&mut self,
|
||||
library_name: &str,
|
||||
functions: &Vec<FunctionDefinition>,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut ff_table: ForeignFunctionTable = Default::default();
|
||||
unsafe {
|
||||
let library = Library::new(library_name)?;
|
||||
for function in functions {
|
||||
let symbol_name: CString = CString::new(function.name.clone())?;
|
||||
let code_ptr: Symbol<*mut c_void> =
|
||||
library.get(&symbol_name.into_bytes_with_nul())?;
|
||||
let mut args: Vec<_> = function.args.iter().map(|x| self.map_type_ffi(x)).collect();
|
||||
let mut cif: ffi_cif = Default::default();
|
||||
prep_cif(
|
||||
&mut cif,
|
||||
ffi_abi_FFI_DEFAULT_ABI,
|
||||
args.len(),
|
||||
self.map_type_ffi(&function.return_value),
|
||||
args.as_mut_ptr(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let return_struct_name = if (*self.map_type_ffi(&function.return_value)).type_
|
||||
as u32
|
||||
== libffi::raw::FFI_TYPE_STRUCT
|
||||
{
|
||||
Some(function.return_value.as_str().to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ff_table.table.insert(
|
||||
function.name.clone(),
|
||||
FunctionImpl {
|
||||
cif,
|
||||
args,
|
||||
code_ptr: CodePtr(code_ptr.into_raw().into_raw() as *mut _),
|
||||
return_struct_name,
|
||||
},
|
||||
);
|
||||
}
|
||||
std::mem::forget(library);
|
||||
}
|
||||
self.merge(ff_table);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_pointer_args(
|
||||
args: &mut [Value],
|
||||
type_args: &[*mut ffi_type],
|
||||
structs_table: &mut HashMap<String, StructImpl>,
|
||||
) -> Result<PointerArgs, FFIError> {
|
||||
let mut pointers = Vec::with_capacity(args.len());
|
||||
let mut _memory = Vec::new();
|
||||
for i in 0..args.len() {
|
||||
let field_type = type_args[i];
|
||||
unsafe {
|
||||
macro_rules! push_int {
|
||||
($type:ty) => {{
|
||||
let n: $type = <$type>::try_from(args[i].as_int()?)
|
||||
.map_err(|_| FFIError::ValueDontFit)?;
|
||||
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||
_memory.push(box_value);
|
||||
}};
|
||||
}
|
||||
|
||||
match (*field_type).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_UINT8 => push_int!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => push_int!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => push_int!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => push_int!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => push_int!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => push_int!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => push_int!(u64),
|
||||
libffi::raw::FFI_TYPE_SINT64 => push_int!(i64),
|
||||
libffi::raw::FFI_TYPE_FLOAT => {
|
||||
let n: f32 = args[i].as_float()? as f32;
|
||||
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||
_memory.push(box_value);
|
||||
}
|
||||
libffi::raw::FFI_TYPE_DOUBLE => {
|
||||
let n: f64 = args[i].as_float()?;
|
||||
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||
_memory.push(box_value);
|
||||
}
|
||||
libffi::raw::FFI_TYPE_POINTER => {
|
||||
let ptr: *mut c_void = args[i].as_ptr()?;
|
||||
pointers.push(ptr);
|
||||
}
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let (mut ptr, _size, _align) =
|
||||
Self::build_struct(&mut args[i], structs_table)?;
|
||||
pointers.push(&mut *ptr as *mut _ as *mut c_void);
|
||||
_memory.push(ptr);
|
||||
}
|
||||
_ => return Err(FFIError::InvalidFFIType),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(PointerArgs { pointers, _memory })
|
||||
}
|
||||
|
||||
fn build_struct(
|
||||
arg: &mut Value,
|
||||
structs_table: &mut HashMap<String, StructImpl>,
|
||||
) -> Result<(Box<dyn Any>, usize, usize), FFIError> {
|
||||
unsafe {
|
||||
match arg {
|
||||
Value::Struct(ref name, ref mut struct_args) => {
|
||||
if let Some(ref mut struct_type) = structs_table.clone().get_mut(name) {
|
||||
let layout = Layout::from_size_align(
|
||||
struct_type.ffi_type.size,
|
||||
struct_type.ffi_type.alignment.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let align = struct_type.ffi_type.alignment as usize;
|
||||
let size = struct_type.ffi_type.size;
|
||||
let ptr = alloc(layout) as *mut c_void;
|
||||
let mut field_ptr = ptr;
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..(struct_type.fields.len() - 1) {
|
||||
macro_rules! try_write_int {
|
||||
($type:ty) => {{
|
||||
field_ptr = field_ptr
|
||||
.add(field_ptr.align_offset(std::mem::align_of::<$type>()));
|
||||
let n: $type = <$type>::try_from(struct_args[i].as_int()?)
|
||||
.map_err(|_| FFIError::ValueDontFit)?;
|
||||
std::ptr::write(field_ptr as *mut $type, n);
|
||||
field_ptr = field_ptr.add(std::mem::size_of::<$type>());
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! write {
|
||||
($type:ty, $value:expr) => {{
|
||||
let data: $type = $value;
|
||||
std::ptr::write(field_ptr as *mut $type, data);
|
||||
field_ptr = field_ptr.add(align);
|
||||
}};
|
||||
}
|
||||
|
||||
let field = struct_type.fields[i];
|
||||
match (*field).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_UINT8 => try_write_int!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => try_write_int!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => try_write_int!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => try_write_int!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => try_write_int!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => try_write_int!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => try_write_int!(u64),
|
||||
libffi::raw::FFI_TYPE_SINT64 => try_write_int!(i64),
|
||||
libffi::raw::FFI_TYPE_POINTER => {
|
||||
write!(*mut c_void, struct_args[i].as_ptr()?)
|
||||
}
|
||||
libffi::raw::FFI_TYPE_FLOAT => {
|
||||
write!(f32, struct_args[i].as_float()? as f32)
|
||||
}
|
||||
libffi::raw::FFI_TYPE_DOUBLE => {
|
||||
write!(f64, struct_args[i].as_float()?)
|
||||
}
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let (struct_ptr, struct_size, struct_align) =
|
||||
Self::build_struct(&mut struct_args[i], structs_table)?;
|
||||
field_ptr = field_ptr.add(field_ptr.align_offset(struct_align));
|
||||
|
||||
std::ptr::copy(
|
||||
&*struct_ptr as *const _ as *const c_void,
|
||||
field_ptr,
|
||||
struct_size,
|
||||
);
|
||||
field_ptr = field_ptr.add(struct_size);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(clippy::from_raw_with_void_ptr)]
|
||||
Ok((Box::from_raw(ptr), size, align))
|
||||
} else {
|
||||
Err(FFIError::InvalidStructName)
|
||||
}
|
||||
}
|
||||
_ => Err(FFIError::ValueCast),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec(&mut self, name: &str, mut args: Vec<Value>) -> Result<Value, FFIError> {
|
||||
let function_impl = self.table.get_mut(name).ok_or(FFIError::FunctionNotFound)?;
|
||||
let mut pointer_args =
|
||||
Self::build_pointer_args(&mut args, &function_impl.args, &mut self.structs)?;
|
||||
|
||||
return unsafe {
|
||||
macro_rules! call_and_return {
|
||||
($type:ty) => {{
|
||||
let mut n: Box<u8> = Box::new(0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void,
|
||||
);
|
||||
Ok(Value::Int(i64::from(*n)))
|
||||
}};
|
||||
}
|
||||
|
||||
match (*function_impl.cif.rtype).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_VOID => call_and_return!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT8 => call_and_return!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => call_and_return!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => call_and_return!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => call_and_return!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => call_and_return!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => call_and_return!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => {
|
||||
let mut n: Box<u64> = Box::new(0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr(),
|
||||
);
|
||||
Ok(Value::Int(
|
||||
i64::try_from(*n).map_err(|_| FFIError::ValueDontFit)?,
|
||||
))
|
||||
}
|
||||
libffi::raw::FFI_TYPE_SINT64 => call_and_return!(i64),
|
||||
libffi::raw::FFI_TYPE_POINTER => call_and_return!(*mut c_void),
|
||||
libffi::raw::FFI_TYPE_FLOAT => {
|
||||
let mut n: Box<f32> = Box::new(0.0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr(),
|
||||
);
|
||||
Ok(Value::Float((*n).into()))
|
||||
}
|
||||
libffi::raw::FFI_TYPE_DOUBLE => {
|
||||
let mut n: Box<f64> = Box::new(0.0);
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *n as *mut _ as *mut c_void,
|
||||
pointer_args.pointers.as_mut_ptr(),
|
||||
);
|
||||
Ok(Value::Float(*n))
|
||||
}
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let name = &function_impl
|
||||
.return_struct_name
|
||||
.clone()
|
||||
.ok_or(FFIError::StructNotFound)?;
|
||||
let struct_type = self.structs.get(name).ok_or(FFIError::StructNotFound)?;
|
||||
let layout = Layout::from_size_align(
|
||||
struct_type.ffi_type.size,
|
||||
struct_type.ffi_type.alignment.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let ptr = alloc(layout) as *mut c_void;
|
||||
|
||||
libffi::raw::ffi_call(
|
||||
&mut function_impl.cif,
|
||||
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||
&mut *ptr as *mut _,
|
||||
pointer_args.pointers.as_mut_ptr(),
|
||||
);
|
||||
let struct_val = self.read_struct(ptr, name, struct_type);
|
||||
#[allow(clippy::from_raw_with_void_ptr)]
|
||||
drop(Box::from_raw(ptr));
|
||||
struct_val
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn read_struct(
|
||||
&self,
|
||||
ptr: *mut c_void,
|
||||
name: &str,
|
||||
struct_type: &StructImpl,
|
||||
) -> Result<Value, FFIError> {
|
||||
unsafe {
|
||||
let mut returns = Vec::new();
|
||||
let mut field_ptr = ptr;
|
||||
|
||||
for i in 0..(struct_type.fields.len() - 1) {
|
||||
let field = struct_type.fields[i];
|
||||
|
||||
macro_rules! read_and_push_int {
|
||||
($type:ty) => {{
|
||||
field_ptr =
|
||||
field_ptr.add(field_ptr.align_offset(std::mem::align_of::<$type>()));
|
||||
let n = std::ptr::read(field_ptr as *mut $type);
|
||||
returns.push(Value::Int(i64::from(n)));
|
||||
field_ptr = field_ptr.add(std::mem::size_of::<$type>());
|
||||
}};
|
||||
}
|
||||
|
||||
match (*field).type_ as u32 {
|
||||
libffi::raw::FFI_TYPE_UINT8 => read_and_push_int!(u8),
|
||||
libffi::raw::FFI_TYPE_SINT8 => read_and_push_int!(i8),
|
||||
libffi::raw::FFI_TYPE_UINT16 => read_and_push_int!(u16),
|
||||
libffi::raw::FFI_TYPE_SINT16 => read_and_push_int!(i16),
|
||||
libffi::raw::FFI_TYPE_UINT32 => read_and_push_int!(u32),
|
||||
libffi::raw::FFI_TYPE_SINT32 => read_and_push_int!(i32),
|
||||
libffi::raw::FFI_TYPE_UINT64 => {
|
||||
field_ptr =
|
||||
field_ptr.add(field_ptr.align_offset(std::mem::align_of::<u64>()));
|
||||
let n = std::ptr::read(field_ptr as *mut u64);
|
||||
returns.push(Value::Int(
|
||||
i64::try_from(n).map_err(|_| FFIError::ValueDontFit)?,
|
||||
));
|
||||
field_ptr = field_ptr.add(std::mem::size_of::<u64>());
|
||||
}
|
||||
libffi::raw::FFI_TYPE_SINT64 => read_and_push_int!(i64),
|
||||
libffi::raw::FFI_TYPE_POINTER => read_and_push_int!(i64),
|
||||
libffi::raw::FFI_TYPE_STRUCT => {
|
||||
let substruct = struct_type.atom_fields[i].as_str();
|
||||
let struct_type = self
|
||||
.structs
|
||||
.get(&*substruct)
|
||||
.ok_or(FFIError::StructNotFound)?;
|
||||
field_ptr = field_ptr
|
||||
.add(field_ptr.align_offset(struct_type.ffi_type.alignment as usize));
|
||||
let struct_val = self.read_struct(field_ptr, &substruct, struct_type);
|
||||
returns.push(struct_val?);
|
||||
field_ptr = field_ptr.add(struct_type.ffi_type.size);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Value::Struct(name.into(), returns))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Value {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
CString(CString),
|
||||
Struct(String, Vec<Value>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
fn as_int(&self) -> Result<i64, FFIError> {
|
||||
match self {
|
||||
Value::Int(n) => Ok(*n),
|
||||
_ => Err(FFIError::ValueCast),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_float(&self) -> Result<f64, FFIError> {
|
||||
match self {
|
||||
Value::Float(n) => Ok(*n),
|
||||
Value::Int(n) => Ok(*n as f64),
|
||||
_ => Err(FFIError::ValueCast),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&mut self) -> Result<*mut c_void, FFIError> {
|
||||
match self {
|
||||
Value::CString(ref mut cstr) => Ok(&mut *cstr as *mut _ as *mut c_void),
|
||||
Value::Int(n) => Ok(*n as *mut c_void),
|
||||
_ => Err(FFIError::ValueCast),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FFIError {
|
||||
ValueCast,
|
||||
ValueDontFit,
|
||||
InvalidFFIType,
|
||||
InvalidStructName,
|
||||
FunctionNotFound,
|
||||
StructNotFound,
|
||||
}
|
||||
320
src/fixtures.rs
320
src/fixtures.rs
@@ -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(®);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
369
src/forms.rs
369
src/forms.rs
@@ -1,45 +1,43 @@
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::disjuncts::VarData;
|
||||
use crate::machine::heap::*;
|
||||
use crate::machine::loader::PredicateQueue;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
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};
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use slice_deque::*;
|
||||
|
||||
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)]
|
||||
#[derive(Debug)]
|
||||
pub enum TopLevel {
|
||||
Fact(Term), // Term, line_num, col_num
|
||||
Predicate(Predicate),
|
||||
Query(Vec<QueryTerm>),
|
||||
Rule(Rule), // Rule, line_num, col_num
|
||||
Fact(Fact, VarData), // Term, line_num, col_num
|
||||
Rule(Rule, VarData), // Rule, line_num, col_num
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -59,6 +57,12 @@ impl AppendOrPrepend {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum VarComparison {
|
||||
Indistinct,
|
||||
Distinct,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Level {
|
||||
Deep,
|
||||
Root,
|
||||
@@ -74,38 +78,153 @@ impl Level {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CallPolicy {
|
||||
Default,
|
||||
Counted,
|
||||
}
|
||||
|
||||
#[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(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)));
|
||||
}
|
||||
|
||||
pub fn push_branch_arm(&mut self, branch: VecDeque<ChunkedTerms>) {
|
||||
match self.chunk_vec.back_mut().unwrap() {
|
||||
ChunkedTerms::Branch(branches) => {
|
||||
branches.push(branch);
|
||||
}
|
||||
ChunkedTerms::Chunk(_) => {
|
||||
self.chunk_vec.push_back(ChunkedTerms::Branch(vec![branch]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_chunk(&mut self) {
|
||||
self.chunk_vec
|
||||
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![])));
|
||||
}
|
||||
|
||||
pub fn push_chunk_term(&mut self, term: QueryTerm) {
|
||||
match self.chunk_vec.back_mut() {
|
||||
Some(ChunkedTerms::Branch(_)) => {
|
||||
self.chunk_vec
|
||||
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
}
|
||||
Some(ChunkedTerms::Chunk(chunk)) => {
|
||||
chunk.push_back(term);
|
||||
}
|
||||
None => {
|
||||
self.chunk_vec
|
||||
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryTerm {
|
||||
// register, clause type, subterms, use default call policy.
|
||||
Clause(Cell<RegType>, ClauseType, Vec<Term>, bool),
|
||||
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),
|
||||
// register, clause type, subterms, clause call policy.
|
||||
Clause(Cell<RegType>, ClauseType, Vec<Term>, CallPolicy),
|
||||
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_default_caller(&mut self) {
|
||||
match self {
|
||||
&mut QueryTerm::Clause(_, _, _, ref mut use_default_cp) => *use_default_cp = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
@@ -149,31 +268,27 @@ impl ClauseInfo for PredicateKey {
|
||||
|
||||
impl ClauseInfo for Term {
|
||||
fn name(&self) -> Option<Atom> {
|
||||
//, atom_tbl: &AtomTable) -> Option<StringBuffer> {
|
||||
match self {
|
||||
Term::Clause(_, name, terms) => {
|
||||
// let str_buf = StringBuffer::from(*name, atom_tbl);
|
||||
|
||||
match name.as_str() {
|
||||
// str_buf.as_str() {
|
||||
":-" => {
|
||||
match name {
|
||||
atom!(":-") => {
|
||||
match terms.len() {
|
||||
1 => None, // a declaration.
|
||||
2 => terms[0].name(), //.map(|name| StringBuffer::from(name, atom_tbl)),
|
||||
1 => None, // a declaration.
|
||||
2 => terms[0].name(),
|
||||
_ => Some(*name),
|
||||
}
|
||||
}
|
||||
_ => Some(*name), //str_buf),
|
||||
}
|
||||
}
|
||||
Term::Literal(_, Literal::Atom(name)) => Some(*name), //Some(StringBuffer::from(*name, atom_tbl)),
|
||||
Term::Literal(_, Literal::Atom(name)) => Some(*name),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
@@ -199,30 +314,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, ..) => {
|
||||
@@ -267,6 +382,7 @@ pub enum MetaSpec {
|
||||
Minus,
|
||||
Plus,
|
||||
Either,
|
||||
Colon,
|
||||
RequiresExpansionWithArgument(usize),
|
||||
}
|
||||
|
||||
@@ -297,7 +413,6 @@ pub(crate) fn fixity(spec: u32) -> Fixity {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl OpDecl {
|
||||
#[inline]
|
||||
pub(crate) fn new(op_desc: OpDesc, name: Atom) -> Self {
|
||||
@@ -316,13 +431,10 @@ 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));
|
||||
|
||||
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)
|
||||
@@ -333,7 +445,7 @@ 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 let Some(desc) = existing_desc {
|
||||
@@ -364,23 +476,28 @@ pub enum AtomOrString {
|
||||
|
||||
impl AtomOrString {
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &str {
|
||||
pub fn as_atom(&self, atom_tbl: &AtomTable) -> Atom {
|
||||
match self {
|
||||
AtomOrString::Atom(atom) if atom == &atom!("[]") => "",
|
||||
AtomOrString::Atom(atom) => atom.as_str(),
|
||||
AtomOrString::String(string) => string.as_str(),
|
||||
&AtomOrString::Atom(atom) => atom,
|
||||
AtomOrString::String(string) => AtomTable::build_with(atom_tbl, string),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_string(self) -> String {
|
||||
pub fn as_str(&self) -> AtomString<'_> {
|
||||
match self {
|
||||
AtomOrString::Atom(atom) => {
|
||||
atom.as_str().to_owned()
|
||||
}
|
||||
AtomOrString::String(string) => {
|
||||
string
|
||||
}
|
||||
AtomOrString::Atom(atom) if atom == &atom!("[]") => AtomString::Static(""),
|
||||
AtomOrString::Atom(atom) => atom.as_str(),
|
||||
AtomOrString::String(string) => AtomString::Static(string.as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AtomOrString> for String {
|
||||
fn from(val: AtomOrString) -> Self {
|
||||
match val {
|
||||
AtomOrString::Atom(atom) => atom.as_str().to_owned(),
|
||||
AtomOrString::String(string) => string,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,7 +539,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);
|
||||
}
|
||||
@@ -436,9 +553,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,
|
||||
}
|
||||
}
|
||||
@@ -470,10 +585,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()),
|
||||
@@ -495,7 +607,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,
|
||||
}
|
||||
@@ -607,7 +719,7 @@ impl ArenaFrom<Number> for Literal {
|
||||
match value {
|
||||
Number::Fixnum(n) => Literal::Fixnum(n),
|
||||
Number::Integer(n) => Literal::Integer(n),
|
||||
Number::Float(f) => Literal::Float(arena_alloc!(f, arena)),
|
||||
Number::Float(OrderedFloat(f)) => Literal::from(float_alloc!(f, arena)),
|
||||
Number::Rational(r) => Literal::Rational(r),
|
||||
}
|
||||
}
|
||||
@@ -619,49 +731,66 @@ 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(n) => typed_arena_ptr_as_cell!(arena_alloc!(n, arena)),
|
||||
Number::Float(OrderedFloat(n)) => HeapCellValue::from(float_alloc!(n, arena)),
|
||||
Number::Rational(n) => typed_arena_ptr_as_cell!(n),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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())),
|
||||
_ => {
|
||||
if self.is_positive() {
|
||||
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 {
|
||||
Number::Fixnum(Fixnum::build_with(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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(_))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,7 +880,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,
|
||||
@@ -760,19 +889,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 {
|
||||
@@ -781,8 +897,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,7 +910,7 @@ pub(crate) struct LocalPredicateSkeleton {
|
||||
pub(crate) is_discontiguous: bool,
|
||||
pub(crate) is_dynamic: bool,
|
||||
pub(crate) is_multifile: bool,
|
||||
pub(crate) clause_clause_locs: SliceDeque<usize>,
|
||||
pub(crate) clause_clause_locs: VecDeque<usize>,
|
||||
pub(crate) clause_assert_margin: usize,
|
||||
pub(crate) retracted_dynamic_clauses: Option<Vec<ClauseIndexInfo>>, // always None if non-dynamic.
|
||||
}
|
||||
@@ -803,7 +922,7 @@ impl LocalPredicateSkeleton {
|
||||
is_discontiguous: false,
|
||||
is_dynamic: false,
|
||||
is_multifile: false,
|
||||
clause_clause_locs: sdeq![],
|
||||
clause_clause_locs: VecDeque::new(),
|
||||
clause_assert_margin: 0,
|
||||
retracted_dynamic_clauses: Some(vec![]),
|
||||
}
|
||||
@@ -828,7 +947,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![]);
|
||||
@@ -844,7 +963,7 @@ impl LocalPredicateSkeleton {
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct PredicateSkeleton {
|
||||
pub(crate) core: LocalPredicateSkeleton,
|
||||
pub(crate) clauses: SliceDeque<ClauseIndexInfo>,
|
||||
pub(crate) clauses: VecDeque<ClauseIndexInfo>,
|
||||
}
|
||||
|
||||
impl PredicateSkeleton {
|
||||
@@ -852,7 +971,7 @@ impl PredicateSkeleton {
|
||||
pub(crate) fn new() -> Self {
|
||||
PredicateSkeleton {
|
||||
core: LocalPredicateSkeleton::new(),
|
||||
clauses: sdeq![],
|
||||
clauses: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,15 +987,17 @@ impl PredicateSkeleton {
|
||||
}
|
||||
|
||||
pub(crate) fn target_pos_of_clause_clause_loc(
|
||||
&self,
|
||||
&mut self,
|
||||
clause_clause_loc: usize,
|
||||
) -> Option<usize> {
|
||||
let search_result = self.core.clause_clause_locs[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[self.core.clause_assert_margin..]
|
||||
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(),
|
||||
|
||||
1487
src/heap_iter.rs
1487
src/heap_iter.rs
File diff suppressed because it is too large
Load Diff
1356
src/heap_print.rs
1356
src/heap_print.rs
File diff suppressed because it is too large
Load Diff
23
src/http.rs
Normal file
23
src/http.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::io::BufRead;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
|
||||
use warp::http;
|
||||
|
||||
pub struct HttpListener {
|
||||
pub incoming: std::sync::mpsc::Receiver<HttpRequest>,
|
||||
}
|
||||
|
||||
pub struct HttpRequest {
|
||||
pub request_data: HttpRequestData,
|
||||
pub response: HttpResponse,
|
||||
}
|
||||
|
||||
pub type HttpResponse = Arc<(Mutex<bool>, Mutex<Option<warp::reply::Response>>, Condvar)>;
|
||||
|
||||
pub struct HttpRequestData {
|
||||
pub method: http::Method,
|
||||
pub headers: http::HeaderMap,
|
||||
pub path: String,
|
||||
pub query: String,
|
||||
pub body: Box<dyn BufRead + Send>,
|
||||
}
|
||||
359
src/indexing.rs
359
src/indexing.rs
@@ -4,8 +4,8 @@ use crate::parser::ast::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexMap;
|
||||
use slice_deque::{sdeq, SliceDeque};
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hash;
|
||||
@@ -108,7 +108,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
self.append_or_prepend,
|
||||
);
|
||||
|
||||
let mut constants = IndexMap::new();
|
||||
let mut constants = IndexMap::with_hasher(FxBuildHasher::default());
|
||||
|
||||
match constant_key {
|
||||
Some(OptArgIndexKey::Literal(_, _, constant, _)) => {
|
||||
@@ -148,19 +148,22 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
index: usize,
|
||||
) {
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
sdeq![
|
||||
vec![
|
||||
IndexedChoiceInstruction::Try(external),
|
||||
IndexedChoiceInstruction::Trust(index)
|
||||
IndexedChoiceInstruction::Trust(index),
|
||||
]
|
||||
.into()
|
||||
} else {
|
||||
sdeq![
|
||||
vec![
|
||||
IndexedChoiceInstruction::Try(index),
|
||||
IndexedChoiceInstruction::Trust(external)
|
||||
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)) => {
|
||||
@@ -182,13 +185,14 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
index: usize,
|
||||
) {
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
sdeq![external, index]
|
||||
vec![external, index].into()
|
||||
} else {
|
||||
sdeq![index, external]
|
||||
vec![index, external].into()
|
||||
};
|
||||
|
||||
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)) => {
|
||||
@@ -208,11 +212,11 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs)
|
||||
if self.append_or_prepend.is_append() =>
|
||||
{
|
||||
uncap_choice_seq_with_trust(indexed_choice_instrs);
|
||||
uncap_choice_seq_with_trust(indexed_choice_instrs.make_contiguous());
|
||||
indexed_choice_instrs.push_back(IndexedChoiceInstruction::Trust(index));
|
||||
}
|
||||
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
|
||||
uncap_choice_seq_with_try(indexed_choice_instrs);
|
||||
uncap_choice_seq_with_try(indexed_choice_instrs.make_contiguous());
|
||||
indexed_choice_instrs.push_front(IndexedChoiceInstruction::Try(index));
|
||||
}
|
||||
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs)
|
||||
@@ -250,7 +254,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
break;
|
||||
}
|
||||
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
|
||||
let mut constants = IndexMap::new();
|
||||
let mut constants = IndexMap::with_hasher(FxBuildHasher::default());
|
||||
constants.insert(orig_constant, *c);
|
||||
|
||||
*c = IndexingCodePtr::Internal(indexing_code_len);
|
||||
@@ -275,10 +279,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(
|
||||
@@ -345,16 +347,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);
|
||||
@@ -390,7 +386,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
self.append_or_prepend,
|
||||
);
|
||||
|
||||
let mut structures = IndexMap::new();
|
||||
let mut structures = IndexMap::with_hasher(FxBuildHasher::default());
|
||||
|
||||
match structure_key {
|
||||
Some(OptArgIndexKey::Structure(_, _, name, arity)) => {
|
||||
@@ -430,19 +426,22 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
index: usize,
|
||||
) {
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
sdeq![
|
||||
vec![
|
||||
IndexedChoiceInstruction::Try(external),
|
||||
IndexedChoiceInstruction::Trust(index)
|
||||
IndexedChoiceInstruction::Trust(index),
|
||||
]
|
||||
.into()
|
||||
} else {
|
||||
sdeq![
|
||||
vec![
|
||||
IndexedChoiceInstruction::Try(index),
|
||||
IndexedChoiceInstruction::Trust(external)
|
||||
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)) => {
|
||||
@@ -464,13 +463,14 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
index: usize,
|
||||
) {
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
sdeq![external, index]
|
||||
vec![external, index].into()
|
||||
} else {
|
||||
sdeq![index, external]
|
||||
vec![index, external].into()
|
||||
};
|
||||
|
||||
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)) => {
|
||||
@@ -570,9 +570,9 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
|
||||
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
sdeq![o, index]
|
||||
vec![o, index].into()
|
||||
} else {
|
||||
sdeq![index, o]
|
||||
vec![index, o].into()
|
||||
};
|
||||
|
||||
self.indexing_code
|
||||
@@ -582,15 +582,17 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
|
||||
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
sdeq![
|
||||
vec![
|
||||
IndexedChoiceInstruction::Try(o),
|
||||
IndexedChoiceInstruction::Trust(index)
|
||||
IndexedChoiceInstruction::Trust(index),
|
||||
]
|
||||
.into()
|
||||
} else {
|
||||
sdeq![
|
||||
vec![
|
||||
IndexedChoiceInstruction::Try(index),
|
||||
IndexedChoiceInstruction::Trust(o)
|
||||
IndexedChoiceInstruction::Trust(o),
|
||||
]
|
||||
.into()
|
||||
};
|
||||
|
||||
self.indexing_code
|
||||
@@ -613,7 +615,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 {
|
||||
@@ -636,11 +638,7 @@ pub(crate) fn merge_clause_index(
|
||||
for overlapping_constant in overlapping_constants {
|
||||
merging_ptr.offset = 0;
|
||||
|
||||
merging_ptr.index_overlapping_constant(
|
||||
*constant,
|
||||
*overlapping_constant,
|
||||
offset,
|
||||
);
|
||||
merging_ptr.index_overlapping_constant(*constant, *overlapping_constant, offset);
|
||||
}
|
||||
}
|
||||
OptArgIndexKey::Structure(_, index_loc, name, arity) => {
|
||||
@@ -667,7 +665,7 @@ pub(crate) fn merge_clause_index(
|
||||
pub(crate) fn remove_constant_indices(
|
||||
constant: Literal,
|
||||
overlapping_constants: &[Literal],
|
||||
indexing_code: &mut Vec<IndexingLine>,
|
||||
indexing_code: &mut [IndexingLine],
|
||||
offset: usize,
|
||||
) {
|
||||
let mut index = 0;
|
||||
@@ -813,7 +811,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 +843,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.remove(&(name, arity));
|
||||
break;
|
||||
}
|
||||
Some(IndexingCodePtr::Internal(o)) => {
|
||||
@@ -879,7 +877,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 +908,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 +948,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,7 +1028,7 @@ 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 {
|
||||
@@ -1051,43 +1049,55 @@ 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,
|
||||
atom_tbl: &AtomTable,
|
||||
// arena: &mut Arena,
|
||||
) -> Vec<Literal> {
|
||||
let mut constants = vec![];
|
||||
@@ -1099,7 +1109,7 @@ pub(crate) fn constant_key_alternatives(
|
||||
}
|
||||
}
|
||||
Literal::Char(c) => {
|
||||
let atom = atom_tbl.build_with(&c.to_string());
|
||||
let atom = AtomTable::build_with(atom_tbl, &c.to_string());
|
||||
constants.push(Literal::Atom(atom));
|
||||
}
|
||||
/*
|
||||
@@ -1116,21 +1126,15 @@ pub(crate) fn constant_key_alternatives(
|
||||
}
|
||||
*/
|
||||
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();
|
||||
let result = (&**n).try_into();
|
||||
if let Ok(value) = result {
|
||||
Fixnum::build_with_checked(value)
|
||||
.map(|n| {
|
||||
constants.push(Literal::Fixnum(n));
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
/*
|
||||
Literal::Usize(n) => {
|
||||
constants.push(Literal::Integer(Rc::new(Integer::from(*n))));
|
||||
|
||||
if let Ok(n) = isize::try_from(*n) {
|
||||
constants.push(Literal::Fixnum(n));
|
||||
}
|
||||
}
|
||||
*/
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1139,16 +1143,16 @@ pub(crate) fn constant_key_alternatives(
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StaticCodeIndices {
|
||||
constants: IndexMap<Literal, VecDeque<IndexedChoiceInstruction>>,
|
||||
constants: IndexMap<Literal, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
|
||||
lists: VecDeque<IndexedChoiceInstruction>,
|
||||
structures: IndexMap<(Atom, usize), VecDeque<IndexedChoiceInstruction>>,
|
||||
structures: IndexMap<(Atom, usize), VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DynamicCodeIndices {
|
||||
constants: IndexMap<Literal, VecDeque<usize>>,
|
||||
constants: IndexMap<Literal, VecDeque<usize>, FxBuildHasher>,
|
||||
lists: VecDeque<usize>,
|
||||
structures: IndexMap<(Atom, usize), VecDeque<usize>>,
|
||||
structures: IndexMap<(Atom, usize), VecDeque<usize>, FxBuildHasher>,
|
||||
}
|
||||
|
||||
pub(crate) trait Indexer {
|
||||
@@ -1156,20 +1160,28 @@ pub(crate) trait Indexer {
|
||||
|
||||
fn new() -> Self;
|
||||
|
||||
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<Self::ThirdLevelIndex>>;
|
||||
fn constants(
|
||||
&mut self,
|
||||
) -> &mut IndexMap<Literal, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
|
||||
fn lists(&mut self) -> &mut VecDeque<Self::ThirdLevelIndex>;
|
||||
fn structures(&mut self) -> &mut IndexMap<(Atom, usize), VecDeque<Self::ThirdLevelIndex>>;
|
||||
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>>,
|
||||
indices: IndexMap<IndexKey, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>,
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexMap<IndexKey, IndexingCodePtr>;
|
||||
) -> IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>;
|
||||
|
||||
fn switch_on<IndexKey: Eq + Hash>(
|
||||
instr_fn: impl FnMut(IndexMap<IndexKey, IndexingCodePtr>) -> IndexingInstruction,
|
||||
index: &mut IndexMap<IndexKey, VecDeque<Self::ThirdLevelIndex>>,
|
||||
instr_fn: impl FnMut(IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>) -> IndexingInstruction,
|
||||
index: &mut IndexMap<IndexKey, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>,
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexingCodePtr;
|
||||
|
||||
@@ -1178,7 +1190,7 @@ pub(crate) trait Indexer {
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexingCodePtr;
|
||||
|
||||
fn remove_instruction_with_offset(code: &mut SliceDeque<Self::ThirdLevelIndex>, offset: usize);
|
||||
fn remove_instruction_with_offset(code: &mut VecDeque<Self::ThirdLevelIndex>, offset: usize);
|
||||
|
||||
fn var_offset_wrapper(var_offset: usize) -> IndexingCodePtr;
|
||||
}
|
||||
@@ -1189,14 +1201,16 @@ impl Indexer for StaticCodeIndices {
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
constants: IndexMap::new(),
|
||||
constants: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||
lists: VecDeque::new(),
|
||||
structures: IndexMap::new(),
|
||||
structures: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<IndexedChoiceInstruction>> {
|
||||
fn constants(
|
||||
&mut self,
|
||||
) -> &mut IndexMap<Literal, VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
|
||||
&mut self.constants
|
||||
}
|
||||
|
||||
@@ -1206,33 +1220,39 @@ impl Indexer for StaticCodeIndices {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn structures(&mut self) -> &mut IndexMap<(Atom, usize), VecDeque<IndexedChoiceInstruction>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn second_level_index<IndexKey: Eq + Hash>(
|
||||
indices: IndexMap<IndexKey, VecDeque<IndexedChoiceInstruction>>,
|
||||
indices: IndexMap<IndexKey, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexMap<IndexKey, IndexingCodePtr> {
|
||||
let mut index_locs = IndexMap::new();
|
||||
) -> IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher> {
|
||||
let mut index_locs = IndexMap::with_hasher(FxBuildHasher::default());
|
||||
|
||||
for (key, mut code) in indices.into_iter() {
|
||||
if code.len() > 1 {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1240,11 +1260,13 @@ impl Indexer for StaticCodeIndices {
|
||||
}
|
||||
|
||||
fn switch_on<IndexKey: Eq + Hash>(
|
||||
mut instr_fn: impl FnMut(IndexMap<IndexKey, IndexingCodePtr>) -> IndexingInstruction,
|
||||
index: &mut IndexMap<IndexKey, VecDeque<IndexedChoiceInstruction>>,
|
||||
mut instr_fn: impl FnMut(
|
||||
IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>,
|
||||
) -> IndexingInstruction,
|
||||
index: &mut IndexMap<IndexKey, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexingCodePtr {
|
||||
let index = mem::replace(index, IndexMap::new());
|
||||
let index = mem::replace(index, IndexMap::with_hasher(FxBuildHasher::default()));
|
||||
let index = Self::second_level_index(index, prelude);
|
||||
|
||||
if index.len() > 1 {
|
||||
@@ -1267,7 +1289,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)
|
||||
@@ -1281,13 +1303,13 @@ impl Indexer for StaticCodeIndices {
|
||||
|
||||
#[inline]
|
||||
fn remove_instruction_with_offset(
|
||||
code: &mut SliceDeque<IndexedChoiceInstruction>,
|
||||
code: &mut VecDeque<IndexedChoiceInstruction>,
|
||||
offset: usize,
|
||||
) {
|
||||
for (index, line) in code.iter().enumerate() {
|
||||
if offset == line.offset() {
|
||||
code.remove(index);
|
||||
cap_choice_seq(code);
|
||||
cap_choice_seq(code.make_contiguous());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1305,14 +1327,14 @@ impl Indexer for DynamicCodeIndices {
|
||||
#[inline]
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
constants: IndexMap::new(),
|
||||
constants: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||
lists: VecDeque::new(),
|
||||
structures: IndexMap::new(),
|
||||
structures: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<usize>> {
|
||||
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<usize>, FxBuildHasher> {
|
||||
&mut self.constants
|
||||
}
|
||||
|
||||
@@ -1322,29 +1344,29 @@ impl Indexer for DynamicCodeIndices {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn structures(&mut self) -> &mut IndexMap<(Atom, usize), VecDeque<usize>> {
|
||||
fn structures(&mut self) -> &mut IndexMap<(Atom, usize), VecDeque<usize>, FxBuildHasher> {
|
||||
&mut self.structures
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn compute_index(_: bool, index: usize) -> usize {
|
||||
fn compute_index(_: bool, index: usize, _: bool) -> usize {
|
||||
index + 1
|
||||
}
|
||||
|
||||
fn second_level_index<IndexKey: Eq + Hash>(
|
||||
indices: IndexMap<IndexKey, VecDeque<usize>>,
|
||||
indices: IndexMap<IndexKey, VecDeque<usize>, FxBuildHasher>,
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexMap<IndexKey, IndexingCodePtr> {
|
||||
let mut index_locs = IndexMap::new();
|
||||
) -> IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher> {
|
||||
let mut index_locs = IndexMap::with_hasher(FxBuildHasher::default());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1352,11 +1374,13 @@ impl Indexer for DynamicCodeIndices {
|
||||
}
|
||||
|
||||
fn switch_on<IndexKey: Eq + Hash>(
|
||||
mut instr_fn: impl FnMut(IndexMap<IndexKey, IndexingCodePtr>) -> IndexingInstruction,
|
||||
index: &mut IndexMap<IndexKey, VecDeque<usize>>,
|
||||
mut instr_fn: impl FnMut(
|
||||
IndexMap<IndexKey, IndexingCodePtr, FxBuildHasher>,
|
||||
) -> IndexingInstruction,
|
||||
index: &mut IndexMap<IndexKey, VecDeque<usize>, FxBuildHasher>,
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexingCodePtr {
|
||||
let index = mem::replace(index, IndexMap::new());
|
||||
let index = mem::replace(index, IndexMap::with_hasher(FxBuildHasher::default()));
|
||||
let index = Self::second_level_index(index, prelude);
|
||||
|
||||
if index.len() > 1 {
|
||||
@@ -1378,8 +1402,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
|
||||
@@ -1390,7 +1416,7 @@ impl Indexer for DynamicCodeIndices {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn remove_instruction_with_offset(code: &mut SliceDeque<usize>, offset: usize) {
|
||||
fn remove_instruction_with_offset(code: &mut VecDeque<usize>, offset: usize) {
|
||||
for (index, line) in code.iter().enumerate() {
|
||||
if offset == *line {
|
||||
code.remove(index);
|
||||
@@ -1409,43 +1435,45 @@ 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,
|
||||
atom_tbl: &AtomTable,
|
||||
constant: Literal,
|
||||
index: usize,
|
||||
) -> Vec<Literal> {
|
||||
let overlapping_constants = constant_key_alternatives(constant, atom_tbl);
|
||||
let code = self.indices.constants().entry(constant).or_insert(VecDeque::new());
|
||||
let code = self.indices.constants().entry(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());
|
||||
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);
|
||||
}
|
||||
@@ -1454,16 +1482,16 @@ impl<I: Indexer> CodeOffsets<I> {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1472,20 +1500,23 @@ impl<I: Indexer> CodeOffsets<I> {
|
||||
optimal_arg: &Term,
|
||||
index: usize,
|
||||
clause_index_info: &mut ClauseIndexInfo,
|
||||
atom_tbl: &mut AtomTable,
|
||||
atom_tbl: &AtomTable,
|
||||
) {
|
||||
match optimal_arg {
|
||||
&Term::Clause(_, name, ref terms) => {
|
||||
clause_index_info.opt_arg_index_key =
|
||||
OptArgIndexKey::Structure(self.optimal_index, 0, name.clone(), terms.len());
|
||||
|
||||
self.index_structure(name, terms.len(), index);
|
||||
&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(..) => {
|
||||
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, terms.len());
|
||||
|
||||
self.index_structure(name, terms.len(), index);
|
||||
}
|
||||
&Term::Literal(_, constant) => {
|
||||
let overlapping_constants = self.index_constant(atom_tbl, constant, index);
|
||||
|
||||
@@ -1534,20 +1565,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;
|
||||
|
||||
|
||||
490
src/iterators.rs
490
src/iterators.rs
@@ -1,49 +1,53 @@
|
||||
use crate::atom_table::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
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>, ClauseType, &'a Vec<Term>),
|
||||
PartialString(Level, &'a Cell<RegType>, Atom, &'a Option<Box<Term>>),
|
||||
Var(Level, &'a Cell<VarReg>, Rc<String>),
|
||||
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>, VarPtr),
|
||||
}
|
||||
|
||||
/*
|
||||
impl<'a> TermRef<'a> {
|
||||
pub(crate) fn level(self) -> Level {
|
||||
pub(crate) fn level(&self) -> Level {
|
||||
match self {
|
||||
TermRef::AnonVar(lvl)
|
||||
| TermRef::Cons(lvl, ..)
|
||||
| TermRef::Literal(lvl, ..)
|
||||
| TermRef::Var(lvl, ..)
|
||||
| TermRef::Clause(lvl, ..) => lvl,
|
||||
TermRef::PartialString(lvl, ..) => lvl,
|
||||
TermRef::AnonVar(lvl) |
|
||||
TermRef::Cons(lvl, ..) |
|
||||
TermRef::Literal(lvl, ..) |
|
||||
TermRef::Var(lvl, ..) |
|
||||
TermRef::Clause(lvl, ..) |
|
||||
TermRef::CompleteString(lvl, ..) |
|
||||
TermRef::PartialString(lvl, ..) => *lvl,
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TermIterState<'a> {
|
||||
AnonVar(Level),
|
||||
Clause(Level, usize, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||
Clause(Level, usize, &'a Cell<RegType>, ClauseType, &'a Vec<Term>),
|
||||
InitialCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
FinalCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
InitialPartialString(Level, &'a Cell<RegType>, Atom, &'a Option<Box<Term>>),
|
||||
FinalPartialString(Level, &'a Cell<RegType>, Atom, &'a Option<Box<Term>>),
|
||||
Var(Level, &'a Cell<VarReg>, Rc<String>),
|
||||
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>, VarPtr),
|
||||
}
|
||||
|
||||
impl<'a> TermIterState<'a> {
|
||||
@@ -51,17 +55,17 @@ impl<'a> TermIterState<'a> {
|
||||
match term {
|
||||
Term::AnonVar => TermIterState::AnonVar(lvl),
|
||||
Term::Clause(cell, name, subterms) => {
|
||||
let ct = ClauseType::Named(subterms.len(), *name, CodeIndex::default());
|
||||
TermIterState::Clause(lvl, 0, cell, ct, subterms)
|
||||
TermIterState::Clause(lvl, 0, cell, *name, subterms)
|
||||
}
|
||||
Term::Cons(cell, head, tail) => {
|
||||
TermIterState::InitialCons(lvl, cell, head.as_ref(), tail.as_ref())
|
||||
}
|
||||
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, tail)
|
||||
}
|
||||
Term::Var(cell, var) => TermIterState::Var(lvl, cell, var.clone()),
|
||||
Term::CompleteString(cell, atom) => TermIterState::CompleteString(lvl, cell, *atom),
|
||||
Term::Var(cell, var_ptr) => TermIterState::Var(lvl, cell, var_ptr.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +81,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()
|
||||
@@ -86,22 +91,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::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,
|
||||
ClauseType::from(*name, terms.len()),
|
||||
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 {
|
||||
@@ -109,46 +113,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(arity), ref terms, _) => {
|
||||
let state = TermIterState::Clause(Level::Root, 1, cell, ClauseType::CallN(arity), 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.clone(), 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> {
|
||||
@@ -160,28 +145,25 @@ impl<'a> Iterator for QueryIterator<'a> {
|
||||
TermIterState::AnonVar(lvl) => {
|
||||
return Some(TermRef::AnonVar(lvl));
|
||||
}
|
||||
TermIterState::Clause(lvl, child_num, cell, ct, child_terms) => {
|
||||
TermIterState::Clause(lvl, child_num, cell, name, child_terms) => {
|
||||
if child_num == child_terms.len() {
|
||||
match ct {
|
||||
ClauseType::CallN(_) => {
|
||||
match name {
|
||||
atom!("$call") if lvl == Level::Root => {
|
||||
self.push_subterm(Level::Shallow, &child_terms[0]);
|
||||
}
|
||||
ClauseType::Named(..) => {
|
||||
_ => {
|
||||
return match lvl {
|
||||
Level::Root => None,
|
||||
lvl => Some(TermRef::Clause(lvl, cell, ct, child_terms)),
|
||||
lvl => Some(TermRef::Clause(lvl, cell, name, child_terms)),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
self.state_stack.push(TermIterState::Clause(
|
||||
lvl,
|
||||
child_num + 1,
|
||||
cell,
|
||||
ct,
|
||||
name,
|
||||
child_terms,
|
||||
));
|
||||
|
||||
@@ -189,20 +171,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));
|
||||
|
||||
if let Some(tail) = tail {
|
||||
self.push_subterm(lvl.child_level(), tail);
|
||||
}
|
||||
self.state_stack
|
||||
.push(TermIterState::FinalPartialString(lvl, cell, string, tail));
|
||||
self.push_subterm(lvl.child_level(), tail);
|
||||
}
|
||||
TermIterState::FinalPartialString(lvl, cell, string, tail) => {
|
||||
return Some(TermRef::PartialString(lvl, cell, string, tail));
|
||||
TermIterState::FinalPartialString(lvl, cell, atom, tail) => {
|
||||
return Some(TermRef::PartialString(lvl, cell, atom, tail));
|
||||
}
|
||||
TermIterState::CompleteString(lvl, cell, atom) => {
|
||||
return Some(TermRef::CompleteString(lvl, cell, atom));
|
||||
}
|
||||
TermIterState::FinalCons(lvl, cell, head, tail) => {
|
||||
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||
@@ -210,8 +194,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));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -223,7 +207,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> {
|
||||
@@ -232,7 +216,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))
|
||||
@@ -240,18 +224,17 @@ 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)]
|
||||
}
|
||||
Term::Clause(cell, name, terms) => {
|
||||
let ct = ClauseType::from(*name, terms.len());
|
||||
vec![TermIterState::Clause(Level::Root, 0, cell, ct, terms)]
|
||||
vec![TermIterState::Clause(Level::Root, 0, cell, *name, terms)]
|
||||
}
|
||||
Term::Cons(cell, head, tail) => vec![TermIterState::InitialCons(
|
||||
Level::Root,
|
||||
@@ -259,19 +242,22 @@ impl<'a> FactIterator<'a> {
|
||||
head.as_ref(),
|
||||
tail.as_ref(),
|
||||
)],
|
||||
Term::PartialString(cell, string_buf, tail_opt) => {
|
||||
Term::PartialString(cell, string_buf, tail) => {
|
||||
vec![TermIterState::InitialPartialString(
|
||||
Level::Root,
|
||||
cell,
|
||||
*string_buf,
|
||||
tail_opt,
|
||||
string_buf,
|
||||
tail,
|
||||
)]
|
||||
}
|
||||
Term::CompleteString(cell, atom) => {
|
||||
vec![TermIterState::CompleteString(Level::Root, cell, *atom)]
|
||||
}
|
||||
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())]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -291,14 +277,14 @@ impl<'a> Iterator for FactIterator<'a> {
|
||||
TermIterState::AnonVar(lvl) => {
|
||||
return Some(TermRef::AnonVar(lvl));
|
||||
}
|
||||
TermIterState::Clause(lvl, _, cell, ct, child_terms) => {
|
||||
TermIterState::Clause(lvl, _, cell, name, child_terms) => {
|
||||
for child_term in child_terms {
|
||||
self.push_subterm(lvl.child_level(), child_term);
|
||||
}
|
||||
|
||||
match lvl {
|
||||
Level::Root if !self.iterable_root => continue,
|
||||
_ => return Some(TermRef::Clause(lvl, cell, ct, child_terms)),
|
||||
Level::Root if !self.iterable_root.iterable() => continue,
|
||||
_ => return Some(TermRef::Clause(lvl, cell, name, child_terms)),
|
||||
};
|
||||
}
|
||||
TermIterState::InitialCons(lvl, cell, head, tail) => {
|
||||
@@ -307,18 +293,18 @@ impl<'a> Iterator for FactIterator<'a> {
|
||||
|
||||
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||
}
|
||||
TermIterState::InitialPartialString(lvl, cell, string_buf, tail_opt) => {
|
||||
if let Some(tail) = tail_opt {
|
||||
self.push_subterm(Level::Deep, tail);
|
||||
}
|
||||
|
||||
return Some(TermRef::PartialString(lvl, cell, string_buf, tail_opt));
|
||||
TermIterState::InitialPartialString(lvl, cell, string_buf, tail) => {
|
||||
self.push_subterm(Level::Deep, tail);
|
||||
return Some(TermRef::PartialString(lvl, cell, string_buf, tail));
|
||||
}
|
||||
TermIterState::CompleteString(lvl, cell, atom) => {
|
||||
return Some(TermRef::CompleteString(lvl, cell, atom));
|
||||
}
|
||||
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));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -328,206 +314,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(&'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_term_sequence(terms: &'a [QueryTerm]) -> Self {
|
||||
ChunkedIterator {
|
||||
chunk_num: 0,
|
||||
iter: Box::new(terms.iter().map(|t| ChunkedTerm::BodyTerm(t))),
|
||||
deep_cut_encountered: false,
|
||||
cut_var_in_head: false,
|
||||
}
|
||||
}
|
||||
*/
|
||||
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(chunk) => {
|
||||
return Some(ClauseItem::Chunk(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
28
src/lib.rs
28
src/lib.rs
@@ -2,6 +2,9 @@
|
||||
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate maplit;
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
@@ -15,11 +18,15 @@ mod allocator;
|
||||
mod arithmetic;
|
||||
pub mod codegen;
|
||||
mod debray_allocator;
|
||||
mod fixtures;
|
||||
#[cfg(feature = "ffi")]
|
||||
mod ffi;
|
||||
mod forms;
|
||||
mod heap_iter;
|
||||
pub mod heap_print;
|
||||
#[cfg(feature = "http")]
|
||||
mod http;
|
||||
mod indexing;
|
||||
mod variable_records;
|
||||
#[macro_use]
|
||||
pub mod instructions {
|
||||
include!(concat!(env!("OUT_DIR"), "/instructions.rs"));
|
||||
@@ -28,7 +35,26 @@ mod iterators;
|
||||
pub mod machine;
|
||||
mod raw_block;
|
||||
pub mod read;
|
||||
#[cfg(feature = "repl")]
|
||||
mod repl_helper;
|
||||
mod targets;
|
||||
pub mod types;
|
||||
|
||||
use instructions::instr;
|
||||
|
||||
mod rcu;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn eval_code(s: &str) -> String {
|
||||
use machine::mock_wam::*;
|
||||
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let mut wam = Machine::with_test_streams();
|
||||
let bytes = wam.test_load_string(s);
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
125
src/lib/assoc.pl
125
src/lib/assoc.pl
@@ -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, ?, ?).
|
||||
|
||||
%! 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, _).
|
||||
|
||||
104
src/lib/atts.pl
104
src/lib/atts.pl
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
1620
src/lib/builtins.pl
1620
src/lib/builtins.pl
File diff suppressed because it is too large
Load Diff
@@ -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).
|
||||
must_be(var, Term),
|
||||
'$read_from_chars'(Chars, Term).
|
||||
|
||||
%% 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),
|
||||
must_be(var, Term),
|
||||
builtins:parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term_from_chars/3),
|
||||
'$read_term_from_chars'(Chars, Term, Singletons, Variables, VariableNames).
|
||||
|
||||
%% 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),
|
||||
@@ -251,7 +389,7 @@ chars_base64(Cs, Bs, Options) :-
|
||||
'$chars_base64'(Cs, Bs, Padding, Charset)
|
||||
; must_be(chars, Cs),
|
||||
( '$first_non_octet'(Cs, N) ->
|
||||
domain_error(byte_char, N, chars_base64/3)
|
||||
domain_error(octet_character, N, chars_base64/3)
|
||||
; '$chars_base64'(Cs, Bs, Padding, Charset)
|
||||
)
|
||||
).
|
||||
|
||||
445
src/lib/clpb.pl
445
src/lib/clpb.pl
@@ -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-2023 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.
|
||||
@@ -932,7 +1251,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 +1427,19 @@ 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.
|
||||
% ```
|
||||
|
||||
|
||||
|
||||
sat_count(Sat0, N) :-
|
||||
catch((parse_sat(Sat0, Sat),
|
||||
@@ -1246,7 +1565,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.
|
||||
%
|
||||
@@ -1255,10 +1574,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),
|
||||
@@ -1371,14 +1690,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),
|
||||
@@ -1398,39 +1717,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.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
@@ -1555,7 +1859,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)]
|
||||
).
|
||||
|
||||
@@ -1657,49 +1962,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).
|
||||
|
||||
1941
src/lib/clpz.pl
1941
src/lib/clpz.pl
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 = [] }).
|
||||
|
||||
|
||||
|
||||
244
src/lib/dcgs.pl
244
src/lib/dcgs.pl
@@ -1,95 +1,165 @@
|
||||
/** 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/4,
|
||||
phrase/5,
|
||||
seq//1,
|
||||
seqq//1,
|
||||
... //0
|
||||
... //0,
|
||||
(-->)/2
|
||||
]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(lists), [append/3, member/2]).
|
||||
:- use_module(library(loader), [strip_module/3]).
|
||||
|
||||
load_context(GRBody, Module, GRBody0) :-
|
||||
strip_module(GRBody, Module, GRBody0),
|
||||
( nonvar(Module) ->
|
||||
true
|
||||
; prolog_load_context(module, Module) ->
|
||||
true
|
||||
; true
|
||||
).
|
||||
|
||||
|
||||
:- meta_predicate phrase(2, ?).
|
||||
|
||||
:- meta_predicate phrase(2, ?, ?).
|
||||
|
||||
:- meta_predicate phrase(2, ?, ?, ?).
|
||||
|
||||
:- meta_predicate phrase(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) :-
|
||||
load_context(GRBody, Module, GRBody0),
|
||||
( var(GRBody0) ->
|
||||
strip_module(GRBody, M, GRBody1),
|
||||
( var(GRBody) ->
|
||||
instantiation_error(phrase/3)
|
||||
; dcg_body(GRBody0, S0, S, GRBody1, Module) ->
|
||||
call(GRBody1)
|
||||
; type_error(callable, GRBody0, phrase/3)
|
||||
; nonvar(GRBody1),
|
||||
dcg_constr(GRBody1),
|
||||
dcg_body(GRBody1, S0, S, GRBody2) ->
|
||||
call(M:GRBody2)
|
||||
; 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 )) :-
|
||||
dcg_non_terminal(NonTerminal, S0, S, Head),
|
||||
dcg_body(GRBody, S0, S1, Goal1, _),
|
||||
dcg_body(GRBody, S0, S1, Goal1),
|
||||
dcg_terminals(Terminals, S, S1, Goal2),
|
||||
Body = ( Goal1, Goal2 ).
|
||||
dcg_rule(( M:NonTerminal --> GRBody ), ( M:Head :- Body )) :-
|
||||
NonTerminal \= ( _, _ ),
|
||||
dcg_non_terminal(NonTerminal, S0, S, Head),
|
||||
dcg_body(GRBody, S0, S, Body, _).
|
||||
dcg_body(GRBody, S0, S, Body).
|
||||
|
||||
% This program uses append/3 as defined in the Prolog prologue.
|
||||
% Expands a DCG rule into a Prolog rule, when no error condition applies.
|
||||
dcg_rule(( NonTerminal, Terminals --> GRBody ), ( Head :- Body )) :-
|
||||
dcg_non_terminal(NonTerminal, S0, S, Head),
|
||||
dcg_body(GRBody, S0, S1, Goal1, _),
|
||||
dcg_body(GRBody, S0, S1, Goal1),
|
||||
dcg_terminals(Terminals, S, S1, Goal2),
|
||||
Body = ( Goal1, Goal2 ).
|
||||
dcg_rule(( NonTerminal --> GRBody ), ( Head :- Body )) :-
|
||||
NonTerminal \= ( _, _ ),
|
||||
dcg_non_terminal(NonTerminal, S0, S, Head),
|
||||
dcg_body(GRBody, S0, S, Body, _).
|
||||
dcg_body(GRBody, S0, S, 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).
|
||||
|
||||
dcg_body(Var, S0, S, Body, M) :-
|
||||
dcg_body(Var, S0, S, Body) :-
|
||||
var(Var),
|
||||
module_call_qualified(M, Var, Var1),
|
||||
Body = phrase(Var1, S0, S).
|
||||
dcg_body(GRBody, S0, S, Body, M) :-
|
||||
Body = phrase(Var, S0, S).
|
||||
dcg_body(GRBody, S0, S, Body) :-
|
||||
nonvar(GRBody),
|
||||
dcg_constr(GRBody),
|
||||
dcg_cbody(GRBody, S0, S, Body, M).
|
||||
dcg_body(NonTerminal, S0, S, Goal1, M) :-
|
||||
dcg_cbody(GRBody, S0, S, Body).
|
||||
dcg_body(NonTerminal, S0, S, Goal1) :-
|
||||
nonvar(NonTerminal),
|
||||
\+ dcg_constr(NonTerminal),
|
||||
NonTerminal \= ( _ -> _ ),
|
||||
NonTerminal \= ( \+ _ ),
|
||||
module_call_qualified(M, Goal, Goal1),
|
||||
dcg_non_terminal(NonTerminal, S0, S, Goal).
|
||||
loader:strip_module(NonTerminal, M, NonTerminal0),
|
||||
dcg_non_terminal(NonTerminal0, S0, S, Goal0),
|
||||
( functor(NonTerminal, (:), 2) ->
|
||||
Goal1 = M:Goal0
|
||||
; Goal1 = Goal0
|
||||
).
|
||||
|
||||
% The following constructs in a grammar rule body
|
||||
% are defined in the corresponding subclauses.
|
||||
@@ -101,64 +171,100 @@ 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.
|
||||
dcg_cbody([], S0, S, S0 = S, _M).
|
||||
dcg_cbody([T|Ts], S0, S, Goal, _M) :-
|
||||
dcg_cbody([], S0, S, S0 = S).
|
||||
dcg_cbody([T|Ts], S0, S, Goal) :-
|
||||
must_be(list, [T|Ts]),
|
||||
dcg_terminals([T|Ts], S0, S, Goal).
|
||||
dcg_cbody(( GRFirst, GRSecond ), S0, S, ( First, Second ), M) :-
|
||||
dcg_body(GRFirst, S0, S1, First, M),
|
||||
dcg_body(GRSecond, S1, S, Second, M).
|
||||
dcg_cbody(( GREither ; GROr ), S0, S, ( Either ; Or ), M) :-
|
||||
dcg_cbody(( GRFirst, GRSecond ), S0, S, ( First, Second )) :-
|
||||
dcg_body(GRFirst, S0, S1, First),
|
||||
dcg_body(GRSecond, S1, S, Second).
|
||||
dcg_cbody(( GREither ; GROr ), S0, S, ( Either ; Or )) :-
|
||||
\+ subsumes_term(( _ -> _ ), GREither),
|
||||
dcg_body(GREither, S0, S, Either, M),
|
||||
dcg_body(GROr, S0, S, Or, M).
|
||||
dcg_cbody(( GRCond ; GRElse ), S0, S, ( Cond ; Else ), M) :-
|
||||
dcg_body(GREither, S0, S, Either),
|
||||
dcg_body(GROr, S0, S, Or).
|
||||
dcg_cbody(( GRCond ; GRElse ), S0, S, ( Cond ; Else )) :-
|
||||
subsumes_term(( _GRIf -> _GRThen ), GRCond),
|
||||
dcg_cbody(GRCond, S0, S, Cond, M),
|
||||
dcg_body(GRElse, S0, S, Else, M).
|
||||
dcg_cbody(( GREither '|' GROr ), S0, S, ( Either ; Or ), M) :-
|
||||
dcg_body(GREither, S0, S, Either, M),
|
||||
dcg_body(GROr, S0, S, Or, M).
|
||||
dcg_cbody({Goal}, S0, S, ( Goal1, S0 = S ), M) :-
|
||||
module_call_qualified(M, Goal, Goal1).
|
||||
dcg_cbody(call(Cont), S0, S, call(Cont1, S0, S), M) :-
|
||||
module_call_qualified(M, Cont, Cont1).
|
||||
dcg_cbody(phrase(Body), S0, S, phrase(Body1, S0, S), M) :-
|
||||
module_call_qualified(M, Body, Body1).
|
||||
dcg_cbody(!, S0, S, ( !, S0 = S ), _M).
|
||||
dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody1,S0,_), S0 = S ), M) :-
|
||||
module_call_qualified(M, GRBody, GRBody1).
|
||||
dcg_cbody(( GRIf -> GRThen ), S0, S, ( If -> Then ), M) :-
|
||||
dcg_body(GRIf, S0, S1, If, M),
|
||||
dcg_body(GRThen, S1, S, Then, M).
|
||||
dcg_cbody(GRCond, S0, S, Cond),
|
||||
dcg_body(GRElse, S0, S, Else).
|
||||
dcg_cbody(( GREither '|' GROr ), S0, S, ( Either ; Or )) :-
|
||||
dcg_body(GREither, S0, S, Either),
|
||||
dcg_body(GROr, S0, S, 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(( GRIf -> GRThen ), S0, S, ( If -> Then )) :-
|
||||
dcg_body(GRIf, S0, S1, If),
|
||||
dcg_body(GRThen, S1, S, Then).
|
||||
|
||||
user:term_expansion(Term0, Term) :-
|
||||
nonvar(Term0),
|
||||
dcg_rule(Term0, Term).
|
||||
|
||||
|
||||
%% seq(Seq)//
|
||||
%
|
||||
% Describes a sequence
|
||||
seq(Xs, Cs0,Cs) :-
|
||||
var(Xs),
|
||||
Cs0 == [],
|
||||
!,
|
||||
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 == [],
|
||||
!,
|
||||
Cs0 = Cs.
|
||||
... --> [] | [_], ... .
|
||||
|
||||
user:goal_expansion(phrase(GRBody, S, S0), GRBody1) :-
|
||||
load_context(GRBody, M, GRBody0),
|
||||
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) :-
|
||||
loader:strip_module(GRBody, M, GRBody0),
|
||||
nonvar(GRBody0),
|
||||
catch(dcgs:dcg_body(GRBody0, S, S0, GRBody1, M),
|
||||
error(E, must_be/2),
|
||||
( GRBody1 = throw(error(E, must_be/2)) )
|
||||
).
|
||||
catch(dcgs:dcg_body(GRBody0, S, S0, GRBody1),
|
||||
E,
|
||||
dcgs:error_goal(E, GRBody1)
|
||||
),
|
||||
( 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)).
|
||||
|
||||
@@ -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, $),
|
||||
@@ -15,12 +33,24 @@
|
||||
:- 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.
|
||||
|
||||
*(_).
|
||||
|
||||
165
src/lib/diag.pl
165
src/lib/diag.pl
@@ -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),
|
||||
|
||||
@@ -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(_)) }.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written September 2018 by Markus Triska (triska@metalevel.at)
|
||||
Written 2018-2023 by Markus Triska (triska@metalevel.at)
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
type_error/3
|
||||
]).
|
||||
|
||||
|
||||
:- meta_predicate check_(1, ?, ?).
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
must_be(Type, Term)
|
||||
|
||||
@@ -28,8 +32,12 @@
|
||||
- boolean
|
||||
- character
|
||||
- chars
|
||||
- in_character
|
||||
- integer
|
||||
- list
|
||||
- octet_character
|
||||
- octet_chars
|
||||
- term
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
must_be(Type, Term) :-
|
||||
@@ -44,9 +52,16 @@ must_be_(var, Term) :-
|
||||
; throw(error(uninstantiation_error(Term), must_be/2))
|
||||
).
|
||||
must_be_(integer, Term) :- check_(integer, integer, Term).
|
||||
must_be_(not_less_than_zero, N) :-
|
||||
must_be(integer, N),
|
||||
( N >= 0 -> true
|
||||
; domain_error(not_less_than_zero, N, must_be/2)
|
||||
).
|
||||
must_be_(atom, Term) :- check_(atom, atom, Term).
|
||||
must_be_(character, T) :- check_(error:character, character, T).
|
||||
must_be_(in_character, T) :- check_(error:in_character, in_character, T).
|
||||
must_be_(chars, Ls) :-
|
||||
can_be(chars, Ls), % prioritize type errors over instantiation errors
|
||||
must_be(list, Ls),
|
||||
( '$is_partial_string'(Ls) ->
|
||||
% The expected case (success) uses a very fast test.
|
||||
@@ -55,9 +70,27 @@ must_be_(chars, Ls) :-
|
||||
true
|
||||
; all_characters(Ls)
|
||||
).
|
||||
must_be_(octet_character, C) :-
|
||||
must_be(character, C),
|
||||
( octet_character(C) -> true
|
||||
; domain_error(octet_character, C, must_be/2)
|
||||
).
|
||||
must_be_(octet_chars, Cs) :-
|
||||
must_be(chars, Cs),
|
||||
( '$first_non_octet'(Cs, C) ->
|
||||
domain_error(octet_character, C, must_be/2)
|
||||
; true
|
||||
).
|
||||
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_(term, Term) :-
|
||||
( 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)
|
||||
% uses library(error), so importing it would create a cyclic dependency.
|
||||
@@ -79,6 +112,15 @@ character(C) :-
|
||||
atom(C),
|
||||
atom_length(C, 1).
|
||||
|
||||
octet_character(C) :-
|
||||
char_code(C, Code),
|
||||
0 =< Code, Code =< 0xff.
|
||||
|
||||
in_character(C) :-
|
||||
( character(C)
|
||||
; C == end_of_file
|
||||
).
|
||||
|
||||
ilist(Ls) :-
|
||||
'$skip_max_list'(_, _, Ls, Rs),
|
||||
( var(Rs) ->
|
||||
@@ -90,10 +132,15 @@ type(type).
|
||||
type(integer).
|
||||
type(atom).
|
||||
type(character).
|
||||
type(in_character).
|
||||
type(octet_character).
|
||||
type(octet_chars).
|
||||
type(chars).
|
||||
type(list).
|
||||
type(var).
|
||||
type(boolean).
|
||||
type(term).
|
||||
type(not_less_than_zero).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
can_be(Type, Term)
|
||||
@@ -117,11 +164,45 @@ can_be(Type, Term) :-
|
||||
).
|
||||
|
||||
can_(integer, Term) :- integer(Term).
|
||||
can_(not_less_than_zero, N) :-
|
||||
( integer(N) ->
|
||||
( N >= 0 -> true
|
||||
; domain_error(not_less_than_zero, N, can_be/2)
|
||||
)
|
||||
; type_error(integer, N, can_be/2)
|
||||
).
|
||||
can_(atom, Term) :- atom(Term).
|
||||
can_(character, T) :- character(T).
|
||||
can_(chars, Ls) :- '$is_partial_string'(Ls).
|
||||
can_(in_character, T) :- in_character(T).
|
||||
can_(chars, Ls) :-
|
||||
( '$is_partial_string'(Ls) -> true
|
||||
; can_be(list, Ls),
|
||||
can_be_chars(Ls)
|
||||
).
|
||||
can_(octet_character, C) :-
|
||||
( octet_character(C) -> true
|
||||
; domain_error(octet_character, C, can_be/2)
|
||||
).
|
||||
can_(octet_chars, Cs) :-
|
||||
can_be(chars, Cs),
|
||||
( '$skip_max_list'(_, _, Cs, []), % temporarily turn Cs into a list
|
||||
'$first_non_octet'(Cs, C) ->
|
||||
domain_error(octet_character, C, can_be/2)
|
||||
; true
|
||||
).
|
||||
can_(list, Term) :- list_or_partial_list(Term).
|
||||
can_(boolean, Term) :- boolean(Term).
|
||||
can_(term, Term) :-
|
||||
( acyclic_term(Term) ->
|
||||
true
|
||||
; type_error(term, Term, can_be/2)
|
||||
).
|
||||
|
||||
can_be_chars(Var) :- var(Var), !.
|
||||
can_be_chars([]).
|
||||
can_be_chars([X|Xs]) :-
|
||||
can_be(character, X),
|
||||
can_be_chars(Xs).
|
||||
|
||||
list_or_partial_list(Ls) :-
|
||||
'$skip_max_list'(_, _, Ls, Rs),
|
||||
|
||||
104
src/lib/ffi.pl
Normal file
104
src/lib/ffi.pl
Normal file
@@ -0,0 +1,104 @@
|
||||
:- module(ffi, [use_foreign_module/2, foreign_struct/2]).
|
||||
|
||||
/** 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`, `uint8`, `sint16`, `uint16`, `sint32`, `uint32`, `sint64`,
|
||||
`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
||||
with `foreign_struct/2`.
|
||||
|
||||
After that, each function on the lists maps to a predicate created in the ffi module which
|
||||
are used to call the native code.
|
||||
The predicate takes the functor name after the function name. Then, the arguments are the input
|
||||
arguments followed by a return argument. However, functions with return type `void` or `bool`
|
||||
don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend
|
||||
on the return value on the native side.
|
||||
|
||||
```
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
|
||||
```
|
||||
|
||||
## 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)).
|
||||
|
||||
%% 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) :-
|
||||
'$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).
|
||||
211
src/lib/files.pl
211
src/lib/files.pl
@@ -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 June 2020 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020, 2022 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,
|
||||
@@ -65,52 +85,87 @@
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
list_of_chars(Cs) :-
|
||||
must_be(list, Cs),
|
||||
maplist(must_be(character), Cs).
|
||||
%% directory_files(+Directory, -Files).
|
||||
%
|
||||
% Returns the list of files *and* directories available at a specific
|
||||
% directory in the current system.
|
||||
|
||||
directory_files(Directory, Files) :-
|
||||
list_of_chars(Directory),
|
||||
must_be(chars, Directory),
|
||||
can_be(list, Files),
|
||||
'$directory_files'(Directory, Files).
|
||||
|
||||
%% file_size(+File, -Size).
|
||||
%
|
||||
% Returns the size (in bytes) of a file. The file must exist.
|
||||
|
||||
file_size(File, Size) :-
|
||||
file_must_exist(File, file_size/2),
|
||||
list_of_chars(File),
|
||||
can_be(integer, Size),
|
||||
'$file_size'(File, Size).
|
||||
|
||||
%% file_exists(+File).
|
||||
%
|
||||
% Succeeds if File is a file that exists in the current system.
|
||||
file_exists(File) :-
|
||||
list_of_chars(File),
|
||||
must_be(chars, File),
|
||||
'$file_exists'(File).
|
||||
|
||||
%% directory_exists(+Directory).
|
||||
%
|
||||
% Succeeds if Directory is a directory that exists in the current system.
|
||||
directory_exists(Directory) :-
|
||||
list_of_chars(Directory),
|
||||
must_be(chars, Directory),
|
||||
'$directory_exists'(Directory).
|
||||
|
||||
%% make_directory(+Directory).
|
||||
%
|
||||
% Succeeds if it creates a new directory named Directory in the current system.
|
||||
% If you want to create a nested directory, use `make_directory_path/1`.
|
||||
make_directory(Directory) :-
|
||||
list_of_chars(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) :-
|
||||
list_of_chars(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),
|
||||
list_of_chars(File),
|
||||
'$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),
|
||||
list_of_chars(File),
|
||||
list_of_chars(Renamed),
|
||||
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),
|
||||
list_of_chars(Directory),
|
||||
must_be(chars, Directory),
|
||||
'$delete_directory'(Directory).
|
||||
|
||||
file_must_exist(File, Context) :-
|
||||
@@ -123,35 +178,34 @@ 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(list, Ps),
|
||||
maplist(must_be(character), Ps),
|
||||
must_be(chars, Ps),
|
||||
can_be(list, Cs),
|
||||
'$path_canonical'(Ps, Cs).
|
||||
|
||||
@@ -162,12 +216,27 @@ 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, it returns a time stamp T with 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, it returns a time stamp T with 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, it returns a time stamp T with the creation time
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_creation_time(File, T) :-
|
||||
file_time_(File, creation, T).
|
||||
|
||||
@@ -177,48 +246,50 @@ 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),
|
||||
( var(Path) ->
|
||||
must_be(list, Segments),
|
||||
maplist(list_of_chars, Segments),
|
||||
append_with_separator(Segments, Sep, Path)
|
||||
; list_of_chars(Path),
|
||||
maplist(must_be(chars), Segments),
|
||||
phrase(append_with_separator(Segments, Sep), Path)
|
||||
; must_be(chars, Path),
|
||||
path_to_segments(Path, Sep, Segments)
|
||||
).
|
||||
|
||||
append_with_separator([], _, []).
|
||||
append_with_separator([Segment|Segments], Sep, Path) :-
|
||||
append_with_separator_(Segments, Segment, Sep, Path).
|
||||
append_with_separator([], _) --> [].
|
||||
append_with_separator([Segment|Segments], Sep) -->
|
||||
append_with_separator_(Segments, Segment, Sep).
|
||||
|
||||
append_with_separator_([], Segment, _, Segment).
|
||||
append_with_separator_([Segment|Segments], Prev, Sep, Path) :-
|
||||
append(Prev, [Sep|Rest], Path),
|
||||
append_with_separator_(Segments, Segment, Sep, Rest).
|
||||
append_with_separator_([], Segment, _) --> seq(Segment).
|
||||
append_with_separator_([Segment|Segments], Prev, Sep) -->
|
||||
seq(Prev), [Sep],
|
||||
append_with_separator_(Segments, Segment, Sep).
|
||||
|
||||
path_to_segments(Path, Sep, Segments) :-
|
||||
( append(Front, [Sep|Ps], Path) ->
|
||||
|
||||
@@ -1,83 +1,17 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2021, 2022 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2024 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,6 +28,61 @@
|
||||
:- 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:
|
||||
%
|
||||
% | `~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.
|
||||
%
|
||||
% 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),
|
||||
@@ -313,14 +302,14 @@ format_number_chars(N0, Chars) :-
|
||||
N is N0, % evaluate compound expression
|
||||
number_chars(N, Chars).
|
||||
|
||||
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,10 +403,32 @@ digits(uppercase, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").
|
||||
Impure I/O, implemented as a small wrapper over format_//2.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% 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(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(Stream, Fs, Args) :-
|
||||
phrase_to_stream(format_(Fs, Args), Stream),
|
||||
flush_output(Stream).
|
||||
@@ -433,9 +444,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 +497,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).
|
||||
@@ -512,7 +526,7 @@ var_name(V, Name=V, Num0, Num) :-
|
||||
Num is Num0 + 1.
|
||||
|
||||
literal(Lit, VNs) -->
|
||||
{ write_term_to_chars(Lit, [quoted(true),variable_names(VNs)], Ls) },
|
||||
{ write_term_to_chars(Lit, [quoted(true),variable_names(VNs),double_quotes(true)], Ls) },
|
||||
seq(Ls).
|
||||
|
||||
portray_(Var, VNs) --> { var(Var) }, !, literal(Var, VNs).
|
||||
|
||||
@@ -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)].
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -1,84 +1,73 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2021 by Markus Triska (triska@metalevel.at)
|
||||
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. Redirects are followed.
|
||||
|
||||
Currently, Options must be the empty list. Options may be
|
||||
added in the future to give more control over the connection.
|
||||
|
||||
We use HTTP/1.0 until we can read chunked transfer-encoding.
|
||||
|
||||
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(sockets)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(lists), [member/2]).
|
||||
:- use_module(library(tls)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
http_open(Address, Stream, Options) :-
|
||||
must_be(list, Options),
|
||||
must_be(list, Address),
|
||||
once(phrase((seq(SchemeCs), "://", seq(Rest)), Address)),
|
||||
atom_chars(Scheme, SchemeCs),
|
||||
chars_host_url(Rest, Host, URL),
|
||||
connect(Scheme, Host, Stream0),
|
||||
format(Stream0, "\
|
||||
GET ~s HTTP/1.0\r\n\
|
||||
Host: ~w\r\n\
|
||||
User-Agent: Scryer Prolog\r\n\
|
||||
Connection: close\r\n\r\n\
|
||||
", [URL,Host]),
|
||||
read_line_to_chars(Stream0, StatusLine, []),
|
||||
once(phrase(("HTTP/1.",(['0']|['1'])," ",[D1]), StatusLine, _)),
|
||||
read_header_lines(Stream0, HeaderLines),
|
||||
handle_response(D1, HeaderLines, Stream0, Stream).
|
||||
%% 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),
|
||||
( member(data(Data), OptionValues) -> true; Data = []),
|
||||
( member(request_headers(RequestHeaders), OptionValues) -> true; RequestHeaders = ['user-agent'("Scryer Prolog")]),
|
||||
( member(status_code(Code), OptionValues) -> true; true),
|
||||
( member(headers(Headers), OptionValues) -> true; true),
|
||||
( member(size(Size), OptionValues) -> member('content-length'(Size), Headers); true),
|
||||
'$http_open'(Address, Response, Method, Code, Data, Headers, RequestHeaders).
|
||||
|
||||
handle_response('2', _, Stream, Stream). % ok
|
||||
handle_response('3', HeaderLines, Stream0, Stream) :- % redirect
|
||||
close(Stream0),
|
||||
once((member(Line, HeaderLines),
|
||||
phrase(("Location: ",seq(Location),"\r\n"), Line))),
|
||||
http_open(Location, Stream, []).
|
||||
parse_http_options(Options, OptionValues) :-
|
||||
maplist(parse_http_options_, Options, OptionValues).
|
||||
|
||||
% Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
||||
parse_http_options_(method(Method), method(Method)) :-
|
||||
( var(Method) ->
|
||||
throw(error(instantiation_error, http_open/3))
|
||||
;
|
||||
member(Method, [get, post, put, delete, patch, head]) -> true
|
||||
;
|
||||
throw(error(domain_error(http_option, method(Method)), _))
|
||||
).
|
||||
|
||||
read_header_lines(Stream, Hs) :-
|
||||
read_line_to_chars(Stream, Cs, []),
|
||||
( Cs == "" -> Hs = []
|
||||
; Cs == "\r\n" -> Hs = []
|
||||
; Hs = [Cs|Rest],
|
||||
read_header_lines(Stream, Rest)
|
||||
).
|
||||
parse_http_options_(data(Data), data(Data)) :-
|
||||
( var(Data) ->
|
||||
throw(error(instantiation_error, http_open/3))
|
||||
; true
|
||||
).
|
||||
|
||||
chars_host_url(Cs, Host, [/|Us]) :-
|
||||
( phrase((seq(Hs),"/",seq(Us)), Cs) ->
|
||||
true
|
||||
; Hs = Cs,
|
||||
Us = []
|
||||
),
|
||||
atom_chars(Host, Hs).
|
||||
|
||||
connect(https, Host, Stream) :-
|
||||
socket_client_open(Host:443, Stream0, []),
|
||||
atom_chars(Host, HostChars),
|
||||
tls_client_context(Context, [hostname(HostChars)]),
|
||||
tls_client_negotiate(Context, Stream0, Stream).
|
||||
connect(http, Host, Stream) :-
|
||||
socket_client_open(Host:80, Stream, []).
|
||||
parse_http_options_(request_headers(Headers), request_headers(Headers)) :-
|
||||
( var(Headers) ->
|
||||
throw(error(instantiation_error, http_open/3))
|
||||
; true
|
||||
).
|
||||
|
||||
parse_http_options_(size(Size), size(Size)).
|
||||
parse_http_options_(status_code(Code), status_code(Code)).
|
||||
parse_http_options_(headers(Headers), headers(Headers)).
|
||||
|
||||
@@ -1,127 +1,214 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written in December 2020 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
|
||||
Part of Scryer Prolog
|
||||
|
||||
This library provides an starting point to build HTTP server based applications.
|
||||
It currently implements a subset of HTTP/1.0. It is recommended to put a reverse
|
||||
proxy like nginx in front of this server to have access to more advanced features
|
||||
(gzip compression, HTTPS, ...)
|
||||
|
||||
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
|
||||
- Keep-Alive support
|
||||
- Session handling via cookies
|
||||
- HTML Templating
|
||||
|
||||
Updated in March 2022 by Adrián Arroyo to use the Hyper backend
|
||||
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.
|
||||
|
||||
```
|
||||
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
|
||||
- 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_headers/2,
|
||||
http_status_code/2,
|
||||
http_body/2,
|
||||
http_redirect/2,
|
||||
http_query/3,
|
||||
url_decode//1
|
||||
http_listen/2,
|
||||
http_listen/3,
|
||||
http_headers/2,
|
||||
http_status_code/2,
|
||||
http_body/2,
|
||||
http_redirect/2,
|
||||
http_query/3,
|
||||
http_basic_auth/4
|
||||
]).
|
||||
|
||||
:- meta_predicate http_listen(?, 2).
|
||||
:- meta_predicate http_listen(?, :).
|
||||
:- meta_predicate http_listen(?, :, ?).
|
||||
|
||||
:- meta_predicate http_basic_auth(:, :, ?, ?).
|
||||
|
||||
:- use_module(library(sockets)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(time)).
|
||||
:- use_module(library(crypto)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(pio)).
|
||||
:- use_module(library(time)).
|
||||
|
||||
% Module prefix workaround with meta_predicate
|
||||
%% 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].
|
||||
|
||||
% Server initialization
|
||||
http_listen_(Port, Handlers) :-
|
||||
must_be(integer, Port),
|
||||
must_be(list, Handlers),
|
||||
once(socket_server_open(Port, Socket)),
|
||||
format("Listening at port ~d\n", [Port]),
|
||||
accept_loop(Socket, 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, TLSKey, TLSCert, ContentLengthLimit),!,
|
||||
format("Listening at ~s\n", [Addr]),
|
||||
http_loop(HttpListener, Handlers).
|
||||
|
||||
% Server loop
|
||||
accept_loop(Socket, Handlers) :-
|
||||
setup_call_cleanup(socket_server_accept(Socket, _Client, Stream, [type(binary)]),
|
||||
(
|
||||
read_header_lines(Stream, Lines),
|
||||
[Request|Headers] = Lines,
|
||||
(
|
||||
(phrase(parse_request(_Version, Method, Path, Queries), Request), maplist(map_parse_header, Headers, HeadersKV)) -> (
|
||||
(
|
||||
member("content-length"-ContentLength, HeadersKV) ->
|
||||
(number_chars(ContentLengthN, ContentLength), get_bytes(Stream, ContentLengthN, Body))
|
||||
;true
|
||||
),
|
||||
current_time(Time),
|
||||
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
|
||||
format("~s ~w ~s\n", [TimeString, Method, Path]),
|
||||
(
|
||||
match_handler(Handlers, Method, Path, Handler) ->
|
||||
(
|
||||
HttpRequest = http_request(HeadersKV, binary(Body), Queries),
|
||||
HttpResponse = http_response(_, _, _),
|
||||
(call(Handler, HttpRequest, HttpResponse) ->
|
||||
send_response(Stream, HttpResponse)
|
||||
; format(Stream, "HTTP/1.0 500 Internal Server Error\r\n\r\n", [])
|
||||
)
|
||||
)
|
||||
; format(Stream, "HTTP/1.0 404 Not Found\r\n\r\n", [])
|
||||
)
|
||||
);(
|
||||
format(Stream, "HTTP/1.0 400 Bad Request\r\n\r\n", []) % bad format
|
||||
)
|
||||
),
|
||||
! % Remove
|
||||
), close(Stream)),
|
||||
accept_loop(Socket, 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),
|
||||
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
|
||||
format("~s ~w ~s\n", [TimeString, RequestMethod, RequestPath]),
|
||||
maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
|
||||
phrase(parse_queries(RequestQueries), RequestQuery),
|
||||
(
|
||||
match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
|
||||
(
|
||||
HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
|
||||
HttpResponse = http_response(_, _, _),
|
||||
(call(Handler, HttpRequest, HttpResponse) ->
|
||||
send_response(ResponseHandle, HttpResponse)
|
||||
; (
|
||||
'$http_answer'(ResponseHandle, 500, [], ResponseStream),
|
||||
call_cleanup(format(ResponseStream, "Internal Server Error", []), close(ResponseStream)))
|
||||
)
|
||||
)
|
||||
; (
|
||||
'$http_answer'(ResponseHandle, 404, [], ResponseStream),
|
||||
call_cleanup(format(ResponseStream, "Not Found", []), close(ResponseStream)))
|
||||
),
|
||||
http_loop(HttpListener, Handlers).
|
||||
|
||||
send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
|
||||
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),
|
||||
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),
|
||||
catch(
|
||||
call_cleanup(
|
||||
setup_call_cleanup(
|
||||
open(Filename, read, FileStream, [type(binary)]),
|
||||
(
|
||||
get_n_chars(FileStream, _, FileCs),
|
||||
format(ResponseStream, "~s", [FileCs])
|
||||
),
|
||||
close(FileStream)
|
||||
),
|
||||
close(ResponseStream)
|
||||
),
|
||||
error(existence_error(stream, _), _),
|
||||
true
|
||||
).
|
||||
|
||||
|
||||
default(Var, Default, Out) :-
|
||||
(var(Var) -> Out = Default
|
||||
; Var = Out
|
||||
).
|
||||
|
||||
map_header_kv(T, K-V) :-
|
||||
T =.. [K0, V],
|
||||
atom_chars(K0, K).
|
||||
|
||||
map_header_kv_2(T, K-V) :-
|
||||
atom_chars(K0, K),
|
||||
T =.. [K0, V].
|
||||
|
||||
match_handler(Handlers, Method, "/", Handler) :-
|
||||
member(H, Handlers),
|
||||
@@ -139,27 +226,6 @@ match_handler(Handlers, Method, Path, Handler) :-
|
||||
var(Var),
|
||||
Var = Path.
|
||||
|
||||
|
||||
% Helper and recommended predicates
|
||||
|
||||
http_headers(http_request(Headers, _, _), Headers).
|
||||
http_headers(http_response(_, _, Headers), Headers).
|
||||
|
||||
http_body(http_request(_, binary(ByteBody), _), text(TextBody)) :- chars_utf8bytes(TextBody, ByteBody).
|
||||
http_body(http_request(Headers, binary(ByteBody), _), form(FormBody)) :-
|
||||
member("content-type"-"application/x-www-form-urlencoded", Headers),
|
||||
chars_utf8bytes(TextBody, ByteBody),
|
||||
phrase(parse_queries(FormBody), TextBody).
|
||||
http_body(http_request(_, Body, _), Body).
|
||||
http_body(http_response(_, Body, _), Body).
|
||||
|
||||
http_status_code(http_response(StatusCode, _, _), StatusCode).
|
||||
|
||||
http_redirect(http_response(307, text("Moved Temporarily"), ["Location"-Uri]), Uri).
|
||||
|
||||
http_query(http_request(_, _, Queries), Key, Value) :- member(Key-Value, Queries).
|
||||
|
||||
% Route matching
|
||||
path(Pattern) -->
|
||||
{
|
||||
Pattern =.. Parts,
|
||||
@@ -183,120 +249,6 @@ path(Pattern) -->
|
||||
|
||||
path([]) --> [].
|
||||
|
||||
% Send responses
|
||||
send_response(Stream, http_response(StatusCode0, file(Filename), Headers)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
format(Stream, "HTTP/1.0 ~d\r\n", [StatusCode]),
|
||||
overwrite_header("connection"-"Close", Headers, Headers0),
|
||||
write_headers(Stream, Headers0),
|
||||
format(Stream, "\r\n", []),
|
||||
setup_call_cleanup(
|
||||
open(Filename, read, FileStream, [type(binary)]),
|
||||
pipe_bytes(FileStream, Stream),
|
||||
close(FileStream)
|
||||
).
|
||||
|
||||
send_response(Stream, http_response(StatusCode0, text(TextResponse), Headers)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
format(Stream, "HTTP/1.0 ~d\r\n", [StatusCode]),
|
||||
overwrite_header("content-type"-"text/plain", Headers, Headers0),
|
||||
overwrite_header("connection"-"Close", Headers0, Headers1),
|
||||
write_headers(Stream, Headers1),
|
||||
format(Stream, "\r\n~s", [TextResponse]).
|
||||
|
||||
send_response(Stream, http_response(StatusCode0, binary(BinaryResponse), Headers)) :-
|
||||
default(StatusCode0, 200, StatusCode),
|
||||
format(Stream, "HTTP/1.0 ~d\r\n", [StatusCode]),
|
||||
overwrite_header("connection"-"Close", Headers, Headers0),
|
||||
write_headers(Stream, Headers0),
|
||||
format(Stream, "\r\n", []),
|
||||
put_bytes(Stream, BinaryResponse).
|
||||
|
||||
default(Var, Default, Out) :-
|
||||
(var(Var) -> Out = Default
|
||||
; Var = Out
|
||||
).
|
||||
|
||||
header([]) --> [].
|
||||
header([Key-Value|Headers]) -->
|
||||
format_("~s: ~s\r\n", [Key, Value]),
|
||||
header(Headers).
|
||||
|
||||
write_headers(Stream, Headers) :-
|
||||
phrase(header(Headers), Cs),
|
||||
format(Stream, "~s", [Cs]).
|
||||
|
||||
overwrite_header(Key-Value, [], [Key-Value]).
|
||||
overwrite_header(Key-Value, [Header|Headers], [Header|HeadersOut]) :-
|
||||
Header = Key0-_,
|
||||
Key0 \= Key,
|
||||
overwrite_header(Key-Value, Headers, HeadersOut).
|
||||
overwrite_header(Key-Value, [Header|Headers], [NewHeader|Headers]) :-
|
||||
Header = Key-_,
|
||||
NewHeader = Key-Value.
|
||||
|
||||
parse_request(http_version(Major, Minor), Method, Path, Queries) -->
|
||||
method(Method),
|
||||
" ",
|
||||
parse_path(Path, Queries),
|
||||
" ",
|
||||
"HTTP/",
|
||||
natural(Major),
|
||||
".",
|
||||
natural(Minor),
|
||||
"\r\n".
|
||||
|
||||
parse_path(Path, Queries) -->
|
||||
string_without("?", Path),
|
||||
"?",
|
||||
parse_queries(Queries).
|
||||
|
||||
parse_path(Path, []) -->
|
||||
string_without(" ", Path).
|
||||
|
||||
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([Key-Value]) -->
|
||||
string_without("=", Key0),
|
||||
{
|
||||
phrase(url_decode(Key), Key0)
|
||||
},
|
||||
"=",
|
||||
string_without(" ", Value0),
|
||||
{
|
||||
phrase(url_decode(Value), Value0)
|
||||
}.
|
||||
|
||||
map_parse_header(Header, HeaderKV) :-
|
||||
phrase(parse_header(HeaderKV), Header).
|
||||
|
||||
parse_header(Key-Value) -->
|
||||
string_without(":", Key0),
|
||||
{
|
||||
chars_lower(Key0, Key)
|
||||
},
|
||||
": ",
|
||||
string_without("\r", Value),
|
||||
"\r\n".
|
||||
|
||||
method(options) --> "OPTIONS".
|
||||
method(get) --> "GET".
|
||||
method(head) --> "HEAD".
|
||||
method(post) --> "POST".
|
||||
method(put) --> "PUT".
|
||||
method(delete) --> "DELETE".
|
||||
|
||||
string_without(Not, [Char|String]) -->
|
||||
[Char],
|
||||
{
|
||||
@@ -307,72 +259,79 @@ string_without(Not, [Char|String]) -->
|
||||
string_without(_, []) -->
|
||||
[].
|
||||
|
||||
natural(Nat) -->
|
||||
natural_(NatChars),
|
||||
%% 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)) :-
|
||||
member("content-type"-"application/x-www-form-urlencoded", Headers),
|
||||
get_n_chars(StreamBody, _, TextBody),
|
||||
phrase(parse_queries(FormBody), TextBody).
|
||||
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),
|
||||
"=",
|
||||
string_without("&", Value0),
|
||||
"&",
|
||||
parse_queries(Queries),
|
||||
{
|
||||
number_chars(Nat, NatChars)
|
||||
phrase(url_decode(Key), Key0),
|
||||
phrase(url_decode(Value), Value0)
|
||||
}.
|
||||
|
||||
natural_([Nat|Nats]) -->
|
||||
[Nat],
|
||||
parse_queries([Key-Value]) -->
|
||||
string_without("=", Key0),
|
||||
"=",
|
||||
string_without(" ", Value0),
|
||||
{
|
||||
char_type(Nat, decimal_digit)
|
||||
},
|
||||
natural_(Nats).
|
||||
phrase(url_decode(Key), Key0),
|
||||
phrase(url_decode(Value), Value0)
|
||||
}.
|
||||
|
||||
natural_([]) -->
|
||||
parse_queries([]) -->
|
||||
[].
|
||||
|
||||
read_header_lines(Stream, Hs) :-
|
||||
read_line_to_chars(Stream, Cs, []),
|
||||
( Cs == "" -> Hs = []
|
||||
; Cs == "\r\n" -> Hs = []
|
||||
; Hs = [Cs|Rest],
|
||||
read_header_lines(Stream, Rest)
|
||||
).
|
||||
|
||||
get_bytes(Stream, Length, Res) :- get_bytes(Stream, Length, [], Res).
|
||||
get_bytes(Stream, Length, Acc, Res) :-
|
||||
(Length > 0 -> (
|
||||
get_byte(Stream, B),
|
||||
B =\= -1,
|
||||
get_bytes(Stream, Length - 1, [B|Acc], Res)
|
||||
); reverse(Acc, Res)).
|
||||
|
||||
put_bytes(_, []).
|
||||
put_bytes(Stream, [Byte|Bytes]) :-
|
||||
put_byte(Stream, Byte),
|
||||
put_bytes(Stream, Bytes).
|
||||
|
||||
pipe_bytes(StreamIn, StreamOut) :-
|
||||
get_byte(StreamIn, Byte),
|
||||
(
|
||||
Byte =\= -1 ->
|
||||
(
|
||||
put_byte(StreamOut, Byte),
|
||||
pipe_bytes(StreamIn, StreamOut)
|
||||
)
|
||||
; true).
|
||||
|
||||
% WARNING: This only works for ASCII chars. This code can be modified to support
|
||||
% Latin1 characters also but a completely different approach is needed for other
|
||||
% languages. Since HTTP internals are ASCII, this is fine for this usecase.
|
||||
chars_lower(Chars, Lower) :-
|
||||
maplist(char_lower, Chars, Lower).
|
||||
char_lower(Char, Lower) :-
|
||||
char_code(Char, Code),
|
||||
((Code >= 65,Code =< 90) ->
|
||||
LowerCode is Code + 32,
|
||||
char_code(Lower, LowerCode)
|
||||
; Char = Lower).
|
||||
|
||||
% Decodes a UTF-8 URL Encoded string: RFC-1738
|
||||
url_decode([Char|Chars]) -->
|
||||
[Char],
|
||||
{
|
||||
Char \= '%'
|
||||
Char \= '%',
|
||||
Char \= (+)
|
||||
},
|
||||
url_decode(Chars).
|
||||
url_decode([' '|Chars]) -->
|
||||
"+",
|
||||
url_decode(Chars).
|
||||
url_decode([Char|Chars]) -->
|
||||
"%",
|
||||
[A],
|
||||
@@ -430,3 +389,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")).
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
%% for builtins that are not part of the ISO standard.
|
||||
%% must be loaded at the REPL with
|
||||
/** Useful general predicates that are not ISO standard yet
|
||||
|
||||
%% ?- use_module(library(iso_ext)).
|
||||
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,
|
||||
% variant/2,
|
||||
copy_term_nat/2]).
|
||||
countall/2,
|
||||
copy_term_nat/2,
|
||||
copy_term/3]).
|
||||
|
||||
:- use_module(library(error), [can_be/2,
|
||||
domain_error/3,
|
||||
@@ -24,27 +28,86 @@
|
||||
|
||||
:- 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)
|
||||
@@ -52,90 +115,114 @@ 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'(S),
|
||||
'$call_with_inference_counting'(call(S)),
|
||||
'$set_cp_by_default'(B),
|
||||
'$get_current_block'(Bb),
|
||||
'$get_current_scc_block'(Bb),
|
||||
( C = _:CC,
|
||||
'$call_with_default_policy'(var(CC)) ->
|
||||
var(CC) ->
|
||||
instantiation_error(setup_call_cleanup/3)
|
||||
; '$call_with_default_policy'(scc_helper(C, G, Bb))
|
||||
; scc_helper(C, G, Bb)
|
||||
).
|
||||
|
||||
:- meta_predicate(scc_helper(?,0,?)).
|
||||
|
||||
:- non_counted_backtracking scc_helper/3.
|
||||
|
||||
scc_helper(C, G, Bb) :-
|
||||
'$get_cp'(Cp),
|
||||
'$install_scc_cleaner'(C, NBb),
|
||||
call(G),
|
||||
( '$check_cp'(Cp) ->
|
||||
'$reset_block'(Bb),
|
||||
'$call_with_default_policy'(run_cleaners_without_handling(Cp))
|
||||
; '$call_with_default_policy'(true)
|
||||
; '$reset_block'(NBb),
|
||||
'$fail'
|
||||
'$install_scc_cleaner'(C),
|
||||
'$call_with_inference_counting'(call(G)),
|
||||
( '$check_cp'(Cp) ->
|
||||
'$reset_scc_block'(Bb),
|
||||
run_cleaners_without_handling(Cp)
|
||||
; true
|
||||
; '$fail'
|
||||
).
|
||||
scc_helper(_, _, Bb) :-
|
||||
'$reset_block'(Bb),
|
||||
'$get_ball'(Ball),
|
||||
'$erase_ball',
|
||||
'$call_with_default_policy'(run_cleaners_with_handling),
|
||||
'$call_with_default_policy'(throw(Ball)).
|
||||
'$reset_scc_block'(Bb),
|
||||
'$push_ball_stack',
|
||||
run_cleaners_with_handling,
|
||||
'$pop_from_ball_stack',
|
||||
'$unwind_stack'.
|
||||
scc_helper(_, _, _) :-
|
||||
'$get_cp'(Cp),
|
||||
'$call_with_default_policy'(run_cleaners_without_handling(Cp)),
|
||||
run_cleaners_without_handling(Cp),
|
||||
'$fail'.
|
||||
|
||||
:- non_counted_backtracking run_cleaners_with_handling/0.
|
||||
|
||||
run_cleaners_with_handling :-
|
||||
'$get_scc_cleaner'(C),
|
||||
'$get_level'(B),
|
||||
'$call_with_default_policy'(catch(C, _, true)),
|
||||
'$get_cp'(B),
|
||||
catch(C, _, true),
|
||||
'$set_cp_by_default'(B),
|
||||
'$call_with_default_policy'(run_cleaners_with_handling).
|
||||
run_cleaners_with_handling.
|
||||
run_cleaners_with_handling :-
|
||||
'$restore_cut_policy'.
|
||||
|
||||
:- non_counted_backtracking run_cleaners_without_handling/1.
|
||||
|
||||
run_cleaners_without_handling(Cp) :-
|
||||
'$get_scc_cleaner'(C),
|
||||
'$get_level'(B),
|
||||
'$get_cp'(B),
|
||||
call(C),
|
||||
'$set_cp_by_default'(B),
|
||||
'$call_with_default_policy'(run_cleaners_without_handling(Cp)).
|
||||
run_cleaners_without_handling(Cp).
|
||||
run_cleaners_without_handling(Cp) :-
|
||||
'$set_cp_by_default'(Cp),
|
||||
'$restore_cut_policy'.
|
||||
|
||||
% 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) :- !.
|
||||
handle_ile(B, E, _) :-
|
||||
'$remove_call_policy_check'(B),
|
||||
'$call_with_default_policy'(throw(E)).
|
||||
|
||||
:- 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.
|
||||
call_with_inference_limit(G, L, R) :-
|
||||
( integer(L) ->
|
||||
( L < 0 ->
|
||||
@@ -148,11 +235,9 @@ call_with_inference_limit(G, L, R) :-
|
||||
),
|
||||
'$get_current_block'(Bb),
|
||||
'$get_b_value'(B),
|
||||
'$call_with_default_policy'(call_with_inference_limit(G, L, R, Bb, B)),
|
||||
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,?,?,?,?)).
|
||||
|
||||
@@ -160,24 +245,39 @@ 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),
|
||||
'$call'(G),
|
||||
'$install_inference_counter'(NBb, L, Count0),
|
||||
'$call_with_inference_counting'(call(G)),
|
||||
'$inference_level'(R, B),
|
||||
'$remove_inference_counter'(B, Count1),
|
||||
'$call_with_default_policy'(is(Diff, L - (Count1 - Count0))),
|
||||
'$call_with_default_policy'(end_block(B, Bb, NBb, Diff)).
|
||||
call_with_inference_limit(_, _, R, Bb, B) :-
|
||||
'$reset_block'(Bb),
|
||||
'$remove_inference_counter'(B, _),
|
||||
( '$get_ball'(Ball),
|
||||
'$get_level'(Cp),
|
||||
'$set_cp_by_default'(Cp)
|
||||
; '$remove_call_policy_check'(B),
|
||||
'$remove_inference_counter'(NBb, Count1),
|
||||
Diff is L - (Count1 - Count0),
|
||||
( '$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
|
||||
),
|
||||
'$erase_ball',
|
||||
'$call_with_default_policy'(handle_ile(B, Ball, R)).
|
||||
'$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, L, L0)
|
||||
%
|
||||
% Explicitly construct a partial string "manually". It can be used as an optimized append/3.
|
||||
% It's not recommended to use this predicate in application code.
|
||||
partial_string(String, L, L0) :-
|
||||
( String == [] ->
|
||||
L = L0
|
||||
@@ -187,9 +287,17 @@ partial_string(String, L, L0) :-
|
||||
'$create_partial_string'(Atom, L, L0)
|
||||
).
|
||||
|
||||
%% 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)
|
||||
@@ -201,35 +309,100 @@ 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), N =< 0,
|
||||
domain_error(positive_integer, N, call_nth/2)
|
||||
( integer(N) ->
|
||||
( N < 0 ->
|
||||
domain_error(not_less_than_zero, N, call_nth/2)
|
||||
; N > 0
|
||||
)
|
||||
; true
|
||||
),
|
||||
setup_call_cleanup(call_nth_nesting(ID),
|
||||
setup_call_cleanup(call_nth_nesting(C, ID),
|
||||
( Goal,
|
||||
retract(i_call_nth_nesting(ID,N0)),
|
||||
bb_get(ID, N0),
|
||||
N1 is N0 + 1,
|
||||
asserta(i_call_nth_nesting(ID,N1)),
|
||||
bb_put(ID, N1),
|
||||
( integer(N) ->
|
||||
N = N1,
|
||||
!
|
||||
; N = N1
|
||||
)
|
||||
),
|
||||
( retract(i_call_nth_nesting(ID,_)),
|
||||
retract(i_call_nth_counter(ID))
|
||||
( bb_get(i_call_nth_counter, C) ->
|
||||
C1 is C - 1,
|
||||
bb_put(i_call_nth_counter, C1)
|
||||
; true
|
||||
)).
|
||||
|
||||
call_nth_nesting(ID) :-
|
||||
( i_call_nth_counter(ID0) ->
|
||||
ID is ID0 + 1
|
||||
; ID = 0
|
||||
call_nth_nesting(C, ID) :-
|
||||
( bb_get(i_call_nth_counter, C0) ->
|
||||
C is C0 + 1
|
||||
; C = 0
|
||||
),
|
||||
asserta(i_call_nth_nesting(ID, 0)),
|
||||
asserta(i_call_nth_counter(ID)).
|
||||
number_chars(C, Cs),
|
||||
atom_chars(Atom, Cs),
|
||||
atom_concat(i_call_nth_nesting_, Atom, 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
|
||||
).
|
||||
|
||||
:- 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).
|
||||
|
||||
@@ -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,10 +108,10 @@ 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
|
||||
|
||||
375
src/lib/lists.pl
375
src/lib/lists.pl
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
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,
|
||||
maplist/7, maplist/8, maplist/9, same_length/2, nth0/3,
|
||||
maplist/7, maplist/8, maplist/9, same_length/2, nth0/3, nth0/4, nth1/3, nth1/4,
|
||||
sum_list/2, transpose/2, list_to_set/2, list_max/2,
|
||||
list_min/2, permutation/2]).
|
||||
|
||||
@@ -50,13 +54,34 @@
|
||||
:- meta_predicate foldl(3, ?, ?, ?).
|
||||
:- meta_predicate foldl(4, ?, ?, ?, ?).
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
:- meta_predicate(resource_error(+,:)).
|
||||
|
||||
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),
|
||||
!,
|
||||
( Xs == [] -> N = M
|
||||
; nonvar(Xs) -> var(N), Xs = [_|_], throw(error(resource_error(finite_memory),length/2))
|
||||
; nonvar(Xs) -> var(N), Xs = [_|_], resource_error(finite_memory,length/2)
|
||||
; nonvar(N) -> R is N-M, length_rundown(Xs, R)
|
||||
; N == Xs -> throw(error(resource_error(finite_memory),length/2))
|
||||
; N == Xs -> failingvarskip(Xs), resource_error(finite_memory,length/2)
|
||||
; length_addendum(Xs, N, M)
|
||||
).
|
||||
length(_, N) :-
|
||||
@@ -65,38 +90,94 @@ length(_, N) :-
|
||||
length(_, N) :-
|
||||
type_error(integer, N, length/2).
|
||||
|
||||
length_rundown(Xs, 0) :- !, Xs = [].
|
||||
length_rundown(Vs, N) :-
|
||||
'$unattributed_var'(Vs), % unconstrained
|
||||
!,
|
||||
'$det_length_rundown'(Vs, N).
|
||||
length_rundown([_|Xs], N) :- % force unification
|
||||
N1 is N-1,
|
||||
length(Xs, N1). % maybe some new info on Xs
|
||||
|
||||
failingvarskip(Xs) :-
|
||||
'$unattributed_var'(Xs), % unconstrained
|
||||
!.
|
||||
failingvarskip([_|Xs0]) :- % force unification
|
||||
'$skip_max_list'(_, _, Xs0,Xs),
|
||||
( nonvar(Xs) -> Xs = [_|_]
|
||||
; failingvarskip(Xs)
|
||||
).
|
||||
|
||||
length_addendum([], N, N).
|
||||
length_addendum([_|Xs], N, M) :-
|
||||
M1 is M + 1,
|
||||
length_addendum(Xs, N, M1).
|
||||
|
||||
length_rundown(Xs, 0) :- !, Xs = [].
|
||||
length_rundown([_|Xs], N) :-
|
||||
N1 is N-1,
|
||||
length_rundown(Xs, N1).
|
||||
%% 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, [L|Ls]) :-
|
||||
member_(Ls, L, X).
|
||||
|
||||
member(X, [X|_]).
|
||||
member(X, [_|Xs]) :- member(X, Xs).
|
||||
|
||||
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)
|
||||
@@ -106,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).
|
||||
|
||||
@@ -194,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),
|
||||
@@ -222,63 +370,152 @@ 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),
|
||||
!,
|
||||
( Skip == N
|
||||
-> Es1 = [E|_]
|
||||
; ( var(Es1) ; Es1 = [_|_] ) % a partial or infinite list
|
||||
-> R is N-Skip,
|
||||
skipn(R,Es1,Es2),
|
||||
Es2 = [E|_]
|
||||
).
|
||||
nth0(N, Es0, E) :-
|
||||
can_be(not_less_than_zero, N),
|
||||
Es0 = [E0|Es1],
|
||||
nth0_el(0,N, E0,E, Es1).
|
||||
|
||||
nth0(N, Es, E) :-
|
||||
can_be(integer, N),
|
||||
can_be(list, Es),
|
||||
( integer(N) ->
|
||||
nth0_index(N, Es, E)
|
||||
; nth0_search(N, Es, E)
|
||||
).
|
||||
skipn(N0, Es0,Es) :-
|
||||
N0>0,
|
||||
N1 is N0-1,
|
||||
Es0 = [_|Es1],
|
||||
skipn(N1, Es1,Es).
|
||||
skipn(0, Es,Es).
|
||||
|
||||
nth0_index(0, [E|_], E) :- !.
|
||||
nth0_index(N, [_|Es], E) :-
|
||||
N > 0,
|
||||
N1 is N - 1,
|
||||
nth0_index(N1, Es, E).
|
||||
nth0_el(N0,N, E0,E, Es0) :-
|
||||
Es0 == [],
|
||||
!, % indexing
|
||||
N0 = N,
|
||||
E0 = E.
|
||||
nth0_el(N,N, E,E, _).
|
||||
nth0_el(N0,N, _,E, [E0|Es0]) :-
|
||||
N1 is N0+1,
|
||||
nth0_el(N1,N, E0,E, Es0).
|
||||
|
||||
nth0_search(N, Es, E) :-
|
||||
nth0_search(0, N, Es, E).
|
||||
%% 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),
|
||||
N \== 0.
|
||||
|
||||
nth0_search(N, N, [E|_], E).
|
||||
nth0_search(N0, N, [_|Es], E) :-
|
||||
N1 is N0 + 1,
|
||||
nth0_search(N1, N, Es, E).
|
||||
skipn(N0, Es0,Es, Xs0,Xs) :-
|
||||
N0>0,
|
||||
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,
|
||||
!,
|
||||
skipn(N, Es0,Es1, Es,Es2),
|
||||
Es1 = [E|Es2].
|
||||
nth0(N, Es0, E, Es) :-
|
||||
can_be(not_less_than_zero, N),
|
||||
Es0 = [E0|Es1],
|
||||
nth0_elx(0,N, E0,E, Es1, Es).
|
||||
|
||||
nth0_elx(N0,N, E0,E, Es0, Es) :-
|
||||
Es0 == [],
|
||||
!,
|
||||
N0 = N,
|
||||
E0 = E,
|
||||
Es0 = Es.
|
||||
nth0_elx(N,N, E,E, Es, Es).
|
||||
nth0_elx(N0,N, E0,E, [E1|Es0], [E0|Es]) :-
|
||||
N1 is N0+1,
|
||||
nth0_elx(N1,N, E1,E, Es0, 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),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = []
|
||||
).
|
||||
|
||||
@@ -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(0, ?, ?).
|
||||
:- 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) :-
|
||||
|
||||
215
src/lib/pio.pl
215
src/lib/pio.pl
@@ -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 and phrase_to_stream/2 let us apply DCGs transparently
|
||||
to files and streams, and therefore decouple side-effects from
|
||||
declarative descriptions.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
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,25 +18,42 @@
|
||||
:- 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_to_lazy_list(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),
|
||||
@@ -47,50 +63,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, 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(Stream, Ls) :-
|
||||
get_stream_buffer_position(Stream, Pos),
|
||||
freeze(Ls, render_step(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(Stream, Pos, Ls) :-
|
||||
set_stream_buffer_position(Stream, Pos),
|
||||
( buffer_at_end_of_stream(Stream) ->
|
||||
Ls = []
|
||||
; chars_to_read(CharsToRead),
|
||||
buffer_get_n_chars(Stream, CharsToRead, Chars),
|
||||
partial_string(Chars, Ls, Ls0),
|
||||
stream_to_lazy_list(Stream, Ls0)
|
||||
).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
phrase_to_stream(+GRBody, +Stream)
|
||||
buffer_at_end_of_stream(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(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(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(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),
|
||||
must_be(chars, Cs),
|
||||
( stream_property(Stream, type(binary)) ->
|
||||
( '$first_non_octet'(Cs, N) ->
|
||||
domain_error(byte_char, N, phrase_to_stream/2)
|
||||
domain_error(octet_character, N, phrase_to_stream/2)
|
||||
; true
|
||||
)
|
||||
; true
|
||||
@@ -100,14 +211,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),
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
:- 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).
|
||||
|
||||
%% 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)) ->
|
||||
@@ -46,6 +60,10 @@ rnd_(N, R0, R) :-
|
||||
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) ->
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
/** 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]).
|
||||
memberd_t/3, tfilter/3, tmember/2, tmember_t/3,
|
||||
tpartition/4]).
|
||||
|
||||
:- use_module(library(dif)).
|
||||
|
||||
@@ -30,13 +43,10 @@ non(false, true).
|
||||
|
||||
:- meta_predicate(tfilter(2, ?, ?)).
|
||||
|
||||
tfilter(C_2, Es, Fs) :-
|
||||
i_tfilter(Es, C_2, Fs).
|
||||
|
||||
i_tfilter([], _, []).
|
||||
i_tfilter([E|Es], C_2, Fs0) :-
|
||||
tfilter(_, [], []).
|
||||
tfilter(C_2, [E|Es], Fs0) :-
|
||||
if_(call(C_2, E), Fs0 = [E|Fs], Fs0 = Fs),
|
||||
i_tfilter(Es, C_2, Fs).
|
||||
tfilter(C_2, Es, Fs).
|
||||
|
||||
:- meta_predicate(tpartition(2, ?, ?, ?)).
|
||||
|
||||
|
||||
132
src/lib/sgml.pl
132
src/lib/sgml.pl
@@ -1,56 +1,71 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Predicates for parsing HTML and XML documents.
|
||||
Written June 2020 by Markus Triska (triska@metalevel.at)
|
||||
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 a stream, specified as stream(S), or a file,
|
||||
specified as file(Name), where Name is a list of characters, or a
|
||||
list of characters with the document contents.
|
||||
|
||||
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 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]).
|
||||
|
||||
@@ -61,21 +76,32 @@
|
||||
:- use_module(library(charsio)).
|
||||
|
||||
load_html(Source, Es, Options) :-
|
||||
must_be_source(Source, load_html/3),
|
||||
must_be(list, Options),
|
||||
load_structure_(Source, Es, Options, html).
|
||||
load_xml(Source, Es, Options) :-
|
||||
must_be_source(Source, load_xml/3),
|
||||
must_be(list, Options),
|
||||
load_structure_(Source, Es, Options, xml).
|
||||
|
||||
must_be_source(Source, Context) :-
|
||||
( var(Source) -> instantiation_error(Context)
|
||||
; is_sgml_source(Source) -> true
|
||||
; domain_error(sgml_source, Source, Context)
|
||||
).
|
||||
|
||||
is_sgml_source(file(Fs)) :- must_be(chars, Fs).
|
||||
is_sgml_source(stream(_)).
|
||||
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) :-
|
||||
must_be(list, Options),
|
||||
must_be(list, Fs),
|
||||
atom_chars(File, Fs),
|
||||
once(phrase_from_file(seq(Cs), File)),
|
||||
once(phrase_from_file(seq(Cs), Fs)),
|
||||
load_(What, Cs, E, Options).
|
||||
load_structure_(stream(Stream), [E], Options, What) :-
|
||||
must_be(list, Options),
|
||||
get_n_chars(Stream, _, Cs),
|
||||
load_(What, Cs, E, Options).
|
||||
|
||||
|
||||
118
src/lib/si.pl
118
src/lib/si.pl
@@ -1,33 +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]).
|
||||
list_si/1,
|
||||
character_si/1,
|
||||
term_si/1,
|
||||
chars_si/1,
|
||||
dif_si/2,
|
||||
when_si/2]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
@@ -42,6 +55,75 @@ integer_si(I) :-
|
||||
atomic_si(AC) :-
|
||||
functor(AC,_,0).
|
||||
|
||||
list_si(L) :-
|
||||
\+ \+ length(L, _),
|
||||
sort(L, _).
|
||||
% list_si(L) :-
|
||||
% \+ \+ length(L, _),
|
||||
% sort(L, _).
|
||||
|
||||
list_si(L0) :-
|
||||
'$skip_max_list'(_,_, L0,L),
|
||||
( nonvar(L) -> L = []
|
||||
; throw(error(instantiation_error, list_si/1))
|
||||
).
|
||||
|
||||
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).
|
||||
|
||||
|
||||
1379
src/lib/simplex.pl
Normal file
1379
src/lib/simplex.pl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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).
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
/** Tabling, also called SLG resolution.
|
||||
|
||||
SLG resolution is an alternative execution strategy that sometimes
|
||||
helps to improve termination and performance characters 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(?, 0).
|
||||
:- 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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
% =============================
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
numbervars(Term, N0, N) :-
|
||||
catch(internal_numbervars(Term, N0, N),
|
||||
error(E,Ctx),
|
||||
( ( var(Ctx) -> Ctx = numbervars/3 ; true ), throw(error(E,Ctx) ) ) ).
|
||||
error(E,Ctx),
|
||||
( ( var(Ctx) -> Ctx = numbervars/3 ; true ), throw(error(E,Ctx) ) ) ).
|
||||
|
||||
internal_numbervars(Term, N0, N) :-
|
||||
must_be(integer, N0),
|
||||
|
||||
178
src/lib/time.pl
178
src/lib/time.pl
@@ -1,47 +1,11 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2021 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2023 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.
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** This library provides predicates for reasoning about time.
|
||||
*/
|
||||
|
||||
:- module(time, [max_sleep_time/1, sleep/1, time/1, current_time/1, format_time//2]).
|
||||
|
||||
:- use_module(library(format)).
|
||||
@@ -51,10 +15,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 +70,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 ->
|
||||
@@ -82,7 +96,7 @@ sleep(T) :-
|
||||
:- 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 +105,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 +123,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,
|
||||
( bb_get('$first_answer', true) ->
|
||||
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).
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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),
|
||||
|
||||
29
src/lib/wasm.pl
Normal file
29
src/lib/wasm.pl
Normal file
@@ -0,0 +1,29 @@
|
||||
/** Predicates for the WebAssembly platform
|
||||
|
||||
This module contains predicates that are only available in
|
||||
the WASM (WebAssembly) version of Scryer Prolog.
|
||||
*/
|
||||
|
||||
:- module(wasm, [js_eval/2]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
%% js_eval(+JsCode, -Result).
|
||||
%
|
||||
% Executes a JavaScript snippet `JsCode` using the platform
|
||||
% `eval` function. `Result` takes the return value of that code.
|
||||
% Strings, booleans, numbers, null and undefined are directly mapped to Prolog.
|
||||
% Arrays, objects, bigints, symbols and functions are not mapped.
|
||||
% Instead, a `js_{type}` atom will be returned.
|
||||
%
|
||||
% Example (on a browser):
|
||||
%
|
||||
% ```
|
||||
% ?- js_eval("prompt('What is your name?')", Name).
|
||||
% % A prompt is showed, with a textbox.
|
||||
% Name = "Whatever was written on the textbox".
|
||||
% ```
|
||||
js_eval(JsCode, Result) :-
|
||||
must_be(chars, JsCode),
|
||||
can_be(chars, Result),
|
||||
'$js_eval'(JsCode, Result).
|
||||
106
src/lib/when.pl
Normal file
106
src/lib/when.pl
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
Provides the predicate `when/2`.
|
||||
*/
|
||||
|
||||
:- module(when, [when/2]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(lambda)).
|
||||
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(debug)).
|
||||
|
||||
:- attribute when_list/1.
|
||||
|
||||
:- meta_predicate(when(+, 0)).
|
||||
|
||||
%% when(Condition, Goal).
|
||||
%
|
||||
% Executes Goal when Condition becomes true.
|
||||
when(Condition, Goal) :-
|
||||
( when_condition(Condition) ->
|
||||
( Condition ->
|
||||
Goal
|
||||
; term_variables(Condition, Vars),
|
||||
maplist(
|
||||
[Goal, Condition]+\Var^(
|
||||
get_atts(Var, when_list(Whens0)) ->
|
||||
Whens = [when(Condition, Goal) | Whens0],
|
||||
put_atts(Var, when_list(Whens))
|
||||
; put_atts(Var, when_list([when(Condition, Goal)]))
|
||||
),
|
||||
Vars
|
||||
)
|
||||
)
|
||||
; throw(error(domain_error(when_condition, Condition),_))
|
||||
).
|
||||
|
||||
when_condition(Cond) :-
|
||||
% Should this be delayed?
|
||||
var(Cond), !, throw(error(instantiation_error,when_condition/1)).
|
||||
when_condition(ground(_)).
|
||||
when_condition(nonvar(_)).
|
||||
when_condition((A, B)) :-
|
||||
when_condition(A),
|
||||
when_condition(B).
|
||||
when_condition((A ; B)) :-
|
||||
when_condition(A),
|
||||
when_condition(B).
|
||||
|
||||
remove_goal([], _, []).
|
||||
remove_goal([G0|G0s], Goal, Goals) :-
|
||||
( G0 == Goal ->
|
||||
remove_goal(G0s, Goal, Goals)
|
||||
; Goals = [G0|Goals1],
|
||||
remove_goal(G0s, Goal, Goals1)
|
||||
).
|
||||
|
||||
vars_remove_goal(Vars, Goal) :-
|
||||
maplist(
|
||||
Goal+\Var^(
|
||||
get_atts(Var, when_list(Whens0)) ->
|
||||
remove_goal(Whens0, Goal, Whens),
|
||||
( Whens = [] ->
|
||||
put_atts(Var, -when_list(_))
|
||||
; put_atts(Var, when_list(Whens))
|
||||
)
|
||||
; true
|
||||
),
|
||||
Vars
|
||||
).
|
||||
|
||||
reinforce_goal(Goal0, Goal) :-
|
||||
Goal = (
|
||||
term_variables(Goal0, Vars),
|
||||
when:vars_remove_goal(Vars, Goal0),
|
||||
Goal0
|
||||
).
|
||||
|
||||
verify_attributes(Var, Value, Goals) :-
|
||||
( get_atts(Var, when_list(Whens)) ->
|
||||
( var(Value) ->
|
||||
( get_atts(Value, when_list(WhensValue)) ->
|
||||
append(Whens, WhensValue, WhensNew),
|
||||
put_atts(Value, when_list(WhensNew))
|
||||
; put_atts(Value, when_list(Whens))
|
||||
),
|
||||
Goals = []
|
||||
; maplist(reinforce_goal, Whens, Goals)
|
||||
)
|
||||
; Goals = []
|
||||
).
|
||||
|
||||
gather_when_goals([], _) --> [].
|
||||
gather_when_goals([When|Whens], Var) -->
|
||||
( { term_variables(When, [V0|_]), Var == V0 } ->
|
||||
[when:When]
|
||||
; []
|
||||
),
|
||||
gather_when_goals(Whens, Var).
|
||||
|
||||
attribute_goals(Var) -->
|
||||
{ get_atts(Var, when_list(Whens)) },
|
||||
gather_when_goals(Whens, Var),
|
||||
{ put_atts(Var, -when_list(_)) }.
|
||||
364
src/lib/xpath.pl
364
src/lib/xpath.pl
@@ -26,22 +26,22 @@
|
||||
|
||||
:- use_module(library(http/http_open)).
|
||||
:- use_module(library(sgml)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(xpath)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
link_to_pl_file(File) :-
|
||||
http_open("https://github.com/mthom/scryer-prolog", S, []),
|
||||
load_html(stream(S), DOM, []),
|
||||
xpath(DOM, //a(@href), File),
|
||||
append(_, ".pl", File).
|
||||
phrase((...,".pl"), File).
|
||||
|
||||
Yielding:
|
||||
|
||||
?- link_to_pl_file(File).
|
||||
%@ File = "/mthom/scryer-prolog/blob/master/src/lib/tabling.pl"
|
||||
%@ ; File = "/mthom/scryer-prolog/blob/master/src/lib/dif.pl"
|
||||
%@ ; File = "/mthom/scryer-prolog/blob/master/src/lib/freeze.pl"
|
||||
%@ ; ...
|
||||
%@ File = "/mthom/scryer-prolog/blob/master/src/lib/dcgs.pl"
|
||||
%@ ; File = "/mthom/scryer-prolog/blob/master/src/lib/pio.pl"
|
||||
%@ ; File = "/mthom/scryer-prolog/blob/master/src/lib/tabling.pl"
|
||||
%@ ; ... .
|
||||
|
||||
Parts of the original functionality may not yet work. Please
|
||||
consider such parts opportunities for improvements, and file
|
||||
@@ -95,219 +95,221 @@
|
||||
op(200, fy, @)
|
||||
]).
|
||||
|
||||
:- use_module(library(lists),[member/2,memberchk/2]).
|
||||
:- use_module(library(lists),[member/2,memberchk/2,reverse/2]).
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(si)).
|
||||
|
||||
/** <module> Select nodes in an XML DOM
|
||||
/** Select nodes in an XML DOM
|
||||
|
||||
The library xpath.pl provides predicates to select nodes from an XML DOM
|
||||
tree as produced by library(sgml) based on descriptions inspired by the
|
||||
XPath language.
|
||||
tree as produced by `library(sgml)` based on descriptions inspired by the
|
||||
[XPath language](http://www.w3.org/TR/xpath).
|
||||
|
||||
The predicate xpath/3 selects a sub-structure of the DOM
|
||||
The predicate `xpath/3` selects a sub-structure of the DOM
|
||||
non-deterministically based on an XPath-like specification. Not all
|
||||
selectors of XPath are implemented, but the ability to mix xpath/3 calls
|
||||
selectors of XPath are implemented, but the ability to mix `xpath/3` calls
|
||||
with arbitrary Prolog code provides a powerful tool for extracting
|
||||
information from XML parse-trees.
|
||||
|
||||
@see http://www.w3.org/TR/xpath
|
||||
*/
|
||||
|
||||
element_name(element(Name,_,_), Name).
|
||||
element_attributes(element(_,Attributes,_), Attributes).
|
||||
element_content(element(_,_,Content), Content).
|
||||
|
||||
%! xpath_chk(+DOM, +Spec, ?Content) is semidet.
|
||||
%% xpath_chk(+DOM, +Spec, ?Content) is semidet.
|
||||
%
|
||||
% Semi-deterministic version of xpath/3.
|
||||
% Semi-deterministic version of `xpath/3`.
|
||||
|
||||
xpath_chk(DOM, Spec, Content) :-
|
||||
xpath(DOM, Spec, Content),
|
||||
!.
|
||||
|
||||
%! xpath(+DOM, +Spec, ?Content) is nondet.
|
||||
%% xpath(+DOM, +Spec, ?Content) is nondet.
|
||||
%
|
||||
% Match an element in a DOM structure. The syntax is inspired by
|
||||
% XPath, using () rather than [] to select inside an element.
|
||||
% First we can construct paths using / and //:
|
||||
% Match an element in a DOM structure. The syntax is inspired by
|
||||
% XPath, using () rather than [] to select inside an element.
|
||||
% First we can construct paths using / and //:
|
||||
%
|
||||
% $ =|//|=Term :
|
||||
% Select any node in the DOM matching term.
|
||||
% $ =|/|=Term :
|
||||
% Match the root against Term.
|
||||
% $ Term :
|
||||
% Select the immediate children of the root matching Term.
|
||||
% - *//Term*
|
||||
% Select any node in the DOM matching term.
|
||||
%
|
||||
% The Terms above are of type _callable_. The functor specifies
|
||||
% the element name. The element name '*' refers to any element.
|
||||
% The name =self= refers to the top-element itself and is often
|
||||
% used for processing matches of an earlier xpath/3 query. A term
|
||||
% NS:Term refers to an XML name in the namespace NS. Optional
|
||||
% arguments specify additional constraints and functions. The
|
||||
% arguments are processed from left to right. Defined conditional
|
||||
% argument values are:
|
||||
% - */Term*
|
||||
% Match the root against Term.
|
||||
%
|
||||
% $ index(?Index) :
|
||||
% True if the element is the Index-th child of its parent,
|
||||
% where 1 denotes the first child. Index can be one of:
|
||||
% $ `Var` :
|
||||
% `Var` is unified with the index of the matched element.
|
||||
% $ =last= :
|
||||
% True for the last element.
|
||||
% $ =last= - `IntExpr` :
|
||||
% True for the last-minus-nth element. For example,
|
||||
% `last-1` is the element directly preceding the last one.
|
||||
% $ `IntExpr` :
|
||||
% True for the element whose index equals `IntExpr`.
|
||||
% $ Integer :
|
||||
% The N-th element with the given name, with 1 denoting the
|
||||
% first element. Same as index(Integer).
|
||||
% $ =last= :
|
||||
% The last element with the given name. Same as
|
||||
% index(last).
|
||||
% $ =last= - IntExpr :
|
||||
% The IntExpr-th element before the last.
|
||||
% Same as index(last-IntExpr).
|
||||
% - *Term*
|
||||
% Select the immediate children of the root matching Term.
|
||||
%
|
||||
% Defined function argument values are:
|
||||
% The Terms above are of type _callable_. The functor specifies
|
||||
% the element name. The element name `*` refers to any element.
|
||||
% The name _self_ refers to the top-element itself and is often
|
||||
% used for processing matches of an earlier `xpath/3` query. A term
|
||||
% NS:Term refers to an XML name in the namespace NS. Optional
|
||||
% arguments specify additional constraints and functions. The
|
||||
% arguments are processed from left to right. Defined conditional
|
||||
% argument values are:
|
||||
%
|
||||
% $ =self= :
|
||||
% Evaluate to the entire element
|
||||
% $ =content= :
|
||||
% Evaluate to the content of the element (a list)
|
||||
% $ =text= :
|
||||
% Evaluates to all text from the sub-tree, represented
|
||||
% as a list of characters.
|
||||
% $ `text(atom)` :
|
||||
% Evaluates to all text from the sub-tree as an atom.
|
||||
% $ =normalize_space= :
|
||||
% As =text=, but uses normalize_space/2 to normalise
|
||||
% white-space in the output
|
||||
% $ =number= :
|
||||
% Extract an integer or float from the value. Ignores
|
||||
% leading and trailing white-space
|
||||
% $ =|@|=Attribute :
|
||||
% Evaluates to the value of the given attribute. Attribute
|
||||
% can be a compound term. In this case the functor name
|
||||
% denotes the element and arguments perform transformations
|
||||
% on the attribute value. Defined transformations are:
|
||||
% - *`index(?Index)`*
|
||||
% True if the element is the Index-th child of its parent,
|
||||
% where 1 denotes the first child. Index can be one of:
|
||||
%
|
||||
% - number
|
||||
% Translate the value into a number using
|
||||
% xsd_number_chars/2.
|
||||
% - integer
|
||||
% As `number`, but subsequently transform the value
|
||||
% into an integer using the round/1 function.
|
||||
% - float
|
||||
% As `number`, but subsequently transform the value
|
||||
% into a float using the float/1 function.
|
||||
% - lower
|
||||
% Translate the value to lower case, preserving
|
||||
% the type.
|
||||
% - upper
|
||||
% Translate the value to upper case, preserving
|
||||
% the type.
|
||||
% - *`Var`*
|
||||
% `Var` is unified with the index of the matched element.
|
||||
% - *`last`*
|
||||
% True for the last element.
|
||||
% - *`last - IntExpr`*
|
||||
% True for the last-minus-nth element. For example,
|
||||
% `last-1` is the element directly preceding the last one.
|
||||
% - *`IntExpr`*
|
||||
% True for the element whose index equals `IntExpr`.
|
||||
% - *`Integer`*
|
||||
% The N-th element with the given name, with 1 denoting the
|
||||
% first element. Same as `index(Integer)`.
|
||||
% - *`last`*
|
||||
% The last element with the given name. Same as
|
||||
% `index(last)`.
|
||||
% - *`last - IntExpr`*
|
||||
% The IntExpr-th element before the last.
|
||||
% Same as `index(last-IntExpr)`.
|
||||
%
|
||||
% In addition, the argument-list can be _conditions_:
|
||||
% Defined function argument values are:
|
||||
%
|
||||
% $ Left = Right :
|
||||
% Succeeds if the left-hand unifies with the right-hand.
|
||||
% If the left-hand side is a function, this is evaluated.
|
||||
% The right-hand side is _never_ evaluated, and thus the
|
||||
% condition `content = content` defines that the content
|
||||
% of the element is the atom `content`.
|
||||
% The functions `lower_case` and `upper_case` can be applied
|
||||
% to Right (see example below).
|
||||
% $ contains(Haystack, Needle) :
|
||||
% Succeeds if Needle is a sub-list of Haystack.
|
||||
% $ XPath :
|
||||
% Succeeds if XPath matches in the currently selected
|
||||
% sub-DOM. For example, the following expression finds
|
||||
% an =h3= element inside a =div= element, where the =div=
|
||||
% element itself contains an =h2= child with a =strong=
|
||||
% child.
|
||||
% - *`self`*
|
||||
% Evaluate to the entire element
|
||||
% - *`content`*
|
||||
% Evaluate to the content of the element (a list)
|
||||
% - *`text`*
|
||||
% Evaluates to all text from the sub-tree, represented
|
||||
% as a list of characters.
|
||||
% - *`text(atom)`*
|
||||
% Evaluates to all text from the sub-tree as an atom.
|
||||
% - *`normalize_space`*
|
||||
% As `text`, but uses `normalize_space/2` to normalise
|
||||
% white-space in the output
|
||||
% - *`number`*
|
||||
% Extract an integer or float from the value. Ignores
|
||||
% leading and trailing white-space
|
||||
% - *`@Attribute`*
|
||||
% Evaluates to the value of the given attribute. Attribute
|
||||
% can be a compound term. In this case the functor name
|
||||
% denotes the element and arguments perform transformations
|
||||
% on the attribute value. Defined transformations are:
|
||||
%
|
||||
% ==
|
||||
% //div(h2/strong)/h3
|
||||
% ==
|
||||
% - *`number`*
|
||||
% Translate the value into a number using
|
||||
% `xsd_number_chars/2`.
|
||||
% - *`integer`*
|
||||
% As `number`, but subsequently transform the value
|
||||
% into an integer using the `round/1` function.
|
||||
% - *`float`*
|
||||
% As `number`, but subsequently transform the value
|
||||
% into a float using the `float/1` function.
|
||||
% - *`lower`*
|
||||
% Translate the value to lower case, preserving
|
||||
% the type.
|
||||
% - *`upper`*
|
||||
% Translate the value to upper case, preserving
|
||||
% the type.
|
||||
%
|
||||
% This is equivalent to the conjunction of XPath goals below.
|
||||
% In addition, the argument-list can be _conditions_:
|
||||
%
|
||||
% ==
|
||||
% ...,
|
||||
% xpath(DOM, //(div), Div),
|
||||
% xpath(Div, h2/strong, _),
|
||||
% xpath(Div, h3, Result)
|
||||
% ==
|
||||
% - *`Left = Right`*
|
||||
% Succeeds if the left-hand unifies with the right-hand.
|
||||
% If the left-hand side is a function, this is evaluated.
|
||||
% The right-hand side is _never_ evaluated, and thus the
|
||||
% condition `content = content` defines that the content
|
||||
% of the element is the atom `content`.
|
||||
% The functions `lower_case` and `upper_case` can be applied
|
||||
% to Right (see example below).
|
||||
% - *`contains(Haystack, Needle)`*
|
||||
% Succeeds if Needle is a sub-list of Haystack.
|
||||
% - *`XPath`*
|
||||
% Succeeds if XPath matches in the currently selected
|
||||
% sub-DOM. For example, the following expression finds
|
||||
% an `h3` element inside a `div` element, where the `div`
|
||||
% element itself contains an `h2` child with a `strong`
|
||||
% child.
|
||||
%
|
||||
% **Examples**:
|
||||
% ```
|
||||
% //div(h2/strong)/h3
|
||||
% ```
|
||||
%
|
||||
% Match each table-row in DOM:
|
||||
% This is equivalent to the conjunction of XPath goals below.
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //tr, TR)
|
||||
% ==
|
||||
% ```
|
||||
% ...,
|
||||
% xpath(DOM, //(div), Div),
|
||||
% xpath(Div, h2/strong, _),
|
||||
% xpath(Div, h3, Result)
|
||||
% ```
|
||||
%
|
||||
% Match the last cell of each tablerow in DOM. This example
|
||||
% illustrates that a result can be the input of subsequent xpath/3
|
||||
% queries. Using multiple queries on the intermediate TR term
|
||||
% guarantee that all results come from the same table-row:
|
||||
% #### Examples
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, /td(last), TD)
|
||||
% ==
|
||||
% Match each table-row in DOM:
|
||||
%
|
||||
% Match each =href= attribute in an <a> element
|
||||
% ```
|
||||
% xpath(DOM, //tr, TR)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //a(@href), HREF)
|
||||
% ==
|
||||
% Match the last cell of each tablerow in DOM. This example
|
||||
% illustrates that a result can be the input of subsequent `xpath/3`
|
||||
% queries. Using multiple queries on the intermediate TR term
|
||||
% guarantee that all results come from the same table-row:
|
||||
%
|
||||
% Suppose we have a table containing rows where each first column
|
||||
% is the name of a product with a link to details and the second
|
||||
% is the price (a number). The following predicate matches the
|
||||
% name, URL and price:
|
||||
% ```
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, /td(last), TD)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% product(DOM, Name, URL, Price) :-
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, td(1), C1),
|
||||
% xpath(C1, /self(normalize_space), Name),
|
||||
% xpath(C1, a(@href), URL),
|
||||
% xpath(TR, td(2, number), Price).
|
||||
% ==
|
||||
% Match each `href` attribute in an `<a>` element
|
||||
%
|
||||
% Suppose we want to select books with genre="thriller" from a
|
||||
% tree containing elements =|<book genre=...>|=
|
||||
% ```
|
||||
% xpath(DOM, //a(@href), HREF)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% thriller(DOM, Book) :-
|
||||
% xpath(DOM, //book(@genre=thiller), Book).
|
||||
% ==
|
||||
% Suppose we have a table containing rows where each first column
|
||||
% is the name of a product with a link to details and the second
|
||||
% is the price (a number). The following predicate matches the
|
||||
% name, URL and price:
|
||||
%
|
||||
% Match the elements =|<table align="center">|= _and_ =|<table
|
||||
% align="CENTER">|=:
|
||||
% ```
|
||||
% product(DOM, Name, URL, Price) :-
|
||||
% xpath(DOM, //tr, TR),
|
||||
% xpath(TR, td(1), C1),
|
||||
% xpath(C1, /self(normalize_space), Name),
|
||||
% xpath(C1, a(@href), URL),
|
||||
% xpath(TR, td(2, number), Price).
|
||||
% ```
|
||||
%
|
||||
% ```prolog
|
||||
% //table(@align(lower) = center)
|
||||
% ```
|
||||
% Suppose we want to select books with genre="thriller" from a
|
||||
% tree containing elements `<book genre=...>`
|
||||
%
|
||||
% Get the `width` and `height` of a `div` element as a number,
|
||||
% and the `div` node itself:
|
||||
% ```
|
||||
% thriller(DOM, Book) :-
|
||||
% xpath(DOM, //book(@genre=thiller), Book).
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //div(@width(number)=W, @height(number)=H), Div)
|
||||
% ==
|
||||
% Match the elements `<table align="center">` _and_ `<table
|
||||
% align="CENTER">`:
|
||||
%
|
||||
% Note that `div` is an infix operator, so parentheses must be
|
||||
% used in cases like the following:
|
||||
% ```
|
||||
% //table(@align(lower) = center)
|
||||
% ```
|
||||
%
|
||||
% ==
|
||||
% xpath(DOM, //(div), Div)
|
||||
% ==
|
||||
% Get the `width` and `height` of a `div` element as a number,
|
||||
% and the `div` node itself:
|
||||
%
|
||||
% ```
|
||||
% xpath(DOM, //div(@width(number)=W, @height(number)=H), Div)
|
||||
% ```
|
||||
%
|
||||
% Note that `div` is an infix operator, so parentheses must be
|
||||
% used in cases like the following:
|
||||
%
|
||||
% ```
|
||||
% xpath(DOM, //(div), Div)
|
||||
% ```
|
||||
|
||||
xpath(DOM, Spec, Content) :-
|
||||
in_dom(Spec, DOM, Content).
|
||||
@@ -635,5 +637,27 @@ text_of_1([C|Cs]) --> seq([C|Cs]).
|
||||
xsd_number_chars(Number, Chars) :-
|
||||
number_chars(Number, Chars).
|
||||
|
||||
normalize_space(Text0, Text) :-
|
||||
Text0 = Text. % no conversion for the moment.
|
||||
normalize_space(Cs0, Cs) :-
|
||||
must_be(chars, Cs0),
|
||||
no_leading_whitespace(Cs0, Cs1),
|
||||
reverse(Cs1, Cs2),
|
||||
no_leading_whitespace(Cs2, Cs3),
|
||||
reverse(Cs3, Cs4),
|
||||
single_intermediate_space(Cs4, Cs).
|
||||
|
||||
no_leading_whitespace([], []).
|
||||
no_leading_whitespace([C0|Cs0], Cs) :-
|
||||
( char_type(C0, whitespace) ->
|
||||
no_leading_whitespace(Cs0, Cs)
|
||||
; Cs = [C0|Cs0]
|
||||
).
|
||||
|
||||
single_intermediate_space([], []).
|
||||
single_intermediate_space([C0|Cs0], [C|Cs]) :-
|
||||
( char_type(C0, whitespace) ->
|
||||
no_leading_whitespace(Cs0, Cs1),
|
||||
C = ' ',
|
||||
single_intermediate_space(Cs1, Cs)
|
||||
; C = C0,
|
||||
single_intermediate_space(Cs0, Cs)
|
||||
).
|
||||
|
||||
1763
src/loader.pl
1763
src/loader.pl
File diff suppressed because it is too large
Load Diff
22
src/machine/args.rs
Normal file
22
src/machine/args.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::env;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineArgs {
|
||||
pub add_history: bool,
|
||||
}
|
||||
|
||||
impl MachineArgs {
|
||||
pub fn new() -> Self {
|
||||
let args: BTreeSet<String> = env::args().collect();
|
||||
Self {
|
||||
add_history: !args.contains("--no-add-history"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MachineArgs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
use dashu::base::{Abs, Gcd, Signed, UnsignedAbs};
|
||||
use dashu::integer::fast_div::ConstDivisor;
|
||||
use dashu::integer::IBig;
|
||||
use divrem::*;
|
||||
use num_order::NumOrd;
|
||||
|
||||
use crate::arena::*;
|
||||
use crate::arithmetic::*;
|
||||
@@ -8,7 +12,7 @@ use crate::heap_iter::*;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::machine::machine_state::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::rug::{Integer, Rational};
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use crate::types::*;
|
||||
|
||||
use crate::fixnum;
|
||||
@@ -47,9 +51,7 @@ macro_rules! drop_iter_on_err {
|
||||
};
|
||||
}
|
||||
|
||||
fn zero_divisor_eval_error(
|
||||
stub_gen: impl Fn() -> FunctorStub + 'static,
|
||||
) -> MachineStubGen {
|
||||
fn zero_divisor_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> MachineStubGen {
|
||||
Box::new(move |machine_st| {
|
||||
let eval_error = machine_st.evaluation_error(EvalError::ZeroDivisor);
|
||||
let stub = stub_gen();
|
||||
@@ -58,9 +60,7 @@ fn zero_divisor_eval_error(
|
||||
})
|
||||
}
|
||||
|
||||
fn undefined_eval_error(
|
||||
stub_gen: impl Fn() -> FunctorStub + 'static,
|
||||
) -> MachineStubGen {
|
||||
fn undefined_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> MachineStubGen {
|
||||
Box::new(move |machine_st| {
|
||||
let eval_error = machine_st.evaluation_error(EvalError::Undefined);
|
||||
let stub = stub_gen();
|
||||
@@ -82,30 +82,20 @@ fn numerical_type_error(
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn sign(n: Number) -> Number {
|
||||
if n.is_positive() {
|
||||
Number::Fixnum(Fixnum::build_with(1))
|
||||
} else if n.is_negative() {
|
||||
Number::Fixnum(Fixnum::build_with(-1))
|
||||
} else {
|
||||
Number::Fixnum(Fixnum::build_with(0))
|
||||
}
|
||||
}
|
||||
|
||||
fn isize_gcd(n1: isize, n2: isize) -> Option<isize> {
|
||||
if n1 == 0 {
|
||||
return n2.checked_abs().map(|n| n as isize);
|
||||
return n2.checked_abs();
|
||||
}
|
||||
|
||||
if n2 == 0 {
|
||||
return n1.checked_abs().map(|n| n as isize);
|
||||
return n1.checked_abs();
|
||||
}
|
||||
|
||||
let n1 = n1.checked_abs();
|
||||
let n2 = n2.checked_abs();
|
||||
|
||||
let mut n1 = if let Some(n1) = n1 { n1 } else { return None };
|
||||
let mut n2 = if let Some(n2) = n2 { n2 } else { return None };
|
||||
let mut n1 = n1?;
|
||||
let mut n2 = n2?;
|
||||
|
||||
let mut shift = 0;
|
||||
|
||||
@@ -125,9 +115,7 @@ fn isize_gcd(n1: isize, n2: isize) -> Option<isize> {
|
||||
}
|
||||
|
||||
if n1 > n2 {
|
||||
let t = n2;
|
||||
n2 = n1;
|
||||
n1 = t;
|
||||
std::mem::swap(&mut n2, &mut n1);
|
||||
}
|
||||
|
||||
n2 -= n1;
|
||||
@@ -169,16 +157,14 @@ pub(crate) fn add(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
Ok(Number::Float(add_f(float_fn_to_f(n1.get_num())?, n2)?))
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1) + &*n2, arena)) // add_i
|
||||
Ok(Number::arena_from(&*n1 + &*n2, arena)) // add_i
|
||||
}
|
||||
(Number::Integer(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Integer(n1)) => {
|
||||
Ok(Number::Float(add_f(float_i_to_f(&n1)?, n2)?))
|
||||
}
|
||||
(Number::Integer(n1), Number::Rational(n2))
|
||||
| (Number::Rational(n2), Number::Integer(n1)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*n1) + &*n2, arena))
|
||||
}
|
||||
| (Number::Rational(n2), Number::Integer(n1)) => Ok(Number::arena_from(&*n1 + &*n2, arena)),
|
||||
(Number::Rational(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Rational(n1)) => {
|
||||
Ok(Number::Float(add_f(float_r_to_f(&n1)?, n2)?))
|
||||
@@ -186,9 +172,7 @@ pub(crate) fn add(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
(Number::Float(OrderedFloat(f1)), Number::Float(OrderedFloat(f2))) => {
|
||||
Ok(Number::Float(add_f(f1, f2)?))
|
||||
}
|
||||
(Number::Rational(r1), Number::Rational(r2)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*r1) + &*r2, arena))
|
||||
}
|
||||
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::arena_from(&*r1 + &*r2, arena)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +185,15 @@ pub(crate) fn neg(n: Number, arena: &mut Arena) -> Number {
|
||||
Number::arena_from(-Integer::from(n.get_num()), arena)
|
||||
}
|
||||
}
|
||||
Number::Integer(n) => Number::arena_from(-Integer::from(&*n), arena),
|
||||
Number::Integer(n) => {
|
||||
let n_clone: Integer = (*n).clone();
|
||||
Number::arena_from(-Integer::from(n_clone), arena)
|
||||
}
|
||||
Number::Float(OrderedFloat(f)) => Number::Float(OrderedFloat(-f)),
|
||||
Number::Rational(r) => Number::arena_from(-Rational::from(&*r), arena),
|
||||
Number::Rational(r) => {
|
||||
let r_clone: Rational = (*r).clone();
|
||||
Number::arena_from(-Rational::from(r_clone), arena)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,12 +203,19 @@ pub(crate) fn abs(n: Number, arena: &mut Arena) -> Number {
|
||||
if let Some(n) = n.get_num().checked_abs() {
|
||||
fixnum!(Number, n, arena)
|
||||
} else {
|
||||
Number::arena_from(Integer::from(n.get_num()).abs(), arena)
|
||||
let arena_int = Integer::from(n.get_num());
|
||||
Number::arena_from(arena_int.abs(), arena)
|
||||
}
|
||||
}
|
||||
Number::Integer(n) => Number::arena_from(Integer::from(n.abs_ref()), arena),
|
||||
Number::Integer(n) => {
|
||||
let n_clone: Integer = (*n).clone();
|
||||
Number::arena_from(Integer::from(n_clone.abs()), arena)
|
||||
}
|
||||
Number::Float(f) => Number::Float(f.abs()),
|
||||
Number::Rational(r) => Number::arena_from(Rational::from(r.abs_ref()), arena),
|
||||
Number::Rational(r) => {
|
||||
let r_clone: Rational = (*r).clone();
|
||||
Number::arena_from(Rational::from(r_clone.abs()), arena)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,7 +254,8 @@ pub(crate) fn mul(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
Ok(Number::Float(mul_f(float_fn_to_f(n1.get_num())?, n2)?))
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1) * &*n2, arena)) // mul_i
|
||||
let n1_clone: Integer = (*n1).clone();
|
||||
Ok(Number::arena_from(Integer::from(n1_clone) * &*n2, arena)) // mul_i
|
||||
}
|
||||
(Number::Integer(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Integer(n1)) => {
|
||||
@@ -265,7 +263,8 @@ pub(crate) fn mul(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
}
|
||||
(Number::Integer(n1), Number::Rational(n2))
|
||||
| (Number::Rational(n2), Number::Integer(n1)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*n1) * &*n2, arena))
|
||||
let n1_clone: Integer = (*n1).clone();
|
||||
Ok(Number::arena_from(Rational::from(n1_clone) * &*n2, arena))
|
||||
}
|
||||
(Number::Rational(n1), Number::Float(OrderedFloat(n2)))
|
||||
| (Number::Float(OrderedFloat(n2)), Number::Rational(n1)) => {
|
||||
@@ -275,7 +274,8 @@ pub(crate) fn mul(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number,
|
||||
Ok(Number::Float(mul_f(f1, f2)?))
|
||||
}
|
||||
(Number::Rational(r1), Number::Rational(r2)) => {
|
||||
Ok(Number::arena_from(Rational::from(&*r1) * &*r2, arena))
|
||||
let r1_clone: Rational = (*r1).clone();
|
||||
Ok(Number::arena_from(Rational::from(r1_clone) * &*r2, arena))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,8 +291,8 @@ pub(crate) fn div(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
}
|
||||
|
||||
pub(crate) fn float_pow(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
let f1 = result_f(&n1, rnd_f);
|
||||
let f2 = result_f(&n2, rnd_f);
|
||||
let f1 = result_f(&n1);
|
||||
let f2 = result_f(&n2);
|
||||
|
||||
let stub_gen = || {
|
||||
let pow_atom = atom!("**");
|
||||
@@ -302,7 +302,7 @@ pub(crate) fn float_pow(n1: Number, n2: Number) -> Result<Number, MachineStubGen
|
||||
let f1 = try_numeric_result!(f1, stub_gen)?;
|
||||
let f2 = try_numeric_result!(f2, stub_gen)?;
|
||||
|
||||
let result = result_f(&Number::Float(OrderedFloat(f1.powf(f2))), rnd_f);
|
||||
let result = result_f(&Number::Float(OrderedFloat(f1.powf(f2))));
|
||||
|
||||
Ok(Number::Float(OrderedFloat(try_numeric_result!(
|
||||
result, stub_gen
|
||||
@@ -348,18 +348,18 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
|
||||
if !(n1_i == 1 || n1_i == 0 || n1_i == -1) && &*n2 < &0 {
|
||||
if !(n1_i == 1 || n1_i == 0 || n1_i == -1) && n2.is_negative() {
|
||||
let n = Number::Fixnum(n1);
|
||||
Err(numerical_type_error(ValidType::Float, n, stub_gen))
|
||||
} else {
|
||||
let n1 = Integer::from(n1_i);
|
||||
Ok(Number::arena_from(binary_pow(n1, &*n2), arena))
|
||||
Ok(Number::arena_from(binary_pow(n1, &n2), arena))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => {
|
||||
let n2_i = n2.get_num();
|
||||
|
||||
if !(&*n1 == &1 || &*n1 == &0 || &*n1 == &-1) && n2_i < 0 {
|
||||
if !(n1.is_one() || n1.is_zero() || n1.num_eq(&-1)) && n2_i < 0 {
|
||||
let n = Number::Integer(n1);
|
||||
Err(numerical_type_error(ValidType::Float, n, stub_gen))
|
||||
} else {
|
||||
@@ -368,11 +368,11 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if !(&*n1 == &1 || &*n1 == &0 || &*n1 == &-1) && &*n2 < &0 {
|
||||
if !(n1.is_one() || n1.is_zero() || n1.num_eq(&-1)) && n2.is_negative() {
|
||||
let n = Number::Integer(n1);
|
||||
Err(numerical_type_error(ValidType::Float, n, stub_gen))
|
||||
} else {
|
||||
Ok(Number::arena_from(binary_pow((*n1).clone(), &*n2), arena))
|
||||
Ok(Number::arena_from(binary_pow((*n1).clone(), &n2), arena))
|
||||
}
|
||||
}
|
||||
(n1, Number::Integer(n2)) => {
|
||||
@@ -400,7 +400,6 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
|
||||
pub(crate) fn pow(n1: Number, n2: Number, culprit: Atom) -> Result<Number, MachineStubGen> {
|
||||
if n2.is_negative() && n1.is_zero() {
|
||||
let stub_gen = move || functor_stub(culprit, 2);
|
||||
|
||||
return Err(undefined_eval_error(stub_gen));
|
||||
}
|
||||
|
||||
@@ -414,7 +413,7 @@ pub(crate) fn float(n: Number) -> Result<f64, MachineStubGen> {
|
||||
functor_stub(is_atom, 2)
|
||||
};
|
||||
|
||||
try_numeric_result!(result_f(&n, rnd_f), stub_gen)
|
||||
try_numeric_result!(result_f(&n), stub_gen)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -430,8 +429,8 @@ where
|
||||
functor_stub(is_atom, 2)
|
||||
};
|
||||
|
||||
let f1 = try_numeric_result!(result_f(&n1, rnd_f), stub_gen)?;
|
||||
let f1 = result_f(&Number::Float(OrderedFloat(f(f1))), rnd_f);
|
||||
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
|
||||
let f1 = result_f(&Number::Float(OrderedFloat(f(f1))));
|
||||
|
||||
try_numeric_result!(f1, stub_gen)
|
||||
}
|
||||
@@ -446,14 +445,14 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
if &*n2 > &n1.get_num() {
|
||||
if (*n2).num_gt(&n1.get_num()) {
|
||||
Ok(Number::Integer(n2))
|
||||
} else {
|
||||
Ok(Number::Fixnum(n1))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => {
|
||||
if &*n1 > &n2.get_num() {
|
||||
if (*n1).num_gt(&n2.get_num()) {
|
||||
Ok(Number::Integer(n1))
|
||||
} else {
|
||||
Ok(Number::Fixnum(n2))
|
||||
@@ -472,8 +471,8 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
functor_stub(max_atom, 2)
|
||||
};
|
||||
|
||||
let f1 = try_numeric_result!(result_f(&n1, rnd_f), stub_gen)?;
|
||||
let f2 = try_numeric_result!(result_f(&n2, rnd_f), stub_gen)?;
|
||||
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
|
||||
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;
|
||||
|
||||
Ok(Number::Float(cmp::max(OrderedFloat(f1), OrderedFloat(f2))))
|
||||
}
|
||||
@@ -490,14 +489,14 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
if &*n2 < &n1.get_num() {
|
||||
if (*n2).num_lt(&n1.get_num()) {
|
||||
Ok(Number::Integer(n2))
|
||||
} else {
|
||||
Ok(Number::Fixnum(n1))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => {
|
||||
if &*n1 < &n2.get_num() {
|
||||
if (*n1).num_lt(&n2.get_num()) {
|
||||
Ok(Number::Integer(n1))
|
||||
} else {
|
||||
Ok(Number::Fixnum(n2))
|
||||
@@ -516,8 +515,8 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
functor_stub(min_atom, 2)
|
||||
};
|
||||
|
||||
let f1 = try_numeric_result!(result_f(&n1, rnd_f), stub_gen)?;
|
||||
let f2 = try_numeric_result!(result_f(&n2, rnd_f), stub_gen)?;
|
||||
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
|
||||
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;
|
||||
|
||||
Ok(Number::Float(cmp::min(OrderedFloat(f1), OrderedFloat(f2))))
|
||||
}
|
||||
@@ -532,7 +531,7 @@ pub fn rational_from_number(
|
||||
match n {
|
||||
Number::Fixnum(n) => Ok(arena_alloc!(Rational::from(n.get_num()), arena)),
|
||||
Number::Rational(r) => Ok(r),
|
||||
Number::Float(OrderedFloat(f)) => match Rational::from_f64(f) {
|
||||
Number::Float(OrderedFloat(f)) => match Rational::simplest_from_f64(f) {
|
||||
Some(r) => Ok(arena_alloc!(r, arena)),
|
||||
None => Err(Box::new(move |machine_st| {
|
||||
let instantiation_error = machine_st.instantiation_error();
|
||||
@@ -541,7 +540,10 @@ pub fn rational_from_number(
|
||||
machine_st.error_form(instantiation_error, stub)
|
||||
})),
|
||||
},
|
||||
Number::Integer(n) => Ok(arena_alloc!(Rational::from(&*n), arena)),
|
||||
Number::Integer(n) => {
|
||||
let n_clone: Integer = (*n).clone();
|
||||
Ok(arena_alloc!(Rational::from(n_clone), arena))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +551,7 @@ pub(crate) fn rdiv(
|
||||
r1: TypedArenaPtr<Rational>,
|
||||
r2: TypedArenaPtr<Rational>,
|
||||
) -> Result<Rational, MachineStubGen> {
|
||||
if &*r2 == &0 {
|
||||
if r2.is_zero() {
|
||||
let stub_gen = || {
|
||||
let rdiv_atom = atom!("rdiv");
|
||||
functor_stub(rdiv_atom, 2)
|
||||
@@ -571,19 +573,17 @@ pub(crate) fn idiv(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number,
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
if n2.get_num() == 0 {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else if let Some(result) = n1.get_num().checked_div(n2.get_num()) {
|
||||
Ok(Number::arena_from(result, arena))
|
||||
} else {
|
||||
if let Some(result) = n1.get_num().checked_div(n2.get_num()) {
|
||||
Ok(Number::arena_from(result, arena))
|
||||
} else {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
let n2 = Integer::from(n2.get_num());
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
let n2 = Integer::from(n2.get_num());
|
||||
|
||||
Ok(Number::arena_from(n1 / n2, arena))
|
||||
}
|
||||
Ok(Number::arena_from(n1 / n2, arena))
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
if &*n2 == &0 {
|
||||
if n2.is_zero() {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
Ok(Number::arena_from(Integer::from(n1) / &*n2, arena))
|
||||
@@ -597,13 +597,10 @@ pub(crate) fn idiv(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number,
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if &*n2 == &0 {
|
||||
if n2.is_zero() {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
Ok(Number::arena_from(
|
||||
<(Integer, Integer)>::from(n1.div_rem_ref(&*n2)).0,
|
||||
arena,
|
||||
))
|
||||
Ok(Number::arena_from(&*n1 / &*n2, arena))
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(_), n2) | (Number::Integer(_), n2) => {
|
||||
@@ -635,6 +632,10 @@ pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
functor_stub(shr_atom, 2)
|
||||
};
|
||||
|
||||
if n2.is_integer() && n2.is_negative() {
|
||||
return shl(n1, neg(n2, arena), arena);
|
||||
}
|
||||
|
||||
match (n1, n2) {
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
@@ -642,34 +643,40 @@ pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
|
||||
let n1 = Integer::from(n1_i);
|
||||
|
||||
if let Ok(n2) = u32::try_from(n2_i) {
|
||||
return Ok(Number::arena_from(n1 >> n2, arena));
|
||||
if let Ok(n2) = usize::try_from(n2_i) {
|
||||
Ok(Number::arena_from(n1 >> n2, arena))
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 >> u32::max_value(), arena));
|
||||
Ok(Number::arena_from(n1 >> usize::max_value(), arena))
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
|
||||
match n2.to_u32() {
|
||||
Some(n2) => Ok(Number::arena_from(n1 >> n2, arena)),
|
||||
_ => Ok(Number::arena_from(n1 >> u32::max_value(), arena)),
|
||||
let result: Result<usize, _> = (&*n2).try_into();
|
||||
|
||||
match result {
|
||||
Ok(n2) => Ok(Number::arena_from(n1 >> n2, arena)),
|
||||
Err(_) => Ok(Number::arena_from(n1 >> usize::max_value(), arena)),
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match u32::try_from(n2.get_num()) {
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 >> u32::max_value()),
|
||||
arena,
|
||||
)),
|
||||
},
|
||||
(Number::Integer(n1), Number::Integer(n2)) => match n2.to_u32() {
|
||||
Some(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 >> u32::max_value()),
|
||||
Integer::from(&*n1 >> usize::max_value()),
|
||||
arena,
|
||||
)),
|
||||
},
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
let result: Result<usize, _> = (&*n2).try_into();
|
||||
|
||||
match result {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
|
||||
Err(_) => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 >> usize::max_value()),
|
||||
arena,
|
||||
)),
|
||||
}
|
||||
}
|
||||
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(n1, _) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
|
||||
@@ -678,10 +685,14 @@ pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
|
||||
pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
let stub_gen = || {
|
||||
let shl_atom = atom!(">>");
|
||||
let shl_atom = atom!("<<");
|
||||
functor_stub(shl_atom, 2)
|
||||
};
|
||||
|
||||
if n2.is_integer() && n2.is_negative() {
|
||||
return shr(n1, neg(n2, arena), arena);
|
||||
}
|
||||
|
||||
match (n1, n2) {
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
@@ -689,31 +700,31 @@ pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
|
||||
let n1 = Integer::from(n1_i);
|
||||
|
||||
if let Ok(n2) = u32::try_from(n2_i) {
|
||||
return Ok(Number::arena_from(n1 << n2, arena));
|
||||
if let Ok(n2) = usize::try_from(n2_i) {
|
||||
Ok(Number::arena_from(n1 << n2, arena))
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 << u32::max_value(), arena));
|
||||
Ok(Number::arena_from(n1 << usize::max_value(), arena))
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
|
||||
match n2.to_u32() {
|
||||
Some(n2) => Ok(Number::arena_from(n1 << n2, arena)),
|
||||
_ => Ok(Number::arena_from(n1 << u32::max_value(), arena)),
|
||||
match (&*n2).try_into() as Result<usize, _> {
|
||||
Ok(n2) => Ok(Number::arena_from(n1 << n2, arena)),
|
||||
_ => Ok(Number::arena_from(n1 << usize::max_value(), arena)),
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match u32::try_from(n2.get_num()) {
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 << u32::max_value()),
|
||||
Integer::from(&*n1 << usize::max_value()),
|
||||
arena,
|
||||
)),
|
||||
},
|
||||
(Number::Integer(n1), Number::Integer(n2)) => match n2.to_u32() {
|
||||
Some(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
|
||||
(Number::Integer(n1), Number::Integer(n2)) => match (&*n2).try_into() as Result<usize, _> {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 << u32::max_value()),
|
||||
Integer::from(&*n1 << usize::max_value()),
|
||||
arena,
|
||||
)),
|
||||
},
|
||||
@@ -803,7 +814,8 @@ pub(crate) fn xor(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
(Number::Integer(_), n2) | (Number::Fixnum(_), n2) => {
|
||||
Err(numerical_type_error(ValidType::Integer, n2, stub_gen))
|
||||
}
|
||||
_ => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(n1, Number::Integer(_)) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
|
||||
_ => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,6 +825,23 @@ pub(crate) fn modulus(x: Number, y: Number, arena: &mut Arena) -> Result<Number,
|
||||
functor_stub(mod_atom, 2)
|
||||
};
|
||||
|
||||
fn ibig_rem_floor(n1: &Integer, n2: &Integer) -> Integer {
|
||||
let ring = ConstDivisor::new(n2.unsigned_abs());
|
||||
let n1 = n1.clone();
|
||||
|
||||
if n2.is_negative() {
|
||||
let unsigned_result = IBig::from(ring.reduce(n1).residue());
|
||||
|
||||
if unsigned_result.is_zero() {
|
||||
unsigned_result
|
||||
} else {
|
||||
unsigned_result + n2
|
||||
}
|
||||
} else {
|
||||
IBig::from(ring.reduce(n1).residue())
|
||||
}
|
||||
}
|
||||
|
||||
match (x, y) {
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
let n2_i = n2.get_num();
|
||||
@@ -825,14 +854,11 @@ pub(crate) fn modulus(x: Number, y: Number, arena: &mut Arena) -> Result<Number,
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
if &*n2 == &0 {
|
||||
if n2.is_zero() {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
Ok(Number::arena_from(
|
||||
<(Integer, Integer)>::from(n1.div_rem_floor_ref(&*n2)).1,
|
||||
arena,
|
||||
))
|
||||
Ok(Number::arena_from(ibig_rem_floor(&n1, &n2), arena))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => {
|
||||
@@ -842,20 +868,14 @@ pub(crate) fn modulus(x: Number, y: Number, arena: &mut Arena) -> Result<Number,
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
let n2 = Integer::from(n2_i);
|
||||
Ok(Number::arena_from(
|
||||
<(Integer, Integer)>::from(n1.div_rem_floor_ref(&n2)).1,
|
||||
arena,
|
||||
))
|
||||
Ok(Number::arena_from(ibig_rem_floor(&n1, &n2), arena))
|
||||
}
|
||||
}
|
||||
(Number::Integer(x), Number::Integer(y)) => {
|
||||
if &*y == &0 {
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if n2.is_zero() {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
Ok(Number::arena_from(
|
||||
<(Integer, Integer)>::from(x.div_rem_floor_ref(&*y)).1,
|
||||
arena,
|
||||
))
|
||||
Ok(Number::arena_from(ibig_rem_floor(&n1, &n2), arena))
|
||||
}
|
||||
}
|
||||
(Number::Integer(_), n2) | (Number::Fixnum(_), n2) => {
|
||||
@@ -883,7 +903,7 @@ pub(crate) fn remainder(x: Number, y: Number, arena: &mut Arena) -> Result<Numbe
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
if &*n2 == &0 {
|
||||
if n2.is_zero() {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
@@ -901,7 +921,7 @@ pub(crate) fn remainder(x: Number, y: Number, arena: &mut Arena) -> Result<Numbe
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if &*n2 == &0 {
|
||||
if n2.is_zero() {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 % &*n2), arena))
|
||||
@@ -928,18 +948,19 @@ pub(crate) fn gcd(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
if let Some(result) = isize_gcd(n1_i, n2_i) {
|
||||
Ok(Number::arena_from(result, arena))
|
||||
} else {
|
||||
Ok(Number::arena_from(
|
||||
Integer::from(n1_i).gcd(&Integer::from(n2_i)),
|
||||
arena,
|
||||
))
|
||||
let value: Integer = Integer::from(n1_i).gcd(&Integer::from(n2_i)).into();
|
||||
Ok(Number::arena_from(value, arena))
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) | (Number::Integer(n2), Number::Fixnum(n1)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
Ok(Number::arena_from(Integer::from(n2.gcd_ref(&n1)), arena))
|
||||
let n2_clone: Integer = (*n2).clone();
|
||||
Ok(Number::arena_from(Integer::from(n2_clone.gcd(&n1)), arena))
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
Ok(Number::arena_from(Integer::from(n1.gcd_ref(&n2)), arena))
|
||||
let n2: isize = (&*n2).try_into().unwrap();
|
||||
let value: Integer = (&*n1).gcd(&Integer::from(n2)).into();
|
||||
Ok(Number::arena_from(value, arena))
|
||||
}
|
||||
(Number::Float(f), _) | (_, Number::Float(f)) => {
|
||||
let n = Number::Float(f);
|
||||
@@ -1008,6 +1029,16 @@ pub(crate) fn atan(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.atan())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_fractional_part(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.fract())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn float_integer_part(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
unary_float_fn_template(n1, |f| f.trunc())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn sqrt(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
if n1.is_negative() {
|
||||
@@ -1079,17 +1110,18 @@ impl MachineState {
|
||||
pub fn get_number(&mut self, at: &ArithmeticTerm) -> Result<Number, MachineStub> {
|
||||
match at {
|
||||
&ArithmeticTerm::Reg(r) => {
|
||||
let value = self.store(self.deref(self[r]));
|
||||
let value = self.store(self.deref(self[r]));
|
||||
|
||||
match Number::try_from(value) {
|
||||
Ok(n) => Ok(n),
|
||||
Err(_) => self.arith_eval_by_metacall(value),
|
||||
}
|
||||
}
|
||||
&ArithmeticTerm::Interm(i) => {
|
||||
Ok(mem::replace(&mut self.interms[i - 1], Number::Fixnum(Fixnum::build_with(0))))
|
||||
}
|
||||
&ArithmeticTerm::Number(n) => Ok(n),
|
||||
&ArithmeticTerm::Interm(i) => Ok(mem::replace(
|
||||
&mut self.interms[i - 1],
|
||||
Number::Fixnum(Fixnum::build_with(0)),
|
||||
)),
|
||||
ArithmeticTerm::Number(n) => Ok(*n),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1102,41 +1134,53 @@ impl MachineState {
|
||||
|
||||
match rational_from_number(n, caller, &mut self.arena) {
|
||||
Ok(r) => Ok(r),
|
||||
Err(e_gen) => Err(e_gen(self))
|
||||
Err(e_gen) => Err(e_gen(self)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn arith_eval_by_metacall(&mut self, value: HeapCellValue) -> Result<Number, MachineStub> {
|
||||
pub(crate) fn arith_eval_by_metacall(
|
||||
&mut self,
|
||||
value: HeapCellValue,
|
||||
) -> Result<Number, MachineStub> {
|
||||
let stub_gen = || functor_stub(atom!("is"), 2);
|
||||
let mut iter = stackless_post_order_iter(&mut self.heap, value);
|
||||
let mut iter =
|
||||
stackful_post_order_iter::<NonListElider>(&mut self.heap, &mut self.stack, value);
|
||||
|
||||
while let Some(value) = iter.next() {
|
||||
if value.is_forwarded() {
|
||||
if value.get_forwarding_bit() {
|
||||
std::mem::drop(iter);
|
||||
|
||||
let (name, arity) = read_heap_cell!(value,
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
(name, arity)
|
||||
}
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::PStr) => {
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
cell_as_atom_cell!(self.heap[s]).get_name_and_arity()
|
||||
}
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset |
|
||||
HeapCellValueTag::PStrLoc) => {
|
||||
(atom!("."), 2)
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var | HeapCellValueTag::StackVar) => {
|
||||
let err = self.instantiation_error();
|
||||
return Err(self.error_form(err, stub_gen()));
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
);
|
||||
|
||||
std::mem::drop(iter);
|
||||
|
||||
let evaluable_error = self.evaluable_error(name, arity);
|
||||
let stub = stub_gen();
|
||||
|
||||
return Err(self.error_form(evaluable_error, stub));
|
||||
return Err(self.error_form(evaluable_error, stub_gen()));
|
||||
}
|
||||
|
||||
let value = unmark_cell_bits!(value);
|
||||
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
if arity == 2 {
|
||||
let a1 = self.interms.pop().unwrap();
|
||||
let a2 = self.interms.pop().unwrap();
|
||||
let a1 = self.interms.pop().unwrap();
|
||||
|
||||
match name {
|
||||
atom!("+") => self.interms.push(drop_iter_on_err!(
|
||||
@@ -1184,7 +1228,7 @@ impl MachineState {
|
||||
|
||||
let result = arena_alloc!(
|
||||
drop_iter_on_err!(self, iter, rdiv(r1, r2)),
|
||||
self.arena
|
||||
&mut self.arena
|
||||
);
|
||||
|
||||
self.interms.push(Number::Rational(result));
|
||||
@@ -1249,6 +1293,12 @@ impl MachineState {
|
||||
atom!("tan") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, tan(a1))
|
||||
))),
|
||||
atom!("float_fractional_part") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, float_fractional_part(a1))
|
||||
))),
|
||||
atom!("float_integer_part") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, float_integer_part(a1))
|
||||
))),
|
||||
atom!("sqrt") => self.interms.push(Number::Float(OrderedFloat(
|
||||
drop_iter_on_err!(self, iter, sqrt(a1))
|
||||
))),
|
||||
@@ -1278,7 +1328,7 @@ impl MachineState {
|
||||
atom!("\\") => self.interms.push(
|
||||
drop_iter_on_err!(self, iter, bitwise_complement(a1, &mut self.arena))
|
||||
),
|
||||
atom!("sign") => self.interms.push(sign(a1)),
|
||||
atom!("sign") => self.interms.push(a1.sign()),
|
||||
_ => {
|
||||
let evaluable_stub = functor_stub(name, 1);
|
||||
std::mem::drop(iter);
|
||||
@@ -1324,7 +1374,7 @@ impl MachineState {
|
||||
self.interms.push(Number::Fixnum(n));
|
||||
}
|
||||
(HeapCellValueTag::F64, fl) => {
|
||||
self.interms.push(Number::Float(**fl));
|
||||
self.interms.push(Number::Float(*fl));
|
||||
}
|
||||
(HeapCellValueTag::Cons, ptr) => {
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
@@ -1334,9 +1384,6 @@ impl MachineState {
|
||||
(ArenaHeaderTag::Rational, r) => {
|
||||
self.interms.push(Number::Rational(r));
|
||||
}
|
||||
(ArenaHeaderTag::F64, fl) => {
|
||||
self.interms.push(Number::Float(*fl));
|
||||
}
|
||||
_ => {
|
||||
std::mem::drop(iter);
|
||||
|
||||
@@ -1376,30 +1423,16 @@ mod tests {
|
||||
use crate::machine::mock_wam::*;
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
|
||||
fn arith_eval_by_metacall_tests() {
|
||||
let mut wam = MachineState::new();
|
||||
let mut op_dir = default_op_dir();
|
||||
|
||||
op_dir.insert(
|
||||
(atom!("+"), Fixity::In),
|
||||
OpDesc::build_with(500, YFX as u8),
|
||||
);
|
||||
op_dir.insert(
|
||||
(atom!("-"), Fixity::In),
|
||||
OpDesc::build_with(500, YFX as u8),
|
||||
);
|
||||
op_dir.insert(
|
||||
(atom!("-"), Fixity::Pre),
|
||||
OpDesc::build_with(200, FY as u8),
|
||||
);
|
||||
op_dir.insert(
|
||||
(atom!("*"), Fixity::In),
|
||||
OpDesc::build_with(400, YFX as u8),
|
||||
);
|
||||
op_dir.insert(
|
||||
(atom!("/"), Fixity::In),
|
||||
OpDesc::build_with(400, YFX as u8),
|
||||
);
|
||||
op_dir.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX as u8));
|
||||
op_dir.insert((atom!("-"), Fixity::In), OpDesc::build_with(500, YFX as u8));
|
||||
op_dir.insert((atom!("-"), Fixity::Pre), OpDesc::build_with(200, FY as u8));
|
||||
op_dir.insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX as u8));
|
||||
op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX as u8));
|
||||
|
||||
let term_write_result =
|
||||
parse_and_write_parsed_term_to_heap(&mut wam, "3 + 4 - 1 + 2.", &op_dir).unwrap();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user