Compare commits
2061 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78b35dc57d | ||
|
|
c65d1204af | ||
|
|
3bfed50d81 | ||
|
|
4523bb5b81 | ||
|
|
75aec69de1 | ||
|
|
9b96735615 | ||
|
|
f74d74fe6e | ||
|
|
4fd059be5f | ||
|
|
81712f4c82 | ||
|
|
98086de77e | ||
|
|
24456e9703 | ||
|
|
7f024f3b8d | ||
|
|
67c1b171c7 | ||
|
|
04fbb0c1ce | ||
|
|
327423ab84 | ||
|
|
20cfffdff5 | ||
|
|
d8e126044f | ||
|
|
61f975ea18 | ||
|
|
25d4950216 | ||
|
|
5aa1521819 | ||
|
|
dd030fa18b | ||
|
|
013df58fea | ||
|
|
745ddc2c87 | ||
|
|
f6d3b2f896 | ||
|
|
7da321ab4f | ||
|
|
92fdc7e380 | ||
|
|
2e811de0f5 | ||
|
|
f02cd0ad3c | ||
|
|
a36df33a67 | ||
|
|
4a6bf5fd5f | ||
|
|
26c0b4fc75 | ||
|
|
fad363e64a | ||
|
|
094cf2ac5d | ||
|
|
cc82727d20 | ||
|
|
924750f826 | ||
|
|
ffbf630b5c | ||
|
|
8073a4ba87 | ||
|
|
ec450fc567 | ||
|
|
2c05ebbded | ||
|
|
0ddda0a864 | ||
|
|
02328d818c | ||
|
|
0f55ba7218 | ||
|
|
1c089a2bbb | ||
|
|
54a887cdc3 | ||
|
|
ca28c76e52 | ||
|
|
a70157003b | ||
|
|
6aa3c7d5d6 | ||
|
|
4ef8c5c47d | ||
|
|
31d17f135a | ||
|
|
03f7b01109 | ||
|
|
bff48e7c7f | ||
|
|
3b67ffa814 | ||
|
|
49b4e4cbcb | ||
|
|
287c308bc3 | ||
|
|
af44d91568 | ||
|
|
40b6890c54 | ||
|
|
7248425a76 | ||
|
|
1dcc1ca524 | ||
|
|
0e17d6acd7 | ||
|
|
c89217903a | ||
|
|
762e6d3ba4 | ||
|
|
e9ae80e250 | ||
|
|
fd7f24e265 | ||
|
|
24450a8827 | ||
|
|
57e8ed65b0 | ||
|
|
3f819e2dfd | ||
|
|
9a7862c322 | ||
|
|
92c77cdff5 | ||
|
|
554e956ef5 | ||
|
|
60d9d01a55 | ||
|
|
cb79552dd0 | ||
|
|
f310ff24a5 | ||
|
|
b1963864d2 | ||
|
|
b4e7000eb2 | ||
|
|
e529e7ba21 | ||
|
|
1697cd5c7f | ||
|
|
dcd7360b17 | ||
|
|
4fd247f881 | ||
|
|
85bc544fb9 | ||
|
|
14646074be | ||
|
|
1ba040c24d | ||
|
|
56f6772422 | ||
|
|
db43d461b9 | ||
|
|
42a50474da | ||
|
|
cf367024fd | ||
|
|
67d198ac77 | ||
|
|
a154a34f87 | ||
|
|
86c90d77dd | ||
|
|
4e1a4dae6c | ||
|
|
cf345d8174 | ||
|
|
a18da368a1 | ||
|
|
5d3295c40c | ||
|
|
de10ccfdee | ||
|
|
65a8ce8e22 | ||
|
|
5a7da721cd | ||
|
|
b234ef7ea3 | ||
|
|
617c961f88 | ||
|
|
e95355e56e | ||
|
|
ff5e9a793b | ||
|
|
101d0548db | ||
|
|
b6a81c51ab | ||
|
|
ba2cd43144 | ||
|
|
bd720b49f3 | ||
|
|
9590d5200c | ||
|
|
12f890e4a2 | ||
|
|
a9cb826bf3 | ||
|
|
4163cb038d | ||
|
|
b051f39145 | ||
|
|
cfc49243c8 | ||
|
|
29430ec88b | ||
|
|
d520046a4f | ||
|
|
814ce2d672 | ||
|
|
4fd37335f5 | ||
|
|
1791bd8626 | ||
|
|
3f5dbc1680 | ||
|
|
44052cb373 | ||
|
|
8c33da11ce | ||
|
|
ab80c84714 | ||
|
|
bb420e9347 | ||
|
|
55a1f8d3da | ||
|
|
fba7790637 | ||
|
|
918dfca409 | ||
|
|
d18f128a3c | ||
|
|
c8b9059289 | ||
|
|
b8a6882a27 | ||
|
|
fb8e3071f2 | ||
|
|
067b5998ee | ||
|
|
2e26f37f5e | ||
|
|
6525c1f543 | ||
|
|
4db0b385f3 | ||
|
|
b7f77d1747 | ||
|
|
811ff65209 | ||
|
|
fd70d8975b | ||
|
|
b746a8f9ab | ||
|
|
483e4568a2 | ||
|
|
9ff1b660f1 | ||
|
|
5ffdd2d91a | ||
|
|
5b81bee941 | ||
|
|
3b9b9e75c4 | ||
|
|
faef45fd68 | ||
|
|
b5b45dde9d | ||
|
|
5ab087bc1e | ||
|
|
7683367c0e | ||
|
|
d2db66b9f7 | ||
|
|
c58d8804a1 | ||
|
|
5d09449c95 | ||
|
|
076a75d138 | ||
|
|
7f69ac3f4a | ||
|
|
7f159a7ed2 | ||
|
|
42274ef3ae | ||
|
|
ab893be418 | ||
|
|
f5e7573bd6 | ||
|
|
9cdad087ef | ||
|
|
e36f96fd47 | ||
|
|
cb25a90250 | ||
|
|
38a9d23174 | ||
|
|
521118265a | ||
|
|
a09306c585 | ||
|
|
8140ff9154 | ||
|
|
3cbe78cb9b | ||
|
|
699afb2c00 | ||
|
|
d079a18459 | ||
|
|
b0566e4150 | ||
|
|
c38a26e6cd | ||
|
|
31030738a4 | ||
|
|
caf84a259e | ||
|
|
1620824d3a | ||
|
|
1d2f5053d6 | ||
|
|
42282c6e6e | ||
|
|
a3f8ddd24a | ||
|
|
bfe808a779 | ||
|
|
28065b0565 | ||
|
|
4953cfd10e | ||
|
|
db972de40c | ||
|
|
330e9ba4ef | ||
|
|
bb09de1805 | ||
|
|
a6a0cef9fc | ||
|
|
c36bd4dc07 | ||
|
|
83f352b95e | ||
|
|
c84a5c3282 | ||
|
|
58af615dd4 | ||
|
|
c4b13a2176 | ||
|
|
039fffb339 | ||
|
|
8613513b9c | ||
|
|
ceb276b249 | ||
|
|
16f281e3d1 | ||
|
|
b593fffc7d | ||
|
|
ce890799bc | ||
|
|
aa65287c3b | ||
|
|
a6522d6317 | ||
|
|
0b45d42912 | ||
|
|
d9829a3606 | ||
|
|
59766e2db4 | ||
|
|
e52a4fbfc0 | ||
|
|
c05afb4705 | ||
|
|
9f209dadd9 | ||
|
|
2ec45b7413 | ||
|
|
bf581879e6 | ||
|
|
9bc3757a9e | ||
|
|
18d0a74f23 | ||
|
|
7a188744da | ||
|
|
7f45ac3f7a | ||
|
|
612861e010 | ||
|
|
fcae0d9fcf | ||
|
|
f446939770 | ||
|
|
92853a6a12 | ||
|
|
4ad113a6f8 | ||
|
|
89ed1aa8de | ||
|
|
d7f5675727 | ||
|
|
4d982d22c1 | ||
|
|
5ed1802f0f | ||
|
|
2716381e7b | ||
|
|
c5c83d724a | ||
|
|
749dedf477 | ||
|
|
911c49c43f | ||
|
|
770a682d8b | ||
|
|
73ca37ecca | ||
|
|
e0f49e8f43 | ||
|
|
dae34b6009 | ||
|
|
5850125d97 | ||
|
|
97bd778745 | ||
|
|
43df2e2649 | ||
|
|
c2f2623471 | ||
|
|
8e4465315f | ||
|
|
86beb222ae | ||
|
|
5e124ccf44 | ||
|
|
47d4e6d2f9 | ||
|
|
33f65210ee | ||
|
|
9ea6cb4cab | ||
|
|
0e583d620a | ||
|
|
b205abe949 | ||
|
|
cb59c3003a | ||
|
|
942095baa7 | ||
|
|
097849385e | ||
|
|
c4783062ff | ||
|
|
063cf0c608 | ||
|
|
a66d666bed | ||
|
|
170818759d | ||
|
|
e41d1b319b | ||
|
|
b9c9de5222 | ||
|
|
d565f5901b | ||
|
|
46317c3a39 | ||
|
|
bcc5bff376 | ||
|
|
5244d71570 | ||
|
|
9fac289d9a | ||
|
|
de665c05d8 | ||
|
|
98b0ab3409 | ||
|
|
3c344b176b | ||
|
|
6093c2858d | ||
|
|
e06ab1ca1c | ||
|
|
5154314786 | ||
|
|
fa07306f09 | ||
|
|
07115ce4f5 | ||
|
|
b656700294 | ||
|
|
7bc7f0ad06 | ||
|
|
495df8846a | ||
|
|
05d48cdcc3 | ||
|
|
dc02be4944 | ||
|
|
462097d956 | ||
|
|
0e374c2e96 | ||
|
|
94a1313916 | ||
|
|
b54a4afad6 | ||
|
|
c71b8e05f0 | ||
|
|
021c01dfd0 | ||
|
|
30f222b837 | ||
|
|
f7d9237bb9 | ||
|
|
49addc7b04 | ||
|
|
2f9996f9ac | ||
|
|
c1218fc986 | ||
|
|
57ef706eb0 | ||
|
|
e4b19dc1dd | ||
|
|
4e60cc46a2 | ||
|
|
d8edf7bfff | ||
|
|
e951db662d | ||
|
|
c5a3ec3ba8 | ||
|
|
f12a90351a | ||
|
|
b162c40007 | ||
|
|
402100fd52 | ||
|
|
2a1b8f37ec | ||
|
|
7d2e59ab64 | ||
|
|
f35298a227 | ||
|
|
198e925430 | ||
|
|
8b7281fad0 | ||
|
|
502574dfc3 | ||
|
|
7a0f4e5787 | ||
|
|
f277f660f3 | ||
|
|
9c037d6028 | ||
|
|
396528189d | ||
|
|
4b882c465c | ||
|
|
8937cac47d | ||
|
|
5a3e2899dd | ||
|
|
3a5ed4723b | ||
|
|
dcf4c44173 | ||
|
|
5763a4b9df | ||
|
|
82200a21eb | ||
|
|
fc7d98d748 | ||
|
|
5dce7d9075 | ||
|
|
f08f539768 | ||
|
|
be45672e22 | ||
|
|
94efb9ffe3 | ||
|
|
fd1e902492 | ||
|
|
8d6e3d7d56 | ||
|
|
a197bb6815 | ||
|
|
9de8456c7f | ||
|
|
ebf8091dba | ||
|
|
47ce84b273 | ||
|
|
449e097f4a | ||
|
|
fe27605497 | ||
|
|
8792ee438c | ||
|
|
7279062d3c | ||
|
|
b87fe1e21f | ||
|
|
d6ac125425 | ||
|
|
b79d8732ea | ||
|
|
3df0806017 | ||
|
|
c7759aa737 | ||
|
|
8ab1155fc5 | ||
|
|
adc77985d7 | ||
|
|
d85fc7c9f8 | ||
|
|
41b083c962 | ||
|
|
4ee6a7bfb8 | ||
|
|
6e53d08d40 | ||
|
|
01285f12c3 | ||
|
|
cd586aab8c | ||
|
|
4da646252b | ||
|
|
cc8bb38abc | ||
|
|
04ba9bc11a | ||
|
|
9b35a316c9 | ||
|
|
3a522f3c98 | ||
|
|
4c44859132 | ||
|
|
ecd77f7512 | ||
|
|
56bd596af3 | ||
|
|
21acb9361a | ||
|
|
c3477d8476 | ||
|
|
cba09d4ea1 | ||
|
|
884b0ca10e | ||
|
|
c9ecfb11d9 | ||
|
|
9288dcabe9 | ||
|
|
73df96244d | ||
|
|
7396630627 | ||
|
|
0ac93751d0 | ||
|
|
400ca21213 | ||
|
|
3286e78cd2 | ||
|
|
24eb9ce483 | ||
|
|
3dc6ed79d2 | ||
|
|
f94294dbd9 | ||
|
|
04ba58067a | ||
|
|
92b262d599 | ||
|
|
7ffb40e0ad | ||
|
|
997161c740 | ||
|
|
84c95c59e9 | ||
|
|
669242a8ce | ||
|
|
2a04d5e799 | ||
|
|
9d52d2a653 | ||
|
|
95f6ebc000 | ||
|
|
6e9cd072c5 | ||
|
|
729f2b1cb9 | ||
|
|
1a01438064 | ||
|
|
34ec6d3167 | ||
|
|
c9295323f6 | ||
|
|
92d543b8a8 | ||
|
|
3f445c76be | ||
|
|
a6e416f13d | ||
|
|
601ff567e3 | ||
|
|
56783b8e4b | ||
|
|
326f18ea75 | ||
|
|
491472a8c5 | ||
|
|
359619e035 | ||
|
|
9454d670c7 | ||
|
|
2fcec4fff9 | ||
|
|
2c2a9fe01e | ||
|
|
165a55dac6 | ||
|
|
196e9c1e47 | ||
|
|
17450520ba | ||
|
|
97702f3071 | ||
|
|
e8408ca93f | ||
|
|
e0dcf88b68 | ||
|
|
cc38cf15a4 | ||
|
|
da4c0a359b | ||
|
|
ce56a7303e | ||
|
|
5667ec8699 | ||
|
|
36b3150225 | ||
|
|
28c338416f | ||
|
|
26a8fc1a37 | ||
|
|
f347baafa3 | ||
|
|
95278c221b | ||
|
|
5a28366158 | ||
|
|
ac0cea8a73 | ||
|
|
814b631543 | ||
|
|
0d8c7f8785 | ||
|
|
5bae8fcaf8 | ||
|
|
4110cfcfcb | ||
|
|
2bdba261ea | ||
|
|
58fb851717 | ||
|
|
1118b37c92 | ||
|
|
39606a2277 | ||
|
|
275d306b69 | ||
|
|
e948413c09 | ||
|
|
f8bb3148d0 | ||
|
|
e7cf70936a | ||
|
|
409287e8f8 | ||
|
|
87d6ef16f7 | ||
|
|
3c08eba559 | ||
|
|
d399b68d01 | ||
|
|
2e9ec653a8 | ||
|
|
7ca782b92d | ||
|
|
996496c3f5 | ||
|
|
90cf713186 | ||
|
|
ca4aaf44de | ||
|
|
b04d845ec0 | ||
|
|
a8ea2b0f97 | ||
|
|
a29227d0d4 | ||
|
|
7f8f137aa0 | ||
|
|
cc7e721611 | ||
|
|
058cbcf19a | ||
|
|
22b815dc5c | ||
|
|
c709853aa7 | ||
|
|
d755bb7e12 | ||
|
|
6d99912b7b | ||
|
|
82dd9e0596 | ||
|
|
6ba3d49034 | ||
|
|
64be8e0fba | ||
|
|
43a297b691 | ||
|
|
df266378c4 | ||
|
|
ec55765cb2 | ||
|
|
de2ff8118f | ||
|
|
c30c9dc98f | ||
|
|
1f872c3f0c | ||
|
|
b6204eb6f1 | ||
|
|
af9f0f81d8 | ||
|
|
1c08b56e05 | ||
|
|
6cb8020f62 | ||
|
|
d742d4cde9 | ||
|
|
b7d06540e6 | ||
|
|
909f2e1058 | ||
|
|
26d39c3617 | ||
|
|
371bc3b231 | ||
|
|
bdeabcdd89 | ||
|
|
7f177c3d03 | ||
|
|
4ed36a12e9 | ||
|
|
a7e93db363 | ||
|
|
789715f31a | ||
|
|
7cc900d437 | ||
|
|
ff0f6f4fc2 | ||
|
|
5698e8f21a | ||
|
|
ddcae2c906 | ||
|
|
468d096ccb | ||
|
|
bca79d12c0 | ||
|
|
03eba9594b | ||
|
|
5e0e3e2754 | ||
|
|
04fd835267 | ||
|
|
f072761150 | ||
|
|
46d1e3bee3 | ||
|
|
f8e6e0252d | ||
|
|
f9e3bdb6b0 | ||
|
|
a80aab48cd | ||
|
|
3a4aa2a541 | ||
|
|
84583da5b8 | ||
|
|
f213956ceb | ||
|
|
c5caa9d311 | ||
|
|
0c4d93f01f | ||
|
|
a4e8bfc1ba | ||
|
|
542b9e1976 | ||
|
|
73a1ee59fa | ||
|
|
2771109427 | ||
|
|
cc420bd31a | ||
|
|
2fe1d2ef53 | ||
|
|
bb624cc971 | ||
|
|
aa7b8e52f3 | ||
|
|
07f358c91b | ||
|
|
8ae0a1a4af | ||
|
|
7647ad14b8 | ||
|
|
21e4347b3e | ||
|
|
f2d041ea23 | ||
|
|
56c1c4e43c | ||
|
|
820011412b | ||
|
|
81afa6942f | ||
|
|
6cb8d7596a | ||
|
|
a2d46af5ac | ||
|
|
6d94b8ab75 | ||
|
|
0166c3bf0f | ||
|
|
6a995fb62b | ||
|
|
d804d8a92e | ||
|
|
cf63e8375d | ||
|
|
1eff758751 | ||
|
|
0a8fc70ba9 | ||
|
|
b4b72a3166 | ||
|
|
a5054c0064 | ||
|
|
705f421d53 | ||
|
|
4dc0114c52 | ||
|
|
5f2c77fa74 | ||
|
|
5f703afed1 | ||
|
|
c6aa2068e2 | ||
|
|
209f7a239a | ||
|
|
0d404ad374 | ||
|
|
d5db0c641c | ||
|
|
21d6220f3f | ||
|
|
e0464d5447 | ||
|
|
309e5b320e | ||
|
|
afa9703eb5 | ||
|
|
56e5da6680 | ||
|
|
a8810d73e4 | ||
|
|
4b31c30cab | ||
|
|
df4b56148f | ||
|
|
f22a48f576 | ||
|
|
d660e4244f | ||
|
|
d383e5eb8b | ||
|
|
e1dc114517 | ||
|
|
73780ebf63 | ||
|
|
a19f7a0b9f | ||
|
|
e07473d155 | ||
|
|
d429b263eb | ||
|
|
b2a0d0c1c7 | ||
|
|
8bafd7adb1 | ||
|
|
76d24fe4e3 | ||
|
|
3bdcc3aba9 | ||
|
|
cb25b27963 | ||
|
|
b3df81c143 | ||
|
|
68b3c480c9 | ||
|
|
9fd1bf5574 | ||
|
|
b491a06c6d | ||
|
|
d16312a314 | ||
|
|
d19a8d6b98 | ||
|
|
c90dd80ece | ||
|
|
eb9d865635 | ||
|
|
694c87cb61 | ||
|
|
c41aba6b90 | ||
|
|
28ea672e36 | ||
|
|
9366a48d6d | ||
|
|
c7e1f5d568 | ||
|
|
d91ee5b77c | ||
|
|
fd97b84916 | ||
|
|
23f59970cb | ||
|
|
8781e03863 | ||
|
|
f2940ddfcf | ||
|
|
140149f051 | ||
|
|
9055370326 | ||
|
|
1af62c8d11 | ||
|
|
ec5430826e | ||
|
|
b2cccab768 | ||
|
|
579aa8acd4 | ||
|
|
9c2cb144b5 | ||
|
|
3d04689660 | ||
|
|
a891cc4edf | ||
|
|
706ab2ae5b | ||
|
|
9c1de8e00b | ||
|
|
1109e05e06 | ||
|
|
ce313b8a6b | ||
|
|
7c96b91663 | ||
|
|
87e966e185 | ||
|
|
1e018681de | ||
|
|
5b324680ba | ||
|
|
abca5fc405 | ||
|
|
6a455d2866 | ||
|
|
70da818101 | ||
|
|
5ec837acb4 | ||
|
|
76e33d051c | ||
|
|
7c5318b784 | ||
|
|
47892bf24a | ||
|
|
8a9cd7779c | ||
|
|
d4c0277065 | ||
|
|
069e132c0e | ||
|
|
4e6c138099 | ||
|
|
0ab355eada | ||
|
|
44825826df | ||
|
|
612f09893c | ||
|
|
e9f507b868 | ||
|
|
78278c804f | ||
|
|
b51460a59a | ||
|
|
181be5be3f | ||
|
|
91d4e91f53 | ||
|
|
ad3ae7991b | ||
|
|
c7caf6b7a9 | ||
|
|
4422ffe39f | ||
|
|
1ff52f70aa | ||
|
|
fce45167a5 | ||
|
|
b8f384045c | ||
|
|
ea95a7900c | ||
|
|
edea1273c8 | ||
|
|
ec9c763211 | ||
|
|
5a08117e75 | ||
|
|
0aec980aa8 | ||
|
|
6b05ee5130 | ||
|
|
1ffbf63d20 | ||
|
|
b905d2758d | ||
|
|
311d985145 | ||
|
|
a9a06b5297 | ||
|
|
6b8e620495 | ||
|
|
577f85099d | ||
|
|
607c84a79b | ||
|
|
2eec6499ff | ||
|
|
cd1150c11d | ||
|
|
987bbdecf5 | ||
|
|
a68394b6f2 | ||
|
|
3d6fbabc86 | ||
|
|
1479b5d38c | ||
|
|
336311ecc8 | ||
|
|
e9bb35c895 | ||
|
|
ab4f93dcee | ||
|
|
140a199805 | ||
|
|
232b66cfc3 | ||
|
|
11ba7e47fd | ||
|
|
c36d4a9c9a | ||
|
|
4b5c22864e | ||
|
|
5e1faeb5d2 | ||
|
|
4b7c2ba6c8 | ||
|
|
b9285f8de1 | ||
|
|
084fc84590 | ||
|
|
7a9b71cc03 | ||
|
|
6b49653754 | ||
|
|
d5fa8cc211 | ||
|
|
10a11c293e | ||
|
|
e486862db8 | ||
|
|
1c8aae0839 | ||
|
|
6feb1f8b82 | ||
|
|
bef8eb538c | ||
|
|
4e3b066555 | ||
|
|
4d23542ef3 | ||
|
|
47a6b498e3 | ||
|
|
b6f77f4e6f | ||
|
|
b96cd06129 | ||
|
|
e8eb6765bd | ||
|
|
8df552e952 | ||
|
|
18c52e076f | ||
|
|
1810dabc14 | ||
|
|
609a3a229f | ||
|
|
dff56643f0 | ||
|
|
77f8d52271 | ||
|
|
1e9821ec0c | ||
|
|
66e047083a | ||
|
|
e6438e79d8 | ||
|
|
a5e72679bc | ||
|
|
15cc916333 | ||
|
|
2f3de51e55 | ||
|
|
cea1353fbb | ||
|
|
bfbafd4168 | ||
|
|
27a15a2464 | ||
|
|
c2a87f898e | ||
|
|
aaa75c3ffc | ||
|
|
2dfa108034 | ||
|
|
4895cb7b22 | ||
|
|
2d3eb8483f | ||
|
|
36ef988514 | ||
|
|
d58dd7cc91 | ||
|
|
7e06abebd5 | ||
|
|
8dd7f51ae3 | ||
|
|
6030fae685 | ||
|
|
0f502fff84 | ||
|
|
789f662ec8 | ||
|
|
72536037ca | ||
|
|
e4eefc92f4 | ||
|
|
23c4e935b4 | ||
|
|
68b4951bc9 | ||
|
|
ed89c43e48 | ||
|
|
16137ace0e | ||
|
|
b4ef7556da | ||
|
|
eea83d9474 | ||
|
|
9d5264c5a3 | ||
|
|
66075bf45b | ||
|
|
35b8f69f92 | ||
|
|
2d5fe21b05 | ||
|
|
b448681872 | ||
|
|
05c14d5780 | ||
|
|
bb9de52a53 | ||
|
|
7595ec16e5 | ||
|
|
d8cf0f320d | ||
|
|
d644a3996e | ||
|
|
525050c407 | ||
|
|
468088fd6b | ||
|
|
95c192a988 | ||
|
|
d6287fae06 | ||
|
|
9e98f75495 | ||
|
|
56681af1b6 | ||
|
|
8bf3d4ea71 | ||
|
|
00eab4d415 | ||
|
|
0876d46880 | ||
|
|
ed803d30cd | ||
|
|
b6504bf377 | ||
|
|
85b4f235ef | ||
|
|
064bfa5e0d | ||
|
|
4a6ffb6b9e | ||
|
|
d952b7ace8 | ||
|
|
3b6138eaaa | ||
|
|
f3167f6b5f | ||
|
|
8c99d748e2 | ||
|
|
0ab928063e | ||
|
|
c9a34bde3f | ||
|
|
7f85c682b8 | ||
|
|
181185bad6 | ||
|
|
01a9fd9e25 | ||
|
|
3be00045cf | ||
|
|
86176dce8d | ||
|
|
1fa8a0a969 | ||
|
|
d5fe2e88d1 | ||
|
|
9177dbce7f | ||
|
|
55a896367d | ||
|
|
f2e49c84cc | ||
|
|
afa0bea1b1 | ||
|
|
f7e31fceac | ||
|
|
c490d818fb | ||
|
|
32bdedde6d | ||
|
|
48960f1beb | ||
|
|
cf65cbe2f1 | ||
|
|
45c35eca3e | ||
|
|
bb2a8c9090 | ||
|
|
6798b22b49 | ||
|
|
3acbe2a418 | ||
|
|
c45cdd6ea0 | ||
|
|
f520c38b60 | ||
|
|
046a2175ab | ||
|
|
83def95383 | ||
|
|
012fefa37e | ||
|
|
4878e42e07 | ||
|
|
1a7992e524 | ||
|
|
94d7c6dc5f | ||
|
|
23bfb4da50 | ||
|
|
1f21a5fe62 | ||
|
|
2173a8e3cb | ||
|
|
067e2633af | ||
|
|
d916da9ba8 | ||
|
|
c2fc33afec | ||
|
|
3d4e400ed2 | ||
|
|
75d719d204 | ||
|
|
172e3e9f34 | ||
|
|
8e32bdd8db | ||
|
|
895b02b641 | ||
|
|
d06b5f7ab5 | ||
|
|
e88ec6736c | ||
|
|
922abf21e7 | ||
|
|
c55bfa2c6e | ||
|
|
5463c1d47f | ||
|
|
3513a9c7ba | ||
|
|
45db236ec7 | ||
|
|
5848ff5735 | ||
|
|
64c9ac4071 | ||
|
|
75fc922898 | ||
|
|
59e2c3fd5e | ||
|
|
5bd73741cf | ||
|
|
fcfa3beaa3 | ||
|
|
dfbdaa9dab | ||
|
|
5f1f07e5a1 | ||
|
|
eace0d9b37 | ||
|
|
d83b32a200 | ||
|
|
9cefb33388 | ||
|
|
bd5d6d0686 | ||
|
|
ecebaf8216 | ||
|
|
8762647b55 | ||
|
|
664c0ee77c | ||
|
|
7a9c3d351d | ||
|
|
69e52d1ed8 | ||
|
|
bd594e0c05 | ||
|
|
3bf42860e2 | ||
|
|
0532f51cc2 | ||
|
|
1808597258 | ||
|
|
2744989cf1 | ||
|
|
0d04abb5c7 | ||
|
|
495168e7bb | ||
|
|
475fbf9561 | ||
|
|
c6264cf098 | ||
|
|
bc9123affb | ||
|
|
7fd12972ad | ||
|
|
2d19243b3b | ||
|
|
8ad4f188f2 | ||
|
|
0c19c56909 | ||
|
|
de35baadf3 | ||
|
|
66c209f9e4 | ||
|
|
d7a3ed2d4a | ||
|
|
0c25ffc26e | ||
|
|
9f864574de | ||
|
|
a38f7c8524 | ||
|
|
bf85cd404c | ||
|
|
102adb3544 | ||
|
|
c2b360d03e | ||
|
|
1bec1b7002 | ||
|
|
1b4b4807a5 | ||
|
|
d4d135f2a9 | ||
|
|
96faad1c01 | ||
|
|
d55d5f15ae | ||
|
|
26e4560429 | ||
|
|
775bd3a08b | ||
|
|
d92d8bce89 | ||
|
|
1b91663244 | ||
|
|
8fb673e93e | ||
|
|
d1372d9b3b | ||
|
|
063f0da565 | ||
|
|
c893247107 | ||
|
|
a5adcfff4c | ||
|
|
d3583276b3 | ||
|
|
55d8de1b23 | ||
|
|
5b829636cb | ||
|
|
68ac92a616 | ||
|
|
db20cda27a | ||
|
|
60dd47c696 | ||
|
|
42da543980 | ||
|
|
e4ea547623 | ||
|
|
33a6c81a07 | ||
|
|
9ba503cd6b | ||
|
|
f3f3dccf8e | ||
|
|
2f0718e885 | ||
|
|
7b8001d060 | ||
|
|
3c022e4332 | ||
|
|
37e34c5209 | ||
|
|
4b71607215 | ||
|
|
e62875ac90 | ||
|
|
835345b89e | ||
|
|
4ddacc707d | ||
|
|
0d653a2ce6 | ||
|
|
3208260ef8 | ||
|
|
2e57789d10 | ||
|
|
b3006f6fd8 | ||
|
|
142ddcd57a | ||
|
|
a562793fce | ||
|
|
e6c4ecfc10 | ||
|
|
5ff579f793 | ||
|
|
8df346f377 | ||
|
|
af76068297 | ||
|
|
5dddf0a460 | ||
|
|
1854338ff4 | ||
|
|
11b96875e0 | ||
|
|
6462c524f4 | ||
|
|
b703303dd4 | ||
|
|
ab77b6b28e | ||
|
|
0da9d1c036 | ||
|
|
88436b8627 | ||
|
|
f6116510a1 | ||
|
|
5a132aaff4 | ||
|
|
59992c8af2 | ||
|
|
ff3ec78df9 | ||
|
|
b980ae1e8c | ||
|
|
97b9d488d4 | ||
|
|
3ebf8d5db9 | ||
|
|
89e33903b2 | ||
|
|
6a610ac57d | ||
|
|
413f797155 | ||
|
|
6c8dc49216 | ||
|
|
9ded1447ec | ||
|
|
516ed1fd5b | ||
|
|
1a4f8f992b | ||
|
|
23adfef281 | ||
|
|
20b6816562 | ||
|
|
c6400550e1 | ||
|
|
f26749ad79 | ||
|
|
177c98fa95 | ||
|
|
7baa187863 | ||
|
|
11d504e8f4 | ||
|
|
83378e4373 | ||
|
|
06abe2302b | ||
|
|
f3e5f7879d | ||
|
|
bc5125b719 | ||
|
|
c6d23f9a0b | ||
|
|
3b8afce7c7 | ||
|
|
39e28f1f2a | ||
|
|
08363b1d73 | ||
|
|
91aa0f200e | ||
|
|
d9e190096c | ||
|
|
4dca86d5cf | ||
|
|
d3b628bb24 | ||
|
|
cfbb05fb1b | ||
|
|
340d428c88 | ||
|
|
d22bbc54c7 | ||
|
|
c5cda99f9d | ||
|
|
67854e0720 | ||
|
|
cfe257495a | ||
|
|
5f4e701461 | ||
|
|
e3622e0860 | ||
|
|
f0e6b8ca47 | ||
|
|
ef0b239e70 | ||
|
|
590d80268e | ||
|
|
a5de329712 | ||
|
|
2889a4438b | ||
|
|
f5952088e3 | ||
|
|
bcacb49c05 | ||
|
|
976b8e426d | ||
|
|
90ecd34cfd | ||
|
|
35670c8654 | ||
|
|
07bc97fb31 | ||
|
|
d01806ee2d | ||
|
|
a54e42961c | ||
|
|
2e46aa33c0 | ||
|
|
d90cf6384c | ||
|
|
af3f676683 | ||
|
|
abf980d603 | ||
|
|
693b6d2547 | ||
|
|
06313cac64 | ||
|
|
cf1e315d72 | ||
|
|
955e1799c8 | ||
|
|
3db86f1e25 | ||
|
|
6f9b6a29c4 | ||
|
|
b056d40eba | ||
|
|
708c3bc3ce | ||
|
|
39efc12dc8 | ||
|
|
4211151fe6 | ||
|
|
3974b6e6cd | ||
|
|
b24df6e195 | ||
|
|
bcd33dc8e3 | ||
|
|
6c66c236fb | ||
|
|
520121b2b2 | ||
|
|
48c1d05151 | ||
|
|
8ba61a1da1 | ||
|
|
cd129e32a7 | ||
|
|
2b3e43f160 | ||
|
|
d7bf04d2c0 | ||
|
|
4cde8cd501 | ||
|
|
fb4e627e62 | ||
|
|
ef3a97cedd | ||
|
|
1a86ad5cec | ||
|
|
529c401eee | ||
|
|
a87236fea2 | ||
|
|
975c1ca62c | ||
|
|
d02b9d848c | ||
|
|
f340f9ac94 | ||
|
|
1b3f290037 | ||
|
|
b551ef315f | ||
|
|
10bb6ab3bb | ||
|
|
4af57b0dd3 | ||
|
|
bc613eeff9 | ||
|
|
7507e88406 | ||
|
|
073f281f1e | ||
|
|
3355b49724 | ||
|
|
0e2db4a23e | ||
|
|
addc817cca | ||
|
|
a0a86d0f62 | ||
|
|
b24e7cce38 | ||
|
|
ffd1b7069f | ||
|
|
0404c3bd94 | ||
|
|
d0b74a95f4 | ||
|
|
0288d5dc19 | ||
|
|
9d06229cba | ||
|
|
c60ada8421 | ||
|
|
74a1d5cf38 | ||
|
|
43532e5322 | ||
|
|
68cd1d6631 | ||
|
|
afc18cc390 | ||
|
|
57d15936cb | ||
|
|
14406dbf76 | ||
|
|
12c561cee0 | ||
|
|
f79b8a1ca5 | ||
|
|
637daa5bda | ||
|
|
2eae6b4be7 | ||
|
|
dc5e935ecd | ||
|
|
538085169a | ||
|
|
7e8a635e7e | ||
|
|
74e76b6f97 | ||
|
|
cb6309d370 | ||
|
|
8065889862 | ||
|
|
e7a8950d09 | ||
|
|
88e9dc2177 | ||
|
|
2c555c969e | ||
|
|
77c04c3a14 | ||
|
|
48cea6efdf | ||
|
|
89a90d9522 | ||
|
|
0026f3fdef | ||
|
|
ee054fd99c | ||
|
|
7abdade7c1 | ||
|
|
79a50de697 | ||
|
|
e47aef5615 | ||
|
|
b96ae1bac0 | ||
|
|
7dafcb0860 | ||
|
|
5a6a686f42 | ||
|
|
b576bb55ef | ||
|
|
fc8205d375 | ||
|
|
7eb0669de5 | ||
|
|
86161ccf2a | ||
|
|
5e55732cb0 | ||
|
|
a87f0481b8 | ||
|
|
c3d61361f8 | ||
|
|
a48934e31c | ||
|
|
ac75f67e2a | ||
|
|
45cfa6c8d8 | ||
|
|
25a06b0bea | ||
|
|
91af72e8b1 | ||
|
|
fd9b354c70 | ||
|
|
1647e67dd4 | ||
|
|
803e120a51 | ||
|
|
6ba374c62b | ||
|
|
ca28f24e42 | ||
|
|
0d4c38138a | ||
|
|
fe291e90f0 | ||
|
|
220e1e8d83 | ||
|
|
2c0fb3adb0 | ||
|
|
b851eefb03 | ||
|
|
eaad2d8b1f | ||
|
|
575d235296 | ||
|
|
faf74519dc | ||
|
|
66becaf91c | ||
|
|
6610ba67c4 | ||
|
|
e05dd5ebb5 | ||
|
|
cf77f29988 | ||
|
|
2be8ed886b | ||
|
|
21e5b0ab52 | ||
|
|
d72cb74ffa | ||
|
|
3c988d555f | ||
|
|
5981a65cc2 | ||
|
|
9900762747 | ||
|
|
0db2ca6659 | ||
|
|
8e68626f14 | ||
|
|
72a88d1f91 | ||
|
|
bd75e9c184 | ||
|
|
23df16ecf9 | ||
|
|
387732d2f9 | ||
|
|
75b01d5021 | ||
|
|
b7fa5db570 | ||
|
|
297f126f66 | ||
|
|
bd6400f17f | ||
|
|
21e55023a4 | ||
|
|
49e024bdd8 | ||
|
|
57877107d8 | ||
|
|
e9b4a99c8f | ||
|
|
7bc4876071 | ||
|
|
e7899ee15e | ||
|
|
7ea20d9ce9 | ||
|
|
3045f327d5 | ||
|
|
2d1f182c49 | ||
|
|
0677f51717 | ||
|
|
a9bfeb0e96 | ||
|
|
c44d27fb7f | ||
|
|
a0a01b8ba7 | ||
|
|
e32215e19a | ||
|
|
893fb0e3cc | ||
|
|
1d2a838717 | ||
|
|
8457a19792 | ||
|
|
0b2638b201 | ||
|
|
4dce57a1b6 | ||
|
|
658b835a70 | ||
|
|
52af955463 | ||
|
|
09e886db0b | ||
|
|
f668640e3d | ||
|
|
afcd44deaa | ||
|
|
8c8c21c63b | ||
|
|
55dabbe16a | ||
|
|
494bd7b79c | ||
|
|
320ee072e6 | ||
|
|
67ef5fe8e6 | ||
|
|
7c5c700bb8 | ||
|
|
dd51908439 | ||
|
|
6a0f638d40 | ||
|
|
2480c6633c | ||
|
|
495025dafb | ||
|
|
ca62e54652 | ||
|
|
44dd9ae9fb | ||
|
|
3f4d62952a | ||
|
|
391cda919f | ||
|
|
44fc61ed86 | ||
|
|
f70685d53c | ||
|
|
807abfef4f | ||
|
|
810d4c51f9 | ||
|
|
1a0684cd20 | ||
|
|
47d811c250 | ||
|
|
4d0998ef72 | ||
|
|
5d2b0377d8 | ||
|
|
f6d69b6051 | ||
|
|
490496f381 | ||
|
|
bc4f719931 | ||
|
|
ded4a75b9c | ||
|
|
9e75eb35a0 | ||
|
|
326fb99849 | ||
|
|
fe1ced9f58 | ||
|
|
00d0502cbd | ||
|
|
c30bd51b91 | ||
|
|
9391dd9d51 | ||
|
|
3cd112b60f | ||
|
|
dce0c43e26 | ||
|
|
80e228f236 | ||
|
|
8ee2545b05 | ||
|
|
9bcc56e337 | ||
|
|
3c5a94452a | ||
|
|
fce7f8aa68 | ||
|
|
9e13f18463 | ||
|
|
2905f6b465 | ||
|
|
9d3f3eb013 | ||
|
|
75b5afa759 | ||
|
|
78c2f19e72 | ||
|
|
9a66a626f7 | ||
|
|
f42b7f4efa | ||
|
|
bf9654e138 | ||
|
|
4decd1d784 | ||
|
|
3e7cd24814 | ||
|
|
3e0bece53a | ||
|
|
c1b89b3b06 | ||
|
|
f5eadb2957 | ||
|
|
ee393c66dd | ||
|
|
72a7765b58 | ||
|
|
176daeec03 | ||
|
|
066f740819 | ||
|
|
04121644a3 | ||
|
|
1e37a0d9e6 | ||
|
|
fc8d33a98a | ||
|
|
2089727c7f | ||
|
|
fbbf705d10 | ||
|
|
e08c302756 | ||
|
|
87ef3519d9 | ||
|
|
9002c33046 | ||
|
|
b9ad6f4fd2 | ||
|
|
57db17853e | ||
|
|
58555d598b | ||
|
|
0eeae24049 | ||
|
|
3def7c66ad | ||
|
|
eeac3bc436 | ||
|
|
bdebc7f32e | ||
|
|
e7d6811948 | ||
|
|
0e263dd753 | ||
|
|
56bbcb1cb3 | ||
|
|
f02648022a | ||
|
|
9bb1f4e6f8 | ||
|
|
d219f0bdc6 | ||
|
|
c8d0f6ff20 | ||
|
|
2813c75292 | ||
|
|
581e055359 | ||
|
|
88a2cfc5e1 | ||
|
|
fb29830521 | ||
|
|
6540fea4da | ||
|
|
4fd33b015e | ||
|
|
d2fcdb3b6c | ||
|
|
c2fe0876c2 | ||
|
|
39aebd7144 | ||
|
|
6a7d0b8c0a | ||
|
|
7b92bee596 | ||
|
|
f7eda362c7 | ||
|
|
0fb8459bc1 | ||
|
|
42d9b57733 | ||
|
|
ebe143d9c9 | ||
|
|
8acbdfbf1d | ||
|
|
6eb36226b1 | ||
|
|
21984f0bbe | ||
|
|
5eac945bc0 | ||
|
|
2736b4198a | ||
|
|
a4f2f7faa6 | ||
|
|
94923175a8 | ||
|
|
f1c629e7bf | ||
|
|
5d0c60bd24 | ||
|
|
59f08cd651 | ||
|
|
63db194140 | ||
|
|
38db8d4e1a | ||
|
|
c2e8cbb846 | ||
|
|
3c567b895f | ||
|
|
673c75fb72 | ||
|
|
7a576ee36e | ||
|
|
73c09e3365 | ||
|
|
07dd2b279e | ||
|
|
c7943d2521 | ||
|
|
9b483d381f | ||
|
|
4e25337779 | ||
|
|
1e5041b8b0 | ||
|
|
3f4bbe9b9e | ||
|
|
56eaf883dc | ||
|
|
d8c0af7ded | ||
|
|
8724a6b6e2 | ||
|
|
b968b2c438 | ||
|
|
533d1ea9ab | ||
|
|
c9e32c449a | ||
|
|
d9e42bfcba | ||
|
|
f552564fc1 | ||
|
|
b21a096516 | ||
|
|
d0b25de554 | ||
|
|
bba836dc31 | ||
|
|
14fc8e2efa | ||
|
|
d8bd4fbea6 | ||
|
|
7cc89da879 | ||
|
|
baed12f45e | ||
|
|
dfbf291725 | ||
|
|
cb07074246 | ||
|
|
0198fe90b6 | ||
|
|
061221b073 | ||
|
|
7e4cfede7d | ||
|
|
48225c3c0a | ||
|
|
02b89362b2 | ||
|
|
e0e812f95c | ||
|
|
2cbc7e4f9a | ||
|
|
7bec37bfd8 | ||
|
|
828550687c | ||
|
|
d3caba4073 | ||
|
|
7e802706fe | ||
|
|
c87e3eac08 | ||
|
|
5b1b12ec9e | ||
|
|
c0c6f13d44 | ||
|
|
20f192daa1 | ||
|
|
0e73b53803 | ||
|
|
e2923c378e | ||
|
|
216d4da85a | ||
|
|
407e775282 | ||
|
|
e71afa63b0 | ||
|
|
30fd99679a | ||
|
|
a9ef15cdfa | ||
|
|
b08442b46f | ||
|
|
f56e2fc48e | ||
|
|
5452b55e38 | ||
|
|
bec8d36961 | ||
|
|
8188e3d0cf | ||
|
|
10e92eec32 | ||
|
|
37f2336eee | ||
|
|
290cb1b517 | ||
|
|
7520fe7000 | ||
|
|
9f861dfe89 | ||
|
|
fffb87d013 | ||
|
|
0cb731c584 | ||
|
|
233faea200 | ||
|
|
1b9015a049 | ||
|
|
111de1462c | ||
|
|
7fb0b4a8df | ||
|
|
627c49c5db | ||
|
|
665b1ad58a | ||
|
|
4623e9d7fc | ||
|
|
a3f0290432 | ||
|
|
69b1798af7 | ||
|
|
10af206024 | ||
|
|
6c23d7aec8 | ||
|
|
7937ccee30 | ||
|
|
b98e8c34eb | ||
|
|
5c2059b4e8 | ||
|
|
f4be0cf4b3 | ||
|
|
5f7abda22d | ||
|
|
51424aed32 | ||
|
|
914fb09ed0 | ||
|
|
7ea7e5c951 | ||
|
|
c69807b416 | ||
|
|
08320119f1 | ||
|
|
49dcd9bb4a | ||
|
|
8f41603101 | ||
|
|
d437609365 | ||
|
|
e1c681fffe | ||
|
|
d4d47182b4 | ||
|
|
bc2d0191ff | ||
|
|
bd222ed2bf | ||
|
|
ceac824e2b | ||
|
|
93b835ae0d | ||
|
|
aa2d57cf37 | ||
|
|
f6d4821a68 | ||
|
|
fa025bcf39 | ||
|
|
842176a595 | ||
|
|
0fb74b56b3 | ||
|
|
87abcd6a52 | ||
|
|
0a71e40030 | ||
|
|
1b9db035ba | ||
|
|
34c3035c2b | ||
|
|
d92951ba5b | ||
|
|
c5749cbbb1 | ||
|
|
a2e5bcc137 | ||
|
|
761d707b69 | ||
|
|
d327a05e12 | ||
|
|
6477d21e24 | ||
|
|
d3612e956e | ||
|
|
5a3ee3a46e | ||
|
|
23cb743ce2 | ||
|
|
40e5a2dc35 | ||
|
|
73eb4079ef | ||
|
|
5b60c8aa7e | ||
|
|
8c5a688566 | ||
|
|
7ea9706c94 | ||
|
|
5976e2d873 | ||
|
|
d32d452583 | ||
|
|
b77bdabca6 | ||
|
|
e8971e0d8b | ||
|
|
498c4660d0 | ||
|
|
54c142fc0d | ||
|
|
6079402dc4 | ||
|
|
3d4a7f97e1 | ||
|
|
d6e04beb95 | ||
|
|
9a225e6244 | ||
|
|
a03f00628b | ||
|
|
360485d830 | ||
|
|
3f1cfd2995 | ||
|
|
08e2b601f1 | ||
|
|
101ed9a633 | ||
|
|
81ecd17b93 | ||
|
|
5a78f02dcb | ||
|
|
0a08464d4f | ||
|
|
a638d42d92 | ||
|
|
90256ea2f5 | ||
|
|
90aefd35f0 | ||
|
|
2f428b7261 | ||
|
|
f935060b2b | ||
|
|
a367812348 | ||
|
|
8e6a89b279 | ||
|
|
0747697d10 | ||
|
|
8c7494885f | ||
|
|
590a0b8077 | ||
|
|
064d261357 | ||
|
|
adb96710bf | ||
|
|
dd64268bc7 | ||
|
|
aecbd0eda3 | ||
|
|
2c1b7e3b14 | ||
|
|
3eb6357892 | ||
|
|
e0c98a5c79 | ||
|
|
dfb60ec2ed | ||
|
|
b85e260b92 | ||
|
|
4d72845c58 | ||
|
|
9067f76bd6 | ||
|
|
6c447da730 | ||
|
|
1a9f6f06df | ||
|
|
5f8bdc564b | ||
|
|
f6498f2a7b | ||
|
|
0ef5f7f9b1 | ||
|
|
a323a4dd8e | ||
|
|
d5581ffd05 | ||
|
|
77968ab33a | ||
|
|
265a5955d7 | ||
|
|
465ab2fa34 | ||
|
|
d60bfef924 | ||
|
|
5225587ff5 | ||
|
|
fec790e108 | ||
|
|
eb5ea28bcd | ||
|
|
a9fe2ab5c4 | ||
|
|
f2db0886dd | ||
|
|
f6926a9642 | ||
|
|
d3442bb08f | ||
|
|
a08f5c3016 | ||
|
|
eff892ccb8 | ||
|
|
b565eaaf5a | ||
|
|
3831a371a4 | ||
|
|
77ec8ebfc3 | ||
|
|
c272e4d1e8 | ||
|
|
d69b7f41f2 | ||
|
|
c8a47d839a | ||
|
|
b3db8913c6 | ||
|
|
e8f8f34a76 | ||
|
|
5bec2c87cb | ||
|
|
d4d283480f | ||
|
|
71a524662d | ||
|
|
172aef7e26 | ||
|
|
395b5faa2d | ||
|
|
f4a765c5ed | ||
|
|
f5ad845d57 | ||
|
|
2a70ca375c | ||
|
|
396c589743 | ||
|
|
7670b81633 | ||
|
|
f9b98f97b6 | ||
|
|
00bf39204d | ||
|
|
e9ba3ad223 | ||
|
|
6b6666be47 | ||
|
|
e2a413df78 | ||
|
|
b24f68eee7 | ||
|
|
72c1a0222c | ||
|
|
bd97083268 | ||
|
|
e7cf5d4f3d | ||
|
|
2429971bcd | ||
|
|
170a71bd02 | ||
|
|
14efbb1356 | ||
|
|
20cd0dd77a | ||
|
|
2db1fac1eb | ||
|
|
ce8490ed41 | ||
|
|
7e208984de | ||
|
|
4d29a3ae3c | ||
|
|
547c63b28e | ||
|
|
9004aae597 | ||
|
|
1bdfc5a1a4 | ||
|
|
195273f01d | ||
|
|
605aea2211 | ||
|
|
d1afcb941f | ||
|
|
cc7e21170f | ||
|
|
30602c0849 | ||
|
|
1dabe95899 | ||
|
|
4995c0ed94 | ||
|
|
df82dbe5f5 | ||
|
|
de9c74e1d9 | ||
|
|
d89eb9ff0e | ||
|
|
5041042925 | ||
|
|
0d983e63a1 | ||
|
|
3f7a60d84b | ||
|
|
1eb9fcf521 | ||
|
|
1571690bbb | ||
|
|
eb2133e648 | ||
|
|
9107b3ddbe | ||
|
|
64433bd8dd | ||
|
|
bc6159c538 | ||
|
|
8e5954f36f | ||
|
|
b53ef148a0 | ||
|
|
3f950490f9 | ||
|
|
49b1c1368e | ||
|
|
bdb5df104a | ||
|
|
700778e574 | ||
|
|
a104b35cea | ||
|
|
bbbf95705b | ||
|
|
927871d73d | ||
|
|
62419e975f | ||
|
|
3fc2c4223b | ||
|
|
84da884211 | ||
|
|
1bc8e9aebf | ||
|
|
a228e46a39 | ||
|
|
81913a5987 | ||
|
|
2d7f31a60d | ||
|
|
804858d736 | ||
|
|
222be9cf6c | ||
|
|
75a52f032b | ||
|
|
a9e0a51059 | ||
|
|
164b993064 | ||
|
|
62b103fa23 | ||
|
|
e17eb01f76 | ||
|
|
2fe55e4715 | ||
|
|
a8a82e45a0 | ||
|
|
fcba140ffe | ||
|
|
b96ff781bc | ||
|
|
94392f248c | ||
|
|
24e6c31c44 | ||
|
|
cab0e4395a | ||
|
|
ae66e299e6 | ||
|
|
88ad2ee103 | ||
|
|
48f3f4ca37 | ||
|
|
f3ab17a3c0 | ||
|
|
5749d43fb6 | ||
|
|
f5d808e68f | ||
|
|
88f1160e2b | ||
|
|
b7218d2279 | ||
|
|
e928a0aaea | ||
|
|
a2447ecaa3 | ||
|
|
09a089d5f1 | ||
|
|
137b0cd837 | ||
|
|
4ffdd9eb15 | ||
|
|
4e29099ed9 | ||
|
|
8900df6f13 | ||
|
|
814c034683 | ||
|
|
e1ec4bee75 | ||
|
|
6ed7767512 | ||
|
|
08b18af8a0 | ||
|
|
75908ab88f | ||
|
|
fcba33997f | ||
|
|
134be307d3 | ||
|
|
092f8e6482 | ||
|
|
cf5960afad | ||
|
|
e5204e55d3 | ||
|
|
61b14d1cd9 | ||
|
|
c2e3b47d29 | ||
|
|
a239007db0 | ||
|
|
c5e5eb8b86 | ||
|
|
a4d15bfb88 | ||
|
|
b33158b92e | ||
|
|
3b30c4d487 | ||
|
|
dfd7ac633a | ||
|
|
213f8a604e | ||
|
|
13499352fd | ||
|
|
70aa4934c5 | ||
|
|
1d538ee70c | ||
|
|
2ed2e6d62f | ||
|
|
aea0570d2b | ||
|
|
def33a77da | ||
|
|
29db677a9e | ||
|
|
d362ca3a6d | ||
|
|
3796792421 | ||
|
|
b8020db029 | ||
|
|
a90030ca2d | ||
|
|
3f8d3afe5c | ||
|
|
155004bdbb | ||
|
|
534c74b67c | ||
|
|
5428935ab1 | ||
|
|
fc14d089b1 | ||
|
|
4e543238e1 | ||
|
|
6579a542b8 | ||
|
|
27e3dcea6c | ||
|
|
01f73b11d8 | ||
|
|
dd07226ab4 | ||
|
|
67d856e4b7 | ||
|
|
6ad1e57123 | ||
|
|
27a52ef56a | ||
|
|
d290596c1d | ||
|
|
342b4a3b52 | ||
|
|
19652e6367 | ||
|
|
1c76c869ef | ||
|
|
706d842102 | ||
|
|
2889631b61 | ||
|
|
5b9f0f45a9 | ||
|
|
ecc059bf8b | ||
|
|
e1f4a50e65 | ||
|
|
ec2c5a9b2c | ||
|
|
4efbc20a4f | ||
|
|
657e4f12bb | ||
|
|
22ffb1f53f | ||
|
|
4854da4805 | ||
|
|
33a0df20c3 | ||
|
|
e4d1e4b7a0 | ||
|
|
dbc193ab2a | ||
|
|
7bde174b40 | ||
|
|
cf8014582b | ||
|
|
e2796dd351 | ||
|
|
0696a18e0b | ||
|
|
0cc9388af0 | ||
|
|
a73529969a | ||
|
|
c55de17ca8 | ||
|
|
41a751e7f5 | ||
|
|
b9a53e441e | ||
|
|
add62c2093 | ||
|
|
de01ac233c | ||
|
|
c9e926f451 | ||
|
|
8d405849be | ||
|
|
5fbdb1af9f | ||
|
|
83350f8866 | ||
|
|
b4ccd889af | ||
|
|
35a3f2dc91 | ||
|
|
ed7a4514de | ||
|
|
2e295e354a | ||
|
|
d71d9cc8ef | ||
|
|
108b62d839 | ||
|
|
46dfaa5b28 | ||
|
|
323e9c3eb3 | ||
|
|
d677d3d2ef | ||
|
|
c39b239d78 | ||
|
|
5f3ab823fd | ||
|
|
03d1da4bf2 | ||
|
|
78656d220b | ||
|
|
7e7aa7992a | ||
|
|
a53b4df8b6 | ||
|
|
3ba2b8ade6 | ||
|
|
79cb4cd6a5 | ||
|
|
d3ab4b5def | ||
|
|
cdeb07520f | ||
|
|
cc77ef680d | ||
|
|
e75ebd9b6e | ||
|
|
62c9b8390b | ||
|
|
0724c044d6 | ||
|
|
bed4afe74f | ||
|
|
d4263cc8b9 | ||
|
|
b4b11465a1 | ||
|
|
daaebc59cb | ||
|
|
79a74038ac | ||
|
|
314baabf1d | ||
|
|
a24fbb8f61 | ||
|
|
32eaab0783 | ||
|
|
e185b626bd | ||
|
|
74dc94f6bc | ||
|
|
099d9aaca6 | ||
|
|
5fa3b9f2fb | ||
|
|
ad8e2ad4f6 | ||
|
|
8203eff47b | ||
|
|
ad333047d9 | ||
|
|
6e5d2d6a36 | ||
|
|
4b610b6293 | ||
|
|
2bd998e82f | ||
|
|
c55cc3c472 | ||
|
|
674483a4c6 | ||
|
|
a16f84560d | ||
|
|
1b4500339e | ||
|
|
3143468751 | ||
|
|
f627b32355 | ||
|
|
10ba6fb773 | ||
|
|
2d3f1e51ec | ||
|
|
1c23336cff | ||
|
|
a622ffddfe | ||
|
|
79cf0c63c4 | ||
|
|
d57a592273 | ||
|
|
4f15802fbc | ||
|
|
357ae7a8aa | ||
|
|
ac00ae4daf | ||
|
|
249f034a38 | ||
|
|
32c612b747 | ||
|
|
386f7cf65d | ||
|
|
8dc07882b6 | ||
|
|
320802633f | ||
|
|
dcc4b91b35 | ||
|
|
e7c27ed999 | ||
|
|
928c9c9aad | ||
|
|
4a90f13dee | ||
|
|
61dddcee3f | ||
|
|
4c510001ce | ||
|
|
82dfa690d2 | ||
|
|
2768beec4c | ||
|
|
bd0aa16322 | ||
|
|
a1746a5bf1 | ||
|
|
fb502fb66b | ||
|
|
c9a69263b6 | ||
|
|
f0c8056334 | ||
|
|
17dd9239b6 | ||
|
|
6c691d9223 | ||
|
|
920644afaf | ||
|
|
8668aee7fd | ||
|
|
4f386b3e5e | ||
|
|
0f4667d942 | ||
|
|
5705c42820 | ||
|
|
f5f0d00a75 | ||
|
|
ec75e21898 | ||
|
|
94e9e17c79 | ||
|
|
5134e64cae | ||
|
|
53d39ccb4b | ||
|
|
b227e160f0 | ||
|
|
88a2b82f7e | ||
|
|
0016365446 | ||
|
|
0fa8feb475 | ||
|
|
eb872f8fca | ||
|
|
5fd78320dd | ||
|
|
3195042c7d | ||
|
|
b0234fa8b7 | ||
|
|
2a629fee37 | ||
|
|
bf13700a95 | ||
|
|
83268f5ff6 | ||
|
|
1eb42654e0 | ||
|
|
e05c34d1ee | ||
|
|
8325bb11e2 | ||
|
|
c1b577f2c7 | ||
|
|
29a61db275 | ||
|
|
e2c61485a1 | ||
|
|
65f200e02c | ||
|
|
aa9432e24f | ||
|
|
30d1585468 | ||
|
|
376c5263f0 | ||
|
|
bcd6d4e8a7 | ||
|
|
32eb896c56 | ||
|
|
4603163e88 | ||
|
|
b3c9fb2fea | ||
|
|
d66ea07ba4 | ||
|
|
496279c92d | ||
|
|
e0ae925ca3 | ||
|
|
1799c19933 | ||
|
|
94eddd3b42 | ||
|
|
79829cb6d2 | ||
|
|
0d20ca0bf2 | ||
|
|
3ede5ea28f | ||
|
|
cfd15f4e02 | ||
|
|
1f9ff2dd88 | ||
|
|
7740a39a4d | ||
|
|
5eda2dfe0e | ||
|
|
1509c6a471 | ||
|
|
19acd608a5 | ||
|
|
bc81ff28d8 | ||
|
|
ba867e95c3 | ||
|
|
53f99ca434 | ||
|
|
8958ea036e | ||
|
|
d3563c30e7 | ||
|
|
0f017339b7 | ||
|
|
4d3a3d806f | ||
|
|
10a0f708b9 | ||
|
|
409d3c168d | ||
|
|
005220b6d4 | ||
|
|
8462b039d3 | ||
|
|
2e3cabce6b | ||
|
|
2c6b615b8c | ||
|
|
f910e013a6 | ||
|
|
5f1b03261c | ||
|
|
862197fc96 | ||
|
|
44ebd894d5 | ||
|
|
b85ff7db36 | ||
|
|
22fd216de5 | ||
|
|
2f02c428cc | ||
|
|
1732413ee1 | ||
|
|
33325f1574 | ||
|
|
4f288c6fd9 | ||
|
|
5ffb4597b3 | ||
|
|
d9de38b935 | ||
|
|
ad459b83c0 | ||
|
|
d96ea6b815 | ||
|
|
40c3e31955 | ||
|
|
f84c831cda | ||
|
|
904cf97f7d | ||
|
|
791e22dc24 | ||
|
|
c82a0b3d2d | ||
|
|
2fc887724b | ||
|
|
e16762a137 | ||
|
|
4a07ffac69 | ||
|
|
9331c4ce9e | ||
|
|
184ef94e7e | ||
|
|
d67740d44d | ||
|
|
7b30daf88d | ||
|
|
d5e76da475 | ||
|
|
5d91c1af81 | ||
|
|
bb013e78d2 | ||
|
|
c22816de08 | ||
|
|
9bb851d437 | ||
|
|
c8a9f8dcf5 | ||
|
|
e8653ffe32 | ||
|
|
5499df418a | ||
|
|
b586618411 | ||
|
|
3b5730be54 | ||
|
|
961c4a1b9c | ||
|
|
72c5908f09 | ||
|
|
2a588ff943 | ||
|
|
24f11b5ae9 | ||
|
|
1d44d99875 | ||
|
|
eebcdfb24f | ||
|
|
526b56a650 | ||
|
|
20675f56e6 | ||
|
|
e5fbdeb3a9 | ||
|
|
7cc92ec1c1 | ||
|
|
fe0bf6f2ef | ||
|
|
828c19d478 | ||
|
|
15db77558c | ||
|
|
18cf4a3bd3 | ||
|
|
e0378acfc2 | ||
|
|
53cb2af202 | ||
|
|
f74005a166 | ||
|
|
b0df981b01 | ||
|
|
888d844ec5 | ||
|
|
3a7eb9fc0c | ||
|
|
46276aacd9 | ||
|
|
3a7a29868f | ||
|
|
0060c8988a | ||
|
|
458f482c2a | ||
|
|
cc9d6587a3 | ||
|
|
dadf1b6ff9 | ||
|
|
db56bb92f0 | ||
|
|
e82e36f51e | ||
|
|
f9aad34d28 | ||
|
|
b2d720b853 | ||
|
|
c10c05e924 | ||
|
|
8909246725 | ||
|
|
48c0b0ab3c | ||
|
|
cbbed310c5 | ||
|
|
9e9c3b6342 | ||
|
|
fefd262c2e | ||
|
|
1108c99688 | ||
|
|
93da831ef0 | ||
|
|
72bf45912e | ||
|
|
5d94276c4f | ||
|
|
b43f27030e | ||
|
|
2845f55157 | ||
|
|
e254d84710 | ||
|
|
0ef7b5488d | ||
|
|
56b04f8df8 | ||
|
|
2d036c6b25 | ||
|
|
ad3be5c848 | ||
|
|
a0544cc345 | ||
|
|
3245261162 | ||
|
|
21b7348c6a | ||
|
|
54d4dc6e1e | ||
|
|
e00d864199 | ||
|
|
ad6fc89a24 | ||
|
|
fc4823ce8e | ||
|
|
0bd9831eec | ||
|
|
dec2ef70c7 | ||
|
|
d19d8ea770 | ||
|
|
34f7752c0f | ||
|
|
da4d061067 | ||
|
|
85155439be | ||
|
|
dd32e69061 | ||
|
|
1c0c660c32 | ||
|
|
2eac902c33 | ||
|
|
9f5322d309 | ||
|
|
f5c2f6f9e9 | ||
|
|
e0e3b180e7 | ||
|
|
041dc039d4 | ||
|
|
ac755b0ada | ||
|
|
c14259060c | ||
|
|
a423eb5323 | ||
|
|
fd732550d8 | ||
|
|
fac7ba70c8 | ||
|
|
e9f8b35918 | ||
|
|
23034dd4f5 | ||
|
|
70ad44adfd | ||
|
|
95ca8a2630 | ||
|
|
a7fd3c50e5 | ||
|
|
5639f40994 | ||
|
|
33a8262334 | ||
|
|
9b4f7ad696 | ||
|
|
eecd809d9a | ||
|
|
240d3cb8f1 | ||
|
|
5720b7b94c | ||
|
|
3855d7ea02 | ||
|
|
9f3351469d | ||
|
|
2c4a0cf65b | ||
|
|
a8f360c8fd | ||
|
|
b3cff6555f | ||
|
|
69706ecab0 | ||
|
|
05d3b97eae | ||
|
|
a38fb41e37 | ||
|
|
204ae377e3 | ||
|
|
3e5a652874 | ||
|
|
862ae26631 | ||
|
|
9e0208ec71 | ||
|
|
510530973c | ||
|
|
4e69301562 | ||
|
|
df06d4b9a2 | ||
|
|
b60561c3bb | ||
|
|
fffa503968 | ||
|
|
8754157810 | ||
|
|
0763686892 | ||
|
|
50776748a7 | ||
|
|
c07274a6fa | ||
|
|
4084005ee7 | ||
|
|
40fce574cf | ||
|
|
fd761735d4 | ||
|
|
9cb98bdcdc | ||
|
|
b5b1f7ff8a | ||
|
|
a86019d53b | ||
|
|
8cd7d08573 | ||
|
|
6d6f6ed8fc | ||
|
|
c9aed1381f | ||
|
|
49ba5f30c5 | ||
|
|
3c6149fd27 | ||
|
|
23f804eda3 | ||
|
|
4e321db189 | ||
|
|
716fde5784 | ||
|
|
c70b873397 | ||
|
|
4c0da691b8 | ||
|
|
d1d36f9af0 | ||
|
|
56efb4ffba | ||
|
|
540bc71873 | ||
|
|
c1df7ead1a | ||
|
|
d170c807e3 | ||
|
|
50c0abc786 | ||
|
|
c5057127ee | ||
|
|
ea7b1a9592 | ||
|
|
26d483f2d7 | ||
|
|
67fce58708 | ||
|
|
ea5771c442 | ||
|
|
fa8a1faeb3 | ||
|
|
d60a000af6 | ||
|
|
3d68e2b74f | ||
|
|
42f6d48abf | ||
|
|
5587957602 | ||
|
|
1d313ebf25 | ||
|
|
cf92349c1d | ||
|
|
b0ae44bc25 | ||
|
|
0c94124fdf | ||
|
|
22298ddb41 | ||
|
|
6f927b9941 | ||
|
|
dc5626f34d | ||
|
|
2717ec2759 | ||
|
|
d75449ea63 | ||
|
|
034f5b16bd | ||
|
|
ff420f1e67 | ||
|
|
5be771842f | ||
|
|
a131c93a14 | ||
|
|
3da3546ef8 | ||
|
|
47f5223c37 | ||
|
|
b8c50eb0fe | ||
|
|
fe05082ddc | ||
|
|
414acda9e0 | ||
|
|
47e3a5e75a | ||
|
|
573df892bc | ||
|
|
bfced59949 | ||
|
|
9a63b1751b | ||
|
|
53ae8b9bd1 | ||
|
|
bf3cc5a367 | ||
|
|
ac00bc6b70 | ||
|
|
ab62603c5a | ||
|
|
dd247cd541 | ||
|
|
0d7a9f32d6 | ||
|
|
0d85ef4404 | ||
|
|
8e9273a96a | ||
|
|
2dfdaaa4ba | ||
|
|
407094cdcb | ||
|
|
0111db6744 | ||
|
|
a49230c0e1 | ||
|
|
d04abc2021 | ||
|
|
b5368207d6 | ||
|
|
b8400e0c92 | ||
|
|
ef6b6cd279 | ||
|
|
e5903d8d6e | ||
|
|
e2a3db1b82 | ||
|
|
9e220e8416 | ||
|
|
605c233753 | ||
|
|
aafe18c2b5 | ||
|
|
262e8af813 | ||
|
|
75aa4ce8a0 | ||
|
|
a494cdaf92 | ||
|
|
848425418b | ||
|
|
989bed88fe | ||
|
|
99f66200f7 | ||
|
|
b642bd2cfd | ||
|
|
c062b7c9da | ||
|
|
7df7901532 | ||
|
|
9dabce819a | ||
|
|
a019109857 | ||
|
|
46cdf14ecc | ||
|
|
fd5ff20b68 | ||
|
|
81f9666e82 | ||
|
|
aa8659d5c7 | ||
|
|
9df2df0f9f | ||
|
|
98d38d763b | ||
|
|
319a4622b3 | ||
|
|
59b4c6dacb | ||
|
|
ec6d725587 | ||
|
|
6f19649186 | ||
|
|
6a583b9b5b | ||
|
|
6617b79cd8 | ||
|
|
46b695d093 | ||
|
|
de8a017e80 | ||
|
|
b71ec91f57 | ||
|
|
1b7c226779 | ||
|
|
ca5a5b4392 | ||
|
|
6ae7ae0210 | ||
|
|
b911d2fda4 | ||
|
|
847a92f580 | ||
|
|
a346493de1 | ||
|
|
efd5d6efab | ||
|
|
b6df5a4b7f | ||
|
|
34ae56e319 | ||
|
|
5870824d01 | ||
|
|
1dbadbfc35 | ||
|
|
d3f1cd7411 | ||
|
|
a821daadd3 | ||
|
|
a39f4b4487 | ||
|
|
f7b401b9b6 | ||
|
|
c342d18f92 | ||
|
|
e0e52a3090 | ||
|
|
6f92480315 | ||
|
|
490d008edf | ||
|
|
ccee17c573 | ||
|
|
dd4832e40b | ||
|
|
ff5b870a9f | ||
|
|
a371580201 | ||
|
|
b499c575bc | ||
|
|
f5a6268cef | ||
|
|
2df4083602 | ||
|
|
2495fb796d | ||
|
|
f7b49740c1 | ||
|
|
7e765fe726 | ||
|
|
1d339f74d1 | ||
|
|
a3bb288f01 | ||
|
|
0e1226573a | ||
|
|
8151b65d12 | ||
|
|
eefd36b50d | ||
|
|
6f4d769187 | ||
|
|
20b76a703a | ||
|
|
dcc09b7bb5 | ||
|
|
4ea0dee90a | ||
|
|
e61116d35a | ||
|
|
f7256c75d5 | ||
|
|
2696fd1291 | ||
|
|
b139620fba | ||
|
|
97115a9c1c | ||
|
|
36134c61b2 | ||
|
|
77cf0fd87b | ||
|
|
ab77a1cbc0 | ||
|
|
8490892493 | ||
|
|
5f35dffa34 | ||
|
|
89a4b5a6ff | ||
|
|
931de7e39c | ||
|
|
ff41d6aef9 | ||
|
|
8e1ba58551 | ||
|
|
2b1692a860 | ||
|
|
7a2981e21c | ||
|
|
f8717359d7 | ||
|
|
fe395e3a8c | ||
|
|
eb9fe851af | ||
|
|
d61851ad02 | ||
|
|
de7a408903 | ||
|
|
4b0f60fe71 | ||
|
|
a167bcff5e | ||
|
|
fb812e6335 | ||
|
|
3fd557c74a | ||
|
|
9b1272da51 | ||
|
|
ae2402f4a7 | ||
|
|
c98e869564 | ||
|
|
e1a019ec56 | ||
|
|
2d7fcbf23d | ||
|
|
b604177ca1 | ||
|
|
f27597cab5 | ||
|
|
40619c8184 | ||
|
|
b0a5df2838 | ||
|
|
ae9232a2cb | ||
|
|
ad28d0025a | ||
|
|
d6772c5ca5 | ||
|
|
a2400767ed | ||
|
|
1873b655a8 | ||
|
|
e27c2b59c8 | ||
|
|
4b50608642 | ||
|
|
e3a7f9eb7a | ||
|
|
c130b3a92e | ||
|
|
20fb6d6669 | ||
|
|
caf20cd43d | ||
|
|
608d661355 | ||
|
|
31c868818b | ||
|
|
443c4b6cd0 | ||
|
|
ee425bb7f0 | ||
|
|
9d1c026231 | ||
|
|
6d7612ead6 | ||
|
|
b3e31094ff | ||
|
|
c9750fab2c | ||
|
|
5a1348e4d1 | ||
|
|
b64d45a74e | ||
|
|
ba5a4c3945 | ||
|
|
9ba37a932b | ||
|
|
8ce377d033 | ||
|
|
a7f3dafd81 | ||
|
|
fb38b4b906 | ||
|
|
f0a1a90b0e | ||
|
|
fb331d39a6 | ||
|
|
7a3e6e19b1 | ||
|
|
5a063c541d | ||
|
|
eef25ec638 | ||
|
|
7141fb18c1 | ||
|
|
2c92be5e3d | ||
|
|
cae0733149 | ||
|
|
5d6ca7a88e | ||
|
|
23b8a4d364 | ||
|
|
c0df8cb578 | ||
|
|
4693c23a49 | ||
|
|
86364ff278 | ||
|
|
60f1f1cb3d | ||
|
|
ab3c520006 | ||
|
|
3798dcb98d | ||
|
|
9daf290432 | ||
|
|
3438f88c1e | ||
|
|
2c8858ff2d | ||
|
|
45ed9405b0 | ||
|
|
075ca78dd2 | ||
|
|
1d964ef921 | ||
|
|
0c6d9b1d3b | ||
|
|
1713f1222b | ||
|
|
450591d5a9 | ||
|
|
5c4863dfb1 | ||
|
|
c6a82cbc07 | ||
|
|
cc8f074d33 | ||
|
|
196d74c3a0 | ||
|
|
f28e87b240 | ||
|
|
2d77ef4245 | ||
|
|
3f578e24b5 | ||
|
|
fb5ee5e190 | ||
|
|
63e8378310 | ||
|
|
c24ebaf506 | ||
|
|
d3a1f11dfe | ||
|
|
f96e694ff8 | ||
|
|
d5d635e50f | ||
|
|
1ad4ce9822 | ||
|
|
3f971174e4 | ||
|
|
16e257ea32 | ||
|
|
37f1f8ac14 | ||
|
|
474fa9b3ec | ||
|
|
6d7b14ceb5 | ||
|
|
af14832b6c | ||
|
|
9ef185978f | ||
|
|
38bb44664c | ||
|
|
124d924ff5 | ||
|
|
e8091d8add | ||
|
|
500bd53b6a | ||
|
|
6996ca832d | ||
|
|
2e15ab44ab | ||
|
|
2ae5472872 | ||
|
|
98a32790cd | ||
|
|
6dcefcfb71 | ||
|
|
988366e37f | ||
|
|
98a37905b8 | ||
|
|
bd832d0fbb | ||
|
|
1f7e18f2a9 | ||
|
|
105e9c8e88 | ||
|
|
99e1a5f117 | ||
|
|
b35b49f7b3 | ||
|
|
5d064b18e6 | ||
|
|
11ea92288d | ||
|
|
1ea6ae9fd9 | ||
|
|
57d739673a | ||
|
|
4d8f76e922 | ||
|
|
fb41d4ab0f | ||
|
|
023ee688d3 | ||
|
|
5f8f0d8573 | ||
|
|
f56e0a6586 | ||
|
|
2c96420bc4 | ||
|
|
8d95eb57fa | ||
|
|
a9a5e79e85 | ||
|
|
2b720b8d0b | ||
|
|
93a0ab7f0a | ||
|
|
18dd55d1f4 | ||
|
|
c9074d8d96 | ||
|
|
f6c995157e | ||
|
|
a059a9fbdc | ||
|
|
2ac6596ba9 | ||
|
|
2d1f57e839 | ||
|
|
7799ed36f0 | ||
|
|
db5ed345bc | ||
|
|
082c6a9e64 | ||
|
|
b74a4d5225 | ||
|
|
16a572c250 | ||
|
|
6a8a2d6f56 | ||
|
|
237c855f10 | ||
|
|
15d18484e3 | ||
|
|
378e9b5539 | ||
|
|
7a135bd87c | ||
|
|
9e907e36e2 | ||
|
|
9ac71f304d | ||
|
|
ab9b604d02 | ||
|
|
155645307a | ||
|
|
4ca7ac4851 | ||
|
|
02eb24cd57 | ||
|
|
787fbe1bb6 | ||
|
|
7678858bab | ||
|
|
bf7a28f07c |
6
.dockerignore
Executable file
6
.dockerignore
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
target
|
||||||
|
Dockerfile
|
||||||
|
README.md
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitmodules
|
||||||
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
*.png binary
|
||||||
|
*.pl text eol=lf
|
||||||
|
*.rs text eol=lf diff=rust
|
||||||
|
*.md text eol=lf diff=markdown
|
||||||
171
.github/workflows/ci.yml
vendored
Normal file
171
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
tags:
|
||||||
|
- "v**"
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * 3' # At 12:00 AM, only on Wednesday
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-test:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- { os: windows-latest, rust-version: stable, shell: 'msys2 {0}', target: 'x86_64-pc-windows-gnu'}
|
||||||
|
- { os: macos-11, rust-version: stable, shell: bash, target: 'x86_64-apple-darwin' }
|
||||||
|
- { os: ubuntu-20.04, rust-version: stable, shell: bash, extra: true, target: 'x86_64-unknown-linux-gnu' }
|
||||||
|
- { os: ubuntu-20.04, rust-version: stable, shell: bash, target: 'i686-unknown-linux-gnu' }
|
||||||
|
- { os: ubuntu-20.04, rust-version: 1.65, shell: bash, target: 'x86_64-unknown-linux-gnu'}
|
||||||
|
- { os: ubuntu-20.04, rust-version: beta, shell: bash, target: 'x86_64-unknown-linux-gnu'}
|
||||||
|
- { os: ubuntu-20.04, rust-version: nightly, shell: bash, target: 'x86_64-unknown-linux-gnu'}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: ${{ matrix.shell }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
if: "!contains(matrix.os,'windows')"
|
||||||
|
id: toolchain
|
||||||
|
with:
|
||||||
|
toolchain: ${{ 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: msys2/setup-msys2@v2
|
||||||
|
if: contains(matrix.os,'windows')
|
||||||
|
with:
|
||||||
|
update: true
|
||||||
|
install: >-
|
||||||
|
base-devel
|
||||||
|
mingw-w64-x86_64-rust
|
||||||
|
- 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') }}
|
||||||
|
|
||||||
|
# Build and test.
|
||||||
|
- name: Build library
|
||||||
|
run: cargo rustc --target ${{ matrix.target }} --verbose --lib -- -D warnings
|
||||||
|
- name: Test
|
||||||
|
if: "!matrix.extra"
|
||||||
|
run: cargo test --target ${{ matrix.target }} --all --verbose
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# On stable rust builds, build a binary and publish as a github actions
|
||||||
|
# artifact. These binaries could be useful for testing the pipeline but
|
||||||
|
# are only retained by github for 90 days.
|
||||||
|
- name: Build release binary
|
||||||
|
if: contains(matrix.rust-version,'stable')
|
||||||
|
run: |
|
||||||
|
cargo rustc --target ${{ matrix.target }} --verbose --bin scryer-prolog --release -- -D warnings
|
||||||
|
echo "$PWD/target/release" >> $GITHUB_PATH
|
||||||
|
- name: Publish release binary artifact
|
||||||
|
if: contains(matrix.rust-version,'stable')
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
path: target/${{ matrix.target }}/release/scryer-prolog*
|
||||||
|
name: scryer-prolog_${{ matrix.os }}_${{ matrix.target }}
|
||||||
|
|
||||||
|
logtalk-test:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [build-test]
|
||||||
|
steps:
|
||||||
|
# Download prebuilt ubuntu binary from build-test job, setup logtalk
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu
|
||||||
|
- run: |
|
||||||
|
chmod +x scryer-prolog
|
||||||
|
echo "$PWD" >> "$GITHUB_PATH"
|
||||||
|
- name: Install Logtalk
|
||||||
|
uses: logtalk-actions/setup-logtalk@master
|
||||||
|
with:
|
||||||
|
logtalk-version: git
|
||||||
|
logtalk-tool-dependencies: false
|
||||||
|
|
||||||
|
# Run logtalk tests.
|
||||||
|
- name: Run Logtalk's prolog compliance test suite
|
||||||
|
working-directory: ${{ env.LOGTALKUSER }}/tests/prolog/
|
||||||
|
run: |
|
||||||
|
pwd
|
||||||
|
scryerlgt -g '{ack(tester)},halt.'
|
||||||
|
logtalk_tester -p scryer -g "set_logtalk_flag(clean,off)" -w -t 360 \
|
||||||
|
-f xunit \
|
||||||
|
-s "$LOGTALKUSER/tests/prolog" \
|
||||||
|
|| echo "::warning ::logtalk compliance suite failed"
|
||||||
|
# -u "https://github.com/LogtalkDotOrg/logtalk3/tree/$LOGTALK_GIT_HASH/tests/prolog/" \
|
||||||
|
- name: Publish Logtalk test logs
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: logtalk-test-logs
|
||||||
|
path: '${{ env.LOGTALKUSER }}/tests/prolog/logtalk_tester_logs'
|
||||||
|
- name: Publish Logtalk test results artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: logtalk-test-results
|
||||||
|
path: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
|
||||||
|
- name: Publish Logtalk test summary
|
||||||
|
uses: EnricoMi/publish-unit-test-result-action/composite@master
|
||||||
|
with:
|
||||||
|
check_name: Logtalk test summary
|
||||||
|
files: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
|
||||||
|
fail_on: nothing
|
||||||
|
comment_mode: off
|
||||||
|
|
||||||
|
# Publish binaries when building for a tag
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs: [build-test]
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
- name: Zip binaries for release
|
||||||
|
run: |
|
||||||
|
zip scryer-prolog_macos-11.zip ./scryer-prolog_macos-11_x86_64-apple-darwin/scryer-prolog
|
||||||
|
zip scryer-prolog_ubuntu-20.04.zip ./scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu/scryer-prolog
|
||||||
|
zip scryer-prolog_windows-latest.zip ./scryer-prolog_windows-latest_x86_64-pc-windows-gnu/scryer-prolog.exe
|
||||||
|
- name: Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
scryer-prolog_macos-11.zip
|
||||||
|
scryer-prolog_ubuntu-20.04.zip
|
||||||
|
scryer-prolog_windows-latest.zip
|
||||||
55
.github/workflows/docker-publish.yml
vendored
Normal file
55
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: Docker Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Workaround: https://github.com/docker/build-push-action/issues/461
|
||||||
|
- name: Setup Docker buildx
|
||||||
|
# https://github.com/docker/setup-buildx-action
|
||||||
|
uses: docker/setup-buildx-action@v2.2.1
|
||||||
|
|
||||||
|
# Login against Docker registry
|
||||||
|
- name: Log into registry
|
||||||
|
# https://github.com/docker/login-action
|
||||||
|
uses: docker/login-action@v2.1.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Extract Docker image tag from git tag. E.g. if git tag is "v0.19.1" then use
|
||||||
|
# Docker image tag "0.19.1". The "latest" tag reflects the most recent build on
|
||||||
|
# master.
|
||||||
|
- name: Extract Docker metadata
|
||||||
|
id: meta
|
||||||
|
# https://github.com/docker/metadata-action
|
||||||
|
uses: docker/metadata-action@v4.1.1
|
||||||
|
with:
|
||||||
|
images: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/scryer-prolog
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
|
# type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||||
|
|
||||||
|
# Build and push Docker image with Buildx
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
# https://github.com/docker/build-push-action
|
||||||
|
uses: docker/build-push-action@v3.2.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
src/static_atoms.rs
|
||||||
target/
|
target/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
.travis.yml
14
.travis.yml
@@ -1,14 +0,0 @@
|
|||||||
language: rust
|
|
||||||
rust:
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- rust: nightly
|
|
||||||
fast_finish: true
|
|
||||||
|
|
||||||
script:
|
|
||||||
- cargo build --verbose --all
|
|
||||||
- cargo test --verbose --all
|
|
||||||
- cargo test --verbose --all --no-default-features --features num
|
|
||||||
2861
Cargo.lock
generated
2861
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
101
Cargo.toml
101
Cargo.toml
@@ -1,32 +1,99 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "scryer-prolog"
|
name = "scryer-prolog"
|
||||||
version = "0.8.118"
|
version = "0.9.2"
|
||||||
authors = ["Mark Thom <markjordanthom@gmail.com>"]
|
authors = ["Mark Thom <markjordanthom@gmail.com>"]
|
||||||
build = "build.rs"
|
edition = "2021"
|
||||||
repository = "https://github.com/mthom/scryer-prolog"
|
|
||||||
description = "A modern Prolog implementation written mostly in Rust."
|
description = "A modern Prolog implementation written mostly in Rust."
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/mthom/scryer-prolog"
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
edition = "2018"
|
keywords = ["prolog", "prolog-interpreter", "prolog-system"]
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
build = "build/main.rs"
|
||||||
|
rust-version = "1.63"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["ffi", "repl", "hostname", "tls", "http"]
|
||||||
|
ffi = ["dep:libffi"]
|
||||||
|
repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
|
||||||
|
hostname = ["dep:hostname"]
|
||||||
|
tls = ["dep:native-tls"]
|
||||||
|
http = ["dep:hyper", "dep:reqwest"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
indexmap = "1.0.2"
|
indexmap = "1.0.2"
|
||||||
|
proc-macro2 = "1.0.36"
|
||||||
[features]
|
quote = "1.0.15"
|
||||||
default = ["rug", "prolog_parser/rug"]
|
strum = "0.23"
|
||||||
num = ["num-rug-adapter", "prolog_parser/num"]
|
strum_macros = "0.23"
|
||||||
|
syn = { version = "1.0.88", features = ['full', 'visit', 'extra-traits'] }
|
||||||
|
to-syn-value = "0.1.0"
|
||||||
|
to-syn-value_derive = "0.1.0"
|
||||||
|
walkdir = "2"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = "0.16.0"
|
bit-set = "0.5.3"
|
||||||
dirs = "2.0.2"
|
bitvec = "1"
|
||||||
downcast = "0.10.0"
|
cpu-time = "1.0.0"
|
||||||
|
dirs-next = "2.0.0"
|
||||||
|
divrem = "0.1.0"
|
||||||
|
fxhash = "0.2.1"
|
||||||
git-version = "0.3.4"
|
git-version = "0.3.4"
|
||||||
indexmap = "1.0.2"
|
indexmap = "1.0.2"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
lexical = "5.2.2"
|
||||||
libc = "0.2.62"
|
libc = "0.2.62"
|
||||||
nix = "0.15.0"
|
modular-bitfield = "0.11.2"
|
||||||
num-rug-adapter = { optional = true, version = "0.1.1" }
|
ordered-float = "2.6.0"
|
||||||
ordered-float = "0.5.0"
|
phf = { version = "0.9", features = ["macros"] }
|
||||||
prolog_parser = { version = "0.8.49", default-features = false }
|
|
||||||
ref_thread_local = "0.0.0"
|
ref_thread_local = "0.0.0"
|
||||||
rug = { version = "1.4.0", optional = true }
|
ripemd160 = "0.8.0"
|
||||||
rustyline = "6.0.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"
|
||||||
|
libloading = "0.7"
|
||||||
|
derive_deref = "1.1.1"
|
||||||
|
http-body-util = "0.1.0-rc.2"
|
||||||
|
bytes = "1"
|
||||||
|
dashu = { git = "https://github.com/coasys/dashu.git", version = "0.3.1" }
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
libffi = { git = "https://github.com/coasys/libffi-rs.git", branch = "windows-space", optional = true, version = "3.2.0" }
|
||||||
|
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 }
|
||||||
|
hyper = { version = "=1.0.0-rc.3", features = ["full"], optional = true }
|
||||||
|
reqwest = { version = "0.11.18", features = ["blocking"], optional = true }
|
||||||
|
tokio = { version = "1.28.2", features = ["full"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
getrandom = { version = "0.2.10", features = ["js"] }
|
||||||
|
tokio = { version = "1.28.2", features = ["sync", "macros", "io-util", "rt", "time"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "wasi")'.dependencies]
|
||||||
|
ring-wasi = { version = "0.16.25" }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "wasi"))'.dependencies]
|
||||||
|
ring = { version = "0.16.13" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_cmd = "1.0.3"
|
||||||
|
predicates-core = "1.0.2"
|
||||||
|
serial_test = "2.0.0"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
modular-bitfield = { git = "https://github.com/mthom/modular-bitfield" }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|||||||
26
Dockerfile
Executable file
26
Dockerfile
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
# See https://github.com/LukeMathWalker/cargo-chef
|
||||||
|
ARG RUST_VERSION=1-buster
|
||||||
|
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
|
||||||
|
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
|
||||||
|
WORKDIR /scryer-prolog
|
||||||
|
COPY . .
|
||||||
|
# Copy over the cached dependencies
|
||||||
|
COPY --from=cacher /scryer-prolog/target target
|
||||||
|
COPY --from=cacher $CARGO_HOME $CARGO_HOME
|
||||||
|
RUN cargo build --release --bin scryer-prolog
|
||||||
|
|
||||||
|
FROM debian:stable-slim
|
||||||
|
COPY --from=builder /scryer-prolog/target/release/scryer-prolog /usr/local/bin
|
||||||
|
ENV RUST_BACKTRACE=1
|
||||||
|
ENTRYPOINT ["/usr/local/bin/scryer-prolog"]
|
||||||
75
INDEX.dj
Normal file
75
INDEX.dj
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Scryer Prolog
|
||||||
|
|
||||||
|
```
|
||||||
|
?- append("Hello, ", X, "Hello, Scryer Prolog!").
|
||||||
|
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.
|
||||||
|
|
||||||
|
Some of the Scryer Prolog features are:
|
||||||
|
|
||||||
|
* ISO standard compliant
|
||||||
|
* Integrated constraint programming libraries: [clp(B)](/clpb.html), [clp(Z)](/clpz.html).
|
||||||
|
* [Definite Clause Grammars](/dcgs.html)
|
||||||
|
* Coroutining support ([`dif/2`](/dif.html), [`freeze/2`](/freeze.html), ...)
|
||||||
|
* [Tabling and SLG resolution](/tabling.html)
|
||||||
|
* Compact string representation
|
||||||
|
* Network libraries ([TCP sockets](/sockets.html), [HTTP server](/http/http_server.html), [HTTP client](/http/http_open.html), ...)
|
||||||
|
* [Cryptographical predicates](/crypto.html)
|
||||||
|
* WAM based engine, cross-platform made in Rust
|
||||||
|
* _and more..._
|
||||||
|
|
||||||
|
## What is Prolog?
|
||||||
|
|
||||||
|
Prolog is a logic programming language created by [Alain Colmerauer](https://en.wikipedia.org/wiki/Alain_Colmerauer) and [Robert Kowalski](https://en.wikipedia.org/wiki/Robert_Kowalski) in 1972.
|
||||||
|
The idea behind Prolog is try to express a task in language similar to First Order Logic.
|
||||||
|
Prolog systems include _unification_ and _non-determinism_ as key concepts upon which we build programs.
|
||||||
|
|
||||||
|
A Prolog program is made up of predicates which define a relation between its arguments. A predicate
|
||||||
|
is made from clauses. A clause can be either a fact or a rule. There's also a toplevel, which we
|
||||||
|
can use to ask and reason about our task.
|
||||||
|
|
||||||
|
It's still to this day one of the best examples and one of the most popular languages in the field
|
||||||
|
of logic programming. That's because Prolog allows us to elegantly solve many tasks with short and
|
||||||
|
general programs.
|
||||||
|
|
||||||
|
If you want a more detailed description of Prolog, check [A Tour of Prolog](https://www.youtube.com/watch?v=8XUutFBbUrg).
|
||||||
|
|
||||||
|
If you want to learn more about Prolog history, check the videos [l'Aventure Prolog](https://www.youtube.com/watch?v=74Ig_QKndvE) and [50 years of Prolog and beyond](https://prologyear.logicprogramming.org/videos/PrologDay_Session_1_talk.mp4).
|
||||||
|
|
||||||
|
## Where can I learn Prolog?
|
||||||
|
|
||||||
|
There are a lot of classical Prolog books. Those books can teach you the basics of Prolog. Some
|
||||||
|
examples are: _The Art of Prolog (Shapiro)_, _Programming in Prolog (Clocksin, Mellish)_ and _The Craft
|
||||||
|
of Prolog (O'Keefe)_. However, most of them are not updated to _modern_ Prolog.
|
||||||
|
We recommend _[The Power of Prolog (Markus Triska)](https://www.metalevel.at/prolog)_ for modern Prolog. For reference about
|
||||||
|
the builtin Prolog modules and libraries in Scryer, check the documentation site. It's this!
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
|
||||||
|
The latest version of Scryer Prolog is *0.9.1*. And it's already useful for lots of tasks.
|
||||||
|
|
||||||
|
Scryer Prolog can be compiled from source, instructions are on the [GitHub README](https://github.com/mthom/scryer-prolog). It runs on Linux, macOS and Windows. Other operating systems may work but they're not regularly tested.
|
||||||
|
|
||||||
|
If you're in Linux, maybe your distribution already has an Scryer Prolog package.
|
||||||
|
|
||||||
|
There's also a [Docker image](https://github.com/mthom/scryer-prolog#docker-install) available.
|
||||||
|
|
||||||
|
## Support and discussions
|
||||||
|
|
||||||
|
If Scryer Prolog crashes or yields unexpected errors, consider filing
|
||||||
|
an [issue](https://github.com/mthom/scryer-prolog/issues).
|
||||||
|
|
||||||
|
To get in touch with the Scryer Prolog community, participate in
|
||||||
|
[discussions](https://github.com/mthom/scryer-prolog/discussions)
|
||||||
|
or visit our #scryer IRC channel on [Libera](https://libera.chat)!
|
||||||
563
README.md
563
README.md
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
# Scryer Prolog
|
# Scryer Prolog
|
||||||
|
|
||||||
Scryer Prolog aims to become to ISO Prolog what GHC is to Haskell: an open
|
Scryer Prolog aims to become to ISO Prolog what GHC is to Haskell: an open
|
||||||
@@ -5,12 +6,17 @@ source industrial strength production environment that is also a
|
|||||||
testbed for bleeding edge research in logic and constraint
|
testbed for bleeding edge research in logic and constraint
|
||||||
programming, which is itself written in a high-level language.
|
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)**.
|
||||||
|
|
||||||
|
The homepage of the project is: [**https://www.scryer.pl**](https://www.scryer.pl)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Phase 1
|
## Phase 1
|
||||||
|
|
||||||
Produce an implementation of the Warren Abstract Machine in Rust, done
|
Produce an implementation of the Warren Abstract Machine in Rust, done
|
||||||
according to the progression of languages in [Warren's Abstract
|
according to the progression of languages in [Warren's Abstract
|
||||||
Machine: A Tutorial
|
Machine: A Tutorial Reconstruction](https://github.com/mthom/scryer-prolog/blob/master/wambook/wambook.pdf).
|
||||||
Reconstruction](http://wambook.sourceforge.net/wambook.pdf).
|
|
||||||
|
|
||||||
Phase 1 has been completed in that Scryer Prolog implements in some form
|
Phase 1 has been completed in that Scryer Prolog implements in some form
|
||||||
all of the WAM book, including lists, cuts, Debray allocation, first
|
all of the WAM book, including lists, cuts, Debray allocation, first
|
||||||
@@ -40,8 +46,8 @@ Extend Scryer Prolog to include the following, among other features:
|
|||||||
- [x] Support for `verify_attributes/3`
|
- [x] Support for `verify_attributes/3`
|
||||||
- [x] Support for `attribute_goals/2` and `project_attributes/2`
|
- [x] Support for `attribute_goals/2` and `project_attributes/2`
|
||||||
- [x] `call_residue_vars/2`
|
- [x] `call_residue_vars/2`
|
||||||
- [x] `if_` and related predicates, following the developments of the
|
- [x] `if_/3` and related predicates, following the developments of the
|
||||||
paper "Indexing `dif/2`".
|
paper "[Indexing `dif/2`](https://arxiv.org/abs/1607.01590)".
|
||||||
- [x] All-solutions predicates (`findall/{3,4}`, `bagof/3`, `setof/3`, `forall/2`).
|
- [x] All-solutions predicates (`findall/{3,4}`, `bagof/3`, `setof/3`, `forall/2`).
|
||||||
- [x] Clause creation and destruction (`asserta/1`, `assertz/1`,
|
- [x] Clause creation and destruction (`asserta/1`, `assertz/1`,
|
||||||
`retract/1`, `abolish/1`) with logical update semantics.
|
`retract/1`, `abolish/1`) with logical update semantics.
|
||||||
@@ -49,15 +55,24 @@ Extend Scryer Prolog to include the following, among other features:
|
|||||||
`bb_put/2` (non-backtrackable) and `bb_b_put/2`
|
`bb_put/2` (non-backtrackable) and `bb_b_put/2`
|
||||||
(backtrackable).
|
(backtrackable).
|
||||||
- [x] Delimited continuations based on reset/3, shift/1 (documented in
|
- [x] Delimited continuations based on reset/3, shift/1 (documented in
|
||||||
"Delimited Continuations for Prolog").
|
"[Delimited Continuations for Prolog](https://biblio.ugent.be/publication/5646080/file/5646081)").
|
||||||
- [x] Tabling library based on delimited continuations
|
- [x] Tabling library based on delimited continuations
|
||||||
(documented in "Tabling as a Library with Delimited Control").
|
(documented in "[Tabling as a Library with Delimited Control](https://biblio.ugent.be/publication/6880648/file/6885145.pdf)").
|
||||||
- [x] A _redone_ representation of strings as difference lists of
|
- [x] A _redone_ representation of strings as difference lists of
|
||||||
characters, using a packed internal representation.
|
characters, using a packed internal representation.
|
||||||
- [x] clp(B) and clp(ℤ) as builtin libraries.
|
- [x] clp(B) and clp(ℤ) as builtin libraries.
|
||||||
- [ ] Streams and predicates for stream control (_in progress_).
|
- [x] Streams and predicates for stream control.
|
||||||
- [ ] A compacting garbage collector satisfying the five
|
- [x] A simple sockets library representing TCP connections as streams.
|
||||||
properties of "Precise Garbage Collection in Prolog."
|
- [x] Incremental compilation and loading process, newly written,
|
||||||
|
primarily in Prolog.
|
||||||
|
- [ ] Improvements to the WAM compiler and heap representation:
|
||||||
|
- [ ] Replacing choice points pivoting on inlined semi-deterministic predicates
|
||||||
|
(`atom`, `var`, etc) with if/else ladders. (_in progress_)
|
||||||
|
- [ ] Inlining all built-ins and system call instructions.
|
||||||
|
- [x] Greatly reducing the number of instructions used to compile disjunctives.
|
||||||
|
- [ ] Storing short atoms to heap cells without writing them to the atom table.
|
||||||
|
- [ ] A compacting garbage collector satisfying the five properties of
|
||||||
|
"[Precise Garbage Collection in Prolog](https://www.complang.tuwien.ac.at/ulrich/papers/PDF/2008-ciclops.pdf)." (_in progress_)
|
||||||
- [ ] Mode declarations.
|
- [ ] Mode declarations.
|
||||||
|
|
||||||
## Phase 3
|
## Phase 3
|
||||||
@@ -76,12 +91,12 @@ nice to have in the future. They'd make a good project for anyone wanting
|
|||||||
to contribute code to Scryer Prolog.
|
to contribute code to Scryer Prolog.
|
||||||
|
|
||||||
1. Implement the global analysis techniques described in Peter van
|
1. Implement the global analysis techniques described in Peter van
|
||||||
Roy's thesis, "Can Logic Programming Execute as Fast as Imperative
|
Roy's thesis, "[Can Logic Programming Execute as Fast as Imperative
|
||||||
Programming?"
|
Programming?](https://www.info.ucl.ac.be/~pvr/Peter.thesis/Peter.thesis.html)"
|
||||||
|
|
||||||
2. Add unum representation and arithmetic, using either an existing
|
2. Add unum representation and arithmetic, using either an existing
|
||||||
unum implementation or an ad hoc one. Unums are described in
|
unum implementation or an ad hoc one. Unums are described in
|
||||||
Gustafson's book "The End of Error."
|
Gustafson's book "[The End of Error](http://www.johngustafson.net/unums.html)."
|
||||||
|
|
||||||
3. Add concurrent tables to manage shared references to atoms and
|
3. Add concurrent tables to manage shared references to atoms and
|
||||||
strings.
|
strings.
|
||||||
@@ -90,6 +105,8 @@ strings.
|
|||||||
|
|
||||||
## Installing Scryer Prolog
|
## Installing Scryer Prolog
|
||||||
|
|
||||||
|
### Native Install
|
||||||
|
|
||||||
First, install the latest stable version of
|
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/en-US/install.html) using your
|
||||||
preferred method. Scryer tends to use features from newer Rust
|
preferred method. Scryer tends to use features from newer Rust
|
||||||
@@ -99,86 +116,121 @@ Rust updated to the latest stable release; any existing Rust
|
|||||||
distribution should be uninstalled from your system before rustup is
|
distribution should be uninstalled from your system before rustup is
|
||||||
used.
|
used.
|
||||||
|
|
||||||
Scryer Prolog can be installed with cargo, like so:
|
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:
|
||||||
$> cargo install scryer-prolog
|
|
||||||
```
|
|
||||||
|
|
||||||
cargo will download and install the libraries Scryer Prolog uses
|
|
||||||
automatically from crates.io. You can find the `scryer-prolog`
|
|
||||||
executable in `~/.cargo/bin`.
|
|
||||||
|
|
||||||
Publishing Rust crates to crates.io and pushing to git are entirely
|
|
||||||
distinct, independent processes, so to be sure you have the latest
|
|
||||||
commit, it is recommended to clone directly from this git repository,
|
|
||||||
which can be done as follows:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$> git clone https://github.com/mthom/scryer-prolog
|
$> git clone https://github.com/mthom/scryer-prolog
|
||||||
$> cd scryer-prolog
|
$> cd scryer-prolog
|
||||||
$> cargo run [--release]
|
$> cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
The optional `--release` flag will perform various optimizations,
|
The `--release` flag performs various optimizations, producing a
|
||||||
producing a faster executable.
|
faster executable.
|
||||||
|
|
||||||
|
After compilation, the executable `scryer-prolog` is available in the
|
||||||
|
directory `target/release` and can be invoked to run the system.
|
||||||
|
|
||||||
|
On Windows, Scryer Prolog is easier to build inside a [MSYS2](https://www.msys2.org/)
|
||||||
|
environment as some crates may require native C compilation. However,
|
||||||
|
the resulting binary does not need MSYS2 to run. When executing Scryer in a shell, it is recommended to use a more advanced shell than mintty (the default MSYS2 shell). The [Windows Terminal](https://github.com/microsoft/terminal) works correctly.
|
||||||
|
|
||||||
|
To build a Windows Installer, you'll need first Scryer Prolog compiled in release mode, then, with WiX Toolset installed, execute:
|
||||||
|
```
|
||||||
|
candle.exe scryer-prolog.wxs
|
||||||
|
light.exe scryer-prolog.wixobj
|
||||||
|
```
|
||||||
|
It will generate a very basic MSI file which installs the main executable and a shortcut in the Start Menu. It can be installed with a double-click. To uninstall, go to the Control Panel and uninstall as usual.
|
||||||
|
|
||||||
|
Scryer Prolog must be built with **Rust 1.63 and up**.
|
||||||
|
|
||||||
|
### Docker Install
|
||||||
|
|
||||||
|
First, install [Docker](https://docs.docker.com/get-docker/) on Linux,
|
||||||
|
Windows, or Mac.
|
||||||
|
|
||||||
|
Once Docker is installed, you can download and run Scryer Prolog with a single
|
||||||
|
command:
|
||||||
|
```
|
||||||
|
$> docker run -it mjt128/scryer-prolog
|
||||||
|
```
|
||||||
|
|
||||||
|
To consult your Prolog files, bind mount your programs folder as a
|
||||||
|
[Docker volume](https://docs.docker.com/storage/volumes/):
|
||||||
|
|
||||||
|
```
|
||||||
|
$> docker run -v /home/user/prolog:/mnt -it mjt128/scryer-prolog
|
||||||
|
?- consult('/mnt/program.pl').
|
||||||
|
true.
|
||||||
|
```
|
||||||
|
|
||||||
|
This works on Windows too:
|
||||||
|
|
||||||
|
```
|
||||||
|
$> docker run -v C:\Users\user\Documents\prolog:/mnt -it mjt128/scryer-prolog
|
||||||
|
?- consult('/mnt/program.pl').
|
||||||
|
true.
|
||||||
|
```
|
||||||
|
|
||||||
## Tutorial
|
## Tutorial
|
||||||
To enter a multi-clause predicate, the directive "[user]" is used.
|
|
||||||
|
|
||||||
For example,
|
Prolog files are loaded by specifying them as arguments on the command
|
||||||
```
|
line. For example, to load `program.pl`, use:
|
||||||
?- [user].
|
|
||||||
(type Enter + Ctrl-D to terminate the stream when finished)
|
|
||||||
p(f(f(X)), h(W), Y) :- g(W), h(W), f(X).
|
|
||||||
p(X, Y, Z) :- h(Y), z(Z).
|
|
||||||
?- [user].
|
|
||||||
(type Enter + Ctrl-D to terminate the stream when finished)
|
|
||||||
h(x). h(y).
|
|
||||||
h(z).
|
|
||||||
```
|
|
||||||
In the example, `Enter + Ctrl-D` is used to terminate the standard
|
|
||||||
input stream. The instructive message is always printed.
|
|
||||||
|
|
||||||
Queries are issued as
|
|
||||||
```
|
```
|
||||||
?- p(X, Y, Z).
|
$> scryer-prolog program.pl
|
||||||
```
|
```
|
||||||
|
|
||||||
Pressing `SPACE` will backtrack through other possible answers, if any exist.
|
Loading a Prolog file is also called “consulting” it. The built-in
|
||||||
Pressing `.` will abort the search and return to the prompt.
|
predicate `consult/1` can be used to consult a file from within
|
||||||
|
Prolog:
|
||||||
|
|
||||||
Wildcards work as well:
|
```
|
||||||
|
?- consult('program.pl').
|
||||||
|
```
|
||||||
|
|
||||||
|
As an abbreviation for `consult/1`, you can specify a *list* of
|
||||||
|
program files, given as *atoms*:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- ['program.pl'].
|
||||||
|
```
|
||||||
|
|
||||||
|
The special notation `[user]` is used to read Prolog text from
|
||||||
|
standard input. For example,
|
||||||
|
|
||||||
```
|
```
|
||||||
?- [user].
|
?- [user].
|
||||||
(type Enter + Ctrl-D to terminate the stream when finished)
|
hello(declarative_world).
|
||||||
member(X, [X|_]).
|
hello(pure_world).
|
||||||
member(X, [_|Xs]) :- member(X, Xs).
|
|
||||||
?- member(X, [a, b, c]).
|
|
||||||
X = a
|
|
||||||
; X = b
|
|
||||||
; X = c
|
|
||||||
; false.
|
|
||||||
```
|
|
||||||
and so do conjunctive queries:
|
|
||||||
```
|
|
||||||
?- [user].
|
|
||||||
(type Enter + Ctrl-D to terminate the stream when finished)
|
|
||||||
f(X) :- g(X).
|
|
||||||
g(x). g(y). g(z).
|
|
||||||
h(call(f, X)).
|
|
||||||
?- h(X), X.
|
|
||||||
X = call(f,x)
|
|
||||||
; X = call(f,y)
|
|
||||||
; X = call(f,z).
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the values of variables belonging to successful queries are
|
Pressing `RETURN` followed by `Ctrl-d` stops reading from
|
||||||
printed out, on one line each. Uninstantiated variables are denoted by
|
standard input and consults the entered Prolog text.
|
||||||
a number preceded by an underscore (`X = _0` in an example above).
|
|
||||||
|
After a program is consulted, you can ask *queries* about the
|
||||||
|
predicates it defines. For example, with the program shown above:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- hello(What).
|
||||||
|
What = declarative_world
|
||||||
|
; What = pure_world.
|
||||||
|
```
|
||||||
|
|
||||||
|
Press `SPACE` to show further answers, if any exist. Press `RETURN`
|
||||||
|
or `.` to abort the search and return to the
|
||||||
|
toplevel prompt. Press `f` to see up to the next multiple of
|
||||||
|
5 answers, and `a` to see all answers. Press `h` to show a help
|
||||||
|
message.
|
||||||
|
|
||||||
|
Use `TAB` to complete atoms and predicate names in queries. For
|
||||||
|
instance, after consulting the program above, typing `decl` followed
|
||||||
|
by `TAB` yields `declarative_world`. Press `TAB` repeatedly
|
||||||
|
to cycle through alternative completions.
|
||||||
|
|
||||||
|
To quit Scryer Prolog, use the standard predicate `halt/0`:
|
||||||
|
|
||||||
To quit scryer-prolog, type
|
|
||||||
```
|
```
|
||||||
?- halt.
|
?- halt.
|
||||||
```
|
```
|
||||||
@@ -190,17 +242,68 @@ arithmetic operators with the usual precedences,
|
|||||||
|
|
||||||
```
|
```
|
||||||
?- write_canonical(-5 + 3 - (2 * 4) // 8), nl.
|
?- write_canonical(-5 + 3 - (2 * 4) // 8), nl.
|
||||||
-(+(-5,3),//(*(2,4),8))
|
-(+(-5,3),//(*(2,4),8))
|
||||||
true.
|
true.
|
||||||
```
|
```
|
||||||
|
|
||||||
New operators can be defined using the `op` declaration.
|
New operators can be defined using the `op` declaration.
|
||||||
|
|
||||||
|
### First instantiated argument indexing
|
||||||
|
|
||||||
|
Scryer Prolog indexes on the leftmost argument that is not a variable
|
||||||
|
in all clauses of a predicate's definition. We call this strategy
|
||||||
|
first *instantiated* argument indexing.
|
||||||
|
|
||||||
|
A key motivation for first instantiated argument indexing is to enable
|
||||||
|
indexing for meta-predicates such as `maplist/N` and `foldl/N`, whose
|
||||||
|
first argument is a partial goal that is a variable in the definition
|
||||||
|
of these predicates and therefore cannot be used for indexing.
|
||||||
|
|
||||||
|
For example, a natural definition of `maplist/2` reads:
|
||||||
|
|
||||||
|
```
|
||||||
|
maplist(_, []).
|
||||||
|
maplist(Goal_1, [L|Ls]) :-
|
||||||
|
call(Goal_1, L),
|
||||||
|
maplist(Goal_1, Ls).
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, first instantiated argument indexing automatically uses
|
||||||
|
the *second* argument for indexing, and thus prevents choicepoints for
|
||||||
|
calls with lists of fixed lengths (and deterministic goals).
|
||||||
|
Conveniently, no auxiliary predicates with reordered arguments are
|
||||||
|
needed to benefit from indexing in such cases.
|
||||||
|
|
||||||
|
Conventional first argument indexing naturally arises as a
|
||||||
|
special case of this strategy, if the first argument is instantiated
|
||||||
|
in any clause of a predicate's definition.
|
||||||
|
|
||||||
### Strings and partial strings
|
### Strings and partial strings
|
||||||
|
|
||||||
|
A very compact internal representation of *strings* is one of the key
|
||||||
|
innovations of Scryer Prolog. This means that terms which appear as
|
||||||
|
lists of characters to Prolog programs are stored in packed
|
||||||
|
UTF-8 encoding by the engine.
|
||||||
|
|
||||||
|
Without this innovation, storing a list of characters in memory
|
||||||
|
would use one memory cell per character, one memory cell per
|
||||||
|
list constructor, and one memory cell for each tail that occurs
|
||||||
|
in the list. Since one memory cell takes 8 bytes on 64-bit
|
||||||
|
machines, the packed representation used by Scryer Prolog yields
|
||||||
|
an up to **24-fold reduction** of memory usage, and
|
||||||
|
corresponding reduction of memory accesses when creating and
|
||||||
|
processing strings.
|
||||||
|
|
||||||
|
Scryer Prolog's compact internal string representation makes it
|
||||||
|
ideally suited for the use case Prolog was originally developed for:
|
||||||
|
efficient and convenient text processing, especially with definite
|
||||||
|
clause grammars (DCGs) as provided by
|
||||||
|
[`library(dcgs)`](src/lib/dcgs.pl) and
|
||||||
|
[`library(pio)`](src/lib/pio.pl) to transparently apply DCGs to files.
|
||||||
|
|
||||||
In Scryer Prolog, the default value of the Prolog flag `double_quotes`
|
In Scryer Prolog, the default value of the Prolog flag `double_quotes`
|
||||||
is `chars`, which is also the recommended setting. This means that
|
is `chars`, which is also the recommended setting. This means that
|
||||||
double-quoted strings are interpreted as lists of *characters*, in the
|
lists of characters can be written as double-quoted strings, in the
|
||||||
tradition of Marseille Prolog.
|
tradition of Marseille Prolog.
|
||||||
|
|
||||||
For example, the following query succeeds:
|
For example, the following query succeeds:
|
||||||
@@ -210,15 +313,9 @@ For example, the following query succeeds:
|
|||||||
true.
|
true.
|
||||||
```
|
```
|
||||||
|
|
||||||
Internally, strings are represented very compactly in packed
|
This shows that the string `"abc"`, which is represented as a sequence
|
||||||
UTF-8 encoding. A naive representation of strings as lists of
|
of 3 bytes internally, appears to Prolog programs as a list of
|
||||||
characters would use one memory cell per character, one
|
characters.
|
||||||
memory cell per list constructor, and one memory cell for
|
|
||||||
each tail that occurs in the list. Since one memory cell takes
|
|
||||||
8 bytes on 64-bit machines, the packed representation used by
|
|
||||||
Scryer Prolog yields an up to **24-fold reduction** of
|
|
||||||
memory usage, and corresponding reduction of memory accesses when
|
|
||||||
creating and processing strings.
|
|
||||||
|
|
||||||
Scryer Prolog uses the same efficient encoding for *partial* strings,
|
Scryer Prolog uses the same efficient encoding for *partial* strings,
|
||||||
which appear to Prolog code as partial lists of characters. The
|
which appear to Prolog code as partial lists of characters. The
|
||||||
@@ -241,15 +338,120 @@ the above example, posting <tt>Ls0 = [a,b,c|Ls]</tt> yields
|
|||||||
the exact same internal representation, and has the advantage that
|
the exact same internal representation, and has the advantage that
|
||||||
only the standard predicate `(=)/2` is used.
|
only the standard predicate `(=)/2` is used.
|
||||||
|
|
||||||
Definite clause grammars as provided by `library(dcgs)` are ideally
|
The efficient internal representation of strings and partial strings
|
||||||
suited for reasoning about strings.
|
was first proposed and explained by Ulrich Neumerkel in
|
||||||
|
issues [#24](https://github.com/mthom/scryer-prolog/issues/24)
|
||||||
|
and [#95](https://github.com/mthom/scryer-prolog/issues/95), and
|
||||||
|
Scryer Prolog is the first Prolog system that implements it.
|
||||||
|
|
||||||
|
### Occurs check and cyclic terms
|
||||||
|
|
||||||
|
The *occurs check* is an element of algorithms that perform
|
||||||
|
syntactic unification, causing the unification to fail if a variable
|
||||||
|
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
|
||||||
|
*occurs check* is omitted yield *cyclic terms*, also called
|
||||||
|
*rational trees*. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- X = f(X), Y = g(X,Y).
|
||||||
|
X = f(X), Y = g(f(X),Y).
|
||||||
|
```
|
||||||
|
|
||||||
|
The creation of cyclic terms often indicates a programming mistake in
|
||||||
|
the formulation of Prolog predicates, and to obtain logically sound
|
||||||
|
results it is desirable to either perform all unifications with
|
||||||
|
*occurs check* enabled, or let Prolog throw an error if enabling
|
||||||
|
the *occurs check* is necessary to prevent a unification.
|
||||||
|
|
||||||
|
Scryer Prolog supports this via the Prolog flag `occurs_check`. It can
|
||||||
|
be set to one of the following values to obtain the desired behaviour:
|
||||||
|
|
||||||
|
- `false`
|
||||||
|
Do not perform the *occurs check*. This is the default.
|
||||||
|
- `true`
|
||||||
|
Perform all unifications with the *occurs check* enabled.
|
||||||
|
- `error`
|
||||||
|
Yield an error if a unification is performed that the
|
||||||
|
*occurs check* would have prevented.
|
||||||
|
|
||||||
|
Especially when starting with Prolog, we recommend to add the
|
||||||
|
following directive to the `~/.scryerrc` configuration file so that
|
||||||
|
programming mistakes in predicates that lead to the creation of cyclic
|
||||||
|
terms are indicated by errors:
|
||||||
|
|
||||||
|
```
|
||||||
|
:- set_prolog_flag(occurs_check, error).
|
||||||
|
```
|
||||||
|
|
||||||
|
Scryer Prolog implements specialized reasoning to make unifications
|
||||||
|
fast in many frequently occurring situations also if the
|
||||||
|
*occurs check* is enabled.
|
||||||
|
|
||||||
|
### Tabling (SLG resolution)
|
||||||
|
|
||||||
|
One of the foremost attractions of Prolog is that logical consequences
|
||||||
|
of pure programs can be derived by various execution strategies
|
||||||
|
that differ regarding essential properties such as termination,
|
||||||
|
completeness and efficiency.
|
||||||
|
|
||||||
|
The default execution strategy of Prolog is depth-first search with
|
||||||
|
chronological backtracking. This strategy is very efficient. Its main
|
||||||
|
drawback is that it is *incomplete*: It may fail to find any solution
|
||||||
|
even if one exists.
|
||||||
|
|
||||||
|
Scryer Prolog supports an alternative execution strategy which is
|
||||||
|
called *tabling* and also known as tabled execution and
|
||||||
|
SLG resolution. To enable tabled execution for a predicate, use
|
||||||
|
[`library(tabling)`](src/lib/tabling.pl) and add a `(table)/1`
|
||||||
|
directive for the desired predicate indicator. For example, if we
|
||||||
|
write:
|
||||||
|
|
||||||
|
```
|
||||||
|
:- use_module(library(tabling)).
|
||||||
|
:- table a/0.
|
||||||
|
|
||||||
|
a :- a.
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the query `?- a.` *terminates* (and fails), whereas it
|
||||||
|
does not terminate with the default execution strategy.
|
||||||
|
|
||||||
|
Scryer Prolog implements tabling via *delimited continuations* as
|
||||||
|
described in [*Tabling as a Library with Delimited
|
||||||
|
Control*](https://biblio.ugent.be/publication/6880648/file/6885145.pdf)
|
||||||
|
by Desouter et. al.
|
||||||
|
|
||||||
|
### Constraint Logic Programming (CLP)
|
||||||
|
|
||||||
|
Scryer Prolog provides excellent support for Constraint Logic
|
||||||
|
Programming (CLP), which is the amalgamation of
|
||||||
|
Logic Programming (LP) and Constraints.
|
||||||
|
|
||||||
|
In addition to built-in support for [`dif/2`](src/lib/dif.pl),
|
||||||
|
[`freeze/2`](src/lib/freeze.pl),
|
||||||
|
[CLP(B)](src/lib/clpb.pl) and [CLP(ℤ)](src/lib/clpz.pl),
|
||||||
|
Scryer provides a convenient way to implement new user-defined
|
||||||
|
constraints: *Attributed variables* are available via
|
||||||
|
[`library(atts)`](src/lib/atts.pl) as in SICStus Prolog,
|
||||||
|
which is one of the most sophisticated and fastest constraint systems
|
||||||
|
in existence. In [`library(iso_ext)`](src/lib/iso_ext.pl),
|
||||||
|
Scryer provides predicates for backtrackable (`bb_b_put/2`) and
|
||||||
|
non-backtrackable (`bb_put/2`) global variables, which are needed to
|
||||||
|
implement certain types of constraint solvers.
|
||||||
|
|
||||||
|
These features make Scryer Prolog an ideal platform for teaching,
|
||||||
|
learning and developing portable CLP applications.
|
||||||
|
|
||||||
### Modules
|
### Modules
|
||||||
|
|
||||||
Scryer has a simple predicate-based module system. It provides a
|
Scryer has a simple predicate-based module system. It provides a
|
||||||
way to separate units of code into distinct namespaces, for both
|
way to separate units of code into distinct namespaces, for both
|
||||||
predicates and operators. See the files
|
predicates and operators. See the files
|
||||||
[`src/prolog/lib/*.pl`](src/prolog/lib) for
|
[`src/lib/*.pl`](src/lib) for
|
||||||
examples.
|
examples.
|
||||||
|
|
||||||
At the time of this writing, many predicates reside in their own
|
At the time of this writing, many predicates reside in their own
|
||||||
@@ -257,31 +459,31 @@ modules that need to be imported before they can be used.
|
|||||||
The modules that ship with Scryer Prolog are also called
|
The modules that ship with Scryer Prolog are also called
|
||||||
*library* modules or *libraries*, and include:
|
*library* modules or *libraries*, and include:
|
||||||
|
|
||||||
* [`lists`](src/prolog/lib/lists.pl)
|
* [`lists`](src/lib/lists.pl)
|
||||||
providing `length/2`, `member/2`, `select/3`, `append/[2,3]`,
|
providing `length/2`, `member/2`, `select/3`, `append/[2,3]`,
|
||||||
`foldl/[4,5]`, `maplist/[2-9]`, `same_length/2`, `transpose/2` etc.
|
`foldl/[4,5]`, `maplist/[2-9]`, `same_length/2`, `transpose/2` etc.
|
||||||
* [`dcgs`](src/prolog/lib/dcgs.pl)
|
* [`dcgs`](src/lib/dcgs.pl)
|
||||||
Definite Clause Grammars (DCGs), a built-in grammar mechanism
|
Definite Clause Grammars (DCGs), a built-in grammar mechanism
|
||||||
that uses the operator `(-->)/2` to define grammar rules,
|
that uses the operator `(-->)/2` to define grammar rules,
|
||||||
and the predicates `phrase/[2,3]` to invoke them.
|
and the predicates `phrase/[2,3]` to invoke them.
|
||||||
* [`dif`](src/prolog/lib/dif.pl)
|
* [`dif`](src/lib/dif.pl)
|
||||||
The predicate `dif/2` provides declarative disequality:
|
The predicate `dif/2` provides declarative disequality:
|
||||||
It is true if and only if its arguments are different, and
|
It is true if and only if its arguments are different, and
|
||||||
delays the test until a sound decision can be made.
|
delays the test until a sound decision can be made.
|
||||||
* [`reif`](src/prolog/lib/reif.pl)
|
* [`reif`](src/lib/reif.pl)
|
||||||
providing `if_/3`, `tfilter/3` and related predicates
|
providing `if_/3`, `tfilter/3` and related predicates
|
||||||
as described in *Indexing dif/2*.
|
as described in *Indexing dif/2*.
|
||||||
* [`clpz`](src/prolog/lib/clpz.pl)
|
* [`clpz`](src/lib/clpz.pl)
|
||||||
CLP(ℤ): Constraint Logic Programming over Integers,
|
CLP(ℤ): Constraint Logic Programming over Integers,
|
||||||
providing declarative integer arithmetic via `(#=)/2`, `(#\=)/2`,
|
providing declarative integer arithmetic via `(#=)/2`, `(#\=)/2`,
|
||||||
`(#>=)/2` etc., and various global constraints and
|
`(#>=)/2` etc., and various global constraints and
|
||||||
enumeration predicates for solving combinatorial tasks.
|
enumeration predicates for solving combinatorial tasks.
|
||||||
* [`pairs`](src/prolog/lib/pairs.pl)
|
* [`pairs`](src/lib/pairs.pl)
|
||||||
By convention, *pairs* are Prolog terms with
|
By convention, *pairs* are Prolog terms with
|
||||||
principal functor `(-)/2`, written as `Key-Value`.
|
principal functor `(-)/2`, written as `Key-Value`.
|
||||||
This library provides `pairs_keys_values/3`,
|
This library provides `pairs_keys_values/3`,
|
||||||
`pairs_keys/2`, and other predicates to reason about pairs.
|
`pairs_keys/2`, and other predicates to reason about pairs.
|
||||||
* [`si`](src/prolog/lib/si.pl)
|
* [`si`](src/lib/si.pl)
|
||||||
The predicates `atom_si/1`, `integer_si/1`, `atomic_si/1`
|
The predicates `atom_si/1`, `integer_si/1`, `atomic_si/1`
|
||||||
and `list_si/1` implement sound type checks. They raise
|
and `list_si/1` implement sound type checks. They raise
|
||||||
instantiation errors if no decision can be made.
|
instantiation errors if no decision can be made.
|
||||||
@@ -290,25 +492,105 @@ The modules that ship with Scryer Prolog are also called
|
|||||||
write `integer_si(X)` to ensure soundness of your programs.
|
write `integer_si(X)` to ensure soundness of your programs.
|
||||||
"si" stands for *sufficiently instantiated*, and also for
|
"si" stands for *sufficiently instantiated*, and also for
|
||||||
*sound inference*.
|
*sound inference*.
|
||||||
* [`error`](src/prolog/lib/error.pl)
|
* [`debug`](src/lib/debug.pl)
|
||||||
`must_be/2` and `can_be/2` complement the type checks provided
|
Various predicates that allow for declarative debugging.
|
||||||
by `library(si)`, and are especially useful for Prolog library
|
* [`pio`](src/lib/pio.pl)
|
||||||
authors.
|
`phrase_from_file/2` applies a DCG nonterminal to the contents of a
|
||||||
* [`tabling`](src/prolog/lib/tabling.pl)
|
file, reading lazily only as much as is needed. Due to the compact
|
||||||
|
internal string representation, also extremely large files can be
|
||||||
|
efficiently processed with Scryer Prolog in this way.
|
||||||
|
`phrase_to_file/2` and `phrase_to_stream/2` write lists of
|
||||||
|
characters described by DCGs to files and streams, respectively.
|
||||||
|
* [`lambda`](src/lib/lambda.pl)
|
||||||
|
Lambda expressions to simplify higher order programming.
|
||||||
|
* [`charsio`](src/lib/charsio.pl) Various predicates that are useful
|
||||||
|
for parsing and reasoning about characters, notably `char_type/2` to
|
||||||
|
classify characters according to their type, and conversion
|
||||||
|
predicates for different encodings of strings.
|
||||||
|
* [`error`](src/lib/error.pl)
|
||||||
|
`must_be/2` and `can_be/2` complement the type checks provided by
|
||||||
|
[`library(si)`](src/lib/si.pl), and are especially useful for
|
||||||
|
Prolog library authors.
|
||||||
|
* [`tabling`](src/lib/tabling.pl)
|
||||||
The operator `(table)/1` is used in directives that prepare
|
The operator `(table)/1` is used in directives that prepare
|
||||||
predicates for tabled execution (SLG resolution).
|
predicates for tabled execution (SLG resolution).
|
||||||
* [`format`](src/prolog/lib/format.pl)
|
* [`format`](src/lib/format.pl)
|
||||||
The nonterminal `format_//2` is used to describe formatted output,
|
The nonterminal `format_//2` is used to describe formatted output,
|
||||||
arranging arguments according to a given format string.
|
arranging arguments according to a given format string.
|
||||||
The predicate `format/2` is provided for impure output.
|
The predicates `format/[2,3]`, `portray_clause/[1,2]` and `listing/1`
|
||||||
* [`assoc`](src/prolog/lib/assoc.pl)
|
provide formatted *impure* output.
|
||||||
|
* [`assoc`](src/lib/assoc.pl)
|
||||||
providing `empty_assoc/1`, `get_assoc/3`, `put_assoc/4` etc.
|
providing `empty_assoc/1`, `get_assoc/3`, `put_assoc/4` etc.
|
||||||
to manage elements in AVL trees which ensure
|
to manage elements in AVL trees which ensure
|
||||||
*O*(log(*N*)) access.
|
*O*(log(*N*)) access.
|
||||||
* [`clpb`](src/prolog/lib/clpb.pl)
|
* [`ordsets`](src/lib/ordsets.pl)
|
||||||
|
represents ordered sets as lists.
|
||||||
|
* [`clpb`](src/lib/clpb.pl)
|
||||||
CLP(B): Constraint Logic Programming over Boolean variables,
|
CLP(B): Constraint Logic Programming over Boolean variables,
|
||||||
a BDD-based SAT solver provided via the predicates
|
a BDD-based SAT solver provided via the predicates
|
||||||
`sat/1`, `taut/2`, `labeling/1` etc.
|
`sat/1`, `taut/2`, `labeling/1` etc.
|
||||||
|
* [`arithmetic`](src/lib/arithmetic.pl)
|
||||||
|
Arithmetic predicates such as `lsb/2`, `msb/2` and
|
||||||
|
`number_to_rational/2`.
|
||||||
|
* [`time`](src/lib/time.pl) Predicates for reasoning about
|
||||||
|
time, including `time/1` to measure the CPU time of a goal,
|
||||||
|
`current_time/1` to obtain the current system time, the nonterminal
|
||||||
|
`format_time//2` to describe strings with dates and times, and
|
||||||
|
`sleep/1` to slow down a computation.
|
||||||
|
* [`files`](src/lib/files.pl)
|
||||||
|
Predicates for reasoning about files and directories, such as
|
||||||
|
`directory_files/2`, `file_exists/1` and `file_size/2`.
|
||||||
|
* [`cont`](src/lib/cont.pl)
|
||||||
|
Provides *delimited continuations* via `reset/3` and `shift/1`.
|
||||||
|
* [`random`](src/lib/random.pl)
|
||||||
|
Probabilistic predicates and random number generators.
|
||||||
|
* [`http/http_open`](src/lib/http/http_open.pl) Open a stream to
|
||||||
|
read answers from web servers. HTTPS is also supported.
|
||||||
|
* [`http/http_server`](src/lib/http/http_server.pl) Runs a HTTP/1.1 and HTTP/2.0 web server. Uses [Hyper](https://hyper.rs) as a backend. Supports some query and form handling.
|
||||||
|
* [`sgml`](src/lib/sgml.pl)
|
||||||
|
`load_html/3` and `load_xml/3` represent HTML and XML documents
|
||||||
|
as Prolog terms for convenient and efficient reasoning. Use
|
||||||
|
[`library(xpath)`](src/lib/iso_ext.pl) to extract information from
|
||||||
|
parsed documents.
|
||||||
|
* [`csv`](src/lib/csv.pl)
|
||||||
|
`parse_csv//1` and `parse_csv//2` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||||||
|
or [`phrase/2`](src/lib/dcgs.pl) to parse csv
|
||||||
|
* [`serialization/abnf`](src/lib/serialization/abnf.pl)
|
||||||
|
DCGs describing the
|
||||||
|
[ABNF grammar core (RFC 5234)](https://tools.ietf.org/html/rfc5234#appendix-B.1),
|
||||||
|
which is used to describe many [IETF](https://www.ietf.org/standards/rfcs/)
|
||||||
|
syntaxes, such as [HTTP v1.1](https://www.rfc-editor.org/rfc/rfc7230.html#page-82),
|
||||||
|
[SMTP](https://www.rfc-editor.org/rfc/rfc5321.html),
|
||||||
|
[iCalendar](https://www.rfc-editor.org/rfc/rfc5545.html), and more.
|
||||||
|
* [`serialization/json`](src/lib/serialization/json.pl)
|
||||||
|
`json_chars//1` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||||||
|
or [`phrase/2`](src/lib/dcgs.pl) to parse and generate
|
||||||
|
[JSON](https://www.json.org/json-en.html).
|
||||||
|
* [`xpath`](src/lib/xpath.pl)
|
||||||
|
The predicate `xpath/3` is used for convenient reasoning about HTML
|
||||||
|
and XML documents, inspired by the XPath language. This library
|
||||||
|
is often used together with [`library(sgml)`](src/lib/sgml.pl).
|
||||||
|
* [`sockets`](src/lib/sockets.pl)
|
||||||
|
Predicates for opening and accepting TCP connections as streams.
|
||||||
|
* [`os`](src/lib/os.pl)
|
||||||
|
Predicates for reasoning about environment variables.
|
||||||
|
* [`iso_ext`](src/lib/iso_ext.pl)
|
||||||
|
Conforming extensions to and candidates for inclusion in the Prolog
|
||||||
|
ISO standard, such as `setup_call_cleanup/3`, `call_nth/2` and
|
||||||
|
`call_with_inference_limit/3`.
|
||||||
|
* [`crypto`](src/lib/crypto.pl)
|
||||||
|
Cryptographically secure random numbers and hashes, HMAC-based key
|
||||||
|
derivation (HKDF), password-based key derivation (PBKDF2),
|
||||||
|
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.
|
||||||
|
* [`uuid`](src/lib/uuid.pl) UUIDv4 generation and hex representation
|
||||||
|
* [`tls`](src/lib/tls.pl)
|
||||||
|
Predicates for negotiating TLS connections explicitly.
|
||||||
|
* [`ugraphs`](src/lib/ugraphs.pl) Graph manipulation library
|
||||||
|
* [`simplex`](src/lib/simplex.pl) Providing `assignment/2`,
|
||||||
|
`transportation/4` and other predicates for solving linear
|
||||||
|
programming problems.
|
||||||
|
|
||||||
To use predicates provided by the `lists` library, write:
|
To use predicates provided by the `lists` library, write:
|
||||||
|
|
||||||
@@ -345,7 +627,6 @@ REPL:
|
|||||||
|
|
||||||
```
|
```
|
||||||
?- [user].
|
?- [user].
|
||||||
(type Enter + Ctrl-D to terminate the stream when finished)
|
|
||||||
:- module(test, [local_member/2]).
|
:- module(test, [local_member/2]).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
@@ -354,3 +635,77 @@ local_member(X, Xs) :- member(X, Xs).
|
|||||||
|
|
||||||
The user listing can also be terminated by placing `end_of_file.` at
|
The user listing can also be terminated by placing `end_of_file.` at
|
||||||
the end of the stream.
|
the end of the stream.
|
||||||
|
|
||||||
|
### Configuration file
|
||||||
|
|
||||||
|
At startup, Scryer Prolog consults the file `~/.scryerrc`, if the file
|
||||||
|
exists. This file is useful to automatically load libraries and define
|
||||||
|
predicates that you need often.
|
||||||
|
|
||||||
|
For example, a sensible starting point for `~/.scryerrc` is:
|
||||||
|
|
||||||
|
```
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(reif)).
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development environment
|
||||||
|
|
||||||
|
To write and edit Prolog programs, we recommend
|
||||||
|
[GNU Emacs](https://www.gnu.org/software/emacs/) with the
|
||||||
|
[Prolog mode](https://bruda.ca/emacs/prolog_mode_for_emacs)
|
||||||
|
maintained by Stefan Bruda.
|
||||||
|
|
||||||
|
Use [ediprolog](https://www.metalevel.at/ediprolog/) to consult
|
||||||
|
Prolog code and evaluate Prolog queries in arbitrary
|
||||||
|
Emacs buffers.
|
||||||
|
|
||||||
|
Emacs definitions that show Prolog terms as trees are available
|
||||||
|
in [tools](tools).
|
||||||
|
|
||||||
|
To *debug* Prolog code, we recommend the predicates from
|
||||||
|
[**`library(debug)`**](src/lib/debug.pl), most notably:
|
||||||
|
|
||||||
|
- `(*)/1` to *"generalize away"* a Prolog goal. Use it to debug
|
||||||
|
unexpected failures by generalizing your definitions until they
|
||||||
|
succeed. Simply place `*` in front of a goal to generalize it away.
|
||||||
|
- `($)/1` to emit a *trace* of the execution, showing when a goal
|
||||||
|
is invoked, and when it has succeeded. Place `$` in front of a goal
|
||||||
|
to emit this information for that goal.
|
||||||
|
|
||||||
|
This way of debugging Prolog code has several major benefits, such as:
|
||||||
|
It stays close to the actual Prolog code under consideration, it does
|
||||||
|
not need additional tools and formalisms for its application, and
|
||||||
|
further, it encourages declarative reasoning that can in principle
|
||||||
|
also be performed automatically.
|
||||||
|
|
||||||
|
## Applications
|
||||||
|
|
||||||
|
Scryer Prolog's strong commitment to the Prolog ISO standard makes it
|
||||||
|
ideally suited for use in corporations and government agencies
|
||||||
|
that are subject to strict regulations pertaining to interoperability,
|
||||||
|
standards compliance and warranty.
|
||||||
|
|
||||||
|
Successful existing applications of Scryer Prolog include the
|
||||||
|
[DocLog](https://github.com/aarroyoc/doclog) system which
|
||||||
|
generates Scryer's own documentation and homepage, [Symbolic
|
||||||
|
Analysis of Grants](https://www.brz.gv.at/en/BRZ-Tech-Blog/Tech-Blog-7-Symbolic-Analysis-of-Grants.html)
|
||||||
|
by the Austrian Federal Computing Center, and parts of the
|
||||||
|
[precautionary](https://github.com/dcnorris/precautionary/tree/main/exec/prolog)
|
||||||
|
package for the analysis of dose-escalation trials in the
|
||||||
|
safety-critical and highly regulated domain of oncology
|
||||||
|
trial design.
|
||||||
|
|
||||||
|
Scryer Prolog is also very well suited for teaching and learning
|
||||||
|
Prolog, and for testing syntactic conformance and hence portability of
|
||||||
|
existing Prolog programs.
|
||||||
|
|
||||||
|
## Support and discussions
|
||||||
|
|
||||||
|
If Scryer Prolog crashes or yields unexpected errors, consider filing
|
||||||
|
an [issue](https://github.com/mthom/scryer-prolog/issues).
|
||||||
|
|
||||||
|
To get in touch with the Scryer Prolog community, participate in
|
||||||
|
[discussions](https://github.com/mthom/scryer-prolog/discussions)
|
||||||
|
or visit our #scryer IRC channel on [Libera](https://libera.chat)!
|
||||||
|
|||||||
60
build.rs
60
build.rs
@@ -1,60 +0,0 @@
|
|||||||
extern crate indexmap;
|
|
||||||
|
|
||||||
use indexmap::IndexSet;
|
|
||||||
|
|
||||||
use std::env;
|
|
||||||
use std::fs::{File, copy, read_dir};
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
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 mut library_index = IndexSet::new();
|
|
||||||
|
|
||||||
let paths = read_dir("./src/prolog/lib").unwrap();
|
|
||||||
|
|
||||||
for item in paths {
|
|
||||||
let item = item.unwrap().path();
|
|
||||||
|
|
||||||
if let Some(file_name) = item.file_name() {
|
|
||||||
if let Some(ext) = item.extension() {
|
|
||||||
if ext == "pl" {
|
|
||||||
let file_stem = item.file_stem().unwrap();
|
|
||||||
let file_str = file_stem.to_string_lossy().to_uppercase();
|
|
||||||
let dest = Path::new(&out_dir).join(file_name);
|
|
||||||
|
|
||||||
match copy(&item, dest) {
|
|
||||||
Ok(_) => {},
|
|
||||||
Err(e) => panic!("die: {:?}", e)
|
|
||||||
};
|
|
||||||
|
|
||||||
let include_line = format!("static {}: &str = include_str!(\"{}.pl\");\n",
|
|
||||||
file_str, file_stem.to_string_lossy());
|
|
||||||
|
|
||||||
libraries.write_all(include_line.as_bytes()).unwrap();
|
|
||||||
library_index.insert(file_stem.to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
libraries.write_all(b"\nref_thread_local! {
|
|
||||||
pub static managed LIBRARIES: IndexMap<&'static str, &'static str> = {
|
|
||||||
let mut m = IndexMap::new();\n").unwrap();
|
|
||||||
|
|
||||||
for item in library_index {
|
|
||||||
let line = format!("\n m.insert(\"{}\", {});", item, item.to_uppercase());
|
|
||||||
libraries.write_all(line.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
libraries.write_all(b"\n\n m\n };
|
|
||||||
}\n").unwrap();
|
|
||||||
|
|
||||||
libraries.write_all(b"\npub static PROJECT_DIR: &'static str = \"").unwrap();
|
|
||||||
libraries.write_all(env::var("CARGO_MANIFEST_DIR").unwrap().as_bytes()).unwrap();
|
|
||||||
libraries.write_all(b"\";\n").unwrap();
|
|
||||||
}
|
|
||||||
3490
build/instructions_template.rs
Normal file
3490
build/instructions_template.rs
Normal file
File diff suppressed because it is too large
Load Diff
114
build/main.rs
Normal file
114
build/main.rs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
mod instructions_template;
|
||||||
|
mod static_string_indexing;
|
||||||
|
|
||||||
|
use instructions_template::generate_instructions_rs;
|
||||||
|
use static_string_indexing::index_static_strings;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
fn find_prolog_files(libraries: &mut File, prefix: &str, current_dir: &Path) {
|
||||||
|
let entries = match current_dir.read_dir() {
|
||||||
|
Ok(entries) => entries,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} 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 line = format!(
|
||||||
|
" m.insert(\"{}\",\n{:?});\n",
|
||||||
|
prefix.to_owned() + name,
|
||||||
|
contain
|
||||||
|
);
|
||||||
|
|
||||||
|
libraries.write_all(line.as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let has_rustfmt = Command::new("rustfmt")
|
||||||
|
.arg("--version")
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.status()
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if !has_rustfmt {
|
||||||
|
println!("Failed to run rustfmt, will skip formatting generated files.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
|
let dest_path = Path::new(&out_dir).join("libraries.rs");
|
||||||
|
|
||||||
|
let mut libraries = File::create(&dest_path).unwrap();
|
||||||
|
let lib_path = Path::new("src/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",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
find_prolog_files(&mut libraries, "", &lib_path);
|
||||||
|
libraries.write_all(b"\n m\n };\n}\n").unwrap();
|
||||||
|
|
||||||
|
let instructions_path = Path::new(&out_dir).join("instructions.rs");
|
||||||
|
let mut instructions_file = File::create(&instructions_path).unwrap();
|
||||||
|
|
||||||
|
let quoted_output = generate_instructions_rs();
|
||||||
|
|
||||||
|
instructions_file
|
||||||
|
.write_all(quoted_output.to_string().as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if has_rustfmt {
|
||||||
|
format_generated_file(instructions_path.as_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
let static_atoms_path = Path::new(&out_dir).join("static_atoms.rs");
|
||||||
|
let mut static_atoms_file = File::create(&static_atoms_path).unwrap();
|
||||||
|
|
||||||
|
let quoted_output = index_static_strings(&instructions_path);
|
||||||
|
|
||||||
|
static_atoms_file
|
||||||
|
.write_all(quoted_output.to_string().as_bytes())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if has_rustfmt {
|
||||||
|
format_generated_file(static_atoms_path.as_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=src/");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_generated_file(path: &Path) {
|
||||||
|
Command::new("rustfmt")
|
||||||
|
.arg(path.as_os_str())
|
||||||
|
.spawn()
|
||||||
|
.unwrap_or_else(|err| {
|
||||||
|
panic!(
|
||||||
|
"{}: rustfmt was detected as available, but failed to format generated file '{}'",
|
||||||
|
err,
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.wait()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
177
build/static_string_indexing.rs
Normal file
177
build/static_string_indexing.rs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use syn::*;
|
||||||
|
use syn::parse::*;
|
||||||
|
use syn::visit::*;
|
||||||
|
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
|
||||||
|
struct StaticStrVisitor {
|
||||||
|
static_strs: IndexSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticStrVisitor {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self { static_strs: IndexSet::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MacroFnArgs {
|
||||||
|
args: Vec<Expr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ReadHeapCellExprAndArms {
|
||||||
|
expr: Expr,
|
||||||
|
arms: Vec<Arm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for ReadHeapCellExprAndArms {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let mut arms = vec![];
|
||||||
|
let expr = input.parse()?;
|
||||||
|
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
arms.push(input.parse()?);
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
if let Ok(_) = input.parse::<Token![,]>() {}
|
||||||
|
arms.push(input.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ReadHeapCellExprAndArms { expr, arms })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for MacroFnArgs {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let mut args = vec![];
|
||||||
|
|
||||||
|
if !input.is_empty() {
|
||||||
|
args.push(input.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
if let Ok(_) = input.parse::<Token![,]>() {}
|
||||||
|
args.push(input.parse()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MacroFnArgs { args })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'ast> Visit<'ast> for StaticStrVisitor {
|
||||||
|
fn visit_macro(&mut self, m: &'ast Macro) {
|
||||||
|
let Macro { path, .. } = m;
|
||||||
|
|
||||||
|
if path.is_ident("atom") {
|
||||||
|
if let Some(Lit::Str(string)) = m.parse_body::<Lit>().ok() {
|
||||||
|
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() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStream {
|
||||||
|
use quote::*;
|
||||||
|
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
fn filter_rust_files(e: &walkdir::DirEntry) -> bool {
|
||||||
|
if e.path().is_dir() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.path().extension().and_then(OsStr::to_str) == Some("rs")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut visitor = StaticStrVisitor::new();
|
||||||
|
|
||||||
|
fn process_filepath(path: &std::path::Path) -> std::result::Result<syn::File, ()> {
|
||||||
|
let mut src = String::new();
|
||||||
|
|
||||||
|
let mut file = match File::open(path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match file.read_to_string(&mut src) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(syntax)
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in WalkDir::new("src/")
|
||||||
|
.into_iter()
|
||||||
|
.filter_entry(filter_rust_files)
|
||||||
|
{
|
||||||
|
let entry = entry.unwrap();
|
||||||
|
|
||||||
|
if entry.path().is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let syntax = match process_filepath(entry.path()) {
|
||||||
|
Ok(syntax) => syntax,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.visit_file(&syntax);
|
||||||
|
}
|
||||||
|
|
||||||
|
match process_filepath(instruction_rs_path) {
|
||||||
|
Ok(syntax) => visitor.visit_file(&syntax),
|
||||||
|
Err(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let indices = (0..visitor.static_strs.len()).map(|i| (i << 3) as u64);
|
||||||
|
let indices_iter = indices.clone();
|
||||||
|
|
||||||
|
let static_strs_len = visitor.static_strs.len();
|
||||||
|
let static_strs: &Vec<_> = &visitor.static_strs.into_iter().collect();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
use phf;
|
||||||
|
|
||||||
|
static STRINGS: [&'static str; #static_strs_len] = [
|
||||||
|
#(
|
||||||
|
#static_strs,
|
||||||
|
)*
|
||||||
|
];
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! atom {
|
||||||
|
#((#static_strs) => { Atom { index: #indices_iter } };)*
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {
|
||||||
|
#(#static_strs => { Atom { index: #indices } },)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
logo/scryer.png
Normal file
BIN
logo/scryer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
896
logo/scryer.svg
Normal file
896
logo/scryer.svg
Normal file
@@ -0,0 +1,896 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<path fill="#57d3de" d="M0 0 h 300 v 300 h -300 z"/>
|
||||||
|
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#800080" stroke="none">
|
||||||
|
<path d="M2388 2423 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||||
|
<path d="M2436 2422 c-3 -5 8 -6 25 -4 16 2 29 6 29 8 0 8 -49 4 -54 -4z"/>
|
||||||
|
<path d="M2310 2416 c0 -10 28 -13 41 -5 10 6 7 9 -13 9 -16 0 -28 -2 -28 -4z"/>
|
||||||
|
<path d="M2501 2415 c3 -2 23 -7 45 -9 86 -10 151 -37 181 -75 8 -10 12 -11
|
||||||
|
13 -4 0 19 -81 69 -130 80 -50 11 -117 16 -109 8z"/>
|
||||||
|
<path d="M2055 2373 c-60 -13 -126 -27 -145 -30 -19 -3 -38 -11 -42 -17 -6 -8
|
||||||
|
-10 -7 -17 2 -7 11 -19 12 -62 3 -30 -6 -82 -12 -116 -14 -46 -1 -63 -6 -66
|
||||||
|
-18 -5 -20 -107 -27 -107 -7 0 9 -3 9 -12 0 -19 -19 -41 -15 -33 6 4 11 3 13
|
||||||
|
-3 7 -6 -5 -12 -19 -14 -30 -2 -11 -9 -19 -16 -18 -6 2 -9 -4 -6 -12 5 -11 11
|
||||||
|
-12 24 -5 30 16 34 11 9 -9 -13 -11 -27 -15 -30 -10 -9 16 -39 -2 -39 -23 0
|
||||||
|
-11 -5 -29 -11 -40 -14 -26 2 -37 45 -32 23 4 27 2 16 -5 -13 -9 -13 -11 3
|
||||||
|
-12 26 -2 31 -4 44 -12 9 -6 11 -3 7 10 -5 17 -4 16 10 -1 8 -10 17 -23 18
|
||||||
|
-28 2 -5 19 -1 39 9 22 11 34 23 31 32 -2 8 0 11 6 7 11 -6 22 16 22 42 -1 11
|
||||||
|
-6 8 -16 -10 -8 -16 -19 -28 -24 -28 -6 0 -10 -7 -10 -15 0 -8 -4 -15 -9 -15
|
||||||
|
-5 0 -16 -3 -25 -6 -11 -4 -16 -1 -16 10 0 16 -10 20 -42 17 -10 -1 -18 3 -18
|
||||||
|
8 0 6 -9 11 -19 11 -47 0 -36 35 23 73 51 32 63 33 94 7 19 -16 21 -23 12 -40
|
||||||
|
-11 -20 -21 -27 -19 -12 4 27 -2 44 -13 35 -7 -5 -26 -10 -43 -11 -64 -4 -47
|
||||||
|
-40 25 -49 95 -13 99 89 4 107 -37 7 -37 8 -8 9 30 1 88 -21 80 -30 -2 -2 2
|
||||||
|
-9 9 -16 10 -9 17 -7 31 9 16 18 17 21 2 15 -9 -3 -18 0 -21 6 -4 12 18 17 78
|
||||||
|
17 20 0 38 10 57 30 15 18 33 28 41 24 8 -3 18 -1 21 5 4 6 16 6 36 -1 23 -7
|
||||||
|
34 -7 45 2 10 8 15 9 15 2 0 -18 -34 -31 -53 -21 -11 7 -23 6 -34 -1 -17 -11
|
||||||
|
-17 -11 0 -6 10 3 28 1 39 -5 16 -8 24 -7 41 8 12 11 29 19 37 18 8 0 22 7 31
|
||||||
|
17 9 10 18 16 20 14 2 -2 23 -1 47 4 25 4 42 4 42 -2 0 -10 42 0 50 12 3 4 25
|
||||||
|
15 50 23 67 24 14 19 -115 -10z m-415 -83 c0 -5 -4 -10 -10 -10 -5 0 -10 5
|
||||||
|
-10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m82 3 c-6 -3 -9 -9 -6 -15 4 -6 1 -9
|
||||||
|
-6 -6 -23 7 -21 24 2 25 13 0 17 -1 10 -4z"/>
|
||||||
|
<path d="M2220 2395 c0 -7 30 -13 34 -7 3 4 -4 9 -15 9 -10 1 -19 0 -19 -2z"/>
|
||||||
|
<path d="M2136 2328 l-21 -17 27 -1 c20 0 26 4 22 15 -5 11 0 15 17 14 13 0
|
||||||
|
18 -3 11 -6 -19 -7 -4 -23 16 -16 15 5 15 4 2 -10 -8 -9 -26 -18 -40 -21 -14
|
||||||
|
-2 -34 -8 -44 -12 -14 -5 -17 -4 -12 4 4 7 3 12 -2 12 -6 0 -12 -6 -15 -14 -4
|
||||||
|
-11 -67 -30 -92 -27 -2 0 -23 -6 -46 -14 -49 -18 -62 -19 -54 -5 4 6 10 8 15
|
||||||
|
5 4 -3 13 2 20 10 21 25 -6 17 -39 -11 -31 -26 -61 -27 -61 -1 0 7 5 18 12 25
|
||||||
|
8 8 8 12 1 12 -6 0 -13 -7 -17 -15 -3 -8 -17 -15 -31 -15 -24 0 -30 9 -26 38
|
||||||
|
1 6 -5 12 -14 12 -18 0 -19 -12 -3 -28 9 -9 8 -12 -7 -12 -10 0 -15 -3 -12 -7
|
||||||
|
4 -3 1 -13 -5 -22 -9 -11 -5 -10 9 2 20 17 50 16 39 -2 -3 -4 5 -8 16 -8 26
|
||||||
|
-2 30 -7 23 -26 -7 -16 -44 -29 -67 -24 -7 2 -10 2 -5 0 17 -10 5 -22 -20 -19
|
||||||
|
-16 2 -22 1 -15 -1 7 -3 10 -9 7 -14 -4 -5 5 -8 19 -7 15 1 26 6 26 11 0 4 5
|
||||||
|
5 10 2 6 -3 10 -2 10 3 0 13 70 49 110 57 19 4 78 19 130 35 52 15 114 33 137
|
||||||
|
39 24 6 47 21 53 31 6 11 25 26 43 33 30 13 29 13 -37 13 -51 0 -74 -4 -90
|
||||||
|
-18z"/>
|
||||||
|
<path d="M2374 2341 c3 -5 15 -7 26 -4 28 7 25 13 -6 13 -14 0 -23 -4 -20 -9z"/>
|
||||||
|
<path d="M1445 2329 c-194 -16 -394 -99 -536 -222 -14 -12 -72 -58 -129 -102
|
||||||
|
-136 -106 -198 -162 -206 -189 -3 -11 -16 -32 -29 -46 -12 -14 -35 -47 -50
|
||||||
|
-75 -15 -27 -32 -57 -39 -65 -10 -13 -9 -13 6 -1 26 20 22 5 -11 -37 -15 -20
|
||||||
|
-42 -75 -58 -122 -17 -47 -38 -102 -47 -123 -9 -22 -12 -36 -7 -33 6 4 2 -13
|
||||||
|
-9 -36 -26 -56 -25 -77 1 -44 11 14 17 30 14 36 -3 5 -1 10 4 10 6 0 11 -8 11
|
||||||
|
-17 0 -15 2 -15 14 2 9 13 11 27 6 41 -11 29 -8 49 13 72 10 11 17 23 16 28
|
||||||
|
-3 20 2 35 10 30 11 -7 23 22 15 35 -10 16 6 31 21 19 9 -8 15 -8 20 0 3 5 1
|
||||||
|
10 -6 10 -8 0 -8 4 1 15 7 8 17 12 24 8 7 -4 6 -1 -1 9 -7 8 -10 18 -6 21 3 4
|
||||||
|
4 7 0 7 -3 0 -13 -7 -21 -16 -17 -16 -36 -11 -36 9 0 8 3 7 9 -2 7 -11 11 -8
|
||||||
|
16 10 4 13 16 30 27 37 10 8 15 19 11 25 -4 7 0 6 10 -2 9 -7 17 -12 18 -10 0
|
||||||
|
2 2 19 4 37 2 19 12 40 22 48 22 16 10 18 -20 2 -12 -6 1 12 30 40 28 29 56
|
||||||
|
52 62 52 13 0 15 27 2 34 -5 3 -8 -2 -7 -11 0 -9 -5 -18 -12 -20 -8 -3 -10 1
|
||||||
|
-5 14 10 28 25 44 50 54 13 5 22 14 21 21 -2 6 6 14 17 16 11 2 25 9 30 14 7
|
||||||
|
8 5 9 -5 4 -8 -4 -4 2 10 13 14 12 35 24 47 27 12 3 29 17 38 31 9 14 23 22
|
||||||
|
30 19 9 -3 12 0 8 9 -3 9 3 18 13 21 11 4 33 17 51 30 34 26 123 43 123 24 0
|
||||||
|
-6 5 -8 12 -4 11 7 26 10 83 18 11 2 24 -2 28 -8 6 -7 7 -6 5 4 -3 8 -15 17
|
||||||
|
-28 20 -20 5 -21 7 -7 18 10 8 17 9 21 3 5 -7 14 -7 29 0 24 11 54 49 38 49
|
||||||
|
-5 0 -11 -5 -13 -11 -2 -6 -23 -11 -46 -11 -23 0 -42 -4 -42 -9 0 -6 -3 -8 -7
|
||||||
|
-6 -5 3 -14 -1 -21 -9 -11 -12 -10 -14 5 -14 16 0 16 -2 2 -15 -17 -17 -53
|
||||||
|
-10 -42 8 4 6 1 7 -7 2 -7 -4 -28 -11 -47 -15 -29 -7 -32 -6 -22 6 7 8 14 12
|
||||||
|
16 10 3 -3 15 7 28 22 20 24 24 25 41 12 10 -8 15 -10 11 -4 -11 19 21 51 60
|
||||||
|
59 21 5 44 9 50 11 7 1 20 9 29 18 17 18 28 21 19 6 -3 -5 3 -10 14 -10 11 0
|
||||||
|
23 5 26 11 4 5 18 14 32 20 13 5 22 13 19 18 -8 12 155 35 203 29 20 -3 47 -2
|
||||||
|
61 2 39 10 -49 16 -135 9z m-940 -679 c3 -5 -1 -10 -10 -10 -9 0 -13 5 -10 10
|
||||||
|
3 6 8 10 10 10 2 0 7 -4 10 -10z"/>
|
||||||
|
<path d="M2490 2321 c-7 -11 -24 -21 -37 -24 -27 -5 -32 9 -5 16 15 4 15 5 -1
|
||||||
|
6 -11 1 -21 -8 -24 -20 -5 -16 -11 -19 -30 -14 -19 4 -23 3 -17 -7 6 -10 5
|
||||||
|
-11 -7 -1 -12 10 -19 8 -33 -8 -11 -13 -25 -19 -37 -16 -11 3 -32 -1 -47 -8
|
||||||
|
-43 -21 -75 -29 -68 -17 4 6 -13 0 -37 -14 -25 -13 -63 -25 -86 -27 -22 -1
|
||||||
|
-41 -5 -41 -9 0 -5 -9 -8 -20 -8 -52 0 -250 -68 -250 -86 0 -3 5 -2 10 1 6 3
|
||||||
|
10 2 10 -4 0 -5 -4 -13 -10 -16 -5 -3 -10 -12 -9 -18 0 -9 2 -9 6 1 2 6 13 12
|
||||||
|
23 12 10 0 22 5 25 10 4 6 11 8 16 5 5 -4 9 -1 9 4 0 6 3 10 8 10 4 -1 26 1
|
||||||
|
49 6 32 6 40 11 36 23 -4 10 -2 13 5 8 7 -4 12 -3 12 2 0 5 6 9 13 9 38 1 102
|
||||||
|
15 115 25 13 11 15 10 9 -4 -4 -12 -2 -15 8 -11 8 3 13 8 11 12 -2 3 9 7 25 8
|
||||||
|
15 1 42 9 59 18 17 9 52 20 78 26 26 5 64 14 84 18 21 5 43 13 50 19 7 6 42
|
||||||
|
15 78 22 36 6 79 16 96 21 41 12 94 11 94 -2 0 -6 -7 -17 -15 -25 -8 -9 -15
|
||||||
|
-11 -15 -5 0 6 -7 8 -15 5 -9 -4 -15 0 -15 10 0 9 -5 16 -11 16 -7 0 -10 -12
|
||||||
|
-7 -30 5 -34 -11 -45 -30 -21 -11 13 -13 13 -9 1 4 -15 -24 -41 -55 -53 -11
|
||||||
|
-4 -21 0 -27 11 -10 16 -10 16 -11 0 0 -18 7 -21 43 -21 13 0 15 -3 6 -13 -14
|
||||||
|
-16 -39 -19 -39 -3 0 5 -5 7 -11 3 -8 -4 -7 -9 2 -15 12 -8 11 -11 -1 -19 -8
|
||||||
|
-5 -20 -10 -27 -10 -6 0 -14 -6 -17 -12 -5 -10 -7 -10 -12 0 -8 18 -24 15 -24
|
||||||
|
-5 0 -9 -3 -14 -6 -10 -4 3 -22 -5 -41 -18 -20 -13 -44 -22 -54 -20 -11 2 -17
|
||||||
|
0 -15 -4 3 -4 -14 -19 -37 -32 -23 -13 -54 -30 -69 -39 -103 -58 -157 -92
|
||||||
|
-170 -107 -8 -10 -25 -45 -37 -78 -12 -33 -33 -74 -47 -92 -14 -18 -22 -33
|
||||||
|
-18 -33 4 0 -1 -10 -12 -22 -17 -19 -56 -132 -69 -200 -2 -9 -8 -25 -13 -35
|
||||||
|
-12 -21 -12 -22 -12 -70 0 -28 -3 -34 -10 -23 -8 12 -10 9 -10 -12 0 -14 -3
|
||||||
|
-34 -7 -44 -5 -13 -3 -16 10 -11 14 5 17 0 17 -28 0 -18 5 -37 10 -40 16 -10
|
||||||
|
12 -25 -7 -25 -11 0 -14 3 -7 8 8 5 6 12 -5 24 -15 15 -16 14 -15 -19 1 -19
|
||||||
|
-2 -49 -7 -66 -5 -17 -5 -35 -1 -39 5 -5 14 9 21 31 15 53 31 61 25 14 -3 -21
|
||||||
|
-6 -57 -8 -81 -1 -23 -8 -48 -15 -55 -10 -10 -12 -8 -9 8 4 24 -33 29 -39 5
|
||||||
|
-4 -13 -3 -13 6 0 15 22 23 7 31 -57 6 -44 8 -50 9 -23 2 28 4 31 10 15 5 -11
|
||||||
|
6 -25 4 -31 -2 -7 0 -29 6 -50 5 -22 13 -59 16 -83 3 -23 11 -54 16 -68 17
|
||||||
|
-45 20 -104 7 -121 -7 -9 -8 -13 -2 -9 7 4 12 -2 12 -18 0 -14 -4 -22 -9 -19
|
||||||
|
-5 2 -2 -11 6 -30 8 -20 15 -46 15 -59 0 -14 13 -35 30 -50 22 -20 30 -23 30
|
||||||
|
-12 0 9 -10 20 -22 26 -18 8 -24 20 -26 51 -3 42 -3 42 18 23 20 -19 21 -19
|
||||||
|
13 1 -4 10 -12 25 -19 31 -7 8 -10 28 -7 52 4 25 -6 84 -26 169 -27 110 -31
|
||||||
|
147 -29 234 1 57 1 172 -1 256 -2 110 1 157 10 168 6 8 10 17 7 20 -3 3 3 37
|
||||||
|
13 75 17 63 51 142 110 249 10 19 29 57 41 84 21 44 33 55 124 110 56 34 144
|
||||||
|
81 195 105 127 59 148 70 219 116 34 22 99 64 146 94 46 29 84 57 84 63 0 5
|
||||||
|
-5 6 -10 3 -6 -4 -26 -1 -45 5 -19 6 -67 12 -107 13 -40 1 -75 5 -78 9 -3 4
|
||||||
|
-12 -1 -20 -11z m-697 -236 c-3 -9 -8 -14 -10 -11 -3 3 -2 9 2 15 9 16 15 13
|
||||||
|
8 -4z"/>
|
||||||
|
<path d="M2025 2320 c-17 -7 -15 -9 13 -9 17 -1 32 4 32 9 0 12 -17 12 -45 0z"/>
|
||||||
|
<path d="M1910 2280 c-20 -12 -10 -19 14 -10 11 3 15 9 10 13 -6 3 -16 2 -24
|
||||||
|
-3z"/>
|
||||||
|
<path d="M1980 2274 c-29 -16 -8 -19 22 -3 16 8 23 8 30 -2 8 -11 9 -11 5 1
|
||||||
|
-5 20 -26 21 -57 4z"/>
|
||||||
|
<path d="M2065 2280 c-3 -5 1 -10 9 -10 9 0 16 5 16 10 0 6 -4 10 -9 10 -6 0
|
||||||
|
-13 -4 -16 -10z"/>
|
||||||
|
<path d="M1806 2271 c-12 -5 -15 -10 -9 -14 6 -4 16 0 23 8 14 17 13 17 -14 6z"/>
|
||||||
|
<path d="M1340 2245 c-14 -8 -37 -14 -51 -15 -14 0 -34 -9 -44 -20 -18 -20
|
||||||
|
-18 -21 -1 -38 14 -14 16 -14 11 -1 -7 19 20 44 57 53 14 3 34 9 44 13 12 4
|
||||||
|
15 3 10 -5 -4 -7 -3 -12 3 -12 6 0 15 9 21 20 9 17 8 20 -7 20 -10 -1 -29 -7
|
||||||
|
-43 -15z"/>
|
||||||
|
<path d="M1190 2209 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M1140 2199 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M1330 2201 c0 -5 5 -13 10 -16 6 -3 10 -2 10 4 0 5 -4 13 -10 16 -5
|
||||||
|
3 -10 2 -10 -4z"/>
|
||||||
|
<path d="M1786 2184 c-11 -8 -16 -14 -10 -14 13 0 39 18 34 24 -3 2 -14 -2
|
||||||
|
-24 -10z"/>
|
||||||
|
<path d="M1294 2168 c6 -37 -36 -64 -48 -31 -3 8 -10 11 -16 8 -5 -3 -10 -1
|
||||||
|
-10 6 0 7 -3 9 -6 6 -4 -4 -1 -13 5 -21 7 -8 11 -19 8 -23 -3 -4 16 -8 41 -8
|
||||||
|
34 0 46 -4 44 -13 -1 -8 2 -11 8 -7 6 3 8 11 4 16 -3 6 2 17 12 25 18 14 18
|
||||||
|
14 -3 5 -29 -15 -32 -14 -26 8 3 11 0 28 -6 38 -10 15 -10 14 -7 -9z"/>
|
||||||
|
<path d="M2355 2150 c-3 -5 -3 -10 2 -10 4 -1 -3 -7 -16 -14 -14 -8 -31 -12
|
||||||
|
-38 -9 -8 3 -13 0 -11 -8 2 -7 -9 -15 -27 -19 -25 -5 -27 -7 -11 -13 11 -5 25
|
||||||
|
-2 35 6 26 22 87 47 115 47 14 0 26 4 26 10 0 6 -15 8 -36 4 -25 -4 -34 -2
|
||||||
|
-29 5 3 6 4 11 1 11 -3 0 -8 -4 -11 -10z"/>
|
||||||
|
<path d="M2135 2140 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M1676 2107 c-11 -8 -17 -18 -15 -23 2 -5 14 1 26 14 24 24 17 30 -11
|
||||||
|
9z"/>
|
||||||
|
<path d="M2025 2109 c-5 -7 -3 -10 6 -7 8 3 22 1 32 -4 11 -5 17 -5 17 2 0 5
|
||||||
|
-4 10 -9 10 -5 0 -16 3 -24 6 -8 3 -18 0 -22 -7z"/>
|
||||||
|
<path d="M2250 2109 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M2200 2099 c0 -5 -5 -7 -12 -3 -6 4 -8 3 -4 -4 3 -6 2 -13 -4 -17 -5
|
||||||
|
-3 -10 -2 -10 3 0 6 -9 8 -20 5 -11 -3 -20 -9 -20 -14 0 -5 -7 -5 -17 -2 -10
|
||||||
|
4 -14 2 -9 -5 9 -15 -13 -27 -27 -13 -13 13 -41 15 -33 3 11 -19 -15 -37 -55
|
||||||
|
-37 -29 0 -44 -5 -46 -15 -4 -13 -3 -13 6 0 9 13 11 13 11 1 0 -12 3 -12 12
|
||||||
|
-3 14 14 42 16 33 2 -7 -11 23 -3 41 11 6 5 15 5 19 1 5 -4 5 2 1 12 -6 16 -4
|
||||||
|
17 10 6 13 -12 17 -11 24 0 5 8 12 11 16 7 4 -4 17 -1 28 6 12 7 35 12 52 12
|
||||||
|
30 -1 31 0 22 27 -10 29 -18 36 -18 17z"/>
|
||||||
|
<path d="M1967 2093 c-12 -12 -7 -22 8 -17 8 4 15 10 15 15 0 11 -14 12 -23 2z"/>
|
||||||
|
<path d="M1367 2084 c-4 -4 -7 -17 -7 -28 1 -19 1 -20 11 -3 11 19 8 43 -4 31z"/>
|
||||||
|
<path d="M1198 2073 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||||
|
<path d="M1326 2065 c-3 -8 -1 -15 4 -15 13 0 22 18 11 24 -5 3 -11 -1 -15 -9z"/>
|
||||||
|
<path d="M2000 2069 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M1065 2055 c-27 -8 -51 -19 -53 -25 -2 -5 -8 -8 -13 -4 -5 3 -14 2
|
||||||
|
-21 -2 -8 -5 -6 -10 8 -15 12 -4 26 -1 36 8 9 9 37 18 61 20 28 3 44 10 44 19
|
||||||
|
0 16 3 16 -62 -1z"/>
|
||||||
|
<path d="M1155 2049 c-23 -9 -21 -10 20 -14 146 -13 178 -14 145 -5 -19 5 -57
|
||||||
|
12 -85 16 -27 3 -52 8 -55 9 -3 1 -14 -1 -25 -6z"/>
|
||||||
|
<path d="M1675 2050 c4 -6 1 -17 -5 -26 -11 -12 -9 -14 11 -10 13 2 32 -1 42
|
||||||
|
-6 13 -6 17 -5 17 7 0 9 -8 18 -17 21 -10 3 -26 10 -37 15 -13 8 -16 7 -11 -1z"/>
|
||||||
|
<path d="M1800 2046 c0 -2 7 -6 15 -10 8 -3 15 -1 15 4 0 6 -7 10 -15 10 -8 0
|
||||||
|
-15 -2 -15 -4z"/>
|
||||||
|
<path d="M903 2029 c3 -9 0 -22 -6 -29 -16 -16 43 10 61 27 11 11 11 13 0 13
|
||||||
|
-8 0 -20 -6 -26 -12 -10 -10 -15 -10 -23 2 -9 13 -10 13 -6 -1z"/>
|
||||||
|
<path d="M1860 2030 c0 -13 11 -13 30 0 12 8 11 10 -7 10 -13 0 -23 -4 -23
|
||||||
|
-10z"/>
|
||||||
|
<path d="M1780 2017 c0 -10 -6 -14 -14 -11 -7 3 -16 -1 -19 -8 -3 -9 2 -14 14
|
||||||
|
-14 19 0 37 32 25 43 -3 4 -6 -1 -6 -10z"/>
|
||||||
|
<path d="M1105 2010 c-3 -5 1 -10 9 -10 9 0 16 5 16 10 0 6 -4 10 -9 10 -6 0
|
||||||
|
-13 -4 -16 -10z"/>
|
||||||
|
<path d="M1160 2005 c0 -17 7 -21 40 -22 22 -1 41 1 44 5 2 4 -12 8 -32 9 -19
|
||||||
|
1 -39 8 -43 15 -6 9 -9 7 -9 -7z"/>
|
||||||
|
<path d="M1896 2008 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/>
|
||||||
|
<path d="M1020 1990 c-11 -11 -22 -20 -25 -19 -3 0 -11 -2 -18 -4 -6 -3 -2 -6
|
||||||
|
10 -6 12 -1 28 6 36 14 9 8 17 11 20 7 2 -4 2 -1 1 6 -3 8 5 11 24 7 22 -4 24
|
||||||
|
-4 7 4 -29 14 -33 13 -55 -9z"/>
|
||||||
|
<path d="M1558 1993 c11 -19 9 -24 -9 -33 -11 -7 -27 -9 -35 -6 -8 3 -13 2 -9
|
||||||
|
-3 8 -14 -17 -19 -43 -9 -17 6 -23 5 -20 -4 9 -26 68 -20 117 11 26 17 50 31
|
||||||
|
54 31 3 0 5 -12 4 -27 -2 -18 -11 -32 -26 -39 -39 -20 -41 -29 -6 -29 18 0 31
|
||||||
|
3 28 8 -2 4 7 7 21 7 16 0 26 6 26 14 0 8 6 17 13 19 6 3 0 6 -15 6 -33 1 -43
|
||||||
|
15 -22 32 14 11 18 11 28 -1 6 -8 22 -15 36 -16 14 -1 34 -4 45 -8 19 -8 19
|
||||||
|
-7 -1 11 -12 10 -24 26 -27 36 -4 10 -12 15 -18 11 -8 -4 -7 -9 2 -15 11 -6
|
||||||
|
10 -9 -3 -14 -9 -4 -27 0 -40 8 -13 8 -36 13 -53 11 -16 -1 -36 2 -44 9 -12
|
||||||
|
10 -13 9 -3 -10z"/>
|
||||||
|
<path d="M1501 1989 c-14 -5 -36 -9 -50 -8 -44 3 -71 -1 -71 -10 0 -6 12 -6
|
||||||
|
29 -1 16 4 32 6 35 3 8 -9 96 7 96 18 0 11 -8 11 -39 -2z"/>
|
||||||
|
<path d="M815 1980 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
|
||||||
|
<path d="M866 1972 c-4 -10 -13 -19 -21 -20 -8 -1 -22 -3 -30 -4 -8 -1 -12 -4
|
||||||
|
-10 -8 2 -4 18 -5 35 -2 18 2 30 0 30 -7 0 -6 -9 -11 -20 -11 -11 0 -20 -5
|
||||||
|
-20 -11 0 -7 8 -9 23 -4 12 4 46 9 75 12 29 3 55 10 59 16 4 6 -7 7 -31 3 -24
|
||||||
|
-5 -36 -3 -36 4 0 6 5 8 10 5 6 -3 10 -1 10 4 0 15 -11 13 -34 -4 -25 -19 -38
|
||||||
|
-10 -30 21 7 28 -2 33 -10 6z"/>
|
||||||
|
<path d="M1300 1980 c-12 -8 -9 -10 13 -10 15 0 27 5 27 10 0 13 -20 13 -40 0z"/>
|
||||||
|
<path d="M1803 1980 c2 -8 10 -14 17 -12 7 1 20 -3 29 -10 9 -7 13 -8 9 -4 -4
|
||||||
|
5 -2 16 5 24 10 13 9 14 -3 7 -20 -12 -43 -12 -53 0 -5 6 -7 4 -4 -5z"/>
|
||||||
|
<path d="M1160 1948 c0 -4 4 -8 9 -8 6 0 12 4 15 8 3 5 -1 9 -9 9 -8 0 -15 -4
|
||||||
|
-15 -9z"/>
|
||||||
|
<path d="M988 1918 c-21 -21 -11 -24 15 -4 10 8 15 16 10 18 -4 3 -16 -3 -25
|
||||||
|
-14z"/>
|
||||||
|
<path d="M1080 1927 c0 -11 -50 -27 -125 -42 -27 -6 -135 -58 -135 -67 0 -10
|
||||||
|
44 3 55 16 13 16 57 32 125 46 30 7 64 17 75 23 28 15 88 20 80 7 -4 -6 6 -10
|
||||||
|
24 -10 16 0 33 5 36 10 4 7 -3 9 -22 5 -21 -5 -24 -4 -13 4 12 9 9 11 -15 12
|
||||||
|
-16 1 -42 3 -57 5 -17 2 -28 -2 -28 -9z"/>
|
||||||
|
<path d="M735 1910 c-26 -28 -15 -31 12 -3 12 13 19 23 14 23 -4 0 -16 -9 -26
|
||||||
|
-20z"/>
|
||||||
|
<path d="M1667 1894 c-28 -15 -36 -26 -14 -21 7 1 11 -7 10 -18 -1 -11 3 -23
|
||||||
|
9 -26 21 -13 -4 -30 -34 -24 -21 5 -28 4 -24 -4 4 -6 12 -9 17 -6 5 4 9 1 9
|
||||||
|
-4 0 -16 16 -13 34 4 8 8 21 15 28 15 9 1 8 4 -3 10 -17 10 -15 59 4 71 21 14
|
||||||
|
-10 16 -36 3z"/>
|
||||||
|
<path d="M1430 1885 c0 -8 2 -15 4 -15 2 0 6 7 10 15 3 8 1 15 -4 15 -6 0 -10
|
||||||
|
-7 -10 -15z"/>
|
||||||
|
<path d="M1485 1890 c-3 -5 3 -10 15 -10 12 0 18 5 15 10 -3 6 -10 10 -15 10
|
||||||
|
-5 0 -12 -4 -15 -10z"/>
|
||||||
|
<path d="M1255 1880 c3 -5 10 -10 16 -10 5 0 9 5 9 10 0 6 -7 10 -16 10 -8 0
|
||||||
|
-12 -4 -9 -10z"/>
|
||||||
|
<path d="M1720 1880 c-12 -8 -10 -10 8 -10 13 0 20 4 17 10 -7 12 -6 12 -25 0z"/>
|
||||||
|
<path d="M685 1870 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M844 1864 c4 -11 1 -14 -12 -10 -13 4 -32 -7 -62 -34 -24 -22 -41
|
||||||
|
-43 -38 -46 3 -3 25 13 48 36 22 22 46 38 51 34 13 -7 41 15 33 27 -9 15 -26
|
||||||
|
10 -20 -7z"/>
|
||||||
|
<path d="M1088 1866 c-26 -20 -22 -26 12 -16 19 6 32 5 36 -2 4 -7 2 -8 -4 -4
|
||||||
|
-7 4 -12 2 -12 -3 0 -16 26 -13 40 4 15 19 13 21 -25 29 -18 3 -37 0 -47 -8z"/>
|
||||||
|
<path d="M1525 1870 c-3 -5 1 -10 10 -10 8 0 24 -6 35 -12 15 -10 22 -10 32 0
|
||||||
|
9 9 8 12 -5 12 -25 0 -30 1 -49 11 -10 6 -19 5 -23 -1z"/>
|
||||||
|
<path d="M1315 1849 c-17 -17 -17 -19 -2 -14 20 8 23 -5 5 -23 -21 -21 8 -14
|
||||||
|
33 8 13 11 20 20 14 19 -24 -3 -34 2 -29 16 8 20 3 18 -21 -6z"/>
|
||||||
|
<path d="M1030 1851 c0 -8 -8 -11 -20 -8 -11 3 -20 1 -20 -4 0 -5 -3 -8 -7 -7
|
||||||
|
-5 2 -19 -5 -33 -15 -30 -21 -22 -22 15 -3 15 8 33 12 40 10 14 -6 41 23 31
|
||||||
|
33 -3 3 -6 1 -6 -6z"/>
|
||||||
|
<path d="M1477 1842 c5 -12 1 -14 -20 -8 -14 3 -29 2 -32 -4 -3 -5 -14 -10
|
||||||
|
-24 -10 -10 0 -25 -8 -32 -17 -13 -16 -12 -17 3 -4 10 7 38 14 62 16 24 2 49
|
||||||
|
6 55 9 16 10 14 1 -9 -36 -11 -18 -20 -29 -20 -24 0 5 -6 4 -12 -1 -7 -6 -23
|
||||||
|
-13 -35 -16 -18 -4 -23 -1 -23 15 0 10 -5 16 -10 13 -6 -4 -8 -10 -5 -15 3 -4
|
||||||
|
-6 -10 -18 -14 -13 -3 -36 -17 -52 -30 -16 -14 -31 -24 -34 -24 -10 2 -81 -32
|
||||||
|
-81 -38 0 -3 12 -3 26 0 17 5 23 3 19 -4 -3 -5 -2 -10 4 -10 6 0 15 -15 20
|
||||||
|
-32 9 -30 10 -31 16 -11 7 22 31 37 45 28 4 -3 10 2 14 10 3 8 13 15 23 15 14
|
||||||
|
0 15 2 4 9 -11 7 -6 13 18 26 22 11 31 21 27 31 -3 9 -2 12 5 9 5 -4 27 6 47
|
||||||
|
22 20 15 38 26 39 23 2 -3 10 6 20 19 12 18 13 26 5 29 -7 2 -10 7 -8 11 2 3
|
||||||
|
-6 11 -19 17 -19 10 -22 9 -18 -4z m-87 -111 c0 -13 -82 -77 -107 -83 -13 -4
|
||||||
|
-23 -5 -23 -4 0 12 61 67 69 62 5 -4 15 3 21 14 11 20 40 28 40 11z"/>
|
||||||
|
<path d="M930 1835 c-14 -8 -20 -14 -14 -15 5 0 19 7 30 15 24 18 16 19 -16 0z"/>
|
||||||
|
<path d="M1220 1834 c0 -8 5 -12 10 -9 6 3 10 10 10 16 0 5 -4 9 -10 9 -5 0
|
||||||
|
-10 -7 -10 -16z"/>
|
||||||
|
<path d="M676 1809 c-21 -11 -41 -29 -43 -40 -3 -10 -9 -17 -12 -14 -4 2 -8
|
||||||
|
-3 -8 -12 0 -10 12 -2 32 21 17 20 35 36 41 36 6 0 19 7 30 15 28 21 5 18 -40
|
||||||
|
-6z"/>
|
||||||
|
<path d="M1085 1817 c-22 -13 -31 -37 -15 -37 6 0 10 5 10 11 0 7 8 18 18 25
|
||||||
|
21 17 14 17 -13 1z"/>
|
||||||
|
<path d="M1233 1818 c-13 -6 -23 -15 -23 -20 0 -4 -4 -8 -10 -8 -5 0 -7 7 -4
|
||||||
|
15 5 13 -13 15 -76 5 -3 0 1 -4 8 -9 12 -7 12 -12 1 -30 -8 -11 -18 -20 -24
|
||||||
|
-19 -5 1 -28 -8 -50 -20 -26 -15 -31 -20 -15 -16 14 3 29 10 35 15 5 5 17 9
|
||||||
|
27 9 9 0 21 6 25 14 9 16 43 24 58 14 6 -3 28 9 50 28 40 34 39 42 -2 22z"/>
|
||||||
|
<path d="M1285 1810 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
|
||||||
|
-7 -4 -4 -10z"/>
|
||||||
|
<path d="M955 1774 c-71 -40 -62 -44 21 -9 32 13 53 24 46 25 -7 0 -10 5 -7
|
||||||
|
10 8 12 11 14 -60 -26z"/>
|
||||||
|
<path d="M804 1785 c-15 -11 -16 -14 -3 -15 8 0 24 7 35 15 15 11 16 14 3 15
|
||||||
|
-8 0 -24 -7 -35 -15z"/>
|
||||||
|
<path d="M1028 1779 c-14 -15 -15 -19 -4 -19 8 0 16 -6 19 -12 2 -7 4 2 5 20
|
||||||
|
0 17 0 32 -1 31 -1 0 -10 -9 -19 -20z"/>
|
||||||
|
<path d="M885 1780 c3 -6 -4 -14 -17 -19 -26 -10 -74 -56 -90 -86 -7 -12 5 -5
|
||||||
|
30 18 23 20 52 37 65 38 13 0 16 3 8 6 -11 4 -9 10 9 23 20 15 20 18 6 23 -10
|
||||||
|
4 -15 3 -11 -3z"/>
|
||||||
|
<path d="M1270 1775 c-7 -8 -18 -15 -24 -14 -24 3 -36 -2 -61 -25 -18 -17 -22
|
||||||
|
-26 -13 -29 7 -3 0 -6 -17 -6 -16 -1 -41 -4 -54 -8 -16 -4 -22 -3 -17 5 4 8
|
||||||
|
-3 8 -26 1 -35 -10 -89 -59 -65 -59 8 0 22 10 31 23 10 14 14 16 10 5 -7 -20
|
||||||
|
20 -26 31 -8 3 6 11 10 16 10 6 0 7 -5 3 -12 -5 -8 2 -9 26 -5 27 6 30 5 21
|
||||||
|
-7 -6 -7 -39 -19 -74 -25 -98 -19 -136 -38 -108 -55 5 -3 20 3 34 14 26 21 57
|
||||||
|
27 57 11 0 -10 -61 -41 -80 -41 -22 0 -80 -41 -80 -56 0 -12 4 -12 20 -2 11 7
|
||||||
|
20 18 20 25 0 14 2 15 40 19 14 1 36 6 50 10 24 7 24 7 8 -13 -10 -11 -18 -22
|
||||||
|
-18 -26 0 -3 8 -4 18 0 9 3 0 -11 -20 -33 -21 -22 -38 -45 -38 -52 0 -6 -9
|
||||||
|
-16 -20 -22 -11 -6 -18 -15 -15 -21 4 -5 -1 -9 -9 -9 -8 0 -17 4 -21 10 -3 5
|
||||||
|
-12 7 -20 3 -24 -9 -17 10 20 49 19 21 49 42 67 47 33 9 33 9 5 10 -19 1 -26
|
||||||
|
5 -21 13 5 7 3 8 -5 4 -7 -5 -10 -10 -8 -13 3 -2 -13 -22 -36 -43 -22 -22 -41
|
||||||
|
-50 -42 -62 -1 -13 -7 -22 -13 -21 -5 1 -15 -4 -21 -11 -10 -12 -6 -13 26 -5
|
||||||
|
56 13 73 11 67 -6 -4 -8 1 -17 10 -21 29 -11 13 -89 -32 -151 -16 -23 -19 -31
|
||||||
|
-8 -27 14 5 53 78 70 127 4 11 11 15 18 11 9 -5 8 -2 -1 10 -8 9 -10 16 -5 14
|
||||||
|
5 -1 21 10 37 25 15 16 32 25 39 21 7 -4 8 -3 4 4 -4 7 -3 12 2 12 10 0 35 50
|
||||||
|
28 57 -2 2 -7 -5 -11 -16 -3 -12 -19 -28 -34 -35 -15 -8 -33 -22 -40 -30 -19
|
||||||
|
-23 -34 -20 -26 5 5 15 4 19 -4 15 -25 -16 -9 27 25 64 37 41 70 51 80 25 4
|
||||||
|
-8 15 -15 26 -15 10 0 16 5 13 10 -4 6 -11 7 -17 4 -7 -4 -8 -2 -4 5 4 6 10 9
|
||||||
|
14 7 4 -2 23 4 42 15 19 10 47 24 63 31 15 7 27 16 27 20 0 8 -66 -19 -90 -38
|
||||||
|
-35 -26 -44 -28 -35 -8 4 10 7 27 7 37 0 9 3 15 9 12 5 -4 12 1 15 9 3 9 10
|
||||||
|
16 16 16 5 0 6 -4 3 -10 -8 -13 -1 -13 40 4 18 8 39 24 45 36 l11 21 -41 -21
|
||||||
|
c-57 -29 -73 -20 -25 13 24 16 33 27 23 27 -26 0 -68 -22 -68 -35 0 -7 -20
|
||||||
|
-29 -45 -49 -25 -21 -45 -45 -46 -54 0 -13 -3 -11 -9 5 -10 24 1 76 21 99 7 8
|
||||||
|
21 11 35 8 13 -3 24 -2 24 2 0 15 57 74 64 67 3 -3 6 0 6 8 0 19 23 69 32 69
|
||||||
|
3 0 4 -9 1 -20 -3 -11 0 -20 6 -20 6 0 11 4 11 10 0 5 7 7 15 4 8 -4 15 -1 15
|
||||||
|
5 0 6 6 11 13 11 6 0 21 8 32 16 20 15 19 16 -5 12 -16 -2 -24 2 -22 10 3 16
|
||||||
|
-3 15 -18 -3z m-200 -89 c0 -3 -4 -8 -10 -11 -5 -3 -10 -1 -10 4 0 6 5 11 10
|
||||||
|
11 6 0 10 -2 10 -4z m30 -186 c-6 -11 -18 -20 -27 -20 -14 0 -14 2 2 20 10 11
|
||||||
|
22 20 27 20 5 0 4 -9 -2 -20z"/>
|
||||||
|
<path d="M1542 1778 c-16 -16 -15 -25 3 -25 16 0 28 24 16 31 -4 3 -13 0 -19
|
||||||
|
-6z"/>
|
||||||
|
<path d="M683 1768 c-10 -18 -13 -39 -3 -33 8 5 21 45 15 45 -2 0 -7 -6 -12
|
||||||
|
-12z"/>
|
||||||
|
<path d="M950 1740 c-8 -5 -12 -11 -9 -14 2 -3 11 -1 19 4 8 5 12 11 9 14 -2
|
||||||
|
3 -11 1 -19 -4z"/>
|
||||||
|
<path d="M749 1713 l-24 -28 28 24 c25 23 32 31 24 31 -2 0 -14 -12 -28 -27z"/>
|
||||||
|
<path d="M1656 1725 c-4 -13 -2 -14 14 -5 23 13 26 20 6 20 -8 0 -16 -7 -20
|
||||||
|
-15z"/>
|
||||||
|
<path d="M975 1715 c-16 -7 -34 -11 -39 -7 -5 3 -6 0 -2 -6 4 -6 -2 -17 -14
|
||||||
|
-24 -11 -7 -20 -17 -20 -22 0 -5 11 1 25 14 14 13 28 20 32 15 5 -4 2 -10 -4
|
||||||
|
-12 -7 -3 -13 -10 -13 -16 0 -6 10 -1 23 11 13 12 30 22 38 22 12 0 12 2 0 9
|
||||||
|
-11 7 -11 9 0 13 8 3 12 7 9 10 -3 3 -18 -1 -35 -7z"/>
|
||||||
|
<path d="M1485 1699 c-4 -17 -9 -28 -12 -25 -3 3 -17 -4 -31 -14 -27 -22 -54
|
||||||
|
-28 -34 -8 7 7 12 16 12 21 0 5 -29 -20 -64 -56 -45 -45 -62 -58 -60 -43 2 12
|
||||||
|
-5 2 -16 -21 -12 -26 -31 -46 -50 -55 -17 -7 -30 -18 -30 -23 0 -11 -69 -52
|
||||||
|
-82 -48 -4 2 -5 -3 -2 -10 3 -8 0 -25 -5 -40 -7 -18 -7 -27 2 -30 7 -2 8 -8 4
|
||||||
|
-13 -13 -13 -27 18 -21 46 5 22 3 22 -17 -8 -13 -19 -27 -31 -35 -28 -8 3 -14
|
||||||
|
-1 -14 -9 0 -7 -6 -20 -13 -27 -27 -27 -67 -85 -67 -96 0 -7 5 -12 10 -12 6 0
|
||||||
|
10 4 10 10 0 5 16 32 36 60 29 40 42 50 65 50 40 0 37 -23 -6 -49 -20 -11 -38
|
||||||
|
-29 -42 -38 -3 -10 -15 -30 -25 -45 -16 -23 -17 -28 -4 -28 8 0 16 -8 19 -18
|
||||||
|
2 -9 4 0 3 21 0 39 17 61 41 52 7 -2 10 2 7 9 -5 12 63 92 71 84 4 -4 12 -40
|
||||||
|
14 -62 1 -4 7 -6 15 -3 24 9 39 -19 32 -58 -4 -19 -10 -35 -15 -35 -4 0 -8 -6
|
||||||
|
-9 -12 -1 -7 -4 -26 -7 -43 -5 -27 -3 -26 14 10 11 22 20 48 20 59 1 14 4 16
|
||||||
|
15 7 19 -16 5 -82 -29 -131 -16 -23 -32 -43 -37 -46 -4 -3 -8 -14 -8 -25 0
|
||||||
|
-29 22 -18 31 15 8 29 36 66 50 66 15 0 15 -35 0 -53 -18 -22 -22 -118 -4
|
||||||
|
-124 10 -4 10 37 -1 72 -2 6 1 19 6 30 8 18 8 18 4 -2 -2 -13 0 -23 6 -23 14
|
||||||
|
0 31 20 61 71 17 29 34 46 45 45 18 -1 19 2 21 64 1 28 5 40 16 40 15 0 19 15
|
||||||
|
16 57 -1 11 2 24 7 27 5 3 7 17 5 31 -3 13 -9 22 -14 20 -4 -3 -6 -12 -3 -20
|
||||||
|
3 -8 -1 -21 -9 -29 -8 -8 -12 -25 -10 -39 3 -14 -2 -29 -12 -37 -15 -12 -16
|
||||||
|
-8 -14 53 1 36 6 64 11 61 5 -3 6 6 2 20 -4 16 -2 26 4 26 6 0 18 15 27 33 12
|
||||||
|
25 13 43 6 73 l-9 39 -1 -40 c-2 -56 -11 -85 -28 -85 -8 0 -12 -4 -9 -9 3 -5
|
||||||
|
-12 -24 -33 -42 -22 -17 -42 -40 -44 -51 -3 -10 -11 -18 -17 -18 -8 0 -10 7
|
||||||
|
-5 23 4 12 8 36 9 53 1 17 10 43 20 59 18 27 20 28 27 10 6 -17 8 -15 8 8 1
|
||||||
|
16 7 43 15 60 45 106 49 131 6 42 -16 -33 -30 -53 -30 -45 0 8 -12 -9 -27 -38
|
||||||
|
-15 -29 -30 -52 -34 -52 -5 0 -6 7 -3 15 4 8 2 15 -4 15 -11 0 -22 -23 -22
|
||||||
|
-46 0 -8 3 -13 8 -13 20 3 32 -2 26 -12 -3 -6 -8 -32 -10 -58 -2 -37 0 -48 11
|
||||||
|
-48 8 0 15 5 15 11 0 15 48 66 61 66 7 0 9 -16 6 -42 -3 -22 -1 -55 4 -72 7
|
||||||
|
-24 6 -34 -7 -43 -10 -7 -11 -13 -5 -18 6 -3 11 -1 11 4 0 6 4 11 8 11 5 0 9
|
||||||
|
-7 9 -15 0 -8 -6 -15 -13 -15 -8 0 -21 -14 -30 -32 -9 -18 -23 -38 -30 -46 -8
|
||||||
|
-7 -14 -20 -14 -28 0 -8 -4 -14 -10 -14 -12 0 -12 2 -1 33 8 19 6 27 -5 31 -8
|
||||||
|
3 -14 10 -14 16 0 6 5 8 11 4 9 -5 10 1 5 20 -4 15 -2 26 4 26 6 0 6 9 -1 26
|
||||||
|
-5 15 -7 36 -4 48 5 15 4 17 -3 7 -10 -14 -17 -2 -27 51 -3 12 -9 24 -15 26
|
||||||
|
-5 2 -10 13 -10 25 -1 22 -1 22 -17 2 -15 -19 -15 -18 -9 14 3 19 17 49 31 67
|
||||||
|
27 36 30 44 15 44 -5 0 -10 -7 -10 -15 0 -8 -6 -15 -14 -15 -8 0 -16 -10 -18
|
||||||
|
-22 -5 -36 -33 -28 -36 11 -2 21 0 31 7 26 6 -3 11 -1 11 4 0 6 6 11 13 11 7
|
||||||
|
0 32 16 55 36 23 19 42 32 42 29 0 -4 6 -2 14 4 11 10 15 8 19 -6 3 -10 5 -3
|
||||||
|
6 15 2 61 111 137 111 77 0 -7 -4 -15 -8 -17 -9 -4 -52 -63 -52 -72 0 -19 23
|
||||||
|
-1 31 24 6 16 15 28 19 25 5 -3 15 14 22 37 16 47 36 55 54 21 6 -13 14 -20
|
||||||
|
17 -16 4 3 7 -3 7 -15 0 -14 -5 -20 -13 -16 -14 5 -45 -69 -39 -93 2 -7 -2
|
||||||
|
-13 -8 -13 -5 0 -10 6 -11 13 -4 58 -18 92 -20 46 0 -13 -2 -34 -3 -46 -4 -31
|
||||||
|
11 -53 23 -34 5 7 14 10 20 7 7 -5 11 2 11 18 0 31 18 73 45 102 11 12 26 30
|
||||||
|
34 39 8 10 23 15 37 12 17 -3 28 3 41 22 14 22 14 24 1 13 -9 -7 -25 -15 -37
|
||||||
|
-17 -18 -3 -20 -1 -11 15 19 35 10 34 -25 -4 -19 -21 -39 -35 -46 -31 -7 5 2
|
||||||
|
20 25 44 26 27 33 40 26 51 -7 10 -10 11 -10 3 0 -7 -11 -13 -25 -13 -27 0
|
||||||
|
-34 17 -10 26 8 4 12 10 9 15 -10 17 -22 9 -29 -22z m19 -59 c-15 -16 -31 -27
|
||||||
|
-36 -24 -12 8 31 54 49 54 8 0 3 -12 -13 -30z m-324 -374 c0 -3 -4 -8 -10 -11
|
||||||
|
-5 -3 -10 -1 -10 4 0 6 5 11 10 11 6 0 10 -2 10 -4z"/>
|
||||||
|
<path d="M906 1707 c3 -10 9 -15 12 -12 3 3 0 11 -7 18 -10 9 -11 8 -5 -6z"/>
|
||||||
|
<path d="M1438 1713 c-20 -5 -24 -23 -5 -23 6 0 21 7 31 15 20 15 11 18 -26 8z"/>
|
||||||
|
<path d="M620 1689 c-22 -18 -22 -19 -3 -10 12 6 25 16 28 21 9 15 3 12 -25
|
||||||
|
-11z"/>
|
||||||
|
<path d="M833 1685 l-28 -26 38 22 c37 21 45 29 28 29 -6 0 -23 -11 -38 -25z"/>
|
||||||
|
<path d="M712 1645 c-20 -19 -32 -35 -26 -35 5 0 25 16 44 35 44 45 31 45 -18
|
||||||
|
0z"/>
|
||||||
|
<path d="M870 1670 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/>
|
||||||
|
<path d="M1645 1640 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M950 1626 c0 -8 -6 -17 -12 -19 -10 -4 -10 -6 -1 -6 7 -1 17 8 23 19
|
||||||
|
7 14 7 20 0 20 -5 0 -10 -6 -10 -14z"/>
|
||||||
|
<path d="M719 1613 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z"/>
|
||||||
|
<path d="M774 1621 c3 -5 -12 -25 -34 -45 -22 -20 -40 -38 -40 -41 0 -13 20
|
||||||
|
-2 53 30 36 35 48 65 26 65 -5 0 -8 -4 -5 -9z"/>
|
||||||
|
<path d="M842 1606 c-15 -17 -19 -25 -10 -28 8 -3 2 -16 -19 -36 -17 -18 -28
|
||||||
|
-32 -23 -32 5 0 12 5 15 10 4 6 10 8 15 5 11 -7 23 13 26 43 2 13 11 33 20 43
|
||||||
|
25 28 3 23 -24 -5z"/>
|
||||||
|
<path d="M555 1609 c-11 -17 1 -21 15 -4 8 9 8 15 2 15 -6 0 -14 -5 -17 -11z"/>
|
||||||
|
<path d="M900 1610 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0
|
||||||
|
-4 -4 -4 -10z"/>
|
||||||
|
<path d="M1627 1579 c-21 -22 -37 -47 -37 -56 0 -8 -9 -26 -20 -40 -15 -19
|
||||||
|
-17 -30 -10 -44 10 -17 5 -60 -8 -84 -10 -16 -11 -55 -2 -55 4 0 7 10 6 23 -2
|
||||||
|
12 3 28 10 35 10 9 14 -4 18 -66 2 -42 8 -96 11 -118 4 -26 3 -44 -4 -48 -6
|
||||||
|
-4 -11 -12 -11 -19 0 -6 11 0 25 15 25 27 33 44 15 33 -5 -3 -7 9 -5 30 4 26
|
||||||
|
1 35 -10 35 -8 0 -15 8 -14 18 0 10 3 12 6 5 10 -26 25 -13 20 17 -4 16 0 48
|
||||||
|
8 71 20 56 19 60 -6 20 -25 -42 -37 -34 -14 11 22 42 36 98 24 98 -8 0 -13
|
||||||
|
-22 -10 -42 0 -5 -3 -8 -9 -8 -5 0 -10 -7 -10 -15 0 -8 -4 -15 -10 -15 -13 0
|
||||||
|
-13 45 1 93 7 27 15 37 25 33 17 -6 18 -1 4 24 -7 13 -7 21 2 26 7 4 8 3 4 -4
|
||||||
|
-4 -7 -5 -12 -2 -12 3 0 15 18 26 40 11 22 19 40 17 40 -2 0 -20 -18 -40 -41z"/>
|
||||||
|
<path d="M1696 1594 c-11 -7 -16 -17 -13 -21 4 -4 15 1 24 10 22 21 14 30 -11
|
||||||
|
11z"/>
|
||||||
|
<path d="M1442 1580 c-9 -14 -9 -20 -1 -20 8 0 7 -6 -2 -17 -11 -15 -11 -16 1
|
||||||
|
-6 7 7 14 24 16 38 3 30 2 31 -14 5z"/>
|
||||||
|
<path d="M652 1561 c-17 -17 -32 -33 -32 -37 0 -5 17 8 37 27 21 18 40 35 42
|
||||||
|
37 2 1 0 2 -5 2 -5 0 -24 -13 -42 -29z"/>
|
||||||
|
<path d="M1765 1581 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/>
|
||||||
|
<path d="M488 1573 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||||
|
<path d="M1060 1565 c-7 -9 -8 -15 -2 -15 5 0 12 7 16 15 3 8 4 15 2 15 -2 0
|
||||||
|
-9 -7 -16 -15z"/>
|
||||||
|
<path d="M1722 1568 c-18 -18 -15 -38 5 -31 11 4 13 8 5 11 -10 3 -10 7 -1 18
|
||||||
|
14 17 8 19 -9 2z"/>
|
||||||
|
<path d="M784 1561 c7 -11 -28 -43 -38 -34 -4 4 -4 0 0 -10 3 -9 2 -17 -3 -17
|
||||||
|
-5 0 -18 -8 -29 -18 -10 -10 -27 -21 -36 -25 -18 -6 -25 -27 -10 -27 12 0 132
|
||||||
|
118 132 130 0 5 -5 10 -11 10 -5 0 -8 -4 -5 -9z"/>
|
||||||
|
<path d="M528 1553 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||||
|
<path d="M1544 1524 c3 -9 6 -18 6 -20 0 -3 4 -3 10 1 11 7 2 35 -12 35 -6 0
|
||||||
|
-7 -7 -4 -16z"/>
|
||||||
|
<path d="M1650 1510 c-7 -14 -7 -20 0 -20 5 0 10 6 10 14 0 8 6 17 13 19 9 4
|
||||||
|
9 6 0 6 -7 1 -17 -8 -23 -19z"/>
|
||||||
|
<path d="M820 1490 c-14 -10 -22 -21 -19 -24 3 -3 16 5 29 19 28 30 24 32 -10
|
||||||
|
5z"/>
|
||||||
|
<path d="M1509 1503 c-2 -29 -11 -58 -20 -70 -9 -10 -8 -13 4 -13 13 0 14 -6
|
||||||
|
6 -36 -6 -19 -7 -48 -4 -64 3 -15 2 -31 -4 -34 -6 -4 -7 -14 -4 -22 5 -13 1
|
||||||
|
-15 -18 -9 -20 7 -22 5 -13 -6 7 -8 14 -33 18 -56 4 -34 3 -41 -9 -36 -17 6
|
||||||
|
-20 -14 -5 -37 8 -12 10 -12 10 5 2 44 21 -30 25 -93 1 -35 6 -66 9 -69 3 -4
|
||||||
|
6 0 6 8 1 8 7 -2 14 -21 8 -19 20 -49 27 -66 11 -28 9 -39 -13 -101 -21 -56
|
||||||
|
-23 -70 -12 -70 8 0 13 6 10 13 -3 8 5 20 17 29 20 14 21 15 2 16 -19 0 -19 1
|
||||||
|
-2 8 10 4 18 10 18 14 -1 4 -1 19 -1 33 0 22 2 25 15 14 8 -7 15 -18 15 -24 0
|
||||||
|
-6 9 -17 21 -24 19 -12 20 -12 13 10 -4 13 -8 42 -9 66 -2 24 -6 46 -9 49 -4
|
||||||
|
4 -6 -6 -5 -23 4 -74 -17 -69 -27 7 -4 27 -10 52 -14 54 -5 3 -6 -4 -3 -15 10
|
||||||
|
-37 -8 -20 -29 28 -34 76 -62 282 -39 282 6 0 11 10 11 23 0 13 3 27 6 30 11
|
||||||
|
10 13 67 3 67 -11 0 -6 45 10 83 9 23 8 31 -4 43 -8 9 -15 12 -16 7z"/>
|
||||||
|
<path d="M1665 1471 c-12 -40 -8 -102 9 -119 16 -16 16 -15 7 11 -8 21 -8 27
|
||||||
|
2 26 23 -3 40 1 34 8 -4 3 -2 12 4 20 6 7 8 13 3 13 -5 0 -2 5 6 10 11 7 11
|
||||||
|
10 2 10 -7 0 -18 -13 -23 -30 -14 -38 -30 -29 -25 14 5 41 -9 69 -19 37z"/>
|
||||||
|
<path d="M532 1467 c-17 -20 -12 -40 6 -28 8 5 12 16 10 26 -3 17 -4 17 -16 2z"/>
|
||||||
|
<path d="M638 1473 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
|
||||||
|
<path d="M761 1466 c-13 -7 -19 -16 -14 -19 6 -4 18 2 28 13 21 23 19 24 -14
|
||||||
|
6z"/>
|
||||||
|
<path d="M482 1450 c-29 -28 -28 -34 3 -14 14 9 25 20 25 25 0 13 -4 11 -28
|
||||||
|
-11z"/>
|
||||||
|
<path d="M588 1449 l-23 -20 25 13 c14 6 31 11 38 10 17 -4 15 5 -3 11 -7 3
|
||||||
|
-24 -3 -37 -14z"/>
|
||||||
|
<path d="M701 1430 c-13 -11 -18 -20 -13 -20 6 0 14 5 17 10 4 6 10 8 14 6 4
|
||||||
|
-3 8 1 8 9 0 8 -1 15 -1 15 -1 -1 -12 -10 -25 -20z"/>
|
||||||
|
<path d="M815 1440 c-3 -5 3 -10 14 -10 12 0 21 5 21 10 0 6 -6 10 -14 10 -8
|
||||||
|
0 -18 -4 -21 -10z"/>
|
||||||
|
<path d="M913 1428 c-4 -7 -8 -20 -10 -28 -1 -10 3 -9 13 4 14 19 18 36 10 36
|
||||||
|
-3 0 -8 -6 -13 -12z"/>
|
||||||
|
<path d="M1026 1423 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z"/>
|
||||||
|
<path d="M1335 1420 c-5 -14 -2 -28 8 -37 13 -13 14 -11 11 18 -5 41 -10 46
|
||||||
|
-19 19z"/>
|
||||||
|
<path d="M778 1423 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||||
|
<path d="M545 1400 c-3 -10 -19 -30 -36 -44 -16 -14 -28 -29 -27 -33 2 -5 -7
|
||||||
|
-21 -18 -36 -16 -19 -27 -25 -40 -21 -17 6 -17 5 0 -8 19 -15 19 -16 -10 -48
|
||||||
|
-16 -17 -40 -36 -54 -42 -22 -9 -23 -11 -7 -17 16 -6 15 -9 -9 -31 -15 -14
|
||||||
|
-24 -20 -21 -13 4 6 3 23 -3 38 -8 20 -7 27 4 31 16 6 40 64 27 64 -4 0 -13
|
||||||
|
-11 -19 -24 -6 -13 -18 -23 -28 -23 -15 2 -17 -6 -15 -49 1 -33 -2 -55 -10
|
||||||
|
-60 -9 -5 -7 -9 7 -15 15 -6 35 10 102 79 45 47 81 90 79 96 -6 15 50 84 74
|
||||||
|
91 10 4 17 11 13 16 -4 6 -11 6 -20 -2 -8 -7 -14 -7 -14 -1 0 5 7 15 16 22 21
|
||||||
|
18 37 50 25 50 -5 0 -12 -9 -16 -20z"/>
|
||||||
|
<path d="M609 1400 c-21 -17 -21 -19 -4 -13 11 3 29 12 40 20 16 10 16 13 3
|
||||||
|
13 -9 0 -26 -9 -39 -20z"/>
|
||||||
|
<path d="M785 1400 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
|
||||||
|
<path d="M1429 1403 c0 -5 0 -35 1 -68 1 -33 -4 -73 -10 -89 -11 -30 0 -56 24
|
||||||
|
-56 8 0 8 2 0 8 -6 4 -10 18 -8 32 1 14 3 31 3 39 1 7 7 11 14 8 10 -4 12 7 9
|
||||||
|
46 -2 29 -9 55 -16 59 -8 6 -7 8 2 8 6 0 12 5 12 10 0 11 -30 14 -31 3z"/>
|
||||||
|
<path d="M469 1387 c5 -5 17 -5 28 2 16 9 15 10 -9 9 -16 -2 -24 -6 -19 -11z"/>
|
||||||
|
<path d="M620 1379 c-26 -14 -30 -19 -15 -19 11 0 34 9 50 20 38 25 12 24 -35
|
||||||
|
-1z"/>
|
||||||
|
<path d="M710 1375 c-30 -17 -43 -19 -35 -5 3 6 1 10 -5 10 -7 0 -10 -6 -7
|
||||||
|
-12 5 -14 56 -12 71 3 17 15 2 18 -24 4z"/>
|
||||||
|
<path d="M733 1347 c-27 -29 -27 -29 -4 -17 14 7 33 20 43 30 14 13 15 17 3
|
||||||
|
17 -8 0 -27 -14 -42 -30z"/>
|
||||||
|
<path d="M1402 1345 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z"/>
|
||||||
|
<path d="M1300 1340 c0 -11 4 -20 8 -20 4 0 8 9 9 20 1 11 -2 20 -8 20 -5 0
|
||||||
|
-9 -9 -9 -20z"/>
|
||||||
|
<path d="M1270 1335 c0 -8 2 -15 4 -15 2 0 6 7 10 15 3 8 1 15 -4 15 -6 0 -10
|
||||||
|
-7 -10 -15z"/>
|
||||||
|
<path d="M574 1314 c-5 -21 -11 -25 -30 -21 -19 3 -22 2 -13 -9 9 -11 7 -15
|
||||||
|
-5 -20 -9 -3 -14 -10 -11 -15 3 -4 -7 -15 -22 -24 -15 -9 -19 -13 -8 -10 11 4
|
||||||
|
28 10 37 12 10 3 15 10 11 16 -4 6 0 8 10 4 10 -3 17 -2 17 3 0 5 10 19 23 31
|
||||||
|
12 12 15 18 7 14 -12 -6 -13 -5 -2 6 14 16 16 39 2 39 -5 0 -13 -12 -16 -26z"/>
|
||||||
|
<path d="M642 1310 c-63 -46 -53 -49 27 -9 20 10 30 18 23 19 -7 0 -10 5 -7
|
||||||
|
10 10 16 0 12 -43 -20z"/>
|
||||||
|
<path d="M410 1314 c0 -8 5 -12 10 -9 6 4 8 11 5 16 -9 14 -15 11 -15 -7z"/>
|
||||||
|
<path d="M803 1313 c-7 -2 -13 -11 -13 -19 0 -8 -7 -14 -16 -14 -10 0 -14 -6
|
||||||
|
-11 -14 8 -20 -16 -26 -29 -8 -10 14 -12 13 -18 -4 -3 -11 -20 -28 -37 -38
|
||||||
|
-17 -10 -28 -20 -25 -23 3 -3 16 1 28 9 13 8 32 18 42 21 15 6 17 5 8 -6 -7
|
||||||
|
-8 -9 -16 -6 -20 3 -3 -7 -13 -23 -23 -15 -10 -30 -19 -32 -20 -1 -2 0 -8 4
|
||||||
|
-14 9 -14 41 19 81 82 16 27 35 48 42 48 7 0 10 5 7 10 -4 6 -1 9 5 8 7 -2 14
|
||||||
|
5 15 15 3 17 -1 19 -22 10z"/>
|
||||||
|
<path d="M900 1309 c0 -19 -52 -129 -61 -129 -5 0 -9 -4 -9 -10 0 -20 28 3 49
|
||||||
|
41 29 51 42 97 30 104 -5 4 -9 1 -9 -6z"/>
|
||||||
|
<path d="M1663 1285 c-7 -21 -8 -46 -3 -63 l8 -27 2 30 c1 17 4 45 6 63 5 42
|
||||||
|
0 41 -13 -3z"/>
|
||||||
|
<path d="M1710 1301 c0 -5 5 -13 10 -16 6 -3 10 -2 10 4 0 5 -4 13 -10 16 -5
|
||||||
|
3 -10 2 -10 -4z"/>
|
||||||
|
<path d="M735 1280 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7
|
||||||
|
-4 -4 -10z"/>
|
||||||
|
<path d="M655 1264 c-11 -8 -25 -12 -33 -9 -7 2 -10 0 -7 -5 3 -6 -2 -10 -12
|
||||||
|
-10 -10 -1 -29 -8 -43 -16 -17 -11 -19 -13 -5 -9 25 9 46 13 65 15 17 1 81 50
|
||||||
|
65 50 -5 -1 -19 -7 -30 -16z"/>
|
||||||
|
<path d="M1696 1265 c-3 -8 -1 -15 4 -15 6 0 10 7 10 15 0 8 -2 15 -4 15 -2 0
|
||||||
|
-6 -7 -10 -15z"/>
|
||||||
|
<path d="M842 1239 c-7 -11 -27 -26 -44 -33 -24 -10 -27 -14 -14 -19 21 -8 63
|
||||||
|
18 72 45 8 27 1 31 -14 7z"/>
|
||||||
|
<path d="M1534 1252 c4 -7 2 -12 -5 -12 -8 0 -10 -12 -5 -37 11 -68 17 -97 21
|
||||||
|
-101 2 -2 -1 -15 -6 -29 -6 -16 -6 -31 1 -41 8 -15 10 -15 10 3 1 11 3 29 5
|
||||||
|
40 14 84 13 115 -2 115 -11 0 -14 8 -11 30 3 17 0 34 -5 37 -6 3 -7 1 -3 -5z"/>
|
||||||
|
<path d="M806 1235 c-3 -8 -1 -15 3 -15 5 0 11 7 15 15 3 8 1 15 -3 15 -5 0
|
||||||
|
-11 -7 -15 -15z"/>
|
||||||
|
<path d="M1296 1223 c-8 -25 -6 -39 6 -35 6 2 8 -8 6 -23 -3 -14 -1 -25 4 -25
|
||||||
|
9 0 5 86 -5 96 -3 3 -8 -3 -11 -13z"/>
|
||||||
|
<path d="M1693 1225 c0 -8 4 -15 9 -15 4 0 8 7 8 15 0 8 -4 15 -8 15 -5 0 -9
|
||||||
|
-7 -9 -15z"/>
|
||||||
|
<path d="M1730 1230 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
|
||||||
|
-10 -4 -10 -10z"/>
|
||||||
|
<path d="M1151 1207 c-16 -41 -13 -57 5 -24 8 16 13 34 11 40 -3 7 -10 -1 -16
|
||||||
|
-16z"/>
|
||||||
|
<path d="M1040 1194 c0 -8 5 -12 10 -9 6 4 8 11 5 16 -9 14 -15 11 -15 -7z"/>
|
||||||
|
<path d="M1102 1189 c3 -8 -3 -21 -12 -29 -9 -8 -22 -30 -30 -48 -8 -21 -16
|
||||||
|
-29 -20 -22 -5 9 -9 9 -14 1 -4 -6 -4 -15 -1 -21 3 -5 1 -10 -6 -10 -10 0 -10
|
||||||
|
-3 -2 -11 13 -13 36 -3 28 12 -4 5 -1 9 4 9 6 0 11 6 11 14 0 8 3 16 8 18 4 2
|
||||||
|
19 19 33 38 23 31 25 37 11 49 -13 12 -14 12 -10 0z"/>
|
||||||
|
<path d="M480 1181 c0 -7 -7 -20 -16 -29 -18 -17 -48 -26 -38 -11 3 5 1 9 -4
|
||||||
|
9 -8 0 -23 -22 -26 -40 -1 -3 -13 -22 -28 -43 -16 -25 -28 -34 -33 -27 -4 6 1
|
||||||
|
21 11 34 11 12 8 11 -6 -4 -21 -21 -35 -25 -85 -26 -86 -2 -123 -8 -137 -22
|
||||||
|
-6 -6 -16 -12 -21 -12 -4 0 -28 -14 -53 -30 -29 -21 -44 -38 -44 -52 0 -20 1
|
||||||
|
-20 13 -5 6 9 28 27 47 41 19 14 45 33 57 41 17 13 26 14 42 5 14 -7 21 -7 21
|
||||||
|
0 0 5 9 10 19 10 11 0 23 5 26 10 13 20 53 10 79 -21 31 -37 33 -69 6 -94 -13
|
||||||
|
-12 -18 -25 -14 -38 4 -13 2 -17 -5 -13 -6 4 -11 1 -11 -7 0 -19 -28 -42 -38
|
||||||
|
-32 -4 4 -1 10 6 12 6 3 12 9 12 15 0 16 -28 -11 -43 -40 -14 -29 -63 -69 -73
|
||||||
|
-59 -4 3 6 20 20 38 28 33 29 53 1 25 -20 -20 -26 -20 -19 -1 4 9 0 15 -10 15
|
||||||
|
-9 0 -15 -3 -15 -7 1 -5 1 -15 0 -23 -1 -12 -3 -13 -10 -1 -6 9 -11 10 -15 2
|
||||||
|
-4 -6 -4 -11 0 -11 4 0 -1 -9 -10 -19 -10 -11 -25 -17 -34 -14 -13 4 -12 2 3
|
||||||
|
-10 16 -13 17 -17 6 -31 -12 -14 -12 -16 4 -10 13 5 16 4 11 -4 -4 -7 -2 -12
|
||||||
|
3 -12 6 0 11 7 11 15 0 8 6 15 14 15 8 0 16 5 18 12 3 8 13 2 28 -15 13 -15
|
||||||
|
30 -27 37 -27 19 0 16 -10 -12 -36 -14 -13 -25 -27 -25 -32 0 -14 -57 -61 -66
|
||||||
|
-54 -4 4 -4 1 1 -7 4 -8 2 -17 -6 -22 -8 -5 -9 -9 -3 -9 6 0 21 11 34 25 12
|
||||||
|
13 28 22 34 18 6 -3 8 -3 4 1 -10 12 120 179 133 171 13 -9 21 15 9 30 -9 11
|
||||||
|
-15 9 -35 -11 -14 -14 -25 -29 -25 -34 0 -11 -3 -12 -39 -14 -40 -2 -33 17 20
|
||||||
|
53 60 41 104 94 95 117 -4 10 -1 14 6 12 20 -7 21 -38 3 -58 -11 -12 -13 -20
|
||||||
|
-6 -25 6 -3 11 1 11 9 0 8 5 18 11 22 8 4 9 -1 3 -17 -4 -13 -9 -37 -10 -52
|
||||||
|
-1 -15 -7 -34 -13 -41 -6 -7 -8 -15 -6 -18 3 -3 1 -17 -4 -32 -9 -23 -9 -25 7
|
||||||
|
-15 16 10 19 21 25 89 2 30 11 38 22 20 3 -5 14 -9 23 -9 15 1 15 2 -1 6 -9 2
|
||||||
|
-14 9 -11 15 4 7 2 8 -4 4 -7 -4 -12 -1 -12 8 0 8 10 17 23 19 l22 4 -22 2
|
||||||
|
c-13 0 -23 4 -24 9 -8 75 -5 102 12 102 6 0 7 -10 3 -22 -6 -20 -4 -20 16 5
|
||||||
|
12 15 20 31 16 34 -3 4 -6 1 -6 -5 0 -7 -4 -12 -10 -12 -5 0 -10 7 -10 15 0 9
|
||||||
|
-6 12 -15 9 -8 -4 -15 -2 -15 2 0 12 20 41 20 31 0 -5 8 -1 17 9 13 13 15 18
|
||||||
|
5 22 -9 3 -6 10 9 23 12 10 17 19 11 19 -14 0 76 84 92 86 24 3 56 17 56 25 0
|
||||||
|
16 -30 10 -37 -7 -6 -15 -8 -15 -20 2 -7 10 -13 13 -13 5z m-355 -415 c-18
|
||||||
|
-28 -42 -40 -50 -26 -4 6 0 10 8 10 8 0 22 9 32 20 25 27 29 25 10 -4z"/>
|
||||||
|
<path d="M922 1156 c-21 -20 -42 -33 -46 -29 -3 3 -6 -1 -6 -10 0 -27 22 -19
|
||||||
|
64 23 50 51 41 63 -12 16z"/>
|
||||||
|
<path d="M620 1170 c-12 -8 -10 -10 8 -10 13 0 20 4 17 10 -7 12 -6 12 -25 0z"/>
|
||||||
|
<path d="M743 1156 c-15 -13 -26 -26 -23 -29 3 -3 21 8 40 24 19 16 29 29 23
|
||||||
|
29 -7 0 -25 -11 -40 -24z"/>
|
||||||
|
<path d="M1393 1113 c-11 -38 -26 -67 -32 -65 -6 1 -11 -1 -12 -5 0 -5 -2 -16
|
||||||
|
-4 -27 -3 -14 -1 -16 8 -7 14 14 30 15 22 1 -3 -5 -1 -10 5 -10 7 0 10 6 7 14
|
||||||
|
-3 8 0 33 7 56 17 57 31 35 35 -55 2 -38 7 -75 12 -82 7 -7 6 -14 -1 -18 -14
|
||||||
|
-8 -13 -52 2 -61 6 -4 8 -3 4 4 -3 6 -1 23 6 39 6 15 12 42 13 58 1 17 5 41 8
|
||||||
|
55 4 14 4 19 1 13 -13 -30 -26 -11 -20 28 4 26 3 38 -4 34 -6 -3 -7 0 -4 9 4
|
||||||
|
11 0 16 -13 16 -16 0 -18 6 -15 35 6 53 -2 42 -25 -32z"/>
|
||||||
|
<path d="M1683 1174 c3 -3 1 -17 -4 -31 -6 -16 -6 -31 1 -41 7 -14 9 -11 10
|
||||||
|
15 0 17 3 38 6 47 4 10 1 16 -7 16 -7 0 -10 -3 -6 -6z"/>
|
||||||
|
<path d="M749 1120 c-28 -27 -56 -50 -63 -50 -12 0 -66 -39 -54 -40 4 0 24 9
|
||||||
|
45 19 21 11 45 21 53 23 8 2 27 19 42 38 15 19 32 41 38 48 7 8 7 12 0 12 -5
|
||||||
|
0 -33 -22 -61 -50z"/>
|
||||||
|
<path d="M1263 1149 c-2 -17 1 -18 13 -9 17 15 18 30 1 30 -7 0 -13 -9 -14
|
||||||
|
-21z"/>
|
||||||
|
<path d="M560 1150 c-13 -9 -13 -10 0 -10 8 0 22 5 30 10 13 9 13 10 0 10 -8
|
||||||
|
0 -22 -5 -30 -10z"/>
|
||||||
|
<path d="M1119 1133 c-10 -11 -10 -14 -1 -9 6 4 12 2 12 -3 0 -6 -5 -11 -12
|
||||||
|
-11 -6 0 -8 -3 -4 -7 9 -9 -54 -73 -73 -74 -9 0 -11 -3 -5 -5 22 -9 87 45 101
|
||||||
|
84 15 45 15 42 4 42 -5 0 -15 -8 -22 -17z"/>
|
||||||
|
<path d="M494 1124 c-18 -15 -18 -15 2 -5 12 6 24 7 28 4 3 -3 6 -1 6 5 0 16
|
||||||
|
-12 15 -36 -4z"/>
|
||||||
|
<path d="M591 1124 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
|
||||||
|
<path d="M610 1131 c0 -5 5 -13 10 -16 6 -3 10 -2 10 4 0 5 -4 13 -10 16 -5 3
|
||||||
|
-10 2 -10 -4z"/>
|
||||||
|
<path d="M953 1110 c-9 -11 -20 -15 -23 -10 -3 5 -14 -5 -24 -23 -14 -26 -14
|
||||||
|
-28 -1 -12 9 11 21 19 27 17 5 -1 16 2 22 8 7 5 15 7 18 5 3 -3 8 3 11 15 7
|
||||||
|
26 -8 26 -30 0z"/>
|
||||||
|
<path d="M1280 1115 c-9 -11 -9 -15 -1 -15 7 0 9 -5 5 -12 -4 -7 -3 -10 1 -5
|
||||||
|
8 6 16 47 10 47 -2 0 -8 -7 -15 -15z"/>
|
||||||
|
<path d="M790 1095 c-20 -24 -6 -27 20 -5 17 15 19 20 8 20 -9 0 -21 -7 -28
|
||||||
|
-15z"/>
|
||||||
|
<path d="M547 1084 c4 -10 10 -11 24 -3 24 13 24 14 -5 15 -17 1 -23 -2 -19
|
||||||
|
-12z"/>
|
||||||
|
<path d="M1625 1090 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M864 1082 c3 -5 -4 -19 -15 -30 -11 -12 -16 -22 -11 -22 12 0 42 34
|
||||||
|
42 48 0 7 -5 12 -11 12 -5 0 -8 -4 -5 -8z"/>
|
||||||
|
<path d="M1261 1074 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
|
||||||
|
<path d="M440 1069 c0 -5 11 -9 25 -9 14 0 25 2 25 4 0 2 -11 6 -25 8 -14 3
|
||||||
|
-25 1 -25 -3z"/>
|
||||||
|
<path d="M967 1062 c-24 -27 -21 -36 4 -11 11 11 19 23 16 25 -2 2 -11 -4 -20
|
||||||
|
-14z"/>
|
||||||
|
<path d="M580 1055 c0 -8 2 -15 4 -15 2 0 6 7 10 15 3 8 1 15 -4 15 -6 0 -10
|
||||||
|
-7 -10 -15z"/>
|
||||||
|
<path d="M1612 1039 c-2 -18 2 -28 8 -24 5 3 8 15 7 25 -5 32 -13 31 -15 -1z"/>
|
||||||
|
<path d="M1143 1048 c-13 -16 -26 -58 -19 -58 8 0 38 61 33 66 -2 3 -9 -1 -14
|
||||||
|
-8z"/>
|
||||||
|
<path d="M694 1035 c-10 -8 -14 -15 -8 -15 6 0 17 7 24 15 16 19 9 19 -16 0z"/>
|
||||||
|
<path d="M722 1028 c-15 -15 -16 -31 0 -26 6 2 12 12 14 21 3 20 2 21 -14 5z"/>
|
||||||
|
<path d="M970 1000 c-17 -22 -27 -40 -21 -40 6 0 11 4 11 9 0 5 14 23 31 40
|
||||||
|
17 17 26 31 21 31 -6 0 -25 -18 -42 -40z"/>
|
||||||
|
<path d="M1095 1020 c-20 -22 -85 -63 -84 -52 5 30 -3 34 -19 9 -10 -15 -28
|
||||||
|
-36 -40 -48 -12 -11 -22 -24 -22 -29 0 -12 30 -14 30 -2 0 4 10 18 21 32 17
|
||||||
|
21 20 22 15 5 -6 -19 -5 -19 8 -2 8 10 17 18 20 18 15 -2 85 52 89 70 6 24 4
|
||||||
|
24 -18 -1z"/>
|
||||||
|
<path d="M1664 1015 c7 -36 12 -41 19 -16 3 11 -2 26 -10 32 -12 10 -13 8 -9
|
||||||
|
-16z"/>
|
||||||
|
<path d="M1710 1030 c0 -5 5 -10 10 -10 6 0 10 5 10 10 0 6 -4 10 -10 10 -5 0
|
||||||
|
-10 -4 -10 -10z"/>
|
||||||
|
<path d="M433 1023 c-7 -2 -13 -9 -13 -15 0 -7 4 -7 13 0 6 5 21 12 32 15 20
|
||||||
|
5 20 5 0 5 -11 0 -26 -2 -32 -5z"/>
|
||||||
|
<path d="M591 1013 c-1 -17 16 -25 25 -12 4 7 3 9 -4 5 -5 -3 -13 0 -15 6 -4
|
||||||
|
9 -6 10 -6 1z"/>
|
||||||
|
<path d="M790 1005 c-6 -8 -10 -19 -8 -24 4 -14 51 10 54 27 1 7 0 8 -3 2 -3
|
||||||
|
-8 -9 -8 -18 0 -9 7 -16 6 -25 -5z"/>
|
||||||
|
<path d="M912 1011 c-11 -6 -10 -10 4 -15 15 -6 13 -11 -14 -37 -18 -17 -32
|
||||||
|
-36 -32 -41 0 -5 -10 -6 -22 -3 -13 3 -18 3 -13 -2 10 -7 11 -15 6 -40 0 -4
|
||||||
|
-6 -2 -13 5 -7 7 -17 12 -22 12 -6 0 -1 -7 11 -15 18 -13 26 -13 43 -3 11 7
|
||||||
|
20 18 20 24 0 6 16 33 35 60 19 27 35 52 35 56 0 10 -22 10 -38 -1z"/>
|
||||||
|
<path d="M241 994 c0 -12 -18 -43 -40 -70 -36 -46 -37 -49 -16 -49 14 0 21 5
|
||||||
|
18 13 -7 17 17 44 33 38 8 -3 20 1 28 9 19 20 26 19 27 -2 0 -15 2 -15 8 2 8
|
||||||
|
21 4 45 -7 43 -24 -3 -41 4 -45 20 -4 13 -5 13 -6 -4z"/>
|
||||||
|
<path d="M455 994 c-16 -7 -33 -10 -37 -7 -5 2 -8 -3 -8 -13 0 -12 4 -14 13
|
||||||
|
-7 12 10 58 24 89 28 30 3 30 12 1 12 -16 0 -41 -6 -58 -13z"/>
|
||||||
|
<path d="M630 980 c-13 -11 -21 -22 -18 -25 3 -3 17 6 32 20 32 30 20 34 -14
|
||||||
|
5z"/>
|
||||||
|
<path d="M116 974 c-3 -8 -10 -13 -15 -9 -5 3 -13 -5 -17 -17 -4 -13 -11 -26
|
||||||
|
-17 -29 -6 -4 -5 -13 2 -25 9 -17 10 -17 11 -1 0 9 6 17 14 17 7 0 16 14 20
|
||||||
|
31 3 17 10 28 16 24 5 -3 10 -2 10 4 0 17 -18 21 -24 5z"/>
|
||||||
|
<path d="M1317 965 c-2 -14 -1 -27 2 -29 4 -3 8 -18 9 -35 2 -24 -1 -29 -13
|
||||||
|
-25 -10 4 -15 0 -15 -12 0 -11 -9 -32 -20 -49 -11 -16 -20 -37 -20 -45 0 -8
|
||||||
|
11 5 25 29 14 23 25 50 25 59 0 14 2 14 13 -1 11 -15 12 -14 14 10 0 15 6 29
|
||||||
|
12 31 6 2 10 22 10 45 -2 31 -3 34 -6 14 -2 -15 -8 -25 -13 -22 -4 3 -6 14 -3
|
||||||
|
24 3 10 0 21 -5 25 -5 3 -12 -6 -15 -19z"/>
|
||||||
|
<path d="M728 964 c-16 -8 -28 -20 -28 -27 0 -9 2 -9 8 0 4 6 20 15 35 18 15
|
||||||
|
4 27 11 27 16 0 12 -10 11 -42 -7z"/>
|
||||||
|
<path d="M475 960 c-3 -5 -14 -10 -25 -10 -10 0 -21 -7 -24 -16 -9 -24 -1 -28
|
||||||
|
17 -9 17 17 31 20 22 5 -12 -19 26 -23 58 -7 31 16 31 16 5 11 -40 -9 -64 2
|
||||||
|
-48 21 7 8 10 15 7 15 -3 0 -9 -4 -12 -10z"/>
|
||||||
|
<path d="M1650 963 c5 -2 8 -9 4 -14 -3 -5 0 -9 6 -9 15 0 7 23 -8 26 -7 1 -8
|
||||||
|
0 -2 -3z"/>
|
||||||
|
<path d="M777 930 c3 -11 10 -23 16 -27 6 -4 7 -1 2 7 -6 10 -4 12 9 7 22 -9
|
||||||
|
11 9 -14 23 -15 8 -17 7 -13 -10z"/>
|
||||||
|
<path d="M1095 920 c-16 -16 -27 -31 -24 -33 2 -2 17 11 32 30 36 41 31 43 -8
|
||||||
|
3z"/>
|
||||||
|
<path d="M1379 928 c0 -13 -2 -33 -3 -46 -2 -13 2 -21 8 -19 13 4 17 87 4 87
|
||||||
|
-4 0 -8 -10 -9 -22z"/>
|
||||||
|
<path d="M1605 940 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7
|
||||||
|
-4 -4 -10z"/>
|
||||||
|
<path d="M668 933 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||||
|
<path d="M1147 927 c-3 -8 1 -14 9 -14 16 0 19 13 5 21 -5 3 -11 0 -14 -7z"/>
|
||||||
|
<path d="M1269 901 c-14 -15 -33 -32 -42 -37 -11 -7 -13 -14 -7 -24 8 -12 13
|
||||||
|
-10 32 14 13 16 31 36 41 45 18 16 23 32 10 30 -5 0 -20 -13 -34 -28z"/>
|
||||||
|
<path d="M1660 894 c0 -19 4 -33 8 -30 8 5 4 66 -4 66 -2 0 -4 -16 -4 -36z"/>
|
||||||
|
<path d="M395 912 c-18 -13 -14 -28 8 -29 16 -2 18 0 8 6 -7 5 -10 14 -7 20 8
|
||||||
|
13 5 14 -9 3z"/>
|
||||||
|
<path d="M574 906 c-10 -8 -17 -17 -14 -19 3 -3 16 3 29 14 27 22 15 26 -15 5z"/>
|
||||||
|
<path d="M1500 901 c-12 -24 -13 -34 -1 -27 9 6 22 46 15 46 -2 0 -8 -9 -14
|
||||||
|
-19z"/>
|
||||||
|
<path d="M26 877 c-10 -22 -15 -44 -13 -50 3 -7 10 2 17 18 l12 30 9 -24 c5
|
||||||
|
-13 12 -21 16 -17 3 3 1 16 -5 29 -6 12 -13 29 -15 37 -3 10 -11 1 -21 -23z"/>
|
||||||
|
<path d="M482 891 c-11 -7 -11 -9 1 -14 9 -3 18 1 21 9 6 16 -2 18 -22 5z"/>
|
||||||
|
<path d="M1173 892 c-6 -4 -14 -21 -17 -37 -6 -28 -6 -29 4 -6 6 14 16 31 22
|
||||||
|
38 12 15 9 16 -9 5z"/>
|
||||||
|
<path d="M1013 863 c-36 -34 -49 -63 -27 -63 8 0 13 4 10 9 -3 4 6 19 19 31
|
||||||
|
14 13 25 18 25 11 0 -7 5 -9 10 -6 12 7 13 45 2 45 -5 0 -22 -12 -39 -27z"/>
|
||||||
|
<path d="M104 865 c-10 -8 -14 -15 -8 -15 6 0 17 7 24 15 16 19 9 19 -16 0z"/>
|
||||||
|
<path d="M1095 869 c-4 -6 -5 -12 -2 -15 2 -3 7 2 10 11 7 17 1 20 -8 4z"/>
|
||||||
|
<path d="M761 854 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
|
||||||
|
<path d="M365 850 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 -7
|
||||||
|
-4 -4 -10z"/>
|
||||||
|
<path d="M900 849 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M952 848 c-15 -15 -16 -31 0 -26 6 2 12 12 14 21 3 20 2 21 -14 5z"/>
|
||||||
|
<path d="M1411 853 c-1 -7 8 -17 19 -23 14 -7 20 -7 20 0 0 5 -6 10 -14 10 -8
|
||||||
|
0 -17 6 -19 13 -4 9 -6 9 -6 0z"/>
|
||||||
|
<path d="M1469 833 c-18 -47 -26 -58 -38 -59 -6 -1 -14 -2 -18 -3 -4 0 2 -8
|
||||||
|
13 -17 18 -15 18 -15 0 -10 -17 6 -17 5 -5 -11 9 -11 10 -14 1 -9 -8 5 -15 -2
|
||||||
|
-22 -18 -5 -14 -16 -26 -25 -26 -8 0 -15 -7 -15 -16 0 -8 -4 -12 -10 -9 -5 3
|
||||||
|
-10 -1 -10 -9 0 -8 8 -19 17 -24 15 -9 14 -11 -5 -23 -13 -8 -18 -17 -12 -24
|
||||||
|
12 -14 -12 -61 -29 -57 -7 1 -10 -2 -7 -7 4 -5 0 -12 -6 -14 -7 -3 -8 -6 -3
|
||||||
|
-6 20 -1 45 27 65 73 24 58 25 64 3 68 -14 3 -9 10 19 28 21 14 36 31 33 38
|
||||||
|
-2 6 1 12 8 12 13 0 50 74 63 124 8 34 -4 33 -17 -1z"/>
|
||||||
|
<path d="M679 831 c-13 -11 -22 -22 -19 -24 3 -3 16 5 29 19 28 29 22 31 -10
|
||||||
|
5z"/>
|
||||||
|
<path d="M555 821 c-3 -5 -1 -12 4 -15 5 -3 11 1 15 9 6 16 -9 21 -19 6z"/>
|
||||||
|
<path d="M1110 815 c0 -8 -5 -14 -12 -12 -7 1 -13 -5 -14 -13 0 -8 -2 -21 -2
|
||||||
|
-28 -1 -7 -8 -12 -14 -11 -27 3 -38 -1 -38 -15 0 -8 4 -17 9 -20 5 -3 8 0 7 7
|
||||||
|
0 6 4 12 9 12 6 0 9 -4 7 -10 -2 -5 8 4 22 20 29 35 50 85 35 85 -5 0 -9 -7
|
||||||
|
-9 -15z"/>
|
||||||
|
<path d="M1166 814 c-4 -9 -4 -19 -1 -22 2 -3 7 3 11 12 4 9 4 19 1 22 -2 3
|
||||||
|
-7 -3 -11 -12z"/>
|
||||||
|
<path d="M1200 809 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M1 736 c-1 -64 0 -69 12 -46 7 14 10 27 5 31 -4 3 -3 21 2 39 13 45
|
||||||
|
13 47 -4 47 -9 0 -14 -19 -15 -71z"/>
|
||||||
|
<path d="M1310 795 c-7 -8 -9 -23 -5 -36 6 -18 4 -21 -10 -15 -15 5 -16 4 -6
|
||||||
|
-8 9 -11 10 -24 2 -52 -5 -20 -7 -34 -4 -30 8 8 34 75 37 96 1 8 8 25 14 38
|
||||||
|
11 18 10 22 -1 22 -8 0 -20 -7 -27 -15z"/>
|
||||||
|
<path d="M624 778 c-11 -13 -30 -28 -44 -35 -20 -9 -21 -12 -6 -12 15 -1 16
|
||||||
|
-4 6 -16 -9 -10 -9 -15 -1 -15 10 0 15 13 12 33 0 4 6 7 14 7 15 0 51 47 42
|
||||||
|
56 -2 3 -13 -5 -23 -18z"/>
|
||||||
|
<path d="M409 788 c-12 -40 23 -62 42 -27 9 18 7 19 -17 13 -19 -5 -25 -4 -20
|
||||||
|
5 4 6 5 11 2 11 -3 0 -6 -1 -7 -2z"/>
|
||||||
|
<path d="M935 780 c-3 -5 -2 -10 4 -10 5 0 13 5 16 10 3 6 2 10 -4 10 -5 0
|
||||||
|
-13 -4 -16 -10z"/>
|
||||||
|
<path d="M660 770 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 -4
|
||||||
|
-4 -4 -10z"/>
|
||||||
|
<path d="M1222 760 c-8 -13 -8 -20 -2 -20 6 0 13 7 17 15 9 24 -1 27 -15 5z"/>
|
||||||
|
<path d="M827 763 c-4 -3 -7 -11 -7 -17 0 -6 5 -5 12 2 6 6 9 14 7 17 -3 3 -9
|
||||||
|
2 -12 -2z"/>
|
||||||
|
<path d="M1002 765 c5 -5 -1 -14 -12 -22 -11 -8 -20 -22 -19 -31 0 -14 2 -14
|
||||||
|
6 -2 4 8 15 22 26 31 17 13 18 17 6 24 -8 5 -11 5 -7 0z"/>
|
||||||
|
<path d="M355 750 c3 -6 1 -10 -5 -10 -6 0 -9 -4 -5 -10 6 -10 35 -2 35 10 0
|
||||||
|
4 -7 11 -16 14 -8 3 -12 2 -9 -4z"/>
|
||||||
|
<path d="M1185 730 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M1469 733 c-1 -5 -1 -12 0 -17 1 -4 -6 -14 -16 -22 -16 -13 -16 -13
|
||||||
|
6 -6 26 9 41 32 21 32 -6 0 -8 5 -5 10 3 6 4 10 1 10 -3 0 -7 -3 -7 -7z"/>
|
||||||
|
<path d="M1147 701 c-4 -17 -2 -18 13 -8 10 6 16 15 14 19 -9 14 -22 9 -27
|
||||||
|
-11z"/>
|
||||||
|
<path d="M780 701 c0 -5 -12 -8 -27 -6 -26 3 -26 2 -8 -12 20 -15 20 -15 0
|
||||||
|
-11 -15 4 -21 0 -21 -13 0 -11 4 -19 8 -19 4 0 8 5 8 10 0 6 6 10 14 10 8 0
|
||||||
|
17 7 20 16 3 8 10 12 16 9 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/>
|
||||||
|
<path d="M810 706 c0 -2 7 -7 16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z"/>
|
||||||
|
<path d="M1590 696 c7 -7 14 -6 21 1 9 9 7 12 -9 10 -14 -1 -18 -5 -12 -11z"/>
|
||||||
|
<path d="M89 666 c-17 -19 -37 -32 -43 -30 -6 2 -20 -10 -30 -26 -19 -34 -17
|
||||||
|
-50 4 -25 7 8 16 15 20 15 4 0 30 23 57 50 28 28 44 50 37 50 -7 0 -27 -15
|
||||||
|
-45 -34z"/>
|
||||||
|
<path d="M250 663 c-12 -21 -31 -43 -41 -51 -18 -13 -27 -42 -11 -42 4 0 18
|
||||||
|
16 32 35 29 39 46 45 35 13 -3 -13 -6 -30 -6 -38 3 -56 0 -83 -8 -99 -14 -26
|
||||||
|
-21 -71 -11 -71 7 0 20 38 25 70 0 3 4 10 8 16 10 17 17 204 7 204 -4 0 -18
|
||||||
|
-17 -30 -37z"/>
|
||||||
|
<path d="M375 690 c-3 -5 1 -10 9 -10 9 0 16 5 16 10 0 6 -4 10 -9 10 -6 0
|
||||||
|
-13 -4 -16 -10z"/>
|
||||||
|
<path d="M880 685 c-11 -13 -10 -14 4 -9 9 3 16 10 16 15 0 13 -6 11 -20 -6z"/>
|
||||||
|
<path d="M923 687 c-3 -6 -1 -7 5 -3 7 4 12 2 12 -4 0 -6 5 -8 10 -5 6 3 10
|
||||||
|
10 10 14 0 11 -30 9 -37 -2z"/>
|
||||||
|
<path d="M1063 685 c-3 -9 -3 -19 1 -22 3 -4 6 1 6 10 0 10 4 17 8 17 14 0 32
|
||||||
|
-23 32 -42 0 -16 3 -16 20 -6 18 12 19 13 2 19 -10 4 -24 14 -31 23 -17 20
|
||||||
|
-30 20 -38 1z"/>
|
||||||
|
<path d="M987 676 c-4 -10 0 -14 12 -13 11 1 17 7 14 14 -7 17 -20 17 -26 -1z"/>
|
||||||
|
<path d="M1576 663 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z"/>
|
||||||
|
<path d="M1073 655 c-7 -8 -13 -19 -13 -25 0 -5 -4 -10 -10 -10 -5 0 -10 -6
|
||||||
|
-10 -12 1 -15 50 38 50 53 0 12 -2 11 -17 -6z"/>
|
||||||
|
<path d="M950 638 c0 -4 7 -8 15 -8 8 0 15 4 15 8 0 5 -7 9 -15 9 -8 0 -15 -4
|
||||||
|
-15 -9z"/>
|
||||||
|
<path d="M1215 640 c4 -6 11 -8 16 -5 14 9 11 15 -7 15 -8 0 -12 -5 -9 -10z"/>
|
||||||
|
<path d="M480 620 c-13 -8 -12 -10 3 -10 9 0 17 5 17 10 0 12 -1 12 -20 0z"/>
|
||||||
|
<path d="M894 622 c-6 -4 -14 -16 -17 -27 -7 -20 -7 -20 8 -1 8 11 21 23 27
|
||||||
|
28 7 4 9 8 3 8 -5 0 -15 -4 -21 -8z"/>
|
||||||
|
<path d="M728 608 c5 -5 16 -8 23 -6 8 3 3 7 -10 11 -17 4 -21 3 -13 -5z"/>
|
||||||
|
<path d="M1405 610 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
|
||||||
|
<path d="M1440 599 c0 -23 10 -28 35 -18 18 7 18 8 1 8 -11 1 -16 6 -13 14 3
|
||||||
|
8 -1 14 -9 14 -8 0 -14 -8 -14 -18z"/>
|
||||||
|
<path d="M813 595 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
|
||||||
|
<path d="M1271 573 c2 -24 2 -25 6 -5 2 12 9 20 14 17 5 -4 9 -1 9 4 0 6 -7
|
||||||
|
11 -15 11 -10 0 -15 -9 -14 -27z"/>
|
||||||
|
<path d="M1125 580 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M1163 565 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/>
|
||||||
|
<path d="M1880 573 c0 -5 7 -19 16 -31 8 -12 12 -22 9 -22 -3 0 1 -10 9 -22
|
||||||
|
20 -28 20 -42 2 -35 -11 4 -11 1 -2 -18 6 -13 14 -21 18 -19 4 3 10 -8 13 -23
|
||||||
|
4 -15 11 -38 16 -51 7 -17 6 -21 -3 -16 -8 5 -9 2 -4 -12 5 -10 9 -26 10 -34
|
||||||
|
0 -8 5 -18 9 -21 5 -4 7 -16 5 -29 -3 -22 -3 -22 -25 -2 -13 10 -19 23 -15 27
|
||||||
|
4 5 1 5 -5 1 -10 -5 -9 -13 3 -32 9 -13 20 -24 25 -24 5 0 9 -12 9 -27 0 -23
|
||||||
|
-2 -25 -11 -13 -9 12 -10 11 -4 -5 4 -11 9 -53 12 -92 4 -55 10 -73 20 -73 13
|
||||||
|
0 14 25 10 162 -4 147 -8 172 -35 254 -16 50 -37 104 -46 122 -16 31 -36 50
|
||||||
|
-36 35z"/>
|
||||||
|
<path d="M30 548 c-12 -22 -12 -22 6 -6 10 10 15 20 12 24 -4 3 -12 -5 -18
|
||||||
|
-18z"/>
|
||||||
|
<path d="M430 559 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M1212 550 c-7 -11 -8 -20 -3 -20 9 0 24 30 18 36 -1 2 -8 -6 -15 -16z"/>
|
||||||
|
<path d="M165 544 c21 -16 19 -37 -6 -52 -12 -8 -18 -19 -15 -28 3 -8 1 -14
|
||||||
|
-5 -14 -6 0 -8 -9 -4 -19 4 -13 -5 -37 -24 -67 -16 -27 -27 -52 -24 -57 7 -12
|
||||||
|
47 59 67 121 19 56 38 78 58 66 10 -6 9 -3 -1 9 -8 11 -9 17 -3 17 6 0 16 10
|
||||||
|
22 23 9 18 9 20 -2 7 -18 -20 -42 -25 -35 -7 4 11 -2 14 -19 13 -20 -1 -22 -3
|
||||||
|
-9 -12z"/>
|
||||||
|
<path d="M998 553 c6 -2 9 -10 6 -15 -4 -7 -2 -8 5 -4 13 9 5 26 -12 25 -9 0
|
||||||
|
-8 -2 1 -6z"/>
|
||||||
|
<path d="M1463 540 c1 -21 12 -26 22 -10 3 6 1 10 -5 10 -6 0 -8 5 -5 10 3 6
|
||||||
|
2 10 -4 10 -5 0 -9 -9 -8 -20z"/>
|
||||||
|
<path d="M1045 518 c-4 -18 -17 -42 -29 -53 -12 -11 -17 -17 -11 -13 7 4 15 5
|
||||||
|
18 1 4 -3 9 1 13 10 3 9 11 15 17 13 7 -1 9 -1 6 1 -3 2 -4 19 -2 38 4 44 -3
|
||||||
|
45 -12 3z"/>
|
||||||
|
<path d="M130 530 c-13 -8 -12 -10 3 -10 9 0 17 5 17 10 0 12 -1 12 -20 0z"/>
|
||||||
|
<path d="M570 525 c-8 -9 -8 -15 -2 -15 12 0 26 19 19 26 -2 2 -10 -2 -17 -11z"/>
|
||||||
|
<path d="M712 501 c-16 -17 -33 -27 -36 -23 -4 3 -4 -1 -1 -10 6 -14 12 -11
|
||||||
|
41 19 19 20 33 37 31 40 -3 2 -18 -10 -35 -26z"/>
|
||||||
|
<path d="M850 516 c0 -8 4 -17 9 -20 5 -4 7 3 4 14 -6 23 -13 26 -13 6z"/>
|
||||||
|
<path d="M1436 516 c3 -9 1 -23 -6 -31 -7 -8 -8 -15 -4 -15 5 0 15 9 21 19 10
|
||||||
|
16 9 22 -3 32 -12 10 -13 9 -8 -5z"/>
|
||||||
|
<path d="M1260 510 c-8 -5 -10 -10 -5 -10 6 0 17 5 25 10 8 5 11 10 5 10 -5 0
|
||||||
|
-17 -5 -25 -10z"/>
|
||||||
|
<path d="M106 499 c10 -17 -56 -72 -71 -59 -8 7 -16 3 -25 -15 -7 -14 -10 -28
|
||||||
|
-6 -32 4 -4 9 3 13 16 3 14 11 20 20 17 8 -3 32 11 55 32 34 32 38 40 24 45
|
||||||
|
-11 5 -15 3 -10 -4z"/>
|
||||||
|
<path d="M1225 500 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M635 474 c-9 -15 -12 -23 -6 -20 6 4 11 2 11 -3 0 -6 5 -11 11 -11 8
|
||||||
|
0 8 5 -1 15 -7 8 -8 15 -3 15 5 0 10 7 10 15 0 20 -2 19 -22 -11z"/>
|
||||||
|
<path d="M100 421 c0 -5 -6 -16 -12 -23 -51 -53 -40 -69 11 -17 34 34 39 49
|
||||||
|
16 49 -8 0 -15 -4 -15 -9z"/>
|
||||||
|
<path d="M176 388 c0 -23 6 -44 12 -46 14 -4 15 -55 2 -100 -8 -29 -7 -32 5
|
||||||
|
-22 17 14 30 93 20 117 -15 33 -15 56 -3 71 11 13 10 15 -3 10 -9 -4 -21 -2
|
||||||
|
-25 2 -6 6 -9 -8 -8 -32z"/>
|
||||||
|
<path d="M735 420 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/>
|
||||||
|
<path d="M1305 380 c-3 -5 1 -10 10 -10 9 0 13 5 10 10 -3 6 -8 10 -10 10 -2
|
||||||
|
0 -7 -4 -10 -10z"/>
|
||||||
|
<path d="M825 370 c-3 -5 -21 -10 -38 -10 -21 0 -28 -3 -19 -9 8 -4 13 -11 13
|
||||||
|
-15 -4 -29 13 -37 22 -12 4 11 1 13 -11 9 -14 -5 -15 -3 -5 7 9 10 15 10 26 1
|
||||||
|
15 -11 22 -7 41 27 9 14 -20 17 -29 2z"/>
|
||||||
|
<path d="M11 323 c-10 -26 -10 -27 4 -9 8 11 15 23 15 28 0 15 -7 8 -19 -19z"/>
|
||||||
|
<path d="M483 335 c-3 -9 -3 -18 -1 -21 3 -3 8 4 11 16 6 23 -1 27 -10 5z"/>
|
||||||
|
<path d="M1840 340 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0
|
||||||
|
-4 -4 -4 -10z"/>
|
||||||
|
<path d="M570 320 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 -4
|
||||||
|
-4 -4 -10z"/>
|
||||||
|
<path d="M873 317 c0 -8 6 -14 14 -14 9 0 10 4 2 14 -6 7 -12 13 -14 13 -1 0
|
||||||
|
-2 -6 -2 -13z"/>
|
||||||
|
<path d="M1356 317 c3 -10 9 -15 12 -12 3 3 0 11 -7 18 -10 9 -11 8 -5 -6z"/>
|
||||||
|
<path d="M166 297 c3 -10 9 -15 12 -12 3 3 0 11 -7 18 -10 9 -11 8 -5 -6z"/>
|
||||||
|
<path d="M94 285 c-10 -8 -15 -15 -10 -15 5 0 4 -6 -1 -12 -5 -7 -10 -29 -11
|
||||||
|
-48 0 -33 0 -32 13 13 7 26 19 54 25 62 16 19 9 19 -16 0z"/>
|
||||||
|
<path d="M492 284 c-12 -8 -28 -12 -35 -8 -6 4 -5 -1 4 -11 13 -13 21 -15 37
|
||||||
|
-6 20 11 38 41 24 41 -4 0 -18 -7 -30 -16z"/>
|
||||||
|
<path d="M590 291 c0 -6 5 -11 11 -11 5 0 7 -4 4 -10 -3 -5 -1 -10 4 -10 16 0
|
||||||
|
13 16 -4 30 -10 9 -15 9 -15 1z"/>
|
||||||
|
<path d="M747 293 c-4 -3 -7 -13 -7 -22 1 -13 3 -13 11 2 11 19 8 33 -4 20z"/>
|
||||||
|
<path d="M127 283 c-11 -18 -4 -63 9 -63 16 0 19 16 4 25 -6 4 -8 15 -5 26 6
|
||||||
|
19 1 26 -8 12z"/>
|
||||||
|
<path d="M627 256 c-3 -8 5 -22 19 -33 13 -10 22 -24 19 -30 -9 -26 11 -12 28
|
||||||
|
19 13 23 21 28 26 19 7 -10 9 -9 14 2 3 9 -1 18 -9 21 -8 3 -17 0 -20 -7 -7
|
||||||
|
-19 -44 -14 -59 7 -11 14 -14 14 -18 2z"/>
|
||||||
|
<path d="M1075 250 c-10 -11 -14 -20 -9 -20 5 0 17 9 27 20 9 11 13 20 8 20
|
||||||
|
-4 0 -16 -9 -26 -20z"/>
|
||||||
|
<path d="M1155 261 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/>
|
||||||
|
<path d="M1450 245 c-7 -8 -16 -12 -21 -9 -5 3 -9 -2 -9 -10 0 -9 7 -16 15
|
||||||
|
-16 8 0 15 7 15 15 0 7 8 19 18 24 9 6 12 11 6 11 -6 0 -17 -7 -24 -15z"/>
|
||||||
|
<path d="M1516 233 c10 -40 15 -42 12 -5 -1 18 -6 32 -10 32 -5 0 -5 -12 -2
|
||||||
|
-27z"/>
|
||||||
|
<path d="M40 219 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 -5
|
||||||
|
-10 -11z"/>
|
||||||
|
<path d="M1010 189 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||||
|
-5 -10 -11z"/>
|
||||||
|
<path d="M1136 192 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4
|
||||||
|
-9 -8z"/>
|
||||||
|
<path d="M583 175 c-7 -8 -13 -18 -13 -22 0 -4 -11 -21 -25 -37 -30 -35 -31
|
||||||
|
-40 -5 -24 10 7 27 27 37 45 9 17 20 30 25 27 4 -3 5 2 2 10 -7 19 -6 19 -21
|
||||||
|
1z"/>
|
||||||
|
<path d="M920 181 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/>
|
||||||
|
<path d="M1531 170 c0 -8 4 -24 9 -35 9 -20 9 -20 9 0 0 11 -4 27 -9 35 -9 13
|
||||||
|
-10 13 -9 0z"/>
|
||||||
|
<path d="M1418 156 c-10 -7 -18 -19 -18 -26 0 -7 9 -3 20 10 28 33 27 39 -2
|
||||||
|
16z"/>
|
||||||
|
<path d="M520 145 c0 -8 2 -15 4 -15 2 0 6 7 10 15 3 8 1 15 -4 15 -6 0 -10
|
||||||
|
-7 -10 -15z"/>
|
||||||
|
<path d="M1602 140 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z"/>
|
||||||
|
<path d="M730 129 c0 -10 5 -21 12 -25 7 -4 8 -3 4 5 -4 6 -3 18 3 25 8 9 7
|
||||||
|
13 -4 13 -8 0 -15 -8 -15 -18z"/>
|
||||||
|
<path d="M666 131 c-4 -7 -5 -15 -2 -18 9 -9 19 4 14 18 -4 11 -6 11 -12 0z"/>
|
||||||
|
<path d="M496 121 c-5 -7 -18 -16 -30 -21 -20 -8 -20 -9 -1 -9 27 -1 48 14 43
|
||||||
|
30 -3 10 -6 10 -12 0z"/>
|
||||||
|
<path d="M923 114 c-15 -16 -16 -24 -4 -24 8 0 24 30 19 35 -2 2 -9 -3 -15
|
||||||
|
-11z"/>
|
||||||
|
<path d="M1497 103 c-3 -16 -3 -30 -1 -33 7 -7 24 23 24 43 0 27 -17 20 -23
|
||||||
|
-10z"/>
|
||||||
|
<path d="M832 105 c3 -3 -5 -18 -17 -31 -13 -14 -21 -32 -18 -40 6 -16 26 -8
|
||||||
|
20 9 -2 6 7 21 20 33 21 20 22 22 6 28 -10 4 -15 4 -11 1z"/>
|
||||||
|
<path d="M380 75 c-7 -9 -10 -18 -6 -22 3 -3 6 -1 6 6 0 7 3 10 8 8 4 -3 13 1
|
||||||
|
20 9 10 11 10 14 -1 14 -8 0 -20 -7 -27 -15z"/>
|
||||||
|
<path d="M1026 73 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z"/>
|
||||||
|
<path d="M450 65 c-7 -8 -8 -15 -2 -15 5 0 15 7 22 15 7 8 8 15 2 15 -5 0 -15
|
||||||
|
-7 -22 -15z"/>
|
||||||
|
<path d="M1920 71 c0 -6 5 -13 11 -16 6 -4 8 -14 4 -22 -4 -13 -3 -14 9 -4 13
|
||||||
|
11 13 15 -2 32 -19 21 -22 23 -22 10z"/>
|
||||||
|
<path d="M66 61 c-6 -9 9 -61 18 -61 2 0 6 8 8 18 6 21 -17 58 -26 43z"/>
|
||||||
|
<path d="M1391 63 c-1 -17 16 -25 25 -12 4 7 3 9 -4 5 -5 -3 -13 0 -15 6 -4 9
|
||||||
|
-6 10 -6 1z"/>
|
||||||
|
<path d="M280 50 c-8 -5 -12 -11 -9 -14 2 -3 11 -1 19 4 8 5 12 11 9 14 -2 3
|
||||||
|
-11 1 -19 -4z"/>
|
||||||
|
<path d="M1252 40 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z"/>
|
||||||
|
<path d="M1465 50 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0
|
||||||
|
-8 -4 -11 -10z"/>
|
||||||
|
<path d="M1626 48 c-7 -21 -3 -48 5 -43 4 3 6 13 5 23 -2 9 4 20 13 24 14 6
|
||||||
|
14 7 -2 7 -10 1 -19 -5 -21 -11z"/>
|
||||||
|
<path d="M7 25 c-4 -26 -4 -26 14 -6 23 24 24 31 5 31 -7 0 -16 -11 -19 -25z"/>
|
||||||
|
<path d="M1520 38 c0 -9 -8 -22 -17 -27 -16 -9 -16 -10 -2 -11 24 0 40 23 29
|
||||||
|
40 -8 13 -10 12 -10 -2z"/>
|
||||||
|
<path d="M930 31 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/>
|
||||||
|
<path d="M735 20 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0 -8
|
||||||
|
-4 -11 -10z"/>
|
||||||
|
</g>
|
||||||
|
<path d="M0 0 h 300 v 15 h -300 z"/>
|
||||||
|
<path d="M0 285 h 300 v 15 h -300 z"/>
|
||||||
|
<path d="M0 0 v 300 h 15 v -300 z"/>
|
||||||
|
<path d="M285 0 v 300 h 15 v -300 z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 54 KiB |
28
scryer-prolog.wxs
Normal file
28
scryer-prolog.wxs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
|
<Product Name="Scryer Prolog" Manufacturer="Scryer Prolog contributors" Id="*" UpgradeCode="cfb2dee4-5dd5-4d7d-b426-cd7340810559" Language="1033" Codepage="1252" Version="0.9.0">
|
||||||
|
<Package Description="An open source industrial strength production environment for ISO Prolog that is also a testbed for bleeding edge research in logic and constraint programming, which is itself written in a high-level language." Platform="x64" Keywords="prolog" Id="*" Compressed="yes" InstallScope="perMachine" InstallerVersion="300" Languages="1033" SummaryCodepage="1252" Manufacturer="Scryer Prolog contributors"/>
|
||||||
|
<Property Id="APPHELPLINK" Value="https://github.com/mthom/scryer-prolog"/>
|
||||||
|
<Media Id="1" Cabinet="scryer.cab" EmbedCab="yes" />
|
||||||
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
|
<Directory Id="ProgramFilesFolder" Name="PFiles">
|
||||||
|
<Directory Id="INSTALLDIR" Name="Scryer Prolog">
|
||||||
|
<Component Id="MainExecutable" Guid="1b41ceda-ba18-47f9-911b-ee41b4f20921">
|
||||||
|
<File Id="ScryerPrologEXE" Name="scryer-prolog.exe" DiskId="1" Source="target/release/scryer-prolog.exe" KeyPath="yes" Checksum="yes"/>
|
||||||
|
</Component>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="ProgramMenuFolder">
|
||||||
|
<Component Id="ApplicationShortcut" Guid="8c9b14a3-e7b1-4d30-a892-61d7371dcae2">
|
||||||
|
<Shortcut Id="ApplicationStarMenuShortcut" Name="Scryer Prolog" Description="Launch Scryer Prolog" Target="[#ScryerPrologEXE]" WorkingDirectory="INSTALLDIR"/>
|
||||||
|
<RemoveFolder Id="ApplicationShortcut" On="uninstall"/>
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\Microsoft\ScryerProlog" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
<Feature Id="Complete" Level="1" Display="expand" ConfigurableDirectory="INSTALLDIR">
|
||||||
|
<ComponentRef Id="MainExecutable"/>
|
||||||
|
<ComponentRef Id="ApplicationShortcut"/>
|
||||||
|
</Feature>
|
||||||
|
</Product>
|
||||||
|
</Wix>
|
||||||
91
src/allocator.rs
Normal file
91
src/allocator.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use crate::parser::ast::*;
|
||||||
|
|
||||||
|
use crate::forms::*;
|
||||||
|
use crate::instructions::*;
|
||||||
|
use crate::targets::*;
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
pub(crate) trait Allocator {
|
||||||
|
fn new() -> Self;
|
||||||
|
|
||||||
|
fn mark_anon_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
lvl: Level,
|
||||||
|
context: GenContext,
|
||||||
|
code: &mut CodeDeque,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
lvl: Level,
|
||||||
|
context: GenContext,
|
||||||
|
cell: &'a Cell<RegType>,
|
||||||
|
code: &mut CodeDeque,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
var_num: usize,
|
||||||
|
lvl: Level,
|
||||||
|
cell: &'a Cell<VarReg>,
|
||||||
|
term_loc: GenContext,
|
||||||
|
code: &mut CodeDeque,
|
||||||
|
r: RegType,
|
||||||
|
is_new_var: bool,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType;
|
||||||
|
|
||||||
|
fn mark_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
var_num: usize,
|
||||||
|
lvl: Level,
|
||||||
|
cell: &'a 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_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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
1123
src/arena.rs
Normal file
1123
src/arena.rs
Normal file
File diff suppressed because it is too large
Load Diff
725
src/arithmetic.rs
Normal file
725
src/arithmetic.rs
Normal file
@@ -0,0 +1,725 @@
|
|||||||
|
use crate::allocator::*;
|
||||||
|
use crate::arena::*;
|
||||||
|
use crate::atom_table::*;
|
||||||
|
use crate::debray_allocator::*;
|
||||||
|
use crate::forms::*;
|
||||||
|
use crate::instructions::*;
|
||||||
|
use crate::iterators::*;
|
||||||
|
use crate::targets::QueryInstruction;
|
||||||
|
use crate::types::*;
|
||||||
|
|
||||||
|
use crate::parser::ast::*;
|
||||||
|
use crate::parser::dashu::{Integer, Rational};
|
||||||
|
|
||||||
|
use crate::machine::machine_errors::*;
|
||||||
|
|
||||||
|
use dashu::base::Abs;
|
||||||
|
use ordered_float::*;
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::cmp::{max, min, Ordering};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::f64;
|
||||||
|
use std::num::FpCategory;
|
||||||
|
use std::ops::Div;
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ArithmeticTerm {
|
||||||
|
Reg(RegType),
|
||||||
|
Interm(usize),
|
||||||
|
Number(Number),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArithmeticTerm {
|
||||||
|
pub(crate) fn interm_or(&self, interm: usize) -> usize {
|
||||||
|
if let &ArithmeticTerm::Interm(interm) = self {
|
||||||
|
interm
|
||||||
|
} else {
|
||||||
|
interm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ArithmeticTerm {
|
||||||
|
fn default() -> Self {
|
||||||
|
ArithmeticTerm::Number(Number::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ArithInstructionIterator<'a> {
|
||||||
|
state_stack: Vec<TermIterState<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type ArithCont = (CodeDeque, Option<ArithmeticTerm>);
|
||||||
|
|
||||||
|
impl<'a> ArithInstructionIterator<'a> {
|
||||||
|
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
|
||||||
|
self.state_stack
|
||||||
|
.push(TermIterState::subterm_to_state(lvl, term));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from(term: &'a Term) -> Result<Self, ArithmeticError> {
|
||||||
|
let state = match term {
|
||||||
|
Term::AnonVar => return Err(ArithmeticError::UninstantiatedVar),
|
||||||
|
Term::Clause(cell, name, terms) => {
|
||||||
|
TermIterState::Clause(Level::Shallow, 0, cell, *name, terms)
|
||||||
|
}
|
||||||
|
Term::Literal(cell, cons) => TermIterState::Literal(Level::Shallow, cell, cons),
|
||||||
|
Term::Cons(..) | Term::PartialString(..) | Term::CompleteString(..) => {
|
||||||
|
return Err(ArithmeticError::NonEvaluableFunctor(
|
||||||
|
Literal::Atom(atom!(".")),
|
||||||
|
2,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Shallow, cell, var_ptr.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ArithInstructionIterator {
|
||||||
|
state_stack: vec![state],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum ArithTermRef<'a> {
|
||||||
|
Literal(&'a Literal),
|
||||||
|
Op(Atom, usize), // name, arity.
|
||||||
|
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ArithInstructionIterator<'a> {
|
||||||
|
type Item = Result<ArithTermRef<'a>, ArithmeticError>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while let Some(iter_state) = self.state_stack.pop() {
|
||||||
|
match iter_state {
|
||||||
|
TermIterState::AnonVar(_) => return Some(Err(ArithmeticError::UninstantiatedVar)),
|
||||||
|
TermIterState::Clause(lvl, child_num, cell, name, subterms) => {
|
||||||
|
let arity = subterms.len();
|
||||||
|
|
||||||
|
if child_num == arity {
|
||||||
|
return Some(Ok(ArithTermRef::Op(name, arity)));
|
||||||
|
} else {
|
||||||
|
self.state_stack.push(TermIterState::Clause(
|
||||||
|
lvl,
|
||||||
|
child_num + 1,
|
||||||
|
cell,
|
||||||
|
name,
|
||||||
|
subterms,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.push_subterm(lvl.child_level(), &subterms[child_num]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TermIterState::Literal(_, _, c) => return Some(Ok(ArithTermRef::Literal(c))),
|
||||||
|
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||||
|
return Some(Ok(ArithTermRef::Var(lvl, cell, var_ptr)));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Some(Err(ArithmeticError::NonEvaluableFunctor(
|
||||||
|
Literal::Atom(atom!(".")),
|
||||||
|
2,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ArithmeticEvaluator<'a> {
|
||||||
|
marker: &'a mut DebrayAllocator,
|
||||||
|
interm: Vec<ArithmeticTerm>,
|
||||||
|
interm_c: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait ArithmeticTermIter<'a> {
|
||||||
|
type Iter: Iterator<Item = Result<ArithTermRef<'a>, ArithmeticError>>;
|
||||||
|
|
||||||
|
fn iter(self) -> Result<Self::Iter, ArithmeticError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ArithmeticTermIter<'a> for &'a Term {
|
||||||
|
type Iter = ArithInstructionIterator<'a>;
|
||||||
|
|
||||||
|
fn iter(self) -> Result<Self::Iter, ArithmeticError> {
|
||||||
|
ArithInstructionIterator::from(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), ArithmeticError> {
|
||||||
|
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::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))
|
||||||
|
)),
|
||||||
|
Literal::Atom(name) if name == &atom!("pi") => interm.push(ArithmeticTerm::Number(
|
||||||
|
Number::Float(OrderedFloat(std::f64::consts::PI))
|
||||||
|
)),
|
||||||
|
Literal::Atom(name) if name == &atom!("epsilon") => interm.push(ArithmeticTerm::Number(
|
||||||
|
Number::Float(OrderedFloat(std::f64::EPSILON))
|
||||||
|
)),
|
||||||
|
_ => return Err(ArithmeticError::NonEvaluableFunctor(*c, 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ArithmeticEvaluator<'a> {
|
||||||
|
pub(crate) fn new(marker: &'a mut DebrayAllocator, target_int: usize) -> Self {
|
||||||
|
ArithmeticEvaluator {
|
||||||
|
marker,
|
||||||
|
interm: Vec::new(),
|
||||||
|
interm_c: target_int,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_unary_instr(
|
||||||
|
&self,
|
||||||
|
name: Atom,
|
||||||
|
a1: ArithmeticTerm,
|
||||||
|
t: usize,
|
||||||
|
) -> Result<Instruction, ArithmeticError> {
|
||||||
|
match name {
|
||||||
|
atom!("abs") => Ok(Instruction::Abs(a1, t)),
|
||||||
|
atom!("-") => Ok(Instruction::Neg(a1, t)),
|
||||||
|
atom!("+") => Ok(Instruction::Plus(a1, t)),
|
||||||
|
atom!("cos") => Ok(Instruction::Cos(a1, t)),
|
||||||
|
atom!("sin") => Ok(Instruction::Sin(a1, t)),
|
||||||
|
atom!("tan") => Ok(Instruction::Tan(a1, t)),
|
||||||
|
atom!("log") => Ok(Instruction::Log(a1, t)),
|
||||||
|
atom!("asinh") => Ok(Instruction::ASinh(a1, t)),
|
||||||
|
atom!("acosh") => Ok(Instruction::ACosh(a1, t)),
|
||||||
|
atom!("atanh") => Ok(Instruction::ATanh(a1, t)),
|
||||||
|
atom!("sinh") => Ok(Instruction::Sinh(a1, t)),
|
||||||
|
atom!("cosh") => Ok(Instruction::Cosh(a1, t)),
|
||||||
|
atom!("tanh") => Ok(Instruction::Tanh(a1, t)),
|
||||||
|
atom!("log10") => Ok(Instruction::Log10(a1, t)),
|
||||||
|
atom!("exp") => Ok(Instruction::Exp(a1, t)),
|
||||||
|
atom!("sqrt") => Ok(Instruction::Sqrt(a1, t)),
|
||||||
|
atom!("acos") => Ok(Instruction::ACos(a1, t)),
|
||||||
|
atom!("asin") => Ok(Instruction::ASin(a1, t)),
|
||||||
|
atom!("atan") => Ok(Instruction::ATan(a1, t)),
|
||||||
|
atom!("float") => Ok(Instruction::Float(a1, t)),
|
||||||
|
atom!("truncate") => Ok(Instruction::Truncate(a1, t)),
|
||||||
|
atom!("round") => Ok(Instruction::Round(a1, t)),
|
||||||
|
atom!("ceiling") => Ok(Instruction::Ceiling(a1, t)),
|
||||||
|
atom!("floor") => Ok(Instruction::Floor(a1, t)),
|
||||||
|
atom!("float_integer_part") => Ok(Instruction::FloatIntegerPart(a1, t)),
|
||||||
|
atom!("float_fractional_part") => Ok(Instruction::FloatFractionalPart(a1, t)),
|
||||||
|
atom!("sign") => Ok(Instruction::Sign(a1, t)),
|
||||||
|
atom!("\\") => Ok(Instruction::BitwiseComplement(a1, t)),
|
||||||
|
_ => Err(ArithmeticError::NonEvaluableFunctor(Literal::Atom(name), 1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_binary_instr(
|
||||||
|
&self,
|
||||||
|
name: Atom,
|
||||||
|
a1: ArithmeticTerm,
|
||||||
|
a2: ArithmeticTerm,
|
||||||
|
t: usize,
|
||||||
|
) -> Result<Instruction, ArithmeticError> {
|
||||||
|
match name {
|
||||||
|
atom!("+") => Ok(Instruction::Add(a1, a2, t)),
|
||||||
|
atom!("-") => Ok(Instruction::Sub(a1, a2, t)),
|
||||||
|
atom!("/") => Ok(Instruction::Div(a1, a2, t)),
|
||||||
|
atom!("//") => Ok(Instruction::IDiv(a1, a2, t)),
|
||||||
|
atom!("max") => Ok(Instruction::Max(a1, a2, t)),
|
||||||
|
atom!("min") => Ok(Instruction::Min(a1, a2, t)),
|
||||||
|
atom!("div") => Ok(Instruction::IntFloorDiv(a1, a2, t)),
|
||||||
|
atom!("rdiv") => Ok(Instruction::RDiv(a1, a2, t)),
|
||||||
|
atom!("*") => Ok(Instruction::Mul(a1, a2, t)),
|
||||||
|
atom!("**") => Ok(Instruction::Pow(a1, a2, t)),
|
||||||
|
atom!("^") => Ok(Instruction::IntPow(a1, a2, t)),
|
||||||
|
atom!(">>") => Ok(Instruction::Shr(a1, a2, t)),
|
||||||
|
atom!("<<") => Ok(Instruction::Shl(a1, a2, t)),
|
||||||
|
atom!("/\\") => Ok(Instruction::And(a1, a2, t)),
|
||||||
|
atom!("\\/") => Ok(Instruction::Or(a1, a2, t)),
|
||||||
|
atom!("xor") => Ok(Instruction::Xor(a1, a2, t)),
|
||||||
|
atom!("mod") => Ok(Instruction::Mod(a1, a2, t)),
|
||||||
|
atom!("rem") => Ok(Instruction::Rem(a1, a2, t)),
|
||||||
|
atom!("gcd") => Ok(Instruction::Gcd(a1, a2, t)),
|
||||||
|
atom!("atan2") => Ok(Instruction::ATan2(a1, a2, t)),
|
||||||
|
_ => Err(ArithmeticError::NonEvaluableFunctor(Literal::Atom(name), 2)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incr_interm(&mut self) -> usize {
|
||||||
|
let temp = self.interm_c;
|
||||||
|
|
||||||
|
self.interm.push(ArithmeticTerm::Interm(temp));
|
||||||
|
self.interm_c += 1;
|
||||||
|
|
||||||
|
temp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instr_from_clause(
|
||||||
|
&mut self,
|
||||||
|
name: Atom,
|
||||||
|
arity: usize,
|
||||||
|
) -> Result<Instruction, ArithmeticError> {
|
||||||
|
match arity {
|
||||||
|
1 => {
|
||||||
|
let a1 = self.interm.pop().unwrap();
|
||||||
|
|
||||||
|
let ninterm = if a1.interm_or(0) == 0 {
|
||||||
|
self.incr_interm()
|
||||||
|
} else {
|
||||||
|
self.interm.push(a1.clone());
|
||||||
|
a1.interm_or(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.get_unary_instr(name, a1, ninterm)
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let a2 = self.interm.pop().unwrap();
|
||||||
|
let a1 = self.interm.pop().unwrap();
|
||||||
|
|
||||||
|
let min_interm = min(a1.interm_or(0), a2.interm_or(0));
|
||||||
|
|
||||||
|
let ninterm = if min_interm == 0 {
|
||||||
|
let max_interm = max(a1.interm_or(0), a2.interm_or(0));
|
||||||
|
|
||||||
|
if max_interm == 0 {
|
||||||
|
self.incr_interm()
|
||||||
|
} else {
|
||||||
|
self.interm.push(ArithmeticTerm::Interm(max_interm));
|
||||||
|
self.interm_c = max_interm + 1;
|
||||||
|
max_interm
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.interm.push(ArithmeticTerm::Interm(min_interm));
|
||||||
|
self.interm_c = min_interm + 1;
|
||||||
|
min_interm
|
||||||
|
};
|
||||||
|
|
||||||
|
self.get_binary_instr(name, a1, a2, ninterm)
|
||||||
|
}
|
||||||
|
_ => Err(ArithmeticError::NonEvaluableFunctor(
|
||||||
|
Literal::Atom(name),
|
||||||
|
arity,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compile_is(
|
||||||
|
&mut self,
|
||||||
|
src: &'a Term,
|
||||||
|
term_loc: GenContext,
|
||||||
|
arg: usize,
|
||||||
|
) -> Result<ArithCont, ArithmeticError> {
|
||||||
|
let mut code = CodeDeque::new();
|
||||||
|
let mut iter = src.iter()?;
|
||||||
|
|
||||||
|
while let Some(term_ref) = iter.next() {
|
||||||
|
match term_ref? {
|
||||||
|
ArithTermRef::Literal(c) => push_literal(&mut self.interm, c)?,
|
||||||
|
ArithTermRef::Var(lvl, cell, name) => {
|
||||||
|
let var_num = name.to_var_num().unwrap();
|
||||||
|
|
||||||
|
let r = if lvl == Level::Shallow {
|
||||||
|
self.marker.mark_non_callable(
|
||||||
|
var_num,
|
||||||
|
arg,
|
||||||
|
term_loc,
|
||||||
|
cell,
|
||||||
|
&mut code,
|
||||||
|
)
|
||||||
|
} else if term_loc.is_last() || cell.get().norm().reg_num() == 0 {
|
||||||
|
let r = self.marker.get_binding(var_num);
|
||||||
|
|
||||||
|
if r.reg_num() == 0 {
|
||||||
|
self.marker.mark_var::<QueryInstruction>(
|
||||||
|
var_num,
|
||||||
|
lvl,
|
||||||
|
cell,
|
||||||
|
term_loc,
|
||||||
|
&mut code,
|
||||||
|
);
|
||||||
|
cell.get().norm()
|
||||||
|
} else {
|
||||||
|
self.marker.increment_running_count(var_num);
|
||||||
|
r
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.marker.increment_running_count(var_num);
|
||||||
|
cell.get().norm()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.interm.push(ArithmeticTerm::Reg(r));
|
||||||
|
}
|
||||||
|
ArithTermRef::Op(name, arity) => {
|
||||||
|
code.push_back(self.instr_from_clause(name, arity)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((code, self.interm.pop()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// integer division rounding function -- 9.1.3.1.
|
||||||
|
pub(crate) fn rnd_i<'a>(n: &'a Number, arena: &mut Arena) -> Number {
|
||||||
|
match n {
|
||||||
|
&Number::Integer(i) => {
|
||||||
|
if let Some(n) = i.to_i64() {
|
||||||
|
fixnum!(Number, n, arena)
|
||||||
|
} else {
|
||||||
|
*n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Number::Fixnum(_) => *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);
|
||||||
|
|
||||||
|
if I64_MIN_TO_F <= f && f <= I64_MAX_TO_F {
|
||||||
|
fixnum!(Number, f.into_inner() as i64, arena)
|
||||||
|
} else {
|
||||||
|
Number::Integer(arena_alloc!(Integer::from(f.0 as i64), arena))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Number::Rational(ref r) => {
|
||||||
|
let (_, floor) = (r.fract(), r.floor());
|
||||||
|
|
||||||
|
if let Some(floor) = floor.to_i64() {
|
||||||
|
fixnum!(Number, floor, arena)
|
||||||
|
} else {
|
||||||
|
Number::Integer(arena_alloc!(floor, arena))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Fixnum> for Integer {
|
||||||
|
#[inline]
|
||||||
|
fn from(n: Fixnum) -> Integer {
|
||||||
|
Integer::from(n.get_num())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// floating point rounding function -- 9.1.4.1.
|
||||||
|
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::Float(OrderedFloat(f)) => f,
|
||||||
|
&Number::Rational(ref r) => r.to_f64().value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// floating point result function -- 9.1.4.2.
|
||||||
|
pub(crate) fn result_f(n: &Number) -> Result<f64, EvalError> {
|
||||||
|
classify_float(rnd_f(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_float(f: f64) -> Result<f64, EvalError> {
|
||||||
|
match f.classify() {
|
||||||
|
FpCategory::Normal | FpCategory::Zero => Ok(f),
|
||||||
|
FpCategory::Infinite => {
|
||||||
|
if OrderedFloat(f) == OrderedFloat(f64::MAX) {
|
||||||
|
Ok(f)
|
||||||
|
} else {
|
||||||
|
Err(EvalError::FloatOverflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FpCategory::Nan => Err(EvalError::Undefined),
|
||||||
|
_ => Ok(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn float_fn_to_f(n: i64) -> Result<f64, EvalError> {
|
||||||
|
classify_float(n as f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn float_i_to_f(n: &Integer) -> Result<f64, EvalError> {
|
||||||
|
classify_float(n.to_f64().value())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn float_r_to_f(r: &Rational) -> Result<f64, EvalError> {
|
||||||
|
classify_float(r.to_f64().value())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn add_f(f1: f64, f2: f64) -> Result<OrderedFloat<f64>, EvalError> {
|
||||||
|
Ok(OrderedFloat(classify_float(f1 + f2)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn mul_f(f1: f64, f2: f64) -> Result<OrderedFloat<f64>, EvalError> {
|
||||||
|
Ok(OrderedFloat(classify_float(f1 * f2)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn div_f(f1: f64, f2: f64) -> Result<OrderedFloat<f64>, EvalError> {
|
||||||
|
if FpCategory::Zero == f2.classify() {
|
||||||
|
Err(EvalError::ZeroDivisor)
|
||||||
|
} else {
|
||||||
|
Ok(OrderedFloat(classify_float(f1 / f2)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<Number> for Number {
|
||||||
|
type Output = Result<Number, EvalError>;
|
||||||
|
|
||||||
|
fn div(self, rhs: Number) -> Self::Output {
|
||||||
|
match (self, rhs) {
|
||||||
|
(Number::Fixnum(n1), Number::Fixnum(n2)) => Ok(Number::Float(div_f(
|
||||||
|
float_fn_to_f(n1.get_num())?,
|
||||||
|
float_fn_to_f(n2.get_num())?,
|
||||||
|
)?)),
|
||||||
|
(Number::Fixnum(n1), Number::Integer(n2)) => Ok(Number::Float(div_f(
|
||||||
|
float_fn_to_f(n1.get_num())?,
|
||||||
|
float_i_to_f(&n2)?,
|
||||||
|
)?)),
|
||||||
|
(Number::Integer(n1), Number::Fixnum(n2)) => Ok(Number::Float(div_f(
|
||||||
|
float_i_to_f(&n1)?,
|
||||||
|
float_fn_to_f(n2.get_num())?,
|
||||||
|
)?)),
|
||||||
|
(Number::Fixnum(n1), Number::Rational(n2)) => Ok(Number::Float(div_f(
|
||||||
|
float_fn_to_f(n1.get_num())?,
|
||||||
|
float_r_to_f(&n2)?,
|
||||||
|
)?)),
|
||||||
|
(Number::Rational(n1), Number::Fixnum(n2)) => Ok(Number::Float(div_f(
|
||||||
|
float_r_to_f(&n1)?,
|
||||||
|
float_fn_to_f(n2.get_num())?,
|
||||||
|
)?)),
|
||||||
|
(Number::Fixnum(n1), Number::Float(OrderedFloat(n2))) => {
|
||||||
|
Ok(Number::Float(div_f(float_fn_to_f(n1.get_num())?, n2)?))
|
||||||
|
}
|
||||||
|
(Number::Float(OrderedFloat(n1)), Number::Fixnum(n2)) => {
|
||||||
|
Ok(Number::Float(div_f(n1, float_fn_to_f(n2.get_num())?)?))
|
||||||
|
}
|
||||||
|
(Number::Integer(n1), Number::Integer(n2)) => Ok(Number::Float(div_f(
|
||||||
|
float_i_to_f(&n1)?,
|
||||||
|
float_i_to_f(&n2)?,
|
||||||
|
)?)),
|
||||||
|
(Number::Integer(n1), Number::Float(OrderedFloat(n2))) => {
|
||||||
|
Ok(Number::Float(div_f(float_i_to_f(&n1)?, n2)?))
|
||||||
|
}
|
||||||
|
(Number::Float(OrderedFloat(n2)), Number::Integer(n1)) => {
|
||||||
|
Ok(Number::Float(div_f(n2, float_i_to_f(&n1)?)?))
|
||||||
|
}
|
||||||
|
(Number::Integer(n1), Number::Rational(n2)) => Ok(Number::Float(div_f(
|
||||||
|
float_i_to_f(&n1)?,
|
||||||
|
float_r_to_f(&n2)?,
|
||||||
|
)?)),
|
||||||
|
(Number::Rational(n2), Number::Integer(n1)) => Ok(Number::Float(div_f(
|
||||||
|
float_r_to_f(&n2)?,
|
||||||
|
float_i_to_f(&n1)?,
|
||||||
|
)?)),
|
||||||
|
(Number::Rational(n1), Number::Float(OrderedFloat(n2))) => {
|
||||||
|
Ok(Number::Float(div_f(float_r_to_f(&n1)?, n2)?))
|
||||||
|
}
|
||||||
|
(Number::Float(OrderedFloat(n2)), Number::Rational(n1)) => {
|
||||||
|
Ok(Number::Float(div_f(n2, float_r_to_f(&n1)?)?))
|
||||||
|
}
|
||||||
|
(Number::Float(OrderedFloat(f1)), Number::Float(OrderedFloat(f2))) => {
|
||||||
|
Ok(Number::Float(div_f(f1, f2)?))
|
||||||
|
}
|
||||||
|
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::Float(div_f(
|
||||||
|
float_r_to_f(&r1)?,
|
||||||
|
float_r_to_f(&r2)?,
|
||||||
|
)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Number {
|
||||||
|
fn eq(&self, rhs: &Self) -> bool {
|
||||||
|
match (self, rhs) {
|
||||||
|
(&Number::Fixnum(n1), &Number::Fixnum(n2)) => n1.eq(&n2),
|
||||||
|
(&Number::Fixnum(n1), &Number::Integer(ref n2)) => n1.get_num().eq(&**n2),
|
||||||
|
(&Number::Integer(ref n1), &Number::Fixnum(n2)) => (&**n1).eq(&n2.get_num()),
|
||||||
|
(&Number::Fixnum(n1), &Number::Rational(ref n2)) => n1.get_num().eq(&**n2),
|
||||||
|
(&Number::Rational(ref n1), &Number::Fixnum(n2)) => (&**n1).eq(&n2.get_num()),
|
||||||
|
(&Number::Fixnum(n1), &Number::Float(n2)) => OrderedFloat(n1.get_num() as f64).eq(&n2),
|
||||||
|
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.eq(&OrderedFloat(n2.get_num() as f64)),
|
||||||
|
(&Number::Integer(ref n1), &Number::Integer(ref n2)) => n1.eq(n2),
|
||||||
|
(&Number::Integer(ref n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(n2),
|
||||||
|
(&Number::Float(n1), &Number::Integer(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
|
||||||
|
(&Number::Integer(ref n1), &Number::Rational(ref n2)) => {
|
||||||
|
#[cfg(feature = "num")]
|
||||||
|
{
|
||||||
|
&Rational::from(&**n1) == &**n2
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "num"))]
|
||||||
|
{
|
||||||
|
&**n1 == &**n2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(&Number::Rational(ref n1), &Number::Integer(ref n2)) => {
|
||||||
|
#[cfg(feature = "num")]
|
||||||
|
{
|
||||||
|
&**n1 == &Rational::from(&**n2)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "num"))]
|
||||||
|
{
|
||||||
|
&**n1 == &**n2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(&Number::Rational(ref n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(&n2),
|
||||||
|
(&Number::Float(n1), &Number::Rational(ref n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
|
||||||
|
(&Number::Float(f1), &Number::Float(f2)) => f1.eq(&f2),
|
||||||
|
(&Number::Rational(ref r1), &Number::Rational(ref r2)) => r1.eq(&r2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Number {}
|
||||||
|
|
||||||
|
impl PartialOrd<usize> for Number {
|
||||||
|
#[inline]
|
||||||
|
fn partial_cmp(&self, rhs: &usize) -> Option<Ordering> {
|
||||||
|
match self {
|
||||||
|
Number::Fixnum(n) => {
|
||||||
|
let n = n.get_num();
|
||||||
|
|
||||||
|
if n < 0i64 {
|
||||||
|
Some(Ordering::Less)
|
||||||
|
} else {
|
||||||
|
(n as usize).partial_cmp(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Number::Integer(n) => (&**n).partial_cmp(rhs),
|
||||||
|
Number::Rational(r) => (&**r).partial_cmp(rhs),
|
||||||
|
Number::Float(f) => f.partial_cmp(&OrderedFloat(*rhs as f64)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<usize> for Number {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, rhs: &usize) -> bool {
|
||||||
|
match self {
|
||||||
|
Number::Fixnum(n) => {
|
||||||
|
let n = n.get_num();
|
||||||
|
|
||||||
|
if n < 0i64 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
(n as usize).eq(rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Number::Integer(n) => (&**n).eq(rhs),
|
||||||
|
Number::Rational(r) => (&**r).eq(rhs),
|
||||||
|
Number::Float(f) => f.eq(&OrderedFloat(*rhs as f64)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Number {
|
||||||
|
fn partial_cmp(&self, rhs: &Number) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(rhs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::Rational(n1), &Number::Fixnum(n2)) => {
|
||||||
|
(&**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)) => n1.cmp(&OrderedFloat(n2.to_f64().value())),
|
||||||
|
(&Number::Integer(n1), &Number::Rational(n2)) => {
|
||||||
|
#[cfg(feature = "num")]
|
||||||
|
{
|
||||||
|
Rational::from(&**n1).cmp(n2)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "num"))]
|
||||||
|
{
|
||||||
|
(&*n1).partial_cmp(&*n2).unwrap_or(Ordering::Less)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(&Number::Rational(n1), &Number::Integer(n2)) => {
|
||||||
|
#[cfg(feature = "num")]
|
||||||
|
{
|
||||||
|
(&**n1).cmp(&Rational::from(&**n2))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "num"))]
|
||||||
|
{
|
||||||
|
(&*n1).partial_cmp(&*n2).unwrap_or(Ordering::Less)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(&Number::Rational(n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).cmp(&n2),
|
||||||
|
(&Number::Float(n1), &Number::Rational(n2)) => n1.cmp(&OrderedFloat(n2.to_f64().value())),
|
||||||
|
(&Number::Float(f1), &Number::Float(f2)) => f1.cmp(&f2),
|
||||||
|
(&Number::Rational(r1), &Number::Rational(r2)) => (*r1).cmp(&*r2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<HeapCellValue> for Number {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(value: HeapCellValue) -> Result<Number, Self::Error> {
|
||||||
|
read_heap_cell!(value,
|
||||||
|
(HeapCellValueTag::Cons, c) => {
|
||||||
|
match_untyped_arena_ptr!(c,
|
||||||
|
(ArenaHeaderTag::Integer, n) => {
|
||||||
|
Ok(Number::Integer(n))
|
||||||
|
}
|
||||||
|
(ArenaHeaderTag::Rational, n) => {
|
||||||
|
Ok(Number::Rational(n))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
(HeapCellValueTag::F64, n) => {
|
||||||
|
Ok(Number::Float(*n))
|
||||||
|
}
|
||||||
|
(HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
|
||||||
|
Ok(Number::Fixnum(n))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes n ^ power. Ignores the sign of power.
|
||||||
|
pub(crate) fn binary_pow(mut n: Integer, power: &Integer) -> Integer {
|
||||||
|
let mut power = Integer::from(power.abs());
|
||||||
|
|
||||||
|
if power == 0 {
|
||||||
|
return Integer::from(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut oddand = Integer::from(1);
|
||||||
|
|
||||||
|
while power > 1 {
|
||||||
|
if power.is_odd() {
|
||||||
|
oddand *= &n;
|
||||||
|
}
|
||||||
|
|
||||||
|
n = n.pow(2);
|
||||||
|
power >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
n * oddand
|
||||||
|
}
|
||||||
401
src/atom_table.rs
Normal file
401
src/atom_table.rs
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
use crate::parser::ast::MAX_ARITY;
|
||||||
|
use crate::raw_block::*;
|
||||||
|
use crate::types::*;
|
||||||
|
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::mem;
|
||||||
|
use std::ptr;
|
||||||
|
use std::slice;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
|
||||||
|
use modular_bitfield::prelude::*;
|
||||||
|
|
||||||
|
#[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"));
|
||||||
|
|
||||||
|
impl<'a> From<&'a Atom> for Atom {
|
||||||
|
#[inline]
|
||||||
|
fn from(atom: &'a Atom) -> Self {
|
||||||
|
*atom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Atom {
|
||||||
|
#[inline]
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
if value { atom!("true") } else { atom!("false") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ATOM_TABLE_INIT_SIZE: usize = 1 << 16;
|
||||||
|
const ATOM_TABLE_ALIGN: usize = 8;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
thread_local! {
|
||||||
|
static ATOM_TABLE_BUF_BASE: std::cell::RefCell<*const u8> = std::cell::RefCell::new(ptr::null_mut());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(test))]
|
||||||
|
static ATOM_TABLE_BUF_BASE: std::sync::atomic::AtomicPtr<u8> =
|
||||||
|
std::sync::atomic::AtomicPtr::new(ptr::null_mut());
|
||||||
|
|
||||||
|
fn set_atom_tbl_buf_base(old_ptr: *const u8, new_ptr: *const u8) -> Result<(), *const u8> {
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
ATOM_TABLE_BUF_BASE.with(|atom_table_buf_base| {
|
||||||
|
let mut borrow = atom_table_buf_base.borrow_mut();
|
||||||
|
if *borrow != old_ptr {
|
||||||
|
Err(*borrow)
|
||||||
|
} else {
|
||||||
|
*borrow = new_ptr;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
};
|
||||||
|
#[cfg(not(test))]
|
||||||
|
{
|
||||||
|
ATOM_TABLE_BUF_BASE
|
||||||
|
.compare_exchange(
|
||||||
|
old_ptr.cast_mut(),
|
||||||
|
new_ptr.cast_mut(),
|
||||||
|
std::sync::atomic::Ordering::Relaxed,
|
||||||
|
std::sync::atomic::Ordering::Relaxed,
|
||||||
|
)
|
||||||
|
.map_err(|ptr| ptr.cast_const())
|
||||||
|
}?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_atom_tbl_buf_base() -> *const u8 {
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
ATOM_TABLE_BUF_BASE.with(|atom_table_buf_base| *atom_table_buf_base.borrow())
|
||||||
|
}
|
||||||
|
#[cfg(not(test))]
|
||||||
|
{
|
||||||
|
ATOM_TABLE_BUF_BASE.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Overwriting atom table base pointer")]
|
||||||
|
fn atomtable_is_not_concurrency_safe() {
|
||||||
|
let _table_a = AtomTable::new();
|
||||||
|
let _table_b = AtomTable::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawBlockTraits for AtomTable {
|
||||||
|
#[inline]
|
||||||
|
fn init_size() -> usize {
|
||||||
|
ATOM_TABLE_INIT_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn align() -> usize {
|
||||||
|
ATOM_TABLE_ALIGN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
struct AtomHeader {
|
||||||
|
#[allow(unused)] m: bool,
|
||||||
|
len: B50,
|
||||||
|
#[allow(unused)] padding: B13,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomHeader {
|
||||||
|
fn build_with(len: u64) -> Self {
|
||||||
|
AtomHeader::new().with_len(len).with_m(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for Atom {
|
||||||
|
#[inline]
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self.as_str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Atom {
|
||||||
|
#[inline]
|
||||||
|
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||||
|
self.as_str().hash(hasher)
|
||||||
|
// hasher.write_usize(self.index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! is_char {
|
||||||
|
($s:expr) => {
|
||||||
|
!$s.is_empty() && $s.chars().nth(1).is_none()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Atom {
|
||||||
|
#[inline]
|
||||||
|
pub fn buf(self) -> *const u8 {
|
||||||
|
let ptr = self.as_ptr();
|
||||||
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
return ptr::null();
|
||||||
|
}
|
||||||
|
|
||||||
|
(ptr as usize + mem::size_of::<AtomHeader>()) as *const u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_static(self) -> bool {
|
||||||
|
(self.index as usize) < STRINGS.len() << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_ptr(self) -> *const u8 {
|
||||||
|
if self.is_static() {
|
||||||
|
ptr::null()
|
||||||
|
} else {
|
||||||
|
(get_atom_tbl_buf_base() as usize + (self.index as usize) - (STRINGS.len() << 3)) as *const u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from(index: u64) -> Self {
|
||||||
|
Self { index }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn len(self) -> usize {
|
||||||
|
if self.is_static() {
|
||||||
|
STRINGS[(self.index >> 3) as usize].len()
|
||||||
|
} else {
|
||||||
|
unsafe { ptr::read(self.as_ptr() as *const AtomHeader).len() as _ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn flat_index(self) -> u64 {
|
||||||
|
(self.index >> 3) as u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_char(self) -> Option<char> {
|
||||||
|
let s = self.as_str();
|
||||||
|
let mut it = s.chars();
|
||||||
|
|
||||||
|
let c1 = it.next();
|
||||||
|
let c2 = it.next();
|
||||||
|
|
||||||
|
if c2.is_none() { c1 } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn chars(&self) -> str::Chars {
|
||||||
|
self.as_str().chars()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
unsafe {
|
||||||
|
let ptr = self.as_ptr();
|
||||||
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
return STRINGS[(self.index >> 3) as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = ptr::read::<AtomHeader>(ptr as *const _);
|
||||||
|
let len = header.len() as usize;
|
||||||
|
let buf = (ptr as usize + mem::size_of::<AtomHeader>()) as *mut u8;
|
||||||
|
|
||||||
|
str::from_utf8_unchecked(slice::from_raw_parts(buf, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defrock_brackets(&self, atom_tbl: &mut AtomTable) -> Self {
|
||||||
|
let s = self.as_str();
|
||||||
|
|
||||||
|
let s = if s.starts_with('(') && s.ends_with(')') {
|
||||||
|
&s['('.len_utf8()..s.len() - ')'.len_utf8()]
|
||||||
|
} else {
|
||||||
|
return *self;
|
||||||
|
};
|
||||||
|
|
||||||
|
atom_tbl.build_with(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Atom {
|
||||||
|
#[inline]
|
||||||
|
fn partial_cmp(&self, other: &Atom) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Atom {
|
||||||
|
#[inline]
|
||||||
|
fn cmp(&self, other: &Atom) -> Ordering {
|
||||||
|
self.as_str().cmp(other.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AtomTable {
|
||||||
|
block: RawBlock<AtomTable>,
|
||||||
|
pub table: IndexSet<Atom>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cold]
|
||||||
|
fn atom_table_base_pointer_mismatch(expected: *const u8, got: *const u8) -> ! {
|
||||||
|
assert_eq!(expected, got, "Overwriting atom table base pointer, expected old value to be {expected:p}, but found {got:p}");
|
||||||
|
unreachable!("This should only be called in a case of a mismatch as such the assert_eq should have failed!")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AtomTable {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Err(got) = set_atom_tbl_buf_base(self.block.base, ptr::null()) {
|
||||||
|
atom_table_base_pointer_mismatch(self.block.base, got);
|
||||||
|
}
|
||||||
|
self.block.deallocate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomTable {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut block = RawBlock::new();
|
||||||
|
|
||||||
|
if let Err(got) = set_atom_tbl_buf_base(ptr::null(), block.base) {
|
||||||
|
block.deallocate();
|
||||||
|
atom_table_base_pointer_mismatch(ptr::null(), got);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
block,
|
||||||
|
table: IndexSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn buf(&self) -> *const u8 {
|
||||||
|
self.block.base as *const u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn top(&self) -> *const u8 {
|
||||||
|
self.block.top
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn lookup_str(&self, string: &str) -> Option<Atom> {
|
||||||
|
STATIC_ATOMS_MAP.get(string).or_else(|| self.table.get(string)).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_with(&mut self, string: &str) -> Atom {
|
||||||
|
if let Some(atom) = self.lookup_str(string) {
|
||||||
|
return atom;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let size = mem::size_of::<AtomHeader>() + string.len();
|
||||||
|
let align_offset = 8 * mem::align_of::<AtomHeader>();
|
||||||
|
let size = (size & !(align_offset - 1)) + align_offset;
|
||||||
|
|
||||||
|
let len_ptr = {
|
||||||
|
let mut ptr;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
ptr = self.block.alloc(size);
|
||||||
|
|
||||||
|
if ptr.is_null() {
|
||||||
|
let old_base = self.block.base;
|
||||||
|
self.block.grow();
|
||||||
|
if let Err(got) = set_atom_tbl_buf_base(old_base, self.block.base) {
|
||||||
|
atom_table_base_pointer_mismatch(old_base, got);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr
|
||||||
|
};
|
||||||
|
|
||||||
|
let ptr_base = self.block.base as usize;
|
||||||
|
|
||||||
|
write_to_ptr(string, len_ptr);
|
||||||
|
|
||||||
|
let atom = Atom {
|
||||||
|
index: ((STRINGS.len() << 3) + len_ptr as usize - ptr_base) as u64,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.table.insert(atom);
|
||||||
|
|
||||||
|
atom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitfield]
|
||||||
|
#[repr(u64)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct AtomCell {
|
||||||
|
name: B46,
|
||||||
|
arity: B10,
|
||||||
|
#[allow(unused)] f: bool,
|
||||||
|
#[allow(unused)] m: bool,
|
||||||
|
#[allow(unused)] tag: B6,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtomCell {
|
||||||
|
#[inline]
|
||||||
|
pub fn build_with(name: u64, arity: u16, tag: HeapCellValueTag) -> Self {
|
||||||
|
if arity > 0 {
|
||||||
|
debug_assert!(arity as usize <= MAX_ARITY);
|
||||||
|
|
||||||
|
AtomCell::new()
|
||||||
|
.with_name(name)
|
||||||
|
.with_arity(arity)
|
||||||
|
.with_f(false)
|
||||||
|
.with_tag(tag as u8)
|
||||||
|
} else {
|
||||||
|
AtomCell::new()
|
||||||
|
.with_name(name)
|
||||||
|
.with_f(false)
|
||||||
|
.with_tag(tag as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_index(self) -> usize {
|
||||||
|
self.name() as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_name(self) -> Atom {
|
||||||
|
Atom::from((self.get_index() 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/bin/scryer-prolog.rs
Normal file
12
src/bin/scryer-prolog.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
fn main() -> std::process::ExitCode {
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
use scryer_prolog::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "repl")]
|
||||||
|
ctrlc::set_handler(move || {
|
||||||
|
scryer_prolog::machine::INTERRUPT.store(true, Ordering::Relaxed);
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
let mut wam = machine::Machine::new();
|
||||||
|
wam.run_top_level()
|
||||||
|
}
|
||||||
1239
src/codegen.rs
Normal file
1239
src/codegen.rs
Normal file
File diff suppressed because it is too large
Load Diff
847
src/debray_allocator.rs
Normal file
847
src/debray_allocator.rs
Normal file
@@ -0,0 +1,847 @@
|
|||||||
|
use crate::allocator::*;
|
||||||
|
use crate::codegen::SubsumedBranchHits;
|
||||||
|
use crate::forms::Level;
|
||||||
|
use crate::instructions::*;
|
||||||
|
use crate::machine::disjuncts::VarData;
|
||||||
|
use crate::parser::ast::*;
|
||||||
|
use crate::targets::*;
|
||||||
|
use crate::variable_records::*;
|
||||||
|
|
||||||
|
use bit_set::*;
|
||||||
|
use bitvec::prelude::*;
|
||||||
|
use fxhash::FxBuildHasher;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
pub type BranchHits = IndexMap<usize, BitVec, FxBuildHasher>; // key: var_num, value: branch arm occurrences.
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct BranchOccurrences {
|
||||||
|
pub hits: BranchHits,
|
||||||
|
pub shallow_safety: BitSet<usize>, // unset means safe, set means unsafe (after the branch merge)
|
||||||
|
pub deep_safety: BitSet<usize>,
|
||||||
|
pub num_branches: usize,
|
||||||
|
pub current_branch: usize,
|
||||||
|
pub subsumed_hits: SubsumedBranchHits,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BranchOccurrences {
|
||||||
|
fn new(num_branches: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
hits: BranchHits::with_hasher(FxBuildHasher::default()),
|
||||||
|
shallow_safety: BitSet::default(),
|
||||||
|
deep_safety: BitSet::default(),
|
||||||
|
num_branches,
|
||||||
|
current_branch: 0,
|
||||||
|
subsumed_hits: SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct BranchStack {
|
||||||
|
stack: Vec<BranchOccurrences>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for BranchStack {
|
||||||
|
type Target = Vec<BranchOccurrences>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for BranchStack {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BranchStack {
|
||||||
|
fn branch_subsumes(&self, branch: &BranchDesignator, sub_branch: &BranchDesignator) -> bool {
|
||||||
|
if branch.branch_stack_num < sub_branch.branch_stack_num {
|
||||||
|
if branch.branch_stack_num == 0 {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let idx = branch.branch_stack_num - 1;
|
||||||
|
self[idx].current_branch == branch.branch_num
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
branch == sub_branch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn safety_unneeded_in_branch(&self, safety: &VarSafetyStatus, branch: &BranchDesignator) -> bool {
|
||||||
|
match safety {
|
||||||
|
VarSafetyStatus::Needed => false,
|
||||||
|
VarSafetyStatus::LocallyUnneeded(planter_branch) =>
|
||||||
|
self.branch_subsumes(planter_branch, branch),
|
||||||
|
VarSafetyStatus::GloballyUnneeded => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
|
||||||
|
if let Some(occurrences) = self.last_mut() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_branch_stack(&mut self, num_branches: usize) {
|
||||||
|
self.push(BranchOccurrences::new(num_branches));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn current_branch_designator(&self) -> BranchDesignator {
|
||||||
|
let branch_stack_num = self.len();
|
||||||
|
let branch_num = self.last()
|
||||||
|
.map(|occurrences| occurrences.current_branch)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
BranchDesignator { branch_stack_num, branch_num }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn incr_current_branch(&mut self) {
|
||||||
|
let branch_occurrences = self.last_mut().unwrap();
|
||||||
|
branch_occurrences.current_branch += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn drain_branches(&mut self, depth: usize) -> std::vec::Drain<BranchOccurrences> {
|
||||||
|
let start_idx = self.len() - depth;
|
||||||
|
self.drain(start_idx ..)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct DebrayAllocator {
|
||||||
|
pub(crate) var_data: VarData, // var_data replaces bindings.
|
||||||
|
pub(crate) branch_stack: BranchStack,
|
||||||
|
pub(crate) in_tail_position: bool,
|
||||||
|
arg_c: usize,
|
||||||
|
temp_lb: usize,
|
||||||
|
perm_lb: usize,
|
||||||
|
arity: usize, // 0 if not at head.
|
||||||
|
shallow_temp_mappings: IndexMap<usize, usize, FxBuildHasher>,
|
||||||
|
in_use: BitSet<usize>, // deep and non-var allocations
|
||||||
|
temp_free_list: Vec<usize>,
|
||||||
|
perm_free_list: VecDeque<(usize, usize)>, // chunk_num, var_num
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebrayAllocator {
|
||||||
|
pub(crate) fn add_branch(&mut self) {
|
||||||
|
let branch_designator = self.branch_stack.current_branch_designator();
|
||||||
|
let subsumed_hits = {
|
||||||
|
let branch_occurrences = self.branch_stack.last_mut().unwrap();
|
||||||
|
|
||||||
|
std::mem::replace(
|
||||||
|
&mut branch_occurrences.subsumed_hits,
|
||||||
|
SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
for var_num in subsumed_hits {
|
||||||
|
match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(_, ref mut allocation) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*allocation = PermVarAllocation::Pending;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pop_branch(&mut self, depth: usize, subsumed_hits: SubsumedBranchHits) {
|
||||||
|
let removed_branches = self.branch_stack.drain_branches(depth);
|
||||||
|
|
||||||
|
let (deep_safety, shallow_safety) = removed_branches
|
||||||
|
.into_iter()
|
||||||
|
.fold((BitSet::default(), BitSet::default()),
|
||||||
|
|(mut deep_safety, mut shallow_safety), branch_occurrences| {
|
||||||
|
deep_safety.union_with(&branch_occurrences.deep_safety);
|
||||||
|
shallow_safety.union_with(&branch_occurrences.shallow_safety);
|
||||||
|
|
||||||
|
(deep_safety, shallow_safety)
|
||||||
|
});
|
||||||
|
|
||||||
|
let branch_designator = self.branch_stack.current_branch_designator();
|
||||||
|
|
||||||
|
let (deep_safety, shallow_safety) = match self.branch_stack.last_mut() {
|
||||||
|
Some(latest_branch) => {
|
||||||
|
latest_branch.deep_safety.union_with(&deep_safety);
|
||||||
|
latest_branch.shallow_safety.union_with(&shallow_safety);
|
||||||
|
|
||||||
|
(&latest_branch.deep_safety, &latest_branch.shallow_safety)
|
||||||
|
}
|
||||||
|
None => (&deep_safety, &shallow_safety)
|
||||||
|
};
|
||||||
|
|
||||||
|
for var_num in subsumed_hits.iter().cloned() {
|
||||||
|
let running_count = self.var_data.records[var_num].running_count;
|
||||||
|
let num_occurrences = self.var_data.records[var_num].num_occurrences;
|
||||||
|
|
||||||
|
match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(_, allocation) => {
|
||||||
|
let shallow_safety = VarSafetyStatus::needed_if(
|
||||||
|
shallow_safety.contains(var_num),
|
||||||
|
branch_designator,
|
||||||
|
);
|
||||||
|
|
||||||
|
let deep_safety = VarSafetyStatus::needed_if(
|
||||||
|
deep_safety.contains(var_num),
|
||||||
|
branch_designator,
|
||||||
|
);
|
||||||
|
|
||||||
|
if running_count < num_occurrences {
|
||||||
|
*allocation = PermVarAllocation::Done { shallow_safety, deep_safety };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.branch_stack.len() > 0 {
|
||||||
|
for var_num in subsumed_hits {
|
||||||
|
self.branch_stack.add_branch_occurrence(var_num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_curr_arg_distinct_from(&self, var_num: usize) -> bool {
|
||||||
|
match self.shallow_temp_mappings.get(&self.arg_c).cloned() {
|
||||||
|
Some(t_var) => t_var != var_num,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn occurs_shallowly_in_head(&self, var_num: usize, r: usize) -> bool {
|
||||||
|
match &self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Temp { temp_var_data, term_loc: GenContext::Head, .. } => {
|
||||||
|
temp_var_data.use_set.contains(&(GenContext::Head, r))
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_in_use(&self, r: usize) -> bool {
|
||||||
|
let in_use_range = r <= self.arity && r >= self.arg_c;
|
||||||
|
in_use_range || self.in_use.contains(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_with_cr(&self, var_num: usize) -> usize {
|
||||||
|
match &self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Temp { temp_var_data, .. } => {
|
||||||
|
for &(_, reg) in temp_var_data.use_set.iter() {
|
||||||
|
if !self.is_in_use(reg) {
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_with_ca(&self, var_num: usize) -> usize {
|
||||||
|
match &self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Temp { temp_var_data, .. } => {
|
||||||
|
for &(_, reg) in temp_var_data.use_set.iter() {
|
||||||
|
if !self.is_in_use(reg) {
|
||||||
|
return reg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_in_last_goal_hint(&self, chunk_num: usize) -> Option<(usize, usize)> {
|
||||||
|
// we want to allocate a register to the k^{th} parameter, par_k.
|
||||||
|
// par_k may not be a temporary variable.
|
||||||
|
let k = self.arg_c;
|
||||||
|
|
||||||
|
match self.shallow_temp_mappings.get(&k).cloned() {
|
||||||
|
Some(t_var) => {
|
||||||
|
// suppose this branch fires. then t_var is a
|
||||||
|
// temp. var. belonging to the current chunk.
|
||||||
|
// consider its use set. T == par_k iff
|
||||||
|
// (GenContext::Last(_), k) is in t_var.use_set.
|
||||||
|
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evacuate_arg<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
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 r != k {
|
||||||
|
let r = RegType::Temp(r);
|
||||||
|
|
||||||
|
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.var_data.records[var_num].allocation.set_register(r.reg_num());
|
||||||
|
self.in_use.insert(r.reg_num());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_reg_to_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
var_num: usize,
|
||||||
|
lvl: Level,
|
||||||
|
term_loc: GenContext,
|
||||||
|
target: &mut CodeDeque,
|
||||||
|
) -> usize {
|
||||||
|
match term_loc {
|
||||||
|
GenContext::Head => {
|
||||||
|
if let Level::Shallow = lvl {
|
||||||
|
self.evacuate_arg::<Target>(0, target);
|
||||||
|
self.alloc_with_cr(var_num)
|
||||||
|
} else {
|
||||||
|
self.alloc_with_ca(var_num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GenContext::Mid(_) => self.alloc_with_ca(var_num),
|
||||||
|
GenContext::Last(chunk_num) => {
|
||||||
|
if let Level::Shallow = lvl {
|
||||||
|
self.evacuate_arg::<Target>(chunk_num, target);
|
||||||
|
self.alloc_with_cr(var_num)
|
||||||
|
} else {
|
||||||
|
self.alloc_with_ca(var_num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_reg_to_non_var(&mut self) -> usize {
|
||||||
|
let mut final_index = 0;
|
||||||
|
|
||||||
|
while let Some(r) = self.temp_free_list.pop() {
|
||||||
|
if !self.is_in_use(r) {
|
||||||
|
self.in_use.insert(r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for index in self.temp_lb.. {
|
||||||
|
if !self.in_use.contains(index) {
|
||||||
|
final_index = index;
|
||||||
|
self.in_use.insert(final_index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.temp_lb = final_index + 1;
|
||||||
|
final_index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_place(&self, var_num: usize, term_loc: GenContext, r: RegType, k: usize) -> bool {
|
||||||
|
match term_loc {
|
||||||
|
GenContext::Head if !r.is_perm() => r.reg_num() == k,
|
||||||
|
_ => {
|
||||||
|
match &self.var_data.records[var_num].allocation {
|
||||||
|
&VarAlloc::Temp { temp_reg, .. } if r.reg_num() == k =>
|
||||||
|
temp_reg == k,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_perm_var(&mut self, var_num: usize, chunk_num: usize) -> usize {
|
||||||
|
let p = if let Some(p) = self.pop_free_perm(chunk_num) {
|
||||||
|
p
|
||||||
|
} else {
|
||||||
|
let p = self.perm_lb;
|
||||||
|
self.perm_lb += 1;
|
||||||
|
|
||||||
|
p
|
||||||
|
};
|
||||||
|
|
||||||
|
self.var_data.records[var_num].allocation = VarAlloc::Perm(p, PermVarAllocation::done());
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_reg_to_free_list(&mut self, r: RegType) {
|
||||||
|
if let RegType::Temp(r) = r {
|
||||||
|
self.in_use.remove(r);
|
||||||
|
self.temp_free_list.push(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_free_list(&mut self) {
|
||||||
|
self.temp_free_list.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_binding(&self, var_num: usize) -> RegType {
|
||||||
|
self.var_data.records[var_num].allocation.as_reg_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn num_perm_vars(&self) -> usize {
|
||||||
|
self.perm_lb - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn increment_running_count(&mut self, var_num: usize) {
|
||||||
|
self.var_data.records[var_num].running_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_perm_to_free_list(&mut self, chunk_num: usize, var_num: usize) {
|
||||||
|
match &self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(..) => {
|
||||||
|
self.perm_free_list.push_back((chunk_num, var_num));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_free_perm(&mut self, chunk_num: usize) -> Option<usize> {
|
||||||
|
while let Some((perm_chunk_num, var_num)) = self.perm_free_list.front().cloned() {
|
||||||
|
if chunk_num > perm_chunk_num {
|
||||||
|
self.perm_free_list.pop_front();
|
||||||
|
|
||||||
|
match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(p, PermVarAllocation::Pending) if *p > 0 => {
|
||||||
|
return Some(std::mem::replace(p, 0));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn free_var(&mut self, chunk_num: usize, var_num: usize) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mark_safe_var_unconditionally(&mut self, var_num: usize) {
|
||||||
|
let branch_designator = self.branch_stack.current_branch_designator();
|
||||||
|
|
||||||
|
match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, shallow_safety, .. }) => {
|
||||||
|
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
}
|
||||||
|
VarAlloc::Temp { safety, .. } => {
|
||||||
|
*safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_safe_var(&mut self, var_num: usize, lvl: Level, term_loc: GenContext) {
|
||||||
|
let branch_designator = self.branch_stack.current_branch_designator();
|
||||||
|
|
||||||
|
match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, shallow_safety, .. }) => {
|
||||||
|
// GetVariable in head chunk is considered safe.
|
||||||
|
if lvl == Level::Deep {
|
||||||
|
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
} else if term_loc == GenContext::Head {
|
||||||
|
*shallow_safety = VarSafetyStatus::GloballyUnneeded;
|
||||||
|
} else {
|
||||||
|
if let Some(temp_var_num) = self.shallow_temp_mappings.get(&self.arg_c).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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VarAlloc::Temp { ref mut safety, .. } => {
|
||||||
|
*safety = VarSafetyStatus::GloballyUnneeded;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn argument_to_value<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
var_num: usize,
|
||||||
|
r: RegType,
|
||||||
|
arg_c: usize,
|
||||||
|
) -> Instruction {
|
||||||
|
let branch_designator = self.branch_stack.current_branch_designator();
|
||||||
|
|
||||||
|
match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(_, PermVarAllocation::Done { ref mut shallow_safety, .. }) => {
|
||||||
|
if !self.in_tail_position || self.branch_stack.safety_unneeded_in_branch(shallow_safety, &branch_designator) {
|
||||||
|
Target::argument_to_value(r, arg_c)
|
||||||
|
} else {
|
||||||
|
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
Target::unsafe_argument_to_value(r, arg_c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VarAlloc::Temp { 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subterm_to_value<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
var_num: usize,
|
||||||
|
r: RegType,
|
||||||
|
) -> Instruction {
|
||||||
|
let branch_designator = self.branch_stack.current_branch_designator();
|
||||||
|
|
||||||
|
match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(_, PermVarAllocation::Done { ref mut deep_safety, .. }) => {
|
||||||
|
if self.branch_stack.safety_unneeded_in_branch(deep_safety, &branch_designator) {
|
||||||
|
Target::subterm_to_value(r)
|
||||||
|
} else {
|
||||||
|
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
Target::unsafe_subterm_to_value(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VarAlloc::Temp { ref mut safety, .. } => {
|
||||||
|
if self.branch_stack.safety_unneeded_in_branch(safety, &branch_designator) {
|
||||||
|
Target::subterm_to_value(r)
|
||||||
|
} else {
|
||||||
|
*safety = VarSafetyStatus::unneeded(branch_designator);
|
||||||
|
Target::unsafe_subterm_to_value(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Allocator for DebrayAllocator {
|
||||||
|
fn new() -> DebrayAllocator {
|
||||||
|
Self {
|
||||||
|
var_data: VarData::default(),
|
||||||
|
in_tail_position: false,
|
||||||
|
arity: 0,
|
||||||
|
arg_c: 1,
|
||||||
|
temp_lb: 1,
|
||||||
|
perm_lb: 1,
|
||||||
|
shallow_temp_mappings: IndexMap::with_hasher(FxBuildHasher::default()),
|
||||||
|
in_use: BitSet::default(),
|
||||||
|
temp_free_list: vec![],
|
||||||
|
perm_free_list: VecDeque::new(),
|
||||||
|
branch_stack: BranchStack { stack: vec![] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_anon_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
lvl: Level,
|
||||||
|
term_loc: GenContext,
|
||||||
|
code: &mut CodeDeque,
|
||||||
|
) {
|
||||||
|
let r = RegType::Temp(self.alloc_reg_to_non_var());
|
||||||
|
|
||||||
|
match lvl {
|
||||||
|
Level::Deep => code.push_back(Target::subterm_to_variable(r)),
|
||||||
|
Level::Root | Level::Shallow => {
|
||||||
|
let k = self.arg_c;
|
||||||
|
|
||||||
|
if let GenContext::Last(chunk_num) = term_loc {
|
||||||
|
self.evacuate_arg::<Target>(chunk_num, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.arg_c += 1;
|
||||||
|
|
||||||
|
code.push_back(Target::argument_to_variable(r, k));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_non_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
lvl: Level,
|
||||||
|
term_loc: GenContext,
|
||||||
|
cell: &'a Cell<RegType>,
|
||||||
|
code: &mut CodeDeque,
|
||||||
|
) {
|
||||||
|
let r = cell.get();
|
||||||
|
|
||||||
|
let r = match lvl {
|
||||||
|
Level::Shallow => {
|
||||||
|
let k = self.arg_c;
|
||||||
|
|
||||||
|
if let GenContext::Last(chunk_num) = term_loc {
|
||||||
|
self.evacuate_arg::<Target>(chunk_num, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.arg_c += 1;
|
||||||
|
RegType::Temp(k)
|
||||||
|
}
|
||||||
|
_ if r.reg_num() == 0 => RegType::Temp(self.alloc_reg_to_non_var()),
|
||||||
|
_ => {
|
||||||
|
self.in_use.insert(r.reg_num());
|
||||||
|
r
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cell.set(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
var_num: usize,
|
||||||
|
lvl: Level,
|
||||||
|
cell: &'a Cell<VarReg>,
|
||||||
|
term_loc: GenContext,
|
||||||
|
code: &mut CodeDeque,
|
||||||
|
) {
|
||||||
|
let (r, is_new_var) = match self.get_binding(var_num) {
|
||||||
|
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());
|
||||||
|
(RegType::Perm(p), true)
|
||||||
|
}
|
||||||
|
r @ RegType::Perm(_) => {
|
||||||
|
let is_new_var = match &mut self.var_data.records[var_num].allocation {
|
||||||
|
VarAlloc::Perm(_, allocation) => if allocation.pending() {
|
||||||
|
*allocation = PermVarAllocation::done();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
(r, is_new_var)
|
||||||
|
}
|
||||||
|
r => (r, false),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.mark_reserved_var::<Target>(var_num, lvl, cell, term_loc, code, r, is_new_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_reserved_var<'a, Target: CompilationTarget<'a>>(
|
||||||
|
&mut self,
|
||||||
|
var_num: usize,
|
||||||
|
lvl: Level,
|
||||||
|
cell: &'a Cell<VarReg>,
|
||||||
|
term_loc: GenContext,
|
||||||
|
code: &mut CodeDeque,
|
||||||
|
r: RegType,
|
||||||
|
is_new_var: bool,
|
||||||
|
) {
|
||||||
|
match lvl {
|
||||||
|
Level::Root | Level::Shallow => {
|
||||||
|
let k = self.arg_c;
|
||||||
|
|
||||||
|
if self.is_curr_arg_distinct_from(var_num) {
|
||||||
|
self.evacuate_arg::<Target>(term_loc.chunk_num(), code);
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.set(VarReg::ArgAndNorm(r, k));
|
||||||
|
|
||||||
|
if !self.in_place(var_num, term_loc, r, k) {
|
||||||
|
if is_new_var {
|
||||||
|
self.mark_safe_var(var_num, lvl, term_loc);
|
||||||
|
code.push_back(Target::argument_to_variable(r, k));
|
||||||
|
} else {
|
||||||
|
code.push_back(self.argument_to_value::<Target>(var_num, r, k));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.arg_c += 1;
|
||||||
|
}
|
||||||
|
Level::Deep if is_new_var => {
|
||||||
|
if let GenContext::Head = term_loc {
|
||||||
|
if self.occurs_shallowly_in_head(var_num, r.reg_num()) {
|
||||||
|
code.push_back(self.subterm_to_value::<Target>(var_num, r));
|
||||||
|
} else {
|
||||||
|
self.mark_safe_var(var_num, lvl, term_loc);
|
||||||
|
code.push_back(Target::subterm_to_variable(r));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.mark_safe_var(var_num, lvl, term_loc);
|
||||||
|
code.push_back(Target::subterm_to_variable(r));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Level::Deep => code.push_back(self.subterm_to_value::<Target>(var_num, r)),
|
||||||
|
}
|
||||||
|
|
||||||
|
let o = r.reg_num();
|
||||||
|
|
||||||
|
if !r.is_perm() {
|
||||||
|
self.shallow_temp_mappings.insert(o, var_num);
|
||||||
|
} else if r.is_perm() && is_new_var {
|
||||||
|
self.branch_stack.add_branch_occurrence(var_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
let record = &mut self.var_data.records[var_num];
|
||||||
|
|
||||||
|
record.allocation.set_register(o);
|
||||||
|
|
||||||
|
if record.running_count < record.num_occurrences {
|
||||||
|
record.running_count += 1;
|
||||||
|
} else {
|
||||||
|
self.free_var(term_loc.chunk_num(), var_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.in_use.insert(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType {
|
||||||
|
match self.get_binding(var_num) {
|
||||||
|
RegType::Perm(0) | RegType::Temp(0) => {
|
||||||
|
RegType::Perm(self.alloc_perm_var(var_num, chunk_num))
|
||||||
|
}
|
||||||
|
r => r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.perm_lb = 1;
|
||||||
|
self.shallow_temp_mappings.clear();
|
||||||
|
self.in_use.clear();
|
||||||
|
self.temp_free_list.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_contents(&mut self) {
|
||||||
|
self.in_use.clear();
|
||||||
|
self.shallow_temp_mappings.clear();
|
||||||
|
self.temp_free_list.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance_arg(&mut self) {
|
||||||
|
self.arg_c += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_at_head(&mut self, args: &Vec<Term>) {
|
||||||
|
self.reset_arg(args.len());
|
||||||
|
self.arity = args.len();
|
||||||
|
|
||||||
|
for (idx, arg) in args.iter().enumerate() {
|
||||||
|
if let &Term::Var(_, ref var) = arg {
|
||||||
|
let var_num = var.to_var_num().unwrap();
|
||||||
|
let r = self.get_binding(var_num);
|
||||||
|
|
||||||
|
if !r.is_perm() && r.reg_num() == 0 {
|
||||||
|
self.in_use.insert(idx + 1);
|
||||||
|
self.shallow_temp_mappings.insert(idx + 1, var_num);
|
||||||
|
self.var_data.records[var_num].allocation.set_register(idx + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_arg(&mut self, arity: usize) {
|
||||||
|
self.arity = 0;
|
||||||
|
self.arg_c = 1;
|
||||||
|
self.temp_lb = arity + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn max_reg_allocated(&self) -> usize {
|
||||||
|
std::cmp::max(self.temp_lb, self.arg_c)
|
||||||
|
}
|
||||||
|
}
|
||||||
219
src/examples/bimetatrans/README.md
Normal file
219
src/examples/bimetatrans/README.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
### BiMetaTrans 1.0
|
||||||
|
|
||||||
|
BiMetaTrans(Prolog, RuleML) is a bidirectional translator capable of
|
||||||
|
parsing and generating a well-formed sublanguage of RuleML/XML to/from
|
||||||
|
a metalogical encoding of our own design called Prolog/'$V'. For later
|
||||||
|
ease of reference:
|
||||||
|
|
||||||
|
* Prolog/'$V' stands for "Metalogic Prolog with variable-as-'$V'-term encoding"
|
||||||
|
* RuleML/XML stands for "RuleML in XML with any kind of rendering"
|
||||||
|
* RuleML/xmL stands for "RuleML in minified XML"
|
||||||
|
* RuleML/Xml stands for "RuleML in indented XML"
|
||||||
|
|
||||||
|
"Minified XML" refers to XML with only necessary whitespace, between
|
||||||
|
element names and attributes, and pairs of attributes, with all
|
||||||
|
indentation stripped out. "Indented XML" contains indentation and
|
||||||
|
newlines, and is usually formatted for human readers.
|
||||||
|
|
||||||
|
The specification of Prolog/'$V', and of the NafHornlogEq sublanguage
|
||||||
|
of RuleML/XML targeted by BiMetaTrans, is contained in the preprint
|
||||||
|
[Invertible Bidirectional Metalogical Translation Between Prolog and
|
||||||
|
RuleML/XML for Knowledge Representation and
|
||||||
|
Querying](http://ruleml.org/papers/RuleMLXMLBiDirTransScryer.pdf) and
|
||||||
|
its accompanying talk
|
||||||
|
[slides](http://ruleml.org/talks/RuleMLXMLBiDirTransScryer-talk.pdf). The
|
||||||
|
preprint and talk also describe the implementation of BiMetaTrans
|
||||||
|
found here, and outlines a strategy for proving the invertibility of
|
||||||
|
BiMetaTrans.
|
||||||
|
|
||||||
|
BiMetaTrans exports a single public predicate subsuming its user
|
||||||
|
API, `parse_ruleml/3`. It has two modes, each corresponding to a single
|
||||||
|
direction of translation:
|
||||||
|
|
||||||
|
```
|
||||||
|
parse_ruleml(+AssertItems, +QueryItems, ?XML) (Prolog->RuleML)
|
||||||
|
parse_ruleml(?AssertItems, ?QueryItems, +XML) (RuleML->Prolog)
|
||||||
|
```
|
||||||
|
|
||||||
|
The modes constrain the inputs to fit one of two patterns, the
|
||||||
|
first where AssertItems and QueryItems are instantiated and XML is
|
||||||
|
possibly a variable, and conversely for the second. Instantiated
|
||||||
|
inputs are expected to be ground, meaning they should not contain
|
||||||
|
free variables.
|
||||||
|
|
||||||
|
We explore several examples of its use in Scryer Prolog.
|
||||||
|
|
||||||
|
Loading BiMetaTrans from the Scryer REPL:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module('src/examples/bimetatrans/bimetatrans').
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `write/1` to print the string to standard output, capturing the
|
||||||
|
`Prolog->RuleML` direction (`write/1` is used because it does not
|
||||||
|
print strings with escape characters, ie., `\"` for double quote):
|
||||||
|
|
||||||
|
```
|
||||||
|
?- parse_ruleml([people('Alex',male),people('Alex',female),people('Siri',female)], [], XML),
|
||||||
|
write(XML).
|
||||||
|
"<Assert mapClosure="universal"><Atom><Rel>people</Rel><Ind>Alex</Ind><Data iso:type="symbol">male</Data></Atom><Atom><Rel>people</Rel><Ind>Alex</Ind><Data iso:type="symbol">female</Data></Atom><Atom><Rel>people</Rel><Ind>Siri</Ind><Data iso:type="symbol">female</Data></Atom></Assert>" XML = "<Assert mapClosure= ...".
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the XML input can contain extraneous whitespace and
|
||||||
|
indentation while generated XML never does (ie., the XML generated by
|
||||||
|
BiMetaTrans is always Ruleml/xmL). `\` is used to continue ISO Prolog
|
||||||
|
strings to the next line but is never stored to the string by the
|
||||||
|
Prolog reader.
|
||||||
|
|
||||||
|
Performing the inverse translation of the previous example
|
||||||
|
(`RuleML->Prolog`), in RuleML/Xml:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- parse_ruleml(AssertItems, QueryItems,
|
||||||
|
"<Assert mapClosure=\"universal\">\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>people</Rel>\
|
||||||
|
<Ind>Alex</Ind>\
|
||||||
|
<Data iso:type=\"symbol\">male</Data>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>people</Rel>\
|
||||||
|
<Ind>Alex</Ind>\
|
||||||
|
<Data iso:type=\"symbol\">female</Data>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>people</Rel>\
|
||||||
|
<Ind>Siri</Ind>\
|
||||||
|
<Data iso:type=\"symbol\">female</Data>\
|
||||||
|
</Atom>\
|
||||||
|
</Assert>").
|
||||||
|
AssertItems = [people('Alex',male),people('Alex',female),people('Siri',female)], QueryItems = [].
|
||||||
|
```
|
||||||
|
|
||||||
|
Double quote characters within strings must be escaped as in
|
||||||
|
`\"`. Non-empty AssertItems and QueryItems lists generated to
|
||||||
|
RuleML/xmL simultaneously:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- parse_ruleml([a(item), b(item), c(item)], [(?- p, q, r(1), s(-2.222342432), t("attached")), (?- u, v('$V'(q)))], XML),
|
||||||
|
write(XML).
|
||||||
|
"<Assert mapClosure="universal"><Atom><Rel>a</Rel><Data iso:type="symbol">item</Data></Atom><Atom><Rel>b</Rel><Data iso:type="symbol">item</Data></Atom><Atom><Rel>c</Rel><Data iso:type="symbol">item</Data></Atom></Assert><Query closure="existential"><And><Atom><Rel>p</Rel></Atom><Atom><Rel>q</Rel></Atom><Atom><Rel>r</Rel><Data iso:type="number">1</Data></Atom><Atom><Rel>s</Rel><Data iso:type="number">-2.222342432</Data></Atom><Atom><Rel>t</Rel><Data iso:type="string">"attached"</Data></Atom></And></Query><Query closure="existential"><And><Atom><Rel>u</Rel></Atom><Atom><Rel>v</Rel><Var>q</Var></Atom></And></Query>" XML = "<Assert mapClosure= ...".
|
||||||
|
```
|
||||||
|
|
||||||
|
To insert escape characters into the printed RuleML/xmL, we use
|
||||||
|
`writeq/1` in place of `write/1`:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- parse_ruleml([a(item), b(item), c(item)], [(?- p, q, r(1), s(-2.222342432), t("attached")), (?- u, v('$V'(q)))], XML),
|
||||||
|
writeq(XML).
|
||||||
|
"<Assert mapClosure=\"universal\"><Atom><Rel>a</Rel><Data iso:type=\"symbol\">item</Data></Atom><Atom><Rel>b</Rel><Data iso:type=\"symbol\">item</Data></Atom><Atom><Rel>c</Rel><Data iso:type=\"symbol\">item</Data></Atom></Assert><Query closure=\"existential\"><And><Atom><Rel>p</Rel></Atom><Atom><Rel>q</Rel></Atom><Atom><Rel>r</Rel><Data iso:type=\"number\">1</Data></Atom><Atom><Rel>s</Rel><Data iso:type=\"number\">-2.222342432</Data></Atom><Atom><Rel>t</Rel><Data iso:type=\"string\">\"attached\"</Data></Atom></And></Query><Query closure=\"existential\"><And><Atom><Rel>u</Rel></Atom><Atom><Rel>v</Rel><Var>q</Var></Atom></And></Query>" XML = "<Assert mapClosure= ...".
|
||||||
|
```
|
||||||
|
|
||||||
|
XML can be pretty printed using a [free online XML pretty
|
||||||
|
printer](https://codebeautify.org/xmlviewer), as here:
|
||||||
|
|
||||||
|
```
|
||||||
|
<Assert mapClosure=\"universal\">
|
||||||
|
<Atom>
|
||||||
|
<Rel>a</Rel>
|
||||||
|
<Data iso:type=\"symbol\">item</Data>
|
||||||
|
</Atom>
|
||||||
|
<Atom>
|
||||||
|
<Rel>b</Rel>
|
||||||
|
<Data iso:type=\"symbol\">item</Data>
|
||||||
|
</Atom>
|
||||||
|
<Atom>
|
||||||
|
<Rel>c</Rel>
|
||||||
|
<Data iso:type=\"symbol\">item</Data>
|
||||||
|
</Atom>
|
||||||
|
</Assert>
|
||||||
|
<Query closure=\"existential\">
|
||||||
|
<And>
|
||||||
|
<Atom>
|
||||||
|
<Rel>p</Rel>
|
||||||
|
</Atom>
|
||||||
|
<Atom>
|
||||||
|
<Rel>q</Rel>
|
||||||
|
</Atom>
|
||||||
|
<Atom>
|
||||||
|
<Rel>r</Rel>
|
||||||
|
<Data iso:type=\"number\">1</Data>
|
||||||
|
</Atom>
|
||||||
|
<Atom>
|
||||||
|
<Rel>s</Rel>
|
||||||
|
<Data iso:type=\"number\">-2.222342432</Data>
|
||||||
|
</Atom>
|
||||||
|
<Atom>
|
||||||
|
<Rel>t</Rel>
|
||||||
|
<Data iso:type=\"string\">\"attached\"</Data>
|
||||||
|
</Atom>
|
||||||
|
</And>
|
||||||
|
</Query>
|
||||||
|
<Query closure=\"existential\">
|
||||||
|
<And>
|
||||||
|
<Atom>
|
||||||
|
<Rel>u</Rel>
|
||||||
|
</Atom>
|
||||||
|
<Atom>
|
||||||
|
<Rel>v</Rel>
|
||||||
|
<Var>q</Var>
|
||||||
|
</Atom>
|
||||||
|
</And>
|
||||||
|
</Query>
|
||||||
|
```
|
||||||
|
|
||||||
|
The AssertItems and QueryItems lists can then be recovered using this
|
||||||
|
RuleML/Xml string, with the `\` character added to the end of each
|
||||||
|
line:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- parse_ruleml(AssertItems, QueryItems,
|
||||||
|
"<Assert mapClosure=\"universal\">\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>a</Rel>\
|
||||||
|
<Data iso:type=\"symbol\">item</Data>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>b</Rel>\
|
||||||
|
<Data iso:type=\"symbol\">item</Data>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>c</Rel>\
|
||||||
|
<Data iso:type=\"symbol\">item</Data>\
|
||||||
|
</Atom>\
|
||||||
|
</Assert>\
|
||||||
|
<Query closure=\"existential\">\
|
||||||
|
<And>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>p</Rel>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>q</Rel>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>r</Rel>\
|
||||||
|
<Data iso:type=\"number\">1</Data>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>s</Rel>\
|
||||||
|
<Data iso:type=\"number\">-2.222342432</Data>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>t</Rel>\
|
||||||
|
<Data iso:type=\"string\">\"attached\"</Data>\
|
||||||
|
</Atom>\
|
||||||
|
</And>\
|
||||||
|
</Query>\
|
||||||
|
<Query closure=\"existential\">\
|
||||||
|
<And>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>u</Rel>\
|
||||||
|
</Atom>\
|
||||||
|
<Atom>\
|
||||||
|
<Rel>v</Rel>\
|
||||||
|
<Var>q</Var>\
|
||||||
|
</Atom>\
|
||||||
|
</And>\
|
||||||
|
</Query>").
|
||||||
|
AssertItems = [a(item),b(item),c(item)], QueryItems = [(?-p,q,r(1),s(-2.222342432),t("attached")),(?-u,v('$V'(q)))].
|
||||||
|
```
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
:- module(ruleml_xml_parser, [parse_ruleml/3]).
|
:- module(bimetatrans, [parse_ruleml/3]).
|
||||||
|
|
||||||
:- use_module(library(dcgs)).
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
:- set_prolog_flag(double_quotes, chars).
|
:- set_prolog_flag(double_quotes, chars).
|
||||||
|
|
||||||
@@ -17,28 +19,34 @@
|
|||||||
* all indentation stripped out. "Indented XML" contains indentation
|
* all indentation stripped out. "Indented XML" contains indentation
|
||||||
* and newlines, and is usually formatted for human readers.
|
* and newlines, and is usually formatted for human readers.
|
||||||
*
|
*
|
||||||
* parse_ruleml/3 is the sole public predicate of BiMetaTrans(RuleML,
|
* parse_ruleml/3 is the sole public predicate of BiMetaTrans(Prolog,
|
||||||
* Prolog), a bidirectional translator capable of parsing well-formed
|
* RuleML), a bidirectional translator capable of parsing well-formed
|
||||||
* RuleML/XML (valid w.r.t. a proposed anchor schema nafhornlogeq
|
* RuleML/XML (valid w.r.t. a proposed anchor schema nafhornlogeq
|
||||||
* defining a sublanguage of the existing anchor schema naffologeq) to
|
* defining a sublanguage of the existing anchor schema naffologeq) to
|
||||||
* an equivalent Prolog/'$V' term and back to RuleML/xmL. It has the
|
* an equivalent Prolog/'$V' term and back to RuleML/xmL. It has the
|
||||||
* modes
|
* modes
|
||||||
*
|
*
|
||||||
* +AssertItem, +QueryItems, ?XML (Prolog->RuleML)
|
* +AssertItems, +QueryItems, ?XML (Prolog->RuleML)
|
||||||
* ?AssertItem, ?QueryItems, +XML (RuleML->Prolog)
|
* ?AssertItems, ?QueryItems, +XML (RuleML->Prolog)
|
||||||
*
|
*
|
||||||
* The modes constrain the inputs to fit one of two patterns, the
|
* The modes constrain the inputs to fit one of two patterns, the
|
||||||
* first where AssertItem and QueryItem are instantiated and XML is
|
* first where AssertItems and QueryItems are instantiated and XML is
|
||||||
* possibly a variable, and conversely for the second. Instantiated
|
* possibly a variable, and conversely for the second. Instantiated
|
||||||
* inputs are expected to be ground, meaning they should not contain
|
* inputs are expected to be ground, meaning they should not contain
|
||||||
* free variables.
|
* free variables.
|
||||||
*
|
*
|
||||||
|
* A RuleML/XML document is assumed to contain an optional Assert
|
||||||
|
* element (with zero or more children) and zero or more Query
|
||||||
|
* elements. The children of the Assert element are the Items of
|
||||||
|
* AssertItems, while the Items of QueryItems each correspond to a
|
||||||
|
* single Query element.
|
||||||
|
*
|
||||||
* parse_ruleml/3 is meant to subsume an invertible function in the
|
* parse_ruleml/3 is meant to subsume an invertible function in the
|
||||||
* sense that the top-level query
|
* sense that the top-level query
|
||||||
*
|
*
|
||||||
* ?- parse_ruleml(AssertItem, QueryItems, XML0), % Prolog->RuleML
|
* ?- parse_ruleml(AssertItems, QueryItems, XML0), % Prolog->RuleML
|
||||||
* parse_ruleml(AssertItem0, QueryItems0, XML), % RuleML->Prolog
|
* parse_ruleml(AssertItems0, QueryItems0, XML), % RuleML->Prolog
|
||||||
* AssertItem0 == AssertItem, % Test for expected round-trip results.
|
* AssertItems0 == AssertItems, % Test for expected round-trip results.
|
||||||
* QueryItems0 == QueryItems,
|
* QueryItems0 == QueryItems,
|
||||||
* XML0 == XML.
|
* XML0 == XML.
|
||||||
*
|
*
|
||||||
@@ -46,10 +54,11 @@
|
|||||||
* not 0-appended are instantiated and therefore ground.
|
* not 0-appended are instantiated and therefore ground.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
parse_ruleml(AssertItem, QueryItem, XML) :-
|
parse_ruleml(AssertItems, QueryItems, XML) :-
|
||||||
( ( var(AssertItem) ; var(QueryItem) ), var(XML) ->
|
( ( var(AssertItems) ; var(QueryItems) ), var(XML) ->
|
||||||
throw(error(instantiation_error, parse_ruleml/2))
|
throw(error(instantiation_error, parse_ruleml/2))
|
||||||
; phrase(ruleml_top_level_items(AssertItem, QueryItem), XML)
|
; phrase(ruleml_top_level_items(AssertItems, QueryItems), XML),
|
||||||
|
!
|
||||||
).
|
).
|
||||||
|
|
||||||
|
|
||||||
@@ -80,24 +89,15 @@ parse_header -->
|
|||||||
*
|
*
|
||||||
* The two arguments are (un)instantiated depending on the direction
|
* The two arguments are (un)instantiated depending on the direction
|
||||||
* in which parse_ruleml/2 operates. In either direction, once
|
* in which parse_ruleml/2 operates. In either direction, once
|
||||||
* ruleml_query_item(QueryItems) or ruleml_assert(AssertItem)
|
* ruleml_query_item(QueryItems) or ruleml_assert(AssertItems)
|
||||||
* succeeds:
|
* succeeds, AssertItems and QueryItems are ground lists of terms,
|
||||||
*
|
* corresponding to Assert/Query performative(s) of the RuleML/XML KB.
|
||||||
* 1) AssertItem/QueryItems is a fully ground list of terms,
|
|
||||||
* corresponding to an Assert/Query performative of the RuleML/XML KB.
|
|
||||||
*
|
|
||||||
* 2) It would not be meaningful to backtrack after the last success,
|
|
||||||
* thus undoing its effects; that is because we have already refuted
|
|
||||||
* the possibility of there being further items for the grammar to
|
|
||||||
* consume. Hence we see that the cut (!) does not restrict the
|
|
||||||
* generality of the grammar.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ruleml_top_level_items(AssertItem, QueryItems) -->
|
ruleml_top_level_items(AssertItems, QueryItems) -->
|
||||||
( ruleml_assert(AssertItem),
|
( ruleml_assert(AssertItems),
|
||||||
ruleml_query_items(QueryItems),
|
ruleml_query_items(QueryItems)
|
||||||
!
|
; { AssertItems = [] },
|
||||||
; { AssertItem = [] },
|
|
||||||
ruleml_query_items(QueryItems)
|
ruleml_query_items(QueryItems)
|
||||||
).
|
).
|
||||||
|
|
||||||
@@ -167,22 +167,16 @@ ruleml_top_level_items(AssertItem, QueryItems) -->
|
|||||||
* outset of ruleml_assert//1 will cause it to fail, meaning that
|
* outset of ruleml_assert//1 will cause it to fail, meaning that
|
||||||
* an empty <Assert></Assert> element is never emitted to the output
|
* an empty <Assert></Assert> element is never emitted to the output
|
||||||
* RuleML/xmL.
|
* RuleML/xmL.
|
||||||
*
|
|
||||||
* The two cuts are green cuts, meaning they do not change the meaning
|
|
||||||
* or output of the program, they simply prevent needless
|
|
||||||
* backtracking.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ruleml_assert(Items) -->
|
ruleml_assert(Items) -->
|
||||||
( { var(Items) } ->
|
( { var(Items) } ->
|
||||||
list_ws("<Assert mapClosure=\"universal\">"),
|
list_ws("<Assert mapClosure=\"universal\">"),
|
||||||
ruleml_assert_items(Items),
|
ruleml_assert_items(Items),
|
||||||
!,
|
|
||||||
list_ws("</Assert>")
|
list_ws("</Assert>")
|
||||||
; "<Assert mapClosure=\"universal\">",
|
; "<Assert mapClosure=\"universal\">",
|
||||||
{ Items \== [] },
|
{ Items \== [] },
|
||||||
ruleml_assert_items(Items),
|
ruleml_assert_items(Items),
|
||||||
!,
|
|
||||||
"</Assert>"
|
"</Assert>"
|
||||||
).
|
).
|
||||||
|
|
||||||
@@ -204,7 +198,6 @@ ruleml_assert(Items) -->
|
|||||||
|
|
||||||
ruleml_assert_items([Item | Items]) -->
|
ruleml_assert_items([Item | Items]) -->
|
||||||
ruleml_assert_item(Item),
|
ruleml_assert_item(Item),
|
||||||
!,
|
|
||||||
ruleml_assert_items(Items).
|
ruleml_assert_items(Items).
|
||||||
ruleml_assert_items([]) --> [].
|
ruleml_assert_items([]) --> [].
|
||||||
|
|
||||||
@@ -212,7 +205,6 @@ ruleml_assert_items([]) --> [].
|
|||||||
ruleml_query_items(Items) -->
|
ruleml_query_items(Items) -->
|
||||||
( { var(Items) } ->
|
( { var(Items) } ->
|
||||||
ruleml_query_item(Item),
|
ruleml_query_item(Item),
|
||||||
!,
|
|
||||||
( { var(Item) } ->
|
( { var(Item) } ->
|
||||||
ruleml_query_items(Items)
|
ruleml_query_items(Items)
|
||||||
; { Items = [Item | Items0] },
|
; { Items = [Item | Items0] },
|
||||||
@@ -220,7 +212,6 @@ ruleml_query_items(Items) -->
|
|||||||
)
|
)
|
||||||
; { Items = [Item | Items0] },
|
; { Items = [Item | Items0] },
|
||||||
ruleml_query_item(Item),
|
ruleml_query_item(Item),
|
||||||
!,
|
|
||||||
ruleml_query_items(Items0)
|
ruleml_query_items(Items0)
|
||||||
).
|
).
|
||||||
ruleml_query_items([]) --> [].
|
ruleml_query_items([]) --> [].
|
||||||
@@ -277,7 +268,7 @@ space(' ') --> " ".
|
|||||||
|
|
||||||
decimal_point('.') --> ".".
|
decimal_point('.') --> ".".
|
||||||
|
|
||||||
sign('-') --> "-", !.
|
sign('-') --> "-".
|
||||||
sign('+') --> "+".
|
sign('+') --> "+".
|
||||||
|
|
||||||
double_quote('"') --> "\"".
|
double_quote('"') --> "\"".
|
||||||
@@ -330,7 +321,6 @@ ruleml_condition(Item) -->
|
|||||||
|
|
||||||
ruleml_item_conjunction([Item | Items]) -->
|
ruleml_item_conjunction([Item | Items]) -->
|
||||||
ruleml_condition(Item),
|
ruleml_condition(Item),
|
||||||
!,
|
|
||||||
ruleml_item_conjunction(Items).
|
ruleml_item_conjunction(Items).
|
||||||
ruleml_item_conjunction([]) --> [].
|
ruleml_item_conjunction([]) --> [].
|
||||||
|
|
||||||
@@ -402,7 +392,6 @@ ruleml_conjunction_of_items(Items) -->
|
|||||||
|
|
||||||
ruleml_item_disjunction([Item | Items]) -->
|
ruleml_item_disjunction([Item | Items]) -->
|
||||||
ruleml_condition(Item),
|
ruleml_condition(Item),
|
||||||
!,
|
|
||||||
ruleml_item_disjunction(Items).
|
ruleml_item_disjunction(Items).
|
||||||
ruleml_item_disjunction([]) --> [].
|
ruleml_item_disjunction([]) --> [].
|
||||||
|
|
||||||
@@ -498,7 +487,7 @@ ruleml_plex(Plex) -->
|
|||||||
list_ws("</Plex>")
|
list_ws("</Plex>")
|
||||||
; list_ws("<Plex/>")
|
; list_ws("<Plex/>")
|
||||||
)
|
)
|
||||||
; { \+ string(Plex),
|
; { ( \+ partial_string(Plex) ; Plex == [] ),
|
||||||
acyclic_term(Plex) },
|
acyclic_term(Plex) },
|
||||||
( { functor(Plex, ('.'), 2) } ->
|
( { functor(Plex, ('.'), 2) } ->
|
||||||
{ split_plex(Plex, PlexItems, RepoVar) },
|
{ split_plex(Plex, PlexItems, RepoVar) },
|
||||||
@@ -547,7 +536,6 @@ ruleml_naf(Item) -->
|
|||||||
|
|
||||||
ruleml_atoms([Item|Items]) -->
|
ruleml_atoms([Item|Items]) -->
|
||||||
ruleml_atom(Item),
|
ruleml_atom(Item),
|
||||||
!,
|
|
||||||
ruleml_atoms(Items).
|
ruleml_atoms(Items).
|
||||||
ruleml_atoms([]) --> [].
|
ruleml_atoms([]) --> [].
|
||||||
|
|
||||||
@@ -582,13 +570,11 @@ ruleml_atom(Item) -->
|
|||||||
list_ws("<Atom>"),
|
list_ws("<Atom>"),
|
||||||
list_ws("<Rel>"),
|
list_ws("<Rel>"),
|
||||||
prolog_symbol(Name),
|
prolog_symbol(Name),
|
||||||
{ Name \== (','), Name \== (';') },
|
|
||||||
list_ws("</Rel>"),
|
list_ws("</Rel>"),
|
||||||
ruleml_items(Args),
|
ruleml_items(Args),
|
||||||
list_ws("</Atom>"),
|
list_ws("</Atom>"),
|
||||||
{ Item =.. [Name | Args] }
|
{ Item =.. [Name | Args] }
|
||||||
; { Item =.. [Name | Args] },
|
; { Item =.. [Name | Args] },
|
||||||
{ Name \== (','), Name \== (';') },
|
|
||||||
"<Atom>",
|
"<Atom>",
|
||||||
"<Rel>",
|
"<Rel>",
|
||||||
prolog_symbol(Name),
|
prolog_symbol(Name),
|
||||||
@@ -652,7 +638,6 @@ ruleml_expr(Item) -->
|
|||||||
( list_ws("<repo>"),
|
( list_ws("<repo>"),
|
||||||
ruleml_item(RepoItem),
|
ruleml_item(RepoItem),
|
||||||
list_ws("</repo>"),
|
list_ws("</repo>"),
|
||||||
!,
|
|
||||||
{ fold_commas(Args, ArgsCommas) },
|
{ fold_commas(Args, ArgsCommas) },
|
||||||
{ Item =.. [Name, (ArgsCommas | RepoItem)] }
|
{ Item =.. [Name, (ArgsCommas | RepoItem)] }
|
||||||
; { Item =.. [Name | Args] }
|
; { Item =.. [Name | Args] }
|
||||||
@@ -789,7 +774,7 @@ constant_chars(symbol, Constant, Chars) :-
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* ruleml_data//1 delegates to ruleml_data_contents//2 to determine
|
* ruleml_data//1 delegates to ruleml_data_contents//2 to determine
|
||||||
* the contents of <Data> nodes, with adjoining xsi:type elements.
|
* the contents of <Data> nodes, with adjoining iso:type elements.
|
||||||
*
|
*
|
||||||
* constant_chars/3 performs type-driven conversion between
|
* constant_chars/3 performs type-driven conversion between
|
||||||
* Prolog/'$V' and RuleML/XML in both directions;
|
* Prolog/'$V' and RuleML/XML in both directions;
|
||||||
@@ -799,13 +784,13 @@ constant_chars(symbol, Constant, Chars) :-
|
|||||||
|
|
||||||
ruleml_data(Name) -->
|
ruleml_data(Name) -->
|
||||||
( { var(Name) } ->
|
( { var(Name) } ->
|
||||||
list_ws("<Data xsi:type=\""),
|
list_ws("<Data iso:type=\""),
|
||||||
prolog_symbol(Type),
|
prolog_symbol(Type),
|
||||||
list_ws("\">"),
|
list_ws("\">"),
|
||||||
ruleml_data_contents(Type, Cs),
|
ruleml_data_contents(Type, Cs),
|
||||||
{ constant_chars(Type, Name, Cs) },
|
{ constant_chars(Type, Name, Cs) },
|
||||||
list_ws("</Data>")
|
list_ws("</Data>")
|
||||||
; "<Data xsi:type=\"",
|
; "<Data iso:type=\"",
|
||||||
{ constant_chars(Type, Name, Cs) },
|
{ constant_chars(Type, Name, Cs) },
|
||||||
prolog_symbol(Type),
|
prolog_symbol(Type),
|
||||||
"\">",
|
"\">",
|
||||||
@@ -823,15 +808,12 @@ ruleml_data(Name) -->
|
|||||||
* converting from lists of characters (here denoted as Cs) to the
|
* converting from lists of characters (here denoted as Cs) to the
|
||||||
* named data type in ISO Prolog. The type information is given
|
* named data type in ISO Prolog. The type information is given
|
||||||
* in the first argument.
|
* in the first argument.
|
||||||
*
|
|
||||||
* Grammatically, ruleml_data_contents//3 is a disjoint union of its
|
|
||||||
* subgrammars, justifying the cut at the end of the first two rules.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ruleml_data_contents(number, Cs) -->
|
ruleml_data_contents(number, Cs) -->
|
||||||
ruleml_number(Cs), !.
|
ruleml_number(Cs).
|
||||||
ruleml_data_contents(symbol, Cs) -->
|
ruleml_data_contents(symbol, Cs) -->
|
||||||
ruleml_symbol(Cs), !.
|
ruleml_symbol(Cs).
|
||||||
ruleml_data_contents(string, Cs) -->
|
ruleml_data_contents(string, Cs) -->
|
||||||
ruleml_string(Cs).
|
ruleml_string(Cs).
|
||||||
|
|
||||||
@@ -1195,8 +1177,7 @@ fold_semicolons(List, Output) :-
|
|||||||
* complicated by Scryer's current lack of multi-argument indexing.
|
* complicated by Scryer's current lack of multi-argument indexing.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fold_list([Item], _, Item) :-
|
fold_list([Item], _, Item).
|
||||||
!.
|
|
||||||
fold_list([Item|Items], F, Form) :-
|
fold_list([Item|Items], F, Form) :-
|
||||||
Form =.. [F, Item, Fs],
|
Form =.. [F, Item, Fs],
|
||||||
fold_list(Items, F, Fs).
|
fold_list(Items, F, Fs).
|
||||||
4061
src/examples/bimetatrans/bimetatrans_tests.pl
Normal file
4061
src/examples/bimetatrans/bimetatrans_tests.pl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,6 @@ verify_attributes(Var, Other, Goals) :-
|
|||||||
( Els = [] -> % exactly one element
|
( Els = [] -> % exactly one element
|
||||||
Goals = [Other=El] % implied binding
|
Goals = [Other=El] % implied binding
|
||||||
; Goals = [],
|
; Goals = [],
|
||||||
put_atts(Other, -dom(_)),
|
|
||||||
put_atts(Other, dom(Dc))% rescue intersection
|
put_atts(Other, dom(Dc))% rescue intersection
|
||||||
)
|
)
|
||||||
; Goals = [],
|
; Goals = [],
|
||||||
31
src/examples/echo_server.pl
Normal file
31
src/examples/echo_server.pl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
:- module(echo_server, [echo_server/0,
|
||||||
|
echo_server/1]).
|
||||||
|
|
||||||
|
:- use_module(library(format)).
|
||||||
|
:- use_module(library(sockets)).
|
||||||
|
|
||||||
|
|
||||||
|
echo_server :-
|
||||||
|
echo_server('127.0.0.1').
|
||||||
|
|
||||||
|
|
||||||
|
echo_server(Addr) :-
|
||||||
|
socket_server_open(Addr:Port, ServerSocket),
|
||||||
|
format("echo_server: connection opened at ~w:~d~n", [Addr, Port]),
|
||||||
|
socket_server_accept(ServerSocket, Client, Stream, [eof_action(eof_code)]),
|
||||||
|
format("echo_server: connection accepted from ~a~n", [Client]),
|
||||||
|
!,
|
||||||
|
echo_loop(Stream),
|
||||||
|
socket_server_close(ServerSocket).
|
||||||
|
|
||||||
|
|
||||||
|
echo_loop(Stream) :-
|
||||||
|
read_term(Stream, Term, []),
|
||||||
|
( Term == end_of_file ->
|
||||||
|
true
|
||||||
|
;
|
||||||
|
format("received: ~w~n", [Term]),
|
||||||
|
!,
|
||||||
|
echo_loop(Stream)
|
||||||
|
).
|
||||||
|
|
||||||
@@ -11,20 +11,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
:- module(least_time, [find_min_time/2,
|
:- module(least_time, [find_min_time/2,
|
||||||
write_time_nl/1]).
|
write_time_nl/1]).
|
||||||
|
|
||||||
|
|
||||||
:- use_module(library(dcgs)).
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(format)).
|
:- use_module(library(format)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
:- use_module(library(reif)).
|
:- use_module(library(reif)).
|
||||||
|
|
||||||
|
|
||||||
permutation([], []).
|
|
||||||
permutation([X|Xs], Ys) :-
|
|
||||||
permutation(Xs, Yss),
|
|
||||||
select(X, Ys, Yss).
|
|
||||||
|
|
||||||
|
|
||||||
valid_time([H1,H2,M1,M2], T) :-
|
valid_time([H1,H2,M1,M2], T) :-
|
||||||
memberd_t(H1, [0,1,2], TH1),
|
memberd_t(H1, [0,1,2], TH1),
|
||||||
memberd_t(H2, [0,1,2,3,4,5,6,7,8,9], TH2),
|
memberd_t(H2, [0,1,2,3,4,5,6,7,8,9], TH2),
|
||||||
@@ -32,10 +27,10 @@ valid_time([H1,H2,M1,M2], T) :-
|
|||||||
memberd_t(M2, [0,1,2,3,4,5,6,7,8,9], TM2),
|
memberd_t(M2, [0,1,2,3,4,5,6,7,8,9], TM2),
|
||||||
( maplist(=(true), [TH1, TH2, TM1, TM2]) ->
|
( maplist(=(true), [TH1, TH2, TM1, TM2]) ->
|
||||||
( H1 =:= 2 ->
|
( H1 =:= 2 ->
|
||||||
( H2 =< 3 ->
|
( H2 =< 3 ->
|
||||||
T = true
|
T = true
|
||||||
; T = false
|
; T = false
|
||||||
)
|
)
|
||||||
; T = true
|
; T = true
|
||||||
)
|
)
|
||||||
; T = false
|
; T = false
|
||||||
@@ -49,15 +44,7 @@ permuted_times(Time, PermutedTimes) :-
|
|||||||
|
|
||||||
find_min_time(Time, Min) :-
|
find_min_time(Time, Min) :-
|
||||||
valid_time(Time, true),
|
valid_time(Time, true),
|
||||||
permuted_times(Time, PermutedTimes),
|
permuted_times(Time, [Min|_]).
|
||||||
find_min_time_(PermutedTimes, Time, Min).
|
|
||||||
|
|
||||||
find_min_time_([], Min, Min).
|
|
||||||
find_min_time_([Time|Times], MinSoFar, Min) :-
|
|
||||||
( Time @< MinSoFar ->
|
|
||||||
find_min_time_(Times, Time, Min)
|
|
||||||
; find_min_time_(Times, MinSoFar, Min)
|
|
||||||
).
|
|
||||||
|
|
||||||
|
|
||||||
write_time_nl(Time) :-
|
write_time_nl(Time) :-
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
:- use_module(library(dcgs)).
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(dif)).
|
:- use_module(library(dif)).
|
||||||
|
:- use_module(library(format)).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
pl_resolution(Clauses0, Chain) :-
|
pl_resolution(Clauses0, Chain) :-
|
||||||
31
src/examples/utf8.pl
Normal file
31
src/examples/utf8.pl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
:- initialization(unit_test).
|
||||||
|
|
||||||
|
unit_test :-
|
||||||
|
chars_utf8bytes("a£\x2124\", Bs),
|
||||||
|
Bs = [97, 194, 163, 226, 132, 164],
|
||||||
|
chars_utf8bytes(Cs, Bs),
|
||||||
|
Cs = "a£\x2124\".
|
||||||
|
|
||||||
|
write_f :-
|
||||||
|
open('x.txt', write, Stream, [type(binary)]),
|
||||||
|
F = put_byte(Stream),
|
||||||
|
chars_utf8bytes("£\x2124\\x2764\\x1F496\\n", Bs),
|
||||||
|
maplist(F, Bs),
|
||||||
|
close(Stream).
|
||||||
|
|
||||||
|
get_bytes(Stream, Res) :- get_bytes(Stream, [], Res).
|
||||||
|
get_bytes(Stream, Acc, Res) :-
|
||||||
|
get_byte(Stream, B),
|
||||||
|
(B =:= -1 ->
|
||||||
|
reverse(Acc, Res)
|
||||||
|
; get_bytes(Stream, [B|Acc], Res)).
|
||||||
|
|
||||||
|
read_f :-
|
||||||
|
open('x.txt', read, Stream, [type(binary)]),
|
||||||
|
get_bytes(Stream, Bs),
|
||||||
|
chars_utf8bytes(Cs, Bs),
|
||||||
|
write(Cs),
|
||||||
|
close(Stream).
|
||||||
444
src/ffi.rs
Normal file
444
src/ffi.rs
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
/* How does FFI work?
|
||||||
|
|
||||||
|
Each WAM machine has a ForeignFunctionTable instance that contains a table of functions and structs.
|
||||||
|
|
||||||
|
Structs are defined via foreign_struct/2. Basic types are defined by libffi, but struct types need to
|
||||||
|
be manually defined to get an ffi_type. Additionally, to recover structs from return arguments, we store
|
||||||
|
fields and atom_fields, as a way to lookup the content of the struct (fields) and the nested structs (atom_fields).
|
||||||
|
|
||||||
|
Functions are defined via use_foreign_module/2. It opens a library and leaks the memory of the library,
|
||||||
|
to prevent Rust freeing the memory. There's no way to recover that memory at the moment. We get a pointer for
|
||||||
|
each function and we build a CIF for each one, with the input arguments and the return argument.
|
||||||
|
|
||||||
|
Exec happens via '$foreign_call', we find the function, we try to cast the values that we have to the definition
|
||||||
|
of the function, we reserve memory for them and we build an array of pointers. To get the return argument, we
|
||||||
|
reserve enough memory for the return and we build the Scryer values from them.
|
||||||
|
|
||||||
|
Structs are a bit tricky as they need to be aligned. For that, we reserve enough memory (libffi calculates that)
|
||||||
|
and for each field: we add to the pointer until we're aligned to the next data type we're going to write, we write it,
|
||||||
|
and finally we add the pointer the size of what we've written.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::atom_table::Atom;
|
||||||
|
|
||||||
|
use std::alloc::{alloc, Layout};
|
||||||
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::ffi::{CString, c_void};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use libffi::low::{ffi_cif, types, CodePtr, ffi_abi_FFI_DEFAULT_ABI, prep_cif, ffi_type, type_tag};
|
||||||
|
use libloading::{Symbol, Library};
|
||||||
|
|
||||||
|
pub struct FunctionDefinition {
|
||||||
|
pub name: String,
|
||||||
|
pub return_value: Atom,
|
||||||
|
pub args: Vec<Atom>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FunctionImpl {
|
||||||
|
cif: ffi_cif,
|
||||||
|
args: Vec<*mut ffi_type>,
|
||||||
|
code_ptr: CodePtr,
|
||||||
|
return_struct_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ForeignFunctionTable {
|
||||||
|
table: HashMap<String, FunctionImpl>,
|
||||||
|
structs: HashMap<String, StructImpl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct StructImpl {
|
||||||
|
ffi_type: ffi_type,
|
||||||
|
fields: Vec<*mut ffi_type>,
|
||||||
|
atom_fields: Vec<Atom>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PointerArgs {
|
||||||
|
pointers: Vec<*mut c_void>,
|
||||||
|
_memory: Vec<Box<dyn Any>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForeignFunctionTable {
|
||||||
|
pub fn merge(&mut self, other: ForeignFunctionTable) {
|
||||||
|
self.table.extend(other.table);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn define_struct(&mut self, name: &str, atom_fields: Vec<Atom>) {
|
||||||
|
let mut fields: Vec<_> = atom_fields.iter().map(|x| self.map_type_ffi(&x)).collect();
|
||||||
|
fields.push(std::ptr::null_mut::<ffi_type>());
|
||||||
|
let mut struct_type: ffi_type = Default::default();
|
||||||
|
struct_type.type_ = type_tag::STRUCT;
|
||||||
|
struct_type.elements = fields.as_mut_ptr();
|
||||||
|
self.structs.insert(name.to_string(), StructImpl { ffi_type: struct_type, fields, atom_fields});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_type_ffi(&mut self, source: &Atom) -> *mut ffi_type {
|
||||||
|
unsafe {
|
||||||
|
match source {
|
||||||
|
atom!("sint64") => &mut types::sint64,
|
||||||
|
atom!("sint32") => &mut types::sint32,
|
||||||
|
atom!("sint16") => &mut types::sint16,
|
||||||
|
atom!("sint8") => &mut types::sint8,
|
||||||
|
atom!("uint64") => &mut types::uint64,
|
||||||
|
atom!("uint32") => &mut types::uint32,
|
||||||
|
atom!("uint16") => &mut types::uint16,
|
||||||
|
atom!("uint8") => &mut types::uint8,
|
||||||
|
atom!("bool") => &mut types::sint8,
|
||||||
|
atom!("void") => &mut types::void,
|
||||||
|
atom!("cstr") => &mut types::pointer,
|
||||||
|
atom!("ptr") => &mut types::pointer,
|
||||||
|
atom!("f32") => &mut types::float,
|
||||||
|
atom!("f64") => &mut types::double,
|
||||||
|
struct_name => {
|
||||||
|
match self.structs.get_mut(struct_name.as_str()) {
|
||||||
|
Some(ref mut struct_type) => {
|
||||||
|
&mut struct_type.ffi_type
|
||||||
|
},
|
||||||
|
None => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_library(&mut self, library_name: &str, functions: &Vec<FunctionDefinition>) -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut ff_table: ForeignFunctionTable = Default::default();
|
||||||
|
unsafe {
|
||||||
|
let library = Library::new(library_name)?;
|
||||||
|
for function in functions {
|
||||||
|
let symbol_name: CString = CString::new(function.name.clone())?;
|
||||||
|
let code_ptr: Symbol<*mut c_void> = library.get(&symbol_name.into_bytes_with_nul())?;
|
||||||
|
let mut args: Vec<_> = function.args.iter().map(|x| self.map_type_ffi(&x)).collect();
|
||||||
|
let mut cif: ffi_cif = Default::default();
|
||||||
|
prep_cif(
|
||||||
|
&mut cif,
|
||||||
|
ffi_abi_FFI_DEFAULT_ABI,
|
||||||
|
args.len(),
|
||||||
|
self.map_type_ffi(&function.return_value),
|
||||||
|
args.as_mut_ptr()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let return_struct_name = if (*self.map_type_ffi(&function.return_value)).type_ as u32 == libffi::raw::FFI_TYPE_STRUCT {
|
||||||
|
Some(function.return_value.as_str().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
ff_table.table.insert(function.name.clone(), FunctionImpl {
|
||||||
|
cif,
|
||||||
|
args,
|
||||||
|
code_ptr: CodePtr(code_ptr.into_raw().into_raw() as *mut _),
|
||||||
|
return_struct_name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
std::mem::forget(library);
|
||||||
|
}
|
||||||
|
self.merge(ff_table);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_pointer_args(args: &mut Vec<Value>, type_args: &Vec<*mut ffi_type>, structs_table: &mut HashMap<String, StructImpl>) -> Result<PointerArgs, FFIError> {
|
||||||
|
let mut pointers = Vec::with_capacity(args.len());
|
||||||
|
let mut _memory = Vec::new();
|
||||||
|
for i in 0..args.len() {
|
||||||
|
let field_type = type_args[i];
|
||||||
|
unsafe {
|
||||||
|
macro_rules! push_int {
|
||||||
|
($type:ty) => {
|
||||||
|
{
|
||||||
|
let n: $type = <$type>::try_from(args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?;
|
||||||
|
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||||
|
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||||
|
_memory.push(box_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (*field_type).type_ as u32 {
|
||||||
|
libffi::raw::FFI_TYPE_UINT8 => push_int!(u8),
|
||||||
|
libffi::raw::FFI_TYPE_SINT8 => push_int!(i8),
|
||||||
|
libffi::raw::FFI_TYPE_UINT16 => push_int!(u16),
|
||||||
|
libffi::raw::FFI_TYPE_SINT16 => push_int!(i16),
|
||||||
|
libffi::raw::FFI_TYPE_UINT32 => push_int!(u32),
|
||||||
|
libffi::raw::FFI_TYPE_SINT32 => push_int!(i32),
|
||||||
|
libffi::raw::FFI_TYPE_UINT64 => push_int!(u64),
|
||||||
|
libffi::raw::FFI_TYPE_SINT64 => push_int!(i64),
|
||||||
|
libffi::raw::FFI_TYPE_FLOAT => {
|
||||||
|
let n: f32 = args[i].as_float()? as f32;
|
||||||
|
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||||
|
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||||
|
_memory.push(box_value);
|
||||||
|
},
|
||||||
|
libffi::raw::FFI_TYPE_DOUBLE => {
|
||||||
|
let n: f64 = args[i].as_float()?;
|
||||||
|
let mut box_value = Box::new(n) as Box<dyn Any>;
|
||||||
|
pointers.push(&mut *box_value as *mut _ as *mut c_void);
|
||||||
|
_memory.push(box_value);
|
||||||
|
},
|
||||||
|
libffi::raw::FFI_TYPE_POINTER => {
|
||||||
|
let ptr: *mut c_void = args[i].as_ptr()?;
|
||||||
|
pointers.push(ptr);
|
||||||
|
},
|
||||||
|
libffi::raw::FFI_TYPE_STRUCT => {
|
||||||
|
let (mut ptr, _size, _align) = Self::build_struct(&mut args[i], structs_table)?;
|
||||||
|
pointers.push(&mut *ptr as *mut _ as *mut c_void);
|
||||||
|
_memory.push(ptr);
|
||||||
|
},
|
||||||
|
_ => return Err(FFIError::InvalidFFIType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(PointerArgs {
|
||||||
|
pointers,
|
||||||
|
_memory
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_struct(arg: &mut Value, structs_table: &mut HashMap<String, StructImpl>) -> Result<(Box<dyn Any>, usize, usize), FFIError> {
|
||||||
|
unsafe {
|
||||||
|
match arg {
|
||||||
|
Value::Struct(ref name, ref mut struct_args) => {
|
||||||
|
if let Some(ref mut struct_type) = structs_table.clone().get_mut(name) {
|
||||||
|
let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap();
|
||||||
|
let align = struct_type.ffi_type.alignment as usize;
|
||||||
|
let size = struct_type.ffi_type.size;
|
||||||
|
let ptr = alloc(layout) as *mut c_void;
|
||||||
|
let mut field_ptr = ptr;
|
||||||
|
|
||||||
|
for i in 0..(struct_type.fields.len()-1) {
|
||||||
|
macro_rules! try_write_int {
|
||||||
|
($type:ty) => {
|
||||||
|
{
|
||||||
|
field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<$type>()));
|
||||||
|
let n: $type = <$type>::try_from(struct_args[i].as_int()?).map_err(|_| FFIError::ValueDontFit)?;
|
||||||
|
std::ptr::write(field_ptr as *mut $type, n);
|
||||||
|
field_ptr = field_ptr.add(std::mem::size_of::<$type>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! write {
|
||||||
|
($type:ty, $value:expr) => {
|
||||||
|
{
|
||||||
|
let data: $type = $value;
|
||||||
|
std::ptr::write(field_ptr as *mut $type, data);
|
||||||
|
field_ptr = field_ptr.add(align);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let field = struct_type.fields[i];
|
||||||
|
match (*field).type_ as u32 {
|
||||||
|
libffi::raw::FFI_TYPE_UINT8 => try_write_int!(u8),
|
||||||
|
libffi::raw::FFI_TYPE_SINT8 => try_write_int!(i8),
|
||||||
|
libffi::raw::FFI_TYPE_UINT16 => try_write_int!(u16),
|
||||||
|
libffi::raw::FFI_TYPE_SINT16 => try_write_int!(i16),
|
||||||
|
libffi::raw::FFI_TYPE_UINT32 => try_write_int!(u32),
|
||||||
|
libffi::raw::FFI_TYPE_SINT32 => try_write_int!(i32),
|
||||||
|
libffi::raw::FFI_TYPE_UINT64 => try_write_int!(u64),
|
||||||
|
libffi::raw::FFI_TYPE_SINT64 => try_write_int!(i64),
|
||||||
|
libffi::raw::FFI_TYPE_POINTER => write!(*mut c_void, struct_args[i].as_ptr()?),
|
||||||
|
libffi::raw::FFI_TYPE_FLOAT => write!(f32, struct_args[i].as_float()? as f32),
|
||||||
|
libffi::raw::FFI_TYPE_DOUBLE => write!(f64, struct_args[i].as_float()?),
|
||||||
|
libffi::raw::FFI_TYPE_STRUCT => {
|
||||||
|
let (struct_ptr, struct_size, struct_align) = Self::build_struct(&mut struct_args[i], structs_table)?;
|
||||||
|
field_ptr = field_ptr.add(field_ptr.align_offset(struct_align));
|
||||||
|
|
||||||
|
std::ptr::copy(& *struct_ptr as *const _ as *const c_void, field_ptr as *mut c_void, struct_size);
|
||||||
|
field_ptr = field_ptr.add(struct_size);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok((Box::from_raw(ptr), size, align));
|
||||||
|
} else {
|
||||||
|
return Err(FFIError::InvalidStructName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(FFIError::ValueCast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(&mut self, name: &str, mut args: Vec<Value>) -> Result<Value, FFIError> {
|
||||||
|
let function_impl = self.table.get_mut(name).ok_or(FFIError::FunctionNotFound)?;
|
||||||
|
let mut pointer_args = Self::build_pointer_args(&mut args, &function_impl.args, &mut self.structs)?;
|
||||||
|
|
||||||
|
return unsafe {
|
||||||
|
macro_rules! call_and_return {
|
||||||
|
($type:ty) => {
|
||||||
|
{
|
||||||
|
let mut n: Box<u8> = Box::new(0);
|
||||||
|
libffi::raw::ffi_call(
|
||||||
|
&mut function_impl.cif,
|
||||||
|
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||||
|
&mut *n as *mut _ as *mut c_void,
|
||||||
|
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||||
|
);
|
||||||
|
Ok(Value::Int(i64::from(*n)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (*function_impl.cif.rtype).type_ as u32 {
|
||||||
|
libffi::raw::FFI_TYPE_VOID => call_and_return!(i32),
|
||||||
|
libffi::raw::FFI_TYPE_UINT8 => call_and_return!(u8),
|
||||||
|
libffi::raw::FFI_TYPE_SINT8 => call_and_return!(i8),
|
||||||
|
libffi::raw::FFI_TYPE_UINT16 => call_and_return!(u16),
|
||||||
|
libffi::raw::FFI_TYPE_SINT16 => call_and_return!(i16),
|
||||||
|
libffi::raw::FFI_TYPE_UINT32 => call_and_return!(u32),
|
||||||
|
libffi::raw::FFI_TYPE_SINT32 => call_and_return!(i32),
|
||||||
|
libffi::raw::FFI_TYPE_UINT64 => {
|
||||||
|
let mut n: Box<u64> = Box::new(0);
|
||||||
|
libffi::raw::ffi_call(
|
||||||
|
&mut function_impl.cif,
|
||||||
|
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||||
|
&mut *n as *mut _ as *mut c_void,
|
||||||
|
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||||
|
);
|
||||||
|
Ok(Value::Int(i64::try_from(*n).map_err(|_| FFIError::ValueDontFit)?))
|
||||||
|
},
|
||||||
|
libffi::raw::FFI_TYPE_SINT64 => call_and_return!(i64),
|
||||||
|
libffi::raw::FFI_TYPE_POINTER => call_and_return!(*mut c_void),
|
||||||
|
libffi::raw::FFI_TYPE_FLOAT => {
|
||||||
|
let mut n: Box<f32> = Box::new(0.0);
|
||||||
|
libffi::raw::ffi_call(
|
||||||
|
&mut function_impl.cif,
|
||||||
|
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||||
|
&mut *n as *mut _ as *mut c_void,
|
||||||
|
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||||
|
);
|
||||||
|
Ok(Value::Float((*n).into()))
|
||||||
|
},
|
||||||
|
libffi::raw::FFI_TYPE_DOUBLE => {
|
||||||
|
let mut n: Box<f64> = Box::new(0.0);
|
||||||
|
libffi::raw::ffi_call(
|
||||||
|
&mut function_impl.cif,
|
||||||
|
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||||
|
&mut *n as *mut _ as *mut c_void,
|
||||||
|
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||||
|
);
|
||||||
|
Ok(Value::Float(*n))
|
||||||
|
},
|
||||||
|
libffi::raw::FFI_TYPE_STRUCT => {
|
||||||
|
let name = &function_impl.return_struct_name.clone().ok_or(FFIError::StructNotFound)?;
|
||||||
|
let struct_type = self.structs.get(name).ok_or(FFIError::StructNotFound)?;
|
||||||
|
let layout = Layout::from_size_align(struct_type.ffi_type.size, struct_type.ffi_type.alignment.into()).unwrap();
|
||||||
|
let ptr = alloc(layout) as *mut c_void;
|
||||||
|
|
||||||
|
libffi::raw::ffi_call(
|
||||||
|
&mut function_impl.cif,
|
||||||
|
Some(*function_impl.code_ptr.as_safe_fun()),
|
||||||
|
&mut *ptr as *mut _ as *mut c_void,
|
||||||
|
pointer_args.pointers.as_mut_ptr() as *mut *mut c_void
|
||||||
|
);
|
||||||
|
let struct_val = self.read_struct(ptr, name, struct_type);
|
||||||
|
drop(Box::from_raw(ptr));
|
||||||
|
struct_val
|
||||||
|
}
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_struct(&self, ptr: *mut c_void, name: &str, struct_type: &StructImpl) -> Result<Value, FFIError> {
|
||||||
|
unsafe {
|
||||||
|
let mut returns = Vec::new();
|
||||||
|
let mut field_ptr = ptr;
|
||||||
|
|
||||||
|
for i in 0..(struct_type.fields.len()-1) {
|
||||||
|
let field = struct_type.fields[i];
|
||||||
|
|
||||||
|
macro_rules! read_and_push_int {
|
||||||
|
($type:ty) => {
|
||||||
|
{
|
||||||
|
field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<$type>()));
|
||||||
|
let n = std::ptr::read(field_ptr as *mut $type);
|
||||||
|
returns.push(Value::Int(i64::from(n)));
|
||||||
|
field_ptr = field_ptr.add(std::mem::size_of::<$type>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (*field).type_ as u32 {
|
||||||
|
libffi::raw::FFI_TYPE_UINT8 => read_and_push_int!(u8),
|
||||||
|
libffi::raw::FFI_TYPE_SINT8 => read_and_push_int!(i8),
|
||||||
|
libffi::raw::FFI_TYPE_UINT16 => read_and_push_int!(u16),
|
||||||
|
libffi::raw::FFI_TYPE_SINT16 => read_and_push_int!(i16),
|
||||||
|
libffi::raw::FFI_TYPE_UINT32 => read_and_push_int!(u32),
|
||||||
|
libffi::raw::FFI_TYPE_SINT32 => read_and_push_int!(i32),
|
||||||
|
libffi::raw::FFI_TYPE_UINT64 => {
|
||||||
|
field_ptr = field_ptr.add(field_ptr.align_offset(std::mem::align_of::<u64>()));
|
||||||
|
let n = std::ptr::read(field_ptr as *mut u64);
|
||||||
|
returns.push(Value::Int(i64::try_from(n).map_err(|_| FFIError::ValueDontFit)?));
|
||||||
|
field_ptr = field_ptr.add(std::mem::size_of::<u64>());
|
||||||
|
},
|
||||||
|
libffi::raw::FFI_TYPE_SINT64 => read_and_push_int!(i64),
|
||||||
|
libffi::raw::FFI_TYPE_POINTER => read_and_push_int!(i64),
|
||||||
|
libffi::raw::FFI_TYPE_STRUCT => {
|
||||||
|
let substruct = struct_type.atom_fields[i].as_str();
|
||||||
|
let struct_type = self.structs.get(substruct).ok_or(FFIError::StructNotFound)?;
|
||||||
|
field_ptr = field_ptr.add(field_ptr.align_offset(struct_type.ffi_type.alignment as usize));
|
||||||
|
let struct_val = self.read_struct(field_ptr, substruct, struct_type);
|
||||||
|
returns.push(struct_val?);
|
||||||
|
field_ptr = field_ptr.add(struct_type.ffi_type.size);
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Value::Struct(name.into(), returns))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Value {
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
CString(CString),
|
||||||
|
Struct(String, Vec<Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
fn as_int(&self) -> Result<i64, FFIError> {
|
||||||
|
match self {
|
||||||
|
Value::Int(n) => Ok(*n),
|
||||||
|
_ => Err(FFIError::ValueCast),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_float(&self) -> Result<f64, FFIError> {
|
||||||
|
match self {
|
||||||
|
Value::Float(n) => Ok(*n),
|
||||||
|
Value::Int(n) => Ok(*n as f64),
|
||||||
|
_ => Err(FFIError::ValueCast),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_ptr(&mut self) -> Result<*mut c_void, FFIError> {
|
||||||
|
match self {
|
||||||
|
Value::CString(ref mut cstr) => Ok(&mut *cstr as *mut _ as *mut c_void),
|
||||||
|
Value::Int(n) => Ok(*n as *mut c_void),
|
||||||
|
_ => Err(FFIError::ValueCast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FFIError {
|
||||||
|
ValueCast,
|
||||||
|
ValueDontFit,
|
||||||
|
InvalidFFIType,
|
||||||
|
InvalidStructName,
|
||||||
|
FunctionNotFound,
|
||||||
|
StructNotFound,
|
||||||
|
}
|
||||||
1031
src/forms.rs
Normal file
1031
src/forms.rs
Normal file
File diff suppressed because it is too large
Load Diff
2919
src/heap_iter.rs
Normal file
2919
src/heap_iter.rs
Normal file
File diff suppressed because it is too large
Load Diff
2034
src/heap_print.rs
Normal file
2034
src/heap_print.rs
Normal file
File diff suppressed because it is too large
Load Diff
54
src/http.rs
Normal file
54
src/http.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use std::sync::{Arc, Mutex, Condvar};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use http_body_util::Full;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use hyper::service::Service;
|
||||||
|
use hyper::{body::Incoming as IncomingBody, Request, Response};
|
||||||
|
|
||||||
|
pub struct HttpListener {
|
||||||
|
pub incoming: std::sync::mpsc::Receiver<HttpRequest>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HttpRequest {
|
||||||
|
pub request: Request<IncomingBody>,
|
||||||
|
pub response: HttpResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type HttpResponse = Arc<(Mutex<bool>, Mutex<Option<Response<Full<Bytes>>>>, Condvar)>;
|
||||||
|
|
||||||
|
pub struct HttpService {
|
||||||
|
pub tx: std::sync::mpsc::SyncSender<HttpRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service<Request<IncomingBody>> for HttpService {
|
||||||
|
type Response = Response<Full<Bytes>>;
|
||||||
|
type Error = hyper::Error;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<IncomingBody>) -> Self::Future {
|
||||||
|
// new connection!
|
||||||
|
// we send the Request info to Prolog
|
||||||
|
let response = Arc::new((Mutex::new(false), Mutex::new(None), Condvar::new()));
|
||||||
|
let http_request = HttpRequest { request: req, response: Arc::clone(&response) };
|
||||||
|
self.tx.send(http_request).unwrap();
|
||||||
|
|
||||||
|
// we wait for the Response info from Prolog
|
||||||
|
{
|
||||||
|
let (ready, _response, cvar) = &*response;
|
||||||
|
let mut ready = ready.lock().unwrap();
|
||||||
|
while !*ready {
|
||||||
|
ready = cvar.wait(ready).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let (_, response, _) = &*response;
|
||||||
|
let response = response.lock().unwrap().take();
|
||||||
|
let res = response.expect("Data race error in HTTP Server");
|
||||||
|
Box::pin(async move {
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1558
src/indexing.rs
Normal file
1558
src/indexing.rs
Normal file
File diff suppressed because it is too large
Load Diff
448
src/iterators.rs
Normal file
448
src/iterators.rs
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
use crate::atom_table::*;
|
||||||
|
use crate::forms::*;
|
||||||
|
use crate::instructions::*;
|
||||||
|
use crate::parser::ast::*;
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::iter::*;
|
||||||
|
use std::vec::Vec;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) enum TermRef<'a> {
|
||||||
|
AnonVar(Level),
|
||||||
|
Cons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||||
|
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||||
|
Clause(Level, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||||
|
PartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||||
|
CompleteString(Level, &'a Cell<RegType>, Atom),
|
||||||
|
Var(Level, &'a Cell<VarReg>, 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum TermIterState<'a> {
|
||||||
|
AnonVar(Level),
|
||||||
|
Clause(Level, usize, &'a Cell<RegType>, Atom, &'a Vec<Term>),
|
||||||
|
Literal(Level, &'a Cell<RegType>, &'a Literal),
|
||||||
|
InitialCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||||
|
FinalCons(Level, &'a Cell<RegType>, &'a Term, &'a Term),
|
||||||
|
InitialPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||||
|
FinalPartialString(Level, &'a Cell<RegType>, &'a String, &'a Box<Term>),
|
||||||
|
CompleteString(Level, &'a Cell<RegType>, Atom),
|
||||||
|
Var(Level, &'a Cell<VarReg>, VarPtr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TermIterState<'a> {
|
||||||
|
pub(crate) fn subterm_to_state(lvl: Level, term: &'a Term) -> TermIterState<'a> {
|
||||||
|
match term {
|
||||||
|
Term::AnonVar => TermIterState::AnonVar(lvl),
|
||||||
|
Term::Clause(cell, name, subterms) => {
|
||||||
|
TermIterState::Clause(lvl, 0, cell, *name, subterms)
|
||||||
|
}
|
||||||
|
Term::Cons(cell, head, tail) => {
|
||||||
|
TermIterState::InitialCons(lvl, cell, head.as_ref(), tail.as_ref())
|
||||||
|
}
|
||||||
|
Term::Literal(cell, constant) => TermIterState::Literal(lvl, cell, constant),
|
||||||
|
Term::PartialString(cell, string_buf, tail) => {
|
||||||
|
TermIterState::InitialPartialString(lvl, cell, string_buf, tail)
|
||||||
|
}
|
||||||
|
Term::CompleteString(cell, atom) => {
|
||||||
|
TermIterState::CompleteString(lvl, cell, *atom)
|
||||||
|
}
|
||||||
|
Term::Var(cell, var_ptr) => TermIterState::Var(lvl, cell, var_ptr.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct QueryIterator<'a> {
|
||||||
|
state_stack: Vec<TermIterState<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> QueryIterator<'a> {
|
||||||
|
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
|
||||||
|
self.state_stack.push(TermIterState::subterm_to_state(lvl, term));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fn from_rule_head_clause(terms: &'a Vec<Term>) -> Self {
|
||||||
|
let state_stack = terms
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
QueryIterator { state_stack }
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
fn from_term(term: &'a Term) -> Self {
|
||||||
|
let state = match term {
|
||||||
|
Term::AnonVar | Term::Cons(..) | Term::Literal(..) |
|
||||||
|
Term::PartialString(..) | Term::CompleteString(..) => {
|
||||||
|
return QueryIterator {
|
||||||
|
state_stack: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Term::Clause(r, name, terms) => TermIterState::Clause(
|
||||||
|
Level::Root,
|
||||||
|
0,
|
||||||
|
r,
|
||||||
|
*name,
|
||||||
|
terms,
|
||||||
|
),
|
||||||
|
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Root, cell, var_ptr.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
QueryIterator {
|
||||||
|
state_stack: vec![state],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_state(&mut self, lvl: Level, term: &'a QueryTerm) {
|
||||||
|
match term {
|
||||||
|
&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, _) => {
|
||||||
|
self.state_stack.push(TermIterState::Clause(lvl, 0, cell, ct.name(), terms));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(term: &'a QueryTerm) -> Self {
|
||||||
|
let mut iter = QueryIterator { state_stack: vec![] };
|
||||||
|
iter.extend_state(Level::Root, term);
|
||||||
|
iter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for QueryIterator<'a> {
|
||||||
|
type Item = TermRef<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while let Some(iter_state) = self.state_stack.pop() {
|
||||||
|
match iter_state {
|
||||||
|
TermIterState::AnonVar(lvl) => {
|
||||||
|
return Some(TermRef::AnonVar(lvl));
|
||||||
|
}
|
||||||
|
TermIterState::Clause(lvl, child_num, cell, name, child_terms) => {
|
||||||
|
if child_num == child_terms.len() {
|
||||||
|
match name {
|
||||||
|
atom!("$call") if lvl == Level::Root => {
|
||||||
|
self.push_subterm(Level::Shallow, &child_terms[0]);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return match lvl {
|
||||||
|
Level::Root => None,
|
||||||
|
lvl => Some(TermRef::Clause(lvl, cell, name, child_terms)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.state_stack.push(TermIterState::Clause(
|
||||||
|
lvl,
|
||||||
|
child_num + 1,
|
||||||
|
cell,
|
||||||
|
name,
|
||||||
|
child_terms,
|
||||||
|
));
|
||||||
|
|
||||||
|
self.push_subterm(lvl.child_level(), &child_terms[child_num]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TermIterState::InitialCons(lvl, cell, head, tail) => {
|
||||||
|
self.state_stack.push(TermIterState::FinalCons(lvl, cell, head, tail));
|
||||||
|
|
||||||
|
self.push_subterm(lvl.child_level(), tail);
|
||||||
|
self.push_subterm(lvl.child_level(), head);
|
||||||
|
}
|
||||||
|
TermIterState::InitialPartialString(lvl, cell, string, tail) => {
|
||||||
|
self.state_stack.push(TermIterState::FinalPartialString(lvl, cell, string, tail));
|
||||||
|
self.push_subterm(lvl.child_level(), tail);
|
||||||
|
}
|
||||||
|
TermIterState::FinalPartialString(lvl, cell, atom, tail) => {
|
||||||
|
return Some(TermRef::PartialString(lvl, cell, atom, tail));
|
||||||
|
}
|
||||||
|
TermIterState::CompleteString(lvl, cell, atom) => {
|
||||||
|
return Some(TermRef::CompleteString(lvl, cell, atom));
|
||||||
|
}
|
||||||
|
TermIterState::FinalCons(lvl, cell, head, tail) => {
|
||||||
|
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||||
|
}
|
||||||
|
TermIterState::Literal(lvl, cell, constant) => {
|
||||||
|
return Some(TermRef::Literal(lvl, cell, constant));
|
||||||
|
}
|
||||||
|
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||||
|
return Some(TermRef::Var(lvl, cell, var_ptr));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct FactIterator<'a> {
|
||||||
|
state_queue: VecDeque<TermIterState<'a>>,
|
||||||
|
iterable_root: RootIterationPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FactIterator<'a> {
|
||||||
|
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
|
||||||
|
self.state_queue
|
||||||
|
.push_back(TermIterState::subterm_to_state(lvl, term));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_rule_head_clause(terms: &'a Vec<Term>) -> Self {
|
||||||
|
let state_queue = terms
|
||||||
|
.iter()
|
||||||
|
.map(|bt| TermIterState::subterm_to_state(Level::Shallow, bt))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
FactIterator {
|
||||||
|
state_queue,
|
||||||
|
iterable_root: RootIterationPolicy::NotIterated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(term: &'a Term, iterable_root: RootIterationPolicy) -> Self {
|
||||||
|
let states = match term {
|
||||||
|
Term::AnonVar => {
|
||||||
|
vec![TermIterState::AnonVar(Level::Root)]
|
||||||
|
}
|
||||||
|
Term::Clause(cell, name, terms) => {
|
||||||
|
vec![TermIterState::Clause(Level::Root, 0, cell, *name, terms)]
|
||||||
|
}
|
||||||
|
Term::Cons(cell, head, tail) => vec![TermIterState::InitialCons(
|
||||||
|
Level::Root,
|
||||||
|
cell,
|
||||||
|
head.as_ref(),
|
||||||
|
tail.as_ref(),
|
||||||
|
)],
|
||||||
|
Term::PartialString(cell, string_buf, tail) => {
|
||||||
|
vec![TermIterState::InitialPartialString(
|
||||||
|
Level::Root,
|
||||||
|
cell,
|
||||||
|
string_buf,
|
||||||
|
tail,
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
Term::CompleteString(cell, atom) => {
|
||||||
|
vec![TermIterState::CompleteString(
|
||||||
|
Level::Root,
|
||||||
|
cell,
|
||||||
|
*atom,
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
Term::Literal(cell, constant) => {
|
||||||
|
vec![TermIterState::Literal(Level::Root, cell, constant)]
|
||||||
|
}
|
||||||
|
Term::Var(cell, var_ptr) => {
|
||||||
|
vec![TermIterState::Var(Level::Root, cell, var_ptr.clone())]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FactIterator {
|
||||||
|
state_queue: VecDeque::from(states),
|
||||||
|
iterable_root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for FactIterator<'a> {
|
||||||
|
type Item = TermRef<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while let Some(state) = self.state_queue.pop_front() {
|
||||||
|
match state {
|
||||||
|
TermIterState::AnonVar(lvl) => {
|
||||||
|
return Some(TermRef::AnonVar(lvl));
|
||||||
|
}
|
||||||
|
TermIterState::Clause(lvl, _, cell, name, child_terms) => {
|
||||||
|
for child_term in child_terms {
|
||||||
|
self.push_subterm(lvl.child_level(), child_term);
|
||||||
|
}
|
||||||
|
|
||||||
|
match lvl {
|
||||||
|
Level::Root if !self.iterable_root.iterable() => continue,
|
||||||
|
_ => return Some(TermRef::Clause(lvl, cell, name, child_terms)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
TermIterState::InitialCons(lvl, cell, head, tail) => {
|
||||||
|
self.push_subterm(Level::Deep, head);
|
||||||
|
self.push_subterm(Level::Deep, tail);
|
||||||
|
|
||||||
|
return Some(TermRef::Cons(lvl, cell, head, tail));
|
||||||
|
}
|
||||||
|
TermIterState::InitialPartialString(lvl, cell, string_buf, tail) => {
|
||||||
|
self.push_subterm(Level::Deep, tail);
|
||||||
|
return Some(TermRef::PartialString(lvl, cell, string_buf, tail));
|
||||||
|
}
|
||||||
|
TermIterState::CompleteString(lvl, cell, atom) => {
|
||||||
|
return Some(TermRef::CompleteString(lvl, cell, atom));
|
||||||
|
}
|
||||||
|
TermIterState::Literal(lvl, cell, constant) => {
|
||||||
|
return Some(TermRef::Literal(lvl, cell, constant))
|
||||||
|
}
|
||||||
|
TermIterState::Var(lvl, cell, var_ptr) => {
|
||||||
|
return Some(TermRef::Var(lvl, cell, var_ptr));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn post_order_iter<'a>(term: &'a Term) -> QueryIterator<'a> {
|
||||||
|
QueryIterator::from_term(term)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn breadth_first_iter<'a>(term: &'a Term, iterable_root: RootIterationPolicy) -> FactIterator<'a> {
|
||||||
|
FactIterator::new(term, iterable_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
enum ClauseIteratorState<'a> {
|
||||||
|
RemainingChunks(&'a VecDeque<ChunkedTerms>, usize),
|
||||||
|
RemainingBranches(&'a Vec<VecDeque<ChunkedTerms>>, usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) enum ClauseItem<'a> {
|
||||||
|
FirstBranch(usize),
|
||||||
|
NextBranch,
|
||||||
|
BranchEnd(usize),
|
||||||
|
Chunk(&'a VecDeque<QueryTerm>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ClauseIterator<'a> {
|
||||||
|
state_stack: Vec<ClauseIteratorState<'a>>,
|
||||||
|
remaining_chunks_on_stack: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_from_chunked_terms<'a>(chunk_vec: &'a VecDeque<ChunkedTerms>) -> ClauseIteratorState<'a> {
|
||||||
|
if chunk_vec.len() == 1 {
|
||||||
|
if let Some(ChunkedTerms::Branch(ref branches)) = chunk_vec.front() {
|
||||||
|
return ClauseIteratorState::RemainingBranches(branches, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClauseIteratorState::RemainingChunks(chunk_vec, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ClauseIterator<'a> {
|
||||||
|
pub fn new(clauses: &'a ChunkedTermVec) -> Self {
|
||||||
|
match state_from_chunked_terms(&clauses.chunk_vec) {
|
||||||
|
state @ ClauseIteratorState::RemainingBranches(..) => {
|
||||||
|
Self {
|
||||||
|
state_stack: vec![state],
|
||||||
|
remaining_chunks_on_stack: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state @ ClauseIteratorState::RemainingChunks(..) => {
|
||||||
|
Self {
|
||||||
|
state_stack: vec![state],
|
||||||
|
remaining_chunks_on_stack: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn in_tail_position(&self) -> bool {
|
||||||
|
self.remaining_chunks_on_stack == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branch_end_depth(&mut self) -> usize {
|
||||||
|
let mut depth = 1;
|
||||||
|
|
||||||
|
while let Some(state) = self.state_stack.pop() {
|
||||||
|
match state {
|
||||||
|
ClauseIteratorState::RemainingBranches(terms, focus) if terms.len() == focus => {
|
||||||
|
depth += 1;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.state_stack.push(state);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for ClauseIterator<'a> {
|
||||||
|
type Item = ClauseItem<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
while let Some(state) = self.state_stack.pop() {
|
||||||
|
match state {
|
||||||
|
ClauseIteratorState::RemainingChunks(chunks, focus) if focus < chunks.len() => {
|
||||||
|
if focus + 1 < chunks.len() {
|
||||||
|
self.state_stack.push(ClauseIteratorState::RemainingChunks(chunks, focus + 1));
|
||||||
|
} else {
|
||||||
|
self.remaining_chunks_on_stack -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &chunks[focus] {
|
||||||
|
ChunkedTerms::Branch(branches) => {
|
||||||
|
self.state_stack.push(ClauseIteratorState::RemainingBranches(branches, 0));
|
||||||
|
}
|
||||||
|
ChunkedTerms::Chunk(chunk) => {
|
||||||
|
return Some(ClauseItem::Chunk(chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClauseIteratorState::RemainingChunks(chunks, focus) => {
|
||||||
|
debug_assert_eq!(chunks.len(), focus);
|
||||||
|
}
|
||||||
|
ClauseIteratorState::RemainingBranches(branches, focus) if focus < branches.len() => {
|
||||||
|
self.state_stack.push(ClauseIteratorState::RemainingBranches(&branches, focus + 1));
|
||||||
|
let state = state_from_chunked_terms(&branches[focus]);
|
||||||
|
|
||||||
|
if let ClauseIteratorState::RemainingChunks(..) = &state {
|
||||||
|
self.remaining_chunks_on_stack += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state_stack.push(state);
|
||||||
|
|
||||||
|
return if focus == 0 {
|
||||||
|
Some(ClauseItem::FirstBranch(branches.len()))
|
||||||
|
} else {
|
||||||
|
Some(ClauseItem::NextBranch)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ClauseIteratorState::RemainingBranches(branches, focus) => {
|
||||||
|
debug_assert_eq!(branches.len(), focus);
|
||||||
|
return Some(ClauseItem::BranchEnd(self.branch_end_depth()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/lib.rs
Normal file
40
src/lib.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#![recursion_limit = "4112"]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate static_assertions;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod macros;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod atom_table;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod arena;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod parser;
|
||||||
|
mod allocator;
|
||||||
|
mod arithmetic;
|
||||||
|
pub mod codegen;
|
||||||
|
mod debray_allocator;
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
mod ffi;
|
||||||
|
mod variable_records;
|
||||||
|
mod forms;
|
||||||
|
mod heap_iter;
|
||||||
|
pub mod heap_print;
|
||||||
|
#[cfg(feature = "http")]
|
||||||
|
mod http;
|
||||||
|
mod indexing;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod instructions {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/instructions.rs"));
|
||||||
|
}
|
||||||
|
mod iterators;
|
||||||
|
pub mod machine;
|
||||||
|
mod raw_block;
|
||||||
|
pub mod read;
|
||||||
|
#[cfg(feature = "repl")]
|
||||||
|
mod repl_helper;
|
||||||
|
mod targets;
|
||||||
|
pub mod types;
|
||||||
|
|
||||||
|
use instructions::instr;
|
||||||
163
src/lib/arithmetic.pl
Normal file
163
src/lib/arithmetic.pl
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/** Arithmetic predicates
|
||||||
|
|
||||||
|
These predicates are additions to standard the arithmetic functions provided by `is/2`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(arithmetic, [expmod/4, lcm/3, lsb/2, msb/2, number_to_rational/2,
|
||||||
|
number_to_rational/3, popcount/2,
|
||||||
|
rational_numerator_denominator/3]).
|
||||||
|
|
||||||
|
:- use_module(library(charsio), [write_term_to_chars/3]).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(lists), [append/3, member/2]).
|
||||||
|
|
||||||
|
|
||||||
|
%% expmod(+Base, +Expo, +Mod, -R).
|
||||||
|
%
|
||||||
|
% Modular exponentiation. Base, Expo and Mod must be integers.
|
||||||
|
expmod(Base, Expo, Mod, R) :-
|
||||||
|
( member(N, [Base, Expo, Mod]), var(N) -> instantiation_error(expmod/4)
|
||||||
|
; member(N, [Base, Expo, Mod]), \+ integer(N) ->
|
||||||
|
type_error(integer, N, expmod/4)
|
||||||
|
; Expo < 0 -> domain_error(not_less_than_zero, Expo, expmod/4)
|
||||||
|
; expmod_(Base, Expo, Mod, 1, R)
|
||||||
|
).
|
||||||
|
|
||||||
|
expmod_(_, _, 1, _, 0) :- !.
|
||||||
|
expmod_(_, 0, _, R, R) :- !.
|
||||||
|
expmod_(Base0, Expo0, Mod, C0, R) :-
|
||||||
|
Expo0 /\ 1 =:= 1,
|
||||||
|
C is (C0 * Base0) mod Mod,
|
||||||
|
!,
|
||||||
|
Expo is Expo0 >> 1,
|
||||||
|
Base is (Base0 * Base0) mod Mod,
|
||||||
|
expmod_(Base, Expo, Mod, C, R).
|
||||||
|
expmod_(Base0, Expo0, Mod, C, R) :-
|
||||||
|
Expo is Expo0 >> 1,
|
||||||
|
Base is (Base0 * Base0) mod Mod,
|
||||||
|
expmod_(Base, Expo, Mod, C, R).
|
||||||
|
|
||||||
|
%% lcm(+A, +B, -Lcm) is det.
|
||||||
|
%
|
||||||
|
% Calculates the Least common multiple for A and B: the smallest positive integer
|
||||||
|
% that is divisible by both A and B.
|
||||||
|
%
|
||||||
|
% A and B need to be integers.
|
||||||
|
lcm(A, B, X) :-
|
||||||
|
builtins:must_be_number(A, lcm/2),
|
||||||
|
builtins:must_be_number(B, lcm/2),
|
||||||
|
( \+ integer(A) -> type_error(integer, A, lcm/2)
|
||||||
|
; \+ integer(B) -> type_error(integer, B, lcm/2)
|
||||||
|
; (A = 0, B = 0) -> X = 0
|
||||||
|
; builtins:can_be_number(X, lcm/2),
|
||||||
|
X is abs(B) // gcd(A,B) * abs(A)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% lsb(+X, -N).
|
||||||
|
%
|
||||||
|
% True iff N is the least significat bit of integer X
|
||||||
|
lsb(X, N) :-
|
||||||
|
builtins:must_be_number(X, lsb/2),
|
||||||
|
( \+ integer(X) -> type_error(integer, X, lsb/2)
|
||||||
|
; X < 1 -> domain_error(not_less_than_one, X, lsb/2)
|
||||||
|
; builtins:can_be_number(N, lsb/2),
|
||||||
|
X1 is X /\ (-X),
|
||||||
|
msb_(X1, -1, N)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% msb(+X, -N).
|
||||||
|
%
|
||||||
|
% True iff N is the most significant bit of integer X
|
||||||
|
msb(X, N) :-
|
||||||
|
builtins:must_be_number(X, msb/2),
|
||||||
|
( \+ integer(X) -> type_error(integer, X, msb/2)
|
||||||
|
; X < 1 -> domain_error(not_less_than_one, X, msb/2)
|
||||||
|
; builtins:can_be_number(N, msb/2),
|
||||||
|
X1 is X >> 1,
|
||||||
|
msb_(X1, 0, N)
|
||||||
|
).
|
||||||
|
|
||||||
|
msb_(0, N, N) :- !.
|
||||||
|
msb_(X, M, N) :-
|
||||||
|
X1 is X >> 1,
|
||||||
|
M1 is M + 1,
|
||||||
|
msb_(X1, M1, N).
|
||||||
|
|
||||||
|
%% number_to_rational(+Real, -Fraction).
|
||||||
|
%
|
||||||
|
% True iff given a number Real, Fraction is the same number represented as a fraction.
|
||||||
|
number_to_rational(Real, Fraction) :-
|
||||||
|
( var(Real) -> instantiation_error(number_to_rational/2)
|
||||||
|
; integer(Real) -> Fraction is Real rdiv 1
|
||||||
|
; (rational(Real) ; float(Real)) ->
|
||||||
|
number_to_rational(1.0e-6, Real, Fraction)
|
||||||
|
; type_error(number, Real, number_to_rational/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
% If 0 <= Eps0 <= 1e-16 then the search is for "infinite" precision.
|
||||||
|
number_to_rational(Eps0, Real0, Fraction) :-
|
||||||
|
( var(Eps0) -> instantiation_error(number_to_rational/3)
|
||||||
|
; \+ number(Eps0) -> type_error(number, Eps0, number_to_rational/3)
|
||||||
|
; Eps0 < 0 -> domain_error(not_less_than_zero, Eps0, number_to_rational/3)
|
||||||
|
; Eps_ is Eps0 rdiv 1,
|
||||||
|
rational_numerator_denominator(Eps_, EpsN, EpsD),
|
||||||
|
Eps = EpsN/EpsD
|
||||||
|
),
|
||||||
|
( var(Real0) -> instantiation_error(number_to_rational/3)
|
||||||
|
; \+ number(Real0) -> type_error(number, Eps0, number_to_rational/3)
|
||||||
|
; Real_ is Real0 rdiv 1,
|
||||||
|
rational_numerator_denominator(Real_, RealN, RealD),
|
||||||
|
Real = RealN/RealD
|
||||||
|
),
|
||||||
|
E0/E1 = Eps,
|
||||||
|
P0/Q0 = Real,
|
||||||
|
( P0 < 0 -> I1 is -1 + P0 // Q0
|
||||||
|
; I1 is P0 // Q0
|
||||||
|
),
|
||||||
|
P1 is P0 mod Q0,
|
||||||
|
Q1 = Q0,
|
||||||
|
( P1 =:= 0 -> Fraction is I1 + 0 rdiv 1
|
||||||
|
; Qn1n is max(P1 * E1 - Q1 * E0, 0),
|
||||||
|
Qn1d is Q1 * E1,
|
||||||
|
Qn1 = Qn1n/Qn1d,
|
||||||
|
Qp1n is P1 * E1 + Q1 * E0,
|
||||||
|
Qp1d = Qn1d,
|
||||||
|
Qp1 = Qp1n/Qp1d,
|
||||||
|
stern_brocot_(Qn1, Qp1, 0/1, 1/0, P2/Q2),
|
||||||
|
Fraction is I1 + P2 rdiv Q2
|
||||||
|
),
|
||||||
|
!.
|
||||||
|
|
||||||
|
stern_brocot_(Qnn/Qnd, Qpn/Qpd, A/B, C/D, Fraction) :-
|
||||||
|
Fn1 is A + C,
|
||||||
|
Fd1 is B + D,
|
||||||
|
simplify_fraction(Fn1/Fd1, Fn/Fd),
|
||||||
|
S1 is sign(Fn * Qnd - Fd * Qnn),
|
||||||
|
S2 is sign(Fn * Qpd - Fd * Qpn),
|
||||||
|
( S1 < 0 -> stern_brocot_(Qnn/Qnd, Qpn/Qpd, Fn/Fd, C/D, Fraction)
|
||||||
|
; S2 > 0 -> stern_brocot_(Qnn/Qnd, Qpn/Qpd, A/B, Fn/Fd, Fraction)
|
||||||
|
; Fraction = Fn/Fd
|
||||||
|
).
|
||||||
|
|
||||||
|
simplify_fraction(A0/B0, A/B) :-
|
||||||
|
G is gcd(A0, B0),
|
||||||
|
A is A0 // G,
|
||||||
|
B is B0 // G.
|
||||||
|
|
||||||
|
%% rational_numerator_denominator(+Fraction, -Numerator, -Denominator).
|
||||||
|
%
|
||||||
|
% True iff given a fraction Fraction, Numerator is the numerator of that fraction
|
||||||
|
% and Denominator the denominator.
|
||||||
|
rational_numerator_denominator(R, N, D) :-
|
||||||
|
write_term_to_chars(R, [], Cs),
|
||||||
|
append(Ns, [' ', r, d, i, v, ' '|Ds], Cs),
|
||||||
|
number_chars(N, Ns),
|
||||||
|
number_chars(D, Ds).
|
||||||
|
|
||||||
|
%% popcount(+Number, -Bits1).
|
||||||
|
%
|
||||||
|
% True iff given an integer Number, Bits1 is the amount of 1 bits the binary representation
|
||||||
|
% of that number has.
|
||||||
|
popcount(X, N) :-
|
||||||
|
must_be(integer, X),
|
||||||
|
'$popcount'(X, N).
|
||||||
@@ -54,31 +54,27 @@
|
|||||||
|
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
/** <module> Binary associations
|
/** Binary associations
|
||||||
|
|
||||||
Assocs are Key-Value associations implemented as a balanced binary tree
|
Assocs are Key-Value associations implemented as a balanced binary tree
|
||||||
(AVL tree).
|
(AVL tree).
|
||||||
|
|
||||||
@see library(pairs), library(rbtrees)
|
Authors: R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
|
||||||
@author R.A.O'Keefe, L.Damas, V.S.Costa and Jan Wielemaker
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
:- meta_predicate map_assoc(1, ?).
|
||||||
:- meta_predicate
|
:- meta_predicate map_assoc(2, ?, ?).
|
||||||
map_assoc(1, ?),
|
|
||||||
map_assoc(2, ?, ?).
|
|
||||||
*/
|
|
||||||
|
|
||||||
%! empty_assoc(?Assoc) is semidet.
|
%% empty_assoc(?Assoc) is semidet.
|
||||||
%
|
%
|
||||||
% Is true if Assoc is the empty association list.
|
% Is true if Assoc is the empty association list.
|
||||||
|
|
||||||
empty_assoc(t).
|
empty_assoc(t).
|
||||||
|
|
||||||
%! assoc_to_list(+Assoc, -Pairs) is det.
|
%% assoc_to_list(+Assoc, -Pairs) is det.
|
||||||
%
|
%
|
||||||
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
|
% Translate Assoc to a list Pairs of Key-Value pairs. The keys
|
||||||
% in Pairs are sorted in ascending order.
|
% in Pairs are sorted in ascending order.
|
||||||
|
|
||||||
assoc_to_list(Assoc, List) :-
|
assoc_to_list(Assoc, List) :-
|
||||||
assoc_to_list(Assoc, List, []).
|
assoc_to_list(Assoc, List, []).
|
||||||
@@ -89,10 +85,10 @@ assoc_to_list(t(Key,Val,_,L,R), List, Rest) :-
|
|||||||
assoc_to_list(t, List, List).
|
assoc_to_list(t, List, List).
|
||||||
|
|
||||||
|
|
||||||
%! assoc_to_keys(+Assoc, -Keys) is det.
|
%% assoc_to_keys(+Assoc, -Keys) is det.
|
||||||
%
|
%
|
||||||
% True if Keys is the list of keys in Assoc. The keys are sorted
|
% True if Keys is the list of keys in Assoc. The keys are sorted
|
||||||
% in ascending order.
|
% in ascending order.
|
||||||
|
|
||||||
assoc_to_keys(Assoc, List) :-
|
assoc_to_keys(Assoc, List) :-
|
||||||
assoc_to_keys(Assoc, List, []).
|
assoc_to_keys(Assoc, List, []).
|
||||||
@@ -103,11 +99,11 @@ assoc_to_keys(t(Key,_,_,L,R), List, Rest) :-
|
|||||||
assoc_to_keys(t, List, List).
|
assoc_to_keys(t, List, List).
|
||||||
|
|
||||||
|
|
||||||
%! assoc_to_values(+Assoc, -Values) is det.
|
%% assoc_to_values(+Assoc, -Values) is det.
|
||||||
%
|
%
|
||||||
% True if Values is the list of values in Assoc. Values are
|
% True if Values is the list of values in Assoc. Values are
|
||||||
% ordered in ascending order of the key to which they were
|
% ordered in ascending order of the key to which they were
|
||||||
% associated. Values may contain duplicates.
|
% associated. Values may contain duplicates.
|
||||||
|
|
||||||
assoc_to_values(Assoc, List) :-
|
assoc_to_values(Assoc, List) :-
|
||||||
assoc_to_values(Assoc, List, []).
|
assoc_to_values(Assoc, List, []).
|
||||||
@@ -117,12 +113,12 @@ assoc_to_values(t(_,Value,_,L,R), List, Rest) :-
|
|||||||
assoc_to_values(R, More, Rest).
|
assoc_to_values(R, More, Rest).
|
||||||
assoc_to_values(t, List, List).
|
assoc_to_values(t, List, List).
|
||||||
|
|
||||||
%! is_assoc(+Assoc) is semidet.
|
%% is_assoc(+Assoc) is semidet.
|
||||||
%
|
%
|
||||||
% True if Assoc is an association list. This predicate checks
|
% True if Assoc is an association list. This predicate checks
|
||||||
% that the structure is valid, elements are in order, and tree
|
% that the structure is valid, elements are in order, and tree
|
||||||
% is balanced to the extent guaranteed by AVL trees. I.e.,
|
% is balanced to the extent guaranteed by AVL trees. I.e.,
|
||||||
% branches of each subtree differ in depth by at most 1.
|
% branches of each subtree differ in depth by at most 1.
|
||||||
|
|
||||||
is_assoc(Assoc) :-
|
is_assoc(Assoc) :-
|
||||||
is_assoc(Assoc, _Min, _Max, _Depth).
|
is_assoc(Assoc, _Min, _Max, _Depth).
|
||||||
@@ -154,12 +150,10 @@ balance(=,-).
|
|||||||
balance(<,<).
|
balance(<,<).
|
||||||
balance(>,>).
|
balance(>,>).
|
||||||
|
|
||||||
%! gen_assoc(?Key, +Assoc, ?Value) is nondet.
|
%% gen_assoc(?Key, +Assoc, ?Value) is nondet.
|
||||||
%
|
%
|
||||||
% True if Key-Value is an association in Assoc. Enumerates keys in
|
% True if Key-Value is an association in Assoc. Enumerates keys in
|
||||||
% ascending order on backtracking.
|
% ascending order on backtracking.
|
||||||
%
|
|
||||||
% @see get_assoc/3.
|
|
||||||
|
|
||||||
gen_assoc(Key, Assoc, Value) :-
|
gen_assoc(Key, Assoc, Value) :-
|
||||||
( ground(Key)
|
( ground(Key)
|
||||||
@@ -174,11 +168,11 @@ gen_assoc_(Key, t(_,_,_,_,R), Val) :-
|
|||||||
gen_assoc_(Key, R, Val).
|
gen_assoc_(Key, R, Val).
|
||||||
|
|
||||||
|
|
||||||
%! get_assoc(+Key, +Assoc, -Value) is semidet.
|
%% get_assoc(+Key, +Assoc, -Value) is semidet.
|
||||||
%
|
%
|
||||||
% True if Key-Value is an association in Assoc.
|
% True if Key-Value is an association in Assoc.
|
||||||
%
|
%
|
||||||
% @error type_error(assoc, Assoc) if Assoc is not an association list.
|
% Throws error: `type_error(assoc, Assoc)` if Assoc is not an association list.
|
||||||
|
|
||||||
get_assoc(Key, Assoc, Val) :-
|
get_assoc(Key, Assoc, Val) :-
|
||||||
must_be(assoc, Assoc),
|
must_be(assoc, Assoc),
|
||||||
@@ -204,9 +198,9 @@ get_assoc(>, Key, _, _, Tree, Val) :-
|
|||||||
% :- endif.
|
% :- endif.
|
||||||
|
|
||||||
|
|
||||||
%! get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
|
%% get_assoc(+Key, +Assoc0, ?Val0, ?Assoc, ?Val) is semidet.
|
||||||
%
|
%
|
||||||
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
|
% True if Key-Val0 is in Assoc0 and Key-Val is in Assoc.
|
||||||
|
|
||||||
get_assoc(Key, t(K,V,B,L,R), Val, t(K,NV,B,NL,NR), NVal) :-
|
get_assoc(Key, t(K,V,B,L,R), Val, t(K,NV,B,NL,NR), NVal) :-
|
||||||
compare(Rel, Key, K),
|
compare(Rel, Key, K),
|
||||||
@@ -219,12 +213,12 @@ get_assoc(>, Key, V, L, R, Val, V, L, NR, NVal) :-
|
|||||||
get_assoc(Key, R, Val, NR, NVal).
|
get_assoc(Key, R, Val, NR, NVal).
|
||||||
|
|
||||||
|
|
||||||
%! list_to_assoc(+Pairs, -Assoc) is det.
|
%% list_to_assoc(+Pairs, -Assoc) is det.
|
||||||
%
|
%
|
||||||
% Create an association from a list Pairs of Key-Value pairs. List
|
% Create an association from a list Pairs of Key-Value pairs. List
|
||||||
% must not contain duplicate keys.
|
% must not contain duplicate keys.
|
||||||
%
|
%
|
||||||
% @error domain_error(unique_key_pairs, List) if List contains duplicate keys
|
% Throws error: `domain_error(unique_key_pairs, List)` if List contains duplicate keys
|
||||||
|
|
||||||
list_to_assoc(List, Assoc) :-
|
list_to_assoc(List, Assoc) :-
|
||||||
( List = [] -> Assoc = t
|
( List = [] -> Assoc = t
|
||||||
@@ -249,13 +243,13 @@ list_to_assoc(N, List, More, Depth, t(K,V,Balance,L,R)) :-
|
|||||||
compare(B, RDepth, LDepth),
|
compare(B, RDepth, LDepth),
|
||||||
balance(B, Balance).
|
balance(B, Balance).
|
||||||
|
|
||||||
%! ord_list_to_assoc(+Pairs, -Assoc) is det.
|
%% ord_list_to_assoc(+Pairs, -Assoc) is det.
|
||||||
%
|
%
|
||||||
% Assoc is created from an ordered list Pairs of Key-Value
|
% Assoc is created from an ordered list Pairs of Key-Value
|
||||||
% pairs. The pairs must occur in strictly ascending order of
|
% pairs. The pairs must occur in strictly ascending order of
|
||||||
% their keys.
|
% their keys.
|
||||||
%
|
%
|
||||||
% @error domain_error(key_ordered_pairs, List) if pairs are not ordered.
|
% Throws error: `domain_error(key_ordered_pairs, List)` if pairs are not ordered.
|
||||||
|
|
||||||
ord_list_to_assoc(Sorted, Assoc) :-
|
ord_list_to_assoc(Sorted, Assoc) :-
|
||||||
( Sorted = [] -> Assoc = t
|
( Sorted = [] -> Assoc = t
|
||||||
@@ -266,9 +260,9 @@ ord_list_to_assoc(Sorted, Assoc) :-
|
|||||||
)
|
)
|
||||||
).
|
).
|
||||||
|
|
||||||
%! ord_pairs(+Pairs) is semidet
|
%% ord_pairs(+Pairs) is semidet
|
||||||
%
|
%
|
||||||
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
|
% True if Pairs is a list of Key-Val pairs strictly ordered by key.
|
||||||
|
|
||||||
ord_pairs([K-_V|Rest]) :-
|
ord_pairs([K-_V|Rest]) :-
|
||||||
ord_pairs(Rest, K).
|
ord_pairs(Rest, K).
|
||||||
@@ -277,9 +271,9 @@ ord_pairs([K-_V|Rest], K0) :-
|
|||||||
K0 @< K,
|
K0 @< K,
|
||||||
ord_pairs(Rest, K).
|
ord_pairs(Rest, K).
|
||||||
|
|
||||||
%! map_assoc(:Pred, +Assoc) is semidet.
|
%% map_assoc(:Pred, +Assoc) is semidet.
|
||||||
%
|
%
|
||||||
% True if Pred(Value) is true for all values in Assoc.
|
% True if Pred(Value) is true for all values in Assoc.
|
||||||
|
|
||||||
map_assoc(Pred, T) :-
|
map_assoc(Pred, T) :-
|
||||||
map_assoc_(T, Pred).
|
map_assoc_(T, Pred).
|
||||||
@@ -290,10 +284,10 @@ map_assoc_(t(_,Val,_,L,R), Pred) :-
|
|||||||
call(Pred, Val),
|
call(Pred, Val),
|
||||||
map_assoc_(R, Pred).
|
map_assoc_(R, Pred).
|
||||||
|
|
||||||
%! map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
|
%% map_assoc(:Pred, +Assoc0, ?Assoc) is semidet.
|
||||||
%
|
%
|
||||||
% Map corresponding values. True if Assoc is Assoc0 with Pred
|
% Map corresponding values. True if Assoc is Assoc0 with Pred
|
||||||
% applied to all corresponding pairs of of values.
|
% applied to all corresponding pairs of of values.
|
||||||
|
|
||||||
map_assoc(Pred, T0, T) :-
|
map_assoc(Pred, T0, T) :-
|
||||||
map_assoc_(T0, Pred, T).
|
map_assoc_(T0, Pred, T).
|
||||||
@@ -305,9 +299,9 @@ map_assoc_(t(Key,Val,B,L0,R0), Pred, t(Key,Ans,B,L1,R1)) :-
|
|||||||
map_assoc_(R0, Pred, R1).
|
map_assoc_(R0, Pred, R1).
|
||||||
|
|
||||||
|
|
||||||
%! max_assoc(+Assoc, -Key, -Value) is semidet.
|
%% max_assoc(+Assoc, -Key, -Value) is semidet.
|
||||||
%
|
%
|
||||||
% True if Key-Value is in Assoc and Key is the largest key.
|
% True if Key-Value is in Assoc and Key is the largest key.
|
||||||
|
|
||||||
max_assoc(t(K,V,_,_,R), Key, Val) :-
|
max_assoc(t(K,V,_,_,R), Key, Val) :-
|
||||||
max_assoc(R, K, V, Key, Val).
|
max_assoc(R, K, V, Key, Val).
|
||||||
@@ -317,9 +311,9 @@ max_assoc(t(K,V,_,_,R), _, _, Key, Val) :-
|
|||||||
max_assoc(R, K, V, Key, Val).
|
max_assoc(R, K, V, Key, Val).
|
||||||
|
|
||||||
|
|
||||||
%! min_assoc(+Assoc, -Key, -Value) is semidet.
|
%% min_assoc(+Assoc, -Key, -Value) is semidet.
|
||||||
%
|
%
|
||||||
% True if Key-Value is in assoc and Key is the smallest key.
|
% True if Key-Value is in assoc and Key is the smallest key.
|
||||||
|
|
||||||
min_assoc(t(K,V,_,L,_), Key, Val) :-
|
min_assoc(t(K,V,_,L,_), Key, Val) :-
|
||||||
min_assoc(L, K, V, Key, Val).
|
min_assoc(L, K, V, Key, Val).
|
||||||
@@ -329,10 +323,10 @@ min_assoc(t(K,V,_,L,_), _, _, Key, Val) :-
|
|||||||
min_assoc(L, K, V, Key, Val).
|
min_assoc(L, K, V, Key, Val).
|
||||||
|
|
||||||
|
|
||||||
%! put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
|
%% put_assoc(+Key, +Assoc0, +Value, -Assoc) is det.
|
||||||
%
|
%
|
||||||
% Assoc is Assoc0, except that Key is associated with
|
% Assoc is Assoc0, except that Key is associated with
|
||||||
% Value. This can be used to insert and change associations.
|
% Value. This can be used to insert and change associations.
|
||||||
|
|
||||||
put_assoc(Key, A0, Value, A) :-
|
put_assoc(Key, A0, Value, A) :-
|
||||||
insert(A0, Key, Value, A, _).
|
insert(A0, Key, Value, A, _).
|
||||||
@@ -364,11 +358,11 @@ table(< , right , - , no , no ) :- !.
|
|||||||
table(> , left , - , no , no ) :- !.
|
table(> , left , - , no , no ) :- !.
|
||||||
table(> , right , - , no , yes ) :- !.
|
table(> , right , - , no , yes ) :- !.
|
||||||
|
|
||||||
%! del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
%% del_min_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||||
%
|
%
|
||||||
% True if Key-Value is in Assoc0 and Key is the smallest key.
|
% True if Key-Value is in Assoc0 and Key is the smallest key.
|
||||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||||
|
|
||||||
del_min_assoc(Tree, Key, Val, NewTree) :-
|
del_min_assoc(Tree, Key, Val, NewTree) :-
|
||||||
del_min_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
del_min_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
||||||
@@ -378,11 +372,11 @@ del_min_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
|
|||||||
del_min_assoc(L, Key, Val, NewL, LeftChanged),
|
del_min_assoc(L, Key, Val, NewL, LeftChanged),
|
||||||
deladjust(LeftChanged, t(K,V,B,NewL,R), left, NewTree, Changed).
|
deladjust(LeftChanged, t(K,V,B,NewL,R), left, NewTree, Changed).
|
||||||
|
|
||||||
%! del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
%% del_max_assoc(+Assoc0, ?Key, ?Val, -Assoc) is semidet.
|
||||||
%
|
%
|
||||||
% True if Key-Value is in Assoc0 and Key is the greatest key.
|
% True if Key-Value is in Assoc0 and Key is the greatest key.
|
||||||
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
% Assoc is Assoc0 with Key-Value removed. Warning: This will
|
||||||
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
% succeed with _no_ bindings for Key or Val if Assoc0 is empty.
|
||||||
|
|
||||||
del_max_assoc(Tree, Key, Val, NewTree) :-
|
del_max_assoc(Tree, Key, Val, NewTree) :-
|
||||||
del_max_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
del_max_assoc(Tree, Key, Val, NewTree, _DepthChanged).
|
||||||
@@ -392,10 +386,10 @@ del_max_assoc(t(K,V,B,L,R), Key, Val, NewTree, Changed) :-
|
|||||||
del_max_assoc(R, Key, Val, NewR, RightChanged),
|
del_max_assoc(R, Key, Val, NewR, RightChanged),
|
||||||
deladjust(RightChanged, t(K,V,B,L,NewR), right, NewTree, Changed).
|
deladjust(RightChanged, t(K,V,B,L,NewR), right, NewTree, Changed).
|
||||||
|
|
||||||
%! del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
|
%% del_assoc(+Key, +Assoc0, ?Value, -Assoc) is semidet.
|
||||||
%
|
%
|
||||||
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
|
% True if Key-Value is in Assoc0. Assoc is Assoc0 with
|
||||||
% Key-Value removed.
|
% Key-Value removed.
|
||||||
|
|
||||||
del_assoc(Key, A0, Value, A) :-
|
del_assoc(Key, A0, Value, A) :-
|
||||||
delete(A0, Key, Value, A, _).
|
delete(A0, Key, Value, A, _).
|
||||||
121
src/lib/atts.pl
Normal file
121
src/lib/atts.pl
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
:- module(atts, [op(1199, fx, attribute),
|
||||||
|
call_residue_vars/2,
|
||||||
|
term_attributed_variables/2]).
|
||||||
|
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(terms)).
|
||||||
|
|
||||||
|
/* represent the list of attributes belonging to a variable,
|
||||||
|
of a particular module, as a list of terms of the form
|
||||||
|
Module:put_atts(V, ListOfAtts). */
|
||||||
|
'$default_attr_list'(Module, V) -->
|
||||||
|
( { Module:get_atts(V, Attributes) } ->
|
||||||
|
'$default_attr_list'(Attributes, Module, V)
|
||||||
|
; []
|
||||||
|
).
|
||||||
|
|
||||||
|
'$default_attr_list'([PG | PGs], Module, AttrVar) -->
|
||||||
|
[Module:put_atts(AttrVar, PG)],
|
||||||
|
'$default_attr_list'(PGs, Module, AttrVar).
|
||||||
|
'$default_attr_list'([], _, _) --> [].
|
||||||
|
|
||||||
|
'$absent_attr'(V, Module, Attr) :-
|
||||||
|
( '$get_from_attr_list'(V, Module, Attr) ->
|
||||||
|
false
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
|
||||||
|
'$copy_attr_list'(L, _Module, []) :- var(L), !.
|
||||||
|
'$copy_attr_list'([Module0:Att|Atts], Module, CopiedAtts) :-
|
||||||
|
( Module0 == Module ->
|
||||||
|
CopiedAtts = [Att|CopiedAtts0],
|
||||||
|
'$copy_attr_list'(Atts, Module, CopiedAtts0)
|
||||||
|
; '$copy_attr_list'(Atts, Module, CopiedAtts)
|
||||||
|
).
|
||||||
|
|
||||||
|
user:term_expansion(Term0, Terms) :-
|
||||||
|
nonvar(Term0),
|
||||||
|
Term0 = (:- attribute Atts),
|
||||||
|
nonvar(Atts),
|
||||||
|
prolog_load_context(module, Module),
|
||||||
|
phrase(expand_terms(Atts, Module), Terms).
|
||||||
|
|
||||||
|
expand_terms(Atts, Module) -->
|
||||||
|
put_attrs_var_check,
|
||||||
|
put_attrs(Atts, Module),
|
||||||
|
get_attrs_var_check(Module),
|
||||||
|
get_attrs(Atts, Module).
|
||||||
|
|
||||||
|
put_attrs_var_check -->
|
||||||
|
[(put_atts(Var, Attr) :- nonvar(Var),
|
||||||
|
throw(error(uninstantiation_error(Var), put_atts/2))),
|
||||||
|
(put_atts(Var, Attr) :- var(Attr),
|
||||||
|
throw(error(instantiation_error, put_atts/2)))].
|
||||||
|
|
||||||
|
get_attrs_var_check(Module) -->
|
||||||
|
[(get_atts(Var, Attr) :- nonvar(Var),
|
||||||
|
throw(error(uninstantiation_error(Var), get_atts/2))),
|
||||||
|
(get_atts(Var, Attr) :- var(Attr),
|
||||||
|
!,
|
||||||
|
'$get_attr_list'(Var, Ls),
|
||||||
|
nonvar(Ls),
|
||||||
|
atts:'$copy_attr_list'(Ls, Module, Attr))].
|
||||||
|
|
||||||
|
put_attrs(Name/Arity, Module) -->
|
||||||
|
put_attr(Name, Arity, Module),
|
||||||
|
[(put_atts(Var, Attr) :- lists:maplist(Module:put_atts(Var), Attr), !)].
|
||||||
|
put_attrs((Name/Arity, Atts), Module) -->
|
||||||
|
{ nonvar(Atts) },
|
||||||
|
put_attr(Name, Arity, Module),
|
||||||
|
put_attrs(Atts, Module).
|
||||||
|
|
||||||
|
get_attrs(Name/Arity, Module) -->
|
||||||
|
get_attr(Name, Arity, Module).
|
||||||
|
get_attrs((Name/Arity, Atts), Module) -->
|
||||||
|
{ nonvar(Atts) },
|
||||||
|
get_attr(Name, Arity, Module),
|
||||||
|
get_attrs(Atts, Module).
|
||||||
|
|
||||||
|
put_attr(Name, Arity, Module) -->
|
||||||
|
{ functor(Attr, Name, Arity) },
|
||||||
|
[(put_atts(V, +Attr) :-
|
||||||
|
!,
|
||||||
|
'$put_to_attr_list'(V, Module, Attr)),
|
||||||
|
(put_atts(V, Attr) :-
|
||||||
|
!,
|
||||||
|
'$put_to_attr_list'(V, Module, Attr)),
|
||||||
|
(put_atts(V, -Attr) :-
|
||||||
|
!,
|
||||||
|
'$del_from_attr_list'(V, Module, Attr))].
|
||||||
|
|
||||||
|
get_attr(Name, Arity, Module) -->
|
||||||
|
{ functor(Attr, Name, Arity) },
|
||||||
|
[(get_atts(V, +Attr) :-
|
||||||
|
!,
|
||||||
|
functor(Attr, _, _),
|
||||||
|
atts:'$get_from_attr_list'(V, Module, Attr)),
|
||||||
|
(get_atts(V, Attr) :-
|
||||||
|
!,
|
||||||
|
functor(Attr, _, _),
|
||||||
|
atts:'$get_from_attr_list'(V, Module, Attr)),
|
||||||
|
(get_atts(V, -Attr) :-
|
||||||
|
!,
|
||||||
|
functor(Attr, _, _),
|
||||||
|
atts:'$absent_attr'(V, Module, Attr))].
|
||||||
|
|
||||||
|
user:goal_expansion(Term, M:put_atts(Var, Attr)) :-
|
||||||
|
nonvar(Term),
|
||||||
|
Term = put_atts(Var, M, Attr).
|
||||||
|
user:goal_expansion(Term, M:get_atts(Var, Attr)) :-
|
||||||
|
nonvar(Term),
|
||||||
|
Term = get_atts(Var, M, Attr).
|
||||||
|
|
||||||
|
:- meta_predicate call_residue_vars(0, ?).
|
||||||
|
|
||||||
|
call_residue_vars(Goal, Vars) :-
|
||||||
|
'$get_attr_var_queue_delim'(B),
|
||||||
|
call(Goal),
|
||||||
|
'$get_attr_var_queue_beyond'(B, Vars).
|
||||||
|
|
||||||
|
term_attributed_variables(Term, Vars) :-
|
||||||
|
'$term_attributed_variables'(Term, Vars).
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/** Predicates that generate integers
|
||||||
|
|
||||||
|
These predicates can be used to reason about integers in a reduced domain that
|
||||||
|
follow some property. `library(clpz)` provides another way of reasoning about
|
||||||
|
integers that may also be interesting.
|
||||||
|
*/
|
||||||
|
|
||||||
:- module(between, [between/3, gen_int/1, gen_nat/1, numlist/2, numlist/3, repeat/1]).
|
:- module(between, [between/3, gen_int/1, gen_nat/1, numlist/2, numlist/3, repeat/1]).
|
||||||
|
|
||||||
%% TODO: numlist/5.
|
%% TODO: numlist/5.
|
||||||
@@ -5,24 +12,52 @@
|
|||||||
:- use_module(library(lists), [length/2]).
|
:- use_module(library(lists), [length/2]).
|
||||||
:- use_module(library(error)).
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
%% between(+Lower, +Upper, -X).
|
||||||
|
%
|
||||||
|
% Given Lower and Upper are both integer numbers, true iff X is an integer so that _Lower =< X =< Upper_.
|
||||||
|
% Can be used both to check if X is between Lower and Upper or to generate an integer between
|
||||||
|
% Lower and Upper.
|
||||||
|
%
|
||||||
|
% Examples:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- between(10, 20, 15).
|
||||||
|
% true.
|
||||||
|
% ?- between(10, 20, 25).
|
||||||
|
% false.
|
||||||
|
% ?- between(3, 5, X).
|
||||||
|
% X = 3
|
||||||
|
% ; X = 4
|
||||||
|
% ; X = 5.
|
||||||
|
% ```
|
||||||
between(Lower, Upper, X) :-
|
between(Lower, Upper, X) :-
|
||||||
must_be(integer, Lower),
|
must_be(integer, Lower),
|
||||||
must_be(integer, Upper),
|
must_be(integer, Upper),
|
||||||
can_be(integer, X),
|
can_be(integer, X),
|
||||||
between_(Lower, Upper, X).
|
( nonvar(X) ->
|
||||||
|
Lower =< X,
|
||||||
|
X =< Upper
|
||||||
|
; Lower =< Upper,
|
||||||
|
between_(Lower, Upper, X)
|
||||||
|
).
|
||||||
|
|
||||||
between_(Lower, Upper, Lower) :-
|
between_(Lower, Upper, Lower1) :-
|
||||||
Lower =< Upper.
|
Lower < Upper,
|
||||||
between_(Lower1, Upper, X) :-
|
!,
|
||||||
Lower1 < Upper,
|
( Lower1 = Lower
|
||||||
Lower2 is Lower1 + 1,
|
; Lower0 is Lower + 1,
|
||||||
between_(Lower2, Upper, X).
|
between_(Lower0, Upper, Lower1)
|
||||||
|
).
|
||||||
|
between_(Lower, Lower, Lower).
|
||||||
|
|
||||||
enumerate_nats(I, I).
|
enumerate_nats(I, I).
|
||||||
enumerate_nats(I0, N) :-
|
enumerate_nats(I0, N) :-
|
||||||
I1 is I0 + 1,
|
I1 is I0 + 1,
|
||||||
enumerate_nats(I1, N).
|
enumerate_nats(I1, N).
|
||||||
|
|
||||||
|
%% gen_nat(?N)
|
||||||
|
%
|
||||||
|
% True iff N is a natural number.
|
||||||
gen_nat(N) :-
|
gen_nat(N) :-
|
||||||
can_be(integer, N),
|
can_be(integer, N),
|
||||||
( var(N) -> enumerate_nats(0, N)
|
( var(N) -> enumerate_nats(0, N)
|
||||||
@@ -37,6 +72,9 @@ enumerate_ints(I0, N) :-
|
|||||||
I1 is I0 + 1,
|
I1 is I0 + 1,
|
||||||
enumerate_ints(I1, N).
|
enumerate_ints(I1, N).
|
||||||
|
|
||||||
|
%% gen_int(?N)
|
||||||
|
%
|
||||||
|
% True iff N is an integer.
|
||||||
gen_int(N) :-
|
gen_int(N) :-
|
||||||
can_be(integer, N),
|
can_be(integer, N),
|
||||||
( var(N) -> enumerate_ints(0, N)
|
( var(N) -> enumerate_ints(0, N)
|
||||||
@@ -48,9 +86,24 @@ repeat_integer(N) :-
|
|||||||
repeat_integer(N0) :-
|
repeat_integer(N0) :-
|
||||||
N0 > 0, N1 is N0 - 1, repeat_integer(N1).
|
N0 > 0, N1 is N0 - 1, repeat_integer(N1).
|
||||||
|
|
||||||
|
%% repeat(+N)
|
||||||
|
%
|
||||||
|
% Succeeds N times. This predicate is only included for compatibility and *should not be used*
|
||||||
|
% because it lacks a declarative interpretation.
|
||||||
repeat(N) :-
|
repeat(N) :-
|
||||||
must_be(integer, N), repeat_integer(N).
|
must_be(integer, N), repeat_integer(N).
|
||||||
|
|
||||||
|
%% numlist(?Upper, ?List)
|
||||||
|
%
|
||||||
|
% True iff List is the list of integers _[1, ..., Upper]_. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- numlist(X, Y).
|
||||||
|
% X = 1, Y = [1],
|
||||||
|
% ; X = 2, Y = [1,2]
|
||||||
|
% ; X = 3, Y = [1,2,3]
|
||||||
|
% ; ... .
|
||||||
|
% ```
|
||||||
numlist(Upper, List) :-
|
numlist(Upper, List) :-
|
||||||
( integer(Upper) -> findall(X, between(1, Upper, X), List)
|
( integer(Upper) -> findall(X, between(1, Upper, X), List)
|
||||||
; List = [_|_], length(List, Upper), findall(X, between(1, Upper, X), List)
|
; List = [_|_], length(List, Upper), findall(X, between(1, Upper, X), List)
|
||||||
@@ -99,5 +152,14 @@ gen_ints(L, U) :-
|
|||||||
),
|
),
|
||||||
L =< U.
|
L =< U.
|
||||||
|
|
||||||
|
%% numlist(?Lower, ?Upper, ?List).
|
||||||
|
%
|
||||||
|
% True iff List is a list of the form _[Lower, ..., Upper]_.
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- numlist(5, 10, X).
|
||||||
|
% X = [5,6,7,8,9,10].
|
||||||
|
% ```
|
||||||
numlist(Lower, Upper, List) :-
|
numlist(Lower, Upper, List) :-
|
||||||
gen_ints(Lower, Upper), findall(X, between(Lower, Upper, X), List).
|
gen_ints(Lower, Upper), findall(X, between(Lower, Upper, X), List).
|
||||||
2212
src/lib/builtins.pl
Normal file
2212
src/lib/builtins.pl
Normal file
File diff suppressed because it is too large
Load Diff
383
src/lib/charsio.pl
Normal file
383
src/lib/charsio.pl
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
/** High-level predicates to work with chars and strings
|
||||||
|
|
||||||
|
This module contains predicates that relates strings of chars
|
||||||
|
to other representations, as well as high-level predicates to
|
||||||
|
read and write chars.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(charsio, [char_type/2,
|
||||||
|
chars_utf8bytes/2,
|
||||||
|
get_single_char/1,
|
||||||
|
get_n_chars/3,
|
||||||
|
get_line_to_chars/3,
|
||||||
|
read_from_chars/2,
|
||||||
|
read_term_from_chars/3,
|
||||||
|
write_term_to_chars/3,
|
||||||
|
chars_base64/3]).
|
||||||
|
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(iso_ext), [partial_string/1,partial_string/3]).
|
||||||
|
|
||||||
|
fabricate_var_name(VarType, VarName, N) :-
|
||||||
|
char_code('A', AC),
|
||||||
|
LN is N mod 26 + AC,
|
||||||
|
char_code(LC, LN),
|
||||||
|
NN is N // 26,
|
||||||
|
( NN =:= 0 ->
|
||||||
|
( VarType == fabricated ->
|
||||||
|
atom_chars(VarName, ['_', LC])
|
||||||
|
; VarType == numbervars ->
|
||||||
|
atom_chars(VarName, [LC])
|
||||||
|
)
|
||||||
|
; number_chars(NN, NNChars),
|
||||||
|
( VarType == fabricated ->
|
||||||
|
atom_chars(VarName, ['_', LC | NNChars])
|
||||||
|
; VarType == numbervars ->
|
||||||
|
atom_chars(VarName, [LC | NNChars])
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
var_list_contains_name([VarName = _ | VarList], VarName0) :-
|
||||||
|
( VarName == VarName0 -> true
|
||||||
|
; var_list_contains_name(VarList, VarName0)
|
||||||
|
).
|
||||||
|
|
||||||
|
var_list_contains_variable([_ = Var | VarList], Var0) :-
|
||||||
|
( Var == Var0 -> true
|
||||||
|
; var_list_contains_variable(VarList, Var0)
|
||||||
|
).
|
||||||
|
|
||||||
|
make_new_var_name(VarType, V, VarName, N, N1, VarList) :-
|
||||||
|
fabricate_var_name(VarType, VarName0, N),
|
||||||
|
( var_list_contains_name(VarList, VarName0) ->
|
||||||
|
N0 is N + 1,
|
||||||
|
make_new_var_name(VarType, V, VarName, N0, N1, VarList)
|
||||||
|
; VarName = VarName0,
|
||||||
|
N1 is N + 1
|
||||||
|
).
|
||||||
|
|
||||||
|
extend_var_list(Vars, VarList, NewVarList, VarType) :-
|
||||||
|
extend_var_list_(Vars, 0, VarList, NewVarList0, VarType),
|
||||||
|
append(VarList, NewVarList0, NewVarList).
|
||||||
|
|
||||||
|
extend_var_list_([], _, _, [], _).
|
||||||
|
extend_var_list_([V|Vs], N, VarList, NewVarList, VarType) :-
|
||||||
|
( var_list_contains_variable(VarList, V) ->
|
||||||
|
extend_var_list_(Vs, N, VarList, NewVarList, VarType)
|
||||||
|
; make_new_var_name(VarType, V, VarName, N, N1, VarList),
|
||||||
|
NewVarList = [VarName = V | NewVarList0],
|
||||||
|
extend_var_list_(Vs, N1, VarList, NewVarList0, VarType)
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
%% char_type(+Char, -Type).
|
||||||
|
%
|
||||||
|
% Given a Char, Type is one of the categories that char fits in.
|
||||||
|
% Possible categories are:
|
||||||
|
%
|
||||||
|
% - `alnum`
|
||||||
|
% - `alpha`
|
||||||
|
% - `alphabetic`
|
||||||
|
% - `alphanumeric`
|
||||||
|
% - `ascii`
|
||||||
|
% - `ascii_graphic`
|
||||||
|
% - `ascii_punctuation`
|
||||||
|
% - `binary_digit`
|
||||||
|
% - `control`
|
||||||
|
% - `decimal_digit`
|
||||||
|
% - `exponent`
|
||||||
|
% - `graphic`
|
||||||
|
% - `graphic_token`
|
||||||
|
% - `hexadecimal_digit`
|
||||||
|
% - `layout`
|
||||||
|
% - `lower`
|
||||||
|
% - `meta`
|
||||||
|
% - `numeric`
|
||||||
|
% - `octal_digit`
|
||||||
|
% - `octet`
|
||||||
|
% - `prolog`
|
||||||
|
% - `sign`
|
||||||
|
% - `solo`
|
||||||
|
% - `symbolic_control`
|
||||||
|
% - `symbolic_hexadecimal`
|
||||||
|
% - `upper`
|
||||||
|
% - `to_lower(Lower)`
|
||||||
|
% - `to_upper(Upper)`
|
||||||
|
% - `whitespace`
|
||||||
|
%
|
||||||
|
% An example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- char_type(a, Type).
|
||||||
|
% Type = alnum
|
||||||
|
% ; Type = alpha
|
||||||
|
% ; Type = alphabetic
|
||||||
|
% ; Type = alphanumeric
|
||||||
|
% ; Type = ascii
|
||||||
|
% ; Type = ascii_graphic
|
||||||
|
% ; Type = hexadecimal_digit
|
||||||
|
% ; Type = lower
|
||||||
|
% ; Type = octet
|
||||||
|
% ; Type = prolog
|
||||||
|
% ; Type = symbolic_control
|
||||||
|
% ; Type = to_lower("a")
|
||||||
|
% ; Type = to_upper("A")
|
||||||
|
% ; false.
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Note that uppercase and lowercase transformations use a string. This is because
|
||||||
|
% some characters do not map 1:1 between lowercase and uppercase.
|
||||||
|
char_type(Char, Type) :-
|
||||||
|
must_be(character, Char),
|
||||||
|
( ground(Type) ->
|
||||||
|
( ctype(Type) ->
|
||||||
|
'$char_type'(Char, Type)
|
||||||
|
; domain_error(char_type, Type, char_type/2)
|
||||||
|
)
|
||||||
|
; ctype(Type),
|
||||||
|
'$char_type'(Char, Type)
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
ctype(alnum).
|
||||||
|
ctype(alpha).
|
||||||
|
ctype(alphabetic).
|
||||||
|
ctype(alphanumeric).
|
||||||
|
ctype(ascii).
|
||||||
|
ctype(ascii_graphic).
|
||||||
|
ctype(ascii_punctuation).
|
||||||
|
ctype(binary_digit).
|
||||||
|
ctype(control).
|
||||||
|
ctype(decimal_digit).
|
||||||
|
ctype(exponent).
|
||||||
|
ctype(graphic).
|
||||||
|
ctype(graphic_token).
|
||||||
|
ctype(hexadecimal_digit).
|
||||||
|
ctype(layout).
|
||||||
|
ctype(lower).
|
||||||
|
ctype(meta).
|
||||||
|
ctype(numeric).
|
||||||
|
ctype(octal_digit).
|
||||||
|
ctype(octet).
|
||||||
|
ctype(prolog).
|
||||||
|
ctype(sign).
|
||||||
|
ctype(solo).
|
||||||
|
ctype(symbolic_control).
|
||||||
|
ctype(symbolic_hexadecimal).
|
||||||
|
ctype(to_lower(_)).
|
||||||
|
ctype(to_upper(_)).
|
||||||
|
ctype(upper).
|
||||||
|
ctype(whitespace).
|
||||||
|
|
||||||
|
|
||||||
|
%% get_single_char(-Char).
|
||||||
|
%
|
||||||
|
% Gets a single char from the current input stream.
|
||||||
|
get_single_char(C) :-
|
||||||
|
( var(C) -> '$get_single_char'(C)
|
||||||
|
; atom_length(C, 1) -> '$get_single_char'(C)
|
||||||
|
; type_error(in_character, C, get_single_char/1)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% read_from_chars(+Chars, -Term).
|
||||||
|
%
|
||||||
|
% Given a string made of chars which contains a representation of
|
||||||
|
% a Prolog term, Term is the Prolog term represented. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- read_from_chars("f(x,y).", X).
|
||||||
|
% X = f(x,y).
|
||||||
|
% ```
|
||||||
|
read_from_chars(Chars, Term) :-
|
||||||
|
must_be(chars, Chars),
|
||||||
|
must_be(var, Term),
|
||||||
|
'$read_from_chars'(Chars, Term).
|
||||||
|
|
||||||
|
%% read_term_from_chars(+Chars, -Term, +Options).
|
||||||
|
%
|
||||||
|
% Like `read_from_chars`, except the reader is configured according to
|
||||||
|
% `Options` which are those of `read_term`.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- read_term_from_chars("f(X,y).", T, [variable_names(['X'=X])]).
|
||||||
|
% T = f(X,y).
|
||||||
|
% ```
|
||||||
|
read_term_from_chars(Chars, Term, Options) :-
|
||||||
|
must_be(chars, Chars),
|
||||||
|
must_be(var, Term),
|
||||||
|
builtins:parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term_from_chars/3),
|
||||||
|
'$read_term_from_chars'(Chars, Term, Singletons, Variables, VariableNames).
|
||||||
|
|
||||||
|
%% write_term_to_chars(+Term, +Options, -Chars).
|
||||||
|
%
|
||||||
|
% Given a Term which is a Prolog term and a set of options, Chars is
|
||||||
|
% string representation of that term. Options available are:
|
||||||
|
%
|
||||||
|
% * `ignore_ops(+Boolean)` if `true`, the generic term representation is used everywhere. In `false`
|
||||||
|
% (default), operators do not use that generic term representation.
|
||||||
|
% * `max_depth(+N)` if the term is nested deeper than N, print the reminder as ellipses.
|
||||||
|
% If N = 0 (default), there's no limit.
|
||||||
|
% * `numbervars(+Boolean)` if true, replaces `$VAR(N)` variables with letters, in order. Default is false.
|
||||||
|
% * `quoted(+Boolean)` if true, strings and atoms that need quotes to be valid Prolog syntax, are quoted. Default is false.
|
||||||
|
% * `variable_names(+List)` assign names to variables in term. List should be a list of terms of format `Name=Var`.
|
||||||
|
% * `double_quotes(+Boolean)` if true, strings are printed in double quotes rather than with list notation. Default is false.
|
||||||
|
write_term_to_chars(_, Options, _) :-
|
||||||
|
var(Options), instantiation_error(write_term_to_chars/3).
|
||||||
|
write_term_to_chars(Term, Options, Chars) :-
|
||||||
|
builtins:parse_write_options(Options,
|
||||||
|
[DoubleQuotes, IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames],
|
||||||
|
write_term_to_chars/3),
|
||||||
|
( nonvar(Chars) ->
|
||||||
|
throw(error(uninstantiation_error(Chars), write_term_to_chars/3))
|
||||||
|
;
|
||||||
|
true
|
||||||
|
),
|
||||||
|
term_variables(Term, Vars),
|
||||||
|
extend_var_list(Vars, VNNames, NewVarNames, numbervars),
|
||||||
|
'$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth, DoubleQuotes).
|
||||||
|
|
||||||
|
% Encodes Ch character to list of Bytes.
|
||||||
|
char_utf8bytes(Ch, Bytes) :-
|
||||||
|
char_code(Ch, Code),
|
||||||
|
phrase(code_to_utf8(Code), Bytes).
|
||||||
|
|
||||||
|
code_to_utf8(Code) --> {Code @< 0x80}, [Code], !.
|
||||||
|
code_to_utf8(Code) --> {Code @< 0x800}, encode(Code, 0xC0, 2), !.
|
||||||
|
code_to_utf8(Code) --> {Code @< 0x10000}, encode(Code, 0xE0, 3), !.
|
||||||
|
code_to_utf8(Code) --> {Code @< 0x110000}, encode(Code, 0xF0, 4), !.
|
||||||
|
|
||||||
|
encode(_, _, 0) --> !.
|
||||||
|
encode(Code, Prefix, Nb) -->
|
||||||
|
{ Nb1 is Nb - 1, Byte is Prefix \/ ((Code >> (6 * Nb1)) /\ 0x3F) },
|
||||||
|
[Byte], encode(Code, 0x80, Nb1).
|
||||||
|
|
||||||
|
% Maps characters and UTF-8 bytes.
|
||||||
|
% If Cs is a variable, parses Bs as a list of UTF-8 bytes.
|
||||||
|
% Otherwise, transform the list of characters Cs to UTF-8 bytes.
|
||||||
|
|
||||||
|
%% chars_utf8bytes(?Chars, ?Bytes).
|
||||||
|
%
|
||||||
|
% Maps a string made of chars with a list of UTF-8 bytes. Some examples:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- chars_utf8bytes("Prolog", X).
|
||||||
|
% X = [80,114,111,108,111,103].
|
||||||
|
% ?- chars_utf8bytes(X, [226, 136, 145]).
|
||||||
|
% X = "∑".
|
||||||
|
% ```
|
||||||
|
chars_utf8bytes(Cs, Bs) :-
|
||||||
|
var(Cs), must_be(list, Bs) ->
|
||||||
|
once(phrase(decode_utf8(Cs), Bs))
|
||||||
|
; (must_be(list, Cs),
|
||||||
|
maplist(must_be(atom), Cs),
|
||||||
|
maplist(char_utf8bytes, Cs, Bss),
|
||||||
|
append(Bss, Bs)).
|
||||||
|
|
||||||
|
decode_utf8([]) --> [].
|
||||||
|
decode_utf8(Chars) --> leading(Nb, Code), continuation(Code, Chars, Nb).
|
||||||
|
|
||||||
|
leading(1, Byte) --> [Byte], {Byte /\ 0x80 =:= 0}.
|
||||||
|
leading(2, Code) --> [Byte], {Byte /\ 0xE0 =:= 0xC0, Code is Byte - 0xC0}.
|
||||||
|
leading(3, Code) --> [Byte], {Byte /\ 0xF0 =:= 0xE0, Code is Byte - 0xE0}.
|
||||||
|
leading(4, Code) --> [Byte], {Byte /\ 0xF8 =:= 0xF0, Code is Byte - 0xF0}.
|
||||||
|
leading(1, 0xFFFD) --> [_]. % invalid first byte
|
||||||
|
|
||||||
|
continuation(Code, [H|T], 1) --> {char_code(H, Code)}, decode_utf8(T).
|
||||||
|
continuation(Code, Chars, Nb) --> [Byte],
|
||||||
|
{Nb1 is Nb - 1, Byte /\ 0xC0 =:= 0x80, NextCode is (Code << 6) \/ (Byte - 0x80)},
|
||||||
|
continuation(NextCode, Chars, Nb1).
|
||||||
|
|
||||||
|
% invalid continuation byte
|
||||||
|
% each remaining continuation byte (if any) will raise 0xFFFD too
|
||||||
|
continuation(_, ['\xFFFD\'|T], _) --> [_], decode_utf8(T).
|
||||||
|
|
||||||
|
%% get_line_to_chars(+Stream, -Chars, +InitialChars).
|
||||||
|
%
|
||||||
|
% Reads chars from stream Stream until it finds a `\n` character.
|
||||||
|
% InitialChars will be appended at the end of Chars
|
||||||
|
get_line_to_chars(Stream, Cs0, Cs) :-
|
||||||
|
'$get_n_chars'(Stream, 1, Char), % this also works for binary streams
|
||||||
|
( Char == [] -> Cs0 = Cs
|
||||||
|
; Char = [C],
|
||||||
|
Cs0 = [C|Rest],
|
||||||
|
( C == '\n' -> Rest = Cs
|
||||||
|
; get_line_to_chars(Stream, Rest, Cs)
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% get_n_chars(+Stream, ?N, -Chars).
|
||||||
|
%
|
||||||
|
% Read N chars from stream Stream. N can be an integer, in that case
|
||||||
|
% only N chars are read, or a variable, unifying N with the number of chars
|
||||||
|
% read until it found EOF.
|
||||||
|
get_n_chars(Stream, N, Cs) :-
|
||||||
|
can_be(integer, N),
|
||||||
|
( var(N) ->
|
||||||
|
get_to_eof(Stream, Cs),
|
||||||
|
length(Cs, N)
|
||||||
|
; N >= 0,
|
||||||
|
'$get_n_chars'(Stream, N, Cs)
|
||||||
|
).
|
||||||
|
|
||||||
|
get_n_chars_wrapper(Stream, N, Cs) :-
|
||||||
|
'$get_n_chars'(Stream, N, Cs).
|
||||||
|
|
||||||
|
get_to_eof(Stream, Cs) :-
|
||||||
|
catch(get_n_chars_wrapper(Stream, 512, Cs0),
|
||||||
|
error(syntax_error(unexpected_end_of_file), _),
|
||||||
|
Cs0 = []),
|
||||||
|
( Cs0 == [] -> Cs = []
|
||||||
|
; partial_string(Cs0, Cs, Rest),
|
||||||
|
get_to_eof(Stream, Rest)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% chars_base64(?Chars, ?Base64, +Options).
|
||||||
|
%
|
||||||
|
% Relation between a list of characters Cs and its Base64 encoding Bs,
|
||||||
|
% also a list of characters.
|
||||||
|
%
|
||||||
|
% At least one of the arguments must be instantiated.
|
||||||
|
%
|
||||||
|
% Options are:
|
||||||
|
%
|
||||||
|
% - `padding(Boolean)`
|
||||||
|
% Whether to use padding: true (the default) or false.
|
||||||
|
% - `charset(C)`
|
||||||
|
% Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5).
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- chars_base64("hello", Bs, []).
|
||||||
|
% Bs = "aGVsbG8=".
|
||||||
|
% ```
|
||||||
|
|
||||||
|
chars_base64(Cs, Bs, Options) :-
|
||||||
|
must_be(list, Options),
|
||||||
|
( member(O, Options), var(O) ->
|
||||||
|
instantiation_error(chars_base64/3)
|
||||||
|
; ( member(padding(Padding), Options) -> true
|
||||||
|
; Padding = true
|
||||||
|
),
|
||||||
|
( member(charset(Charset), Options) -> true
|
||||||
|
; Charset = standard
|
||||||
|
)
|
||||||
|
),
|
||||||
|
must_be(boolean, Padding),
|
||||||
|
must_be(atom, Charset),
|
||||||
|
( member(Charset, [standard,url]) -> true
|
||||||
|
; domain_error(charset, Charset, chars_base64/3)
|
||||||
|
),
|
||||||
|
( var(Cs) ->
|
||||||
|
must_be(chars, Bs),
|
||||||
|
'$chars_base64'(Cs, Bs, Padding, Charset)
|
||||||
|
; must_be(chars, Cs),
|
||||||
|
( '$first_non_octet'(Cs, N) ->
|
||||||
|
domain_error(octet_character, N, chars_base64/3)
|
||||||
|
; '$chars_base64'(Cs, Bs, Padding, Charset)
|
||||||
|
)
|
||||||
|
).
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* CLP(B): Constraint Logic Programming over Boolean Variables
|
/* CLP(B): Constraint Logic Programming over Boolean Variables
|
||||||
|
|
||||||
Copyright (C): 2019 Markus Triska
|
Copyright (C): 2019-2023 Markus Triska
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
E-mail: triska@metalevel.at
|
E-mail: triska@metalevel.at
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
:- module(clpb, [op(300, fy, ~),
|
:- module(clpb, [op(300, fy, ~),
|
||||||
op(500, yfx, #),
|
op(500, yfx, #),
|
||||||
sat/1,
|
sat/1,
|
||||||
taut/2,
|
taut/2,
|
||||||
labeling/1,
|
labeling/1,
|
||||||
sat_count/2,
|
sat_count/2,
|
||||||
@@ -31,9 +31,10 @@
|
|||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
:- use_module(library(iso_ext)).
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(random)).
|
||||||
:- use_module(library(pairs)).
|
:- use_module(library(pairs)).
|
||||||
:- use_module(library(dcgs)).
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(error), []).
|
:- use_module(library(error), [domain_error/3, type_error/3]).
|
||||||
|
|
||||||
:- attribute
|
:- attribute
|
||||||
clpb/1,
|
clpb/1,
|
||||||
@@ -48,17 +49,6 @@
|
|||||||
Compatibility predicates.
|
Compatibility predicates.
|
||||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
group_pairs_by_key([], []).
|
|
||||||
group_pairs_by_key([M-N|T0], [M-[N|TN]|T]) :-
|
|
||||||
same_key(M, T0, TN, T1),
|
|
||||||
group_pairs_by_key(T1, T).
|
|
||||||
|
|
||||||
same_key(M0, [M-N|T0], [N|TN], T) :-
|
|
||||||
M0 == M,
|
|
||||||
!,
|
|
||||||
same_key(M, T0, TN, T).
|
|
||||||
same_key(_, L, [], L).
|
|
||||||
|
|
||||||
must_be(What, Term) :- must_be(What, unknown(Term)-1, Term).
|
must_be(What, Term) :- must_be(What, unknown(Term)-1, Term).
|
||||||
|
|
||||||
must_be(acyclic, Where, Term) :- !,
|
must_be(acyclic, Where, Term) :- !,
|
||||||
@@ -97,16 +87,10 @@ instantiation_error(_, Goal-Arg) :-
|
|||||||
domain_error(Expectation, Term) :-
|
domain_error(Expectation, Term) :-
|
||||||
domain_error(Expectation, Term, unknown(Term)-1).
|
domain_error(Expectation, Term, unknown(Term)-1).
|
||||||
|
|
||||||
domain_error(Expectation, Term, Goal-Arg) :-
|
|
||||||
throw(error(domain_error(Expectation, Term), domain_error(Goal, Arg, Expectation, Term))).
|
|
||||||
|
|
||||||
|
|
||||||
type_error(Expectation, Term) :-
|
type_error(Expectation, Term) :-
|
||||||
type_error(Expectation, Term, unknown(Term)-1).
|
type_error(Expectation, Term, unknown(Term)-1).
|
||||||
|
|
||||||
type_error(Expectation, Term, Goal-Arg) :-
|
|
||||||
throw(error(type_error(Expectation, Term), type_error(Goal, Arg, Expectation, Term))).
|
|
||||||
|
|
||||||
partition(Pred, Ls0, As, Bs) :-
|
partition(Pred, Ls0, As, Bs) :-
|
||||||
include(Pred, Ls0, As),
|
include(Pred, Ls0, As),
|
||||||
exclude(Pred, Ls0, Bs).
|
exclude(Pred, Ls0, Bs).
|
||||||
@@ -121,6 +105,262 @@ goal_expansion(del_attr(Var, Module), (var(Var) -> put_atts(Var, -Access);true))
|
|||||||
Access =.. [Module,_].
|
Access =.. [Module,_].
|
||||||
|
|
||||||
|
|
||||||
|
/** Constraint Logic Programming over Boolean variables
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
This library provides CLP(B), Constraint Logic Programming over
|
||||||
|
Boolean variables. It can be used to model and solve combinatorial
|
||||||
|
problems such as verification, allocation and covering tasks.
|
||||||
|
|
||||||
|
CLP(B) is an instance of the general CLP(_X_) scheme,
|
||||||
|
extending logic programming with reasoning over specialised domains.
|
||||||
|
|
||||||
|
The implementation is based on reduced and ordered Binary Decision
|
||||||
|
Diagrams (BDDs).
|
||||||
|
|
||||||
|
Benchmarks and usage examples of this library are available from:
|
||||||
|
[*https://www.metalevel.at/clpb/*](https://www.metalevel.at/clpb/)
|
||||||
|
|
||||||
|
## Boolean expressions
|
||||||
|
|
||||||
|
A _Boolean expression_ is one of:
|
||||||
|
|
||||||
|
| `0` | false |
|
||||||
|
| `1` | true |
|
||||||
|
| _variable_ | unknown truth value |
|
||||||
|
| _atom_ | universally quantified variable |
|
||||||
|
| ~ _Expr_ | logical NOT |
|
||||||
|
| _Expr_ + _Expr_ | logical OR |
|
||||||
|
| _Expr_ * _Expr_ | logical AND |
|
||||||
|
| _Expr_ # _Expr_ | exclusive OR |
|
||||||
|
| _Var_ ^ _Expr_ | existential quantification |
|
||||||
|
| _Expr_ =:= _Expr_ | equality |
|
||||||
|
| _Expr_ =\= _Expr_ | disequality (same as #) |
|
||||||
|
| _Expr_ =< _Expr_ | less or equal (implication) |
|
||||||
|
| _Expr_ >= _Expr_ | greater or equal |
|
||||||
|
| _Expr_ < _Expr_ | less than |
|
||||||
|
| _Expr_ > _Expr_ | greater than |
|
||||||
|
| card(Is,Exprs) | cardinality constraint (_see below_) |
|
||||||
|
| `+(Exprs)` | n-fold disjunction (_see below_) |
|
||||||
|
| `*(Exprs)` | n-fold conjunction (_see below_) |
|
||||||
|
|
||||||
|
where _Expr_ again denotes a Boolean expression.
|
||||||
|
|
||||||
|
The Boolean expression `card(Is,Exprs)` is true iff the number of true
|
||||||
|
expressions in the list `Exprs` is a member of the list `Is` of
|
||||||
|
integers and integer ranges of the form `From-To`. For example, to
|
||||||
|
state that precisely two of the three variables `X`, `Y` and `Z` are
|
||||||
|
`true`, you can use `sat(card([2],[X,Y,Z]))`.
|
||||||
|
|
||||||
|
`+(Exprs)` and `*(Exprs)` denote, respectively, the disjunction and
|
||||||
|
conjunction of all elements in the list `Exprs` of Boolean
|
||||||
|
expressions.
|
||||||
|
|
||||||
|
Atoms denote parametric values that are universally quantified. All
|
||||||
|
universal quantifiers appear implicitly in front of the entire
|
||||||
|
expression. In residual goals, universally quantified variables always
|
||||||
|
appear on the right-hand side of equations. Therefore, they can be
|
||||||
|
used to express functional dependencies on input variables.
|
||||||
|
|
||||||
|
## Interface predicates
|
||||||
|
|
||||||
|
The most frequently used CLP(B) predicates are:
|
||||||
|
|
||||||
|
* `sat(+Expr)`
|
||||||
|
True iff the Boolean expression Expr is satisfiable.
|
||||||
|
|
||||||
|
* `taut(+Expr, -T)`
|
||||||
|
If Expr is a tautology with respect to the posted constraints, succeeds
|
||||||
|
with *T = 1*. If Expr cannot be satisfied, succeeds with *T = 0*.
|
||||||
|
Otherwise, it fails.
|
||||||
|
|
||||||
|
* `labeling(+Vs)`
|
||||||
|
Assigns truth values to the variables Vs such that all constraints
|
||||||
|
are satisfied.
|
||||||
|
|
||||||
|
The unification of a CLP(B) variable _X_ with a term _T_ is equivalent
|
||||||
|
to posting the constraint sat(X=:=T).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Here is an example session with a few queries and their answers:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(clpb)).
|
||||||
|
true.
|
||||||
|
|
||||||
|
?- sat(X*Y).
|
||||||
|
X = 1, Y = 1.
|
||||||
|
|
||||||
|
?- sat(X * ~X).
|
||||||
|
false.
|
||||||
|
|
||||||
|
?- taut(X * ~X, T).
|
||||||
|
T = 0, clpb:sat(X=:=X).
|
||||||
|
|
||||||
|
?- sat(X^Y^(X+Y)).
|
||||||
|
clpb:sat(X=:=X), clpb:sat(Y=:=Y).
|
||||||
|
|
||||||
|
?- sat(X*Y + X*Z), labeling([X,Y,Z]).
|
||||||
|
X = 1, Y = 0, Z = 1
|
||||||
|
; X = 1, Y = 1, Z = 0
|
||||||
|
; X = 1, Y = 1, Z = 1.
|
||||||
|
|
||||||
|
?- sat(X =< Y), sat(Y =< Z), taut(X =< Z, T).
|
||||||
|
T = 1, clpb:sat(X=:=X*Y), clpb:sat(Y=:=Y*Z).
|
||||||
|
|
||||||
|
?- sat(1#X#a#b).
|
||||||
|
clpb:sat(X=:=a#b).
|
||||||
|
```
|
||||||
|
|
||||||
|
The pending residual goals constrain remaining variables to Boolean
|
||||||
|
expressions and are declaratively equivalent to the original query.
|
||||||
|
The last example illustrates that when applicable, remaining variables
|
||||||
|
are expressed as functions of universally quantified variables.
|
||||||
|
|
||||||
|
## Obtaining BDDs
|
||||||
|
|
||||||
|
By default, CLP(B) residual goals appear in (approximately) algebraic
|
||||||
|
normal form (ANF). This projection is often computationally expensive.
|
||||||
|
We can assert `clpb:clpb_residuals(bdd)` to see the BDD representation
|
||||||
|
of all constraints. This results in faster projection to residual
|
||||||
|
goals, and is also useful for learning more about BDDs. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- asserta(clpb:clpb_residuals(bdd)).
|
||||||
|
true.
|
||||||
|
|
||||||
|
?- sat(X#Y).
|
||||||
|
node(3)- (v(X, 0)->node(2);node(1)),
|
||||||
|
node(1)- (v(Y, 1)->true;false),
|
||||||
|
node(2)- (v(Y, 1)->false;true).
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this representation cannot be pasted back on the toplevel,
|
||||||
|
and its details are subject to change. Use copy_term/3 to obtain
|
||||||
|
such answers as Prolog terms.
|
||||||
|
|
||||||
|
The variable order of the BDD is determined by the order in which the
|
||||||
|
variables first appear in constraints. To obtain different orders,
|
||||||
|
we can for example use:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- sat(+[1,Y,X]), sat(X#Y).
|
||||||
|
node(3)- (v(Y, 0)->node(2);node(1)),
|
||||||
|
node(1)- (v(X, 1)->true;false),
|
||||||
|
node(2)- (v(X, 1)->false;true).
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enabling monotonic CLP(B)
|
||||||
|
|
||||||
|
In the default execution mode, CLP(B) constraints are _not_ monotonic.
|
||||||
|
This means that _adding_ constraints can yield new solutions. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- sat(X=:=1), X = 1+0.
|
||||||
|
false.
|
||||||
|
|
||||||
|
?- X = 1+0, sat(X=:=1), X = 1+0.
|
||||||
|
X = 1+0.
|
||||||
|
```
|
||||||
|
|
||||||
|
This behaviour is highly problematic from a logical point of view, and
|
||||||
|
it may render [*declarative
|
||||||
|
debugging*](https://www.metalevel.at/prolog/debugging)
|
||||||
|
techniques inapplicable.
|
||||||
|
|
||||||
|
Assert `clpb:monotonic` to make CLP(B) *monotonic*. If this mode is
|
||||||
|
enabled, then you must wrap CLP(B) variables with the functor
|
||||||
|
`v/1`. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- asserta(clpb:monotonic).
|
||||||
|
true.
|
||||||
|
|
||||||
|
?- sat(v(X)=:=1#1).
|
||||||
|
X = 0.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Pigeons
|
||||||
|
|
||||||
|
In this example, we are attempting to place _I_ pigeons into _J_ holes
|
||||||
|
in such a way that each hole contains at most one pigeon. One
|
||||||
|
interesting property of this task is that it can be formulated using
|
||||||
|
only _cardinality constraints_ (`card/2`). Another interesting aspect
|
||||||
|
is that this task has no short resolution refutations in general.
|
||||||
|
|
||||||
|
In the following, we use [*Prolog DCG
|
||||||
|
notation*](https://www.metalevel.at/prolog/dcg) to describe a
|
||||||
|
list `Cs` of CLP(B) constraints that must all be satisfied.
|
||||||
|
|
||||||
|
```
|
||||||
|
:- use_module(library(clpb)).
|
||||||
|
:- use_module(library(clpz)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
|
||||||
|
pigeon(I, J, Rows, Cs) :-
|
||||||
|
length(Rows, I), length(Row, J),
|
||||||
|
maplist(same_length(Row), Rows),
|
||||||
|
transpose(Rows, TRows),
|
||||||
|
phrase((all_cards(Rows,[1]),all_cards(TRows,[0,1])), Cs).
|
||||||
|
|
||||||
|
all_cards([], _) --> [].
|
||||||
|
all_cards([Ls|Lss], Cs) --> [card(Cs,Ls)], all_cards(Lss, Cs).
|
||||||
|
```
|
||||||
|
|
||||||
|
Example queries:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- pigeon(9, 8, Rows, Cs), sat(*(Cs)).
|
||||||
|
false.
|
||||||
|
|
||||||
|
?- pigeon(2, 3, Rows, Cs), sat(*(Cs)),
|
||||||
|
append(Rows, Vs), labeling(Vs),
|
||||||
|
maplist(portray_clause, Rows).
|
||||||
|
[0,0,1].
|
||||||
|
[0,1,0].
|
||||||
|
etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Boolean circuit
|
||||||
|
|
||||||
|
Consider a Boolean circuit that express the Boolean function =|XOR|=
|
||||||
|
with 4 =|NAND|= gates. We can model such a circuit with CLP(B)
|
||||||
|
constraints as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
:- use_module(library(clpb)).
|
||||||
|
|
||||||
|
nand_gate(X, Y, Z) :- sat(Z =:= ~(X*Y)).
|
||||||
|
|
||||||
|
xor(X, Y, Z) :-
|
||||||
|
nand_gate(X, Y, T1),
|
||||||
|
nand_gate(X, T1, T2),
|
||||||
|
nand_gate(Y, T1, T3),
|
||||||
|
nand_gate(T2, T3, Z).
|
||||||
|
```
|
||||||
|
|
||||||
|
Using universally quantified variables, we can show that the circuit
|
||||||
|
does compute =|XOR|= as intended:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- xor(x, y, Z).
|
||||||
|
clpb:sat(Z=:=x#y).
|
||||||
|
```
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
The interface predicates of this library follow the example of
|
||||||
|
[*SICStus Prolog*](https://sicstus.sics.se).
|
||||||
|
|
||||||
|
Use SICStus Prolog for higher performance in many cases.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
Each CLP(B) variable belongs to exactly one BDD. Each CLP(B)
|
Each CLP(B) variable belongs to exactly one BDD. Each CLP(B)
|
||||||
variable gets an attribute (in module "clpb") of the form:
|
variable gets an attribute (in module "clpb") of the form:
|
||||||
@@ -847,9 +1087,8 @@ verify_attributes(Var, Other, Gs) :-
|
|||||||
( integer(Other) ->
|
( integer(Other) ->
|
||||||
( between(0, 1, Other) ->
|
( between(0, 1, Other) ->
|
||||||
root_get_formula_bdd(Root, Sat, BDD0),
|
root_get_formula_bdd(Root, Sat, BDD0),
|
||||||
bdd_restriction(BDD0, I, Other, BDD),
|
|
||||||
root_put_formula_bdd(Root, Sat, BDD),
|
root_put_formula_bdd(Root, Sat, BDD),
|
||||||
Gs = [satisfiable_bdd(BDD)]
|
Gs = [bdd_restriction(BDD0,I,Other,BDD),satisfiable_bdd(BDD)]
|
||||||
; no_truth_value(Other)
|
; no_truth_value(Other)
|
||||||
)
|
)
|
||||||
; atom(Other) ->
|
; atom(Other) ->
|
||||||
@@ -1125,19 +1364,19 @@ indomain(1).
|
|||||||
%
|
%
|
||||||
% Examples:
|
% Examples:
|
||||||
%
|
%
|
||||||
% ==
|
% ```
|
||||||
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
|
% ?- sat(A =< B), Vs = [A,B], sat_count(+[1|Vs], Count).
|
||||||
% Vs = [A, B],
|
% Vs = [A,B], Count = 3, clpb:sat(A=:=A*B).
|
||||||
% Count = 3,
|
|
||||||
% sat(A=:=A*B).
|
|
||||||
%
|
%
|
||||||
% ?- length(Vs, 120),
|
% ?- length(Vs, 120),
|
||||||
% sat_count(+Vs, CountOr),
|
% sat_count(+Vs, CountOr),
|
||||||
% sat_count(*(Vs), CountAnd).
|
% sat_count(*(Vs), CountAnd).
|
||||||
% Vs = [...],
|
% Vs = [...],
|
||||||
% CountOr = 1329227995784915872903807060280344575,
|
% CountOr = 1329227995784915872903807060280344575,
|
||||||
% CountAnd = 1.
|
% CountAnd = 1.
|
||||||
% ==
|
% ```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
sat_count(Sat0, N) :-
|
sat_count(Sat0, N) :-
|
||||||
catch((parse_sat(Sat0, Sat),
|
catch((parse_sat(Sat0, Sat),
|
||||||
@@ -1263,7 +1502,7 @@ random_bindings(VNum, Node) -->
|
|||||||
% linear objective function over Boolean variables Vs with integer
|
% linear objective function over Boolean variables Vs with integer
|
||||||
% coefficients Weights. This predicate assigns 0 and 1 to the
|
% coefficients Weights. This predicate assigns 0 and 1 to the
|
||||||
% variables in Vs such that all stated constraints are satisfied, and
|
% variables in Vs such that all stated constraints are satisfied, and
|
||||||
% Maximum is the maximum of sum(Weight_i*V_i) over all admissible
|
% Maximum is the maximum of `sum(Weight_i*V_i)` over all admissible
|
||||||
% assignments. On backtracking, all admissible assignments that
|
% assignments. On backtracking, all admissible assignments that
|
||||||
% attain the optimum are generated.
|
% attain the optimum are generated.
|
||||||
%
|
%
|
||||||
@@ -1272,10 +1511,11 @@ random_bindings(VNum, Node) -->
|
|||||||
%
|
%
|
||||||
% Example:
|
% Example:
|
||||||
%
|
%
|
||||||
% ==
|
% ```
|
||||||
% ?- sat(A#B), weighted_maximum([1,2,1], [A,B,C], Maximum).
|
% ?- sat(A#B), weighted_maximum([1,2,1], [A,B,C], Maximum).
|
||||||
% A = 0, B = 1, C = 1, Maximum = 3.
|
% A = 0, B = 1, C = 1, Maximum = 3
|
||||||
% ==
|
% ; false.
|
||||||
|
% ```
|
||||||
|
|
||||||
weighted_maximum(Ws, Vars, Max) :-
|
weighted_maximum(Ws, Vars, Max) :-
|
||||||
must_be(list(integer), Ws),
|
must_be(list(integer), Ws),
|
||||||
@@ -1293,13 +1533,18 @@ weighted_maximum(Ws, Vars, Max) :-
|
|||||||
maplist(var_with_index, Vars, IVs),
|
maplist(var_with_index, Vars, IVs),
|
||||||
pairs_keys_values(Pairs0, IVs, Ws),
|
pairs_keys_values(Pairs0, IVs, Ws),
|
||||||
keysort(Pairs0, Pairs1),
|
keysort(Pairs0, Pairs1),
|
||||||
pairs_keys_values(Pairs1, IVs1, WeightsIndexOrder),
|
% sum linear combinations of repeated variables
|
||||||
|
group_pairs_by_key(Pairs1, Groups),
|
||||||
|
maplist(group_sumweights_pair, Groups, Pairs2),
|
||||||
|
pairs_keys_values(Pairs2, IVs1, WeightsIndexOrder),
|
||||||
pairs_values(IVs1, VarsIndexOrder),
|
pairs_values(IVs1, VarsIndexOrder),
|
||||||
% Pairs is a list of Var-Weight terms, in index order of Vars
|
% Pairs is a list of Var-Weight terms, in index order of Vars
|
||||||
pairs_keys_values(Pairs, VarsIndexOrder, WeightsIndexOrder),
|
pairs_keys_values(Pairs, VarsIndexOrder, WeightsIndexOrder),
|
||||||
bdd_maximum(BDD, Pairs, Max),
|
bdd_maximum(BDD, Pairs, Max),
|
||||||
max_labeling(BDD, Pairs).
|
max_labeling(BDD, Pairs).
|
||||||
|
|
||||||
|
group_sumweights_pair((I-V)-Ws, (I-V)-W) :- sum_list(Ws, W).
|
||||||
|
|
||||||
max_labeling(1, Pairs) :- max_upto(Pairs, _, _).
|
max_labeling(1, Pairs) :- max_upto(Pairs, _, _).
|
||||||
max_labeling(node(_,Var,Low,High,Aux), Pairs0) :-
|
max_labeling(node(_,Var,Low,High,Aux), Pairs0) :-
|
||||||
max_upto(Pairs0, Var, Pairs),
|
max_upto(Pairs0, Var, Pairs),
|
||||||
@@ -1383,6 +1628,7 @@ skip_to_var_(Var, Weight, [Var0-Weight0|VWs0], VWs) -->
|
|||||||
|
|
||||||
attribute_goals(Var) -->
|
attribute_goals(Var) -->
|
||||||
{ var_index_root(Var, _, Root) },
|
{ var_index_root(Var, _, Root) },
|
||||||
|
!,
|
||||||
( { root_get_formula_bdd(Root, Formula, BDD) } ->
|
( { root_get_formula_bdd(Root, Formula, BDD) } ->
|
||||||
{ del_bdd(Root) },
|
{ del_bdd(Root) },
|
||||||
( { clpb_residuals(bdd) } ->
|
( { clpb_residuals(bdd) } ->
|
||||||
@@ -1410,6 +1656,10 @@ attribute_goals(Var) -->
|
|||||||
booleans(RestVs)
|
booleans(RestVs)
|
||||||
; boolean(Var) % the variable may have occurred only in taut/2
|
; boolean(Var) % the variable may have occurred only in taut/2
|
||||||
).
|
).
|
||||||
|
attribute_goals(Var) -->
|
||||||
|
{ get_atts(Var, clpb_bdd(BDD)),
|
||||||
|
ground(BDD),
|
||||||
|
put_atts(Var, -clpb_bdd(_)) }.
|
||||||
|
|
||||||
del_clpb(Var) :-
|
del_clpb(Var) :-
|
||||||
del_attr(Var, clpb),
|
del_attr(Var, clpb),
|
||||||
@@ -1534,8 +1784,8 @@ pairs_([], _) --> [].
|
|||||||
pairs_([B|Bs], A) --> [A-B], pairs_(Bs, A).
|
pairs_([B|Bs], A) --> [A-B], pairs_(Bs, A).
|
||||||
|
|
||||||
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
Set the Prolog flag clpb_residuals to bdd to obtain the BDD nodes
|
Assert clpb:clpb_residuals(bdd) to obtain the BDD nodes as
|
||||||
as residuals. Note that they cannot be used as regular goals.
|
residuals. Note that they cannot be used as regular goals.
|
||||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
nodes([]) --> [].
|
nodes([]) --> [].
|
||||||
@@ -1563,9 +1813,10 @@ sats([]) --> [].
|
|||||||
sats([A|As]) --> [clpb:sat(A)], sats(As).
|
sats([A|As]) --> [clpb:sat(A)], sats(As).
|
||||||
|
|
||||||
booleans([]) --> [].
|
booleans([]) --> [].
|
||||||
booleans([B|Bs]) --> boolean(B), { del_clpb(B) }, booleans(Bs).
|
booleans([B|Bs]) --> boolean(B), booleans(Bs).
|
||||||
|
|
||||||
boolean(Var) -->
|
boolean(Var) -->
|
||||||
|
{ del_clpb(Var) },
|
||||||
( { get_attr(Var, clpb_omit_boolean, true) } -> []
|
( { get_attr(Var, clpb_omit_boolean, true) } -> []
|
||||||
; [clpb:sat(Var =:= Var)]
|
; [clpb:sat(Var =:= Var)]
|
||||||
).
|
).
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
|||||||
:- module(cont, [reset/3, shift/1]).
|
:- module(cont, [reset/3, shift/1]).
|
||||||
|
|
||||||
|
:- meta_predicate reset(0, ?, ?).
|
||||||
|
|
||||||
reset(Goal, Ball, Cont) :-
|
reset(Goal, Ball, Cont) :-
|
||||||
call(Goal),
|
call(Goal),
|
||||||
'$reset_cont_marker',
|
'$reset_cont_marker',
|
||||||
@@ -10,8 +12,8 @@ shift(Ball) :-
|
|||||||
'$nextEP'(first, E, P),
|
'$nextEP'(first, E, P),
|
||||||
get_chunks(E, P, L),
|
get_chunks(E, P, L),
|
||||||
( L == [] ->
|
( L == [] ->
|
||||||
Cont = none
|
Cont = cont(true)
|
||||||
; Cont = cont(call_continuation(L))
|
; Cont = cont(cont:call_continuation(L))
|
||||||
),
|
),
|
||||||
'$write_cont_and_term'(_, _, Cont, Ball),
|
'$write_cont_and_term'(_, _, Cont, Ball),
|
||||||
'$unwind_environments'.
|
'$unwind_environments'.
|
||||||
842
src/lib/crypto.pl
Normal file
842
src/lib/crypto.pl
Normal file
@@ -0,0 +1,842 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written 2020-2023 by Markus Triska (triska@metalevel.at)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
|
||||||
|
/** Predicates for cryptographic applications.
|
||||||
|
|
||||||
|
This library assumes that the Prolog flag `double_quotes` is set to `chars`.
|
||||||
|
In Scryer Prolog, lists of characters are very efficiently represented,
|
||||||
|
and strings have the advantage that the atom table remains unmodified.
|
||||||
|
|
||||||
|
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,
|
||||||
|
[hex_bytes/2, % ?Hex, ?Bytes
|
||||||
|
crypto_n_random_bytes/2, % +N, -Bytes
|
||||||
|
crypto_data_hash/3, % +Data, -Hash, +Options
|
||||||
|
crypto_data_hkdf/4, % +Data, +Length, -Bytes, +Options
|
||||||
|
crypto_password_hash/2, % +Password, ?Hash
|
||||||
|
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_new_keypair/1, % -KeyPair
|
||||||
|
ed25519_keypair_public_key/2, % +KeyPair, +PublicKey
|
||||||
|
ed25519_sign/4, % +KeyPair, +Data, -Signature, +Options
|
||||||
|
ed25519_verify/4, % +PublicKey, +Data, +Signature, +Options
|
||||||
|
curve25519_generator/1, % -Generator
|
||||||
|
curve25519_scalar_mult/3, % +Scalar, +Point, -Result
|
||||||
|
crypto_name_curve/2, % +Name, -Curve
|
||||||
|
crypto_curve_order/2, % +Curve, -Order
|
||||||
|
crypto_curve_generator/2, % +Curve, -Generator
|
||||||
|
crypto_curve_scalar_mult/4 % +Curve, +Scalar, +Point, -Result
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(between)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(clpz)).
|
||||||
|
:- use_module(library(arithmetic)).
|
||||||
|
:- use_module(library(format)).
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(si)).
|
||||||
|
:- use_module(library(iso_ext), [partial_string/3]).
|
||||||
|
|
||||||
|
|
||||||
|
%% hex_bytes(?Hex, ?Bytes) is det.
|
||||||
|
%
|
||||||
|
% Relation between a hexadecimal sequence and a list of bytes. Hex
|
||||||
|
% is a string of hexadecimal numbers. Bytes is a list of _integers_
|
||||||
|
% between 0 and 255 that represent the sequence as a list of bytes.
|
||||||
|
% At least one of the arguments must be instantiated.
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- hex_bytes("501ACE", Bs).
|
||||||
|
% Bs = [80,26,206].
|
||||||
|
% ```
|
||||||
|
|
||||||
|
hex_bytes(Hs, Bytes) :-
|
||||||
|
( ground(Hs) ->
|
||||||
|
must_be(chars, Hs),
|
||||||
|
( phrase(hex_bytes(Hs), Bytes) ->
|
||||||
|
true
|
||||||
|
; domain_error(hex_encoding, Hs, hex_bytes/2)
|
||||||
|
)
|
||||||
|
; must_be_bytes(Bytes, hex_bytes/2),
|
||||||
|
phrase(bytes_hex(Bytes), Hs)
|
||||||
|
).
|
||||||
|
|
||||||
|
hex_bytes([]) --> [].
|
||||||
|
hex_bytes([H1,H2|Hs]) --> [Byte],
|
||||||
|
{ char_hexval(H1, High),
|
||||||
|
char_hexval(H2, Low),
|
||||||
|
Byte #= High*16 + Low },
|
||||||
|
hex_bytes(Hs).
|
||||||
|
|
||||||
|
bytes_hex([]) --> [].
|
||||||
|
bytes_hex([B|Bs]) --> [C0,C1],
|
||||||
|
{ High #= B>>4,
|
||||||
|
Low #= B /\ 0xf,
|
||||||
|
char_hexval(C0, High),
|
||||||
|
char_hexval(C1, Low)
|
||||||
|
},
|
||||||
|
bytes_hex(Bs).
|
||||||
|
|
||||||
|
char_hexval(C, H) :- nth0(H, "0123456789abcdef", C), !.
|
||||||
|
char_hexval(C, H) :- nth0(H, "0123456789ABCDEF", C), !.
|
||||||
|
|
||||||
|
|
||||||
|
must_be_bytes(Bytes, Context) :-
|
||||||
|
must_be(list, Bytes),
|
||||||
|
maplist(must_be(integer), Bytes),
|
||||||
|
( member(B, Bytes), \+ between(0, 255, B) ->
|
||||||
|
type_error(byte, B, Context)
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
must_be_octet_chars(Chars, Context) :-
|
||||||
|
must_be(chars, Chars),
|
||||||
|
( '$first_non_octet'(Chars, F) ->
|
||||||
|
domain_error(octet_character, F, Context)
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Cryptographically secure random numbers
|
||||||
|
=======================================
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
%% crypto_n_random_bytes(+N, -Bytes) is det.
|
||||||
|
%
|
||||||
|
% Bytes is unified with a list of N cryptographically secure
|
||||||
|
% pseudo-random bytes. Each byte is an integer between 0 and 255. If
|
||||||
|
% the internal pseudo-random number generator (PRNG) has not been
|
||||||
|
% seeded with enough entropy to ensure an unpredictable byte
|
||||||
|
% sequence, an exception is thrown.
|
||||||
|
%
|
||||||
|
% One way to relate such a list of bytes to an _integer_ is to use
|
||||||
|
% CLP(ℤ) constraints as follows:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% :- use_module(library(clpz)).
|
||||||
|
% :- use_module(library(lists)).
|
||||||
|
%
|
||||||
|
% bytes_integer(Bs, N) :-
|
||||||
|
% foldl(pow, Bs, 0-0, N-_).
|
||||||
|
%
|
||||||
|
% pow(B, N0-I0, N-I) :-
|
||||||
|
% B in 0..255,
|
||||||
|
% N #= N0 + B*256^I0,
|
||||||
|
% I #= I0 + 1.
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% With this definition, we can generate a random 256-bit integer
|
||||||
|
% _from_ a list of 32 random _bytes_:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- crypto_n_random_bytes(32, Bs),
|
||||||
|
% bytes_integer(Bs, I).
|
||||||
|
% Bs = [146,166,162,210,242,7,25,132,64,94|...],
|
||||||
|
% I = 337420085690608915485...(56 digits omitted).
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% The above relation also works in the other direction, letting you
|
||||||
|
% translate an integer _to_ a list of bytes. In addition, you can
|
||||||
|
% use `hex_bytes/2` to convert bytes to _tokens_ that can be easily
|
||||||
|
% exchanged in your applications.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- crypto_n_random_bytes(12, Bs),
|
||||||
|
% hex_bytes(Hex, Bs).
|
||||||
|
% Bs = [34,25,50,72,58,63,50,172,32,46|...], Hex = "221932483a3f32ac202 ...".
|
||||||
|
% ```
|
||||||
|
|
||||||
|
crypto_n_random_bytes(N, Bs) :-
|
||||||
|
must_be(integer, N),
|
||||||
|
length(Bs, N),
|
||||||
|
maplist(crypto_random_byte, Bs).
|
||||||
|
|
||||||
|
crypto_random_byte(B) :- '$crypto_random_byte'(B).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Hashing
|
||||||
|
=======
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
%% crypto_data_hash(+Data, -Hash, +Options)
|
||||||
|
%
|
||||||
|
% Where Data is a list of characters, and Hash is the computed hash
|
||||||
|
% as a list of hexadecimal characters.
|
||||||
|
%
|
||||||
|
% Options is a list of:
|
||||||
|
%
|
||||||
|
% - `algorithm(+A)`
|
||||||
|
% where `A` is one of `ripemd160`, `sha256`, `sha384`, `sha512`,
|
||||||
|
% `sha512_256`, `sha3_224`, `sha3_256`, `sha3_384`,
|
||||||
|
% `sha3_512`, `blake2s256`, `blake2b512`, or a variable. If `A` is
|
||||||
|
% a variable, then it is unified with the default algorithm,
|
||||||
|
% which is an algorithm that is considered cryptographically
|
||||||
|
% secure at the time of this writing.
|
||||||
|
%
|
||||||
|
% - `encoding(+Encoding)`
|
||||||
|
% The default encoding is `utf8`. The alternative is `octet`, to
|
||||||
|
% treat the input as a list of raw bytes.
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- crypto_data_hash("abc", Hs, [algorithm(sha256)]).
|
||||||
|
% Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad".
|
||||||
|
% ```
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
SHA256 is the current default for several hash-related predicates.
|
||||||
|
It is deemed sufficiently secure for the foreseeable future. Yet,
|
||||||
|
application programmers must be aware that the default may change in
|
||||||
|
future versions. The hash predicates all yield the algorithm they
|
||||||
|
used if a Prolog variable is used for the pertaining option.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
crypto_data_hash(Data0, Hash, Options0) :-
|
||||||
|
must_be(list, Options0),
|
||||||
|
options_data_chars(Options0, Data0, Data, Encoding),
|
||||||
|
functor_hash_options(algorithm, A, 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).
|
||||||
|
|
||||||
|
options_data_chars(Options, Data, Chars, Encoding) :-
|
||||||
|
option(encoding(Encoding), Options, utf8),
|
||||||
|
must_be(atom, Encoding),
|
||||||
|
encoding_chars(Encoding, Data, Chars).
|
||||||
|
|
||||||
|
default_hash(sha256).
|
||||||
|
|
||||||
|
functor_hash_options(F, Hash, Options0, [Option|Options]) :-
|
||||||
|
Option =.. [F,Hash],
|
||||||
|
( select(Option, Options0, Options) ->
|
||||||
|
( var(Hash) ->
|
||||||
|
default_hash(Hash)
|
||||||
|
; must_be(atom, Hash)
|
||||||
|
)
|
||||||
|
; Options = Options0,
|
||||||
|
default_hash(Hash)
|
||||||
|
).
|
||||||
|
|
||||||
|
hash_algorithm(ripemd160).
|
||||||
|
hash_algorithm(sha256).
|
||||||
|
hash_algorithm(sha512).
|
||||||
|
hash_algorithm(sha384).
|
||||||
|
hash_algorithm(sha512_256).
|
||||||
|
hash_algorithm(sha3_224).
|
||||||
|
hash_algorithm(sha3_256).
|
||||||
|
hash_algorithm(sha3_384).
|
||||||
|
hash_algorithm(sha3_512).
|
||||||
|
hash_algorithm(blake2s256).
|
||||||
|
hash_algorithm(blake2b512).
|
||||||
|
|
||||||
|
|
||||||
|
%% crypto_data_hkdf(+Data, +Length, -Bytes, +Options) is det.
|
||||||
|
%
|
||||||
|
% Concentrate possibly dispersed entropy of Data and then expand it
|
||||||
|
% to the desired length. Data is a list of characters.
|
||||||
|
%
|
||||||
|
% Bytes is unified with a list of bytes of length Length, and is
|
||||||
|
% suitable as input keying material and initialization vectors to
|
||||||
|
% symmetric encryption algorithms.
|
||||||
|
%
|
||||||
|
% Admissible options are:
|
||||||
|
%
|
||||||
|
% - `algorithm(+Algorithm)`
|
||||||
|
% One of `sha256`, `sha384` or `sha512`. If you specify a variable,
|
||||||
|
% then it is unified with the algorithm that was used, which is a
|
||||||
|
% cryptographically secure algorithm by default.
|
||||||
|
% - `info(+Info)`
|
||||||
|
% Optional context and application specific information,
|
||||||
|
% specified as a list of characters. The default is `[]`.
|
||||||
|
% - `salt(+List)`
|
||||||
|
% Optionally, a list of bytes that are used as salt. The
|
||||||
|
% 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.
|
||||||
|
%
|
||||||
|
% The `info/1` option can be used to generate multiple keys from a
|
||||||
|
% single master key, using for example values such as "key" and
|
||||||
|
% "iv", or the name of a file that is to be encrypted.
|
||||||
|
%
|
||||||
|
% See `crypto_n_random_bytes/2` to obtain a suitable salt.
|
||||||
|
|
||||||
|
crypto_data_hkdf(Data0, L, Bytes, Options0) :-
|
||||||
|
functor_hash_options(algorithm, Algorithm, Options0, Options),
|
||||||
|
( hkdf_algorithm(Algorithm) -> true
|
||||||
|
; domain_error(hkdf_algorithm, Algorithm, crypto_data_hkdf/4)
|
||||||
|
),
|
||||||
|
must_be(integer, L),
|
||||||
|
L #>= 0,
|
||||||
|
options_data_chars(Options, Data0, Data, Encoding),
|
||||||
|
option(salt(SaltBytes), Options, []),
|
||||||
|
must_be_bytes(SaltBytes, crypto_data_hkdf/4),
|
||||||
|
option(info(Info0), Options, []),
|
||||||
|
chars_bytes_(Info0, Info, crypto_data_hkdf/4),
|
||||||
|
'$crypto_data_hkdf'(Data, Encoding, SaltBytes, Info, Algorithm, L, Bytes).
|
||||||
|
|
||||||
|
hkdf_algorithm(sha256).
|
||||||
|
hkdf_algorithm(sha384).
|
||||||
|
hkdf_algorithm(sha512).
|
||||||
|
|
||||||
|
option(What, Options, Default) :-
|
||||||
|
( member(V, Options), var(V) ->
|
||||||
|
instantiation_error(option/3)
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
( member(What, Options) -> true
|
||||||
|
; What =.. [_,Default]
|
||||||
|
).
|
||||||
|
|
||||||
|
chars_bytes_(Cs, Bytes, Context) :-
|
||||||
|
must_be(list, Cs),
|
||||||
|
( maplist(integer, Cs) -> Bytes = Cs
|
||||||
|
; chars_utf8bytes(Cs, Bytes)
|
||||||
|
),
|
||||||
|
must_be_bytes(Bytes, Context).
|
||||||
|
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
The so-called modular crypt format (MCF) is a standard for encoding
|
||||||
|
password hash strings. However, there's no official specification
|
||||||
|
document describing it. Nor is there a central registry of
|
||||||
|
identifiers or rules. This page describes what is known about it:
|
||||||
|
|
||||||
|
https://pythonhosted.org/passlib/modular_crypt_format.html
|
||||||
|
|
||||||
|
As of 2016, the MCF is deprecated in favor of the PHC String Format:
|
||||||
|
|
||||||
|
https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
|
||||||
|
|
||||||
|
This is what we are using below. For the time being, it is best to
|
||||||
|
treat these hashes as opaque terms in applications. Please let me
|
||||||
|
know if you need to rely on any specifics of this format.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
%% crypto_password_hash(+Password, ?Hash) is semidet.
|
||||||
|
%
|
||||||
|
% If Hash is instantiated, the predicate succeeds _iff_ the hash
|
||||||
|
% matches the given password. Otherwise, the call is equivalent to
|
||||||
|
% `crypto_password_hash(Password, Hash, [])` and computes a
|
||||||
|
% password-based hash using the default options.
|
||||||
|
|
||||||
|
crypto_password_hash(Password0, Hash) :-
|
||||||
|
( nonvar(Hash) ->
|
||||||
|
chars_bytes_(Password0, Password, crypto_password_hash/2),
|
||||||
|
must_be(list, Hash),
|
||||||
|
dollar_segments(Hash, [[],"pbkdf2-sha512",[t,=|CsIterations],SaltB64,HashB64]),
|
||||||
|
number_chars(Iterations, CsIterations),
|
||||||
|
bytes_base64(SaltBytes, SaltB64),
|
||||||
|
bytes_base64(HashBytes, HashB64),
|
||||||
|
'$crypto_password_hash'(Password, SaltBytes, Iterations, HashBytes)
|
||||||
|
; crypto_password_hash(Password0, Hash, [])
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
dollar_segments(Ls, Segments) :-
|
||||||
|
( append(Front, [$|Ds], Ls) ->
|
||||||
|
Segments = [Front|Rest],
|
||||||
|
dollar_segments(Ds, Rest)
|
||||||
|
; Segments = [Ls]
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
%% crypto_password_hash(+Password, -Hash, +Options) is det.
|
||||||
|
%
|
||||||
|
% Derive Hash based on Password. This predicate is similar to
|
||||||
|
% `crypto_data_hash/3` in that it derives a hash from given data.
|
||||||
|
% However, it is tailored for the specific use case of _passwords_.
|
||||||
|
% One essential distinction is that for this use case, the derivation
|
||||||
|
% of a hash should be _as slow as possible_ to counteract brute-force
|
||||||
|
% attacks over possible passwords.
|
||||||
|
%
|
||||||
|
% Another important distinction is that equal passwords must yield,
|
||||||
|
% with very high probability, _different_ hashes. For this reason,
|
||||||
|
% cryptographically strong random numbers are automatically added to
|
||||||
|
% the password before a hash is derived.
|
||||||
|
%
|
||||||
|
% Hash is unified with a string that contains the computed hash and
|
||||||
|
% all parameters that were used, except for the password. Instead of
|
||||||
|
% 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
|
||||||
|
% of length. The maximal length may increase in the future.
|
||||||
|
%
|
||||||
|
% Admissible options are:
|
||||||
|
%
|
||||||
|
% - `algorithm(+Algorithm)`
|
||||||
|
% The algorithm to use. Currently, the only available algorithm
|
||||||
|
% is `'pbkdf2-sha512'`, which is therefore also the default.
|
||||||
|
% - `cost(+C)`
|
||||||
|
% C is an integer, denoting the binary logarithm of the number
|
||||||
|
% of _iterations_ used for the derivation of the hash. This
|
||||||
|
% means that the number of iterations is set to 2^C. Currently,
|
||||||
|
% the default is 17, and thus more than one hundred _thousand_
|
||||||
|
% iterations. You should set this option as high as your server
|
||||||
|
% and users can tolerate. The default is subject to change and
|
||||||
|
% will likely increase in the future or adapt to new algorithms.
|
||||||
|
% - `salt(+Salt)`
|
||||||
|
% Use the given list of bytes as salt. By default,
|
||||||
|
% cryptographically secure random numbers are generated for this
|
||||||
|
% purpose. The default is intended to be secure, and constitutes
|
||||||
|
% the typical use case of this predicate.
|
||||||
|
%
|
||||||
|
% Currently, PBKDF2 with SHA-512 is used as the hash derivation
|
||||||
|
% function, using 128 bits of salt. All default parameters, including
|
||||||
|
% the algorithm, are subject to change, and other algorithms will also
|
||||||
|
% become available in the future. Since computed hashes store all
|
||||||
|
% parameters that were used during their derivation, such changes will
|
||||||
|
% not affect the operation of existing deployments. Note though that
|
||||||
|
% new hashes will then be computed with the new default parameters.
|
||||||
|
%
|
||||||
|
% See `crypto_data_hkdf/4` for generating keys from Hash.
|
||||||
|
|
||||||
|
crypto_password_hash(Password0, Hash, Options) :-
|
||||||
|
chars_bytes_(Password0, Password, crypto_password_hash/3),
|
||||||
|
must_be(list, Options),
|
||||||
|
option(cost(C), Options, 17),
|
||||||
|
Iterations #= 2^C,
|
||||||
|
Algorithm = 'pbkdf2-sha512', % current default and only option
|
||||||
|
option(algorithm(Algorithm), Options, Algorithm),
|
||||||
|
( member(salt(SaltBytes), Options) ->
|
||||||
|
must_be_bytes(SaltBytes, crypto_password_hash/2)
|
||||||
|
; crypto_n_random_bytes(16, SaltBytes)
|
||||||
|
),
|
||||||
|
'$crypto_password_hash'(Password, SaltBytes, Iterations, HashBytes),
|
||||||
|
bytes_base64(HashBytes, HashB64),
|
||||||
|
bytes_base64(SaltBytes, SaltB64),
|
||||||
|
phrase(format_("$pbkdf2-sha512$t=~d$~s$~s", [Iterations,SaltB64,HashB64]), Hash).
|
||||||
|
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Bidirectional Bytes <-> Base64 conversion *without padding*.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
bytes_base64(Bytes, Base64) :-
|
||||||
|
( var(Bytes) ->
|
||||||
|
chars_base64(Chars, Base64, [padding(false)]),
|
||||||
|
maplist(char_code, Chars, Bytes)
|
||||||
|
; maplist(char_code, Chars, Bytes),
|
||||||
|
chars_base64(Chars, Base64, [padding(false)])
|
||||||
|
).
|
||||||
|
|
||||||
|
%% crypto_data_encrypt(+PlainText, +Algorithm, +Key, +IV, -CipherText, +Options).
|
||||||
|
%
|
||||||
|
% Encrypt the given PlainText, using the symmetric algorithm
|
||||||
|
% Algorithm, key Key, and initialization vector (or nonce) IV, to
|
||||||
|
% give CipherText.
|
||||||
|
%
|
||||||
|
% PlainText must be a list of characters, Key and IV must be lists of
|
||||||
|
% bytes, and CipherText is created as a list of characters.
|
||||||
|
%
|
||||||
|
% Keys and IVs 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`. This input is often a shared
|
||||||
|
% secret, such as a negotiated point on an elliptic curve, or the hash
|
||||||
|
% that was computed from a password via `crypto_password_hash/3` with a
|
||||||
|
% freshly generated and specified _salt_.
|
||||||
|
%
|
||||||
|
% Reusing the same combination of Key and IV typically leaks at least
|
||||||
|
% _some_ information about the plaintext. For example, identical
|
||||||
|
% plaintexts will then correspond to identical ciphertexts. For some
|
||||||
|
% algorithms, reusing an IV with the same Key has disastrous results
|
||||||
|
% and can cause the loss of all properties that are otherwise
|
||||||
|
% guaranteed. Especially in such cases, an IV is also called a
|
||||||
|
% _nonce_ (number used once).
|
||||||
|
%
|
||||||
|
% It is safe to store and transfer the used initialization vector (or
|
||||||
|
% nonce) in plain text, but the key _must be kept secret_.
|
||||||
|
%
|
||||||
|
% Currently, the only supported algorithm is 'chacha20-poly1305', a
|
||||||
|
% powerful and efficient _authenticated_ encryption scheme, providing
|
||||||
|
% secrecy and at the same time reliable protection against undetected
|
||||||
|
% _modifications_ of the encrypted data. This is a very good choice
|
||||||
|
% for virtually all use cases. It is a stream cipher and can encrypt
|
||||||
|
% data of any length up to 256 GB. Further, the encrypted data has
|
||||||
|
% exactly the same length as the original, and no padding is used.
|
||||||
|
%
|
||||||
|
% Options:
|
||||||
|
%
|
||||||
|
% - `encoding(+Encoding)`
|
||||||
|
% Encoding to use for PlainText. Default is utf8. The alternative
|
||||||
|
% is octet to treat PlainText as raw bytes.
|
||||||
|
%
|
||||||
|
% - `tag(-List)`
|
||||||
|
% For authenticated encryption schemes, List is unified with a
|
||||||
|
% list of _bytes_ holding the tag. This tag must be provided for
|
||||||
|
% decryption.
|
||||||
|
%
|
||||||
|
% - `aad(+Data)`
|
||||||
|
% Data is additional authenticated data (AAD), a list of
|
||||||
|
% characters. It is authenticated in that it influences the tag,
|
||||||
|
% but it is not encrypted. The `encoding/1` option also specifies
|
||||||
|
% the encoding of Data.
|
||||||
|
%
|
||||||
|
% Here is an example encryption and decryption, using the ChaCha20
|
||||||
|
% stream cipher with the Poly1305 authenticator. This cipher uses a
|
||||||
|
% 256-bit key and a 96-bit nonce, i.e., 32 and 12 _bytes_,
|
||||||
|
% respectively:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- Algorithm = 'chacha20-poly1305',
|
||||||
|
% crypto_n_random_bytes(32, Key),
|
||||||
|
% crypto_n_random_bytes(12, IV),
|
||||||
|
% crypto_data_encrypt("this text is to be encrypted", Algorithm,
|
||||||
|
% Key, IV, CipherText, [tag(Tag)]),
|
||||||
|
% crypto_data_decrypt(CipherText, Algorithm,
|
||||||
|
% Key, IV, RecoveredText, [tag(Tag)]).
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Yielding:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% Algorithm = 'chacha20-poly1305',
|
||||||
|
% Key = [113,247,153,134,177,220,13,193,50,150|...],
|
||||||
|
% IV = [135,20,149,153,63,35,68,114,247,171|...],
|
||||||
|
% CipherText = "\x94\0Ej\x94\®Â\x95\óÑÆXÃn¾ð©b\x1c\ ...",
|
||||||
|
% RecoveredText = "this text is to be ...",
|
||||||
|
% Tag = [152,117,152,17,162,75,150,206,144,40|...]
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% In this example, we use `crypto_n_random_bytes/2` to generate a key
|
||||||
|
% and nonce from cryptographically secure random numbers. For
|
||||||
|
% repeated applications, you must ensure that a nonce is only used
|
||||||
|
% _once_ together with the same key. Note that for _authenticated_
|
||||||
|
% encryption schemes, the _tag_ that was computed during encryption
|
||||||
|
% is necessary for decryption. It is safe to store and transfer the
|
||||||
|
% tag in plain text.
|
||||||
|
%
|
||||||
|
% See also `crypto_data_decrypt/6`, and `hex_bytes/2` for conversion
|
||||||
|
% between bytes and hex encoding.
|
||||||
|
|
||||||
|
crypto_data_encrypt(PlainText0, Algorithm, Key, IV, CipherText, Options) :-
|
||||||
|
options_data_chars(Options, PlainText0, PlainText, Encoding),
|
||||||
|
option(tag(Tag), Options, _),
|
||||||
|
( nonvar(Tag) ->
|
||||||
|
must_be_bytes(Tag, crypto_data_encrypt/6)
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
option(aad(AAD0), Options, []),
|
||||||
|
encoding_chars(Encoding, AAD0, AAD),
|
||||||
|
must_be_bytes(Key, crypto_data_encrypt/6),
|
||||||
|
must_be_bytes(IV, crypto_data_encrypt/6),
|
||||||
|
must_be(atom, Algorithm),
|
||||||
|
( Algorithm = 'chacha20-poly1305' -> true
|
||||||
|
; domain_error('chacha20-poly1305', Algorithm, crypto_data_encrypt/6)
|
||||||
|
),
|
||||||
|
algorithm_key_iv(Algorithm, Key, IV),
|
||||||
|
'$crypto_data_encrypt'(PlainText, AAD, Encoding, Key, IV, Tag, CipherText).
|
||||||
|
|
||||||
|
algorithm_key_iv('chacha20-poly1305', Key, IV) :-
|
||||||
|
length(Key, 32),
|
||||||
|
length(IV, 12).
|
||||||
|
|
||||||
|
%% crypto_data_decrypt(+CipherText, +Algorithm, +Key, +IV, -PlainText, +Options).
|
||||||
|
%
|
||||||
|
% Decrypt the given CipherText, using the symmetric algorithm
|
||||||
|
% Algorithm, key Key, and initialization vector IV, to give
|
||||||
|
% PlainText. CipherText must be a list of characters, and Key and IV
|
||||||
|
% must be lists of bytes. PlainText is created as a list of
|
||||||
|
% characters.
|
||||||
|
%
|
||||||
|
% Currently, the only supported algorithm is 'chacha20-poly1305',
|
||||||
|
% a very secure, fast and versatile authenticated encryption method.
|
||||||
|
%
|
||||||
|
% 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.
|
||||||
|
%
|
||||||
|
% - `tag(+Tag)`
|
||||||
|
% For authenticated encryption schemes, the tag must be specified as
|
||||||
|
% a list of bytes exactly as they were generated upon encryption.
|
||||||
|
%
|
||||||
|
% - `aad(+Data)`
|
||||||
|
% Any additional authenticated data (AAD) must be specified. The
|
||||||
|
% `encoding/1` option also specifies the encoding of Data.
|
||||||
|
|
||||||
|
crypto_data_decrypt(CipherText0, Algorithm, Key, IV, PlainText, Options) :-
|
||||||
|
option(tag(Tag), Options, []),
|
||||||
|
must_be_bytes(Tag, crypto_data_decrypt/6),
|
||||||
|
must_be_bytes(Key, crypto_data_decrypt/6),
|
||||||
|
must_be_bytes(IV, crypto_data_decrypt/6),
|
||||||
|
must_be(atom, Algorithm),
|
||||||
|
option(encoding(Encoding), Options, utf8),
|
||||||
|
option(aad(AAD0), Options, []),
|
||||||
|
encoding_chars(Encoding, AAD0, AAD),
|
||||||
|
must_be(atom, Encoding),
|
||||||
|
member(Encoding, [utf8,octet]),
|
||||||
|
encoding_chars(octet, CipherText0, CipherText1),
|
||||||
|
maplist(char_code, TagChars, Tag),
|
||||||
|
% we append the tag very efficiently, retaining a compact
|
||||||
|
% internal string representation of the ciphertext
|
||||||
|
partial_string(CipherText1, CipherText, TagChars),
|
||||||
|
( Algorithm = 'chacha20-poly1305' -> true
|
||||||
|
; domain_error('chacha20-poly1305', Algorithm, crypto_data_decrypt/6)
|
||||||
|
),
|
||||||
|
algorithm_key_iv(Algorithm, Key, IV),
|
||||||
|
'$crypto_data_decrypt'(CipherText, AAD, Key, IV, Encoding, PlainText).
|
||||||
|
|
||||||
|
|
||||||
|
encoding_chars(octet, Bs, Cs) :-
|
||||||
|
must_be(list, Bs),
|
||||||
|
( maplist(integer, Bs) ->
|
||||||
|
maplist(char_code, Cs, Bs)
|
||||||
|
; Bs = Cs
|
||||||
|
),
|
||||||
|
must_be_octet_chars(Cs, crypto_encoding).
|
||||||
|
encoding_chars(utf8, Cs, Cs) :-
|
||||||
|
must_be(chars, Cs).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Digital signatures with Ed25519
|
||||||
|
===============================
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
%% ed25519_new_keypair(-Pair)
|
||||||
|
%
|
||||||
|
% Yields a new Ed25519 key pair Pair, a list of characters. 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_new_keypair(Pair) :-
|
||||||
|
'$ed25519_new_keypair'(Pair).
|
||||||
|
|
||||||
|
%% ed25519_keypair_public_key(+Pair, -PublicKey)
|
||||||
|
%
|
||||||
|
% PublicKey is the public key of the given key pair. The public key
|
||||||
|
% can be used for signature verification, and can be shared freely.
|
||||||
|
% 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).
|
||||||
|
|
||||||
|
%% ed25519_sign(+Key, +Data, -Signature, +Options)
|
||||||
|
%
|
||||||
|
% Key and Data must be lists of characters. Key is a key pair in
|
||||||
|
% 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),
|
||||||
|
options_data_chars(Options, Data0, Data, Encoding),
|
||||||
|
'$ed25519_sign'(Key, Data, Encoding, Signature0),
|
||||||
|
hex_bytes(Signature, Signature0).
|
||||||
|
|
||||||
|
%% ed25519_verify(+Key, +Data, +Signature, +Options)
|
||||||
|
%
|
||||||
|
% Key and Data must be lists of characters. Key is a public key.
|
||||||
|
% Succeeds if Data was signed with the private key corresponding to
|
||||||
|
% Key, where Signature is a list of hexadecimal characters as
|
||||||
|
% generated by `ed25519_sign/4`. Fails otherwise.
|
||||||
|
%
|
||||||
|
% Currently, the only option for signing and verifying is:
|
||||||
|
%
|
||||||
|
% - `encoding(+Encoding)`
|
||||||
|
% The default encoding of Data is `utf8`. The alternative is `octet`,
|
||||||
|
% which treats Data as a list of raw bytes.
|
||||||
|
|
||||||
|
ed25519_verify(Key, Data0, Signature0, Options) :-
|
||||||
|
must_be_octet_chars(Key, ed25519_verify),
|
||||||
|
options_data_chars(Options, Data0, Data, Encoding),
|
||||||
|
hex_bytes(Signature0, Signature),
|
||||||
|
'$ed25519_verify'(Key, Data, Encoding, Signature).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
X25519: ECDH key exchange over Curve25519
|
||||||
|
=========================================
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
%% curve25519_generator(-Gs)
|
||||||
|
%
|
||||||
|
% Points on Curve25519 are represented as lists of characters that
|
||||||
|
% denote the u-coordinate of the Montgomery curve. Gs is the
|
||||||
|
% generator point of Curve25519.
|
||||||
|
|
||||||
|
curve25519_generator(Gs) :-
|
||||||
|
length(Gs0, 32),
|
||||||
|
Gs0 = [9|Zs],
|
||||||
|
maplist(=(0), Zs),
|
||||||
|
maplist(char_code, Gs, Gs0).
|
||||||
|
|
||||||
|
%% curve25519_scalar_mult(+Scalar, +Ps, -Rs)
|
||||||
|
%
|
||||||
|
% Scalar must be an integer between 0 and 2^256-1,
|
||||||
|
% or a list of 32 bytes, and Ps must be a point on the curve.
|
||||||
|
% Computes the point _Rs = Scalar*Ps as_ mandated by X25519.
|
||||||
|
%
|
||||||
|
% Alice and Bob can use this to establish a shared secret as follows,
|
||||||
|
% where Gs is the generator point of Curve25519:
|
||||||
|
%
|
||||||
|
% 1. Alice creates a random integer _a_ and sends _As = a*Gs_ to Bob.
|
||||||
|
%
|
||||||
|
% 2. Bob creates a random integer _b_ and sends _Bs = b*Gs_ to Alice.
|
||||||
|
%
|
||||||
|
% 3. Alice computes _Rs = a*Bs_.
|
||||||
|
%
|
||||||
|
% 4. Bob computes _Rs = b*As_.
|
||||||
|
%
|
||||||
|
% 5. Alice and Bob use `crypto_data_hkdf/4` on Rs with suitable
|
||||||
|
% (same) parameters to obtain lists of bytes that can be used as
|
||||||
|
% keys and initialization vectors for symmetric encryption.
|
||||||
|
%
|
||||||
|
% If _a_ and _b_ are kept secret, this method is considered very secure.
|
||||||
|
|
||||||
|
curve25519_scalar_mult(Scalar, Point, Result) :-
|
||||||
|
( integer_si(Scalar) ->
|
||||||
|
length(ScalarBytes, 32),
|
||||||
|
bytes_integer(ScalarBytes, Scalar)
|
||||||
|
; ScalarBytes = Scalar,
|
||||||
|
must_be_bytes(ScalarBytes, curve25519_scalar_mult/3),
|
||||||
|
length(ScalarBytes, 32)
|
||||||
|
),
|
||||||
|
must_be(chars, Point),
|
||||||
|
length(Point, 32),
|
||||||
|
maplist(char_code, Point, PointBytes),
|
||||||
|
'$curve25519_scalar_mult'(ScalarBytes, PointBytes, Result).
|
||||||
|
|
||||||
|
bytes_integer(Bs, N) :-
|
||||||
|
foldl(pow, Bs, t(0,0,N), t(N,_,_)).
|
||||||
|
|
||||||
|
pow(B, t(N0,P0,I0), t(N,P,I)) :-
|
||||||
|
( integer(I0) ->
|
||||||
|
B #= I0 mod 256,
|
||||||
|
I #= I0 >> 8
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
B in 0..255,
|
||||||
|
N #= N0 + B*256^P0,
|
||||||
|
P #= P0 + 1.
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Operations on Elliptic Curves
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Sample use: Establishing a shared secret S, using ECDH key exchange.
|
||||||
|
|
||||||
|
?- crypto_name_curve(secp256k1, C),
|
||||||
|
crypto_curve_generator(C, Generator),
|
||||||
|
PrivateKey = 10,
|
||||||
|
crypto_curve_scalar_mult(C, PrivateKey, Generator, PublicKey),
|
||||||
|
Random = 12,
|
||||||
|
crypto_curve_scalar_mult(C, Random, Generator, R),
|
||||||
|
crypto_curve_scalar_mult(C, Random, PublicKey, S),
|
||||||
|
crypto_curve_scalar_mult(C, PrivateKey, R, S).
|
||||||
|
|
||||||
|
For better security, new code should use Curve25519 instead.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
An elliptic curve over a prime field F_p is represented as:
|
||||||
|
|
||||||
|
curve(Name,P,A,B,point(X,Y),Order,FieldLength,Cofactor).
|
||||||
|
|
||||||
|
First, we define suitable accessors.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
curve_name(curve(Name,_,_,_,_,_,_,_), Name).
|
||||||
|
curve_p(curve(_,P,_,_,_,_,_,_), P).
|
||||||
|
curve_a(curve(_,_,A,_,_,_,_,_), A).
|
||||||
|
curve_b(curve(_,_,_,B,_,_,_,_), B).
|
||||||
|
curve_field_length(curve(_,_,_,_,_,_,FieldLength,_), FieldLength).
|
||||||
|
|
||||||
|
crypto_curve_generator(curve(_,_,_,_,G,_,_,_), G).
|
||||||
|
crypto_curve_order(curve(_,_,_,_,_,Order,_,_), Order).
|
||||||
|
|
||||||
|
crypto_curve_scalar_mult(Curve, Scalar, point(X,Y), point(RX, RY)) :-
|
||||||
|
must_be(integer, Scalar),
|
||||||
|
must_be_on_curve(Curve, point(X,Y)),
|
||||||
|
curve_name(Curve, Name),
|
||||||
|
curve_field_length(Curve, L0),
|
||||||
|
L #= 2*L0, % for hex encoding
|
||||||
|
phrase(format_("04~|~`0t~16r~*+~`0t~16r~*+", [X,L,Y,L]), PointHex),
|
||||||
|
hex_bytes(PointHex, PointBytes),
|
||||||
|
once(bytes_integer(ScalarBytes, Scalar)),
|
||||||
|
'$crypto_curve_scalar_mult'(Name, ScalarBytes, PointBytes, [_|Us]),
|
||||||
|
maplist(char_code, Us, Bs),
|
||||||
|
length(XBs, 32),
|
||||||
|
append(XBs, YBs, Bs),
|
||||||
|
maplist(reverse, [XBs,YBs], RBs),
|
||||||
|
maplist(bytes_integer, RBs, [RX,RY]).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
?- crypto_name_curve(secp256k1, Curve),
|
||||||
|
crypto_curve_generator(Curve, G),
|
||||||
|
crypto_curve_scalar_mult(Curve, 2, G, R).
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Validation.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
curve_contains_point(Curve, point(QX,QY)) :-
|
||||||
|
curve_a(Curve, A),
|
||||||
|
curve_b(Curve, B),
|
||||||
|
curve_p(Curve, P),
|
||||||
|
QY^2 mod P #= (QX^3 + A*QX + B) mod P.
|
||||||
|
|
||||||
|
must_be_on_curve(Curve, P) :-
|
||||||
|
\+ curve_contains_point(Curve, P),
|
||||||
|
domain_error(point_on_curve, P, crypto_elliptic_curves).
|
||||||
|
must_be_on_curve(Curve, P) :- curve_contains_point(Curve, P).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Predefined curves
|
||||||
|
=================
|
||||||
|
|
||||||
|
List available curves:
|
||||||
|
|
||||||
|
$ openssl ecparam -list_curves
|
||||||
|
|
||||||
|
Show curve parameters for secp256k1:
|
||||||
|
|
||||||
|
$ openssl ecparam -param_enc explicit -conv_form uncompressed \
|
||||||
|
-text -no_seed -name secp256k1
|
||||||
|
|
||||||
|
You must remove the leading "04:" from the generator.
|
||||||
|
|
||||||
|
The field length depends on the order of the curve and can be computed
|
||||||
|
with order_field_length/2.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
order_field_length(Order, L) :-
|
||||||
|
fitting_exponent(Order, 0, E),
|
||||||
|
L #= (E + 7) // 8.
|
||||||
|
|
||||||
|
fitting_exponent(N, E0, E) :-
|
||||||
|
( 2^E0 #>= N -> E #= E0
|
||||||
|
; E1 #= E0 + 1,
|
||||||
|
fitting_exponent(N, E1, E)
|
||||||
|
).
|
||||||
|
|
||||||
|
crypto_name_curve(secp256k1,
|
||||||
|
curve(secp256k1,
|
||||||
|
0x00fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
|
||||||
|
0x0,
|
||||||
|
0x7,
|
||||||
|
point(0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
|
||||||
|
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8),
|
||||||
|
0x00fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,
|
||||||
|
32,
|
||||||
|
1)).
|
||||||
251
src/lib/csv.pl
Normal file
251
src/lib/csv.pl
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/** Predicates for parsing CSV data
|
||||||
|
|
||||||
|
## Read CSV files.
|
||||||
|
|
||||||
|
Only two options with default values:
|
||||||
|
|
||||||
|
- `token_separator(',')`
|
||||||
|
- `with_header(true)`
|
||||||
|
|
||||||
|
### Examples:
|
||||||
|
|
||||||
|
Parsing a CSV string:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(csv)).
|
||||||
|
?- use_module(library(dcgs)).
|
||||||
|
?- phrase(parse_csv(Data), "col1,col2,col3,col4\none,2,,three").
|
||||||
|
Data = frame(["col1","col2","col3","col4"],[["one",2,[],"three"]]).
|
||||||
|
```
|
||||||
|
|
||||||
|
With some options:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- phrase(parse_csv(Data, [with_header(false), token_separator(';')]), "one;2;;three").
|
||||||
|
Data = frame([],[["one",2,[],"three"]]).
|
||||||
|
```
|
||||||
|
|
||||||
|
Parsing a CSV file:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(csv)).
|
||||||
|
?- use_module(library(pio)).
|
||||||
|
?- phrase_from_file(parse_csv(frame(Header, Rows)), './test.csv').
|
||||||
|
```
|
||||||
|
|
||||||
|
## Write CSV files
|
||||||
|
|
||||||
|
Four options with default values :
|
||||||
|
|
||||||
|
- `line_separator('\n')`
|
||||||
|
- `token_separator(',')`
|
||||||
|
- `with_header(true)`
|
||||||
|
- `null_value(empty)`
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
Writing a CSV file:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(csv)).
|
||||||
|
?- write_csv('./test.csv', frame(["col1","col2","col3","col4"], [["one",2,[],"three"]])).
|
||||||
|
```
|
||||||
|
|
||||||
|
With some options
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(csv)).
|
||||||
|
?- write_csv('./test.csv', frame(
|
||||||
|
["col1","col2","col3","col4"],
|
||||||
|
[["one",2,[],"three"]]
|
||||||
|
),
|
||||||
|
[with_header(false), line_separator('\r\n'), token_separator(';'), null_value('\\N')]).
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(csv, [
|
||||||
|
parse_csv//1,
|
||||||
|
parse_csv//2,
|
||||||
|
write_csv/2,
|
||||||
|
write_csv/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(format)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
|
||||||
|
option(W, O) :-
|
||||||
|
( member(W, O) -> true
|
||||||
|
; throw(error(domain_error(csv_option, W), option/2))).
|
||||||
|
|
||||||
|
|
||||||
|
option_extends([], Opt, Opt).
|
||||||
|
option_extends([X | Y], Opt0, Opt) :-
|
||||||
|
functor(X, Name, 1),
|
||||||
|
F0 =.. [Name, _],
|
||||||
|
( select(F0, Opt0, R) ->
|
||||||
|
option_extends(Y, [X | R], Opt)
|
||||||
|
; option_extends(Y, [X | Opt0], Opt) ).
|
||||||
|
|
||||||
|
|
||||||
|
%% -- write --
|
||||||
|
|
||||||
|
|
||||||
|
escaped_field([], []).
|
||||||
|
escaped_field(['"' | Y], ['"', '"' | R]) :-
|
||||||
|
escaped_field(Y, R).
|
||||||
|
escaped_field([X | Y], [X | R]) :-
|
||||||
|
X \== '"',
|
||||||
|
escaped_field(Y, R).
|
||||||
|
|
||||||
|
|
||||||
|
ensure_escaped(Field, Field) :-
|
||||||
|
(atom(Field); integer(Field); float(Field)).
|
||||||
|
ensure_escaped([X | Y], Field) :-
|
||||||
|
escaped_field([X | Y], Field).
|
||||||
|
|
||||||
|
|
||||||
|
write_field(Out, Field, Opt) :-
|
||||||
|
( Field \== [] ->
|
||||||
|
ensure_escaped(Field, Field0),
|
||||||
|
format(Out, "~w", [Field0])
|
||||||
|
; option(null_value(Null_Value), Opt),
|
||||||
|
( Null_Value == empty -> true
|
||||||
|
; format(Out, "~w", [Null_Value]))).
|
||||||
|
|
||||||
|
|
||||||
|
write_row(Out, [Field], Opt) :-
|
||||||
|
write_field(Out, Field, Opt).
|
||||||
|
write_row(Out, [Field, X | Y], Opt) :-
|
||||||
|
write_field(Out, Field, Opt),
|
||||||
|
option(token_separator(Tk_Sep), Opt),
|
||||||
|
format(Out, "~w", [Tk_Sep]),
|
||||||
|
write_row(Out, [X | Y], Opt).
|
||||||
|
|
||||||
|
|
||||||
|
write_rows(Out, [Row], Opt) :-
|
||||||
|
write_row(Out, Row, Opt).
|
||||||
|
write_rows(Out, [Row, X | Y], Opt) :-
|
||||||
|
option(line_separator(Line_Sep), Opt),
|
||||||
|
write_row(Out, Row, Opt),
|
||||||
|
format(Out, "~w", [Line_Sep]),
|
||||||
|
write_rows(Out, [X | Y], Opt).
|
||||||
|
|
||||||
|
|
||||||
|
write_csv_(Out, frame(Header, Rows), Opt) :-
|
||||||
|
option(with_header(With_Header), Opt),
|
||||||
|
( With_Header == true ->
|
||||||
|
write_row(Out, Header, Opt),
|
||||||
|
option(line_separator(Line_Sep), Opt),
|
||||||
|
format(Out, "~w", [Line_Sep])
|
||||||
|
; true),
|
||||||
|
write_rows(Out, Rows, Opt).
|
||||||
|
|
||||||
|
|
||||||
|
write_csv(File_Name, Frm, Opt) :-
|
||||||
|
option_extends(Opt, [
|
||||||
|
null_value(empty),
|
||||||
|
token_separator(','),
|
||||||
|
with_header(true),
|
||||||
|
line_separator('\n')
|
||||||
|
], Opt0),
|
||||||
|
setup_call_cleanup(
|
||||||
|
open(File_Name, write, Out),
|
||||||
|
write_csv_(Out, Frm, Opt0),
|
||||||
|
close(Out)).
|
||||||
|
write_csv(File_Name, Frm) :-
|
||||||
|
write_csv(File_Name, Frm, []).
|
||||||
|
|
||||||
|
|
||||||
|
%% -- read --
|
||||||
|
|
||||||
|
|
||||||
|
tokens([], Opt), [Tk_Sep] -->
|
||||||
|
{ option(token_separator(Tk_Sep), Opt) },
|
||||||
|
[Tk_Sep],
|
||||||
|
!.
|
||||||
|
tokens([], _), "\r\n" -->
|
||||||
|
"\r\n",
|
||||||
|
!.
|
||||||
|
tokens([], _), "\n" -->
|
||||||
|
"\n",
|
||||||
|
!.
|
||||||
|
tokens([], _), "\r" -->
|
||||||
|
"\r",
|
||||||
|
!.
|
||||||
|
tokens([X | Y], Opt) -->
|
||||||
|
[X],
|
||||||
|
!,
|
||||||
|
tokens(Y, Opt).
|
||||||
|
tokens([], _) --> [].
|
||||||
|
|
||||||
|
|
||||||
|
field(R, Opt) -->
|
||||||
|
"\"",
|
||||||
|
!,
|
||||||
|
string_tokens(R, Opt).
|
||||||
|
field(R, Opt) -->
|
||||||
|
tokens(R0, Opt),
|
||||||
|
{ R0 \== [],
|
||||||
|
catch(number_chars(R, R0), _, R = R0)
|
||||||
|
}.
|
||||||
|
field([], _) --> [].
|
||||||
|
|
||||||
|
|
||||||
|
string_tokens(R, Opt) -->
|
||||||
|
[X],
|
||||||
|
( { X == '"' } ->
|
||||||
|
( "\"" ->
|
||||||
|
{ R = [X | Y] },
|
||||||
|
string_tokens(Y, Opt)
|
||||||
|
; { R = [] })
|
||||||
|
; { R = [X | Y] },
|
||||||
|
string_tokens(Y, Opt)).
|
||||||
|
|
||||||
|
|
||||||
|
end_token --> "\r\n".
|
||||||
|
end_token --> "\n".
|
||||||
|
end_token --> "\r".
|
||||||
|
end_token --> [].
|
||||||
|
|
||||||
|
|
||||||
|
separator(Opt) -->
|
||||||
|
{ option(token_separator(Tk_Sep), Opt) },
|
||||||
|
[Tk_Sep].
|
||||||
|
|
||||||
|
|
||||||
|
row([X | Y], Opt) -->
|
||||||
|
field(X, Opt),
|
||||||
|
!,
|
||||||
|
( separator(Opt) ->
|
||||||
|
row(Y, Opt)
|
||||||
|
; end_token ->
|
||||||
|
{ Y = [] }).
|
||||||
|
|
||||||
|
|
||||||
|
rows(R, Opt) -->
|
||||||
|
row(X, Opt),
|
||||||
|
!,
|
||||||
|
( { X \== [[]] } ->
|
||||||
|
rows(Y, Opt),
|
||||||
|
{ R = [X | Y] }
|
||||||
|
; { R = [] }).
|
||||||
|
|
||||||
|
|
||||||
|
parse_csv(frame(Header, Rows), Opt) -->
|
||||||
|
{ option_extends(Opt, [
|
||||||
|
with_header(true),
|
||||||
|
token_separator(',')
|
||||||
|
], Opt0)
|
||||||
|
},
|
||||||
|
( { option(with_header(With_Header), Opt0),
|
||||||
|
With_Header == true } ->
|
||||||
|
row(Header, Opt0),
|
||||||
|
{ Header \== [[]] },
|
||||||
|
end_token
|
||||||
|
; { Header = [] }),
|
||||||
|
rows(Rows, Opt0).
|
||||||
|
parse_csv(R) -->
|
||||||
|
parse_csv(R, []).
|
||||||
@@ -1,51 +1,80 @@
|
|||||||
:- module(dcgs, [op(1200, xfx, -->),
|
/** Support for Definite Clause Grammars.
|
||||||
op(1105, xfy, '|'),
|
|
||||||
phrase/2,
|
A Prolog definite clause grammar (DCG) describes a sequence. Operationally, DCGs
|
||||||
phrase/3]).
|
can be used to parse, generate, complete and check sequences manifested as lists.
|
||||||
|
|
||||||
|
Check [The Power of Prolog chapter on DCGs](https://www.metalevel.at/prolog/dcg)
|
||||||
|
to learn more about them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
:- module(dcgs,
|
||||||
|
[op(1105, xfy, '|'),
|
||||||
|
phrase/2,
|
||||||
|
phrase/3,
|
||||||
|
seq//1,
|
||||||
|
seqq//1,
|
||||||
|
... //0
|
||||||
|
]).
|
||||||
|
|
||||||
:- use_module(library(error)).
|
:- use_module(library(error)).
|
||||||
:- use_module(library(lists), [append/3]).
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(lists), [append/3, member/2]).
|
||||||
|
:- use_module(library(loader), [strip_module/3]).
|
||||||
|
|
||||||
user:term_expansion(Term0, Term) :-
|
:- meta_predicate phrase(2, ?).
|
||||||
nonvar(Term0),
|
|
||||||
dcg_rule(Term0, (Head :- Body)),
|
:- meta_predicate phrase(2, ?, ?).
|
||||||
Term = (Head :- Body).
|
|
||||||
|
%% phrase(+Body, ?Ls).
|
||||||
|
%
|
||||||
|
% True iff Body describes the list Ls. Body must be a DCG body.
|
||||||
|
% It is equivalent to `phrase(Body, Ls, [])`.
|
||||||
|
%
|
||||||
|
% Examples:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% as --> [].
|
||||||
|
% as --> [a], as.
|
||||||
|
%
|
||||||
|
% ?- phrase(as, Ls).
|
||||||
|
% Ls = []
|
||||||
|
% ; Ls = "a"
|
||||||
|
% ; Ls = "aa"
|
||||||
|
% ; Ls = "aaa"
|
||||||
|
% ; ... .
|
||||||
|
%
|
||||||
|
% ?- phrase(as, "aaa").
|
||||||
|
% true.
|
||||||
|
% ```
|
||||||
|
|
||||||
phrase(GRBody, S0) :-
|
phrase(GRBody, S0) :-
|
||||||
phrase(GRBody, S0, []).
|
phrase(GRBody, S0, []).
|
||||||
|
|
||||||
|
%% phrase(+Body, ?Ls, ?Ls0).
|
||||||
|
%
|
||||||
|
% True iff Body describes part of the list Ls and the rest of Ls is Ls0.
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- phrase(seq(X), "aaa", Y).
|
||||||
|
% X = [], Y = "aaa"
|
||||||
|
% ; X = "a", Y = "aa"
|
||||||
|
% ; X = "aa", Y = "a"
|
||||||
|
% ; X = "aaa", Y = [].
|
||||||
|
% ```
|
||||||
phrase(GRBody, S0, S) :-
|
phrase(GRBody, S0, S) :-
|
||||||
( var(GRBody) -> throw(error(instantiation_error, phrase/3))
|
strip_module(GRBody, M, GRBody1),
|
||||||
; dcg_constr(GRBody) -> phrase_(GRBody, S0, S)
|
( var(GRBody) ->
|
||||||
; functor(GRBody, _, _) -> call(GRBody, S0, S)
|
instantiation_error(phrase/3)
|
||||||
; throw(error(type_error(callable, GRBody), phrase/3))
|
; nonvar(GRBody1),
|
||||||
|
dcg_constr(GRBody1),
|
||||||
|
dcg_body(GRBody1, S0, S, GRBody2) ->
|
||||||
|
call(M:GRBody2)
|
||||||
|
; call(M:GRBody1, S0, S)
|
||||||
).
|
).
|
||||||
|
|
||||||
phrase_([], S, S).
|
|
||||||
phrase_(!, S, S).
|
|
||||||
phrase_((A, B), S0, S) :-
|
|
||||||
phrase(A, S0, S1), phrase(B, S1, S).
|
|
||||||
phrase_((A -> B ; C), S0, S) :-
|
|
||||||
!,
|
|
||||||
( phrase(A, S0, S1) ->
|
|
||||||
phrase(B, S1, S)
|
|
||||||
; phrase(C, S0, S)
|
|
||||||
).
|
|
||||||
phrase_((A ; B), S0, S) :-
|
|
||||||
( phrase(A, S0, S) ; phrase(B, S0, S) ).
|
|
||||||
phrase_((A | B), S0, S) :-
|
|
||||||
( phrase(A, S0, S) ; phrase(B, S0, S) ).
|
|
||||||
phrase_({G}, S0, S) :-
|
|
||||||
( call(G), S0 = S ).
|
|
||||||
phrase_(call(G), S0, S) :-
|
|
||||||
call(G, S0, S).
|
|
||||||
phrase_((A -> B), S0, S) :-
|
|
||||||
phrase((A -> B ; fail), S0, S).
|
|
||||||
phrase_(phrase(NonTerminal), S0, S) :-
|
|
||||||
phrase(NonTerminal, S0, S).
|
|
||||||
phrase_([T|Ts], S0, S) :-
|
|
||||||
append([T|Ts], S, S0).
|
|
||||||
|
|
||||||
% The same version of the below two dcg_rule clauses, but with module scoping.
|
% The same version of the below two dcg_rule clauses, but with module scoping.
|
||||||
dcg_rule(( M:NonTerminal, Terminals --> GRBody ), ( M:Head :- Body )) :-
|
dcg_rule(( M:NonTerminal, Terminals --> GRBody ), ( M:Head :- Body )) :-
|
||||||
dcg_non_terminal(NonTerminal, S0, S, Head),
|
dcg_non_terminal(NonTerminal, S0, S, Head),
|
||||||
@@ -84,12 +113,17 @@ dcg_body(GRBody, S0, S, Body) :-
|
|||||||
nonvar(GRBody),
|
nonvar(GRBody),
|
||||||
dcg_constr(GRBody),
|
dcg_constr(GRBody),
|
||||||
dcg_cbody(GRBody, S0, S, Body).
|
dcg_cbody(GRBody, S0, S, Body).
|
||||||
dcg_body(NonTerminal, S0, S, Goal) :-
|
dcg_body(NonTerminal, S0, S, Goal1) :-
|
||||||
nonvar(NonTerminal),
|
nonvar(NonTerminal),
|
||||||
\+ dcg_constr(NonTerminal),
|
\+ dcg_constr(NonTerminal),
|
||||||
NonTerminal \= ( _ -> _ ),
|
NonTerminal \= ( _ -> _ ),
|
||||||
NonTerminal \= ( \+ _ ),
|
NonTerminal \= ( \+ _ ),
|
||||||
dcg_non_terminal(NonTerminal, S0, S, Goal).
|
loader:strip_module(NonTerminal, M, NonTerminal0),
|
||||||
|
dcg_non_terminal(NonTerminal0, S0, S, Goal0),
|
||||||
|
( functor(NonTerminal, (:), 2) ->
|
||||||
|
Goal1 = M:Goal0
|
||||||
|
; Goal1 = Goal0
|
||||||
|
).
|
||||||
|
|
||||||
% The following constructs in a grammar rule body
|
% The following constructs in a grammar rule body
|
||||||
% are defined in the corresponding subclauses.
|
% are defined in the corresponding subclauses.
|
||||||
@@ -133,3 +167,53 @@ dcg_cbody(\+ GRBody, S0, S, ( \+ phrase(GRBody,S0,_), S0 = S )).
|
|||||||
dcg_cbody(( GRIf -> GRThen ), S0, S, ( If -> Then )) :-
|
dcg_cbody(( GRIf -> GRThen ), S0, S, ( If -> Then )) :-
|
||||||
dcg_body(GRIf, S0, S1, If),
|
dcg_body(GRIf, S0, S1, If),
|
||||||
dcg_body(GRThen, S1, S, Then).
|
dcg_body(GRThen, S1, S, Then).
|
||||||
|
|
||||||
|
user:term_expansion(Term0, Term) :-
|
||||||
|
nonvar(Term0),
|
||||||
|
dcg_rule(Term0, Term).
|
||||||
|
|
||||||
|
|
||||||
|
%% seq(Seq)//
|
||||||
|
%
|
||||||
|
% Describes a sequence
|
||||||
|
seq(Xs, Cs0,Cs) :-
|
||||||
|
var(Xs),
|
||||||
|
Cs0 == [],
|
||||||
|
!,
|
||||||
|
Xs = [],
|
||||||
|
Cs0 = Cs.
|
||||||
|
seq([]) --> [].
|
||||||
|
seq([E|Es]) --> [E], seq(Es).
|
||||||
|
|
||||||
|
%% seqq(SeqOfSeqs)//
|
||||||
|
%
|
||||||
|
% Describes a sequence of sequences
|
||||||
|
seqq([]) --> [].
|
||||||
|
seqq([Es|Ess]) --> seq(Es), seqq(Ess).
|
||||||
|
|
||||||
|
%% ...//
|
||||||
|
%
|
||||||
|
% Describes an arbitrary number of elements
|
||||||
|
...(Cs0,Cs) :-
|
||||||
|
Cs0 == [],
|
||||||
|
!,
|
||||||
|
Cs0 = Cs.
|
||||||
|
... --> [] | [_], ... .
|
||||||
|
|
||||||
|
error_goal(error(E, must_be/2), error(E, must_be/2)).
|
||||||
|
error_goal(error(E, (=..)/2), error(E, (=..)/2)).
|
||||||
|
error_goal(E, _) :- throw(E).
|
||||||
|
|
||||||
|
user:goal_expansion(phrase(GRBody, S, S0), GRBody2) :-
|
||||||
|
loader:strip_module(GRBody, M, GRBody0),
|
||||||
|
nonvar(GRBody0),
|
||||||
|
catch(dcgs:dcg_body(GRBody0, S, S0, GRBody1),
|
||||||
|
E,
|
||||||
|
dcgs:error_goal(E, GRBody1)
|
||||||
|
),
|
||||||
|
( GRBody = (_:_) ->
|
||||||
|
GRBody2 = M:GRBody1
|
||||||
|
; GRBody2 = GRBody1
|
||||||
|
).
|
||||||
|
|
||||||
|
user:goal_expansion(phrase(GRBody, S), phrase(GRBody, S, [])).
|
||||||
57
src/lib/debug.pl
Normal file
57
src/lib/debug.pl
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/** Declarative debugging.
|
||||||
|
|
||||||
|
This library provides three predicates with associated operators.
|
||||||
|
The operators can be placed in front of goals to debug Prolog
|
||||||
|
programs.
|
||||||
|
|
||||||
|
Of these predicates, the most frequently used is `(*)/1`, with
|
||||||
|
associated prefix operator `*` (star). Placing `*` in front of a
|
||||||
|
goal means to _generalize away_ the goal. `* Goal` acts as if `Goal`
|
||||||
|
did not appear at all in the source code. It is declaratively
|
||||||
|
equivalent to _commenting out_ the goal, and easier to write,
|
||||||
|
because `*` can also be placed in front of the last goal in a clause
|
||||||
|
without any additional changes.
|
||||||
|
|
||||||
|
Source: [https://stackoverflow.com/a/30791637](https://stackoverflow.com/a/30791637)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:- module(debug, [
|
||||||
|
op(900, fx, $),
|
||||||
|
op(900, fx, $-),
|
||||||
|
op(950, fy, *),
|
||||||
|
(*)/1,
|
||||||
|
($)/1,
|
||||||
|
($-)/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(format), [portray_clause/1]).
|
||||||
|
|
||||||
|
:- meta_predicate *(0).
|
||||||
|
:- meta_predicate $(0).
|
||||||
|
:- meta_predicate $-(0).
|
||||||
|
|
||||||
|
%% $-(Goal)
|
||||||
|
%
|
||||||
|
% Portray exceptions thrown by Goal.
|
||||||
|
|
||||||
|
$-(G_0) :-
|
||||||
|
catch(G_0, Ex, ( portray_clause(exception:Ex:G_0), throw(Ex) ) ).
|
||||||
|
|
||||||
|
%% $(Goal)
|
||||||
|
%
|
||||||
|
% Provide a _trace_ for calls of Goal.
|
||||||
|
|
||||||
|
$(G_0) :-
|
||||||
|
portray_clause(call:G_0),
|
||||||
|
$-G_0,
|
||||||
|
portray_clause(exit:G_0).
|
||||||
|
|
||||||
|
%% *(Goal)
|
||||||
|
%
|
||||||
|
% Generalize away Goal.
|
||||||
|
|
||||||
|
|
||||||
|
*(_).
|
||||||
187
src/lib/diag.pl
Normal file
187
src/lib/diag.pl
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
:- module(diag, [wam_instructions/2, inlined_instructions/2]).
|
||||||
|
|
||||||
|
/** Diagnostics library
|
||||||
|
|
||||||
|
The predicate `wam_instructions/2` _decompiles_ a predicate so that
|
||||||
|
we can inspect its Warren Abstract Machine (WAM) instructions.
|
||||||
|
In this way, we can verify and reason about compiled programs,
|
||||||
|
and detect opportunities for optimization.
|
||||||
|
|
||||||
|
For example, we have:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(lists)).
|
||||||
|
true.
|
||||||
|
?- use_module(library(diag)).
|
||||||
|
true.
|
||||||
|
?- use_module(library(format)).
|
||||||
|
true.
|
||||||
|
?- wam_instructions(append/3, Is),
|
||||||
|
maplist(portray_clause, Is).
|
||||||
|
switch_on_term(1,external(1),external(2),external(6),fail).
|
||||||
|
try_me_else(4).
|
||||||
|
get_constant(level(shallow),[],x(1)).
|
||||||
|
get_value(x(2),3).
|
||||||
|
proceed.
|
||||||
|
trust_me(0).
|
||||||
|
get_list(level(shallow),x(1)).
|
||||||
|
unify_variable(x(4)).
|
||||||
|
unify_variable(x(1)).
|
||||||
|
get_list(level(shallow),x(3)).
|
||||||
|
unify_value(x(4)).
|
||||||
|
unify_variable(x(3)).
|
||||||
|
execute(append,3).
|
||||||
|
Is = [switch_on_term(1,external(1),external(2),external(6),fail)|...].
|
||||||
|
```
|
||||||
|
|
||||||
|
`inlined_instructions/2` decompiles predicates at the code offset in
|
||||||
|
its first argument.
|
||||||
|
|
||||||
|
For example, given the program
|
||||||
|
|
||||||
|
```
|
||||||
|
?- [user].
|
||||||
|
:- use_module(library(clpz)).
|
||||||
|
|
||||||
|
all_eq(Vs, E) :- maplist(#=(E), Vs).
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
we inspect the code of `all_eqs/2` using `wam_instructions/2`,
|
||||||
|
revealing:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- wam_instructions(all_eq/2, Is),
|
||||||
|
maplist(portray_clause, Is).
|
||||||
|
put_structure('$aux',2,x(3)).
|
||||||
|
set_local_value(x(2)).
|
||||||
|
set_void(1).
|
||||||
|
set_constant('$index_ptr'(115334)).
|
||||||
|
get_variable(x(4),1).
|
||||||
|
put_structure(:,2,x(1)).
|
||||||
|
set_constant(user).
|
||||||
|
set_local_value(x(3)).
|
||||||
|
get_variable(x(5),2).
|
||||||
|
put_value(x(4),2).
|
||||||
|
execute(maplist,2).
|
||||||
|
Is = [put_structure('$aux',2,x(3)),set_local_value(x(2)),set_void(1),set_constant('$index_ptr'(115334)),get_variable(x(4),1),put_structure(:,2,x(1)),set_constant(user),set_local_value(x(3)),get_variable(x(5),2),put_value(x(4),2),execute(maplist,2)].
|
||||||
|
```
|
||||||
|
|
||||||
|
The `'$index_ptr(115334)` functor gives a code offset to an inlined
|
||||||
|
predicate compiled for the use of maplist/2. `inlined_instructions/2`
|
||||||
|
can be used to decompile its source code:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- inlined_instructions(115334, Is),
|
||||||
|
maplist(portray_clause, Is).
|
||||||
|
allocate(1).
|
||||||
|
get_level(y(1)).
|
||||||
|
get_variable(x(5),2).
|
||||||
|
put_value(x(3),2).
|
||||||
|
get_variable(x(6),3).
|
||||||
|
put_value(x(5),3).
|
||||||
|
put_unsafe_value(1,4).
|
||||||
|
deallocate.
|
||||||
|
jmp_by_execute(1).
|
||||||
|
try_me_else(8).
|
||||||
|
call(integer,1).
|
||||||
|
neck_cut.
|
||||||
|
get_variable(x(5),1).
|
||||||
|
put_value(x(2),1).
|
||||||
|
get_variable(x(6),2).
|
||||||
|
put_value(x(5),2).
|
||||||
|
jmp_by_execute(7).
|
||||||
|
try_me_else(12).
|
||||||
|
allocate(3).
|
||||||
|
get_level(y(1)).
|
||||||
|
get_variable(y(3),1).
|
||||||
|
get_variable(y(2),2).
|
||||||
|
call_default(true,0).
|
||||||
|
call(var,1).
|
||||||
|
cut(y(1)).
|
||||||
|
put_unsafe_value(3,1).
|
||||||
|
put_unsafe_value(2,2).
|
||||||
|
deallocate.
|
||||||
|
execute_default(is,2).
|
||||||
|
default_retry_me_else(4).
|
||||||
|
call(integer,1).
|
||||||
|
neck_cut.
|
||||||
|
execute(=:=,2).
|
||||||
|
default_trust_me(0).
|
||||||
|
allocate(2).
|
||||||
|
get_variable(y(1),1).
|
||||||
|
get_variable(y(2),3).
|
||||||
|
put_value(y(2),1).
|
||||||
|
call_default(is,2).
|
||||||
|
put_unsafe_value(2,1).
|
||||||
|
put_unsafe_value(1,2).
|
||||||
|
deallocate.
|
||||||
|
execute_default(clpz_equal,2).
|
||||||
|
default_retry_me_else(4).
|
||||||
|
call(integer,1).
|
||||||
|
neck_cut.
|
||||||
|
jmp_by_execute(29).
|
||||||
|
try_me_else(12).
|
||||||
|
allocate(3).
|
||||||
|
get_level(y(1)).
|
||||||
|
get_variable(y(3),1).
|
||||||
|
get_variable(y(2),2).
|
||||||
|
call_default(true,0).
|
||||||
|
call(var,1).
|
||||||
|
cut(y(1)).
|
||||||
|
put_unsafe_value(3,1).
|
||||||
|
put_unsafe_value(2,2).
|
||||||
|
deallocate.
|
||||||
|
execute_default(is,2).
|
||||||
|
default_trust_me(0).
|
||||||
|
allocate(2).
|
||||||
|
get_variable(y(2),1).
|
||||||
|
get_variable(y(1),3).
|
||||||
|
put_value(y(1),1).
|
||||||
|
call_default(is,2).
|
||||||
|
put_unsafe_value(2,1).
|
||||||
|
put_unsafe_value(1,2).
|
||||||
|
deallocate.
|
||||||
|
execute_default(clpz_equal,2).
|
||||||
|
default_trust_me(0).
|
||||||
|
execute_default(clpz_equal,2).
|
||||||
|
Is = [allocate(1),get_level(y(1)),get_variable(x(5),2),put_value(x(3),2),get_variable(x(6),3),put_value(x(5),3),put_unsafe_value(1,4),deallocate,jmp_by_execute(1),try_me_else(8),call(integer,1),neck_cut,get_variable(x(5),1),put_value(x(2),1),get_variable(x(6),2),put_value(x(5),2),jmp_by_execute(7),try_me_else(12),allocate(3),get_level(...),...].
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
%% wam_instructions(+PI, -Instrs)
|
||||||
|
%
|
||||||
|
% _Instrs_ are the WAM instructions corresponding to predicate indicator _PI_.
|
||||||
|
|
||||||
|
wam_instructions(Clause, Listing) :-
|
||||||
|
( nonvar(Clause) ->
|
||||||
|
( Clause = Name / Arity ->
|
||||||
|
fetch_instructions(user, Name, Arity, Listing)
|
||||||
|
; Clause = Module : (Name / Arity) ->
|
||||||
|
fetch_instructions(Module, Name, Arity, Listing)
|
||||||
|
)
|
||||||
|
; throw(error(instantiation_error, wam_instructions/2))
|
||||||
|
).
|
||||||
|
|
||||||
|
%% inlined_instructions(+IndexPtr, -Instrs)
|
||||||
|
%
|
||||||
|
% _Instrs_ are the WAM instructions corresponding to code offset _IndexPtr_.
|
||||||
|
|
||||||
|
inlined_instructions(IndexPtr, Listing) :-
|
||||||
|
must_be(integer, IndexPtr),
|
||||||
|
( IndexPtr >= 0 ->
|
||||||
|
'$inlined_instructions'(IndexPtr, Listing)
|
||||||
|
; throw(error(domain_error(not_less_than_zero, IndexPtr), inlined_instructions/2))
|
||||||
|
).
|
||||||
|
|
||||||
|
fetch_instructions(Module, Name, Arity, Listing) :-
|
||||||
|
must_be(atom, Module),
|
||||||
|
must_be(atom, Name),
|
||||||
|
must_be(integer, Arity),
|
||||||
|
( Arity >= 0 ->
|
||||||
|
'$wam_instructions'(Module, Name, Arity, Listing)
|
||||||
|
; throw(error(domain_error(not_less_than_zero, Arity), wam_instructions/2))
|
||||||
|
).
|
||||||
78
src/lib/dif.pl
Normal file
78
src/lib/dif.pl
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
Provides predicate `dif/2`. `dif/2` is a constraint that is true only if both of its
|
||||||
|
arguments are different terms.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(dif, [dif/2]).
|
||||||
|
|
||||||
|
:- use_module(library(atts)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(lists), [append/3]).
|
||||||
|
|
||||||
|
:- attribute dif/1.
|
||||||
|
|
||||||
|
put_dif_att(Var, X, Y) :-
|
||||||
|
( get_atts(Var, +dif(Z)) ->
|
||||||
|
sort([X \== Y | Z], NewZ),
|
||||||
|
put_atts(Var, +dif(NewZ))
|
||||||
|
; put_atts(Var, +dif([X \== Y]))
|
||||||
|
).
|
||||||
|
|
||||||
|
dif_set_variables([], _, _).
|
||||||
|
dif_set_variables([Var|Vars], X, Y) :-
|
||||||
|
put_dif_att(Var, X, Y),
|
||||||
|
dif_set_variables(Vars, X, Y).
|
||||||
|
|
||||||
|
append_goals([], _).
|
||||||
|
append_goals([Var|Vars], Goals) :-
|
||||||
|
( get_atts(Var, +dif(VarGoals)) ->
|
||||||
|
append(Goals, VarGoals, NewGoals0),
|
||||||
|
sort(NewGoals0, NewGoals)
|
||||||
|
; NewGoals = Goals
|
||||||
|
),
|
||||||
|
put_atts(Var, +dif(NewGoals)),
|
||||||
|
append_goals(Vars, Goals).
|
||||||
|
|
||||||
|
verify_attributes(Var, Value, Goals) :-
|
||||||
|
( get_atts(Var, +dif(Goals)) ->
|
||||||
|
term_variables(Value, ValueVars),
|
||||||
|
append_goals(ValueVars, Goals)
|
||||||
|
; Goals = []
|
||||||
|
).
|
||||||
|
|
||||||
|
%% dif(?X, ?Y).
|
||||||
|
%
|
||||||
|
% True iff X and Y are different terms. Unlike `\=/2`, `dif/2` is more declarative because if X and Y can
|
||||||
|
% unify but they're not yet equal, the decision is delayed, and prevents X and Y to become equal later.
|
||||||
|
% Examples:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- dif(a, a).
|
||||||
|
% false.
|
||||||
|
% ?- dif(a, b).
|
||||||
|
% true.
|
||||||
|
% ?- dif(X, b).
|
||||||
|
% dif:dif(X,b).
|
||||||
|
% ?- dif(X, b), X = b.
|
||||||
|
% false.
|
||||||
|
% ```
|
||||||
|
dif(X, Y) :-
|
||||||
|
X \== Y,
|
||||||
|
( X \= Y -> true
|
||||||
|
; term_variables(dif(X,Y), Vars),
|
||||||
|
dif_set_variables(Vars, X, Y)
|
||||||
|
).
|
||||||
|
|
||||||
|
gather_dif_goals(_, []) --> [].
|
||||||
|
gather_dif_goals(V, [(X \== Y) | Goals]) -->
|
||||||
|
( { term_variables(X-Y, [V0 | _]),
|
||||||
|
V == V0 } ->
|
||||||
|
[dif:dif(X, Y)]
|
||||||
|
; []
|
||||||
|
),
|
||||||
|
gather_dif_goals(V, Goals).
|
||||||
|
|
||||||
|
attribute_goals(X) -->
|
||||||
|
{ get_atts(X, +dif(Goals)) },
|
||||||
|
gather_dif_goals(X, Goals),
|
||||||
|
{ put_atts(X, -dif(_)) }.
|
||||||
224
src/lib/error.pl
Normal file
224
src/lib/error.pl
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written 2018-2023 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/1,
|
||||||
|
domain_error/3,
|
||||||
|
type_error/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
|
||||||
|
:- 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) :-
|
||||||
|
must_be_(type, Type),
|
||||||
|
must_be_(Type, Term).
|
||||||
|
|
||||||
|
must_be_(Type, _) :-
|
||||||
|
var(Type),
|
||||||
|
instantiation_error(must_be/2).
|
||||||
|
must_be_(var, Term) :-
|
||||||
|
( var(Term) -> true
|
||||||
|
; throw(error(uninstantiation_error(Term), must_be/2))
|
||||||
|
).
|
||||||
|
must_be_(integer, Term) :- check_(integer, integer, Term).
|
||||||
|
must_be_(not_less_than_zero, N) :-
|
||||||
|
must_be(integer, N),
|
||||||
|
( N >= 0 -> true
|
||||||
|
; domain_error(not_less_than_zero, N, must_be/2)
|
||||||
|
).
|
||||||
|
must_be_(atom, Term) :- check_(atom, atom, Term).
|
||||||
|
must_be_(character, T) :- check_(error:character, character, T).
|
||||||
|
must_be_(in_character, T) :- check_(error:in_character, in_character, T).
|
||||||
|
must_be_(chars, Ls) :-
|
||||||
|
can_be(chars, Ls), % prioritize type errors over instantiation errors
|
||||||
|
must_be(list, Ls),
|
||||||
|
( '$is_partial_string'(Ls) ->
|
||||||
|
% The expected case (success) uses a very fast test.
|
||||||
|
% We cannot use partial_string/1 from library(iso_ext),
|
||||||
|
% because that library itself imports library(error).
|
||||||
|
true
|
||||||
|
; all_characters(Ls)
|
||||||
|
).
|
||||||
|
must_be_(octet_character, C) :-
|
||||||
|
must_be(character, C),
|
||||||
|
( octet_character(C) -> true
|
||||||
|
; domain_error(octet_character, C, must_be/2)
|
||||||
|
).
|
||||||
|
must_be_(octet_chars, Cs) :-
|
||||||
|
must_be(chars, Cs),
|
||||||
|
( '$first_non_octet'(Cs, C) ->
|
||||||
|
domain_error(octet_character, C, must_be/2)
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
must_be_(list, Term) :- check_(error:ilist, list, Term).
|
||||||
|
must_be_(type, Term) :- check_(error:type, type, Term).
|
||||||
|
must_be_(boolean, Term) :- check_(error:boolean, boolean, Term).
|
||||||
|
must_be_(term, Term) :-
|
||||||
|
( acyclic_term(Term) ->
|
||||||
|
( ground(Term) -> true
|
||||||
|
; instantiation_error(must_be/2)
|
||||||
|
)
|
||||||
|
; type_error(term, Term, must_be/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
% We cannot use maplist(must_be(character), Cs), because library(lists)
|
||||||
|
% uses library(error), so importing it would create a cyclic dependency.
|
||||||
|
|
||||||
|
all_characters([]).
|
||||||
|
all_characters([C|Cs]) :-
|
||||||
|
must_be(character, C),
|
||||||
|
all_characters(Cs).
|
||||||
|
|
||||||
|
check_(Pred, Type, Term) :-
|
||||||
|
( var(Term) -> instantiation_error(must_be/2)
|
||||||
|
; call(Pred, Term) -> true
|
||||||
|
; type_error(Type, Term, must_be/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
boolean(B) :- ( B == true ; B == false ).
|
||||||
|
|
||||||
|
character(C) :-
|
||||||
|
atom(C),
|
||||||
|
atom_length(C, 1).
|
||||||
|
|
||||||
|
octet_character(C) :-
|
||||||
|
char_code(C, Code),
|
||||||
|
0 =< Code, Code =< 0xff.
|
||||||
|
|
||||||
|
in_character(C) :-
|
||||||
|
( character(C)
|
||||||
|
; C == end_of_file
|
||||||
|
).
|
||||||
|
|
||||||
|
ilist(Ls) :-
|
||||||
|
'$skip_max_list'(_, _, Ls, Rs),
|
||||||
|
( var(Rs) ->
|
||||||
|
instantiation_error(must_be/2)
|
||||||
|
; Rs == []
|
||||||
|
).
|
||||||
|
|
||||||
|
type(type).
|
||||||
|
type(integer).
|
||||||
|
type(atom).
|
||||||
|
type(character).
|
||||||
|
type(in_character).
|
||||||
|
type(octet_character).
|
||||||
|
type(octet_chars).
|
||||||
|
type(chars).
|
||||||
|
type(list).
|
||||||
|
type(var).
|
||||||
|
type(boolean).
|
||||||
|
type(term).
|
||||||
|
type(not_less_than_zero).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
can_be(Type, Term)
|
||||||
|
|
||||||
|
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),
|
||||||
|
( var(Term) -> true
|
||||||
|
; can_(Type, Term) -> true
|
||||||
|
; type_error(Type, Term, can_be/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
can_(integer, Term) :- integer(Term).
|
||||||
|
can_(not_less_than_zero, N) :-
|
||||||
|
( integer(N) ->
|
||||||
|
( N >= 0 -> true
|
||||||
|
; domain_error(not_less_than_zero, N, can_be/2)
|
||||||
|
)
|
||||||
|
; type_error(integer, N, can_be/2)
|
||||||
|
).
|
||||||
|
can_(atom, Term) :- atom(Term).
|
||||||
|
can_(character, T) :- character(T).
|
||||||
|
can_(in_character, T) :- in_character(T).
|
||||||
|
can_(chars, Ls) :-
|
||||||
|
( '$is_partial_string'(Ls) -> true
|
||||||
|
; can_be(list, Ls),
|
||||||
|
can_be_chars(Ls)
|
||||||
|
).
|
||||||
|
can_(octet_character, C) :-
|
||||||
|
( octet_character(C) -> true
|
||||||
|
; domain_error(octet_character, C, can_be/2)
|
||||||
|
).
|
||||||
|
can_(octet_chars, Cs) :-
|
||||||
|
can_be(chars, Cs),
|
||||||
|
( '$skip_max_list'(_, _, Cs, []), % temporarily turn Cs into a list
|
||||||
|
'$first_non_octet'(Cs, C) ->
|
||||||
|
domain_error(octet_character, C, can_be/2)
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
can_(list, Term) :- list_or_partial_list(Term).
|
||||||
|
can_(boolean, Term) :- boolean(Term).
|
||||||
|
can_(term, Term) :-
|
||||||
|
( acyclic_term(Term) ->
|
||||||
|
true
|
||||||
|
; type_error(term, Term, can_be/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
can_be_chars(Var) :- var(Var), !.
|
||||||
|
can_be_chars([]).
|
||||||
|
can_be_chars([X|Xs]) :-
|
||||||
|
can_be(character, X),
|
||||||
|
can_be_chars(Xs).
|
||||||
|
|
||||||
|
list_or_partial_list(Ls) :-
|
||||||
|
'$skip_max_list'(_, _, Ls, Rs),
|
||||||
|
( var(Rs) -> true
|
||||||
|
; Rs == []
|
||||||
|
).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Shorthands for throwing ISO errors.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
instantiation_error(Context) :-
|
||||||
|
throw(error(instantiation_error, Context)).
|
||||||
|
|
||||||
|
domain_error(Type, Term, Context) :-
|
||||||
|
throw(error(domain_error(Type, Term), Context)).
|
||||||
|
|
||||||
|
type_error(Type, Term, Context) :-
|
||||||
|
throw(error(type_error(Type, Term), Context)).
|
||||||
104
src/lib/ffi.pl
Normal file
104
src/lib/ffi.pl
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
:- module(ffi, [use_foreign_module/2, foreign_struct/2]).
|
||||||
|
|
||||||
|
/** Foreign Function Interface
|
||||||
|
|
||||||
|
This module contains predicates used to call native code (exposed by the C ABI).
|
||||||
|
It uses [libffi](https://sourceware.org/libffi/) under the hood. The bridge is very simple
|
||||||
|
and is very unsafe and should be used with care. FFI isn't the only way to communicate with
|
||||||
|
the outside world in Prolog: sockets, pipes and HTTP may be good enough for your use case.
|
||||||
|
|
||||||
|
The main predicate is `use_foreign_module/2`. It takes a library name (which depending on the
|
||||||
|
operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each
|
||||||
|
function is defined by its name, a list of the type of the arguments, and the return argument.
|
||||||
|
|
||||||
|
Types available are: `sint8`, `uint8`, `sint16`, `uint16`, `sint32`, `uint32`, `sint64`,
|
||||||
|
`uint64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
|
||||||
|
with `foreign_struct/2`.
|
||||||
|
|
||||||
|
After that, each function on the lists maps to a predicate created in the ffi module which
|
||||||
|
are used to call the native code.
|
||||||
|
The predicate takes the functor name after the function name. Then, the arguments are the input
|
||||||
|
arguments followed by a return argument. However, functions with return type `void` or `bool`
|
||||||
|
don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend
|
||||||
|
on the return value on the native side.
|
||||||
|
|
||||||
|
```
|
||||||
|
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
|
||||||
|
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_foreign_module("./libraylib.so", ['InitWindow'([sint32, sint32, cstr], void)]).
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- ffi:'InitWindow'(800, 600, "Scryer Prolog + Raylib").
|
||||||
|
```
|
||||||
|
|
||||||
|
And a new window should pop up!
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
%% foreign_struct(+Name, +Elements).
|
||||||
|
%
|
||||||
|
% Defines a new struct type with name Name, composed of the elements Elements, which is a list
|
||||||
|
% of other types.
|
||||||
|
%
|
||||||
|
% The name of the types doesn't matter, but the order of Elements must match the ones in the
|
||||||
|
% native code.
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- foreign_struct(color, [uint8, uint8, uint8, uint8]).
|
||||||
|
% ```
|
||||||
|
foreign_struct(Name, Elements) :-
|
||||||
|
'$define_foreign_struct'(Name, Elements).
|
||||||
|
|
||||||
|
use_foreign_module(LibName, Predicates) :-
|
||||||
|
'$load_foreign_lib'(LibName, Predicates),
|
||||||
|
maplist(assert_predicate, Predicates).
|
||||||
|
|
||||||
|
assert_predicate(PredicateDefinition) :-
|
||||||
|
PredicateDefinition =.. [Name, Inputs, void],
|
||||||
|
length(Inputs, NumInputs),
|
||||||
|
functor(Head, Name, NumInputs),
|
||||||
|
term_variables(Head, TermList),
|
||||||
|
Body = (
|
||||||
|
'$foreign_call'(Name, TermList, _),!
|
||||||
|
),
|
||||||
|
Predicate = (Head:-Body),
|
||||||
|
assertz(ffi:Predicate).
|
||||||
|
|
||||||
|
assert_predicate(PredicateDefinition) :-
|
||||||
|
PredicateDefinition =.. [Name, Inputs, bool],
|
||||||
|
length(Inputs, NumInputs),
|
||||||
|
functor(Head, Name, NumInputs),
|
||||||
|
term_variables(Head, TermList),
|
||||||
|
Body = (
|
||||||
|
'$foreign_call'(Name, TermList, 1),!
|
||||||
|
),
|
||||||
|
Predicate = (Head:-Body),
|
||||||
|
assertz(ffi:Predicate).
|
||||||
|
|
||||||
|
assert_predicate(PredicateDefinition) :-
|
||||||
|
PredicateDefinition =.. [Name, Inputs, Return],
|
||||||
|
\+ member(Return, [void, bool]),
|
||||||
|
length(Inputs, NumInputs),
|
||||||
|
NumArgs is NumInputs + 1,
|
||||||
|
functor(Head, Name, NumArgs),
|
||||||
|
term_variables(Head, TermList),
|
||||||
|
Body = (
|
||||||
|
lists:append(TermListInputs, [TermListReturn], TermList),
|
||||||
|
'$foreign_call'(Name, TermListInputs, TermListReturn),!
|
||||||
|
),
|
||||||
|
Predicate = (Head:-Body),
|
||||||
|
assertz(ffi:Predicate).
|
||||||
299
src/lib/files.pl
Normal file
299
src/lib/files.pl
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
/** Predicates for reasoning about files and directories.
|
||||||
|
|
||||||
|
In this library, directories and files are represented as
|
||||||
|
_lists of characters_. This is an ideal representation:
|
||||||
|
|
||||||
|
* Lists of characters can be conveniently reasoned about with DCGs
|
||||||
|
and built-in Prolog predicates from `library(lists)`. This alone
|
||||||
|
is already a very compelling argument to use them.
|
||||||
|
* Other Scryer libraries such as `library(http/http_open)` also already
|
||||||
|
use lists of characters to represent paths.
|
||||||
|
* File names are mostly ephemeral, so it is good for efficiency
|
||||||
|
that they can quickly allocated transiently on the heap, leaving the
|
||||||
|
atom table mostly unaffected. Indexing is almost never needed
|
||||||
|
for file names. If needed, it should be added to the engine.
|
||||||
|
* The previous point is also good for security, since the system
|
||||||
|
leaves little trace of which files were even accessed.
|
||||||
|
* Scryer Prolog represents lists of characters extremely compactly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written 2020, 2022 by Markus Triska (triska@metalevel.at)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
|
||||||
|
Predicates for reasoning about files and directories.
|
||||||
|
|
||||||
|
In this library, directories and files are represented as
|
||||||
|
*lists of characters*. This is an ideal representation:
|
||||||
|
|
||||||
|
-) Lists of characters can be conveniently reasoned about with DCGs
|
||||||
|
and built-in Prolog predicates from library(lists). This alone
|
||||||
|
is already a very compelling argument to use them.
|
||||||
|
-) Other Scryer libraries such as library(http/http_open) also already
|
||||||
|
use lists of characters to represent paths.
|
||||||
|
-) File names are mostly ephemeral, so it is good for efficiency
|
||||||
|
that they can quickly allocated transiently on the heap, leaving the
|
||||||
|
atom table mostly unaffected. Indexing is almost never needed
|
||||||
|
for file names. If needed, it should be added to the engine.
|
||||||
|
-) The previous point is also good for security, since the system
|
||||||
|
leaves little trace of which files were even accessed.
|
||||||
|
-) Scryer Prolog represents lists of characters extremely compactly.
|
||||||
|
|
||||||
|
Some Prolog programmers will likely find this representation quite
|
||||||
|
unusual, because files are represented as *atoms* in many systems.
|
||||||
|
|
||||||
|
However, the ISO standard only demands that sources and sinks be
|
||||||
|
*ground* terms, so lists of characters are completely admissible:
|
||||||
|
|
||||||
|
A source/sink is specified as an implementation defined
|
||||||
|
ground term in a call of open/4 (8.11.5). All subsequent
|
||||||
|
references to the source/sink are made by referring to a
|
||||||
|
stream-term (7.10.2) or alias (7.10.2.2).
|
||||||
|
|
||||||
|
I believe that with the advent of Scryer Prolog and its efficient
|
||||||
|
representation of strings as lists of characters, we should take
|
||||||
|
this opportunity for improvement, and in fact extend the use of
|
||||||
|
lists of characters also to other predicates like open/3.
|
||||||
|
|
||||||
|
Please note that we *cannot* simply accept *both* representations,
|
||||||
|
because that would invalidate the type errors raised by this library,
|
||||||
|
and make future extensions for type checking impossible.
|
||||||
|
|
||||||
|
So I ask you: Please try out this representation for a few months.
|
||||||
|
It simplifies working with files considerably. Once we have collected
|
||||||
|
more experience with this representations, please let us consider
|
||||||
|
how to proceed in such a way that we can call it an improvement.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
:- module(files, [directory_files/2,
|
||||||
|
file_size/2,
|
||||||
|
file_exists/1,
|
||||||
|
directory_exists/1,
|
||||||
|
delete_file/1,
|
||||||
|
rename_file/2,
|
||||||
|
file_copy/2,
|
||||||
|
delete_directory/1,
|
||||||
|
make_directory/1,
|
||||||
|
make_directory_path/1,
|
||||||
|
working_directory/2,
|
||||||
|
path_canonical/2,
|
||||||
|
path_segments/2,
|
||||||
|
file_modification_time/2,
|
||||||
|
file_creation_time/2,
|
||||||
|
file_access_time/2]).
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
|
||||||
|
%% directory_files(+Directory, -Files).
|
||||||
|
%
|
||||||
|
% Returns the list of 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).
|
||||||
|
%
|
||||||
|
% Returns the size (in bytes) of a file. The file must exist.
|
||||||
|
|
||||||
|
file_size(File, Size) :-
|
||||||
|
file_must_exist(File, file_size/2),
|
||||||
|
can_be(integer, Size),
|
||||||
|
'$file_size'(File, Size).
|
||||||
|
|
||||||
|
%% file_exists(+File).
|
||||||
|
%
|
||||||
|
% Succeeds if 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.
|
||||||
|
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.
|
||||||
|
% If you want to create a nested directory, use `make_directory_path/1`.
|
||||||
|
make_directory(Directory) :-
|
||||||
|
must_be(chars, Directory),
|
||||||
|
'$make_directory'(Directory).
|
||||||
|
|
||||||
|
%% make_directory_path(+Directory).
|
||||||
|
%
|
||||||
|
% Similar to `make_directory/1` but recursively creates directories if they're missing.
|
||||||
|
% Equivalent to mkdir -p in Unix.
|
||||||
|
make_directory_path(Directory) :-
|
||||||
|
must_be(chars, Directory),
|
||||||
|
'$make_directory_path'(Directory).
|
||||||
|
|
||||||
|
%% delete_file(+File).
|
||||||
|
%
|
||||||
|
% Succeeds if deletes File from the current system.
|
||||||
|
delete_file(File) :-
|
||||||
|
file_must_exist(File, delete_file/1),
|
||||||
|
'$delete_file'(File).
|
||||||
|
|
||||||
|
%% rename_file(+File, +Renamed).
|
||||||
|
%
|
||||||
|
% Succeeds if File is renamed to Renamed
|
||||||
|
rename_file(File, Renamed) :-
|
||||||
|
file_must_exist(File, rename_file/2),
|
||||||
|
must_be(chars, Renamed),
|
||||||
|
'$rename_file'(File, Renamed).
|
||||||
|
|
||||||
|
%% file_copy(+File, +Copied).
|
||||||
|
%
|
||||||
|
% Succeeds if File is copied to Copied
|
||||||
|
file_copy(File, Copied) :-
|
||||||
|
file_must_exist(File, file_copy/2),
|
||||||
|
must_be(chars, Copied),
|
||||||
|
'$file_copy'(File, Copied).
|
||||||
|
|
||||||
|
%% delete_directory(+Directory).
|
||||||
|
%
|
||||||
|
% Succeeds if Directory is deleted from the current system.
|
||||||
|
% Directory must be empty.
|
||||||
|
delete_directory(Directory) :-
|
||||||
|
directory_must_exist(Directory, delete_directory/1),
|
||||||
|
must_be(chars, Directory),
|
||||||
|
'$delete_directory'(Directory).
|
||||||
|
|
||||||
|
file_must_exist(File, Context) :-
|
||||||
|
( file_exists(File) -> true
|
||||||
|
; throw(error(existence_error(file, File), Context))
|
||||||
|
).
|
||||||
|
|
||||||
|
directory_must_exist(Directory, Context) :-
|
||||||
|
( directory_exists(Directory) -> true
|
||||||
|
; throw(error(existence_error(directory, Directory), Context))
|
||||||
|
).
|
||||||
|
|
||||||
|
%% workind_directory(Dir0, Dir).
|
||||||
|
%
|
||||||
|
% Dir0 is the current working directory, and the working directory
|
||||||
|
% is changed to Dir.
|
||||||
|
%
|
||||||
|
% Use `working_directory/2` to determine the current working directory,
|
||||||
|
% and leave it as is.
|
||||||
|
|
||||||
|
working_directory(Dir0, Dir) :-
|
||||||
|
can_be(list, Dir0),
|
||||||
|
can_be(list, Dir),
|
||||||
|
'$working_directory'(Dir0, Dir).
|
||||||
|
|
||||||
|
%% path_canonical(Ps, Cs).
|
||||||
|
%
|
||||||
|
% True iff Cs is the canonical, absolute path of Ps.
|
||||||
|
%
|
||||||
|
% All intermediate components are normalized, and all symbolic links
|
||||||
|
% are resolved.
|
||||||
|
%
|
||||||
|
% The predicate fails in the following situations, though not
|
||||||
|
% necessarily *only* in these cases:
|
||||||
|
%
|
||||||
|
% 1. Ps is a path that does not exist.
|
||||||
|
% 2. A non-final component in Ps is not a directory.
|
||||||
|
|
||||||
|
path_canonical(Ps, Cs) :-
|
||||||
|
must_be(chars, Ps),
|
||||||
|
can_be(list, Cs),
|
||||||
|
'$path_canonical'(Ps, Cs).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
T is, respectively, the modification, access or creation time of File.
|
||||||
|
T is a time stamp, suitable for use in format_time//2 in library(time).
|
||||||
|
|
||||||
|
For two time stamps A and B, if A precedes B, then A @< B holds.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
%% file_modification_time(+File, -T).
|
||||||
|
%
|
||||||
|
% For a file File that must exist, it returns a time stamp T with the modification time
|
||||||
|
%
|
||||||
|
% T is a time stamp compatible with `library(time)`.
|
||||||
|
file_modification_time(File, T) :-
|
||||||
|
file_time_(File, modification, T).
|
||||||
|
|
||||||
|
%% file_access_time(+File, -T).
|
||||||
|
%
|
||||||
|
% For a file File that must exist, it returns a time stamp T with the access time
|
||||||
|
%
|
||||||
|
% T is a time stamp compatible with `library(time)`.
|
||||||
|
file_access_time(File, T) :-
|
||||||
|
file_time_(File, access, T).
|
||||||
|
|
||||||
|
%% file_creation_time(+File, -T).
|
||||||
|
%
|
||||||
|
% For a file File that must exist, it returns a time stamp T with the creation time
|
||||||
|
%
|
||||||
|
% T is a time stamp compatible with `library(time)`.
|
||||||
|
file_creation_time(File, T) :-
|
||||||
|
file_time_(File, creation, T).
|
||||||
|
|
||||||
|
file_time_(File, Which, T) :-
|
||||||
|
file_must_exist(File, file_time_/3),
|
||||||
|
'$file_time'(File, Which, T0),
|
||||||
|
read_from_chars(T0, T).
|
||||||
|
|
||||||
|
|
||||||
|
%% path_segments(Ps, Segments).
|
||||||
|
%
|
||||||
|
% True iff Segments are the segments of Ps.
|
||||||
|
%
|
||||||
|
% Segments is the list of components of the path Ps that are
|
||||||
|
% separated by the platform-specific directory separator. Each
|
||||||
|
% segment is a list of characters.
|
||||||
|
%
|
||||||
|
% At least one of the arguments must be instantiated.
|
||||||
|
%
|
||||||
|
% Examples:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- path_segments("/hello/there", Segments).
|
||||||
|
% Segments = [[],"hello","there"].
|
||||||
|
% ?- path_segments(Path, ["hello","there"]).
|
||||||
|
% Path = "hello/there".
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% To obtain the platform-specific directory separator, you can use:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- path_segments(Separator, ["",""]).
|
||||||
|
% Separator = "/".
|
||||||
|
% ```
|
||||||
|
|
||||||
|
path_segments(Path, Segments) :-
|
||||||
|
'$directory_separator'(Sep),
|
||||||
|
( var(Path) ->
|
||||||
|
must_be(list, Segments),
|
||||||
|
maplist(must_be(chars), Segments),
|
||||||
|
phrase(append_with_separator(Segments, Sep), Path)
|
||||||
|
; must_be(chars, Path),
|
||||||
|
path_to_segments(Path, Sep, Segments)
|
||||||
|
).
|
||||||
|
|
||||||
|
append_with_separator([], _) --> [].
|
||||||
|
append_with_separator([Segment|Segments], Sep) -->
|
||||||
|
append_with_separator_(Segments, Segment, Sep).
|
||||||
|
|
||||||
|
append_with_separator_([], Segment, _) --> seq(Segment).
|
||||||
|
append_with_separator_([Segment|Segments], Prev, Sep) -->
|
||||||
|
seq(Prev), [Sep],
|
||||||
|
append_with_separator_(Segments, Segment, Sep).
|
||||||
|
|
||||||
|
path_to_segments(Path, Sep, Segments) :-
|
||||||
|
( append(Front, [Sep|Ps], Path) ->
|
||||||
|
Segments = [Front|Rest],
|
||||||
|
path_to_segments(Ps, Sep, Rest)
|
||||||
|
; Segments = [Path]
|
||||||
|
).
|
||||||
650
src/lib/format.pl
Normal file
650
src/lib/format.pl
Normal file
@@ -0,0 +1,650 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written 2020-2023 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.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
/** This library provides the nonterminal `format_//2` to describe
|
||||||
|
formatted strings. `format/[2,3]` are provided for _impure_ output.
|
||||||
|
|
||||||
|
The entire library only works if the Prolog flag `double_quotes`
|
||||||
|
is set to `chars`, the default value in Scryer Prolog. This should
|
||||||
|
also stay that way, to encourage a sensible environment.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(format, [format_//2,
|
||||||
|
format/2,
|
||||||
|
format/3,
|
||||||
|
portray_clause_//1,
|
||||||
|
portray_clause/1,
|
||||||
|
portray_clause/2,
|
||||||
|
listing/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(between)).
|
||||||
|
:- use_module(library(pio)).
|
||||||
|
|
||||||
|
%% format_(+FormatString, +Arguments)//
|
||||||
|
%
|
||||||
|
% Usage:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% phrase(format_(FormatString, Arguments), Ls)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% `format_//2` describes a list of characters Ls that are formatted
|
||||||
|
% according to FormatString. FormatString is a string (i.e., a list of
|
||||||
|
% characters) that specifies the layout of Ls. The characters in
|
||||||
|
% FormatString are used literally, except for the following tokens
|
||||||
|
% with special meaning:
|
||||||
|
%
|
||||||
|
% | `~w` | use the next available argument from Arguments here |
|
||||||
|
% | `~q` | use the next argument here, formatted as by `writeq/1` |
|
||||||
|
% | `~a` | use the next argument here, which must be an atom |
|
||||||
|
% | `~s` | use the next argument here, which must be a string |
|
||||||
|
% | `~d` | use the next argument here, which must be an integer |
|
||||||
|
% | `~f` | use the next argument here, a floating point number |
|
||||||
|
% | `~Nf` | where N is an integer: format the float argument |
|
||||||
|
% | | using N digits after the decimal point |
|
||||||
|
% | `~Nd` | like ~d, placing the last N digits after a decimal point; |
|
||||||
|
% | | if N is 0 or omitted, no decimal point is used. |
|
||||||
|
% | `~ND` | like ~Nd, separating digits to the left of the decimal point |
|
||||||
|
% | | in groups of three, using the character "," (comma) |
|
||||||
|
% | `~NU` | like ~ND, using "_" (underscore) to separate groups of digits |
|
||||||
|
% | `~NL` | format an integer so that at most N digits appear on a line. |
|
||||||
|
% | | If N is 0 or omitted, it defaults to 72. |
|
||||||
|
% | `~Nr` | where N is an integer between 2 and 36: format the |
|
||||||
|
% | | next argument, which must be an integer, in radix N. |
|
||||||
|
% | | The characters "a" to "z" are used for radices 10 to 36. |
|
||||||
|
% | | If N is omitted, it defaults to 8 (octal). |
|
||||||
|
% | `~NR` | like ~Nr, except that "A" to "Z" are used for radices > 9 |
|
||||||
|
% | `~|` | place a tab stop at this position |
|
||||||
|
% | `~N|` | where N is an integer: place a tab stop at text column N |
|
||||||
|
% | `~N+` | where N is an integer: place a tab stop N characters |
|
||||||
|
% | | after the previous tab stop (or start of line) |
|
||||||
|
% | `~t` | distribute spaces evenly between the two closest tab stops |
|
||||||
|
% | ``~`Ct`` | like ~t, use character C instead of spaces to fill the space |
|
||||||
|
% | `~n` | newline |
|
||||||
|
% | `~Nn` | N newlines |
|
||||||
|
% | `~i` | ignore the next argument |
|
||||||
|
% | `~~` | the literal ~ |
|
||||||
|
%
|
||||||
|
% Instead of `~N`, you can write `~*` to use the next argument from
|
||||||
|
% Arguments as the numeric argument.
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs).
|
||||||
|
% Cs = "hello\n......there!".
|
||||||
|
% ```
|
||||||
|
|
||||||
|
format_(Fs, Args) -->
|
||||||
|
{ must_be(list, Fs),
|
||||||
|
must_be(list, Args),
|
||||||
|
unique_variable_names(Args, VNs),
|
||||||
|
phrase(cells(Fs,Args,0,[],VNs), Cells) },
|
||||||
|
format_cells(Cells).
|
||||||
|
|
||||||
|
format_cells([]) --> [].
|
||||||
|
format_cells([Cell|Cells]) -->
|
||||||
|
format_cell(Cell),
|
||||||
|
format_cells(Cells).
|
||||||
|
|
||||||
|
format_cell(newline) --> "\n".
|
||||||
|
format_cell(cell(From,To,Es)) -->
|
||||||
|
% distribute the space between the glue elements
|
||||||
|
{ phrase(elements_gluevars(Es, 0, Length), Vs),
|
||||||
|
( Vs = [] -> true
|
||||||
|
; Space is To - From - Length,
|
||||||
|
( Space =< 0 -> maplist(=(0), Vs)
|
||||||
|
; length(Vs, NumGlue),
|
||||||
|
Distr is Space // NumGlue,
|
||||||
|
Delta is Space - Distr*NumGlue,
|
||||||
|
( Delta =:= 0 ->
|
||||||
|
maplist(=(Distr), Vs)
|
||||||
|
; BigGlue is Distr + Delta,
|
||||||
|
reverse(Vs, [BigGlue|Rest]),
|
||||||
|
maplist(=(Distr), Rest)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) },
|
||||||
|
format_elements(Es).
|
||||||
|
|
||||||
|
format_elements([]) --> [].
|
||||||
|
format_elements([E|Es]) -->
|
||||||
|
format_element(E),
|
||||||
|
format_elements(Es).
|
||||||
|
|
||||||
|
format_element(chars(Cs)) --> seq(Cs).
|
||||||
|
format_element(glue(Fill,Num)) -->
|
||||||
|
{ length(Ls, Num),
|
||||||
|
maplist(=(Fill), Ls) },
|
||||||
|
seq(Ls).
|
||||||
|
|
||||||
|
elements_gluevars([], N, N) --> [].
|
||||||
|
elements_gluevars([E|Es], N0, N) -->
|
||||||
|
element_gluevar(E, N0, N1),
|
||||||
|
elements_gluevars(Es, N1, N).
|
||||||
|
|
||||||
|
element_gluevar(chars(Cs), N0, N) -->
|
||||||
|
{ length(Cs, L),
|
||||||
|
N is N0 + L }.
|
||||||
|
element_gluevar(glue(_,V), N, N) --> [V].
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Our key datastructure is a list of cells and newlines.
|
||||||
|
A cell has the shape cell(From,To,Elements), where
|
||||||
|
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).
|
||||||
|
"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.
|
||||||
|
|
||||||
|
newline is used if ~n occurs in a format string.
|
||||||
|
It is used because a newline character does not
|
||||||
|
consume whitespace in the sense of format strings.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
cells([], Args, Tab, Es, _) --> !,
|
||||||
|
( { Args == [] } -> cell(Tab, Tab, Es)
|
||||||
|
; { domain_error(empty_list, Args, format_//2) }
|
||||||
|
).
|
||||||
|
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).
|
||||||
|
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).
|
||||||
|
cells([~,a|Fs], [Arg|Args], Tab, Es, VNs) --> !,
|
||||||
|
{ atom_chars(Arg, Chars) },
|
||||||
|
cells(Fs, Args, Tab, [chars(Chars)|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).
|
||||||
|
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).
|
||||||
|
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).
|
||||||
|
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).
|
||||||
|
cells([~,i|Fs], [_|Args], Tab, Es, VNs) --> !,
|
||||||
|
cells(Fs, Args, Tab, Es, VNs).
|
||||||
|
cells([~,n|Fs], Args, Tab, Es, VNs) --> !,
|
||||||
|
cell(Tab, Tab, Es),
|
||||||
|
n_newlines(1),
|
||||||
|
cells(Fs, Args, 0, [], VNs).
|
||||||
|
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||||
|
{ numeric_argument(Fs0, Num, [n|Fs], Args0, Args) },
|
||||||
|
!,
|
||||||
|
cell(Tab, Tab, Es),
|
||||||
|
n_newlines(Num),
|
||||||
|
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([~|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).
|
||||||
|
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).
|
||||||
|
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).
|
||||||
|
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),
|
||||||
|
cells(Fs, Args, Tab, [], VNs).
|
||||||
|
cells([~|Fs0], Args0, Tab, Es, VNs) -->
|
||||||
|
{ numeric_argument(Fs0, Num, ['|'|Fs], Args0, Args) },
|
||||||
|
!,
|
||||||
|
cell(Tab, Num, Es),
|
||||||
|
cells(Fs, Args, Num, [], VNs).
|
||||||
|
cells([~|Fs0], Args0, Tab0, Es, VNs) -->
|
||||||
|
{ numeric_argument(Fs0, Num, [+|Fs], Args0, Args) },
|
||||||
|
!,
|
||||||
|
{ Tab is Tab0 + Num },
|
||||||
|
cell(Tab0, Tab, Es),
|
||||||
|
cells(Fs, Args, Tab, [], VNs).
|
||||||
|
cells([~|Cs], Args, _, _, _) -->
|
||||||
|
( { Args == [] } ->
|
||||||
|
{ domain_error(non_empty_list, [], format_//2) }
|
||||||
|
; { domain_error(format_string, [~|Cs], format_//2) }
|
||||||
|
).
|
||||||
|
cells(Fs0, Args, Tab, Es, VNs) -->
|
||||||
|
{ phrase(upto_what(Fs1, ~), Fs0, Fs),
|
||||||
|
Fs1 = [_|_] },
|
||||||
|
cells(Fs, Args, Tab, [chars(Fs1)|Es], VNs).
|
||||||
|
|
||||||
|
format_number_chars(N0, Chars) :-
|
||||||
|
N is N0, % evaluate compound expression
|
||||||
|
number_chars(N, Chars).
|
||||||
|
|
||||||
|
n_newlines(0) --> !.
|
||||||
|
n_newlines(N0) --> { N0 > 0, N is N0 - 1 }, [newline], n_newlines(N).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
?- 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 = [].
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
separate_digits_fractional(Arg, Sep, Num, Cs) :-
|
||||||
|
number_chars(Num, NCs),
|
||||||
|
phrase(("~",seq(NCs),"d"), FStr),
|
||||||
|
phrase(format_(FStr, [Arg]), Cs0),
|
||||||
|
phrase(upto_what(Bs0, .), Cs0, Ds),
|
||||||
|
reverse(Bs0, Bs1),
|
||||||
|
phrase(groups_of_three(Bs1,Sep), Bs2),
|
||||||
|
reverse(Bs2, Bs),
|
||||||
|
append(Bs, Ds, Cs).
|
||||||
|
|
||||||
|
upto_what([], W), [W] --> [W], !.
|
||||||
|
upto_what([C|Cs], W) --> [C], !, upto_what(Cs, W).
|
||||||
|
upto_what([], _) --> [].
|
||||||
|
|
||||||
|
groups_of_three([A,B,C,D|Rs], Sep) --> !, [A,B,C,Sep], groups_of_three([D|Rs], Sep).
|
||||||
|
groups_of_three(Ls, _) --> seq(Ls).
|
||||||
|
|
||||||
|
split_lines_width(Cs, Num) -->
|
||||||
|
( { length(Prefix, Num),
|
||||||
|
append(Prefix, [R|Rs], Cs) } ->
|
||||||
|
seq(Prefix), "_\n",
|
||||||
|
split_lines_width([R|Rs], Num)
|
||||||
|
; seq(Cs)
|
||||||
|
).
|
||||||
|
|
||||||
|
cell(From, To, Es0) -->
|
||||||
|
( { Es0 == [] } -> []
|
||||||
|
; { reverse(Es0, Es) },
|
||||||
|
[cell(From,To,Es)]
|
||||||
|
).
|
||||||
|
|
||||||
|
%?- format:numeric_argument("2f", Num, [f|Fs], Args0, Args).
|
||||||
|
|
||||||
|
%?- format:numeric_argument("100b", Num, Rs, Args0, Args).
|
||||||
|
|
||||||
|
numeric_argument(Ds, Num, Rest, Args0, Args) :-
|
||||||
|
( Ds = [*|Rest] ->
|
||||||
|
Args0 = [Num|Args]
|
||||||
|
; phrase(numeric_argument_(Ds, Rest), Ns),
|
||||||
|
foldl(plus_times10, Ns, 0, Num),
|
||||||
|
Args0 = Args
|
||||||
|
).
|
||||||
|
|
||||||
|
numeric_argument_([D|Ds], Rest) -->
|
||||||
|
( { member(D, "0123456789") } ->
|
||||||
|
{ number_chars(N, [D]) },
|
||||||
|
[N],
|
||||||
|
numeric_argument_(Ds, Rest)
|
||||||
|
; { Rest = [D|Ds] }
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
plus_times10(D, N0, N) :- N is D + N0*10.
|
||||||
|
|
||||||
|
radix_error(lowercase, R) --> format_("~~~dr", [R]).
|
||||||
|
radix_error(uppercase, R) --> format_("~~~dR", [R]).
|
||||||
|
|
||||||
|
integer_to_radix(I0, R, Which, Cs) :-
|
||||||
|
I is I0, % evaluate compound expression
|
||||||
|
must_be(integer, I),
|
||||||
|
must_be(integer, R),
|
||||||
|
( \+ between(2, 36, R) ->
|
||||||
|
phrase(radix_error(Which,R), Es),
|
||||||
|
domain_error(format_string, Es, format_//2)
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
digits(Which, Ds),
|
||||||
|
( I < 0 ->
|
||||||
|
Pos is abs(I),
|
||||||
|
phrase(integer_to_radix_(Pos, R, Ds), Cs0, "-")
|
||||||
|
; I =:= 0 -> Cs0 = "0"
|
||||||
|
; phrase(integer_to_radix_(I, R, Ds), Cs0)
|
||||||
|
),
|
||||||
|
reverse(Cs0, Cs).
|
||||||
|
|
||||||
|
integer_to_radix_(0, _, _) --> !.
|
||||||
|
integer_to_radix_(I0, R, Ds) -->
|
||||||
|
{ M is I0 mod R,
|
||||||
|
nth0(M, Ds, D),
|
||||||
|
I is I0 // R },
|
||||||
|
[D],
|
||||||
|
integer_to_radix_(I, R, Ds).
|
||||||
|
|
||||||
|
digits(lowercase, "0123456789abcdefghijklmnopqrstuvwxyz").
|
||||||
|
digits(uppercase, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").
|
||||||
|
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Impure I/O, implemented as a small wrapper over format_//2.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
%% format(+Fs, +Args)
|
||||||
|
%
|
||||||
|
% The predicate `format/2` is like `format_//2`, except that it
|
||||||
|
% outputs the text on the terminal instead of describing it
|
||||||
|
% declaratively as a list of characters.
|
||||||
|
%
|
||||||
|
% If at all possible, `format_//2` should be used, to stress pure
|
||||||
|
% parts that enable easy testing etc. If necessary, you can emit the
|
||||||
|
% described list of characters `Ls` with `maplist(put_char, Ls)` or,
|
||||||
|
% much faster, with `format("~s", [Ls])`. Ideally, however, you use
|
||||||
|
% `phrase_to_file/[2,3]` or `phrase_to_stream/2` from `library(pio)`
|
||||||
|
% to write the described list directly to a file or stream,
|
||||||
|
% respectively: `phrase_to_stream(format_(..., [...]), S)`. The
|
||||||
|
% advantage of this is that an ideal implementation writes the
|
||||||
|
% characters as they become known, without manifesting the list.
|
||||||
|
|
||||||
|
format(Fs, Args) :-
|
||||||
|
current_output(Stream),
|
||||||
|
format(Stream, Fs, Args).
|
||||||
|
|
||||||
|
%% format(Stream, FormatString, Arguments)
|
||||||
|
%
|
||||||
|
% Output the described string to the given Stream. If Stream is a
|
||||||
|
% binary stream, then the code of each emitted character must be in
|
||||||
|
% 0..255.
|
||||||
|
|
||||||
|
format(Stream, Fs, Args) :-
|
||||||
|
phrase_to_stream(format_(Fs, Args), Stream),
|
||||||
|
flush_output(Stream).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
?- phrase(format:cells("hello", [], 0, [], []), Cs).
|
||||||
|
|
||||||
|
?- phrase(format:cells("hello~10|", [], 0, [], []), Cs).
|
||||||
|
?- phrase(format:cells("~ta~t~10|", [], 0, [], []), Cs).
|
||||||
|
|
||||||
|
?- phrase(format_("~`at~50|", []), Ls).
|
||||||
|
|
||||||
|
?- 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 ...")])]
|
||||||
|
?- phrase(format:cells("~ta~t~4|", [], 0, [], []), Cs).
|
||||||
|
Cs = [cell(0,4,[glue(' ',_A),chars("a"),glue(' ',_B)])]
|
||||||
|
|
||||||
|
?- phrase(format:format_cell(cell(0,1,[glue(a,_94)])), Ls).
|
||||||
|
|
||||||
|
?- phrase(format:format_cell(cell(0,50,[chars("hello")])), Ls).
|
||||||
|
|
||||||
|
?- phrase(format_("~`at~50|~n", []), Ls).
|
||||||
|
?- phrase(format_("hello~n~tthere~6|", []), Ls).
|
||||||
|
|
||||||
|
?- format("~ta~t~4|", []).
|
||||||
|
a true.
|
||||||
|
|
||||||
|
?- format("~ta~tb~tc~10|", []).
|
||||||
|
a b c true.
|
||||||
|
|
||||||
|
?- format("~tabc~3|", []).
|
||||||
|
|
||||||
|
?- format("~ta~t~4|", []).
|
||||||
|
|
||||||
|
?- format("~ta~t~tb~tc~20|", []).
|
||||||
|
a b c true.
|
||||||
|
|
||||||
|
?- format("~2f~n", [3]).
|
||||||
|
3.00
|
||||||
|
true.
|
||||||
|
|
||||||
|
?- format("~20f", [0.1]).
|
||||||
|
0.10000000000000000000 true.
|
||||||
|
|
||||||
|
?- X is atan(2), format("~7f~n", [X]).
|
||||||
|
1.1071487
|
||||||
|
X = 1.1071487177940906.
|
||||||
|
|
||||||
|
?- format("~`at~50|~n", []).
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
true
|
||||||
|
|
||||||
|
?- format("~t~N", []).
|
||||||
|
|
||||||
|
?- format("~q", [.]).
|
||||||
|
'.' true.
|
||||||
|
|
||||||
|
?- format("~12r", [300]).
|
||||||
|
210 true.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
We also provide rudimentary versions of portray_clause/1 and listing/1.
|
||||||
|
|
||||||
|
In the eventual library organization, portray_clause/1 and
|
||||||
|
related predicates may be placed in their own dedicated library.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
|
||||||
|
%% portray_clause(+Term)
|
||||||
|
%
|
||||||
|
% `portray_clause/1` is useful for printing solutions in such a way
|
||||||
|
% that they can be read back with `read/1`.
|
||||||
|
|
||||||
|
portray_clause(Term) :-
|
||||||
|
current_output(Out),
|
||||||
|
portray_clause(Out, Term).
|
||||||
|
|
||||||
|
portray_clause(Stream, Term) :-
|
||||||
|
phrase_to_stream(portray_clause_(Term), Stream),
|
||||||
|
flush_output(Stream).
|
||||||
|
|
||||||
|
portray_clause_(Term) -->
|
||||||
|
{ unique_variable_names(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).
|
||||||
|
|
||||||
|
portray_(Var, VNs) --> { var(Var) }, !, literal(Var, VNs).
|
||||||
|
portray_((Head :- Body), VNs) --> !,
|
||||||
|
literal(Head, VNs), " :-\n",
|
||||||
|
body_(Body, 0, 3, VNs).
|
||||||
|
portray_((Head --> Body), VNs) --> !,
|
||||||
|
literal(Head, VNs), " -->\n",
|
||||||
|
body_(Body, 0, 3, VNs).
|
||||||
|
portray_(Any, VNs) --> literal(Any, VNs).
|
||||||
|
|
||||||
|
|
||||||
|
body_(Var, C, I, VNs) --> { var(Var) }, !,
|
||||||
|
indent_to(C, I),
|
||||||
|
literal(Var, VNs).
|
||||||
|
body_((A,B), C, I, VNs) --> !,
|
||||||
|
body_(A, C, I, VNs), ",\n",
|
||||||
|
body_(B, 0, I, VNs).
|
||||||
|
body_(Body, C, I, VNs) -->
|
||||||
|
{ body_if_then_else(Body, If, Then, Else) },
|
||||||
|
!,
|
||||||
|
indent_to(C, I),
|
||||||
|
"( ",
|
||||||
|
{ C1 is I + 3 },
|
||||||
|
body_(If, C1, C1, VNs), " ->\n",
|
||||||
|
body_(Then, 0, C1, VNs), "\n",
|
||||||
|
else_branch(Else, I, VNs).
|
||||||
|
body_((A;B), C, I, VNs) --> !,
|
||||||
|
indent_to(C, I),
|
||||||
|
"( ",
|
||||||
|
{ C1 is I + 3 },
|
||||||
|
body_(A, C1, C1, VNs), "\n",
|
||||||
|
else_branch(B, I, VNs).
|
||||||
|
body_(Goal, C, I, VNs) -->
|
||||||
|
indent_to(C, I), literal(Goal, VNs).
|
||||||
|
|
||||||
|
|
||||||
|
% True iff Body has the shape ( If -> Then ; Else ).
|
||||||
|
body_if_then_else(Body, If, Then, Else) :-
|
||||||
|
nonvar(Body),
|
||||||
|
Body = (A ; Else),
|
||||||
|
nonvar(A),
|
||||||
|
A = (If -> Then).
|
||||||
|
|
||||||
|
else_branch(Else, I, VNs) -->
|
||||||
|
indent_to(0, I),
|
||||||
|
"; ",
|
||||||
|
{ C is I + 3 },
|
||||||
|
( { body_if_then_else(Else, If, Then, NextElse) } ->
|
||||||
|
body_(If, C, C, VNs), " ->\n",
|
||||||
|
body_(Then, 0, C, VNs), "\n",
|
||||||
|
else_branch(NextElse, I, VNs)
|
||||||
|
; { nonvar(Else), Else = ( A ; B ) } ->
|
||||||
|
body_(A, C, C, VNs), "\n",
|
||||||
|
else_branch(B, I, VNs)
|
||||||
|
; body_(Else, C, C, VNs), "\n",
|
||||||
|
indent_to(0, I),
|
||||||
|
")"
|
||||||
|
).
|
||||||
|
|
||||||
|
indent_to(CurrentColumn, Indent) -->
|
||||||
|
format_("~t~*|", [Indent-CurrentColumn]).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
?- portray_clause(a).
|
||||||
|
a.
|
||||||
|
|
||||||
|
?- portray_clause((a :- b)).
|
||||||
|
a :-
|
||||||
|
b.
|
||||||
|
|
||||||
|
?- portray_clause((a :- b, c, d)).
|
||||||
|
a :-
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
d.
|
||||||
|
true.
|
||||||
|
|
||||||
|
|
||||||
|
?- portray_clause([a,b,c,d]).
|
||||||
|
"abcd".
|
||||||
|
|
||||||
|
?- portray_clause(X).
|
||||||
|
?- portray_clause((f(X) :- X)).
|
||||||
|
|
||||||
|
?- portray_clause((h :- ( a -> b; c))).
|
||||||
|
|
||||||
|
?- portray_clause((h :- ( (a -> x ; y) -> b; c))).
|
||||||
|
|
||||||
|
?- portray_clause((h(X) :- ( (a(X) ; y(A,B)) -> b; c))).
|
||||||
|
|
||||||
|
?- portray_clause((h :- (a,d;b,c) ; (b,e;d))).
|
||||||
|
|
||||||
|
?- portray_clause((a :- b ; c ; d)).
|
||||||
|
|
||||||
|
?- portray_clause((h :- L = '.')).
|
||||||
|
|
||||||
|
?- portray_clause(-->(a, (b, {t}, d))).
|
||||||
|
|
||||||
|
?- portray_clause((A :- B)).
|
||||||
|
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
listing(PI) :-
|
||||||
|
nonvar(PI),
|
||||||
|
( PI = Name/Arity0 ->
|
||||||
|
Arity = Arity0
|
||||||
|
; PI = Name//Arity0 ->
|
||||||
|
Arity is Arity0 + 2
|
||||||
|
; type_error(predicate_indicator, PI, listing/1)
|
||||||
|
),
|
||||||
|
functor(Head, Name, Arity),
|
||||||
|
\+ \+ clause(Head, _), % only true if there is at least one clause
|
||||||
|
( clause(Head, Body),
|
||||||
|
( Body == true ->
|
||||||
|
portray_clause(Head)
|
||||||
|
; portray_clause((Head :- Body))
|
||||||
|
),
|
||||||
|
false
|
||||||
|
; true
|
||||||
|
).
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
:- module(freeze, [freeze/2]).
|
:- module(freeze, [freeze/2]).
|
||||||
|
|
||||||
|
/** Provides the constraint `freeze/2`.
|
||||||
|
*/
|
||||||
|
|
||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
:- use_module(library(dcgs)).
|
:- use_module(library(dcgs)).
|
||||||
|
|
||||||
|
:- meta_predicate freeze(-, 0).
|
||||||
|
|
||||||
:- attribute frozen/1.
|
:- attribute frozen/1.
|
||||||
|
|
||||||
verify_attributes(Var, Other, Goals) :-
|
verify_attributes(Var, Other, Goals) :-
|
||||||
@@ -17,6 +22,15 @@ verify_attributes(Var, Other, Goals) :-
|
|||||||
).
|
).
|
||||||
verify_attributes(_, _, []).
|
verify_attributes(_, _, []).
|
||||||
|
|
||||||
|
%% freeze(Var, Goal)
|
||||||
|
%
|
||||||
|
% Schedules Goal to be executed when Var is instantiated. This can
|
||||||
|
% be useful to observe the exact moment a variable becomes bound to a
|
||||||
|
% more concrete term, for example when creating animations of search
|
||||||
|
% processes. Higher-level constructs such as `phrase_from_file/2` can
|
||||||
|
% also be implemented with `freeze/2`, by scheduling a goal that
|
||||||
|
% reads additional data from a file as soon as it is needed.
|
||||||
|
|
||||||
freeze(X, Goal) :-
|
freeze(X, Goal) :-
|
||||||
put_atts(Fresh, frozen(Goal)),
|
put_atts(Fresh, frozen(Goal)),
|
||||||
Fresh = X.
|
Fresh = X.
|
||||||
@@ -24,5 +38,5 @@ freeze(X, Goal) :-
|
|||||||
attribute_goals(Var) -->
|
attribute_goals(Var) -->
|
||||||
{ get_atts(Var, frozen(Goals)),
|
{ get_atts(Var, frozen(Goals)),
|
||||||
put_atts(Var, -frozen(_)) },
|
put_atts(Var, -frozen(_)) },
|
||||||
[freeze(Var, Goals)].
|
[freeze:freeze(Var, Goals)].
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@ gensym(Base, Unique) :-
|
|||||||
must_be(var, Unique),
|
must_be(var, Unique),
|
||||||
atom_si(Base),
|
atom_si(Base),
|
||||||
gensym_key(Base, BaseKey),
|
gensym_key(Base, BaseKey),
|
||||||
( bb_get(BaseKey, UniqueID0) ->
|
( bb_get(BaseKey, UniqueID0) -> true
|
||||||
UniqueID is UniqueID0 + 1,
|
; UniqueID0 = 0
|
||||||
bb_put(BaseKey, UniqueID),
|
),
|
||||||
append_id(Base, UniqueID, Unique)
|
UniqueID is UniqueID0 + 1,
|
||||||
; bb_put(BaseKey, 1),
|
append_id(Base, UniqueID, Unique),
|
||||||
append_id(Base, 1, Unique)
|
bb_put(BaseKey, UniqueID).
|
||||||
).
|
|
||||||
|
|
||||||
reset_gensym(Base) :-
|
reset_gensym(Base) :-
|
||||||
atom_si(Base),
|
atom_si(Base),
|
||||||
bb_put(Base, 0).
|
gensym_key(Base, BaseKey),
|
||||||
|
bb_put(BaseKey, 0).
|
||||||
73
src/lib/http/http_open.pl
Normal file
73
src/lib/http/http_open.pl
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written 2022 by Adrián Arroyo Calle (adrian.arroyocalle@gmail.com)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Make HTTP requests.
|
||||||
|
|
||||||
|
This library contains the predicate `http_open/3` which allows you to perform HTTP(S) calls.
|
||||||
|
Useful for making API calls, or parsing websites. It uses Hyper underneath.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(http_open, [http_open/3]).
|
||||||
|
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
%% http_open(+Address, -Stream, +Options).
|
||||||
|
%
|
||||||
|
% Yields Stream to read the body of an HTTP reply from Address.
|
||||||
|
% Address is a list of characters, and includes the method. Both HTTP
|
||||||
|
% and HTTPS are supported.
|
||||||
|
%
|
||||||
|
% Options supported:
|
||||||
|
%
|
||||||
|
% * `method(+Method)`: Sets the HTTP method of the call. Method can be `get` (default), `head`, `delete`, `post`, `put` or `patch`.
|
||||||
|
% * `data(+Data)`: Data to be sent in the request. Useful for POST, PUT and PATCH operations.
|
||||||
|
% * `size(-Size)`: Unifies with the value of the Content-Length header
|
||||||
|
% * `request_headers(+RequestHeaders)`: Headers to be used in the request
|
||||||
|
% * `headers(-ListHeaders)`: Unifies with a list with all headers returned in the response
|
||||||
|
% * `status_code(-Code)`: Unifies with the status code of the request (200, 201, 404, ...)
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- http_open("https://www.example.com", S, []), get_n_chars(S, N, HTML).
|
||||||
|
% S = '$stream'(0x7fb548001be8), N = 1256, HTML = "<!doctype html>\n<ht ...".
|
||||||
|
% ```
|
||||||
|
http_open(Address, Response, Options) :-
|
||||||
|
parse_http_options(Options, OptionValues),
|
||||||
|
( member(method(Method), OptionValues) -> true; Method = get),
|
||||||
|
( member(data(Data), OptionValues) -> true; Data = []),
|
||||||
|
( member(request_headers(RequestHeaders), OptionValues) -> true; RequestHeaders = ['user-agent'("Scryer Prolog")]),
|
||||||
|
( member(status_code(Code), OptionValues) -> true; true),
|
||||||
|
( member(headers(Headers), OptionValues) -> true; true),
|
||||||
|
( member(size(Size), OptionValues) -> member('content-length'(Size), Headers); true),
|
||||||
|
'$http_open'(Address, Response, Method, Code, Data, Headers, RequestHeaders).
|
||||||
|
|
||||||
|
parse_http_options(Options, OptionValues) :-
|
||||||
|
maplist(parse_http_options_, Options, OptionValues).
|
||||||
|
|
||||||
|
parse_http_options_(method(Method), method(Method)) :-
|
||||||
|
( var(Method) ->
|
||||||
|
throw(error(instantiation_error, http_open/3))
|
||||||
|
;
|
||||||
|
member(Method, [get, post, put, delete, patch, head]) -> true
|
||||||
|
;
|
||||||
|
throw(error(domain_error(http_option, method(Method)), _))
|
||||||
|
).
|
||||||
|
|
||||||
|
parse_http_options_(data(Data), data(Data)) :-
|
||||||
|
( var(Data) ->
|
||||||
|
throw(error(instantiation_error, http_open/3))
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
|
||||||
|
parse_http_options_(request_headers(Headers), request_headers(Headers)) :-
|
||||||
|
( var(Headers) ->
|
||||||
|
throw(error(instantiation_error, http_open/3))
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
|
||||||
|
parse_http_options_(size(Size), size(Size)).
|
||||||
|
parse_http_options_(status_code(Code), status_code(Code)).
|
||||||
|
parse_http_options_(headers(Headers), headers(Headers)).
|
||||||
354
src/lib/http/http_server.pl
Normal file
354
src/lib/http/http_server.pl
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written in December 2020 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
|
||||||
|
Updated in March 2022 by Adrián Arroyo to use the Hyper backend
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
I place this code in the public domain. Use it in any way you want.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** This library provides an starting point to build HTTP server based applications.
|
||||||
|
It is based on [Hyper](https://hyper.rs/), which allows for HTTP/1.0, HTTP/1.1 and HTTP/2. However,
|
||||||
|
some advanced features that Hyper provides are still not accesible.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The main predicate of the library is `http_listen/2`, which needs a port number
|
||||||
|
(usually 80) and a list of handlers. A handler is a compound term with the functor
|
||||||
|
as one HTTP method (in lowercase) and followed by a Route Match and a predicate
|
||||||
|
which will handle the call.
|
||||||
|
|
||||||
|
```
|
||||||
|
text_handler(Request, Response) :-
|
||||||
|
http_status_code(Response, 200),
|
||||||
|
http_body(Response, text("Welcome to Scryer Prolog!")).
|
||||||
|
|
||||||
|
parameter_handler(User, Request, Response) :-
|
||||||
|
http_body(Response, text(User)).
|
||||||
|
|
||||||
|
http_listen(7890, [
|
||||||
|
get(echo, text_handler), % GET /echo
|
||||||
|
post(user/User, parameter_handler(User)) % POST /user/<User>
|
||||||
|
]).
|
||||||
|
```
|
||||||
|
|
||||||
|
Every handler predicate will have at least 2-arity, with Request and Response.
|
||||||
|
Although you can work directly with `http_request` and `http_response` terms, it is
|
||||||
|
recommeded to use the helper predicates, which are easier to understand and cleaner:
|
||||||
|
|
||||||
|
- `http_headers(Response/Request, Headers)`
|
||||||
|
- `http_status_code(Responde, StatusCode)`
|
||||||
|
- `http_body(Response/Request, text(Body))`
|
||||||
|
- `http_body(Response/Request, binary(Body))`
|
||||||
|
- `http_body(Request, form(Form))`
|
||||||
|
- `http_body(Response, file(Filename))`
|
||||||
|
- `http_redirect(Response, Url)`
|
||||||
|
- `http_query(Request, QueryName, QueryValue)`
|
||||||
|
|
||||||
|
Some things that are still missing:
|
||||||
|
|
||||||
|
- Read forms in multipart format
|
||||||
|
- HTTP Basic Auth
|
||||||
|
- Session handling via cookies
|
||||||
|
- HTML Templating (but you can use [Teruel](https://github.com/aarroyoc/teruel/), [Marquete](https://github.com/aarroyoc/marquete/) or [Djota](https://github.com/aarroyoc/djota) for that)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
:- module(http_server, [
|
||||||
|
http_listen/2,
|
||||||
|
http_headers/2,
|
||||||
|
http_status_code/2,
|
||||||
|
http_body/2,
|
||||||
|
http_redirect/2,
|
||||||
|
http_query/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- meta_predicate http_listen(?, :).
|
||||||
|
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(crypto)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(format)).
|
||||||
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(pio)).
|
||||||
|
:- use_module(library(time)).
|
||||||
|
|
||||||
|
%% http_listen(+Port, +Handlers).
|
||||||
|
%
|
||||||
|
% Listens for HTTP connections on port Port. Each handler on the list Handlers should be of the form: `HttpVerb(PathUnification, Predicate)`.
|
||||||
|
% For example: `get(user/User, get_info(User))` will match an HTTP request that is a GET, the path unifies with /user/User (where User is a variable)
|
||||||
|
% and it will call `get_info` with three arguments: an `http_request` term, an `http_response` term and User.
|
||||||
|
http_listen(Port, Module:Handlers0) :-
|
||||||
|
must_be(integer, Port),
|
||||||
|
must_be(list, Handlers0),
|
||||||
|
maplist(module_qualification(Module), Handlers0, Handlers),
|
||||||
|
http_listen_(Port, Handlers).
|
||||||
|
|
||||||
|
module_qualification(M, H0, H) :-
|
||||||
|
H0 =.. [Method, Path, Goal],
|
||||||
|
H =.. [Method, Path, M:Goal].
|
||||||
|
|
||||||
|
http_listen_(Port, Handlers) :-
|
||||||
|
phrase(format_("0.0.0.0:~d", [Port]), Addr),
|
||||||
|
'$http_listen'(Addr, HttpListener),!,
|
||||||
|
format("Listening at ~s\n", [Addr]),
|
||||||
|
http_loop(HttpListener, Handlers).
|
||||||
|
|
||||||
|
http_loop(HttpListener, Handlers) :-
|
||||||
|
'$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
|
||||||
|
current_time(Time),
|
||||||
|
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
|
||||||
|
format("~s ~w ~s\n", [TimeString, RequestMethod, RequestPath]),
|
||||||
|
maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
|
||||||
|
phrase(parse_queries(RequestQueries), RequestQuery),
|
||||||
|
(
|
||||||
|
match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
|
||||||
|
(
|
||||||
|
HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
|
||||||
|
HttpResponse = http_response(_, _, _),
|
||||||
|
(call(Handler, HttpRequest, HttpResponse) ->
|
||||||
|
send_response(ResponseHandle, HttpResponse)
|
||||||
|
; (
|
||||||
|
'$http_answer'(ResponseHandle, 500, [], ResponseStream),
|
||||||
|
call_cleanup(format(ResponseStream, "Internal Server Error", []), close(ResponseStream)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
; (
|
||||||
|
'$http_answer'(ResponseHandle, 404, [], ResponseStream),
|
||||||
|
call_cleanup(format(ResponseStream, "Not Found"), close(ResponseStream)))
|
||||||
|
),
|
||||||
|
http_loop(HttpListener, Handlers).
|
||||||
|
|
||||||
|
send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
|
||||||
|
default(StatusCode0, 200, StatusCode),
|
||||||
|
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||||
|
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
|
||||||
|
open(stream(ResponseStream0), write, ResponseStream, [type(text)]),
|
||||||
|
catch(
|
||||||
|
call_cleanup(format(ResponseStream, "~s", [ResponseText]),close(ResponseStream)),
|
||||||
|
error(existence_error(stream, _), _),
|
||||||
|
true
|
||||||
|
).
|
||||||
|
|
||||||
|
send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), ResponseHeaders0)) :-
|
||||||
|
default(StatusCode0, 200, StatusCode),
|
||||||
|
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||||
|
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
|
||||||
|
catch(
|
||||||
|
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
|
||||||
|
error(existence_error(stream, _), _),
|
||||||
|
true
|
||||||
|
).
|
||||||
|
|
||||||
|
send_response(ResponseHandle, http_response(StatusCode0, file(Filename), ResponseHeaders0)) :-
|
||||||
|
default(StatusCode0, 200, StatusCode),
|
||||||
|
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
|
||||||
|
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
|
||||||
|
catch(
|
||||||
|
call_cleanup(
|
||||||
|
setup_call_cleanup(
|
||||||
|
open(Filename, read, FileStream, [type(binary)]),
|
||||||
|
(
|
||||||
|
get_n_chars(FileStream, _, FileCs),
|
||||||
|
format(ResponseStream, "~s", [FileCs])
|
||||||
|
),
|
||||||
|
close(FileStream)
|
||||||
|
),
|
||||||
|
close(ResponseStream)
|
||||||
|
),
|
||||||
|
error(existence_error(stream, _), _),
|
||||||
|
true
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
default(Var, Default, Out) :-
|
||||||
|
(var(Var) -> Out = Default
|
||||||
|
; Var = Out
|
||||||
|
).
|
||||||
|
|
||||||
|
map_header_kv(T, K-V) :-
|
||||||
|
T =.. [K0, V],
|
||||||
|
atom_chars(K0, K).
|
||||||
|
|
||||||
|
map_header_kv_2(T, K-V) :-
|
||||||
|
atom_chars(K0, K),
|
||||||
|
T =.. [K0, V].
|
||||||
|
|
||||||
|
match_handler(Handlers, Method, "/", Handler) :-
|
||||||
|
member(H, Handlers),
|
||||||
|
H =.. [Method, /, Handler].
|
||||||
|
match_handler(Handlers, Method, Path, Handler) :-
|
||||||
|
member(H, Handlers),
|
||||||
|
copy_term(H, H1),
|
||||||
|
H1 =.. [Method, Pattern, Handler],
|
||||||
|
\+ var(Pattern),
|
||||||
|
phrase(path(Pattern), Path).
|
||||||
|
match_handler(Handlers, Method, Path, Handler) :-
|
||||||
|
member(H, Handlers),
|
||||||
|
copy_term(H, H1),
|
||||||
|
H1 =.. [Method, Var, Handler],
|
||||||
|
var(Var),
|
||||||
|
Var = Path.
|
||||||
|
|
||||||
|
path(Pattern) -->
|
||||||
|
{
|
||||||
|
Pattern =.. Parts,
|
||||||
|
length(Parts, 3),
|
||||||
|
nth0(1, Parts, Pattern0),
|
||||||
|
nth0(2, Parts, PartAtom),
|
||||||
|
(var(PartAtom) -> Part = PartAtom; atom_chars(PartAtom, Part))
|
||||||
|
},
|
||||||
|
path(Pattern0),
|
||||||
|
"/",
|
||||||
|
string_without("/", Part).
|
||||||
|
|
||||||
|
path(Pattern) -->
|
||||||
|
{
|
||||||
|
Pattern =.. Parts,
|
||||||
|
Parts = [PartAtom],
|
||||||
|
(var(PartAtom) -> Part = PartAtom; atom_chars(PartAtom, Part))
|
||||||
|
},
|
||||||
|
"/",
|
||||||
|
string_without("/", Part).
|
||||||
|
|
||||||
|
path([]) --> [].
|
||||||
|
|
||||||
|
string_without(Not, [Char|String]) -->
|
||||||
|
[Char],
|
||||||
|
{
|
||||||
|
\+ member(Char, Not)
|
||||||
|
},
|
||||||
|
string_without(Not, String).
|
||||||
|
|
||||||
|
string_without(_, []) -->
|
||||||
|
[].
|
||||||
|
|
||||||
|
%% http_headers(?Request_Response, ?Headers).
|
||||||
|
%
|
||||||
|
% True iff `Request_Response` is a request or response with headers Headers. Can be used both to get headers (usually in from a request)
|
||||||
|
% and to add headers (usually in a response).
|
||||||
|
http_headers(http_request(Headers, _, _), Headers).
|
||||||
|
http_headers(http_response(_, _, Headers), Headers).
|
||||||
|
|
||||||
|
%% http_body(?Request_Response, ?Body).
|
||||||
|
%
|
||||||
|
% True iff Body is the body of the request or response. A body can be of the following types:
|
||||||
|
%
|
||||||
|
% * `bytes(Bytes)` for both requests and responses, interprets the body as bytes
|
||||||
|
% * `text(Bytes)` for both requests and responses, interprets the body as text
|
||||||
|
% * `form(Form)` only for requests, interprets the body as an `application/x-www-form-urlencoded` form.
|
||||||
|
% * `file(File)` only for responses, interprets the body as the content of a file (useful to send static files).
|
||||||
|
http_body(http_request(_, stream(StreamBody), _), bytes(BytesBody)) :- get_n_chars(StreamBody, _, BytesBody).
|
||||||
|
http_body(http_request(_, stream(StreamBody), _), text(TextBody)) :- get_n_chars(StreamBody, _, TextBody).
|
||||||
|
http_body(http_request(Headers, stream(StreamBody), _), form(FormBody)) :-
|
||||||
|
member("content-type"-"application/x-www-form-urlencoded", Headers),
|
||||||
|
get_n_chars(StreamBody, _, TextBody),
|
||||||
|
phrase(parse_queries(FormBody), TextBody).
|
||||||
|
http_body(http_request(_, Body, _), Body).
|
||||||
|
http_body(http_response(_, Body, _), Body).
|
||||||
|
|
||||||
|
%% http_status_code(?Response, ?StatusCode).
|
||||||
|
%
|
||||||
|
% True iff the status code of the response Response unifies with StatusCode.
|
||||||
|
http_status_code(http_response(StatusCode, _, _), StatusCode).
|
||||||
|
|
||||||
|
%% http_redirect(-Response, +Uri).
|
||||||
|
%
|
||||||
|
% True iff Response is a response that redirects the user to the uri Uri.
|
||||||
|
http_redirect(http_response(307, text("Moved Temporarily"), ["Location"-Uri]), Uri).
|
||||||
|
|
||||||
|
%% http_query(+Request, ?Key, ?Value).
|
||||||
|
%
|
||||||
|
% True iff there's a query in request Request with key Key and value Value.
|
||||||
|
http_query(http_request(_, _, Queries), Key, Value) :- member(Key-Value, Queries).
|
||||||
|
|
||||||
|
parse_queries([Key-Value|Queries]) -->
|
||||||
|
string_without("=", Key0),
|
||||||
|
{
|
||||||
|
phrase(url_decode(Key), Key0)
|
||||||
|
},
|
||||||
|
"=",
|
||||||
|
string_without("&", Value0),
|
||||||
|
{
|
||||||
|
phrase(url_decode(Value), Value0)
|
||||||
|
},
|
||||||
|
"&",
|
||||||
|
parse_queries(Queries).
|
||||||
|
|
||||||
|
parse_queries([Key-Value]) -->
|
||||||
|
string_without("=", Key0),
|
||||||
|
{
|
||||||
|
phrase(url_decode(Key), Key0)
|
||||||
|
},
|
||||||
|
"=",
|
||||||
|
string_without(" ", Value0),
|
||||||
|
{
|
||||||
|
phrase(url_decode(Value), Value0)
|
||||||
|
}.
|
||||||
|
|
||||||
|
parse_queries([]) -->
|
||||||
|
[].
|
||||||
|
|
||||||
|
% Decodes a UTF-8 URL Encoded string: RFC-1738
|
||||||
|
url_decode([Char|Chars]) -->
|
||||||
|
[Char],
|
||||||
|
{
|
||||||
|
Char \= '%'
|
||||||
|
},
|
||||||
|
url_decode(Chars).
|
||||||
|
url_decode([Char|Chars]) -->
|
||||||
|
"%",
|
||||||
|
[A],
|
||||||
|
[B],
|
||||||
|
{
|
||||||
|
hex_bytes([A,B], Bytes),
|
||||||
|
Bytes = [FirstByte|_],
|
||||||
|
FirstByte < 128,
|
||||||
|
chars_utf8bytes(Chars0, Bytes),
|
||||||
|
Chars0 = [Char]
|
||||||
|
},
|
||||||
|
url_decode(Chars).
|
||||||
|
url_decode([Char|Chars]) -->
|
||||||
|
"%",
|
||||||
|
[A, B],
|
||||||
|
"%",
|
||||||
|
[C, D],
|
||||||
|
{
|
||||||
|
hex_bytes([A,B,C,D], Bytes),
|
||||||
|
Bytes = [FirstByte|_],
|
||||||
|
FirstByte < 224,
|
||||||
|
chars_utf8bytes(Chars0, Bytes),
|
||||||
|
Chars0 = [Char]
|
||||||
|
},
|
||||||
|
url_decode(Chars).
|
||||||
|
url_decode([Char|Chars]) -->
|
||||||
|
"%",
|
||||||
|
[A, B],
|
||||||
|
"%",
|
||||||
|
[C, D],
|
||||||
|
"%",
|
||||||
|
[E, F],
|
||||||
|
{
|
||||||
|
hex_bytes([A,B,C,D,E,F], Bytes),
|
||||||
|
Bytes = [FirstByte|_],
|
||||||
|
FirstByte < 240,
|
||||||
|
chars_utf8bytes(Chars0, Bytes),
|
||||||
|
Chars0 = [Char]
|
||||||
|
},
|
||||||
|
url_decode(Chars).
|
||||||
|
url_decode([Char|Chars]) -->
|
||||||
|
"%",
|
||||||
|
[A, B],
|
||||||
|
"%",
|
||||||
|
[C, D],
|
||||||
|
"%",
|
||||||
|
[E, F],
|
||||||
|
"%",
|
||||||
|
[H, I],
|
||||||
|
{
|
||||||
|
hex_bytes([A,B,C,D,E,F,H,I], Bytes),
|
||||||
|
chars_utf8bytes(Chars0, Bytes),
|
||||||
|
Chars0 = [Char]
|
||||||
|
},
|
||||||
|
url_decode(Chars).
|
||||||
|
|
||||||
|
url_decode([]) --> [].
|
||||||
415
src/lib/iso_ext.pl
Normal file
415
src/lib/iso_ext.pl
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
/** Useful general predicates that are not ISO standard yet
|
||||||
|
|
||||||
|
Predicates available here are similar to the ones defined in builtin.pl,
|
||||||
|
but they're not part of the ISO Prolog standard at the moment.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(iso_ext, [bb_b_put/2,
|
||||||
|
bb_get/2,
|
||||||
|
bb_put/2,
|
||||||
|
call_cleanup/2,
|
||||||
|
call_with_inference_limit/3,
|
||||||
|
forall/2,
|
||||||
|
partial_string/1,
|
||||||
|
partial_string/3,
|
||||||
|
partial_string_tail/2,
|
||||||
|
setup_call_cleanup/3,
|
||||||
|
succ/2,
|
||||||
|
call_nth/2,
|
||||||
|
countall/2,
|
||||||
|
copy_term_nat/2,
|
||||||
|
asserta/2,
|
||||||
|
assertz/2]).
|
||||||
|
|
||||||
|
:- use_module(library(error), [can_be/2,
|
||||||
|
domain_error/3,
|
||||||
|
instantiation_error/1,
|
||||||
|
type_error/3]).
|
||||||
|
|
||||||
|
:- use_module(library(lists), [maplist/3]).
|
||||||
|
|
||||||
|
:- meta_predicate(forall(0, 0)).
|
||||||
|
|
||||||
|
%% forall(Generate, Test).
|
||||||
|
%
|
||||||
|
% For all bindings possible by Generate, Test must be true.
|
||||||
|
%
|
||||||
|
% In this example, it checks that all numbers are even:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- Ns = [2,4,6], forall(member(N, Ns), 0 is N mod 2).
|
||||||
|
% Ns = [2,4,6].
|
||||||
|
% ```
|
||||||
|
forall(Generate, Test) :-
|
||||||
|
\+ (Generate, \+ Test).
|
||||||
|
|
||||||
|
% (non-)backtrackable global variables.
|
||||||
|
|
||||||
|
%% bb_put(+Key, +Value).
|
||||||
|
%
|
||||||
|
% Sets a global variable named Key (must be an atom) with value Value.
|
||||||
|
% The global variable isn't backtrackable. Check `bb_b_put/2` for the
|
||||||
|
% backtrackable version.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- bb_put(city, "Valladolid").
|
||||||
|
% true.
|
||||||
|
% ?- bb_get(city, X).
|
||||||
|
% X = "Valladolid".
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% In this example one can understand the difference between `bb_put/2` and
|
||||||
|
% `bb_b_put/2`:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||||
|
% X = "Salamanca".
|
||||||
|
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||||
|
% X = "Valladolid".
|
||||||
|
% ```
|
||||||
|
bb_put(Key, Value) :-
|
||||||
|
( atom(Key) ->
|
||||||
|
'$store_global_var'(Key, Value)
|
||||||
|
; type_error(atom, Key, bb_put/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
% backtrackable global variables.
|
||||||
|
|
||||||
|
%% bb_b_put(+Key, +Value).
|
||||||
|
%
|
||||||
|
% Sets a global variable named Key (must be an atom) with value Value.
|
||||||
|
% The global variable is backtrackable. Check `bb_put/2` for the
|
||||||
|
% non-backtrackable version.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- bb_b_put(city, "Valladolid").
|
||||||
|
% true.
|
||||||
|
% ?- bb_get(city, X).
|
||||||
|
% X = "Valladolid".
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% In this example one can understand the difference between `bb_put/2` and
|
||||||
|
% `bb_b_put/2`:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- bb_put(city, "Valladolid"), (bb_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||||
|
% X = "Salamanca".
|
||||||
|
% ?- bb_put(city, "Valladolid"), (bb_b_put(city, "Salamanca"), false);(bb_get(city, X)).
|
||||||
|
% X = "Valladolid".
|
||||||
|
% ```
|
||||||
|
bb_b_put(Key, Value) :-
|
||||||
|
( atom(Key) ->
|
||||||
|
'$store_backtrackable_global_var'(Key, Value)
|
||||||
|
; type_error(atom, Key, bb_b_put/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% bb_get(+Key, -Value).
|
||||||
|
%
|
||||||
|
% Gets the value Value of a global variable named Key (must be an atom)
|
||||||
|
bb_get(Key, Value) :-
|
||||||
|
( atom(Key) ->
|
||||||
|
'$fetch_global_var'(Key, Value)
|
||||||
|
; type_error(atom, Key, bb_get/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
%% succ(?I, ?S).
|
||||||
|
%
|
||||||
|
% True iff S is the successor of the non-negative integer I.
|
||||||
|
% At least one of the arguments must be instantiated.
|
||||||
|
|
||||||
|
succ(I, S) :-
|
||||||
|
can_be(not_less_than_zero, I),
|
||||||
|
can_be(not_less_than_zero, S),
|
||||||
|
( integer(S) ->
|
||||||
|
S > 0,
|
||||||
|
I is S-1
|
||||||
|
; integer(I) ->
|
||||||
|
S is I+1
|
||||||
|
; instantiation_error(succ/2)
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
% setup_call_cleanup.
|
||||||
|
|
||||||
|
:- meta_predicate(call_cleanup(0, 0)).
|
||||||
|
|
||||||
|
%% call_cleanup(Goal, Cleanup).
|
||||||
|
%
|
||||||
|
% Executes Goal and then, either on success or failure, executes Cleanup.
|
||||||
|
% The success or failure of Cleanup is ignored and choice points created inside are destroyed.
|
||||||
|
call_cleanup(G, C) :- setup_call_cleanup(true, G, C).
|
||||||
|
|
||||||
|
:- meta_predicate(setup_call_cleanup(0, 0, 0)).
|
||||||
|
|
||||||
|
:- non_counted_backtracking setup_call_cleanup/3.
|
||||||
|
|
||||||
|
%% setup_call_cleanup(Setup, Goal, Cleanup).
|
||||||
|
%
|
||||||
|
% If Setup succeeds, Cleanup will be called after the execution of Goal. Goal itself can succeed or not.
|
||||||
|
%
|
||||||
|
% In this example, we use the predicate to always close an open file:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- setup_call_cleanup(open(File, read, Stream), do_something_with_stream(Stream), close(Stream)).
|
||||||
|
% ```
|
||||||
|
setup_call_cleanup(S, G, C) :-
|
||||||
|
'$get_b_value'(B),
|
||||||
|
'$call_with_inference_counting'(call(S)),
|
||||||
|
'$set_cp_by_default'(B),
|
||||||
|
'$get_current_scc_block'(Bb),
|
||||||
|
( C = _:CC,
|
||||||
|
var(CC) ->
|
||||||
|
instantiation_error(setup_call_cleanup/3)
|
||||||
|
; scc_helper(C, G, Bb)
|
||||||
|
).
|
||||||
|
|
||||||
|
:- meta_predicate(scc_helper(?,0,?)).
|
||||||
|
|
||||||
|
:- non_counted_backtracking scc_helper/3.
|
||||||
|
|
||||||
|
scc_helper(C, G, Bb) :-
|
||||||
|
'$get_cp'(Cp),
|
||||||
|
'$install_scc_cleaner'(C),
|
||||||
|
'$call_with_inference_counting'(call(G)),
|
||||||
|
( '$check_cp'(Cp) ->
|
||||||
|
'$reset_scc_block'(Bb),
|
||||||
|
run_cleaners_without_handling(Cp)
|
||||||
|
; true
|
||||||
|
; '$fail'
|
||||||
|
).
|
||||||
|
scc_helper(_, _, Bb) :-
|
||||||
|
'$reset_scc_block'(Bb),
|
||||||
|
'$push_ball_stack',
|
||||||
|
run_cleaners_with_handling,
|
||||||
|
'$pop_from_ball_stack',
|
||||||
|
'$unwind_stack'.
|
||||||
|
scc_helper(_, _, _) :-
|
||||||
|
'$get_cp'(Cp),
|
||||||
|
run_cleaners_without_handling(Cp),
|
||||||
|
'$fail'.
|
||||||
|
|
||||||
|
:- non_counted_backtracking run_cleaners_with_handling/0.
|
||||||
|
|
||||||
|
run_cleaners_with_handling :-
|
||||||
|
'$get_scc_cleaner'(C),
|
||||||
|
'$get_cp'(B),
|
||||||
|
catch(C, _, true),
|
||||||
|
'$set_cp_by_default'(B),
|
||||||
|
run_cleaners_with_handling.
|
||||||
|
run_cleaners_with_handling :-
|
||||||
|
'$restore_cut_policy'.
|
||||||
|
|
||||||
|
:- non_counted_backtracking run_cleaners_without_handling/1.
|
||||||
|
|
||||||
|
run_cleaners_without_handling(Cp) :-
|
||||||
|
'$get_scc_cleaner'(C),
|
||||||
|
'$get_cp'(B),
|
||||||
|
call(C),
|
||||||
|
'$set_cp_by_default'(B),
|
||||||
|
run_cleaners_without_handling(Cp).
|
||||||
|
run_cleaners_without_handling(Cp) :-
|
||||||
|
'$set_cp_by_default'(Cp),
|
||||||
|
'$restore_cut_policy'.
|
||||||
|
|
||||||
|
% call_with_inference_limit
|
||||||
|
|
||||||
|
:- non_counted_backtracking end_block/4.
|
||||||
|
|
||||||
|
end_block(_, Bb, NBb, _L) :-
|
||||||
|
'$clean_up_block'(NBb),
|
||||||
|
'$reset_block'(Bb).
|
||||||
|
end_block(B, _Bb, NBb, L) :-
|
||||||
|
'$install_inference_counter'(B, L, _),
|
||||||
|
'$reset_block'(NBb),
|
||||||
|
'$fail'.
|
||||||
|
|
||||||
|
:- non_counted_backtracking handle_ile/3.
|
||||||
|
|
||||||
|
handle_ile(B, inference_limit_exceeded(B), inference_limit_exceeded) :-
|
||||||
|
!,
|
||||||
|
'$pop_ball_stack'.
|
||||||
|
handle_ile(B, _, _) :-
|
||||||
|
'$remove_call_policy_check'(B),
|
||||||
|
'$pop_from_ball_stack',
|
||||||
|
'$unwind_stack'.
|
||||||
|
|
||||||
|
:- meta_predicate(call_with_inference_limit(0, ?, ?)).
|
||||||
|
|
||||||
|
:- non_counted_backtracking call_with_inference_limit/3.
|
||||||
|
|
||||||
|
%% call_with_inference_limit(Goal, Limit, Result).
|
||||||
|
%
|
||||||
|
% Similar to `call(Goal)` but it limits the number of inferences for each solution of Goal.
|
||||||
|
call_with_inference_limit(G, L, R) :-
|
||||||
|
( integer(L) ->
|
||||||
|
( L < 0 ->
|
||||||
|
domain_error(not_less_than_zero, L, call_with_inference_limit/3)
|
||||||
|
; true
|
||||||
|
)
|
||||||
|
; var(L) ->
|
||||||
|
instantiation_error(call_with_inference_limit/3)
|
||||||
|
; type_error(integer, L, call_with_inference_limit/3)
|
||||||
|
),
|
||||||
|
'$get_current_block'(Bb),
|
||||||
|
'$get_b_value'(B),
|
||||||
|
call_with_inference_limit(G, L, R, Bb, B),
|
||||||
|
'$remove_call_policy_check'(B).
|
||||||
|
|
||||||
|
install_inference_counter(B, L, Count0) :-
|
||||||
|
'$install_inference_counter'(B, L, Count0).
|
||||||
|
|
||||||
|
:- meta_predicate(call_with_inference_limit(0,?,?,?,?)).
|
||||||
|
|
||||||
|
:- non_counted_backtracking call_with_inference_limit/5.
|
||||||
|
|
||||||
|
call_with_inference_limit(G, L, R, Bb, B) :-
|
||||||
|
'$install_new_block'(NBb),
|
||||||
|
'$install_inference_counter'(B, L, Count0),
|
||||||
|
'$call_with_inference_counting'(call(G)),
|
||||||
|
'$inference_level'(R, B),
|
||||||
|
'$remove_inference_counter'(B, Count1),
|
||||||
|
Diff is L - (Count1 - Count0),
|
||||||
|
end_block(B, Bb, NBb, Diff).
|
||||||
|
call_with_inference_limit(_, _, R, Bb, B) :-
|
||||||
|
'$reset_block'(Bb),
|
||||||
|
'$remove_inference_counter'(B, _),
|
||||||
|
( '$get_ball'(Ball),
|
||||||
|
'$push_ball_stack',
|
||||||
|
'$get_cp'(Cp),
|
||||||
|
'$set_cp_by_default'(Cp)
|
||||||
|
; '$remove_call_policy_check'(B),
|
||||||
|
'$fail'
|
||||||
|
),
|
||||||
|
handle_ile(B, Ball, R).
|
||||||
|
|
||||||
|
%% partial_string(String, L, L0)
|
||||||
|
%
|
||||||
|
% Explicitly construct a partial string "manually". It can be used as an optimized append/3.
|
||||||
|
% It's not recommended to use this predicate in application code.
|
||||||
|
partial_string(String, L, L0) :-
|
||||||
|
( String == [] ->
|
||||||
|
L = L0
|
||||||
|
; catch(atom_chars(Atom, String),
|
||||||
|
error(E, _),
|
||||||
|
throw(error(E, partial_string/3))),
|
||||||
|
'$create_partial_string'(Atom, L, L0)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% partial_string(+String)
|
||||||
|
%
|
||||||
|
% Succeeds if String is a _partial string_. A partial string is a string composed of several smaller
|
||||||
|
% strings, even just one. That means all strings in Scryer are partial strings.
|
||||||
|
partial_string(String) :-
|
||||||
|
'$is_partial_string'(String).
|
||||||
|
|
||||||
|
%% partial_string_tail(+String, -Tail).
|
||||||
|
%
|
||||||
|
% Unifies Tail with the last section of the partial string.
|
||||||
|
% It's not recommended to use this predicate in application code.
|
||||||
|
partial_string_tail(String, Tail) :-
|
||||||
|
( partial_string(String) ->
|
||||||
|
'$partial_string_tail'(String, Tail)
|
||||||
|
; throw(error(type_error(partial_string, String), partial_string_tail/2))
|
||||||
|
).
|
||||||
|
|
||||||
|
:- dynamic(i_call_nth_nesting/2).
|
||||||
|
:- dynamic(i_call_nth_counter/1).
|
||||||
|
|
||||||
|
:- meta_predicate(call_nth(0, ?)).
|
||||||
|
|
||||||
|
%% call_nth(Goal, N).
|
||||||
|
%
|
||||||
|
% Succeeds when Goal succeeded for the Nth time (there are at least N solutions)
|
||||||
|
call_nth(Goal, N) :-
|
||||||
|
can_be(integer, N),
|
||||||
|
( integer(N) ->
|
||||||
|
( N < 0 ->
|
||||||
|
domain_error(not_less_than_zero, N, call_nth/2)
|
||||||
|
; N > 0
|
||||||
|
)
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
setup_call_cleanup(call_nth_nesting(C, ID),
|
||||||
|
( Goal,
|
||||||
|
bb_get(ID, N0),
|
||||||
|
N1 is N0 + 1,
|
||||||
|
bb_put(ID, N1),
|
||||||
|
( integer(N) ->
|
||||||
|
N = N1,
|
||||||
|
!
|
||||||
|
; N = N1
|
||||||
|
)
|
||||||
|
),
|
||||||
|
( bb_get(i_call_nth_counter, C) ->
|
||||||
|
C1 is C - 1,
|
||||||
|
bb_put(i_call_nth_counter, C1)
|
||||||
|
; true
|
||||||
|
)).
|
||||||
|
|
||||||
|
call_nth_nesting(C, ID) :-
|
||||||
|
( bb_get(i_call_nth_counter, C0) ->
|
||||||
|
C is C0 + 1
|
||||||
|
; C = 0
|
||||||
|
),
|
||||||
|
number_chars(C, Cs),
|
||||||
|
atom_chars(Atom, Cs),
|
||||||
|
atom_concat(i_call_nth_nesting_, Atom, ID),
|
||||||
|
bb_put(ID, 0),
|
||||||
|
bb_put(i_call_nth_counter, C).
|
||||||
|
|
||||||
|
%% countall(Goal, N).
|
||||||
|
%
|
||||||
|
% countall(Goal, N) counts all solutions of Goal and unifies N with
|
||||||
|
% this number of solutions. This predicate always succeeds once.
|
||||||
|
|
||||||
|
:- meta_predicate(countall(0, ?)).
|
||||||
|
|
||||||
|
countall(Goal, N) :-
|
||||||
|
can_be(integer, N),
|
||||||
|
( integer(N) ->
|
||||||
|
( N < 0 ->
|
||||||
|
domain_error(not_less_than_zero, N, countall/2)
|
||||||
|
; N > 0
|
||||||
|
)
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
setup_call_cleanup(call_nth_nesting(C, ID),
|
||||||
|
( ( Goal,
|
||||||
|
bb_get(ID, N0),
|
||||||
|
N1 is N0 + 1,
|
||||||
|
bb_put(ID, N1),
|
||||||
|
false
|
||||||
|
; bb_get(ID, N)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
( bb_get(i_call_nth_counter, C) ->
|
||||||
|
C1 is C - 1,
|
||||||
|
bb_put(i_call_nth_counter, C1)
|
||||||
|
; true
|
||||||
|
)).
|
||||||
|
|
||||||
|
%% copy_term_nat(Source, Dest)
|
||||||
|
%
|
||||||
|
% Similar to `copy_term/2` but without attribute variables
|
||||||
|
copy_term_nat(Source, Dest) :-
|
||||||
|
'$copy_term_without_attr_vars'(Source, Dest).
|
||||||
|
|
||||||
|
%% asserta(Module, Rule_Fact).
|
||||||
|
%
|
||||||
|
% Similar to `asserta/1` but allows specifying a Module
|
||||||
|
asserta(Module, (Head :- Body)) :-
|
||||||
|
!,
|
||||||
|
'$asserta'(Module, Head, Body).
|
||||||
|
asserta(Module, Fact) :-
|
||||||
|
'$asserta'(Module, Fact, 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).
|
||||||
|
|
||||||
233
src/lib/lambda.pl
Normal file
233
src/lib/lambda.pl
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
/*
|
||||||
|
Author: Ulrich Neumerkel
|
||||||
|
E-mail: ulrich@complang.tuwien.ac.at
|
||||||
|
Copyright (C): 2009 Ulrich Neumerkel. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY Ulrich Neumerkel ``AS IS'' AND ANY
|
||||||
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Ulrich Neumerkel OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
The views and conclusions contained in the software and documentation
|
||||||
|
are those of the authors and should not be interpreted as representing
|
||||||
|
official policies, either expressed or implied, of Ulrich Neumerkel.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(lambda, [
|
||||||
|
(^)/3, (^)/4, (^)/5, (^)/6, (^)/7, (^)/8, (^)/9, (^)/10,
|
||||||
|
(\)/1, (\)/2, (\)/3, (\)/4, (\)/5, (\)/6, (\)/7, (\)/8,
|
||||||
|
(+\)/2, (+\)/3, (+\)/4, (+\)/5, (+\)/6, (+\)/7, (+\)/8,
|
||||||
|
(+\)/9, op(201,xfx,+\)]).
|
||||||
|
|
||||||
|
:- use_module(library(iso_ext)).
|
||||||
|
|
||||||
|
/** <module> Lambda expressions
|
||||||
|
|
||||||
|
This library provides lambda expressions to simplify higher order
|
||||||
|
programming based on call/N.
|
||||||
|
|
||||||
|
Lambda expressions are represented by ordinary Prolog terms.
|
||||||
|
There are two kinds of lambda expressions:
|
||||||
|
|
||||||
|
```
|
||||||
|
Free+\X1^X2^ ..^XN^Goal
|
||||||
|
|
||||||
|
\X1^X2^ ..^XN^Goal
|
||||||
|
```
|
||||||
|
|
||||||
|
The second is a shorthand for `t+\X1^X2^..^XN^Goal`.
|
||||||
|
|
||||||
|
Xi are the parameters.
|
||||||
|
|
||||||
|
Goal is a goal or continuation. Syntax note: Operators within Goal
|
||||||
|
require parentheses due to the low precedence of the ^ operator.
|
||||||
|
|
||||||
|
Free contains variables that are valid outside the scope of the lambda
|
||||||
|
expression. They are thus free variables within.
|
||||||
|
|
||||||
|
All other variables of Goal are considered local variables. They must
|
||||||
|
not appear outside the lambda expression. This restriction is
|
||||||
|
currently not checked. Violations may lead to unexpected bindings.
|
||||||
|
|
||||||
|
In the following example the parentheses around X>3 are necessary.
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(lambda)).
|
||||||
|
?- use_module(library(lists)).
|
||||||
|
|
||||||
|
?- maplist(\X^(X>3),[4,5,9]).
|
||||||
|
true.
|
||||||
|
```
|
||||||
|
|
||||||
|
In the following X is a variable that is shared by both instances of
|
||||||
|
the lambda expression. The second query illustrates the cooperation of
|
||||||
|
continuations and lambdas. The lambda expression is in this case a
|
||||||
|
continuation expecting a further argument.
|
||||||
|
|
||||||
|
```
|
||||||
|
?- use_module(library(dif)).
|
||||||
|
true.
|
||||||
|
|
||||||
|
?- Xs = [A,B], maplist(X+\Y^dif(X,Y), Xs).
|
||||||
|
Xs = [A,B], dif:dif(X,A), dif:dif(X,B).
|
||||||
|
|
||||||
|
?- Xs = [A,B], maplist(X+\dif(X), Xs).
|
||||||
|
Xs = [A,B], dif:dif(X,A), dif:dif(X,B).
|
||||||
|
```
|
||||||
|
|
||||||
|
The following queries are all equivalent. To see this, use
|
||||||
|
the fact `f(x,y)`.
|
||||||
|
|
||||||
|
```
|
||||||
|
?- call(f,A1,A2).
|
||||||
|
?- call(\X^f(X),A1,A2).
|
||||||
|
?- call(\X^Y^f(X,Y), A1,A2).
|
||||||
|
?- call(\X^(X+\Y^f(X,Y)), A1,A2).
|
||||||
|
?- call(call(f, A1),A2).
|
||||||
|
?- call(f(A1),A2).
|
||||||
|
?- f(A1,A2).
|
||||||
|
A1 = x, A2 = y.
|
||||||
|
```
|
||||||
|
|
||||||
|
Further discussions
|
||||||
|
[http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord](http://www.complang.tuwien.ac.at/ulrich/Prolog-inedit/ISO-Hiord)
|
||||||
|
|
||||||
|
@tbd Static expansion similar to apply_macros.
|
||||||
|
@author Ulrich Neumerkel
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- meta_predicate ^(?,0,?).
|
||||||
|
:- meta_predicate ^(?,1,?,?).
|
||||||
|
:- meta_predicate ^(?,2,?,?,?).
|
||||||
|
:- meta_predicate ^(?,3,?,?,?,?).
|
||||||
|
:- meta_predicate ^(?,4,?,?,?,?,?).
|
||||||
|
:- meta_predicate ^(?,5,?,?,?,?,?,?).
|
||||||
|
:- meta_predicate ^(?,6,?,?,?,?,?,?,?).
|
||||||
|
:- meta_predicate ^(?,7,?,?,?,?,?,?,?,?).
|
||||||
|
:- meta_predicate \(0).
|
||||||
|
:- meta_predicate \(1,?).
|
||||||
|
:- meta_predicate \(2,?,?).
|
||||||
|
:- meta_predicate \(3,?,?,?).
|
||||||
|
:- meta_predicate \(4,?,?,?,?).
|
||||||
|
:- meta_predicate \(5,?,?,?,?,?).
|
||||||
|
:- meta_predicate \(6,?,?,?,?,?,?).
|
||||||
|
:- meta_predicate \(7,?,?,?,?,?,?,?).
|
||||||
|
:- meta_predicate +\(?,0).
|
||||||
|
:- meta_predicate +\(?,1,?).
|
||||||
|
:- meta_predicate +\(?,2,?,?).
|
||||||
|
:- meta_predicate +\(?,3,?,?,?).
|
||||||
|
:- meta_predicate +\(?,4,?,?,?,?).
|
||||||
|
:- meta_predicate +\(?,5,?,?,?,?,?).
|
||||||
|
:- meta_predicate +\(?,6,?,?,?,?,?,?).
|
||||||
|
:- meta_predicate +\(?,7,?,?,?,?,?,?,?).
|
||||||
|
|
||||||
|
:- meta_predicate no_hat_call(0).
|
||||||
|
|
||||||
|
^(V1,C_0,V1) :-
|
||||||
|
no_hat_call(C_0).
|
||||||
|
^(V1,C_1,V1,V2) :-
|
||||||
|
call(C_1,V2).
|
||||||
|
^(V1,C_2,V1,V2,V3) :-
|
||||||
|
call(C_2,V2,V3).
|
||||||
|
^(V1,C_3,V1,V2,V3,V4) :-
|
||||||
|
call(C_3,V2,V3,V4).
|
||||||
|
^(V1,C_4,V1,V2,V3,V4,V5) :-
|
||||||
|
call(C_4,V2,V3,V4,V5).
|
||||||
|
^(V1,C_5,V1,V2,V3,V4,V5,V6) :-
|
||||||
|
call(C_5,V2,V3,V4,V5,V6).
|
||||||
|
^(V1,C_6,V1,V2,V3,V4,V5,V6,V7) :-
|
||||||
|
call(C_6,V2,V3,V4,V5,V6,V7).
|
||||||
|
^(V1,C_7,V1,V2,V3,V4,V5,V6,V7,V8) :-
|
||||||
|
call(C_7,V2,V3,V4,V5,V6,V7,V8).
|
||||||
|
|
||||||
|
\(FC_0) :-
|
||||||
|
copy_term_nat(FC_0,C_0),
|
||||||
|
no_hat_call(C_0).
|
||||||
|
\(FC_1,V1) :-
|
||||||
|
copy_term_nat(FC_1,C_1),
|
||||||
|
call(C_1,V1).
|
||||||
|
\(FC_2,V1,V2) :-
|
||||||
|
copy_term_nat(FC_2,C_2),
|
||||||
|
call(C_2,V1,V2).
|
||||||
|
\(FC_3,V1,V2,V3) :-
|
||||||
|
copy_term_nat(FC_3,C_3),
|
||||||
|
call(C_3,V1,V2,V3).
|
||||||
|
\(FC_4,V1,V2,V3,V4) :-
|
||||||
|
copy_term_nat(FC_4,C_4),
|
||||||
|
call(C_4,V1,V2,V3,V4).
|
||||||
|
\(FC_5,V1,V2,V3,V4,V5) :-
|
||||||
|
copy_term_nat(FC_5,C_5),
|
||||||
|
call(C_5,V1,V2,V3,V4,V5).
|
||||||
|
\(FC_6,V1,V2,V3,V4,V5,V6) :-
|
||||||
|
copy_term_nat(FC_6,C_6),
|
||||||
|
call(C_6,V1,V2,V3,V4,V5,V6).
|
||||||
|
\(FC_7,V1,V2,V3,V4,V5,V6,V7) :-
|
||||||
|
copy_term_nat(FC_7,C_7),
|
||||||
|
call(C_7,V1,V2,V3,V4,V5,V6,V7).
|
||||||
|
|
||||||
|
|
||||||
|
+\(GV,FC_0) :-
|
||||||
|
copy_term_nat(GV+FC_0,GV+C_0),
|
||||||
|
no_hat_call(C_0).
|
||||||
|
+\(GV,FC_1,V1) :-
|
||||||
|
copy_term_nat(GV+FC_1,GV+C_1),
|
||||||
|
call(C_1,V1).
|
||||||
|
+\(GV,FC_2,V1,V2) :-
|
||||||
|
copy_term_nat(GV+FC_2,GV+C_2),
|
||||||
|
call(C_2,V1,V2).
|
||||||
|
+\(GV,FC_3,V1,V2,V3) :-
|
||||||
|
copy_term_nat(GV+FC_3,GV+C_3),
|
||||||
|
call(C_3,V1,V2,V3).
|
||||||
|
+\(GV,FC_4,V1,V2,V3,V4) :-
|
||||||
|
copy_term_nat(GV+FC_4,GV+C_4),
|
||||||
|
call(C_4,V1,V2,V3,V4).
|
||||||
|
+\(GV,FC_5,V1,V2,V3,V4,V5) :-
|
||||||
|
copy_term_nat(GV+FC_5,GV+C_5),
|
||||||
|
call(C_5,V1,V2,V3,V4,V5).
|
||||||
|
+\(GV,FC_6,V1,V2,V3,V4,V5,V6) :-
|
||||||
|
copy_term_nat(GV+FC_6,GV+C_6),
|
||||||
|
call(C_6,V1,V2,V3,V4,V5,V6).
|
||||||
|
+\(GV,FC_7,V1,V2,V3,V4,V5,V6,V7) :-
|
||||||
|
copy_term_nat(GV+FC_7,GV+C_7),
|
||||||
|
call(C_7,V1,V2,V3,V4,V5,V6,V7).
|
||||||
|
|
||||||
|
|
||||||
|
%% no_hat_call(:Goal_0)
|
||||||
|
%
|
||||||
|
% Like call, but issues an error for a goal (^)/2. Such goals are
|
||||||
|
% likely the result of an insufficient number of arguments.
|
||||||
|
|
||||||
|
no_hat_call(MGoal_0) :-
|
||||||
|
strip_module(MGoal_0, _, Goal_0),
|
||||||
|
( nonvar(Goal_0),
|
||||||
|
Goal_0 = (_^_)
|
||||||
|
-> throw(
|
||||||
|
error(
|
||||||
|
existence_error(lambda_parameter,MGoal_0),
|
||||||
|
_))
|
||||||
|
; call(MGoal_0)
|
||||||
|
).
|
||||||
|
|
||||||
|
% I would like to replace this by:
|
||||||
|
% V1^Goal :- throw(error(existence_error(lambda_parameter,V1^Goal),_)).
|
||||||
540
src/lib/lists.pl
Normal file
540
src/lib/lists.pl
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
/**
|
||||||
|
List manipulation predicates
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5,
|
||||||
|
memberchk/2, reverse/2, length/2, maplist/2,
|
||||||
|
maplist/3, maplist/4, maplist/5, maplist/6,
|
||||||
|
maplist/7, maplist/8, maplist/9, same_length/2, nth0/3, nth0/4, nth1/3, nth1/4,
|
||||||
|
sum_list/2, transpose/2, list_to_set/2, list_max/2,
|
||||||
|
list_min/2, permutation/2]).
|
||||||
|
|
||||||
|
/* Author: Mark Thom, Jan Wielemaker, and Richard O'Keefe
|
||||||
|
Copyright (c) 2018-2021, Mark Thom
|
||||||
|
Copyright (c) 2002-2020, University of Amsterdam
|
||||||
|
VU University Amsterdam
|
||||||
|
SWI-Prolog Solutions b.v.
|
||||||
|
All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
:- 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 foldl(3, ?, ?, ?).
|
||||||
|
:- meta_predicate foldl(4, ?, ?, ?, ?).
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
:- meta_predicate(resource_error(+,:)).
|
||||||
|
|
||||||
|
resource_error(Resource, Context) :-
|
||||||
|
throw(error(resource_error(Resource), Context)).
|
||||||
|
|
||||||
|
%% length(?Xs, ?N).
|
||||||
|
%
|
||||||
|
% Relates a list to its length (number of elements). It can be used to count the elements of a current list or
|
||||||
|
% to create a list full of free variables with N length.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- length("abc", 3).
|
||||||
|
% true.
|
||||||
|
% ?- length("abc", N).
|
||||||
|
% N = 3.
|
||||||
|
% ?- length(Xs, 3).
|
||||||
|
% Xs = [_A,_B,_C].
|
||||||
|
% ```
|
||||||
|
|
||||||
|
length(Xs0, N) :-
|
||||||
|
'$skip_max_list'(M, N, Xs0,Xs),
|
||||||
|
!,
|
||||||
|
( Xs == [] -> N = M
|
||||||
|
; nonvar(Xs) -> var(N), Xs = [_|_], resource_error(finite_memory,length/2)
|
||||||
|
; nonvar(N) -> R is N-M, length_rundown(Xs, R)
|
||||||
|
; N == Xs -> failingvarskip(Xs), resource_error(finite_memory,length/2)
|
||||||
|
; length_addendum(Xs, N, M)
|
||||||
|
).
|
||||||
|
length(_, N) :-
|
||||||
|
integer(N), !,
|
||||||
|
domain_error(not_less_than_zero, N, length/2).
|
||||||
|
length(_, N) :-
|
||||||
|
type_error(integer, N, length/2).
|
||||||
|
|
||||||
|
length_rundown(Xs, 0) :- !, Xs = [].
|
||||||
|
length_rundown(Vs, N) :-
|
||||||
|
'$unattributed_var'(Vs), % unconstrained
|
||||||
|
!,
|
||||||
|
'$det_length_rundown'(Vs, N).
|
||||||
|
length_rundown([_|Xs], N) :- % force unification
|
||||||
|
N1 is N-1,
|
||||||
|
length(Xs, N1). % maybe some new info on Xs
|
||||||
|
|
||||||
|
failingvarskip(Xs) :-
|
||||||
|
'$unattributed_var'(Xs), % unconstrained
|
||||||
|
!.
|
||||||
|
failingvarskip([_|Xs0]) :- % force unification
|
||||||
|
'$skip_max_list'(_, _, Xs0,Xs),
|
||||||
|
( nonvar(Xs) -> Xs = [_|_]
|
||||||
|
; failingvarskip(Xs)
|
||||||
|
).
|
||||||
|
|
||||||
|
length_addendum([], N, N).
|
||||||
|
length_addendum([_|Xs], N, M) :-
|
||||||
|
M1 is M + 1,
|
||||||
|
length_addendum(Xs, N, M1).
|
||||||
|
|
||||||
|
%% member(?X, ?Xs).
|
||||||
|
%
|
||||||
|
% Succeeds when X unifies with an item of the list Xs, which can be at any position.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- member(X, "hello world").
|
||||||
|
% X = h
|
||||||
|
% ; ... .
|
||||||
|
% ```
|
||||||
|
|
||||||
|
member(X, [L|Ls]) :-
|
||||||
|
member_(Ls, L, X).
|
||||||
|
|
||||||
|
member_(_, X, X).
|
||||||
|
member_([L|Ls], _, X) :-
|
||||||
|
member_(Ls, L, X).
|
||||||
|
|
||||||
|
%% select(X, Xs0, Xs1).
|
||||||
|
%
|
||||||
|
% Succeeds when the list Xs1 is the list Xs0 without the item X
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- select(c, "abcd", X).
|
||||||
|
% X = "abd"
|
||||||
|
% ; false.
|
||||||
|
% ```
|
||||||
|
select(X, [X|Xs], Xs).
|
||||||
|
select(X, [Y|Xs], [Y|Ys]) :- select(X, Xs, Ys).
|
||||||
|
|
||||||
|
%% append(+XsXs, ?Xs).
|
||||||
|
%
|
||||||
|
% Concatenates a list of lists
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- append([[1, 2], [3]], Xs).
|
||||||
|
% Xs = [1,2,3].
|
||||||
|
% ```
|
||||||
|
append([], []).
|
||||||
|
append([L0|Ls0], Ls) :-
|
||||||
|
append(L0, Rest, Ls),
|
||||||
|
append(Ls0, Rest).
|
||||||
|
|
||||||
|
%% append(Xs0, Xs1, Xs).
|
||||||
|
%
|
||||||
|
% List Xs is the concatenation of Xs0 and Xs1
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- append([1,2,3], [4,5,6], Xs).
|
||||||
|
% Xs = [1,2,3,4,5,6].
|
||||||
|
% ```
|
||||||
|
append([], R, R).
|
||||||
|
append([X|L], R, [X|S]) :- append(L, R, S).
|
||||||
|
|
||||||
|
%% memberchk(?X, +Xs).
|
||||||
|
%
|
||||||
|
% This predicate is similar to `member/2`, but it only provides a single answer
|
||||||
|
memberchk(X, Xs) :- member(X, Xs), !.
|
||||||
|
|
||||||
|
%% reverse(?Xs, ?Ys).
|
||||||
|
%
|
||||||
|
% Xs is the Ys list in reverse order
|
||||||
|
%
|
||||||
|
% ?- reverse([1,2,3], [3,2,1]).
|
||||||
|
% true.
|
||||||
|
%
|
||||||
|
reverse(Xs, Ys) :-
|
||||||
|
( nonvar(Xs) -> reverse(Xs, Ys, [], Xs)
|
||||||
|
; reverse(Ys, Xs, [], Ys)
|
||||||
|
).
|
||||||
|
|
||||||
|
reverse([], [], YsRev, YsRev).
|
||||||
|
reverse([_|Xs], [Y1|Ys], YsPreludeRev, Xss) :-
|
||||||
|
reverse(Xs, Ys, [Y1|YsPreludeRev], Xss).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the list Xs0
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- maplist(write, [1,2,3]).
|
||||||
|
% 123 true.
|
||||||
|
% ```
|
||||||
|
maplist(_, []).
|
||||||
|
maplist(Cont1, [E1|E1s]) :-
|
||||||
|
call(Cont1, E1),
|
||||||
|
maplist(Cont1, E1s).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0, ?Xs1).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the lists Xs0 and Xs1.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- maplist(length, ["hello", "prolog", "marseille"], Xs1).
|
||||||
|
% Xs1 = [5,6,9].
|
||||||
|
% ```
|
||||||
|
maplist(_, [], []).
|
||||||
|
maplist(Cont2, [E1|E1s], [E2|E2s]) :-
|
||||||
|
call(Cont2, E1, E2),
|
||||||
|
maplist(Cont2, E1s, E2s).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1 and Xs2.
|
||||||
|
maplist(_, [], [], []).
|
||||||
|
maplist(Cont3, [E1|E1s], [E2|E2s], [E3|E3s]) :-
|
||||||
|
call(Cont3, E1, E2, E3),
|
||||||
|
maplist(Cont3, E1s, E2s, E3s).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2 and Xs3.
|
||||||
|
maplist(_, [], [], [], []).
|
||||||
|
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s]) :-
|
||||||
|
call(Cont, E1, E2, E3, E4),
|
||||||
|
maplist(Cont, E1s, E2s, E3s, E4s).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3 and Xs4.
|
||||||
|
maplist(_, [], [], [], [], []).
|
||||||
|
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s]) :-
|
||||||
|
call(Cont, E1, E2, E3, E4, E5),
|
||||||
|
maplist(Cont, E1s, E2s, E3s, E4s, E5s).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4 and Xs5.
|
||||||
|
maplist(_, [], [], [], [], [], []).
|
||||||
|
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s]) :-
|
||||||
|
call(Cont, E1, E2, E3, E4, E5, E6),
|
||||||
|
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5 and Xs6.
|
||||||
|
maplist(_, [], [], [], [], [], [], []).
|
||||||
|
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s]) :-
|
||||||
|
call(Cont, E1, E2, E3, E4, E5, E6, E7),
|
||||||
|
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s).
|
||||||
|
|
||||||
|
%% maplist(+Predicate, ?Xs0, ?Xs1, ?Xs2, ?Xs3, ?Xs4, ?Xs5, ?Xs6, ?Xs7).
|
||||||
|
%
|
||||||
|
% This is a metapredicate that applies predicate to each element of the lists Xs0, Xs1, Xs2, Xs3, Xs4, Xs5, Xs6 and Xs7.
|
||||||
|
maplist(_, [], [], [], [], [], [], [], []).
|
||||||
|
maplist(Cont, [E1|E1s], [E2|E2s], [E3|E3s], [E4|E4s], [E5|E5s], [E6|E6s], [E7|E7s], [E8|E8s]) :-
|
||||||
|
call(Cont, E1, E2, E3, E4, E5, E6, E7, E8),
|
||||||
|
maplist(Cont, E1s, E2s, E3s, E4s, E5s, E6s, E7s, E8s).
|
||||||
|
|
||||||
|
%% sum_list(+Xs, -Sum).
|
||||||
|
%
|
||||||
|
% Takes a lists of numbers and unifies Sum with the result of summing all the elements of the list.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- sum_list([2,2,2], 6).
|
||||||
|
% true.
|
||||||
|
% ```
|
||||||
|
sum_list(Ls, S) :-
|
||||||
|
foldl(lists:sum_, Ls, 0, S).
|
||||||
|
|
||||||
|
sum_(L, S0, S) :- S is S0 + L.
|
||||||
|
|
||||||
|
|
||||||
|
%% same_length(?Xs, ?Ys).
|
||||||
|
%
|
||||||
|
% Succeeds if Xs and Ys are lists of the same length
|
||||||
|
same_length([], []).
|
||||||
|
same_length([_|As], [_|Bs]) :-
|
||||||
|
same_length(As, Bs).
|
||||||
|
|
||||||
|
%% foldl(+Predicate, ?Ls, +A0, ?A).
|
||||||
|
%
|
||||||
|
% foldl, sometimes called reduce, is a metapredicate that takes a predicate, a list of items
|
||||||
|
% and a starting value, and outputs a single value. The predicate _Predicate_ must be able to take the current
|
||||||
|
% element of the list, the previous value of the computation and the next value of the computation.
|
||||||
|
%
|
||||||
|
% For example, if we define sum_ as:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% sum_(L, S0, S) :- S is S0 + L.
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Then we can define `sum_list/2` as the following:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% sum_list(Ls, S) :- foldl(sum_, Ls, 0, S).
|
||||||
|
% ```
|
||||||
|
|
||||||
|
foldl(_, [], A, A).
|
||||||
|
foldl(G_3, [L|Ls], A0, A) :-
|
||||||
|
call(G_3, L, A0, A1),
|
||||||
|
foldl(G_3, Ls, A1, A).
|
||||||
|
|
||||||
|
%% foldl(+Predicate, ?Ls0, ?Ls1, +A0, ?A).
|
||||||
|
%
|
||||||
|
% Same as `foldl/4` but with an extra list
|
||||||
|
|
||||||
|
foldl(_, [], [], A, A).
|
||||||
|
foldl(G_4, [X|Xs], [Y|Ys], A0, A) :-
|
||||||
|
call(G_4, X, Y, A0, A1),
|
||||||
|
foldl(G_4, Xs, Ys, A1, A).
|
||||||
|
|
||||||
|
%% transpose(?Ls, ?Ts).
|
||||||
|
%
|
||||||
|
% If Ls is a list of lists, Ts contains the transposition
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- transpose([[1,1],[2,2]], Ts).
|
||||||
|
% Ts = [[1,2],[1,2]].
|
||||||
|
% ```
|
||||||
|
transpose(Ls, Ts) :-
|
||||||
|
lists_transpose(Ls, Ts).
|
||||||
|
|
||||||
|
lists_transpose([], []).
|
||||||
|
lists_transpose([L|Ls], Ts) :-
|
||||||
|
maplist(lists:same_length(L), Ls),
|
||||||
|
foldl(lists:transpose_, L, Ts, [L|Ls], _).
|
||||||
|
|
||||||
|
transpose_(_, Fs, Lists0, Lists) :-
|
||||||
|
maplist(lists:list_first_rest, Lists0, Fs, Lists).
|
||||||
|
|
||||||
|
list_first_rest([L|Ls], L, Ls).
|
||||||
|
|
||||||
|
%% list_to_set(+Ls0, -Set).
|
||||||
|
%
|
||||||
|
% Takes a list Ls0 and returns a list Set that doesn't contain any repeated element
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- list_to_set([2,3,4,4,1,2], Set).
|
||||||
|
% Set = [2,3,4,1].
|
||||||
|
% ```
|
||||||
|
list_to_set(Ls0, Ls) :-
|
||||||
|
maplist(lists:with_var, Ls0, LVs0),
|
||||||
|
keysort(LVs0, LVs),
|
||||||
|
same_elements(LVs),
|
||||||
|
pick_firsts(LVs0, Ls).
|
||||||
|
|
||||||
|
pick_firsts([], []).
|
||||||
|
pick_firsts([E-V|EVs], Fs0) :-
|
||||||
|
( V == visited ->
|
||||||
|
Fs0 = Fs
|
||||||
|
; V = visited,
|
||||||
|
Fs0 = [E|Fs]
|
||||||
|
),
|
||||||
|
pick_firsts(EVs, Fs).
|
||||||
|
|
||||||
|
with_var(E, E-_).
|
||||||
|
|
||||||
|
same_elements([]).
|
||||||
|
same_elements([EV|EVs]) :-
|
||||||
|
foldl(lists:unify_same, EVs, EV, _).
|
||||||
|
|
||||||
|
unify_same(E-V, Prev-Var, E-V) :-
|
||||||
|
( Prev == E ->
|
||||||
|
Var = V
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
|
||||||
|
%% nth0(?N, ?Ls, ?E).
|
||||||
|
%
|
||||||
|
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from zero.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- nth0(2, [1,2,3,4], 3).
|
||||||
|
% true.
|
||||||
|
% ```
|
||||||
|
nth0(N, Es0, E) :-
|
||||||
|
nonvar(N),
|
||||||
|
'$skip_max_list'(Skip, N, Es0,Es1),
|
||||||
|
!,
|
||||||
|
( Skip == N
|
||||||
|
-> Es1 = [E|_]
|
||||||
|
; ( var(Es1) ; Es1 = [_|_] ) % a partial or infinite list
|
||||||
|
-> R is N-Skip,
|
||||||
|
skipn(R,Es1,Es2),
|
||||||
|
Es2 = [E|_]
|
||||||
|
).
|
||||||
|
nth0(N, Es0, E) :-
|
||||||
|
can_be(not_less_than_zero, N),
|
||||||
|
Es0 = [E0|Es1],
|
||||||
|
nth0_el(0,N, E0,E, Es1).
|
||||||
|
|
||||||
|
skipn(N0, Es0,Es) :-
|
||||||
|
N0>0,
|
||||||
|
N1 is N0-1,
|
||||||
|
Es0 = [_|Es1],
|
||||||
|
skipn(N1, Es1,Es).
|
||||||
|
skipn(0, Es,Es).
|
||||||
|
|
||||||
|
nth0_el(N0,N, E0,E, Es0) :-
|
||||||
|
Es0 == [],
|
||||||
|
!, % indexing
|
||||||
|
N0 = N,
|
||||||
|
E0 = E.
|
||||||
|
nth0_el(N,N, E,E, _).
|
||||||
|
nth0_el(N0,N, _,E, [E0|Es0]) :-
|
||||||
|
N1 is N0+1,
|
||||||
|
nth0_el(N1,N, E0,E, Es0).
|
||||||
|
|
||||||
|
%% nth1(?N, ?Ls, ?E).
|
||||||
|
%
|
||||||
|
% Succeeds if in the N position of the list Ls, we found the element E. The elements start counting from one.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- nth1(2, [1,2,3,4], 2).
|
||||||
|
% true.
|
||||||
|
% ```
|
||||||
|
nth1(N, Es0, E) :-
|
||||||
|
N \== 0,
|
||||||
|
nth0(N, [_|Es0], E),
|
||||||
|
N \== 0.
|
||||||
|
|
||||||
|
skipn(N0, Es0,Es, Xs0,Xs) :-
|
||||||
|
N0>0,
|
||||||
|
N1 is N0-1,
|
||||||
|
Es0 = [E|Es1],
|
||||||
|
Xs0 = [E|Xs1],
|
||||||
|
skipn(N1, Es1,Es, Xs1,Xs).
|
||||||
|
skipn(0, Es,Es, Xs,Xs).
|
||||||
|
|
||||||
|
%% nth0(?N, ?Ls, ?E, ?Rs).
|
||||||
|
%
|
||||||
|
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from zero.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- nth0(2, [1,2,3,4], 3, [1,2,4]).
|
||||||
|
% true.
|
||||||
|
% ```
|
||||||
|
nth0(N, Es0, E, Es) :-
|
||||||
|
integer(N),
|
||||||
|
N >= 0,
|
||||||
|
!,
|
||||||
|
skipn(N, Es0,Es1, Es,Es2),
|
||||||
|
Es1 = [E|Es2].
|
||||||
|
nth0(N, Es0, E, Es) :-
|
||||||
|
can_be(not_less_than_zero, N),
|
||||||
|
Es0 = [E0|Es1],
|
||||||
|
nth0_elx(0,N, E0,E, Es1, Es).
|
||||||
|
|
||||||
|
nth0_elx(N0,N, E0,E, Es0, Es) :-
|
||||||
|
Es0 == [],
|
||||||
|
!,
|
||||||
|
N0 = N,
|
||||||
|
E0 = E,
|
||||||
|
Es0 = Es.
|
||||||
|
nth0_elx(N,N, E,E, Es, Es).
|
||||||
|
nth0_elx(N0,N, E0,E, [E1|Es0], [E0|Es]) :-
|
||||||
|
N1 is N0+1,
|
||||||
|
nth0_elx(N1,N, E1,E, Es0, Es).
|
||||||
|
|
||||||
|
% p.p.8.5
|
||||||
|
|
||||||
|
%% nth1(?N, ?Ls, ?E, ?Rs).
|
||||||
|
%
|
||||||
|
% Succeeds if in the N position of the list Ls, we found the element E and the rest of the list is Rs. The elements start counting from one.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- nth1(2, [1,2,3,4], 2, [1,3,4]).
|
||||||
|
% true.
|
||||||
|
% ```
|
||||||
|
nth1(N, Es0, E, Es) :-
|
||||||
|
N \== 0,
|
||||||
|
nth0(N, [_|Es0], E, [_|Es]),
|
||||||
|
N \== 0.
|
||||||
|
|
||||||
|
%% list_max(+Xs, -Max).
|
||||||
|
%
|
||||||
|
% Takes a list Xs and unifies with the maximum value of the list
|
||||||
|
list_max([N|Ns], Max) :-
|
||||||
|
foldl(lists:list_max_, Ns, N, Max).
|
||||||
|
|
||||||
|
list_max_(N, Max0, Max) :-
|
||||||
|
Max is max(N, Max0).
|
||||||
|
|
||||||
|
%% list_min(+Xs, -Min).
|
||||||
|
%
|
||||||
|
% Takes a list Xs and unifies with the minimum value of the list
|
||||||
|
list_min([N|Ns], Min) :-
|
||||||
|
foldl(lists:list_min_, Ns, N, Min).
|
||||||
|
|
||||||
|
list_min_(N, Min0, Min) :-
|
||||||
|
Min is min(N, Min0).
|
||||||
|
|
||||||
|
%% permutation(?Xs, ?Ys) is nondet.
|
||||||
|
%
|
||||||
|
% True when Xs is a permutation of Ys. This can solve for Ys given
|
||||||
|
% Xs or Xs given Ys, or even enumerate Xs and Ys together. The
|
||||||
|
% predicate `permutation/2` is primarily intended to generate
|
||||||
|
% permutations. Note that a list of length N has N! permutations,
|
||||||
|
% and unbounded permutation generation becomes prohibitively
|
||||||
|
% expensive, even for rather short lists (10! = 3,628,800).
|
||||||
|
%
|
||||||
|
% The example below illustrates that Xs and Ys being proper lists
|
||||||
|
% is not a sufficient condition to use the above replacement.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- permutation([1,2], [X,Y]).
|
||||||
|
% X = 1, Y = 2
|
||||||
|
% ; X = 2, Y = 1
|
||||||
|
% ; false.
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Throws `type_error(list, Arg)` if either argument is not a proper
|
||||||
|
% or partial list.
|
||||||
|
|
||||||
|
permutation(Xs, Ys) :-
|
||||||
|
'$skip_max_list'(Xlen, _, Xs, XTail),
|
||||||
|
'$skip_max_list'(Ylen, _, Ys, YTail),
|
||||||
|
( XTail == [], YTail == [] % both proper lists
|
||||||
|
-> Xlen == Ylen
|
||||||
|
; var(XTail), YTail == [] % partial, proper
|
||||||
|
-> length(Xs, Ylen)
|
||||||
|
; XTail == [], var(YTail) % proper, partial
|
||||||
|
-> length(Ys, Xlen)
|
||||||
|
; var(XTail), var(YTail) % partial, partial
|
||||||
|
-> length(Xs, Len),
|
||||||
|
length(Ys, Len)
|
||||||
|
; must_be(list, Xs), % either is not a list
|
||||||
|
must_be(list, Ys)
|
||||||
|
),
|
||||||
|
perm(Xs, Ys).
|
||||||
|
|
||||||
|
perm([], []).
|
||||||
|
perm(List, [First|Perm]) :-
|
||||||
|
select(First, List, Rest),
|
||||||
|
perm(Rest, Perm).
|
||||||
129
src/lib/ops_and_meta_predicates.pl
Normal file
129
src/lib/ops_and_meta_predicates.pl
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
:- op(400, yfx, /).
|
||||||
|
|
||||||
|
% 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. */
|
||||||
|
:- op(700, fx, non_counted_backtracking).
|
||||||
|
|
||||||
|
% arithmetic operators.
|
||||||
|
:- op(700, xfx, is).
|
||||||
|
:- op(500, yfx, +).
|
||||||
|
:- op(500, yfx, -).
|
||||||
|
:- op(400, yfx, *).
|
||||||
|
:- op(200, xfx, **).
|
||||||
|
:- op(200, xfy, ^).
|
||||||
|
:- op(500, yfx, /\).
|
||||||
|
:- op(500, yfx, \/).
|
||||||
|
:- op(500, yfx, xor).
|
||||||
|
:- op(400, yfx, div).
|
||||||
|
:- op(400, yfx, //).
|
||||||
|
:- op(400, yfx, rdiv).
|
||||||
|
:- op(400, yfx, <<).
|
||||||
|
:- op(400, yfx, >>).
|
||||||
|
:- op(400, yfx, mod).
|
||||||
|
:- op(400, yfx, rem).
|
||||||
|
:- op(200, fy, +).
|
||||||
|
:- op(200, fy, -).
|
||||||
|
:- op(200, fy, \).
|
||||||
|
|
||||||
|
% arithmetic comparison operators.
|
||||||
|
:- op(700, xfx, >).
|
||||||
|
:- op(700, xfx, <).
|
||||||
|
:- op(700, xfx, =\=).
|
||||||
|
:- op(700, xfx, =:=).
|
||||||
|
:- op(700, xfx, >=).
|
||||||
|
:- op(700, xfx, =<).
|
||||||
|
|
||||||
|
% term comparison.
|
||||||
|
:- op(700, xfx, ==).
|
||||||
|
:- op(700, xfx, \==).
|
||||||
|
:- op(700, xfx, @=<).
|
||||||
|
:- op(700, xfx, @>=).
|
||||||
|
:- op(700, xfx, @<).
|
||||||
|
:- op(700, xfx, @>).
|
||||||
|
|
||||||
|
% conditional operators.
|
||||||
|
:- op(1050, xfy, ->).
|
||||||
|
:- op(1100, xfy, ;).
|
||||||
|
|
||||||
|
% control.
|
||||||
|
:- op(700, xfx, =).
|
||||||
|
:- op(700, xfx, =..).
|
||||||
|
:- op(700, xfx, \=).
|
||||||
|
:- op(900, fy, \+).
|
||||||
|
|
||||||
|
:- 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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?).
|
||||||
@@ -54,42 +54,41 @@
|
|||||||
|
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
/** <module> Ordered set manipulation
|
/** Ordered set manipulation
|
||||||
|
|
||||||
Ordered sets are lists with unique elements sorted to the standard order
|
Ordered sets are lists with unique elements sorted to the standard order
|
||||||
of terms (see sort/2). Exploiting ordering, many of the set operations
|
of terms (see `sort/2`). Exploiting ordering, many of the set operations
|
||||||
can be expressed in order N rather than N^2 when dealing with unordered
|
can be expressed in order N rather than N^2 when dealing with unordered
|
||||||
sets that may contain duplicates. The library(ordsets) is available in a
|
sets that may contain duplicates. The library(ordsets) is available in a
|
||||||
number of Prolog implementations. Our predicates are designed to be
|
number of Prolog implementations. Our predicates are designed to be
|
||||||
compatible with common practice in the Prolog community. The
|
compatible with common practice in the Prolog community.
|
||||||
implementation is incomplete and relies partly on library(oset), an
|
|
||||||
older ordered set library distributed with SWI-Prolog. New applications
|
|
||||||
are advised to use library(ordsets).
|
|
||||||
Some of these predicates match directly to corresponding list
|
Some of these predicates match directly to corresponding list
|
||||||
operations. It is advised to use the versions from this library to make
|
operations. It is advised to use the versions from this library to make
|
||||||
clear you are operating on ordered sets. An exception is member/2. See
|
clear you are operating on ordered sets. An exception is `member/2`. See
|
||||||
ord_memberchk/2.
|
`ord_memberchk/2`.
|
||||||
|
|
||||||
The ordsets library is based on the standard order of terms. This
|
The ordsets library is based on the standard order of terms. This
|
||||||
implies it can handle all Prolog terms, including variables. Note
|
implies it can handle all Prolog terms, including variables. Note
|
||||||
however, that the ordering is not stable if a term inside the set is
|
however, that the ordering is not stable if a term inside the set is
|
||||||
further instantiated. Also note that variable ordering changes if
|
further instantiated. Also note that variable ordering changes if
|
||||||
variables in the set are unified with each other or a variable in the
|
variables in the set are unified with each other or a variable in the
|
||||||
set is unified with a variable that is `older' than the newest variable
|
set is unified with a variable that is _older_ than the newest variable
|
||||||
in the set. In practice, this implies that it is allowed to use
|
in the set. In practice, this implies that it is allowed to use
|
||||||
member(X, OrdSet) on an ordered set that holds variables only if X is a
|
member(X, OrdSet) on an ordered set that holds variables only if X is a
|
||||||
fresh variable. In other cases one should cease using it as an ordset
|
fresh variable. In other cases one should cease using it as an ordset
|
||||||
because the order it relies on may have been changed.
|
because the order it relies on may have been changed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
%! is_ordset(@Term) is semidet.
|
%% is_ordset(@Term) is semidet.
|
||||||
%
|
%
|
||||||
% True if Term is an ordered set. All predicates in this library
|
% True if Term is an ordered set. All predicates in this library
|
||||||
% expect ordered sets as input arguments. Failing to fullfil this
|
% expect ordered sets as input arguments. Failing to fullfil this
|
||||||
% assumption results in undefined behaviour. Typically, ordered
|
% assumption results in undefined behaviour. Typically, ordered
|
||||||
% sets are created by predicates from this library, sort/2 or
|
% sets are created by predicates from this library, `sort/2` or
|
||||||
% setof/3.
|
% `setof/3`.
|
||||||
|
|
||||||
is_ordset(Term) :-
|
is_ordset(Term) :-
|
||||||
'$skip_max_list'(_, -1, Term, Tail), Tail == [], %% is_list(Term),
|
'$skip_max_list'(_, _, Term, Tail), Tail == [], %% is_list(Term),
|
||||||
is_ordset2(Term).
|
is_ordset2(Term).
|
||||||
|
|
||||||
is_ordset2([]).
|
is_ordset2([]).
|
||||||
@@ -102,37 +101,35 @@ is_ordset3([H2|T], H) :-
|
|||||||
is_ordset3(T, H2).
|
is_ordset3(T, H2).
|
||||||
|
|
||||||
|
|
||||||
%! ord_empty(?List) is semidet.
|
%% ord_empty(?List) is semidet.
|
||||||
%
|
%
|
||||||
% True when List is the empty ordered set. Simply unifies list
|
% True when List is the empty ordered set. Simply unifies list
|
||||||
% with the empty list. Not part of Quintus.
|
% with the empty list. Not part of Quintus.
|
||||||
|
|
||||||
ord_empty([]).
|
ord_empty([]).
|
||||||
|
|
||||||
|
|
||||||
%! ord_seteq(+Set1, +Set2) is semidet.
|
%% ord_seteq(+Set1, +Set2) is semidet.
|
||||||
%
|
%
|
||||||
% True if Set1 and Set2 have the same elements. As both are
|
% True if Set1 and Set2 have the same elements. As both are
|
||||||
% canonical sorted lists, this is the same as ==/2.
|
% canonical sorted lists, this is the same as `==/2`.
|
||||||
%
|
|
||||||
% @compat sicstus
|
|
||||||
|
|
||||||
ord_seteq(Set1, Set2) :-
|
ord_seteq(Set1, Set2) :-
|
||||||
Set1 == Set2.
|
Set1 == Set2.
|
||||||
|
|
||||||
|
|
||||||
%! list_to_ord_set(+List, -OrdSet) is det.
|
%% list_to_ord_set(+List, -OrdSet) is det.
|
||||||
%
|
%
|
||||||
% Transform a list into an ordered set. This is the same as
|
% Transform a list into an ordered set. This is the same as
|
||||||
% sorting the list.
|
% sorting the list.
|
||||||
|
|
||||||
list_to_ord_set(List, Set) :-
|
list_to_ord_set(List, Set) :-
|
||||||
sort(List, Set).
|
sort(List, Set).
|
||||||
|
|
||||||
|
|
||||||
%! ord_intersect(+Set1, +Set2) is semidet.
|
%% ord_intersect(+Set1, +Set2) is semidet.
|
||||||
%
|
%
|
||||||
% True if both ordered sets have a non-empty intersection.
|
% True if both ordered sets have a non-empty intersection.
|
||||||
|
|
||||||
ord_intersect([H1|T1], L2) :-
|
ord_intersect([H1|T1], L2) :-
|
||||||
ord_intersect_(L2, H1, T1).
|
ord_intersect_(L2, H1, T1).
|
||||||
@@ -148,31 +145,29 @@ ord_intersect__(>, H1, T1, _H2, T2) :-
|
|||||||
ord_intersect_(T2, H1, T1).
|
ord_intersect_(T2, H1, T1).
|
||||||
|
|
||||||
|
|
||||||
%! ord_disjoint(+Set1, +Set2) is semidet.
|
%% ord_disjoint(+Set1, +Set2) is semidet.
|
||||||
%
|
%
|
||||||
% True if Set1 and Set2 have no common elements. This is the
|
% True if Set1 and Set2 have no common elements. This is the
|
||||||
% negation of ord_intersect/2.
|
% negation of `ord_intersect/2`.
|
||||||
|
|
||||||
ord_disjoint(Set1, Set2) :-
|
ord_disjoint(Set1, Set2) :-
|
||||||
\+ ord_intersect(Set1, Set2).
|
\+ ord_intersect(Set1, Set2).
|
||||||
|
|
||||||
|
|
||||||
%! ord_intersect(+Set1, +Set2, -Intersection)
|
%% ord_intersect(+Set1, +Set2, -Intersection)
|
||||||
%
|
%
|
||||||
% Intersection holds the common elements of Set1 and Set2.
|
% Intersection holds the common elements of Set1 and Set2.
|
||||||
%
|
%
|
||||||
% @deprecated Use ord_intersection/3
|
% This predicate is *deprecated*. Use `ord_intersection/3`
|
||||||
|
|
||||||
ord_intersect(Set1, Set2, Intersection) :-
|
ord_intersect(Set1, Set2, Intersection) :-
|
||||||
oset_int(Set1, Set2, Intersection).
|
oset_int(Set1, Set2, Intersection).
|
||||||
|
|
||||||
|
|
||||||
%! ord_intersection(+PowerSet, -Intersection)
|
%% ord_intersection(+PowerSet, -Intersection)
|
||||||
%
|
%
|
||||||
% Intersection of a powerset. True when Intersection is an ordered
|
% Intersection of a powerset. True when Intersection is an ordered
|
||||||
% set holding all elements common to all sets in PowerSet.
|
% set holding all elements common to all sets in PowerSet.
|
||||||
%
|
|
||||||
% @compat sicstus
|
|
||||||
|
|
||||||
ord_intersection(PowerSet, Intersection) :-
|
ord_intersection(PowerSet, Intersection) :-
|
||||||
key_by_length(PowerSet, Pairs),
|
key_by_length(PowerSet, Pairs),
|
||||||
@@ -190,10 +185,10 @@ l_int([_-H|T], S0, S) :-
|
|||||||
l_int(T, S1, S).
|
l_int(T, S1, S).
|
||||||
|
|
||||||
|
|
||||||
%! ord_intersection(+Set1, +Set2, -Intersection) is det.
|
%% ord_intersection(+Set1, +Set2, -Intersection) is det.
|
||||||
%
|
%
|
||||||
% Intersection holds the common elements of Set1 and Set2. Uses
|
% Intersection holds the common elements of Set1 and Set2. Uses
|
||||||
% ord_disjoint/2 if Intersection is bound to `[]` on entry.
|
% `ord_disjoint/2` if Intersection is bound to `[]` on entry.
|
||||||
|
|
||||||
ord_intersection(Set1, Set2, Intersection) :-
|
ord_intersection(Set1, Set2, Intersection) :-
|
||||||
( Intersection == []
|
( Intersection == []
|
||||||
@@ -202,13 +197,11 @@ ord_intersection(Set1, Set2, Intersection) :-
|
|||||||
).
|
).
|
||||||
|
|
||||||
|
|
||||||
%! ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
|
%% ord_intersection(+Set1, +Set2, ?Intersection, ?Difference) is det.
|
||||||
%
|
%
|
||||||
% Intersection and difference between two ordered sets.
|
% Intersection and difference between two ordered sets.
|
||||||
% Intersection is the intersection between Set1 and Set2, while
|
% Intersection is the intersection between Set1 and Set2, while
|
||||||
% Difference is defined by ord_subtract(Set2, Set1, Difference).
|
% Difference is defined by `ord_subtract(Set2, Set1, Difference)`.
|
||||||
%
|
|
||||||
% @see ord_intersection/3 and ord_subtract/3.
|
|
||||||
|
|
||||||
ord_intersection([], L, [], L) :- !.
|
ord_intersection([], L, [], L) :- !.
|
||||||
ord_intersection([_|_], [], [], []) :- !.
|
ord_intersection([_|_], [], [], []) :- !.
|
||||||
@@ -224,35 +217,35 @@ ord_intersection2(>, H1, T1, H2, T2, Intersection, [H2|HDiff]) :-
|
|||||||
ord_intersection([H1|T1], T2, Intersection, HDiff).
|
ord_intersection([H1|T1], T2, Intersection, HDiff).
|
||||||
|
|
||||||
|
|
||||||
%! ord_add_element(+Set1, +Element, ?Set2) is det.
|
%% ord_add_element(+Set1, +Element, ?Set2) is det.
|
||||||
%
|
%
|
||||||
% Insert an element into the set. This is the same as
|
% Insert an element into the set. This is the same as
|
||||||
% ord_union(Set1, [Element], Set2).
|
% `ord_union(Set1, [Element], Set2)`.
|
||||||
|
|
||||||
ord_add_element(Set1, Element, Set2) :-
|
ord_add_element(Set1, Element, Set2) :-
|
||||||
oset_addel(Set1, Element, Set2).
|
oset_addel(Set1, Element, Set2).
|
||||||
|
|
||||||
|
|
||||||
%! ord_del_element(+Set, +Element, -NewSet) is det.
|
%% ord_del_element(+Set, +Element, -NewSet) is det.
|
||||||
%
|
%
|
||||||
% Delete an element from an ordered set. This is the same as
|
% Delete an element from an ordered set. This is the same as
|
||||||
% ord_subtract(Set, [Element], NewSet).
|
% `ord_subtract(Set, [Element], NewSet)`.
|
||||||
|
|
||||||
ord_del_element(Set, Element, NewSet) :-
|
ord_del_element(Set, Element, NewSet) :-
|
||||||
oset_delel(Set, Element, NewSet).
|
oset_delel(Set, Element, NewSet).
|
||||||
|
|
||||||
|
|
||||||
%! ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
|
%% ord_selectchk(+Item, ?Set1, ?Set2) is semidet.
|
||||||
%
|
%
|
||||||
% Selectchk/3, specialised for ordered sets. Is true when
|
% `selectchk/3`, specialised for ordered sets. Is true when
|
||||||
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
|
% select(Item, Set1, Set2) and Set1, Set2 are both sorted lists
|
||||||
% without duplicates. This implementation is only expected to work
|
% without duplicates. This implementation is only expected to work
|
||||||
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
|
% for Item ground and either Set1 or Set2 ground. The "chk" suffix
|
||||||
% is meant to remind you of memberchk/2, which also expects its
|
% is meant to remind you of `memberchk/2`, which also expects its
|
||||||
% first argument to be ground. ord_selectchk(X, S, T) =>
|
% first argument to be ground. `ord_selectchk(X, S, T) =>
|
||||||
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).
|
% ord_memberchk(X, S) & \+ ord_memberchk(X, T).`
|
||||||
%
|
%
|
||||||
% @author Richard O'Keefe
|
% Author: Richard O'Keefe
|
||||||
|
|
||||||
ord_selectchk(Item, [X|Set1], [X|Set2]) :-
|
ord_selectchk(Item, [X|Set1], [X|Set2]) :-
|
||||||
X @< Item,
|
X @< Item,
|
||||||
@@ -266,19 +259,19 @@ ord_selectchk(Item, [Item|Set1], Set1) :-
|
|||||||
).
|
).
|
||||||
|
|
||||||
|
|
||||||
%! ord_memberchk(+Element, +OrdSet) is semidet.
|
%% ord_memberchk(+Element, +OrdSet) is semidet.
|
||||||
%
|
%
|
||||||
% True if Element is a member of OrdSet, compared using ==. Note
|
% True if Element is a member of OrdSet, compared using ==. Note
|
||||||
% that _enumerating_ elements of an ordered set can be done using
|
% that _enumerating_ elements of an ordered set can be done using
|
||||||
% member/2.
|
% `member/2`.
|
||||||
%
|
%
|
||||||
% Some Prolog implementations also provide ord_member/2, with the
|
% Some Prolog implementations also provide `ord_member/2`, with the
|
||||||
% same semantics as ord_memberchk/2. We believe that having a
|
% same semantics as `ord_memberchk/2`. We believe that having a
|
||||||
% semidet ord_member/2 is unacceptably inconsistent with the *_chk
|
% semidet `ord_member/2` is unacceptably inconsistent with the \*\_chk
|
||||||
% convention. Portable code should use ord_memberchk/2 or
|
% convention. Portable code should use `ord_memberchk/2` or
|
||||||
% member/2.
|
% `member/2`.
|
||||||
%
|
%
|
||||||
% @author Richard O'Keefe
|
% Author: Richard O'Keefe
|
||||||
|
|
||||||
ord_memberchk(Item, [X1,X2,X3,X4|Xs]) :-
|
ord_memberchk(Item, [X1,X2,X3,X4|Xs]) :-
|
||||||
!,
|
!,
|
||||||
@@ -303,9 +296,9 @@ ord_memberchk(Item, [X1]) :-
|
|||||||
Item == X1.
|
Item == X1.
|
||||||
|
|
||||||
|
|
||||||
%! ord_subset(+Sub, +Super) is semidet.
|
%% ord_subset(+Sub, +Super) is semidet.
|
||||||
%
|
%
|
||||||
% Is true if all elements of Sub are in Super
|
% Is true if all elements of Sub are in Super
|
||||||
|
|
||||||
ord_subset([], _).
|
ord_subset([], _).
|
||||||
ord_subset([H1|T1], [H2|T2]) :-
|
ord_subset([H1|T1], [H2|T2]) :-
|
||||||
@@ -319,22 +312,20 @@ ord_subset_(=, _, T1, T2) :-
|
|||||||
ord_subset(T1, T2).
|
ord_subset(T1, T2).
|
||||||
|
|
||||||
|
|
||||||
%! ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
|
%% ord_subtract(+InOSet, +NotInOSet, -Diff) is det.
|
||||||
%
|
%
|
||||||
% Diff is the set holding all elements of InOSet that are not in
|
% Diff is the set holding all elements of InOSet that are not in
|
||||||
% NotInOSet.
|
% NotInOSet.
|
||||||
|
|
||||||
ord_subtract(InOSet, NotInOSet, Diff) :-
|
ord_subtract(InOSet, NotInOSet, Diff) :-
|
||||||
oset_diff(InOSet, NotInOSet, Diff).
|
oset_diff(InOSet, NotInOSet, Diff).
|
||||||
|
|
||||||
|
|
||||||
%! ord_union(+SetOfSets, -Union) is det.
|
%% ord_union(+SetOfSets, -Union) is det.
|
||||||
%
|
%
|
||||||
% True if Union is the union of all elements in the superset
|
% True if Union is the union of all elements in the superset
|
||||||
% SetOfSets. Each member of SetOfSets must be an ordered set, the
|
% SetOfSets. Each member of SetOfSets must be an ordered set, the
|
||||||
% sets need not be ordered in any way.
|
% sets need not be ordered in any way.
|
||||||
%
|
|
||||||
% @author Copied from YAP, probably originally by Richard O'Keefe.
|
|
||||||
|
|
||||||
ord_union([], []).
|
ord_union([], []).
|
||||||
ord_union([Set|Sets], Union) :-
|
ord_union([Set|Sets], Union) :-
|
||||||
@@ -355,18 +346,18 @@ ord_union_all(N, Sets0, Union, Sets) :-
|
|||||||
).
|
).
|
||||||
|
|
||||||
|
|
||||||
%! ord_union(+Set1, +Set2, ?Union) is det.
|
%% ord_union(+Set1, +Set2, ?Union) is det.
|
||||||
%
|
%
|
||||||
% Union is the union of Set1 and Set2
|
% Union is the union of Set1 and Set2
|
||||||
|
|
||||||
ord_union(Set1, Set2, Union) :-
|
ord_union(Set1, Set2, Union) :-
|
||||||
oset_union(Set1, Set2, Union).
|
oset_union(Set1, Set2, Union).
|
||||||
|
|
||||||
|
|
||||||
%! ord_union(+Set1, +Set2, -Union, -New) is det.
|
%% ord_union(+Set1, +Set2, -Union, -New) is det.
|
||||||
%
|
%
|
||||||
% True iff ord_union(Set1, Set2, Union) and
|
% True iff `ord_union(Set1, Set2, Union)` and
|
||||||
% ord_subtract(Set2, Set1, New).
|
% `ord_subtract(Set2, Set1, New)`.
|
||||||
|
|
||||||
ord_union([], Set2, Set2, Set2).
|
ord_union([], Set2, Set2, Set2).
|
||||||
ord_union([H|T], Set2, Union, New) :-
|
ord_union([H|T], Set2, Union, New) :-
|
||||||
@@ -390,26 +381,26 @@ ord_union_2([H|T], H2, T2, Union, New) :-
|
|||||||
ord_union(Order, H, T, H2, T2, Union, New).
|
ord_union(Order, H, T, H2, T2, Union, New).
|
||||||
|
|
||||||
|
|
||||||
%! ord_symdiff(+Set1, +Set2, ?Difference) is det.
|
%% ord_symdiff(+Set1, +Set2, ?Difference) is det.
|
||||||
%
|
%
|
||||||
% Is true when Difference is the symmetric difference of Set1 and
|
% Is true when Difference is the symmetric difference of Set1 and
|
||||||
% Set2. I.e., Difference contains all elements that are not in the
|
% Set2. I.e., Difference contains all elements that are not in the
|
||||||
% intersection of Set1 and Set2. The semantics is the same as the
|
% intersection of Set1 and Set2. The semantics is the same as the
|
||||||
% sequence below (but the actual implementation requires only a
|
% sequence below (but the actual implementation requires only a
|
||||||
% single scan).
|
% single scan).
|
||||||
%
|
%
|
||||||
% ==
|
% ```
|
||||||
% ord_union(Set1, Set2, Union),
|
% ord_union(Set1, Set2, Union),
|
||||||
% ord_intersection(Set1, Set2, Intersection),
|
% ord_intersection(Set1, Set2, Intersection),
|
||||||
% ord_subtract(Union, Intersection, Difference).
|
% ord_subtract(Union, Intersection, Difference).
|
||||||
% ==
|
% ```
|
||||||
%
|
%
|
||||||
% For example:
|
% For example:
|
||||||
%
|
%
|
||||||
% ==
|
% ```
|
||||||
% ?- ord_symdiff([1,2], [2,3], X).
|
% ?- ord_symdiff([1,2], [2,3], X).
|
||||||
% X = [1,3].
|
% X = [1,3].
|
||||||
% ==
|
% ```
|
||||||
|
|
||||||
ord_symdiff([], Set2, Set2).
|
ord_symdiff([], Set2, Set2).
|
||||||
ord_symdiff([H1|T1], Set2, Difference) :-
|
ord_symdiff([H1|T1], Set2, Difference) :-
|
||||||
@@ -457,7 +448,7 @@ ord_symdiff(>, H1, T1, H2, Set2, [H2|Difference]) :-
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
/** <module> Ordered set manipulation
|
/* Ordered set manipulation
|
||||||
|
|
||||||
This library defines set operations on sets represented as ordered
|
This library defines set operations on sets represented as ordered
|
||||||
lists.
|
lists.
|
||||||
112
src/lib/os.pl
Normal file
112
src/lib/os.pl
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Predicates for reasoning about the operating system (OS) environment.
|
||||||
|
Written July 2020 by Markus Triska (triska@metalevel.at).
|
||||||
|
|
||||||
|
Lists of characters are used throughout to represent keys and values.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
?- getenv("LANG", Ls).
|
||||||
|
Ls = "en_US.UTF-8".
|
||||||
|
|
||||||
|
Public domain code.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
/** Predicates for reasoning about the operating system (OS) environment.
|
||||||
|
|
||||||
|
This includes predicates about environment variables, calls to shell and
|
||||||
|
finding out the PID of the running system.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(os, [getenv/2,
|
||||||
|
setenv/2,
|
||||||
|
unsetenv/1,
|
||||||
|
shell/1,
|
||||||
|
shell/2,
|
||||||
|
pid/1]).
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(si)).
|
||||||
|
|
||||||
|
%% getenv(+Key, -Value).
|
||||||
|
%
|
||||||
|
% True iff Value contains the value of the environment variable Key.
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- getenv("LANG", Ls).
|
||||||
|
% Ls = "en_US.UTF-8".
|
||||||
|
% ```
|
||||||
|
getenv(Key, Value) :-
|
||||||
|
must_be_env_var(Key),
|
||||||
|
'$getenv'(Key, Value).
|
||||||
|
|
||||||
|
%% setenv(+Key, +Value).
|
||||||
|
%
|
||||||
|
% Sets the environment variable Key to Value
|
||||||
|
setenv(Key, Value) :-
|
||||||
|
must_be_env_var(Key),
|
||||||
|
must_be_chars(Value),
|
||||||
|
'$setenv'(Key, Value).
|
||||||
|
|
||||||
|
%% unsetenv(+Key).
|
||||||
|
%
|
||||||
|
% Unsets the environment variable Key
|
||||||
|
unsetenv(Key) :-
|
||||||
|
must_be_env_var(Key),
|
||||||
|
'$unsetenv'(Key).
|
||||||
|
|
||||||
|
%% shell(+Command)
|
||||||
|
%
|
||||||
|
% Equivalent to `shell(Command, 0)`.
|
||||||
|
shell(Command) :- shell(Command, 0).
|
||||||
|
|
||||||
|
%% shell(+Command, -Status).
|
||||||
|
%
|
||||||
|
% True iff executes Command in a shell of the operating system and the exit code is Status.
|
||||||
|
% Keep in mind the shell syntax is dependant on the operating system, so it should be
|
||||||
|
% used very carefully.
|
||||||
|
%
|
||||||
|
% Example (using Linux and fish shell):
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- shell("echo $SHELL", Status).
|
||||||
|
% /bin/fish
|
||||||
|
% Status = 0.
|
||||||
|
% ```
|
||||||
|
shell(Command, Status) :-
|
||||||
|
must_be_chars(Command),
|
||||||
|
can_be(integer, Status),
|
||||||
|
'$shell'(Command, Status).
|
||||||
|
|
||||||
|
%% pid(-PID).
|
||||||
|
%
|
||||||
|
% True iff PID is the process identification number of current Scryer Prolog instance.
|
||||||
|
pid(PID) :-
|
||||||
|
can_be(integer, PID),
|
||||||
|
'$pid'(PID).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
For now, we only support a restricted subset of variable names.
|
||||||
|
|
||||||
|
The reason is that Rust may panic if a key is empty, contains an
|
||||||
|
ASCII equals sign '=' or the NUL character '\0', or when the value
|
||||||
|
contains the NUL character.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
must_be_env_var(Cs) :-
|
||||||
|
must_be_chars(Cs),
|
||||||
|
Cs = [_|_],
|
||||||
|
( maplist(permitted, Cs) -> true
|
||||||
|
; domain_error(env_var, Cs, os)
|
||||||
|
).
|
||||||
|
|
||||||
|
permitted(C) :- char_type(C, alnum).
|
||||||
|
permitted(C) :- char_type(C, ascii_punctuation).
|
||||||
|
permitted('_').
|
||||||
|
|
||||||
|
must_be_chars(Cs) :-
|
||||||
|
must_be(list, Cs),
|
||||||
|
maplist(must_be(character), Cs).
|
||||||
55
src/lib/pairs.pl
Normal file
55
src/lib/pairs.pl
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/** Reasoning about pairs.
|
||||||
|
|
||||||
|
Pairs are Prolog terms with principal functor `(-)/2`. A pair
|
||||||
|
often has the form `Key-Value`. The predicates of this library
|
||||||
|
relate pairs to keys and values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(pairs, [pairs_keys_values/3,
|
||||||
|
pairs_keys/2,
|
||||||
|
pairs_values/2,
|
||||||
|
group_pairs_by_key/2,
|
||||||
|
map_list_to_pairs/3]).
|
||||||
|
|
||||||
|
|
||||||
|
:- meta_predicate map_list_to_pairs(2, ?, ?).
|
||||||
|
|
||||||
|
%% pairs_keys_values(?Pairs, ?Keys, ?Values)
|
||||||
|
%
|
||||||
|
% The first argument is a list of Pairs, the second the corresponding
|
||||||
|
% Keys, and the third argument the corresponding values.
|
||||||
|
|
||||||
|
pairs_keys_values([], [], []).
|
||||||
|
pairs_keys_values([A-B|ABs], [A|As], [B|Bs]) :-
|
||||||
|
pairs_keys_values(ABs, As, Bs).
|
||||||
|
|
||||||
|
%% pairs_keys(?Pairs, ?Keys)
|
||||||
|
%
|
||||||
|
% Same as `pairs_keys_values(Pairs, Keys, _)`.
|
||||||
|
|
||||||
|
pairs_keys(Ps, Ks) :- pairs_keys_values(Ps, Ks, _).
|
||||||
|
|
||||||
|
%% pairs_values(?Pairs, ?Values)
|
||||||
|
%
|
||||||
|
% Same as `pairs_keys_values(Pairs, _, Values)`.
|
||||||
|
|
||||||
|
pairs_values(Ps, Vs) :- pairs_keys_values(Ps, _, Vs).
|
||||||
|
|
||||||
|
map_list_to_pairs(Pred, Ls, Ps) :-
|
||||||
|
map_list_to_pairs2(Ls, Pred, Ps).
|
||||||
|
|
||||||
|
map_list_to_pairs2([], _, []).
|
||||||
|
map_list_to_pairs2([H|T0], Pred, [K-H|T]) :-
|
||||||
|
call(Pred, H, K),
|
||||||
|
map_list_to_pairs2(T0, Pred, T).
|
||||||
|
|
||||||
|
|
||||||
|
group_pairs_by_key([], []).
|
||||||
|
group_pairs_by_key([K-V|KVs0], [K-[V|Vs]|KVs]) :-
|
||||||
|
same_key(K, KVs0, Vs, KVs1),
|
||||||
|
group_pairs_by_key(KVs1, KVs).
|
||||||
|
|
||||||
|
same_key(K0, [K1-V|KVs0], [V|Vs], KVs) :-
|
||||||
|
K0 == K1, !,
|
||||||
|
same_key(K0, KVs0, Vs, KVs).
|
||||||
|
same_key(_, KVs, [], KVs).
|
||||||
118
src/lib/pio.pl
Normal file
118
src/lib/pio.pl
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/** Pure I/O.
|
||||||
|
|
||||||
|
Our goal is to encourage the use of definite clause grammars (DCGs)
|
||||||
|
for describing strings. The predicates `phrase_from_file/[2,3]`,
|
||||||
|
`phrase_to_file/[2,3]` and `phrase_to_stream/2` let us apply DCGs
|
||||||
|
transparently to files and streams, and therefore decouple side-effects
|
||||||
|
from declarative descriptions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(pio, [phrase_from_file/2,
|
||||||
|
phrase_from_file/3,
|
||||||
|
phrase_to_file/2,
|
||||||
|
phrase_to_file/3,
|
||||||
|
phrase_to_stream/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(freeze)).
|
||||||
|
:- use_module(library(iso_ext), [setup_call_cleanup/3, partial_string/3]).
|
||||||
|
:- use_module(library(lists), [member/2, maplist/2]).
|
||||||
|
:- use_module(library(charsio), [get_n_chars/3]).
|
||||||
|
|
||||||
|
:- meta_predicate(phrase_from_file(2, ?)).
|
||||||
|
:- meta_predicate(phrase_from_file(2, ?, ?)).
|
||||||
|
:- meta_predicate(phrase_to_file(2, ?)).
|
||||||
|
:- meta_predicate(phrase_to_file(2, ?, ?)).
|
||||||
|
:- meta_predicate(phrase_to_stream(2, ?)).
|
||||||
|
|
||||||
|
%% phrase_from_file(+GRBody, +File)
|
||||||
|
%
|
||||||
|
% True if grammar rule body GRBody covers the contents of File,
|
||||||
|
% represented as a list of characters.
|
||||||
|
|
||||||
|
phrase_from_file(NT, File) :-
|
||||||
|
phrase_from_file(NT, File, []).
|
||||||
|
|
||||||
|
%% phrase_from_file(+GRBody, +File, +Options)
|
||||||
|
%
|
||||||
|
% Like `phrase_from_file/2`, using Options to open the file.
|
||||||
|
|
||||||
|
phrase_from_file(NT, File, Options) :-
|
||||||
|
( var(File) -> instantiation_error(phrase_from_file/3)
|
||||||
|
; must_be(list, Options),
|
||||||
|
( member(Var, Options), var(Var) -> instantiation_error(phrase_from_file/3)
|
||||||
|
; member(type(Type), Options) ->
|
||||||
|
must_be(atom, Type),
|
||||||
|
member(Type, [text,binary])
|
||||||
|
; Type = text
|
||||||
|
),
|
||||||
|
setup_call_cleanup(open(File, read, Stream, [reposition(true)|Options]),
|
||||||
|
( stream_to_lazy_list(Stream, Xs),
|
||||||
|
phrase(NT, Xs) ),
|
||||||
|
close(Stream))
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
stream_to_lazy_list(Stream, Xs) :-
|
||||||
|
stream_property(Stream, position(Pos)),
|
||||||
|
freeze(Xs, reader_step(Stream, Pos, Xs)).
|
||||||
|
|
||||||
|
reader_step(Stream, Pos, Xs0) :-
|
||||||
|
set_stream_position(Stream, Pos),
|
||||||
|
( at_end_of_stream(Stream)
|
||||||
|
-> Xs0 = []
|
||||||
|
; get_n_chars(Stream, 4096, Cs),
|
||||||
|
partial_string(Cs, Xs0, Xs),
|
||||||
|
stream_to_lazy_list(Stream, Xs)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% phrase_to_stream(+GRBody, +Stream)
|
||||||
|
%
|
||||||
|
% Emit the list of characters described by the grammar rule body
|
||||||
|
% GRBody to Stream.
|
||||||
|
%
|
||||||
|
% An ideal implementation of `phrase_to_stream/2` writes each
|
||||||
|
% character as soon as it becomes known and no choice-points remain,
|
||||||
|
% and thus avoids the manifestation of the entire string in memory.
|
||||||
|
% See [#691](https://github.com/mthom/scryer-prolog/issues/691) for
|
||||||
|
% more information.
|
||||||
|
%
|
||||||
|
% The current preliminary implementation is provided so that Prolog
|
||||||
|
% programmers can already get used to describing output with DCGs,
|
||||||
|
% and then writing it to a file when necessary. This simple
|
||||||
|
% implementation suffices as long as the entire contents can be
|
||||||
|
% represented in memory, and thus covers a large number of use cases.
|
||||||
|
|
||||||
|
phrase_to_stream(GRBody, Stream) :-
|
||||||
|
phrase(GRBody, Cs),
|
||||||
|
must_be(chars, Cs),
|
||||||
|
( stream_property(Stream, type(binary)) ->
|
||||||
|
( '$first_non_octet'(Cs, N) ->
|
||||||
|
domain_error(octet_character, N, phrase_to_stream/2)
|
||||||
|
; true
|
||||||
|
)
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
% we use a specialised internal predicate that uses only a
|
||||||
|
% single "write" operation for efficiency. It is equivalent to
|
||||||
|
% maplist(put_char(Stream), Cs). It also works for binary streams.
|
||||||
|
'$put_chars'(Stream, Cs).
|
||||||
|
|
||||||
|
%% phrase_to_file(+GRBody, +File)
|
||||||
|
%
|
||||||
|
% Write the string described by GRBody to File.
|
||||||
|
|
||||||
|
phrase_to_file(GRBody, File) :-
|
||||||
|
phrase_to_file(GRBody, File, []).
|
||||||
|
|
||||||
|
|
||||||
|
%% phrase_to_file(+GRBody, +File, +Options)
|
||||||
|
%
|
||||||
|
% Like `phrase_to_file/2`, using Options to open the file.
|
||||||
|
|
||||||
|
phrase_to_file(GRBody, File, Options) :-
|
||||||
|
setup_call_cleanup(open(File, write, Stream, Options),
|
||||||
|
phrase_to_stream(GRBody, Stream),
|
||||||
|
close(Stream)).
|
||||||
77
src/lib/random.pl
Normal file
77
src/lib/random.pl
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
This library provides probabilistic predicates and random number generators.
|
||||||
|
|
||||||
|
To retain desirable declarative properties, predicates that internally
|
||||||
|
use random numbers should be equipped with an argument that specifies
|
||||||
|
the random seed. This makes everything completely reproducible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(random, [maybe/0, random/1, random_integer/3, set_random/1]).
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
%% maybe.
|
||||||
|
%
|
||||||
|
% Succeeds with probability 0.5.
|
||||||
|
maybe :- '$maybe'.
|
||||||
|
|
||||||
|
% The higher the precision, the slower it gets.
|
||||||
|
random_number_precision(64).
|
||||||
|
|
||||||
|
%% random(-R).
|
||||||
|
%
|
||||||
|
% Generates a random floating number between 0 (inclusive) and 1 (exclusive).
|
||||||
|
random(R) :-
|
||||||
|
var(R),
|
||||||
|
random_number_precision(N),
|
||||||
|
rnd(N, R).
|
||||||
|
|
||||||
|
%% random_integer(+Lower, +Upper, -R).
|
||||||
|
%
|
||||||
|
% Generates a random integer number between Lower (inclusive) and Upper (exclusive).
|
||||||
|
%
|
||||||
|
% Throws `instantiation_error` if Lower or Upper are variables.
|
||||||
|
%
|
||||||
|
% Throws `type_error` if Lower or Upper aren't integers.
|
||||||
|
random_integer(Lower, Upper, R) :-
|
||||||
|
var(R),
|
||||||
|
( (var(Lower) ; var(Upper)) ->
|
||||||
|
instantiation_error(random_integer/3)
|
||||||
|
; \+ integer(Lower) ->
|
||||||
|
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)
|
||||||
|
).
|
||||||
|
|
||||||
|
rnd(N, R) :-
|
||||||
|
rnd_(N, 0, R).
|
||||||
|
|
||||||
|
rnd_(0, R, R) :- !.
|
||||||
|
rnd_(N, R0, R) :-
|
||||||
|
maybe,
|
||||||
|
!,
|
||||||
|
N1 is N - 1,
|
||||||
|
rnd_(N1, R0, R).
|
||||||
|
rnd_(N, R0, R) :-
|
||||||
|
N1 is N - 1,
|
||||||
|
R1 is R0 + 1.0 / 2.0 ^ N,
|
||||||
|
rnd_(N1, R1, R).
|
||||||
|
|
||||||
|
%% set_random(+Seed).
|
||||||
|
%
|
||||||
|
% Sets a seed that will be used for subsequent random generations in this library.
|
||||||
|
% It's necessary to set a seed to provide reproducible executions using this library.
|
||||||
|
set_random(Seed) :-
|
||||||
|
( nonvar(Seed) ->
|
||||||
|
( Seed = seed(S) ->
|
||||||
|
( var(S) -> instantiation_error(set_random/1)
|
||||||
|
; integer(S) -> '$set_seed'(S)
|
||||||
|
; type_error(integer, S, set_random/1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
; instantiation_error(set_random/1)
|
||||||
|
).
|
||||||
|
|
||||||
@@ -1,9 +1,24 @@
|
|||||||
|
/** Predicates from [*Indexing dif/2*](https://arxiv.org/abs/1607.01590).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- tfilter(=(a), [X,Y], Es).
|
||||||
|
X = a, Y = a, Es = "aa"
|
||||||
|
; X = a, Es = "a", dif:dif(a,Y)
|
||||||
|
; Y = a, Es = "a", dif:dif(a,X)
|
||||||
|
; Es = [], dif:dif(a,X), dif:dif(a,Y).
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
:- module(reif, [if_/3, (=)/3, (',')/3, (;)/3, cond_t/3, dif/3,
|
:- module(reif, [if_/3, (=)/3, (',')/3, (;)/3, cond_t/3, dif/3,
|
||||||
memberd_t/3, tfilter/3, tmember/2, tmember_t/3,
|
memberd_t/3, tfilter/3, tmember/2, tmember_t/3,
|
||||||
tpartition/4]).
|
tpartition/4]).
|
||||||
|
|
||||||
:- use_module(library(dif)).
|
:- use_module(library(dif)).
|
||||||
|
|
||||||
|
:- meta_predicate(if_(1, 0, 0)).
|
||||||
|
|
||||||
if_(If_1, Then_0, Else_0) :-
|
if_(If_1, Then_0, Else_0) :-
|
||||||
call(If_1, T),
|
call(If_1, T),
|
||||||
( T == true -> call(Then_0)
|
( T == true -> call(Then_0)
|
||||||
@@ -26,13 +41,14 @@ dif(X, Y, T) :-
|
|||||||
non(true, false).
|
non(true, false).
|
||||||
non(false, true).
|
non(false, true).
|
||||||
|
|
||||||
tfilter(C_2, Es, Fs) :-
|
:- meta_predicate(tfilter(2, ?, ?)).
|
||||||
i_tfilter(Es, C_2, Fs).
|
|
||||||
|
|
||||||
i_tfilter([], _, []).
|
tfilter(_, [], []).
|
||||||
i_tfilter([E|Es], C_2, Fs0) :-
|
tfilter(C_2, [E|Es], Fs0) :-
|
||||||
if_(call(C_2, E), Fs0 = [E|Fs], Fs0 = Fs),
|
if_(call(C_2, E), Fs0 = [E|Fs], Fs0 = Fs),
|
||||||
i_tfilter(Es, C_2, Fs).
|
tfilter(C_2, Es, Fs).
|
||||||
|
|
||||||
|
:- meta_predicate(tpartition(2, ?, ?, ?)).
|
||||||
|
|
||||||
tpartition(P_2, Xs, Ts, Fs) :-
|
tpartition(P_2, Xs, Ts, Fs) :-
|
||||||
i_tpartition(Xs, P_2, Ts, Fs).
|
i_tpartition(Xs, P_2, Ts, Fs).
|
||||||
@@ -44,12 +60,18 @@ i_tpartition([X|Xs], P_2, Ts0, Fs0) :-
|
|||||||
, ( Fs0 = [X|Fs], Ts0 = Ts ) ),
|
, ( Fs0 = [X|Fs], Ts0 = Ts ) ),
|
||||||
i_tpartition(Xs, P_2, Ts, Fs).
|
i_tpartition(Xs, P_2, Ts, Fs).
|
||||||
|
|
||||||
|
:- meta_predicate(','(1, 1, ?)).
|
||||||
|
|
||||||
','(A_1, B_1, T) :-
|
','(A_1, B_1, T) :-
|
||||||
if_(A_1, call(B_1, T), T = false).
|
if_(A_1, call(B_1, T), T = false).
|
||||||
|
|
||||||
|
:- meta_predicate(';'(1, 1, ?)).
|
||||||
|
|
||||||
';'(A_1, B_1, T) :-
|
';'(A_1, B_1, T) :-
|
||||||
if_(A_1, T = true, call(B_1, T)).
|
if_(A_1, T = true, call(B_1, T)).
|
||||||
|
|
||||||
|
:- meta_predicate(cond_t(1, 0, ?)).
|
||||||
|
|
||||||
cond_t(If_1, Then_0, T) :-
|
cond_t(If_1, Then_0, T) :-
|
||||||
if_(If_1, ( Then_0, T = true ), T = false ).
|
if_(If_1, ( Then_0, T = true ), T = false ).
|
||||||
|
|
||||||
@@ -60,8 +82,13 @@ i_memberd_t([], _, false).
|
|||||||
i_memberd_t([X|Xs], E, T) :-
|
i_memberd_t([X|Xs], E, T) :-
|
||||||
if_( X = E, T = true, i_memberd_t(Xs, E, T) ).
|
if_( X = E, T = true, i_memberd_t(Xs, E, T) ).
|
||||||
|
|
||||||
|
:- meta_predicate(tmember(2, ?)).
|
||||||
|
|
||||||
tmember(P_2, [X|Xs]) :-
|
tmember(P_2, [X|Xs]) :-
|
||||||
if_( call(P_2, X), true, tmember(P_2, Xs) ).
|
if_( call(P_2, X), true, tmember(P_2, Xs) ).
|
||||||
|
|
||||||
|
:- meta_predicate(tmember_t(2, ?, ?)).
|
||||||
|
|
||||||
|
tmember_t(_P_2, [], false).
|
||||||
tmember_t(P_2, [X|Xs], T) :-
|
tmember_t(P_2, [X|Xs], T) :-
|
||||||
if_( call(P_2, X), T = true, tmember_t(P_2, Xs, T) ).
|
if_( call(P_2, X), T = true, tmember_t(P_2, Xs, T) ).
|
||||||
166
src/lib/serialization/abnf.pl
Normal file
166
src/lib/serialization/abnf.pl
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written Apr 2021 by Aram Panasenco (panasenco@ucla.edu)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
|
||||||
|
[Core Rules](https://tools.ietf.org/html/rfc5234#appendix-B.1) of the
|
||||||
|
Augmented Backus-Naur Form specification (ABNF - RFC 5234). ABNF commonly
|
||||||
|
serves as the definition language for IETF communication protocols, so
|
||||||
|
having these DCGs can be extremely useful for reasoning about most IETF
|
||||||
|
syntaxes. The DCGs are presented in the order they appear in the RFC.
|
||||||
|
While some DCGs below use `char_type/2`, the most common ones are defined
|
||||||
|
manually in order to take advantage of Prolog's first-argument indexing.
|
||||||
|
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2021, Aram Panasenco
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
:- module(abnf, [abnf_alpha//1,
|
||||||
|
abnf_bit//1,
|
||||||
|
abnf_char//1,
|
||||||
|
abnf_cr//0,
|
||||||
|
abnf_crlf//0,
|
||||||
|
abnf_ctl//1,
|
||||||
|
abnf_digit//1,
|
||||||
|
abnf_dquote//0,
|
||||||
|
abnf_hexdig//1,
|
||||||
|
abnf_htab//0,
|
||||||
|
abnf_lf//0,
|
||||||
|
abnf_lwsp//0,
|
||||||
|
abnf_octet//1,
|
||||||
|
abnf_sp//0,
|
||||||
|
abnf_vchar//1,
|
||||||
|
abnf_wsp//0 ]).
|
||||||
|
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(dif)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
abnf_alpha('a') --> "a".
|
||||||
|
abnf_alpha('b') --> "b".
|
||||||
|
abnf_alpha('c') --> "c".
|
||||||
|
abnf_alpha('d') --> "d".
|
||||||
|
abnf_alpha('e') --> "e".
|
||||||
|
abnf_alpha('f') --> "f".
|
||||||
|
abnf_alpha('g') --> "g".
|
||||||
|
abnf_alpha('h') --> "h".
|
||||||
|
abnf_alpha('i') --> "i".
|
||||||
|
abnf_alpha('j') --> "j".
|
||||||
|
abnf_alpha('k') --> "k".
|
||||||
|
abnf_alpha('l') --> "l".
|
||||||
|
abnf_alpha('m') --> "m".
|
||||||
|
abnf_alpha('n') --> "n".
|
||||||
|
abnf_alpha('o') --> "o".
|
||||||
|
abnf_alpha('p') --> "p".
|
||||||
|
abnf_alpha('q') --> "q".
|
||||||
|
abnf_alpha('r') --> "r".
|
||||||
|
abnf_alpha('s') --> "s".
|
||||||
|
abnf_alpha('t') --> "t".
|
||||||
|
abnf_alpha('u') --> "u".
|
||||||
|
abnf_alpha('v') --> "v".
|
||||||
|
abnf_alpha('w') --> "w".
|
||||||
|
abnf_alpha('x') --> "x".
|
||||||
|
abnf_alpha('y') --> "y".
|
||||||
|
abnf_alpha('z') --> "z".
|
||||||
|
abnf_alpha('A') --> "A".
|
||||||
|
abnf_alpha('B') --> "B".
|
||||||
|
abnf_alpha('C') --> "C".
|
||||||
|
abnf_alpha('D') --> "D".
|
||||||
|
abnf_alpha('E') --> "E".
|
||||||
|
abnf_alpha('F') --> "F".
|
||||||
|
abnf_alpha('G') --> "G".
|
||||||
|
abnf_alpha('H') --> "H".
|
||||||
|
abnf_alpha('I') --> "I".
|
||||||
|
abnf_alpha('J') --> "J".
|
||||||
|
abnf_alpha('K') --> "K".
|
||||||
|
abnf_alpha('L') --> "L".
|
||||||
|
abnf_alpha('M') --> "M".
|
||||||
|
abnf_alpha('N') --> "N".
|
||||||
|
abnf_alpha('O') --> "O".
|
||||||
|
abnf_alpha('P') --> "P".
|
||||||
|
abnf_alpha('Q') --> "Q".
|
||||||
|
abnf_alpha('R') --> "R".
|
||||||
|
abnf_alpha('S') --> "S".
|
||||||
|
abnf_alpha('T') --> "T".
|
||||||
|
abnf_alpha('U') --> "U".
|
||||||
|
abnf_alpha('V') --> "V".
|
||||||
|
abnf_alpha('W') --> "W".
|
||||||
|
abnf_alpha('X') --> "X".
|
||||||
|
abnf_alpha('Y') --> "Y".
|
||||||
|
abnf_alpha('Z') --> "Z".
|
||||||
|
|
||||||
|
abnf_bit('0') --> "0".
|
||||||
|
abnf_bit('1') --> "1".
|
||||||
|
|
||||||
|
abnf_char(Char) --> [Char], { dif(Char, '\x0000\'), char_type(Char, ascii) }. %'
|
||||||
|
|
||||||
|
abnf_cr --> "\r".
|
||||||
|
|
||||||
|
abnf_crlf --> "\r\n".
|
||||||
|
|
||||||
|
abnf_ctl(Char) --> [Char], { char_type(Char, ascii), char_type(Char, control) }.
|
||||||
|
|
||||||
|
abnf_digit('0') --> "0".
|
||||||
|
abnf_digit('1') --> "1".
|
||||||
|
abnf_digit('2') --> "2".
|
||||||
|
abnf_digit('3') --> "3".
|
||||||
|
abnf_digit('4') --> "4".
|
||||||
|
abnf_digit('5') --> "5".
|
||||||
|
abnf_digit('6') --> "6".
|
||||||
|
abnf_digit('7') --> "7".
|
||||||
|
abnf_digit('8') --> "8".
|
||||||
|
abnf_digit('9') --> "9".
|
||||||
|
|
||||||
|
abnf_dquote --> "\"".
|
||||||
|
|
||||||
|
abnf_hexdig(Char) --> abnf_digit(Char).
|
||||||
|
abnf_hexdig('A') --> "A".
|
||||||
|
abnf_hexdig('B') --> "B".
|
||||||
|
abnf_hexdig('C') --> "C".
|
||||||
|
abnf_hexdig('D') --> "D".
|
||||||
|
abnf_hexdig('E') --> "E".
|
||||||
|
abnf_hexdig('F') --> "F".
|
||||||
|
|
||||||
|
abnf_htab --> "\t".
|
||||||
|
|
||||||
|
abnf_lf --> "\n".
|
||||||
|
|
||||||
|
abnf_lwsp --> "".
|
||||||
|
abnf_lwsp --> abnf_wsp, abnf_lwsp.
|
||||||
|
abnf_lwsp --> abnf_crlf, abnf_wsp, abnf_lwsp.
|
||||||
|
|
||||||
|
abnf_octet(Char) --> [Char], char_type(Char, octet).
|
||||||
|
|
||||||
|
abnf_sp --> " ".
|
||||||
|
|
||||||
|
abnf_vchar(Char) --> [Char], char_type(Char, ascii_graphic).
|
||||||
|
|
||||||
|
abnf_wsp --> abnf_sp.
|
||||||
|
abnf_wsp --> abnf_htab.
|
||||||
273
src/lib/serialization/json.pl
Normal file
273
src/lib/serialization/json.pl
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written Apr 2021 by Aram Panasenco (panasenco@ucla.edu)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
|
||||||
|
`json_chars//1` can be used with [`phrase_from_file/2`](src/lib/pio.pl)
|
||||||
|
or [`phrase/2`](src/lib/dcgs.pl) to parse and generate [JSON](https://www.json.org/json-en.html).
|
||||||
|
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2021, Aram Panasenco
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
:- module(json, [
|
||||||
|
json_chars//1
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(dif)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
/* The DCGs are written to match the McKeeman form presented on the right side of https://www.json.org/json-en.html
|
||||||
|
as closely as possible. Note that the names in the McKeeman form conflict with the pictures on the site. */
|
||||||
|
json_chars(Internal) --> json_element(Internal).
|
||||||
|
|
||||||
|
/* Because it's impossible to distinguish between an empty array [] and an empty string "", we distinguish between
|
||||||
|
different types of values based on their principal functor. The principal functors match the types defined in
|
||||||
|
the JSON Schema spec here: https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.1.1
|
||||||
|
EXCEPT we don't yet support the integer type. There are plans for more JSON Schema support in the near future. */
|
||||||
|
json_value(pairs(Pairs)) --> json_object(Pairs).
|
||||||
|
json_value(list(List)) --> json_array(List).
|
||||||
|
json_value(string(Chars)) --> json_string(Chars).
|
||||||
|
json_value(number(Number)) --> json_number(Number).
|
||||||
|
json_value(boolean(Bool)) --> json_boolean(Bool).
|
||||||
|
json_value(null) --> "null".
|
||||||
|
|
||||||
|
/* We pull json_boolean out into its own predicate in order to take advantage of first argument indexing and not leave
|
||||||
|
choice points. For more details, watch this video on decomposing arguments: https://youtu.be/FZLofckPu4A?t=1648 */
|
||||||
|
json_boolean(true) --> "true".
|
||||||
|
json_boolean(false) --> "false".
|
||||||
|
|
||||||
|
json_object([]) --> "{", json_ws, "}".
|
||||||
|
json_object([Pair|Pairs]) -->
|
||||||
|
"{",
|
||||||
|
json_members(Pairs, Pair),
|
||||||
|
"}".
|
||||||
|
|
||||||
|
/* `json_members//2` below is implemented with a lagged argument to take advantage of first argument indexing.
|
||||||
|
This is a pure performance-driven decision that doesn't affect the logic. The predicate could equivalently be
|
||||||
|
implementes as `json_members//1` below:
|
||||||
|
```
|
||||||
|
json_members([Key-Value, Pair2 | Pairs]) --> json_member(Key, Value), ",", json_members([Pair2 | Pairs]).
|
||||||
|
```
|
||||||
|
That's a logically equivalent and equally clean representation to the lagged argument. However, it leaves
|
||||||
|
choice points, while using the lagged argument doesn't. For more info, watch: https://youtu.be/FZLofckPu4A?t=1737
|
||||||
|
*/
|
||||||
|
json_members([], Key-Value) --> json_member(Key, Value).
|
||||||
|
json_members([NextPair|Pairs], Key-Value) -->
|
||||||
|
json_member(Key, Value),
|
||||||
|
",",
|
||||||
|
json_members(Pairs, NextPair).
|
||||||
|
|
||||||
|
json_member(string(Key), Value) --> json_ws, json_string(Key), json_ws, ":", json_element(Value).
|
||||||
|
|
||||||
|
json_array([]) --> "[", json_ws, "]".
|
||||||
|
json_array([Value|Values]) --> "[", json_elements(Values, Value), "]".
|
||||||
|
|
||||||
|
/* Also using a lagged argument with `json_elements//2` to take advantage of first-argument indexing */
|
||||||
|
json_elements([], Value) --> json_element(Value).
|
||||||
|
json_elements([NextValue|Values], Value) -->
|
||||||
|
json_element(Value),
|
||||||
|
",",
|
||||||
|
json_elements(Values, NextValue).
|
||||||
|
|
||||||
|
json_element(Value) --> json_ws, json_value(Value), json_ws.
|
||||||
|
|
||||||
|
json_string(Chars) --> "\"", json_characters(Chars), "\"".
|
||||||
|
|
||||||
|
json_characters("") --> "".
|
||||||
|
json_characters([Char|Chars]) --> json_character(Char), json_characters(Chars).
|
||||||
|
|
||||||
|
/* Note on variable instantiation checks (`var/1` and `nonvar/1`) used below and in Prolog in general.
|
||||||
|
Instantiation checks should never be used to change the logic of your program. Instead, they are one of
|
||||||
|
many tools to adjust the 'control' or 'search strategy' used by Prolog to execute the logic of your program.
|
||||||
|
For a general overview of the idea, read Bob Kowalski's "Algorithm = Logic + Control":
|
||||||
|
https://www.doc.ic.ac.uk/~rak/papers/algorithm%20=%20logic%20+%20control.pdf
|
||||||
|
For an introduction to search strategies in Prolog, read: https://www.metalevel.at/prolog/sorting#searching
|
||||||
|
It's tempting to use instantiation checks to be more strict while generating and more relaxed while parsing.
|
||||||
|
In fact, the early version of this library aimed to return exactly one result when generating. However, doing that
|
||||||
|
is **wrong** and leads to difficult-to-catch bugs. Instead, adjust the search strategy to return the most ideal
|
||||||
|
and strictest answer FIRST and then return less ideal answers on backtracking.
|
||||||
|
As an example, consider a string containing just the forward slash. The JSON standard recommends the forward slash
|
||||||
|
be escaped with a backslash, but allows it to not be escaped. Attempting to force stricter behavior with
|
||||||
|
instantiation checks can lead to this confusing mess:
|
||||||
|
```
|
||||||
|
phrase(json:json_characters("/"), External).
|
||||||
|
External = "\\/".
|
||||||
|
?- phrase(json:json_characters(Internal), "/").
|
||||||
|
Internal = "/"
|
||||||
|
; false.
|
||||||
|
?- phrase(json:json_characters("/"), "/").
|
||||||
|
false.
|
||||||
|
```
|
||||||
|
To avoid such bugs, never use instantiation checks to reduce the number of right answers, but rather to adjust
|
||||||
|
the *path* used to traverse those answers. */
|
||||||
|
|
||||||
|
escape_char('"', '"').
|
||||||
|
escape_char('\\', '\\').
|
||||||
|
escape_char('/', '/').
|
||||||
|
escape_char('\b', 'b').
|
||||||
|
escape_char('\f', 'f').
|
||||||
|
escape_char('\n', 'n').
|
||||||
|
escape_char('\r', 'r').
|
||||||
|
escape_char('\t', 't').
|
||||||
|
|
||||||
|
json_character(EscapeChar) -->
|
||||||
|
{ escape_char(EscapeChar, PrintChar) },
|
||||||
|
"\\",
|
||||||
|
[PrintChar].
|
||||||
|
json_character(PrintChar) -->
|
||||||
|
[PrintChar],
|
||||||
|
{ dif(PrintChar, '\\'),
|
||||||
|
dif(PrintChar, '"'),
|
||||||
|
char_code(PrintChar, PrintCharCode),
|
||||||
|
PrintCharCode >= 32 }.
|
||||||
|
json_character(EscapeChar) -->
|
||||||
|
"\\u",
|
||||||
|
json_hex(H1),
|
||||||
|
json_hex(H2),
|
||||||
|
json_hex(H3),
|
||||||
|
json_hex(H4),
|
||||||
|
{ ( nonvar(H1) ->
|
||||||
|
EscapeCharCode is H1 * 16^3 + H2 * 16^2 + H3 * 16 + H4,
|
||||||
|
char_code(EscapeChar, EscapeCharCode)
|
||||||
|
; char_code(EscapeChar, EscapeCharCode),
|
||||||
|
H1 is (EscapeCharCode // 16^3) mod 16,
|
||||||
|
H2 is (EscapeCharCode // 16^2) mod 16,
|
||||||
|
H3 is (EscapeCharCode // 16^1) mod 16,
|
||||||
|
H4 is (EscapeCharCode // 16^0) mod 16
|
||||||
|
) }.
|
||||||
|
|
||||||
|
json_hex(Digit) --> json_digit(Digit).
|
||||||
|
json_hex(10) --> "a".
|
||||||
|
json_hex(11) --> "b".
|
||||||
|
json_hex(12) --> "c".
|
||||||
|
json_hex(13) --> "d".
|
||||||
|
json_hex(14) --> "e".
|
||||||
|
json_hex(15) --> "f".
|
||||||
|
json_hex(10) --> "A".
|
||||||
|
json_hex(11) --> "B".
|
||||||
|
json_hex(12) --> "C".
|
||||||
|
json_hex(13) --> "D".
|
||||||
|
json_hex(14) --> "E".
|
||||||
|
json_hex(15) --> "F".
|
||||||
|
|
||||||
|
/* I can't think of any alternatives to using `number_chars/2` when generating, though this leads
|
||||||
|
to under-reporting of correct solutions. At least matching solutions unify when both are instantiated...
|
||||||
|
```
|
||||||
|
?- phrase(json:json_number(N), "123E2").
|
||||||
|
N = 12300
|
||||||
|
; false.
|
||||||
|
?- phrase(json:json_number(12300), Cs).
|
||||||
|
Cs = "12300".
|
||||||
|
?- phrase(json:json_number(12300), "123E2").
|
||||||
|
true
|
||||||
|
; false.
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
parsing, [C] --> [C], { nonvar(C) }.
|
||||||
|
|
||||||
|
json_number(Number) -->
|
||||||
|
( parsing ->
|
||||||
|
json_sign_noplus(Sign),
|
||||||
|
json_integer(Integer),
|
||||||
|
json_fraction(Fraction),
|
||||||
|
json_exponent(Exponent),
|
||||||
|
{ ( Exponent >= 0 ->
|
||||||
|
Base = 10
|
||||||
|
; Base = 10.0
|
||||||
|
),
|
||||||
|
Number is Sign * (Integer + Fraction) * Base ^ Exponent }
|
||||||
|
; { number_chars(Number, NumberChars) },
|
||||||
|
NumberChars
|
||||||
|
).
|
||||||
|
|
||||||
|
json_integer(Digit) --> json_digit(Digit).
|
||||||
|
json_integer(TotalValue) -->
|
||||||
|
json_onenine(FirstDigit),
|
||||||
|
json_digits(RemainingValue, Power),
|
||||||
|
{ TotalValue is FirstDigit * 10 ^ (Power + 1) + RemainingValue }.
|
||||||
|
|
||||||
|
json_digits(Digit, 0) --> json_digit(Digit).
|
||||||
|
json_digits(Value, Power) -->
|
||||||
|
json_digit(FirstDigit),
|
||||||
|
json_digits(RemainingValue, NextPower),
|
||||||
|
{ Power is NextPower + 1,
|
||||||
|
Value is FirstDigit * 10^Power + RemainingValue }.
|
||||||
|
|
||||||
|
json_digit(0) --> "0".
|
||||||
|
json_digit(Digit) --> json_onenine(Digit).
|
||||||
|
|
||||||
|
json_onenine(1) --> "1".
|
||||||
|
json_onenine(2) --> "2".
|
||||||
|
json_onenine(3) --> "3".
|
||||||
|
json_onenine(4) --> "4".
|
||||||
|
json_onenine(5) --> "5".
|
||||||
|
json_onenine(6) --> "6".
|
||||||
|
json_onenine(7) --> "7".
|
||||||
|
json_onenine(8) --> "8".
|
||||||
|
json_onenine(9) --> "9".
|
||||||
|
|
||||||
|
json_fraction(0) --> "".
|
||||||
|
json_fraction(Fraction) -->
|
||||||
|
".",
|
||||||
|
json_digits(Value, Power),
|
||||||
|
{ Fraction is Value / 10.0 ^ (Power + 1) }.
|
||||||
|
|
||||||
|
json_exponent(0) --> "".
|
||||||
|
json_exponent(Exponent) -->
|
||||||
|
json_exponent_signifier,
|
||||||
|
json_sign(Sign),
|
||||||
|
json_digits(Value, _),
|
||||||
|
{ Exponent is Sign * Value }.
|
||||||
|
|
||||||
|
json_exponent_signifier --> "E".
|
||||||
|
json_exponent_signifier --> "e".
|
||||||
|
|
||||||
|
json_sign_noplus(1) --> "".
|
||||||
|
json_sign_noplus(-1) --> "-".
|
||||||
|
|
||||||
|
json_sign(Sign) --> json_sign_noplus(Sign).
|
||||||
|
json_sign(1) --> "+".
|
||||||
|
|
||||||
|
/* Make `json_ws/0` greedy when parsing, lazy when generating */
|
||||||
|
json_ws_empty --> "".
|
||||||
|
json_ws_nonempty --> " ".
|
||||||
|
json_ws_nonempty --> "\n".
|
||||||
|
json_ws_nonempty --> "\r".
|
||||||
|
json_ws_nonempty --> "\t".
|
||||||
|
json_ws_greedy --> json_ws_nonempty, json_ws_greedy.
|
||||||
|
json_ws_greedy --> json_ws_empty.
|
||||||
|
json_ws_lazy --> json_ws_empty.
|
||||||
|
json_ws_lazy --> json_ws_nonempty, json_ws_lazy.
|
||||||
|
json_ws -->
|
||||||
|
( parsing ->
|
||||||
|
json_ws_greedy
|
||||||
|
; json_ws_lazy
|
||||||
|
).
|
||||||
109
src/lib/sgml.pl
Normal file
109
src/lib/sgml.pl
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Predicates for parsing HTML and XML documents.
|
||||||
|
Written 2020-2022 by Markus Triska (triska@metalevel.at)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
/** Predicates for parsing HTML and XML documents.
|
||||||
|
|
||||||
|
Currently, two predicates are provided:
|
||||||
|
|
||||||
|
- `load_html(+Source, -Es, +Options)`
|
||||||
|
- `load_xml(+Source, -Es, +Options)`
|
||||||
|
|
||||||
|
These predicates parse HTML and XML documents, respectively.
|
||||||
|
|
||||||
|
Source must be one of:
|
||||||
|
|
||||||
|
- a list of characters with the document contents
|
||||||
|
- `stream(S)`, specifying a stream S from which to read the content
|
||||||
|
- `file(Name)`, where Name is a list of characters specifying a file name.
|
||||||
|
|
||||||
|
Es is unified with the abstract syntax tree of the parsed document,
|
||||||
|
represented as a list of elements where each is of the form:
|
||||||
|
|
||||||
|
* a list of characters, representing text
|
||||||
|
|
||||||
|
* `element(Name, Attrs, Children)`
|
||||||
|
|
||||||
|
- `Name`, an atom, is the name of the tag
|
||||||
|
|
||||||
|
- `Attrs` is a list of `Key=Value` pairs:
|
||||||
|
`Key` is an atom, and `Value` is a list of characters
|
||||||
|
|
||||||
|
- `Children` is a list of elements as specified here.
|
||||||
|
|
||||||
|
Currently, Options are ignored. In the future, more options may be
|
||||||
|
provided to control parsing.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []).
|
||||||
|
```
|
||||||
|
|
||||||
|
Yielding:
|
||||||
|
|
||||||
|
```
|
||||||
|
Es = [element(html,[],
|
||||||
|
[element(head,[],
|
||||||
|
[element(title,[],
|
||||||
|
["Hello!"])]),
|
||||||
|
element(body,[],[])])].
|
||||||
|
```
|
||||||
|
|
||||||
|
`library(xpath)` provides convenient reasoning about parsed documents.
|
||||||
|
For example, to fetch the title of the document above, we can use:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- load_html("<html><head><title>Hello!</title></head></html>", Es, []),
|
||||||
|
xpath(Es, //title(text), T).
|
||||||
|
```
|
||||||
|
|
||||||
|
Yielding `T = "Hello!"`.
|
||||||
|
|
||||||
|
Use `http_open/3` from `library(http/http_open)` to read answers from
|
||||||
|
web servers via streams.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(sgml, [load_html/3,
|
||||||
|
load_xml/3]).
|
||||||
|
|
||||||
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(pio)).
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
|
||||||
|
load_html(Source, Es, Options) :-
|
||||||
|
must_be_source(Source, load_html/3),
|
||||||
|
must_be(list, Options),
|
||||||
|
load_structure_(Source, Es, Options, html).
|
||||||
|
load_xml(Source, Es, Options) :-
|
||||||
|
must_be_source(Source, load_xml/3),
|
||||||
|
must_be(list, Options),
|
||||||
|
load_structure_(Source, Es, Options, xml).
|
||||||
|
|
||||||
|
must_be_source(Source, Context) :-
|
||||||
|
( var(Source) -> instantiation_error(Context)
|
||||||
|
; is_sgml_source(Source) -> true
|
||||||
|
; domain_error(sgml_source, Source, Context)
|
||||||
|
).
|
||||||
|
|
||||||
|
is_sgml_source(file(Fs)) :- must_be(chars, Fs).
|
||||||
|
is_sgml_source(stream(_)).
|
||||||
|
is_sgml_source([]).
|
||||||
|
is_sgml_source([C|Cs]) :- must_be(chars, [C|Cs]).
|
||||||
|
|
||||||
|
load_structure_([], [], _, _).
|
||||||
|
load_structure_([C|Cs], [E], Options, What) :-
|
||||||
|
load_(What, [C|Cs], E, Options).
|
||||||
|
load_structure_(file(Fs), [E], Options, What) :-
|
||||||
|
once(phrase_from_file(seq(Cs), Fs)),
|
||||||
|
load_(What, Cs, E, Options).
|
||||||
|
load_structure_(stream(Stream), [E], Options, What) :-
|
||||||
|
get_n_chars(Stream, _, Cs),
|
||||||
|
load_(What, Cs, E, Options).
|
||||||
|
|
||||||
|
load_(html, Cs, E, Options) :- '$load_html'(Cs, E, Options).
|
||||||
|
load_(xml, Cs, E, Options) :- '$load_xml'(Cs, E, Options).
|
||||||
94
src/lib/si.pl
Normal file
94
src/lib/si.pl
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
|
||||||
|
/** Safe type tests.
|
||||||
|
|
||||||
|
"si" stands for "sufficiently instantiated". It can also be read as
|
||||||
|
"safe inference", so possibly also other predicates are candidates
|
||||||
|
for this library.
|
||||||
|
|
||||||
|
A safe type test:
|
||||||
|
|
||||||
|
- throws an *instantiation error* if the argument is
|
||||||
|
not sufficiently instantiated to make a sound decision
|
||||||
|
- *succeeds* if the argument is of the specified type
|
||||||
|
- *fails* otherwise.
|
||||||
|
|
||||||
|
For instance, `atom_si(A)` yields an *instantiation error* if `A` is a
|
||||||
|
variable. This is logically sound, since in that case the argument
|
||||||
|
is not sufficiently instantiated to make any decision.
|
||||||
|
|
||||||
|
The definitions are taken from [Safer type tests in Prolog](https://stackoverflow.com/questions/27306453/safer-type-tests-in-prolog).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- chars_si(Cs).
|
||||||
|
error(instantiation_error,list_si/1).
|
||||||
|
?- chars_si([h|Cs]).
|
||||||
|
error(instantiation_error,list_si/1).
|
||||||
|
?- chars_si("hello").
|
||||||
|
true.
|
||||||
|
?- chars_si(hello).
|
||||||
|
false.
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(si, [atom_si/1,
|
||||||
|
integer_si/1,
|
||||||
|
atomic_si/1,
|
||||||
|
list_si/1,
|
||||||
|
character_si/1,
|
||||||
|
chars_si/1,
|
||||||
|
dif_si/2]).
|
||||||
|
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
atom_si(A) :-
|
||||||
|
functor(A, _, 0), % for the instantiation error
|
||||||
|
atom(A).
|
||||||
|
|
||||||
|
integer_si(I) :-
|
||||||
|
functor(I, _, 0),
|
||||||
|
integer(I).
|
||||||
|
|
||||||
|
atomic_si(AC) :-
|
||||||
|
functor(AC,_,0).
|
||||||
|
|
||||||
|
% list_si(L) :-
|
||||||
|
% \+ \+ length(L, _),
|
||||||
|
% sort(L, _).
|
||||||
|
|
||||||
|
list_si(L0) :-
|
||||||
|
'$skip_max_list'(_,_, L0,L),
|
||||||
|
( nonvar(L) -> L = []
|
||||||
|
; throw(error(instantiation_error, list_si/1))
|
||||||
|
).
|
||||||
|
|
||||||
|
character_si(Ch) :-
|
||||||
|
functor(Ch,Ch,0),
|
||||||
|
atom(Ch),
|
||||||
|
atom_length(Ch,1).
|
||||||
|
|
||||||
|
chars_si(Chs0) :-
|
||||||
|
'$skip_max_list'(_,_, Chs0,Chs),
|
||||||
|
( nonvar(Chs) -> Chs == [] ; true ), % fails for infinite lists too
|
||||||
|
failnochars(Chs0, Uninstantiated),
|
||||||
|
( nonvar(Uninstantiated)
|
||||||
|
-> throw(error(instantiation_error, chars_si/1))
|
||||||
|
; true
|
||||||
|
).
|
||||||
|
|
||||||
|
failnochars(Chs0, U) :-
|
||||||
|
( var(Chs0) -> U = true
|
||||||
|
; Chs0 == [] -> true
|
||||||
|
; Chs0 = [Ch|Chs1],
|
||||||
|
( nonvar(Ch) -> atom(Ch), atom_length(Ch,1)
|
||||||
|
; U = true
|
||||||
|
),
|
||||||
|
failnochars(Chs1, U)
|
||||||
|
).
|
||||||
|
|
||||||
|
dif_si(X, Y) :-
|
||||||
|
X \== Y,
|
||||||
|
( X \= Y -> true
|
||||||
|
; throw(error(instantiation_error,dif_si/2))
|
||||||
|
).
|
||||||
1379
src/lib/simplex.pl
Normal file
1379
src/lib/simplex.pl
Normal file
File diff suppressed because it is too large
Load Diff
94
src/lib/sockets.pl
Normal file
94
src/lib/sockets.pl
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
Predicates for handling network sockets, both as a server and as a client.
|
||||||
|
As a server, you should open a socket an call `socket_server_accept/4` to get a stream for each connection.
|
||||||
|
As a client, you should just open a socket and you will receive a stream.
|
||||||
|
In both cases, with a stream, you can use the usual predicates to read and write to the stream.
|
||||||
|
*/
|
||||||
|
:- module(sockets, [socket_client_open/3,
|
||||||
|
socket_server_open/2,
|
||||||
|
socket_server_accept/4,
|
||||||
|
socket_server_close/1,
|
||||||
|
current_hostname/1]).
|
||||||
|
|
||||||
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
%% socket_client_open(+Addr, -Stream, +Options).
|
||||||
|
%
|
||||||
|
% Open a socket to a server, returning a stream. Addr must satisfy `Addr = Address:Port`.
|
||||||
|
%
|
||||||
|
% The following options are available:
|
||||||
|
%
|
||||||
|
% * `alias(+Alias)`: Set an alias to the stream
|
||||||
|
% * `eof_action(+Action)`: Defined what happens if the end of the stream is reached. Values: `error`, `eof_code` and `reset`.
|
||||||
|
% * `reposition(+Boolean)`: Specifies whether repositioning is required for the stream. `false` is the default.
|
||||||
|
% * `type(+Type)`: Type can be `text` or `binary`. Defines the type of the stream, if it's optimized for plain text
|
||||||
|
% or just binary
|
||||||
|
%
|
||||||
|
socket_client_open(Addr, Stream, Options) :-
|
||||||
|
( var(Addr) ->
|
||||||
|
throw(error(instantiation_error, socket_client_open/3))
|
||||||
|
;
|
||||||
|
true
|
||||||
|
),
|
||||||
|
must_be(var, Stream),
|
||||||
|
must_be(list, Options),
|
||||||
|
( Addr = Address:Port,
|
||||||
|
atom(Address),
|
||||||
|
( atom(Port) ; integer(Port) ) ->
|
||||||
|
true
|
||||||
|
;
|
||||||
|
throw(error(type_error(socket_address, Addr), socket_client_open/3))
|
||||||
|
),
|
||||||
|
builtins:parse_stream_options(Options,
|
||||||
|
[Alias, EOFAction, Reposition, Type],
|
||||||
|
socket_client_open/3),
|
||||||
|
'$socket_client_open'(Address, Port, Stream, Alias, EOFAction, Reposition, Type).
|
||||||
|
|
||||||
|
%% socket_server_open(+Addr, -ServerSocket).
|
||||||
|
%
|
||||||
|
% Open a server socket, returning a ServerSocket. Use that ServerSocket to accept incoming connections in
|
||||||
|
% `socket_server_accept/4`. Addr must satisfy `Addr = Address:Port`. Depending on the operating system
|
||||||
|
% configuration, some ports might be reserved for superusers.
|
||||||
|
socket_server_open(Addr, ServerSocket) :-
|
||||||
|
must_be(var, ServerSocket),
|
||||||
|
( ( integer(Addr) ; var(Addr) ) ->
|
||||||
|
'$socket_server_open'([], Addr, ServerSocket)
|
||||||
|
;
|
||||||
|
Addr = Address:Port,
|
||||||
|
must_be(atom, Address),
|
||||||
|
can_be(integer, Port),
|
||||||
|
'$socket_server_open'(Address, Port, ServerSocket)
|
||||||
|
).
|
||||||
|
|
||||||
|
%% socket_server_accept(+ServerSocket, -Client, -Stream, +Options).
|
||||||
|
%
|
||||||
|
% Given a ServerSocket and a list of Options, accepts a incoming connection, returning data from the Client and
|
||||||
|
% a Stream to read or write data.
|
||||||
|
%
|
||||||
|
% The following options are available:
|
||||||
|
%
|
||||||
|
% * `alias(+Alias)`: Set an alias to the stream
|
||||||
|
% * `eof_action(+Action)`: Defined what happens if the end of the stream is reached. Values: `error`, `eof_code` and `reset`.
|
||||||
|
% * `reposition(+Boolean)`: Specifies whether repositioning is required for the stream. `false` is the default.
|
||||||
|
% * `type(+Type)`: Type can be `text` or `binary`. Defines the type of the stream, if it's optimized for plain text
|
||||||
|
% or just binary
|
||||||
|
%
|
||||||
|
socket_server_accept(ServerSocket, Client, Stream, Options) :-
|
||||||
|
must_be(var, Client),
|
||||||
|
must_be(var, Stream),
|
||||||
|
builtins:parse_stream_options(Options,
|
||||||
|
[Alias, EOFAction, Reposition, Type],
|
||||||
|
socket_server_accept/4),
|
||||||
|
'$socket_server_accept'(ServerSocket, Client, Stream, Alias, EOFAction, Reposition, Type).
|
||||||
|
|
||||||
|
%% socket_server_close(+ServerSocket).
|
||||||
|
%
|
||||||
|
% Stops listening on that ServerSocket. It's recommended to always close a ServerSocket once it's no longer needed
|
||||||
|
socket_server_close(ServerSocket) :-
|
||||||
|
'$socket_server_close'(ServerSocket).
|
||||||
|
|
||||||
|
%% current_hostname(-HostName).
|
||||||
|
%
|
||||||
|
% Returns the current hostname of the computer in which Scryer Prolog is executing right now
|
||||||
|
current_hostname(HostName) :-
|
||||||
|
'$current_hostname'(HostName).
|
||||||
@@ -1,3 +1,29 @@
|
|||||||
|
/** Tabling, also called SLG resolution.
|
||||||
|
|
||||||
|
SLG resolution is an alternative execution strategy that sometimes
|
||||||
|
helps to improve termination and performance characters of Prolog
|
||||||
|
predicates.
|
||||||
|
|
||||||
|
To enable this execution strategy for a Prolog predicate, add a
|
||||||
|
`(table)/1` directive, using the prefix operator `table` that this
|
||||||
|
module defines. For example, to enable tabling for the predicate
|
||||||
|
`p/2`, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
:- use_module(library(tabling)).
|
||||||
|
|
||||||
|
:- table p/2.
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The possibility to apply different execution strategies is one of
|
||||||
|
the greatest attractions of pure Prolog code, and one of the
|
||||||
|
strongest arguments for keeping to the pure core of Prolog as far
|
||||||
|
as possible.
|
||||||
|
|
||||||
|
Scryer Prolog implements tabling as described by Desouter et al. in [*Tabling as a Library with Delimited Control*](https://www.ijcai.org/Proceedings/16/Papers/619.pdf).
|
||||||
|
*/
|
||||||
|
|
||||||
:- module(tabling,
|
:- module(tabling,
|
||||||
[ start_tabling/2, % +Wrapper, :Worker.
|
[ start_tabling/2, % +Wrapper, :Worker.
|
||||||
@@ -8,12 +34,12 @@
|
|||||||
op(1150, fx, table)
|
op(1150, fx, table)
|
||||||
]).
|
]).
|
||||||
|
|
||||||
:- use_module('tabling/double_linked_list').
|
:- use_module(library(tabling/double_linked_list)).
|
||||||
:- use_module('tabling/table_data_structure').
|
:- use_module(library(tabling/table_data_structure)).
|
||||||
:- use_module('tabling/batched_worklist').
|
:- use_module(library(tabling/batched_worklist)).
|
||||||
:- use_module('tabling/wrapper').
|
:- use_module(library(tabling/global_worklist)).
|
||||||
:- use_module('tabling/global_worklist').
|
:- use_module(library(tabling/table_link_manager)).
|
||||||
:- use_module('tabling/table_link_manager').
|
:- use_module(library(tabling/wrapper)).
|
||||||
|
|
||||||
:- use_module(library(cont)).
|
:- use_module(library(cont)).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
@@ -66,6 +92,9 @@ table_and_status_for_variant(V,T,S) :-
|
|||||||
table_for_variant(V,T),
|
table_for_variant(V,T),
|
||||||
tbd_table_status(T,S).
|
tbd_table_status(T,S).
|
||||||
|
|
||||||
|
|
||||||
|
:- meta_predicate start_tabling(?, :).
|
||||||
|
|
||||||
start_tabling(Wrapper,Worker) :-
|
start_tabling(Wrapper,Worker) :-
|
||||||
put_new_trie_table_link,
|
put_new_trie_table_link,
|
||||||
put_new_global_worklist,
|
put_new_global_worklist,
|
||||||
@@ -135,19 +164,17 @@ activate(Wrapper,Worker,T) :-
|
|||||||
|
|
||||||
delim(Wrapper,Worker,Table) :-
|
delim(Wrapper,Worker,Table) :-
|
||||||
% debug(tabling, 'ACT: ~p on ~p', [Wrapper, Table]),
|
% debug(tabling, 'ACT: ~p on ~p', [Wrapper, Table]),
|
||||||
reset(Worker,SourceCall,Continuation),
|
catch(reset(Worker,SourceCall,Continuation),
|
||||||
( Continuation == none, var(SourceCall) ->
|
_,
|
||||||
|
fail),
|
||||||
|
( Continuation = none ->
|
||||||
( add_answer(Table,Wrapper)
|
( add_answer(Table,Wrapper)
|
||||||
-> true %debug(tabling, 'ADD: ~p', [Wrapper])
|
-> true %debug(tabling, 'ADD: ~p', [Wrapper])
|
||||||
; %debug(tabling, 'DUP: ~p', [Wrapper]),
|
; %debug(tabling, 'DUP: ~p', [Wrapper]),
|
||||||
fail
|
fail
|
||||||
)
|
)
|
||||||
;
|
;
|
||||||
( Continuation = cont(Cont) ->
|
Continuation = cont(Cont),
|
||||||
true
|
|
||||||
; Continuation = none ->
|
|
||||||
Cont = true
|
|
||||||
),
|
|
||||||
SourceCall = call_info(_,SourceTable),
|
SourceCall = call_info(_,SourceTable),
|
||||||
TargetCall = call_info(Wrapper,Table),
|
TargetCall = call_info(Wrapper,Table),
|
||||||
Dependency = dependency(SourceCall,Cont,TargetCall),
|
Dependency = dependency(SourceCall,Cont,TargetCall),
|
||||||
@@ -45,14 +45,24 @@
|
|||||||
wkl_worklist_work_done/1 % +WorkList
|
wkl_worklist_work_done/1 % +WorkList
|
||||||
]).
|
]).
|
||||||
|
|
||||||
:- use_module(global_worklist).
|
:- use_module(library(tabling/global_worklist)).
|
||||||
:- use_module(double_linked_list).
|
:- use_module(library(tabling/double_linked_list)).
|
||||||
|
|
||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
:- attribute executing_all_work/1, worklist_presence/1, wkl_answer_cluster/1, wkl_suspension_cluster/1, wkl_answer_cluster_pointer_flag/1.
|
:- attribute executing_all_work/1, worklist_presence/1, wkl_answer_cluster/1, wkl_suspension_cluster/1, wkl_answer_cluster_pointer_flag/1.
|
||||||
|
|
||||||
|
verify_attributes(_, _, []).
|
||||||
|
|
||||||
|
attribute_goals(X) -->
|
||||||
|
{ put_atts(X, -executing_all_work(_)),
|
||||||
|
put_atts(X, -worklist_presence(_)),
|
||||||
|
put_atts(X, -wkl_answer_cluster(_)),
|
||||||
|
put_atts(X, -wkl_suspension_cluster(_)),
|
||||||
|
put_atts(X, -wkl_answer_cluster_pointer_flag(_)) }.
|
||||||
|
|
||||||
/** <module> Tabling Worklist management
|
/** <module> Tabling Worklist management
|
||||||
|
|
||||||
A batched worklist: a worklist that clusters suspensions and answers as
|
A batched worklist: a worklist that clusters suspensions and answers as
|
||||||
@@ -161,6 +171,8 @@ wkl_p_swap_answer_continuation(Worklist,InnerAnswerClusterPointer,SuspensionClus
|
|||||||
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer).
|
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer).
|
||||||
|
|
||||||
% Update the pointer if the answer cluster it points to is no longer the rightmost inner answer cluster.
|
% Update the pointer if the answer cluster it points to is no longer the rightmost inner answer cluster.
|
||||||
|
% Strangely, this predicate was intentionally named "wkl_p_update_righmost_inner_answer_cluster_pointer"
|
||||||
|
% in the original library.
|
||||||
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer) :-
|
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer) :-
|
||||||
( wkl_p_answer_cluster_currently_moved_completely(Worklist,InnerAnswerClusterPointer) ->
|
( wkl_p_answer_cluster_currently_moved_completely(Worklist,InnerAnswerClusterPointer) ->
|
||||||
wkl_p_find_new_rightmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer,NewRiacPointer),
|
wkl_p_find_new_rightmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer,NewRiacPointer),
|
||||||
@@ -49,9 +49,15 @@
|
|||||||
]).
|
]).
|
||||||
|
|
||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
|
||||||
:- attribute dll_element/1, dll_next/1, dll_prev/1.
|
:- attribute dll_element/1, dll_next/1, dll_prev/1.
|
||||||
|
|
||||||
|
attribute_goals(X) -->
|
||||||
|
{ put_atts(X, -dll_element(_)),
|
||||||
|
put_atts(X, -dll_next(_)),
|
||||||
|
put_atts(X, -dll_prev(_)) }.
|
||||||
|
|
||||||
% A circular double linked list
|
% A circular double linked list
|
||||||
% =============================
|
% =============================
|
||||||
|
|
||||||
@@ -9,10 +9,15 @@
|
|||||||
]).
|
]).
|
||||||
|
|
||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(iso_ext)).
|
:- use_module(library(iso_ext)).
|
||||||
|
|
||||||
:- attribute table_global_worklist/1.
|
:- attribute table_global_worklist/1.
|
||||||
|
|
||||||
|
verify_attributes(_, _, []).
|
||||||
|
|
||||||
|
attribute_goals(X) --> { put_atts(X, -table_global_worklist(_)) }.
|
||||||
|
|
||||||
put_new_global_worklist :-
|
put_new_global_worklist :-
|
||||||
( bb_get(table_global_worklist_initialized, _) ->
|
( bb_get(table_global_worklist_initialized, _) ->
|
||||||
true
|
true
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
get_nb_identifiers/3 % +Table, -NbWorklistID, -NbAnswerTreeID
|
get_nb_identifiers/3 % +Table, -NbWorklistID, -NbAnswerTreeID
|
||||||
]).
|
]).
|
||||||
|
|
||||||
:- use_module(table_link_manager).
|
:- use_module(library(tabling/table_link_manager)).
|
||||||
:- use_module(trie).
|
:- use_module(library(tabling/trie)).
|
||||||
|
|
||||||
/* Part of SWI-Prolog
|
/* Part of SWI-Prolog
|
||||||
|
|
||||||
@@ -53,14 +53,21 @@
|
|||||||
POSSIBILITY OF SUCH DAMAGE.
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
:- use_module(batched_worklist).
|
:- use_module(library(tabling/batched_worklist)).
|
||||||
|
|
||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(gensym)).
|
:- use_module(library(gensym)).
|
||||||
:- use_module(library(iso_ext)).
|
:- use_module(library(iso_ext)).
|
||||||
|
|
||||||
:- attribute table_status/1, newly_created_table_identifiers/1.
|
:- attribute table_status/1, newly_created_table_identifiers/1.
|
||||||
|
|
||||||
|
verify_attributes(_, _, []).
|
||||||
|
|
||||||
|
attribute_goals(X) -->
|
||||||
|
{ put_atts(X, -table_status(_)),
|
||||||
|
put_atts(X, -newly_created_table_identifiers(_)) }.
|
||||||
|
|
||||||
% This file defines the table datastructure.
|
% This file defines the table datastructure.
|
||||||
%
|
%
|
||||||
% The table datastructure contains the following sub-structures:
|
% The table datastructure contains the following sub-structures:
|
||||||
@@ -43,14 +43,20 @@
|
|||||||
]).
|
]).
|
||||||
|
|
||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
:- use_module(library(iso_ext)).
|
:- use_module(library(iso_ext)).
|
||||||
:- use_module(library(terms)).
|
:- use_module(library(terms)).
|
||||||
|
|
||||||
:- use_module(trie).
|
:- use_module(library(tabling/trie)).
|
||||||
|
|
||||||
:- attribute trie_table_link/1.
|
:- attribute trie_table_link/1.
|
||||||
|
|
||||||
|
verify_attributes(_, _, []).
|
||||||
|
|
||||||
|
attribute_goals(X) -->
|
||||||
|
{ put_atts(X, -trie_table_link(_)) }.
|
||||||
|
|
||||||
% This file defines a call pattern trie.
|
% This file defines a call pattern trie.
|
||||||
%
|
%
|
||||||
% This data structure keeps the relation between a variant and the
|
% This data structure keeps the relation between a variant and the
|
||||||
@@ -41,12 +41,21 @@
|
|||||||
trie_get_all_values/2 % +Trie, -Value
|
trie_get_all_values/2 % +Trie, -Value
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(format)).
|
||||||
|
|
||||||
:- use_module(library(assoc)).
|
:- use_module(library(assoc)).
|
||||||
:- use_module(library(atts)).
|
:- use_module(library(atts)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(lists)).
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
:- attribute maybe_just/1, children/1.
|
:- attribute maybe_just/1, children/1.
|
||||||
|
|
||||||
|
verify_attributes(_, _, []).
|
||||||
|
|
||||||
|
attribute_goals(X) -->
|
||||||
|
{ put_atts(X, -maybe_just(_)),
|
||||||
|
put_atts(X, -children(_)) }.
|
||||||
|
|
||||||
% Implementation of a prefix tree, a.k.a. trie %
|
% Implementation of a prefix tree, a.k.a. trie %
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
@@ -66,7 +75,7 @@
|
|||||||
% p_trie_arity_univ(+Term,-FunctorData,-ArgumentsList).
|
% p_trie_arity_univ(+Term,-FunctorData,-ArgumentsList).
|
||||||
p_trie_arity_univ(Term,functor_data(Name,Arity),Arguments) :-
|
p_trie_arity_univ(Term,functor_data(Name,Arity),Arguments) :-
|
||||||
( var(Term) ->
|
( var(Term) ->
|
||||||
Name = var,
|
Name = Term,
|
||||||
Arity = 0,
|
Arity = 0,
|
||||||
Arguments = []
|
Arguments = []
|
||||||
; Term =.. [Name|Arguments],
|
; Term =.. [Name|Arguments],
|
||||||
@@ -40,6 +40,8 @@
|
|||||||
:- use_module(library(dcgs)).
|
:- use_module(library(dcgs)).
|
||||||
:- use_module(library(error)).
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
:- multifile(tabled/2).
|
||||||
|
|
||||||
%%:- multifile
|
%%:- multifile
|
||||||
%% system:term_expansion/2,
|
%% system:term_expansion/2,
|
||||||
%% tabled/2.
|
%% tabled/2.
|
||||||
@@ -54,8 +56,8 @@
|
|||||||
%% table(PIList) :-
|
%% table(PIList) :-
|
||||||
%% throw(error(context_error(nodirective, table(PIList)), _)).
|
%% throw(error(context_error(nodirective, table(PIList)), _)).
|
||||||
|
|
||||||
instantiation_error(Var) :-
|
%% instantiation_error(Var) :-
|
||||||
throw(error(instantiation_error(Var), _)).
|
%% throw(error(instantiation_error(Var), _)).
|
||||||
|
|
||||||
wrappers(Var) -->
|
wrappers(Var) -->
|
||||||
{ var(Var), !,
|
{ var(Var), !,
|
||||||
@@ -75,7 +77,7 @@ wrappers(Name/Arity) -->
|
|||||||
atom_concat(Name, ' tabled', WrapName),
|
atom_concat(Name, ' tabled', WrapName),
|
||||||
Head =.. [Name|Args],
|
Head =.. [Name|Args],
|
||||||
WrappedHead =.. [WrapName|Args],
|
WrappedHead =.. [WrapName|Args],
|
||||||
'$module_of'(Module, Name) %prolog_load_context(module, Module)
|
prolog_load_context(module, Module)
|
||||||
},
|
},
|
||||||
[ ( Head :-
|
[ ( Head :-
|
||||||
start_tabling(Module:Head, WrappedHead)
|
start_tabling(Module:Head, WrappedHead)
|
||||||
@@ -93,10 +95,15 @@ rename((Head --> Body), (NewHead --> Body), Module) :- !,
|
|||||||
functor(Head, Name, Arity),
|
functor(Head, Name, Arity),
|
||||||
PlainArity is Arity+1,
|
PlainArity is Arity+1,
|
||||||
functor(PlainHead, Name, PlainArity),
|
functor(PlainHead, Name, PlainArity),
|
||||||
table_wrapper:tabled(PlainHead, Module),
|
catch(table_wrapper:tabled(PlainHead, Module),
|
||||||
|
error(existence_error(procedure, tabled/2), _),
|
||||||
|
false),
|
||||||
rename_term(Head, NewHead).
|
rename_term(Head, NewHead).
|
||||||
rename(Head, NewHead, Module) :-
|
rename(Head, NewHead, Module) :-
|
||||||
table_wrapper:tabled(Head, Module), !,
|
catch(table_wrapper:tabled(Head, Module),
|
||||||
|
error(existence_error(procedure, tabled/2), _),
|
||||||
|
false),
|
||||||
|
!,
|
||||||
rename_term(Head, NewHead).
|
rename_term(Head, NewHead).
|
||||||
|
|
||||||
rename_term(Compound0, Compound) :-
|
rename_term(Compound0, Compound) :-
|
||||||
@@ -109,10 +116,10 @@ rename_term(Name, WrapName) :-
|
|||||||
|
|
||||||
|
|
||||||
user:term_expansion(Term0, Clauses) :-
|
user:term_expansion(Term0, Clauses) :-
|
||||||
nonvar(Term0),
|
nonvar(Term0),
|
||||||
Term0 = (:- table Preds),
|
Term0 = (:- table Preds),
|
||||||
phrase(wrappers(Preds), Clauses).
|
phrase(wrappers(Preds), Clauses).
|
||||||
user:term_expansion(Clause, NewClause) :-
|
user:term_expansion(Clause, NewClause) :-
|
||||||
nonvar(Clause),
|
nonvar(Clause),
|
||||||
'$module_of'(Module, Clause),
|
prolog_load_context(module, Module),
|
||||||
rename(Clause, NewClause, Module).
|
rename(Clause, NewClause, Module).
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
numbervars(Term, N0, N) :-
|
numbervars(Term, N0, N) :-
|
||||||
catch(internal_numbervars(Term, N0, N),
|
catch(internal_numbervars(Term, N0, N),
|
||||||
error(E,Ctx),
|
error(E,Ctx),
|
||||||
( ( var(Ctx) -> Ctx = numbervars/3 ; true ), throw(error(E,Ctx) ) ) ).
|
( ( var(Ctx) -> Ctx = numbervars/3 ; true ), throw(error(E,Ctx) ) ) ).
|
||||||
|
|
||||||
internal_numbervars(Term, N0, N) :-
|
internal_numbervars(Term, N0, N) :-
|
||||||
must_be(integer, N0),
|
must_be(integer, N0),
|
||||||
171
src/lib/time.pl
Normal file
171
src/lib/time.pl
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written 2020-2023 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]).
|
||||||
|
|
||||||
|
:- use_module(library(format)).
|
||||||
|
:- use_module(library(iso_ext)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(charsio), [read_from_chars/2]).
|
||||||
|
|
||||||
|
|
||||||
|
%% current_time(-T)
|
||||||
|
%
|
||||||
|
% Yields the current system time _T_ in an opaque form, called a
|
||||||
|
% _time stamp_. Use `format_time//2` to describe strings that contain
|
||||||
|
% attributes of the time stamp.
|
||||||
|
|
||||||
|
current_time(T) :-
|
||||||
|
'$current_time'(T0),
|
||||||
|
read_from_chars(T0, T).
|
||||||
|
|
||||||
|
%% format_time(FormatString, TimeStamp)//
|
||||||
|
%
|
||||||
|
% The nonterminal format_time//2 describes a list of characters that
|
||||||
|
% are formatted according to a format string. Usage:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% phrase(format_time(FormatString, TimeStamp), Cs)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% TimeStamp represents a moment in time in an opaque form, as for
|
||||||
|
% example obtained by `current_time/1`.
|
||||||
|
%
|
||||||
|
% FormatString is a list of characters that are interpreted literally,
|
||||||
|
% except for the following specifiers (and possibly more in the future):
|
||||||
|
%
|
||||||
|
% | `%Y` | year of the time stamp. Example: 2020. |
|
||||||
|
% | `%m` | month number (01-12), zero-padded to 2 digits |
|
||||||
|
% | `%d` | day number (01-31), zero-padded to 2 digits |
|
||||||
|
% | `%H` | hour number (00-24), zero-padded to 2 digits |
|
||||||
|
% | `%M` | minute number (00-59), zero-padded to 2 digits |
|
||||||
|
% | `%S` | second number (00-60), zero-padded to 2 digits |
|
||||||
|
% | `%b` | abbreviated month name, always 3 letters |
|
||||||
|
% | `%a` | abbreviated weekday name, always 3 letters |
|
||||||
|
% | `%A` | full weekday name |
|
||||||
|
% | `%j` | day of the year (001-366), zero-padded to 3 digits |
|
||||||
|
% | `%%` | the literal `%` |
|
||||||
|
%
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- current_time(T), phrase(format_time("%d.%m.%Y (%H:%M:%S)", T), Cs).
|
||||||
|
% T = [...], Cs = "11.06.2020 (00:24:32)".
|
||||||
|
% ```
|
||||||
|
|
||||||
|
format_time([], _) --> [].
|
||||||
|
format_time(['%','%'|Fs], T) --> !, "%", format_time(Fs, T).
|
||||||
|
format_time(['%',Spec|Fs], T) --> !,
|
||||||
|
( { member(Spec=Value, T) } ->
|
||||||
|
seq(Value)
|
||||||
|
; { domain_error(time_specifier, Spec, format_time//2) }
|
||||||
|
),
|
||||||
|
format_time(Fs, T).
|
||||||
|
format_time([F|Fs], T) --> [F], format_time(Fs, T).
|
||||||
|
|
||||||
|
%% max_sleep_time(T)
|
||||||
|
%
|
||||||
|
% The maximum admissible time span for `sleep/1`.
|
||||||
|
|
||||||
|
max_sleep_time(0xfffffffffffffbff).
|
||||||
|
|
||||||
|
|
||||||
|
%% sleep(S)
|
||||||
|
%
|
||||||
|
% Sleeps for S seconds (a floating point number or integer).
|
||||||
|
|
||||||
|
sleep(T) :-
|
||||||
|
builtins:must_be_number(T, sleep),
|
||||||
|
( T < 0 ->
|
||||||
|
domain_error(not_less_than_zero, T, sleep/1)
|
||||||
|
; max_sleep_time(N), T > N ->
|
||||||
|
throw(error(representation_error(max_sleep_time), sleep/1))
|
||||||
|
; '$sleep'(T)
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
% '$cpu_now' can be replaced by statistics/2 once that is implemented.
|
||||||
|
|
||||||
|
:- meta_predicate time(0).
|
||||||
|
|
||||||
|
:- dynamic(time_id/1).
|
||||||
|
:- dynamic(time_state/2).
|
||||||
|
|
||||||
|
time_next_id(N) :-
|
||||||
|
( retract(time_id(N0)) ->
|
||||||
|
N is N0 + 1
|
||||||
|
; N = 0
|
||||||
|
),
|
||||||
|
asserta(time_id(N)).
|
||||||
|
|
||||||
|
|
||||||
|
%% time(Goal)
|
||||||
|
%
|
||||||
|
% Reports the execution time of Goal.
|
||||||
|
|
||||||
|
time(Goal) :-
|
||||||
|
'$cpu_now'(T0),
|
||||||
|
time_next_id(ID),
|
||||||
|
setup_call_cleanup(asserta(time_state(ID, T0)),
|
||||||
|
( call_cleanup(catch(Goal, E, (report_time(ID),throw(E))),
|
||||||
|
Det = true),
|
||||||
|
time_true(ID),
|
||||||
|
( Det == true -> !
|
||||||
|
; true
|
||||||
|
)
|
||||||
|
; report_time(ID),
|
||||||
|
false
|
||||||
|
),
|
||||||
|
retract(time_state(ID, _))).
|
||||||
|
|
||||||
|
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)),
|
||||||
|
false.
|
||||||
|
|
||||||
|
report_time(ID) :-
|
||||||
|
time_state(ID, T0),
|
||||||
|
'$cpu_now'(T),
|
||||||
|
Time is T - T0,
|
||||||
|
( bb_get('$answer_count', 0) ->
|
||||||
|
Pre = " ", Post = ""
|
||||||
|
; Pre = "", Post = " "
|
||||||
|
),
|
||||||
|
format("~s% CPU time: ~3fs~n~s", [Pre,Time,Post]).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
?- time((true;false)).
|
||||||
|
%@ % CPU time: 0.006s
|
||||||
|
%@ true
|
||||||
|
%@ ; % CPU time: 0.001s
|
||||||
|
%@ false.
|
||||||
|
|
||||||
|
:- time(use_module(library(clpz))).
|
||||||
|
%@ % CPU time: 3.711s
|
||||||
|
%@ true.
|
||||||
|
|
||||||
|
:- time(use_module(library(lists))).
|
||||||
|
%@ % CPU time: 0.006s
|
||||||
|
%@ 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.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
110
src/lib/tls.pl
Normal file
110
src/lib/tls.pl
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Negotiation of TLS connections.
|
||||||
|
Written Dec. 2021 by Markus Triska (triska@metalevel.at)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
:- module(tls, [tls_client_context/2, % -Context, +Options
|
||||||
|
tls_client_negotiate/3, % +Context, +Stream0, -Stream
|
||||||
|
tls_server_context/2, % -Context, +Options
|
||||||
|
tls_server_negotiate/3 % +Context, +Stream0, -Stream
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
TLS Clients
|
||||||
|
===========
|
||||||
|
|
||||||
|
Use tls_client_context/2 to create a TLS context, for example with:
|
||||||
|
|
||||||
|
tls_client_context(Context, [hostname("metalevel.at")])
|
||||||
|
|
||||||
|
Using the context and an existing stream S0 (for example, the
|
||||||
|
result of socket_client_open/3), a TLS stream S can be negotiated
|
||||||
|
with:
|
||||||
|
|
||||||
|
tls_client_negotiate(Context, S0, S)
|
||||||
|
|
||||||
|
S will be an encrypted and authenticated stream with the server.
|
||||||
|
|
||||||
|
The advantage of separating the creation of the client context from
|
||||||
|
negotiating a connection is that the context can be created only once,
|
||||||
|
and quickly reused if needed. This is currently not implemented: In
|
||||||
|
the present implementation, a new internal "Connector" is created for
|
||||||
|
every connection, using the specified hostname.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
tls_client_context(tls_context(Host), Options) :-
|
||||||
|
must_be(list, Options),
|
||||||
|
( member(hostname(Host), Options) ->
|
||||||
|
must_be(chars, Host)
|
||||||
|
; Host = ""
|
||||||
|
).
|
||||||
|
|
||||||
|
tls_client_negotiate(tls_context(Host), S0, S) :-
|
||||||
|
'$tls_client_connect'(Host, S0, S).
|
||||||
|
|
||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
TLS Servers
|
||||||
|
===========
|
||||||
|
|
||||||
|
Use tls_server_context/2 to create a TLS context, for example with:
|
||||||
|
|
||||||
|
tls_server_context(Context, [pkcs12(Chars)])
|
||||||
|
|
||||||
|
where Chars is a list of characters with the contents of a
|
||||||
|
DER-formatted PKCS #12 archive. The option password(Ps) can be used
|
||||||
|
to specify the password Ps (also a string) for decrypting the key.
|
||||||
|
On some versions of OSX, and potentially also on other platforms,
|
||||||
|
empty passwords are not supported.
|
||||||
|
|
||||||
|
The archive should contain a leaf certificate and its private key,
|
||||||
|
as well any intermediate certificates that should be sent to
|
||||||
|
clients to allow them to build a chain to a trusted root. The chain
|
||||||
|
certificates should be in order from the leaf certificate towards
|
||||||
|
the root.
|
||||||
|
|
||||||
|
PKCS #12 archives typically have the file extension .p12 or .pfx,
|
||||||
|
and can be created with the OpenSSL pkcs12 tool:
|
||||||
|
|
||||||
|
$ openssl pkcs12 -export -out identity.pfx \
|
||||||
|
-inkey key.pem -in cert.pem -certfile chain_certs.pem
|
||||||
|
|
||||||
|
|
||||||
|
You can use phrase_from_file/3 from library(pio) and seq//1 from
|
||||||
|
library(dcgs) to read the contents of "identity.pfx" into a string:
|
||||||
|
|
||||||
|
phrase_from_file(seq(Chars), "identity.pfx", [type(binary)])
|
||||||
|
|
||||||
|
The obtained context should be treated as an opaque Prolog term.
|
||||||
|
|
||||||
|
Using the context and an existing stream S0 (for example, the
|
||||||
|
result of socket_server_accept/4), a TLS stream S can be negotiated
|
||||||
|
by a Prolog-based server with:
|
||||||
|
|
||||||
|
tls_server_negotiate(Context, S0, S)
|
||||||
|
|
||||||
|
S will be an encrypted and authenticated stream with the client.
|
||||||
|
|
||||||
|
The advantage of separating the creation of the server context from
|
||||||
|
negotiating a connection is that the context can be created only
|
||||||
|
once, and quickly cloned for every incoming connection. This is
|
||||||
|
currently not implemented: In the present implementation, a new context
|
||||||
|
is created for every connection, using the specified parameters.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
tls_server_context(tls_context(Cert,Password), Options) :-
|
||||||
|
( member(pcks12(Cert), Options) ->
|
||||||
|
must_be(chars, Cert)
|
||||||
|
; domain_error(contains_pcks12, Options, tls_server_context/2)
|
||||||
|
),
|
||||||
|
( member(password(Password), Options) ->
|
||||||
|
must_be(chars, Password)
|
||||||
|
; Password = ""
|
||||||
|
).
|
||||||
|
|
||||||
|
tls_server_negotiate(tls_context(Cert,Password), S0, S) :-
|
||||||
|
'$tls_accept_client'(Cert, Password, S0, S).
|
||||||
|
|
||||||
633
src/lib/ugraphs.pl
Normal file
633
src/lib/ugraphs.pl
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
/* Author: R.A.O'Keefe, Vitor Santos Costa, Jan Wielemaker
|
||||||
|
E-mail: J.Wielemaker@vu.nl
|
||||||
|
WWW: http://www.swi-prolog.org
|
||||||
|
Copyright (c) 1984-2021, VU University Amsterdam
|
||||||
|
CWI, Amsterdam
|
||||||
|
SWI-Prolog Solutions .b.v
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(ugraphs,
|
||||||
|
[ add_edges/3, % +Graph, +Edges, -NewGraph
|
||||||
|
add_vertices/3, % +Graph, +Vertices, -NewGraph
|
||||||
|
complement/2, % +Graph, -NewGraph
|
||||||
|
compose/3, % +LeftGraph, +RightGraph, -NewGraph
|
||||||
|
del_edges/3, % +Graph, +Edges, -NewGraph
|
||||||
|
del_vertices/3, % +Graph, +Vertices, -NewGraph
|
||||||
|
edges/2, % +Graph, -Edges
|
||||||
|
neighbors/3, % +Vertex, +Graph, -Vertices
|
||||||
|
neighbours/3, % +Vertex, +Graph, -Vertices
|
||||||
|
reachable/3, % +Vertex, +Graph, -Vertices
|
||||||
|
top_sort/2, % +Graph, -Sort
|
||||||
|
top_sort/3, % +Graph, -Sort0, -Sort
|
||||||
|
transitive_closure/2, % +Graph, -Closure
|
||||||
|
transpose_ugraph/2, % +Graph, -NewGraph
|
||||||
|
vertices/2, % +Graph, -Vertices
|
||||||
|
vertices_edges_to_ugraph/3, % +Vertices, +Edges, -Graph
|
||||||
|
ugraph_union/3, % +Graph1, +Graph2, -Graph
|
||||||
|
connect_ugraph/3 % +Graph1, -Start, -Graph
|
||||||
|
]).
|
||||||
|
|
||||||
|
/** Graph manipulation library
|
||||||
|
|
||||||
|
The S-representation of a graph is a list of (vertex-neighbours) pairs,
|
||||||
|
where the pairs are in standard order (as produced by keysort) and the
|
||||||
|
neighbours of each vertex are also in standard order (as produced by
|
||||||
|
sort). This form is convenient for many calculations.
|
||||||
|
|
||||||
|
A new UGraph from raw data can be created using
|
||||||
|
`vertices_edges_to_ugraph/3`.
|
||||||
|
|
||||||
|
Adapted to support some of the functionality of the SICStus ugraphs
|
||||||
|
library by Vitor Santos Costa.
|
||||||
|
|
||||||
|
Ported from YAP 5.0.1 to SWI-Prolog by Jan Wielemaker.
|
||||||
|
|
||||||
|
Ported from SWI-Prolog to Scryer by [Adrián Arroyo Calle](https://adrianistan.eu)
|
||||||
|
|
||||||
|
License: BSD-2 or Artistic 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
:- use_module(library(pairs)).
|
||||||
|
:- use_module(library(ordsets)).
|
||||||
|
|
||||||
|
%% vertices(+Graph, -Vertices)
|
||||||
|
%
|
||||||
|
% Unify Vertices with all vertices appearing in Graph. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- vertices([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
|
||||||
|
% L = [1, 2, 3, 4, 5]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
vertices([], []) :- !.
|
||||||
|
vertices([Vertex-_|Graph], [Vertex|Vertices]) :-
|
||||||
|
vertices(Graph, Vertices).
|
||||||
|
|
||||||
|
|
||||||
|
%% vertices_edges_to_ugraph(+Vertices, +Edges, -UGraph) is det.
|
||||||
|
%
|
||||||
|
% Create a UGraph from Vertices and edges. Given a graph with a
|
||||||
|
% set of Vertices and a set of Edges, Graph must unify with the
|
||||||
|
% corresponding S-representation. Note that the vertices without
|
||||||
|
% edges will appear in Vertices but not in Edges. Moreover, it is
|
||||||
|
% sufficient for a vertice to appear in Edges.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- vertices_edges_to_ugraph([],[1-3,2-4,4-5,1-5], L).
|
||||||
|
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[]]
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% In this case all vertices are defined implicitly. The next
|
||||||
|
% example shows three unconnected vertices:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- vertices_edges_to_ugraph([6,7,8],[1-3,2-4,4-5,1-5], L).
|
||||||
|
% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[], 6-[], 7-[], 8-[]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
vertices_edges_to_ugraph(Vertices, Edges, Graph) :-
|
||||||
|
sort(Edges, EdgeSet),
|
||||||
|
p_to_s_vertices(EdgeSet, IVertexBag),
|
||||||
|
append(Vertices, IVertexBag, VertexBag),
|
||||||
|
sort(VertexBag, VertexSet),
|
||||||
|
p_to_s_group(VertexSet, EdgeSet, Graph).
|
||||||
|
|
||||||
|
|
||||||
|
%% add_vertices(+Graph, +Vertices, -NewGraph)
|
||||||
|
%
|
||||||
|
% Unify NewGraph with a new graph obtained by adding the list of
|
||||||
|
% Vertices to Graph. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- add_vertices([1-[3,5],2-[]], [0,1,2,9], NG).
|
||||||
|
% NG = [0-[], 1-[3,5], 2-[], 9-[]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
% replace with real msort/2 when available
|
||||||
|
msort_(List, Sorted) :-
|
||||||
|
pairs_keys(Pairs, List),
|
||||||
|
keysort(Pairs, SortedPairs),
|
||||||
|
pairs_keys(SortedPairs, Sorted).
|
||||||
|
|
||||||
|
add_vertices(Graph, Vertices, NewGraph) :-
|
||||||
|
% msort/2 not available in Scryer Prolog yet: msort(Vertices, V1),
|
||||||
|
msort_(Vertices, V1),
|
||||||
|
add_vertices_to_s_graph(V1, Graph, NewGraph).
|
||||||
|
|
||||||
|
add_vertices_to_s_graph(L, [], NL) :-
|
||||||
|
!,
|
||||||
|
add_empty_vertices(L, NL).
|
||||||
|
add_vertices_to_s_graph([], L, L) :- !.
|
||||||
|
add_vertices_to_s_graph([V1|VL], [V-Edges|G], NGL) :-
|
||||||
|
compare(Res, V1, V),
|
||||||
|
add_vertices_to_s_graph(Res, V1, VL, V, Edges, G, NGL).
|
||||||
|
|
||||||
|
add_vertices_to_s_graph(=, _, VL, V, Edges, G, [V-Edges|NGL]) :-
|
||||||
|
add_vertices_to_s_graph(VL, G, NGL).
|
||||||
|
add_vertices_to_s_graph(<, V1, VL, V, Edges, G, [V1-[]|NGL]) :-
|
||||||
|
add_vertices_to_s_graph(VL, [V-Edges|G], NGL).
|
||||||
|
add_vertices_to_s_graph(>, V1, VL, V, Edges, G, [V-Edges|NGL]) :-
|
||||||
|
add_vertices_to_s_graph([V1|VL], G, NGL).
|
||||||
|
|
||||||
|
add_empty_vertices([], []).
|
||||||
|
add_empty_vertices([V|G], [V-[]|NG]) :-
|
||||||
|
add_empty_vertices(G, NG).
|
||||||
|
|
||||||
|
%% del_vertices(+Graph, +Vertices, -NewGraph) is det.
|
||||||
|
%
|
||||||
|
% Unify NewGraph with a new graph obtained by deleting the list of
|
||||||
|
% Vertices and all the edges that start from or go to a vertex in
|
||||||
|
% Vertices to the Graph. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- del_vertices([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[2,6],8-[]],
|
||||||
|
% [2,1],
|
||||||
|
% NL).
|
||||||
|
% NL = [3-[],4-[5],5-[],6-[],7-[6],8-[]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
del_vertices(Graph, Vertices, NewGraph) :-
|
||||||
|
sort(Vertices, V1), % JW: was msort
|
||||||
|
( V1 = []
|
||||||
|
-> Graph = NewGraph
|
||||||
|
; del_vertices(Graph, V1, V1, NewGraph)
|
||||||
|
).
|
||||||
|
|
||||||
|
del_vertices(G, [], V1, NG) :-
|
||||||
|
!,
|
||||||
|
del_remaining_edges_for_vertices(G, V1, NG).
|
||||||
|
del_vertices([], _, _, []).
|
||||||
|
del_vertices([V-Edges|G], [V0|Vs], V1, NG) :-
|
||||||
|
compare(Res, V, V0),
|
||||||
|
split_on_del_vertices(Res, V,Edges, [V0|Vs], NVs, V1, NG, NGr),
|
||||||
|
del_vertices(G, NVs, V1, NGr).
|
||||||
|
|
||||||
|
del_remaining_edges_for_vertices([], _, []).
|
||||||
|
del_remaining_edges_for_vertices([V0-Edges|G], V1, [V0-NEdges|NG]) :-
|
||||||
|
ord_subtract(Edges, V1, NEdges),
|
||||||
|
del_remaining_edges_for_vertices(G, V1, NG).
|
||||||
|
|
||||||
|
split_on_del_vertices(<, V, Edges, Vs, Vs, V1, [V-NEdges|NG], NG) :-
|
||||||
|
ord_subtract(Edges, V1, NEdges).
|
||||||
|
split_on_del_vertices(>, V, Edges, [_|Vs], Vs, V1, [V-NEdges|NG], NG) :-
|
||||||
|
ord_subtract(Edges, V1, NEdges).
|
||||||
|
split_on_del_vertices(=, _, _, [_|Vs], Vs, _, NG, NG).
|
||||||
|
|
||||||
|
%% add_edges(+Graph, +Edges, -NewGraph)
|
||||||
|
%
|
||||||
|
% Unify NewGraph with a new graph obtained by adding the list of Edges
|
||||||
|
% to Graph. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- add_edges([1-[3,5],2-[4],3-[],4-[5],
|
||||||
|
% 5-[],6-[],7-[],8-[]],
|
||||||
|
% [1-6,2-3,3-2,5-7,3-2,4-5],
|
||||||
|
% NL).
|
||||||
|
% NL = [1-[3,5,6], 2-[3,4], 3-[2], 4-[5],
|
||||||
|
% 5-[7], 6-[], 7-[], 8-[]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
add_edges(Graph, Edges, NewGraph) :-
|
||||||
|
p_to_s_graph(Edges, G1),
|
||||||
|
ugraph_union(Graph, G1, NewGraph).
|
||||||
|
|
||||||
|
%% ugraph_union(+Graph1, +Graph2, -NewGraph)
|
||||||
|
%
|
||||||
|
% NewGraph is the union of Graph1 and Graph2. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- ugraph_union([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
|
||||||
|
% L = [1-[2], 2-[3,4], 3-[1,2,4]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
ugraph_union(Set1, [], Set1) :- !.
|
||||||
|
ugraph_union([], Set2, Set2) :- !.
|
||||||
|
ugraph_union([Head1-E1|Tail1], [Head2-E2|Tail2], Union) :-
|
||||||
|
compare(Order, Head1, Head2),
|
||||||
|
ugraph_union(Order, Head1-E1, Tail1, Head2-E2, Tail2, Union).
|
||||||
|
|
||||||
|
ugraph_union(=, Head-E1, Tail1, _-E2, Tail2, [Head-Es|Union]) :-
|
||||||
|
ord_union(E1, E2, Es),
|
||||||
|
ugraph_union(Tail1, Tail2, Union).
|
||||||
|
ugraph_union(<, Head1, Tail1, Head2, Tail2, [Head1|Union]) :-
|
||||||
|
ugraph_union(Tail1, [Head2|Tail2], Union).
|
||||||
|
ugraph_union(>, Head1, Tail1, Head2, Tail2, [Head2|Union]) :-
|
||||||
|
ugraph_union([Head1|Tail1], Tail2, Union).
|
||||||
|
|
||||||
|
%% del_edges(+Graph, +Edges, -NewGraph)
|
||||||
|
%
|
||||||
|
% Unify NewGraph with a new graph obtained by removing the list of
|
||||||
|
% Edges from Graph. Notice that no vertices are deleted. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- del_edges([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[],8-[]],
|
||||||
|
% [1-6,2-3,3-2,5-7,3-2,4-5,1-3],
|
||||||
|
% NL).
|
||||||
|
% NL = [1-[5],2-[4],3-[],4-[],5-[],6-[],7-[],8-[]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
del_edges(Graph, Edges, NewGraph) :-
|
||||||
|
p_to_s_graph(Edges, G1),
|
||||||
|
graph_subtract(Graph, G1, NewGraph).
|
||||||
|
|
||||||
|
%% graph_subtract(+Set1, +Set2, ?Difference)
|
||||||
|
%
|
||||||
|
% Is based on `ord_subtract/3`
|
||||||
|
|
||||||
|
graph_subtract(Set1, [], Set1) :- !.
|
||||||
|
graph_subtract([], _, []).
|
||||||
|
graph_subtract([Head1-E1|Tail1], [Head2-E2|Tail2], Difference) :-
|
||||||
|
compare(Order, Head1, Head2),
|
||||||
|
graph_subtract(Order, Head1-E1, Tail1, Head2-E2, Tail2, Difference).
|
||||||
|
|
||||||
|
graph_subtract(=, H-E1, Tail1, _-E2, Tail2, [H-E|Difference]) :-
|
||||||
|
ord_subtract(E1,E2,E),
|
||||||
|
graph_subtract(Tail1, Tail2, Difference).
|
||||||
|
graph_subtract(<, Head1, Tail1, Head2, Tail2, [Head1|Difference]) :-
|
||||||
|
graph_subtract(Tail1, [Head2|Tail2], Difference).
|
||||||
|
graph_subtract(>, Head1, Tail1, _, Tail2, Difference) :-
|
||||||
|
graph_subtract([Head1|Tail1], Tail2, Difference).
|
||||||
|
|
||||||
|
%% edges(+Graph, -Edges)
|
||||||
|
%
|
||||||
|
% Unify Edges with all edges appearing in Graph. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- edges([1-[3,5],2-[4],3-[],4-[5],5-[]], L).
|
||||||
|
% L = [1-3, 1-5, 2-4, 4-5]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
edges(Graph, Edges) :-
|
||||||
|
s_to_p_graph(Graph, Edges).
|
||||||
|
|
||||||
|
p_to_s_graph(P_Graph, S_Graph) :-
|
||||||
|
sort(P_Graph, EdgeSet),
|
||||||
|
p_to_s_vertices(EdgeSet, VertexBag),
|
||||||
|
sort(VertexBag, VertexSet),
|
||||||
|
p_to_s_group(VertexSet, EdgeSet, S_Graph).
|
||||||
|
|
||||||
|
|
||||||
|
p_to_s_vertices([], []).
|
||||||
|
p_to_s_vertices([A-Z|Edges], [A,Z|Vertices]) :-
|
||||||
|
p_to_s_vertices(Edges, Vertices).
|
||||||
|
|
||||||
|
|
||||||
|
p_to_s_group([], _, []).
|
||||||
|
p_to_s_group([Vertex|Vertices], EdgeSet, [Vertex-Neibs|G]) :-
|
||||||
|
p_to_s_group(EdgeSet, Vertex, Neibs, RestEdges),
|
||||||
|
p_to_s_group(Vertices, RestEdges, G).
|
||||||
|
|
||||||
|
|
||||||
|
p_to_s_group([V1-X|Edges], V2, [X|Neibs], RestEdges) :- V1 == V2,
|
||||||
|
!,
|
||||||
|
p_to_s_group(Edges, V2, Neibs, RestEdges).
|
||||||
|
p_to_s_group(Edges, _, [], Edges).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
s_to_p_graph([], []) :- !.
|
||||||
|
s_to_p_graph([Vertex-Neibs|G], P_Graph) :-
|
||||||
|
s_to_p_graph(Neibs, Vertex, P_Graph, Rest_P_Graph),
|
||||||
|
s_to_p_graph(G, Rest_P_Graph).
|
||||||
|
|
||||||
|
|
||||||
|
s_to_p_graph([], _, P_Graph, P_Graph) :- !.
|
||||||
|
s_to_p_graph([Neib|Neibs], Vertex, [Vertex-Neib|P], Rest_P) :-
|
||||||
|
s_to_p_graph(Neibs, Vertex, P, Rest_P).
|
||||||
|
|
||||||
|
%% transitive_closure(+Graph, -Closure)
|
||||||
|
%
|
||||||
|
% Generate the graph Closure as the transitive closure of Graph.
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- transitive_closure([1-[2,3],2-[4,5],4-[6]],L).
|
||||||
|
% L = [1-[2,3,4,5,6], 2-[4,5,6], 4-[6]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
transitive_closure(Graph, Closure) :-
|
||||||
|
warshall(Graph, Graph, Closure).
|
||||||
|
|
||||||
|
warshall([], Closure, Closure) :- !.
|
||||||
|
warshall([V-_|G], E, Closure) :-
|
||||||
|
memberchk(V-Y, E), % Y := E(v)
|
||||||
|
warshall(E, V, Y, NewE),
|
||||||
|
warshall(G, NewE, Closure).
|
||||||
|
|
||||||
|
|
||||||
|
warshall([X-Neibs|G], V, Y, [X-NewNeibs|NewG]) :-
|
||||||
|
memberchk(V, Neibs),
|
||||||
|
!,
|
||||||
|
ord_union(Neibs, Y, NewNeibs),
|
||||||
|
warshall(G, V, Y, NewG).
|
||||||
|
warshall([X-Neibs|G], V, Y, [X-Neibs|NewG]) :-
|
||||||
|
!,
|
||||||
|
warshall(G, V, Y, NewG).
|
||||||
|
warshall([], _, _, []).
|
||||||
|
|
||||||
|
%% transpose_ugraph(Graph, NewGraph) is det.
|
||||||
|
%
|
||||||
|
% Unify NewGraph with a new graph obtained from Graph by replacing
|
||||||
|
% all edges of the form V1-V2 by edges of the form V2-V1. The cost
|
||||||
|
% is O(|V|\*log(|V|)). Notice that an undirected graph is its own
|
||||||
|
% transpose. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- transpose([1-[3,5],2-[4],3-[],4-[5],
|
||||||
|
% 5-[],6-[],7-[],8-[]], NL).
|
||||||
|
% NL = [1-[],2-[],3-[1],4-[2],5-[1,4],6-[],7-[],8-[]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
transpose_ugraph(Graph, NewGraph) :-
|
||||||
|
edges(Graph, Edges),
|
||||||
|
vertices(Graph, Vertices),
|
||||||
|
flip_edges(Edges, TransposedEdges),
|
||||||
|
vertices_edges_to_ugraph(Vertices, TransposedEdges, NewGraph).
|
||||||
|
|
||||||
|
flip_edges([], []).
|
||||||
|
flip_edges([Key-Val|Pairs], [Val-Key|Flipped]) :-
|
||||||
|
flip_edges(Pairs, Flipped).
|
||||||
|
|
||||||
|
%% compose(+LeftGraph, +RightGraph, -NewGraph)
|
||||||
|
%
|
||||||
|
% Compose NewGraph by connecting the _drains_ of LeftGraph to the
|
||||||
|
% _sources_ of RightGraph. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- compose([1-[2],2-[3]],[2-[4],3-[1,2,4]],L).
|
||||||
|
% L = [1-[4], 2-[1,2,4], 3-[]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
compose(G1, G2, Composition) :-
|
||||||
|
vertices(G1, V1),
|
||||||
|
vertices(G2, V2),
|
||||||
|
ord_union(V1, V2, V),
|
||||||
|
compose(V, G1, G2, Composition).
|
||||||
|
|
||||||
|
compose([], _, _, []) :- !.
|
||||||
|
compose([Vertex|Vertices], [Vertex-Neibs|G1], G2,
|
||||||
|
[Vertex-Comp|Composition]) :-
|
||||||
|
!,
|
||||||
|
compose1(Neibs, G2, [], Comp),
|
||||||
|
compose(Vertices, G1, G2, Composition).
|
||||||
|
compose([Vertex|Vertices], G1, G2, [Vertex-[]|Composition]) :-
|
||||||
|
compose(Vertices, G1, G2, Composition).
|
||||||
|
|
||||||
|
|
||||||
|
compose1([V1|Vs1], [V2-N2|G2], SoFar, Comp) :-
|
||||||
|
compare(Rel, V1, V2),
|
||||||
|
!,
|
||||||
|
compose1(Rel, V1, Vs1, V2, N2, G2, SoFar, Comp).
|
||||||
|
compose1(_, _, Comp, Comp).
|
||||||
|
|
||||||
|
|
||||||
|
compose1(<, _, Vs1, V2, N2, G2, SoFar, Comp) :-
|
||||||
|
!,
|
||||||
|
compose1(Vs1, [V2-N2|G2], SoFar, Comp).
|
||||||
|
compose1(>, V1, Vs1, _, _, G2, SoFar, Comp) :-
|
||||||
|
!,
|
||||||
|
compose1([V1|Vs1], G2, SoFar, Comp).
|
||||||
|
compose1(=, V1, Vs1, V1, N2, G2, SoFar, Comp) :-
|
||||||
|
ord_union(N2, SoFar, Next),
|
||||||
|
compose1(Vs1, G2, Next, Comp).
|
||||||
|
|
||||||
|
%% top_sort(+Graph, -Sorted) is semidet.
|
||||||
|
%
|
||||||
|
% Sorted is a topological sorted list of nodes in Graph. A
|
||||||
|
% toplogical sort is possible if the graph is connected and
|
||||||
|
% acyclic. In the example we show how topological sorting works
|
||||||
|
% for a linear graph:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- top_sort([1-[2], 2-[3], 3-[]], L).
|
||||||
|
% L = [1, 2, 3]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
top_sort(Graph, Sorted) :-
|
||||||
|
vertices_and_zeros(Graph, Vertices, Counts0),
|
||||||
|
count_edges(Graph, Vertices, Counts0, Counts1),
|
||||||
|
select_zeros(Counts1, Vertices, Zeros),
|
||||||
|
top_sort(Zeros, Sorted, Graph, Vertices, Counts1).
|
||||||
|
|
||||||
|
%% top_sort(+Graph, -Sorted, ?Tail) is semidet.
|
||||||
|
%
|
||||||
|
% The predicate `top_sort/3` is a difference list version of
|
||||||
|
% `top_sort/2`.
|
||||||
|
|
||||||
|
top_sort(Graph, Sorted0, Sorted) :-
|
||||||
|
vertices_and_zeros(Graph, Vertices, Counts0),
|
||||||
|
count_edges(Graph, Vertices, Counts0, Counts1),
|
||||||
|
select_zeros(Counts1, Vertices, Zeros),
|
||||||
|
top_sort(Zeros, Sorted, Sorted0, Graph, Vertices, Counts1).
|
||||||
|
|
||||||
|
|
||||||
|
vertices_and_zeros([], [], []) :- !.
|
||||||
|
vertices_and_zeros([Vertex-_|Graph], [Vertex|Vertices], [0|Zeros]) :-
|
||||||
|
vertices_and_zeros(Graph, Vertices, Zeros).
|
||||||
|
|
||||||
|
|
||||||
|
count_edges([], _, Counts, Counts) :- !.
|
||||||
|
count_edges([_-Neibs|Graph], Vertices, Counts0, Counts2) :-
|
||||||
|
incr_list(Neibs, Vertices, Counts0, Counts1),
|
||||||
|
count_edges(Graph, Vertices, Counts1, Counts2).
|
||||||
|
|
||||||
|
|
||||||
|
incr_list([], _, Counts, Counts) :- !.
|
||||||
|
incr_list([V1|Neibs], [V2|Vertices], [M|Counts0], [N|Counts1]) :-
|
||||||
|
V1 == V2,
|
||||||
|
!,
|
||||||
|
N is M+1,
|
||||||
|
incr_list(Neibs, Vertices, Counts0, Counts1).
|
||||||
|
incr_list(Neibs, [_|Vertices], [N|Counts0], [N|Counts1]) :-
|
||||||
|
incr_list(Neibs, Vertices, Counts0, Counts1).
|
||||||
|
|
||||||
|
|
||||||
|
select_zeros([], [], []) :- !.
|
||||||
|
select_zeros([0|Counts], [Vertex|Vertices], [Vertex|Zeros]) :-
|
||||||
|
!,
|
||||||
|
select_zeros(Counts, Vertices, Zeros).
|
||||||
|
select_zeros([_|Counts], [_|Vertices], Zeros) :-
|
||||||
|
select_zeros(Counts, Vertices, Zeros).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
top_sort([], [], Graph, _, Counts) :-
|
||||||
|
!,
|
||||||
|
vertices_and_zeros(Graph, _, Counts).
|
||||||
|
top_sort([Zero|Zeros], [Zero|Sorted], Graph, Vertices, Counts1) :-
|
||||||
|
graph_memberchk(Zero-Neibs, Graph),
|
||||||
|
decr_list(Neibs, Vertices, Counts1, Counts2, Zeros, NewZeros),
|
||||||
|
top_sort(NewZeros, Sorted, Graph, Vertices, Counts2).
|
||||||
|
|
||||||
|
top_sort([], Sorted0, Sorted0, Graph, _, Counts) :-
|
||||||
|
!,
|
||||||
|
vertices_and_zeros(Graph, _, Counts).
|
||||||
|
top_sort([Zero|Zeros], [Zero|Sorted], Sorted0, Graph, Vertices, Counts1) :-
|
||||||
|
graph_memberchk(Zero-Neibs, Graph),
|
||||||
|
decr_list(Neibs, Vertices, Counts1, Counts2, Zeros, NewZeros),
|
||||||
|
top_sort(NewZeros, Sorted, Sorted0, Graph, Vertices, Counts2).
|
||||||
|
|
||||||
|
graph_memberchk(Element1-Edges, [Element2-Edges2|_]) :-
|
||||||
|
Element1 == Element2,
|
||||||
|
!,
|
||||||
|
Edges = Edges2.
|
||||||
|
graph_memberchk(Element, [_|Rest]) :-
|
||||||
|
graph_memberchk(Element, Rest).
|
||||||
|
|
||||||
|
|
||||||
|
decr_list([], _, Counts, Counts, Zeros, Zeros) :- !.
|
||||||
|
decr_list([V1|Neibs], [V2|Vertices], [1|Counts1], [0|Counts2], Zi, Zo) :-
|
||||||
|
V1 == V2,
|
||||||
|
!,
|
||||||
|
decr_list(Neibs, Vertices, Counts1, Counts2, [V2|Zi], Zo).
|
||||||
|
decr_list([V1|Neibs], [V2|Vertices], [N|Counts1], [M|Counts2], Zi, Zo) :-
|
||||||
|
V1 == V2,
|
||||||
|
!,
|
||||||
|
M is N-1,
|
||||||
|
decr_list(Neibs, Vertices, Counts1, Counts2, Zi, Zo).
|
||||||
|
decr_list(Neibs, [_|Vertices], [N|Counts1], [N|Counts2], Zi, Zo) :-
|
||||||
|
decr_list(Neibs, Vertices, Counts1, Counts2, Zi, Zo).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% neighbours(+Vertex, +Graph, -Neigbours) is det.
|
||||||
|
%
|
||||||
|
% Neigbours is a sorted list of the neighbours of Vertex in Graph.
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- neighbours(4,[1-[3,5],2-[4],3-[],
|
||||||
|
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
|
||||||
|
% NL = [1,2,7,5]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
%% neighbors(+Vertex, +Graph, -Neigbours) is det.
|
||||||
|
%
|
||||||
|
% Same as `neighbours/3`.
|
||||||
|
|
||||||
|
neighbors(Vertex, Graph, Neig) :-
|
||||||
|
neighbours(Vertex, Graph, Neig).
|
||||||
|
|
||||||
|
neighbours(V,[V0-Neig|_],Neig) :-
|
||||||
|
V == V0,
|
||||||
|
!.
|
||||||
|
neighbours(V,[_|G],Neig) :-
|
||||||
|
neighbours(V,G,Neig).
|
||||||
|
|
||||||
|
|
||||||
|
%% connect_ugraph(+UGraphIn, -Start, -UGraphOut) is det.
|
||||||
|
%
|
||||||
|
% Adds Start as an additional vertex that is connected to all vertices
|
||||||
|
% in UGraphIn. This can be used to create an topological sort for a
|
||||||
|
% not connected graph. Start is before any vertex in UGraphIn in the
|
||||||
|
% standard order of terms. No vertex in UGraphIn can be a variable.
|
||||||
|
%
|
||||||
|
% Can be used to order a not-connected graph as follows:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% top_sort_unconnected(Graph, Vertices) :-
|
||||||
|
% ( top_sort(Graph, Vertices)
|
||||||
|
% -> true
|
||||||
|
% ; connect_ugraph(Graph, Start, Connected),
|
||||||
|
% top_sort(Connected, Ordered0),
|
||||||
|
% Ordered0 = [Start|Vertices]
|
||||||
|
% ).
|
||||||
|
% ```
|
||||||
|
|
||||||
|
connect_ugraph([], 0, []) :- !.
|
||||||
|
connect_ugraph(Graph, Start, [Start-Vertices|Graph]) :-
|
||||||
|
vertices(Graph, Vertices),
|
||||||
|
Vertices = [First|_],
|
||||||
|
before(First, Start).
|
||||||
|
|
||||||
|
%% before(+Term, -Before) is det.
|
||||||
|
%
|
||||||
|
% Unify Before to a term that comes before Term in the standard
|
||||||
|
% order of terms.
|
||||||
|
%
|
||||||
|
% Throws `instantiation_error` if Term is unbound.
|
||||||
|
|
||||||
|
before(X, _) :-
|
||||||
|
var(X),
|
||||||
|
!,
|
||||||
|
instantiation_error(X).
|
||||||
|
before(Number, Start) :-
|
||||||
|
number(Number),
|
||||||
|
!,
|
||||||
|
Start is Number - 1.
|
||||||
|
before(_, 0).
|
||||||
|
|
||||||
|
|
||||||
|
%% complement(+UGraphIn, -UGraphOut)
|
||||||
|
%
|
||||||
|
% UGraphOut is a ugraph with an edge between all vertices that are
|
||||||
|
% _not_ connected in UGraphIn and all edges from UGraphIn removed.
|
||||||
|
% Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- complement([1-[3,5],2-[4],3-[],
|
||||||
|
% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL).
|
||||||
|
% NL = [1-[2,4,6,7,8],2-[1,3,5,6,7,8],3-[1,2,4,5,6,7,8],
|
||||||
|
% 4-[3,5,6,8],5-[1,2,3,4,6,7,8],6-[1,2,3,4,5,7,8],
|
||||||
|
% 7-[1,2,3,4,5,6,8],8-[1,2,3,4,5,6,7]]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
|
||||||
|
% TODO: Simple two-step algorithm. You could be smarter, I suppose.
|
||||||
|
|
||||||
|
complement(G, NG) :-
|
||||||
|
vertices(G,Vs),
|
||||||
|
complement(G,Vs,NG).
|
||||||
|
|
||||||
|
complement([], _, []).
|
||||||
|
complement([V-Ns|G], Vs, [V-INs|NG]) :-
|
||||||
|
ord_add_element(Ns,V,Ns1),
|
||||||
|
ord_subtract(Vs,Ns1,INs),
|
||||||
|
complement(G, Vs, NG).
|
||||||
|
|
||||||
|
%% reachable(+Vertex, +UGraph, -Vertices)
|
||||||
|
%
|
||||||
|
% True when Vertices is an ordered set of vertices reachable in
|
||||||
|
% UGraph, including Vertex. Example:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ?- reachable(1,[1-[3,5],2-[4],3-[],4-[5],5-[]],V).
|
||||||
|
% V = [1, 3, 5]
|
||||||
|
% ```
|
||||||
|
|
||||||
|
reachable(N, G, Rs) :-
|
||||||
|
reachable([N], G, [N], Rs).
|
||||||
|
|
||||||
|
reachable([], _, Rs, Rs).
|
||||||
|
reachable([N|Ns], G, Rs0, RsF) :-
|
||||||
|
neighbours(N, G, Nei),
|
||||||
|
ord_union(Rs0, Nei, Rs1, D),
|
||||||
|
append(Ns, D, Nsi),
|
||||||
|
reachable(Nsi, G, Rs1, RsF).
|
||||||
125
src/lib/uuid.pl
Normal file
125
src/lib/uuid.pl
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
Written in February 2021 by Adrián Arroyo (adrian.arroyocalle@gmail.com)
|
||||||
|
Part of Scryer-Prolog
|
||||||
|
I place this code in the public domain. Use it in any way you want.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
/**
|
||||||
|
This library provides reasoning and working with [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)
|
||||||
|
(only version 4 right now).
|
||||||
|
|
||||||
|
There are three predicates:
|
||||||
|
|
||||||
|
* `uuidv4/1`, to generate a new UUIDv4
|
||||||
|
* `uuidv4_string/1`, to generate a new UUIDv4 in string hex representation
|
||||||
|
* `uuid_string/2`, to converte between UUID list of bytes and UUID hex representation
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
?- uuidv4(X).
|
||||||
|
X = [42,147,248,242,117,196,79,2,129,159|...].
|
||||||
|
?- uuidv4_string(X).
|
||||||
|
X = "428499fc-76e3-4240- ...".
|
||||||
|
?- uuidv4(X), uuid_string(X, S).
|
||||||
|
X = [173,12,244,152,139,118,64,139,137,4|...], S = "ad0cf498-8b76-408b- ...".
|
||||||
|
?- uuid_string(X, "61ae692e-eaf6-4199-8dd3-9f01db70a20b").
|
||||||
|
X = [97,174,105,46,234,246,65,153,141,211|...].
|
||||||
|
*/
|
||||||
|
|
||||||
|
:- module(uuid, [
|
||||||
|
uuidv4/1,
|
||||||
|
uuidv4_string/1,
|
||||||
|
uuid_string/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(crypto)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(lists)).
|
||||||
|
|
||||||
|
/*
|
||||||
|
An UUID is made of 16 bytes, composed of 5 sections:
|
||||||
|
time_low - 4
|
||||||
|
time_mid - 2
|
||||||
|
time_hi_and_version - 2
|
||||||
|
clock_seq_hi_and_res_clock_seq_low - 2
|
||||||
|
node - 6
|
||||||
|
UUID v4 can be generated from a set of 16 random bytes: https://www.rfc-archive.org/getrfc.php?rfc=4122#gsc.tab=0 (section 4.4)
|
||||||
|
*/
|
||||||
|
|
||||||
|
%% uuidv4(-Uuid).
|
||||||
|
%
|
||||||
|
% Generates a new UUID v4 (random). It unifies with a list of bytes.
|
||||||
|
uuidv4(Uuid) :-
|
||||||
|
crypto_n_random_bytes(16, Bytes),
|
||||||
|
Bytes = [B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16],
|
||||||
|
byte_bits(B9, BitsClockSeqHi0),
|
||||||
|
BitsClockSeqHi0 = [_X7, _X6, X5, X4, X3, X2, X1, X0],
|
||||||
|
NewBitsClockSeqHi0 = [1, 0, X5, X4, X3, X2, X1, X0],
|
||||||
|
byte_bits(NewClockSeqHi0, NewBitsClockSeqHi0),
|
||||||
|
byte_bits(B7, BitsTimeHi),
|
||||||
|
BitsTimeHi = [_Y7, _Y6, _Y5, _Y4, Y3, Y2, Y1, Y0],
|
||||||
|
NewBitsTimeHi = [0, 1, 0, 0, Y3, Y2, Y1, Y0],
|
||||||
|
byte_bits(NewTimeHi, NewBitsTimeHi),
|
||||||
|
Uuid = [B1, B2, B3, B4, B5, B6, NewTimeHi, B8, NewClockSeqHi0, B10, B11, B12, B13, B14, B15, B16].
|
||||||
|
|
||||||
|
%% uuidv4_string(-UuidString).
|
||||||
|
%
|
||||||
|
% Generates a new UUID v4 (random). It unifies with a string representation of the UUID.
|
||||||
|
% It is equivalent of calling `uuidv4/1` followed by `uuid_string/2`.
|
||||||
|
uuidv4_string(String) :- uuidv4(Uuid), uuid_string(Uuid, String).
|
||||||
|
|
||||||
|
%% uuid_string(?UuidBytes, ?UuidString).
|
||||||
|
%
|
||||||
|
% Translates between the bytes representation and the string representation of the same UUID.
|
||||||
|
uuid_string(Uuid, String) :-
|
||||||
|
Uuid = [B1, B2, B3, B4, B5, B6, B7, B8, B9, B10, B11, B12, B13, B14, B15, B16],
|
||||||
|
phrase(uuid_([S1, S2, S3, S4, S5]), String),
|
||||||
|
hex_bytes(S1, [B1, B2, B3, B4]),
|
||||||
|
hex_bytes(S2, [B5, B6]),
|
||||||
|
hex_bytes(S3, [B7, B8]),
|
||||||
|
hex_bytes(S4, [B9, B10]),
|
||||||
|
hex_bytes(S5, [B11, B12, B13, B14, B15, B16]).
|
||||||
|
|
||||||
|
uuid_([S1, S2, S3, S4, S5]) -->
|
||||||
|
{
|
||||||
|
length(S1, 8),
|
||||||
|
length(S2, 4),
|
||||||
|
length(S3, 4),
|
||||||
|
length(S4, 4),
|
||||||
|
length(S5, 12)
|
||||||
|
},
|
||||||
|
S1,
|
||||||
|
"-",
|
||||||
|
S2,
|
||||||
|
"-",
|
||||||
|
S3,
|
||||||
|
"-",
|
||||||
|
S4,
|
||||||
|
"-",
|
||||||
|
S5.
|
||||||
|
|
||||||
|
byte_bits(Byte, Bits) :-
|
||||||
|
\+ var(Byte),
|
||||||
|
byte_bits_(Byte, Bits),
|
||||||
|
length(Bits, 8),!.
|
||||||
|
|
||||||
|
byte_bits(Byte, Bits) :-
|
||||||
|
\+ var(Bits),
|
||||||
|
length(Bits, 8),
|
||||||
|
byte_bits__(Byte, Bits),!.
|
||||||
|
|
||||||
|
byte_bits_(0, [0]).
|
||||||
|
byte_bits_(1, [1]).
|
||||||
|
byte_bits_(Byte, Bits) :-
|
||||||
|
R is Byte // 2,
|
||||||
|
M is Byte mod 2,
|
||||||
|
byte_bits_(R, Bits0),
|
||||||
|
append(Bits0, [M], Bits).
|
||||||
|
|
||||||
|
byte_bits__(0, []).
|
||||||
|
byte_bits__(Byte, Bits) :-
|
||||||
|
length(Bits, N),
|
||||||
|
Bits = [Bit|Bits0],
|
||||||
|
byte_bits__(Byte0, Bits0),
|
||||||
|
Byte is Byte0 + Bit*(2 ^ (N-1)).
|
||||||
663
src/lib/xpath.pl
Normal file
663
src/lib/xpath.pl
Normal file
@@ -0,0 +1,663 @@
|
|||||||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
The original copyright and licence information is shown below.
|
||||||
|
|
||||||
|
Adapted in June 2020 by Markus Triska (triska@metalevel.at)
|
||||||
|
Part of Scryer Prolog.
|
||||||
|
|
||||||
|
In order to improve portability and reliability of this library, I
|
||||||
|
have changed various parts of the original code to make it
|
||||||
|
conforming to the Prolog ISO standard. I have removed all
|
||||||
|
occurrences of proprietary non-standard ad hoc data types.
|
||||||
|
|
||||||
|
In Scryer Prolog, we represent strings as lists of characters.
|
||||||
|
This makes them amenable to processing with DCGs and other
|
||||||
|
built-in mechanisms such as existing predicates on lists.
|
||||||
|
Scryer Prolog represents lists of characters very compactly,
|
||||||
|
making it an ideal representation for large amounts of text. It
|
||||||
|
fully conforms to the ISO standard and is very portable.
|
||||||
|
|
||||||
|
This library is intended to be used in tandem with load_html/3
|
||||||
|
from library(sgml), which creates DOM trees from markup documents.
|
||||||
|
It can be combined with http_open/3 from library(http/http_open)
|
||||||
|
to read such documents directly from network connections.
|
||||||
|
|
||||||
|
For instance, to inspect all links to Prolog files (*.pl) on
|
||||||
|
Scryer Prolog's project page, we can use:
|
||||||
|
|
||||||
|
:- use_module(library(http/http_open)).
|
||||||
|
:- use_module(library(sgml)).
|
||||||
|
:- use_module(library(xpath)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
|
||||||
|
link_to_pl_file(File) :-
|
||||||
|
http_open("https://github.com/mthom/scryer-prolog", S, []),
|
||||||
|
load_html(stream(S), DOM, []),
|
||||||
|
xpath(DOM, //a(@href), File),
|
||||||
|
phrase((...,".pl"), File).
|
||||||
|
|
||||||
|
Yielding:
|
||||||
|
|
||||||
|
?- link_to_pl_file(File).
|
||||||
|
%@ File = "/mthom/scryer-prolog/blob/master/src/lib/dcgs.pl"
|
||||||
|
%@ ; File = "/mthom/scryer-prolog/blob/master/src/lib/pio.pl"
|
||||||
|
%@ ; File = "/mthom/scryer-prolog/blob/master/src/lib/tabling.pl"
|
||||||
|
%@ ; ... .
|
||||||
|
|
||||||
|
Parts of the original functionality may not yet work. Please
|
||||||
|
consider such parts opportunities for improvements, and file
|
||||||
|
issues in case you need additional features.
|
||||||
|
|
||||||
|
The original copyright information follows.
|
||||||
|
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
|
||||||
|
Author: Jan Wielemaker
|
||||||
|
E-mail: J.Wielemaker@vu.nl
|
||||||
|
WWW: http://www.swi-prolog.org
|
||||||
|
Copyright (c) 2009-2019, University of Amsterdam
|
||||||
|
VU University Amsterdam
|
||||||
|
CWI, Amsterdam
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in
|
||||||
|
the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||||
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||||||
|
|
||||||
|
:- module(xpath,
|
||||||
|
[ xpath/3, % +DOM, +Spec, -Value
|
||||||
|
xpath_chk/3, % +DOM, +Spec, -Value
|
||||||
|
|
||||||
|
op(400, fx, //),
|
||||||
|
op(400, fx, /),
|
||||||
|
op(200, fy, @)
|
||||||
|
]).
|
||||||
|
|
||||||
|
:- use_module(library(lists),[member/2,memberchk/2,reverse/2]).
|
||||||
|
:- use_module(library(charsio)).
|
||||||
|
:- use_module(library(error)).
|
||||||
|
:- use_module(library(dcgs)).
|
||||||
|
:- use_module(library(si)).
|
||||||
|
|
||||||
|
/** Select nodes in an XML DOM
|
||||||
|
|
||||||
|
The library xpath.pl provides predicates to select nodes from an XML DOM
|
||||||
|
tree as produced by `library(sgml)` based on descriptions inspired by the
|
||||||
|
[XPath language](http://www.w3.org/TR/xpath).
|
||||||
|
|
||||||
|
The predicate `xpath/3` selects a sub-structure of the DOM
|
||||||
|
non-deterministically based on an XPath-like specification. Not all
|
||||||
|
selectors of XPath are implemented, but the ability to mix `xpath/3` calls
|
||||||
|
with arbitrary Prolog code provides a powerful tool for extracting
|
||||||
|
information from XML parse-trees.
|
||||||
|
*/
|
||||||
|
|
||||||
|
element_name(element(Name,_,_), Name).
|
||||||
|
element_attributes(element(_,Attributes,_), Attributes).
|
||||||
|
element_content(element(_,_,Content), Content).
|
||||||
|
|
||||||
|
%% xpath_chk(+DOM, +Spec, ?Content) is semidet.
|
||||||
|
%
|
||||||
|
% Semi-deterministic version of `xpath/3`.
|
||||||
|
|
||||||
|
xpath_chk(DOM, Spec, Content) :-
|
||||||
|
xpath(DOM, Spec, Content),
|
||||||
|
!.
|
||||||
|
|
||||||
|
%% xpath(+DOM, +Spec, ?Content) is nondet.
|
||||||
|
%
|
||||||
|
% Match an element in a DOM structure. The syntax is inspired by
|
||||||
|
% XPath, using () rather than [] to select inside an element.
|
||||||
|
% First we can construct paths using / and //:
|
||||||
|
%
|
||||||
|
% - *//Term*
|
||||||
|
% Select any node in the DOM matching term.
|
||||||
|
%
|
||||||
|
% - */Term*
|
||||||
|
% Match the root against Term.
|
||||||
|
%
|
||||||
|
% - *Term*
|
||||||
|
% Select the immediate children of the root matching Term.
|
||||||
|
%
|
||||||
|
% The Terms above are of type _callable_. The functor specifies
|
||||||
|
% the element name. The element name `*` refers to any element.
|
||||||
|
% The name _self_ refers to the top-element itself and is often
|
||||||
|
% used for processing matches of an earlier `xpath/3` query. A term
|
||||||
|
% NS:Term refers to an XML name in the namespace NS. Optional
|
||||||
|
% arguments specify additional constraints and functions. The
|
||||||
|
% arguments are processed from left to right. Defined conditional
|
||||||
|
% argument values are:
|
||||||
|
%
|
||||||
|
% - *`index(?Index)`*
|
||||||
|
% True if the element is the Index-th child of its parent,
|
||||||
|
% where 1 denotes the first child. Index can be one of:
|
||||||
|
%
|
||||||
|
% - *`Var`*
|
||||||
|
% `Var` is unified with the index of the matched element.
|
||||||
|
% - *`last`*
|
||||||
|
% True for the last element.
|
||||||
|
% - *`last - IntExpr`*
|
||||||
|
% True for the last-minus-nth element. For example,
|
||||||
|
% `last-1` is the element directly preceding the last one.
|
||||||
|
% - *`IntExpr`*
|
||||||
|
% True for the element whose index equals `IntExpr`.
|
||||||
|
% - *`Integer`*
|
||||||
|
% The N-th element with the given name, with 1 denoting the
|
||||||
|
% first element. Same as `index(Integer)`.
|
||||||
|
% - *`last`*
|
||||||
|
% The last element with the given name. Same as
|
||||||
|
% `index(last)`.
|
||||||
|
% - *`last - IntExpr`*
|
||||||
|
% The IntExpr-th element before the last.
|
||||||
|
% Same as `index(last-IntExpr)`.
|
||||||
|
%
|
||||||
|
% Defined function argument values are:
|
||||||
|
%
|
||||||
|
% - *`self`*
|
||||||
|
% Evaluate to the entire element
|
||||||
|
% - *`content`*
|
||||||
|
% Evaluate to the content of the element (a list)
|
||||||
|
% - *`text`*
|
||||||
|
% Evaluates to all text from the sub-tree, represented
|
||||||
|
% as a list of characters.
|
||||||
|
% - *`text(atom)`*
|
||||||
|
% Evaluates to all text from the sub-tree as an atom.
|
||||||
|
% - *`normalize_space`*
|
||||||
|
% As `text`, but uses `normalize_space/2` to normalise
|
||||||
|
% white-space in the output
|
||||||
|
% - *`number`*
|
||||||
|
% Extract an integer or float from the value. Ignores
|
||||||
|
% leading and trailing white-space
|
||||||
|
% - *`@Attribute`*
|
||||||
|
% Evaluates to the value of the given attribute. Attribute
|
||||||
|
% can be a compound term. In this case the functor name
|
||||||
|
% denotes the element and arguments perform transformations
|
||||||
|
% on the attribute value. Defined transformations are:
|
||||||
|
%
|
||||||
|
% - *`number`*
|
||||||
|
% Translate the value into a number using
|
||||||
|
% `xsd_number_chars/2`.
|
||||||
|
% - *`integer`*
|
||||||
|
% As `number`, but subsequently transform the value
|
||||||
|
% into an integer using the `round/1` function.
|
||||||
|
% - *`float`*
|
||||||
|
% As `number`, but subsequently transform the value
|
||||||
|
% into a float using the `float/1` function.
|
||||||
|
% - *`lower`*
|
||||||
|
% Translate the value to lower case, preserving
|
||||||
|
% the type.
|
||||||
|
% - *`upper`*
|
||||||
|
% Translate the value to upper case, preserving
|
||||||
|
% the type.
|
||||||
|
%
|
||||||
|
% In addition, the argument-list can be _conditions_:
|
||||||
|
%
|
||||||
|
% - *`Left = Right`*
|
||||||
|
% Succeeds if the left-hand unifies with the right-hand.
|
||||||
|
% If the left-hand side is a function, this is evaluated.
|
||||||
|
% The right-hand side is _never_ evaluated, and thus the
|
||||||
|
% condition `content = content` defines that the content
|
||||||
|
% of the element is the atom `content`.
|
||||||
|
% The functions `lower_case` and `upper_case` can be applied
|
||||||
|
% to Right (see example below).
|
||||||
|
% - *`contains(Haystack, Needle)`*
|
||||||
|
% Succeeds if Needle is a sub-list of Haystack.
|
||||||
|
% - *`XPath`*
|
||||||
|
% Succeeds if XPath matches in the currently selected
|
||||||
|
% sub-DOM. For example, the following expression finds
|
||||||
|
% an `h3` element inside a `div` element, where the `div`
|
||||||
|
% element itself contains an `h2` child with a `strong`
|
||||||
|
% child.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% //div(h2/strong)/h3
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% This is equivalent to the conjunction of XPath goals below.
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% ...,
|
||||||
|
% xpath(DOM, //(div), Div),
|
||||||
|
% xpath(Div, h2/strong, _),
|
||||||
|
% xpath(Div, h3, Result)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% #### Examples
|
||||||
|
%
|
||||||
|
% Match each table-row in DOM:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% xpath(DOM, //tr, TR)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Match the last cell of each tablerow in DOM. This example
|
||||||
|
% illustrates that a result can be the input of subsequent `xpath/3`
|
||||||
|
% queries. Using multiple queries on the intermediate TR term
|
||||||
|
% guarantee that all results come from the same table-row:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% xpath(DOM, //tr, TR),
|
||||||
|
% xpath(TR, /td(last), TD)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Match each `href` attribute in an `<a>` element
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% xpath(DOM, //a(@href), HREF)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Suppose we have a table containing rows where each first column
|
||||||
|
% is the name of a product with a link to details and the second
|
||||||
|
% is the price (a number). The following predicate matches the
|
||||||
|
% name, URL and price:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% product(DOM, Name, URL, Price) :-
|
||||||
|
% xpath(DOM, //tr, TR),
|
||||||
|
% xpath(TR, td(1), C1),
|
||||||
|
% xpath(C1, /self(normalize_space), Name),
|
||||||
|
% xpath(C1, a(@href), URL),
|
||||||
|
% xpath(TR, td(2, number), Price).
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Suppose we want to select books with genre="thriller" from a
|
||||||
|
% tree containing elements `<book genre=...>`
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% thriller(DOM, Book) :-
|
||||||
|
% xpath(DOM, //book(@genre=thiller), Book).
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Match the elements `<table align="center">` _and_ `<table
|
||||||
|
% align="CENTER">`:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% //table(@align(lower) = center)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Get the `width` and `height` of a `div` element as a number,
|
||||||
|
% and the `div` node itself:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% xpath(DOM, //div(@width(number)=W, @height(number)=H), Div)
|
||||||
|
% ```
|
||||||
|
%
|
||||||
|
% Note that `div` is an infix operator, so parentheses must be
|
||||||
|
% used in cases like the following:
|
||||||
|
%
|
||||||
|
% ```
|
||||||
|
% xpath(DOM, //(div), Div)
|
||||||
|
% ```
|
||||||
|
|
||||||
|
xpath(DOM, Spec, Content) :-
|
||||||
|
in_dom(Spec, DOM, Content).
|
||||||
|
|
||||||
|
in_dom(//Spec, DOM, Value) :-
|
||||||
|
!,
|
||||||
|
element_spec(Spec, Name, Modifiers),
|
||||||
|
sub_dom(I, Len, Name, E, DOM),
|
||||||
|
modifiers(Modifiers, I, Len, E, Value).
|
||||||
|
in_dom(/Spec, E, Value) :-
|
||||||
|
!,
|
||||||
|
element_spec(Spec, Name, Modifiers),
|
||||||
|
( Name == self
|
||||||
|
-> true
|
||||||
|
; element_name(E, Name)
|
||||||
|
),
|
||||||
|
modifiers(Modifiers, 1, 1, E, Value).
|
||||||
|
in_dom(A/B, DOM, Value) :-
|
||||||
|
!,
|
||||||
|
in_dom(A, DOM, Value0),
|
||||||
|
in_dom(B, Value0, Value).
|
||||||
|
in_dom(A//B, DOM, Value) :-
|
||||||
|
!,
|
||||||
|
in_dom(A, DOM, Value0),
|
||||||
|
in_dom(//B, Value0, Value).
|
||||||
|
in_dom(Spec, element(_, _, Content), Value) :-
|
||||||
|
element_spec(Spec, Name, Modifiers),
|
||||||
|
count_named_elements(Content, Name, CLen),
|
||||||
|
CLen > 0,
|
||||||
|
nth_element(N, Name, E, Content),
|
||||||
|
modifiers(Modifiers, N, CLen, E, Value).
|
||||||
|
|
||||||
|
element_spec(Var, _, _) :-
|
||||||
|
var(Var),
|
||||||
|
!,
|
||||||
|
instantiation_error(Var).
|
||||||
|
element_spec(NS:Term, NS:Name, Modifiers) :-
|
||||||
|
!,
|
||||||
|
callable_name_arguments(Term, Name0, Modifiers),
|
||||||
|
star(Name0, Name).
|
||||||
|
element_spec(Term, Name, Modifiers) :-
|
||||||
|
!,
|
||||||
|
callable_name_arguments(Term, Name0, Modifiers),
|
||||||
|
star(Name0, Name).
|
||||||
|
|
||||||
|
callable_name_arguments(Atom, Name, Arguments) :-
|
||||||
|
atom(Atom),
|
||||||
|
!,
|
||||||
|
Name = Atom, Arguments = [].
|
||||||
|
callable_name_arguments(Compound, Name, Arguments) :-
|
||||||
|
Compound =.. [Name|Arguments].
|
||||||
|
|
||||||
|
|
||||||
|
star(*, _) :- !.
|
||||||
|
star(Name, Name).
|
||||||
|
|
||||||
|
|
||||||
|
%! sub_dom(-Index, -Count, +Name, -Sub, +DOM) is nondet.
|
||||||
|
%
|
||||||
|
% Sub is a node in DOM with Name.
|
||||||
|
%
|
||||||
|
% @param Count is the total number of nodes in the content
|
||||||
|
% list Sub appears that have the same name.
|
||||||
|
% @param Index is the 1-based index of Sub of nodes with
|
||||||
|
% Name.
|
||||||
|
|
||||||
|
sub_dom(1, 1, Name, DOM, DOM) :-
|
||||||
|
element_name(DOM, Name0),
|
||||||
|
\+ Name \= Name0.
|
||||||
|
sub_dom(N, Len, Name, E, element(_,_,Content)) :-
|
||||||
|
!,
|
||||||
|
sub_dom_2(N, Len, Name, E, Content).
|
||||||
|
sub_dom(N, Len, Name, E, Content) :-
|
||||||
|
list_si(Content),
|
||||||
|
sub_dom_2(N, Len, Name, E, Content).
|
||||||
|
|
||||||
|
sub_dom_2(N, Len, Name, Element, Content) :-
|
||||||
|
( count_named_elements(Content, Name, Len),
|
||||||
|
nth_element(N, Name, Element, Content)
|
||||||
|
; member(element(_,_,C2), Content),
|
||||||
|
sub_dom_2(N, Len, Name, Element, C2)
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
%! count_named_elements(+Content, +Name, -Count) is det.
|
||||||
|
%
|
||||||
|
% Count is the number of nodes with Name in Content.
|
||||||
|
|
||||||
|
count_named_elements(Content, Name, Count) :-
|
||||||
|
count_named_elements(Content, Name, 0, Count).
|
||||||
|
|
||||||
|
count_named_elements([], _, Count, Count).
|
||||||
|
count_named_elements([element(Name,_,_)|T], Name0, C0, C) :-
|
||||||
|
\+ Name \= Name0,
|
||||||
|
!,
|
||||||
|
C1 is C0+1,
|
||||||
|
count_named_elements(T, Name0, C1, C).
|
||||||
|
count_named_elements([_|T], Name, C0, C) :-
|
||||||
|
count_named_elements(T, Name, C0, C).
|
||||||
|
|
||||||
|
|
||||||
|
%! nth_element(?N, +Name, -Element, +Content:list) is nondet.
|
||||||
|
%
|
||||||
|
% True if Element is the N-th element with name in Content.
|
||||||
|
|
||||||
|
nth_element(N, Name, Element, Content) :-
|
||||||
|
nth_element_(1, N, Name, Element, Content).
|
||||||
|
|
||||||
|
nth_element_(I, N, Name, E, [H|T]) :-
|
||||||
|
element_name(H, Name0),
|
||||||
|
\+ Name \= Name0,
|
||||||
|
!,
|
||||||
|
( N = I,
|
||||||
|
E = H
|
||||||
|
; I2 is I + 1,
|
||||||
|
( nonvar(N), I2 > N
|
||||||
|
-> !, fail
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
nth_element_(I2, N, Name, E, T)
|
||||||
|
).
|
||||||
|
nth_element_(I, N, Name, E, [_|T]) :-
|
||||||
|
nth_element_(I, N, Name, E, T).
|
||||||
|
|
||||||
|
|
||||||
|
%! modifiers(+Modifiers, +I, +Clen, +DOM, -Value)
|
||||||
|
%
|
||||||
|
%
|
||||||
|
|
||||||
|
modifiers([], _, _, Value, Value).
|
||||||
|
modifiers([H|T], I, L, Value0, Value) :-
|
||||||
|
modifier(H, I, L, Value0, Value1),
|
||||||
|
modifiers(T, I, L, Value1, Value).
|
||||||
|
|
||||||
|
modifier(M, _, _, _, _) :-
|
||||||
|
var(M),
|
||||||
|
!,
|
||||||
|
instantiation_error(M).
|
||||||
|
modifier(Index, I, L, Value0, Value) :-
|
||||||
|
implicit_index_modifier(Index),
|
||||||
|
!,
|
||||||
|
Value = Value0,
|
||||||
|
index_modifier(Index, I, L).
|
||||||
|
modifier(index(Index), I, L, Value, Value) :-
|
||||||
|
!,
|
||||||
|
index_modifier(Index, I, L).
|
||||||
|
modifier(Function, _, _, In, Out) :-
|
||||||
|
xpath_function(Function),
|
||||||
|
!,
|
||||||
|
xpath_function(Function, In, Out).
|
||||||
|
modifier(Function, _, _, In, Out) :-
|
||||||
|
xpath_condition(Function, In),
|
||||||
|
Out = In.
|
||||||
|
|
||||||
|
implicit_index_modifier(I) :-
|
||||||
|
integer(I),
|
||||||
|
!.
|
||||||
|
implicit_index_modifier(last).
|
||||||
|
implicit_index_modifier(last-_Expr).
|
||||||
|
|
||||||
|
index_modifier(Var, I, _L) :-
|
||||||
|
var(Var),
|
||||||
|
!,
|
||||||
|
Var = I.
|
||||||
|
index_modifier(last, I, L) :-
|
||||||
|
!,
|
||||||
|
I =:= L.
|
||||||
|
index_modifier(last-Expr, I, L) :-
|
||||||
|
!,
|
||||||
|
I =:= L-Expr.
|
||||||
|
index_modifier(N, I, _) :-
|
||||||
|
N =:= I.
|
||||||
|
|
||||||
|
xpath_function(self, DOM, DOM). % self
|
||||||
|
xpath_function(content, Element, Value) :- % content
|
||||||
|
element_content(Element, Value).
|
||||||
|
xpath_function(text, DOM, Text) :- % text
|
||||||
|
text_of_dom(DOM, chars, Text).
|
||||||
|
xpath_function(text(As), DOM, Text) :- % text(atom)
|
||||||
|
text_of_dom(DOM, As, Text).
|
||||||
|
xpath_function(normalize_space, DOM, Text) :- % normalize_space
|
||||||
|
text_of_dom(DOM, chars, Text0),
|
||||||
|
normalize_space(Text0, Text).
|
||||||
|
xpath_function(number, DOM, Number) :- % number
|
||||||
|
text_of_dom(DOM, chars, Text0),
|
||||||
|
normalize_space(Text0, Text),
|
||||||
|
catch(xsd_number_chars(Number, Text), _, fail).
|
||||||
|
xpath_function(@Name, element(_, Attrs, _), Value) :- % @Name
|
||||||
|
( atom(Name)
|
||||||
|
-> memberchk(Name=Value, Attrs)
|
||||||
|
; compound(Name)
|
||||||
|
-> Name =.. [AName|AOps],
|
||||||
|
memberchk(AName=Value0, Attrs),
|
||||||
|
translate_attribute(AOps, Value0, Value)
|
||||||
|
; member(Name=Value, Attrs)
|
||||||
|
).
|
||||||
|
xpath_function(quote(Value), _, Value). % quote(Value)
|
||||||
|
|
||||||
|
xpath_function(self).
|
||||||
|
xpath_function(content).
|
||||||
|
xpath_function(text).
|
||||||
|
xpath_function(text(_)).
|
||||||
|
xpath_function(normalize_space).
|
||||||
|
xpath_function(number).
|
||||||
|
xpath_function(@_).
|
||||||
|
xpath_function(quote(_)).
|
||||||
|
|
||||||
|
translate_attribute([], Value, Value).
|
||||||
|
translate_attribute([H|T], Value0, Value) :-
|
||||||
|
translate_attr(H, Value0, Value1),
|
||||||
|
translate_attribute(T, Value1, Value).
|
||||||
|
|
||||||
|
translate_attr(number, Value0, Value) :-
|
||||||
|
xsd_number_chars(Value, Value0).
|
||||||
|
translate_attr(integer, Value0, Value) :-
|
||||||
|
xsd_number_chars(Value1, Value0),
|
||||||
|
Value is round(Value1).
|
||||||
|
translate_attr(float, Value0, Value) :-
|
||||||
|
xsd_number_chars(Value1, Value0),
|
||||||
|
Value is float(Value1).
|
||||||
|
% The implementation of these translations is left for later.
|
||||||
|
% translate_attr(lower, Value0, Value) :- ...
|
||||||
|
% translate_attr(upper, Value0, Value) :- ...
|
||||||
|
|
||||||
|
xpath_condition(Left = Right, Value) :- % =
|
||||||
|
!,
|
||||||
|
var_or_function(Left, Value, LeftValue),
|
||||||
|
process_equality(LeftValue, Right).
|
||||||
|
xpath_condition(contains(Haystack, Needle), Value) :- % contains(Haystack, Needle)
|
||||||
|
!,
|
||||||
|
val_or_function(Haystack, Value, HaystackValue),
|
||||||
|
val_or_function(Needle, Value, NeedleValue),
|
||||||
|
( phrase((...,seq(NeedleValue),...), HaystackValue)
|
||||||
|
-> true
|
||||||
|
).
|
||||||
|
xpath_condition(Spec, Dom) :-
|
||||||
|
in_dom(Spec, Dom, _).
|
||||||
|
|
||||||
|
|
||||||
|
%! process_equality(+Left, +Right) is semidet.
|
||||||
|
%
|
||||||
|
% Provides (very) partial support for XSLT functions that can be
|
||||||
|
% applied according to the XPath 2 specification.
|
||||||
|
%
|
||||||
|
% For example the XPath expression in [1], and the equivalent
|
||||||
|
% Prolog expression in [2], would both match the HTML element in
|
||||||
|
% [3].
|
||||||
|
%
|
||||||
|
% ==
|
||||||
|
% [1] //table[align=lower-case(center)]
|
||||||
|
% [2] //table(@align=lower_case(center))
|
||||||
|
% [3] <table align="CENTER">
|
||||||
|
% ==
|
||||||
|
|
||||||
|
process_equality(Left, Right) :-
|
||||||
|
var(Right),
|
||||||
|
!,
|
||||||
|
Left = Right.
|
||||||
|
process_equality(Left, lower_case(Right)) :-
|
||||||
|
!,
|
||||||
|
downcase_atom(Left, Right).
|
||||||
|
process_equality(Left, upper_case(Right)) :-
|
||||||
|
!,
|
||||||
|
upcase_atom(Left, Right).
|
||||||
|
process_equality(Left, Right) :-
|
||||||
|
Left = Right.
|
||||||
|
|
||||||
|
|
||||||
|
var_or_function(Arg, _, Arg) :-
|
||||||
|
var(Arg),
|
||||||
|
!.
|
||||||
|
var_or_function(Func, Value0, Value) :-
|
||||||
|
xpath_function(Func),
|
||||||
|
!,
|
||||||
|
xpath_function(Func, Value0, Value).
|
||||||
|
var_or_function(Value, _, Value).
|
||||||
|
|
||||||
|
val_or_function(Arg, _, Arg) :-
|
||||||
|
var(Arg),
|
||||||
|
!,
|
||||||
|
instantiation_error(Arg).
|
||||||
|
val_or_function(Func, Value0, Value) :- % TBD
|
||||||
|
xpath_function(Func, Value0, Value),
|
||||||
|
!.
|
||||||
|
val_or_function(Value, _, Value).
|
||||||
|
|
||||||
|
|
||||||
|
%! text_of_dom(+DOM, +As, -Text:(chars | atom)) is det.
|
||||||
|
%
|
||||||
|
% Text is the joined textual content of DOM.
|
||||||
|
|
||||||
|
text_of_dom(DOM, As, Text) :-
|
||||||
|
phrase(text_of(DOM), Text0),
|
||||||
|
( As == chars ->
|
||||||
|
Text = Text0
|
||||||
|
; As == atom ->
|
||||||
|
atom_chars(Text, Text0)
|
||||||
|
; domain_error(As, atom_or_chars, xpath)
|
||||||
|
).
|
||||||
|
|
||||||
|
text_of(element(_,_,Content)) -->
|
||||||
|
text_of_list(Content).
|
||||||
|
text_of([]) -->
|
||||||
|
[].
|
||||||
|
text_of([H|T]) -->
|
||||||
|
text_of(H),
|
||||||
|
text_of(T).
|
||||||
|
|
||||||
|
|
||||||
|
text_of_list([]) -->
|
||||||
|
[].
|
||||||
|
text_of_list([H|T]) -->
|
||||||
|
text_of_1(H),
|
||||||
|
text_of_list(T).
|
||||||
|
|
||||||
|
|
||||||
|
text_of_1(element(_,_,Content)) -->
|
||||||
|
text_of_list(Content).
|
||||||
|
text_of_1([C|Cs]) --> seq([C|Cs]).
|
||||||
|
|
||||||
|
% For now, we use number_chars/2 to parse XML numbers.
|
||||||
|
% If the need arises, we can extend this to additional
|
||||||
|
% float constants and syntax that may occur in XML files.
|
||||||
|
|
||||||
|
xsd_number_chars(Number, Chars) :-
|
||||||
|
number_chars(Number, Chars).
|
||||||
|
|
||||||
|
normalize_space(Cs0, Cs) :-
|
||||||
|
must_be(chars, Cs0),
|
||||||
|
no_leading_whitespace(Cs0, Cs1),
|
||||||
|
reverse(Cs1, Cs2),
|
||||||
|
no_leading_whitespace(Cs2, Cs3),
|
||||||
|
reverse(Cs3, Cs4),
|
||||||
|
single_intermediate_space(Cs4, Cs).
|
||||||
|
|
||||||
|
no_leading_whitespace([], []).
|
||||||
|
no_leading_whitespace([C0|Cs0], Cs) :-
|
||||||
|
( char_type(C0, whitespace) ->
|
||||||
|
no_leading_whitespace(Cs0, Cs)
|
||||||
|
; Cs = [C0|Cs0]
|
||||||
|
).
|
||||||
|
|
||||||
|
single_intermediate_space([], []).
|
||||||
|
single_intermediate_space([C0|Cs0], [C|Cs]) :-
|
||||||
|
( char_type(C0, whitespace) ->
|
||||||
|
no_leading_whitespace(Cs0, Cs1),
|
||||||
|
C = ' ',
|
||||||
|
single_intermediate_space(Cs1, Cs)
|
||||||
|
; C = C0,
|
||||||
|
single_intermediate_space(Cs0, Cs)
|
||||||
|
).
|
||||||
1765
src/loader.pl
Normal file
1765
src/loader.pl
Normal file
File diff suppressed because it is too large
Load Diff
16
src/machine/args.rs
Normal file
16
src/machine/args.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MachineArgs {
|
||||||
|
pub add_history: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MachineArgs {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let args: BTreeSet<String> = env::args().collect();
|
||||||
|
Self {
|
||||||
|
add_history: !args.contains("--no-add-history"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1545
src/machine/arithmetic_ops.rs
Normal file
1545
src/machine/arithmetic_ops.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user