Compare commits
915 Commits
v0.9.3
...
exian-v0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
896b90e57d | ||
|
|
e7ac3ae0f7 | ||
|
|
60595f990f | ||
|
|
042631cb4c | ||
|
|
32b6458deb | ||
|
|
12db664bd9 | ||
|
|
e9ff1348b1 | ||
|
|
fc6e007ad1 | ||
|
|
7637a0f7d4 | ||
|
|
ba7b54e8f8 | ||
|
|
012e3de3a1 | ||
|
|
72cdba82f5 | ||
|
|
24f431e2ca | ||
|
|
8afcc7742b | ||
|
|
e9834ffa3e | ||
|
|
28ec457159 | ||
|
|
1ece558a71 | ||
|
|
be18eda696 | ||
|
|
fdbaf6ac9d | ||
|
|
5c341ce371 | ||
|
|
5f58e1cfd7 | ||
|
|
dc05526c71 | ||
|
|
ffea37d899 | ||
|
|
368c9fd9dd | ||
|
|
c067bfa832 | ||
|
|
aa3d8b8fba | ||
|
|
14ce052bf2 | ||
|
|
252361fc20 | ||
|
|
78c08b87b6 | ||
|
|
eb91b3c1fb | ||
|
|
a140720c6f | ||
|
|
6f3f66c407 | ||
|
|
1f7a60dec9 | ||
|
|
ce9b41815d | ||
|
|
80bf276e09 | ||
|
|
8bc8171ec7 | ||
|
|
3b5b768f4a | ||
|
|
176858ad42 | ||
|
|
eba681786d | ||
|
|
f4297e365f | ||
|
|
cd777294b9 | ||
|
|
3e929511bb | ||
|
|
d1356db7e3 | ||
|
|
7b2bf73ba1 | ||
|
|
edd6d44bd6 | ||
|
|
4b5e4a2745 | ||
|
|
3a4dfc46da | ||
|
|
d8346b1651 | ||
|
|
20d52c093a | ||
|
|
35d34231f2 | ||
|
|
90e6e84312 | ||
|
|
cd501beb0b | ||
|
|
39d02a0caf | ||
|
|
e168b31963 | ||
|
|
eed0a5babb | ||
|
|
ad29f0f180 | ||
|
|
b14bda310f | ||
|
|
39cf60bc05 | ||
|
|
02b8a6010c | ||
|
|
b2cfc8b6f2 | ||
|
|
c5389dbbf2 | ||
|
|
633b86ddc4 | ||
|
|
348c5029b3 | ||
|
|
7ffd93c0d7 | ||
|
|
6338a40eed | ||
|
|
3aa272e44d | ||
|
|
52f3ddd20d | ||
|
|
f61c069c2b | ||
|
|
f1171e2fb2 | ||
|
|
e38ee14580 | ||
|
|
93cc4d4898 | ||
|
|
87f5295bd0 | ||
|
|
97607d2bcd | ||
|
|
216c251c48 | ||
|
|
8af0a19130 | ||
|
|
9485ae2ca5 | ||
|
|
18c451e11c | ||
|
|
a4c071a44b | ||
|
|
c0fb3fd429 | ||
|
|
9fb7d15f0a | ||
|
|
a8fa8993e1 | ||
|
|
af3792f79a | ||
|
|
d83827b21f | ||
|
|
ee39be4361 | ||
|
|
bc4689ca4a | ||
|
|
86dc22a850 | ||
|
|
9cc170f2ad | ||
|
|
61f8bc008c | ||
|
|
948cbd8f40 | ||
|
|
e1a52a4dde | ||
|
|
7b960a7d63 | ||
|
|
c5fd4c1d8a | ||
|
|
7dc6d6e4e0 | ||
|
|
1d643d8385 | ||
|
|
badd6304c7 | ||
|
|
a8da2b6a99 | ||
|
|
a43bac8fd8 | ||
|
|
72d5574ff0 | ||
|
|
a2e19bd483 | ||
|
|
d907f86c8d | ||
|
|
fadd3a0839 | ||
|
|
45041be336 | ||
|
|
b2d639b159 | ||
|
|
3a6b92d227 | ||
|
|
409159d68d | ||
|
|
143f32be23 | ||
|
|
d0a6dc9df2 | ||
|
|
62e43a3ab0 | ||
|
|
eb82d1b6d4 | ||
|
|
70d65bcea8 | ||
|
|
25edde2eb4 | ||
|
|
ab675e071a | ||
|
|
dc08a4ab11 | ||
|
|
1120bcde29 | ||
|
|
dc495f10f8 | ||
|
|
d2ffd4f4bf | ||
|
|
87dbad2294 | ||
|
|
ca52c65902 | ||
|
|
7f233da10c | ||
|
|
ff546a0f9a | ||
|
|
746d4fd106 | ||
|
|
97e2e5d7f0 | ||
|
|
1ee4f7a55f | ||
|
|
43dd1587fb | ||
|
|
8971809c83 | ||
|
|
e321f29b9c | ||
|
|
129c80bf16 | ||
|
|
183761eba4 | ||
|
|
6c7833b9c7 | ||
|
|
82e989f631 | ||
|
|
2b052bf8dd | ||
|
|
003e9d4610 | ||
|
|
d79ece2497 | ||
|
|
ae0baf4893 | ||
|
|
1114700ce6 | ||
|
|
dea43a0244 | ||
|
|
0c2c6124eb | ||
|
|
9cccd509e3 | ||
|
|
36bdab84ba | ||
|
|
cf51338a77 | ||
|
|
f738b42e49 | ||
|
|
f45426e8ab | ||
|
|
3d2439c92b | ||
|
|
6205f2f1f1 | ||
|
|
760e1d2aac | ||
|
|
e1246f0c83 | ||
|
|
bd1f8bb37e | ||
|
|
620459ea1e | ||
|
|
d2502b0520 | ||
|
|
34eab2e73a | ||
|
|
db3f2717bc | ||
|
|
7227e1d97c | ||
|
|
9d8906da30 | ||
|
|
f524062e5b | ||
|
|
eb29390a79 | ||
|
|
f91aedcc2f | ||
|
|
4f04d2dfca | ||
|
|
de89a78cbb | ||
|
|
e7600884fb | ||
|
|
3365cffa96 | ||
|
|
df3f1236f7 | ||
|
|
8766851919 | ||
|
|
014d1b0095 | ||
|
|
c885d1a7e7 | ||
|
|
7593f88d5a | ||
|
|
6885074006 | ||
|
|
73cc872b50 | ||
|
|
fae5e13bd5 | ||
|
|
6d7c217227 | ||
|
|
166ec02973 | ||
|
|
1d26f98688 | ||
|
|
58b9a6ad69 | ||
|
|
a639fec153 | ||
|
|
495bcd73f2 | ||
|
|
ae3019d923 | ||
|
|
72631b3e0b | ||
|
|
8cc74b2af7 | ||
|
|
7199e9a2db | ||
|
|
edf21494d9 | ||
|
|
b364c9073a | ||
|
|
9b293cf1b6 | ||
|
|
65dda019a2 | ||
|
|
4aa2a45736 | ||
|
|
4efceda8c5 | ||
|
|
5dc8983eec | ||
|
|
49b30e9f81 | ||
|
|
b78ccf8f28 | ||
|
|
2dc66d33dd | ||
|
|
e303e5a805 | ||
|
|
762b63e1f4 | ||
|
|
2844b5a958 | ||
|
|
1332611f83 | ||
|
|
725def07cd | ||
|
|
cbdd0fbf15 | ||
|
|
87ca083cfa | ||
|
|
d01557f3c1 | ||
|
|
8b009448f4 | ||
|
|
709dc041eb | ||
|
|
8763a42c98 | ||
|
|
d6b6eda77d | ||
|
|
4e0493474e | ||
|
|
a9847eef65 | ||
|
|
cb9b988905 | ||
|
|
073ec4c1d0 | ||
|
|
d51cbd67e0 | ||
|
|
e9bb41f0d6 | ||
|
|
b12d1ece17 | ||
|
|
706ffe7ad9 | ||
|
|
258244caf2 | ||
|
|
39ea2d5899 | ||
|
|
41b90c6e46 | ||
|
|
6bcbb91e08 | ||
|
|
e1e52338d6 | ||
|
|
995a39419a | ||
|
|
4644ea2404 | ||
|
|
f3cd6326a3 | ||
|
|
bf31d3f9a9 | ||
|
|
4c46f0e54d | ||
|
|
e563fd8e4f | ||
|
|
1297a24469 | ||
|
|
907e64e185 | ||
|
|
46f259bf7d | ||
|
|
634ccf22aa | ||
|
|
16b3bea48e | ||
|
|
e20363b462 | ||
|
|
549a26dd03 | ||
|
|
8981ab72d1 | ||
|
|
98dae9aecc | ||
|
|
22080f3787 | ||
|
|
9e1e99f961 | ||
|
|
eef7b06919 | ||
|
|
2ad870c740 | ||
|
|
3dee07f648 | ||
|
|
9daf016d63 | ||
|
|
fadcfb966e | ||
|
|
57f76169fd | ||
|
|
ae4d12a123 | ||
|
|
3c85ef2724 | ||
|
|
15f1320d05 | ||
|
|
7b357ba84d | ||
|
|
c0cd371056 | ||
|
|
79be0c0625 | ||
|
|
c0f72704ec | ||
|
|
f7bbdfe73a | ||
|
|
1ef681bd21 | ||
|
|
34ac85bb6d | ||
|
|
a69303dc93 | ||
|
|
778068129f | ||
|
|
ce56b5ed6b | ||
|
|
8e01eea440 | ||
|
|
1cf5de8f5c | ||
|
|
686f20db74 | ||
|
|
17377064cf | ||
|
|
f49a0ae79b | ||
|
|
4b480a7eab | ||
|
|
4a6f86591c | ||
|
|
37ac5bb3c1 | ||
|
|
9456517bed | ||
|
|
8f514ce419 | ||
|
|
e40125ff3a | ||
|
|
c790bbfce9 | ||
|
|
b767e4de25 | ||
|
|
ca173873af | ||
|
|
036c59e5fa | ||
|
|
7809edca19 | ||
|
|
a42a88a2c9 | ||
|
|
9dab070c11 | ||
|
|
ec28872a9a | ||
|
|
eb96a0c957 | ||
|
|
90f4716cc3 | ||
|
|
3a6289324e | ||
|
|
f496abbbef | ||
|
|
f1793744bc | ||
|
|
bd4b305649 | ||
|
|
638a765937 | ||
|
|
78fec46517 | ||
|
|
00a44921db | ||
|
|
d79bc199b3 | ||
|
|
4da0f90097 | ||
|
|
d18ef52e67 | ||
|
|
925921e9ab | ||
|
|
b98076b39e | ||
|
|
3090cb0084 | ||
|
|
971521791c | ||
|
|
2c8041250d | ||
|
|
1b22c89211 | ||
|
|
4e207fc34a | ||
|
|
8558bd2214 | ||
|
|
ba9fed3eae | ||
|
|
024c498bee | ||
|
|
8c348a7160 | ||
|
|
1f82dafcb6 | ||
|
|
d2dfb7ccda | ||
|
|
20525e5013 | ||
|
|
14c8fc3e34 | ||
|
|
3d60094330 | ||
|
|
ae27e78d80 | ||
|
|
3826ef38cd | ||
|
|
7b6f4439bb | ||
|
|
464cfd9213 | ||
|
|
9dbf592a58 | ||
|
|
e273b007a2 | ||
|
|
6df4eaf809 | ||
|
|
165d47c7b5 | ||
|
|
a45a1f6197 | ||
|
|
16187390cd | ||
|
|
57bf1ec39b | ||
|
|
7ac84d3565 | ||
|
|
32d91449b5 | ||
|
|
3799974b82 | ||
|
|
9da0a06afa | ||
|
|
b8526e0510 | ||
|
|
2d4fcf9714 | ||
|
|
4f3843742c | ||
|
|
a0e3283d64 | ||
|
|
c9a0f3332e | ||
|
|
aeacccbe4c | ||
|
|
5d468d3e19 | ||
|
|
080a4fed03 | ||
|
|
3b89c1124d | ||
|
|
236ed93c6c | ||
|
|
cea8e6b1c9 | ||
|
|
93e804eeaa | ||
|
|
b32273fc79 | ||
|
|
4fc4152eac | ||
|
|
914318b14e | ||
|
|
3fc5709c50 | ||
|
|
28926486e0 | ||
|
|
ad211d5e05 | ||
|
|
5386c183d2 | ||
|
|
0a2457943e | ||
|
|
baae1dca15 | ||
|
|
4e032c8a28 | ||
|
|
7a6620b52d | ||
|
|
dd6533e76c | ||
|
|
d262159399 | ||
|
|
c77ea48fbd | ||
|
|
35c1880937 | ||
|
|
e5cca9d10a | ||
|
|
f45b0bcfe8 | ||
|
|
8966e175f1 | ||
|
|
2a218f34b9 | ||
|
|
d8213e29c5 | ||
|
|
7f2ce57ba7 | ||
|
|
949d316773 | ||
|
|
0cf46d3ec4 | ||
|
|
e68ac8347f | ||
|
|
d4bf52e82c | ||
|
|
22538a05be | ||
|
|
6d80c843e6 | ||
|
|
b8ccebbf24 | ||
|
|
7108e87e92 | ||
|
|
fdc35f8b40 | ||
|
|
2fe7b55343 | ||
|
|
f509d3d66f | ||
|
|
079a69396a | ||
|
|
c548b14f50 | ||
|
|
590d0daa24 | ||
|
|
7a5a5bb795 | ||
|
|
5a869e8b48 | ||
|
|
28678e708b | ||
|
|
f69c58c6b6 | ||
|
|
0cde8f9a43 | ||
|
|
20735ea34c | ||
|
|
f3a92c6551 | ||
|
|
e2d1a2b6bc | ||
|
|
d3361c16d8 | ||
|
|
9420c7e41e | ||
|
|
a7598184bf | ||
|
|
bc0f614bf9 | ||
|
|
b1e66f686b | ||
|
|
b74c8139d1 | ||
|
|
75d499bf8a | ||
|
|
f1100271e2 | ||
|
|
143430dd9c | ||
|
|
00e6e323f8 | ||
|
|
b76bdd75e4 | ||
|
|
1ed4fe6555 | ||
|
|
894405149b | ||
|
|
eff094cc29 | ||
|
|
6cedbc33c0 | ||
|
|
2856cc4371 | ||
|
|
a683d3e363 | ||
|
|
2257503951 | ||
|
|
531d482ba7 | ||
|
|
28c386422b | ||
|
|
edaba024b2 | ||
|
|
333d6740ae | ||
|
|
56cb27f71c | ||
|
|
d57f871a12 | ||
|
|
afc7b74bbd | ||
|
|
d287095e61 | ||
|
|
a599a11169 | ||
|
|
b7943837a0 | ||
|
|
e01a4cdc85 | ||
|
|
555dff5bad | ||
|
|
75ac3fffae | ||
|
|
8dc80b815f | ||
|
|
6175e334a5 | ||
|
|
7e22c12a8d | ||
|
|
b5030be34f | ||
|
|
c59f54252c | ||
|
|
a9a77779e3 | ||
|
|
5f035ec6ad | ||
|
|
9eef380764 | ||
|
|
ce6a0a1541 | ||
|
|
b919a39b96 | ||
|
|
84fc639f31 | ||
|
|
a15b2789e2 | ||
|
|
2e910de485 | ||
|
|
74e3d32ca9 | ||
|
|
500a6cdaf1 | ||
|
|
17293a5753 | ||
|
|
a75266c3ce | ||
|
|
a92eac919f | ||
|
|
2ff50f06bc | ||
|
|
e219e1c483 | ||
|
|
eef9f74a68 | ||
|
|
eae1f53846 | ||
|
|
48a42ae140 | ||
|
|
44facf07ab | ||
|
|
9265d66f58 | ||
|
|
3d3baeed82 | ||
|
|
e21c772181 | ||
|
|
2f82c78bc0 | ||
|
|
ec6286ffab | ||
|
|
bb5adba4ef | ||
|
|
33d8abfed8 | ||
|
|
d336cbc86a | ||
|
|
79fbd9e90f | ||
|
|
cab61738ad | ||
|
|
e6cc408ff2 | ||
|
|
dc8348b843 | ||
|
|
cb040dafc1 | ||
|
|
c13fc2d1b6 | ||
|
|
1375f448a0 | ||
|
|
0433706db6 | ||
|
|
e74fd11b0d | ||
|
|
4480e7c066 | ||
|
|
71dec62ce2 | ||
|
|
658e39aae6 | ||
|
|
29fc55cb28 | ||
|
|
8e7dc9dba8 | ||
|
|
abe1f37167 | ||
|
|
8cde3f8ce3 | ||
|
|
dba9c9a0a7 | ||
|
|
ea02b54e7f | ||
|
|
0427d9ef11 | ||
|
|
c1571bf063 | ||
|
|
cef45de74a | ||
|
|
d7bf7445f1 | ||
|
|
8e5ddebddb | ||
|
|
ff03432624 | ||
|
|
7fd409140c | ||
|
|
9f6bbe949c | ||
|
|
e78914219e | ||
|
|
620077d8c1 | ||
|
|
556b7c733f | ||
|
|
e577a5430b | ||
|
|
e8ef9bc33a | ||
|
|
7b18b1d47a | ||
|
|
c39ea4818a | ||
|
|
90d418a181 | ||
|
|
9785a04b3a | ||
|
|
6dd0ec0b98 | ||
|
|
e9f03c89a6 | ||
|
|
8f41c1aa7e | ||
|
|
442636c131 | ||
|
|
190929e4ac | ||
|
|
2f47343cb5 | ||
|
|
3bc1885606 | ||
|
|
0552530b41 | ||
|
|
951c88d5f3 | ||
|
|
383eb5bdd9 | ||
|
|
3fa6b69a79 | ||
|
|
0bc3d67298 | ||
|
|
a898d7a667 | ||
|
|
238f1bd639 | ||
|
|
4b0f9558b3 | ||
|
|
c922ed8b0a | ||
|
|
3af24a8067 | ||
|
|
fdd6579230 | ||
|
|
16734ec7e4 | ||
|
|
d32f3ee2f3 | ||
|
|
ada9ba98cc | ||
|
|
a885e87ef7 | ||
|
|
e369c15a27 | ||
|
|
afa7af3de6 | ||
|
|
d3071988b2 | ||
|
|
08aed04dd2 | ||
|
|
7af93f1532 | ||
|
|
2b73107631 | ||
|
|
61c258f49e | ||
|
|
6f0c7f42c6 | ||
|
|
7b09d447d6 | ||
|
|
9f4d31a825 | ||
|
|
806e980cca | ||
|
|
12a61cdff8 | ||
|
|
808ea9fc79 | ||
|
|
e1d830d680 | ||
|
|
6be671d7d9 | ||
|
|
5c39e2a5a1 | ||
|
|
e24396d78a | ||
|
|
264e3e9dd6 | ||
|
|
3f25ee3749 | ||
|
|
c24a51b633 | ||
|
|
8925133f40 | ||
|
|
2faab30af9 | ||
|
|
c1b88cee99 | ||
|
|
8736526b35 | ||
|
|
7afc08af47 | ||
|
|
12aaae5fc0 | ||
|
|
e0ec5e75b4 | ||
|
|
7092e278d7 | ||
|
|
82a7d7efbd | ||
|
|
37a906cf37 | ||
|
|
bacfed8983 | ||
|
|
ed1b25cb30 | ||
|
|
bc262db6d7 | ||
|
|
86a2c5e2ed | ||
|
|
e77c97e0d1 | ||
|
|
48f3a7b87e | ||
|
|
e5aaa9d277 | ||
|
|
938401dbc6 | ||
|
|
620bf385d1 | ||
|
|
cc51b8150d | ||
|
|
775b14c537 | ||
|
|
7cfe8ee55c | ||
|
|
058b9c5e51 | ||
|
|
deed6fb984 | ||
|
|
2d04c0dcf8 | ||
|
|
5a0abd713e | ||
|
|
df33da111e | ||
|
|
17a816eff4 | ||
|
|
880e22d164 | ||
|
|
c16d2d4147 | ||
|
|
59af900aaf | ||
|
|
add03cb4ee | ||
|
|
409f56ea37 | ||
|
|
2814d79b56 | ||
|
|
4009b40029 | ||
|
|
260c52adec | ||
|
|
b1f057b67d | ||
|
|
177bc16255 | ||
|
|
4ce4cd1bc1 | ||
|
|
9e8d8aa19e | ||
|
|
4d3ceb387e | ||
|
|
5038b716eb | ||
|
|
e310f9683e | ||
|
|
c29b76143c | ||
|
|
3ad4b05a97 | ||
|
|
0da313eb84 | ||
|
|
6b50ec328c | ||
|
|
1eb6a883f6 | ||
|
|
e7fd408548 | ||
|
|
eaa95293e4 | ||
|
|
8ac663d703 | ||
|
|
902712695c | ||
|
|
5d03be4b09 | ||
|
|
689632b51c | ||
|
|
1b19ae81d5 | ||
|
|
a0d790445d | ||
|
|
213ee5ca45 | ||
|
|
74720d4d2e | ||
|
|
39bd520542 | ||
|
|
496e4e9f56 | ||
|
|
89c1ea4232 | ||
|
|
a2a50586aa | ||
|
|
0284a2092d | ||
|
|
c414f75329 | ||
|
|
943dce566b | ||
|
|
b32498b37c | ||
|
|
ea041a40f9 | ||
|
|
1d66f91a41 | ||
|
|
fee7ba58b0 | ||
|
|
8e53d12776 | ||
|
|
08a9f7c068 | ||
|
|
33793193cc | ||
|
|
285f11ccdc | ||
|
|
d87400afa0 | ||
|
|
fcb41542c3 | ||
|
|
87b4c9d736 | ||
|
|
6386e70584 | ||
|
|
722975d77e | ||
|
|
8943962704 | ||
|
|
ed57ef3d01 | ||
|
|
9e7d41025a | ||
|
|
d2f236d116 | ||
|
|
1ff995fedb | ||
|
|
1b16c6c73f | ||
|
|
ceac9c9095 | ||
|
|
0005dcaacb | ||
|
|
467a82fe46 | ||
|
|
ce40f8f10c | ||
|
|
d2d451b182 | ||
|
|
52fa51853e | ||
|
|
6575b1b573 | ||
|
|
cba81b4dc7 | ||
|
|
91253917b4 | ||
|
|
821358c062 | ||
|
|
3de0a4e2ad | ||
|
|
b8a3067744 | ||
|
|
7509cc1a07 | ||
|
|
dd2548453b | ||
|
|
a93585080c | ||
|
|
5f30c9599a | ||
|
|
4b3edfd370 | ||
|
|
d6ac03552d | ||
|
|
a72632ae85 | ||
|
|
2d18e0d719 | ||
|
|
45a8a7bb31 | ||
|
|
1a9d10d298 | ||
|
|
80a0cab6ab | ||
|
|
96720457d0 | ||
|
|
f4d2839dc7 | ||
|
|
070f1e8dfa | ||
|
|
42a0d686a5 | ||
|
|
79bc2d9c68 | ||
|
|
f3eea6302d | ||
|
|
bcfa0219e2 | ||
|
|
5e1effc69a | ||
|
|
be23c74b17 | ||
|
|
8fe1c206d3 | ||
|
|
a6157a926d | ||
|
|
9837187183 | ||
|
|
c2657cce37 | ||
|
|
7d6109feca | ||
|
|
cf14b222f2 | ||
|
|
238343f389 | ||
|
|
77e90ca725 | ||
|
|
f734c273ca | ||
|
|
e23e5203de | ||
|
|
a2dd189241 | ||
|
|
0ed4e41000 | ||
|
|
352919862f | ||
|
|
aab6567750 | ||
|
|
ea9e24f214 | ||
|
|
40fc4f9392 | ||
|
|
795a7c690d | ||
|
|
2fdbb94c79 | ||
|
|
767d90d5e7 | ||
|
|
bc04790fd5 | ||
|
|
58f7c94e74 | ||
|
|
8af29c200d | ||
|
|
1db6bff890 | ||
|
|
0292cb73d3 | ||
|
|
7b33eafbb0 | ||
|
|
ee60c463c8 | ||
|
|
e1a6b5eb03 | ||
|
|
4ebdd97ad3 | ||
|
|
6f7f979c05 | ||
|
|
ca8e280b4c | ||
|
|
b26e8b0e34 | ||
|
|
df41eb1870 | ||
|
|
90c602f867 | ||
|
|
6b8a679a51 | ||
|
|
8b16a0e1f6 | ||
|
|
ec251b254c | ||
|
|
e0c8163211 | ||
|
|
01f757c940 | ||
|
|
42f4827854 | ||
|
|
f2d3779fa9 | ||
|
|
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 |
0
.dockerignore
Executable file → Normal file
0
.dockerignore
Executable file → Normal file
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
|
||||
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@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ inputs.cache-context }}_${{ inputs.targets }}_rustc-${{ steps.toolchain.outputs.cachekey }}_cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
# Remove build artifacts for the current crate, since it will be rebuilt every
|
||||
# run anyway, but keep dependency artifacts to cache them.
|
||||
# Must be placed after actions/cache so its post step runs first.
|
||||
- uses: pyTooling/Actions/with-post-step@v4.1.0
|
||||
with:
|
||||
main: bash ./.github/actions/setup-rust/cleanup.sh
|
||||
post: bash ./.github/actions/setup-rust/cleanup.sh
|
||||
13
.github/actions/setup-rust/cleanup.sh
vendored
Executable file
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
|
||||
215
.github/workflows/ci.yml
vendored
215
.github/workflows/ci.yml
vendored
@@ -2,7 +2,9 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches:
|
||||
- master
|
||||
- rebis-dev
|
||||
tags:
|
||||
- "v**"
|
||||
pull_request:
|
||||
@@ -10,87 +12,84 @@ on:
|
||||
- cron: '0 0 * * 3' # At 12:00 AM, only on Wednesday
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
style:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: stable
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
components: clippy, rustfmt
|
||||
cache-context: style
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --check
|
||||
- name: Check clippy
|
||||
run: cargo clippy --no-deps --all-targets
|
||||
if: ${{ !cancelled() }}
|
||||
|
||||
read-msrv:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
msrv: ${{ steps.read-declared-msrv.outputs.msrv }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: read-declared-msrv
|
||||
name: Read msrv from Cargo.toml rust_version field
|
||||
run: echo "msrv=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name = "scryer-prolog") | ."rust_version"')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
build-test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: [read-msrv]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# operating systems
|
||||
- { os: windows-latest, rust-version: stable, publish: true, target: 'x86_64-pc-windows-msvc'}
|
||||
- { os: macos-11, rust-version: stable, publish: true, target: 'x86_64-apple-darwin' }
|
||||
- { os: ubuntu-20.04, rust-version: stable, publish: true, target: 'x86_64-unknown-linux-gnu' }
|
||||
- { os: windows-latest, rust-version: stable, target: 'x86_64-pc-windows-msvc', publish: true }
|
||||
- { os: macos-latest, rust-version: stable, target: 'x86_64-apple-darwin', publish: true }
|
||||
# architectures
|
||||
- { os: ubuntu-22.04, rust-version: stable, publish: true, target: 'x86_64-unknown-linux-gnu', extra: true }
|
||||
- { os: ubuntu-22.04, rust-version: stable, publish: true, target: 'i686-unknown-linux-gnu' }
|
||||
- { os: ubuntu-22.04, rust-version: nightly, publish: true, target: 'wasm32-unknown-unknown', args: '--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: stable, target: 'x86_64-unknown-linux-gnu', publish: true }
|
||||
- { os: ubuntu-22.04, rust-version: stable, target: 'i686-unknown-linux-gnu', publish: true }
|
||||
# FIXME(issue #2138): run wasm tests, failing to run since https://github.com/mthom/scryer-prolog/pull/2137 removed wasm-pack
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features', use_swap: true }
|
||||
# Cargo.toml rust-version
|
||||
- { os: ubuntu-22.04, rust-version: "${{ needs.read-msrv.outputs.msrv }}" , target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri"}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
id: toolchain
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actionhippie/swap-space@v1
|
||||
if: matrix.use_swap
|
||||
with:
|
||||
toolchain: ${{ matrix.rust-version }}
|
||||
size: 10G
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: ${{ matrix.rust-version }}
|
||||
targets: ${{ matrix.target }}
|
||||
components: clippy, rustfmt
|
||||
- name: Install i686 dependencies
|
||||
if: "contains(matrix.target,'i686')"
|
||||
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: ${{ matrix.os }}_${{ matrix.target }}_rustc-${{ steps.toolchain.outputs.cachekey }}_cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
cache-context: ${{ matrix.os }}
|
||||
components: ${{ matrix.components }}
|
||||
|
||||
# Build and test.
|
||||
- name: Build library
|
||||
run: cargo rustc --lib --target ${{ matrix.target }} ${{ matrix.args }} --verbose
|
||||
run: cargo build --all-targets --target ${{ matrix.target }} ${{ matrix.args }} --verbose
|
||||
- name: Test
|
||||
if: "!matrix.extra"
|
||||
run: cargo test --target ${{ matrix.target }} ${{ matrix.args }} --all --verbose || echo "::warning ::Tests failed"
|
||||
run: cargo test --target ${{ matrix.target }} ${{ matrix.test-args }} --all
|
||||
|
||||
# Extra steps only run once to avoid duplication, when matrix.extra is true
|
||||
- name: Test and report
|
||||
if: matrix.extra
|
||||
run: |
|
||||
cargo install cargo2junit --force
|
||||
RUSTC_BOOTSTRAP=1 cargo test --all -- -Z unstable-options --format json --report-time | cargo2junit > cargo_test_results.xml
|
||||
- name: Publish cargo test results artifact
|
||||
if: matrix.extra
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: cargo-test-results
|
||||
path: cargo_test_results.xml
|
||||
- name: Publish cargo test summary
|
||||
if: matrix.extra
|
||||
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
|
||||
- name: Check formatting
|
||||
if: matrix.extra
|
||||
run: cargo fmt --check || echo "::warning ::cargo fmt found some formatting changes that may improve readability"
|
||||
- name: Check clippy
|
||||
if: matrix.extra
|
||||
run: cargo clippy --no-deps || echo "::warning ::cargo clippy found some code style changes that may be more idiomatic"
|
||||
- name: Check miri
|
||||
if: matrix.miri
|
||||
run: cargo miri test
|
||||
|
||||
# On stable rust builds, build a binary and publish as a github actions
|
||||
# artifact. These binaries could be useful for testing the pipeline but
|
||||
@@ -100,25 +99,36 @@ jobs:
|
||||
run: |
|
||||
cargo rustc --target ${{ matrix.target }} ${{ matrix.args }} --verbose --bin scryer-prolog --release
|
||||
echo "$PWD/target/release" >> $GITHUB_PATH
|
||||
|
||||
- name: Install cargo-deb for creating debian packages
|
||||
if: ${{ matrix.publish && contains(matrix.target, 'linux') }}
|
||||
run: cargo install cargo-deb --force --locked
|
||||
|
||||
- name: Build release debian packages
|
||||
if: ${{ matrix.publish && contains(matrix.target, 'linux') }}
|
||||
run: cargo deb --target ${{ matrix.target }}
|
||||
|
||||
- name: Publish release binary artifact
|
||||
if: matrix.publish
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: target/${{ matrix.target }}/release/scryer-prolog*
|
||||
path: |
|
||||
target/${{ matrix.target }}/release/scryer-prolog*
|
||||
target/${{ matrix.target }}/debian/scryer-prolog*.deb
|
||||
name: scryer-prolog_${{ matrix.os }}_${{ matrix.target }}
|
||||
|
||||
logtalk-test:
|
||||
# if: false # uncomment to disable job
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [build-test]
|
||||
steps:
|
||||
# Download prebuilt ubuntu binary from build-test job, setup logtalk
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu
|
||||
name: scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu
|
||||
- run: |
|
||||
chmod +x scryer-prolog
|
||||
echo "$PWD" >> "$GITHUB_PATH"
|
||||
chmod +x release/scryer-prolog
|
||||
echo "$PWD/release" >> "$GITHUB_PATH"
|
||||
- name: Install Logtalk
|
||||
uses: logtalk-actions/setup-logtalk@master
|
||||
with:
|
||||
@@ -137,12 +147,12 @@ jobs:
|
||||
|| echo "::warning ::logtalk compliance suite failed"
|
||||
# -u "https://github.com/LogtalkDotOrg/logtalk3/tree/$LOGTALK_GIT_HASH/tests/prolog/" \
|
||||
- name: Publish Logtalk test logs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: logtalk-test-logs
|
||||
path: '${{ env.LOGTALKUSER }}/tests/prolog/logtalk_tester_logs'
|
||||
- name: Publish Logtalk test results artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: logtalk-test-results
|
||||
path: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
|
||||
@@ -154,26 +164,75 @@ jobs:
|
||||
fail_on: nothing
|
||||
comment_mode: off
|
||||
|
||||
report:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Rust
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
rust-version: stable
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
cache-context: report
|
||||
- name: Install CLI tools
|
||||
run: |
|
||||
cargo install cargo2junit --force
|
||||
version=`yq -ptoml -oj -r '.package[] | select(.name == "iai-callgrind") | .version' Cargo.lock`
|
||||
echo installing iai-callgrind "$version"
|
||||
cargo install iai-callgrind-runner --force --version "$version"
|
||||
sudo apt install valgrind -y
|
||||
|
||||
- name: Test and report
|
||||
run: |
|
||||
RUSTC_BOOTSTRAP=1 cargo test --all -- -Z unstable-options --format json --report-time | cargo2junit > cargo_test_results.xml
|
||||
- name: Publish cargo test results artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cargo-test-results
|
||||
path: cargo_test_results.xml
|
||||
- name: Publish cargo test summary
|
||||
uses: EnricoMi/publish-unit-test-result-action/composite@master
|
||||
with:
|
||||
check_name: Cargo test summary
|
||||
files: cargo_test_results.xml
|
||||
fail_on: nothing
|
||||
comment_mode: off
|
||||
|
||||
- run: cargo build --all-targets --release
|
||||
- run: cargo test --bench setup --release
|
||||
- run: cargo bench --bench run_iai -- --save-summary=json
|
||||
- run: cargo bench --bench run_criterion
|
||||
- run: cargo bench --bench run_criterion -- --profile-time 60
|
||||
|
||||
- name: Publish benchmark results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark-results
|
||||
path: |
|
||||
target/criterion/*
|
||||
target/iai/*
|
||||
target/benchmark_inference_counts.json
|
||||
|
||||
# Publish binaries when building for a tag
|
||||
release:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [build-test]
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Zip binaries for release
|
||||
run: |
|
||||
zip scryer-prolog_macos-11.zip ./scryer-prolog_macos-11_x86_64-apple-darwin/scryer-prolog
|
||||
zip scryer-prolog_ubuntu-20.04.zip ./scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu/scryer-prolog
|
||||
zip scryer-prolog_ubuntu-22.04.zip ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/scryer-prolog
|
||||
zip scryer-prolog_macos-latest.zip ./scryer-prolog_macos-latest_x86_64-apple-darwin/scryer-prolog
|
||||
zip scryer-prolog_ubuntu-22.04_i686.zip ./scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu/release/scryer-prolog ./scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu/debian/scryer-prolog*.deb
|
||||
zip scryer-prolog_ubuntu-22.04_x86_64.zip ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/release/scryer-prolog ./scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu/debian/scryer-prolog*.deb
|
||||
zip scryer-prolog_windows-latest.zip ./scryer-prolog_windows-latest_x86_64-pc-windows-msvc/scryer-prolog.exe
|
||||
zip scryer-prolog_wasm32.zip ./scryer-prolog_ubuntu-22.04_wasm32-unknown-unknown/scryer-prolog.wasm
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
scryer-prolog_macos-11.zip
|
||||
scryer-prolog_ubuntu-20.04.zip
|
||||
scryer-prolog_ubuntu-22.04.zip
|
||||
scryer-prolog_macos-latest.zip
|
||||
scryer-prolog_ubuntu-22.04_i686.zip
|
||||
scryer-prolog_ubuntu-22.04_x86_64.zip
|
||||
scryer-prolog_windows-latest.zip
|
||||
scryer-prolog_wasm32.zip
|
||||
|
||||
16
.github/workflows/docker-publish.yml
vendored
16
.github/workflows/docker-publish.yml
vendored
@@ -14,17 +14,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Workaround: https://github.com/docker/build-push-action/issues/461
|
||||
- name: Setup Docker buildx
|
||||
# https://github.com/docker/setup-buildx-action
|
||||
uses: docker/setup-buildx-action@v2.2.1
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# Login against Docker registry
|
||||
- name: Log into registry
|
||||
# https://github.com/docker/login-action
|
||||
uses: docker/login-action@v2.1.0
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
# https://github.com/docker/metadata-action
|
||||
uses: docker/metadata-action@v4.1.1
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/scryer-prolog
|
||||
tags: |
|
||||
@@ -47,7 +47,13 @@ jobs:
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
# https://github.com/docker/build-push-action
|
||||
uses: docker/build-push-action@v3.2.0
|
||||
uses: docker/build-push-action@v6
|
||||
# v4 adds SLSA Provenance attestation which is
|
||||
# - unsupported by AWS Lambda
|
||||
# - limited support by Google Cloud Run
|
||||
# > If deploying a multi-architecture image, the manifest list must include linux/amd64.
|
||||
# see https://github.com/docker/build-push-action/releases/tag/v4.0.0
|
||||
# we might want to disable this if its a problem for someone
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
src/static_atoms.rs
|
||||
target/
|
||||
|
||||
.direnv/
|
||||
|
||||
|
||||
|
||||
3068
Cargo.lock
generated
3068
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
183
Cargo.toml
183
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "scryer-prolog"
|
||||
version = "0.9.3"
|
||||
version = "0.10.0"
|
||||
authors = ["Mark Thom <markjordanthom@gmail.com>"]
|
||||
edition = "2021"
|
||||
description = "A modern Prolog implementation written mostly in Rust."
|
||||
@@ -10,7 +10,8 @@ license = "BSD-3-Clause"
|
||||
keywords = ["prolog", "prolog-interpreter", "prolog-system"]
|
||||
categories = ["command-line-utilities"]
|
||||
build = "build/main.rs"
|
||||
rust-version = "1.70"
|
||||
# Remember to check CI
|
||||
rust-version = "1.85"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
@@ -22,94 +23,146 @@ repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
|
||||
hostname = ["dep:hostname"]
|
||||
tls = ["dep:native-tls"]
|
||||
http = ["dep:warp", "dep:reqwest"]
|
||||
rust_beta_channel = []
|
||||
crypto-full = []
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "deny", check-cfg = [
|
||||
'cfg(rust_version, values("1.87.0"))',
|
||||
] }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
indexmap = "1.0.2"
|
||||
proc-macro2 = "1.0.36"
|
||||
quote = "1.0.15"
|
||||
strum = "0.23"
|
||||
strum_macros = "0.23"
|
||||
syn = { version = "2.0.32", features = ['full', 'visit', 'extra-traits'] }
|
||||
indexmap = "2.3.0"
|
||||
proc-macro2 = "1.0.86"
|
||||
quote = "1.0.36"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
syn = { version = "2.0.72", features = ['full', 'visit', 'extra-traits'] }
|
||||
to-syn-value = "0.1.1"
|
||||
to-syn-value_derive = "0.1.1"
|
||||
version_check = "0.9.5"
|
||||
walkdir = "2"
|
||||
|
||||
[dependencies]
|
||||
bit-set = "0.5.3"
|
||||
arcu = { version = "0.1.2", features = ["thread_local_counter"] }
|
||||
base64 = "0.22.1"
|
||||
bit-set = "0.8.0"
|
||||
bitvec = "1"
|
||||
cpu-time = "1.0.0"
|
||||
dirs-next = "2.0.0"
|
||||
divrem = "0.1.0"
|
||||
fxhash = "0.2.1"
|
||||
git-version = "0.3.4"
|
||||
indexmap = "1.0.2"
|
||||
lazy_static = "1.4.0"
|
||||
lexical = "5.2.2"
|
||||
libc = "0.2.62"
|
||||
modular-bitfield = "0.11.2"
|
||||
ordered-float = "2.6.0"
|
||||
phf = { version = "0.9", features = ["macros"] }
|
||||
ref_thread_local = "0.0.0"
|
||||
ripemd160 = "0.8.0"
|
||||
sha3 = "0.8.2"
|
||||
blake2 = "0.8.1"
|
||||
crrl = "0.6.0"
|
||||
chrono = "0.4.11"
|
||||
select = "0.6.0"
|
||||
roxmltree = "0.11.0"
|
||||
base64 = "0.12.3"
|
||||
smallvec = "1.8.0"
|
||||
static_assertions = "1.1.0"
|
||||
ryu = "1.0.9"
|
||||
futures = "0.3"
|
||||
regex = "1.9.1"
|
||||
libloading = "0.7"
|
||||
derive_deref = "1.1.1"
|
||||
blake2 = "0.10.6"
|
||||
bytes = "1"
|
||||
dashu = "0.4.0"
|
||||
chrono = "0.4.38"
|
||||
cpu-time = "1.0.0"
|
||||
crrl = "0.9.0"
|
||||
dashu = { version = "0.4.2", features = ["rand"] }
|
||||
derive_more = "0.99.18"
|
||||
dirs-next = "2.0.0"
|
||||
divrem = "1.0.0"
|
||||
futures = "0.3"
|
||||
fxhash = "0.2.1"
|
||||
git-version = "0.3.9"
|
||||
indexmap = "2.3.0"
|
||||
lazy_static = "1.5.0"
|
||||
lexical = "7.0.4"
|
||||
libc = "0.2.155"
|
||||
libloading = "0.8"
|
||||
scryer-modular-bitfield = "0.11.4"
|
||||
num-order = { version = "1.2.0" }
|
||||
ordered-float = "5.0.0"
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
puruspe = "0.4.1"
|
||||
rand = "0.8.5"
|
||||
ring = { version = "0.17.8", features = ["wasm32_unknown_unknown_js"] }
|
||||
ripemd = "0.1.3"
|
||||
roxmltree = "0.20.0"
|
||||
ryu = "1.0.18"
|
||||
sha3 = "0.10.8"
|
||||
smallvec = "1.13.2"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
scraper = { version = "0.23.1", default-features = false, features = [
|
||||
"errors",
|
||||
] }
|
||||
ego-tree = "0.10.0"
|
||||
|
||||
|
||||
serde_json = "1.0.122"
|
||||
serde = "1.0.204"
|
||||
parking_lot = "0.12.4"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
libffi = { version = "3.2.0", optional = true }
|
||||
hostname = { version = "0.3.1", optional = true }
|
||||
crossterm = { version = "0.20.0", optional = true }
|
||||
ctrlc = { version = "3.2.2", optional = true }
|
||||
rustyline = { version = "12.0.0", optional = true }
|
||||
native-tls = { version = "0.2.4", optional = true }
|
||||
warp = { version = "=0.3.5", features = ["tls"], optional = true }
|
||||
reqwest = { version = "0.11.18", features = ["blocking"], optional = true }
|
||||
tokio = { version = "1.28.2", features = ["full"] }
|
||||
crossterm = { version = "0.28.1", optional = true }
|
||||
ctrlc = { version = "3.4.4", optional = true }
|
||||
hostname = { version = "0.4.0", optional = true }
|
||||
libffi = { version = "4.0.0", optional = true }
|
||||
native-tls = { version = "0.2.12", optional = true }
|
||||
# the version requirement of reqwest is kept low for compatibility with old deno versions
|
||||
# that pin reqwest to 0.11.20
|
||||
reqwest = { version = "0.11.0", optional = true }
|
||||
rustyline = { version = "14.0.0", optional = true }
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
warp = { version = "0.3.7", features = ["tls"], optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.10", features = ["js"] }
|
||||
tokio = { version = "1.28.2", features = ["sync", "macros", "io-util", "rt", "time"] }
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
tokio = { version = "1.39.2", features = [
|
||||
"sync",
|
||||
"macros",
|
||||
"io-util",
|
||||
"rt",
|
||||
"time",
|
||||
] }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1.0"
|
||||
wasm-bindgen = "0.2.87"
|
||||
wasm-bindgen = "0.2.92"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
serde-wasm-bindgen = "0.6"
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Document",
|
||||
"Window",
|
||||
"Element",
|
||||
]}
|
||||
|
||||
[target.'cfg(target_os = "wasi")'.dependencies]
|
||||
ring-wasi = { version = "0.16.25" }
|
||||
|
||||
[target.'cfg(not(target_os = "wasi"))'.dependencies]
|
||||
ring = { version = "0.16.13" }
|
||||
"Performance",
|
||||
] }
|
||||
js-sys = "0.3"
|
||||
ouroboros = "0.18"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "1.0.3"
|
||||
predicates-core = "1.0.2"
|
||||
current_platform = "0.2.0"
|
||||
maplit = "1.0.2"
|
||||
serial_test = "2.0.0"
|
||||
serial_test = "3.1.1"
|
||||
|
||||
[patch.crates-io]
|
||||
modular-bitfield = { git = "https://github.com/mthom/modular-bitfield" }
|
||||
[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
iai-callgrind = "0.12.1"
|
||||
trycmd = "0.15.6"
|
||||
|
||||
[target.'cfg(not(any(target_os = "windows", all(target_arch = "wasm32", target_os = "unknown"))))'.dev-dependencies]
|
||||
pprof = { version = "0.13.0", features = ["criterion", "flamegraph"] }
|
||||
|
||||
[profile.bench]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
debug = 2
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
lto = "off"
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
lto = "off"
|
||||
panic = "abort"
|
||||
codegen-units = 256
|
||||
|
||||
[[bench]]
|
||||
name = "run_criterion"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "run_iai"
|
||||
harness = false
|
||||
|
||||
17
Dockerfile
Executable file → Normal file
17
Dockerfile
Executable file → Normal file
@@ -1,18 +1,19 @@
|
||||
# See https://github.com/LukeMathWalker/cargo-chef
|
||||
ARG RUST_VERSION=1-buster
|
||||
FROM rust:${RUST_VERSION} as planner
|
||||
ARG DEBIAN_RELEASE=bookworm
|
||||
ARG RUST_VERSION=1-${DEBIAN_RELEASE}
|
||||
FROM rust:${RUST_VERSION} AS planner
|
||||
WORKDIR /scryer-prolog
|
||||
RUN cargo install cargo-chef
|
||||
COPY . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM rust:${RUST_VERSION} as cacher
|
||||
FROM rust:${RUST_VERSION} AS cacher
|
||||
WORKDIR /scryer-prolog
|
||||
RUN cargo install cargo-chef
|
||||
COPY --from=planner /scryer-prolog/recipe.json recipe.json
|
||||
RUN cargo chef cook --release --recipe-path recipe.json
|
||||
|
||||
FROM rust:${RUST_VERSION} as builder
|
||||
FROM rust:${RUST_VERSION} AS builder
|
||||
WORKDIR /scryer-prolog
|
||||
COPY . .
|
||||
# Copy over the cached dependencies
|
||||
@@ -20,9 +21,11 @@ COPY --from=cacher /scryer-prolog/target target
|
||||
COPY --from=cacher $CARGO_HOME $CARGO_HOME
|
||||
RUN cargo build --release --bin scryer-prolog
|
||||
|
||||
# Newer versions of Debian (i.e. bookworm) contain libssl3 instead of libssl1.1
|
||||
# which we depend on.
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:${DEBIAN_RELEASE}-slim
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends openssl \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=builder /scryer-prolog/target/release/scryer-prolog /usr/local/bin
|
||||
ENV RUST_BACKTRACE=1
|
||||
# Sanity check the binary: if it can't be executed (e.g. if there are missing libraries)
|
||||
|
||||
21
INDEX.dj
21
INDEX.dj
@@ -5,13 +5,6 @@
|
||||
X = "Scryer Prolog!".
|
||||
```
|
||||
|
||||
``` =html
|
||||
<div style="border: solid #00007f 3px;padding-left: 15px;padding-right: 15px;font-style: italic;background-color: #00007f30;">
|
||||
<h4>Scryer Prolog Meetup 2023</h4>
|
||||
<p>The first annual Scryer Prolog meetup is going to happen in Düsseldorf (Germany) on the 9th and 10th of November 2023. Join us to discover the present and future of Scryer Prolog! Participation is free, registration not required. <a href="https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2023/">More details here.</a></p>
|
||||
</div>
|
||||
```
|
||||
|
||||
{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.
|
||||
@@ -28,6 +21,7 @@ Some of the Scryer Prolog features are:
|
||||
* [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..._
|
||||
|
||||
@@ -61,11 +55,14 @@ the builtin Prolog modules and libraries in Scryer, check the documentation site
|
||||
|
||||
## Downloads
|
||||
|
||||
The latest version of Scryer Prolog is *0.9.2*. And it's already useful for lots of tasks.
|
||||
The latest version of Scryer Prolog is *0.9.4*. And it's already useful for lots of tasks.
|
||||
|
||||
| Windows | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.2/scryer-prolog_windows-latest.zip) |
|
||||
| macOS (Intel) | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.2/scryer-prolog_macos-11.zip) |
|
||||
| Linux | [Download](https://github.com/mthom/scryer-prolog/releases/download/v0.9.2/scryer-prolog_ubuntu-20.04.zip) |
|
||||
| Windows (64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_windows-latest_x86_64-pc-windows-msvc.zip) |
|
||||
| macOS (Intel) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_macos-11_x86_64-apple-darwin.zip) |
|
||||
| macOS (ARM) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog-macos-arm.zip) |
|
||||
| Linux (Ubuntu 20.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu.zip) |
|
||||
| Linux (Ubuntu 22.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu.zip) |
|
||||
| Linux (Ubuntu 22.04, 32 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu.zip) |
|
||||
|
||||
Scryer Prolog can also be compiled from source, instructions are on the [GitHub README](https://github.com/mthom/scryer-prolog). It runs on Linux, macOS and Windows. Other operating systems may work but they're not regularly tested.
|
||||
|
||||
@@ -80,4 +77,4 @@ 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)!
|
||||
or visit our #scryer IRC channel on [Libera](https://libera.chat)!
|
||||
|
||||
263
README.md
263
README.md
@@ -1,4 +1,3 @@
|
||||
|
||||
# Scryer Prolog
|
||||
|
||||
Scryer Prolog aims to become to ISO Prolog what GHC is to Haskell: an open
|
||||
@@ -6,7 +5,10 @@ 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.
|
||||
|
||||
As of July 2023, **Scryer Prolog passes all [syntactic conformity tests](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing)**.
|
||||
**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)
|
||||
|
||||
@@ -70,7 +72,7 @@ Extend Scryer Prolog to include the following, among other features:
|
||||
(`atom`, `var`, etc) with if/else ladders. (_in progress_)
|
||||
- [ ] Inlining all built-ins and system call instructions.
|
||||
- [x] Greatly reducing the number of instructions used to compile disjunctives.
|
||||
- [ ] Storing short atoms to heap cells without writing them to the atom table.
|
||||
- [x] Storing short atoms to heap cells without writing them to the atom table.
|
||||
- [ ] A compacting garbage collector satisfying the five properties of
|
||||
"[Precise Garbage Collection in Prolog](https://www.complang.tuwien.ac.at/ulrich/papers/PDF/2008-ciclops.pdf)." (_in progress_)
|
||||
- [ ] Mode declarations.
|
||||
@@ -110,12 +112,12 @@ strings.
|
||||
Precompiled binaries for several platforms are available for download
|
||||
at:
|
||||
|
||||
**https://github.com/mthom/scryer-prolog/releases/tag/v0.9.2**
|
||||
**https://github.com/mthom/scryer-prolog/releases/latest**
|
||||
|
||||
### Native Compilation
|
||||
|
||||
First, install the latest stable version of
|
||||
[Rust](https://www.rust-lang.org/en-US/install.html) using your
|
||||
[Rust](https://www.rust-lang.org/tools/install) using your
|
||||
preferred method. Scryer tends to use features from newer Rust
|
||||
releases, whereas Rust packages in Linux distributions, Macports,
|
||||
etc. tend to lag behind. [rustup](http://rustup.rs) will keep your
|
||||
@@ -123,9 +125,13 @@ Rust updated to the latest stable release; any existing Rust
|
||||
distribution should be uninstalled from your system before rustup is
|
||||
used.
|
||||
|
||||
Currently the only way to install the latest version of Scryer is to
|
||||
clone directly from this git repository, and compile the system. This
|
||||
can be done as follows:
|
||||
|
||||
> [!NOTE]
|
||||
> The minimum rust toolchain version required can be found in the [Cargo.toml](https://github.com/mthom/scryer-prolog/blob/master/Cargo.toml#L13)
|
||||
under the `package.rust-version` key.
|
||||
> The accuracy of this value is validated in CI
|
||||
|
||||
### From a local git checkout
|
||||
|
||||
```
|
||||
$> git clone https://github.com/mthom/scryer-prolog
|
||||
@@ -139,6 +145,32 @@ faster executable.
|
||||
After compilation, the executable `scryer-prolog` is available in the
|
||||
directory `target/release` and can be invoked to run the system.
|
||||
|
||||
### Via `cargo install`
|
||||
|
||||
#### From git
|
||||
|
||||
```
|
||||
cargo install --locked --git https://github.com/mthom/scryer-prolog.git
|
||||
```
|
||||
|
||||
Afterwards the `scryer-prolog` binary will be in the `$HOME/.cargo/bin` directory which is usually added to your PATH
|
||||
during the installation of the rust toolchain.
|
||||
|
||||
#### From Crates.io [](https://crates.io/crates/scryer-prolog) 
|
||||
|
||||
> [!NOTE]
|
||||
> The lates crates.io release can be significantly behind the version available in the git repository
|
||||
> The crates.io badge in this sections title is a link to the crates.io page.
|
||||
> The msrv badge in the section title referece to the minimum rust toolchain version required to compile the latest crates.io release
|
||||
|
||||
`scryer-prolog` is also release on crates.io and can be installed with
|
||||
|
||||
```
|
||||
cargo install --locked scryer-prolog
|
||||
```
|
||||
|
||||
### Caveats for Windows
|
||||
|
||||
On Windows, Scryer Prolog is easier to build inside a [MSYS2](https://www.msys2.org/)
|
||||
environment as some crates may require native C compilation. However,
|
||||
the resulting binary does not need MSYS2 to run. When executing Scryer in a shell, it is recommended to use a more advanced shell than mintty (the default MSYS2 shell). The [Windows Terminal](https://github.com/microsoft/terminal) works correctly.
|
||||
@@ -150,8 +182,6 @@ light.exe scryer-prolog.wixobj
|
||||
```
|
||||
It will generate a very basic MSI file which installs the main executable and a shortcut in the Start Menu. It can be installed with a double-click. To uninstall, go to the Control Panel and uninstall as usual.
|
||||
|
||||
Scryer Prolog must be built with **Rust 1.70 and up**.
|
||||
|
||||
### Building WebAssembly
|
||||
|
||||
Scryer Prolog has basic WebAssembly support. You can follow `wasm-pack`'s [official instructions](https://rustwasm.github.io/docs/wasm-pack/quickstart.html) to install `wasm-pack` and build it in any way you like.
|
||||
@@ -167,57 +197,91 @@ Then a `pkg` directory will be created, containing everything you need for a web
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Scryer Prolog - Sudoku Solver Example</title>
|
||||
<script type="module">
|
||||
import init, { eval_code } from './pkg/scryer_prolog.js';
|
||||
|
||||
const run = async () => {
|
||||
await init("./pkg/scryer_prolog_bg.wasm");
|
||||
let code = `
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
sudoku(Rows) :-
|
||||
length(Rows, 9), maplist(same_length(Rows), Rows),
|
||||
append(Rows, Vs), Vs ins 1..9,
|
||||
maplist(all_distinct, Rows),
|
||||
transpose(Rows, Columns),
|
||||
maplist(all_distinct, Columns),
|
||||
Rows = [As,Bs,Cs,Ds,Es,Fs,Gs,Hs,Is],
|
||||
blocks(As, Bs, Cs),
|
||||
blocks(Ds, Es, Fs),
|
||||
blocks(Gs, Hs, Is).
|
||||
|
||||
blocks([], [], []).
|
||||
blocks([N1,N2,N3|Ns1], [N4,N5,N6|Ns2], [N7,N8,N9|Ns3]) :-
|
||||
all_distinct([N1,N2,N3,N4,N5,N6,N7,N8,N9]),
|
||||
blocks(Ns1, Ns2, Ns3).
|
||||
|
||||
problem(1, [[_,_,_,_,_,_,_,_,_],
|
||||
[_,_,_,_,_,3,_,8,5],
|
||||
[_,_,1,_,2,_,_,_,_],
|
||||
[_,_,_,5,_,7,_,_,_],
|
||||
[_,_,4,_,_,_,1,_,_],
|
||||
[_,9,_,_,_,_,_,_,_],
|
||||
[5,_,_,_,_,_,_,7,3],
|
||||
[_,_,2,_,1,_,_,_,_],
|
||||
[_,_,_,_,4,_,_,_,9]]).
|
||||
|
||||
main :-
|
||||
problem(1, Rows), sudoku(Rows), maplist(portray_clause, Rows).
|
||||
|
||||
:- initialization(main).
|
||||
`;
|
||||
const result = eval_code(code);
|
||||
document.write(`<p>Sudoku solver returns:</p><pre>${result}</pre>`);
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Scryer Prolog - Sudoku Solver Example</title>
|
||||
<script type="module">
|
||||
import initScryer, { MachineBuilder } from "./pkg/scryer_prolog.js";
|
||||
|
||||
// Initialize Scryer Prolog with WASM
|
||||
const wasm = await fetch("./pkg/scryer_prolog_bg.wasm");
|
||||
const module = await WebAssembly.compile(await wasm.arrayBuffer());
|
||||
await initScryer(module);
|
||||
|
||||
// Set up the Prolog machine
|
||||
const machine = new MachineBuilder().build();
|
||||
|
||||
// Knowledge base: Sudoku rules and problem definition
|
||||
const kb = `
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(clpz)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
sudoku(Rows) :-
|
||||
length(Rows, 9), maplist(same_length(Rows), Rows),
|
||||
append(Rows, Vs), Vs ins 1..9,
|
||||
maplist(all_distinct, Rows),
|
||||
transpose(Rows, Columns),
|
||||
maplist(all_distinct, Columns),
|
||||
Rows = [A,B,C,D,E,F,G,H,I],
|
||||
blocks(A, B, C),
|
||||
blocks(D, E, F),
|
||||
blocks(G, H, I).
|
||||
|
||||
blocks([], [], []).
|
||||
blocks([A,B,C|T1], [D,E,F|T2], [G,H,I|T3]) :-
|
||||
all_distinct([A,B,C,D,E,F,G,H,I]),
|
||||
blocks(T1, T2, T3).
|
||||
|
||||
problem(1, [[_,_,_,_,_,_,_,_,_],
|
||||
[_,_,_,_,_,3,_,8,5],
|
||||
[_,_,1,_,2,_,_,_,_],
|
||||
[_,_,_,5,_,7,_,_,_],
|
||||
[_,_,4,_,_,_,1,_,_],
|
||||
[_,9,_,_,_,_,_,_,_],
|
||||
[5,_,_,_,_,_,_,7,3],
|
||||
[_,_,2,_,1,_,_,_,_],
|
||||
[_,_,_,_,4,_,_,_,9]]).
|
||||
`;
|
||||
|
||||
machine.consultModuleString("user", kb);
|
||||
|
||||
// Run the query
|
||||
const query = "problem(1, Rows), sudoku(Rows), maplist(portray_clause, Rows).";
|
||||
const answers = machine.runQuery(query);
|
||||
|
||||
const formattedSolutions = [];
|
||||
|
||||
// Format the answers
|
||||
for (const solution of answers) {
|
||||
const rows = solution.bindings["Rows"].list;
|
||||
|
||||
const grid = rows.map(row =>
|
||||
row.list.map(cell => cell.integer)
|
||||
);
|
||||
|
||||
const formatted = grid.map(row => `[${row.join(", ")}]`).join("\n");
|
||||
formattedSolutions.push(formatted);
|
||||
}
|
||||
run();
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
|
||||
// Output results
|
||||
const solutionDiv = document.querySelector("#soduku-solution");
|
||||
for (const solution of formattedSolutions) {
|
||||
const newPre = document.createElement("pre");
|
||||
newPre.textContent = solution;
|
||||
solutionDiv.appendChild(newPre);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Sudoku solver returns:</p>
|
||||
<div id="soduku-solution">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
```
|
||||
|
||||
@@ -225,6 +289,12 @@ Then you can serve it with your favorite http server like `python -m http.server
|
||||
|
||||
### Docker Install
|
||||
|
||||
Pre-built [Docker images are available on Docker Hub](https://hub.docker.com/r/mjt128/scryer-prolog/tags).
|
||||
The `latest` tag reflects the state on `master`, which might be unstable.
|
||||
There are also tags for Scryer releases 0.9.2 and up.
|
||||
Note though, that the base images are not kept up to date at the moment,
|
||||
so be wary of security vulnerabilities (see [#2646](https://github.com/mthom/scryer-prolog/issues/2646)).
|
||||
|
||||
First, install [Docker](https://docs.docker.com/get-docker/) on Linux,
|
||||
Windows, or Mac.
|
||||
|
||||
@@ -313,6 +383,35 @@ To quit Scryer Prolog, use the standard predicate `halt/0`:
|
||||
?- halt.
|
||||
```
|
||||
|
||||
### Starting Scryer Prolog
|
||||
|
||||
Scryer Prolog can be started from the command line by specifying
|
||||
options, files and additional arguments. All components are optional:
|
||||
|
||||
<pre>
|
||||
scryer-prolog [OPTIONS] [FILES] [-- ARGUMENTS]
|
||||
</pre>
|
||||
|
||||
The supported options are:
|
||||
|
||||
```
|
||||
-h, --help Display help message
|
||||
-v, --version Print version information and exit
|
||||
-g, --goal GOAL Run the query GOAL after consulting files
|
||||
-f Fast startup. Do not load initialization file (~/.scryerrc)
|
||||
--no-add-history Prevent adding input to history file (~/.scryer_history)
|
||||
```
|
||||
|
||||
All specified Prolog files are consulted.
|
||||
|
||||
After Prolog files, application-specific arguments can be specified on
|
||||
the command line. These arguments can be accessed from within Prolog
|
||||
applications with the predicate `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
|
||||
@@ -430,7 +529,7 @@ is unified with a term that contains that variable as a proper
|
||||
subterm. For efficiency, the *occurs check* is omitted by default
|
||||
in Scryer Prolog and many other Prolog systems.
|
||||
|
||||
In Scryer Prolog, performing unifications which succeed only if the
|
||||
In Scryer Prolog, unifications which succeed only if the
|
||||
*occurs check* is omitted yield *cyclic terms*, also called
|
||||
*rational trees*. For example:
|
||||
|
||||
@@ -662,9 +761,13 @@ The modules that ship with Scryer Prolog are also called
|
||||
public key signatures and signature verification with Ed25519,
|
||||
ECDH key exchange over Curve25519 (X25519), authenticated symmetric
|
||||
encryption with ChaCha20-Poly1305, and reasoning about elliptic curves.
|
||||
* [`process`](src/lib/process.pl)
|
||||
Create and manage parallel processes.
|
||||
* [`uuid`](src/lib/uuid.pl) UUIDv4 generation and hex representation
|
||||
* [`tls`](src/lib/tls.pl)
|
||||
Predicates for negotiating TLS connections explicitly.
|
||||
* [`numerics/special_functions`](src/lib/numerics/special_functions.pl)
|
||||
Predicates for erf, gamma, beta, and related special functions.
|
||||
* [`ugraphs`](src/lib/ugraphs.pl) Graph manipulation library
|
||||
* [`simplex`](src/lib/simplex.pl) Providing `assignment/2`,
|
||||
`transportation/4` and other predicates for solving linear
|
||||
@@ -765,20 +868,42 @@ 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.
|
||||
Successful existing applications of Scryer Prolog include:
|
||||
|
||||
- [DocLog](https://github.com/aarroyoc/doclog) which generates
|
||||
Scryer's own documentation and homepage
|
||||
- [Grants4Companies](https://arxiv.org/abs/2406.15293): reasoning
|
||||
about business grants in the Austrian public administration
|
||||
- parts of the [precautionary](https://github.com/dcnorris/precautionary/tree/main/exec/prolog)
|
||||
package for the analysis of dose-escalation trials in the
|
||||
safety-critical and highly regulated domain of oncology
|
||||
trial design, described in [*An Executable Specification of
|
||||
Oncology Dose-Escalation Protocols with Prolog*](https://arxiv.org/abs/2402.08334)
|
||||
and culminating in [**DEDUCTION**](https://codeberg.org/dcnorris/DEDUCTION)
|
||||
- semantic reasoning and queries in [AD4M](https://github.com/coasys/ad4m),
|
||||
an agent-centric distributed application meta-ontology.
|
||||
|
||||
Scryer Prolog is also very well suited for teaching and learning
|
||||
Prolog, and for testing syntactic conformance and hence portability of
|
||||
existing Prolog programs.
|
||||
|
||||
## Scryer Prolog Meetups
|
||||
|
||||
Scryer Prolog Meetups are an excellent opportunity to present and get
|
||||
to know the latest developments in Scryer Prolog and its
|
||||
applications, to exchange ideas about current plans and
|
||||
future directions, and to discuss projects and visions
|
||||
in person.
|
||||
|
||||
- [Scryer Prolog Meetup 2023](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2023/)
|
||||
in Düsseldorf, Germany. Its [announcement](https://github.com/mthom/scryer-prolog/discussions/1813)
|
||||
and [discussion](https://github.com/mthom/scryer-prolog/discussions/2160).
|
||||
- [Scryer Prolog Meetup 2024](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2024)
|
||||
in Vienna, Austria. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2377).
|
||||
- **Save the date:** The [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/)
|
||||
will take place on Nov. 13th and 14th 2025 in Düsseldorf, Germany.
|
||||
Its [announcement](https://github.com/mthom/scryer-prolog/discussions/2948).
|
||||
|
||||
## Support and discussions
|
||||
|
||||
If Scryer Prolog crashes or yields unexpected errors, consider filing
|
||||
|
||||
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::LeafAnswer;
|
||||
|
||||
use super::setup;
|
||||
|
||||
#[library_benchmark]
|
||||
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
|
||||
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
|
||||
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())]
|
||||
fn bench(mut run: impl FnMut() -> Vec<LeafAnswer>) -> Vec<LeafAnswer> {
|
||||
run()
|
||||
}
|
||||
|
||||
library_benchmark_group!(
|
||||
name = benches;
|
||||
benchmarks = bench
|
||||
);
|
||||
|
||||
main!(library_benchmark_groups = benches);
|
||||
|
||||
pub fn call_main() {
|
||||
main()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
fn main() {
|
||||
iai::call_main();
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
||||
fn main() {}
|
||||
140
benches/setup.rs
Normal file
140
benches/setup.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
|
||||
use maplit::btreemap;
|
||||
use scryer_prolog::{LeafAnswer, Machine, MachineBuilder, Term};
|
||||
|
||||
pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
|
||||
[
|
||||
(
|
||||
"count_edges", // name of the benchmark
|
||||
"benches/edges.pl", // name of the prolog module file to load. use the same file in multiple benchmarks
|
||||
"independent_set_count(ky, Count).", // query to benchmark in the context of the loaded module. consider making the query adjustable to tune the run time to ~0.1s
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Count" => Term::integer(2869176) },
|
||||
),
|
||||
(
|
||||
"numlist",
|
||||
"benches/numlist.pl",
|
||||
"run_numlist(1000000, Head).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Head" => Term::integer(1) },
|
||||
),
|
||||
(
|
||||
"csv_codename",
|
||||
"benches/csv.pl",
|
||||
"get_codename(\"0020\",Name).",
|
||||
Strategy::Reuse,
|
||||
btreemap! { "Name" => Term::string("SPACE") },
|
||||
),
|
||||
]
|
||||
.map(|b| {
|
||||
(
|
||||
b.0,
|
||||
PrologBenchmark {
|
||||
name: b.0,
|
||||
filename: b.1,
|
||||
query: b.2,
|
||||
strategy: b.3,
|
||||
bindings: b.4,
|
||||
},
|
||||
)
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
pub enum Strategy {
|
||||
#[allow(dead_code)]
|
||||
Fresh,
|
||||
Reuse,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PrologBenchmark {
|
||||
pub name: &'static str,
|
||||
pub filename: &'static str,
|
||||
pub query: &'static str,
|
||||
pub strategy: Strategy,
|
||||
pub bindings: BTreeMap<&'static str, Term>,
|
||||
}
|
||||
|
||||
impl PrologBenchmark {
|
||||
pub fn make_machine(&self) -> Machine {
|
||||
let program = fs::read_to_string(self.filename).unwrap();
|
||||
let module_name = Path::new(self.filename)
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap();
|
||||
let mut machine = MachineBuilder::default().build();
|
||||
machine.load_module_string(module_name, program);
|
||||
machine
|
||||
}
|
||||
|
||||
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
|
||||
pub fn setup(&self) -> impl FnMut() -> Vec<LeafAnswer> {
|
||||
let mut machine = self.make_machine();
|
||||
let query = self.query;
|
||||
move || {
|
||||
use criterion::black_box;
|
||||
black_box(
|
||||
machine
|
||||
.run_query(black_box(query))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
#[test]
|
||||
fn validate_benchmarks() {
|
||||
use super::prolog_benches;
|
||||
use scryer_prolog::LeafAnswer;
|
||||
use std::{fmt::Write, fs};
|
||||
|
||||
struct BenchResult {
|
||||
pub name: &'static str,
|
||||
pub setup_inference_count: u64,
|
||||
pub query_inference_count: u64,
|
||||
}
|
||||
|
||||
let mut results: Vec<BenchResult> = vec![];
|
||||
|
||||
for (_, r) in prolog_benches() {
|
||||
let mut machine = r.make_machine();
|
||||
let setup_inference_count = machine.get_inference_count();
|
||||
|
||||
let result: Vec<_> = machine
|
||||
.run_query(r.query)
|
||||
.collect::<Result<_, _>>()
|
||||
.unwrap();
|
||||
let query_inference_count = machine.get_inference_count() - setup_inference_count;
|
||||
|
||||
let expected = [LeafAnswer::from_bindings(r.bindings.clone())];
|
||||
assert_eq!(result, expected, "validating benchmark {}", r.name);
|
||||
|
||||
results.push(BenchResult {
|
||||
name: r.name,
|
||||
setup_inference_count,
|
||||
query_inference_count,
|
||||
})
|
||||
}
|
||||
|
||||
let mut json: String = Default::default();
|
||||
json.push('[');
|
||||
for r in results {
|
||||
json.push('\n');
|
||||
write!(
|
||||
json,
|
||||
r#"{{"name":"{}","setup_inference_count":{},"query_inference_count":{}}},"#,
|
||||
r.name, r.setup_inference_count, r.query_inference_count
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
json.pop(); // trailing comma
|
||||
json.push_str("\n]");
|
||||
fs::write("target/benchmark_inference_counts.json", json).expect("Unable to write file");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,43 +5,48 @@ use instructions_template::generate_instructions_rs;
|
||||
use static_string_indexing::index_static_strings;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::path::MAIN_SEPARATOR_STR;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn find_prolog_files(libraries: &mut File, prefix: &str, current_dir: &Path) {
|
||||
fn find_prolog_files(path_prefix: &str, current_dir: &Path) -> Vec<(String, PathBuf)> {
|
||||
let mut libraries = vec![];
|
||||
|
||||
let entries = match current_dir.read_dir() {
|
||||
Ok(entries) => entries,
|
||||
Err(_) => return,
|
||||
Err(_) => return libraries,
|
||||
};
|
||||
|
||||
for entry in entries.filter_map(Result::ok).map(|e| e.path()) {
|
||||
if entry.is_dir() {
|
||||
if let Some(file_name) = entry.file_name() {
|
||||
let new_prefix = prefix.to_owned() + file_name.to_str().unwrap() + "/";
|
||||
find_prolog_files(libraries, &new_prefix, &entry);
|
||||
let file_name = file_name.to_str().unwrap();
|
||||
let new_path_prefix = format!("{path_prefix}{file_name}/");
|
||||
let new_libs = find_prolog_files(&new_path_prefix, &entry);
|
||||
libraries.extend(new_libs);
|
||||
}
|
||||
} else if entry.is_file() {
|
||||
let ext = std::ffi::OsStr::new("pl");
|
||||
if entry.extension() == Some(ext) {
|
||||
let contain = String::from_utf8(fs::read(&entry).unwrap()).unwrap();
|
||||
let name = entry.file_stem().unwrap().to_str().unwrap();
|
||||
let lib_name = format!("{path_prefix}{name}");
|
||||
|
||||
let line = format!(
|
||||
" m.insert(\"{}\",\n{:?});\n",
|
||||
prefix.to_owned() + name,
|
||||
contain
|
||||
);
|
||||
|
||||
libraries.write_all(line.as_bytes()).unwrap();
|
||||
libraries.push((lib_name, entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
libraries
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if version_check::is_min_version("1.87.0").unwrap_or(false) {
|
||||
println!(r#"cargo:rustc-cfg=rust_version="1.87.0""#);
|
||||
}
|
||||
|
||||
let has_rustfmt = Command::new("rustfmt")
|
||||
.arg("--version")
|
||||
.stdin(Stdio::inherit())
|
||||
@@ -55,19 +60,23 @@ fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let dest_path = Path::new(&out_dir).join("libraries.rs");
|
||||
|
||||
let mut libraries = File::create(&dest_path).unwrap();
|
||||
let lib_path = Path::new("src/lib");
|
||||
let mut libraries = File::create(dest_path).unwrap();
|
||||
let lib_path = Path::new("src").join("lib");
|
||||
|
||||
libraries
|
||||
.write_all(
|
||||
b"ref_thread_local::ref_thread_local! {
|
||||
pub(crate) static managed LIBRARIES: IndexMap<&'static str, &'static str> = {
|
||||
let mut m = IndexMap::new();\n",
|
||||
let constants = find_prolog_files("", &lib_path);
|
||||
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
|
||||
writeln!(libraries, "{{").unwrap();
|
||||
for (name, lib_path) in constants {
|
||||
let path = format!("{}{}", MAIN_SEPARATOR_STR, lib_path.display());
|
||||
writeln!(
|
||||
libraries,
|
||||
"m.insert(\"{name}\", include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), {path:?})));"
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
find_prolog_files(&mut libraries, "", &lib_path);
|
||||
libraries.write_all(b"\n m\n };\n}\n").unwrap();
|
||||
}
|
||||
writeln!(libraries, "}}").unwrap();
|
||||
|
||||
let instructions_path = Path::new(&out_dir).join("instructions.rs");
|
||||
let mut instructions_file = File::create(&instructions_path).unwrap();
|
||||
|
||||
@@ -35,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()?);
|
||||
}
|
||||
|
||||
@@ -52,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()?);
|
||||
}
|
||||
|
||||
@@ -65,27 +65,37 @@ impl<'ast> Visit<'ast> for StaticStrVisitor {
|
||||
let Macro { path, .. } = m;
|
||||
|
||||
if path.is_ident("atom") {
|
||||
if let Some(Lit::Str(string)) = m.parse_body::<Lit>().ok() {
|
||||
if let Ok(Lit::Str(string)) = m.parse_body::<Lit>() {
|
||||
self.static_strs.insert(string.value());
|
||||
}
|
||||
} else if path.is_ident("read_heap_cell") || path.is_ident("match_untyped_arena_ptr") {
|
||||
if let Some(m) = m.parse_body::<ReadHeapCellExprAndArms>().ok() {
|
||||
if let Ok(m) = m.parse_body::<ReadHeapCellExprAndArms>() {
|
||||
self.visit_expr(&m.expr);
|
||||
|
||||
for e in m.arms {
|
||||
self.visit_arm(&e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(m) = m.parse_body::<MacroFnArgs>().ok() {
|
||||
for e in m.args {
|
||||
self.visit_expr(&e);
|
||||
}
|
||||
} else if let Ok(m) = m.parse_body::<MacroFnArgs>() {
|
||||
for e in m.args {
|
||||
self.visit_expr(&e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const INLINED_ATOM_MAX_LEN: usize = 6;
|
||||
|
||||
fn static_string_index(string: &str, index: usize) -> u64 {
|
||||
if !string.is_empty() && string.len() <= INLINED_ATOM_MAX_LEN && !string.contains('\u{0}') {
|
||||
let mut string_buf: [u8; 8] = [0u8; 8];
|
||||
string_buf[..string.len()].copy_from_slice(string.as_bytes());
|
||||
(u64::from_le_bytes(string_buf) << 1) | 1
|
||||
} else {
|
||||
(index << 1) as u64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStream {
|
||||
use quote::*;
|
||||
|
||||
@@ -116,14 +126,19 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
|
||||
match file.read_to_string(&mut src) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
panic!("error reading file: {:?}", e);
|
||||
panic!("error reading file: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
let syntax = match syn::parse_file(&src) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
panic!("parse error: {} in file {:?}", e, path);
|
||||
println!("cargo::warning=parse error: {e} in file {path:?}");
|
||||
syn::File {
|
||||
shebang: None,
|
||||
attrs: vec![],
|
||||
items: vec![],
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(syntax)
|
||||
@@ -147,33 +162,48 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
|
||||
visitor.visit_file(&syntax);
|
||||
}
|
||||
|
||||
match process_filepath(instruction_rs_path) {
|
||||
Ok(syntax) => visitor.visit_file(&syntax),
|
||||
Err(_) => {}
|
||||
if let Ok(syntax) = process_filepath(instruction_rs_path) {
|
||||
visitor.visit_file(&syntax)
|
||||
}
|
||||
|
||||
let indices = (0..visitor.static_strs.len()).map(|i| (i << 3) as u64);
|
||||
let indices_iter = indices.clone();
|
||||
let mut static_str_keys = vec![];
|
||||
let mut static_strs = vec![];
|
||||
let mut static_str_indices = vec![];
|
||||
|
||||
let static_strs_len = visitor.static_strs.len();
|
||||
let static_strs: &Vec<_> = &visitor.static_strs.into_iter().collect();
|
||||
let indices: Vec<u64> = visitor
|
||||
.static_strs
|
||||
.iter()
|
||||
.map(|string| {
|
||||
let index = static_string_index(string, static_strs.len());
|
||||
|
||||
static_str_keys.push(string);
|
||||
|
||||
if index & 1 == 1 {
|
||||
index
|
||||
} else {
|
||||
static_str_indices.push(index);
|
||||
static_strs.push(string);
|
||||
index
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let static_strs_len = static_strs.len();
|
||||
|
||||
quote! {
|
||||
use phf;
|
||||
|
||||
static STRINGS: [&'static str; #static_strs_len] = [
|
||||
static STRINGS: [&str; #static_strs_len] = [
|
||||
#(
|
||||
#static_strs,
|
||||
)*
|
||||
];
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! atom {
|
||||
#((#static_strs) => { Atom { index: #indices_iter } };)*
|
||||
#((#static_str_keys) => { Atom { index: #indices } };)*
|
||||
($name:literal) => {compile_error!(concat!("unknown static atom ", $name))};
|
||||
}
|
||||
|
||||
pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {
|
||||
#(#static_strs => { Atom { index: #indices } },)*
|
||||
#(#static_strs => { Atom { index: #static_str_indices } },)*
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
14
doclog.config.pl
Normal file
14
doclog.config.pl
Normal file
@@ -0,0 +1,14 @@
|
||||
project_name("Scryer Prolog").
|
||||
readme_file("INDEX.dj").
|
||||
source_lib_folder("src/lib").
|
||||
websource("https://github.com/mthom/scryer-prolog/tree/master/src/lib").
|
||||
omit(["ops_and_meta_predicates.pl", "tabling"]).
|
||||
learn_pages_source_folder("learn").
|
||||
learn_pages_categories(["First steps", "Tutorials"]).
|
||||
learn_pages([
|
||||
page("Let's play Brisca", "Tutorials", "lets-play-brisca.dj"),
|
||||
page("Test page", "First steps", "test-page.dj")
|
||||
]).
|
||||
copy_file("logo/scryer.png", "scryer.png").
|
||||
copy_file("learn/Spanish_deck_Fournier.jpg", "learn/Spanish_deck_Fournier.jpg").
|
||||
copy_file("learn/brisca-interactive.png", "learn/brisca-interactive.png").
|
||||
82
flake.lock
generated
Normal file
82
flake.lock
generated
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1744868846,
|
||||
"narHash": "sha256-5RJTdUHDmj12Qsv7XOhuospjAjATNiTMElplWnJE9Hs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ebe4301cbd8f81c4f8d3244b3632338bbeb6d49c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1745289264,
|
||||
"narHash": "sha256-7nt+UJ7qaIUe2J7BdnEEph9n2eKEwxUwKS/QIr091uA=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "3b7171858c20d5293360042936058fb0c4cb93a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
85
flake.nix
Normal file
85
flake.nix
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
description = "A modern Prolog implementation written mostly in Rust";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, flake-utils, rust-overlay, ... }:
|
||||
let
|
||||
meta = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package;
|
||||
inherit (meta) name version;
|
||||
overlays = [
|
||||
(import rust-overlay)
|
||||
(self: super: {
|
||||
rustToolchainDev = super.rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" ];
|
||||
};
|
||||
rustToolchainDevWasm = super.rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" ];
|
||||
targets = [ "wasm32-unknown-unknown" ];
|
||||
};
|
||||
rustToolchainNightly = super.rust-bin.selectLatestNightlyWith (toolchain:
|
||||
toolchain.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer" "miri" ];
|
||||
}
|
||||
);
|
||||
})
|
||||
];
|
||||
in flake-utils.lib.eachDefaultSystem(system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system overlays; };
|
||||
nativeBuildInputs = with pkgs; [ pkg-config ];
|
||||
buildInputs = with pkgs; [ openssl ] ++
|
||||
lib.optionals pkgs.stdenv.isDarwin [
|
||||
pkgs.darwin.apple_sdk.frameworks.SystemConfiguration
|
||||
];
|
||||
in
|
||||
{
|
||||
devShells = {
|
||||
default = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } {
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
rustToolchainDev
|
||||
]);
|
||||
};
|
||||
wasm-js = pkgs.mkShell.override { stdenv = pkgs.clangMultiStdenv; } {
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
wasm-pack
|
||||
rustToolchainDevWasm
|
||||
]);
|
||||
TARGET_CC = "${pkgs.clangMultiStdenv.cc}/bin/clang";
|
||||
hardeningDisable = [ "all" ];
|
||||
};
|
||||
# For use with Miri and stuff like it
|
||||
nightly = pkgs.mkShell {
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs ++ (with pkgs; [
|
||||
rustToolchainNightly
|
||||
]);
|
||||
};
|
||||
};
|
||||
|
||||
packages = rec {
|
||||
default = scryer-prolog;
|
||||
scryer-prolog = pkgs.rustPlatform.buildRustPackage {
|
||||
pname = name;
|
||||
inherit version;
|
||||
src = ./.;
|
||||
nativeBuildInputs = nativeBuildInputs;
|
||||
buildInputs = buildInputs;
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
};
|
||||
release = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
491
flamegraph.svg
491
flamegraph.svg
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 4.8 MiB |
BIN
learn/Spanish_deck_Fournier.jpg
Normal file
BIN
learn/Spanish_deck_Fournier.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 629 KiB |
BIN
learn/brisca-interactive.png
Normal file
BIN
learn/brisca-interactive.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
457
learn/lets-play-brisca.dj
Normal file
457
learn/lets-play-brisca.dj
Normal file
@@ -0,0 +1,457 @@
|
||||
# Let's play Brisca
|
||||
|
||||
In this article, we'll see how to use modern Prolog to model the game of [Brisca](https://en.wikipedia.org/wiki/Brisca). First things first, what is Brisca?
|
||||
|
||||
## Rules of the game
|
||||
|
||||
Brisca is a traditional card game from Spain, although similar games both in name and in rules are spread across the Mediterranean. It's also similar to Tute. It is played using the traditional Spanish deck, usually with 40 cards in 4 suites (oros, espadas, bastos, copas). Here you have the famous Castillian pattern by Heraclio Fournier, the one I play in my family with my grandparents.
|
||||
|
||||

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

|
||||
3
learn/test-page.dj
Normal file
3
learn/test-page.dj
Normal file
@@ -0,0 +1,3 @@
|
||||
# Test page
|
||||
|
||||
This is a page about Scryer Prolog
|
||||
@@ -14,7 +14,7 @@ pub(crate) trait Allocator {
|
||||
lvl: Level,
|
||||
context: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
) -> RegType;
|
||||
|
||||
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
@@ -24,11 +24,12 @@ pub(crate) trait Allocator {
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
cell: &Cell<VarReg>,
|
||||
term_loc: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
r: RegType,
|
||||
@@ -41,51 +42,16 @@ pub(crate) trait Allocator {
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
cell: &Cell<VarReg>,
|
||||
context: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
);
|
||||
|
||||
fn reset(&mut self);
|
||||
fn reset_arg(&mut self, arg_num: usize);
|
||||
fn reset_at_head(&mut self, args: &Vec<Term>);
|
||||
fn reset_at_head(&mut self, args: &[Term]);
|
||||
fn reset_contents(&mut self);
|
||||
|
||||
fn advance_arg(&mut self);
|
||||
|
||||
/*
|
||||
fn bindings(&self) -> &AllocVarDict;
|
||||
fn bindings_mut(&mut self) -> &mut AllocVarDict;
|
||||
fn take_bindings(self) -> AllocVarDict;
|
||||
*/
|
||||
|
||||
fn max_reg_allocated(&self) -> usize;
|
||||
|
||||
// TODO: wha.. why?? grrr. it drains the VarStatus data from vs (which it owns!)
|
||||
// into self.bindings and perm_vs after all is computed (i.e. vs.populate_restricting_sets()
|
||||
// and vs.set_perm_vals(has_deep_cut) have both been called).
|
||||
/*
|
||||
fn drain_var_data<'a>(
|
||||
&mut self,
|
||||
vs: VariableFixtures,
|
||||
num_of_chunks: usize,
|
||||
) -> VariableFixtures {
|
||||
let mut perm_vs = VariableFixtures::new();
|
||||
|
||||
for (var, var_status) in vs.into_iter() {
|
||||
match var_status {
|
||||
VarStatus::Temp(chunk_num, tvd) => {
|
||||
self.bindings_mut()
|
||||
.insert(var.clone(), VarAlloc::Temp(chunk_num, 0, tvd));
|
||||
}
|
||||
VarStatus::Perm(_) => {
|
||||
self.bindings_mut().insert(var.clone(), VarAlloc::Perm(0));
|
||||
perm_vs.insert(var, var_status);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
perm_vs
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
933
src/arena.rs
933
src/arena.rs
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
|
||||
|
||||
use crate::allocator::*;
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
@@ -5,6 +7,7 @@ use crate::debray_allocator::*;
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::iterators::*;
|
||||
use crate::offset_table::*;
|
||||
use crate::targets::QueryInstruction;
|
||||
use crate::types::*;
|
||||
|
||||
@@ -16,7 +19,7 @@ use crate::machine::machine_errors::*;
|
||||
use dashu::base::Abs;
|
||||
use dashu::base::BitTest;
|
||||
use num_order::NumOrd;
|
||||
use ordered_float::*;
|
||||
use ordered_float::{Float, OrderedFloat};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::cmp::{max, min, Ordering};
|
||||
@@ -86,7 +89,7 @@ impl<'a> ArithInstructionIterator<'a> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ArithTermRef<'a> {
|
||||
Literal(&'a Literal),
|
||||
Literal(Literal),
|
||||
Op(Atom, usize), // name, arity.
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
}
|
||||
@@ -115,7 +118,7 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
|
||||
self.push_subterm(lvl.child_level(), &subterms[child_num]);
|
||||
}
|
||||
}
|
||||
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(c))),
|
||||
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(*c))),
|
||||
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||
return Some(Ok(ArithTermRef::Var(lvl, cell, var_ptr)));
|
||||
}
|
||||
@@ -157,7 +160,9 @@ fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), Ari
|
||||
match c {
|
||||
Literal::Fixnum(n) => interm.push(ArithmeticTerm::Number(Number::Fixnum(*n))),
|
||||
Literal::Integer(n) => interm.push(ArithmeticTerm::Number(Number::Integer(*n))),
|
||||
Literal::Float(n) => interm.push(ArithmeticTerm::Number(Number::Float(*n.as_ptr()))),
|
||||
&Literal::F64(_offset, n) => {
|
||||
interm.push(ArithmeticTerm::Number(Number::Float(n)));
|
||||
}
|
||||
Literal::Rational(n) => interm.push(ArithmeticTerm::Number(Number::Rational(*n))),
|
||||
Literal::Atom(name) if name == &atom!("e") => interm.push(ArithmeticTerm::Number(
|
||||
Number::Float(OrderedFloat(std::f64::consts::E)),
|
||||
@@ -166,7 +171,7 @@ fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), Ari
|
||||
Number::Float(OrderedFloat(std::f64::consts::PI)),
|
||||
)),
|
||||
Literal::Atom(name) if name == &atom!("epsilon") => interm.push(ArithmeticTerm::Number(
|
||||
Number::Float(OrderedFloat(std::f64::EPSILON)),
|
||||
Number::Float(OrderedFloat(f64::EPSILON)),
|
||||
)),
|
||||
_ => return Err(ArithmeticError::NonEvaluableFunctor(*c, 0)),
|
||||
}
|
||||
@@ -268,7 +273,7 @@ impl<'a> ArithmeticEvaluator<'a> {
|
||||
let ninterm = if a1.interm_or(0) == 0 {
|
||||
self.incr_interm()
|
||||
} else {
|
||||
self.interm.push(a1.clone());
|
||||
self.interm.push(a1);
|
||||
a1.interm_or(0)
|
||||
};
|
||||
|
||||
@@ -312,11 +317,10 @@ impl<'a> ArithmeticEvaluator<'a> {
|
||||
arg: usize,
|
||||
) -> Result<ArithCont, ArithmeticError> {
|
||||
let mut code = CodeDeque::new();
|
||||
let mut iter = src.iter()?;
|
||||
|
||||
while let Some(term_ref) = iter.next() {
|
||||
for term_ref in src.iter()? {
|
||||
match term_ref? {
|
||||
ArithTermRef::Literal(c) => push_literal(&mut self.interm, c)?,
|
||||
ArithTermRef::Literal(c) => push_literal(&mut self.interm, &c)?,
|
||||
ArithTermRef::Var(lvl, cell, name) => {
|
||||
let var_num = name.to_var_num().unwrap();
|
||||
|
||||
@@ -353,36 +357,43 @@ impl<'a> ArithmeticEvaluator<'a> {
|
||||
}
|
||||
|
||||
// integer division rounding function -- 9.1.3.1.
|
||||
pub(crate) fn rnd_i<'a>(n: &'a Number, arena: &mut Arena) -> Number {
|
||||
pub(crate) fn rnd_i(n: &'_ Number, arena: &mut Arena) -> Result<Number, EvalError> {
|
||||
match n {
|
||||
&Number::Integer(i) => {
|
||||
let result = (&*i).try_into();
|
||||
if let Ok(value) = result{
|
||||
fixnum!(Number, value, arena)
|
||||
if let Ok(value) = Fixnum::build_with_checked(&*i) {
|
||||
Ok(Number::Fixnum(value))
|
||||
} else {
|
||||
*n
|
||||
Ok(*n)
|
||||
}
|
||||
}
|
||||
&Number::Fixnum(_) => *n,
|
||||
Number::Fixnum(_) => Ok(*n),
|
||||
&Number::Float(f) => {
|
||||
let f = f.floor();
|
||||
|
||||
const I64_MIN_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MIN as f64);
|
||||
const I64_MAX_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MAX as f64);
|
||||
const FIXNUM_MIN_TO_F: OrderedFloat<f64> = OrderedFloat(Fixnum::MIN as f64);
|
||||
const FIXNUM_MAX_TO_F: OrderedFloat<f64> = OrderedFloat(Fixnum::MAX as f64);
|
||||
|
||||
if I64_MIN_TO_F <= f && f <= I64_MAX_TO_F {
|
||||
fixnum!(Number, f.into_inner() as i64, arena)
|
||||
if (FIXNUM_MIN_TO_F..=FIXNUM_MAX_TO_F).contains(&f) {
|
||||
Ok(Number::Fixnum(
|
||||
// Safety: We checked that the value is in range
|
||||
unsafe { Fixnum::build_with_unchecked(f.into_inner() as i64) },
|
||||
))
|
||||
} else {
|
||||
Number::Integer(arena_alloc!(Integer::from(f.0 as i64), arena))
|
||||
Ok(Number::Integer(arena_alloc!(
|
||||
Integer::try_from(classify_float(f.0)?).unwrap_or_else(|_| {
|
||||
unreachable!();
|
||||
}),
|
||||
arena
|
||||
)))
|
||||
}
|
||||
}
|
||||
&Number::Rational(ref r) => {
|
||||
let (_, floor) = (r.fract(), r.floor());
|
||||
Number::Rational(ref r) => {
|
||||
let floor = r.floor();
|
||||
|
||||
if let Ok(value) = (&floor).try_into() {
|
||||
fixnum!(Number, value, arena)
|
||||
if let Ok(value) = Fixnum::build_with_checked(&floor) {
|
||||
Ok(Number::Fixnum(value))
|
||||
} else {
|
||||
Number::Integer(arena_alloc!(floor, arena))
|
||||
Ok(Number::Integer(arena_alloc!(floor, arena)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -399,9 +410,9 @@ impl From<Fixnum> for Integer {
|
||||
pub(crate) fn rnd_f(n: &Number) -> f64 {
|
||||
match n {
|
||||
&Number::Fixnum(n) => n.get_num() as f64,
|
||||
&Number::Integer(ref n) => n.to_f64().value(),
|
||||
Number::Integer(ref n) => n.to_f64().value(),
|
||||
&Number::Float(OrderedFloat(f)) => f,
|
||||
&Number::Rational(ref r) => r.to_f64().value(),
|
||||
Number::Rational(ref r) => r.to_f64().value(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,47 +540,33 @@ impl PartialEq for Number {
|
||||
fn eq(&self, rhs: &Self) -> bool {
|
||||
match (self, rhs) {
|
||||
(&Number::Fixnum(n1), &Number::Fixnum(n2)) => n1.eq(&n2),
|
||||
(&Number::Fixnum(n1), &Number::Integer(ref n2)) => n1.get_num().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::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)) => {
|
||||
(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)) => {
|
||||
(&Number::Float(n1), Number::Integer(ref n2)) => {
|
||||
n1.eq(&OrderedFloat(n2.to_f64().value()))
|
||||
}
|
||||
(&Number::Integer(ref n1), &Number::Rational(ref n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
&Rational::from(&**n1) == &**n2
|
||||
}
|
||||
#[cfg(not(feature = "num"))]
|
||||
{
|
||||
(&**n1).num_eq(&**n2)
|
||||
}
|
||||
}
|
||||
(&Number::Rational(ref n1), &Number::Integer(ref n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
&**n1 == &Rational::from(&**n2)
|
||||
}
|
||||
#[cfg(not(feature = "num"))]
|
||||
{
|
||||
(&**n1).num_eq(&**n2)
|
||||
}
|
||||
}
|
||||
(&Number::Rational(ref n1), &Number::Float(n2)) => {
|
||||
(Number::Integer(ref n1), Number::Rational(ref n2)) => n1.num_eq(&**n2),
|
||||
(Number::Rational(ref n1), Number::Integer(ref n2)) => n1.num_eq(&**n2),
|
||||
(Number::Rational(ref n1), &Number::Float(n2)) => {
|
||||
OrderedFloat(n1.to_f64().value()).eq(&n2)
|
||||
}
|
||||
(&Number::Float(n1), &Number::Rational(ref n2)) => {
|
||||
(&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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,8 +586,8 @@ impl PartialOrd<usize> for Number {
|
||||
(n as usize).partial_cmp(rhs)
|
||||
}
|
||||
}
|
||||
Number::Integer(n) => Some((&**n).num_cmp(rhs)),
|
||||
Number::Rational(r) => Some((&**r).num_cmp(&Integer::from(*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)),
|
||||
}
|
||||
}
|
||||
@@ -609,8 +606,8 @@ impl PartialEq<usize> for Number {
|
||||
(n as usize).eq(rhs)
|
||||
}
|
||||
}
|
||||
Number::Integer(n) => (&**n).num_eq(rhs),
|
||||
Number::Rational(r) => (&**r).num_eq(&Integer::from(*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)),
|
||||
}
|
||||
}
|
||||
@@ -626,38 +623,24 @@ 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().value()).cmp(n2),
|
||||
(&Number::Float(n1), &Number::Integer(ref n2)) => {
|
||||
(&Number::Float(n1), Number::Integer(ref n2)) => {
|
||||
n1.cmp(&OrderedFloat(n2.to_f64().value()))
|
||||
}
|
||||
(&Number::Integer(n1), &Number::Rational(n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
Rational::from(&**n1).cmp(n2)
|
||||
}
|
||||
#[cfg(not(feature = "num"))]
|
||||
{
|
||||
(&*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
|
||||
}
|
||||
(*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
|
||||
}
|
||||
(&Number::Rational(n1), &Number::Integer(n2)) => {
|
||||
#[cfg(feature = "num")]
|
||||
{
|
||||
(&**n1).cmp(&Rational::from(&**n2))
|
||||
}
|
||||
#[cfg(not(feature = "num"))]
|
||||
{
|
||||
(&*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
|
||||
}
|
||||
(*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
|
||||
}
|
||||
(&Number::Rational(n1), &Number::Float(n2)) => {
|
||||
OrderedFloat(n1.to_f64().value()).cmp(&n2)
|
||||
@@ -671,11 +654,11 @@ impl Ord for Number {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<HeapCellValue> for Number {
|
||||
impl TryFrom<(HeapCellValue, &'_ F64Table)> for Number {
|
||||
type Error = ();
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: HeapCellValue) -> Result<Number, Self::Error> {
|
||||
fn try_from((value, f64_tbl): (HeapCellValue, &F64Table)) -> Result<Number, Self::Error> {
|
||||
read_heap_cell!(value,
|
||||
(HeapCellValueTag::Cons, c) => {
|
||||
match_untyped_arena_ptr!(c,
|
||||
@@ -690,8 +673,9 @@ impl TryFrom<HeapCellValue> for Number {
|
||||
}
|
||||
)
|
||||
}
|
||||
(HeapCellValueTag::F64, n) => {
|
||||
Ok(Number::Float(*n))
|
||||
(HeapCellValueTag::F64Offset, offset) => {
|
||||
let n = f64_tbl.get_entry(offset);
|
||||
Ok(Number::Float(n))
|
||||
}
|
||||
(HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
|
||||
Ok(Number::Fixnum(n))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
|
||||
|
||||
use crate::parser::ast::MAX_ARITY;
|
||||
use crate::raw_block::*;
|
||||
use crate::rcu::{Rcu, RcuRef};
|
||||
use crate::types::*;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
@@ -8,26 +9,138 @@ use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
use std::str;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::Weak;
|
||||
|
||||
use arcu::atomic::Arcu;
|
||||
use arcu::epoch_counters::GlobalEpochCounterPool;
|
||||
use arcu::rcu_ref::RcuRef;
|
||||
use arcu::Rcu;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use modular_bitfield::prelude::*;
|
||||
use scryer_modular_bitfield::prelude::*;
|
||||
|
||||
#[bitfield]
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtomCell {
|
||||
name: B48,
|
||||
arity: B8,
|
||||
#[allow(unused)]
|
||||
f: bool,
|
||||
#[allow(unused)]
|
||||
m: bool,
|
||||
#[allow(unused)]
|
||||
is_inlined: bool,
|
||||
#[allow(unused)]
|
||||
tag: B5,
|
||||
}
|
||||
|
||||
const INLINED_ATOM_MAX_LEN: usize = 6;
|
||||
|
||||
const_assert!(INLINED_ATOM_MAX_LEN < mem::size_of::<AtomCell>());
|
||||
const_assert!(mem::size_of::<AtomCell>() == 8);
|
||||
|
||||
const_assert!(INLINED_ATOM_MAX_LEN < mem::size_of::<Atom>());
|
||||
const_assert!(mem::size_of::<Atom>() == 8);
|
||||
|
||||
impl AtomCell {
|
||||
#[inline]
|
||||
fn new_static(index: u64) -> Self {
|
||||
// upper 23 bits of index must be 0
|
||||
debug_assert_eq!(index & !((1 << 49) - 1), 0);
|
||||
AtomCell::new()
|
||||
.with_name(index)
|
||||
.with_arity(0u8)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
.with_is_inlined(false)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn new_inlined(string: &str, arity: u8) -> Self {
|
||||
debug_assert!(string.len() <= INLINED_ATOM_MAX_LEN);
|
||||
|
||||
let mut string_buf: [u8; 8] = [0u8; 8];
|
||||
string_buf[..string.len()].copy_from_slice(string.as_bytes());
|
||||
let encoding = u64::from_le_bytes(string_buf);
|
||||
|
||||
AtomCell::new()
|
||||
.with_name(encoding)
|
||||
.with_arity(arity)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
.with_is_inlined(true)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_char_inlined(c: char) -> Self {
|
||||
if c == '\u{0}' {
|
||||
return Self::new_static(NULL_ATOM.flat_index());
|
||||
}
|
||||
|
||||
let mut char_buf = [0u8; 8];
|
||||
c.encode_utf8(&mut char_buf);
|
||||
|
||||
let encoding = u64::from_le_bytes(char_buf);
|
||||
|
||||
AtomCell::new()
|
||||
.with_name(encoding)
|
||||
.with_arity(0u8)
|
||||
.with_m(false)
|
||||
.with_f(false)
|
||||
.with_is_inlined(true)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build_with(atom_index: u64, arity: u8) -> Self {
|
||||
debug_assert!((arity as usize) <= MAX_ARITY);
|
||||
|
||||
AtomCell::new()
|
||||
.with_name(atom_index >> 1)
|
||||
.with_arity(arity)
|
||||
.with_f(false)
|
||||
.with_m(false)
|
||||
.with_is_inlined(atom_index & 1 == 1)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(self) -> Atom {
|
||||
Atom {
|
||||
index: (self.name() << 1) | self.is_inlined() as u64,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_arity(self) -> usize {
|
||||
self.arity() as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name_and_arity(self) -> (Atom, usize) {
|
||||
(self.get_name(), self.get_arity())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Atom {
|
||||
pub index: u64,
|
||||
}
|
||||
|
||||
const_assert!(mem::size_of::<Atom>() == 8);
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/static_atoms.rs"));
|
||||
|
||||
// populate these in STRINGS so they can be used from build_functor
|
||||
const _: Atom = atom!(".");
|
||||
const _: Atom = atom!("[]");
|
||||
const NULL_ATOM: Atom = atom!("\0");
|
||||
|
||||
impl<'a> From<&'a Atom> for Atom {
|
||||
#[inline]
|
||||
fn from(atom: &'a Atom) -> Self {
|
||||
@@ -35,41 +148,31 @@ impl<'a> From<&'a Atom> for Atom {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Atom {
|
||||
#[inline]
|
||||
fn from(value: bool) -> Self {
|
||||
if value {
|
||||
atom!("true")
|
||||
} else {
|
||||
atom!("false")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl indexmap::Equivalent<Atom> for str {
|
||||
fn equivalent(&self, key: &Atom) -> bool {
|
||||
&*key.as_str() == self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for Atom {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_str().deref() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for Atom {
|
||||
fn eq(&self, &other: &&str) -> bool {
|
||||
self.as_str().deref() == other
|
||||
}
|
||||
}
|
||||
|
||||
const ATOM_TABLE_INIT_SIZE: usize = 1 << 16;
|
||||
const ATOM_TABLE_ALIGN: usize = 8;
|
||||
|
||||
#[inline(always)]
|
||||
fn global_atom_table() -> &'static RwLock<Weak<AtomTable>> {
|
||||
#[cfg(feature = "rust_beta_channel")]
|
||||
{
|
||||
// const Weak::new will be stabilized in 1.73 which is currently in beta,
|
||||
// till then we need a OnceLock for initialization
|
||||
static GLOBAL_ATOM_TABLE: RwLock<Weak<AtomTable>> = RwLock::const_new(Weak::new());
|
||||
&GLOBAL_ATOM_TABLE
|
||||
}
|
||||
#[cfg(not(feature = "rust_beta_channel"))]
|
||||
{
|
||||
use std::sync::OnceLock;
|
||||
static GLOBAL_ATOM_TABLE: OnceLock<RwLock<Weak<AtomTable>>> = OnceLock::new();
|
||||
GLOBAL_ATOM_TABLE.get_or_init(|| RwLock::new(Weak::new()))
|
||||
}
|
||||
static GLOBAL_ATOM_TABLE: RwLock<Weak<AtomTable>> = RwLock::new(Weak::new());
|
||||
&GLOBAL_ATOM_TABLE
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -99,6 +202,12 @@ struct AtomHeader {
|
||||
padding: B13,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct AtomData {
|
||||
header: AtomHeader,
|
||||
data: str,
|
||||
}
|
||||
|
||||
impl AtomHeader {
|
||||
fn build_with(len: u64) -> Self {
|
||||
AtomHeader::new().with_len(len).with_m(false)
|
||||
@@ -109,32 +218,23 @@ impl Hash for Atom {
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
self.as_str().hash(hasher)
|
||||
// hasher.write_usize(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! is_char {
|
||||
($s:expr) => {
|
||||
!$s.is_empty() && $s.chars().nth(1).is_none()
|
||||
};
|
||||
}
|
||||
|
||||
pub enum AtomString<'a> {
|
||||
Static(&'a str),
|
||||
Inlined([u8; 8]),
|
||||
Dynamic(AtomTableRef<str>),
|
||||
}
|
||||
|
||||
impl AtomString<'_> {
|
||||
pub fn map<F>(self, f: F) -> Self
|
||||
where
|
||||
for<'a> F: FnOnce(&'a str) -> &'a str,
|
||||
{
|
||||
match self {
|
||||
Self::Static(reference) => Self::Static(f(reference)),
|
||||
Self::Dynamic(guard) => Self::Dynamic(AtomTableRef::map(guard, f)),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn inlined_to_str(bytes: &[u8; 8]) -> &str {
|
||||
let slice_len = bytes
|
||||
.iter()
|
||||
.position(|&b| b == 0u8)
|
||||
.unwrap_or(INLINED_ATOM_MAX_LEN);
|
||||
|
||||
unsafe { str::from_utf8_unchecked(&bytes[..slice_len]) }
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AtomString<'_> {
|
||||
@@ -154,6 +254,7 @@ impl std::ops::Deref for AtomString<'_> {
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Static(reference) => reference,
|
||||
Self::Inlined(inlined) => inlined_to_str(inlined),
|
||||
Self::Dynamic(guard) => guard.deref(),
|
||||
}
|
||||
}
|
||||
@@ -171,25 +272,48 @@ impl rustyline::completion::Candidate for AtomString<'_> {
|
||||
}
|
||||
|
||||
impl Atom {
|
||||
#[inline(always)]
|
||||
pub fn is_static(self) -> bool {
|
||||
(self.index as usize) < STRINGS.len() << 3
|
||||
#[inline]
|
||||
fn new_inlined(string: &str) -> Self {
|
||||
AtomCell::new_inlined(string, 0).get_name()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_ptr(self) -> Option<AtomTableRef<u8>> {
|
||||
fn is_static(self) -> bool {
|
||||
if self.is_inlined() {
|
||||
true
|
||||
} else {
|
||||
(self.flat_index() as usize) < STRINGS.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn flat_index(self) -> u64 {
|
||||
self.index >> 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_inlined(self) -> bool {
|
||||
self.index & 1 == 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn as_ptr(self) -> Option<AtomTableRef<AtomData>> {
|
||||
if self.is_static() {
|
||||
None
|
||||
} else {
|
||||
let atom_table =
|
||||
arc_atom_table().expect("We should only have an Atom while there is an AtomTable");
|
||||
unsafe {
|
||||
AtomTableRef::try_map(atom_table.buf(), |buf| {
|
||||
(buf as *const u8)
|
||||
.offset(((self.index as usize) - (STRINGS.len() << 3)) as isize)
|
||||
.as_ref()
|
||||
})
|
||||
}
|
||||
|
||||
AtomTableRef::try_map(atom_table.inner.read(), |buf| unsafe {
|
||||
let ptr = buf
|
||||
.block
|
||||
.base
|
||||
.add(self.flat_index() as usize - STRINGS.len());
|
||||
// TODO use std::ptr::from_raw_parts instead when feature ptr_metadata is stable rust-lang/rust#81513
|
||||
let atom_data = &*(std::ptr::slice_from_raw_parts(ptr, 0) as *const AtomData);
|
||||
let len = atom_data.header.len();
|
||||
Some(&*(std::ptr::slice_from_raw_parts(ptr, len as usize) as *const AtomData))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,18 +324,19 @@ impl Atom {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn len(self) -> usize {
|
||||
if self.is_static() {
|
||||
STRINGS[(self.index >> 3) as usize].len()
|
||||
if let Some(s) = self.inlined_str() {
|
||||
s.len()
|
||||
} else if self.is_static() {
|
||||
let index = self.flat_index();
|
||||
STRINGS[index as usize].len()
|
||||
} else {
|
||||
let ptr = self.as_ptr().unwrap();
|
||||
let ptr = ptr.deref() as *const u8 as *const AtomHeader;
|
||||
unsafe { ptr::read(ptr) }.len() as _
|
||||
let len: u64 = self.as_ptr().unwrap().header.len();
|
||||
len as usize
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn flat_index(self) -> u64 {
|
||||
(self.index >> 3) as u64
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn as_char(self) -> Option<char> {
|
||||
@@ -229,23 +354,25 @@ impl Atom {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> AtomString<'static> {
|
||||
if self.is_static() {
|
||||
AtomString::Static(STRINGS[(self.index >> 3) as usize])
|
||||
fn inlined_str<'a>(&self) -> Option<AtomString<'a>> {
|
||||
if self.is_inlined() {
|
||||
Some(AtomString::Inlined(self.flat_index().to_le_bytes()))
|
||||
} else {
|
||||
if let Some(ptr) = self.as_ptr() {
|
||||
AtomString::Dynamic(AtomTableRef::map(ptr, |ptr| {
|
||||
let header =
|
||||
unsafe { ptr::read::<AtomHeader>(ptr as *const u8 as *const AtomHeader) };
|
||||
let len = header.len() as usize;
|
||||
let buf =
|
||||
unsafe { (ptr as *const u8).offset(mem::size_of::<AtomHeader>() as isize) };
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { str::from_utf8_unchecked(slice::from_raw_parts(buf, len)) }
|
||||
}))
|
||||
} else {
|
||||
AtomString::Static(&STRINGS[(self.index >> 3) as usize])
|
||||
}
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> AtomString<'static> {
|
||||
if let Some(s) = self.inlined_str() {
|
||||
s
|
||||
} else if self.is_static() {
|
||||
let index = self.flat_index() as usize;
|
||||
AtomString::Static(STRINGS[index])
|
||||
} else if let Some(ptr) = self.as_ptr() {
|
||||
AtomString::Dynamic(AtomTableRef::map(ptr, |ptr| &ptr.data))
|
||||
} else {
|
||||
AtomString::Static(STRINGS[(self.index >> 1) as usize])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,14 +385,14 @@ impl Atom {
|
||||
return *self;
|
||||
};
|
||||
|
||||
AtomTable::build_with(&atom_tbl, &sub_str)
|
||||
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 {
|
||||
@@ -285,17 +412,17 @@ impl Ord for Atom {
|
||||
#[derive(Debug)]
|
||||
pub struct InnerAtomTable {
|
||||
block: RawBlock<AtomTable>,
|
||||
pub table: Rcu<IndexSet<Atom>>,
|
||||
pub table: Arcu<IndexSet<Atom>, GlobalEpochCounterPool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AtomTable {
|
||||
inner: Rcu<InnerAtomTable>,
|
||||
inner: Arcu<InnerAtomTable, GlobalEpochCounterPool>,
|
||||
// this lock is taking during resizing
|
||||
update: Mutex<()>,
|
||||
}
|
||||
|
||||
pub type AtomTableRef<M> = RcuRef<InnerAtomTable, M>;
|
||||
pub type AtomTableRef<M> = arcu::rcu_ref::RcuRef<InnerAtomTable, M>;
|
||||
|
||||
impl InnerAtomTable {
|
||||
#[inline(always)]
|
||||
@@ -303,7 +430,7 @@ impl InnerAtomTable {
|
||||
STATIC_ATOMS_MAP
|
||||
.get(string)
|
||||
.cloned()
|
||||
.or_else(|| self.table.active_epoch().get(string).cloned())
|
||||
.or_else(|| self.table.read().get(string).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,10 +448,13 @@ impl AtomTable {
|
||||
atom_table
|
||||
} else {
|
||||
let atom_table = Arc::new(Self {
|
||||
inner: Rcu::new(InnerAtomTable {
|
||||
block: RawBlock::new(),
|
||||
table: Rcu::new(IndexSet::new()),
|
||||
}),
|
||||
inner: Arcu::new(
|
||||
InnerAtomTable {
|
||||
block: RawBlock::new(),
|
||||
table: Arcu::new(IndexSet::new(), GlobalEpochCounterPool),
|
||||
},
|
||||
GlobalEpochCounterPool,
|
||||
),
|
||||
update: Mutex::new(()),
|
||||
});
|
||||
*guard = Arc::downgrade(&atom_table);
|
||||
@@ -333,21 +463,18 @@ impl AtomTable {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn buf(&self) -> AtomTableRef<u8> {
|
||||
AtomTableRef::<InnerAtomTable>::map(self.inner.active_epoch(), |inner| {
|
||||
unsafe { inner.block.base.as_ref() }.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn active_table(&self) -> RcuRef<IndexSet<Atom>, IndexSet<Atom>> {
|
||||
self.inner.active_epoch().table.active_epoch()
|
||||
self.inner.read().table.read()
|
||||
}
|
||||
|
||||
pub fn build_with(atom_table: &AtomTable, string: &str) -> Atom {
|
||||
if !string.is_empty() && string.len() <= INLINED_ATOM_MAX_LEN && !string.contains('\u{0}') {
|
||||
return Atom::new_inlined(string);
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut block_epoch = atom_table.inner.active_epoch();
|
||||
let mut table_epoch = block_epoch.table.active_epoch();
|
||||
let mut block_epoch = atom_table.inner.read();
|
||||
let mut table_epoch = block_epoch.table.read();
|
||||
|
||||
if let Some(atom) = block_epoch.lookup_str(string) {
|
||||
return atom;
|
||||
@@ -356,10 +483,8 @@ impl AtomTable {
|
||||
// take a lock to prevent concurrent updates
|
||||
let update_guard = atom_table.update.lock().unwrap();
|
||||
|
||||
let is_same_allocation =
|
||||
RcuRef::same_epoch(&block_epoch, &atom_table.inner.active_epoch());
|
||||
let is_same_atom_list =
|
||||
RcuRef::same_epoch(&table_epoch, &block_epoch.table.active_epoch());
|
||||
let is_same_allocation = RcuRef::same_epoch(&block_epoch, &atom_table.inner.read());
|
||||
let is_same_atom_list = RcuRef::same_epoch(&table_epoch, &block_epoch.table.read());
|
||||
|
||||
if !(is_same_allocation && is_same_atom_list) {
|
||||
// some other thread raced us between our lookup and
|
||||
@@ -369,8 +494,7 @@ impl AtomTable {
|
||||
}
|
||||
|
||||
let size = mem::size_of::<AtomHeader>() + string.len();
|
||||
let align_offset = 8 * mem::align_of::<AtomHeader>();
|
||||
let size = (size & !(align_offset - 1)) + align_offset;
|
||||
let size = size.next_multiple_of(AtomTable::align());
|
||||
|
||||
unsafe {
|
||||
let len_ptr = loop {
|
||||
@@ -379,26 +503,31 @@ impl AtomTable {
|
||||
if ptr.is_null() {
|
||||
// garbage collection would go here
|
||||
let new_block = block_epoch.block.grow_new().unwrap();
|
||||
let new_table = Rcu::new(table_epoch.clone());
|
||||
let new_table = Arcu::new(table_epoch.clone(), GlobalEpochCounterPool);
|
||||
let new_alloc = InnerAtomTable {
|
||||
block: new_block,
|
||||
table: new_table,
|
||||
};
|
||||
atom_table.inner.replace(new_alloc);
|
||||
block_epoch = atom_table.inner.active_epoch();
|
||||
table_epoch = block_epoch.table.active_epoch();
|
||||
block_epoch = atom_table.inner.read();
|
||||
table_epoch = block_epoch.table.read();
|
||||
} else {
|
||||
break ptr;
|
||||
}
|
||||
};
|
||||
|
||||
let ptr_base = block_epoch.block.base as usize;
|
||||
let ptr_base = block_epoch.block.base.addr();
|
||||
|
||||
write_to_ptr(string, len_ptr);
|
||||
|
||||
let atom = Atom {
|
||||
index: ((STRINGS.len() << 3) + len_ptr as usize - ptr_base) as u64,
|
||||
};
|
||||
let atom = AtomCell::new()
|
||||
.with_name((STRINGS.len() + len_ptr.addr() - ptr_base) as u64)
|
||||
.with_arity(0)
|
||||
.with_f(false)
|
||||
.with_m(false)
|
||||
.with_is_inlined(false)
|
||||
.with_tag(HeapCellValueTag::Atom as u8)
|
||||
.get_name();
|
||||
|
||||
let mut table = table_epoch.clone();
|
||||
table.insert(atom);
|
||||
@@ -415,57 +544,3 @@ impl AtomTable {
|
||||
|
||||
unsafe impl Send for AtomTable {}
|
||||
unsafe impl Sync for AtomTable {}
|
||||
|
||||
#[bitfield]
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AtomCell {
|
||||
name: B46,
|
||||
arity: B10,
|
||||
#[allow(unused)]
|
||||
f: bool,
|
||||
#[allow(unused)]
|
||||
m: bool,
|
||||
#[allow(unused)]
|
||||
tag: B6,
|
||||
}
|
||||
|
||||
impl AtomCell {
|
||||
#[inline]
|
||||
pub fn build_with(name: u64, arity: u16, tag: HeapCellValueTag) -> Self {
|
||||
if arity > 0 {
|
||||
debug_assert!(arity as usize <= MAX_ARITY);
|
||||
|
||||
AtomCell::new()
|
||||
.with_name(name)
|
||||
.with_arity(arity)
|
||||
.with_f(false)
|
||||
.with_tag(tag as u8)
|
||||
} else {
|
||||
AtomCell::new()
|
||||
.with_name(name)
|
||||
.with_f(false)
|
||||
.with_tag(tag as u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_index(self) -> usize {
|
||||
self.name() as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name(self) -> Atom {
|
||||
Atom::from((self.get_index() as u64) << 3)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_arity(self) -> usize {
|
||||
self.arity() as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_name_and_arity(self) -> (Atom, usize) {
|
||||
(Atom::from((self.get_index() as u64) << 3), self.get_arity())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
fn main() -> std::process::ExitCode {
|
||||
use scryer_prolog::*;
|
||||
use scryer_prolog::atom_table::Atom;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[cfg(feature = "repl")]
|
||||
ctrlc::set_handler(move || {
|
||||
scryer_prolog::machine::INTERRUPT.store(true, Ordering::Relaxed);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
return std::process::ExitCode::SUCCESS;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut wam = machine::Machine::new(Default::default());
|
||||
wam.run_top_level(atom!("$toplevel"), (atom!("$repl"), 1))
|
||||
})
|
||||
return scryer_prolog::run_binary();
|
||||
}
|
||||
|
||||
325
src/codegen.rs
325
src/codegen.rs
@@ -8,10 +8,9 @@ use crate::instructions::*;
|
||||
use crate::iterators::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::targets::*;
|
||||
use crate::temp_v;
|
||||
use crate::types::*;
|
||||
use crate::variable_records::*;
|
||||
|
||||
use crate::instr;
|
||||
use crate::machine::disjuncts::*;
|
||||
use crate::machine::machine_errors::*;
|
||||
|
||||
@@ -60,6 +59,7 @@ impl BranchCodeStack {
|
||||
marker: &mut DebrayAllocator,
|
||||
) -> SubsumedBranchHits {
|
||||
let mut subsumed_hits = SubsumedBranchHits::with_hasher(FxBuildHasher::default());
|
||||
let mut propagated_var_nums = IndexSet::with_hasher(FxBuildHasher::default());
|
||||
|
||||
for idx in (self.stack.len() - depth..self.stack.len()).rev() {
|
||||
let branch = &mut marker.branch_stack[idx];
|
||||
@@ -85,9 +85,17 @@ impl BranchCodeStack {
|
||||
}
|
||||
}
|
||||
|
||||
if idx > self.stack.len() - depth {
|
||||
propagated_var_nums.insert(var_num);
|
||||
}
|
||||
|
||||
subsumed_hits.insert(var_num);
|
||||
}
|
||||
}
|
||||
|
||||
for var_num in propagated_var_nums.drain(..) {
|
||||
marker.branch_stack[idx - 1].add_branch_occurrence(var_num);
|
||||
}
|
||||
}
|
||||
|
||||
subsumed_hits
|
||||
@@ -111,7 +119,7 @@ impl BranchCodeStack {
|
||||
jump_span -= code.len() + 1;
|
||||
} else {
|
||||
jump_span -= code.len() + 1;
|
||||
code.push_back(instr!("jmp_by_call", jump_span as usize));
|
||||
code.push_back(instr!("jmp_by_call", jump_span));
|
||||
|
||||
jump_span -= 1;
|
||||
}
|
||||
@@ -124,9 +132,9 @@ impl BranchCodeStack {
|
||||
|
||||
for mut branch_arm in self.stack.drain(self.stack.len() - depth..).rev() {
|
||||
let num_branch_arms = branch_arm.len();
|
||||
branch_arm
|
||||
.last_mut()
|
||||
.map(|code| code.extend(combined_code.drain(..)));
|
||||
if let Some(code) = branch_arm.last_mut() {
|
||||
code.extend(combined_code.drain(..))
|
||||
}
|
||||
|
||||
for (idx, code) in branch_arm.into_iter().enumerate() {
|
||||
combined_code.push_back(if idx == 0 {
|
||||
@@ -261,8 +269,7 @@ impl CodeGenSettings {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CodeGenerator<'a> {
|
||||
pub(crate) atom_tbl: &'a AtomTable,
|
||||
pub(crate) struct CodeGenerator {
|
||||
marker: DebrayAllocator,
|
||||
settings: CodeGenSettings,
|
||||
pub(crate) skeleton: PredicateSkeleton,
|
||||
@@ -277,7 +284,6 @@ impl DebrayAllocator {
|
||||
code: &mut CodeDeque,
|
||||
) -> RegType {
|
||||
self.mark_var::<QueryInstruction>(var_num, Level::Shallow, vr, term_loc, code);
|
||||
|
||||
vr.get().norm()
|
||||
}
|
||||
|
||||
@@ -296,7 +302,14 @@ impl DebrayAllocator {
|
||||
self.mark_var_in_non_callable(var_num, term_loc, vr, code);
|
||||
temp_v!(arg)
|
||||
} else {
|
||||
self.increment_running_count(var_num);
|
||||
if let VarAlloc::Perm(_, PermVarAllocation::Pending) =
|
||||
&self.var_data.records[var_num].allocation
|
||||
{
|
||||
self.mark_var_in_non_callable(var_num, term_loc, vr, code);
|
||||
} else {
|
||||
self.increment_running_count(var_num);
|
||||
}
|
||||
|
||||
RegType::Perm(p)
|
||||
}
|
||||
}
|
||||
@@ -305,33 +318,12 @@ impl DebrayAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
// if the final argument of the structure is a Literal::Index,
|
||||
// decrement the arity of the PutStructure instruction by 1.
|
||||
fn trim_structure_by_last_arg(instr: &mut Instruction, last_arg: &Term) {
|
||||
match instr {
|
||||
Instruction::PutStructure(_, ref mut arity, _)
|
||||
| Instruction::GetStructure(.., ref mut arity, _) => {
|
||||
if let Term::Literal(_, Literal::CodeIndex(_)) = last_arg {
|
||||
// it is acceptable if arity == 0 is the result of
|
||||
// this decrement. call/N will have to read the index
|
||||
// constant for '$call_inline' to succeed. to find it,
|
||||
// it must know the heap location of the index.
|
||||
// self.store must stop before reading the atom into a
|
||||
// register.
|
||||
|
||||
*arity -= 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
trait AddToFreeList<'a, Target: CompilationTarget<'a>> {
|
||||
fn add_term_to_free_list(&mut self, r: RegType);
|
||||
fn add_subterm_to_free_list(&mut self, term: &Term);
|
||||
}
|
||||
|
||||
impl<'a, 'b> AddToFreeList<'a, FactInstruction> for CodeGenerator<'b> {
|
||||
impl<'a> AddToFreeList<'a, FactInstruction> for CodeGenerator {
|
||||
fn add_term_to_free_list(&mut self, r: RegType) {
|
||||
self.marker.add_reg_to_free_list(r);
|
||||
}
|
||||
@@ -339,7 +331,7 @@ impl<'a, 'b> AddToFreeList<'a, FactInstruction> for CodeGenerator<'b> {
|
||||
fn add_subterm_to_free_list(&mut self, _term: &Term) {}
|
||||
}
|
||||
|
||||
impl<'a, 'b> AddToFreeList<'a, QueryInstruction> for CodeGenerator<'b> {
|
||||
impl<'a> AddToFreeList<'a, QueryInstruction> for CodeGenerator {
|
||||
#[inline(always)]
|
||||
fn add_term_to_free_list(&mut self, _r: RegType) {}
|
||||
|
||||
@@ -361,10 +353,9 @@ fn structure_cell(term: &Term) -> Option<&Cell<RegType>> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> CodeGenerator<'b> {
|
||||
pub(crate) fn new(atom_tbl: &'b AtomTable, settings: CodeGenSettings) -> Self {
|
||||
impl CodeGenerator {
|
||||
pub(crate) fn new(settings: CodeGenSettings) -> Self {
|
||||
CodeGenerator {
|
||||
atom_tbl,
|
||||
marker: DebrayAllocator::new(),
|
||||
settings,
|
||||
skeleton: PredicateSkeleton::new(),
|
||||
@@ -376,7 +367,7 @@ impl<'b> CodeGenerator<'b> {
|
||||
Target: crate::targets::CompilationTarget<'a>,
|
||||
{
|
||||
if let Some(ref mut instr) = target.back_mut() {
|
||||
if Target::is_void_instr(&*instr) {
|
||||
if Target::is_void_instr(instr) {
|
||||
Target::incr_void_instr(instr);
|
||||
return;
|
||||
}
|
||||
@@ -418,10 +409,10 @@ impl<'b> CodeGenerator<'b> {
|
||||
.mark_non_var::<Target>(Level::Deep, term_loc, cell, target);
|
||||
target.push_back(Target::clause_arg_to_instr(cell.get()));
|
||||
}
|
||||
&Term::Literal(_, ref constant) => {
|
||||
target.push_back(Target::constant_subterm(constant.clone()));
|
||||
Term::Literal(_, ref constant) => {
|
||||
target.push_back(Target::constant_subterm(*constant));
|
||||
}
|
||||
&Term::Var(ref cell, ref var_ptr) => {
|
||||
Term::Var(ref cell, ref var_ptr) => {
|
||||
self.deep_var_instr::<Target>(
|
||||
cell,
|
||||
var_ptr.to_var_num().unwrap(),
|
||||
@@ -432,99 +423,99 @@ impl<'b> CodeGenerator<'b> {
|
||||
};
|
||||
}
|
||||
|
||||
fn compile_target<'a, Target, Iter>(&mut self, iter: Iter, term_loc: GenContext) -> CodeDeque
|
||||
fn compile_target<'a, Target, Iter>(&mut self, iter: Iter, context: GenContext) -> CodeDeque
|
||||
where
|
||||
Target: crate::targets::CompilationTarget<'a>,
|
||||
Iter: Iterator<Item = TermRef<'a>>,
|
||||
CodeGenerator<'b>: AddToFreeList<'a, Target>,
|
||||
CodeGenerator: AddToFreeList<'a, Target>,
|
||||
{
|
||||
let mut target = CodeDeque::new();
|
||||
|
||||
for term in iter {
|
||||
match term {
|
||||
TermRef::AnonVar(lvl @ Level::Shallow) => {
|
||||
if let GenContext::Head = term_loc {
|
||||
if let GenContext::Head = context {
|
||||
self.marker.advance_arg();
|
||||
} else {
|
||||
self.marker
|
||||
.mark_anon_var::<Target>(lvl, term_loc, &mut target);
|
||||
.mark_anon_var::<Target>(lvl, context, &mut target);
|
||||
}
|
||||
}
|
||||
TermRef::Clause(lvl, cell, name, terms) => {
|
||||
self.marker
|
||||
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
|
||||
target.push_back(Target::to_structure(lvl, name, terms.len(), cell.get()));
|
||||
let terms_range =
|
||||
if let Some(subterm @ Term::Literal(_, Literal::CodeIndexOffset(_))) =
|
||||
terms.last()
|
||||
{
|
||||
self.subterm_to_instr::<Target>(subterm, context, &mut target);
|
||||
0..terms.len() - 1
|
||||
} else {
|
||||
0..terms.len()
|
||||
};
|
||||
|
||||
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_term_to_free_list(
|
||||
self.marker
|
||||
.mark_non_var::<Target>(lvl, context, cell, &mut target);
|
||||
target.push_back(Target::to_structure(lvl, name, terms_range.end, cell.get()));
|
||||
|
||||
<CodeGenerator as AddToFreeList<'a, Target>>::add_term_to_free_list(
|
||||
self,
|
||||
cell.get(),
|
||||
);
|
||||
|
||||
if let Some(instr) = target.back_mut() {
|
||||
if let Some(term) = terms.last() {
|
||||
trim_structure_by_last_arg(instr, term);
|
||||
}
|
||||
for subterm in &terms[terms_range.clone()] {
|
||||
self.subterm_to_instr::<Target>(subterm, context, &mut target);
|
||||
}
|
||||
|
||||
for subterm in terms {
|
||||
self.subterm_to_instr::<Target>(subterm, term_loc, &mut target);
|
||||
}
|
||||
|
||||
for subterm in terms {
|
||||
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
|
||||
for subterm in &terms[terms_range] {
|
||||
<CodeGenerator as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
|
||||
self, subterm,
|
||||
);
|
||||
}
|
||||
}
|
||||
TermRef::Cons(lvl, cell, head, tail) => {
|
||||
self.marker
|
||||
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
|
||||
.mark_non_var::<Target>(lvl, context, cell, &mut target);
|
||||
target.push_back(Target::to_list(lvl, cell.get()));
|
||||
|
||||
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_term_to_free_list(
|
||||
<CodeGenerator as AddToFreeList<'a, Target>>::add_term_to_free_list(
|
||||
self,
|
||||
cell.get(),
|
||||
);
|
||||
|
||||
self.subterm_to_instr::<Target>(head, term_loc, &mut target);
|
||||
self.subterm_to_instr::<Target>(tail, term_loc, &mut target);
|
||||
self.subterm_to_instr::<Target>(head, context, &mut target);
|
||||
self.subterm_to_instr::<Target>(tail, context, &mut target);
|
||||
|
||||
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
|
||||
<CodeGenerator as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
|
||||
self, head,
|
||||
);
|
||||
<CodeGenerator<'b> as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
|
||||
<CodeGenerator as AddToFreeList<'a, Target>>::add_subterm_to_free_list(
|
||||
self, tail,
|
||||
);
|
||||
}
|
||||
TermRef::Literal(lvl @ Level::Shallow, cell, Literal::String(ref string)) => {
|
||||
self.marker
|
||||
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
|
||||
target.push_back(Target::to_pstr(lvl, *string, cell.get(), false));
|
||||
}
|
||||
TermRef::Literal(lvl @ Level::Shallow, cell, constant) => {
|
||||
self.marker
|
||||
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
|
||||
.mark_non_var::<Target>(lvl, context, cell, &mut target);
|
||||
target.push_back(Target::to_constant(lvl, *constant, cell.get()));
|
||||
}
|
||||
TermRef::PartialString(lvl, cell, string, tail) => {
|
||||
self.marker
|
||||
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
|
||||
let atom = AtomTable::build_with(&self.atom_tbl, &string);
|
||||
.mark_non_var::<Target>(lvl, context, cell, &mut target);
|
||||
|
||||
target.push_back(Target::to_pstr(lvl, atom, cell.get(), true));
|
||||
self.subterm_to_instr::<Target>(tail, term_loc, &mut target);
|
||||
target.push_back(Target::to_pstr(lvl, string.clone(), cell.get()));
|
||||
self.subterm_to_instr::<Target>(tail, context, &mut target);
|
||||
}
|
||||
TermRef::CompleteString(lvl, cell, atom) => {
|
||||
TermRef::CompleteString(lvl, cell, string) => {
|
||||
self.marker
|
||||
.mark_non_var::<Target>(lvl, term_loc, cell, &mut target);
|
||||
target.push_back(Target::to_pstr(lvl, atom, cell.get(), false));
|
||||
.mark_non_var::<Target>(lvl, context, cell, &mut target);
|
||||
|
||||
target.push_back(Target::to_pstr(lvl, string.clone(), cell.get()));
|
||||
target.push_back(Target::constant_subterm(Literal::Atom(atom!("[]"))));
|
||||
}
|
||||
TermRef::Var(lvl @ Level::Shallow, cell, var) => {
|
||||
self.marker.mark_var::<Target>(
|
||||
var.to_var_num().unwrap(),
|
||||
lvl,
|
||||
cell,
|
||||
term_loc,
|
||||
context,
|
||||
&mut target,
|
||||
);
|
||||
}
|
||||
@@ -543,14 +534,14 @@ impl<'b> CodeGenerator<'b> {
|
||||
match call_policy {
|
||||
CallPolicy::Default => {
|
||||
if self.marker.in_tail_position {
|
||||
code.push_back(call_instr.to_execute().to_default());
|
||||
code.push_back(call_instr.into_execute().into_default());
|
||||
} else {
|
||||
code.push_back(call_instr.to_default())
|
||||
code.push_back(call_instr.into_default())
|
||||
}
|
||||
}
|
||||
CallPolicy::Counted => {
|
||||
if self.marker.in_tail_position {
|
||||
code.push_back(call_instr.to_execute());
|
||||
code.push_back(call_instr.into_execute());
|
||||
} else {
|
||||
code.push_back(call_instr)
|
||||
}
|
||||
@@ -558,10 +549,10 @@ impl<'b> CodeGenerator<'b> {
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_inlined<'a>(
|
||||
fn compile_inlined(
|
||||
&mut self,
|
||||
ct: &InlinedClauseType,
|
||||
terms: &'a Vec<Term>,
|
||||
terms: &'_ [Term],
|
||||
term_loc: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
) -> Result<(), CompilationError> {
|
||||
@@ -585,13 +576,11 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
compare_number_instr!(cmp, at_1, at_2)
|
||||
}
|
||||
&InlinedClauseType::IsAtom(..) => match &terms[0] {
|
||||
&Term::Literal(_, Literal::Char(_))
|
||||
| &Term::Literal(_, Literal::Atom(atom!("[]")))
|
||||
| &Term::Literal(_, Literal::Atom(..)) => {
|
||||
InlinedClauseType::IsAtom(..) => match &terms[0] {
|
||||
Term::Literal(_, Literal::Atom(..)) => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -608,21 +597,18 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("$fail")
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsAtomic(..) => match &terms[0] {
|
||||
&Term::AnonVar
|
||||
| &Term::Clause(..)
|
||||
| &Term::Cons(..)
|
||||
| &Term::PartialString(..)
|
||||
| &Term::CompleteString(..) => {
|
||||
InlinedClauseType::IsAtomic(..) => match &terms[0] {
|
||||
Term::AnonVar
|
||||
| Term::Clause(..)
|
||||
| Term::Cons(..)
|
||||
| Term::PartialString(..)
|
||||
| Term::CompleteString(..) => {
|
||||
instr!("$fail")
|
||||
}
|
||||
&Term::Literal(_, Literal::String(_)) => {
|
||||
instr!("$fail")
|
||||
}
|
||||
&Term::Literal(..) => {
|
||||
Term::Literal(..) => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -636,15 +622,14 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("atomic", r)
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsCompound(..) => match &terms[0] {
|
||||
&Term::Clause(..)
|
||||
| &Term::Cons(..)
|
||||
| &Term::PartialString(..)
|
||||
| &Term::CompleteString(..)
|
||||
| &Term::Literal(_, Literal::String(..)) => {
|
||||
InlinedClauseType::IsCompound(..) => match &terms[0] {
|
||||
Term::Clause(..)
|
||||
| Term::Cons(..)
|
||||
| Term::PartialString(..)
|
||||
| Term::CompleteString(..) => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -661,11 +646,11 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("$fail")
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsRational(..) => match &terms[0] {
|
||||
&Term::Literal(_, Literal::Rational(_)) => {
|
||||
InlinedClauseType::IsRational(..) => match terms[0] {
|
||||
Term::Literal(_, Literal::Rational(_)) => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
let r = self.marker.mark_non_callable(
|
||||
name.to_var_num().unwrap(),
|
||||
@@ -680,11 +665,11 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("$fail")
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsFloat(..) => match &terms[0] {
|
||||
&Term::Literal(_, Literal::Float(_)) => {
|
||||
InlinedClauseType::IsFloat(..) => match terms[0] {
|
||||
Term::Literal(_, Literal::F64(..)) => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -701,14 +686,14 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("$fail")
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsNumber(..) => match &terms[0] {
|
||||
&Term::Literal(_, Literal::Float(_))
|
||||
| &Term::Literal(_, Literal::Rational(_))
|
||||
| &Term::Literal(_, Literal::Integer(_))
|
||||
| &Term::Literal(_, Literal::Fixnum(_)) => {
|
||||
InlinedClauseType::IsNumber(..) => match terms[0] {
|
||||
Term::Literal(_, Literal::F64(..))
|
||||
| Term::Literal(_, Literal::Rational(_))
|
||||
| Term::Literal(_, Literal::Integer(_))
|
||||
| Term::Literal(_, Literal::Fixnum(_)) => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -725,11 +710,11 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("$fail")
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsNonVar(..) => match &terms[0] {
|
||||
&Term::AnonVar => {
|
||||
InlinedClauseType::IsNonVar(..) => match terms[0] {
|
||||
Term::AnonVar => {
|
||||
instr!("$fail")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -746,11 +731,11 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("$succeed")
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsInteger(..) => match &terms[0] {
|
||||
&Term::Literal(_, Literal::Integer(_)) | &Term::Literal(_, Literal::Fixnum(_)) => {
|
||||
InlinedClauseType::IsInteger(..) => match &terms[0] {
|
||||
Term::Literal(_, Literal::Integer(_)) | Term::Literal(_, Literal::Fixnum(_)) => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -767,18 +752,18 @@ impl<'b> CodeGenerator<'b> {
|
||||
instr!("$fail")
|
||||
}
|
||||
},
|
||||
&InlinedClauseType::IsVar(..) => match &terms[0] {
|
||||
&Term::Literal(..)
|
||||
| &Term::Clause(..)
|
||||
| &Term::Cons(..)
|
||||
| &Term::PartialString(..)
|
||||
| &Term::CompleteString(..) => {
|
||||
InlinedClauseType::IsVar(..) => match terms[0] {
|
||||
Term::Literal(..)
|
||||
| Term::Clause(..)
|
||||
| Term::Cons(..)
|
||||
| Term::PartialString(..)
|
||||
| Term::CompleteString(..) => {
|
||||
instr!("$fail")
|
||||
}
|
||||
&Term::AnonVar => {
|
||||
Term::AnonVar => {
|
||||
instr!("$succeed")
|
||||
}
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
self.marker.reset_arg(1);
|
||||
|
||||
let r = self.marker.mark_non_callable(
|
||||
@@ -796,7 +781,6 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
// inlined predicates are never counted, so this overrides nothing.
|
||||
self.add_call(code, call_instr, CallPolicy::Counted);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -813,7 +797,7 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
fn compile_is_call(
|
||||
&mut self,
|
||||
terms: &Vec<Term>,
|
||||
terms: &[Term],
|
||||
code: &mut CodeDeque,
|
||||
term_loc: GenContext,
|
||||
call_policy: CallPolicy,
|
||||
@@ -828,8 +812,8 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
self.marker.reset_arg(2);
|
||||
|
||||
let at = match &terms[0] {
|
||||
&Term::Var(ref vr, ref name) => {
|
||||
let at = match terms[0] {
|
||||
Term::Var(ref vr, ref name) => {
|
||||
let var_num = name.to_var_num().unwrap();
|
||||
|
||||
if self.marker.var_data.records[var_num].num_occurrences > 1 {
|
||||
@@ -844,6 +828,9 @@ impl<'b> CodeGenerator<'b> {
|
||||
self.marker.mark_safe_var_unconditionally(var_num);
|
||||
compile_expr!(self, &terms[1], term_loc, code)
|
||||
} else {
|
||||
self.marker
|
||||
.mark_anon_var::<QueryInstruction>(Level::Shallow, term_loc, code);
|
||||
|
||||
if let Term::Var(ref vr, ref var) = &terms[1] {
|
||||
let var_num = var.to_var_num().unwrap();
|
||||
|
||||
@@ -871,10 +858,10 @@ impl<'b> CodeGenerator<'b> {
|
||||
compile_expr!(self, &terms[1], term_loc, code)
|
||||
}
|
||||
}
|
||||
&Term::Literal(
|
||||
Term::Literal(
|
||||
_,
|
||||
c @ Literal::Integer(_)
|
||||
| c @ Literal::Float(_)
|
||||
| c @ Literal::F64(..)
|
||||
| c @ Literal::Rational(_)
|
||||
| c @ Literal::Fixnum(_),
|
||||
) => {
|
||||
@@ -892,11 +879,10 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
let at = at.unwrap_or(interm!(1));
|
||||
self.add_call(code, instr!("is", temp_v!(1), at), call_policy);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_seq<'a>(
|
||||
fn compile_seq(
|
||||
&mut self,
|
||||
clauses: &ChunkedTermVec,
|
||||
code: &mut CodeDeque,
|
||||
@@ -907,9 +893,9 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
while let Some(clause_item) = clause_iter.next() {
|
||||
match clause_item {
|
||||
ClauseItem::Chunk(chunk) => {
|
||||
for (idx, term) in chunk.iter().enumerate() {
|
||||
let term_loc = if idx + 1 < chunk.len() {
|
||||
ClauseItem::Chunk { terms } => {
|
||||
for (idx, term) in terms.iter().enumerate() {
|
||||
let term_loc = if idx + 1 < terms.len() {
|
||||
GenContext::Mid(chunk_num)
|
||||
} else {
|
||||
self.marker.in_tail_position = clause_iter.in_tail_position();
|
||||
@@ -1066,7 +1052,7 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
self.marker.reset_at_head(args);
|
||||
|
||||
let iter = FactIterator::from_rule_head_clause(&args);
|
||||
let iter = FactIterator::from_rule_head_clause(args);
|
||||
let fact = self.compile_target::<FactInstruction, _>(iter, GenContext::Head);
|
||||
|
||||
if self.marker.max_reg_allocated() > MAX_ARITY {
|
||||
@@ -1074,7 +1060,7 @@ impl<'b> CodeGenerator<'b> {
|
||||
}
|
||||
|
||||
self.marker.reset_free_list();
|
||||
code.extend(fact.into_iter());
|
||||
code.extend(fact);
|
||||
|
||||
self.compile_seq(clauses, &mut code)?;
|
||||
|
||||
@@ -1099,7 +1085,7 @@ impl<'b> CodeGenerator<'b> {
|
||||
return Err(CompilationError::ExceededMaxArity);
|
||||
}
|
||||
|
||||
code.extend(compiled_fact.into_iter());
|
||||
code.extend(compiled_fact);
|
||||
}
|
||||
|
||||
code.push(instr!("proceed"));
|
||||
@@ -1112,7 +1098,7 @@ impl<'b> CodeGenerator<'b> {
|
||||
let iter = QueryIterator::new(term);
|
||||
let query = self.compile_target::<QueryInstruction, _>(iter, term_loc);
|
||||
|
||||
code.extend(query.into_iter());
|
||||
code.extend(query);
|
||||
|
||||
match term {
|
||||
&QueryTerm::Clause(_, ref ct, _, call_policy) => {
|
||||
@@ -1130,27 +1116,24 @@ impl<'b> CodeGenerator<'b> {
|
||||
'outer: for (right, clause) in clauses.iter().enumerate() {
|
||||
if let Some(args) = clause.args() {
|
||||
for (instantiated_arg_index, arg) in args.iter().enumerate() {
|
||||
match arg {
|
||||
Term::Var(..) | Term::AnonVar => {}
|
||||
_ => {
|
||||
if optimal_index != instantiated_arg_index {
|
||||
if left >= right {
|
||||
optimal_index = instantiated_arg_index;
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
subseqs.push(ClauseSpan {
|
||||
left,
|
||||
right,
|
||||
instantiated_arg_index: optimal_index,
|
||||
});
|
||||
|
||||
if !matches!(arg, Term::Var(..) | Term::AnonVar) {
|
||||
if optimal_index != instantiated_arg_index {
|
||||
if left >= right {
|
||||
optimal_index = instantiated_arg_index;
|
||||
left = right;
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
continue 'outer;
|
||||
subseqs.push(ClauseSpan {
|
||||
left,
|
||||
right,
|
||||
instantiated_arg_index: optimal_index,
|
||||
});
|
||||
|
||||
optimal_index = instantiated_arg_index;
|
||||
left = right;
|
||||
}
|
||||
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1204,12 +1187,12 @@ impl<'b> CodeGenerator<'b> {
|
||||
|
||||
let clause_code = match clause {
|
||||
PredicateClause::Fact(fact, var_data) => {
|
||||
let var_data = std::mem::replace(var_data, VarData::default());
|
||||
self.compile_fact(&fact, var_data)?
|
||||
let var_data = std::mem::take(var_data);
|
||||
self.compile_fact(fact, var_data)?
|
||||
}
|
||||
PredicateClause::Rule(rule, var_data) => {
|
||||
let var_data = std::mem::replace(var_data, VarData::default());
|
||||
self.compile_rule(&rule, var_data)?
|
||||
let var_data = std::mem::take(var_data);
|
||||
self.compile_rule(rule, var_data)?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1237,15 +1220,13 @@ impl<'b> CodeGenerator<'b> {
|
||||
skip_stub_try_me_else = !self.settings.is_dynamic();
|
||||
}
|
||||
|
||||
let arg = clause
|
||||
.args()
|
||||
.and_then(|args| args.iter().nth(optimal_index));
|
||||
let arg = clause.args().and_then(|args| args.get(optimal_index));
|
||||
|
||||
if let Some(arg) = arg {
|
||||
let index = code.len();
|
||||
|
||||
if clauses_len > 1 || self.settings.is_extensible {
|
||||
code_offsets.index_term(arg, index, &mut clause_index_info, self.atom_tbl);
|
||||
code_offsets.index_term(arg, index, &mut clause_index_info);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::allocator::*;
|
||||
use crate::codegen::SubsumedBranchHits;
|
||||
use crate::forms::Level;
|
||||
use crate::forms::{GenContext, Level};
|
||||
use crate::instructions::*;
|
||||
use crate::machine::disjuncts::VarData;
|
||||
use crate::parser::ast::*;
|
||||
@@ -39,6 +39,19 @@ impl BranchOccurrences {
|
||||
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)]
|
||||
@@ -92,17 +105,7 @@ impl BranchStack {
|
||||
|
||||
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
|
||||
if let Some(occurrences) = self.last_mut() {
|
||||
debug_assert!(occurrences.current_branch < occurrences.num_branches);
|
||||
|
||||
let num_branches = occurrences.num_branches;
|
||||
|
||||
let entry = occurrences
|
||||
.hits
|
||||
.entry(var_num)
|
||||
.or_insert_with(|| BitVec::repeat(false, num_branches));
|
||||
|
||||
entry.set(occurrences.current_branch, true);
|
||||
occurrences.subsumed_hits.insert(var_num);
|
||||
occurrences.add_branch_occurrence(var_num);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +133,10 @@ impl BranchStack {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn drain_branches(&mut self, depth: usize) -> std::vec::Drain<BranchOccurrences> {
|
||||
pub(crate) fn drain_branches(
|
||||
&mut self,
|
||||
depth: usize,
|
||||
) -> std::vec::Drain<'_, BranchOccurrences> {
|
||||
let start_idx = self.len() - depth;
|
||||
self.drain(start_idx..)
|
||||
}
|
||||
@@ -166,30 +172,26 @@ impl DebrayAllocator {
|
||||
for var_num in subsumed_hits {
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, ref mut allocation) => {
|
||||
match allocation {
|
||||
PermVarAllocation::Done {
|
||||
shallow_safety,
|
||||
deep_safety,
|
||||
..
|
||||
} => {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +254,7 @@ impl DebrayAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
if self.branch_stack.len() > 0 {
|
||||
if !self.branch_stack.is_empty() {
|
||||
for var_num in subsumed_hits {
|
||||
self.branch_stack.add_branch_occurrence(var_num);
|
||||
}
|
||||
@@ -295,11 +297,9 @@ impl DebrayAllocator {
|
||||
let mut result = 0;
|
||||
|
||||
for reg in self.temp_lb.. {
|
||||
if !self.is_in_use(reg) {
|
||||
if !temp_var_data.no_use_set.contains(reg) {
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
if !self.is_in_use(reg) && !temp_var_data.no_use_set.contains(reg) {
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,13 +321,12 @@ impl DebrayAllocator {
|
||||
let mut result = 0;
|
||||
|
||||
for reg in self.temp_lb.. {
|
||||
if !self.is_in_use(reg) {
|
||||
if !temp_var_data.no_use_set.contains(reg) {
|
||||
if !temp_var_data.conflict_set.contains(reg) {
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !self.is_in_use(reg)
|
||||
&& !temp_var_data.no_use_set.contains(reg)
|
||||
&& !temp_var_data.conflict_set.contains(reg)
|
||||
{
|
||||
result = reg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,16 +348,15 @@ impl DebrayAllocator {
|
||||
// consider its use set. T == par_k iff
|
||||
// (GenContext::Last(_), k) is in t_var.use_set.
|
||||
|
||||
match &self.var_data.records[t_var].allocation {
|
||||
VarAlloc::Temp { temp_var_data, .. } => {
|
||||
if !temp_var_data
|
||||
.use_set
|
||||
.contains(&(GenContext::Last(chunk_num), k))
|
||||
{
|
||||
return Some((t_var, 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)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
@@ -372,25 +370,22 @@ impl DebrayAllocator {
|
||||
chunk_num: usize,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
match self.alloc_in_last_goal_hint(chunk_num) {
|
||||
Some((var_num, 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_back(Target::move_to_register(r, k));
|
||||
code.push_back(Target::move_to_register(r, k));
|
||||
|
||||
self.shallow_temp_mappings.swap_remove(&k);
|
||||
self.shallow_temp_mappings.insert(r.reg_num(), var_num);
|
||||
self.shallow_temp_mappings.swap_remove(&k);
|
||||
self.shallow_temp_mappings.insert(r.reg_num(), var_num);
|
||||
|
||||
self.var_data.records[var_num]
|
||||
.allocation
|
||||
.set_register(r.reg_num());
|
||||
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());
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -493,11 +488,8 @@ impl DebrayAllocator {
|
||||
}
|
||||
|
||||
fn add_perm_to_free_list(&mut self, chunk_num: usize, var_num: usize) {
|
||||
match &self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(..) => {
|
||||
self.perm_free_list.push_back((chunk_num, var_num));
|
||||
}
|
||||
_ => {}
|
||||
if let VarAlloc::Perm(..) = &self.var_data.records[var_num].allocation {
|
||||
self.perm_free_list.push_back((chunk_num, var_num));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,12 +513,9 @@ impl DebrayAllocator {
|
||||
}
|
||||
|
||||
pub(crate) fn free_var(&mut self, chunk_num: usize, var_num: usize) {
|
||||
match &mut self.var_data.records[var_num].allocation {
|
||||
VarAlloc::Perm(_, allocation) => {
|
||||
*allocation = PermVarAllocation::Pending;
|
||||
self.add_perm_to_free_list(chunk_num, var_num);
|
||||
}
|
||||
_ => {}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,7 +537,9 @@ impl DebrayAllocator {
|
||||
VarAlloc::Temp { safety, .. } => {
|
||||
*safety = VarSafetyStatus::unneeded(branch_designator);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,18 +561,15 @@ impl DebrayAllocator {
|
||||
*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).cloned()
|
||||
{
|
||||
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!(),
|
||||
} 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -621,16 +609,9 @@ impl DebrayAllocator {
|
||||
Target::unsafe_argument_to_value(r, arg_c)
|
||||
}
|
||||
}
|
||||
VarAlloc::Temp { ref mut safety, .. } => {
|
||||
if self
|
||||
.branch_stack
|
||||
.safety_unneeded_in_branch(safety, &branch_designator)
|
||||
{
|
||||
Target::argument_to_value(r, arg_c)
|
||||
} else {
|
||||
*safety = VarSafetyStatus::GloballyUnneeded;
|
||||
Target::unsafe_argument_to_value(r, arg_c)
|
||||
}
|
||||
VarAlloc::Temp { .. } => {
|
||||
debug_assert!(matches!(r, RegType::Temp(_)));
|
||||
Target::argument_to_value(r, arg_c)
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
@@ -703,7 +684,7 @@ impl Allocator for DebrayAllocator {
|
||||
lvl: Level,
|
||||
term_loc: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
) -> RegType {
|
||||
let r = RegType::Temp(self.alloc_reg_to_non_var());
|
||||
|
||||
match lvl {
|
||||
@@ -720,6 +701,8 @@ impl Allocator for DebrayAllocator {
|
||||
code.push_back(Target::argument_to_variable(r, k));
|
||||
}
|
||||
};
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
|
||||
@@ -756,7 +739,7 @@ impl Allocator for DebrayAllocator {
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
cell: &Cell<VarReg>,
|
||||
term_loc: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
) {
|
||||
@@ -764,11 +747,11 @@ impl Allocator for DebrayAllocator {
|
||||
RegType::Temp(0) => {
|
||||
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 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(_) => {
|
||||
@@ -796,7 +779,7 @@ impl Allocator for DebrayAllocator {
|
||||
&mut self,
|
||||
var_num: usize,
|
||||
lvl: Level,
|
||||
cell: &'a Cell<VarReg>,
|
||||
cell: &Cell<VarReg>,
|
||||
term_loc: GenContext,
|
||||
code: &mut CodeDeque,
|
||||
r: RegType,
|
||||
@@ -862,8 +845,21 @@ impl Allocator for DebrayAllocator {
|
||||
|
||||
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType {
|
||||
match self.get_binding(var_num) {
|
||||
RegType::Perm(0) | RegType::Temp(0) => {
|
||||
RegType::Perm(self.alloc_perm_var(var_num, chunk_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,
|
||||
}
|
||||
@@ -886,12 +882,12 @@ impl Allocator for DebrayAllocator {
|
||||
self.arg_c += 1;
|
||||
}
|
||||
|
||||
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 {
|
||||
if let Term::Var(_, ref var) = arg {
|
||||
let var_num = var.to_var_num().unwrap();
|
||||
let r = self.get_binding(var_num);
|
||||
|
||||
|
||||
1213
src/ffi.rs
1213
src/ffi.rs
File diff suppressed because it is too large
Load Diff
235
src/forms.rs
235
src/forms.rs
@@ -1,8 +1,8 @@
|
||||
use crate::arena::*;
|
||||
use crate::atom_table::*;
|
||||
use crate::functor_macro::*;
|
||||
use crate::instructions::*;
|
||||
use crate::machine::disjuncts::VarData;
|
||||
use crate::machine::heap::*;
|
||||
use crate::machine::loader::PredicateQueue;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::machine::machine_indices::*;
|
||||
@@ -11,6 +11,7 @@ use crate::parser::dashu::{Integer, Rational};
|
||||
use crate::parser::parser::CompositeOpDesc;
|
||||
use crate::types::*;
|
||||
|
||||
use dashu::base::Signed;
|
||||
use fxhash::FxBuildHasher;
|
||||
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
@@ -23,22 +24,8 @@ use std::fmt;
|
||||
use std::ops::{AddAssign, Deref, DerefMut};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{is_infix, is_postfix};
|
||||
|
||||
pub type PredicateKey = (Atom, usize); // name, arity.
|
||||
|
||||
/*
|
||||
// vars of predicate, toplevel offset. Vec<Term> is always a vector
|
||||
// of vars (we get their adjoining cells this way).
|
||||
pub type JumpStub = Vec<Term>;
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TopLevel {
|
||||
Fact(Fact, VarData), // Term, line_num, col_num
|
||||
Rule(Rule, VarData), // Rule, line_num, col_num
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AppendOrPrepend {
|
||||
Append,
|
||||
@@ -83,6 +70,28 @@ pub enum CallPolicy {
|
||||
Counted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum GenContext {
|
||||
Head,
|
||||
Mid(usize),
|
||||
Last(usize), // Mid & Last: chunk_num
|
||||
}
|
||||
|
||||
impl GenContext {
|
||||
#[inline]
|
||||
pub fn chunk_num(&self) -> usize {
|
||||
match self {
|
||||
GenContext::Head => 0,
|
||||
&GenContext::Mid(cn) | &GenContext::Last(cn) => cn,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_last(self) -> bool {
|
||||
matches!(self, GenContext::Last(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ChunkType {
|
||||
Head,
|
||||
@@ -99,11 +108,7 @@ pub enum RootIterationPolicy {
|
||||
impl RootIterationPolicy {
|
||||
#[inline(always)]
|
||||
pub fn iterable(&self) -> bool {
|
||||
if let RootIterationPolicy::Iterated = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(self, RootIterationPolicy::Iterated)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +131,7 @@ impl ChunkType {
|
||||
#[derive(Debug)]
|
||||
pub enum ChunkedTerms {
|
||||
Branch(Vec<VecDeque<ChunkedTerms>>),
|
||||
Chunk(VecDeque<QueryTerm>),
|
||||
Chunk { terms: VecDeque<QueryTerm> },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -151,6 +156,7 @@ impl DerefMut for ChunkedTermVec {
|
||||
}
|
||||
|
||||
impl ChunkedTermVec {
|
||||
#[allow(clippy::new_without_default)]
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@@ -163,35 +169,32 @@ impl ChunkedTermVec {
|
||||
.push_back(ChunkedTerms::Branch(Vec::with_capacity(capacity)));
|
||||
}
|
||||
|
||||
pub fn push_branch_arm(&mut self, branch: VecDeque<ChunkedTerms>) {
|
||||
match self.chunk_vec.back_mut().unwrap() {
|
||||
ChunkedTerms::Branch(branches) => {
|
||||
branches.push(branch);
|
||||
}
|
||||
ChunkedTerms::Chunk(_) => {
|
||||
self.chunk_vec.push_back(ChunkedTerms::Branch(vec![branch]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_chunk(&mut self) {
|
||||
self.chunk_vec
|
||||
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![])));
|
||||
let chunk = ChunkedTerms::Chunk {
|
||||
terms: VecDeque::from(vec![]),
|
||||
};
|
||||
self.chunk_vec.push_back(chunk);
|
||||
}
|
||||
|
||||
pub fn push_chunk_term(&mut self, term: QueryTerm) {
|
||||
match self.chunk_vec.back_mut() {
|
||||
Some(ChunkedTerms::Branch(_)) => {
|
||||
self.chunk_vec
|
||||
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
let chunk = ChunkedTerms::Chunk {
|
||||
terms: VecDeque::from(vec![term]),
|
||||
};
|
||||
|
||||
self.chunk_vec.push_back(chunk);
|
||||
}
|
||||
Some(ChunkedTerms::Chunk(chunk)) => {
|
||||
chunk.push_back(term);
|
||||
Some(ChunkedTerms::Chunk { terms, .. }) => {
|
||||
terms.push_back(term);
|
||||
}
|
||||
None => {
|
||||
self.chunk_vec
|
||||
.push_back(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
let chunk = ChunkedTerms::Chunk {
|
||||
terms: VecDeque::from(vec![term]),
|
||||
};
|
||||
|
||||
self.chunk_vec.push_back(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,8 +205,8 @@ pub enum QueryTerm {
|
||||
// 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
|
||||
LocalCut { var_num: usize, cut_prev: bool }, // var_num
|
||||
GlobalCut(usize), // var_num
|
||||
GetCutPoint { var_num: usize, prev_b: bool },
|
||||
GetLevel(usize), // var_num
|
||||
}
|
||||
@@ -211,7 +214,7 @@ pub enum QueryTerm {
|
||||
impl QueryTerm {
|
||||
pub(crate) fn arity(&self) -> usize {
|
||||
match self {
|
||||
&QueryTerm::Clause(_, _, ref subterms, ..) => subterms.len(),
|
||||
QueryTerm::Clause(_, _, subterms, ..) => subterms.len(),
|
||||
&QueryTerm::GetLevel(_) | &QueryTerm::GetCutPoint { .. } => 1,
|
||||
_ => 0,
|
||||
}
|
||||
@@ -316,15 +319,15 @@ impl ClauseInfo for Rule {
|
||||
impl ClauseInfo for PredicateClause {
|
||||
fn name(&self) -> Option<Atom> {
|
||||
match self {
|
||||
&PredicateClause::Fact(ref term, ..) => term.head.name(),
|
||||
&PredicateClause::Rule(ref rule, ..) => rule.name(),
|
||||
PredicateClause::Fact(ref term, ..) => term.head.name(),
|
||||
PredicateClause::Rule(ref rule, ..) => rule.name(),
|
||||
}
|
||||
}
|
||||
|
||||
fn arity(&self) -> usize {
|
||||
match self {
|
||||
&PredicateClause::Fact(ref term, ..) => term.head.arity(),
|
||||
&PredicateClause::Rule(ref rule, ..) => rule.arity(),
|
||||
PredicateClause::Fact(ref term, ..) => term.head.arity(),
|
||||
PredicateClause::Rule(ref rule, ..) => rule.arity(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +342,7 @@ impl PredicateClause {
|
||||
pub(crate) fn args(&self) -> Option<&[Term]> {
|
||||
match self {
|
||||
PredicateClause::Fact(term, ..) => match &term.head {
|
||||
Term::Clause(_, _, args) => Some(&args),
|
||||
Term::Clause(_, _, args) => Some(args),
|
||||
_ => None,
|
||||
},
|
||||
PredicateClause::Rule(rule, ..) => {
|
||||
@@ -368,9 +371,9 @@ pub enum ModuleSource {
|
||||
|
||||
impl ModuleSource {
|
||||
pub(crate) fn as_functor_stub(&self) -> MachineStub {
|
||||
match self {
|
||||
match *self {
|
||||
ModuleSource::Library(name) => {
|
||||
functor!(atom!("library"), [atom(name)])
|
||||
functor!(atom!("library"), [atom_as_cell(name)])
|
||||
}
|
||||
ModuleSource::File(name) => {
|
||||
functor!(name)
|
||||
@@ -405,16 +408,6 @@ pub struct OpDecl {
|
||||
pub(crate) name: Atom,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn fixity(spec: u32) -> Fixity {
|
||||
match spec {
|
||||
XFY | XFX | YFX => Fixity::In,
|
||||
XF | YF => Fixity::Post,
|
||||
FX | FY => Fixity::Pre,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl OpDecl {
|
||||
#[inline]
|
||||
pub(crate) fn new(op_desc: OpDesc, name: Atom) -> Self {
|
||||
@@ -431,15 +424,12 @@ impl OpDecl {
|
||||
}
|
||||
|
||||
pub(crate) fn insert_into_op_dir(&self, op_dir: &mut OpDir) -> Option<OpDesc> {
|
||||
let key = (self.name, fixity(self.op_desc.get_spec() as u32));
|
||||
let key = (self.name, self.op_desc.get_spec().fixity());
|
||||
|
||||
match op_dir.get_mut(&key) {
|
||||
Some(cell) => {
|
||||
let (old_prec, old_spec) = cell.get();
|
||||
cell.set(self.op_desc.get_prec(), self.op_desc.get_spec());
|
||||
return Some(OpDesc::build_with(old_prec, old_spec));
|
||||
}
|
||||
None => {}
|
||||
if let Some(cell) = op_dir.get_mut(&key) {
|
||||
let (old_prec, old_spec) = cell.get();
|
||||
cell.set(self.op_desc.get_prec(), self.op_desc.get_spec());
|
||||
return Some(OpDesc::build_with(old_prec, old_spec));
|
||||
}
|
||||
|
||||
op_dir.insert(key, self.op_desc)
|
||||
@@ -450,9 +440,9 @@ impl OpDecl {
|
||||
existing_desc: Option<CompositeOpDesc>,
|
||||
op_dir: &mut OpDir,
|
||||
) -> Result<(), SessionError> {
|
||||
let (spec, name) = (self.op_desc.get_spec(), self.name.clone());
|
||||
let (spec, name) = (self.op_desc.get_spec(), self.name);
|
||||
|
||||
if is_infix!(spec as u32) {
|
||||
if spec.is_infix() {
|
||||
if let Some(desc) = existing_desc {
|
||||
if desc.post > 0 {
|
||||
return Err(SessionError::OpIsInfixAndPostFix(name));
|
||||
@@ -460,7 +450,7 @@ impl OpDecl {
|
||||
}
|
||||
}
|
||||
|
||||
if is_postfix!(spec as u32) {
|
||||
if spec.is_postfix() {
|
||||
if let Some(desc) = existing_desc {
|
||||
if desc.inf > 0 {
|
||||
return Err(SessionError::OpIsInfixAndPostFix(name));
|
||||
@@ -484,7 +474,7 @@ impl AtomOrString {
|
||||
pub fn as_atom(&self, atom_tbl: &AtomTable) -> Atom {
|
||||
match self {
|
||||
&AtomOrString::Atom(atom) => atom,
|
||||
AtomOrString::String(string) => AtomTable::build_with(atom_tbl, &string),
|
||||
AtomOrString::String(string) => AtomTable::build_with(atom_tbl, string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,10 +486,11 @@ impl AtomOrString {
|
||||
AtomOrString::String(string) => AtomString::Static(string.as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_string(self) -> String {
|
||||
match self {
|
||||
impl From<AtomOrString> for String {
|
||||
fn from(val: AtomOrString) -> Self {
|
||||
match val {
|
||||
AtomOrString::Atom(atom) => atom.as_str().to_owned(),
|
||||
AtomOrString::String(string) => string,
|
||||
}
|
||||
@@ -543,7 +534,7 @@ pub(crate) fn fetch_op_spec(name: Atom, arity: usize, op_dir: &OpDir) -> Option<
|
||||
}
|
||||
}),
|
||||
1 => {
|
||||
if let Some(op_desc) = op_dir.get(&(name.clone(), Fixity::Pre)) {
|
||||
if let Some(op_desc) = op_dir.get(&(name, Fixity::Pre)) {
|
||||
if op_desc.get_prec() > 0 {
|
||||
return Some(*op_desc);
|
||||
}
|
||||
@@ -635,9 +626,9 @@ impl Default for Number {
|
||||
impl fmt::Display for Number {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Number::Float(fl) => write!(f, "{}", fl),
|
||||
Number::Integer(n) => write!(f, "{}", n),
|
||||
Number::Rational(r) => write!(f, "{}", r),
|
||||
Number::Float(fl) => write!(f, "{fl}"),
|
||||
Number::Integer(n) => write!(f, "{n}"),
|
||||
Number::Rational(r) => write!(f, "{r}"),
|
||||
Number::Fixnum(n) => write!(f, "{}", n.get_num()),
|
||||
}
|
||||
}
|
||||
@@ -706,17 +697,18 @@ impl ArenaFrom<isize> for Number {
|
||||
impl ArenaFrom<u32> for Number {
|
||||
#[inline]
|
||||
fn arena_from(value: u32, _arena: &mut Arena) -> Number {
|
||||
Number::Fixnum(Fixnum::build_with(value as i64))
|
||||
Number::Fixnum(Fixnum::build_with(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaFrom<i32> for Number {
|
||||
#[inline]
|
||||
fn arena_from(value: i32, _arena: &mut Arena) -> Number {
|
||||
Number::Fixnum(Fixnum::build_with(value as i64))
|
||||
Number::Fixnum(Fixnum::build_with(value))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl ArenaFrom<Number> for Literal {
|
||||
#[inline]
|
||||
fn arena_from(value: Number, arena: &mut Arena) -> Literal {
|
||||
@@ -728,6 +720,21 @@ impl ArenaFrom<Number> for Literal {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl ArenaFrom<u64> for HeapCellValue {
|
||||
#[inline]
|
||||
fn arena_from(value: u64, arena: &mut Arena) -> HeapCellValue {
|
||||
HeapCellValue::from(fixnum!(Literal, value as i64, arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaFrom<usize> for HeapCellValue {
|
||||
#[inline]
|
||||
fn arena_from(value: usize, arena: &mut Arena) -> HeapCellValue {
|
||||
HeapCellValue::arena_from(value as u64, arena)
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaFrom<Number> for HeapCellValue {
|
||||
#[inline]
|
||||
@@ -735,7 +742,7 @@ impl ArenaFrom<Number> for HeapCellValue {
|
||||
match value {
|
||||
Number::Fixnum(n) => fixnum_as_cell!(n),
|
||||
Number::Integer(n) => typed_arena_ptr_as_cell!(n),
|
||||
Number::Float(OrderedFloat(n)) => HeapCellValue::from(float_alloc!(n, arena)),
|
||||
Number::Float(n) => HeapCellValue::from(arena.f64_tbl.build_with(n)),
|
||||
Number::Rational(n) => typed_arena_ptr_as_cell!(n),
|
||||
}
|
||||
}
|
||||
@@ -744,11 +751,15 @@ impl ArenaFrom<Number> for HeapCellValue {
|
||||
impl Number {
|
||||
pub(crate) fn sign(&self) -> Number {
|
||||
match self {
|
||||
&Number::Float(f) if f == 0.0 => Number::Float(OrderedFloat(0f64)),
|
||||
&Number::Float(f) => Number::Float(OrderedFloat(f.signum())),
|
||||
Number::Float(f) if *f == 0.0 => Number::Float(OrderedFloat(0f64)),
|
||||
Number::Float(f) => Number::Float(OrderedFloat(f.signum())),
|
||||
_ => {
|
||||
if self.is_positive() {
|
||||
Number::Fixnum(Fixnum::build_with(1))
|
||||
if self.is_zero() {
|
||||
Number::Fixnum(Fixnum::build_with(0))
|
||||
} else {
|
||||
Number::Fixnum(Fixnum::build_with(1))
|
||||
}
|
||||
} else if self.is_negative() {
|
||||
Number::Fixnum(Fixnum::build_with(-1))
|
||||
} else {
|
||||
@@ -761,46 +772,43 @@ impl Number {
|
||||
#[inline]
|
||||
pub(crate) fn is_positive(&self) -> bool {
|
||||
match self {
|
||||
&Number::Fixnum(n) => n.get_num() > 0,
|
||||
&Number::Integer(ref n) => &**n > &Integer::from(0),
|
||||
&Number::Float(f) => f.is_sign_positive(),
|
||||
&Number::Rational(ref r) => &**r > &Rational::from(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 < &Integer::from(0),
|
||||
&Number::Float(OrderedFloat(f)) => f.is_sign_negative() && OrderedFloat(f) != -0f64,
|
||||
&Number::Rational(ref r) => &**r < &Rational::from(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 == &Integer::from(0),
|
||||
&Number::Float(f) => f == OrderedFloat(0f64) || f == OrderedFloat(-0f64),
|
||||
&Number::Rational(ref r) => &**r == &Rational::from(0),
|
||||
Number::Fixnum(n) => n.get_num() == 0,
|
||||
Number::Integer(ref n) => n.is_zero(),
|
||||
&Number::Float(OrderedFloat(f)) => f == 0.0 || f == -0.0,
|
||||
Number::Rational(ref r) => r.is_zero(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_integer(&self) -> bool {
|
||||
match self {
|
||||
Number::Fixnum(_) | Number::Integer(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, Number::Fixnum(_) | Number::Integer(_))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum OptArgIndexKey {
|
||||
Literal(usize, usize, Literal, Vec<Literal>), // index, IndexingCode location, opt arg, alternatives
|
||||
List(usize, usize), // index, IndexingCode location
|
||||
Literal(usize, usize, Literal, Option<Literal>), // index, IndexingCode location, opt arg, alternatives
|
||||
List(usize, usize), // index, IndexingCode location
|
||||
None,
|
||||
Structure(usize, usize, Atom, usize), // index, IndexingCode location, name, arity
|
||||
}
|
||||
@@ -883,7 +891,7 @@ impl ClauseIndexInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(crate) struct PredicateInfo {
|
||||
pub(crate) is_extensible: bool,
|
||||
pub(crate) is_discontiguous: bool,
|
||||
@@ -892,19 +900,6 @@ pub(crate) struct PredicateInfo {
|
||||
pub(crate) has_clauses: bool,
|
||||
}
|
||||
|
||||
impl Default for PredicateInfo {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
PredicateInfo {
|
||||
is_extensible: false,
|
||||
is_discontiguous: false,
|
||||
is_dynamic: false,
|
||||
is_multifile: false,
|
||||
has_clauses: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PredicateInfo {
|
||||
#[inline]
|
||||
pub(crate) fn compile_incrementally(&self) -> bool {
|
||||
@@ -963,7 +958,7 @@ impl LocalPredicateSkeleton {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn add_retracted_dynamic_clause_info(&mut self, clause_info: ClauseIndexInfo) {
|
||||
debug_assert_eq!(self.is_dynamic, true);
|
||||
debug_assert!(self.is_dynamic);
|
||||
|
||||
if self.retracted_dynamic_clauses.is_none() {
|
||||
self.retracted_dynamic_clauses = Some(vec![]);
|
||||
@@ -1008,7 +1003,7 @@ impl PredicateSkeleton {
|
||||
) -> Option<usize> {
|
||||
let search_result = self.core.clause_clause_locs.make_contiguous()
|
||||
[0..self.core.clause_assert_margin]
|
||||
.binary_search_by(|loc| clause_clause_loc.cmp(&loc));
|
||||
.binary_search_by(|loc| clause_clause_loc.cmp(loc));
|
||||
|
||||
match search_result {
|
||||
Ok(loc) => Some(loc),
|
||||
|
||||
798
src/functor_macro.rs
Normal file
798
src/functor_macro.rs
Normal file
@@ -0,0 +1,798 @@
|
||||
//! A macro to construct functor terms ready to be written to the WAM
|
||||
//! heap.
|
||||
|
||||
use crate::atom_table::*;
|
||||
use crate::instructions::IndexingCodePtr;
|
||||
use crate::machine::heap::Heap;
|
||||
use crate::parser::ast::Fixnum;
|
||||
use crate::types::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) enum FunctorElement {
|
||||
AbsoluteCell(HeapCellValue),
|
||||
Cell(HeapCellValue),
|
||||
InnerFunctor(u64, Vec<FunctorElement>),
|
||||
String(u64, String),
|
||||
}
|
||||
|
||||
// helper macros
|
||||
macro_rules! count {
|
||||
() => (0);
|
||||
( $x:tt $($xs:tt)* ) => (1 + count!($($xs)*));
|
||||
}
|
||||
|
||||
// core macros
|
||||
|
||||
/*
|
||||
* functor! is more declarative now, with fewer effects and more
|
||||
* work done at compile time using const functions. With these
|
||||
* advantages come new quirks: expressions must generally be wrapped
|
||||
* in round parentheses for rustc to parse them. See the tests module
|
||||
* below for examples, especially those involving atom!
|
||||
* subexpressions.
|
||||
*/
|
||||
|
||||
macro_rules! functor {
|
||||
($name:expr) => ({
|
||||
vec![FunctorElement::Cell(atom_as_cell!($name))]
|
||||
});
|
||||
($name:expr, [$($dt:ident($($value:tt),*)),+]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[FunctorElement::Cell(atom_as_cell!($name, count!($($dt) *)))],
|
||||
1,
|
||||
[])
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! inner_functor {
|
||||
($name:expr, $res_len:expr, [$($dt:ident($($value:tt),*)),+]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[FunctorElement::Cell(atom_as_cell!($name, count!($($dt) *)))],
|
||||
1 + $res_len,
|
||||
[])
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! build_functor {
|
||||
([], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
vec![$($res,)* $($subfunctor),*]
|
||||
});
|
||||
([indexing_code_ptr($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
let (inner_functor, cell_size) = indexing_code_ptr($e);
|
||||
let referent = if cell_size == 1 {
|
||||
heap_loc_as_cell!(1u64 + count!($($dt)*) + $res_len)
|
||||
} else {
|
||||
str_loc_as_cell!(1u64 + count!($($dt)*) + $res_len)
|
||||
};
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(referent)],
|
||||
1 + cell_size + $res_len,
|
||||
[$($subfunctor, )* FunctorElement::InnerFunctor(cell_size, inner_functor)])
|
||||
});
|
||||
([fixnum($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(fixnum_as_cell!(/*FIXME this is not safe*/ unsafe{Fixnum::build_with_unchecked($e as i64)}))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([cell($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::AbsoluteCell($e)],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([literal($e:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::AbsoluteCell(HeapCellValue::from($e))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([number($n:expr, $arena:expr) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
let number_cell = HeapCellValue::arena_from($n, $arena);
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(number_cell)],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([list([]) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(empty_list_as_cell!())],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([list([$id:ident($($id_value:tt),*) $(, $in_dt:ident($($in_value:tt),*))*]) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
build_functor!([functor((atom!(".")), [$id($($id_value),*), list([$($in_dt($($in_value),*)),*])])
|
||||
$(, $dt($($value),*))*],
|
||||
[$($res),*],
|
||||
$res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([string($s:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
#[allow(unused_parens)]
|
||||
let string = $s;
|
||||
let pstr_len = cell_index!(Heap::compute_pstr_size(&string)) as u64;
|
||||
let result_len = 1 + count!($($dt)*) + $res_len;
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(pstr_loc_as_cell!(heap_index!(result_len as usize) as u64))],
|
||||
1 + $res_len + pstr_len,
|
||||
[$($subfunctor, )* FunctorElement::String(pstr_len, string)])
|
||||
});
|
||||
([atom_as_cell($n:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(atom_as_cell!($n))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([functor($stub:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
let result_len = 1u64 + count!($($dt)*) + $res_len;
|
||||
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&$stub)) as u64;
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(str_loc_as_cell!(result_len))],
|
||||
1 + $res_len + inner_functor_size,
|
||||
[$($subfunctor, )*
|
||||
FunctorElement::InnerFunctor(inner_functor_size, $stub)])
|
||||
});
|
||||
([$id:ident($n:expr) $(, $dt:ident($($value:tt),*))*], [$($res:expr),*], $res_len:expr, [$($subfunctor:expr),*]) => ({
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell($id!($n))],
|
||||
1 + $res_len,
|
||||
[$($subfunctor),*])
|
||||
});
|
||||
([functor($name:expr, [$($in_dt:ident($($in_value:tt),*)),+]) $(, $dt:ident($($value:tt),*))*],
|
||||
[$($res:expr),*],
|
||||
$res_len:expr,
|
||||
[$($subfunctor:expr),*]) => ({
|
||||
let result_len = 1u64 + count!($($dt)*) + $res_len;
|
||||
let inner_functor = inner_functor!($name, 0, [$($in_dt($($in_value),*)),*]);
|
||||
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&inner_functor)) as u64;
|
||||
|
||||
build_functor!([$($dt($($value),*)),*],
|
||||
[$($res, )* FunctorElement::Cell(str_loc_as_cell!(result_len))],
|
||||
1 + $res_len + inner_functor_size,
|
||||
[$($subfunctor, )*
|
||||
FunctorElement::InnerFunctor(inner_functor_size, inner_functor)])
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn indexing_code_ptr(code_ptr: IndexingCodePtr) -> (Vec<FunctorElement>, u64) {
|
||||
match code_ptr {
|
||||
IndexingCodePtr::DynamicExternal(o) => {
|
||||
(functor!(atom!("dynamic_external"), [fixnum(o)]), 2)
|
||||
}
|
||||
IndexingCodePtr::External(o) => (functor!(atom!("external"), [fixnum(o)]), 2),
|
||||
IndexingCodePtr::Internal(o) => (functor!(atom!("internal"), [fixnum(o)]), 2),
|
||||
IndexingCodePtr::Fail => (vec![FunctorElement::Cell(atom_as_cell!(atom!("fail")))], 1),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn variadic_functor(
|
||||
name: Atom,
|
||||
arity: usize,
|
||||
iter: impl Iterator<Item = Vec<FunctorElement>>,
|
||||
) -> Vec<FunctorElement> {
|
||||
let mut arg_vec = vec![
|
||||
FunctorElement::Cell(atom_as_cell!(name, arity)),
|
||||
FunctorElement::Cell(list_loc_as_cell!(2)),
|
||||
];
|
||||
|
||||
let key_value_pairs: Vec<_> = iter.collect();
|
||||
let num_items = key_value_pairs.len();
|
||||
let mut functor_offset = 0;
|
||||
|
||||
for (idx, kv_func) in key_value_pairs.iter().enumerate() {
|
||||
let functor_size = cell_index!(Heap::compute_functor_byte_size(kv_func));
|
||||
|
||||
arg_vec.push(FunctorElement::Cell(str_loc_as_cell!(
|
||||
2 + num_items * 2 + functor_offset
|
||||
)));
|
||||
arg_vec.push(FunctorElement::Cell(list_loc_as_cell!(4 + 2 * idx)));
|
||||
|
||||
functor_offset += functor_size;
|
||||
}
|
||||
|
||||
arg_vec.pop();
|
||||
arg_vec.push(FunctorElement::Cell(empty_list_as_cell!()));
|
||||
|
||||
arg_vec.extend(key_value_pairs.into_iter().map(|kv_func| {
|
||||
let inner_functor_size = cell_index!(Heap::compute_functor_byte_size(&kv_func));
|
||||
FunctorElement::InnerFunctor(inner_functor_size as u64, kv_func)
|
||||
}));
|
||||
|
||||
arg_vec
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unused_parens)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use indexmap::indexmap;
|
||||
use std::string::String;
|
||||
use FunctorElement::*;
|
||||
|
||||
#[test]
|
||||
fn basic_terms() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[atom_as_cell((atom!("a"))), char_as_cell('c')]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("first"), 2)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(char_as_cell!('c')));
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
char_as_cell('c')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 5);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("second"), 3)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(4)));
|
||||
assert_eq!(functor[3], Cell(char_as_cell!('c')));
|
||||
assert_eq!(
|
||||
functor[4],
|
||||
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("third"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
functor((atom!("c")), [fixnum(1), fixnum(2)]),
|
||||
char_as_cell('c')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 7);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("third"), 4)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(5)));
|
||||
assert_eq!(functor[3], Cell(str_loc_as_cell!(8)));
|
||||
assert_eq!(functor[4], Cell(char_as_cell!('c')));
|
||||
assert_eq!(
|
||||
functor[5],
|
||||
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
assert_eq!(
|
||||
functor[6],
|
||||
InnerFunctor(3, functor!(atom!("c"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("fourth"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
functor((atom!("c")), [fixnum(1)]),
|
||||
functor((atom!("d")), [fixnum(453), fixnum(2)]),
|
||||
char_as_cell('c')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 9);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("fourth"), 5)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(6)));
|
||||
assert_eq!(functor[3], Cell(str_loc_as_cell!(9)));
|
||||
assert_eq!(functor[4], Cell(str_loc_as_cell!(11)));
|
||||
assert_eq!(functor[5], Cell(char_as_cell!('c')));
|
||||
assert_eq!(
|
||||
functor[6],
|
||||
InnerFunctor(3, functor!(atom!("b"), [fixnum(1), fixnum(2)]))
|
||||
);
|
||||
assert_eq!(
|
||||
functor[7],
|
||||
InnerFunctor(2, functor!(atom!("c"), [fixnum(1)]))
|
||||
);
|
||||
assert_eq!(
|
||||
functor[8],
|
||||
InnerFunctor(3, functor!(atom!("d"), [fixnum(453), fixnum(2)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_terms_in_heap() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[atom_as_cell((atom!("a"))), char_as_cell('b')]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(0));
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 2));
|
||||
assert_eq!(heap[1], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[2], char_as_cell!('b'));
|
||||
|
||||
heap.truncate(2);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor((atom!("b")), [fixnum(1), fixnum(2)]),
|
||||
functor((atom!("c")), [fixnum(1), fixnum(2)]),
|
||||
char_as_cell('b')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 7);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(2));
|
||||
|
||||
assert_eq!(heap[2], atom_as_cell!(atom!("second"), 4));
|
||||
assert_eq!(heap[3], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[4], str_loc_as_cell!(7));
|
||||
assert_eq!(heap[5], str_loc_as_cell!(10));
|
||||
assert_eq!(heap[6], char_as_cell!('b'));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("b"), 2));
|
||||
assert_eq!(heap[8], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[9], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
assert_eq!(heap[10], atom_as_cell!(atom!("c"), 2));
|
||||
assert_eq!(heap[11], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[12], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_functors() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor(
|
||||
(atom!("d")),
|
||||
[
|
||||
fixnum(1),
|
||||
functor(
|
||||
(atom!("b")),
|
||||
[atom_as_cell((atom!("c"))), char_as_cell('c')]
|
||||
)
|
||||
]
|
||||
),
|
||||
functor((atom!("e")), [fixnum(453), fixnum(2)]),
|
||||
char_as_cell('b')
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 7);
|
||||
|
||||
assert_eq!(functor[0], Cell(atom_as_cell!(atom!("first"), 4)));
|
||||
assert_eq!(functor[1], Cell(atom_as_cell!(atom!("a"))));
|
||||
assert_eq!(functor[2], Cell(str_loc_as_cell!(5)));
|
||||
assert_eq!(functor[3], Cell(str_loc_as_cell!(11)));
|
||||
assert_eq!(functor[4], Cell(char_as_cell!('b')));
|
||||
assert_eq!(
|
||||
functor[5],
|
||||
InnerFunctor(
|
||||
6,
|
||||
vec![
|
||||
Cell(atom_as_cell!(atom!("d"), 2)),
|
||||
Cell(fixnum_as_cell!(Fixnum::build_with(1))),
|
||||
Cell(str_loc_as_cell!(3)),
|
||||
InnerFunctor(
|
||||
3,
|
||||
functor!(atom!("b"), [atom_as_cell((atom!("c"))), char_as_cell('c')])
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
functor[6],
|
||||
InnerFunctor(3, functor!(atom!("e"), [fixnum(453), fixnum(2)]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_functors_in_heap() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
functor(
|
||||
(atom!("second")),
|
||||
[
|
||||
fixnum(1),
|
||||
functor(
|
||||
(atom!("third")),
|
||||
[atom_as_cell((atom!("b"))), char_as_cell('c')]
|
||||
)
|
||||
]
|
||||
),
|
||||
functor((atom!("fourth")), [fixnum(453), fixnum(2)]),
|
||||
char_as_cell('b')
|
||||
]
|
||||
);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(0));
|
||||
|
||||
assert_eq!(heap.cell_len(), 14);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 4));
|
||||
assert_eq!(heap[1], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(5));
|
||||
assert_eq!(heap[3], str_loc_as_cell!(11));
|
||||
assert_eq!(heap[4], char_as_cell!('b'));
|
||||
assert_eq!(heap[5], atom_as_cell!(atom!("second"), 2));
|
||||
assert_eq!(heap[6], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[7], str_loc_as_cell!(8));
|
||||
assert_eq!(heap[8], atom_as_cell!(atom!("third"), 2));
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("b")));
|
||||
assert_eq!(heap[10], char_as_cell!('c'));
|
||||
assert_eq!(heap[11], atom_as_cell!(atom!("fourth"), 2));
|
||||
assert_eq!(heap[12], fixnum_as_cell!(Fixnum::build_with(453)));
|
||||
assert_eq!(heap[13], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functors_with_strings_in_heap() {
|
||||
let functor = functor!(atom!("first"), [string((String::from("a string")))]);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, str_loc_as_cell!(0));
|
||||
assert_eq!(heap.cell_len(), 5);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 1));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(2)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(2), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[4], empty_list_as_cell!());
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[string((String::from("a stuttered\0 string")))]
|
||||
);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 10);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("second"), 1));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(2)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(2), "a stuttered".len()),
|
||||
"a stuttered"
|
||||
);
|
||||
assert_eq!(heap[4], list_loc_as_cell!(5));
|
||||
assert_eq!(heap[5], char_as_cell!('\u{0}'));
|
||||
assert_eq!(heap[6], pstr_loc_as_cell!(heap_index!(7)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(7), " string".len()),
|
||||
" string"
|
||||
);
|
||||
assert_eq!(heap[9], empty_list_as_cell!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functors_with_lists_in_heap() {
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[list([fixnum(1), atom_as_cell((atom!("a"))), fixnum(2)])]
|
||||
);
|
||||
|
||||
assert_eq!(functor.len(), 3);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 11);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 1));
|
||||
assert_eq!(heap[1], str_loc_as_cell!(2));
|
||||
assert_eq!(heap[2], atom_as_cell!(atom!("."), 2));
|
||||
assert_eq!(heap[3], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[4], str_loc_as_cell!(5));
|
||||
assert_eq!(heap[5], atom_as_cell!(atom!("."), 2));
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[7], str_loc_as_cell!(8));
|
||||
assert_eq!(heap[8], atom_as_cell!(atom!("."), 2));
|
||||
assert_eq!(heap[9], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
assert_eq!(heap[10], empty_list_as_cell!());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inlined_atoms() {
|
||||
let atom_table = AtomTable::new();
|
||||
let inlined = AtomTable::build_with(&atom_table, "inline");
|
||||
|
||||
assert!(inlined.is_inlined());
|
||||
assert_eq!(&*inlined.as_str(), "inline");
|
||||
|
||||
let non_inlined = AtomTable::build_with(&atom_table, "longer non-inlined atom");
|
||||
|
||||
assert!(!non_inlined.is_inlined());
|
||||
assert_eq!(&*non_inlined.as_str(), "longer non-inlined atom");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functors_with_indexing_code_ptr() {
|
||||
let code_ptr = IndexingCodePtr::Internal(0);
|
||||
let functor = functor!(
|
||||
atom!("first"),
|
||||
[
|
||||
string((String::from("a string"))),
|
||||
indexing_code_ptr(code_ptr)
|
||||
]
|
||||
);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 8);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("first"), 2));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(3), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("internal"), 1));
|
||||
assert_eq!(heap[7], fixnum_as_cell!(Fixnum::build_with(0)));
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let functor = functor!(
|
||||
atom!("second"),
|
||||
[
|
||||
string((String::from("a string"))),
|
||||
functor(
|
||||
(atom!("third")),
|
||||
[
|
||||
atom_as_cell((atom!("a"))),
|
||||
string((String::from("another string"))),
|
||||
indexing_code_ptr(code_ptr)
|
||||
]
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 15);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("second"), 2));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(3), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("third"), 3));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[8], pstr_loc_as_cell!(heap_index!(10)));
|
||||
assert_eq!(heap[9], str_loc_as_cell!(13));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(10), "another string".len()),
|
||||
"another string"
|
||||
);
|
||||
assert_eq!(heap[12], empty_list_as_cell!());
|
||||
assert_eq!(heap[13], atom_as_cell!(atom!("internal"), 1));
|
||||
assert_eq!(heap[14], fixnum_as_cell!(Fixnum::build_with(0)));
|
||||
|
||||
let functor = functor!(
|
||||
atom!("fourth"),
|
||||
[
|
||||
string((String::from("a string"))),
|
||||
functor(
|
||||
(atom!("a")),
|
||||
[
|
||||
functor(
|
||||
(atom!("fifth")),
|
||||
[
|
||||
fixnum(5),
|
||||
string((String::from("another string"))),
|
||||
indexing_code_ptr(code_ptr)
|
||||
]
|
||||
),
|
||||
string((String::from("and another")))
|
||||
]
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 21);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("fourth"), 2));
|
||||
assert_eq!(heap[1], pstr_loc_as_cell!(heap_index!(3)));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(3), "a string".len()),
|
||||
"a string"
|
||||
);
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("a"), 2));
|
||||
assert_eq!(heap[7], str_loc_as_cell!(9));
|
||||
assert_eq!(heap[8], pstr_loc_as_cell!(heap_index!(18))); // <-- wrong!
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("fifth"), 3));
|
||||
assert_eq!(heap[10], fixnum_as_cell!(Fixnum::build_with(5)));
|
||||
assert_eq!(heap[11], pstr_loc_as_cell!(heap_index!(13)));
|
||||
assert_eq!(heap[12], str_loc_as_cell!(16));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(13), "another string".len()),
|
||||
"another string"
|
||||
);
|
||||
assert_eq!(heap[15], empty_list_as_cell!());
|
||||
assert_eq!(heap[16], atom_as_cell!(atom!("internal"), 1));
|
||||
assert_eq!(heap[17], fixnum_as_cell!(Fixnum::build_with(0)));
|
||||
assert_eq!(
|
||||
heap.slice_to_str(heap_index!(18), "and another".len()),
|
||||
"and another"
|
||||
);
|
||||
assert_eq!(heap[20], empty_list_as_cell!());
|
||||
|
||||
let constants = indexmap![
|
||||
atom_as_cell!(atom!("a")) => IndexingCodePtr::External(2),
|
||||
atom_as_cell!(atom!("d")) => IndexingCodePtr::External(7),
|
||||
];
|
||||
|
||||
let functor = variadic_functor(
|
||||
atom!("switch_on_constants"),
|
||||
1,
|
||||
constants
|
||||
.iter()
|
||||
.map(|(c, ptr)| functor!(atom!(":"), [cell((*c)), indexing_code_ptr((*ptr))])),
|
||||
);
|
||||
|
||||
heap.truncate(0);
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor);
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("switch_on_constants"), 1));
|
||||
assert_eq!(heap[1], list_loc_as_cell!(2));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(6));
|
||||
assert_eq!(heap[3], list_loc_as_cell!(4));
|
||||
assert_eq!(heap[4], str_loc_as_cell!(11));
|
||||
assert_eq!(heap[5], empty_list_as_cell!());
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!(":"), 2));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[8], str_loc_as_cell!(9));
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("external"), 1));
|
||||
assert_eq!(heap[10], fixnum_as_cell!(Fixnum::build_with(2)));
|
||||
assert_eq!(heap[11], atom_as_cell!(atom!(":"), 2));
|
||||
assert_eq!(heap[12], atom_as_cell!(atom!("d")));
|
||||
assert_eq!(heap[13], str_loc_as_cell!(14));
|
||||
assert_eq!(heap[14], atom_as_cell!(atom!("external"), 1));
|
||||
assert_eq!(heap[15], fixnum_as_cell!(Fixnum::build_with(7)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undefined_procedure_functor() {
|
||||
// existence_error
|
||||
let culprit = functor!(atom!("/"), [atom_as_cell((atom!("a"))), fixnum(1)]);
|
||||
|
||||
let stub = functor!(
|
||||
atom!("existence_error"),
|
||||
[
|
||||
atom_as_cell((atom!("procedure"))),
|
||||
functor((culprit.clone()))
|
||||
]
|
||||
);
|
||||
|
||||
println!("{stub:?}");
|
||||
|
||||
// now the error form
|
||||
let lineless_error_form = functor!(atom!("error"), [functor(stub), functor(culprit)]);
|
||||
|
||||
println!("{lineless_error_form:?}");
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(lineless_error_form);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("error"), 2));
|
||||
assert_eq!(heap[1], str_loc_as_cell!(3));
|
||||
assert_eq!(heap[2], str_loc_as_cell!(9));
|
||||
assert_eq!(heap[3], atom_as_cell!(atom!("existence_error"), 2));
|
||||
assert_eq!(heap[4], atom_as_cell!(atom!("procedure")));
|
||||
assert_eq!(heap[5], str_loc_as_cell!(6)); // is str_loc_as_cell!(3)
|
||||
assert_eq!(heap[6], atom_as_cell!(atom!("/"), 2));
|
||||
assert_eq!(heap[7], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[8], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
assert_eq!(heap[9], atom_as_cell!(atom!("/"), 2));
|
||||
assert_eq!(heap[10], atom_as_cell!(atom!("a")));
|
||||
assert_eq!(heap[11], fixnum_as_cell!(Fixnum::build_with(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn argless_functor() {
|
||||
let name = functor!(atom!("[]"));
|
||||
|
||||
assert_eq!(name.len(), 1);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(name);
|
||||
let loc = functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(loc, heap_loc_as_cell!(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn predefined_subfunctors() {
|
||||
let stub = functor!(atom!("sub"), [atom_as_cell((atom!("[]")))]);
|
||||
let name = functor!(atom!("super"), [functor(stub)]);
|
||||
|
||||
let mut heap = Heap::new();
|
||||
let mut functor_writer = Heap::functor_writer(name);
|
||||
|
||||
functor_writer(&mut heap).unwrap();
|
||||
|
||||
assert_eq!(heap.cell_len(), 4);
|
||||
|
||||
assert_eq!(heap[0], atom_as_cell!(atom!("super"), 1));
|
||||
assert_eq!(heap[1], str_loc_as_cell!(2));
|
||||
assert_eq!(heap[2], atom_as_cell!(atom!("sub"), 1));
|
||||
assert_eq!(heap[3], empty_list_as_cell!());
|
||||
}
|
||||
}
|
||||
1303
src/heap_iter.rs
1303
src/heap_iter.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
use std::sync::{Arc, Mutex, Condvar};
|
||||
use std::io::BufRead;
|
||||
use bytes::{buf::Reader, Bytes};
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
|
||||
use warp::http;
|
||||
|
||||
@@ -19,5 +19,5 @@ pub struct HttpRequestData {
|
||||
pub headers: http::HeaderMap,
|
||||
pub path: String,
|
||||
pub query: String,
|
||||
pub body: Box<dyn BufRead + Send>,
|
||||
pub body: Reader<Bytes>,
|
||||
}
|
||||
|
||||
240
src/indexing.rs
240
src/indexing.rs
@@ -3,6 +3,7 @@ use crate::parser::ast::*;
|
||||
|
||||
use crate::forms::*;
|
||||
use crate::instructions::*;
|
||||
use crate::types::HeapCellValue;
|
||||
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexMap;
|
||||
@@ -112,15 +113,15 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
|
||||
match constant_key {
|
||||
Some(OptArgIndexKey::Literal(_, _, constant, _)) => {
|
||||
constants.insert(*constant, constant_ptr);
|
||||
constants.insert(HeapCellValue::from(*constant), constant_ptr);
|
||||
}
|
||||
_ if constant_ptr.is_external() => {
|
||||
// this must be a defunct clause, because it's been deleted
|
||||
// from the skeleton.
|
||||
debug_assert!(constant_key.is_none());
|
||||
}
|
||||
_ => {
|
||||
if let IndexingCodePtr::DynamicExternal(_) = constant_ptr {
|
||||
// this must be a defunct clause, because it's been deleted
|
||||
// from the skeleton.
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +145,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
fn add_static_indexed_choice_for_constant(
|
||||
&mut self,
|
||||
external: usize,
|
||||
constant: Literal,
|
||||
constant: HeapCellValue,
|
||||
index: usize,
|
||||
) {
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
@@ -181,7 +182,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
fn add_dynamic_indexed_choice_for_constant(
|
||||
&mut self,
|
||||
external: usize,
|
||||
constant: Literal,
|
||||
constant: HeapCellValue,
|
||||
index: usize,
|
||||
) {
|
||||
let third_level_index = if self.append_or_prepend.is_append() {
|
||||
@@ -235,8 +236,8 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
|
||||
fn index_overlapping_constant(
|
||||
&mut self,
|
||||
orig_constant: Literal,
|
||||
overlapping_constant: Literal,
|
||||
orig_constant: HeapCellValue,
|
||||
overlapping_constant: HeapCellValue,
|
||||
index: usize,
|
||||
) {
|
||||
loop {
|
||||
@@ -316,7 +317,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn index_constant(&mut self, constant: Literal, index: usize) {
|
||||
fn index_constant(&mut self, constant: HeapCellValue, index: usize) {
|
||||
loop {
|
||||
let indexing_code_len = self.indexing_code.len();
|
||||
|
||||
@@ -633,12 +634,15 @@ pub(crate) fn merge_clause_index(
|
||||
match &opt_arg_index_key {
|
||||
OptArgIndexKey::Literal(_, index_loc, constant, ref overlapping_constants) => {
|
||||
let offset = new_clause_loc - index_loc + 1;
|
||||
merging_ptr.index_constant(*constant, offset);
|
||||
merging_ptr.index_constant(HeapCellValue::from(*constant), offset);
|
||||
|
||||
for overlapping_constant in overlapping_constants {
|
||||
if let Some(overlapping_constant) = overlapping_constants {
|
||||
merging_ptr.offset = 0;
|
||||
|
||||
merging_ptr.index_overlapping_constant(*constant, *overlapping_constant, offset);
|
||||
merging_ptr.index_overlapping_constant(
|
||||
HeapCellValue::from(*constant),
|
||||
HeapCellValue::from(*overlapping_constant),
|
||||
offset,
|
||||
);
|
||||
}
|
||||
}
|
||||
OptArgIndexKey::Structure(_, index_loc, name, arity) => {
|
||||
@@ -664,8 +668,8 @@ pub(crate) fn merge_clause_index(
|
||||
|
||||
pub(crate) fn remove_constant_indices(
|
||||
constant: Literal,
|
||||
overlapping_constants: &[Literal],
|
||||
indexing_code: &mut Vec<IndexingLine>,
|
||||
overlapping_constants: Option<Literal>,
|
||||
indexing_code: &mut [IndexingLine],
|
||||
offset: usize,
|
||||
) {
|
||||
let mut index = 0;
|
||||
@@ -693,7 +697,7 @@ pub(crate) fn remove_constant_indices(
|
||||
|
||||
let mut constants_index = 0;
|
||||
|
||||
for constant in iter {
|
||||
for constant in iter.map(|l| HeapCellValue::from(*l)) {
|
||||
loop {
|
||||
match &mut indexing_code[index] {
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
|
||||
@@ -701,13 +705,11 @@ pub(crate) fn remove_constant_indices(
|
||||
)) => {
|
||||
constants_index = index;
|
||||
|
||||
let constant = *constant;
|
||||
|
||||
match constants.get(&constant).cloned() {
|
||||
Some(IndexingCodePtr::DynamicExternal(_))
|
||||
| Some(IndexingCodePtr::External(_))
|
||||
| Some(IndexingCodePtr::Fail) => {
|
||||
constants.remove(&constant);
|
||||
constants.swap_remove(&constant);
|
||||
break;
|
||||
}
|
||||
Some(IndexingCodePtr::Internal(o)) => {
|
||||
@@ -740,7 +742,7 @@ pub(crate) fn remove_constant_indices(
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
|
||||
ref mut constants,
|
||||
)) => {
|
||||
constants.insert(*constant, ext);
|
||||
constants.insert(constant, ext);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
@@ -773,7 +775,7 @@ pub(crate) fn remove_constant_indices(
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
|
||||
ref mut constants,
|
||||
)) => {
|
||||
constants.insert(*constant, ext);
|
||||
constants.insert(constant, ext);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
@@ -811,7 +813,7 @@ pub(crate) fn remove_constant_indices(
|
||||
pub(crate) fn remove_structure_index(
|
||||
name: Atom,
|
||||
arity: usize,
|
||||
indexing_code: &mut Vec<IndexingLine>,
|
||||
indexing_code: &mut [IndexingLine],
|
||||
offset: usize,
|
||||
) {
|
||||
let mut index = 0;
|
||||
@@ -843,10 +845,10 @@ pub(crate) fn remove_structure_index(
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
|
||||
structures_index = index;
|
||||
|
||||
match structures.get(&(name.clone(), arity)).cloned() {
|
||||
match structures.get(&(name, arity)).cloned() {
|
||||
Some(IndexingCodePtr::DynamicExternal(_))
|
||||
| Some(IndexingCodePtr::External(_)) => {
|
||||
structures.remove(&(name.clone(), arity));
|
||||
structures.swap_remove(&(name, arity));
|
||||
break;
|
||||
}
|
||||
Some(IndexingCodePtr::Internal(o)) => {
|
||||
@@ -877,7 +879,7 @@ pub(crate) fn remove_structure_index(
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
|
||||
ref mut structures,
|
||||
)) => {
|
||||
structures.insert((name.clone(), arity), ext);
|
||||
structures.insert((name, arity), ext);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
@@ -908,7 +910,7 @@ pub(crate) fn remove_structure_index(
|
||||
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
|
||||
ref mut structures,
|
||||
)) => {
|
||||
structures.insert((name.clone(), arity), ext);
|
||||
structures.insert((name, arity), ext);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
@@ -948,7 +950,7 @@ pub(crate) fn remove_structure_index(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn remove_list_index(indexing_code: &mut Vec<IndexingLine>, offset: usize) {
|
||||
pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usize) {
|
||||
let mut index = 0;
|
||||
|
||||
match &mut indexing_code[index] {
|
||||
@@ -1028,12 +1030,12 @@ pub(crate) fn remove_list_index(indexing_code: &mut Vec<IndexingLine>, offset: u
|
||||
|
||||
pub(crate) fn remove_index(
|
||||
opt_arg_index_key: &OptArgIndexKey,
|
||||
indexing_code: &mut Vec<IndexingLine>,
|
||||
indexing_code: &mut [IndexingLine],
|
||||
clause_loc: usize,
|
||||
) {
|
||||
match opt_arg_index_key {
|
||||
OptArgIndexKey::Literal(_, _, constant, ref overlapping_constants) => {
|
||||
remove_constant_indices(*constant, overlapping_constants, indexing_code, clause_loc);
|
||||
remove_constant_indices(*constant, *overlapping_constants, indexing_code, clause_loc);
|
||||
}
|
||||
OptArgIndexKey::Structure(_, _, name, arity) => {
|
||||
remove_structure_index(*name, *arity, indexing_code, clause_loc);
|
||||
@@ -1049,102 +1051,72 @@ pub(crate) fn remove_index(
|
||||
|
||||
#[inline]
|
||||
fn cap_choice_seq(prelude: &mut [IndexedChoiceInstruction]) {
|
||||
prelude.first_mut().map(|instr| {
|
||||
if let Some(instr) = prelude.first_mut() {
|
||||
*instr = IndexedChoiceInstruction::Try(instr.offset());
|
||||
});
|
||||
}
|
||||
|
||||
cap_choice_seq_with_trust(prelude);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cap_choice_seq_with_trust(prelude: &mut [IndexedChoiceInstruction]) {
|
||||
prelude.last_mut().map(|instr| match instr {
|
||||
IndexedChoiceInstruction::Retry(i) => {
|
||||
*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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
IndexedChoiceInstruction::DefaultRetry(i) => {
|
||||
*instr = IndexedChoiceInstruction::DefaultTrust(*i);
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn uncap_choice_seq_with_trust(prelude: &mut [IndexedChoiceInstruction]) {
|
||||
prelude.last_mut().map(|instr| match instr {
|
||||
IndexedChoiceInstruction::Trust(i) => {
|
||||
*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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
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: &AtomTable,
|
||||
// arena: &mut Arena,
|
||||
) -> Vec<Literal> {
|
||||
let mut constants = vec![];
|
||||
pub(crate) fn constant_key_alternatives(constant: Literal) -> Option<Literal> {
|
||||
let n = match &constant {
|
||||
Literal::Rational(n) if n.denominator().is_one() => n.numerator(),
|
||||
Literal::Integer(n) => n,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
match constant {
|
||||
Literal::Atom(ref name) => {
|
||||
if let Some(c) = name.as_char() {
|
||||
constants.push(Literal::Char(c));
|
||||
}
|
||||
}
|
||||
Literal::Char(c) => {
|
||||
let atom = AtomTable::build_with(&atom_tbl, &c.to_string());
|
||||
constants.push(Literal::Atom(atom));
|
||||
}
|
||||
/*
|
||||
Literal::Fixnum(ref n) => {
|
||||
constants.push(Literal::Integer(arena_alloc!(n, arena))); //Rc::new(Integer::from(*n))));
|
||||
|
||||
/*
|
||||
if *n >= 0 {
|
||||
if let Ok(n) = usize::try_from(*n) {
|
||||
constants.push(Literal::Usize(n));
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
*/
|
||||
Literal::Integer(ref n) => {
|
||||
let result = (&**n).try_into();
|
||||
if let Ok(value) = result {
|
||||
Fixnum::build_with_checked(value).map(|n| {
|
||||
constants.push(Literal::Fixnum(n));
|
||||
}).unwrap();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
constants
|
||||
Fixnum::build_with_checked(n).map(Literal::Fixnum).ok()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StaticCodeIndices {
|
||||
constants: IndexMap<Literal, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
|
||||
constants: IndexMap<HeapCellValue, VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
|
||||
lists: VecDeque<IndexedChoiceInstruction>,
|
||||
structures: IndexMap<(Atom, usize), VecDeque<IndexedChoiceInstruction>, FxBuildHasher>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DynamicCodeIndices {
|
||||
constants: IndexMap<Literal, VecDeque<usize>, FxBuildHasher>,
|
||||
constants: IndexMap<HeapCellValue, VecDeque<usize>, FxBuildHasher>,
|
||||
lists: VecDeque<usize>,
|
||||
structures: IndexMap<(Atom, usize), VecDeque<usize>, FxBuildHasher>,
|
||||
}
|
||||
@@ -1156,7 +1128,7 @@ pub(crate) trait Indexer {
|
||||
|
||||
fn constants(
|
||||
&mut self,
|
||||
) -> &mut IndexMap<Literal, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
|
||||
) -> &mut IndexMap<HeapCellValue, VecDeque<Self::ThirdLevelIndex>, FxBuildHasher>;
|
||||
fn lists(&mut self) -> &mut VecDeque<Self::ThirdLevelIndex>;
|
||||
fn structures(
|
||||
&mut self,
|
||||
@@ -1204,7 +1176,7 @@ impl Indexer for StaticCodeIndices {
|
||||
#[inline]
|
||||
fn constants(
|
||||
&mut self,
|
||||
) -> &mut IndexMap<Literal, VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
|
||||
) -> &mut IndexMap<HeapCellValue, VecDeque<IndexedChoiceInstruction>, FxBuildHasher> {
|
||||
&mut self.constants
|
||||
}
|
||||
|
||||
@@ -1245,10 +1217,8 @@ impl Indexer for StaticCodeIndices {
|
||||
index_locs.insert(key, IndexingCodePtr::Internal(prelude.len() + 1));
|
||||
cap_choice_seq_with_trust(code.make_contiguous());
|
||||
prelude.push_back(IndexingLine::from(code));
|
||||
} else {
|
||||
code.front().map(|i| {
|
||||
index_locs.insert(key, IndexingCodePtr::External(i.offset()));
|
||||
});
|
||||
} else if let Some(i) = code.front() {
|
||||
index_locs.insert(key, IndexingCodePtr::External(i.offset()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,7 +1255,7 @@ impl Indexer for StaticCodeIndices {
|
||||
) -> IndexingCodePtr {
|
||||
if lists.len() > 1 {
|
||||
cap_choice_seq_with_trust(lists.make_contiguous());
|
||||
let lists = mem::replace(lists, VecDeque::new());
|
||||
let lists = std::mem::take(lists);
|
||||
prelude.push_back(IndexingLine::from(lists));
|
||||
|
||||
IndexingCodePtr::Internal(1)
|
||||
@@ -1330,7 +1300,7 @@ impl Indexer for DynamicCodeIndices {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn constants(&mut self) -> &mut IndexMap<Literal, VecDeque<usize>, FxBuildHasher> {
|
||||
fn constants(&mut self) -> &mut IndexMap<HeapCellValue, VecDeque<usize>, FxBuildHasher> {
|
||||
&mut self.constants
|
||||
}
|
||||
|
||||
@@ -1361,10 +1331,8 @@ impl Indexer for DynamicCodeIndices {
|
||||
prelude.push_back(IndexingLine::DynamicIndexedChoice(
|
||||
code.into_iter().collect(),
|
||||
));
|
||||
} else {
|
||||
code.front().map(|i| {
|
||||
index_locs.insert(key, IndexingCodePtr::DynamicExternal(*i));
|
||||
});
|
||||
} else if let Some(i) = code.front() {
|
||||
index_locs.insert(key, IndexingCodePtr::DynamicExternal(*i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1400,7 +1368,7 @@ impl Indexer for DynamicCodeIndices {
|
||||
prelude: &mut VecDeque<IndexingLine>,
|
||||
) -> IndexingCodePtr {
|
||||
if lists.len() > 1 {
|
||||
let lists = mem::replace(lists, VecDeque::new());
|
||||
let lists = std::mem::take(lists);
|
||||
prelude.push_back(IndexingLine::DynamicIndexedChoice(
|
||||
lists.into_iter().collect(),
|
||||
));
|
||||
@@ -1451,18 +1419,13 @@ impl<I: Indexer> CodeOffsets<I> {
|
||||
self.indices.lists().push_back(index);
|
||||
}
|
||||
|
||||
fn index_constant(
|
||||
&mut self,
|
||||
atom_tbl: &AtomTable,
|
||||
constant: Literal,
|
||||
index: usize,
|
||||
) -> Vec<Literal> {
|
||||
let overlapping_constants = constant_key_alternatives(constant, atom_tbl);
|
||||
fn index_constant(&mut self, constant: Literal, index: usize) -> Option<Literal> {
|
||||
let overlapping_constant_opt = constant_key_alternatives(constant);
|
||||
let code = self
|
||||
.indices
|
||||
.constants()
|
||||
.entry(constant)
|
||||
.or_insert(VecDeque::new());
|
||||
.entry(HeapCellValue::from(constant))
|
||||
.or_default();
|
||||
|
||||
let is_initial_index = code.is_empty();
|
||||
code.push_back(I::compute_index(
|
||||
@@ -1471,12 +1434,8 @@ impl<I: Indexer> CodeOffsets<I> {
|
||||
self.non_counted_bt,
|
||||
));
|
||||
|
||||
for constant in &overlapping_constants {
|
||||
let code = self
|
||||
.indices
|
||||
.constants()
|
||||
.entry(*constant)
|
||||
.or_insert(VecDeque::new());
|
||||
if let Some(constant) = overlapping_constant_opt.map(HeapCellValue::from) {
|
||||
let code = self.indices.constants().entry(constant).or_default();
|
||||
|
||||
let is_initial_index = code.is_empty();
|
||||
let index = I::compute_index(is_initial_index, index, self.non_counted_bt);
|
||||
@@ -1484,15 +1443,11 @@ impl<I: Indexer> CodeOffsets<I> {
|
||||
code.push_back(index);
|
||||
}
|
||||
|
||||
overlapping_constants
|
||||
overlapping_constant_opt
|
||||
}
|
||||
|
||||
fn index_structure(&mut self, name: Atom, arity: usize, index: usize) -> usize {
|
||||
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();
|
||||
@@ -1510,25 +1465,24 @@ impl<I: Indexer> CodeOffsets<I> {
|
||||
optimal_arg: &Term,
|
||||
index: usize,
|
||||
clause_index_info: &mut ClauseIndexInfo,
|
||||
atom_tbl: &AtomTable,
|
||||
) {
|
||||
match optimal_arg {
|
||||
&Term::Clause(_, atom!("."), ref terms) if terms.len() == 2 => {
|
||||
clause_index_info.opt_arg_index_key = OptArgIndexKey::List(self.optimal_index, 0);
|
||||
self.index_list(index);
|
||||
}
|
||||
&Term::Cons(..) | &Term::Literal(_, Literal::String(_)) | &Term::PartialString(..) => {
|
||||
&Term::Cons(..) | &Term::PartialString(..) | &Term::CompleteString(..) => {
|
||||
clause_index_info.opt_arg_index_key = OptArgIndexKey::List(self.optimal_index, 0);
|
||||
self.index_list(index);
|
||||
}
|
||||
&Term::Clause(_, name, ref terms) => {
|
||||
clause_index_info.opt_arg_index_key =
|
||||
OptArgIndexKey::Structure(self.optimal_index, 0, name.clone(), terms.len());
|
||||
OptArgIndexKey::Structure(self.optimal_index, 0, name, terms.len());
|
||||
|
||||
self.index_structure(name, terms.len(), index);
|
||||
}
|
||||
&Term::Literal(_, constant) => {
|
||||
let overlapping_constants = self.index_constant(atom_tbl, constant, index);
|
||||
let overlapping_constants = self.index_constant(constant, index);
|
||||
|
||||
clause_index_info.opt_arg_index_key =
|
||||
OptArgIndexKey::Literal(self.optimal_index, 0, constant, overlapping_constants);
|
||||
@@ -1575,20 +1529,14 @@ impl<I: Indexer> CodeOffsets<I> {
|
||||
&mut prelude,
|
||||
);
|
||||
|
||||
match &mut str_loc {
|
||||
IndexingCodePtr::Internal(ref mut i) => {
|
||||
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
if let IndexingCodePtr::Internal(ref mut i) = &mut str_loc {
|
||||
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
|
||||
}
|
||||
|
||||
match &mut lst_loc {
|
||||
IndexingCodePtr::Internal(ref mut i) => {
|
||||
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
|
||||
*i += emitted_switch_on_structure as usize; // str_loc.is_internal() as usize;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
if let IndexingCodePtr::Internal(ref mut i) = &mut lst_loc {
|
||||
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
|
||||
*i += emitted_switch_on_structure as usize; // str_loc.is_internal() as usize;
|
||||
}
|
||||
|
||||
let var_offset = 1 + skip_stub_try_me_else as usize;
|
||||
|
||||
|
||||
@@ -6,35 +6,22 @@ use crate::parser::ast::*;
|
||||
use std::cell::Cell;
|
||||
use std::collections::VecDeque;
|
||||
use std::iter::*;
|
||||
use std::rc::Rc;
|
||||
use std::vec::Vec;
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum TermRef<'a> {
|
||||
AnonVar(Level),
|
||||
Cons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||
Clause(Level, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||
PartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Atom),
|
||||
PartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Rc<String>),
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
}
|
||||
|
||||
/*
|
||||
impl<'a> TermRef<'a> {
|
||||
pub(crate) fn level(&self) -> Level {
|
||||
match self {
|
||||
TermRef::AnonVar(lvl) |
|
||||
TermRef::Cons(lvl, ..) |
|
||||
TermRef::Literal(lvl, ..) |
|
||||
TermRef::Var(lvl, ..) |
|
||||
TermRef::Clause(lvl, ..) |
|
||||
TermRef::CompleteString(lvl, ..) |
|
||||
TermRef::PartialString(lvl, ..) => *lvl,
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[allow(clippy::borrowed_box)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TermIterState<'a> {
|
||||
AnonVar(Level),
|
||||
@@ -42,9 +29,9 @@ pub(crate) enum TermIterState<'a> {
|
||||
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||
InitialCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
FinalCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||
InitialPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||
FinalPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Atom),
|
||||
InitialPartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
|
||||
FinalPartialString(Level, &'a Cell<RegType>, Rc<String>, &'a Box<Term>),
|
||||
CompleteString(Level, &'a Cell<RegType>, Rc<String>),
|
||||
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||
}
|
||||
|
||||
@@ -60,9 +47,11 @@ impl<'a> TermIterState<'a> {
|
||||
}
|
||||
Term::Literal(cell, constant) => TermIterState::Literal(lvl, cell, constant),
|
||||
Term::PartialString(cell, string_buf, tail) => {
|
||||
TermIterState::InitialPartialString(lvl, cell, string_buf, tail)
|
||||
TermIterState::InitialPartialString(lvl, cell, string_buf.clone(), tail)
|
||||
}
|
||||
Term::CompleteString(cell, string) => {
|
||||
TermIterState::CompleteString(lvl, cell, string.clone())
|
||||
}
|
||||
Term::CompleteString(cell, atom) => TermIterState::CompleteString(lvl, cell, *atom),
|
||||
Term::Var(cell, var_ptr) => TermIterState::Var(lvl, cell, var_ptr.clone()),
|
||||
}
|
||||
}
|
||||
@@ -113,11 +102,11 @@ impl<'a> QueryIterator<'a> {
|
||||
|
||||
fn extend_state(&mut self, lvl: Level, term: &'a QueryTerm) {
|
||||
match term {
|
||||
&QueryTerm::Clause(ref cell, ClauseType::CallN(_), ref terms, _) => {
|
||||
QueryTerm::Clause(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, _) => {
|
||||
QueryTerm::Clause(ref cell, ref ct, ref terms, _) => {
|
||||
self.state_stack
|
||||
.push(TermIterState::Clause(lvl, 0, cell, ct.name(), terms));
|
||||
}
|
||||
@@ -180,11 +169,11 @@ impl<'a> Iterator for QueryIterator<'a> {
|
||||
.push(TermIterState::FinalPartialString(lvl, cell, string, tail));
|
||||
self.push_subterm(lvl.child_level(), tail);
|
||||
}
|
||||
TermIterState::FinalPartialString(lvl, cell, atom, tail) => {
|
||||
return Some(TermRef::PartialString(lvl, cell, atom, tail));
|
||||
TermIterState::FinalPartialString(lvl, cell, string, tail) => {
|
||||
return Some(TermRef::PartialString(lvl, cell, string, tail));
|
||||
}
|
||||
TermIterState::CompleteString(lvl, cell, atom) => {
|
||||
return Some(TermRef::CompleteString(lvl, cell, atom));
|
||||
TermIterState::CompleteString(lvl, cell, string) => {
|
||||
return Some(TermRef::CompleteString(lvl, cell, string));
|
||||
}
|
||||
TermIterState::FinalCons(lvl, cell, head, tail) => {
|
||||
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||
@@ -214,7 +203,7 @@ impl<'a> FactIterator<'a> {
|
||||
.push_back(TermIterState::subterm_to_state(lvl, term));
|
||||
}
|
||||
|
||||
pub(crate) fn from_rule_head_clause(terms: &'a Vec<Term>) -> Self {
|
||||
pub(crate) fn from_rule_head_clause(terms: &'a [Term]) -> Self {
|
||||
let state_queue = terms
|
||||
.iter()
|
||||
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt))
|
||||
@@ -240,16 +229,20 @@ impl<'a> FactIterator<'a> {
|
||||
head.as_ref(),
|
||||
tail.as_ref(),
|
||||
)],
|
||||
Term::PartialString(cell, string_buf, tail) => {
|
||||
Term::PartialString(cell, string, tail) => {
|
||||
vec![TermIterState::InitialPartialString(
|
||||
Level::Root,
|
||||
cell,
|
||||
string_buf,
|
||||
string.clone(),
|
||||
tail,
|
||||
)]
|
||||
}
|
||||
Term::CompleteString(cell, atom) => {
|
||||
vec![TermIterState::CompleteString(Level::Root, cell, *atom)]
|
||||
Term::CompleteString(cell, string) => {
|
||||
vec![TermIterState::CompleteString(
|
||||
Level::Root,
|
||||
cell,
|
||||
string.clone(),
|
||||
)]
|
||||
}
|
||||
Term::Literal(cell, constant) => {
|
||||
vec![TermIterState::Literal(Level::Root, cell, constant)]
|
||||
@@ -312,14 +305,14 @@ 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,
|
||||
pub(crate) fn breadth_first_iter(
|
||||
term: &Term,
|
||||
iterable_root: RootIterationPolicy,
|
||||
) -> FactIterator<'a> {
|
||||
) -> FactIterator<'_> {
|
||||
FactIterator::new(term, iterable_root)
|
||||
}
|
||||
|
||||
@@ -334,7 +327,7 @@ pub(crate) enum ClauseItem<'a> {
|
||||
FirstBranch(usize),
|
||||
NextBranch,
|
||||
BranchEnd(usize),
|
||||
Chunk(&'a VecDeque<QueryTerm>),
|
||||
Chunk { terms: &'a VecDeque<QueryTerm> },
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -343,7 +336,7 @@ pub(crate) struct ClauseIterator<'a> {
|
||||
remaining_chunks_on_stack: usize,
|
||||
}
|
||||
|
||||
fn state_from_chunked_terms<'a>(chunk_vec: &'a VecDeque<ChunkedTerms>) -> ClauseIteratorState<'a> {
|
||||
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);
|
||||
@@ -410,8 +403,8 @@ impl<'a> Iterator for ClauseIterator<'a> {
|
||||
self.state_stack
|
||||
.push(ClauseIteratorState::RemainingBranches(branches, 0));
|
||||
}
|
||||
ChunkedTerms::Chunk(chunk) => {
|
||||
return Some(ClauseItem::Chunk(chunk));
|
||||
ChunkedTerms::Chunk { ref terms } => {
|
||||
return Some(ClauseItem::Chunk { terms });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -422,7 +415,7 @@ impl<'a> Iterator for ClauseIterator<'a> {
|
||||
if focus < branches.len() =>
|
||||
{
|
||||
self.state_stack
|
||||
.push(ClauseIteratorState::RemainingBranches(&branches, focus + 1));
|
||||
.push(ClauseIteratorState::RemainingBranches(branches, focus + 1));
|
||||
let state = state_from_chunked_terms(&branches[focus]);
|
||||
|
||||
if let ClauseIteratorState::RemainingChunks(..) = &state {
|
||||
|
||||
72
src/lib.rs
72
src/lib.rs
@@ -1,57 +1,83 @@
|
||||
//! A free software ISO Prolog system.
|
||||
#![recursion_limit = "4112"]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate static_assertions;
|
||||
#[cfg(test)]
|
||||
#[macro_use] extern crate maplit;
|
||||
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
pub(crate) mod macros;
|
||||
#[macro_use]
|
||||
pub mod atom_table;
|
||||
pub(crate) mod atom_table;
|
||||
#[macro_use]
|
||||
pub mod arena;
|
||||
pub(crate) mod arena;
|
||||
pub(crate) mod offset_table;
|
||||
#[macro_use]
|
||||
pub mod parser;
|
||||
pub(crate) mod parser;
|
||||
#[macro_use]
|
||||
pub(crate) mod functor_macro;
|
||||
mod allocator;
|
||||
mod arithmetic;
|
||||
pub mod codegen;
|
||||
pub(crate) mod codegen;
|
||||
mod debray_allocator;
|
||||
#[cfg(feature = "ffi")]
|
||||
mod ffi;
|
||||
mod forms;
|
||||
mod heap_iter;
|
||||
pub mod heap_print;
|
||||
pub(crate) mod heap_print;
|
||||
#[cfg(feature = "http")]
|
||||
mod http;
|
||||
mod indexing;
|
||||
mod variable_records;
|
||||
#[macro_use]
|
||||
pub mod instructions {
|
||||
pub(crate) mod instructions {
|
||||
include!(concat!(env!("OUT_DIR"), "/instructions.rs"));
|
||||
}
|
||||
mod iterators;
|
||||
pub mod machine;
|
||||
pub(crate) mod machine;
|
||||
mod raw_block;
|
||||
pub mod read;
|
||||
pub(crate) mod read;
|
||||
#[cfg(feature = "repl")]
|
||||
mod repl_helper;
|
||||
mod targets;
|
||||
pub mod types;
|
||||
pub(crate) mod types;
|
||||
|
||||
use instructions::instr;
|
||||
|
||||
mod rcu;
|
||||
// Re-exports
|
||||
pub use machine::config::*;
|
||||
pub use machine::lib_machine::*;
|
||||
pub use machine::Machine;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
pub mod wasm;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn eval_code(s: &str) -> String {
|
||||
use machine::mock_wam::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
/// The entry point for the Scryer Prolog CLI.
|
||||
pub fn run_binary() -> std::process::ExitCode {
|
||||
use crate::atom_table::Atom;
|
||||
use crate::machine::INTERRUPT;
|
||||
|
||||
let mut wam = Machine::with_test_streams();
|
||||
let bytes = wam.test_load_string(s);
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
#[cfg(feature = "repl")]
|
||||
ctrlc::set_handler(move || {
|
||||
INTERRUPT.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
runtime.block_on(async move {
|
||||
let mut wam = MachineBuilder::default()
|
||||
.with_streams(StreamConfig::stdio())
|
||||
.build();
|
||||
wam.run_module_predicate(atom!("$toplevel"), (atom!("$repl"), 0))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ Assocs are Key-Value associations implemented as a balanced binary tree
|
||||
Authors: R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
|
||||
*/
|
||||
|
||||
:- meta_predicate map_assoc(1, ?).
|
||||
:- meta_predicate map_assoc(2, ?, ?).
|
||||
:- meta_predicate(map_assoc(1, ?)).
|
||||
:- meta_predicate(map_assoc(2, ?, ?)).
|
||||
|
||||
%% empty_assoc(?Assoc) is semidet.
|
||||
%
|
||||
@@ -299,7 +299,7 @@ map_assoc_(t(Key,Val,B,L0,R0), Pred, t(Key,Ans,B,L1,R1)) :-
|
||||
map_assoc_(R0, Pred, R1).
|
||||
|
||||
|
||||
%% max_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%% max_assoc(+Assoc, -Key, -Value) is semidet.
|
||||
%
|
||||
% True if Key-Value is in Assoc and Key is the largest key.
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -110,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).
|
||||
|
||||
@@ -113,7 +113,7 @@ call(_, _, _, _, _, _, _, _).
|
||||
call(_, _, _, _, _, _, _, _, _).
|
||||
|
||||
|
||||
:- meta_predicate catch(0, ?, 0).
|
||||
:- meta_predicate(catch(0, ?, 0)).
|
||||
|
||||
% flags.
|
||||
|
||||
@@ -125,12 +125,12 @@ call(_, _, _, _, _, _, _, _, _).
|
||||
%
|
||||
% The flags that Scryer Prolog support are:
|
||||
%
|
||||
% * `max_arity`: The max arity a predicate can have in Prolog. On Scryer is set to 1023. Read only.
|
||||
% * `max_arity`: The max arity a predicate can have in Prolog. On Scryer is set to 255. Read only.
|
||||
% * `bounded`: `true` if integer arithmethic is bounded between some min/max values. On Scryer is always set
|
||||
% to `false` since it supports unbounded integer arithmethic. Read only.
|
||||
% * `integer_rounding_function`: Describes the rounding donde by `//` and `rem` functions. On Scryer is
|
||||
% always set to `toward_zero`. Read only
|
||||
% * `double_quotes`: Determines how double quoted strings are red by Prolog. Scryer uses `chars` by default
|
||||
% * `double_quotes`: Determines how double quoted strings are read by Prolog. Scryer uses `chars` by default
|
||||
% which is a list of one-character atoms. Other values are codes (list of integers representing characters),
|
||||
% and atom which creates a whole atom for the string value. Read and write.
|
||||
% * `max_integer`: Maximum integer supported by the system. As Scryer Prolog has unbounded integer arithmethic,
|
||||
@@ -145,8 +145,8 @@ call(_, _, _, _, _, _, _, _, _).
|
||||
% `fail` (the call silently fails) and `warn` (the call fails and a warning about the undefined predicate is printed).
|
||||
% * `answer_write_options`: Additional write options used by the top level for writing answers.
|
||||
%
|
||||
current_prolog_flag(Flag, Value) :- Flag == max_arity, !, Value = 1023.
|
||||
current_prolog_flag(max_arity, 1023).
|
||||
current_prolog_flag(Flag, Value) :- Flag == max_arity, !, Value = 255.
|
||||
current_prolog_flag(max_arity, 255).
|
||||
current_prolog_flag(Flag, Value) :- Flag == bounded, !, Value = false.
|
||||
current_prolog_flag(bounded, false).
|
||||
current_prolog_flag(Flag, Value) :- Flag == integer_rounding_function, !, Value == toward_zero.
|
||||
@@ -248,7 +248,7 @@ flag_domain_error(Flag, Value) :-
|
||||
fail :- '$fail'.
|
||||
|
||||
|
||||
:- meta_predicate \+(0).
|
||||
:- meta_predicate(\+(0)).
|
||||
|
||||
%% \+(Goal)
|
||||
%
|
||||
@@ -263,7 +263,7 @@ X \= X :- !, '$fail'.
|
||||
_ \= _.
|
||||
|
||||
|
||||
:- meta_predicate once(0).
|
||||
:- meta_predicate(once(0)).
|
||||
|
||||
%% once(Goal)
|
||||
%
|
||||
@@ -278,11 +278,11 @@ repeat.
|
||||
repeat :- repeat.
|
||||
|
||||
|
||||
:- meta_predicate ','(0,0).
|
||||
:- meta_predicate(','(0,0)).
|
||||
|
||||
:- meta_predicate ;(0,0).
|
||||
:- meta_predicate(;(0,0)).
|
||||
|
||||
:- meta_predicate ->(0,0).
|
||||
:- meta_predicate(->(0,0)).
|
||||
|
||||
%% ->(G1, G2)
|
||||
%
|
||||
@@ -364,7 +364,7 @@ cont_list_goal(Conts, '$call'(builtins:dispatch_call_list(Conts))).
|
||||
|
||||
dispatch_prep(Gs, B, Conts) :-
|
||||
( callable(Gs) ->
|
||||
strip_module(Gs, M, Gs0),
|
||||
loader:strip_module(Gs, M, Gs0),
|
||||
( nonvar(Gs0),
|
||||
dispatch_prep_(Gs0, B, Conts) ->
|
||||
true
|
||||
@@ -553,7 +553,7 @@ get_args([Arg|Args], Func, I0, N) :-
|
||||
get_args(Args, Func, I1, N).
|
||||
|
||||
|
||||
:- meta_predicate parse_options_list(?, 2, ?, ?, ?).
|
||||
:- meta_predicate(parse_options_list(?, 2, ?, ?, ?)).
|
||||
|
||||
parse_options_list(Options, Selector, DefaultPairs, OptionValues, Stub) :-
|
||||
'$skip_max_list'(_, _, Options, Tail),
|
||||
@@ -661,7 +661,7 @@ write_term(Term, Options) :-
|
||||
|
||||
%% write_term(+Stream, +Term, +Options).
|
||||
%
|
||||
% Write Term to the stream Stream according to some output syntax options. The options avaibale are:
|
||||
% Write Term to the stream Stream according to some output syntax options. The options available are:
|
||||
%
|
||||
% * `ignore_ops(+Boolean)` if `true`, the generic term representation is used everywhere. In `false`
|
||||
% (default), operators do not use that generic term representation.
|
||||
@@ -766,6 +766,9 @@ read(Term) :-
|
||||
read_term(Stream, Term, []).
|
||||
% read(Stream, Term).
|
||||
|
||||
%% read(+Stream, -Term).
|
||||
%
|
||||
% Same as read_term/3 with all default options.
|
||||
read(Stream, Term) :-
|
||||
read_term(Stream, Term, []).
|
||||
|
||||
@@ -813,7 +816,7 @@ catch(G,C,R) :-
|
||||
'$get_current_block'(Bb),
|
||||
catch(G,C,R,Bb).
|
||||
|
||||
:- meta_predicate catch(0, ?, 0, ?).
|
||||
:- meta_predicate(catch(0, ?, 0, ?)).
|
||||
|
||||
:- non_counted_backtracking catch/4.
|
||||
|
||||
@@ -881,7 +884,7 @@ throw(Ball) :-
|
||||
truncate_lh_to(LhLength) :- '$truncate_lh_to'(LhLength).
|
||||
|
||||
|
||||
:- meta_predicate findall(?, 0, ?).
|
||||
:- meta_predicate(findall(?, 0, ?)).
|
||||
|
||||
:- non_counted_backtracking findall_cleanup/2.
|
||||
|
||||
@@ -924,7 +927,7 @@ findall(Template, Goal, Solutions) :-
|
||||
'$get_lh_from_offset_diff'(LhOffset, Solutions0, Solutions1).
|
||||
|
||||
|
||||
:- meta_predicate findall(?, 0, ?, ?).
|
||||
:- meta_predicate(findall(?, 0, ?, ?)).
|
||||
|
||||
:- non_counted_backtracking findall/4.
|
||||
|
||||
@@ -1042,7 +1045,7 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses)
|
||||
).
|
||||
|
||||
|
||||
:- meta_predicate bagof(?, 0, ?).
|
||||
:- meta_predicate(bagof(?, 0, ?)).
|
||||
|
||||
:- non_counted_backtracking bagof/3.
|
||||
|
||||
@@ -1088,7 +1091,7 @@ iterate_variants_and_sort([_|GroupSolutions], Ws, Solution) :-
|
||||
iterate_variants_and_sort(GroupSolutions, Ws, Solution).
|
||||
|
||||
|
||||
:- meta_predicate setof(?, 0, ?).
|
||||
:- meta_predicate(setof(?, 0, ?)).
|
||||
|
||||
:- non_counted_backtracking setof/3.
|
||||
|
||||
@@ -1165,29 +1168,40 @@ clause(H, B) :-
|
||||
).
|
||||
|
||||
|
||||
:- meta_predicate asserta(:).
|
||||
:- meta_predicate(asserta(:)).
|
||||
|
||||
%% asserta(Clause).
|
||||
%
|
||||
% Asserts (inserts) a new clause (rule or fact) into the current module.
|
||||
% The clause will be inserted at the beginning of the module.
|
||||
asserta(Clause0) :-
|
||||
loader:strip_subst_module(Clause0, user, Module, Clause),
|
||||
iso_ext:asserta(Module, Clause).
|
||||
loader:strip_module(Clause0, Module, Clause),
|
||||
asserta_(Module, Clause).
|
||||
|
||||
asserta_(Module, (Head :- Body)) :-
|
||||
!,
|
||||
'$asserta'(Module, Head, Body).
|
||||
asserta_(Module, Fact) :-
|
||||
'$asserta'(Module, Fact, true).
|
||||
|
||||
:- meta_predicate assertz(:).
|
||||
:- meta_predicate(assertz(:)).
|
||||
|
||||
%% assertz(Clause).
|
||||
%
|
||||
% Asserts (inserts) a new clause (rule or fact) into the current module.
|
||||
% The clase will be inserted at the end of the module.
|
||||
assertz(Clause0) :-
|
||||
loader:strip_subst_module(Clause0, user, Module, Clause),
|
||||
iso_ext:assertz(Module, Clause).
|
||||
loader:strip_module(Clause0, Module, Clause),
|
||||
assertz_(Module, Clause).
|
||||
|
||||
assertz_(Module, (Head :- Body)) :-
|
||||
!,
|
||||
'$assertz'(Module, Head, Body).
|
||||
assertz_(Module, Fact) :-
|
||||
'$assertz'(Module, Fact, true).
|
||||
|
||||
|
||||
:- meta_predicate retract(:).
|
||||
:- meta_predicate(retract(:)).
|
||||
|
||||
%% retract(Clause)
|
||||
%
|
||||
@@ -1196,12 +1210,9 @@ assertz(Clause0) :-
|
||||
retract(Clause0) :-
|
||||
loader:strip_module(Clause0, Module, Clause),
|
||||
( Clause \= (_ :- _) ->
|
||||
loader:strip_module(Clause, Module, Head),
|
||||
( var(Module) -> Module = user
|
||||
; true
|
||||
),
|
||||
loader:strip_subst_module(Clause, Module, InnerModule, Head),
|
||||
Body = true,
|
||||
retract_module_clause(Head, Body, Module)
|
||||
retract_module_clause(Head, Body, InnerModule)
|
||||
; Clause = (Head :- Body) ->
|
||||
retract_module_clause(Head, Body, Module)
|
||||
).
|
||||
@@ -1225,7 +1236,10 @@ call_retract_helper(Head, Body, P, Module) :-
|
||||
; ClauseQualifier = Module
|
||||
),
|
||||
ClauseQualifier:'$clause'(Head, Body),
|
||||
'$get_clause_p'(Head, P, Module).
|
||||
% ensure '$get_clause_p'/3 is not the last clause so it can
|
||||
% recover the choice point of '$clause' if necessary.
|
||||
'$get_clause_p'(Head, P, Module),
|
||||
true.
|
||||
|
||||
call_retract(Head, Body, Name, Arity, Module) :-
|
||||
findall(P, builtins:call_retract_helper(Head, Body, P, Module), Ps),
|
||||
@@ -1264,7 +1278,7 @@ retract_module_clause(Head, Body, Module) :-
|
||||
; throw(error(type_error(callable, Head), retract/1))
|
||||
).
|
||||
|
||||
:- meta_predicate retractall(:).
|
||||
:- meta_predicate(retractall(:)).
|
||||
|
||||
%% retractall(Head)
|
||||
%
|
||||
@@ -1304,7 +1318,7 @@ module_abolish(Pred, Module) :-
|
||||
; throw(error(type_error(predicate_indicator, Module:Pred), abolish/1))
|
||||
).
|
||||
|
||||
:- meta_predicate abolish(:).
|
||||
:- meta_predicate(abolish(:)).
|
||||
|
||||
%% abolish(Pred).
|
||||
%
|
||||
@@ -1353,11 +1367,7 @@ current_predicate(Pred) :-
|
||||
( var(Pred) ->
|
||||
'$get_db_refs'(_, _, _, PIs),
|
||||
lists:member(Pred, PIs)
|
||||
; '$strip_module'(Pred, Module, UnqualifiedPred),
|
||||
( var(Module),
|
||||
\+ functor(Pred, (:), 2)
|
||||
; atom(Module)
|
||||
),
|
||||
; loader:strip_module(Pred, Module, UnqualifiedPred),
|
||||
UnqualifiedPred = Name/Arity ->
|
||||
( ( nonvar(Name), \+ atom(Name)
|
||||
; nonvar(Arity), \+ integer(Arity)
|
||||
@@ -1709,20 +1719,27 @@ must_be_number(N, PI) :-
|
||||
).
|
||||
|
||||
|
||||
chars_or_vars(Cs, _) :-
|
||||
chars_or_vars(Cs, PI) :-
|
||||
( '$is_partial_string'(Cs) ->
|
||||
% use a fast test for the expected case
|
||||
true
|
||||
; chars_or_vars_(Cs, PI)
|
||||
).
|
||||
|
||||
chars_or_vars_(Cs, _) :-
|
||||
( var(Cs) ->
|
||||
!
|
||||
; Cs == [] ->
|
||||
!
|
||||
).
|
||||
chars_or_vars([C|Cs], PI) :-
|
||||
chars_or_vars_([C|Cs], PI) :-
|
||||
( nonvar(C) ->
|
||||
( atom(C),
|
||||
atom_length(C, 1) ->
|
||||
chars_or_vars(Cs, PI)
|
||||
chars_or_vars_(Cs, PI)
|
||||
; throw(error(type_error(character, C), PI))
|
||||
)
|
||||
; chars_or_vars(Cs, PI)
|
||||
; chars_or_vars_(Cs, PI)
|
||||
).
|
||||
|
||||
|
||||
@@ -2178,12 +2195,10 @@ stream_property(S, P) :-
|
||||
%% at_end_of_stream(+Stream).
|
||||
%
|
||||
% True iff the stream Stream has ended
|
||||
at_end_of_stream(S_or_a) :-
|
||||
( var(S_or_a) ->
|
||||
at_end_of_stream(S) :-
|
||||
( var(S) ->
|
||||
throw(error(instantiation_error, at_end_of_stream/1))
|
||||
; atom(S_or_a) ->
|
||||
stream_property(S, alias(S_or_a))
|
||||
; S = S_or_a
|
||||
; true
|
||||
),
|
||||
stream_property(S, end_of_stream(E)),
|
||||
( E = at -> true ; E = past ).
|
||||
@@ -2195,7 +2210,7 @@ at_end_of_stream :-
|
||||
current_input(S),
|
||||
stream_property(S, end_of_stream(E)),
|
||||
!,
|
||||
( E = at ; E = past ).
|
||||
( E = at -> true ; E = past ).
|
||||
|
||||
%% set_stream_position(+Stream, +Position).
|
||||
%
|
||||
@@ -2211,7 +2226,7 @@ set_stream_position(S_or_a, Position) :-
|
||||
|
||||
%% callable(X).
|
||||
%
|
||||
% True iff X is bound o an atom or a compund term.
|
||||
% True iff X is bound to an atom or a compound term.
|
||||
|
||||
:- non_counted_backtracking callable/1.
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ read and write chars.
|
||||
:- 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) :-
|
||||
@@ -74,9 +75,10 @@ extend_var_list_([V|Vs], N, VarList, NewVarList, VarType) :-
|
||||
).
|
||||
|
||||
|
||||
%% char_type(+Char, -Type).
|
||||
%% char_type(?Char, ?Type).
|
||||
%
|
||||
% Given a Char, Type is one of the categories that char fits in.
|
||||
% Type is one of the categories that Char fits in.
|
||||
% At least one of the arguments must be ground.
|
||||
% Possible categories are:
|
||||
%
|
||||
% - `alnum`
|
||||
@@ -132,17 +134,27 @@ extend_var_list_([V|Vs], N, VarList, NewVarList, VarType) :-
|
||||
% 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).
|
||||
@@ -183,7 +195,7 @@ get_single_char(C) :-
|
||||
; type_error(in_character, C, get_single_char/1)
|
||||
).
|
||||
|
||||
%% read_from_chars(+Chars, -Term).
|
||||
%% read_from_chars(+Chars, ?Term).
|
||||
%
|
||||
% Given a string made of chars which contains a representation of
|
||||
% a Prolog term, Term is the Prolog term represented. Example:
|
||||
@@ -194,10 +206,10 @@ get_single_char(C) :-
|
||||
% ```
|
||||
read_from_chars(Chars, Term) :-
|
||||
must_be(chars, Chars),
|
||||
must_be(var, Term),
|
||||
'$read_from_chars'(Chars, Term).
|
||||
'$read_from_chars'(Chars, Term0),
|
||||
Term = Term0.
|
||||
|
||||
%% read_term_from_chars(+Chars, -Term, +Options).
|
||||
%% read_term_from_chars(+Chars, ?Term, +Options).
|
||||
%
|
||||
% Like `read_from_chars`, except the reader is configured according to
|
||||
% `Options` which are those of `read_term`.
|
||||
@@ -208,9 +220,9 @@ read_from_chars(Chars, Term) :-
|
||||
% ```
|
||||
read_term_from_chars(Chars, Term, Options) :-
|
||||
must_be(chars, Chars),
|
||||
must_be(var, Term),
|
||||
builtins:parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term_from_chars/3),
|
||||
'$read_term_from_chars'(Chars, Term, Singletons, Variables, VariableNames).
|
||||
'$read_term_from_chars'(Chars, Term0, Singletons, Variables, VariableNames),
|
||||
Term = Term0.
|
||||
|
||||
%% write_term_to_chars(+Term, +Options, -Chars).
|
||||
%
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Author: Markus Triska
|
||||
E-mail: triska@metalevel.at
|
||||
WWW: https://www.metalevel.at
|
||||
Copyright (C): 2019-2023 Markus Triska
|
||||
Copyright (C): 2019-2025 Markus Triska
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
@@ -189,18 +189,18 @@ A _Boolean expression_ is one of:
|
||||
| `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_) |
|
||||
| `~` _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_) |
|
||||
|
||||
@@ -1150,8 +1150,9 @@ verify_attributes(Var, Other, Gs) :-
|
||||
( integer(Other) ->
|
||||
( between(0, 1, Other) ->
|
||||
root_get_formula_bdd(Root, Sat, BDD0),
|
||||
bdd_restriction(BDD0, I, Other, BDD),
|
||||
root_put_formula_bdd(Root, Sat, BDD),
|
||||
Gs = [bdd_restriction(BDD0,I,Other,BDD),satisfiable_bdd(BDD)]
|
||||
Gs = [satisfiable_bdd(BDD)]
|
||||
; no_truth_value(Other)
|
||||
)
|
||||
; atom(Other) ->
|
||||
@@ -1251,7 +1252,7 @@ bdd_restriction_(Node, VI, Value, Res) -->
|
||||
node_id(Node, ID) },
|
||||
( { I0 =:= VI } ->
|
||||
( { Value =:= 0 } -> { Res = Low }
|
||||
; { Value =:= 1 } -> { Res = High }
|
||||
; { Res = High }
|
||||
)
|
||||
; { I0 > VI } -> { Res = Node }
|
||||
; state(G0), { get_assoc(ID, G0, Res) } -> []
|
||||
@@ -1550,11 +1551,15 @@ random_bindings(VNum, Node) -->
|
||||
{ node_var_low_high(Node, Var, Low, High),
|
||||
bdd_count(Node, VNum, Total),
|
||||
bdd_count(Low, VNum, LCount) },
|
||||
( { maybe(LCount, Total) } ->
|
||||
( { weighted_maybe(LCount, Total) } ->
|
||||
[Var=0], random_bindings(VNum, Low)
|
||||
; [Var=1], random_bindings(VNum, High)
|
||||
).
|
||||
|
||||
weighted_maybe(K, N) :-
|
||||
random_integer(0, N, X),
|
||||
X < K.
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Find solutions with maximum weight.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
398
src/lib/clpz.pl
398
src/lib/clpz.pl
@@ -3,7 +3,7 @@
|
||||
Author: Markus Triska
|
||||
E-mail: triska@metalevel.at
|
||||
WWW: https://www.metalevel.at
|
||||
Copyright (C): 2016-2023 Markus Triska
|
||||
Copyright (C): 2016-2024 Markus Triska
|
||||
|
||||
This library provides CLP(ℤ):
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
fd_dom/2,
|
||||
|
||||
% for use in predicates from library(reif)
|
||||
clpz_t/2,
|
||||
(#=)/3,
|
||||
(#<)/3
|
||||
|
||||
@@ -457,7 +458,7 @@ replacements of these low-level arithmetic predicates, often yielding
|
||||
more general programs. See [`n_factorial/2`](#clpz-factorial) for an
|
||||
example.
|
||||
|
||||
This library uses goal_expansion/2 to automatically rewrite
|
||||
This library uses `goal_expansion/2` to automatically rewrite
|
||||
constraints at compilation time so that low-level arithmetic
|
||||
predicates are _automatically_ used whenever possible. For example,
|
||||
the predicate:
|
||||
@@ -1015,6 +1016,9 @@ X in inf..sup.
|
||||
needed to schedule the propagators!
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- meta_predicate(duophrase(4, ?, ?)).
|
||||
:- meta_predicate(duophrase(4, ?, ?, ?, ?)).
|
||||
|
||||
duophrase(NT, As, Bs) :-
|
||||
duophrase(NT, As, [], Bs, []).
|
||||
|
||||
@@ -1735,7 +1739,7 @@ intervals_to_domain(Is, D) :-
|
||||
% _Lower_ must be an integer or the atom *inf*, which
|
||||
% denotes negative infinity. _Upper_ must be an integer or
|
||||
% the atom *sup*, which denotes positive infinity.
|
||||
% * Domain1 \/ Domain2
|
||||
% * Domain1 `\/` Domain2
|
||||
% The union of Domain1 and Domain2.
|
||||
|
||||
Var in Dom :- clpz_in(Var, Dom).
|
||||
@@ -2409,22 +2413,22 @@ sum_finite_domains([C|Cs], [V|Vs], Inf0, Sup0, Inf, Sup) ++>
|
||||
),
|
||||
sum_finite_domains(Cs, Vs, Inf2, Sup2, Inf, Sup).
|
||||
|
||||
remove_dist_upper_lower([], _, _, _).
|
||||
remove_dist_upper_lower([C|Cs], [V|Vs], D1, D2) :-
|
||||
( fd_get(V, VD, VPs) ->
|
||||
remove_dist_upper_lower([], _, _, _) --> [].
|
||||
remove_dist_upper_lower([C|Cs], [V|Vs], D1, D2) -->
|
||||
( { fd_get(V, VD, VPs) } ->
|
||||
( C < 0 ->
|
||||
domain_supremum(VD, n(Sup)),
|
||||
L is Sup + D1//C,
|
||||
domain_remove_smaller_than(VD, L, VD1),
|
||||
domain_infimum(VD1, n(Inf)),
|
||||
G is Inf - D2//C,
|
||||
domain_remove_greater_than(VD1, G, VD2)
|
||||
; domain_infimum(VD, n(Inf)),
|
||||
G is Inf + D1//C,
|
||||
domain_remove_greater_than(VD, G, VD1),
|
||||
domain_supremum(VD1, n(Sup)),
|
||||
L is Sup - D2//C,
|
||||
domain_remove_smaller_than(VD1, L, VD2)
|
||||
{ domain_supremum(VD, n(Sup)),
|
||||
L is Sup + D1//C,
|
||||
domain_remove_smaller_than(VD, L, VD1),
|
||||
domain_infimum(VD1, n(Inf)),
|
||||
G is Inf - D2//C,
|
||||
domain_remove_greater_than(VD1, G, VD2) }
|
||||
; { domain_infimum(VD, n(Inf)),
|
||||
G is Inf + D1//C,
|
||||
domain_remove_greater_than(VD, G, VD1),
|
||||
domain_supremum(VD1, n(Sup)),
|
||||
L is Sup - D2//C,
|
||||
domain_remove_smaller_than(VD1, L, VD2) }
|
||||
),
|
||||
fd_put(V, VD2, VPs)
|
||||
; true
|
||||
@@ -2432,16 +2436,16 @@ remove_dist_upper_lower([C|Cs], [V|Vs], D1, D2) :-
|
||||
remove_dist_upper_lower(Cs, Vs, D1, D2).
|
||||
|
||||
|
||||
remove_dist_upper_leq([], _, _).
|
||||
remove_dist_upper_leq([C|Cs], [V|Vs], D1) :-
|
||||
( fd_get(V, VD, VPs) ->
|
||||
remove_dist_upper_leq([], _, _) --> [].
|
||||
remove_dist_upper_leq([C|Cs], [V|Vs], D1) -->
|
||||
( { fd_get(V, VD, VPs) } ->
|
||||
( C < 0 ->
|
||||
domain_supremum(VD, n(Sup)),
|
||||
L is Sup + D1//C,
|
||||
domain_remove_smaller_than(VD, L, VD1)
|
||||
; domain_infimum(VD, n(Inf)),
|
||||
G is Inf + D1//C,
|
||||
domain_remove_greater_than(VD, G, VD1)
|
||||
{ domain_supremum(VD, n(Sup)),
|
||||
L is Sup + D1//C,
|
||||
domain_remove_smaller_than(VD, L, VD1) }
|
||||
; { domain_infimum(VD, n(Inf)),
|
||||
G is Inf + D1//C,
|
||||
domain_remove_greater_than(VD, G, VD1) }
|
||||
),
|
||||
fd_put(V, VD1, VPs)
|
||||
; true
|
||||
@@ -2449,18 +2453,18 @@ remove_dist_upper_leq([C|Cs], [V|Vs], D1) :-
|
||||
remove_dist_upper_leq(Cs, Vs, D1).
|
||||
|
||||
|
||||
remove_dist_upper([], _).
|
||||
remove_dist_upper([C*V|CVs], D) :-
|
||||
( fd_get(V, VD, VPs) ->
|
||||
remove_dist_upper([], _) --> [].
|
||||
remove_dist_upper([C*V|CVs], D) -->
|
||||
( { fd_get(V, VD, VPs) } ->
|
||||
( C < 0 ->
|
||||
( domain_supremum(VD, n(Sup)) ->
|
||||
L is Sup + D//C,
|
||||
domain_remove_smaller_than(VD, L, VD1)
|
||||
( { domain_supremum(VD, n(Sup)) } ->
|
||||
{ L is Sup + D//C,
|
||||
domain_remove_smaller_than(VD, L, VD1) }
|
||||
; VD1 = VD
|
||||
)
|
||||
; ( domain_infimum(VD, n(Inf)) ->
|
||||
G is Inf + D//C,
|
||||
domain_remove_greater_than(VD, G, VD1)
|
||||
; ( { domain_infimum(VD, n(Inf)) } ->
|
||||
{ G is Inf + D//C,
|
||||
domain_remove_greater_than(VD, G, VD1) }
|
||||
; VD1 = VD
|
||||
)
|
||||
),
|
||||
@@ -2469,18 +2473,18 @@ remove_dist_upper([C*V|CVs], D) :-
|
||||
),
|
||||
remove_dist_upper(CVs, D).
|
||||
|
||||
remove_dist_lower([], _).
|
||||
remove_dist_lower([C*V|CVs], D) :-
|
||||
( fd_get(V, VD, VPs) ->
|
||||
remove_dist_lower([], _) --> [].
|
||||
remove_dist_lower([C*V|CVs], D) -->
|
||||
( { fd_get(V, VD, VPs) } ->
|
||||
( C < 0 ->
|
||||
( domain_infimum(VD, n(Inf)) ->
|
||||
G is Inf - D//C,
|
||||
domain_remove_greater_than(VD, G, VD1)
|
||||
( { domain_infimum(VD, n(Inf)) } ->
|
||||
{ G is Inf - D//C,
|
||||
domain_remove_greater_than(VD, G, VD1) }
|
||||
; VD1 = VD
|
||||
)
|
||||
; ( domain_supremum(VD, n(Sup)) ->
|
||||
L is Sup - D//C,
|
||||
domain_remove_smaller_than(VD, L, VD1)
|
||||
; ( { domain_supremum(VD, n(Sup)) } ->
|
||||
{ L is Sup - D//C,
|
||||
domain_remove_smaller_than(VD, L, VD1) }
|
||||
; VD1 = VD
|
||||
)
|
||||
),
|
||||
@@ -2489,26 +2493,26 @@ remove_dist_lower([C*V|CVs], D) :-
|
||||
),
|
||||
remove_dist_lower(CVs, D).
|
||||
|
||||
remove_upper([], _).
|
||||
remove_upper([C*X|CXs], Max) :-
|
||||
( fd_get(X, XD, XPs) ->
|
||||
remove_upper([], _) --> [].
|
||||
remove_upper([C*X|CXs], Max) -->
|
||||
( { fd_get(X, XD, XPs) } ->
|
||||
D is Max//C,
|
||||
( C < 0 ->
|
||||
domain_remove_smaller_than(XD, D, XD1)
|
||||
; domain_remove_greater_than(XD, D, XD1)
|
||||
{ domain_remove_smaller_than(XD, D, XD1) }
|
||||
; { domain_remove_greater_than(XD, D, XD1) }
|
||||
),
|
||||
fd_put(X, XD1, XPs)
|
||||
; true
|
||||
),
|
||||
remove_upper(CXs, Max).
|
||||
|
||||
remove_lower([], _).
|
||||
remove_lower([C*X|CXs], Min) :-
|
||||
( fd_get(X, XD, XPs) ->
|
||||
remove_lower([], _) --> [].
|
||||
remove_lower([C*X|CXs], Min) -->
|
||||
( { fd_get(X, XD, XPs) } ->
|
||||
D is -Min//C,
|
||||
( C < 0 ->
|
||||
domain_remove_greater_than(XD, D, XD1)
|
||||
; domain_remove_smaller_than(XD, D, XD1)
|
||||
{ domain_remove_greater_than(XD, D, XD1) }
|
||||
; { domain_remove_smaller_than(XD, D, XD1) }
|
||||
),
|
||||
fd_put(X, XD1, XPs)
|
||||
; true
|
||||
@@ -2747,20 +2751,24 @@ propagator_init_trigger(Vs, P) :-
|
||||
prop_init(Prop, V) :- init_propagator(V, Prop).
|
||||
|
||||
geq(A, B) :-
|
||||
( fd_get(A, AD, APs) ->
|
||||
domain_infimum(AD, AI),
|
||||
( fd_get(B, BD, _) ->
|
||||
domain_supremum(BD, BS),
|
||||
( AI cis_geq BS -> true
|
||||
; propagator_init_trigger(pgeq(A,B))
|
||||
new_queue(Q),
|
||||
phrase((geq(A, B),do_queue), [Q], _).
|
||||
|
||||
geq(A, B) -->
|
||||
( { fd_get(A, AD, APs) } ->
|
||||
{ domain_infimum(AD, AI) },
|
||||
( { fd_get(B, BD, _) } ->
|
||||
{ domain_supremum(BD, BS) },
|
||||
( { AI cis_geq BS } -> true
|
||||
; { propagator_init_trigger(pgeq(A,B)) }
|
||||
)
|
||||
; ( AI cis_geq n(B) -> true
|
||||
; domain_remove_smaller_than(AD, B, AD1),
|
||||
; ( { AI cis_geq n(B) } -> true
|
||||
; { domain_remove_smaller_than(AD, B, AD1) },
|
||||
fd_put(A, AD1, APs)
|
||||
)
|
||||
)
|
||||
; fd_get(B, BD, BPs) ->
|
||||
domain_remove_greater_than(BD, A, BD1),
|
||||
; { fd_get(B, BD, BPs) } ->
|
||||
{ domain_remove_greater_than(BD, A, BD1) },
|
||||
fd_put(B, BD1, BPs)
|
||||
; A >= B
|
||||
).
|
||||
@@ -4164,6 +4172,7 @@ var(V) --> { var(V) }.
|
||||
ground(T) --> { ground(T) }.
|
||||
|
||||
true --> [].
|
||||
false --> { false }.
|
||||
|
||||
X >= Y --> { X >= Y }.
|
||||
X =< Y --> { X =< Y }.
|
||||
@@ -4221,10 +4230,7 @@ activate_propagator(propagator(P,State)) -->
|
||||
)
|
||||
).
|
||||
|
||||
enable_queue :- true. % NOP
|
||||
disable_queue :- true. % NOP
|
||||
|
||||
%do_queue --> print_queue, { false }.
|
||||
%do_queue --> print_queue, false.
|
||||
do_queue -->
|
||||
( queue_enabled ->
|
||||
( queue_get_goal(Goal) -> { call(Goal) }, do_queue
|
||||
@@ -4507,13 +4513,13 @@ run_propagator(pelement(N, Is, V), MState) -->
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
run_propagator(pgcc_single(Vs, Pairs), _) --> { gcc_global(Vs, Pairs) }.
|
||||
run_propagator(pgcc_single(Vs, Pairs), _) --> gcc_global(Vs, Pairs).
|
||||
|
||||
run_propagator(pgcc_check_single(Pairs), _) --> { gcc_check(Pairs) }.
|
||||
run_propagator(pgcc_check_single(Pairs), _) --> gcc_check(Pairs).
|
||||
|
||||
run_propagator(pgcc_check(Pairs), _) --> { gcc_check(Pairs) }.
|
||||
run_propagator(pgcc_check(Pairs), _) --> gcc_check(Pairs).
|
||||
|
||||
run_propagator(pgcc(Vs, _, Pairs), _) --> { gcc_global(Vs, Pairs) }.
|
||||
run_propagator(pgcc(Vs, _, Pairs), _) --> gcc_global(Vs, Pairs).
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
@@ -4588,7 +4594,7 @@ run_propagator(pserialized(S_I, D_I, S_J, D_J, _), MState) -->
|
||||
kill(MState),
|
||||
( S_I + D_I =< S_J -> []
|
||||
; S_J + D_J =< S_I -> []
|
||||
; { false }
|
||||
; false
|
||||
)
|
||||
; serialize_lower_upper(S_I, D_I, S_J, D_J, MState),
|
||||
serialize_lower_upper(S_J, D_J, S_I, D_I, MState)
|
||||
@@ -4661,7 +4667,7 @@ run_propagator(x_eq_abs_plus_v(X,V), MState) -->
|
||||
( nonvar(V) ->
|
||||
( V =:= 0 -> kill(MState), { X in 0..sup }
|
||||
; V < 0 -> kill(MState), { X #= V / 2 }
|
||||
; V > 0 -> { false }
|
||||
; false % V > 0
|
||||
)
|
||||
; nonvar(X) ->
|
||||
kill(MState),
|
||||
@@ -4753,55 +4759,55 @@ run_propagator(scalar_product_neq(Cs0,Vs0,P0), MState) -->
|
||||
) }.
|
||||
|
||||
run_propagator(scalar_product_leq(Cs0,Vs0,P0), MState) -->
|
||||
{ coeffs_variables_const(Cs0, Vs0, Cs, Vs, 0, I),
|
||||
P is P0 - I,
|
||||
( Vs = [] -> kill(MState), P >= 0
|
||||
; duophrase(sum_finite_domains(Cs, Vs, 0, 0, Inf, Sup), Infs, Sups),
|
||||
D1 is P - Inf,
|
||||
disable_queue,
|
||||
( Infs == [], Sups == [] ->
|
||||
Inf =< P,
|
||||
( Sup =< P -> kill(MState)
|
||||
; remove_dist_upper_leq(Cs, Vs, D1)
|
||||
)
|
||||
; Infs == [] -> Inf =< P, remove_dist_upper(Sups, D1)
|
||||
; Infs = [_] -> remove_upper(Infs, D1)
|
||||
; true
|
||||
),
|
||||
enable_queue
|
||||
) }.
|
||||
{ coeffs_variables_const(Cs0, Vs0, Cs, Vs, 0, I) },
|
||||
P is P0 - I,
|
||||
( Vs = [] -> kill(MState), P >= 0
|
||||
; { duophrase(sum_finite_domains(Cs, Vs, 0, 0, Inf, Sup), Infs, Sups) },
|
||||
D1 is P - Inf,
|
||||
disable_queue,
|
||||
( Infs == [], Sups == [] ->
|
||||
Inf =< P,
|
||||
( Sup =< P -> kill(MState)
|
||||
; remove_dist_upper_leq(Cs, Vs, D1)
|
||||
)
|
||||
; Infs == [] -> Inf =< P, remove_dist_upper(Sups, D1)
|
||||
; Infs = [_] -> remove_upper(Infs, D1)
|
||||
; true
|
||||
),
|
||||
enable_queue
|
||||
).
|
||||
|
||||
run_propagator(scalar_product_eq(Cs0,Vs0,P0), MState) -->
|
||||
{ coeffs_variables_const(Cs0, Vs0, Cs, Vs, 0, I),
|
||||
P is P0 - I,
|
||||
( Vs = [] -> kill(MState), P =:= 0
|
||||
; Vs = [V], Cs = [C] -> kill(MState), P mod C =:= 0, V is P // C
|
||||
; Cs == [1,1] -> kill(MState), Vs = [A,B], A + B #= P
|
||||
; Cs == [1,-1] -> kill(MState), Vs = [A,B], A #= P + B
|
||||
; Cs == [-1,1] -> kill(MState), Vs = [A,B], B #= P + A
|
||||
; Cs == [-1,-1] -> kill(MState), Vs = [A,B], P1 is -P, A + B #= P1
|
||||
; P =:= 0, Cs == [1,1,-1] -> kill(MState), Vs = [A,B,C], A + B #= C
|
||||
; P =:= 0, Cs == [1,-1,1] -> kill(MState), Vs = [A,B,C], A + C #= B
|
||||
; P =:= 0, Cs == [-1,1,1] -> kill(MState), Vs = [A,B,C], B + C #= A
|
||||
; duophrase(sum_finite_domains(Cs, Vs, 0, 0, Inf, Sup), Infs, Sups),
|
||||
% nl, writeln(Infs-Sups-Inf-Sup),
|
||||
D1 is P - Inf,
|
||||
D2 is Sup - P,
|
||||
disable_queue,
|
||||
( Infs == [], Sups == [] ->
|
||||
between(Inf, Sup, P),
|
||||
remove_dist_upper_lower(Cs, Vs, D1, D2)
|
||||
; Sups = [] -> P =< Sup, remove_dist_lower(Infs, D2)
|
||||
; Infs = [] -> Inf =< P, remove_dist_upper(Sups, D1)
|
||||
; Sups = [_], Infs = [_] ->
|
||||
remove_lower(Sups, D2),
|
||||
remove_upper(Infs, D1)
|
||||
; Infs = [_] -> remove_upper(Infs, D1)
|
||||
; Sups = [_] -> remove_lower(Sups, D2)
|
||||
; true
|
||||
),
|
||||
enable_queue
|
||||
) }.
|
||||
{ coeffs_variables_const(Cs0, Vs0, Cs, Vs, 0, I) },
|
||||
P is P0 - I,
|
||||
( Vs = [] -> kill(MState), P =:= 0
|
||||
; Vs = [V], Cs = [C] -> kill(MState), P mod C =:= 0, V is P // C
|
||||
; Cs == [1,1] -> kill(MState), Vs = [A,B], { A + B #= P }
|
||||
; Cs == [1,-1] -> kill(MState), Vs = [A,B], { A #= P + B }
|
||||
; Cs == [-1,1] -> kill(MState), Vs = [A,B], { B #= P + A }
|
||||
; Cs == [-1,-1] -> kill(MState), Vs = [A,B], P1 is -P, { A + B #= P1 }
|
||||
; P =:= 0, Cs == [1,1,-1] -> kill(MState), Vs = [A,B,C], { A + B #= C }
|
||||
; P =:= 0, Cs == [1,-1,1] -> kill(MState), Vs = [A,B,C], { A + C #= B }
|
||||
; P =:= 0, Cs == [-1,1,1] -> kill(MState), Vs = [A,B,C], { B + C #= A }
|
||||
; { duophrase(sum_finite_domains(Cs, Vs, 0, 0, Inf, Sup), Infs, Sups) },
|
||||
% { nl, writeln(Infs-Sups-Inf-Sup) },
|
||||
D1 is P - Inf,
|
||||
D2 is Sup - P,
|
||||
disable_queue,
|
||||
( Infs == [], Sups == [] ->
|
||||
{ between(Inf, Sup, P) },
|
||||
remove_dist_upper_lower(Cs, Vs, D1, D2)
|
||||
; Sups = [] -> P =< Sup, remove_dist_lower(Infs, D2)
|
||||
; Infs = [] -> Inf =< P, remove_dist_upper(Sups, D1)
|
||||
; Sups = [_], Infs = [_] ->
|
||||
remove_lower(Sups, D2),
|
||||
remove_upper(Infs, D1)
|
||||
; Infs = [_] -> remove_upper(Infs, D1)
|
||||
; Sups = [_] -> remove_lower(Sups, D2)
|
||||
; true
|
||||
),
|
||||
enable_queue
|
||||
).
|
||||
|
||||
% X + Y = Z
|
||||
run_propagator(pplus(X,Y,Z,Morph), MState) -->
|
||||
@@ -5048,8 +5054,8 @@ run_propagator(ptzdiv(X,Y,Z,Morph), MState) -->
|
||||
%% % Z = X mod Y
|
||||
|
||||
run_propagator(pmod(X,Y,Z), MState) -->
|
||||
( Y == 0 -> { false }
|
||||
; Y == Z -> { false }
|
||||
( Y == 0 -> false
|
||||
; Y == Z -> false
|
||||
; X == Y -> kill(MState), queue_goal(Z = 0)
|
||||
; true
|
||||
),
|
||||
@@ -5058,7 +5064,7 @@ run_propagator(pmod(X,Y,Z), MState) -->
|
||||
Z is X mod Y
|
||||
; nonvar(Y), nonvar(Z) ->
|
||||
( Y > 0 -> Z >= 0, Z < Y
|
||||
; Y < 0 -> Z =< 0, Z > Y
|
||||
; Z =< 0, Z > Y % Y < 0
|
||||
),
|
||||
( { fd_get(X, _, n(XL), _, _) } ->
|
||||
( (XL - Z) mod Y =\= 0 ->
|
||||
@@ -5082,11 +5088,21 @@ run_propagator(pmod(X,Y,Z), MState) -->
|
||||
; nonvar(Z), nonvar(X) ->
|
||||
( Z > 0 ->
|
||||
( X < 0 -> true
|
||||
; X >= Z
|
||||
; X >= Z,
|
||||
% due to X = Z+Y*_ and Y > Z
|
||||
( X-Z > 0 ->
|
||||
X-Z > Z
|
||||
; true
|
||||
)
|
||||
)
|
||||
; Z < 0 ->
|
||||
( X > 0 -> true
|
||||
; X =< Z
|
||||
; X =< Z,
|
||||
% due to X = Z+Y*_ and Y < Z
|
||||
( X-Z < 0 ->
|
||||
X-Z < Z
|
||||
; true
|
||||
)
|
||||
)
|
||||
; Z =:= 0 % Multiple solutions so do nothing special.
|
||||
),
|
||||
@@ -5094,7 +5110,23 @@ run_propagator(pmod(X,Y,Z), MState) -->
|
||||
YU < X, X =< 0 } -> kill(MState), Z =:= X
|
||||
; { fd_get(Y, _, n(YL), _, _),
|
||||
YL > X, X >= 0 } -> kill(MState), Z =:= X
|
||||
; ( Z > 0 ->
|
||||
; ( Z > 0, X < 0 ->
|
||||
{ fd_get(Y, YD, YPs),
|
||||
YMin is Z+1,
|
||||
YMax is Z-X,
|
||||
domain_remove_smaller_than(YD, YMin, YD1),
|
||||
domain_remove_greater_than(YD1, YMax, YD2) },
|
||||
fd_put(Y, YD2, YPs)
|
||||
% queue_goal((Y #> Z, Y #=< Z-X))
|
||||
; Z < 0, X > 0 ->
|
||||
{ fd_get(Y, YD, YPs),
|
||||
YMax is Z-1,
|
||||
YMin is Z-X,
|
||||
domain_remove_greater_than(YD, YMax, YD1),
|
||||
domain_remove_smaller_than(YD1, YMin, YD2) },
|
||||
fd_put(Y, YD2, YPs)
|
||||
% queue_goal((Y #< Z, Y #>= Z-X))
|
||||
; Z > 0 ->
|
||||
{ fd_get(Y, YD, YPs),
|
||||
YMin is Z + 1,
|
||||
domain_remove_smaller_than(YD, YMin, YD1) },
|
||||
@@ -5106,7 +5138,16 @@ run_propagator(pmod(X,Y,Z), MState) -->
|
||||
domain_remove_greater_than(YD, YMax, YD1) },
|
||||
fd_put(Y, YD1, YPs)
|
||||
% queue_goal(Y #< Z)
|
||||
; true
|
||||
; Z =:= 0,
|
||||
( X =:= 0 ->
|
||||
kill(MState) % trivial
|
||||
; % only 4 solutions {-abs(X),-1,1,abs(X)}
|
||||
{ YL is -abs(X), YU is abs(X),
|
||||
fd_get(Y, YD0, YPs),
|
||||
domain_remove_smaller_than(YD0, YL, YD1),
|
||||
domain_remove_greater_than(YD1, YU, YD) },
|
||||
fd_put(Y, YD, YPs)
|
||||
)
|
||||
)
|
||||
)
|
||||
; run_propagator(pmodz(X,Y,Z), MState),
|
||||
@@ -5127,7 +5168,7 @@ run_propagator(pmodz(X,Y,Z), MState) -->
|
||||
fd_put(Z, ZD2, ZPs)
|
||||
% queue_goal(Z #=< X)
|
||||
)
|
||||
; X < 0 ->
|
||||
; X < 0,
|
||||
( { fd_get(Y, _, _, n(YU), _), YU < X } ->
|
||||
kill(MState),
|
||||
queue_goal(Z = X)
|
||||
@@ -5167,7 +5208,7 @@ run_propagator(pmodz(X,Y,Z), MState) -->
|
||||
fd_put(Z, ZD5, ZPs)
|
||||
% queue_goal(Z in ZMin..0)
|
||||
)
|
||||
; Y > 0 ->
|
||||
; Y > 0,
|
||||
( { fd_get(X, _, n(XL), n(XU), _), XL >= 0, Y > XU } ->
|
||||
kill(MState),
|
||||
queue_goal(Z = X)
|
||||
@@ -5378,8 +5419,9 @@ run_propagator(pmax(X,Y,Z), MState) -->
|
||||
; nonvar(Z) ->
|
||||
( Z =:= X -> kill(MState), queue_goal(X #>= Y)
|
||||
; Z > X -> queue_goal(Z = Y)
|
||||
; { false } % Z < X
|
||||
; false % Z < X
|
||||
)
|
||||
; Y == Z -> kill(MState), queue_goal(Y #>= X)
|
||||
; { fd_get(Y, _, YInf, YSup, _) },
|
||||
( { YInf cis_gt n(X) } -> queue_goal(Z = Y)
|
||||
; { YSup cis_lt n(X) } -> queue_goal(Z = X)
|
||||
@@ -5394,7 +5436,7 @@ run_propagator(pmax(X,Y,Z), MState) -->
|
||||
; { fd_get(Z, ZD, ZPs) } ->
|
||||
{ fd_get(X, _, XInf, XSup, _),
|
||||
fd_get(Y, _, YInf, YSup, _) },
|
||||
( { YInf cis_gt YSup } -> kill(MState), queue_goal(Z = Y)
|
||||
( { YInf cis_gt XSup } -> kill(MState), queue_goal(Z = Y)
|
||||
; { YSup cis_lt XInf } -> kill(MState), queue_goal(Z = X)
|
||||
; { n(M) cis max(XSup, YSup) } ->
|
||||
{ domain_remove_greater_than(ZD, M, ZD1) },
|
||||
@@ -5413,8 +5455,9 @@ run_propagator(pmin(X,Y,Z), MState) -->
|
||||
; nonvar(Z) ->
|
||||
( Z =:= X -> kill(MState), { X #=< Y }
|
||||
; Z < X -> Z = Y
|
||||
; { false } % Z > X
|
||||
; false % Z > X
|
||||
)
|
||||
; Y == Z -> kill(MState), queue_goal(Y #=< X)
|
||||
; { fd_get(Y, _, YInf, YSup, _) },
|
||||
( { YSup cis_lt n(X) } -> Z = Y
|
||||
; { YInf cis_gt n(X) } -> Z = X
|
||||
@@ -5429,7 +5472,7 @@ run_propagator(pmin(X,Y,Z), MState) -->
|
||||
; { fd_get(Z, ZD, ZPs) } ->
|
||||
{ fd_get(X, _, XInf, XSup, _),
|
||||
fd_get(Y, _, YInf, YSup, _) },
|
||||
( { YSup cis_lt YInf } -> kill(MState), Z = Y
|
||||
( { YSup cis_lt XInf } -> kill(MState), Z = Y
|
||||
; { YInf cis_gt XSup } -> kill(MState), Z = X
|
||||
; { n(M) cis min(XInf, YInf) } ->
|
||||
{ domain_remove_smaller_than(ZD, M, ZD1) },
|
||||
@@ -5448,6 +5491,7 @@ run_propagator(pexp(X,Y,Z,Morph), MState) -->
|
||||
morph_into_propagator(MState, [Y,Z], reified_eq(1,Y,1,0,[],Z), Morph)
|
||||
; Y == 0 -> kill(MState), Z = 1
|
||||
; Y == 1 -> kill(MState), Z = X
|
||||
; Y == Z -> kill(MState), X = Y, queue_goal(X in -1\/1)
|
||||
; nonvar(X) ->
|
||||
( nonvar(Y) ->
|
||||
( Y >= 0 -> true ; X =:= -1 ),
|
||||
@@ -5536,7 +5580,7 @@ run_propagator(pexp(X,Y,Z,Morph), MState) -->
|
||||
fd_put(Z, ZD2, ZPs),
|
||||
{ ( even(Y), ZU = n(Num) ->
|
||||
integer_kth_root_leq(Num, Y, RU),
|
||||
( XL cis_geq n(0), ZL = n(Num1) ->
|
||||
( XL cis_geq n(0), ZL = n(Num1), Num1 >= 0 ->
|
||||
integer_kth_root_leq(Num1, Y, RL0),
|
||||
( RL0^Y < Num1 -> RL is RL0 + 1
|
||||
; RL = RL0
|
||||
@@ -5726,8 +5770,7 @@ run_propagator(reified_fd(V,B), MState) -->
|
||||
B = 1
|
||||
; { B == 0 } ->
|
||||
( { fd_inf(V, inf) } -> []
|
||||
; { fd_sup(V, sup) } -> []
|
||||
; { false }
|
||||
; { fd_sup(V, sup) }
|
||||
)
|
||||
; []
|
||||
).
|
||||
@@ -6130,7 +6173,7 @@ difference_arcs([V|Vs], FL0) -->
|
||||
|
||||
writeln(T) :- write(T), nl.
|
||||
|
||||
:- meta_predicate must_succeed(0).
|
||||
:- meta_predicate(must_succeed(0)).
|
||||
|
||||
must_succeed(G) :-
|
||||
( G -> true
|
||||
@@ -6254,7 +6297,7 @@ put_free(F) :- put_attr(F, free, true).
|
||||
|
||||
free_node(F) :- get_attr(F, free, true).
|
||||
|
||||
:- meta_predicate with_local_attributes(?, 0, ?).
|
||||
:- meta_predicate(with_local_attributes(?, 0, ?)).
|
||||
|
||||
:- dynamic(nat_copy/1).
|
||||
|
||||
@@ -6788,13 +6831,20 @@ gcc_pairs([Key-Num0|KNs], Vs, [Key-Num|Rest]) :-
|
||||
Constraint", AAAI-96 Portland, OR, USA, pp 209--215, 1996
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
gcc_global(Vs, KNs) :-
|
||||
gcc_check(KNs),
|
||||
% previously: call do_queue/0 (now a NOP) here to reach a
|
||||
% fix-point: all elements of clpz_gcc_vs must be variables. We
|
||||
% must ensure this holds if gcc_check/1 is later rewritten to
|
||||
% actually disable the queue.
|
||||
with_local_attributes(Vs,
|
||||
gcc_global(Vs, KNs) -->
|
||||
% at this point, all elements of clpz_gcc_vs must be
|
||||
% variables, which a previously scheduled and called
|
||||
% gcc_check//1 ensures. Note that gcc_check//1 disables the
|
||||
% queue and accumulates constraints in the queue. Do we need
|
||||
% to insert a call of do_queue//0 here to reach a fixpoint? I
|
||||
% think not, because verify_attributes/3 gives each variable
|
||||
% that is involved in a unification an opportunity to schedule
|
||||
% its propagators, even if the unifications happen
|
||||
% simultaneously (such as [A,B] = [0,1], which can happen in
|
||||
% the propagator of tuples_in/2). Hence: We need this only if
|
||||
% an example shows it, ideally found by a systematic search
|
||||
% that can be used to test the implementation.
|
||||
{ with_local_attributes(Vs,
|
||||
(gcc_arcs(KNs, S, Vals),
|
||||
variables_with_num_occurrences(Vs, VNs),
|
||||
maplist(target_to_v(T), VNs),
|
||||
@@ -6805,9 +6855,9 @@ gcc_global(Vs, KNs) :-
|
||||
gcc_consistent(T),
|
||||
scc(Vals, gcc_successors),
|
||||
phrase(gcc_goals(Vals), Gs)
|
||||
; Gs = [] )), Gs),
|
||||
; Gs = [] )), Gs) },
|
||||
disable_queue,
|
||||
maplist(call, Gs),
|
||||
neq_nums(Gs),
|
||||
enable_queue.
|
||||
|
||||
gcc_consistent(T) :-
|
||||
@@ -6834,7 +6884,7 @@ gcc_edge_goal(arc_to(_,_,V,F), Val) -->
|
||||
get_attr(Val, lowlink, L2),
|
||||
L1 =\= L2,
|
||||
get_attr(Val, value, Value) } ->
|
||||
[clpz:neq_num(V, Value)]
|
||||
[neq_num(V, Value)]
|
||||
; []
|
||||
).
|
||||
|
||||
@@ -7004,7 +7054,7 @@ gcc_succ_edge(arc_from(_,_,V,F)) -->
|
||||
consistency.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
gcc_check(Pairs) :-
|
||||
gcc_check(Pairs) -->
|
||||
disable_queue,
|
||||
gcc_check_(Pairs),
|
||||
enable_queue.
|
||||
@@ -7014,36 +7064,36 @@ gcc_done(Num) :-
|
||||
del_attr(Num, clpz_gcc_num),
|
||||
del_attr(Num, clpz_gcc_occurred).
|
||||
|
||||
gcc_check_([]).
|
||||
gcc_check_([Key-Num0|KNs]) :-
|
||||
( get_attr(Num0, clpz_gcc_vs, Vs) ->
|
||||
get_attr(Num0, clpz_gcc_num, Num),
|
||||
get_attr(Num0, clpz_gcc_occurred, Occ0),
|
||||
vs_key_min_others(Vs, Key, 0, Min, Os),
|
||||
put_attr(Num0, clpz_gcc_vs, Os),
|
||||
put_attr(Num0, clpz_gcc_occurred, Occ1),
|
||||
Occ1 is Occ0 + Min,
|
||||
gcc_check_([]) --> [].
|
||||
gcc_check_([Key-Num0|KNs]) -->
|
||||
( { get_attr(Num0, clpz_gcc_vs, Vs) } ->
|
||||
{ get_attr(Num0, clpz_gcc_num, Num),
|
||||
get_attr(Num0, clpz_gcc_occurred, Occ0),
|
||||
vs_key_min_others(Vs, Key, 0, Min, Os),
|
||||
put_attr(Num0, clpz_gcc_vs, Os),
|
||||
put_attr(Num0, clpz_gcc_occurred, Occ1),
|
||||
Occ1 is Occ0 + Min },
|
||||
geq(Num, Occ1),
|
||||
% The queue is disabled for efficiency here in any case.
|
||||
% If it were enabled, make sure to retain the invariant
|
||||
% that gcc_global is never triggered during an
|
||||
% inconsistent state (after gcc_done/1 but before all
|
||||
% relevant constraints are posted).
|
||||
( Occ1 == Num -> all_neq(Os, Key), gcc_done(Num0)
|
||||
; Os == [] -> gcc_done(Num0), Num = Occ1
|
||||
; length(Os, L),
|
||||
Max is Occ1 + L,
|
||||
( Occ1 == Num -> all_neq(Os, Key), { gcc_done(Num0) }
|
||||
; Os == [] -> { gcc_done(Num0) }, Num = Occ1
|
||||
; { length(Os, L),
|
||||
Max is Occ1 + L },
|
||||
geq(Max, Num),
|
||||
( nonvar(Num) -> Diff is Num - Occ1
|
||||
; fd_get(Num, ND, _),
|
||||
domain_infimum(ND, n(NInf)),
|
||||
( { nonvar(Num) } -> Diff is Num - Occ1
|
||||
; { fd_get(Num, ND, _),
|
||||
domain_infimum(ND, n(NInf)) },
|
||||
Diff is NInf - Occ1
|
||||
),
|
||||
L >= Diff,
|
||||
( L =:= Diff ->
|
||||
Num is Occ1 + Diff,
|
||||
maplist(=(Key), Os),
|
||||
gcc_done(Num0)
|
||||
{ maplist(=(Key), Os),
|
||||
gcc_done(Num0) }
|
||||
; true
|
||||
)
|
||||
)
|
||||
@@ -7967,13 +8017,13 @@ coeff_var_term(C-V, T) :- ( C =:= 1 -> T = #V ; T = C * #V ).
|
||||
Reified predicates for use with predicates from library(reif).
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
#=(X, Y, T) :-
|
||||
X #= Y #<==> B,
|
||||
clpz_t(Expr, T) :-
|
||||
Expr #<==> #B,
|
||||
zo_t(B, T).
|
||||
|
||||
#<(X, Y, T) :-
|
||||
X #< Y #<==> B,
|
||||
zo_t(B, T).
|
||||
#=(X, Y, T) :- clpz_t(X #= Y, T).
|
||||
|
||||
#<(X, Y, T) :- clpz_t(X #< Y, T).
|
||||
|
||||
zo_t(0, false).
|
||||
zo_t(1, true).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
:- module(cont, [reset/3, shift/1]).
|
||||
|
||||
:- meta_predicate reset(0, ?, ?).
|
||||
:- meta_predicate(reset(0, ?, ?)).
|
||||
|
||||
reset(Goal, Ball, Cont) :-
|
||||
call(Goal),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020-2023 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2025 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
/** Predicates for cryptographic applications.
|
||||
@@ -10,10 +10,6 @@
|
||||
|
||||
Especially for cryptographic applications, it is an advantage that
|
||||
using strings leaves little trace of what was processed in the system.
|
||||
|
||||
For predicates that accept an `encoding/1` option to specify the encoding
|
||||
of the input data, if `encoding(octet)` is used, then the input can also
|
||||
be specified as a list of _bytes_, i.e., integers between 0 and 255.
|
||||
*/
|
||||
|
||||
:- module(crypto,
|
||||
@@ -25,6 +21,7 @@
|
||||
crypto_password_hash/3, % +Password, -Hash, +Options
|
||||
crypto_data_encrypt/6, % +PlainText, +Algorithm, +Key, +IV, -CipherText, +Options
|
||||
crypto_data_decrypt/6, % +CipherText, +Algorithm, +Key, +IV, -PlainText, +Options
|
||||
ed25519_seed_keypair/2, % +Seed, -KeyPair
|
||||
ed25519_new_keypair/1, % -KeyPair
|
||||
ed25519_keypair_public_key/2, % +KeyPair, +PublicKey
|
||||
ed25519_sign/4, % +KeyPair, +Data, -Signature, +Options
|
||||
@@ -90,9 +87,36 @@ bytes_hex([B|Bs]) --> [C0,C1],
|
||||
},
|
||||
bytes_hex(Bs).
|
||||
|
||||
char_hexval(C, H) :-
|
||||
integer(H),
|
||||
!,
|
||||
hexval_char(H, C).
|
||||
char_hexval(C, H) :- nth0(H, "0123456789abcdef", C), !.
|
||||
char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !.
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
We specialize char_hexval/2 for use if the value is given,
|
||||
so that it works in constant time in this case.
|
||||
|
||||
The security of HMAC verification depends on this property.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
hexval_char(0, '0').
|
||||
hexval_char(1, '1').
|
||||
hexval_char(2, '2').
|
||||
hexval_char(3, '3').
|
||||
hexval_char(4, '4').
|
||||
hexval_char(5, '5').
|
||||
hexval_char(6, '6').
|
||||
hexval_char(7, '7').
|
||||
hexval_char(8, '8').
|
||||
hexval_char(9, '9').
|
||||
hexval_char(0xa, a).
|
||||
hexval_char(0xb, b).
|
||||
hexval_char(0xc, c).
|
||||
hexval_char(0xd, d).
|
||||
hexval_char(0xe, e).
|
||||
hexval_char(0xf, f).
|
||||
|
||||
must_be_bytes(Bytes, Context) :-
|
||||
must_be(list, Bytes),
|
||||
@@ -189,7 +213,19 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B).
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% The default encoding is `utf8`. The alternative is `octet`, to
|
||||
% treat the input as a list of raw bytes.
|
||||
% use the character code of each character in Data as a byte
|
||||
% value.
|
||||
%
|
||||
% - `hmac(+Key)`
|
||||
% Compute a hash-based message authentication code (HMAC) using
|
||||
% Key, a list of bytes. This option is currently supported for
|
||||
% algorithms `sha256`, `sha384` and `sha512`. If `Hash` is
|
||||
% instantiated, then it is compared with the computed HMAC
|
||||
% in such a way that no information about the expected HMAC
|
||||
% is revealed, using a comparison of strings that always takes
|
||||
% the same time independent of whether and where the strings
|
||||
% differ. This option can therefore also be used to safely
|
||||
% _verify_ a given HMAC.
|
||||
%
|
||||
% Example:
|
||||
%
|
||||
@@ -213,8 +249,33 @@ crypto_data_hash(Data0, Hash, Options0) :-
|
||||
( hash_algorithm(A) -> true
|
||||
; domain_error(hash_algorithm, A, crypto_data_hash/3)
|
||||
),
|
||||
'$crypto_data_hash'(Data, Encoding, HashBytes, A),
|
||||
hex_bytes(Hash, HashBytes).
|
||||
( member(HMAC, Options0), nonvar(HMAC), HMAC = hmac(Ks) ->
|
||||
must_be_bytes(Ks, crypto_data_hash/3),
|
||||
hmac_algorithm(A),
|
||||
'$crypto_hmac'(Data, Encoding, Ks, HashBytes, A),
|
||||
( var(Hash) ->
|
||||
hex_bytes(Hash, HashBytes)
|
||||
; must_be(chars, Hash),
|
||||
hex_bytes(HashMAC, HashBytes),
|
||||
chars_equal_constant_time(Hash, HashMAC)
|
||||
)
|
||||
; '$crypto_data_hash'(Data, Encoding, HashBytes, A),
|
||||
hex_bytes(Hash, HashBytes)
|
||||
).
|
||||
|
||||
chars_equal_constant_time(As, Bs) :-
|
||||
maplist(chars_xor, As, Bs, Xs),
|
||||
sum_list(Xs, Sum),
|
||||
Sum =:= 0.
|
||||
|
||||
chars_xor(A, B, Xor) :-
|
||||
char_code(A, CA),
|
||||
char_code(B, CB),
|
||||
Xor is xor(CA,CB).
|
||||
|
||||
hmac_algorithm(sha256).
|
||||
hmac_algorithm(sha384).
|
||||
hmac_algorithm(sha512).
|
||||
|
||||
options_data_chars(Options, Data, Chars, Encoding) :-
|
||||
option(encoding(Encoding), Options, utf8),
|
||||
@@ -270,7 +331,8 @@ hash_algorithm(blake2b512).
|
||||
% default is all zeroes.
|
||||
% - `encoding(+Encoding)`
|
||||
% The default encoding is `utf8`. The alternative is `octet`,
|
||||
% to treat the input as a list of raw bytes.
|
||||
% to use the character code of each character in Data as a byte
|
||||
% value.
|
||||
%
|
||||
% The `info/1` option can be used to generate multiple keys from a
|
||||
% single master key, using for example values such as "key" and
|
||||
@@ -377,7 +439,7 @@ dollar_segments(Ls, Segments) :-
|
||||
% storing passwords, store these hashes. Later, you can verify the
|
||||
% validity of a password with `crypto_password_hash/2`, comparing the
|
||||
% then entered password to the stored hash. If you need to export this
|
||||
% atom, you should treat it as opaque ASCII data with up to 255 bytes
|
||||
% string, you should treat it as opaque ASCII data with up to 255 bytes
|
||||
% of length. The maximal length may increase in the future.
|
||||
%
|
||||
% Admissible options are:
|
||||
@@ -476,8 +538,9 @@ bytes_base64(Bytes, Base64) :-
|
||||
% Options:
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% Encoding to use for PlainText. Default is utf8. The alternative
|
||||
% is octet to treat PlainText as raw bytes.
|
||||
% Encoding to use for PlainText. The default is `utf8`. The
|
||||
% alternative is `octet`, to use the character code of each
|
||||
% character in PlainText as a byte value.
|
||||
%
|
||||
% - `tag(-List)`
|
||||
% For authenticated encryption schemes, List is unified with a
|
||||
@@ -563,8 +626,9 @@ algorithm_key_iv('chacha20-poly1305', Key, IV) :-
|
||||
% Options is a list of:
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% Encoding to use for PlainText. The default is utf8. The
|
||||
% alternative is octet, which is used if the data are raw bytes.
|
||||
% Encoding to use for PlainText. The default is `utf8`. The
|
||||
% alternative is `octet`, to obtain a list of characters where each
|
||||
% character code corresponds to a decrypted octet of CipherText.
|
||||
%
|
||||
% - `tag(+Tag)`
|
||||
% For authenticated encryption schemes, the tag must be specified as
|
||||
@@ -600,6 +664,8 @@ crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :-
|
||||
encoding_chars(octet, Bs, Cs) :-
|
||||
must_be(list, Bs),
|
||||
( maplist(integer, Bs) ->
|
||||
% the ability to use integers is deprecated and a
|
||||
% candidate for removal in the future!
|
||||
maplist(char_code, Cs, Bs)
|
||||
; Bs = Cs
|
||||
),
|
||||
@@ -612,6 +678,50 @@ encoding_chars(utf8, Cs, Cs) :-
|
||||
===============================
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% ed25519_seed_keypair(+Seed, -Pair)
|
||||
%
|
||||
% Use Seed to deterministically generate an Ed25519 key pair Pair, a
|
||||
% list of characters. Seed must be a list of 32 bytes. It can be
|
||||
% chosen at random (using for example `crypto_n_random_bytes/2`) or
|
||||
% derived from input keying material (IKM) using for example
|
||||
% `crypto_data_hkdf/4`. The pair contains the private key and must be
|
||||
% kept absolutely secret. Pair can be used for signing. Its public
|
||||
% key can be obtained with `ed25519_keypair_public_key/2`.
|
||||
|
||||
ed25519_seed_keypair(Seed, Pair) :-
|
||||
must_be_bytes(Seed, ed25519_keypair_from_seed/2),
|
||||
length(Seed, 32),
|
||||
'$ed25519_seed_to_public_key'(Seed, Public),
|
||||
maplist(char_code, Public, PublicBytes),
|
||||
phrase(ed25519_PKCS8v2(Seed,PublicBytes), DERs),
|
||||
maplist(char_code, Pair, DERs).
|
||||
|
||||
% DER (and hence BER) encoding of an Ed25519 private key and
|
||||
% corresponding public key in PKCS#8v2 format (RFC 5958) as specified
|
||||
% in RFC 8410.
|
||||
|
||||
ed25519_PKCS8v2(Seed, PublicBytes) -->
|
||||
[0x30,81], % a SEQUENCE of 81 bytes follows
|
||||
|
||||
% the publicKey is present, hence we set version to v2
|
||||
[2,1,1], % the integer 1 denoting version 2 (awesome design!)
|
||||
|
||||
% privateKeyAlgorithm: SEQUENCE
|
||||
[0x30,5], % a SEQUENCE of 5 bytes follows
|
||||
[6,3], % an OBJECT IDENTIFIER of 3 bytes follows
|
||||
[43,101,112], % OID of Ed25519
|
||||
|
||||
% privateKey: OCTET STRING
|
||||
[4,34], % an OCTET STRING of 34 bytes follows
|
||||
[4,32], % an OCTET STRING of 32 bytes follows
|
||||
seq(Seed), % the seed is the private key
|
||||
|
||||
% publicKey: [1] IMPLICIT BIT STRING; context-specific, hence bit 7 set
|
||||
[0b10000001], % the public key follows
|
||||
[33], % a BIT STRING of length 33 follows
|
||||
[0], % 32 bytes is divisible by 8, hence 0 unused bits
|
||||
seq(PublicBytes).
|
||||
|
||||
%% ed25519_new_keypair(-Pair)
|
||||
%
|
||||
% Yields a new Ed25519 key pair Pair, a list of characters. The
|
||||
@@ -620,7 +730,8 @@ encoding_chars(utf8, Cs, Cs) :-
|
||||
% with `ed25519_keypair_public_key/2`.
|
||||
|
||||
ed25519_new_keypair(Pair) :-
|
||||
'$ed25519_new_keypair'(Pair).
|
||||
crypto_n_random_bytes(32, Bytes),
|
||||
ed25519_seed_keypair(Bytes, Pair).
|
||||
|
||||
%% ed25519_keypair_public_key(+Pair, -PublicKey)
|
||||
%
|
||||
@@ -629,8 +740,11 @@ ed25519_new_keypair(Pair) :-
|
||||
% The public key is represented as a list of characters.
|
||||
|
||||
ed25519_keypair_public_key(Pair, PublicKey) :-
|
||||
must_be_octet_chars(Pair, ed25519_keypair_public_key),
|
||||
'$ed25519_keypair_public_key'(Pair, PublicKey).
|
||||
must_be_octet_chars(Pair, ed25519_keypair_public_key/2),
|
||||
reverse(Pair, RPs),
|
||||
length(RPublicKey, 32),
|
||||
phrase((seq(RPublicKey),...), RPs),
|
||||
reverse(RPublicKey, PublicKey).
|
||||
|
||||
%% ed25519_sign(+Key, +Data, -Signature, +Options)
|
||||
%
|
||||
@@ -638,10 +752,154 @@ ed25519_keypair_public_key(Pair, PublicKey) :-
|
||||
% PKCS#8 v2 format as generated by `ed25519_new_keypair/1`. Sign Data
|
||||
% with Key, yielding Signature as a list of hexadecimal characters.
|
||||
|
||||
ed25519_sign(Key, Data0, Signature, Options) :-
|
||||
must_be_octet_chars(Key, ed25519_sign),
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Side-channel attacks on Ed25519 predicates
|
||||
==========================================
|
||||
|
||||
Ed25519 predicates where the private key occurs as part of the
|
||||
arguments are potentially subject to side-channel attacks, since
|
||||
key pairs are represented as strings in the context of Ed25519.
|
||||
|
||||
The compact string representation used by Scryer Prolog means that
|
||||
different characters may occupy different numbers of bytes: Due
|
||||
to UTF-8 encoding, characters with codes 1..127 occupy exactly 1 byte,
|
||||
characters with codes 128..2047 occupy exactly 2 bytes, and '0'
|
||||
is represented as a list element occupying an entire cell and
|
||||
dedicated list constructor in addition to string termination and
|
||||
possibly padding.
|
||||
|
||||
This difference is located at the level of the Rust engine. To
|
||||
Prolog code, any two characters look conceptually the same (i.e.,
|
||||
they are both atoms of length 1), and the internal difference in
|
||||
representation cannot be observed at all.
|
||||
|
||||
Very precise timing information or other measurements about
|
||||
operations that reason about such strings may yield information
|
||||
that is meant to stay secret. For example, if Ks is a secret key
|
||||
stored as a list of characters, then the time it takes to run
|
||||
phrase(..., Ks) may reveal the number of bytes in Ks that are 0 or
|
||||
greater than 127.
|
||||
|
||||
To test whether it is possible to detect such differences, I use
|
||||
exp(N) which succeeds exactly 2^N times:
|
||||
|
||||
exp(E) :-
|
||||
N is 2^E,
|
||||
between(1, N, _).
|
||||
|
||||
Here is an example query that uses partial_string/1 to traverse
|
||||
various strings consisting uniformly of characters with the same
|
||||
code, such as 0, 32, 255 and others, in the hope to detect
|
||||
differences in timing if only in such extreme cases:
|
||||
|
||||
?- length(Ls, 256),
|
||||
member(Code, [12,0,55,0,0,32,255,10,0,127,64]),
|
||||
portray_clause(byte=Code),
|
||||
maplist(=(Code), Ls),
|
||||
atom_codes(A, Ls),
|
||||
atom_chars(A, Cs),
|
||||
time((exp(21),partial_string(Cs),false)).
|
||||
%@ byte=12.
|
||||
%@ % CPU time: 1.537s, 14_680_107 inferences
|
||||
%@ byte=0.
|
||||
%@ % CPU time: 1.526s, 14_680_107 inferences
|
||||
%@ byte=55.
|
||||
%@ % CPU time: 1.534s, 14_680_107 inferences
|
||||
%@ byte=0.
|
||||
%@ % CPU time: 1.556s, 14_680_107 inferences
|
||||
%@ byte=0.
|
||||
%@ % CPU time: 1.520s, 14_680_107 inferences
|
||||
%@ byte=32.
|
||||
%@ % CPU time: 1.524s, 14_680_107 inferences
|
||||
%@ byte=255.
|
||||
%@ % CPU time: 1.522s, 14_680_107 inferences
|
||||
%@ byte=10.
|
||||
%@ % CPU time: 1.526s, 14_680_107 inferences
|
||||
%@ byte=0.
|
||||
%@ % CPU time: 1.522s, 14_680_107 inferences
|
||||
%@ byte=127.
|
||||
%@ % CPU time: 1.522s, 14_680_107 inferences
|
||||
%@ byte=64.
|
||||
%@ % CPU time: 1.517s, 14_680_107 inferences
|
||||
%@ false.
|
||||
|
||||
This shows that there is enough variety between runs that
|
||||
traversing a list with 256 elements that are all '\x0\' may even,
|
||||
and unexpectedly, be faster than traversing a list consisting
|
||||
entirely of characters with character code 32, which in turn may be
|
||||
slower than processing a list with 256 characters that all have
|
||||
code 255 and thus occupy twice as much space. This holds even over
|
||||
millions of runs. Reasons for such variety can include CPU power
|
||||
saving mechanisms, dynamic optimizations, prefetching heuristics,
|
||||
branch prediction algorithms, varying system loads etc.
|
||||
|
||||
This gives rise to the suspicion that any such timing differences
|
||||
would be extremely hard to exploit, at least on the architecture I
|
||||
tested it on, also since partial_string/1 is a very low-level
|
||||
operation and any actual processing (using phrase/2 etc.) would
|
||||
introduce additional overheads that in all likelihood far outweigh
|
||||
any differences that can be measured with partial_string/1.
|
||||
|
||||
Any resulting differences in timing and resource use, if they are
|
||||
measurable at all in any way, can at most reveal one bit per byte.
|
||||
Note also that Ed25519 private keys are chosen randomly, and hence
|
||||
half of their bytes are expected to be in 128..255. The predicates
|
||||
remain completely safe to use in all scenarios where no information
|
||||
about the private key can be gathered by unauthorized parties.
|
||||
|
||||
Still, the concern remains: We know that different keys may occupy
|
||||
different numbers of bytes in the internal compact representation
|
||||
of strings used by Scryer Prolog, and it may be possible to exploit
|
||||
these differences to obtain information that is meant to be kept
|
||||
secret. We must therefore keep an eye on this issue. For example,
|
||||
it may become a concern on very slow devices such as ID-cards where
|
||||
Scryer Prolog may be deployed in the future and where such timing
|
||||
differences may be detectable, or if Scryer Prolog itself becomes
|
||||
so fast that the relative overhead of such low-level operations
|
||||
becomes greater and thus more easily measurable.
|
||||
|
||||
Possible mitigations in such situations would be to:
|
||||
|
||||
1. use lists of integers to represent Ed25519 key pairs, resulting
|
||||
in a 24-fold space increase. This may be prohibitive in
|
||||
applications that manage a great number of keys. The Rust code
|
||||
would not be affected by this change, since it already operates
|
||||
on bytes. A Prolog application may implement this privately, and
|
||||
also encourage an API change of this library.
|
||||
2. introduce a compact internal representation for lists of bytes,
|
||||
which appear to Prolog programs as lists of characters.
|
||||
|
||||
(2) seems to be the better solution despite the implementation
|
||||
overhead: In addition to the improved security properties due to
|
||||
the elimination of side-channel attacks when reasoning about keys,
|
||||
all applications that reason about binary data would benefit from a
|
||||
more compact representation of binary data. Such an additional
|
||||
compact representation should only be attempted if the amount of
|
||||
Rust code it impacts is kept to the absolute minimum, certainly
|
||||
much smaller than what the compact string representation as it is
|
||||
currently implemented affects.
|
||||
|
||||
*No* solution would be to:
|
||||
|
||||
- eliminate the compact string representation from the engine and
|
||||
use plain lists of characters to represent Ed25519 key pairs,
|
||||
- continue to use lists of characters for Ed25519 key pairs,
|
||||
and ensure that they are never coalesced into compact strings
|
||||
(a future GC compaction step may need to be adapted for this)
|
||||
|
||||
This is because atom names are still represented in UTF-8 encoding,
|
||||
and are hence also susceptible to side-channel attacks due to their
|
||||
using different numbers of bytes for different codes.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
ed25519_sign(KeyPair, Data0, Signature, Options) :-
|
||||
must_be_octet_chars(KeyPair, ed25519_sign/4),
|
||||
length(Prefix, 16),
|
||||
length(PrivateKeyChars, 32),
|
||||
phrase((seq(Prefix),seq(PrivateKeyChars),...), KeyPair),
|
||||
maplist(char_code, PrivateKeyChars, PrivateKey),
|
||||
options_data_chars(Options, Data0, Data, Encoding),
|
||||
'$ed25519_sign'(Key, Data, Encoding, Signature0),
|
||||
'$ed25519_sign_raw'(PrivateKey, Data, Encoding, Signature0),
|
||||
hex_bytes(Signature, Signature0).
|
||||
|
||||
%% ed25519_verify(+Key, +Data, +Signature, +Options)
|
||||
@@ -655,13 +913,14 @@ ed25519_sign(Key, Data0, Signature, Options) :-
|
||||
%
|
||||
% - `encoding(+Encoding)`
|
||||
% The default encoding of Data is `utf8`. The alternative is `octet`,
|
||||
% which treats Data as a list of raw bytes.
|
||||
% to use the character code of each character in Data as a byte
|
||||
% value.
|
||||
|
||||
ed25519_verify(Key, Data0, Signature0, Options) :-
|
||||
must_be_octet_chars(Key, ed25519_verify),
|
||||
must_be_octet_chars(Key, ed25519_verify/4),
|
||||
options_data_chars(Options, Data0, Data, Encoding),
|
||||
hex_bytes(Signature0, Signature),
|
||||
'$ed25519_verify'(Key, Data, Encoding, Signature).
|
||||
'$ed25519_verify_raw'(Key, Data, Encoding, Signature).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
X25519: ECDH key exchange over Curve25519
|
||||
@@ -761,9 +1020,26 @@ curve_a(curve(_,_,A,_,_,_,_,_), A).
|
||||
curve_b(curve(_,_,_,B,_,_,_,_), B).
|
||||
curve_field_length(curve(_,_,_,_,_,_,FieldLength,_), FieldLength).
|
||||
|
||||
%% crypto_curve_generator(+Curve, -G)
|
||||
%
|
||||
% Yields the generator point G of Curve.
|
||||
|
||||
crypto_curve_generator(curve(_,_,_,_,G,_,_,_), G).
|
||||
|
||||
%% crypto_curve_order(+Curve, -Order)
|
||||
%
|
||||
% Yields the order of Curve.
|
||||
|
||||
crypto_curve_order(curve(_,_,_,_,_,Order,_,_), Order).
|
||||
|
||||
%% crypto_curve_scalar_mult(+Curve, +Scalar, +Point, -Result)
|
||||
%
|
||||
% Computes the point _Result = Scalar*Point_. Scalar must be an
|
||||
% integer, and Point must be a point on Curve. This operation can be
|
||||
% used to negotiate a shared secret over a public channel. Consider
|
||||
% using `curve25519_scalar_mult/3` instead for more desirable
|
||||
% security properties.
|
||||
|
||||
crypto_curve_scalar_mult(Curve, Scalar, point(X,Y), point(RX, RY)) :-
|
||||
must_be(integer, Scalar),
|
||||
must_be_on_curve(Curve, point(X,Y)),
|
||||
@@ -830,6 +1106,12 @@ fitting_exponent(N, E0, E) :-
|
||||
fitting_exponent(N, E1, E)
|
||||
).
|
||||
|
||||
%% crypto_name_curve(+Name, -Curve)
|
||||
%
|
||||
% Yields a representation of the elliptic curve with name Name.
|
||||
% Currently, the only supported name is `secp256k1`, a Koblitz curve
|
||||
% regarded as secure.
|
||||
|
||||
crypto_name_curve(secp256k1,
|
||||
curve(secp256k1,
|
||||
0x00fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
|
||||
|
||||
@@ -221,7 +221,7 @@ row([X | Y], Opt) -->
|
||||
!,
|
||||
( separator(Opt) ->
|
||||
row(Y, Opt)
|
||||
; end_token ->
|
||||
; end_token,
|
||||
{ Y = [] }).
|
||||
|
||||
|
||||
|
||||
@@ -12,9 +12,12 @@ to learn more about them.
|
||||
[op(1105, xfy, '|'),
|
||||
phrase/2,
|
||||
phrase/3,
|
||||
phrase//2,
|
||||
phrase//3,
|
||||
seq//1,
|
||||
seqq//1,
|
||||
... //0
|
||||
... //0,
|
||||
(-->)/2
|
||||
]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
@@ -22,9 +25,17 @@ to learn more about them.
|
||||
:- use_module(library(lists), [append/3, member/2]).
|
||||
:- use_module(library(loader), [strip_module/3]).
|
||||
|
||||
:- meta_predicate phrase(2, ?).
|
||||
:- meta_predicate(phrase(2, ?)).
|
||||
|
||||
:- meta_predicate phrase(2, ?, ?).
|
||||
:- meta_predicate(phrase(2, ?, ?)).
|
||||
|
||||
:- meta_predicate(phrase(3, ?, ?, ?)).
|
||||
|
||||
:- meta_predicate(phrase(4, ?, ?, ?, ?)).
|
||||
|
||||
:- meta_predicate(','(2, 2, ?, ?)).
|
||||
|
||||
:- meta_predicate(;(2, 2, ?, ?)).
|
||||
|
||||
%% phrase(+Body, ?Ls).
|
||||
%
|
||||
@@ -75,6 +86,34 @@ phrase(GRBody, S0, S) :-
|
||||
; call(M:GRBody1, S0, S)
|
||||
).
|
||||
|
||||
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),
|
||||
@@ -101,7 +140,10 @@ dcg_rule(( NonTerminal --> GRBody ), ( Head :- Body )) :-
|
||||
dcg_non_terminal(NonTerminal, S0, S, Goal) :-
|
||||
NonTerminal =.. NonTerminalUniv,
|
||||
append(NonTerminalUniv, [S0, S], GoalUniv),
|
||||
Goal =.. GoalUniv.
|
||||
( callable(NonTerminal) ->
|
||||
Goal =.. GoalUniv
|
||||
; Goal = NonTerminal % let call/N throw an error instead of throwing one here.
|
||||
).
|
||||
|
||||
dcg_terminals(Terminals, S0, S, S0 = List) :-
|
||||
append(Terminals, S, List).
|
||||
@@ -116,8 +158,6 @@ dcg_body(GRBody, S0, S, Body) :-
|
||||
dcg_body(NonTerminal, S0, S, Goal1) :-
|
||||
nonvar(NonTerminal),
|
||||
\+ dcg_constr(NonTerminal),
|
||||
NonTerminal \= ( _ -> _ ),
|
||||
NonTerminal \= ( \+ _ ),
|
||||
loader:strip_module(NonTerminal, M, NonTerminal0),
|
||||
dcg_non_terminal(NonTerminal0, S0, S, Goal0),
|
||||
( functor(NonTerminal, (:), 2) ->
|
||||
@@ -135,9 +175,13 @@ dcg_constr(( _'|'_ )). % 7.14.6 - alternative
|
||||
dcg_constr({_}). % 7.14.7
|
||||
dcg_constr(call(_)). % 7.14.8
|
||||
dcg_constr(phrase(_)). % 7.14.9
|
||||
dcg_constr(phrase(_,_)). % extension of 7.14.9
|
||||
dcg_constr(phrase(_,_,_)). % extension of 7.14.9
|
||||
dcg_constr(!). % 7.14.10
|
||||
%% dcg_constr(\+ _). % 7.14.11 - not (existence implementation dep.)
|
||||
dcg_constr((_->_)). % 7.14.12 - if-then (existence implementation dep.)
|
||||
dcg_constr(\+ G_0) :- % 7.14.11 - not (existence implementation def.)
|
||||
throw(error(representation_error(dcg_body), [culprit- (\+ G_0)])).
|
||||
dcg_constr((If->Then)) :- % 7.14.12 - if-then (existence implementation def.)
|
||||
throw(error(representation_error(dcg_body), [culprit- (If->Then)])).
|
||||
|
||||
% The principal functor of the first argument indicates
|
||||
% the construct to be expanded.
|
||||
@@ -162,16 +206,21 @@ dcg_cbody(( GREither '|' GROr ), S0, S, ( Either ; Or )) :-
|
||||
dcg_cbody({Goal}, S0, S, ( Goal, S0 = S )).
|
||||
dcg_cbody(call(Cont), S0, S, call(Cont, S0, S)).
|
||||
dcg_cbody(phrase(Body), S0, S, phrase(Body, S0, S)).
|
||||
dcg_cbody(phrase(Body, Arg), S0, S, phrase(Body, Arg, S0, S)).
|
||||
dcg_cbody(phrase(Body, Arg1, Arg2), S0, S, phrase(Body, Arg1, Arg2, S0, S)).
|
||||
dcg_cbody(!, S0, S, ( !, S0 = S )).
|
||||
dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )).
|
||||
% dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )).
|
||||
dcg_cbody(( GRIf -> GRThen ), S0, S, ( If -> Then )) :-
|
||||
dcg_body(GRIf, S0, S1, If),
|
||||
dcg_body(GRThen, S1, S, Then).
|
||||
|
||||
|
||||
% When DCG expansion throws an exception – remove offending term and rethrow.
|
||||
user:term_expansion(throw_dcg_expansion_error(E), _) :-
|
||||
throw(E).
|
||||
user:term_expansion(Term0, Term) :-
|
||||
nonvar(Term0),
|
||||
dcg_rule(Term0, Term).
|
||||
|
||||
catch(dcg_rule(Term0, Term), E, Term = throw_dcg_expansion_error(E)).
|
||||
|
||||
%% seq(Seq)//
|
||||
%
|
||||
@@ -200,8 +249,13 @@ seqq([Es|Ess]) --> seq(Es), seqq(Ess).
|
||||
Cs0 = Cs.
|
||||
... --> [] | [_], ... .
|
||||
|
||||
% defer instantiation errors until runtime. instantiations may be made
|
||||
% then.
|
||||
error_goal(error(instantiation_error, _Context), _).
|
||||
error_goal(error(E, must_be/2), error(E, must_be/2)).
|
||||
error_goal(error(E, (=..)/2), error(E, (=..)/2)).
|
||||
error_goal(error(representation_error(dcg_body), Context),
|
||||
error(representation_error(dcg_body), Context)).
|
||||
error_goal(E, _) :- throw(E).
|
||||
|
||||
user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
|
||||
@@ -211,9 +265,20 @@ user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
|
||||
E,
|
||||
dcgs:error_goal(E, GRBody1)
|
||||
),
|
||||
( GRBody = (_:_) ->
|
||||
( E = error(instantiation_error, _),
|
||||
GRBody0 = [T|Ts] ->
|
||||
GRBody2 = (error:must_be(list, [T|Ts]),
|
||||
lists:append([T|Ts], S0, S))
|
||||
; GRBody = (_:_) ->
|
||||
GRBody2 = M:GRBody1
|
||||
; GRBody2 = GRBody1
|
||||
).
|
||||
|
||||
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)).
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
|
||||
:- use_module(library(format), [portray_clause/1]).
|
||||
|
||||
:- meta_predicate *(0).
|
||||
:- meta_predicate $(0).
|
||||
:- meta_predicate $-(0).
|
||||
:- meta_predicate(*(0)).
|
||||
:- meta_predicate($(0)).
|
||||
:- meta_predicate($-(0)).
|
||||
|
||||
%% $-(Goal)
|
||||
%
|
||||
@@ -53,5 +53,4 @@ $(G_0) :-
|
||||
%
|
||||
% Generalize away Goal.
|
||||
|
||||
|
||||
*(_).
|
||||
|
||||
@@ -33,11 +33,13 @@ remove_goal([G0|G0s], Goal0, Goals) :-
|
||||
|
||||
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))
|
||||
( 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).
|
||||
|
||||
|
||||
131
src/lib/error.pl
131
src/lib/error.pl
@@ -1,44 +1,49 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2018-2023 by Markus Triska (triska@metalevel.at)
|
||||
Written 2018-2025 by Markus Triska (triska@metalevel.at)
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
:- module(error, [must_be/2,
|
||||
can_be/2,
|
||||
instantiation_error/0,
|
||||
domain_error/2,
|
||||
type_error/2,
|
||||
representation_error/1,
|
||||
resource_error/1,
|
||||
instantiation_error/1,
|
||||
domain_error/3,
|
||||
type_error/3
|
||||
type_error/3,
|
||||
call_with_error_context/2
|
||||
]).
|
||||
|
||||
|
||||
:- meta_predicate check_(1, ?, ?).
|
||||
:- meta_predicate(check_(1, ?, ?)).
|
||||
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
must_be(Type, Term)
|
||||
|
||||
This predicate is intended for type-checks of built-in predicates.
|
||||
|
||||
It asserts that Term is:
|
||||
|
||||
1) instantiated *and*
|
||||
2) instantiated to an instance of the given Type.
|
||||
|
||||
It corresponds to usage mode +Term.
|
||||
|
||||
Currently, the following types are supported:
|
||||
|
||||
- atom
|
||||
- boolean
|
||||
- character
|
||||
- chars
|
||||
- in_character
|
||||
- integer
|
||||
- list
|
||||
- octet_character
|
||||
- octet_chars
|
||||
- term
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
%% must_be(Type, Term)
|
||||
%
|
||||
% This predicate is intended for type-checks of built-in predicates.
|
||||
%
|
||||
% It asserts that Term is:
|
||||
%
|
||||
% 1) instantiated *and*
|
||||
% 2) instantiated to an instance of the given Type.
|
||||
%
|
||||
% It corresponds to usage mode +Term.
|
||||
%
|
||||
% Currently, the following types are supported:
|
||||
%
|
||||
% - atom
|
||||
% - boolean
|
||||
% - character
|
||||
% - chars
|
||||
% - in_character
|
||||
% - integer
|
||||
% - list
|
||||
% - octet_character
|
||||
% - octet_chars
|
||||
% - pair
|
||||
% - term
|
||||
|
||||
must_be(Type, Term) :-
|
||||
must_be_(type, Type),
|
||||
@@ -84,6 +89,7 @@ must_be_(octet_chars, Cs) :-
|
||||
must_be_(list, Term) :- check_(error:ilist, list, Term).
|
||||
must_be_(type, Term) :- check_(error:type, type, Term).
|
||||
must_be_(boolean, Term) :- check_(error:boolean, boolean, Term).
|
||||
must_be_(pair, Term) :- check_(error:pair, pair, Term).
|
||||
must_be_(term, Term) :-
|
||||
( acyclic_term(Term) ->
|
||||
( ground(Term) -> true
|
||||
@@ -106,6 +112,9 @@ check_(Pred, Type, Term) :-
|
||||
; type_error(Type, Term, must_be/2)
|
||||
).
|
||||
|
||||
|
||||
pair(_-_).
|
||||
|
||||
boolean(B) :- ( B == true ; B == false ).
|
||||
|
||||
character(C) :-
|
||||
@@ -141,20 +150,18 @@ type(var).
|
||||
type(boolean).
|
||||
type(term).
|
||||
type(not_less_than_zero).
|
||||
type(pair).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
can_be(Type, Term)
|
||||
|
||||
This predicate is intended for type-checks of built-in predicates.
|
||||
|
||||
It asserts that there is a substitution which, if applied to Term,
|
||||
makes it an instance of Type.
|
||||
|
||||
It corresponds to usage mode ?Term.
|
||||
|
||||
It supports the same types as must_be/2.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% can_be(Type, Term)
|
||||
%
|
||||
% This predicate is intended for type-checks of built-in predicates.
|
||||
%
|
||||
% It asserts that there is a substitution which, if applied to Term,
|
||||
% makes it an instance of Type.
|
||||
%
|
||||
% It corresponds to usage mode ?Term.
|
||||
%
|
||||
% It supports the same types as must_be/2.
|
||||
|
||||
can_be(Type, Term) :-
|
||||
must_be(type, Type),
|
||||
@@ -192,6 +199,7 @@ can_(octet_chars, Cs) :-
|
||||
).
|
||||
can_(list, Term) :- list_or_partial_list(Term).
|
||||
can_(boolean, Term) :- boolean(Term).
|
||||
can_(pair, Term) :- pair(Term).
|
||||
can_(term, Term) :-
|
||||
( acyclic_term(Term) ->
|
||||
true
|
||||
@@ -212,6 +220,30 @@ list_or_partial_list(Ls) :-
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Shorthands for throwing ISO errors.
|
||||
|
||||
The variants without context promote the use of
|
||||
call_with_error_context/2.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
instantiation_error :-
|
||||
throw(error(instantiation_error, [])).
|
||||
|
||||
domain_error(Type, Term) :-
|
||||
throw(error(domain_error(Type, Term), [])).
|
||||
|
||||
type_error(Type, Term) :-
|
||||
throw(error(type_error(Type, Term), [])).
|
||||
|
||||
representation_error(Flag) :-
|
||||
throw(error(representation_error(Flag), [])).
|
||||
|
||||
resource_error(Resource) :-
|
||||
throw(error(resource_error(Resource), [])).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
The variants *with* context would not have been needed if
|
||||
call_with_error_context/2 had been found earlier. In the future,
|
||||
we may be able to remove them.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
instantiation_error(Context) :-
|
||||
@@ -222,3 +254,20 @@ domain_error(Type, Term, Context) :-
|
||||
|
||||
type_error(Type, Term, Context) :-
|
||||
throw(error(type_error(Type, Term), Context)).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
call_with_error_context/2
|
||||
|
||||
See https://github.com/mthom/scryer-prolog/discussions/2839 .
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% call_with_error_context(+Goal, +Pair)
|
||||
%
|
||||
% Call _Goal_ with error context _Pair_.
|
||||
%
|
||||
% Examples of error contexts: `predicate-PI`, `file-Filename` etc.
|
||||
|
||||
:- meta_predicate(call_with_error_context(0,+)).
|
||||
call_with_error_context(G_0, Pair) :-
|
||||
must_be(pair, Pair),
|
||||
catch(G_0, error(E,Pairs), throw(error(E,[Pair|Pairs]))).
|
||||
|
||||
120
src/lib/ffi.pl
120
src/lib/ffi.pl
@@ -1,4 +1,4 @@
|
||||
:- module(ffi, [use_foreign_module/2, foreign_struct/2]).
|
||||
:- module(ffi, [use_foreign_module/2, foreign_struct/2, with_locals/2, allocate/4, deallocate/3, read_ptr/3, array_type/3]).
|
||||
|
||||
/** Foreign Function Interface
|
||||
|
||||
@@ -11,8 +11,8 @@ The main predicate is `use_foreign_module/2`. It takes a library name (which dep
|
||||
operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each
|
||||
function is defined by its name, a list of the type of the arguments, and the return argument.
|
||||
|
||||
Types available are: `sint8`, `uint8`, `sint16`, `uint16`, `sint32`, `uint32`, `sint64`,
|
||||
`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
||||
Types available are: `sint8`/`i8`, `uint8`/`u8`, `sint16`/`i16`, `uint16`/`u16`, `sint32`/`i32`, `uint32`/`u32`, `sint64`/`i64`,
|
||||
`uint64`/`u64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
||||
with `foreign_struct/2`.
|
||||
|
||||
After that, each function on the lists maps to a predicate created in the ffi module which
|
||||
@@ -27,6 +27,12 @@ ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return typ
|
||||
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
|
||||
```
|
||||
|
||||
## Notes regarding cstr
|
||||
|
||||
- When using `cstr` as an argument type the string will be deallocated once the function returns.
|
||||
- When using `cstr` as a return type the string will be copied and won't be deallocated.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
|
||||
@@ -46,6 +52,9 @@ And a new window should pop up!
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(iso_ext)).
|
||||
|
||||
%% foreign_struct(+Name, +Elements).
|
||||
%
|
||||
@@ -63,6 +72,21 @@ And a new window should pop up!
|
||||
foreign_struct(Name, Elements) :-
|
||||
'$define_foreign_struct'(Name, Elements).
|
||||
|
||||
|
||||
%% use_foreign_module(+LibName, +Predicates)
|
||||
%
|
||||
% - LibName the path to the shared library to load/bind
|
||||
% - Predicates list of function definitions
|
||||
%
|
||||
% Each function definition is a functor of arity 2.
|
||||
% The functor name is the name of the function to bind,
|
||||
% the first argument is the list of arguments of the function,
|
||||
% the second argument is the return type of the function.
|
||||
%
|
||||
% This will define a predicate in the ffi module with the defined name,
|
||||
% for void and bool return type functions the arity will match the length of the arguments list,
|
||||
% for other return types there will be an additional out parameter.
|
||||
%
|
||||
use_foreign_module(LibName, Predicates) :-
|
||||
'$load_foreign_lib'(LibName, Predicates),
|
||||
maplist(assert_predicate, Predicates).
|
||||
@@ -102,3 +126,93 @@ assert_predicate(PredicateDefinition) :-
|
||||
),
|
||||
Predicate = (Head:-Body),
|
||||
assertz(ffi:Predicate).
|
||||
|
||||
%% allocate(+Allocator, +Type, +Args, -Ptr)
|
||||
%
|
||||
% Using the Allocator allocate Type initialized with Args and
|
||||
% unify Ptr with a pointer to that allocation.
|
||||
%
|
||||
allocate(Allocator, Type, Args, Ptr) :-
|
||||
must_be(var, Ptr),
|
||||
must_be(atom, Type),
|
||||
must_be(atom, Allocator),
|
||||
'$ffi_allocate'(Allocator, Type, Args, Ptr).
|
||||
|
||||
|
||||
%% read_ptr(+Type, +Ptr, -Value)
|
||||
%
|
||||
% Read a value of Type from the pointer Ptr and unify the read value with Value
|
||||
%
|
||||
% For type cstr take read a nul-terminated utf-8 string starting at Ptr.
|
||||
%
|
||||
read_ptr(Type, Ptr, Value) :-
|
||||
must_be(atom, Type),
|
||||
must_be(integer, Ptr),
|
||||
'$ffi_read_ptr'(Type, Ptr, Value).
|
||||
|
||||
%% deallocate(+Allocator, +Type, +Ptr)
|
||||
%
|
||||
% Deallocate the allocation at Ptr of Type allocated with Allocator
|
||||
%
|
||||
deallocate(Allocator, Type, Ptr) :-
|
||||
must_be(atom, Allocator),
|
||||
must_be(integer, Ptr),
|
||||
'$ffi_deallocate'(Allocator, Type, Ptr).
|
||||
|
||||
:- dynamic(is_array_type_defined/1).
|
||||
|
||||
%% array_type(+ElemType, +Len, -ArrayType)
|
||||
%
|
||||
% unify the ffi type for an array of lenth Len with element type ElemType with ArrayType
|
||||
%
|
||||
array_type(ElemType, Len, ArrayType) :-
|
||||
(Len =< 0 -> domain_error(greater_than_zero, Len, array_type/3); true),
|
||||
phrase(format_("$[~a;~d]", [ElemType, Len]), ArrayTypeName),
|
||||
atom_chars(ArrayType, ArrayTypeName),
|
||||
(is_array_type_defined(ArrayType) -> true
|
||||
; length(Fields, Len),
|
||||
maplist(=(ElemType), Fields),
|
||||
foreign_struct(ArrayType, Fields),
|
||||
assertz(is_array_type_defined(ArrayType))
|
||||
).
|
||||
|
||||
:- meta_predicate(with_locals(?, 0)).
|
||||
|
||||
%% with_locals(+Locals, :Goal)
|
||||
%
|
||||
% Allocate the Locals, evaluate the Goal and deallocate the Locals.
|
||||
% The Locals will also be cleandup when Goal fails or throws an error.
|
||||
%
|
||||
% Locals is a list of local variable definitions let(-Ptr, +Type, +Args).
|
||||
% Ptr will be unified with the pointer to the local of Type initialized with Args.
|
||||
%
|
||||
with_locals(Locals, Goal) :-
|
||||
verify_locals(Locals),
|
||||
setup_call_cleanup(
|
||||
allocate_locals(Locals),
|
||||
Goal,
|
||||
deallocate_locals(Locals)
|
||||
).
|
||||
|
||||
verify_locals(Locals) :-
|
||||
must_be(list, Locals),
|
||||
( maplist(verify_local, Locals) -> true
|
||||
; domain_error(locals_decl_list, Locals, [verify_locals/1])
|
||||
).
|
||||
|
||||
verify_local(let(Var, Type, Init)) :-
|
||||
must_be(var, Var),
|
||||
must_be(atom, Type),
|
||||
ground(Init).
|
||||
|
||||
allocate_locals([]).
|
||||
allocate_locals([let(Var, Type, Init) | Ls]) :-
|
||||
allocate(rust, Type, Init , Var),
|
||||
(catch(allocate_locals(Ls), E, (deallocate_locals([let(Var, Type, Init)]), throw(E))) -> true
|
||||
; deallocate_locals([let(Var, Type, Init)]), false
|
||||
).
|
||||
|
||||
deallocate_locals([]).
|
||||
deallocate_locals([let(Var, Type, _) | Ls]) :-
|
||||
deallocate(rust, Type, Var),
|
||||
deallocate_locals(Ls).
|
||||
|
||||
@@ -18,7 +18,7 @@ _lists of characters_. This is an ideal representation:
|
||||
*/
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020, 2022 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2025 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
|
||||
Predicates for reasoning about files and directories.
|
||||
@@ -89,17 +89,17 @@ _lists of characters_. This is an ideal representation:
|
||||
|
||||
%% directory_files(+Directory, -Files).
|
||||
%
|
||||
% Returns the list of files *and* directories available at a specific
|
||||
% directory in the current system.
|
||||
% True if `Files` are the files *and* directories available at a specific
|
||||
% `Directory` in the current system.
|
||||
|
||||
directory_files(Directory, Files) :-
|
||||
must_be(chars, Directory),
|
||||
can_be(list, Files),
|
||||
'$directory_files'(Directory, Files).
|
||||
|
||||
%% file_size(+File, -Size).
|
||||
%% file_size(+File, ?Size).
|
||||
%
|
||||
% Returns the size (in bytes) of a file. The file must exist.
|
||||
% True iff `Size` is the size (in bytes) of `File`. The file must exist.
|
||||
|
||||
file_size(File, Size) :-
|
||||
file_must_exist(File, file_size/2),
|
||||
@@ -108,22 +108,25 @@ file_size(File, Size) :-
|
||||
|
||||
%% file_exists(+File).
|
||||
%
|
||||
% Succeeds if File is a file that exists in the current system.
|
||||
% True iff `File` is a file that exists in the current system.
|
||||
|
||||
file_exists(File) :-
|
||||
must_be(chars, File),
|
||||
'$file_exists'(File).
|
||||
|
||||
%% directory_exists(+Directory).
|
||||
%
|
||||
% Succeeds if Directory is a directory that exists in the current system.
|
||||
% True iff `Directory` is a directory that exists in the current system.
|
||||
|
||||
directory_exists(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
'$directory_exists'(Directory).
|
||||
|
||||
%% make_directory(+Directory).
|
||||
%
|
||||
% Succeeds if it creates a new directory named Directory in the current system.
|
||||
% Creates a new directory named `Directory`.
|
||||
% If you want to create a nested directory, use `make_directory_path/1`.
|
||||
|
||||
make_directory(Directory) :-
|
||||
must_be(chars, Directory),
|
||||
'$make_directory'(Directory).
|
||||
@@ -178,7 +181,7 @@ directory_must_exist(Directory, Context) :-
|
||||
; throw(error(existence_error(directory, Directory), Context))
|
||||
).
|
||||
|
||||
%% workind_directory(Dir0, Dir).
|
||||
%% working_directory(Dir0, Dir).
|
||||
%
|
||||
% Dir0 is the current working directory, and the working directory
|
||||
% is changed to Dir.
|
||||
@@ -216,25 +219,26 @@ path_canonical(Ps, Cs) :-
|
||||
For two time stamps A and B, if A precedes B, then A @< B holds.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
%% file_modification_time(+File, -T).
|
||||
%% file_modification_time(+File, ?T).
|
||||
%
|
||||
% For a file File that must exist, it returns a time stamp T with the modification time
|
||||
% For a file `File` that must exist, `T` is the modification time.
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_modification_time(File, T) :-
|
||||
file_time_(File, modification, T).
|
||||
|
||||
%% file_access_time(+File, -T).
|
||||
%% file_access_time(+File, ?T).
|
||||
%
|
||||
% For a file File that must exist, it returns a time stamp T with the access time
|
||||
% For a file `File` that must exist, `T` is the access time.
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
|
||||
file_access_time(File, T) :-
|
||||
file_time_(File, access, T).
|
||||
|
||||
%% file_creation_time(+File, -T).
|
||||
%% file_creation_time(+File, ?T).
|
||||
%
|
||||
% For a file File that must exist, it returns a time stamp T with the creation time
|
||||
% For a file `File` that must exist, `T` is the creation time.
|
||||
%
|
||||
% T is a time stamp compatible with `library(time)`.
|
||||
file_creation_time(File, T) :-
|
||||
@@ -246,7 +250,7 @@ file_time_(File, Which, T) :-
|
||||
read_from_chars(T0, T).
|
||||
|
||||
|
||||
%% path_segments(Ps, Segments).
|
||||
%% path_segments(?Ps, ?Segments).
|
||||
%
|
||||
% True iff Segments are the segments of Ps.
|
||||
%
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020-2023 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2025 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
I place this code in the public domain. Use it in any way you want.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
@@ -42,7 +42,6 @@
|
||||
% FormatString are used literally, except for the following tokens
|
||||
% with special meaning:
|
||||
%
|
||||
% | `~w` | use the next available argument from Arguments here |
|
||||
% | `~q` | use the next argument here, formatted as by `writeq/1` |
|
||||
% | `~a` | use the next argument here, which must be an atom |
|
||||
% | `~s` | use the next argument here, which must be a string |
|
||||
@@ -72,6 +71,7 @@
|
||||
% | `~Nn` | N newlines |
|
||||
% | `~i` | ignore the next argument |
|
||||
% | `~~` | the literal ~ |
|
||||
% | `~w` | format like `write/1` would; consider using `~q`, `~d`, etc. |
|
||||
%
|
||||
% Instead of `~N`, you can write `~*` to use the next argument from
|
||||
% Arguments as the numeric argument.
|
||||
@@ -84,12 +84,66 @@
|
||||
% ```
|
||||
|
||||
format_(Fs, Args) -->
|
||||
{ must_be(list, Fs),
|
||||
must_be(list, Args),
|
||||
unique_variable_names(Args, VNs),
|
||||
phrase(cells(Fs,Args,0,[],VNs), Cells) },
|
||||
{ format_args_cells(Fs, Args, Cells) },
|
||||
format_cells(Cells).
|
||||
|
||||
format_args_cells(Fs, Args, Cells) :-
|
||||
must_be(chars, Fs),
|
||||
must_be(list, Args),
|
||||
unique_variable_names(fabricated, Args, VNs),
|
||||
phrase(cells(Fs,Args,0,[],VNs), Cells).
|
||||
|
||||
unique_variable_names(Type, Term, VNs) :-
|
||||
term_variables(Term, Vs),
|
||||
foldl(var_name(Type), Vs, VNs, 0, _).
|
||||
|
||||
var_name(Type, V, Name=V, Num0, Num) :-
|
||||
charsio:fabricate_var_name(Type, Name, Num0),
|
||||
Num is Num0 + 1.
|
||||
|
||||
user:goal_expansion(format_(Fs,Args,Cs0,Cs),
|
||||
format:format_cells(Cells, Cs0, Cs)) :-
|
||||
catch(format_args_cells(Fs,Args,Cells),
|
||||
E,
|
||||
% no partial evaluation for uses of format_//2 that
|
||||
% cannot be compiled statically, for example those where
|
||||
% the argument list is a variable, or where ~*n occurs
|
||||
% in the format string, or a domain error occurs
|
||||
( ( E = error(instantiation_error,_)
|
||||
; E = error(domain_error(_,_), _)
|
||||
) ->
|
||||
false
|
||||
; throw(E)
|
||||
)).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Partial evaluation of goals involving conditions that can be
|
||||
checked at compilation time. This is especially useful for the
|
||||
common case of conditions that test a numeric argument against 0.
|
||||
It is currently used for the goals of ~d.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
goal_pe(G0, G) :- var(G0), !, G = G0.
|
||||
goal_pe((A0,B0), (A,B)) :- !, goal_pe(A0, A), goal_pe(B0, B).
|
||||
goal_pe((Body0 ; Else0), Body) :-
|
||||
nonvar(Body0),
|
||||
Body0 = ( If -> Then0 ),
|
||||
!,
|
||||
( ground(If) ->
|
||||
( If ->
|
||||
goal_pe(Then0, Body)
|
||||
; goal_pe(Else0, Body)
|
||||
)
|
||||
; goal_pe(Then0, Then),
|
||||
goal_pe(Else0, Else),
|
||||
Body = ( If -> Then ; Else )
|
||||
).
|
||||
goal_pe(Goal, Goal).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
format_cells//1 is an interpreter for cells, describing a string.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
format_cells([]) --> [].
|
||||
format_cells([Cell|Cells]) -->
|
||||
format_cell(Cell),
|
||||
@@ -125,6 +179,7 @@ format_element(glue(Fill,Num)) -->
|
||||
{ length(Ls, Num),
|
||||
maplist(=(Fill), Ls) },
|
||||
seq(Ls).
|
||||
format_element(goal(_)) --> [].
|
||||
|
||||
elements_gluevars([], N, N) --> [].
|
||||
elements_gluevars([E|Es], N0, N) -->
|
||||
@@ -132,9 +187,11 @@ elements_gluevars([E|Es], N0, N) -->
|
||||
elements_gluevars(Es, N1, N).
|
||||
|
||||
element_gluevar(chars(Cs), N0, N) -->
|
||||
{ length(Cs, L),
|
||||
{ must_be(chars, Cs),
|
||||
length(Cs, L),
|
||||
N is N0 + L }.
|
||||
element_gluevar(glue(_,V), N, N) --> [V].
|
||||
element_gluevar(goal(G), N, N) --> { G }.
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Our key datastructure is a list of cells and newlines.
|
||||
@@ -142,12 +199,16 @@ element_gluevar(glue(_,V), N, N) --> [V].
|
||||
From and To denote the positions of surrounding tab stops.
|
||||
|
||||
Elements is a list of elements that occur in a cell,
|
||||
namely terms of the form chars(Cs) and glue(Char, Var).
|
||||
namely terms of the form chars(Cs), glue(Char, Var)
|
||||
and goal(G).
|
||||
|
||||
"glue" elements (TeX terminology) are evenly stretched
|
||||
to fill the remaining whitespace in the cell. For each
|
||||
glue element, the character Char is used for filling,
|
||||
and Var is a free variable that is used when the
|
||||
available space is distributed.
|
||||
available space is distributed. Goals are dynamically
|
||||
executed to obtain characters. In this way, format strings
|
||||
can be parsed and compiled statically when possible.
|
||||
|
||||
newline is used if ~n occurs in a format string.
|
||||
It is used because a newline character does not
|
||||
@@ -161,54 +222,55 @@ cells([], Args, Tab, Es, _) --> !,
|
||||
cells([~,~|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [chars("~")|Es], VNs).
|
||||
cells([~,w|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
{ write_term_to_chars(Arg, [numbervars(true),variable_names(VNs)], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
|
||||
{ G = write_term_to_chars(Arg, [numbervars(true),variable_names(VNs)], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~,q|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
{ write_term_to_chars(Arg, [quoted(true),numbervars(true),variable_names(VNs)], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
|
||||
{ G = write_term_to_chars(Arg, [quoted(true),numbervars(true),variable_names(VNs)], Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~,a|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
{ atom_chars(Arg, Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
|
||||
{ G = atom_chars(Arg, Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, [d|Fs], Args0, [Arg0|Args]) },
|
||||
!,
|
||||
{ Arg is Arg0, % evaluate compound expression
|
||||
must_be(integer, Arg),
|
||||
number_chars(Arg, Cs0) },
|
||||
( { Num =:= 0 } -> { Cs = Cs0 }
|
||||
; { length(Cs0, L),
|
||||
( L =< Num ->
|
||||
Delta is Num - L,
|
||||
length(Zs, Delta),
|
||||
maplist(=('0'), Zs),
|
||||
phrase(("0.",seq(Zs),seq(Cs0)), Cs)
|
||||
; BeforeComma is L - Num,
|
||||
length(Bs, BeforeComma),
|
||||
append(Bs, Ds, Cs0),
|
||||
phrase((seq(Bs),".",seq(Ds)), Cs)
|
||||
) }
|
||||
),
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
|
||||
{ G0 = ( Arg is Arg0, % evaluate compound expression
|
||||
must_be(integer, Arg),
|
||||
number_chars(Arg, Cs0),
|
||||
( Num =:= 0 -> Cs = Cs0
|
||||
; length(Cs0, L),
|
||||
( L =< Num ->
|
||||
Delta is Num - L,
|
||||
length(Zs, Delta),
|
||||
maplist(=('0'), Zs),
|
||||
phrase(("0.",seq(Zs),seq(Cs0)), Cs)
|
||||
; BeforeComma is L - Num,
|
||||
length(Bs, BeforeComma),
|
||||
append(Bs, Ds, Cs0),
|
||||
phrase((seq(Bs),".",seq(Ds)), Cs)
|
||||
)
|
||||
)),
|
||||
goal_pe(G0, G) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, ['D'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ separate_digits_fractional(Arg, ',', Num, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
|
||||
{ G = separate_digits_fractional(Arg, ',', Num, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, ['U'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ separate_digits_fractional(Arg, '_', Num, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
|
||||
{ G = separate_digits_fractional(Arg, '_', Num, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num0, ['L'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ ( Num0 =:= 0 ->
|
||||
Num = 72
|
||||
; Num = Num0
|
||||
),
|
||||
phrase(format_("~d", [Arg]), Cs0),
|
||||
phrase(split_lines_width(Cs0, Num), Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
|
||||
{ G = (( Num0 =:= 0 ->
|
||||
Num = 72
|
||||
; Num = Num0
|
||||
),
|
||||
phrase(format_("~d", [Arg]), Cs0),
|
||||
phrase(split_lines_width(Cs0, Num), Cs) ) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~,i|Fs], [_|Args], Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, Es, VNs).
|
||||
cells([~,n|Fs], Args, Tab, Es, VNs) --> !,
|
||||
@@ -223,59 +285,40 @@ cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
cells(Fs, Args, 0, [], VNs).
|
||||
cells([~,s|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [chars(Arg)|Es], VNs).
|
||||
cells([~,f|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||
{ format_number_chars(Arg, Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
|
||||
cells([~,f|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells([~,'6',f|Fs], Args, Tab, Es, VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, [f|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ format_number_chars(Arg, Cs0),
|
||||
phrase(upto_what(Bs, .), Cs0, Cs),
|
||||
( Num =:= 0 -> Chars = Bs
|
||||
; ( Cs = ['.'|Rest] ->
|
||||
length(Rest, L),
|
||||
( Num < L ->
|
||||
length(Ds, Num),
|
||||
append(Ds, _, Rest)
|
||||
; Num =:= L ->
|
||||
Ds = Rest
|
||||
; Num > L,
|
||||
Delta is Num - L,
|
||||
% we should look into the float with
|
||||
% greater accuracy here, and use the
|
||||
% actual digits instead of 0.
|
||||
length(Zs, Delta),
|
||||
maplist(=('0'), Zs),
|
||||
append(Rest, Zs, Ds)
|
||||
)
|
||||
; length(Ds, Num),
|
||||
maplist(=('0'), Ds)
|
||||
),
|
||||
append(Bs, ['.'|Ds], Chars)
|
||||
) },
|
||||
cells(Fs, Args, Tab, [chars(Chars)|Es], VNs).
|
||||
{ G = phrase(float_with_n_decimal_digits(Arg, Num), Chars) },
|
||||
cells(Fs, Args, Tab, [chars(Chars),goal(G)|Es], VNs).
|
||||
cells([~,r|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells([~,'8',r|Fs], Args, Tab, Es, VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, [r|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ integer_to_radix(Arg, Num, lowercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
|
||||
{ G = integer_to_radix(Arg, Num, lowercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~,'R'|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells([~,'8','R'|Fs], Args, Tab, Es, VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, ['R'|Fs], Args0, [Arg|Args]) },
|
||||
!,
|
||||
{ integer_to_radix(Arg, Num, uppercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs)|Es], VNs).
|
||||
{ G = integer_to_radix(Arg, Num, uppercase, Cs) },
|
||||
cells(Fs, Args, Tab, [chars(Cs),goal(G)|Es], VNs).
|
||||
cells([~,'`',Char,t|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [glue(Char,_)|Es], VNs).
|
||||
cells([~,t|Fs], Args, Tab, Es, VNs) --> !,
|
||||
cells(Fs, Args, Tab, [glue(' ',_)|Es], VNs).
|
||||
cells([~,'|'|Fs], Args, Tab0, Es, VNs) --> !,
|
||||
{ phrase(elements_gluevars(Es, 0, Width), _),
|
||||
Tab is Tab0 + Width },
|
||||
cell(Tab0, Tab, Es),
|
||||
( { ground(Tab0), Es = [chars(Cs)], ground(Cs) } ->
|
||||
{ length(Cs, Width),
|
||||
Tab is Tab0 + Width },
|
||||
cell(Tab0, Tab, Es)
|
||||
; { G = (phrase(elements_gluevars(Es, 0, Width), _),
|
||||
Tab is Tab0 + Width) },
|
||||
cell(Tab0, Tab, [goal(G)|Es])
|
||||
),
|
||||
cells(Fs, Args, Tab, [], VNs).
|
||||
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, ['|'|Fs], Args0, Args) },
|
||||
@@ -285,8 +328,12 @@ cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||
cells([~|Fs0], Args0, Tab0, Es, VNs) -->
|
||||
{ numeric_argument(Fs0, Num, [+|Fs], Args0, Args) },
|
||||
!,
|
||||
{ Tab is Tab0 + Num },
|
||||
cell(Tab0, Tab, Es),
|
||||
( { ground(Tab0+Num) } ->
|
||||
{ Tab is Tab0 + Num },
|
||||
cell(Tab0, Tab, Es)
|
||||
; { G = (Tab is Tab0 + Num) },
|
||||
cell(Tab0, Tab, [goal(G)|Es])
|
||||
),
|
||||
cells(Fs, Args, Tab, [], VNs).
|
||||
cells([~|Cs], Args, _, _, _) -->
|
||||
( { Args == [] } ->
|
||||
@@ -298,18 +345,27 @@ cells(Fs0, Args, Tab, Es, VNs) -->
|
||||
Fs1 = [_|_] },
|
||||
cells(Fs, Args, Tab, [chars(Fs1)|Es], VNs).
|
||||
|
||||
format_number_chars(N0, Chars) :-
|
||||
N is N0, % evaluate compound expression
|
||||
number_chars(N, Chars).
|
||||
float_with_n_decimal_digits(F, N) -->
|
||||
{ Fr is abs(float_fractional_part(F)),
|
||||
FrR0 is round(Fr*10^N),
|
||||
I0 is truncate(F),
|
||||
( FrR0 >= 10^N
|
||||
-> I is I0+truncate(sign(F)), FrR = FrR0
|
||||
; I = I0, FrR is FrR0+10^N
|
||||
)
|
||||
},
|
||||
( { I=0, F<0, FrR>10^N } -> "-0" ; { number_chars(I, Is) }, seq(Is) ),
|
||||
".",
|
||||
( { FrR = 1 } -> "0" ; { number_chars(FrR, ['1'|FrRs]) }, seq(FrRs) ).
|
||||
|
||||
n_newlines(0) --> !.
|
||||
n_newlines(N0) --> { N0 > 0, N is N0 - 1 }, [newline], n_newlines(N).
|
||||
n_newlines(0) --> [].
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
?- phrase(upto_what(Cs, ~), "abc~test", Rest).
|
||||
Cs = [a,b,c], Rest = [~,t,e,s,t].
|
||||
?- phrase(upto_what(Cs, ~), "abc", Rest).
|
||||
Cs = [a,b,c], Rest = [].
|
||||
?- phrase(format:upto_what(Cs, ~), "abc~test", Rest).
|
||||
Cs = "abc", Rest = "~test".
|
||||
?- phrase(format:upto_what(Cs, ~), "abc", Rest).
|
||||
Cs = "abc", Rest = [].
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
separate_digits_fractional(Arg, Sep, Num, Cs) :-
|
||||
@@ -419,9 +475,11 @@ digits(uppercase, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").
|
||||
% advantage of this is that an ideal implementation writes the
|
||||
% characters as they become known, without manifesting the list.
|
||||
|
||||
format(Fs, Args) :-
|
||||
current_output(Stream),
|
||||
format(Stream, Fs, Args).
|
||||
format(_, _) :- not_used.
|
||||
|
||||
user:goal_expansion(format(Fs, Args),
|
||||
( current_output(Stream),
|
||||
format(Stream, Fs, Args))).
|
||||
|
||||
%% format(Stream, FormatString, Arguments)
|
||||
%
|
||||
@@ -429,9 +487,11 @@ format(Fs, Args) :-
|
||||
% binary stream, then the code of each emitted character must be in
|
||||
% 0..255.
|
||||
|
||||
format(Stream, Fs, Args) :-
|
||||
phrase_to_stream(format_(Fs, Args), Stream),
|
||||
flush_output(Stream).
|
||||
format(_, _, _) :- not_used.
|
||||
|
||||
user:goal_expansion(format(Stream, Fs, Args),
|
||||
( pio:phrase_to_stream(format:format_(Fs, Args), Stream),
|
||||
flush_output(Stream))).
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
?- phrase(format:cells("hello", [], 0, [], []), Cs).
|
||||
@@ -444,9 +504,9 @@ format(Stream, Fs, Args) :-
|
||||
?- phrase(format:cells("~`at~50|", [], 0, [], []), Cs),
|
||||
phrase(format:format_cells(Cs), Ls).
|
||||
?- phrase(format:cells("~ta~t~tb~tc~21|", [], 0, [], []), Cs).
|
||||
Cs = [cell(0,21,[glue(' ',_A),chars("a"),glue(' ',_B),glue(' ',_C),chars("b"),glue(' ',_D),chars("c ...")])]
|
||||
Cs = [cell(0,21,[glue(' ',_A),chars("a"),glue(' ',_B),glue(' ',_C),chars("b"),glue(' ',_D),chars("c")])].
|
||||
?- phrase(format:cells("~ta~t~4|", [], 0, [], []), Cs).
|
||||
Cs = [cell(0,4,[glue(' ',_A),chars("a"),glue(' ',_B)])]
|
||||
Cs = [cell(0,4,[glue(' ',_A),chars("a"),glue(' ',_B)])].
|
||||
|
||||
?- phrase(format:format_cell(cell(0,1,[glue(a,_94)])), Ls).
|
||||
|
||||
@@ -514,20 +574,29 @@ portray_clause(Stream, Term) :-
|
||||
flush_output(Stream).
|
||||
|
||||
portray_clause_(Term) -->
|
||||
{ unique_variable_names(Term, VNs) },
|
||||
{ unique_variable_names(numbervars, Term, VNs) },
|
||||
portray_(Term, VNs), ".\n".
|
||||
|
||||
unique_variable_names(Term, VNs) :-
|
||||
term_variables(Term, Vs),
|
||||
foldl(var_name, Vs, VNs, 0, _).
|
||||
|
||||
var_name(V, Name=V, Num0, Num) :-
|
||||
charsio:fabricate_var_name(numbervars, Name, Num0),
|
||||
Num is Num0 + 1.
|
||||
|
||||
literal(Lit, VNs) -->
|
||||
{ write_term_to_chars(Lit, [quoted(true),variable_names(VNs),double_quotes(true)], Ls) },
|
||||
seq(Ls).
|
||||
( { nonvar(Lit),
|
||||
\+ number(Lit),
|
||||
functor(Lit, F, A),
|
||||
current_op(Pri, _, F),
|
||||
( A =:= 0
|
||||
; Pri >= 1000
|
||||
) } ->
|
||||
"(", seq(Ls), ")"
|
||||
; seq(Ls)
|
||||
).
|
||||
|
||||
literal_(Lit, VNs) -->
|
||||
{ phrase(literal(Lit, VNs), Ls) },
|
||||
seq(Ls),
|
||||
( { phrase((...,[Last]), Ls), char_type(Last, graphic_token) } ->
|
||||
" "
|
||||
; ""
|
||||
).
|
||||
|
||||
portray_(Var, VNs) --> { var(Var) }, !, literal(Var, VNs).
|
||||
portray_((Head :- Body), VNs) --> !,
|
||||
@@ -536,7 +605,7 @@ portray_((Head :- Body), VNs) --> !,
|
||||
portray_((Head --> Body), VNs) --> !,
|
||||
literal(Head, VNs), " -->\n",
|
||||
body_(Body, 0, 3, VNs).
|
||||
portray_(Any, VNs) --> literal(Any, VNs).
|
||||
portray_(Any, VNs) --> literal_(Any, VNs).
|
||||
|
||||
|
||||
body_(Var, C, I, VNs) --> { var(Var) }, !,
|
||||
@@ -561,7 +630,7 @@ body_((A;B), C, I, VNs) --> !,
|
||||
body_(A, C1, C1, VNs), "\n",
|
||||
else_branch(B, I, VNs).
|
||||
body_(Goal, C, I, VNs) -->
|
||||
indent_to(C, I), literal(Goal, VNs).
|
||||
indent_to(C, I), literal_(Goal, VNs).
|
||||
|
||||
|
||||
% True iff Body has the shape ( If -> Then ; Else ).
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(dcgs)).
|
||||
|
||||
:- meta_predicate freeze(-, 0).
|
||||
:- meta_predicate(freeze(-, 0)).
|
||||
|
||||
:- attribute frozen/1.
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ as one HTTP method (in lowercase) and followed by a Route Match and a predicate
|
||||
which will handle the call.
|
||||
|
||||
```
|
||||
:- use_module(library(http/http_server)).
|
||||
|
||||
text_handler(Request, Response) :-
|
||||
http_status_code(Response, 200),
|
||||
http_body(Response, text("Welcome to Scryer Prolog!")).
|
||||
@@ -24,10 +26,11 @@ text_handler(Request, Response) :-
|
||||
parameter_handler(User, Request, Response) :-
|
||||
http_body(Response, text(User)).
|
||||
|
||||
http_listen(7890, [
|
||||
get(echo, text_handler), % GET /echo
|
||||
post(user/User, parameter_handler(User)) % POST /user/<User>
|
||||
]).
|
||||
run:-
|
||||
http_listen(7890, [
|
||||
get(echo, text_handler), % GET /echo
|
||||
post(user/User, parameter_handler(User)) % POST /user/<User>
|
||||
]).
|
||||
```
|
||||
|
||||
Every handler predicate will have at least 2-arity, with Request and Response.
|
||||
@@ -62,10 +65,10 @@ Some things that are still missing:
|
||||
http_basic_auth/4
|
||||
]).
|
||||
|
||||
:- meta_predicate http_listen(?, :).
|
||||
:- meta_predicate http_listen(?, :, ?).
|
||||
:- meta_predicate(http_listen(?, :)).
|
||||
:- meta_predicate(http_listen(?, :, ?)).
|
||||
|
||||
:- meta_predicate http_basic_auth(:, :, ?, ?).
|
||||
:- meta_predicate(http_basic_auth(:, :, ?, ?)).
|
||||
|
||||
:- use_module(library(charsio)).
|
||||
:- use_module(library(crypto)).
|
||||
|
||||
@@ -9,6 +9,7 @@ but they're not part of the ISO Prolog standard at the moment.
|
||||
bb_put/2,
|
||||
call_cleanup/2,
|
||||
call_with_inference_limit/3,
|
||||
call_residue_vars/2,
|
||||
forall/2,
|
||||
partial_string/1,
|
||||
partial_string/3,
|
||||
@@ -18,16 +19,18 @@ but they're not part of the ISO Prolog standard at the moment.
|
||||
call_nth/2,
|
||||
countall/2,
|
||||
copy_term_nat/2,
|
||||
asserta/2,
|
||||
assertz/2]).
|
||||
copy_term/3]).
|
||||
|
||||
:- use_module(library(error), [can_be/2,
|
||||
must_be/2,
|
||||
domain_error/3,
|
||||
instantiation_error/1,
|
||||
type_error/3]).
|
||||
|
||||
:- use_module(library(lists), [maplist/3]).
|
||||
|
||||
:- use_module(library('$project_atts')).
|
||||
|
||||
:- meta_predicate(forall(0, 0)).
|
||||
|
||||
%% forall(Generate, Test).
|
||||
@@ -221,6 +224,7 @@ run_cleaners_without_handling(Cp) :-
|
||||
%% call_with_inference_limit(Goal, Limit, Result).
|
||||
%
|
||||
% Similar to `call(Goal)` but it limits the number of inferences for each solution of Goal.
|
||||
% Calls to it may be nested, but only the last limit will be in power.
|
||||
call_with_inference_limit(G, L, R) :-
|
||||
( integer(L) ->
|
||||
( L < 0 ->
|
||||
@@ -272,17 +276,15 @@ call_with_inference_limit(_, _, R, Bb, B) :-
|
||||
; nonvar(R)
|
||||
).
|
||||
|
||||
%% partial_string(String, L, L0)
|
||||
%% partial_string(String, Ls0, Ls)
|
||||
%
|
||||
% Explicitly construct a partial string "manually". It can be used as an optimized append/3.
|
||||
% It's not recommended to use this predicate in application code.
|
||||
partial_string(String, L, L0) :-
|
||||
partial_string(String, Ls0, Ls) :-
|
||||
must_be(chars, String),
|
||||
( String == [] ->
|
||||
L = L0
|
||||
; catch(atom_chars(Atom, String),
|
||||
error(E, _),
|
||||
throw(error(E, partial_string/3))),
|
||||
'$create_partial_string'(Atom, L, L0)
|
||||
Ls0 = Ls
|
||||
; '$create_partial_string'(String, Ls0, Ls)
|
||||
).
|
||||
|
||||
%% partial_string(+String)
|
||||
@@ -384,21 +386,23 @@ countall(Goal, N) :-
|
||||
copy_term_nat(Source, Dest) :-
|
||||
'$copy_term_without_attr_vars'(Source, Dest).
|
||||
|
||||
%% asserta(Module, Rule_Fact).
|
||||
%% copy_term(+Term, -Copy, -Gs).
|
||||
%
|
||||
% Similar to `asserta/1` but allows specifying a Module
|
||||
asserta(Module, (Head :- Body)) :-
|
||||
!,
|
||||
'$asserta'(Module, Head, Body).
|
||||
asserta(Module, Fact) :-
|
||||
'$asserta'(Module, Fact, true).
|
||||
% 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
|
||||
).
|
||||
|
||||
%% assertz(Module, Rule_Fact).
|
||||
%
|
||||
% Similar to `assertz/1` but allows specifying a Module
|
||||
assertz(Module, (Head :- Body)) :-
|
||||
!,
|
||||
'$assertz'(Module, Head, Body).
|
||||
assertz(Module, Fact) :-
|
||||
'$assertz'(Module, Fact, true).
|
||||
:- meta_predicate(call_residue_vars(0, ?)).
|
||||
|
||||
call_residue_vars(Goal, Vars) :-
|
||||
can_be(list, Vars),
|
||||
'$get_attr_var_queue_delim'(B),
|
||||
call(Goal),
|
||||
'$get_attr_var_queue_beyond'(B, Vars).
|
||||
|
||||
@@ -117,32 +117,32 @@ Further discussions
|
||||
@author Ulrich Neumerkel
|
||||
*/
|
||||
|
||||
:- meta_predicate ^(?,0,?).
|
||||
:- meta_predicate ^(?,1,?,?).
|
||||
:- meta_predicate ^(?,2,?,?,?).
|
||||
:- meta_predicate ^(?,3,?,?,?,?).
|
||||
:- meta_predicate ^(?,4,?,?,?,?,?).
|
||||
:- meta_predicate ^(?,5,?,?,?,?,?,?).
|
||||
:- meta_predicate ^(?,6,?,?,?,?,?,?,?).
|
||||
:- meta_predicate ^(?,7,?,?,?,?,?,?,?,?).
|
||||
:- meta_predicate \(0).
|
||||
:- meta_predicate \(1,?).
|
||||
:- meta_predicate \(2,?,?).
|
||||
:- meta_predicate \(3,?,?,?).
|
||||
:- meta_predicate \(4,?,?,?,?).
|
||||
:- meta_predicate \(5,?,?,?,?,?).
|
||||
:- meta_predicate \(6,?,?,?,?,?,?).
|
||||
:- meta_predicate \(7,?,?,?,?,?,?,?).
|
||||
:- meta_predicate +\(?,0).
|
||||
:- meta_predicate +\(?,1,?).
|
||||
:- meta_predicate +\(?,2,?,?).
|
||||
:- meta_predicate +\(?,3,?,?,?).
|
||||
:- meta_predicate +\(?,4,?,?,?,?).
|
||||
:- meta_predicate +\(?,5,?,?,?,?,?).
|
||||
:- meta_predicate +\(?,6,?,?,?,?,?,?).
|
||||
:- meta_predicate +\(?,7,?,?,?,?,?,?,?).
|
||||
:- meta_predicate(^(?,0,?)).
|
||||
:- meta_predicate(^(?,1,?,?)).
|
||||
:- meta_predicate(^(?,2,?,?,?)).
|
||||
:- meta_predicate(^(?,3,?,?,?,?)).
|
||||
:- meta_predicate(^(?,4,?,?,?,?,?)).
|
||||
:- meta_predicate(^(?,5,?,?,?,?,?,?)).
|
||||
:- meta_predicate(^(?,6,?,?,?,?,?,?,?)).
|
||||
:- meta_predicate(^(?,7,?,?,?,?,?,?,?,?)).
|
||||
:- meta_predicate(\(0)).
|
||||
:- meta_predicate(\(1,?)).
|
||||
:- meta_predicate(\(2,?,?)).
|
||||
:- meta_predicate(\(3,?,?,?)).
|
||||
:- meta_predicate(\(4,?,?,?,?)).
|
||||
:- meta_predicate(\(5,?,?,?,?,?)).
|
||||
:- meta_predicate(\(6,?,?,?,?,?,?)).
|
||||
:- meta_predicate(\(7,?,?,?,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,0)).
|
||||
:- meta_predicate(+\(?,1,?)).
|
||||
:- meta_predicate(+\(?,2,?,?)).
|
||||
:- meta_predicate(+\(?,3,?,?,?)).
|
||||
:- meta_predicate(+\(?,4,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,5,?,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,6,?,?,?,?,?,?)).
|
||||
:- meta_predicate(+\(?,7,?,?,?,?,?,?,?)).
|
||||
|
||||
:- meta_predicate no_hat_call(0).
|
||||
:- meta_predicate(no_hat_call(0)).
|
||||
|
||||
^(V1,C_0,V1) :-
|
||||
no_hat_call(C_0).
|
||||
|
||||
@@ -42,17 +42,17 @@ List manipulation predicates
|
||||
:- use_module(library(error)).
|
||||
|
||||
|
||||
:- meta_predicate maplist(1, ?).
|
||||
:- meta_predicate maplist(2, ?, ?).
|
||||
:- meta_predicate maplist(3, ?, ?, ?).
|
||||
:- meta_predicate maplist(4, ?, ?, ?, ?).
|
||||
:- meta_predicate maplist(5, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate maplist(6, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate maplist(7, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate maplist(8, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate(maplist(1, ?)).
|
||||
:- meta_predicate(maplist(2, ?, ?)).
|
||||
:- meta_predicate(maplist(3, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(4, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(5, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(6, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(7, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(maplist(8, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
|
||||
:- meta_predicate foldl(3, ?, ?, ?).
|
||||
:- meta_predicate foldl(4, ?, ?, ?, ?).
|
||||
:- meta_predicate(foldl(3, ?, ?, ?)).
|
||||
:- meta_predicate(foldl(4, ?, ?, ?, ?)).
|
||||
|
||||
:- use_module(library(error)).
|
||||
|
||||
|
||||
187
src/lib/numerics/quadtests.pl
Normal file
187
src/lib/numerics/quadtests.pl
Normal file
@@ -0,0 +1,187 @@
|
||||
% Efforts toward literate tests with quads
|
||||
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(pio)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(reif)).
|
||||
:- use_module(library(debug)).
|
||||
:- use_module(library(lambda)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(time)).
|
||||
|
||||
:- use_module(testutils).
|
||||
:- use_module(special_functions).
|
||||
|
||||
portray_term(Stream) :-
|
||||
read_term(Stream, Term, []),
|
||||
portray_clause(Term).
|
||||
|
||||
?- check_module_quads(special_functions, _).
|
||||
% Checking 11 quads ..
|
||||
% CHECKING.. (?-A=0.6174468790806071,erf(A,A),B is-A,erf(B,B)).
|
||||
% CHECKING.. (?-try_falsify(odd_t(erf,real(A)))).
|
||||
% CHECKING.. (?-witness(odd_t(erf,real(A),false))).
|
||||
% CHECKING.. (?-witness((real(A),erf(A,B),erf(-A,C),abs(B+C)>0))).
|
||||
% CHECKING.. (?-length(A,B)).
|
||||
% CHECKING.. (?-real(A),erf(A,B),erfc(A,C),abs(B+C-1)>epsilon).
|
||||
% CHECKING.. (?-try_falsify(δ_inverses_t(40*epsilon,erf,inverf,interval(-2,2,A)))).
|
||||
% CHECKING.. (?-try_falsify(δ_inverses_t(40*epsilon,erfc,inverfc,interval(-2,2,A)))).
|
||||
% CHECKING.. (?-A=10,B is A+1,gamma(B,C),int_realfact(A,D)).
|
||||
% CHECKING.. (?-gamma_P_Q(1.2,2.3,A,B),abs(A+B-1)<epsilon).
|
||||
% CHECKING.. (?-A=1.5,B=0.7,invgammp(A,B,C),gamma_P_Q(A,C,D,E),abs(B-D)<epsilon).
|
||||
true.
|
||||
|
||||
check_module_quads(Module, Quads) :-
|
||||
use_module(Module),
|
||||
read_quads(Module, Quads),
|
||||
zip(Qs, ADs, Quads),
|
||||
length(Qs, NQ),
|
||||
format("% Checking ~d quads ..~n", [NQ]),
|
||||
maplist(check_qu_ad, Qs, ADs).
|
||||
|
||||
read_quads(Module, Quads) :-
|
||||
module_terms(Module, Terms),
|
||||
terms_quads(Terms, Quads).
|
||||
|
||||
module_terms(Module, Terms) :-
|
||||
module_file(Module, File),
|
||||
setup_call_cleanup(
|
||||
open(File, read, Stream, [type(text)]),
|
||||
read_terms(Stream, Terms),
|
||||
close(Stream)
|
||||
).
|
||||
|
||||
module_file(Module, File) :- atom_concat(Module, '.pl', File).
|
||||
|
||||
% Given a list of terms, filter out the predicate clauses.
|
||||
% TODO: Arg 1 is really a list of Term-VarNames _pairs_;
|
||||
% it would be very nice to find a less unsightly
|
||||
% name than 'TermVN' for these!
|
||||
terms_quads([Term|Terms], Quads) :-
|
||||
( term_type(Term, clause) -> terms_quads(Terms, Quads)
|
||||
; Quads = [Term|Quads_],
|
||||
terms_quads(Terms, Quads_)
|
||||
).
|
||||
terms_quads([], []).
|
||||
|
||||
term_type(Term-_, Type) :-
|
||||
( Term = (?- _) -> Type = query
|
||||
; Term = (_,_) -> Type = answer_description
|
||||
; Term = (_;_) -> Type = answer_description
|
||||
; Term == true -> Type = answer_description
|
||||
; Term == false -> Type = answer_description
|
||||
; Type = clause
|
||||
).
|
||||
|
||||
?- term_type(test("erf is odd",try_falsify(odd_t(erf,real(_L))))-_, Type).
|
||||
Type = clause.
|
||||
|
||||
read_terms(Stream, Terms) :-
|
||||
read_terms_(Stream, [], Terms).
|
||||
read_terms_(Stream, Terms0, Terms) :-
|
||||
Options = [variable_names(VarNames)],
|
||||
read_term(Stream, Term, Options),
|
||||
( Term = end_of_file -> reverse(Terms0, Terms)
|
||||
; read_terms_(Stream, [Term-VarNames|Terms0], Terms)
|
||||
).
|
||||
|
||||
%% zip(+Xs, +Ys, ?XYs)
|
||||
%% zip(?Xs, ?Ys, +XYs)
|
||||
%
|
||||
% List XYs interleaves same-length lists Xs and Ys.
|
||||
zip([X|Xs], [Y|Ys], [X,Y|XYs]) :-
|
||||
zip(Xs, Ys, XYs).
|
||||
zip([], [], []).
|
||||
|
||||
?- zip(Xs, Ys, XYs). % MGQ does not terminate
|
||||
error('$interrupt_thrown',repl/0).
|
||||
|
||||
% The following suggested by Ulrich via Quad Works chat
|
||||
?- zip(Xs, Ys, XYs), false. % loops
|
||||
|
||||
?- zip(X, [4,5,6], [1,4,2,5,3,6]).
|
||||
X = [1,2,3].
|
||||
|
||||
?- zip([1,2,3], Y, [1,4,2,5,3,6]).
|
||||
Y = [4,5,6].
|
||||
|
||||
?- zip([1,2,3], [4,5,6], Z).
|
||||
Z = [1,4,2,5,3,6].
|
||||
|
||||
?- zip(Xs, Ys, [1,4,2,5,3,6]).
|
||||
Xs = [1,2,3], Ys = [4,5,6].
|
||||
|
||||
% 3. Demonstrate checking 1 quad, the top two elements of a QAs list.
|
||||
check_qu_ad(Q-QVN, A-AVN) :-
|
||||
Q = ?-(G),
|
||||
phrase(portray_clause_(Q), LitQ), % NB: LitQ terminates w/ newline
|
||||
format("% CHECKING.. ",[]),
|
||||
( A == true -> call(G)
|
||||
; A == false -> ( call(G) -> false
|
||||
; true
|
||||
)
|
||||
; phrase(unconj(A), As) ->
|
||||
( length(As, N),
|
||||
n_answers(N, A, AVN, ADs),
|
||||
n_answers(N, G, QVN, Answers),
|
||||
maplist(contains, ADs, Answers)
|
||||
)
|
||||
; % Otherwise, we have the ',' case of a solitary answer
|
||||
call(G),
|
||||
call(A),
|
||||
QVN == AVN
|
||||
),
|
||||
format("~s", [LitQ]).
|
||||
|
||||
% Answer-description AD (qua set-of-bindings) contains Answer.
|
||||
contains(AD, Answer) :- append(Answer, _, AD).
|
||||
|
||||
?- contains(['Xs'=[C],'L'=1,'_A'=C,'_B'=D], ['Xs'=[A],'L'=1]).
|
||||
C = A.
|
||||
|
||||
?- check_qu_ad((?-length(_F,_G))-['Xs'=_F,'L'=_G],(_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)-['Xs'=_H,'L'=_I,'_A'=_J,'_B'=_K]).
|
||||
% CHECKING.. (?-length(A,B)).
|
||||
_F = [_A,_B], _G = 2, _H = [_J,_K], _I = 2.
|
||||
|
||||
% Unravel the nested (;)/1 applications of multiple-AD structures.
|
||||
unconj(Conj) --> { Conj = (Elt;Conj_) },
|
||||
[Elt],
|
||||
unconj(Conj_).
|
||||
unconj(...) --> [].
|
||||
|
||||
?- phrase(unconj((_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)), List).
|
||||
List = [(_H=[],_I=0),(_H=[_J],_I=1),(_H=[_J,_K],_I=2)].
|
||||
|
||||
empty_anstack :-
|
||||
( retract('$anstack'(_)), fail
|
||||
; asserta('$anstack'([]))
|
||||
).
|
||||
|
||||
push(VN) :-
|
||||
retract('$anstack'(As)),
|
||||
asserta('$anstack'([VN|As])).
|
||||
|
||||
backtrack(N) :-
|
||||
( '$anstack'(Ans),
|
||||
length(Ans, N) -> true
|
||||
; fail
|
||||
).
|
||||
|
||||
n_answers(N, G, VN, ADs) :-
|
||||
must_be(integer, N),
|
||||
( N > 0 -> n_answers_(N, G, VN, ADs)
|
||||
; domain_error(not_less_than_zero, N, n_answers/4)
|
||||
).
|
||||
|
||||
n_answers_(N, G, VN, ADs) :-
|
||||
empty_anstack,
|
||||
call(G), push(VN),
|
||||
backtrack(N),
|
||||
!,
|
||||
retract('$anstack'(As)),
|
||||
reverse(As, ADs).
|
||||
|
||||
?- n_answers(3, length(Xs, L), ('Xs'=Xs,'Len'=L), ADs).
|
||||
Xs = [_A,_B], L = 2, ADs = [('Xs'=[],'Len'=0),('Xs'=[_C],'Len'=1),('Xs'=[_D,_E],'Len'=2)].
|
||||
257
src/lib/numerics/special_functions.pl
Normal file
257
src/lib/numerics/special_functions.pl
Normal file
@@ -0,0 +1,257 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2025 by David C. Norris (david@precisionmethods.guru)
|
||||
As with all things floating-point, use at your own risk.
|
||||
Part of Scryer Prolog.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Special math functions in the Error, Gamma and Beta families
|
||||
|
||||
The underlying Rust implementations come from the
|
||||
[puruspe](https://docs.rs/puruspe/latest/puruspe/) crate.
|
||||
*/
|
||||
|
||||
:- module(special_functions, [
|
||||
erf/2
|
||||
,erfc/2
|
||||
,inverf/2
|
||||
,inverfc/2
|
||||
,gamma/2
|
||||
,gamma/3
|
||||
,gamma_P_Q/4
|
||||
,invgammp/3
|
||||
,log_gamma/2
|
||||
,beta/3
|
||||
,betai/4
|
||||
,invbetai/4
|
||||
,test/2
|
||||
,test_special_functions/0
|
||||
,try_falsify/1
|
||||
,witness/1
|
||||
]).
|
||||
|
||||
:- use_module(library(numerics/testutils)).
|
||||
|
||||
%% erf(+Xexpr, -Erf)
|
||||
%
|
||||
% X is Xexpr ∈ ℝ, Erf is erf(X).
|
||||
%
|
||||
% [DLMF §7.2.1](https://dlmf.nist.gov/7.2#E1),
|
||||
% [`puruspe::error::erf`](https://docs.rs/puruspe/latest/puruspe/error/fn.erf.html)
|
||||
erf(Xexpr, Erf) :-
|
||||
X is Xexpr,
|
||||
builtins:must_be_number(X, erf/2),
|
||||
'$erf'(X, Erf).
|
||||
|
||||
% Demonstrate the roots of x - erf(x))
|
||||
?- X0 = 0.6174468790806071, erf(X0, X0), _X0 is -X0, erf(_X0, _X0).
|
||||
X0 = 0.6174468790806071, _X0 = -0.6174468790806071.
|
||||
|
||||
% erf is an odd function:
|
||||
?- try_falsify(odd_t(erf, real(_))).
|
||||
false.
|
||||
|
||||
% Another way to say the same thing..
|
||||
?- witness(odd_t(erf, real(_), false)).
|
||||
false.
|
||||
|
||||
% ..and yet one more:
|
||||
?- witness((real(X), erf(X,Erf), erf(-X,_Erf), abs(Erf+_Erf) > 0)).
|
||||
false.
|
||||
|
||||
%% erfc(+X, -Erfc)
|
||||
%
|
||||
% Erfc is erfc(X) for X ∈ ℝ.
|
||||
%
|
||||
% [DLMF §7.2.2](https://dlmf.nist.gov/7.2#E2),
|
||||
% [`puruspe::error::erfc`](https://docs.rs/puruspe/latest/puruspe/error/fn.erfc.html)
|
||||
erfc(X, Erfc) :-
|
||||
builtins:must_be_number(X, erfc/2),
|
||||
'$erfc'(X, Erfc).
|
||||
|
||||
?- real(X), erf(X, Erf), erfc(X, Erfc), abs(Erf+Erfc-1) > epsilon.
|
||||
false.
|
||||
|
||||
%% inverf(+ErfX, -X)
|
||||
%
|
||||
% X is erf⁻¹(ErfX) for ErfX ∈ (-1,1).
|
||||
inverf(ErfX, X) :-
|
||||
builtins:must_be_number(ErfX, inverf/2),
|
||||
'$inverf'(ErfX, X).
|
||||
|
||||
?- try_falsify(δ_inverses_t(40*epsilon, erf, inverf, interval(-2,2,_))).
|
||||
false.
|
||||
|
||||
%% inverfc(+ErfcX, -X)
|
||||
%
|
||||
% X is erfc⁻¹(ErfcX) for ErfcX ∈ (0,2).
|
||||
inverfc(ErfcX, X) :-
|
||||
builtins:must_be_number(ErfcX, inverfc/2),
|
||||
'$inverfc'(ErfcX, X).
|
||||
|
||||
?- try_falsify(δ_inverses_t(40*epsilon, erfc, inverfc, interval(-2,2,_))).
|
||||
false.
|
||||
|
||||
%% gamma(+X, -Gamma)
|
||||
%
|
||||
% Gamma is Γ(X), the [ordinary] gamma function evaluated at X ∈ ℝ.
|
||||
%
|
||||
% [DLMF §5.2.1](https://dlmf.nist.gov/5.2#E1)
|
||||
% [`puruspe::gamma::gamma`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gamma.html)
|
||||
gamma(X, Gamma) :-
|
||||
builtins:must_be_number(X, gamma/2),
|
||||
'$gamma'(X, Gamma).
|
||||
|
||||
% Γ(n+1) ≡ n!
|
||||
?- N = 10, N1 is N+1, gamma(N1, ΓN1), int_realfact(N, Γ11).
|
||||
N = 10, N1 = 11, ΓN1 = 3628800.0, Γ11 = 3628800.0.
|
||||
|
||||
|
||||
%% gamma(+A, +X, -Gamma)
|
||||
%
|
||||
% Gamma is Γ(A,X), the upper incomplete gamma function, where A > 0
|
||||
% is the shape parameter and X ≥ 0 is the lower limit of integration.
|
||||
%
|
||||
% [DLMF §8.2.2](https://dlmf.nist.gov/8.2#E2),
|
||||
gamma(A, X, Gamma) :-
|
||||
builtins:must_be_number(A, gammq/3),
|
||||
builtins:must_be_number(X, gammq/3),
|
||||
'$gammq'(A, X, Q),
|
||||
gamma(A, GammaA),
|
||||
Gamma is Q*GammaA.
|
||||
|
||||
%% gamma_P_Q(+A, +X, -P, -Q)
|
||||
%
|
||||
% For shape parameter A > 0 and lower limit of integration X ≥ 0,
|
||||
%
|
||||
% * P is γ(A,X)/Γ(X), the regularized _lower_ incomplete gamma function, and
|
||||
%
|
||||
% * Q is Γ(A,X)/Γ(X), the regularized _upper_ incomplete gamma function.
|
||||
%
|
||||
% [DLMF §8.2.4](https://dlmf.nist.gov/8.2#E4),
|
||||
% [`puruspe::gammp::gammp`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gammp.html),
|
||||
% [`puruspe::gammp::gammq`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.gammq.html)
|
||||
gamma_P_Q(A, X, P, Q) :-
|
||||
builtins:must_be_number(A, gamma_P_Q/4),
|
||||
builtins:must_be_number(X, gamma_P_Q/4),
|
||||
'$gammp'(A, X, P),
|
||||
'$gammq'(A, X, Q).
|
||||
|
||||
% P + Q ≈ 1
|
||||
?- gamma_P_Q(1.2, 2.3, P, Q), abs(P + Q - 1) < epsilon.
|
||||
P = 0.8621845438106976, Q = 0.1378154561893024.
|
||||
|
||||
%% invgammp(+A, +P, -X)
|
||||
%
|
||||
% Given shape parameter A > 0 and probability P ∈ [0,1),
|
||||
%
|
||||
% X is the unique solution of P = P(A,X), where P(-,-) is the
|
||||
% regularized lower incomplete gamma function.
|
||||
%
|
||||
% [`puruspe::gamma::invgammp`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.invgammp.html)
|
||||
invgammp(A, P, X) :-
|
||||
builtins:must_be_number(A, invgammp/3),
|
||||
builtins:must_be_number(P, invgammp/3),
|
||||
'$invgammp'(P, A, X).
|
||||
|
||||
?- A = 1.5, P = 0.7, invgammp(A, P, X), gamma_P_Q(A, X, P_, _), abs(P-P_) < epsilon.
|
||||
A = 1.5, P = 0.7, X = 1.8324353915624363, P_ = 0.7000000000000001.
|
||||
|
||||
%% log_gamma(+X, -LogGamma)
|
||||
%
|
||||
% LogGamma is ln(Γ(X)), the natural logarithm of Γ(X).
|
||||
%
|
||||
% [`puruspe::gamma::ln_gamma`](https://docs.rs/puruspe/latest/puruspe/gamma/fn.ln_gamma.html)
|
||||
log_gamma(X, LnGamma) :-
|
||||
builtins:must_be_number(X, log_gamma/2),
|
||||
'$ln_gamma'(X, LnGamma).
|
||||
|
||||
%% beta(+X, +Y, -B)
|
||||
%
|
||||
% B is B(X,Y) ≡ Γ(X)*Γ(Y)/Γ(X+Y)
|
||||
%
|
||||
% [DLMF §5.12.1](https://dlmf.nist.gov/5.12#E1)
|
||||
% [`puruspe::beta::beta`](https://docs.rs/puruspe/latest/puruspe/beta/fn.beta.html)
|
||||
beta(X, Y, B) :-
|
||||
builtins:must_be_number(X, beta/3),
|
||||
builtins:must_be_number(Y, beta/3),
|
||||
'$beta'(X, Y, B).
|
||||
|
||||
%% betai(+A, +B, +X, -Ix)
|
||||
%
|
||||
% Given:
|
||||
%
|
||||
% * shape parameters A > 0 and B > 0,
|
||||
% * upper limit of integration X ∈ [0,1],
|
||||
%
|
||||
% Ix is Iₓ(A,B) ≡ B(X;A,B)/B(A,B), the regularized incomplete beta function;
|
||||
%
|
||||
% [DLMF §8.17.2](https://dlmf.nist.gov/8.17#E2),
|
||||
% [`puruspe::beta::betai`](https://docs.rs/puruspe/latest/puruspe/beta/fn.betai.html)
|
||||
betai(A, B, X, Ix) :-
|
||||
builtins:must_be_number(A, betai/4),
|
||||
builtins:must_be_number(B, betai/4),
|
||||
builtins:must_be_number(X, betai/4),
|
||||
'$betai'(A, B, X, Ix).
|
||||
|
||||
%% invbetai(+A, +B, +P, -X)
|
||||
%
|
||||
% Given:
|
||||
%
|
||||
% * shape parameters A > 0 and B > 0,
|
||||
% * probability P ∈ [0,1],
|
||||
%
|
||||
% X ∈ [0,1] is the unique solution of P = Iₓ(A,B) ≡ B(X;A,B)/B(A,B).
|
||||
%
|
||||
% [`puruspe::beta::invbetai`](https://docs.rs/puruspe/latest/puruspe/beta/fn.invbetai.html)
|
||||
invbetai(A, B, P, X) :-
|
||||
builtins:must_be_number(A, invbetai/4),
|
||||
builtins:must_be_number(B, invbetai/4),
|
||||
builtins:must_be_number(P, invbetai/4),
|
||||
'$invbetai'(P, A, B, X).
|
||||
|
||||
|
||||
% ============================== TESTS ==============================
|
||||
|
||||
%% test_special_functions
|
||||
%
|
||||
% Run all tests defined in this module. (These tests _succeed_ when
|
||||
% they find counterexamples, so the 'desirable' result is `false`.)
|
||||
test_special_functions :-
|
||||
format("Seeking counterexamples to assertions:~n", []),
|
||||
test(T, G), format("% ~s ~n", [T]),
|
||||
call(G).
|
||||
|
||||
% We default to 1M falsification attempts per assertion, and -- more
|
||||
% importantly -- use the (unexported) testutils:try_falsify_/2, to
|
||||
% avoid testutils:reproducibly/0 fixing an RNG seed. Thus we obtain
|
||||
% truly pseudorandom tests untainted by seed-hacking impropriety.
|
||||
:- meta_predicate(try_falsify(1)).
|
||||
try_falsify(G) :- testutils:try_falsify_(10^6, G).
|
||||
|
||||
% A unary witness/1 predicate similarly renders queries more concise.
|
||||
:- meta_predicate(witness(0)).
|
||||
witness(G) :- testutils:witness(10^6, G).
|
||||
|
||||
:- discontiguous(test/2).
|
||||
|
||||
%% test(+Name, ?Goal)
|
||||
%
|
||||
% Tests have the signature established by @bakaq's test_framework,
|
||||
% each with a user-facing string Name, and a Goal which serves as an
|
||||
% _assertion_ by succeeding iff the Name'd desirable property holds.
|
||||
test("erf is odd", try_falsify(odd_t(erf, real(_)))).
|
||||
|
||||
test("pos root of erf(x)-x", \+ (X0 = 0.6174468790806071, erf(X0, X0))).
|
||||
|
||||
test("erfc ≈ 1 - erf", try_falsify(erf_plus_erfc_unity_t(real(_)))).
|
||||
|
||||
erf_plus_erfc_unity_t(Any, T) :-
|
||||
call_free(Any, X), erf(X, Erf), erfc(X, Erfc),
|
||||
( abs(Erf + Erfc - 1) < epsilon -> T = true
|
||||
; T = false
|
||||
).
|
||||
|
||||
test("inverf ≈ erf⁻¹",
|
||||
try_falsify(δ_inverses_t(40*epsilon, erf, inverf, interval(-2,2,_)))).
|
||||
|
||||
test("('false' is good)", false).
|
||||
147
src/lib/numerics/testutils.pl
Normal file
147
src/lib/numerics/testutils.pl
Normal file
@@ -0,0 +1,147 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2025 by David C. Norris (david@precisionmethods.guru)
|
||||
As with all things floating-point, use at your own risk.
|
||||
Part of Scryer Prolog.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** Utility predicates for testing numerics
|
||||
|
||||
*/
|
||||
|
||||
:- module(testutils, [
|
||||
try_falsify/2
|
||||
,witness/2
|
||||
,real/1
|
||||
,posreal/1
|
||||
,interval/3
|
||||
,call_free/2
|
||||
,odd_t/3
|
||||
,δ_inverses_t/5
|
||||
,int_realfact/2
|
||||
]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(random)).
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(format)).
|
||||
|
||||
%% real(-X)
|
||||
%
|
||||
% X = tan(U) for uniform U ~ U[-π,π).
|
||||
real(X) :-
|
||||
random(U),
|
||||
X is tan(pi*(U - 0.5)).
|
||||
|
||||
%% posreal(-X)
|
||||
%
|
||||
% X = 1/U for uniform U ~ U[0,1).
|
||||
posreal(X) :-
|
||||
random(U),
|
||||
X is 1/U - 1.
|
||||
|
||||
%% interval(+A, +B, -X)
|
||||
%
|
||||
% X ~ U[A,B).
|
||||
interval(A, B, X) :-
|
||||
random(U),
|
||||
X is A + (B-A)*U.
|
||||
|
||||
:- meta_predicate(try_falsify(+, 1)).
|
||||
|
||||
% NOTE: NON-reproducible tests would in fact be MORE STRINGENT,
|
||||
% creating opportunities to detect rare error cases over time.
|
||||
reproducibly :- set_random(seed(2025)).
|
||||
|
||||
%% try_falsify(+IntExpr, ?G_1)
|
||||
%
|
||||
% Make (N is IntExpr) attempts to falsify the partial goal G/1,
|
||||
% reporting the first counterexample found.
|
||||
try_falsify(N, G_1) :- reproducibly -> try_falsify_(N, G_1).
|
||||
try_falsify_(IntExpr, G_1) :- N is IntExpr, must_be(integer, N), N > 0,
|
||||
( call(G_1, false) -> counterexample(G_1)
|
||||
; N_ is N - 1,
|
||||
try_falsify_(N_, G_1)
|
||||
).
|
||||
|
||||
%% call_free(?G, -V)
|
||||
%
|
||||
% Call goal G, the single free variable of which is bound to V.
|
||||
call_free(G, V) :- term_variables(G, [V]), call(G).
|
||||
|
||||
:- meta_predicate(odd_t(2, 0, ?)).
|
||||
|
||||
:- meta_predicate(witness(+, 0)).
|
||||
|
||||
%% witness(+N, ?OhNo)
|
||||
%
|
||||
% Make N attempts to satisfy the goal OhNo, reporting the first
|
||||
% counterexample found.
|
||||
witness(N, OhNo) :- N > 0,
|
||||
( call(OhNo) -> counterexample(OhNo)
|
||||
; N_ is N - 1,
|
||||
witness(N_, OhNo)
|
||||
).
|
||||
|
||||
% The goal below is obviously _designed_ to succeed 10% of the time,
|
||||
% giving us plenty of chances to see 'counterexamples':
|
||||
?- witness(10, (random(X), format("% X = ~f~n", [X]), X > 0.9)).
|
||||
% X = 0.8084510175379878
|
||||
% X = 0.3173976152322231
|
||||
% X = 0.6016479707924276
|
||||
% X = 0.2782828276608216
|
||||
% X = 0.5737059731916494
|
||||
% X = 0.4300520177737992
|
||||
% X = 0.5411038305190763
|
||||
% X = 0.6901983097300066
|
||||
% X = 0.05013429563996663
|
||||
% X = 0.7321078902421636
|
||||
false.
|
||||
% X = 0.6401599325865659
|
||||
% X = 0.7024957773981413
|
||||
% X = 0.5798797162672757
|
||||
% X = 0.3107013295892864
|
||||
% X = 0.3792925200660049
|
||||
% X = 0.3424593598278811
|
||||
% X = 0.0589899862019303
|
||||
% X = 0.9692529829158145
|
||||
% COUNTEREXAMPLE: user:random(0.9692529829158145),(current_output(user_output),pio:phrase_to_stream(format:format_([%, ,X, ,=, ,~,f,~,n],[0.9692529829158145]),user_output),flush_output(user_output)),0.9692529829158145>0.9
|
||||
X = 0.9692529829158145.
|
||||
|
||||
%% odd_t(+F_2, +Any, ?T)
|
||||
%
|
||||
% T is the truth-value from testing that function F_2 is
|
||||
% [odd](https://en.wikipedia.org/wiki/Even_and_odd_functions) at a
|
||||
% value X obtained via call_free(Any, X).
|
||||
odd_t(F, Any, T) :-
|
||||
( call_free(Any, X),
|
||||
_X is -X,
|
||||
call(F, X, Fx),
|
||||
call(F, _X, _Fx),
|
||||
_Fx is -Fx -> T = true
|
||||
; T = false
|
||||
).
|
||||
|
||||
:- meta_predicate(δ_inverses_t(?, 2, 2, 0, ?)).
|
||||
|
||||
%% δ_inverses_t(+Δ, +F_2, +Finv_2, +Any, ?T)
|
||||
%
|
||||
% For given functions F_2 and Finv_2, T is the truth-value from
|
||||
% testing that Finv_2 ∘ F_2 is within Δ of the identity at a value X
|
||||
% obtained via call_free(Any, X).
|
||||
δ_inverses_t(Δ, F, Finv, Any, T) :-
|
||||
( call_free(Any, X),
|
||||
call(F, X, Fx),
|
||||
call(Finv, Fx, X_),
|
||||
( abs(X - X_) < Δ -> T = true
|
||||
; T = false
|
||||
)
|
||||
).
|
||||
|
||||
%% int_realfact(+N, -FactN)
|
||||
%
|
||||
% FactN is floating-point N!
|
||||
int_realfact(N, FactN) :-
|
||||
N > 0, N_ is N - 1, int_realfact(N_, FactN_), FactN is N*FactN_.
|
||||
int_realfact(0, 1.0).
|
||||
|
||||
counterexample(G) :- format("% COUNTEREXAMPLE: ~w~n", [G]).
|
||||
@@ -3,11 +3,16 @@
|
||||
% module resolution operator.
|
||||
:- op(600, xfy, :).
|
||||
|
||||
:- op(1199, fx, meta_predicate).
|
||||
|
||||
/* this is an implementation specific declarative operator used to implement call_with_inference_limit/3
|
||||
and setup_call_cleanup/3. switches to the default trust_me and retry_me_else. Indexing choice
|
||||
instructions are unchanged. */
|
||||
% Implementation specific declarative operator used to implement
|
||||
% call_with_inference_limit/3 and setup_call_cleanup/3. Compiler switches
|
||||
% to the default trust_me, retry_me_else and some other instructions for all
|
||||
% predicates that are marked with it. Indexing choice instructions are unchanged.
|
||||
%
|
||||
% Implementation details:
|
||||
% Default instructions are not subject to inference counting, so their
|
||||
% execution will not be considered if they happen to be called by
|
||||
% call_with_inference_limit/3.
|
||||
%
|
||||
:- op(700, fx, non_counted_backtracking).
|
||||
|
||||
% arithmetic operators.
|
||||
@@ -19,7 +24,6 @@
|
||||
:- op(200, xfy, ^).
|
||||
:- op(500, yfx, /\).
|
||||
:- op(500, yfx, \/).
|
||||
:- op(500, yfx, xor).
|
||||
:- op(400, yfx, div).
|
||||
:- op(400, yfx, //).
|
||||
:- op(400, yfx, rdiv).
|
||||
@@ -60,70 +64,70 @@
|
||||
:- op(1200, xfx, -->).
|
||||
|
||||
% meta_predicate declarations for call/{1, 66}.
|
||||
:- meta_predicate call(0).
|
||||
:- meta_predicate call(1, ?).
|
||||
:- meta_predicate call(2, ?, ?).
|
||||
:- meta_predicate call(3, ?, ?, ?).
|
||||
:- meta_predicate call(4, ?, ?, ?, ?).
|
||||
:- meta_predicate call(5, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(6, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(7, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(8, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(9, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(10, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(11, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(12, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(13, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(14, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(15, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(16, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(17, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(18, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(19, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(20, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(21, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(22, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(23, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(24, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(25, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(26, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(27, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(28, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(29, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(30, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(31, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(32, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(33, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(34, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(35, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(36, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(37, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(38, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(39, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(40, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(41, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(42, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(43, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(44, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(45, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(46, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(47, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(48, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(49, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(50, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(51, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(52, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(53, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(54, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(55, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(56, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(57, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(58, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(59, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(61, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(62, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(63, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(64, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate call(65, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||
:- meta_predicate(call(0)).
|
||||
:- meta_predicate(call(1, ?)).
|
||||
:- meta_predicate(call(2, ?, ?)).
|
||||
:- meta_predicate(call(3, ?, ?, ?)).
|
||||
:- meta_predicate(call(4, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(5, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(6, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(7, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(8, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(9, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(10, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(11, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(12, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(13, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(14, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(15, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(16, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(17, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(18, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(19, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(20, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(21, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(22, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(23, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(24, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(25, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(26, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(27, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(28, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(29, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(30, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(31, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(32, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(33, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(34, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(35, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(36, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(37, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(38, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(39, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(40, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(41, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(42, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(43, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(44, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(45, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(46, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(47, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(48, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(49, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(50, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(51, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(52, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(53, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(54, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(55, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(56, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(57, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(58, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(59, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(60, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(61, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(62, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(63, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(64, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
:- meta_predicate(call(65, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)).
|
||||
|
||||
@@ -23,7 +23,9 @@ finding out the PID of the running system.
|
||||
unsetenv/1,
|
||||
shell/1,
|
||||
shell/2,
|
||||
pid/1]).
|
||||
pid/1,
|
||||
raw_argv/1,
|
||||
argv/1]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(charsio)).
|
||||
@@ -110,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 = []
|
||||
).
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
map_list_to_pairs/3]).
|
||||
|
||||
|
||||
:- meta_predicate map_list_to_pairs(2, ?, ?).
|
||||
:- meta_predicate(map_list_to_pairs(2, ?, ?)).
|
||||
|
||||
%% pairs_keys_values(?Pairs, ?Keys, ?Values)
|
||||
%
|
||||
|
||||
@@ -39,7 +39,8 @@
|
||||
% represented as a list of characters.
|
||||
|
||||
phrase_from_stream(GRBody, Stream) :-
|
||||
stream_to_lazy_list(Stream, Ls),
|
||||
stream_property(Stream, reposition(Reposition)),
|
||||
stream_to_lazy_list(Reposition, Stream, Ls),
|
||||
phrase(GRBody, Ls).
|
||||
|
||||
%% phrase_from_file(+GRBody, +File)
|
||||
@@ -64,7 +65,7 @@ phrase_from_file(NT, File, Options) :-
|
||||
; Type = text
|
||||
),
|
||||
setup_call_cleanup(
|
||||
open(File, read, Stream, Options),
|
||||
open(File, read, Stream, [reposition(true)|Options]),
|
||||
phrase_from_stream(NT, Stream),
|
||||
close(Stream)
|
||||
)
|
||||
@@ -73,34 +74,41 @@ phrase_from_file(NT, File, Options) :-
|
||||
% How many chars to read from stream and buffer in each step
|
||||
chars_to_read(4096).
|
||||
|
||||
stream_to_lazy_list(Stream, Ls) :-
|
||||
get_stream_buffer_position(Stream, Pos),
|
||||
freeze(Ls, render_step(Stream, Pos, Ls)).
|
||||
stream_to_lazy_list(Reposition, Stream, Ls) :-
|
||||
get_stream_buffer_position(Reposition, Stream, Pos),
|
||||
freeze(Ls, render_step(Reposition, Stream, Pos, Ls)).
|
||||
|
||||
render_step(Stream, Pos, Ls) :-
|
||||
set_stream_buffer_position(Stream, Pos),
|
||||
( buffer_at_end_of_stream(Stream) ->
|
||||
render_step(Reposition, Stream, Pos, Ls) :-
|
||||
set_stream_buffer_position(Reposition, Stream, Pos),
|
||||
( buffer_at_end_of_stream(Reposition, Stream) ->
|
||||
Ls = []
|
||||
; chars_to_read(CharsToRead),
|
||||
buffer_get_n_chars(Stream, CharsToRead, Chars),
|
||||
buffer_get_n_chars(Reposition, Stream, CharsToRead, Chars),
|
||||
partial_string(Chars, Ls, Ls0),
|
||||
stream_to_lazy_list(Stream, Ls0)
|
||||
stream_to_lazy_list(Reposition, Stream, Ls0)
|
||||
).
|
||||
|
||||
buffer_at_end_of_stream(Stream) :-
|
||||
buffer_at_end_of_stream(true, Stream) :- at_end_of_stream(Stream).
|
||||
buffer_at_end_of_stream(false, Stream) :-
|
||||
stream_bufferids(Stream, _, BufferPosId, _),
|
||||
bb_get(BufferPosId, Pos),
|
||||
Pos = eof.
|
||||
|
||||
get_stream_buffer_position(Stream, Pos) :-
|
||||
get_stream_buffer_position(true, Stream, Pos) :-
|
||||
stream_property(Stream, position(Pos)).
|
||||
get_stream_buffer_position(false, Stream, Pos) :-
|
||||
stream_bufferids(Stream, _, BufferPosId, _),
|
||||
bb_get(BufferPosId, Pos).
|
||||
|
||||
set_stream_buffer_position(Stream, Pos) :-
|
||||
set_stream_buffer_position(true, Stream, Pos) :-
|
||||
set_stream_position(Stream, Pos).
|
||||
set_stream_buffer_position(false, Stream, Pos) :-
|
||||
stream_bufferids(Stream, _, BufferPosId, _),
|
||||
bb_put(BufferPosId, Pos).
|
||||
|
||||
buffer_get_n_chars(Stream, N, Chars) :-
|
||||
buffer_get_n_chars(true, Stream, N, Chars) :-
|
||||
get_n_chars(Stream, N, Chars).
|
||||
buffer_get_n_chars(false, Stream, N, Chars) :-
|
||||
stream_bufferids(Stream, BufferId, BufferPosId, BufferLenId),
|
||||
buffer_prepare_for_n(Stream, BufferId, BufferPosId, BufferLenId, N),
|
||||
bb_get(BufferId, Buffer),
|
||||
|
||||
224
src/lib/process.pl
Normal file
224
src/lib/process.pl
Normal file
@@ -0,0 +1,224 @@
|
||||
:- module(process, [
|
||||
process_create/3,
|
||||
process_id/2,
|
||||
process_release/1,
|
||||
process_wait/2,
|
||||
process_wait/3,
|
||||
process_kill/1
|
||||
]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(lists), [member/2, maplist/2, maplist/3, append/2]).
|
||||
:- use_module(library(reif), [tfilter/3, memberd_t/3]).
|
||||
|
||||
|
||||
%% process_create(+Exe, +Args:list, +Options).
|
||||
%
|
||||
% Create a new process by executing the executable Exe and passing it the Arguments Args.
|
||||
%
|
||||
% Note: On windows please take note of [windows argument splitting](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting).
|
||||
%
|
||||
% Options is a list consisting of the following options:
|
||||
%
|
||||
% * `cwd(+Path)` Set the processes working directory to `Path`
|
||||
% * `process(-Process)` `Process` will be assigned a process handle for the spawned process
|
||||
% * `env(+List)` Don't inherit environment variables and set the variables defined in `List`
|
||||
% * `environment(+List)` Inherit environment variables and set/override the variables defined in `List`
|
||||
% * `stdin(Spec)`, `stdout(Spec)` or `stderr(Spec)` defines how to redirect the spawned processes io streams
|
||||
%
|
||||
% The elements of `List` in `env(List)`/`environment(List)` List must be string pairs using `=/2`.
|
||||
% `env/1` and `environment/1` may not be both specified.
|
||||
%
|
||||
% The following stdio `Spec` are available:
|
||||
%
|
||||
% * `std` inherit the current processes original stdio streams (does currently not account for stdio being changed by `set_input` or `set_output`)
|
||||
% * `file(+Path)` attach the strea to the file at `Path`
|
||||
% * `null` discards writes and behaves as eof for read. Equivalent to using `file(/dev/null)`
|
||||
% * `pipe(-Steam)` create a new pipe and assigne one end to the created process and the other end to `Stream`
|
||||
%
|
||||
% Specifying an option multiple times is an error, when an option is not specified the following defaults apply:
|
||||
%
|
||||
% - `cwd(".")`
|
||||
% - `environment([])`
|
||||
% - `stdin(std)`, `stdout(std)`, `stderr(std)`
|
||||
%
|
||||
process_create(Exe, Args, Options) :- call_with_error_context(process_create_(Exe, Args, Options), predicate-process_create/3).
|
||||
|
||||
process_create_(Exe, Args, Options) :-
|
||||
must_be(chars, Exe),
|
||||
must_be(list, Args),
|
||||
maplist(must_be(chars), Args),
|
||||
must_be(list, Options),
|
||||
check_options(
|
||||
[
|
||||
option([stdin], valid_stdio, stdin(std), stdin(Stdin)),
|
||||
option([stdout], valid_stdio, stdout(std), stdout(Stdout)),
|
||||
option([stderr], valid_stdio, stderr(std), stderr(Stderr)),
|
||||
option([env, environment], valid_env, environment([]), Env),
|
||||
option([process], valid_uninit_process, process(_), process(Process)),
|
||||
option([cwd], valid_cwd, cwd("."), cwd(Cwd))
|
||||
],
|
||||
Options,
|
||||
process_create_option
|
||||
),
|
||||
Stdin =.. Stdin1,
|
||||
Stdout =.. Stdout1,
|
||||
Stderr =.. Stderr1,
|
||||
simplify_env(Env, Env1),
|
||||
'$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Process).
|
||||
|
||||
%% process_id(+Process, -Pid).
|
||||
%
|
||||
process_id(Process, Pid) :- call_with_error_context(process_id_(Process, Pid), predicate-process_id/2).
|
||||
|
||||
process_id_(Process, Pid) :-
|
||||
valid_process(Process),
|
||||
must_be(var, Pid),
|
||||
'$process_id'(Process, Pid).
|
||||
|
||||
%% process_wait(+Process, Status).
|
||||
%
|
||||
% See `process_create/3` with `Options = []`
|
||||
%
|
||||
process_wait(Process, Status) :- call_with_error_context(process_wait(Process, Status, []), predicate-process_wait/2).
|
||||
|
||||
|
||||
%% process_wait(+Process, Status, Options).
|
||||
%
|
||||
% Wait for the process behind the process handle `Process` to exit.
|
||||
%
|
||||
% When the process exits regulary `Status` will be unified with `exit(Exit)` where `Exit` is the processes exit code.
|
||||
% When the process exits was killed `Status` will be unified with `killed(Signal)` where `Signal` is the signal number that killed the process.
|
||||
% When the process doesn't exit before the timeout `Status` will be unified with `timeout`.
|
||||
%
|
||||
% `Options` is a a list of the following options
|
||||
%
|
||||
% * timeout(Timeout) supported values for `Timeout` are 0 or `infinite`
|
||||
%
|
||||
% Each options may be specified at most once, when an option is not specified the following defaults apply:
|
||||
%
|
||||
% - timeout(infinite)
|
||||
%
|
||||
process_wait(Process, Status, Options) :- call_with_error_context(process_wait_(Process, Status, Options), predicate-process_wait/3).
|
||||
|
||||
process_wait_(Process, Status, Options) :-
|
||||
valid_process(Process),
|
||||
check_options(
|
||||
[
|
||||
option([timeout], valid_timeout, timeout(infinite), timeout(Timeout))
|
||||
],
|
||||
Options,
|
||||
process_wait_option
|
||||
),
|
||||
'$process_wait'(Process, Exit, Timeout),
|
||||
Exit = Status.
|
||||
|
||||
valid_timeout(timeout(infinite)).
|
||||
valid_timeout(timeout(0)).
|
||||
|
||||
|
||||
%% process_kill(+Process).
|
||||
%
|
||||
% Kill the process using the process handle `Process`.
|
||||
% On Unix this sends SIGKILL.
|
||||
%
|
||||
% Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1`
|
||||
%
|
||||
process_kill(Process) :- call_with_error_context(process_kill_(Process), predicate-process_kill/1).
|
||||
|
||||
process_kill_(Process) :-
|
||||
valid_process(Process),
|
||||
'$process_kill'(Process).
|
||||
|
||||
%% process_release(+Process)
|
||||
%
|
||||
% wait for the process to exit (if not already) and release process handle `Process`
|
||||
%
|
||||
% It's an error if `Process` is not a valid process handle
|
||||
%
|
||||
process_release(Process) :- call_with_error_context(process_release_(Process), predicate-process_release/1).
|
||||
|
||||
process_release_(Process) :-
|
||||
valid_process(Process),
|
||||
process_wait(Process, _),
|
||||
'$process_release'(Process).
|
||||
|
||||
|
||||
must_be_known_options(Valid, Options, Domain) :- call_with_error_context(must_be_known_options_(Valid, [], Options, Domain),predicate-must_be_known_options/3).
|
||||
|
||||
must_be_known_options_(_, _, [], _).
|
||||
must_be_known_options_(Valid, Found, [X|XS], Domain) :-
|
||||
( functor(X, Option, 1) -> true
|
||||
; domain_error(Domain, X, [])
|
||||
) ,
|
||||
( member(Option, Found) -> domain_error(non_duplicate_options, Option , [])
|
||||
; member(Option, Valid) -> true
|
||||
; domain_error(Domain, Option, [])
|
||||
),
|
||||
must_be_known_options_(Valid, [Option | Found], XS, Domain).
|
||||
|
||||
check_options(KnownOptions, Options, Domain) :- call_with_error_context(check_options_(KnownOptions, Options, Domain), predicate-check_options/3).
|
||||
|
||||
check_options_(KnownOptions, Options, Domain) :-
|
||||
maplist(option_names, KnownOptions, Namess),
|
||||
append(Namess, Names),
|
||||
must_be_known_options(Names, Options, Domain),
|
||||
extract_options(KnownOptions, Options).
|
||||
|
||||
option_names(option(Names,_,_,_), Names).
|
||||
|
||||
extract_options(KnownOptions, Options) :- call_with_error_context(extract_options_(KnownOptions, Options), predicate-extract_options/2).
|
||||
|
||||
extract_options_([], _).
|
||||
extract_options_([X | XS], Options) :-
|
||||
option(Kinds, Pred, Default, Choice) = X,
|
||||
tfilter(find_option(Kinds), Options, Solutions),
|
||||
( Solutions = [] -> Choice = Default
|
||||
; Solutions = [Provided] -> functor(Pred, Name, Arity), ArityP1 is Arity+1, call_with_error_context(call(Pred, Provided),predicate-Name/ArityP1), Choice = Provided
|
||||
; domain_error(non_conflicting_options, Solutions, [])
|
||||
),
|
||||
extract_options_(XS, Options).
|
||||
|
||||
find_option(Names, Found, T) :-
|
||||
functor(Found, Name, 1),
|
||||
memberd_t(Name, Names, T).
|
||||
|
||||
valid_stdio(IO) :- arg(1, IO, Arg),
|
||||
( var(Arg) -> instantiation_error([])
|
||||
; valid_stdio_(Arg) -> true
|
||||
; domain_error(stdio_spec, Arg, [])
|
||||
).
|
||||
|
||||
valid_stdio_(std).
|
||||
valid_stdio_(null).
|
||||
valid_stdio_(pipe(Stream)) :- must_be(var, Stream).
|
||||
valid_stdio_(file(Path)) :- must_be(chars, Path).
|
||||
|
||||
valid_env(env(E)) :-
|
||||
must_be(list, E),
|
||||
( valid_env_(E) -> true
|
||||
; domain_error(process_create_option, env(E), [])
|
||||
).
|
||||
valid_env(environment(E)) :-
|
||||
must_be(list, E),
|
||||
( valid_env_(E) -> true
|
||||
; domain_error(process_create_option, environment(E), [])
|
||||
).
|
||||
|
||||
valid_env_([]).
|
||||
valid_env_([N=V|ES]) :-
|
||||
must_be(chars, N),
|
||||
must_be(chars, V),
|
||||
valid_env_(ES).
|
||||
|
||||
valid_uninit_process(process(Process)) :- must_be(var, Process).
|
||||
|
||||
valid_process(Process) :- var(Process) -> instantiation_error([]) ; true.
|
||||
|
||||
valid_cwd(cwd(Cwd)) :- must_be(chars, Cwd).
|
||||
|
||||
simplify_env(E, [Kind, Envs1]) :- E =.. [Kind, Envs], simplify_env_(Envs, Envs1).
|
||||
|
||||
simplify_env_([],[]).
|
||||
simplify_env_([N=V|E],[[N, V]|E1]) :- simplify_env_(E, E1).
|
||||
@@ -15,16 +15,14 @@ the random seed. This makes everything completely reproducible.
|
||||
% Succeeds with probability 0.5.
|
||||
maybe :- '$maybe'.
|
||||
|
||||
% The higher the precision, the slower it gets.
|
||||
random_number_precision(64).
|
||||
|
||||
%% random(-R).
|
||||
%
|
||||
% Generates a random floating number between 0 (inclusive) and 1 (exclusive).
|
||||
random(R) :-
|
||||
var(R),
|
||||
random_number_precision(N),
|
||||
rnd(N, R).
|
||||
N is 2^50,
|
||||
'$random_integer'(0, N, K),
|
||||
R is K/N.
|
||||
|
||||
%% random_integer(+Lower, +Upper, -R).
|
||||
%
|
||||
@@ -41,25 +39,10 @@ random_integer(Lower, Upper, R) :-
|
||||
type_error(integer, Lower, random_integer/3)
|
||||
; \+ integer(Upper) ->
|
||||
type_error(integer, Upper, random_integer/3)
|
||||
; Upper > Lower,
|
||||
random(R0),
|
||||
R is floor((Upper - Lower) * R0 + Lower)
|
||||
; Lower < Upper,
|
||||
'$random_integer'(Lower, Upper, R)
|
||||
).
|
||||
|
||||
rnd(N, R) :-
|
||||
rnd_(N, 0, R).
|
||||
|
||||
rnd_(0, R, R) :- !.
|
||||
rnd_(N, R0, R) :-
|
||||
maybe,
|
||||
!,
|
||||
N1 is N - 1,
|
||||
rnd_(N1, R0, R).
|
||||
rnd_(N, R0, R) :-
|
||||
N1 is N - 1,
|
||||
R1 is R0 + 1.0 / 2.0 ^ N,
|
||||
rnd_(N1, R1, R).
|
||||
|
||||
%% set_random(+Seed).
|
||||
%
|
||||
% Sets a seed that will be used for subsequent random generations in this library.
|
||||
|
||||
@@ -96,14 +96,14 @@ is_sgml_source([]).
|
||||
is_sgml_source([C|Cs]) :- must_be(chars, [C|Cs]).
|
||||
|
||||
load_structure_([], [], _, _).
|
||||
load_structure_([C|Cs], [E], Options, What) :-
|
||||
load_(What, [C|Cs], E, Options).
|
||||
load_structure_(file(Fs), [E], Options, What) :-
|
||||
load_structure_([C|Cs], [E|Es], Options, What) :-
|
||||
load_(What, [C|Cs], [E|Es], Options).
|
||||
load_structure_(file(Fs), [E|Es], Options, What) :-
|
||||
once(phrase_from_file(seq(Cs), Fs)),
|
||||
load_(What, Cs, E, Options).
|
||||
load_structure_(stream(Stream), [E], Options, What) :-
|
||||
load_(What, Cs, [E|Es], Options).
|
||||
load_structure_(stream(Stream), [E|Es], Options, What) :-
|
||||
get_n_chars(Stream, _, Cs),
|
||||
load_(What, Cs, E, Options).
|
||||
load_(What, Cs, [E|Es], Options).
|
||||
|
||||
load_(html, Cs, E, Options) :- '$load_html'(Cs, E, Options).
|
||||
load_(xml, Cs, E, Options) :- '$load_xml'(Cs, E, Options).
|
||||
|
||||
@@ -37,8 +37,10 @@
|
||||
atomic_si/1,
|
||||
list_si/1,
|
||||
character_si/1,
|
||||
term_si/1,
|
||||
chars_si/1,
|
||||
dif_si/2]).
|
||||
dif_si/2,
|
||||
when_si/2]).
|
||||
|
||||
:- use_module(library(lists)).
|
||||
|
||||
@@ -68,6 +70,11 @@ character_si(Ch) :-
|
||||
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
|
||||
@@ -92,3 +99,31 @@ dif_si(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).
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Tabling, also called SLG resolution.
|
||||
|
||||
SLG resolution is an alternative execution strategy that sometimes
|
||||
helps to improve termination and performance characters of Prolog
|
||||
helps to improve termination and performance characteristics of Prolog
|
||||
predicates.
|
||||
|
||||
To enable this execution strategy for a Prolog predicate, add a
|
||||
@@ -93,7 +93,7 @@ table_and_status_for_variant(V,T,S) :-
|
||||
tbd_table_status(T,S).
|
||||
|
||||
|
||||
:- meta_predicate start_tabling(?, :).
|
||||
:- meta_predicate(start_tabling(?, :)).
|
||||
|
||||
start_tabling(Wrapper,Worker) :-
|
||||
put_new_trie_table_link,
|
||||
|
||||
100
src/lib/time.pl
100
src/lib/time.pl
@@ -1,12 +1,18 @@
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Written 2020-2023 by Markus Triska (triska@metalevel.at)
|
||||
Written 2020-2024 by Markus Triska (triska@metalevel.at)
|
||||
Part of Scryer Prolog.
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
/** This library provides predicates for reasoning about time.
|
||||
*/
|
||||
|
||||
:- module(time, [max_sleep_time/1, sleep/1, time/1, current_time/1, format_time//2]).
|
||||
:- module(time, [max_sleep_time/1,
|
||||
sleep/1,
|
||||
time/1,
|
||||
current_time/1,
|
||||
format_time//2,
|
||||
statistics/2
|
||||
]).
|
||||
|
||||
:- use_module(library(format)).
|
||||
:- use_module(library(iso_ext)).
|
||||
@@ -91,12 +97,21 @@ sleep(T) :-
|
||||
).
|
||||
|
||||
|
||||
% '$cpu_now' can be replaced by statistics/2 once that is implemented.
|
||||
%% statistics(?Keyword, ?List)
|
||||
%
|
||||
% Preliminary support for statistics/2, yielding timing information.
|
||||
% The only supported `Keyword` is `runtime`. The first element of
|
||||
% `List` is the CPU time in milliseconds, the second element is
|
||||
% currently not supported.
|
||||
|
||||
:- meta_predicate time(0).
|
||||
statistics(runtime, [T,unsupported]) :-
|
||||
'$cpu_now'(T0),
|
||||
T is T0*1000.
|
||||
|
||||
:- meta_predicate(time(0)).
|
||||
|
||||
:- dynamic(time_id/1).
|
||||
:- dynamic(time_state/2).
|
||||
:- dynamic(time_state/3).
|
||||
|
||||
time_next_id(N) :-
|
||||
( retract(time_id(N0)) ->
|
||||
@@ -111,9 +126,9 @@ time_next_id(N) :-
|
||||
% 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),
|
||||
@@ -123,49 +138,72 @@ time(Goal) :-
|
||||
; report_time(ID),
|
||||
false
|
||||
),
|
||||
retract(time_state(ID, _))).
|
||||
retract(time_state(ID, _, _))).
|
||||
|
||||
cputime_inferences(T, I) :-
|
||||
'$cpu_now'(T),
|
||||
'$inference_count'(I).
|
||||
|
||||
time_true(ID) :-
|
||||
report_time(ID).
|
||||
time_true(ID) :-
|
||||
% on backtracking, update the stored CPU time for this ID
|
||||
retract(time_state(ID, _)),
|
||||
'$cpu_now'(T0),
|
||||
asserta(time_state(ID, T0)),
|
||||
retract(time_state(ID, _, _)),
|
||||
cputime_inferences(T0, I0),
|
||||
asserta(time_state(ID, T0, I0)),
|
||||
false.
|
||||
|
||||
report_time(ID) :-
|
||||
time_state(ID, T0),
|
||||
'$cpu_now'(T),
|
||||
time_state(ID, T0, I0),
|
||||
cputime_inferences(T, I),
|
||||
Time is T - T0,
|
||||
Inferences0 is I - I0,
|
||||
% we must subtract the number of inferences that time/1 itself takes;
|
||||
% this may have to be adapted if the implementation changes,
|
||||
% so that (for example) true/1 takes exactly 1 inference.
|
||||
( bb_get('$answer_count', 0) ->
|
||||
Inferences is Inferences0 - 60,
|
||||
Pre = " ", Post = ""
|
||||
; Pre = "", Post = " "
|
||||
; Inferences is Inferences0 - 9,
|
||||
Pre = "", Post = " "
|
||||
),
|
||||
format("~s% CPU time: ~3fs~n~s", [Pre,Time,Post]).
|
||||
phrase((Pre,"% CPU time: ", format_("~3f", [Time]), "s, ",
|
||||
format_("~U", [Inferences])," inference",s_if_necessary(Inferences),"\n",
|
||||
Post), Cs),
|
||||
format("~s", [Cs]).
|
||||
|
||||
s_if_necessary(Inferences) -->
|
||||
{ compare(C, 1, Inferences) },
|
||||
s_(C).
|
||||
|
||||
s_(=) --> "".
|
||||
s_(<) --> "s".
|
||||
s_(>) --> " (exception?)".
|
||||
|
||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
?- time((true;false)).
|
||||
%@ % CPU time: 0.006s
|
||||
%@ true
|
||||
%@ ; % CPU time: 0.001s
|
||||
%@ false.
|
||||
% CPU time: 0.000s, 1 inference
|
||||
true
|
||||
; % CPU time: 0.000s, 0 inference (exception?)
|
||||
false.
|
||||
|
||||
:- time(use_module(library(clpz))).
|
||||
%@ % CPU time: 3.711s
|
||||
%@ true.
|
||||
% CPU time: 0.343s, 409_874 inferences
|
||||
true.
|
||||
|
||||
:- time(use_module(library(lists))).
|
||||
%@ % CPU time: 0.006s
|
||||
%@ true.
|
||||
% CPU time: 0.000s, 19 inferences
|
||||
true.
|
||||
|
||||
?- time(member(X, "abc")).
|
||||
%@ % CPU time: 0.005s
|
||||
%@ X = a
|
||||
%@ ; % CPU time: 0.000s
|
||||
%@ X = b
|
||||
%@ ; % CPU time: 0.000s
|
||||
%@ X = c
|
||||
%@ ; % CPU time: 0.000s
|
||||
%@ false.
|
||||
% CPU time: 0.000s, 1 inference
|
||||
X = a
|
||||
; % CPU time: 0.000s, 3 inferences
|
||||
X = b
|
||||
; % CPU time: 0.000s, 3 inferences
|
||||
X = c.
|
||||
|
||||
?- time((repeat,false)).
|
||||
% CPU time: 2.726s, 53_330_502 inferences
|
||||
error('$interrupt_thrown',repl/0).
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||
|
||||
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).
|
||||
107
src/lib/when.pl
Normal file
107
src/lib/when.pl
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
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.
|
||||
% Condition may consist of `ground(T)`, `nonvar(T)`, `C1,C2`, `C1;C2`.
|
||||
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(_)) }.
|
||||
394
src/loader.pl
394
src/loader.pl
File diff suppressed because it is too large
Load Diff
@@ -14,3 +14,9 @@ impl MachineArgs {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MachineArgs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use dashu::base::{Abs, Gcd, Signed, UnsignedAbs};
|
||||
use dashu::integer::IBig;
|
||||
use dashu::integer::fast_div::ConstDivisor;
|
||||
use dashu::integer::IBig;
|
||||
use divrem::*;
|
||||
use num_order::NumOrd;
|
||||
|
||||
@@ -11,20 +11,18 @@ use crate::forms::*;
|
||||
use crate::heap_iter::*;
|
||||
use crate::machine::machine_errors::*;
|
||||
use crate::machine::machine_state::*;
|
||||
use crate::offset_table::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::parser::dashu::{Integer, Rational};
|
||||
use crate::types::*;
|
||||
|
||||
use crate::fixnum;
|
||||
|
||||
use ordered_float::*;
|
||||
use ordered_float::{Float, OrderedFloat};
|
||||
|
||||
use std::cmp;
|
||||
use std::convert::TryFrom;
|
||||
use std::f64;
|
||||
use std::mem;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! try_numeric_result {
|
||||
($e: expr, $stub_gen: expr) => {
|
||||
match $e {
|
||||
@@ -51,7 +49,7 @@ macro_rules! drop_iter_on_err {
|
||||
};
|
||||
}
|
||||
|
||||
fn zero_divisor_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> MachineStubGen {
|
||||
fn zero_divisor_eval_error(stub_gen: impl Fn() -> MachineStub + 'static) -> MachineStubGen {
|
||||
Box::new(move |machine_st| {
|
||||
let eval_error = machine_st.evaluation_error(EvalError::ZeroDivisor);
|
||||
let stub = stub_gen();
|
||||
@@ -60,7 +58,7 @@ fn zero_divisor_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> Mach
|
||||
})
|
||||
}
|
||||
|
||||
fn undefined_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> MachineStubGen {
|
||||
fn undefined_eval_error(stub_gen: impl Fn() -> MachineStub + 'static) -> MachineStubGen {
|
||||
Box::new(move |machine_st| {
|
||||
let eval_error = machine_st.evaluation_error(EvalError::Undefined);
|
||||
let stub = stub_gen();
|
||||
@@ -72,7 +70,7 @@ fn undefined_eval_error(stub_gen: impl Fn() -> FunctorStub + 'static) -> Machine
|
||||
fn numerical_type_error(
|
||||
valid_type: ValidType,
|
||||
n: Number,
|
||||
stub_gen: impl Fn() -> FunctorStub + 'static,
|
||||
stub_gen: impl Fn() -> MachineStub + 'static,
|
||||
) -> MachineStubGen {
|
||||
Box::new(move |machine_st| {
|
||||
let type_error = machine_st.type_error(valid_type, n);
|
||||
@@ -84,18 +82,18 @@ fn numerical_type_error(
|
||||
|
||||
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;
|
||||
|
||||
@@ -115,9 +113,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;
|
||||
@@ -202,11 +198,10 @@ pub(crate) fn neg(n: Number, arena: &mut Arena) -> Number {
|
||||
pub(crate) fn abs(n: Number, arena: &mut Arena) -> Number {
|
||||
match n {
|
||||
Number::Fixnum(n) => {
|
||||
if let Some(n) = n.get_num().checked_abs() {
|
||||
fixnum!(Number, n, arena)
|
||||
if let Some(n) = n.checked_abs() {
|
||||
Number::Fixnum(n)
|
||||
} else {
|
||||
let arena_int = Integer::from(n.get_num());
|
||||
Number::arena_from(arena_int.abs(), arena)
|
||||
Number::arena_from(Integer::from(Fixnum::MAX + 1), arena)
|
||||
}
|
||||
}
|
||||
Number::Integer(n) => {
|
||||
@@ -350,22 +345,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 < &Integer::from(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 == &Integer::from(1)
|
||||
|| &*n1 == &Integer::from(0)
|
||||
|| &*n1 == &Integer::from(-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 {
|
||||
@@ -374,15 +365,11 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if !(&*n1 == &Integer::from(1)
|
||||
|| &*n1 == &Integer::from(0)
|
||||
|| &*n1 == &Integer::from(-1))
|
||||
&& &*n2 < &Integer::from(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)) => {
|
||||
@@ -455,26 +442,21 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
if (&*n2).num_gt(&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).num_gt(&n2.get_num()) {
|
||||
if (*n1).num_gt(&n2.get_num()) {
|
||||
Ok(Number::Integer(n1))
|
||||
} else {
|
||||
Ok(Number::Fixnum(n2))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if n1 > n2 {
|
||||
Ok(Number::Integer(n1))
|
||||
} else {
|
||||
Ok(Number::Integer(n2))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => Ok(Number::Integer(cmp::max(n1, n2))),
|
||||
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::Rational(cmp::max(r1, r2))),
|
||||
(n1, n2) => {
|
||||
let stub_gen = || {
|
||||
let max_atom = atom!("max");
|
||||
@@ -484,7 +466,15 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
|
||||
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;
|
||||
|
||||
Ok(Number::Float(cmp::max(OrderedFloat(f1), OrderedFloat(f2))))
|
||||
match OrderedFloat(f1).cmp(&OrderedFloat(f2)) {
|
||||
cmp::Ordering::Less => Ok(n2),
|
||||
cmp::Ordering::Equal => {
|
||||
// Note: n1 and n2 were compared as floats,
|
||||
// so we return the second argument as a floating point value.
|
||||
Ok(Number::Float(OrderedFloat(f2)))
|
||||
}
|
||||
cmp::Ordering::Greater => Ok(n1),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,26 +489,21 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
if (&*n2).num_lt(&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).num_lt(&n2.get_num()) {
|
||||
if (*n1).num_lt(&n2.get_num()) {
|
||||
Ok(Number::Integer(n1))
|
||||
} else {
|
||||
Ok(Number::Fixnum(n2))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if n1 < n2 {
|
||||
Ok(Number::Integer(n1))
|
||||
} else {
|
||||
Ok(Number::Integer(n2))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => Ok(Number::Integer(cmp::min(n1, n2))),
|
||||
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::Rational(cmp::min(r1, r2))),
|
||||
(n1, n2) => {
|
||||
let stub_gen = || {
|
||||
let min_atom = atom!("min");
|
||||
@@ -528,20 +513,28 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
|
||||
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
|
||||
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;
|
||||
|
||||
Ok(Number::Float(cmp::min(OrderedFloat(f1), OrderedFloat(f2))))
|
||||
match OrderedFloat(f1).cmp(&OrderedFloat(f2)) {
|
||||
cmp::Ordering::Less => Ok(n1),
|
||||
cmp::Ordering::Equal => {
|
||||
// Note: n1 and n2 were compared as floats,
|
||||
// so we return the first argument as a floating point value.
|
||||
Ok(Number::Float(OrderedFloat(f1)))
|
||||
}
|
||||
cmp::Ordering::Greater => Ok(n2),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rational_from_number(
|
||||
n: Number,
|
||||
stub_gen: impl Fn() -> FunctorStub + 'static,
|
||||
stub_gen: impl Fn() -> MachineStub + 'static,
|
||||
arena: &mut Arena,
|
||||
) -> Result<TypedArenaPtr<Rational>, MachineStubGen> {
|
||||
match n {
|
||||
Number::Fixnum(n) => Ok(arena_alloc!(Rational::from(n.get_num()), arena)),
|
||||
Number::Rational(r) => Ok(r),
|
||||
Number::Float(OrderedFloat(f)) => match Rational::simplest_from_f64(f) {
|
||||
Number::Float(OrderedFloat(f)) => match Rational::try_from(f).ok() {
|
||||
Some(r) => Ok(arena_alloc!(r, arena)),
|
||||
None => Err(Box::new(move |machine_st| {
|
||||
let instantiation_error = machine_st.instantiation_error();
|
||||
@@ -583,15 +576,13 @@ 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)) => {
|
||||
@@ -638,123 +629,110 @@ pub(crate) fn int_floor_div(
|
||||
idiv(n1, n2, arena)
|
||||
}
|
||||
|
||||
pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
pub(crate) fn shr(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
let stub_gen = || {
|
||||
let shr_atom = atom!(">>");
|
||||
functor_stub(shr_atom, 2)
|
||||
};
|
||||
|
||||
if n2.is_integer() && n2.is_negative() {
|
||||
return shl(n1, neg(n2, arena), arena);
|
||||
if rhs.is_integer() && rhs.is_negative() {
|
||||
return shl(lhs, neg(rhs, arena), arena);
|
||||
}
|
||||
|
||||
match (n1, n2) {
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
let n2_i = n2.get_num();
|
||||
match lhs {
|
||||
Number::Fixnum(lhs) => {
|
||||
let rhs = match rhs {
|
||||
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(u32::MAX),
|
||||
Number::Integer(int) => (&*int).try_into().unwrap_or(u32::MAX),
|
||||
other => {
|
||||
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
|
||||
}
|
||||
};
|
||||
|
||||
let n1 = Integer::from(n1_i);
|
||||
|
||||
if let Ok(n2) = usize::try_from(n2_i) {
|
||||
return Ok(Number::arena_from(n1 >> n2, arena));
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 >> usize::max_value(), arena));
|
||||
}
|
||||
let res = lhs.get_num().checked_shr(rhs).unwrap_or(0);
|
||||
Ok(Number::arena_from(res, arena))
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
|
||||
let result: Result<usize, _> = (&*n2).try_into();
|
||||
|
||||
match result {
|
||||
Ok(n2) => {
|
||||
Ok(Number::arena_from(n1 >> n2, arena))
|
||||
Number::Integer(lhs) => {
|
||||
// Note: bigints require `log(n)` bits of space. If `rhs > usize::MAX`,
|
||||
// then this clamping only becomes an issue when `lhs < 2 ^ (usize::MAX)`:
|
||||
// - on 32-bit systems, `lhs` would need to be 512MiB big (1/8th of the addressable memory)
|
||||
// - on 64-bit systems, `lhs` would need to be 2EiB big (!!!)
|
||||
let rhs = match rhs {
|
||||
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(usize::MAX),
|
||||
Number::Integer(int) => (&*int).try_into().unwrap_or(usize::MAX),
|
||||
other => {
|
||||
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
|
||||
}
|
||||
Err(_) => {
|
||||
Ok(Number::arena_from(n1 >> usize::max_value(), arena))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Number::arena_from(Integer::from(&*lhs >> rhs), arena))
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 >> usize::max_value()),
|
||||
arena,
|
||||
)),
|
||||
},
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
let result: Result<usize, _> = (&*n2).try_into();
|
||||
|
||||
match result {
|
||||
Ok(n2) => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena))
|
||||
}
|
||||
Err(_) => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 >> usize::max_value()), arena))
|
||||
}
|
||||
}
|
||||
},
|
||||
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(n1, _) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
|
||||
other => Err(numerical_type_error(ValidType::Integer, other, stub_gen)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
pub(crate) fn shl(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
let stub_gen = || {
|
||||
let shl_atom = atom!("<<");
|
||||
functor_stub(shl_atom, 2)
|
||||
};
|
||||
|
||||
if n2.is_integer() && n2.is_negative() {
|
||||
return shr(n1, neg(n2, arena), arena);
|
||||
if rhs.is_integer() && rhs.is_negative() {
|
||||
return shr(lhs, neg(rhs, arena), arena);
|
||||
}
|
||||
|
||||
match (n1, n2) {
|
||||
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
|
||||
let n1_i = n1.get_num();
|
||||
let n2_i = n2.get_num();
|
||||
let rhs = match rhs {
|
||||
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(usize::MAX),
|
||||
Number::Integer(int) => (&*int).try_into().unwrap_or(usize::MAX),
|
||||
other => {
|
||||
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
|
||||
}
|
||||
};
|
||||
|
||||
let n1 = Integer::from(n1_i);
|
||||
match lhs {
|
||||
Number::Fixnum(lhs) => {
|
||||
let lhs = lhs.get_num();
|
||||
|
||||
if let Ok(n2) = usize::try_from(n2_i) {
|
||||
return Ok(Number::arena_from(n1 << n2, arena));
|
||||
if let Some(res) = checked_signed_shl(lhs, rhs) {
|
||||
Ok(Number::arena_from(res, arena))
|
||||
} else {
|
||||
return Ok(Number::arena_from(n1 << usize::max_value(), arena));
|
||||
let lhs = Integer::from(lhs);
|
||||
Ok(Number::arena_from(
|
||||
Integer::from(lhs << (rhs as usize)),
|
||||
arena,
|
||||
))
|
||||
}
|
||||
}
|
||||
(Number::Fixnum(n1), Number::Integer(n2)) => {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
Number::Integer(lhs) => Ok(Number::arena_from(
|
||||
Integer::from(&*lhs << (rhs as usize)),
|
||||
arena,
|
||||
)),
|
||||
other => Err(numerical_type_error(ValidType::Integer, other, stub_gen)),
|
||||
}
|
||||
}
|
||||
|
||||
match (&*n2).try_into() as Result<u32, _> {
|
||||
Ok(n2) => {
|
||||
let n1: u64 = n1.try_into().unwrap();
|
||||
Ok(Number::arena_from(n1 << n2, arena))
|
||||
},
|
||||
_ => {
|
||||
Ok(Number::arena_from(n1 << usize::max_value(), arena))
|
||||
}
|
||||
}
|
||||
/// Returns `x << shift`, checking for overflow and for values of `shift` that are too big.
|
||||
#[inline]
|
||||
fn checked_signed_shl(x: i64, shift: usize) -> Option<i64> {
|
||||
if shift == 0 {
|
||||
return Some(x);
|
||||
}
|
||||
|
||||
if x >= 0 {
|
||||
// Note: for unsigned integers, the condition would usually be spelled
|
||||
// `shift <= x.leading_zeros()`, but since the MSB for signed integers
|
||||
// controls the sign, we need to make sure that `shift` is at most
|
||||
// `x.leading_zeros() - 1`.
|
||||
if shift < x.leading_zeros() as usize {
|
||||
Some(x << shift)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
|
||||
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
|
||||
_ => Ok(Number::arena_from(
|
||||
Integer::from(&*n1 << usize::max_value()),
|
||||
arena,
|
||||
)),
|
||||
},
|
||||
(Number::Integer(n1), Number::Integer(n2)) => match (&*n2).try_into() as Result<u32, _> {
|
||||
Ok(n2) => {
|
||||
let n1: u64 = (&*n1).try_into().unwrap();
|
||||
Ok(Number::arena_from(Integer::from(n1 << n2), arena))
|
||||
},
|
||||
_ => {
|
||||
Ok(Number::arena_from(Integer::from(&*n1 << usize::max_value()),arena))
|
||||
}
|
||||
},
|
||||
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
|
||||
(n1, _) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
|
||||
} else {
|
||||
let y = x.checked_neg()?;
|
||||
// FIXME: incorrectly rejects `-2 ^ 62 << 1`. This is currently a non-issue,
|
||||
// since the bitshift is then done as a `Number::Integer`
|
||||
checked_signed_shl(y, shift).and_then(|res| res.checked_neg())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,7 +860,7 @@ pub(crate) fn modulus(x: Number, y: Number, arena: &mut Arena) -> Result<Number,
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
let n1 = Integer::from(n1.get_num());
|
||||
Ok(Number::arena_from(ibig_rem_floor(&n1, &*n2), arena))
|
||||
Ok(Number::arena_from(ibig_rem_floor(&n1, &n2), arena))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Fixnum(n2)) => {
|
||||
@@ -892,14 +870,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(ibig_rem_floor(&*n1, &n2), arena))
|
||||
Ok(Number::arena_from(ibig_rem_floor(&n1, &n2), arena))
|
||||
}
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
if n2.is_zero() {
|
||||
Err(zero_divisor_eval_error(stub_gen))
|
||||
} else {
|
||||
Ok(Number::arena_from(ibig_rem_floor(&*n1, &*n2), arena))
|
||||
Ok(Number::arena_from(ibig_rem_floor(&n1, &n2), arena))
|
||||
}
|
||||
}
|
||||
(Number::Integer(_), n2) | (Number::Fixnum(_), n2) => {
|
||||
@@ -982,8 +960,7 @@ pub(crate) fn gcd(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
|
||||
Ok(Number::arena_from(Integer::from(n2_clone.gcd(&n1)), arena))
|
||||
}
|
||||
(Number::Integer(n1), Number::Integer(n2)) => {
|
||||
let n2: isize = (&*n2).try_into().unwrap();
|
||||
let value: Integer = (&*n1).gcd(&Integer::from(n2)).into();
|
||||
let value: Integer = (&*n1).gcd(&*n2).into();
|
||||
Ok(Number::arena_from(value, arena))
|
||||
}
|
||||
(Number::Float(f), _) | (_, Number::Float(f)) => {
|
||||
@@ -1079,7 +1056,12 @@ pub(crate) fn sqrt(n1: Number) -> Result<f64, MachineStubGen> {
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn floor(n1: Number, arena: &mut Arena) -> Number {
|
||||
rnd_i(&n1, arena)
|
||||
rnd_i(&n1, arena).unwrap_or_else(|_err| {
|
||||
// FIXME: Currently floor/1 (and several call sites) are infallible,
|
||||
// but the failing cases (when `n1` is `Number::Float(NAN)` or `Number::Float(INFINITY)`)
|
||||
// are not reachable with standard is/2 operations.
|
||||
todo!("Make floor/1 fallible");
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -1102,21 +1084,27 @@ pub(crate) fn truncate(n: Number, arena: &mut Arena) -> Number {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn round(n: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
let stub_gen = || {
|
||||
let is_atom = atom!("is");
|
||||
functor_stub(is_atom, 2)
|
||||
pub(crate) fn round(num: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
let res = match num {
|
||||
Number::Fixnum(_) | Number::Integer(_) => num,
|
||||
Number::Rational(rat) => Number::arena_from(rat.round(), arena),
|
||||
Number::Float(f) => Number::Float(OrderedFloat((*f).round())),
|
||||
};
|
||||
|
||||
let result = add(n, Number::Float(OrderedFloat(0.5f64)), arena);
|
||||
let result = try_numeric_result!(result, stub_gen)?;
|
||||
// FIXME: make round/1 return EvalError
|
||||
rnd_i(&res, arena).map_err(|err| -> MachineStubGen {
|
||||
Box::new(move |machine_st| {
|
||||
let eval_error = machine_st.evaluation_error(err);
|
||||
let stub = functor_stub(atom!("round"), 1);
|
||||
|
||||
Ok(floor(result, arena))
|
||||
machine_st.error_form(eval_error, stub)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn bitwise_complement(n1: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
|
||||
match n1 {
|
||||
Number::Fixnum(n) => Ok(Number::Fixnum(Fixnum::build_with(!n.get_num()))),
|
||||
Number::Fixnum(n) => Ok(Number::Fixnum(!n)),
|
||||
Number::Integer(n1) => Ok(Number::arena_from(Integer::from(!&*n1), arena)),
|
||||
_ => {
|
||||
let stub_gen = || {
|
||||
@@ -1136,23 +1124,26 @@ impl MachineState {
|
||||
&ArithmeticTerm::Reg(r) => {
|
||||
let value = self.store(self.deref(self[r]));
|
||||
|
||||
match Number::try_from(value) {
|
||||
match Number::try_from((value, &self.arena.f64_tbl)) {
|
||||
Ok(n) => Ok(n),
|
||||
Err(_) => self.arith_eval_by_metacall(value),
|
||||
Err(_) => {
|
||||
self.heap[0] = value;
|
||||
self.arith_eval_by_metacall(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
&ArithmeticTerm::Interm(i) => Ok(mem::replace(
|
||||
&mut self.interms[i - 1],
|
||||
Number::Fixnum(Fixnum::build_with(0)),
|
||||
)),
|
||||
&ArithmeticTerm::Number(n) => Ok(n),
|
||||
ArithmeticTerm::Number(n) => Ok(*n),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rational(
|
||||
&mut self,
|
||||
at: &ArithmeticTerm,
|
||||
caller: impl Fn() -> FunctorStub + 'static,
|
||||
caller: impl Fn() -> MachineStub + 'static,
|
||||
) -> Result<TypedArenaPtr<Rational>, MachineStub> {
|
||||
let n = self.get_number(at)?;
|
||||
|
||||
@@ -1164,11 +1155,11 @@ impl MachineState {
|
||||
|
||||
pub(crate) fn arith_eval_by_metacall(
|
||||
&mut self,
|
||||
value: HeapCellValue,
|
||||
term_loc: usize,
|
||||
) -> Result<Number, MachineStub> {
|
||||
let stub_gen = || functor_stub(atom!("is"), 2);
|
||||
let mut iter = stackful_post_order_iter::<NonListElider>
|
||||
(&mut self.heap, &mut self.stack, value);
|
||||
let mut iter =
|
||||
stackful_post_order_iter::<NonListElider>(&mut self.heap, &mut self.stack, term_loc);
|
||||
|
||||
while let Some(value) = iter.next() {
|
||||
if value.get_forwarding_bit() {
|
||||
@@ -1181,7 +1172,7 @@ impl MachineState {
|
||||
(HeapCellValueTag::Str, s) => {
|
||||
cell_as_atom_cell!(self.heap[s]).get_name_and_arity()
|
||||
}
|
||||
(HeapCellValueTag::Lis | HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset |
|
||||
(HeapCellValueTag::Lis | // HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset |
|
||||
HeapCellValueTag::PStrLoc) => {
|
||||
(atom!("."), 2)
|
||||
}
|
||||
@@ -1397,8 +1388,9 @@ impl MachineState {
|
||||
(HeapCellValueTag::Fixnum, n) => {
|
||||
self.interms.push(Number::Fixnum(n));
|
||||
}
|
||||
(HeapCellValueTag::F64, fl) => {
|
||||
self.interms.push(Number::Float(*fl));
|
||||
(HeapCellValueTag::F64Offset, offset) => {
|
||||
let fl = self.arena.f64_tbl.get_entry(offset);
|
||||
self.interms.push(Number::Float(fl));
|
||||
}
|
||||
(HeapCellValueTag::Cons, ptr) => {
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
@@ -1451,17 +1443,17 @@ mod tests {
|
||||
let mut wam = MachineState::new();
|
||||
let mut op_dir = default_op_dir();
|
||||
|
||||
op_dir.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX as u8));
|
||||
op_dir.insert((atom!("-"), Fixity::In), OpDesc::build_with(500, YFX as u8));
|
||||
op_dir.insert((atom!("-"), Fixity::Pre), OpDesc::build_with(200, FY as u8));
|
||||
op_dir.insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX as u8));
|
||||
op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX as u8));
|
||||
op_dir.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX));
|
||||
op_dir.insert((atom!("-"), Fixity::In), OpDesc::build_with(500, YFX));
|
||||
op_dir.insert((atom!("-"), Fixity::Pre), OpDesc::build_with(200, FY));
|
||||
op_dir.insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX));
|
||||
op_dir.insert((atom!("/"), Fixity::In), OpDesc::build_with(400, YFX));
|
||||
|
||||
let term_write_result =
|
||||
parse_and_write_parsed_term_to_heap(&mut wam, "3 + 4 - 1 + 2.", &op_dir).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wam.arith_eval_by_metacall(heap_loc_as_cell!(term_write_result.heap_loc)),
|
||||
wam.arith_eval_by_metacall(term_write_result.heap_loc),
|
||||
Ok(Number::Fixnum(Fixnum::build_with(8))),
|
||||
);
|
||||
|
||||
@@ -1471,7 +1463,7 @@ mod tests {
|
||||
parse_and_write_parsed_term_to_heap(&mut wam, "5 * 4 - 1.", &op_dir).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wam.arith_eval_by_metacall(heap_loc_as_cell!(term_write_result.heap_loc)),
|
||||
wam.arith_eval_by_metacall(term_write_result.heap_loc),
|
||||
Ok(Number::Fixnum(Fixnum::build_with(19))),
|
||||
);
|
||||
|
||||
@@ -1481,7 +1473,7 @@ mod tests {
|
||||
parse_and_write_parsed_term_to_heap(&mut wam, "sign(-1).", &op_dir).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wam.arith_eval_by_metacall(heap_loc_as_cell!(term_write_result.heap_loc)),
|
||||
wam.arith_eval_by_metacall(term_write_result.heap_loc),
|
||||
Ok(Number::Fixnum(Fixnum::build_with(-1)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use crate::heap_iter::*;
|
||||
use crate::machine::*;
|
||||
use crate::parser::ast::*;
|
||||
use crate::temp_v;
|
||||
use crate::types::*;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::vec::IntoIter;
|
||||
|
||||
pub(super) type Bindings = Vec<(usize, HeapCellValue)>;
|
||||
|
||||
@@ -56,32 +54,36 @@ impl MachineState {
|
||||
self.attr_var_init.bindings.push((h, addr));
|
||||
}
|
||||
|
||||
fn populate_var_and_value_lists(&mut self) -> (HeapCellValue, HeapCellValue) {
|
||||
fn populate_var_and_value_lists(&mut self) -> Result<(HeapCellValue, HeapCellValue), usize> {
|
||||
let size = self.attr_var_init.bindings.len();
|
||||
|
||||
let iter = self
|
||||
.attr_var_init
|
||||
.bindings
|
||||
.iter()
|
||||
.map(|(ref h, _)| attr_var_as_cell!(*h));
|
||||
|
||||
let var_list_addr = heap_loc_as_cell!(iter_to_heap_list(&mut self.heap, iter));
|
||||
let var_list_addr = sized_iter_to_heap_list(&mut self.heap, size, iter)?;
|
||||
let iter = self.attr_var_init.bindings.drain(0..).map(|(_, ref v)| *v);
|
||||
let value_list_addr = heap_loc_as_cell!(iter_to_heap_list(&mut self.heap, iter));
|
||||
let value_list_addr = sized_iter_to_heap_list(&mut self.heap, size, iter)?;
|
||||
|
||||
(var_list_addr, value_list_addr)
|
||||
Ok((var_list_addr, value_list_addr))
|
||||
}
|
||||
|
||||
fn verify_attributes(&mut self) {
|
||||
fn verify_attributes(&mut self) -> Result<(), usize> {
|
||||
for (h, _) in &self.attr_var_init.bindings {
|
||||
self.heap[*h] = attr_var_as_cell!(*h);
|
||||
}
|
||||
|
||||
let (var_list_addr, value_list_addr) = self.populate_var_and_value_lists();
|
||||
let (var_list_addr, value_list_addr) = self.populate_var_and_value_lists()?;
|
||||
|
||||
self[temp_v!(1)] = var_list_addr;
|
||||
self[temp_v!(2)] = value_list_addr;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn gather_attr_vars_created_since(&mut self, b: usize) -> IntoIter<HeapCellValue> {
|
||||
pub(super) fn gather_attr_vars_created_since(&mut self, b: usize) -> Vec<HeapCellValue> {
|
||||
let mut attr_vars: Vec<_> = if b >= self.attr_var_init.attr_var_queue.len() {
|
||||
vec![]
|
||||
} else {
|
||||
@@ -105,10 +107,10 @@ impl MachineState {
|
||||
});
|
||||
|
||||
attr_vars.dedup();
|
||||
attr_vars.into_iter()
|
||||
attr_vars
|
||||
}
|
||||
|
||||
pub(super) fn verify_attr_interrupt(&mut self, p: usize, arity: usize) {
|
||||
pub(super) fn verify_attr_interrupt(&mut self, p: usize, arity: usize) -> Result<(), usize> {
|
||||
self.allocate(arity + 3);
|
||||
|
||||
let e = self.e;
|
||||
@@ -118,23 +120,40 @@ impl MachineState {
|
||||
and_frame[i] = self.registers[i];
|
||||
}
|
||||
|
||||
and_frame[arity + 1] = fixnum_as_cell!(Fixnum::build_with(self.b0 as i64));
|
||||
and_frame[arity + 2] = fixnum_as_cell!(Fixnum::build_with(self.num_of_args as i64));
|
||||
and_frame[arity + 3] = fixnum_as_cell!(Fixnum::build_with(self.attr_var_init.cp as i64));
|
||||
and_frame[arity + 1] =
|
||||
fixnum_as_cell!(
|
||||
/* FIXME this is not safe */
|
||||
unsafe { Fixnum::build_with_unchecked(self.b0 as i64) }
|
||||
);
|
||||
and_frame[arity + 2] =
|
||||
fixnum_as_cell!(
|
||||
/* FIXME this is not safe */
|
||||
unsafe { Fixnum::build_with_unchecked(self.num_of_args as i64) }
|
||||
);
|
||||
and_frame[arity + 3] =
|
||||
fixnum_as_cell!(
|
||||
/* FIXME this is not safe */
|
||||
unsafe { Fixnum::build_with_unchecked(self.attr_var_init.cp as i64) }
|
||||
);
|
||||
|
||||
self.verify_attributes();
|
||||
self.verify_attributes()?;
|
||||
|
||||
self.num_of_args = 3;
|
||||
self.b0 = self.b;
|
||||
self.p = p;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn attr_vars_of_term(&mut self, cell: HeapCellValue) -> Vec<HeapCellValue> {
|
||||
debug_assert!(cell.is_ref());
|
||||
|
||||
let mut seen_set = IndexSet::new();
|
||||
let mut seen_vars = vec![];
|
||||
|
||||
let mut iter = stackful_preorder_iter::<NonListElider>
|
||||
(&mut self.heap, &mut self.stack, cell);
|
||||
self.heap[0] = cell;
|
||||
|
||||
let mut iter = stackful_preorder_iter::<NonListElider>(&mut self.heap, &mut self.stack, 0);
|
||||
|
||||
while let Some(value) = iter.next() {
|
||||
read_heap_cell!(value,
|
||||
|
||||
@@ -41,7 +41,7 @@ fn capture_offset(line: &Instruction, index: usize, stack: &mut Vec<usize>) -> b
|
||||
/* This function walks the code of a single predicate, supposed to
|
||||
* begin in code at the offset p. Each instruction is passed to the
|
||||
* walker function.
|
||||
*/
|
||||
*/
|
||||
pub(crate) fn walk_code(code: &Code, p: usize, mut walker: impl FnMut(&Instruction)) {
|
||||
let mut stack = vec![p];
|
||||
let mut visited_indices = IndexSet::with_hasher(FxBuildHasher::default());
|
||||
|
||||
@@ -42,42 +42,35 @@ pub(super) fn bootstrapping_compile(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lower_bound_of_target_clause(skeleton: &PredicateSkeleton, target_pos: usize) -> usize {
|
||||
fn lower_bound_of_target_clause(skeleton: &mut PredicateSkeleton, target_pos: usize) -> usize {
|
||||
if target_pos == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let arg_num = skeleton.clauses[target_pos - 1].opt_arg_index_key.arg_num();
|
||||
debug_assert!(skeleton.clauses.len() >= 2);
|
||||
|
||||
if arg_num == 0 {
|
||||
return target_pos - 1;
|
||||
}
|
||||
let index = target_pos - 1;
|
||||
|
||||
let mut index_loc_opt = None;
|
||||
let index = if let Some(index_loc) = skeleton.clauses[index]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc()
|
||||
{
|
||||
let search_result = skeleton.clauses.make_contiguous()
|
||||
[0..skeleton.core.clause_assert_margin]
|
||||
.partition_point(|clause_index_info| clause_index_info.clause_start > index_loc);
|
||||
|
||||
for index in (0..target_pos).rev() {
|
||||
let current_arg_num = skeleton.clauses[index].opt_arg_index_key.arg_num();
|
||||
|
||||
if current_arg_num == 0 || current_arg_num != arg_num {
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
if let Some(index_loc) = index_loc_opt {
|
||||
let current_index_loc = skeleton.clauses[index]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc();
|
||||
|
||||
if Some(index_loc) != current_index_loc {
|
||||
return index + 1;
|
||||
}
|
||||
if search_result < skeleton.core.clause_assert_margin {
|
||||
search_result
|
||||
} else {
|
||||
index_loc_opt = skeleton.clauses[index]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc();
|
||||
skeleton.clauses.make_contiguous()[skeleton.core.clause_assert_margin..]
|
||||
.partition_point(|clause_index_info| clause_index_info.clause_start < index_loc)
|
||||
+ skeleton.core.clause_assert_margin
|
||||
}
|
||||
}
|
||||
} else {
|
||||
index
|
||||
};
|
||||
|
||||
0
|
||||
index.clamp(0, skeleton.clauses.len() - 2)
|
||||
}
|
||||
|
||||
fn derelictize_try_me_else(
|
||||
@@ -140,7 +133,7 @@ fn merge_indices(
|
||||
);
|
||||
|
||||
retraction_info.push_record(RetractionRecord::AddedIndex(
|
||||
skeleton[clause_index].opt_arg_index_key.clone(),
|
||||
skeleton[clause_index].opt_arg_index_key,
|
||||
clause_loc,
|
||||
));
|
||||
} else {
|
||||
@@ -253,7 +246,7 @@ fn remove_index_from_subsequence(
|
||||
// appear anywhere inside an Internal record.
|
||||
retraction_info.push_record(RetractionRecord::RemovedIndex(
|
||||
index_loc,
|
||||
opt_arg_index_key.clone(),
|
||||
*opt_arg_index_key,
|
||||
offset,
|
||||
));
|
||||
}
|
||||
@@ -282,28 +275,25 @@ fn merge_indexed_subsequences(
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
match &mut code[inner_try_me_else_loc] {
|
||||
Instruction::TryMeElse(ref mut o) => {
|
||||
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
|
||||
inner_try_me_else_loc,
|
||||
*o,
|
||||
));
|
||||
if let Instruction::TryMeElse(ref mut o) = &mut code[inner_try_me_else_loc] {
|
||||
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
|
||||
inner_try_me_else_loc,
|
||||
*o,
|
||||
));
|
||||
|
||||
match *o {
|
||||
0 => {
|
||||
code[inner_try_me_else_loc] = Instruction::TrustMe(0);
|
||||
}
|
||||
o => match &code[inner_try_me_else_loc + o] {
|
||||
Instruction::RevJmpBy(0) => {
|
||||
code[inner_try_me_else_loc] = Instruction::TrustMe(o);
|
||||
}
|
||||
_ => {
|
||||
code[inner_try_me_else_loc] = Instruction::RetryMeElse(o);
|
||||
}
|
||||
},
|
||||
match *o {
|
||||
0 => {
|
||||
code[inner_try_me_else_loc] = Instruction::TrustMe(0);
|
||||
}
|
||||
o => match &code[inner_try_me_else_loc + o] {
|
||||
Instruction::RevJmpBy(0) => {
|
||||
code[inner_try_me_else_loc] = Instruction::TrustMe(o);
|
||||
}
|
||||
_ => {
|
||||
code[inner_try_me_else_loc] = Instruction::RetryMeElse(o);
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
thread_choice_instr_at_to(
|
||||
@@ -333,8 +323,8 @@ fn merge_indexed_subsequences(
|
||||
retraction_info,
|
||||
);
|
||||
}
|
||||
None => match &mut code[outer_threaded_choice_instr_loc] {
|
||||
Instruction::TryMeElse(ref mut o) => {
|
||||
None => {
|
||||
if let Instruction::TryMeElse(ref mut o) = &mut code[outer_threaded_choice_instr_loc] {
|
||||
retraction_info
|
||||
.push_record(RetractionRecord::ModifiedTryMeElse(inner_trust_me_loc, *o));
|
||||
|
||||
@@ -342,8 +332,7 @@ fn merge_indexed_subsequences(
|
||||
|
||||
return Some(IndexPtr::index(outer_threaded_choice_instr_loc + 1));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
@@ -711,31 +700,25 @@ fn remove_non_leading_clause(
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize_retract(
|
||||
fn finalize_retract<'a, LS: LoadState<'a>>(
|
||||
payload: &mut <LS as LoadState<'a>>::LoaderFieldType,
|
||||
key: PredicateKey,
|
||||
compilation_target: CompilationTarget,
|
||||
skeleton: &mut PredicateSkeleton,
|
||||
code_index: CodeIndex,
|
||||
target_pos: usize,
|
||||
index_ptr_opt: Option<IndexPtr>,
|
||||
retraction_info: &mut RetractionInfo,
|
||||
) -> usize {
|
||||
let clause_clause_loc = delete_from_skeleton(
|
||||
compilation_target,
|
||||
key,
|
||||
skeleton,
|
||||
target_pos,
|
||||
retraction_info,
|
||||
&mut payload.retraction_info,
|
||||
);
|
||||
|
||||
if let Some(index_ptr) = index_ptr_opt {
|
||||
set_code_index(
|
||||
retraction_info,
|
||||
&compilation_target,
|
||||
key,
|
||||
code_index,
|
||||
index_ptr,
|
||||
);
|
||||
set_code_index::<LS>(payload, &compilation_target, key, code_index, index_ptr);
|
||||
}
|
||||
|
||||
clause_clause_loc
|
||||
@@ -825,7 +808,7 @@ fn prepend_compiled_clause(
|
||||
skeleton.clauses[0].clause_start = clause_loc + 2;
|
||||
|
||||
retraction_info.push_record(RetractionRecord::AddedIndex(
|
||||
skeleton.clauses[0].opt_arg_index_key.clone(),
|
||||
skeleton.clauses[0].opt_arg_index_key,
|
||||
skeleton.clauses[0].clause_start,
|
||||
));
|
||||
|
||||
@@ -919,7 +902,7 @@ fn prepend_compiled_clause(
|
||||
retraction_info,
|
||||
);
|
||||
|
||||
code.extend(prepend_queue.into_iter());
|
||||
code.extend(prepend_queue);
|
||||
|
||||
if skeleton.core.is_dynamic {
|
||||
clause_loc
|
||||
@@ -975,7 +958,7 @@ fn prepend_compiled_clause(
|
||||
|
||||
internalize_choice_instr_at(code, old_clause_start, retraction_info);
|
||||
|
||||
code.extend(prepend_queue.into_iter());
|
||||
code.extend(prepend_queue);
|
||||
|
||||
clause_loc // + (outer_thread_choice_offset == 0 as usize)
|
||||
}
|
||||
@@ -1004,7 +987,7 @@ fn prepend_compiled_clause(
|
||||
|
||||
internalize_choice_instr_at(code, old_clause_start, retraction_info);
|
||||
|
||||
code.extend(prepend_queue.into_iter());
|
||||
code.extend(prepend_queue);
|
||||
|
||||
// skeleton.clauses[0].opt_arg_index_key += clause_loc;
|
||||
skeleton.clauses[0].clause_start = clause_loc;
|
||||
@@ -1029,7 +1012,7 @@ fn prepend_compiled_clause(
|
||||
|
||||
internalize_choice_instr_at(code, old_clause_start, retraction_info);
|
||||
|
||||
code.extend(prepend_queue.into_iter());
|
||||
code.extend(prepend_queue);
|
||||
|
||||
// skeleton.clauses[0].opt_arg_index_key += clause_loc;
|
||||
skeleton.clauses[0].clause_start = clause_loc;
|
||||
@@ -1087,7 +1070,7 @@ fn append_compiled_clause(
|
||||
skeleton.clauses[target_pos].opt_arg_index_key += index_loc - 1;
|
||||
|
||||
retraction_info.push_record(RetractionRecord::AddedIndex(
|
||||
skeleton.clauses[target_pos].opt_arg_index_key.clone(),
|
||||
skeleton.clauses[target_pos].opt_arg_index_key,
|
||||
skeleton.clauses[target_pos].clause_start,
|
||||
));
|
||||
|
||||
@@ -1134,21 +1117,18 @@ fn append_compiled_clause(
|
||||
skeleton.clauses[target_pos].opt_arg_index_key += clause_loc;
|
||||
code.extend(clause_code.drain(1..));
|
||||
|
||||
match skeleton.clauses[target_pos]
|
||||
if let Some(index_loc) = skeleton.clauses[target_pos]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc()
|
||||
{
|
||||
Some(index_loc) => {
|
||||
// point to the inner-threaded TryMeElse(0) if target_pos is
|
||||
// indexed, and make switch_on_term point one line after it in
|
||||
// its variable offset.
|
||||
skeleton.clauses[target_pos].clause_start += 2;
|
||||
// point to the inner-threaded TryMeElse(0) if target_pos is
|
||||
// indexed, and make switch_on_term point one line after it in
|
||||
// its variable offset.
|
||||
skeleton.clauses[target_pos].clause_start += 2;
|
||||
|
||||
if !skeleton.core.is_dynamic {
|
||||
set_switch_var_offset(code, index_loc, 2, retraction_info);
|
||||
}
|
||||
if !skeleton.core.is_dynamic {
|
||||
set_switch_var_offset(code, index_loc, 2, retraction_info);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
match skeleton.clauses[lower_bound]
|
||||
@@ -1208,11 +1188,8 @@ fn print_overwrite_warning(
|
||||
key: PredicateKey,
|
||||
is_dynamic: bool,
|
||||
) {
|
||||
if let CompilationTarget::Module(module_name) = compilation_target {
|
||||
match module_name {
|
||||
atom!("builtins") | atom!("loader") => return,
|
||||
_ => {}
|
||||
}
|
||||
if let CompilationTarget::Module(atom!("builtins") | atom!("loader")) = compilation_target {
|
||||
return;
|
||||
}
|
||||
|
||||
match code_ptr.tag() {
|
||||
@@ -1222,7 +1199,7 @@ fn print_overwrite_warning(
|
||||
}
|
||||
|
||||
println!(
|
||||
"Warning: overwriting {}/{} because the clauses are discontiguous",
|
||||
"% Warning: overwriting {}/{} because the clauses are discontiguous",
|
||||
key.0.as_str(),
|
||||
key.1
|
||||
);
|
||||
@@ -1254,12 +1231,9 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
settings: CodeGenSettings,
|
||||
) -> Result<StandaloneCompileResult, SessionError> {
|
||||
let mut preprocessor = Preprocessor::new(settings);
|
||||
let clause = preprocessor.try_term_to_tl(self, term)?;
|
||||
|
||||
let clause = self.try_term_to_tl(term, &mut preprocessor)?;
|
||||
// let queue = preprocessor.parse_queue(self)?;
|
||||
|
||||
let mut cg = CodeGenerator::new(&LS::machine_st(&mut self.payload).atom_tbl, settings);
|
||||
|
||||
let mut cg = CodeGenerator::new(settings);
|
||||
let clause_code = cg.compile_predicate(vec![clause])?;
|
||||
|
||||
Ok(StandaloneCompileResult {
|
||||
@@ -1274,7 +1248,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
mut predicates: PredicateQueue,
|
||||
settings: CodeGenSettings,
|
||||
) -> Result<CodeIndex, SessionError> {
|
||||
let code_index = self.get_or_insert_code_index(key, predicates.compilation_target);
|
||||
let code_idx = self.get_or_insert_code_index(key, predicates.compilation_target);
|
||||
|
||||
LS::err_on_builtin_overwrite(self, key)?;
|
||||
|
||||
@@ -1285,11 +1259,10 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
let mut preprocessor = Preprocessor::new(settings);
|
||||
|
||||
for term in predicates.predicates.drain(0..) {
|
||||
clauses.push(self.try_term_to_tl(term, &mut preprocessor)?);
|
||||
clauses.push(preprocessor.try_term_to_tl(self, term)?);
|
||||
}
|
||||
|
||||
let mut cg = CodeGenerator::new(&LS::machine_st(&mut self.payload).atom_tbl, settings);
|
||||
|
||||
let mut cg = CodeGenerator::new(settings);
|
||||
let mut code = cg.compile_predicate(clauses)?;
|
||||
|
||||
if settings.is_extensible {
|
||||
@@ -1302,11 +1275,8 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
clause_clause_locs.push_back(clause_index_info.clause_start);
|
||||
}
|
||||
|
||||
match &mut code[0] {
|
||||
Instruction::TryMeElse(0) => {
|
||||
code_ptr += 1;
|
||||
}
|
||||
_ => {}
|
||||
if let Instruction::TryMeElse(0) = &mut code[0] {
|
||||
code_ptr += 1;
|
||||
}
|
||||
|
||||
match self
|
||||
@@ -1317,7 +1287,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
Some(skeleton) => {
|
||||
let skeleton_clause_len = skeleton.clauses.len();
|
||||
|
||||
skeleton.clauses.extend(cg.skeleton.clauses.into_iter());
|
||||
skeleton.clauses.extend(cg.skeleton.clauses);
|
||||
skeleton
|
||||
.core
|
||||
.clause_clause_locs
|
||||
@@ -1337,7 +1307,8 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
.clause_clause_locs
|
||||
.extend(&clause_clause_locs.make_contiguous()[0..]);
|
||||
|
||||
let skeleton = cg.skeleton;
|
||||
let mut skeleton = cg.skeleton;
|
||||
skeleton.core.is_dynamic = settings.is_dynamic();
|
||||
|
||||
self.add_extensible_predicate(key, skeleton, predicates.compilation_target);
|
||||
}
|
||||
@@ -1350,9 +1321,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
);
|
||||
}
|
||||
|
||||
let index_ptr = LS::machine_st(&mut self.payload)
|
||||
.arena
|
||||
.code_index_tbl
|
||||
.get_entry(code_idx.into());
|
||||
|
||||
print_overwrite_warning(
|
||||
&predicates.compilation_target,
|
||||
code_index.get(),
|
||||
index_ptr,
|
||||
key,
|
||||
settings.is_dynamic(),
|
||||
);
|
||||
@@ -1363,16 +1339,16 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
IndexPtr::index(code_ptr)
|
||||
};
|
||||
|
||||
set_code_index(
|
||||
&mut self.payload.retraction_info,
|
||||
set_code_index::<LS>(
|
||||
&mut self.payload,
|
||||
&predicates.compilation_target,
|
||||
key,
|
||||
code_index,
|
||||
code_idx,
|
||||
index_ptr,
|
||||
);
|
||||
|
||||
self.wam_prelude.code.extend(code.into_iter());
|
||||
Ok(code_index)
|
||||
self.wam_prelude.code.extend(code);
|
||||
Ok(code_idx)
|
||||
}
|
||||
|
||||
fn extend_local_predicate_skeleton(
|
||||
@@ -1537,6 +1513,8 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
|
||||
let code_len = self.wam_prelude.code.len();
|
||||
|
||||
standalone_skeleton.clauses[0].clause_start += code_len;
|
||||
|
||||
let skeleton = match self
|
||||
.wam_prelude
|
||||
.indices
|
||||
@@ -1549,8 +1527,8 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
match append_or_prepend {
|
||||
AppendOrPrepend::Append => {
|
||||
let clause_index_info = standalone_skeleton.clauses.pop_back().unwrap();
|
||||
skeleton.clauses.push_back(clause_index_info);
|
||||
|
||||
skeleton.clauses.push_back(clause_index_info);
|
||||
skeleton.core.clause_clause_locs.push_back(code_len);
|
||||
|
||||
self.payload
|
||||
@@ -1563,7 +1541,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
let global_clock = LS::machine_st(&mut self.payload).global_clock;
|
||||
|
||||
let result = append_compiled_clause(
|
||||
&mut self.wam_prelude.code,
|
||||
self.wam_prelude.code,
|
||||
clause_code,
|
||||
skeleton,
|
||||
&mut self.payload.retraction_info,
|
||||
@@ -1572,19 +1550,19 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
|
||||
self.push_back_to_local_predicate_skeleton(&compilation_target, &key, code_len);
|
||||
|
||||
let code_index = self.get_or_insert_code_index(key, compilation_target);
|
||||
let code_idx = self.get_or_insert_code_index(key, compilation_target);
|
||||
|
||||
if let Some(new_code_ptr) = result {
|
||||
set_code_index(
|
||||
&mut self.payload.retraction_info,
|
||||
set_code_index::<LS>(
|
||||
&mut self.payload,
|
||||
&compilation_target,
|
||||
key,
|
||||
code_index,
|
||||
code_idx,
|
||||
new_code_ptr,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(code_index)
|
||||
Ok(code_idx)
|
||||
}
|
||||
AppendOrPrepend::Prepend => {
|
||||
let clause_index_info = standalone_skeleton.clauses.pop_back().unwrap();
|
||||
@@ -1603,7 +1581,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
let global_clock = LS::machine_st(&mut self.payload).global_clock;
|
||||
|
||||
let new_code_ptr = prepend_compiled_clause(
|
||||
&mut self.wam_prelude.code,
|
||||
self.wam_prelude.code,
|
||||
compilation_target,
|
||||
key,
|
||||
clause_code,
|
||||
@@ -1614,17 +1592,17 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
|
||||
self.push_front_to_local_predicate_skeleton(&compilation_target, &key, code_len);
|
||||
|
||||
let code_index = self.get_or_insert_code_index(key, compilation_target);
|
||||
let code_idx = self.get_or_insert_code_index(key, compilation_target);
|
||||
|
||||
set_code_index(
|
||||
&mut self.payload.retraction_info,
|
||||
set_code_index::<LS>(
|
||||
&mut self.payload,
|
||||
&compilation_target,
|
||||
key,
|
||||
code_index,
|
||||
code_idx,
|
||||
new_code_ptr,
|
||||
);
|
||||
|
||||
Ok(code_index)
|
||||
Ok(code_idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1646,7 +1624,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
.switch_on_term_loc()
|
||||
{
|
||||
Some(index_loc) => find_inner_choice_instr(
|
||||
&self.wam_prelude.code,
|
||||
self.wam_prelude.code,
|
||||
skeleton.clauses[target_pos].clause_start,
|
||||
index_loc,
|
||||
),
|
||||
@@ -1672,7 +1650,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
|
||||
pub(super) fn retract_clause(&mut self, key: PredicateKey, target_pos: usize) -> usize {
|
||||
let payload_compilation_target = self.payload.compilation_target;
|
||||
let code_index = self.get_or_insert_code_index(key, payload_compilation_target);
|
||||
let code_idx_offset = self.get_or_insert_code_index(key, payload_compilation_target);
|
||||
|
||||
let skeleton = self
|
||||
.wam_prelude
|
||||
@@ -1687,115 +1665,109 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
if target_pos == 0 || (lower_bound + 1 == target_pos && lower_bound_is_unindexed) {
|
||||
// the clause preceding target_pos, if there is one, is of
|
||||
// key type OptArgIndexKey::None.
|
||||
match skeleton.clauses[target_pos]
|
||||
if let Some(index_loc) = skeleton.clauses[target_pos]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc()
|
||||
{
|
||||
Some(index_loc) => {
|
||||
let inner_clause_start = find_inner_choice_instr(
|
||||
code,
|
||||
skeleton.clauses[target_pos].clause_start,
|
||||
index_loc,
|
||||
);
|
||||
let inner_clause_start = find_inner_choice_instr(
|
||||
code,
|
||||
skeleton.clauses[target_pos].clause_start,
|
||||
index_loc,
|
||||
);
|
||||
|
||||
remove_index_from_subsequence(
|
||||
code,
|
||||
&skeleton.clauses[target_pos].opt_arg_index_key,
|
||||
inner_clause_start,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
remove_index_from_subsequence(
|
||||
code,
|
||||
&skeleton.clauses[target_pos].opt_arg_index_key,
|
||||
inner_clause_start,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
|
||||
match derelictize_try_me_else(
|
||||
code,
|
||||
inner_clause_start,
|
||||
&mut self.payload.retraction_info,
|
||||
) {
|
||||
Some(offset) => {
|
||||
let instr_loc = find_inner_choice_instr(
|
||||
code,
|
||||
inner_clause_start + offset,
|
||||
index_loc,
|
||||
);
|
||||
match derelictize_try_me_else(
|
||||
code,
|
||||
inner_clause_start,
|
||||
&mut self.payload.retraction_info,
|
||||
) {
|
||||
Some(offset) => {
|
||||
let instr_loc =
|
||||
find_inner_choice_instr(code, inner_clause_start + offset, index_loc);
|
||||
|
||||
let clause_loc = blunt_leading_choice_instr(
|
||||
code,
|
||||
instr_loc,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
let clause_loc = blunt_leading_choice_instr(
|
||||
code,
|
||||
instr_loc,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
|
||||
set_switch_var_offset(
|
||||
code,
|
||||
index_loc,
|
||||
clause_loc - index_loc,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
set_switch_var_offset(
|
||||
code,
|
||||
index_loc,
|
||||
clause_loc - index_loc,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
|
||||
self.payload.retraction_info.push_record(
|
||||
RetractionRecord::SkeletonClauseStartReplaced(
|
||||
payload_compilation_target,
|
||||
key,
|
||||
target_pos + 1,
|
||||
skeleton.clauses[target_pos + 1].clause_start,
|
||||
),
|
||||
);
|
||||
|
||||
skeleton.clauses[target_pos + 1].clause_start =
|
||||
skeleton.clauses[target_pos].clause_start;
|
||||
|
||||
let update_code_index = target_pos == 0
|
||||
&& skeleton.clauses[target_pos + 1]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc()
|
||||
.is_none();
|
||||
|
||||
let index_ptr_opt = if update_code_index {
|
||||
Some(IndexPtr::index(clause_loc))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return finalize_retract(
|
||||
key,
|
||||
self.payload.retraction_info.push_record(
|
||||
RetractionRecord::SkeletonClauseStartReplaced(
|
||||
payload_compilation_target,
|
||||
skeleton,
|
||||
code_index,
|
||||
target_pos,
|
||||
index_ptr_opt,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let index_ptr_opt = if target_pos > 0 {
|
||||
let preceding_choice_instr_loc =
|
||||
skeleton.clauses[target_pos - 1].clause_start;
|
||||
|
||||
remove_non_leading_clause(
|
||||
code,
|
||||
preceding_choice_instr_loc,
|
||||
skeleton.clauses[target_pos].clause_start - 2,
|
||||
&mut self.payload.retraction_info,
|
||||
)
|
||||
} else {
|
||||
remove_leading_unindexed_clause(
|
||||
code,
|
||||
skeleton.clauses[target_pos].clause_start - 2,
|
||||
&mut self.payload.retraction_info,
|
||||
)
|
||||
};
|
||||
|
||||
return finalize_retract(
|
||||
key,
|
||||
payload_compilation_target,
|
||||
skeleton,
|
||||
code_index,
|
||||
target_pos,
|
||||
index_ptr_opt,
|
||||
target_pos + 1,
|
||||
skeleton.clauses[target_pos + 1].clause_start,
|
||||
),
|
||||
);
|
||||
|
||||
skeleton.clauses[target_pos + 1].clause_start =
|
||||
skeleton.clauses[target_pos].clause_start;
|
||||
|
||||
let update_code_index = target_pos == 0
|
||||
&& skeleton.clauses[target_pos + 1]
|
||||
.opt_arg_index_key
|
||||
.switch_on_term_loc()
|
||||
.is_none();
|
||||
|
||||
let index_ptr_opt = if update_code_index {
|
||||
Some(IndexPtr::index(clause_loc))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return finalize_retract::<LS>(
|
||||
&mut self.payload,
|
||||
key,
|
||||
payload_compilation_target,
|
||||
skeleton,
|
||||
code_idx_offset,
|
||||
target_pos,
|
||||
index_ptr_opt,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let index_ptr_opt = if target_pos > 0 {
|
||||
let preceding_choice_instr_loc =
|
||||
skeleton.clauses[target_pos - 1].clause_start;
|
||||
|
||||
remove_non_leading_clause(
|
||||
code,
|
||||
preceding_choice_instr_loc,
|
||||
skeleton.clauses[target_pos].clause_start - 2,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
}
|
||||
)
|
||||
} else {
|
||||
remove_leading_unindexed_clause(
|
||||
code,
|
||||
skeleton.clauses[target_pos].clause_start - 2,
|
||||
&mut self.payload.retraction_info,
|
||||
)
|
||||
};
|
||||
|
||||
return finalize_retract::<LS>(
|
||||
&mut self.payload,
|
||||
key,
|
||||
payload_compilation_target,
|
||||
skeleton,
|
||||
code_idx_offset,
|
||||
target_pos,
|
||||
index_ptr_opt,
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1824,16 +1796,13 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
Instruction::RevJmpBy(target_indexing_loc - later_indexing_loc),
|
||||
);
|
||||
|
||||
match target_indexing_line {
|
||||
Instruction::IndexingCode(indexing_code) => {
|
||||
self.payload.retraction_info.push_record(
|
||||
RetractionRecord::ReplacedIndexingLine(
|
||||
target_indexing_loc,
|
||||
indexing_code,
|
||||
),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
if let Instruction::IndexingCode(indexing_code) = target_indexing_line {
|
||||
self.payload.retraction_info.push_record(
|
||||
RetractionRecord::ReplacedIndexingLine(
|
||||
target_indexing_loc,
|
||||
indexing_code,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
result = merge_indexed_subsequences(
|
||||
@@ -1977,16 +1946,15 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
|
||||
match &mut code[preceding_choice_instr_loc] {
|
||||
Instruction::TryMeElse(0) => {
|
||||
set_switch_var_offset(
|
||||
code,
|
||||
index_loc,
|
||||
preceding_choice_instr_loc + 1 - index_loc,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
if let Instruction::TryMeElse(0) =
|
||||
&mut code[preceding_choice_instr_loc]
|
||||
{
|
||||
set_switch_var_offset(
|
||||
code,
|
||||
index_loc,
|
||||
preceding_choice_instr_loc + 1 - index_loc,
|
||||
&mut self.payload.retraction_info,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2019,14 +1987,14 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
}
|
||||
};
|
||||
|
||||
finalize_retract(
|
||||
finalize_retract::<LS>(
|
||||
&mut self.payload,
|
||||
key,
|
||||
payload_compilation_target,
|
||||
skeleton,
|
||||
code_index,
|
||||
code_idx_offset,
|
||||
target_pos,
|
||||
index_ptr_opt,
|
||||
&mut self.payload.retraction_info,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2068,16 +2036,11 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
{
|
||||
Some(skeleton) if append_or_prepend.is_append() => {
|
||||
let tail_num = skeleton.core.clause_clause_locs.len() - num_clause_predicates;
|
||||
skeleton.core.clause_clause_locs.make_contiguous()[tail_num..]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect()
|
||||
skeleton.core.clause_clause_locs.make_contiguous()[tail_num..].to_vec()
|
||||
}
|
||||
Some(skeleton) => skeleton.core.clause_clause_locs.make_contiguous()
|
||||
[0..num_clause_predicates]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
.to_vec(),
|
||||
None => {
|
||||
unreachable!()
|
||||
}
|
||||
@@ -2173,7 +2136,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
.map(|skeleton| skeleton.predicate_info())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut predicate_info = self
|
||||
let predicate_info = self
|
||||
.wam_prelude
|
||||
.indices
|
||||
.get_predicate_skeleton(&self.payload.predicates.compilation_target, &key)
|
||||
@@ -2205,46 +2168,45 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
if is_cross_module_clause {
|
||||
if !local_predicate_info.is_extensible {
|
||||
if predicate_info.is_multifile {
|
||||
println!(
|
||||
"Warning: overwriting multifile predicate {}:{}/{} because \
|
||||
it was not locally declared multifile.",
|
||||
self.payload.predicates.compilation_target,
|
||||
key.0.as_str(),
|
||||
key.1
|
||||
if is_cross_module_clause && !local_predicate_info.is_extensible {
|
||||
if predicate_info.is_multifile {
|
||||
println!(
|
||||
"% Warning: overwriting multifile predicate {}:{}/{} because \
|
||||
it was not locally declared multifile.",
|
||||
self.payload.predicates.compilation_target,
|
||||
key.0.as_str(),
|
||||
key.1
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(skeleton) = self
|
||||
.wam_prelude
|
||||
.indices
|
||||
.remove_predicate_skeleton(&self.payload.predicates.compilation_target, &key)
|
||||
{
|
||||
let compilation_target = self.payload.predicates.compilation_target;
|
||||
|
||||
if predicate_info.is_dynamic {
|
||||
let clause_clause_compilation_target = match compilation_target {
|
||||
CompilationTarget::User => CompilationTarget::Module(atom!("builtins")),
|
||||
module => module,
|
||||
};
|
||||
|
||||
self.retract_local_clauses_by_locs(
|
||||
clause_clause_compilation_target,
|
||||
(atom!("$clause"), 2),
|
||||
(0..skeleton.clauses.len()).map(Some).collect(),
|
||||
false, // the builtin M:'$clause'/2 is never dynamic.
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(skeleton) = self.wam_prelude.indices.remove_predicate_skeleton(
|
||||
&self.payload.predicates.compilation_target,
|
||||
&key,
|
||||
) {
|
||||
let compilation_target = self.payload.predicates.compilation_target;
|
||||
|
||||
if predicate_info.is_dynamic {
|
||||
let clause_clause_compilation_target = match compilation_target {
|
||||
CompilationTarget::User => {
|
||||
CompilationTarget::Module(atom!("builtins"))
|
||||
}
|
||||
module => module,
|
||||
};
|
||||
|
||||
self.retract_local_clauses_by_locs(
|
||||
clause_clause_compilation_target,
|
||||
(atom!("$clause"), 2),
|
||||
(0..skeleton.clauses.len()).map(Some).collect(),
|
||||
false, // the builtin M:'$clause'/2 is never dynamic.
|
||||
);
|
||||
|
||||
predicate_info.is_dynamic = false;
|
||||
}
|
||||
|
||||
self.payload.retraction_info.push_record(
|
||||
RetractionRecord::RemovedSkeleton(compilation_target, key, skeleton),
|
||||
);
|
||||
}
|
||||
self.payload
|
||||
.retraction_info
|
||||
.push_record(RetractionRecord::RemovedSkeleton(
|
||||
compilation_target,
|
||||
key,
|
||||
skeleton,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2259,23 +2221,24 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
|
||||
};
|
||||
|
||||
let predicates = self.payload.predicates.take();
|
||||
let code_index = self.compile(key, predicates, settings)?;
|
||||
let offset = self.compile(key, predicates, settings)?;
|
||||
|
||||
if let Some(filename) = self.listing_src_file_name() {
|
||||
match self.wam_prelude.indices.modules.get_mut(&filename) {
|
||||
Some(ref mut module) => {
|
||||
let index_ptr = code_index.get();
|
||||
let code_index = module.code_dir.entry(key).or_insert(code_index).clone();
|
||||
if let Some(ref mut module) = self.wam_prelude.indices.modules.get_mut(&filename) {
|
||||
let index_ptr = LS::machine_st(&mut self.payload)
|
||||
.arena
|
||||
.code_index_tbl
|
||||
.get_entry(offset.into());
|
||||
|
||||
set_code_index(
|
||||
&mut self.payload.retraction_info,
|
||||
&CompilationTarget::Module(filename),
|
||||
key,
|
||||
code_index,
|
||||
index_ptr,
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
let offset = *module.code_dir.entry(key).or_insert(offset);
|
||||
|
||||
set_code_index::<LS>(
|
||||
&mut self.payload,
|
||||
&CompilationTarget::Module(filename),
|
||||
key,
|
||||
offset,
|
||||
index_ptr,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2344,7 +2307,7 @@ impl Machine {
|
||||
};
|
||||
|
||||
let StandaloneCompileResult { clause_code, .. } = compile()?;
|
||||
self.code.extend(clause_code.into_iter());
|
||||
self.code.extend(clause_code);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,32 +1,368 @@
|
||||
pub struct MachineConfig {
|
||||
pub streams: StreamConfig,
|
||||
pub toplevel: &'static str,
|
||||
}
|
||||
use std::borrow::Cow;
|
||||
use std::io::Write;
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
|
||||
pub enum StreamConfig {
|
||||
Stdio,
|
||||
use rand::{rngs::StdRng, SeedableRng};
|
||||
|
||||
use crate::Machine;
|
||||
|
||||
use super::{
|
||||
bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Arena, Atom,
|
||||
Callback, CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState, Stream,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
enum OutputStreamConfigInner {
|
||||
#[default]
|
||||
Memory,
|
||||
Stdout,
|
||||
Stderr,
|
||||
Callback(Callback),
|
||||
}
|
||||
|
||||
impl Default for MachineConfig {
|
||||
impl std::fmt::Debug for OutputStreamConfigInner {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Memory => write!(f, "Memory"),
|
||||
Self::Stdout => write!(f, "Stdout"),
|
||||
Self::Stderr => write!(f, "Stderr"),
|
||||
Self::Callback(_) => f.debug_tuple("Callback").field(&"<callback>").finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for an output stream.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct OutputStreamConfig {
|
||||
inner: OutputStreamConfigInner,
|
||||
}
|
||||
|
||||
impl OutputStreamConfig {
|
||||
/// Sends output to stdout.
|
||||
pub fn stdout() -> Self {
|
||||
Self {
|
||||
inner: OutputStreamConfigInner::Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends output to stderr.
|
||||
pub fn stderr() -> Self {
|
||||
Self {
|
||||
inner: OutputStreamConfigInner::Stderr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps output in a memory buffer.
|
||||
pub fn memory() -> Self {
|
||||
Self {
|
||||
inner: OutputStreamConfigInner::Memory,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls a callback with the output whenever the stream is written to.
|
||||
pub fn callback(callback: Callback) -> Self {
|
||||
Self {
|
||||
inner: OutputStreamConfigInner::Callback(callback),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_stream(self, arena: &mut Arena) -> Stream {
|
||||
match self.inner {
|
||||
OutputStreamConfigInner::Memory => Stream::from_owned_string("".to_owned(), arena),
|
||||
OutputStreamConfigInner::Stdout => Stream::stdout(arena),
|
||||
OutputStreamConfigInner::Stderr => Stream::stderr(arena),
|
||||
OutputStreamConfigInner::Callback(callback) => Stream::from_callback(callback, arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputStreamConfigInner {
|
||||
String(Cow<'static, str>),
|
||||
Stdin,
|
||||
Channel(Receiver<Vec<u8>>),
|
||||
}
|
||||
|
||||
impl Default for InputStreamConfigInner {
|
||||
fn default() -> Self {
|
||||
MachineConfig {
|
||||
streams: StreamConfig::Stdio,
|
||||
toplevel: include_str!("../toplevel.pl"),
|
||||
Self::String("".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for an input stream;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputStreamConfig {
|
||||
inner: InputStreamConfigInner,
|
||||
}
|
||||
|
||||
impl InputStreamConfig {
|
||||
/// Gets input from string.
|
||||
pub fn string(s: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
inner: InputStreamConfigInner::String(s.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets input from stdin.
|
||||
pub fn stdin() -> Self {
|
||||
Self {
|
||||
inner: InputStreamConfigInner::Stdin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Connects the input to the receiving end of a channel.
|
||||
pub fn channel() -> (UserInput, Self) {
|
||||
let (sender, receiver) = channel();
|
||||
(
|
||||
UserInput { inner: sender },
|
||||
Self {
|
||||
inner: InputStreamConfigInner::Channel(receiver),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn into_stream(self, arena: &mut Arena, add_history: bool) -> Stream {
|
||||
match self.inner {
|
||||
InputStreamConfigInner::String(s) => match s {
|
||||
Cow::Owned(s) => Stream::from_owned_string(s, arena),
|
||||
Cow::Borrowed(s) => Stream::from_static_string(s, arena),
|
||||
},
|
||||
InputStreamConfigInner::Stdin => Stream::stdin(arena, add_history),
|
||||
InputStreamConfigInner::Channel(channel) => Stream::input_channel(channel, arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineConfig {
|
||||
pub fn in_memory() -> Self {
|
||||
MachineConfig {
|
||||
streams: StreamConfig::Memory,
|
||||
..Default::default()
|
||||
/// Describes how the streams of a [`Machine`](crate::Machine) will be handled.
|
||||
pub struct StreamConfig {
|
||||
user_input: InputStreamConfig,
|
||||
user_output: OutputStreamConfig,
|
||||
user_error: OutputStreamConfig,
|
||||
}
|
||||
|
||||
impl Default for StreamConfig {
|
||||
fn default() -> Self {
|
||||
Self::in_memory()
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamConfig {
|
||||
/// Binds the input, output and error streams to stdin, stdout and stderr.
|
||||
pub fn stdio() -> Self {
|
||||
StreamConfig {
|
||||
user_input: InputStreamConfig::stdin(),
|
||||
user_output: OutputStreamConfig::stdout(),
|
||||
user_error: OutputStreamConfig::stderr(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_toplevel(mut self, toplevel: &'static str) -> Self {
|
||||
self.toplevel = toplevel;
|
||||
/// Binds the output and error streams to memory buffers and has an empty input.
|
||||
pub fn in_memory() -> Self {
|
||||
StreamConfig {
|
||||
user_input: InputStreamConfig::string(String::new()),
|
||||
user_output: OutputStreamConfig::memory(),
|
||||
user_error: OutputStreamConfig::memory(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls the given callbacks when the respective streams are written to.
|
||||
///
|
||||
/// This also returns a handler to the stdin of the [`Machine`](crate::Machine).
|
||||
pub fn from_callbacks(stdout: Option<Callback>, stderr: Option<Callback>) -> (UserInput, Self) {
|
||||
let (user_input, channel_stream) = InputStreamConfig::channel();
|
||||
(
|
||||
user_input,
|
||||
StreamConfig {
|
||||
user_input: channel_stream,
|
||||
user_output: stdout
|
||||
.map_or_else(OutputStreamConfig::memory, OutputStreamConfig::callback),
|
||||
user_error: stderr
|
||||
.map_or_else(OutputStreamConfig::memory, OutputStreamConfig::callback),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Configures the `user_input` stream.
|
||||
pub fn with_user_input(self, user_input: InputStreamConfig) -> Self {
|
||||
Self { user_input, ..self }
|
||||
}
|
||||
|
||||
/// Configures the `user_output` stream.
|
||||
pub fn with_user_output(self, user_output: OutputStreamConfig) -> Self {
|
||||
Self {
|
||||
user_output,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the `user_error` stream.
|
||||
pub fn with_user_error(self, user_error: OutputStreamConfig) -> Self {
|
||||
Self { user_error, ..self }
|
||||
}
|
||||
|
||||
fn into_streams(self, arena: &mut Arena, add_history: bool) -> (Stream, Stream, Stream) {
|
||||
(
|
||||
self.user_input.into_stream(arena, add_history),
|
||||
self.user_output.into_stream(arena),
|
||||
self.user_error.into_stream(arena),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler for the stdin of the [`Machine`](crate::Machine).
|
||||
#[derive(Debug)]
|
||||
pub struct UserInput {
|
||||
inner: Sender<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Write for UserInput {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.inner
|
||||
.send(buf.into())
|
||||
.map(|_| buf.len())
|
||||
.map_err(|_| std::io::ErrorKind::BrokenPipe.into())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes how a [`Machine`](crate::Machine) will be configured.
|
||||
pub struct MachineBuilder {
|
||||
pub(crate) streams: StreamConfig,
|
||||
pub(crate) toplevel: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl Default for MachineBuilder {
|
||||
/// Defaults to using in-memory streams.
|
||||
fn default() -> Self {
|
||||
MachineBuilder {
|
||||
streams: Default::default(),
|
||||
toplevel: default_toplevel().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineBuilder {
|
||||
/// Creates a default configuration.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Uses the given `crate::StreamConfig` in this configuration.
|
||||
pub fn with_streams(mut self, streams: StreamConfig) -> Self {
|
||||
self.streams = streams;
|
||||
self
|
||||
}
|
||||
|
||||
/// Uses the given toplevel in this configuration.
|
||||
pub fn with_toplevel(mut self, toplevel: impl Into<Cow<'static, str>>) -> Self {
|
||||
self.toplevel = toplevel.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the [`Machine`](crate::Machine) from this configuration.
|
||||
pub fn build(self) -> Machine {
|
||||
let args = MachineArgs::new();
|
||||
let mut machine_st = MachineState::new();
|
||||
|
||||
let (user_input, user_output, user_error) = self
|
||||
.streams
|
||||
.into_streams(&mut machine_st.arena, args.add_history);
|
||||
|
||||
let mut wam = Machine {
|
||||
machine_st,
|
||||
indices: IndexStore::new(),
|
||||
code: vec![],
|
||||
user_input,
|
||||
user_output,
|
||||
user_error,
|
||||
load_contexts: vec![],
|
||||
#[cfg(feature = "ffi")]
|
||||
foreign_function_table: Default::default(),
|
||||
rng: StdRng::from_entropy(),
|
||||
};
|
||||
|
||||
let mut lib_path = current_dir();
|
||||
|
||||
lib_path.pop();
|
||||
lib_path.push("lib");
|
||||
|
||||
wam.add_impls_to_indices();
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
libraries::get("ops_and_meta_predicates")
|
||||
.expect("library ops_and_meta_predicates should exist"),
|
||||
&mut wam.machine_st.arena,
|
||||
),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(
|
||||
atom!("ops_and_meta_predicates.pl"),
|
||||
lib_path.clone(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(
|
||||
libraries::get("builtins").expect("library builtins should exist"),
|
||||
&mut wam.machine_st.arena,
|
||||
),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(atom!("builtins.pl"), lib_path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(builtins) = wam.indices.modules.get_mut(&atom!("builtins")) {
|
||||
load_module(
|
||||
&mut wam.machine_st,
|
||||
&mut wam.indices.code_dir,
|
||||
&mut wam.indices.op_dir,
|
||||
&mut wam.indices.meta_predicates,
|
||||
&CompilationTarget::User,
|
||||
builtins,
|
||||
);
|
||||
|
||||
import_builtin_impls(&wam.indices.code_dir, builtins);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
lib_path.pop(); // remove the "lib" at the end
|
||||
|
||||
bootstrapping_compile(
|
||||
Stream::from_static_string(include_str!("../loader.pl"), &mut wam.machine_st.arena),
|
||||
&mut wam,
|
||||
ListingSource::from_file_and_path(atom!("loader.pl"), lib_path.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
wam.configure_modules();
|
||||
|
||||
if let Some(loader) = wam.indices.modules.get(&atom!("loader")) {
|
||||
load_module(
|
||||
&mut wam.machine_st,
|
||||
&mut wam.indices.code_dir,
|
||||
&mut wam.indices.op_dir,
|
||||
&mut wam.indices.meta_predicates,
|
||||
&CompilationTarget::User,
|
||||
loader,
|
||||
);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
wam.load_special_forms();
|
||||
wam.load_top_level(self.toplevel);
|
||||
wam.configure_streams();
|
||||
|
||||
wam
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a static string slice to the default toplevel
|
||||
pub fn default_toplevel() -> &'static str {
|
||||
include_str!("../toplevel.pl")
|
||||
}
|
||||
|
||||
@@ -1,12 +1,71 @@
|
||||
use fxhash::FxBuildHasher;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use crate::atom_table::*;
|
||||
use crate::machine::get_structure_index;
|
||||
use crate::machine::heap::*;
|
||||
use crate::machine::stack::*;
|
||||
use crate::types::*;
|
||||
|
||||
use std::mem;
|
||||
use std::ops::IndexMut;
|
||||
use scryer_modular_bitfield::specifiers::*;
|
||||
use scryer_modular_bitfield::*;
|
||||
|
||||
type Trail = Vec<(Ref, HeapCellValue)>;
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem;
|
||||
use std::ops::{IndexMut, Range};
|
||||
|
||||
#[derive(BitfieldSpecifier, Copy, Clone, Debug)]
|
||||
#[bits = 6]
|
||||
enum TrailRefTag {
|
||||
HeapCell = 0b001011,
|
||||
StackCell = 0b001101,
|
||||
AttrVar = 0b010001,
|
||||
PStrLoc = 0b001111,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[repr(u64)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||
struct TrailRef {
|
||||
val: B56,
|
||||
#[allow(unused)]
|
||||
m: bool,
|
||||
#[allow(unused)]
|
||||
f: bool,
|
||||
tag: TrailRefTag,
|
||||
}
|
||||
|
||||
impl TrailRef {
|
||||
#[inline(always)]
|
||||
fn heap_cell(h: usize) -> Self {
|
||||
TrailRef::new()
|
||||
.with_tag(TrailRefTag::HeapCell)
|
||||
.with_val(h as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn stack_cell(h: usize) -> Self {
|
||||
TrailRef::new()
|
||||
.with_tag(TrailRefTag::StackCell)
|
||||
.with_val(h as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn attr_var(h: usize) -> Self {
|
||||
TrailRef::new()
|
||||
.with_tag(TrailRefTag::AttrVar)
|
||||
.with_val(h as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn pstr_loc(h: usize) -> Self {
|
||||
TrailRef::new()
|
||||
.with_tag(TrailRefTag::PStrLoc)
|
||||
.with_val(h as u64)
|
||||
}
|
||||
}
|
||||
|
||||
type Trail = Vec<(TrailRef, HeapCellValue)>;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AttrVarPolicy {
|
||||
@@ -17,21 +76,39 @@ pub enum AttrVarPolicy {
|
||||
pub trait CopierTarget: IndexMut<usize, Output = HeapCellValue> {
|
||||
fn store(&self, value: HeapCellValue) -> HeapCellValue;
|
||||
fn deref(&self, value: HeapCellValue) -> HeapCellValue;
|
||||
fn push(&mut self, value: HeapCellValue);
|
||||
fn push_attr_var_queue(&mut self, attr_var_loc: usize);
|
||||
fn stack(&mut self) -> &mut Stack;
|
||||
fn threshold(&self) -> usize;
|
||||
// returns the tail location of the pstr on success
|
||||
fn as_slice_from<'a>(&'a self, from: usize) -> Box<dyn Iterator<Item = u8> + 'a>;
|
||||
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize>;
|
||||
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize>;
|
||||
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize>;
|
||||
}
|
||||
|
||||
pub(crate) fn copy_term<T: CopierTarget>(
|
||||
target: T,
|
||||
addr: HeapCellValue,
|
||||
attr_var_policy: AttrVarPolicy,
|
||||
) {
|
||||
) -> Result<usize, usize> {
|
||||
let mut copy_term_state = CopyTermState::new(target, attr_var_policy);
|
||||
let old_threshold = copy_term_state.target.threshold();
|
||||
|
||||
copy_term_state.copy_term_impl(addr);
|
||||
copy_term_state.copy_attr_var_lists();
|
||||
copy_term_state.copy_term_impl(addr)?;
|
||||
copy_term_state.copy_attr_var_lists()?;
|
||||
copy_term_state.unwind_trail();
|
||||
|
||||
let new_threshold = copy_term_state.target.threshold();
|
||||
copy_term_state.copy_pstrs()?;
|
||||
|
||||
Ok(new_threshold - old_threshold)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PStrData {
|
||||
pre_old_h_tail_loc: usize,
|
||||
post_old_h_tail_loc: usize,
|
||||
post_old_h_pstr_loc_locs: IndexSet<usize, FxBuildHasher>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -42,6 +119,9 @@ struct CopyTermState<T: CopierTarget> {
|
||||
target: T,
|
||||
attr_var_policy: AttrVarPolicy,
|
||||
attr_var_list_locs: Vec<(usize, HeapCellValue)>,
|
||||
// keys of pstr_loc_locs are byte indices rounded down to the
|
||||
// nearest cell boundary
|
||||
pstr_loc_locs: BTreeMap<usize, PStrData>,
|
||||
}
|
||||
|
||||
impl<T: CopierTarget> CopyTermState<T> {
|
||||
@@ -53,6 +133,7 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
target,
|
||||
attr_var_policy,
|
||||
attr_var_list_locs: vec![],
|
||||
pstr_loc_locs: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,18 +144,17 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
|
||||
fn trail_list_cell(&mut self, addr: usize, threshold: usize) {
|
||||
let trail_item = mem::replace(&mut self.target[addr], list_loc_as_cell!(threshold));
|
||||
self.trail.push((Ref::heap_cell(addr), trail_item));
|
||||
self.trail.push((TrailRef::heap_cell(addr), trail_item));
|
||||
}
|
||||
|
||||
fn copy_list(&mut self, addr: usize) {
|
||||
fn copy_list(&mut self, addr: usize) -> Result<(), usize> {
|
||||
for offset in 0..2 {
|
||||
read_heap_cell!(self.target[addr + offset],
|
||||
(HeapCellValueTag::Lis, h) => {
|
||||
if h >= self.old_h {
|
||||
*self.value_at_scan() = list_loc_as_cell!(h);
|
||||
self.scan += 1;
|
||||
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -83,104 +163,134 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
}
|
||||
|
||||
let threshold = self.target.threshold();
|
||||
self.target.copy_slice_to_end(addr..addr + 2)?;
|
||||
|
||||
*self.value_at_scan() = list_loc_as_cell!(threshold);
|
||||
|
||||
for i in 0..2 {
|
||||
let hcv = self.target[addr + i];
|
||||
self.target.push(hcv);
|
||||
}
|
||||
|
||||
let cdr = self
|
||||
.target
|
||||
.store(self.target.deref(heap_loc_as_cell!(addr + 1)));
|
||||
|
||||
if !cdr.is_var() {
|
||||
// mark addr + 1 as a list back edge in the cdr of the list
|
||||
self.trail_list_cell(addr + 1, threshold);
|
||||
self.target[addr + 1].set_mark_bit(true);
|
||||
self.target[addr + 1].set_forwarding_bit(true);
|
||||
} else {
|
||||
let car = self
|
||||
.target
|
||||
.store(self.target.deref(heap_loc_as_cell!(addr)));
|
||||
|
||||
if !car.is_var() {
|
||||
// mark addr as a list back edge in the car of the list
|
||||
self.trail_list_cell(addr, threshold);
|
||||
self.target[addr].set_mark_bit(true);
|
||||
}
|
||||
}
|
||||
|
||||
self.scan += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_partial_string(&mut self, scan_tag: HeapCellValueTag, pstr_loc: usize) {
|
||||
read_heap_cell!(self.target[pstr_loc],
|
||||
(HeapCellValueTag::PStrLoc, h) => {
|
||||
debug_assert!(h >= self.old_h);
|
||||
|
||||
*self.value_at_scan() = match scan_tag {
|
||||
HeapCellValueTag::PStrLoc => {
|
||||
pstr_loc_as_cell!(h)
|
||||
}
|
||||
tag => {
|
||||
debug_assert_eq!(tag, HeapCellValueTag::PStrOffset);
|
||||
pstr_offset_as_cell!(h)
|
||||
}
|
||||
};
|
||||
|
||||
fn copy_partial_string(&mut self, pstr_loc: usize) -> Result<(), usize> {
|
||||
match self.pstr_loc_locs.range_mut(..=pstr_loc).next_back() {
|
||||
Some((
|
||||
_prev_pstr_loc,
|
||||
&mut PStrData {
|
||||
pre_old_h_tail_loc,
|
||||
ref mut post_old_h_pstr_loc_locs,
|
||||
..
|
||||
},
|
||||
)) if pre_old_h_tail_loc >= cell_index!(pstr_loc) => {
|
||||
post_old_h_pstr_loc_locs.insert(self.scan);
|
||||
self.scan += 1;
|
||||
return;
|
||||
}
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
debug_assert!(h >= self.old_h);
|
||||
debug_assert_eq!(scan_tag, HeapCellValueTag::PStrOffset);
|
||||
|
||||
*self.value_at_scan() = pstr_offset_as_cell!(h);
|
||||
self.scan += 1;
|
||||
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
);
|
||||
}
|
||||
|
||||
let threshold = self.target.threshold();
|
||||
let offset = self
|
||||
.target
|
||||
.as_slice_from(pstr_loc)
|
||||
.take_while(|b| *b != 0u8)
|
||||
.count();
|
||||
|
||||
let replacement = read_heap_cell!(self.target[pstr_loc],
|
||||
(HeapCellValueTag::CStr) => {
|
||||
debug_assert_eq!(scan_tag, HeapCellValueTag::PStrOffset);
|
||||
let left_pstr_boundary = cell_index!(pstr_loc + offset);
|
||||
let flag = u64::from_be_bytes(self.target[left_pstr_boundary].into_bytes());
|
||||
let pstr_loc_idx = cell_index!(pstr_loc);
|
||||
|
||||
*self.value_at_scan() = pstr_offset_as_cell!(threshold);
|
||||
self.target.push(self.target[pstr_loc]);
|
||||
if flag == 1 {
|
||||
if left_pstr_boundary != pstr_loc_idx {
|
||||
let mut pstr_data = self
|
||||
.pstr_loc_locs
|
||||
.remove(&heap_index!(left_pstr_boundary))
|
||||
.unwrap();
|
||||
|
||||
heap_loc_as_cell!(threshold)
|
||||
pstr_data.post_old_h_pstr_loc_locs.insert(self.scan);
|
||||
self.pstr_loc_locs
|
||||
.insert(heap_index!(cell_index!(pstr_loc)), pstr_data);
|
||||
|
||||
let old_cell = self.target[pstr_loc_idx];
|
||||
self.target[pstr_loc_idx] = HeapCellValue::from_bytes(u64::to_be_bytes(1));
|
||||
self.trail
|
||||
.push((TrailRef::pstr_loc(pstr_loc_idx), old_cell));
|
||||
} else {
|
||||
let pstr_data = self
|
||||
.pstr_loc_locs
|
||||
.get_mut(&heap_index!(left_pstr_boundary))
|
||||
.unwrap();
|
||||
pstr_data.post_old_h_pstr_loc_locs.insert(self.scan);
|
||||
}
|
||||
_ => {
|
||||
*self.value_at_scan() = if scan_tag == HeapCellValueTag::PStrLoc {
|
||||
pstr_loc_as_cell!(threshold)
|
||||
} else {
|
||||
debug_assert_eq!(scan_tag, HeapCellValueTag::PStrOffset);
|
||||
pstr_offset_as_cell!(threshold)
|
||||
};
|
||||
} else {
|
||||
let old_cell = self.target[pstr_loc_idx];
|
||||
self.target[pstr_loc_idx] = HeapCellValue::from_bytes(u64::to_be_bytes(1));
|
||||
self.trail
|
||||
.push((TrailRef::pstr_loc(pstr_loc_idx), old_cell));
|
||||
|
||||
self.target.push(self.target[pstr_loc]);
|
||||
self.target.push(self.target[pstr_loc + 1]);
|
||||
let old_tail_idx = if (pstr_loc + offset + 1) % Heap::heap_cell_alignment() == 0 {
|
||||
cell_index!(pstr_loc + offset) + 2
|
||||
} else {
|
||||
cell_index!(pstr_loc + offset) + 1
|
||||
};
|
||||
|
||||
pstr_loc_as_cell!(threshold)
|
||||
}
|
||||
);
|
||||
let tail_cell = self.target[old_tail_idx];
|
||||
|
||||
let new_tail_idx = self.target.threshold();
|
||||
let mut writer = self.target.reserve(1)?;
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_cell(tail_cell);
|
||||
});
|
||||
|
||||
let mut post_old_h_pstr_loc_locs = IndexSet::with_hasher(FxBuildHasher::default());
|
||||
post_old_h_pstr_loc_locs.insert(self.scan);
|
||||
|
||||
let pstr_data = PStrData {
|
||||
pre_old_h_tail_loc: old_tail_idx,
|
||||
post_old_h_tail_loc: new_tail_idx,
|
||||
post_old_h_pstr_loc_locs,
|
||||
};
|
||||
|
||||
self.pstr_loc_locs
|
||||
.insert(heap_index!(pstr_loc_idx), pstr_data);
|
||||
}
|
||||
|
||||
self.scan += 1;
|
||||
|
||||
let trail_item = mem::replace(&mut self.target[pstr_loc], replacement);
|
||||
self.trail.push((Ref::heap_cell(pstr_loc), trail_item));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_attr_var_lists(&mut self) {
|
||||
fn copy_attr_var_lists(&mut self) -> Result<(), usize> {
|
||||
while !self.attr_var_list_locs.is_empty() {
|
||||
let iter = mem::replace(&mut self.attr_var_list_locs, vec![]);
|
||||
let mut list_loc_vec = std::mem::take(&mut self.attr_var_list_locs);
|
||||
|
||||
for (threshold, list_loc) in iter {
|
||||
while let Some((threshold, list_loc)) = list_loc_vec.pop() {
|
||||
self.target[threshold] = list_loc_as_cell!(self.target.threshold());
|
||||
self.copy_attr_var_list(list_loc);
|
||||
self.target.push_attr_var_queue(threshold - 1);
|
||||
self.copy_attr_var_list(list_loc)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -188,48 +298,49 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
* structure which is ensured by this function and not at all by
|
||||
* the vanilla copier.
|
||||
*/
|
||||
fn copy_attr_var_list(&mut self, mut list_addr: HeapCellValue) {
|
||||
fn copy_attr_var_list(&mut self, mut list_addr: HeapCellValue) -> Result<(), usize> {
|
||||
while let HeapCellValueTag::Lis = list_addr.get_tag() {
|
||||
let threshold = self.target.threshold();
|
||||
let heap_loc = list_addr.get_value() as usize;
|
||||
let str_loc = self.target[heap_loc].get_value() as usize;
|
||||
let str_cell = self.target[str_loc];
|
||||
let mut writer = self.target.reserve(3).unwrap();
|
||||
|
||||
self.target.push(heap_loc_as_cell!(threshold + 2));
|
||||
self.target.push(heap_loc_as_cell!(threshold + 1));
|
||||
writer.write_with(|section| {
|
||||
section.push_cell(heap_loc_as_cell!(threshold + 2));
|
||||
section.push_cell(heap_loc_as_cell!(threshold + 1));
|
||||
|
||||
read_heap_cell!(self.target[str_loc],
|
||||
(HeapCellValueTag::Atom) => {
|
||||
self.target.push(self.target[str_loc]);
|
||||
if str_cell.to_atom().is_some() {
|
||||
section.push_cell(str_cell);
|
||||
}
|
||||
(HeapCellValueTag::Str) => {
|
||||
self.copy_term_impl(self.target[str_loc]);
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
debug_assert_eq!(str_cell.get_tag(), HeapCellValueTag::Str);
|
||||
|
||||
self.copy_term_impl(str_cell)?;
|
||||
list_addr = self.target[heap_loc + 1];
|
||||
|
||||
if HeapCellValueTag::Lis == list_addr.get_tag() {
|
||||
self.target[threshold + 1] = list_loc_as_cell!(self.target.threshold());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reinstantiate_var(&mut self, addr: HeapCellValue, frontier: usize) {
|
||||
fn reinstantiate_var(&mut self, addr: HeapCellValue, frontier: usize) -> Result<(), usize> {
|
||||
read_heap_cell!(addr,
|
||||
(HeapCellValueTag::Var, h) => {
|
||||
self.target[frontier] = heap_loc_as_cell!(frontier);
|
||||
self.target[h] = heap_loc_as_cell!(frontier);
|
||||
|
||||
self.trail.push((Ref::heap_cell(h), heap_loc_as_cell!(h)));
|
||||
self.trail.push((TrailRef::heap_cell(h), heap_loc_as_cell!(h)));
|
||||
}
|
||||
(HeapCellValueTag::StackVar, s) => {
|
||||
self.target[frontier] = heap_loc_as_cell!(frontier);
|
||||
self.target.stack()[s] = heap_loc_as_cell!(frontier);
|
||||
|
||||
self.trail.push((Ref::stack_cell(s), stack_loc_as_cell!(s)));
|
||||
self.trail.push((TrailRef::stack_cell(s), stack_loc_as_cell!(s)));
|
||||
}
|
||||
(HeapCellValueTag::AttrVar, h) => {
|
||||
let threshold = if let AttrVarPolicy::DeepCopy = self.attr_var_policy {
|
||||
@@ -241,14 +352,18 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
self.target[frontier] = heap_loc_as_cell!(threshold);
|
||||
self.target[h] = heap_loc_as_cell!(threshold);
|
||||
|
||||
self.trail.push((Ref::attr_var(h), attr_var_as_cell!(h)));
|
||||
self.trail.push((TrailRef::attr_var(h), attr_var_as_cell!(h)));
|
||||
|
||||
if let AttrVarPolicy::DeepCopy = self.attr_var_policy {
|
||||
self.target.push(attr_var_as_cell!(threshold));
|
||||
self.target.push(heap_loc_as_cell!(threshold + 1));
|
||||
let mut writer = self.target.reserve(2)?;
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_cell(attr_var_as_cell!(threshold));
|
||||
section.push_cell(heap_loc_as_cell!(threshold + 1));
|
||||
});
|
||||
|
||||
let old_list_link = self.target[h + 1];
|
||||
self.trail.push((Ref::heap_cell(h + 1), old_list_link));
|
||||
self.trail.push((TrailRef::heap_cell(h + 1), old_list_link));
|
||||
self.target[h + 1] = heap_loc_as_cell!(threshold + 1);
|
||||
|
||||
if old_list_link.get_tag() == HeapCellValueTag::Lis {
|
||||
@@ -260,9 +375,12 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
unreachable!()
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_var(&mut self, addr: HeapCellValue) {
|
||||
fn copy_var(&mut self, addr: HeapCellValue) -> Result<(), usize> {
|
||||
let index = addr.get_value() as usize;
|
||||
let rd = self.target.deref(addr);
|
||||
let ra = self.target.store(rd);
|
||||
|
||||
@@ -271,48 +389,66 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
if h >= self.old_h {
|
||||
*self.value_at_scan() = ra;
|
||||
self.scan += 1;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Lis, h) => {
|
||||
if h >= self.old_h && self.target[index].get_mark_bit() {
|
||||
*self.value_at_scan() = heap_loc_as_cell!(
|
||||
if ra.get_forwarding_bit() {
|
||||
h + 1
|
||||
} else {
|
||||
h
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
self.scan += 1;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
);
|
||||
|
||||
if rd == ra {
|
||||
self.reinstantiate_var(ra, self.scan);
|
||||
self.reinstantiate_var(ra, self.scan)?;
|
||||
self.scan += 1;
|
||||
} else {
|
||||
*self.value_at_scan() = ra;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_structure(&mut self, addr: usize) {
|
||||
fn copy_structure(&mut self, addr: usize) -> Result<(), usize> {
|
||||
read_heap_cell!(self.target[addr],
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
(HeapCellValueTag::Atom, (_name, arity)) => {
|
||||
let threshold = self.target.threshold();
|
||||
|
||||
*self.value_at_scan() = str_loc_as_cell!(threshold);
|
||||
let index_cell = self.target[addr.saturating_sub(1)];
|
||||
|
||||
let str_cell = if get_structure_index(index_cell).is_some() {
|
||||
// copy the index pointer trailing this
|
||||
// inlined or expanded goal.
|
||||
let mut writer = self.target.reserve(1).unwrap();
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_cell(index_cell);
|
||||
});
|
||||
|
||||
str_loc_as_cell!(threshold + 1)
|
||||
} else {
|
||||
str_loc_as_cell!(threshold)
|
||||
};
|
||||
|
||||
*self.value_at_scan() = str_cell;
|
||||
self.target.copy_slice_to_end(addr .. addr + 1 + arity)?;
|
||||
|
||||
let trail_item = mem::replace(
|
||||
&mut self.target[addr],
|
||||
str_loc_as_cell!(threshold),
|
||||
str_cell,
|
||||
);
|
||||
|
||||
self.trail.push((Ref::heap_cell(addr), trail_item));
|
||||
self.target.push(atom_as_cell!(name, arity));
|
||||
|
||||
for i in 0..arity {
|
||||
let hcv = self.target[addr + 1 + i];
|
||||
self.target.push(hcv);
|
||||
}
|
||||
|
||||
let index_cell = self.target[addr + 1 + arity];
|
||||
|
||||
if get_structure_index(index_cell).is_some() {
|
||||
// copy the index pointer trailing this
|
||||
// inlined or expanded goal.
|
||||
self.target.push(index_cell);
|
||||
}
|
||||
self.trail.push((TrailRef::heap_cell(addr), trail_item));
|
||||
}
|
||||
(HeapCellValueTag::Str, h) => {
|
||||
*self.value_at_scan() = str_loc_as_cell!(h);
|
||||
@@ -323,11 +459,16 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
);
|
||||
|
||||
self.scan += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_term_impl(&mut self, addr: HeapCellValue) {
|
||||
fn copy_term_impl(&mut self, addr: HeapCellValue) -> Result<(), usize> {
|
||||
self.scan = self.target.threshold();
|
||||
self.target.push(addr);
|
||||
let mut writer = self.target.reserve(1)?;
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_cell(addr);
|
||||
});
|
||||
|
||||
while self.scan < self.target.threshold() {
|
||||
let addr = *self.value_at_scan();
|
||||
@@ -336,33 +477,64 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
(HeapCellValueTag::Lis, h) => {
|
||||
if h >= self.old_h {
|
||||
self.scan += 1;
|
||||
continue;
|
||||
} else {
|
||||
self.copy_list(h);
|
||||
self.copy_list(h)
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::AttrVar | HeapCellValueTag::Var) => {
|
||||
self.copy_var(addr);
|
||||
self.copy_var(addr)
|
||||
}
|
||||
(HeapCellValueTag::Str, h) => {
|
||||
self.copy_structure(h);
|
||||
self.copy_structure(h)
|
||||
}
|
||||
(HeapCellValueTag::PStrLoc | HeapCellValueTag::PStrOffset, pstr_loc) => {
|
||||
self.copy_partial_string(addr.get_tag(), pstr_loc);
|
||||
(HeapCellValueTag::PStrLoc, pstr_loc) => {
|
||||
self.copy_partial_string(pstr_loc)
|
||||
}
|
||||
_ => {
|
||||
self.scan += 1;
|
||||
continue;
|
||||
}
|
||||
);
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_pstrs(&mut self) -> Result<(), usize> {
|
||||
while let Some((least_pstr_loc, pstr_data)) = self.pstr_loc_locs.pop_first() {
|
||||
let threshold = heap_index!(self.target.threshold());
|
||||
|
||||
for pstr_loc_loc in pstr_data.post_old_h_pstr_loc_locs {
|
||||
let pstr_loc = self.target[pstr_loc_loc].get_value() as usize;
|
||||
self.target[pstr_loc_loc] =
|
||||
pstr_loc_as_cell!(threshold + pstr_loc - least_pstr_loc);
|
||||
}
|
||||
|
||||
self.target.copy_pstr_to_threshold(least_pstr_loc)?;
|
||||
|
||||
let mut writer = self.target.reserve(1)?;
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_cell(heap_loc_as_cell!(pstr_data.post_old_h_tail_loc));
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unwind_trail(&mut self) {
|
||||
for (r, value) in self.trail.drain(0..) {
|
||||
let index = r.get_value() as usize;
|
||||
for (r, value) in self.trail.drain(..) {
|
||||
let index = r.val() as usize;
|
||||
|
||||
match r.get_tag() {
|
||||
RefTag::AttrVar | RefTag::HeapCell => self.target[index] = value,
|
||||
RefTag::StackCell => self.target.stack()[index] = value,
|
||||
match r.tag() {
|
||||
TrailRefTag::AttrVar | TrailRefTag::HeapCell => {
|
||||
self.target[index] = value;
|
||||
self.target[index].set_mark_bit(false);
|
||||
self.target[index].set_forwarding_bit(false);
|
||||
}
|
||||
TrailRefTag::StackCell => self.target.stack()[index] = value,
|
||||
TrailRefTag::PStrLoc => self.target[index] = value,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,19 +543,26 @@ impl<T: CopierTarget> CopyTermState<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::functor_macro::*;
|
||||
use crate::machine::mock_wam::*;
|
||||
|
||||
#[test]
|
||||
fn copier_tests() {
|
||||
let mut wam = MockWAM::new();
|
||||
|
||||
// clear the heap of resource error data etc
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
let f_atom = atom!("f");
|
||||
let a_atom = atom!("a");
|
||||
let b_atom = atom!("b");
|
||||
|
||||
wam.machine_st
|
||||
.heap
|
||||
.extend(functor!(f_atom, [atom(a_atom), atom(b_atom)]));
|
||||
let mut functor_writer = Heap::functor_writer(functor!(
|
||||
f_atom,
|
||||
[atom_as_cell(a_atom), atom_as_cell(b_atom)]
|
||||
));
|
||||
|
||||
functor_writer(&mut wam.machine_st.heap).unwrap();
|
||||
|
||||
assert_eq!(wam.machine_st.heap[0], atom_as_cell!(f_atom, 2));
|
||||
assert_eq!(wam.machine_st.heap[1], atom_as_cell!(a_atom));
|
||||
@@ -391,7 +570,7 @@ mod tests {
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy);
|
||||
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
// check that the original heap state is still intact.
|
||||
@@ -406,69 +585,266 @@ mod tests {
|
||||
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
let pstr_var_cell =
|
||||
put_partial_string(&mut wam.machine_st.heap, "abc ", &wam.machine_st.atom_tbl);
|
||||
let pstr_cell = wam.machine_st.heap[pstr_var_cell.get_value() as usize];
|
||||
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
|
||||
|
||||
wam.machine_st.heap.pop();
|
||||
wam.machine_st.heap.push(pstr_loc_as_cell!(2));
|
||||
writer.write_with(|section| {
|
||||
section.push_pstr("abc ");
|
||||
section.push_cell(pstr_loc_as_cell!(heap_index!(2)));
|
||||
|
||||
let pstr_second_var_cell =
|
||||
put_partial_string(&mut wam.machine_st.heap, "def", &wam.machine_st.atom_tbl);
|
||||
let pstr_second_cell = wam.machine_st.heap[pstr_second_var_cell.get_value() as usize];
|
||||
|
||||
wam.machine_st.heap.pop();
|
||||
wam.machine_st
|
||||
.heap
|
||||
.push(pstr_loc_as_cell!(wam.machine_st.heap.len() + 1));
|
||||
|
||||
wam.machine_st.heap.push(pstr_offset_as_cell!(0));
|
||||
wam.machine_st
|
||||
.heap
|
||||
.push(fixnum_as_cell!(Fixnum::build_with(0i64)));
|
||||
section.push_pstr("def");
|
||||
section.push_cell(pstr_loc_as_cell!(0));
|
||||
});
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy);
|
||||
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
print_heap_terms(wam.machine_st.heap[6..].iter(), 6);
|
||||
|
||||
assert_eq!(wam.machine_st.heap[0], pstr_cell);
|
||||
assert_eq!(wam.machine_st.heap[1], pstr_loc_as_cell!(2));
|
||||
assert_eq!(wam.machine_st.heap[2], pstr_second_cell);
|
||||
assert_eq!(wam.machine_st.heap[3], pstr_loc_as_cell!(4));
|
||||
assert_eq!(wam.machine_st.heap[4], pstr_offset_as_cell!(0));
|
||||
assert_eq!(wam.machine_st.heap.slice_to_str(0, "abc ".len()), "abc ");
|
||||
assert_eq!(wam.machine_st.heap[1], pstr_loc_as_cell!(heap_index!(2)));
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[5],
|
||||
fixnum_as_cell!(Fixnum::build_with(0i64))
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(2), "def".len()),
|
||||
"def"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[3], pstr_loc_as_cell!(0));
|
||||
|
||||
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(heap_index!(7)));
|
||||
assert_eq!(wam.machine_st.heap[5], pstr_loc_as_cell!(heap_index!(9)));
|
||||
assert_eq!(wam.machine_st.heap[6], pstr_loc_as_cell!(heap_index!(7)));
|
||||
|
||||
assert_eq!(wam.machine_st.heap[7], pstr_cell);
|
||||
assert_eq!(wam.machine_st.heap[8], pstr_loc_as_cell!(9));
|
||||
assert_eq!(wam.machine_st.heap[9], pstr_second_cell);
|
||||
assert_eq!(wam.machine_st.heap[10], pstr_loc_as_cell!(11));
|
||||
assert_eq!(wam.machine_st.heap[11], pstr_offset_as_cell!(7));
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[12],
|
||||
fixnum_as_cell!(Fixnum::build_with(0i64))
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(7), "abc ".len()),
|
||||
"abc "
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[8], heap_loc_as_cell!(5));
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(9), "def".len()),
|
||||
"def"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[10], heap_loc_as_cell!(6));
|
||||
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
wam.machine_st.heap.extend(functor!(
|
||||
f_atom,
|
||||
[
|
||||
atom(a_atom),
|
||||
atom(b_atom),
|
||||
atom(a_atom),
|
||||
cell(str_loc_as_cell!(0))
|
||||
]
|
||||
));
|
||||
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_pstr("abc ");
|
||||
section.push_cell(pstr_loc_as_cell!(heap_index!(2) + 9));
|
||||
|
||||
section.push_pstr("defdefdefdef");
|
||||
section.push_cell(pstr_loc_as_cell!(0));
|
||||
});
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy);
|
||||
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(wam.machine_st.heap.slice_to_str(0, "abc ".len()), "abc ");
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[1],
|
||||
pstr_loc_as_cell!(heap_index!(2) + 9)
|
||||
);
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(2), "defdefdefdef".len()),
|
||||
"defdefdefdef"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(0));
|
||||
|
||||
assert_eq!(wam.machine_st.heap[5], pstr_loc_as_cell!(heap_index!(8)));
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[6],
|
||||
pstr_loc_as_cell!(heap_index!(10) + 1)
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[7], pstr_loc_as_cell!(heap_index!(8)));
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(8), "abc ".len()),
|
||||
"abc "
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[9], heap_loc_as_cell!(6));
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(10), "fdef".len()),
|
||||
"fdef"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[11], heap_loc_as_cell!(7));
|
||||
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_pstr("012345678912345");
|
||||
section.push_cell(pstr_loc_as_cell!(heap_index!(0)));
|
||||
});
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
|
||||
"012345678912345"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[3], pstr_loc_as_cell!(heap_index!(0)));
|
||||
|
||||
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(heap_index!(6)));
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(6), "012345678912345".len()),
|
||||
"012345678912345"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[5], pstr_loc_as_cell!(heap_index!(6)));
|
||||
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_pstr("012345678912345");
|
||||
section.push_cell(pstr_loc_as_cell!(heap_index!(0) + 9));
|
||||
});
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, pstr_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
|
||||
"012345678912345"
|
||||
);
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[3],
|
||||
pstr_loc_as_cell!(heap_index!(0) + 9)
|
||||
);
|
||||
|
||||
assert_eq!(wam.machine_st.heap[4], pstr_loc_as_cell!(heap_index!(6)));
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[5],
|
||||
pstr_loc_as_cell!(heap_index!(6) + 9)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(6), "012345678912345".len()),
|
||||
"012345678912345"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[9], heap_loc_as_cell!(5));
|
||||
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_pstr("012345678912345");
|
||||
section.push_cell(pstr_loc_as_cell!(heap_index!(0) + 7));
|
||||
});
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, pstr_loc_as_cell!(11), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
|
||||
"012345678912345"
|
||||
);
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[3],
|
||||
pstr_loc_as_cell!(heap_index!(0) + 7)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[4],
|
||||
pstr_loc_as_cell!(heap_index!(6) + 11)
|
||||
);
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[5],
|
||||
pstr_loc_as_cell!(heap_index!(6) + 7)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(6), "012345678912345".len()),
|
||||
"012345678912345"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[9], heap_loc_as_cell!(5));
|
||||
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
let mut writer = wam.machine_st.heap.reserve(4).unwrap();
|
||||
|
||||
writer.write_with(|section| {
|
||||
section.push_pstr("012345678912345");
|
||||
section.push_cell(pstr_loc_as_cell!(heap_index!(0) + 12));
|
||||
});
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, pstr_loc_as_cell!(11), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st.heap.slice_to_str(0, "012345678912345".len()),
|
||||
"012345678912345"
|
||||
);
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[3],
|
||||
pstr_loc_as_cell!(heap_index!(0) + 12)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[4],
|
||||
pstr_loc_as_cell!(heap_index!(6) + 3)
|
||||
);
|
||||
assert_eq!(
|
||||
wam.machine_st.heap[5],
|
||||
pstr_loc_as_cell!(heap_index!(6) + 4)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
wam.machine_st
|
||||
.heap
|
||||
.slice_to_str(heap_index!(6), "8912345".len()),
|
||||
"8912345"
|
||||
);
|
||||
assert_eq!(wam.machine_st.heap[8], heap_loc_as_cell!(5));
|
||||
|
||||
wam.machine_st.heap.clear();
|
||||
|
||||
let mut functor_writer = Heap::functor_writer(functor!(
|
||||
f_atom,
|
||||
[
|
||||
atom_as_cell(a_atom),
|
||||
atom_as_cell(b_atom),
|
||||
atom_as_cell(a_atom),
|
||||
str_loc_as_cell(0)
|
||||
]
|
||||
));
|
||||
|
||||
functor_writer(&mut wam.machine_st.heap).unwrap();
|
||||
|
||||
{
|
||||
let wam = TermCopyingMockWAM { wam: &mut wam };
|
||||
copy_term(wam, str_loc_as_cell!(0), AttrVarPolicy::DeepCopy).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(wam.machine_st.heap[0], atom_as_cell!(f_atom, 4));
|
||||
@@ -476,7 +852,6 @@ mod tests {
|
||||
assert_eq!(wam.machine_st.heap[2], atom_as_cell!(b_atom));
|
||||
assert_eq!(wam.machine_st.heap[3], atom_as_cell!(a_atom));
|
||||
assert_eq!(wam.machine_st.heap[4], str_loc_as_cell!(0));
|
||||
|
||||
assert_eq!(wam.machine_st.heap[5], str_loc_as_cell!(6));
|
||||
assert_eq!(wam.machine_st.heap[6], atom_as_cell!(f_atom, 4));
|
||||
assert_eq!(wam.machine_st.heap[7], atom_as_cell!(a_atom));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::atom_table::*;
|
||||
use crate::machine::heap::*;
|
||||
use crate::types::*;
|
||||
|
||||
/* Use the pointer reversal technique of the Deutsch-Schorr-Waite
|
||||
@@ -11,18 +12,18 @@ use crate::types::*;
|
||||
* - Cells are only marked during the backward phase
|
||||
* - Visiting subterms of a visited compound does not immediately shift to the backward phase
|
||||
* - The heads of LIS structures are both marked and forwarded rather
|
||||
* than just forwarded to distinguish them from tails;
|
||||
* than just forwarded to distinguish them from tails
|
||||
* continue_forwarding() checks for this before entering the forward
|
||||
* phase
|
||||
*
|
||||
* Commonalities with the GC marking algorithm:
|
||||
* - The contents of forwarded cells are modified only when they are unforwarded
|
||||
* - Marked (but unforwarded!) cells immediately shift to the backward phase
|
||||
*/
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CycleDetectingIter<'a, const STOP_AT_CYCLES: bool> {
|
||||
pub(crate) heap: &'a mut [HeapCellValue],
|
||||
pub(crate) heap: &'a mut Heap,
|
||||
start: usize,
|
||||
current: usize,
|
||||
next: u64,
|
||||
@@ -31,7 +32,7 @@ pub(crate) struct CycleDetectingIter<'a, const STOP_AT_CYCLES: bool> {
|
||||
}
|
||||
|
||||
impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
pub(crate) fn new(heap: &'a mut [HeapCellValue], start: usize) -> Self {
|
||||
pub(crate) fn new(heap: &'a mut Heap, start: usize) -> Self {
|
||||
heap[start].set_forwarding_bit(true);
|
||||
let next = heap[start].get_value();
|
||||
|
||||
@@ -73,7 +74,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
fn traverse_subterm(&mut self, h: usize, arity: usize) -> Option<usize> {
|
||||
let mut last_cell_loc = h + arity - 1;
|
||||
|
||||
for idx in (h .. h + arity).rev() {
|
||||
for idx in (h..h + arity).rev() {
|
||||
if self.heap[idx].get_forwarding_bit() {
|
||||
if self.cycle_detection_active() {
|
||||
self.cycle_found = true;
|
||||
@@ -93,8 +94,8 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
|
||||
#[inline]
|
||||
fn continue_forwarding(&self) -> bool {
|
||||
self.heap[self.current].get_mark_bit() != self.mark_phase ||
|
||||
self.heap[self.current].get_forwarding_bit()
|
||||
self.heap[self.current].get_mark_bit() != self.mark_phase
|
||||
|| self.heap[self.current].get_forwarding_bit()
|
||||
}
|
||||
|
||||
fn forward(&mut self) -> Option<HeapCellValue> {
|
||||
@@ -127,7 +128,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
self.current = next;
|
||||
self.next = temp;
|
||||
|
||||
if self.next < self.heap.len() as u64 {
|
||||
if self.next < self.heap.cell_len() as u64 {
|
||||
return Some(HeapCellValue::build_with(tag, next as u64));
|
||||
}
|
||||
}
|
||||
@@ -136,10 +137,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
let cell = self.heap[h];
|
||||
let arity = cell_as_atom_cell!(self.heap[h]).get_arity();
|
||||
|
||||
let last_cell_loc = match self.traverse_subterm(h + 1, arity) {
|
||||
Some(last_cell_loc) => last_cell_loc,
|
||||
None => return None,
|
||||
};
|
||||
let last_cell_loc = self.traverse_subterm(h + 1, arity)?;
|
||||
|
||||
if last_cell_loc == h {
|
||||
if self.backward() {
|
||||
@@ -150,7 +148,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
}
|
||||
|
||||
if self.cycle_detection_active() {
|
||||
for idx in (h + 1 .. last_cell_loc).rev() {
|
||||
for idx in (h + 1..last_cell_loc).rev() {
|
||||
if self.heap[idx].get_forwarding_bit() {
|
||||
self.cycle_found = true;
|
||||
return None;
|
||||
@@ -170,13 +168,10 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
let mut cell = self.heap[self.current];
|
||||
cell.set_value(self.next);
|
||||
|
||||
let last_cell_loc = match self.traverse_subterm(self.next as usize, 2) {
|
||||
Some(last_cell_loc) => last_cell_loc,
|
||||
None => return None,
|
||||
};
|
||||
let last_cell_loc = self.traverse_subterm(self.next as usize, 2)?;
|
||||
|
||||
if self.cycle_detection_active() {
|
||||
for idx in (self.next as usize .. last_cell_loc).rev() {
|
||||
for idx in (self.next as usize..last_cell_loc).rev() {
|
||||
if self.heap[idx].get_forwarding_bit() {
|
||||
self.cycle_found = true;
|
||||
return None;
|
||||
@@ -205,10 +200,9 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
}
|
||||
HeapCellValueTag::PStrLoc => {
|
||||
let h = self.next as usize;
|
||||
let cell = self.heap[h];
|
||||
let last_cell_loc = h + 1;
|
||||
let tail_idx = self.heap.scan_slice_to_str(h).tail_idx;
|
||||
|
||||
if self.heap[last_cell_loc].get_forwarding_bit() {
|
||||
if self.heap[tail_idx].get_forwarding_bit() {
|
||||
if self.cycle_detection_active() {
|
||||
self.cycle_found = true;
|
||||
return None;
|
||||
@@ -219,45 +213,13 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.heap[last_cell_loc].set_forwarding_bit(true);
|
||||
self.heap[tail_idx].set_forwarding_bit(true);
|
||||
|
||||
self.next = self.heap[last_cell_loc].get_value();
|
||||
self.heap[last_cell_loc].set_value(self.current as u64);
|
||||
self.current = last_cell_loc;
|
||||
self.next = self.heap[tail_idx].get_value();
|
||||
self.heap[tail_idx].set_value(self.current as u64);
|
||||
self.current = tail_idx;
|
||||
|
||||
return Some(cell);
|
||||
}
|
||||
HeapCellValueTag::PStrOffset => {
|
||||
let h = self.next as usize;
|
||||
let cell = self.heap[h];
|
||||
let last_cell_loc = h + 1;
|
||||
|
||||
if self.heap[h].get_tag() == HeapCellValueTag::PStr {
|
||||
if self.heap[last_cell_loc].get_forwarding_bit() {
|
||||
if self.cycle_detection_active() {
|
||||
self.cycle_found = true;
|
||||
return None;
|
||||
} else if self.backward() {
|
||||
return None;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
self.heap[last_cell_loc].set_forwarding_bit(true);
|
||||
|
||||
self.next = self.heap[last_cell_loc].get_value();
|
||||
self.heap[last_cell_loc].set_value(self.current as u64);
|
||||
self.current = last_cell_loc;
|
||||
} else {
|
||||
debug_assert!(self.heap[h].get_tag() == HeapCellValueTag::CStr);
|
||||
|
||||
self.next = self.heap[h].get_value();
|
||||
self.heap[h].set_value(self.current as u64);
|
||||
self.current = h;
|
||||
}
|
||||
|
||||
return Some(cell);
|
||||
return Some(pstr_loc_as_cell!(h));
|
||||
}
|
||||
tag @ HeapCellValueTag::Atom => {
|
||||
let cell = HeapCellValue::build_with(tag, self.next);
|
||||
@@ -269,11 +231,6 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
HeapCellValueTag::PStr => {
|
||||
if self.backward() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Some(self.backward_and_return());
|
||||
}
|
||||
@@ -309,19 +266,19 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
HeapCellValueTag::Str => {
|
||||
let mut new_str_back_link = self.current;
|
||||
|
||||
for idx in (0 .. self.current).rev() {
|
||||
if self.heap[idx].get_tag() == HeapCellValueTag::Atom {
|
||||
if cell_as_atom_cell!(self.heap[idx]).get_arity() > 0 {
|
||||
new_str_back_link = idx;
|
||||
break;
|
||||
}
|
||||
for idx in (0..self.current).rev() {
|
||||
if self.heap[idx].get_tag() == HeapCellValueTag::Atom
|
||||
&& cell_as_atom_cell!(self.heap[idx]).get_arity() > 0
|
||||
{
|
||||
new_str_back_link = idx;
|
||||
break;
|
||||
}
|
||||
|
||||
if self.heap[idx].get_mark_bit() != self.mark_phase {
|
||||
if !self.heap[idx].get_forwarding_bit() {
|
||||
new_str_back_link = idx;
|
||||
break;
|
||||
}
|
||||
if self.heap[idx].get_mark_bit() != self.mark_phase
|
||||
&& !self.heap[idx].get_forwarding_bit()
|
||||
{
|
||||
new_str_back_link = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +359,7 @@ impl<'a, const STOP_AT_CYCLES: bool> CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
self.next = self.heap[self.start].get_value();
|
||||
self.current = self.start;
|
||||
|
||||
while let Some(_) = self.forward() {}
|
||||
while self.forward().is_some() {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,7 +372,6 @@ impl<'a, const STOP_AT_CYCLES: bool> Iterator for CycleDetectingIter<'a, STOP_AT
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a, const STOP_AT_CYCLES: bool> Drop for CycleDetectingIter<'a, STOP_AT_CYCLES> {
|
||||
fn drop(&mut self) {
|
||||
self.invert_marker();
|
||||
|
||||
@@ -95,11 +95,6 @@ pub struct ChunkInfo {
|
||||
vars: Vec<VarInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BranchArm {
|
||||
pub arm_terms: Vec<QueryTerm>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct BranchInfo {
|
||||
branch_num: BranchNumber,
|
||||
@@ -205,10 +200,12 @@ impl VarData {
|
||||
|
||||
match build_stack.front_mut() {
|
||||
Some(ChunkedTerms::Branch(_)) => {
|
||||
build_stack.push_front(ChunkedTerms::Chunk(VecDeque::from(vec![term])));
|
||||
build_stack.push_front(ChunkedTerms::Chunk {
|
||||
terms: VecDeque::from(vec![term]),
|
||||
});
|
||||
}
|
||||
Some(ChunkedTerms::Chunk(chunk)) => {
|
||||
chunk.push_front(term);
|
||||
Some(ChunkedTerms::Chunk { terms, .. }) => {
|
||||
terms.push_front(term);
|
||||
}
|
||||
None => {
|
||||
unreachable!()
|
||||
@@ -226,7 +223,7 @@ fn merge_branch_seq(branches: impl Iterator<Item = BranchInfo>) -> BranchInfo {
|
||||
|
||||
for mut branch in branches {
|
||||
branch_info.branch_num = branch.branch_num;
|
||||
branch_info.chunks.extend(branch.chunks.drain(..));
|
||||
branch_info.chunks.append(&mut branch.chunks);
|
||||
}
|
||||
|
||||
branch_info.branch_num.delta = branch_info.branch_num.delta * Integer::from(2);
|
||||
@@ -298,7 +295,7 @@ impl VariableClassifier {
|
||||
|
||||
fn merge_branches(&mut self) {
|
||||
for branches in self.branch_map.values_mut() {
|
||||
let mut old_branches = std::mem::replace(branches, vec![]);
|
||||
let mut old_branches = std::mem::take(branches);
|
||||
|
||||
while let Some(last_branch_num) = old_branches.last().map(|bi| &bi.branch_num) {
|
||||
let mut old_branches_len = old_branches.len();
|
||||
@@ -361,10 +358,7 @@ impl VariableClassifier {
|
||||
.current_chunk_type
|
||||
.to_gen_context(self.current_chunk_num);
|
||||
|
||||
let branch_info_v = self
|
||||
.branch_map
|
||||
.entry(var_info.var_ptr.clone())
|
||||
.or_insert_with(|| vec![]);
|
||||
let branch_info_v = self.branch_map.entry(var_info.var_ptr.clone()).or_default();
|
||||
|
||||
let needs_new_branch = if let Some(last_bi) = branch_info_v.last() {
|
||||
!self.root_set.contains(&last_bi.branch_num)
|
||||
@@ -420,56 +414,48 @@ impl VariableClassifier {
|
||||
arity: term.arity(),
|
||||
};
|
||||
|
||||
match term {
|
||||
Term::Clause(_, _, terms) => {
|
||||
for term in terms.into_iter() {
|
||||
for term_ref in breadth_first_iter(term, RootIterationPolicy::Iterated) {
|
||||
if let TermRef::Var(lvl, _, var_ptr) = term_ref {
|
||||
// a body term, so we need the child level here.
|
||||
let lvl = lvl.child_level();
|
||||
if let Term::Clause(_, _, terms) = term {
|
||||
for term in terms.iter() {
|
||||
for term_ref in breadth_first_iter(term, RootIterationPolicy::Iterated) {
|
||||
if let TermRef::Var(lvl, _, var_ptr) = term_ref {
|
||||
// a body term, so we need the child level here.
|
||||
let lvl = lvl.child_level();
|
||||
|
||||
// the body of the if let here is an inlined
|
||||
// "probe_head_var". note the difference between it
|
||||
// and "probe_body_var".
|
||||
let branch_info_v = self
|
||||
.branch_map
|
||||
.entry(var_ptr.clone())
|
||||
.or_insert_with(|| vec![]);
|
||||
// the body of the if let here is an inlined
|
||||
// "probe_head_var". note the difference between it
|
||||
// and "probe_body_var".
|
||||
let branch_info_v = self.branch_map.entry(var_ptr.clone()).or_default();
|
||||
let needs_new_branch = branch_info_v.is_empty();
|
||||
|
||||
let needs_new_branch = branch_info_v.is_empty();
|
||||
|
||||
if needs_new_branch {
|
||||
branch_info_v
|
||||
.push(BranchInfo::new(self.current_branch_num.clone()));
|
||||
}
|
||||
|
||||
let branch_info = branch_info_v.last_mut().unwrap();
|
||||
let needs_new_chunk = branch_info.chunks.is_empty();
|
||||
|
||||
if needs_new_chunk {
|
||||
branch_info.chunks.push(ChunkInfo {
|
||||
chunk_num: self.current_chunk_num,
|
||||
term_loc: GenContext::Head,
|
||||
vars: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let chunk_info = branch_info.chunks.last_mut().unwrap();
|
||||
let var_info = VarInfo {
|
||||
var_ptr,
|
||||
classify_info,
|
||||
chunk_type: self.current_chunk_type,
|
||||
lvl,
|
||||
};
|
||||
|
||||
chunk_info.vars.push(var_info);
|
||||
if needs_new_branch {
|
||||
branch_info_v.push(BranchInfo::new(self.current_branch_num.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
classify_info.arg_c += 1;
|
||||
let branch_info = branch_info_v.last_mut().unwrap();
|
||||
let needs_new_chunk = branch_info.chunks.is_empty();
|
||||
|
||||
if needs_new_chunk {
|
||||
branch_info.chunks.push(ChunkInfo {
|
||||
chunk_num: self.current_chunk_num,
|
||||
term_loc: GenContext::Head,
|
||||
vars: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let chunk_info = branch_info.chunks.last_mut().unwrap();
|
||||
let var_info = VarInfo {
|
||||
var_ptr,
|
||||
classify_info,
|
||||
chunk_type: self.current_chunk_type,
|
||||
lvl,
|
||||
};
|
||||
|
||||
chunk_info.vars.push(var_info);
|
||||
}
|
||||
}
|
||||
|
||||
classify_info.arg_c += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -538,7 +524,10 @@ impl VariableClassifier {
|
||||
build_stack.push_chunk_term(if is_global {
|
||||
QueryTerm::GlobalCut(var_num)
|
||||
} else {
|
||||
QueryTerm::LocalCut { var_num, cut_prev: false }
|
||||
QueryTerm::LocalCut {
|
||||
var_num,
|
||||
cut_prev: false,
|
||||
}
|
||||
});
|
||||
}
|
||||
TraversalState::CutPrev(var_num) => {
|
||||
@@ -548,7 +537,10 @@ impl VariableClassifier {
|
||||
|
||||
self.probe_in_situ_var(var_num);
|
||||
|
||||
build_stack.push_chunk_term(QueryTerm::LocalCut { var_num, cut_prev: true });
|
||||
build_stack.push_chunk_term(QueryTerm::LocalCut {
|
||||
var_num,
|
||||
cut_prev: true,
|
||||
});
|
||||
}
|
||||
TraversalState::Fail => {
|
||||
build_stack.push_chunk_term(QueryTerm::Fail);
|
||||
@@ -587,7 +579,7 @@ impl VariableClassifier {
|
||||
mut terms,
|
||||
) if terms.len() == 3 => {
|
||||
if let Some(last_arg) = terms.last() {
|
||||
if let Term::Literal(_, Literal::CodeIndex(_)) = last_arg {
|
||||
if let Term::Literal(_, Literal::CodeIndexOffset(_)) = last_arg {
|
||||
terms.pop();
|
||||
state_stack.push(TraversalState::Term(Term::Clause(
|
||||
Cell::default(),
|
||||
@@ -667,12 +659,16 @@ impl VariableClassifier {
|
||||
state_stack.last(),
|
||||
Some(TraversalState::RemoveBranchNum)
|
||||
) {
|
||||
// check if the second-to-last element is a regular BuildDisjunct, as we don't
|
||||
// want to add GetPrevLevel in case of a TrustMe.
|
||||
matches!(
|
||||
state_stack.iter().rev().nth(1),
|
||||
Some(TraversalState::BuildDisjunct(..))
|
||||
)
|
||||
// check if the second-to-last element
|
||||
// is a regular BuildDisjunct, as we
|
||||
// don't want to add GetPrevLevel in
|
||||
// case of a TrustMe.
|
||||
match state_stack.iter().rev().nth(1) {
|
||||
Some(&TraversalState::BuildDisjunct(preceding_len)) => {
|
||||
preceding_len + 1 == build_stack.len()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@@ -705,7 +701,9 @@ impl VariableClassifier {
|
||||
state_stack.push(TraversalState::BuildDisjunct(build_stack_len));
|
||||
state_stack.push(TraversalState::Fail);
|
||||
state_stack.push(TraversalState::CutPrev(self.var_num));
|
||||
state_stack.push(TraversalState::ResetGlobalCutVarOverride(self.global_cut_var_num_override));
|
||||
state_stack.push(TraversalState::ResetGlobalCutVarOverride(
|
||||
self.global_cut_var_num_override,
|
||||
));
|
||||
state_stack.push(TraversalState::Term(not_term));
|
||||
state_stack.push(TraversalState::OverrideGlobalCutVar(self.var_num));
|
||||
state_stack.push(TraversalState::GetCutPoint {
|
||||
@@ -804,7 +802,7 @@ impl VariableClassifier {
|
||||
self.call_policy,
|
||||
));
|
||||
}
|
||||
Term::Literal(_, Literal::Atom(atom!("!")) | Literal::Char('!')) => {
|
||||
Term::Literal(_, Literal::Atom(atom!("!"))) => {
|
||||
let (var_num, is_global) =
|
||||
if let Some(var_num) = self.global_cut_var_num_override {
|
||||
(var_num, false)
|
||||
@@ -882,10 +880,10 @@ impl BranchMap {
|
||||
|
||||
for var_info in chunk.vars.iter_mut() {
|
||||
if var_info.lvl == Level::Shallow {
|
||||
let term_loc = var_info.chunk_type.to_gen_context(chunk.chunk_num);
|
||||
let context = var_info.chunk_type.to_gen_context(chunk.chunk_num);
|
||||
temp_var_data
|
||||
.use_set
|
||||
.insert((term_loc, var_info.classify_info.arg_c));
|
||||
.insert((context, var_info.classify_info.arg_c));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1598
src/machine/gc.rs
1598
src/machine/gc.rs
File diff suppressed because it is too large
Load Diff
1270
src/machine/heap.rs
1270
src/machine/heap.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,484 +0,0 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::atom_table;
|
||||
use crate::heap_print::{HCPrinter, HCValueOutputter, PrinterOutputter};
|
||||
use crate::machine::{BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS};
|
||||
use crate::machine::mock_wam::CompositeOpDir;
|
||||
use crate::parser::parser::{Parser, Tokens};
|
||||
use crate::read::write_term_to_heap;
|
||||
use crate::machine::machine_indices::VarKey;
|
||||
use crate::parser::ast::{Var, VarPtr};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{
|
||||
Machine, MachineConfig, QueryResult, QueryResolutionLine,
|
||||
Atom, AtomCell, HeapCellValue, HeapCellValueTag, Value, QueryResolution,
|
||||
streams::Stream
|
||||
};
|
||||
|
||||
impl Machine {
|
||||
pub fn new_lib() -> Self {
|
||||
Machine::new(MachineConfig::in_memory())
|
||||
}
|
||||
|
||||
pub fn load_module_string(&mut self, module_name: &str, program: String) {
|
||||
let stream = Stream::from_owned_string(program, &mut self.machine_st.arena);
|
||||
self.load_file(module_name, stream);
|
||||
}
|
||||
|
||||
pub fn consult_module_string(&mut self, module_name: &str, program: String) {
|
||||
let stream = Stream::from_owned_string(program, &mut self.machine_st.arena);
|
||||
self.machine_st.registers[1] = stream_as_cell!(stream);
|
||||
self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(&self.machine_st.atom_tbl, module_name));
|
||||
|
||||
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
|
||||
}
|
||||
|
||||
fn allocate_stub_choice_point(&mut self) {
|
||||
// NOTE: create a choice point to terminate the dispatch_loop
|
||||
// if an exception is thrown. since the and/or stack is presumed empty,
|
||||
|
||||
let stub_b = self.machine_st.stack.allocate_or_frame(0);
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(0);
|
||||
|
||||
or_frame.prelude.num_cells = 0;
|
||||
or_frame.prelude.e = 0;
|
||||
or_frame.prelude.cp = 0;
|
||||
or_frame.prelude.b = 0;
|
||||
or_frame.prelude.bp = BREAK_FROM_DISPATCH_LOOP_LOC;
|
||||
or_frame.prelude.boip = 0;
|
||||
or_frame.prelude.biip = 0;
|
||||
or_frame.prelude.tr = 0;
|
||||
or_frame.prelude.h = 0;
|
||||
or_frame.prelude.b0 = 0;
|
||||
or_frame.prelude.attr_var_queue_len = 0;
|
||||
|
||||
self.machine_st.b = stub_b;
|
||||
}
|
||||
|
||||
pub fn run_query(&mut self, query: String) -> QueryResult {
|
||||
println!("Query: {}", query);
|
||||
// Parse the query so we can analyze and then call the term
|
||||
let mut parser = Parser::new(
|
||||
Stream::from_owned_string(query, &mut self.machine_st.arena),
|
||||
&mut self.machine_st
|
||||
);
|
||||
let op_dir = CompositeOpDir::new(&self.indices.op_dir, None);
|
||||
let term = parser.read_term(&op_dir, Tokens::Default).expect("Failed to parse query");
|
||||
|
||||
// Write parsed term to heap
|
||||
let term_write_result = write_term_to_heap(&term, &mut self.machine_st.heap, &mut self.machine_st.atom_tbl).expect("couldn't write term to heap");
|
||||
|
||||
// Write term to heap
|
||||
self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
|
||||
|
||||
self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
|
||||
self.machine_st.p = self.indices.code_dir.get(&(atom!("call"), 1)).expect("couldn't get code index").local().unwrap();
|
||||
|
||||
let var_names: IndexMap<_, _> = term_write_result.var_dict.iter()
|
||||
.map(|(var_key, cell)| match var_key {
|
||||
// NOTE: not the intention behind Var::InSitu here but
|
||||
// we can hijack it to store anonymous variables
|
||||
// without creating problems.
|
||||
VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))),
|
||||
VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.allocate_stub_choice_point();
|
||||
|
||||
let stub_b = self.machine_st.b;
|
||||
|
||||
let mut matches: Vec<QueryResolutionLine> = Vec::new();
|
||||
// Call the term
|
||||
loop {
|
||||
self.dispatch_loop();
|
||||
|
||||
//println!("b: {}", self.machine_st.b);
|
||||
//println!("stub_b: {}", stub_b);
|
||||
//println!("fail: {}", self.machine_st.fail);
|
||||
|
||||
if self.machine_st.ball.stub.len() != 0 {
|
||||
// NOTE: this means an exception was thrown, at which
|
||||
// point we backtracked to the stub choice point.
|
||||
// this should halt the search for solutions as it
|
||||
// does in the Scryer top-level. the exception term is
|
||||
// contained in self.machine_st.ball.
|
||||
let error_string = self.machine_st.ball.stub
|
||||
.iter()
|
||||
.filter(|h| match h.get_tag() {
|
||||
HeapCellValueTag::Atom => true,
|
||||
HeapCellValueTag::Fixnum => true,
|
||||
_ => false,
|
||||
})
|
||||
.map(|h| match h.get_tag() {
|
||||
HeapCellValueTag::Atom => {
|
||||
let (name, _) = cell_as_atom_cell!(h).get_name_and_arity();
|
||||
name.as_str().to_string()
|
||||
}
|
||||
HeapCellValueTag::Fixnum => {
|
||||
h.get_value().clone().to_string()
|
||||
},
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ");
|
||||
|
||||
return Err(error_string);
|
||||
}
|
||||
|
||||
/*
|
||||
if self.machine_st.fail {
|
||||
// NOTE: only print results on success
|
||||
self.machine_st.fail = false;
|
||||
println!("fail!");
|
||||
matches.push(QueryResolutionLine::False);
|
||||
break;
|
||||
};
|
||||
*/
|
||||
|
||||
if term_write_result.var_dict.is_empty() {
|
||||
if self.machine_st.p == LIB_QUERY_SUCCESS {
|
||||
matches.push(QueryResolutionLine::True);
|
||||
break;
|
||||
} else if self.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
|
||||
// NOTE: only print results on success
|
||||
// self.machine_st.fail = false;
|
||||
// println!("b == stub_b");
|
||||
matches.push(QueryResolutionLine::False);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut bindings: BTreeMap<String, Value> = BTreeMap::new();
|
||||
|
||||
for (var_key, term_to_be_printed) in &term_write_result.var_dict {
|
||||
if var_key.to_string().starts_with("_") {
|
||||
continue;
|
||||
}
|
||||
let mut printer = HCPrinter::new(
|
||||
&mut self.machine_st.heap,
|
||||
Arc::clone(&self.machine_st.atom_tbl),
|
||||
&mut self.machine_st.stack,
|
||||
&self.indices.op_dir,
|
||||
PrinterOutputter::new(),
|
||||
*term_to_be_printed,
|
||||
);
|
||||
|
||||
printer.ignore_ops = false;
|
||||
printer.numbervars = true;
|
||||
printer.quoted = true;
|
||||
printer.max_depth = 1000; // NOTE: set this to 0 for unbounded depth
|
||||
printer.double_quotes = true;
|
||||
printer.var_names = var_names.clone();
|
||||
|
||||
let outputter = printer.print();
|
||||
|
||||
let output: String = outputter.result();
|
||||
println!("Result: {} = {}", var_key.to_string(), output);
|
||||
|
||||
bindings.insert(var_key.to_string(), Value::try_from(output).expect("asdfs"));
|
||||
}
|
||||
|
||||
matches.push(QueryResolutionLine::Match(bindings));
|
||||
|
||||
// NOTE: there are outstanding choicepoints, backtrack
|
||||
// through them for further solutions. if
|
||||
// self.machine_st.b == stub_b we've backtracked to the stub
|
||||
// choice point, so we should break.
|
||||
self.machine_st.backtrack();
|
||||
|
||||
if self.machine_st.b <= stub_b {
|
||||
// NOTE: out of choicepoints to backtrack through, no
|
||||
// more solutions to gather.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: deallocate stub choice point
|
||||
if self.machine_st.b == stub_b {
|
||||
self.trust_me();
|
||||
}
|
||||
|
||||
Ok(QueryResolution::from(matches))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ordered_float::OrderedFloat;
|
||||
|
||||
use super::*;
|
||||
use crate::machine::{QueryMatch, Value, QueryResolution};
|
||||
|
||||
#[test]
|
||||
fn programatic_query() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
let query = String::from(r#"triple("a",P,"b")."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p1"),
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p2"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
|
||||
Ok(QueryResolution::True)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("x","y","z")."#)),
|
||||
Ok(QueryResolution::False)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failing_query() {
|
||||
let mut machine = Machine::new_lib();
|
||||
let query = String::from(r#"triple("a",P,"b")."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Err(String::from("error existence_error procedure / triple 3 / triple 3"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_results() {
|
||||
let mut machine = Machine::new_lib();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
:- discontiguous(subject_class/2).
|
||||
:- discontiguous(constructor/2).
|
||||
|
||||
subject_class("Todo", c).
|
||||
constructor(c, '[{action: "addLink", source: "this", predicate: "todo://state", target: "todo://ready"}]').
|
||||
|
||||
subject_class("Recipe", xyz).
|
||||
constructor(xyz, '[{action: "addLink", source: "this", predicate: "recipe://title", target: "literal://string:Meta%20Muffins"}]').
|
||||
"#.to_string());
|
||||
|
||||
let result = machine.run_query(String::from("subject_class(\"Todo\", C), constructor(C, Actions)."));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"C" => Value::from("c"),
|
||||
"Actions" => Value::from("[{action: \"addLink\", source: \"this\", predicate: \"todo://state\", target: \"todo://ready\"}]"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
let result = machine.run_query(String::from("subject_class(\"Recipe\", C), constructor(C, Actions)."));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"C" => Value::from("xyz"),
|
||||
"Actions" => Value::from("[{action: \"addLink\", source: \"this\", predicate: \"recipe://title\", target: \"literal://string:Meta%20Muffins\"}]"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
let result = machine.run_query(String::from("subject_class(Class, _)."));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"Class" => Value::from("Todo")
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"Class" => Value::from("Recipe")
|
||||
}),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_results() {
|
||||
let mut machine = Machine::new_lib();
|
||||
machine.load_module_string(
|
||||
"facts",
|
||||
r#"
|
||||
list([1,2,3]).
|
||||
"#.to_string());
|
||||
|
||||
let result = machine.run_query(String::from("list(X)."));
|
||||
assert_eq!(
|
||||
result,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"X" => Value::List(
|
||||
Vec::from([
|
||||
Value::Float(OrderedFloat::from(1.0)),
|
||||
Value::Float(OrderedFloat::from(2.0)),
|
||||
Value::Float(OrderedFloat::from(3.0))
|
||||
])
|
||||
)
|
||||
}),
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn consult() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
let query = String::from(r#"triple("a",P,"b")."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p1"),
|
||||
}),
|
||||
QueryMatch::from(btreemap! {
|
||||
"P" => Value::from("p2"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
|
||||
Ok(QueryResolution::True)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("x","y","z")."#)),
|
||||
Ok(QueryResolution::False)
|
||||
);
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
triple("a", "new", "b").
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","p1","b")."#)),
|
||||
Ok(QueryResolution::False)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
machine.run_query(String::from(r#"triple("a","new","b")."#)),
|
||||
Ok(QueryResolution::True)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#[ignore = "fails on windows"]
|
||||
#[test]
|
||||
fn stress_integration_test() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
// File with test commands, i.e. program code to consult and queries to run
|
||||
let code = include_str!("./lib_integration_test_commands.txt");
|
||||
|
||||
// Split the code into blocks
|
||||
let blocks = code.split("=====");
|
||||
|
||||
let mut i = 0;
|
||||
// Iterate over the blocks
|
||||
for block in blocks {
|
||||
// Trim the block to remove any leading or trailing whitespace
|
||||
let block = block.trim();
|
||||
|
||||
// Skip empty blocks
|
||||
if block.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the block is a query
|
||||
if block.starts_with("query") {
|
||||
// Extract the query from the block
|
||||
let query = &block[5..];
|
||||
|
||||
i += 1;
|
||||
println!("query #{}: {}", i, query);
|
||||
// Parse and execute the query
|
||||
let result = machine.run_query(query.to_string());
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Print the result
|
||||
println!("{:?}", result);
|
||||
} else if block.starts_with("consult") {
|
||||
// Extract the code from the block
|
||||
let code = &block[7..];
|
||||
|
||||
println!("load code: {}", code);
|
||||
|
||||
// Load the code into the machine
|
||||
machine.consult_module_string("facts", code.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn findall() {
|
||||
let mut machine = Machine::new_lib();
|
||||
|
||||
machine.consult_module_string(
|
||||
"facts",
|
||||
String::from(
|
||||
r#"
|
||||
triple("a", "p1", "b").
|
||||
triple("a", "p2", "b").
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
let query = String::from(r#"findall([Predicate, Target], triple(_,Predicate,Target), Result)."#);
|
||||
let output = machine.run_query(query);
|
||||
assert_eq!(
|
||||
output,
|
||||
Ok(QueryResolution::Matches(vec![
|
||||
QueryMatch::from(btreemap! {
|
||||
"Predicate" => Value::from("Predicate"),
|
||||
"Result" => Value::List(
|
||||
Vec::from([
|
||||
Value::List([Value::from("p1"), Value::from("b")].into()),
|
||||
Value::List([Value::from("p2"), Value::from("b")].into()),
|
||||
])
|
||||
),
|
||||
"Target" => Value::from("Target"),
|
||||
}),
|
||||
]))
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
13106
src/machine/lib_machine/lib_integration_test_commands.txt
Normal file
13106
src/machine/lib_machine/lib_integration_test_commands.txt
Normal file
File diff suppressed because one or more lines are too long
621
src/machine/lib_machine/mod.rs
Normal file
621
src/machine/lib_machine/mod.rs
Normal file
@@ -0,0 +1,621 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::atom_table;
|
||||
use crate::heap_iter::{stackful_post_order_iter, NonListElider};
|
||||
use crate::machine::machine_indices::VarKey;
|
||||
use crate::machine::mock_wam::CompositeOpDir;
|
||||
use crate::machine::{
|
||||
ArenaHeaderTag, Fixnum, Number, BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS,
|
||||
};
|
||||
use crate::offset_table::*;
|
||||
use crate::parser::ast::{Var, VarPtr};
|
||||
use crate::parser::parser::{Parser, Tokens};
|
||||
use crate::read::{write_term_to_heap, TermWriteResult};
|
||||
use crate::types::UntypedArenaPtr;
|
||||
|
||||
use dashu::{Integer, Rational};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Represents a leaf answer from a query.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LeafAnswer {
|
||||
/// A `true` leaf answer.
|
||||
True,
|
||||
/// A `false` leaf answer.
|
||||
///
|
||||
/// This means that there are no more answers for the query.
|
||||
False,
|
||||
/// An exception leaf answer.
|
||||
Exception(Term),
|
||||
/// A leaf answer with bindings.
|
||||
#[non_exhaustive]
|
||||
LeafAnswer {
|
||||
/// The bindings of variables in the query.
|
||||
bindings: BTreeMap<String, Term>,
|
||||
//residual_goals: Vec<Term>,
|
||||
},
|
||||
}
|
||||
|
||||
impl LeafAnswer {
|
||||
/// Creates a leaf answer with no residual goals.
|
||||
pub fn from_bindings<S: Into<String>>(bindings: impl IntoIterator<Item = (S, Term)>) -> Self {
|
||||
LeafAnswer::LeafAnswer {
|
||||
bindings: bindings.into_iter().map(|(k, v)| (k.into(), v)).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a Prolog term.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Term {
|
||||
/// An arbitrary precision integer.
|
||||
Integer(Integer),
|
||||
/// An arbitrary precision rational.
|
||||
Rational(Rational),
|
||||
/// A float.
|
||||
Float(f64),
|
||||
/// A Prolog atom.
|
||||
Atom(String),
|
||||
/// A Prolog string.
|
||||
///
|
||||
/// In particular, this represents Prolog lists of characters.
|
||||
String(String),
|
||||
/// A Prolog list.
|
||||
List(Vec<Term>),
|
||||
/// A Prolog compound term.
|
||||
Compound(String, Vec<Term>),
|
||||
/// A Prolog variable.
|
||||
Var(String),
|
||||
}
|
||||
|
||||
impl Term {
|
||||
/// Creates an integer term.
|
||||
pub fn integer(value: impl Into<Integer>) -> Self {
|
||||
Term::Integer(value.into())
|
||||
}
|
||||
|
||||
/// Creates a rational term.
|
||||
pub fn rational(value: impl Into<Rational>) -> Self {
|
||||
Term::Rational(value.into())
|
||||
}
|
||||
|
||||
/// Creates a float term.
|
||||
pub fn float(value: impl Into<f64>) -> Self {
|
||||
Term::Float(value.into())
|
||||
}
|
||||
|
||||
/// Creates an atom term.
|
||||
pub fn atom(value: impl Into<String>) -> Self {
|
||||
Term::Atom(value.into())
|
||||
}
|
||||
|
||||
/// Creates a string term.
|
||||
///
|
||||
/// In specific, this represents a list of chars in Prolog.
|
||||
pub fn string(value: impl Into<String>) -> Self {
|
||||
Term::String(value.into())
|
||||
}
|
||||
|
||||
/// Creates a list term.
|
||||
pub fn list(value: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::List(value.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Creates a compound term.
|
||||
pub fn compound(functor: impl Into<String>, args: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::Compound(functor.into(), args.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Creates a variable.
|
||||
pub fn variable(value: impl Into<String>) -> Self {
|
||||
Term::Var(value.into())
|
||||
}
|
||||
|
||||
/// Creates a conjunction, giving the atom `true` if empty.
|
||||
pub fn conjunction(value: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::try_conjunction(value).unwrap_or(Term::atom("true"))
|
||||
}
|
||||
|
||||
/// Creates a conjunction, giving `None` if empty.
|
||||
pub fn try_conjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
|
||||
let mut iter = value.into_iter();
|
||||
iter.next().map(|first| {
|
||||
Term::try_conjunction(iter)
|
||||
.map(|rest| Term::compound(",", [first.clone(), rest]))
|
||||
.unwrap_or(first)
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a disjunction, giving the atom `false` if empty.
|
||||
pub fn disjunction(value: impl IntoIterator<Item = Term>) -> Self {
|
||||
Term::try_disjunction(value).unwrap_or(Term::atom("false"))
|
||||
}
|
||||
|
||||
/// Creates a disjunction, giving `None` if empty.
|
||||
pub fn try_disjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
|
||||
let mut iter = value.into_iter();
|
||||
iter.next().map(|first| {
|
||||
Term::try_disjunction(iter)
|
||||
.map(|rest| Term::compound(";", [first.clone(), rest]))
|
||||
.unwrap_or(first)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This is an auxiliary function to turn a count into names of anonymous variables like _A, _B,
|
||||
/// _AB, etc...
|
||||
fn count_to_letter_code(mut count: usize) -> String {
|
||||
let mut letters = Vec::new();
|
||||
|
||||
loop {
|
||||
let letter_idx = (count % 26) as u32;
|
||||
letters.push(char::from_u32('A' as u32 + letter_idx).unwrap());
|
||||
count /= 26;
|
||||
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
letters.into_iter().chain("_".chars()).rev().collect()
|
||||
}
|
||||
|
||||
impl Term {
|
||||
pub(crate) fn from_heapcell(
|
||||
machine: &mut Machine,
|
||||
heap_cell: HeapCellValue,
|
||||
var_names: &mut IndexMap<HeapCellValue, VarPtr>,
|
||||
) -> Self {
|
||||
// Adapted from MachineState::read_term_from_heap
|
||||
let mut term_stack = vec![];
|
||||
|
||||
machine.machine_st.heap[0] = heap_cell;
|
||||
|
||||
let mut iter = stackful_post_order_iter::<NonListElider>(
|
||||
&mut machine.machine_st.heap,
|
||||
&mut machine.machine_st.stack,
|
||||
0,
|
||||
);
|
||||
|
||||
let mut anon_count: usize = 0;
|
||||
let var_ptr_cmp = |a, b| match a {
|
||||
Var::Named(name_a) => match b {
|
||||
Var::Named(name_b) => name_a.cmp(&name_b),
|
||||
_ => Ordering::Less,
|
||||
},
|
||||
_ => match b {
|
||||
Var::Named(_) => Ordering::Greater,
|
||||
_ => Ordering::Equal,
|
||||
},
|
||||
};
|
||||
|
||||
while let Some(addr) = iter.next() {
|
||||
let addr = unmark_cell_bits!(addr);
|
||||
|
||||
read_heap_cell!(addr,
|
||||
(HeapCellValueTag::Lis) => {
|
||||
let tail = term_stack.pop().unwrap();
|
||||
let head = term_stack.pop().unwrap();
|
||||
|
||||
let list = match tail {
|
||||
Term::Atom(atom) if atom == "[]" => match head {
|
||||
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
|
||||
// Handle lists of char as strings
|
||||
Term::String(a.to_string())
|
||||
}
|
||||
_ => Term::List(vec![head]),
|
||||
},
|
||||
Term::List(elems) if elems.is_empty() => match head {
|
||||
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
|
||||
// Handle lists of char as strings
|
||||
Term::String(a.to_string())
|
||||
},
|
||||
_ => Term::List(vec![head]),
|
||||
},
|
||||
Term::List(mut elems) => {
|
||||
elems.insert(0, head);
|
||||
Term::List(elems)
|
||||
},
|
||||
Term::String(mut elems) => match head {
|
||||
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
|
||||
// Handle lists of char as strings
|
||||
elems.insert(0, a.chars().next().unwrap());
|
||||
Term::String(elems)
|
||||
},
|
||||
_ => {
|
||||
let mut elems: Vec<Term> = elems
|
||||
.chars()
|
||||
.map(|x| Term::Atom(x.into()))
|
||||
.collect();
|
||||
elems.insert(0, head);
|
||||
Term::List(elems)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
Term::Compound(".".into(), vec![head, tail])
|
||||
}
|
||||
};
|
||||
term_stack.push(list);
|
||||
}
|
||||
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => {
|
||||
let var = var_names.get(&addr).map(|x| x.borrow().clone());
|
||||
match var {
|
||||
Some(Var::Named(name)) => term_stack.push(Term::Var(name.as_ref().to_owned())),
|
||||
_ => {
|
||||
let anon_name = loop {
|
||||
// Generate a name for the anonymous variable
|
||||
let anon_name = Rc::new(count_to_letter_code(anon_count));
|
||||
|
||||
// Find if this name is already being used
|
||||
var_names.sort_by(|_, a, _, b| {
|
||||
var_ptr_cmp(a.borrow().clone(), b.borrow().clone())
|
||||
});
|
||||
let binary_result = var_names.binary_search_by(|_,a| {
|
||||
let var_ptr = Var::Named(anon_name.clone());
|
||||
var_ptr_cmp(a.borrow().clone(), var_ptr.clone())
|
||||
});
|
||||
|
||||
match binary_result {
|
||||
Ok(_) => anon_count += 1, // Name already used
|
||||
Err(_) => {
|
||||
// Name not used, assign it to this variable
|
||||
let var_ptr = VarPtr::from(Var::Named(anon_name.clone()));
|
||||
var_names.insert(addr, var_ptr);
|
||||
break anon_name;
|
||||
},
|
||||
}
|
||||
};
|
||||
term_stack.push(Term::Var(anon_name.as_ref().to_owned()));
|
||||
},
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::F64Offset, offset) => {
|
||||
let f = machine.machine_st.arena.f64_tbl.get_entry(offset);
|
||||
term_stack.push(Term::Float(f.into()));
|
||||
}
|
||||
(HeapCellValueTag::Fixnum, n) => {
|
||||
term_stack.push(Term::Integer(n.into()));
|
||||
}
|
||||
(HeapCellValueTag::Cons, ptr) => {
|
||||
if let Ok(n) = Number::try_from((addr, &machine.machine_st.arena.f64_tbl)) {
|
||||
match n {
|
||||
Number::Integer(i) => term_stack.push(Term::Integer((*i).clone())),
|
||||
Number::Rational(r) => term_stack.push(Term::Rational((*r).clone())),
|
||||
_ => { unreachable!() },
|
||||
}
|
||||
} else {
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::Stream, stream) => {
|
||||
let stream_term = if let Some(alias) = stream.options().get_alias() {
|
||||
Term::atom(alias.as_str().to_string())
|
||||
} else {
|
||||
Term::compound("$stream", [
|
||||
Term::integer(stream.as_ptr().addr())
|
||||
])
|
||||
};
|
||||
term_stack.push(stream_term);
|
||||
}
|
||||
(ArenaHeaderTag::Dropped, _stream) => {
|
||||
term_stack.push(Term::atom("$dropped_value"));
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::Atom, (name, arity)) => {
|
||||
if arity == 0 {
|
||||
let atom_name = name.as_str().to_string();
|
||||
if atom_name == "[]" {
|
||||
term_stack.push(Term::List(vec![]));
|
||||
} else {
|
||||
term_stack.push(Term::Atom(atom_name));
|
||||
}
|
||||
} else {
|
||||
let subterms = term_stack
|
||||
.drain(term_stack.len() - arity ..)
|
||||
.collect();
|
||||
|
||||
term_stack.push(Term::Compound(name.as_str().to_string(), subterms));
|
||||
}
|
||||
}
|
||||
(HeapCellValueTag::PStrLoc, pstr_loc) => {
|
||||
let tail = term_stack.pop().unwrap();
|
||||
let char_iter = iter.base_iter.heap.char_iter(pstr_loc);
|
||||
|
||||
match tail {
|
||||
Term::Atom(atom) => {
|
||||
if atom == "[]" {
|
||||
term_stack.push(Term::String(atom.as_str().to_string()));
|
||||
}
|
||||
},
|
||||
Term::List(l) if l.is_empty() => {
|
||||
term_stack.push(Term::String(char_iter.collect()));
|
||||
}
|
||||
Term::List(l) => {
|
||||
let mut list: Vec<Term> = char_iter
|
||||
.map(|x| Term::Atom(x.to_string()))
|
||||
.collect();
|
||||
list.extend(l.into_iter());
|
||||
term_stack.push(Term::List(list));
|
||||
},
|
||||
_ => {
|
||||
let mut list: Vec<Term> = char_iter
|
||||
.map(|x| Term::Atom(x.to_string()))
|
||||
.collect();
|
||||
|
||||
let mut partial_list = Term::Compound(
|
||||
".".into(),
|
||||
vec![
|
||||
list.pop().unwrap(),
|
||||
tail,
|
||||
],
|
||||
);
|
||||
|
||||
while let Some(last) = list.pop() {
|
||||
partial_list = Term::Compound(
|
||||
".".into(),
|
||||
vec![
|
||||
last,
|
||||
partial_list,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
term_stack.push(partial_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
debug_assert_eq!(term_stack.len(), 1);
|
||||
term_stack.pop().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator though the leaf answers of a query.
|
||||
pub struct QueryState<'a> {
|
||||
machine: &'a mut Machine,
|
||||
term: TermWriteResult,
|
||||
stub_b: usize,
|
||||
var_names: IndexMap<HeapCellValue, VarPtr>,
|
||||
called: bool,
|
||||
}
|
||||
|
||||
impl Drop for QueryState<'_> {
|
||||
fn drop(&mut self) {
|
||||
// FIXME: This may be wrong if the iterator is not fully consumend, but from testing it
|
||||
// seems fine. Is this really ok?
|
||||
self.machine.trust_me();
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for QueryState<'_> {
|
||||
type Item = Result<LeafAnswer, Term>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let var_names = &mut self.var_names;
|
||||
let term_write_result = &self.term;
|
||||
let machine = &mut self.machine;
|
||||
|
||||
// No more choicepoints, end iteration
|
||||
if self.called && machine.machine_st.b <= self.stub_b {
|
||||
return None;
|
||||
}
|
||||
|
||||
machine.dispatch_loop();
|
||||
|
||||
self.called = true;
|
||||
|
||||
if !machine.machine_st.ball.stub.is_empty() {
|
||||
// NOTE: this means an exception was thrown, at which
|
||||
// point we backtracked to the stub choice point.
|
||||
// this should halt the search for solutions as it
|
||||
// does in the Scryer top-level. the exception term is
|
||||
// contained in self.machine_st.ball.
|
||||
let h = machine.machine_st.heap.cell_len();
|
||||
|
||||
if let Err(resource_err_loc) = machine
|
||||
.machine_st
|
||||
.heap
|
||||
.append(&machine.machine_st.ball.stub)
|
||||
{
|
||||
return Some(Err(Term::from_heapcell(
|
||||
machine,
|
||||
machine.machine_st.heap[resource_err_loc],
|
||||
&mut IndexMap::new(),
|
||||
)));
|
||||
}
|
||||
|
||||
let exception_term =
|
||||
Term::from_heapcell(machine, machine.machine_st.heap[h], &mut var_names.clone());
|
||||
|
||||
if let Term::Compound(functor, args) = &exception_term {
|
||||
if functor == "error" && args.len() == 2 {
|
||||
// We have an error
|
||||
return Some(Err(exception_term));
|
||||
}
|
||||
}
|
||||
|
||||
// We have an exception that is not an error
|
||||
return Some(Ok(LeafAnswer::Exception(exception_term)));
|
||||
}
|
||||
|
||||
if machine.machine_st.p == LIB_QUERY_SUCCESS {
|
||||
if term_write_result.var_dict.is_empty() {
|
||||
self.machine.machine_st.backtrack();
|
||||
return Some(Ok(LeafAnswer::True));
|
||||
}
|
||||
} else if machine.machine_st.p == BREAK_FROM_DISPATCH_LOOP_LOC {
|
||||
return Some(Ok(LeafAnswer::False));
|
||||
}
|
||||
|
||||
let mut bindings: BTreeMap<String, Term> = BTreeMap::new();
|
||||
|
||||
let var_dict = &term_write_result.var_dict;
|
||||
|
||||
for (var_key, term_to_be_printed) in var_dict.iter() {
|
||||
let mut var_name = var_key.to_string();
|
||||
if var_name.starts_with('_') {
|
||||
let should_print = var_names.values().any(|x| match x.borrow().clone() {
|
||||
Var::Named(v) => *v == *var_name,
|
||||
_ => false,
|
||||
});
|
||||
if !should_print {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut term =
|
||||
Term::from_heapcell(machine, *term_to_be_printed, &mut var_names.clone());
|
||||
|
||||
if let Term::Var(ref term_str) = term {
|
||||
if *term_str == var_name {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Var dict is in the order things appear in the query. If var_name appears
|
||||
// after term in the query, switch their places.
|
||||
let var_name_idx = var_dict
|
||||
.get_index_of(&VarKey::VarPtr(Var::from(var_name.clone()).into()))
|
||||
.unwrap();
|
||||
let term_idx =
|
||||
var_dict.get_index_of(&VarKey::VarPtr(Var::from(term_str.clone()).into()));
|
||||
if let Some(idx) = term_idx {
|
||||
if idx < var_name_idx {
|
||||
let new_term = Term::Var(var_name);
|
||||
let new_var_name = term_str.into();
|
||||
term = new_term;
|
||||
var_name = new_var_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bindings.insert(var_name, term);
|
||||
}
|
||||
|
||||
// NOTE: there are outstanding choicepoints, backtrack
|
||||
// through them for further solutions. if
|
||||
// self.machine_st.b == stub_b we've backtracked to the stub
|
||||
// choice point, so we should break.
|
||||
self.machine.machine_st.backtrack();
|
||||
|
||||
Some(Ok(LeafAnswer::LeafAnswer { bindings }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
/// Loads a module into the [`Machine`] from a string.
|
||||
pub fn load_module_string(&mut self, module_name: &str, program: impl Into<String>) {
|
||||
let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
|
||||
self.load_file(module_name, stream);
|
||||
}
|
||||
|
||||
/// Consults a module into the [`Machine`] from a string.
|
||||
pub fn consult_module_string(&mut self, module_name: &str, program: impl Into<String>) {
|
||||
let stream = Stream::from_owned_string(program.into(), &mut self.machine_st.arena);
|
||||
self.machine_st.registers[1] = stream_as_cell!(stream);
|
||||
self.machine_st.registers[2] = atom_as_cell!(&atom_table::AtomTable::build_with(
|
||||
&self.machine_st.atom_tbl,
|
||||
module_name
|
||||
));
|
||||
|
||||
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
|
||||
}
|
||||
|
||||
pub(crate) fn allocate_stub_choice_point(&mut self) {
|
||||
// NOTE: create a choice point to terminate the dispatch_loop
|
||||
// if an exception is thrown.
|
||||
|
||||
let stub_b = self.machine_st.stack.allocate_or_frame(0);
|
||||
let or_frame = self.machine_st.stack.index_or_frame_mut(stub_b);
|
||||
|
||||
or_frame.prelude.num_cells = 0;
|
||||
or_frame.prelude.e = 0;
|
||||
or_frame.prelude.cp = 0;
|
||||
or_frame.prelude.b = 0;
|
||||
or_frame.prelude.bp = BREAK_FROM_DISPATCH_LOOP_LOC;
|
||||
or_frame.prelude.boip = 0;
|
||||
or_frame.prelude.biip = 0;
|
||||
or_frame.prelude.tr = 0;
|
||||
or_frame.prelude.h = 0;
|
||||
or_frame.prelude.b0 = 0;
|
||||
or_frame.prelude.attr_var_queue_len = 0;
|
||||
|
||||
self.machine_st.b = stub_b;
|
||||
self.machine_st.hb = self.machine_st.heap.cell_len();
|
||||
self.machine_st.block = stub_b;
|
||||
}
|
||||
|
||||
/// Runs a query.
|
||||
pub fn run_query(&mut self, query: impl Into<String>) -> QueryState<'_> {
|
||||
let mut parser = Parser::new(
|
||||
Stream::from_owned_string(query.into(), &mut self.machine_st.arena),
|
||||
&mut self.machine_st,
|
||||
);
|
||||
let op_dir = CompositeOpDir::new(&self.indices.op_dir, None);
|
||||
let term = parser
|
||||
.read_term(&op_dir, Tokens::Default)
|
||||
.expect("Failed to parse query");
|
||||
|
||||
self.allocate_stub_choice_point();
|
||||
|
||||
// Write parsed term to heap
|
||||
let term_write_result = write_term_to_heap(&term, &mut self.machine_st.heap)
|
||||
.expect("couldn't write term to heap");
|
||||
|
||||
let var_names: IndexMap<_, _> = term_write_result
|
||||
.var_dict
|
||||
.iter()
|
||||
.map(|(var_key, cell)| match var_key {
|
||||
// NOTE: not the intention behind Var::InSitu here but
|
||||
// we can hijack it to store anonymous variables
|
||||
// without creating problems.
|
||||
VarKey::AnonVar(h) => (*cell, VarPtr::from(Var::InSitu(*h))),
|
||||
VarKey::VarPtr(var_ptr) => (*cell, var_ptr.clone()),
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Write term to heap
|
||||
self.machine_st.registers[1] = self.machine_st.heap[term_write_result.heap_loc];
|
||||
|
||||
self.machine_st.cp = LIB_QUERY_SUCCESS; // BREAK_FROM_DISPATCH_LOOP_LOC;
|
||||
let call_index_p = self
|
||||
.indices
|
||||
.code_dir
|
||||
.get(&(atom!("call"), 1))
|
||||
.cloned()
|
||||
.map(|offset| {
|
||||
self.machine_st
|
||||
.arena
|
||||
.code_index_tbl
|
||||
.get_entry(offset.into())
|
||||
.p() as usize
|
||||
})
|
||||
.expect("couldn't get code index");
|
||||
|
||||
self.machine_st.execute_at_index(1, call_index_p);
|
||||
|
||||
let stub_b = self.machine_st.b;
|
||||
QueryState {
|
||||
machine: self,
|
||||
term: term_write_result,
|
||||
stub_b,
|
||||
var_names,
|
||||
called: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user