Compare commits
796 Commits
major_clea
...
main
Author | SHA1 | Date | |
---|---|---|---|
bb345a7938 | |||
04a1c81a2a | |||
f54d2d0d22 | |||
3a6c05a79c | |||
ea88ffc451 | |||
a997665d1a | |||
dc9b451b37 | |||
e99a7b5e1e | |||
74bb8a429a | |||
d587d87ed7 | |||
87f597624a | |||
78840cf3e7 | |||
c0b998c0d8 | |||
e2df823566 | |||
f08c7c6f1f | |||
a16f7795de | |||
314913dbf5 | |||
9294b8f7ed | |||
ea46bfe5da | |||
9650183c73 | |||
3330939128 | |||
dd13d2efee | |||
b0af1609f0 | |||
80b6380255 | |||
5ff5568e95 | |||
cfb4fa66b5 | |||
8c72e921dc | |||
0dcfcd5375 | |||
a2fa5afcd4 | |||
c77e1c89a9 | |||
34dca01d85 | |||
0e7aae79f9 | |||
a2e7a4e73e | |||
a9e675cb4f | |||
60535e20d6 | |||
e85297bcbb | |||
1947d66be5 | |||
ae6d3d7a82 | |||
7e4f6654a4 | |||
32ff52d9b3 | |||
febd7bb054 | |||
604962c535 | |||
1f9adfab2f | |||
10abd91c85 | |||
bd23853657 | |||
382d43ef77 | |||
4d6412006a | |||
d7be203bfc | |||
c264aaeb13 | |||
af4e43fc9f | |||
caf1c22af0 | |||
e304195661 | |||
004cf73959 | |||
c0f74f1e09 | |||
648bbdbc4d | |||
1da61f0873 | |||
8db37bcdfd | |||
6caae6c952 | |||
f15cf84c4d | |||
13b4cfffcc | |||
9d5c9d9fd0 | |||
65e513488f | |||
1c8e8b35b9 | |||
3f8b554a86 | |||
8c79157fa7 | |||
6bcc5b1555 | |||
b7153df9be | |||
bfade78a0d | |||
8b70a1dcc4 | |||
589e59b8c4 | |||
86e055a562 | |||
19f2d19134 | |||
95e5b3062d | |||
44659ec34a | |||
0c758c9768 | |||
12d34d6354 | |||
3609fddaea | |||
5d62d897f4 | |||
565d3c54f1 | |||
02ee7d126c | |||
c7bba88875 | |||
5eb901f468 | |||
055f0a4100 | |||
d5fc68e466 | |||
743679371d | |||
2864bee057 | |||
2527cc2de7 | |||
ea0be43199 | |||
5bbda1a24e | |||
f1034534ed | |||
02877d06d3 | |||
30b2801a38 | |||
9c612317dc | |||
6e1ef77192 | |||
c1e372d7cf | |||
61d633392c | |||
b6c3b1e5ba | |||
259721f6cb | |||
bdc34cb10e | |||
dddbf77fac | |||
79cc59c952 | |||
35f73d60f0 | |||
437a6bf164 | |||
d857f42434 | |||
9d0aa13e8a | |||
9b1dc6c55c | |||
5015cf4cc4 | |||
5411a1db27 | |||
ba904fdcd8 | |||
9d1996c9ec | |||
88684f49b6 | |||
d0d80cf407 | |||
47d6ea417d | |||
daca836db1 | |||
60df8b4d64 | |||
95e22aa97f | |||
7afde989f9 | |||
85241c04b9 | |||
c1eccf049f | |||
855782c370 | |||
a6da34a79c | |||
3df42f6a47 | |||
1d03876b89 | |||
5441938725 | |||
c31991cc24 | |||
9fe3e440ac | |||
182ef66f23 | |||
d68c789ea8 | |||
1d0bafa4a3 | |||
2c485cf046 | |||
c5c2fe95ae | |||
23f733d8b2 | |||
57edf48e36 | |||
573e5ae470 | |||
31bb7eaffc | |||
d4a8a65096 | |||
dee09d3583 | |||
be77d64c39 | |||
03113fdf5e | |||
bf4f319302 | |||
a1dd7e3bb9 | |||
5b3232c723 | |||
413cb70731 | |||
915340fbdb | |||
85b95a56aa | |||
2cb03297c5 | |||
56f1222cfc | |||
89573e81b9 | |||
fa4c2d48a3 | |||
0a16c5e0ca | |||
2f8c46f0a5 | |||
37dc2e05c5 | |||
f02c3d1fb5 | |||
6ca96bc1dc | |||
00555785e3 | |||
6c76006ad9 | |||
0d55bb7244 | |||
fd4ffeec7c | |||
71a4f863e3 | |||
9cb6873618 | |||
b66710e3eb | |||
4653a3f506 | |||
97bde437e8 | |||
d1bdabed56 | |||
ba80774e7b | |||
aa8b1215a8 | |||
9418cd5b70 | |||
8534f18c9a | |||
2ad3440097 | |||
3b7987c218 | |||
a0439675b7 | |||
82a2b8f6b7 | |||
5c54a5b9bd | |||
9144257524 | |||
caf59894b6 | |||
8b33df3d4a | |||
78c9ed97e2 | |||
6ff25a22ec | |||
d4d58e793b | |||
67e5de6664 | |||
7b055d79b5 | |||
974310ffab | |||
86f8e47b0c | |||
e4204c1b0d | |||
8f58bf30a4 | |||
c3790e90bf | |||
4ba3a47ae5 | |||
f936c30b4f | |||
0ed2733991 | |||
8798efc0af | |||
8f20e53880 | |||
fcfcb4a429 | |||
85f5f44946 | |||
5b8ec74d05 | |||
c406039c99 | |||
32347ec512 | |||
e9ec838b25 | |||
b8957190e0 | |||
3ac15fe70b | |||
03d44434e2 | |||
616f890028 | |||
812d930488 | |||
406edda573 | |||
3c2e3699ab | |||
1ecaac0819 | |||
5dcdfe83f9 | |||
9349724bac | |||
c42fca496b | |||
5c9a8dab31 | |||
72a019cfe5 | |||
7b25b593ef | |||
bd76622543 | |||
d6ab891d7f | |||
0aebd81665 | |||
0edb42836d | |||
3c2a70803e | |||
4433c587f5 | |||
d32061ebba | |||
a5a35b6d8c | |||
d0feac667f | |||
2a0e4c9b78 | |||
14aa7c242a | |||
e643ebe114 | |||
f2e5b8d499 | |||
012728b00a | |||
d032334635 | |||
5f186ade41 | |||
8043e24637 | |||
0c3e8d5edf | |||
b02e5244f2 | |||
5f733ba1dd | |||
561a290b16 | |||
511cc10e98 | |||
e45ac042b9 | |||
ab53df56bc | |||
8b14d74eba | |||
f510cce0ee | |||
6a488c2245 | |||
de3c83a6f5 | |||
e84bb2ea70 | |||
0d0a2d2237 | |||
23782c37a3 | |||
87bced9719 | |||
956def5ec6 | |||
e1e40cf931 | |||
e22d0254f5 | |||
08a9b265ec | |||
80bf09d807 | |||
2eff51815a | |||
a6334070ae | |||
f9480ddc24 | |||
169c1a9e3f | |||
4846b3f74d | |||
2e7acbeb64 | |||
83f856385b | |||
cf9a9837c8 | |||
8b5eb9977c | |||
c0ab5a84a2 | |||
d5c2ae92c9 | |||
fab66a4877 | |||
e3d821a1c3 | |||
0fd19a623d | |||
0b64afccb1 | |||
fa67a568d9 | |||
b024d81148 | |||
207d155a25 | |||
447643f315 | |||
8ae453add7 | |||
8bff39a7db | |||
7d721beb31 | |||
fedefdb29f | |||
84429ef187 | |||
e911853cb5 | |||
bfb07047a5 | |||
26348fb82e | |||
b48b5d4369 | |||
40b5d15b96 | |||
44d6a88faa | |||
486530610b | |||
81b7888920 | |||
f4ee3ffcf8 | |||
441df54a44 | |||
f4d29eca38 | |||
fdf324c866 | |||
43b2393d8a | |||
e7b5390a55 | |||
8666f1cd9b | |||
3ff3a7568c | |||
535e120256 | |||
17286896a8 | |||
d32633272c | |||
58780b5530 | |||
64a3ce4cd3 | |||
5c8e72a6f7 | |||
b55a79d6bf | |||
a78d560a0d | |||
5757f52dbd | |||
b1337900fb | |||
83aa53b998 | |||
049790726a | |||
1d75bd6594 | |||
7e7448fe52 | |||
87455ed792 | |||
2b8dda14e3 | |||
40a71da3a5 | |||
f3bef42563 | |||
390511fa20 | |||
5ad6012021 | |||
0fb0b63a97 | |||
a61c1756f2 | |||
f62cb13089 | |||
2a0737fd45 | |||
0ba54e9717 | |||
755fe5d899 | |||
c71c4d2d07 | |||
bf11bd1f0f | |||
c56a187d05 | |||
2c374a1cd7 | |||
74cfef1832 | |||
0e3a3e94c8 | |||
2463e44301 | |||
78228ce8d6 | |||
f2c0786bfb | |||
de30f241a8 | |||
c0254e8a94 | |||
77814c4576 | |||
7259206c98 | |||
3a2dd28efb | |||
3b0c74010e | |||
f5836b66dc | |||
9a9d9458ae | |||
9338d73621 | |||
28c65b0715 | |||
0c73f80947 | |||
24e21aa0b5 | |||
37e3e1116d | |||
1687fd7fe3 | |||
a46e5dd365 | |||
8f0d07b546 | |||
f2823b6236 | |||
2ae75dcdd0 | |||
9ea203f419 | |||
e1b04328d5 | |||
f50b765c1e | |||
82fbf796f3 | |||
60f8aab805 | |||
ed82f3c64f | |||
f389f7e422 | |||
24a2642f17 | |||
2cf580d111 | |||
a048577143 | |||
929468338d | |||
55a8661618 | |||
d0dba35285 | |||
8c8fde94ce | |||
b9081f8653 | |||
580b85e2d0 | |||
83018ec5ec | |||
c1b71ffccc | |||
9766777a47 | |||
60bd8f5352 | |||
57782d3ed6 | |||
cccbe7a325 | |||
77134e5292 | |||
4805a53269 | |||
fa2ce8a0bf | |||
bf519ec087 | |||
1c24286696 | |||
d5d51e9849 | |||
a52e78150e | |||
8dd62e623e | |||
a639641ed2 | |||
097b09b6e3 | |||
f5e822e916 | |||
e4ea402dfa | |||
9840c3c193 | |||
1fe26e0296 | |||
35eca1f7b4 | |||
285e9e7217 | |||
b17da5ad3c | |||
4d7f59aee2 | |||
06f3a9b746 | |||
cda0203242 | |||
76a67c5117 | |||
e295aebf56 | |||
f64babc546 | |||
7328467e64 | |||
5d01f1caf9 | |||
692f1145cd | |||
a60df0274c | |||
dfee50003a | |||
28e0ec27e8 | |||
6983d282d8 | |||
8c5ac0b89e | |||
80a7700d68 | |||
b81c65629b | |||
61f136edd2 | |||
2268fc827d | |||
f2bfe2ed06 | |||
0ca443b133 | |||
161e99a2dd | |||
bd491c014d | |||
51c7ae148c | |||
1607db20f9 | |||
5d7122aefa | |||
cc188a233b | |||
473f0ee075 | |||
2f06b18c3c | |||
3fae807d9f | |||
2047e0bf82 | |||
0e479197a7 | |||
175d82d382 | |||
77e84f9fa8 | |||
02dd33ab1a | |||
9640feb65b | |||
d3f5585d07 | |||
501801b63e | |||
a3917238d9 | |||
a02cee0b9f | |||
3addb767fa | |||
62ece61ce6 | |||
2dbbc7b128 | |||
c47d09fd1d | |||
790438d1e3 | |||
ad409b69f3 | |||
4ab838509b | |||
994fa7310b | |||
c85958064a | |||
f3fe03a95f | |||
1794f7559c | |||
48f3ccdd58 | |||
dd72faf7c8 | |||
e84e022eed | |||
0e52ed7a49 | |||
dce6dfbc40 | |||
ecd83a17e7 | |||
9bd39338d5 | |||
db94fbdb5b | |||
7ec640a3a1 | |||
92f098b58b | |||
7e152f9f51 | |||
699576c4c7 | |||
a79cb0b3e1 | |||
1e7636903e | |||
adfd3aa5d4 | |||
fe0bb0a0b5 | |||
29bbcb019d | |||
9a2e4f3649 | |||
6130f73ca8 | |||
822f12b44d | |||
49fe4555c6 | |||
a177f19f28 | |||
2da0a6a28b | |||
7dc7f7a351 | |||
97268c272e | |||
37d54499da | |||
2cbeb4b551 | |||
fbaf640fce | |||
38ffd9b01b | |||
a5c5075e6b | |||
63f648c3ac | |||
2e9a523058 | |||
18859cda77 | |||
f106d64367 | |||
c75dedb117 | |||
5e8945cab5 | |||
37d59f562d | |||
49dfdc4e10 | |||
b6b7a61727 | |||
fbcb28ce24 | |||
ff5c4972eb | |||
40172e3ffb | |||
64ce3d56e4 | |||
af20dab0d2 | |||
572d5a9d18 | |||
34cea3518d | |||
6bdefd0698 | |||
d06a614cfa | |||
a05d9016f2 | |||
890baa5d51 | |||
041480a953 | |||
1cd101db3f | |||
240c045a0c | |||
4b89ea0e96 | |||
88906fb6d7 | |||
fb413e24b0 | |||
d98f724355 | |||
b3e04f987f | |||
a28ac297c1 | |||
578cb6ad16 | |||
880fb7cd1b | |||
a94251e707 | |||
1593080b8d | |||
e429693364 | |||
0b8880ae55 | |||
e7e5d1c08d | |||
fecc62811d | |||
0de25215b8 | |||
c2d8bd299f | |||
51dd918789 | |||
ed4820a137 | |||
859d8db384 | |||
d37c618ead | |||
b3dd610949 | |||
69da32d414 | |||
aa79bea9a7 | |||
ccdcc7c791 | |||
7dc62bfd5f | |||
799467b25b | |||
7c809fa764 | |||
c0791ebb83 | |||
d5df74363a | |||
b0d80ab867 | |||
dd062e63f1 | |||
cddf199156 | |||
d53ddd07eb | |||
dbabf874b7 | |||
9e0c0b4db3 | |||
e448c9dd4c | |||
a0b754cc1c | |||
54071eb8c0 | |||
f5bcf9511a | |||
f625568ced | |||
c8dfbda447 | |||
d4c0633fab | |||
781e3d4263 | |||
a0999e30f1 | |||
7b78250eca | |||
8ea6b4be81 | |||
7be9300f14 | |||
f7bc43d7e3 | |||
aadb51e1f1 | |||
9c77ae2410 | |||
8fb8a456cd | |||
12210fd3ec | |||
9eb047a913 | |||
42f48e8d76 | |||
47c1617602 | |||
109c3f033c | |||
70f55c85f4 | |||
2b546e7b63 | |||
d9889ef2d8 | |||
71807c0777 | |||
d400b8bb6a | |||
c659b56105 | |||
bcd8e7c669 | |||
8478d59000 | |||
70face6765 | |||
3d64883e2a | |||
4726288b9a | |||
dbbb912b82 | |||
fb78798a1d | |||
fe1e27fd70 | |||
40783422d8 | |||
200a5d9127 | |||
e1002b21d9 | |||
d7a5586bc9 | |||
70303a77e6 | |||
e728aa8fbb | |||
f1f4d48d3a | |||
e50b231958 | |||
4b460c0e68 | |||
2871fd125a | |||
966983920e | |||
6b0bb0016f | |||
a0a9bc2fdf | |||
2b797c19f7 | |||
7dfc026be5 | |||
b392a4c7aa | |||
13c95dd12f | |||
7263507e84 | |||
004b7be27a | |||
eaff59c88d | |||
a8f840a305 | |||
9bb4e1b944 | |||
cb56fd05cd | |||
e858e7e20f | |||
50b7b4bfc6 | |||
d7d8fd2499 | |||
fd0204fefa | |||
690e248df6 | |||
e29e092875 | |||
96afe7d3a3 | |||
bb7cda1242 | |||
5de92ced6e | |||
936b1f5de9 | |||
177888c962 | |||
169260b8c1 | |||
9b74023ade | |||
413add3ba8 | |||
896a0855e0 | |||
d46a592f87 | |||
65ee161a96 | |||
d076a329d2 | |||
21fea2b43f | |||
bcc89f2c7d | |||
16d443d8a6 | |||
c7b189a83f | |||
6d50ac5b37 | |||
953454a140 | |||
b7ae0f1b52 | |||
18b8fd6681 | |||
7dcfccf7cb | |||
1750132ed8 | |||
3a97ba76a0 | |||
dd5136827c | |||
27c25a587b | |||
791610b350 | |||
bc5cadc446 | |||
c3402394a2 | |||
fed119f38b | |||
199e1c9184 | |||
062a3b606c | |||
765decdd41 | |||
1b367d4dfb | |||
4ea19f238e | |||
f9b4b1bc01 | |||
0e2d1021cb | |||
46419956bd | |||
3224c04f72 | |||
e9bfd9f1f8 | |||
15b1808741 | |||
5b79af6e85 | |||
fdf286cb51 | |||
4be32d0a5d | |||
346d9ba878 | |||
565fd450a9 | |||
780ea0858b | |||
bf72e779fe | |||
764ea0550d | |||
cabbf8821f | |||
f544bd008e | |||
a3591d19af | |||
2dd1628bca | |||
e272d99bae | |||
eba12b13a3 | |||
b9190514c4 | |||
5f958c72b8 | |||
3064a92e73 | |||
05c9e70d49 | |||
62185ff087 | |||
a92074a77b | |||
0c1a2f4499 | |||
56fbbdee0b | |||
b7288ceed8 | |||
5571418d44 | |||
ec9f17070c | |||
f70c8f2b40 | |||
d99ebc0a44 | |||
dac7656572 | |||
a6a02f26e4 | |||
32028acab2 | |||
7ee7a083ae | |||
d99e3cb861 | |||
85d954181b | |||
c51b142130 | |||
4d76023775 | |||
65d2fd3270 | |||
fdf6983ab2 | |||
37a88df613 | |||
bff5ba81a3 | |||
4db3ae7cb8 | |||
799b5d838c | |||
da5122358e | |||
13394e6a8f | |||
28efa78db1 | |||
76be50eab3 | |||
5cb86b80df | |||
459acb2d63 | |||
0ed30c7220 | |||
e5aeaa67d8 | |||
9d5b7b6606 | |||
95d9a720a3 | |||
65ee472a4a | |||
4137a1a693 | |||
4179f6ebe5 | |||
fb7675a782 | |||
8ff4b4ba82 | |||
f70656c837 | |||
989afec531 | |||
0eb3df9108 | |||
bec6eb5aeb | |||
bdbd1fc412 | |||
6dbae12315 | |||
6b88fbf8b9 | |||
cc76ca89cc | |||
25e3941315 | |||
900de8ca4b | |||
c4b51a1ef9 | |||
939b7464c6 | |||
64fb20c45e | |||
e3b55092b3 | |||
bd4983b821 | |||
a1500bf262 | |||
1585145ff4 | |||
fb3cd6e6da | |||
69347ad435 | |||
ca72fe04f1 | |||
eaf26fec5e | |||
0eac67eb3a | |||
37fd722fa6 | |||
255843cb3b | |||
927a2cfbf9 | |||
88d05f0dc9 | |||
0805b96809 | |||
a5f3127bcf | |||
01bdaa308d | |||
dbf9ab0d00 | |||
2bbbfa34a4 | |||
979335f497 | |||
ef12d97895 | |||
14eedc6a2a | |||
5559860699 | |||
3a63d4973d | |||
52027db6c3 | |||
4afc8face8 | |||
5450f00174 | |||
a52eadc5ad | |||
d4a5424ad5 | |||
f835f2817f | |||
86ce1dc3af | |||
ca04103372 | |||
dab3d2de8e | |||
6c699ec900 | |||
4f5ad1e4aa | |||
d05b5a8628 | |||
a46d5bb4ea | |||
1094a5662c | |||
fd33f330f7 | |||
bda217135e | |||
ee4f37080e | |||
7003c37aac | |||
a53f83f03a | |||
4b0910a545 | |||
c82f631524 | |||
d27c98e393 | |||
97640c1b9b | |||
d2e0de0483 | |||
7eecb7b070 | |||
1819c7e646 | |||
ee692b360e | |||
8c4b2c9eef | |||
122d81f252 | |||
d8705c5d50 | |||
c466096c8d | |||
9f2b0461df | |||
1ce2178af5 | |||
172a6fa860 | |||
51869f04b6 | |||
50a7a7aca1 | |||
edded5043d | |||
5e105177cf | |||
ec074177d5 | |||
c2ba519240 | |||
91e94a5adc | |||
e7f5d66297 | |||
e1c3e8bc0d | |||
a6e52e4ee6 | |||
85cb641af8 | |||
933ab3900b | |||
d9f065fbb6 | |||
540f59e6d8 | |||
b8f2fe7eb4 | |||
ed1f139595 | |||
4c68bc0260 | |||
d3601be44c | |||
fc3dfc0e03 | |||
97319d28b2 | |||
89a4c09307 | |||
b8c54ea8bd | |||
4323c50d32 | |||
390d1aa504 | |||
5e685d6641 | |||
c2f0ec28ba | |||
a23688803c | |||
85419c47be | |||
1f5dacad7d | |||
52493a0b73 | |||
18508fa217 | |||
e9bc16af0d | |||
3f4c4ff464 | |||
3c72e4f988 | |||
41a268389c | |||
bbab728ce9 | |||
daf78919da | |||
1e665a6f13 | |||
b7e0828ced | |||
924b388f2c | |||
d243c030e8 | |||
b1266df835 | |||
b5b317df95 | |||
f2049225fe | |||
90c0304af5 | |||
d997bbd08a | |||
ddd5912248 | |||
4479f340d7 |
1759
Cargo.lock
generated
1759
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
50
Cargo.toml
50
Cargo.toml
@ -1,48 +1,16 @@
|
||||
[package]
|
||||
name = "dust-lang"
|
||||
description = "General purpose programming language"
|
||||
version = "0.4.1"
|
||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||
[workspace]
|
||||
members = ["dust-lang", "dust-shell"]
|
||||
default-members = ["dust-lang"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Jeff Anderson"]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["Jeff Anderson"]
|
||||
default-run = "dust"
|
||||
|
||||
[[bin]]
|
||||
name = "dust"
|
||||
path = "src/main.rs"
|
||||
readme = "README.md"
|
||||
repository = "https://git.jeffa.io/jeff/dust.git"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4.4", features = ["derive"] }
|
||||
csv = "1.2.2"
|
||||
libc = "0.2.148"
|
||||
log = "0.4.20"
|
||||
rand = "0.8.5"
|
||||
rayon = "1.8.0"
|
||||
reqwest = { version = "0.11.20", features = ["blocking", "json"] }
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
toml = "0.8.1"
|
||||
tree-sitter = "0.20.10"
|
||||
enum-iterator = "1.4.1"
|
||||
env_logger = "0.10"
|
||||
reedline = { version = "0.28.0", features = ["clipboard", "sqlite"] }
|
||||
crossterm = "0.27.0"
|
||||
nu-ansi-term = "0.49.0"
|
||||
humantime = "2.1.0"
|
||||
stanza = "0.5.1"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
env_logger = "0.10"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
|
218
README.md
218
README.md
@ -1,188 +1,50 @@
|
||||
# Dust
|
||||
|
||||
!!! Dust is an experimental project under active development. !!!
|
||||
|
||||
Dust is a general purpose programming language that emphasises concurrency and correctness.
|
||||
|
||||
A basic dust program:
|
||||
|
||||
```dust
|
||||
output("Hello world!")
|
||||
```
|
||||
|
||||
Dust can do two (or more) things at the same time with effortless concurrency:
|
||||
|
||||
```dust
|
||||
async {
|
||||
output('will this one finish first?')
|
||||
output('or will this one?')
|
||||
}
|
||||
```
|
||||
|
||||
You can use Dust to run complex operations simply and safely. You can even invoke other programs, run them at the same time, capture their output, and pipe them together.
|
||||
|
||||
```dust
|
||||
# Run each statment in this block in its own thread.
|
||||
async {
|
||||
# Invoke another program and capture its output.
|
||||
ip_info = ^ip address;
|
||||
|
||||
# Pipe the output to another program.
|
||||
^ls -1 --all --long docs/ | ^rg .md | ^echo;
|
||||
|
||||
# This block is not async and the statements will be run in order.
|
||||
{
|
||||
file = fs:read_file('Cargo.toml')
|
||||
|
||||
# This loop will run each iteration in its own thread. If one of them
|
||||
# reaches a "break" statement, they will all stop.
|
||||
async for line in str:lines(file) {
|
||||
if str:contains(line, 'author') {
|
||||
output(line)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dust is an interpreted, strictly typed language with first class functions, embracing concurrency by allowing any group of statements to be executed in parallel. Dust includes built-in tooling to import and export data in a variety of formats, including JSON, TOML, YAML and CSV. Dust aims to be panic-free. That means that the interpreter will only fail to run a program due to an intended error, such as a type error or syntax error. If your program passes the these checks, it will run correctly.
|
||||
|
||||
<!--toc:start-->
|
||||
- [Dust](#dust)
|
||||
- [Features](#features)
|
||||
- [Usage](#usage)
|
||||
- [Dust Language](#dust-language)
|
||||
- [Installation](#installation)
|
||||
- [Benchmarks](#benchmarks)
|
||||
- [Implementation](#implementation)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
<!--toc:end-->
|
||||
|
||||
## Features
|
||||
|
||||
- Simplicity: Dust is designed to be easy to learn.
|
||||
- Speed: Dust is built on [Tree Sitter] and [Rust] to prioritize performance and correctness. See [Benchmarks] below.
|
||||
- Concurrency: Safe, effortless parallel code using thread pools.
|
||||
- Safety: Written in safe, stable Rust.
|
||||
- Correctness: Type checking makes it easy to write good code.
|
||||
|
||||
## Installation
|
||||
|
||||
### Cargo
|
||||
|
||||
You must have the default rust toolchain installed and up-to-date. Install [rustup] if it is not already installed. Run `cargo install dust-lang` then run `dust` to start the interactive shell.
|
||||
|
||||
### Build From Source
|
||||
|
||||
To build from source, clone the repository and build the parser. To do so, enter the `tree-sitter-dust` directory and run `tree-sitter-generate`. In the project root, run `cargo run` to start the shell. If you get errors about a linking with C, read them carefully to determine which prerequisites are needed.
|
||||
|
||||
On Fedora, you can install these prerequisites with:
|
||||
|
||||
```sh
|
||||
sudo dnf group install -y 'C Development Tools and Libraries' && sudo dnf install -y cmake
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
After installation, the command line interpreter can be given source code to run or it can launch the command-line shell. As with intepreters like `sh` and `bash`, you can use the `-c` flag to pass source code directly.
|
||||
|
||||
```sh
|
||||
dust -c "output('Hello world!')"
|
||||
# Output: Hello world!
|
||||
```
|
||||
|
||||
Or just provide a path to the source file.
|
||||
|
||||
```sh
|
||||
dust examples/hello_world.ds
|
||||
```
|
||||
|
||||
Run `dust --help` to see the available commands and options.
|
||||
|
||||
```txt
|
||||
General purpose programming language
|
||||
|
||||
Usage: dust [OPTIONS] [PATH]
|
||||
|
||||
Arguments:
|
||||
[PATH] Location of the file to run
|
||||
|
||||
Options:
|
||||
-c, --command <COMMAND> Dust source code to evaluate
|
||||
-i, --input <INPUT> Data to assign to the "input" variable
|
||||
-p, --input-path <INPUT_PATH> A path to file whose contents will be assigned to the "input" variable
|
||||
-t, --tree Show the syntax tree
|
||||
-h, --help Print help
|
||||
-V, --version Print version
|
||||
```
|
||||
|
||||
## Dust Language
|
||||
|
||||
See the [Language Reference](/docs/language.md) for more information.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Dust is at an early development stage and these tests are overly simple. Better benchmarks are needed to get a realistic idea of how Dust performs real work. For now, these tests are just for fun.
|
||||
The examples given were tested using [Hyperfine] on a single-core cloud instance with 1024 MB RAM. Each test was run 1000 times. The test script is shown below. Each test asks the program to read a JSON file and count the objects. Dust is a command line shell, programming language and data manipulation tool so three appropriate targets were chosen for comparison: nushell, NodeJS and jq. The programs produced identical output with the exception that NodeJS printed in color.
|
||||
|
||||
For the first test, a file with four entries was used.
|
||||
|
||||
| Command | Mean [ms] | Min [ms] | Max [ms]
|
||||
|:---|---:|---:|---:|
|
||||
| Dust | 3.1 ± 0.5 | 2.4 | 8.4 |
|
||||
| jq | 33.7 ± 2.2 | 30.0 | 61.8 |
|
||||
| NodeJS | 226.4 ± 13.1 | 197.6 | 346.2 |
|
||||
| Nushell | 51.6 ± 3.7 | 45.4 | 104.3 |
|
||||
|
||||
The second set of data is from the GitHub API, it consists of 100 commits from the jq GitHub repo.
|
||||
|
||||
| Command | Mean [ms] | Min [ms] | Max [ms] |
|
||||
|:---|---:|---:|---:|
|
||||
| Dust | 6.8 ± 0.6 | 5.7 | 12.0 | 2.20 ± 0.40 |
|
||||
| jq | 43.3 ± 3.6 | 37.6 | 81.6 | 13.95 ± 2.49 |
|
||||
| NodeJS | 224.9 ± 12.3 | 194.8 | 298.5 |
|
||||
| Nushell | 59.2 ± 5.7 | 49.7 | 125.0 | 19.11 ± 3.55 |
|
||||
|
||||
This data came from CERN, it is a massive file of 100,000 entries.
|
||||
|
||||
| Command | Mean [ms] | Min [ms] | Max [ms] |
|
||||
|:---|---:|---:|---:|
|
||||
| Dust | 1080.8 ± 38.7 | 975.3 | 1326.6 |
|
||||
| jq | 1305.3 ± 64.3 | 1159.7 | 1925.1 |
|
||||
| NodeJS | 1850.5 ± 72.5 | 1641.9 | 2395.1 |
|
||||
| Nushell | 1850.5 ± 86.2 | 1625.5 | 2400.7 |
|
||||
|
||||
The tests were run after 5 warmup runs and the cache was cleared before each run.
|
||||
|
||||
```sh
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--warmup 5 \
|
||||
--prepare "rm -rf /root/.cache" \
|
||||
--runs 1000 \
|
||||
--parameter-list data_path seaCreatures.json,jq_data.json,dielectron.json \
|
||||
--export-markdown test_output.md \
|
||||
"dust -c '(length (from_json input))' -p {data_path}" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}',(err,data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
||||
```
|
||||
Dust is a high-level interpreted programming language with static types that focuses on ease of use,
|
||||
performance and correctness.
|
||||
|
||||
## Implementation
|
||||
|
||||
Dust is formally defined as a Tree Sitter grammar in the tree-sitter-dust directory. Tree sitter generates a parser, written in C, from a set of rules defined in JavaScript. Dust itself is a rust binary that calls the C parser using FFI.
|
||||
Dust is implemented in Rust and is divided into several parts, primarily the lexer, compiler, and
|
||||
virtual machine. All of Dust's components are designed with performance in mind and the codebase
|
||||
uses as few dependencies as possible.
|
||||
|
||||
Tests are written in three places: in the Rust library, in Dust as examples and in the Tree Sitter test format. Generally, features are added by implementing and testing the syntax in the tree-sitter-dust repository, then writing library tests to evaluate the new syntax. Implementation tests run the Dust files in the "examples" directory and should be used to demonstrate and verify that features work together.
|
||||
### Lexer
|
||||
|
||||
Tree Sitter generates a concrete syntax tree, which Dust traverses to create an abstract syntax tree that can run the Dust code. The CST generation is an extra step but it allows easy testing of the parser, defining the language in one file and makes the syntax easy to modify and expand. Because it uses Tree Sitter, developer-friendly features like syntax highlighting and code navigation are already available in any text editor that supports Tree Sitter.
|
||||
The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy
|
||||
capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type,
|
||||
may contain a reference to some data from the source code. The data is only copied in the case of an
|
||||
error, because it improves the usability of the codebase for errors to own their data when possible.
|
||||
In a successfully executed program, no part of the source code is copied unless it is a string
|
||||
literal or identifier.
|
||||
|
||||
## Acknowledgements
|
||||
### Compiler
|
||||
|
||||
Dust began as a fork of [evalexpr]. Some of the original code is still in place but the project has dramatically changed and no longer uses any of its parsing or interpreting.
|
||||
The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a
|
||||
Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the
|
||||
tokens, which are generated one at a time by the lexer.
|
||||
|
||||
[Tree Sitter]: https://tree-sitter.github.io/tree-sitter/
|
||||
[Rust]: https://rust-lang.org
|
||||
[evalexpr]: https://github.com/ISibboI/evalexpr
|
||||
[rustup]: https://rustup.rs
|
||||
[Hyperfine]: https://github.com/sharkdp/hyperfine
|
||||
#### Parsing
|
||||
|
||||
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
|
||||
sequence of tokens into a chunk.
|
||||
|
||||
#### Optimizing
|
||||
|
||||
When generating instructions for a register-based virtual machine, there are opportunities to
|
||||
optimize the generated code, usually by consolidating register use or reusing registers within an
|
||||
expression. While it is best to output optimal code in the first place, it is not always possible.
|
||||
Dust's compiler has a simple peephole optimizer that can be used to modify isolated sections of the
|
||||
instruction list through a mutable reference.
|
||||
|
||||
### Instructions
|
||||
|
||||
### Virtual Machine
|
||||
|
||||
## Previous Implementations
|
||||
|
||||
## Inspiration
|
||||
|
||||
- [The Implementation of Lua 5.0](https://www.lua.org/doc/jucs05.pdf)
|
||||
- [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
|
||||
- [Crafting Interpreters](https://craftinginterpreters.com/)
|
||||
|
17
build.rs
17
build.rs
@ -1,17 +0,0 @@
|
||||
fn main() {
|
||||
let src_dir = std::path::Path::new("tree-sitter-dust/src");
|
||||
let mut c_config = cc::Build::new();
|
||||
|
||||
c_config.include(src_dir);
|
||||
c_config
|
||||
.flag_if_supported("-Wno-unused-parameter")
|
||||
.flag_if_supported("-Wno-unused-but-set-variable")
|
||||
.flag_if_supported("-Wno-trigraphs");
|
||||
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
|
||||
c_config.file(&parser_path);
|
||||
c_config.compile("parser");
|
||||
|
||||
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
|
||||
}
|
515
docs/language.md
515
docs/language.md
@ -1,515 +0,0 @@
|
||||
# Dust Language Reference
|
||||
|
||||
!!! This is a **work in progress** and has incomplete information. !!!
|
||||
|
||||
This is an in-depth description of the syntax and abstractions used by the Dust language. It is not
|
||||
necessary to read or understand all of it before you start using Dust. Instead, refer to it when
|
||||
you need help with the syntax or understanding how the code is run.
|
||||
|
||||
Each section of this document corresponds to a node in the concrete syntax tree. Creating this tree
|
||||
is the first step in interpreting Dust code. Second, the syntax tree is traversed and an abstract
|
||||
tree is generated. Each node in the syntax tree corresponds to a node in the abstract tree. Third,
|
||||
the abstract tree is verified to ensure that it will not generate any values that violate the type
|
||||
restrictions. Finally, the abstract tree is run, beginning at the [root](#root).
|
||||
|
||||
You may reference the [grammar file](tree-sitter-dust/grammar.js) and the [Tree Sitter docs]
|
||||
(https://tree-sitter.github.io/) while reading this guide to understand how the language is parsed.
|
||||
|
||||
<!--toc:start-->
|
||||
- [Dust Language Reference](#dust-language-reference)
|
||||
- [Root](#root)
|
||||
- [Values](#values)
|
||||
- [Boolean](#boolean)
|
||||
- [Integer](#integer)
|
||||
- [Float](#float)
|
||||
- [Range](#range)
|
||||
- [String](#string)
|
||||
- [List](#list)
|
||||
- [Map](#map)
|
||||
- [Function](#function)
|
||||
- [Option](#option)
|
||||
- [Structure](#structure)
|
||||
- [Types](#types)
|
||||
- [Basic Types](#basic-types)
|
||||
- [Number](#number)
|
||||
- [Any](#any)
|
||||
- [None](#none)
|
||||
- [List Type](#list-type)
|
||||
- [Map Type](#map-type)
|
||||
- [Iter](#iter)
|
||||
- [Function Type](#function-type)
|
||||
- [Option Type](#option-type)
|
||||
- [Custom Types](#custom-types)
|
||||
- [Statements](#statements)
|
||||
- [Assignment](#assignment)
|
||||
- [Blocks](#blocks)
|
||||
- [Synchronous Blocks](#synchronous-blocks)
|
||||
- [Asynchronous Blocks](#asynchronous-blocks)
|
||||
- [Break](#break)
|
||||
- [For Loop](#for-loop)
|
||||
- [While Loop](#while-loop)
|
||||
- [If/Else](#ifelse)
|
||||
- [Match](#match)
|
||||
- [Pipe](#pipe)
|
||||
- [Expression](#expression)
|
||||
- [Expressions](#expressions)
|
||||
- [Identifier](#identifier)
|
||||
- [Index](#index)
|
||||
- [Logic](#logic)
|
||||
- [Math](#math)
|
||||
- [Value](#value)
|
||||
- [New](#new)
|
||||
- [Command](#command)
|
||||
- [Built-In Values](#built-in-values)
|
||||
- [Comments](#comments)
|
||||
<!--toc:end-->
|
||||
|
||||
## Root
|
||||
|
||||
The root node represents all of the source code. It is a sequence of [statements](#statements) that
|
||||
are executed synchronously, in order. The output of the program is always the result of the final
|
||||
statement or the first error encountered.
|
||||
|
||||
## Values
|
||||
|
||||
There are ten kinds of value in Dust. Some are very simple and are parsed directly from the source
|
||||
code, some are collections and others are used in special ways, like functions and structures. All
|
||||
values can be assinged to an [identifier](#identifiers).
|
||||
|
||||
Dust does not have a null type. Absent values are represented with the `none` value, which is a
|
||||
kind of [option](#option). You may not create a variable without a value and no variable can ever
|
||||
be in an 'undefined' state during execution.
|
||||
|
||||
### Boolean
|
||||
|
||||
Booleans are true or false. They are represented by the literal tokens `true` and `false`.
|
||||
|
||||
### Integer
|
||||
|
||||
Integers are whole numbers that may be positive, negative or zero. Internally, an integer is a
|
||||
signed 64-bit value.
|
||||
|
||||
```dust
|
||||
42
|
||||
```
|
||||
|
||||
Integers always **overflow** when their maximum or minimum value is reached. Overflowing means that
|
||||
if the value is too high or low for the 64-bit integer, it will wrap around. You can use the built-
|
||||
in values `int:max` and `int:min` to get the highest and lowest possible values.
|
||||
|
||||
```dust
|
||||
assert_equal(int:max + 1, int:min)
|
||||
assert_equal(int:min - 1, int:max)
|
||||
```
|
||||
|
||||
### Float
|
||||
|
||||
A float is a numeric value with a decimal. Floats are 64-bit and, like integers, will **overflow**
|
||||
at their bounds.
|
||||
|
||||
```dust
|
||||
42.0
|
||||
```
|
||||
|
||||
### Range
|
||||
|
||||
A range represents a contiguous sequence of integers. Dust ranges are **inclusive** so both the high
|
||||
and low bounds will be represented.
|
||||
|
||||
```dust
|
||||
0..100
|
||||
```
|
||||
|
||||
### String
|
||||
|
||||
A string is a **utf-8** sequence used to represent text. Strings can be wrapped in single or double quotes as well as backticks.
|
||||
|
||||
```dust
|
||||
'42'
|
||||
"42"
|
||||
`42`
|
||||
'forty-two'
|
||||
```
|
||||
|
||||
### List
|
||||
|
||||
A list is **collection** of values stored as a sequence and accessible by [indexing](#index) their position with an integer. Lists indexes begin at zero for the first item.
|
||||
|
||||
```dust
|
||||
[ 42 'forty-two' ]
|
||||
[ 123, 'one', 'two', 'three' ]
|
||||
```
|
||||
|
||||
Note that the commas are optional, including trailing commas.
|
||||
|
||||
```dust
|
||||
[1 2 3 4 5]:2
|
||||
# Output: 3
|
||||
```
|
||||
|
||||
### Map
|
||||
|
||||
Maps are flexible collections with arbitrary **key-value pairs**, similar to JSON objects. A map is
|
||||
created with a pair of curly braces and its entries are variables declared inside those braces. Map
|
||||
contents can be accessed using a colon `:`. Commas may optionally be included after the key-value
|
||||
pairs.
|
||||
|
||||
```dust
|
||||
reminder = {
|
||||
message = "Buy milk"
|
||||
tags = ["groceries", "home"]
|
||||
}
|
||||
|
||||
reminder:message
|
||||
# Output: Buy milk
|
||||
```
|
||||
|
||||
Internally a map is represented by a B-tree. The implicit advantage of using a B-tree instead of a
|
||||
hash map is that a B-tree is sorted and therefore can be easily compared to another. Maps are also
|
||||
used by the interpreter as the data structure for holding variables. You can even inspect the active
|
||||
**execution context** by calling the built-in `context()` function.
|
||||
|
||||
The map stores each [identifier](#identifiers)'s key with a value and the value's type. For internal
|
||||
use by the interpreter, a type can be set to a key without a value. This makes it possible to check
|
||||
the types of values before they are computed.
|
||||
|
||||
### Function
|
||||
|
||||
A function encapsulates a section of the abstract tree so that it can be run seperately and with
|
||||
different arguments. The function body is a [block](#block), so adding `async` will cause the body
|
||||
to run like any other `async` block. Unlike some languages, there are no concepts like futures or
|
||||
async functions in Dust.
|
||||
|
||||
Functions are **first-class values** in Dust, so they can be assigned to variables like any other
|
||||
value.
|
||||
|
||||
```dust
|
||||
# This simple function has no arguments and no return value.
|
||||
say_hi = () <none> {
|
||||
output("hi") # The "output" function is a built-in that prints to stdout.
|
||||
}
|
||||
|
||||
# This function has one argument and will return a value.
|
||||
add_one = (number <num>) <num> {
|
||||
number + 1
|
||||
}
|
||||
|
||||
say_hi()
|
||||
assert_equal(add_one(3), 4)
|
||||
```
|
||||
|
||||
Functions can also be **anonymous**. This is useful for using **callbacks** (i.e. functions that are
|
||||
called by another function).
|
||||
|
||||
```dust
|
||||
# Use a callback to retain only the numeric characters in a string.
|
||||
str:retain(
|
||||
'a1b2c3'
|
||||
(char <str>) <bool> {
|
||||
is_some(int:parse(char))
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Option
|
||||
|
||||
An option represents a value that may not be present. It has two variants: **some** and **none**.
|
||||
|
||||
```dust
|
||||
say_something = (message <option(str)>) <str> {
|
||||
either_or(message, "hiya")
|
||||
}
|
||||
|
||||
say_something(some("goodbye"))
|
||||
# goodbye
|
||||
|
||||
say_something(none)
|
||||
# hiya
|
||||
```
|
||||
|
||||
Dust includes built-in functions to work with option values: `is_none`, `is_some` and `either_or`.
|
||||
|
||||
### Structure
|
||||
|
||||
A structure is a **concrete type value**. It is a value, like any other, and can be [assigned]
|
||||
(#assignment) to an [identifier](#identifier). It can then be instantiated as a [map](#map) that
|
||||
will only allow the variables present in the structure. Default values may be provided for each
|
||||
variable in the structure, which will be propagated to the map it creates. Values without defaults
|
||||
must be given a value during instantiation.
|
||||
|
||||
```dust
|
||||
struct User {
|
||||
name <str>
|
||||
email <str>
|
||||
id <int> = generate_id()
|
||||
}
|
||||
|
||||
bob = new User {
|
||||
name = "Bob"
|
||||
email = "bob@example.com"
|
||||
}
|
||||
|
||||
# The variable "bob" is a structured map.
|
||||
```
|
||||
|
||||
A map created by using [new](#new) is called a **structured map**. In other languages it may be
|
||||
called a "homomorphic mapped type". Dust will generate errors if you try to set any values on the
|
||||
structured map that are not allowed by the structure.
|
||||
|
||||
## Types
|
||||
|
||||
Dust enforces strict type checking. To make the language easier to write, **type inference** is used
|
||||
to allow variables to be declared without specifying the type. Instead, the interpreter will figure
|
||||
it out and set the strictest type possible.
|
||||
|
||||
To make the type-setting syntax easier to distinguish from the rest of your code, a **type
|
||||
specification** is wrapped in pointed brackets. So variable assignment using types looks like this:
|
||||
|
||||
```dust
|
||||
my_float <float> = 666.0
|
||||
```
|
||||
|
||||
### Basic Types
|
||||
|
||||
The simple types, and their notation are:
|
||||
|
||||
- boolean `bool`
|
||||
- integer `int`
|
||||
- float `float`
|
||||
- string `str`
|
||||
|
||||
### Number
|
||||
|
||||
The `num` type may represent a value of type `int` or `float`.
|
||||
|
||||
### Any
|
||||
|
||||
The `any` type does not enforce type bounds.
|
||||
|
||||
### None
|
||||
|
||||
The `none` type indicates that no value should be found after executing the statement or block, with
|
||||
one expection: the `none` variant of the `option` type.
|
||||
|
||||
### List Type
|
||||
|
||||
A list's contents can be specified to create type-safe lists. The `list(str)` type would only allow
|
||||
string values. Writing `list` without the parentheses and content type is equivalent to writing
|
||||
`list(any)`.
|
||||
|
||||
### Map Type
|
||||
|
||||
The `map` type is unstructured and can hold any key-value pair.
|
||||
|
||||
### Iter
|
||||
|
||||
The `iter` type refers to types that can be used with a [for loop](#for-loop). These include `list`,
|
||||
`range`, `string` and `map`.
|
||||
|
||||
### Function Type
|
||||
|
||||
A function's type specification is more complex than other types. A function value must always have
|
||||
its arguments and return type specified when the **function value** is created.
|
||||
|
||||
```dust
|
||||
my_function = (number <int>, text <str>) <none> {
|
||||
output(number)
|
||||
output(text)
|
||||
}
|
||||
```
|
||||
|
||||
But what if we need to specify a **function type** without creating the function value? This is
|
||||
necessary when using callbacks or defining structures that have functions set at instantiation.
|
||||
|
||||
```dust
|
||||
use_adder = (adder <(int) -> int>, number <int>) -> <int> {
|
||||
adder(number)
|
||||
}
|
||||
|
||||
use_adder(
|
||||
(i <int>) <int> { i + 2 }
|
||||
40
|
||||
)
|
||||
|
||||
# Output: 42
|
||||
```
|
||||
|
||||
```dust
|
||||
struct Message {
|
||||
send_n_times <(str, int) -> none>
|
||||
}
|
||||
|
||||
stdout_message = new Message {
|
||||
send_n_times = (content <str>, n <int>) <none> {
|
||||
for _ in 0..n {
|
||||
output(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option Type
|
||||
|
||||
The `option(type)` type is expected to be either `some(value)` or `none`. The type of the value
|
||||
inside the `some` is always specified.
|
||||
|
||||
```dust
|
||||
result <option(str)> = none
|
||||
|
||||
for file in fs:read_dir("./") {
|
||||
if file:size > 100 {
|
||||
result = some(file:path)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
output(result)
|
||||
```
|
||||
|
||||
```dust
|
||||
get_line_break_index(text <str>) <some(int)> {
|
||||
str:find(text, '\n')
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Types
|
||||
|
||||
Custom types such as **structures** are referenced by their variable identifier.
|
||||
|
||||
```dust
|
||||
File = struct {
|
||||
path <str>
|
||||
size <int>
|
||||
type <str>
|
||||
}
|
||||
|
||||
print_file_info(file <File>) <none> {
|
||||
info = file:path
|
||||
+ '\n'
|
||||
+ file:size
|
||||
+ '\n'
|
||||
+ file:type
|
||||
|
||||
output(info)
|
||||
}
|
||||
```
|
||||
|
||||
## Statements
|
||||
|
||||
TODO
|
||||
|
||||
### Assignment
|
||||
|
||||
TODO
|
||||
|
||||
### Blocks
|
||||
|
||||
TODO
|
||||
|
||||
#### Synchronous Blocks
|
||||
|
||||
TODO
|
||||
|
||||
#### Asynchronous Blocks
|
||||
|
||||
```dust
|
||||
# An async block will run each statement in its own thread.
|
||||
async {
|
||||
output(random_integer())
|
||||
output(random_float())
|
||||
output(random_boolean())
|
||||
}
|
||||
```
|
||||
|
||||
```dust
|
||||
data = async {
|
||||
output("Reading a file...")
|
||||
read("examples/assets/faithful.csv")
|
||||
}
|
||||
```
|
||||
|
||||
### Break
|
||||
|
||||
TODO
|
||||
|
||||
### For Loop
|
||||
|
||||
TODO
|
||||
|
||||
```dust
|
||||
list = [ 1, 2, 3 ]
|
||||
|
||||
for number in list {
|
||||
output(number + 1)
|
||||
}
|
||||
```
|
||||
|
||||
### While Loop
|
||||
|
||||
TODO
|
||||
|
||||
A **while** loop continues until a predicate is false.
|
||||
|
||||
```dust
|
||||
i = 0
|
||||
while i < 10 {
|
||||
output(i)
|
||||
i += 1
|
||||
}
|
||||
```
|
||||
|
||||
### If/Else
|
||||
|
||||
TODO
|
||||
|
||||
### Match
|
||||
|
||||
TODO
|
||||
|
||||
### Pipe
|
||||
|
||||
TODO
|
||||
|
||||
### Expression
|
||||
|
||||
TODO
|
||||
|
||||
## Expressions
|
||||
|
||||
TODO
|
||||
|
||||
#### Identifier
|
||||
|
||||
TODO
|
||||
|
||||
#### Index
|
||||
|
||||
TODO
|
||||
|
||||
#### Logic
|
||||
|
||||
TODO
|
||||
|
||||
#### Math
|
||||
|
||||
TODO
|
||||
|
||||
#### Value
|
||||
|
||||
TODO
|
||||
|
||||
#### New
|
||||
|
||||
TODO
|
||||
|
||||
#### Command
|
||||
|
||||
TODO
|
||||
|
||||
## Built-In Values
|
||||
|
||||
TODO
|
||||
|
||||
## Comments
|
||||
|
||||
TODO
|
23
dust-lang/Cargo.toml
Normal file
23
dust-lang/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "dust-lang"
|
||||
description = "Interpreter library for the Dust programming language"
|
||||
version = "0.5.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
annotate-snippets = "0.11.4"
|
||||
colored = "2.1.0"
|
||||
log = "0.4.22"
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_json = "1.0.117"
|
||||
getrandom = { version = "0.2", features = [
|
||||
"js",
|
||||
] } # Indirect dependency, for wasm builds
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11.5"
|
402
dust-lang/src/chunk.rs
Normal file
402
dust-lang/src/chunk.rs
Normal file
@ -0,0 +1,402 @@
|
||||
//! In-memory representation of a Dust program or function.
|
||||
//!
|
||||
//! A chunk consists of a sequence of instructions and their positions, a list of constants, and a
|
||||
//! list of locals that can be executed by the Dust virtual machine. Chunks have a name when they
|
||||
//! belong to a named function.
|
||||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Disassembler, Instruction, Operation, Span, Type, Value};
|
||||
|
||||
/// In-memory representation of a Dust program or function.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Clone, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Chunk {
|
||||
name: Option<String>,
|
||||
pub is_poisoned: bool,
|
||||
|
||||
instructions: Vec<(Instruction, Span)>,
|
||||
constants: Vec<Value>,
|
||||
locals: Vec<Local>,
|
||||
|
||||
current_scope: Scope,
|
||||
block_index: u8,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn new(name: Option<String>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
is_poisoned: false,
|
||||
instructions: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
locals: Vec::new(),
|
||||
current_scope: Scope::default(),
|
||||
block_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_data(
|
||||
name: Option<String>,
|
||||
instructions: Vec<(Instruction, Span)>,
|
||||
constants: Vec<Value>,
|
||||
locals: Vec<Local>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
is_poisoned: false,
|
||||
instructions,
|
||||
constants,
|
||||
locals,
|
||||
current_scope: Scope::default(),
|
||||
block_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&String> {
|
||||
self.name.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: String) {
|
||||
self.name = Some(name);
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.instructions.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.instructions.is_empty()
|
||||
}
|
||||
|
||||
pub fn constants(&self) -> &Vec<Value> {
|
||||
&self.constants
|
||||
}
|
||||
|
||||
pub fn constants_mut(&mut self) -> &mut Vec<Value> {
|
||||
&mut self.constants
|
||||
}
|
||||
|
||||
pub fn take_constants(self) -> Vec<Value> {
|
||||
self.constants
|
||||
}
|
||||
|
||||
pub fn instructions(&self) -> &Vec<(Instruction, Span)> {
|
||||
&self.instructions
|
||||
}
|
||||
|
||||
pub fn instructions_mut(&mut self) -> &mut Vec<(Instruction, Span)> {
|
||||
&mut self.instructions
|
||||
}
|
||||
|
||||
pub fn get_instruction(&self, index: usize) -> Result<&(Instruction, Span), ChunkError> {
|
||||
self.instructions
|
||||
.get(index)
|
||||
.ok_or(ChunkError::InstructionIndexOutOfBounds { index })
|
||||
}
|
||||
|
||||
pub fn locals(&self) -> &Vec<Local> {
|
||||
&self.locals
|
||||
}
|
||||
|
||||
pub fn locals_mut(&mut self) -> &mut Vec<Local> {
|
||||
&mut self.locals
|
||||
}
|
||||
|
||||
pub fn get_local(&self, index: u8) -> Result<&Local, ChunkError> {
|
||||
self.locals
|
||||
.get(index as usize)
|
||||
.ok_or(ChunkError::LocalIndexOutOfBounds {
|
||||
index: index as usize,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_local_mut(&mut self, index: u8) -> Result<&mut Local, ChunkError> {
|
||||
self.locals
|
||||
.get_mut(index as usize)
|
||||
.ok_or(ChunkError::LocalIndexOutOfBounds {
|
||||
index: index as usize,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn current_scope(&self) -> Scope {
|
||||
self.current_scope
|
||||
}
|
||||
|
||||
pub fn get_constant(&self, index: u8) -> Result<&Value, ChunkError> {
|
||||
self.constants
|
||||
.get(index as usize)
|
||||
.ok_or(ChunkError::ConstantIndexOutOfBounds {
|
||||
index: index as usize,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn push_or_get_constant(&mut self, value: Value) -> u8 {
|
||||
if let Some(index) = self
|
||||
.constants
|
||||
.iter()
|
||||
.position(|constant| constant == &value)
|
||||
{
|
||||
return index as u8;
|
||||
}
|
||||
|
||||
self.constants.push(value);
|
||||
|
||||
(self.constants.len() - 1) as u8
|
||||
}
|
||||
|
||||
pub fn get_identifier(&self, local_index: u8) -> Option<String> {
|
||||
self.locals.get(local_index as usize).and_then(|local| {
|
||||
self.constants
|
||||
.get(local.identifier_index as usize)
|
||||
.map(|value| value.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn begin_scope(&mut self) {
|
||||
self.block_index += 1;
|
||||
self.current_scope.block_index = self.block_index;
|
||||
self.current_scope.depth += 1;
|
||||
}
|
||||
|
||||
pub fn end_scope(&mut self) {
|
||||
self.current_scope.depth -= 1;
|
||||
|
||||
if self.current_scope.depth == 0 {
|
||||
self.current_scope.block_index = 0;
|
||||
} else {
|
||||
self.current_scope.block_index -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_not_poisoned(&self) -> Result<(), ChunkError> {
|
||||
if self.is_poisoned {
|
||||
Err(ChunkError::PoisonedChunk)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_constant_type(&self, constant_index: u8) -> Option<Type> {
|
||||
self.constants
|
||||
.get(constant_index as usize)
|
||||
.map(|value| value.r#type())
|
||||
}
|
||||
|
||||
pub fn get_local_type(&self, local_index: u8) -> Option<Type> {
|
||||
self.locals.get(local_index as usize)?.r#type.clone()
|
||||
}
|
||||
|
||||
pub fn get_register_type(&self, register_index: u8) -> Option<Type> {
|
||||
let local_type_option = self
|
||||
.locals
|
||||
.iter()
|
||||
.find(|local| local.register_index == register_index)
|
||||
.map(|local| local.r#type.clone());
|
||||
|
||||
if let Some(local_type) = local_type_option {
|
||||
return local_type;
|
||||
}
|
||||
|
||||
self.instructions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(index, (instruction, _))| {
|
||||
if let Operation::LoadList = instruction.operation() {
|
||||
if instruction.a() == register_index {
|
||||
let mut length = (instruction.c() - instruction.b() + 1) as usize;
|
||||
let mut item_type = Type::Any;
|
||||
let distance_to_end = self.len() - index;
|
||||
|
||||
for (instruction, _) in self
|
||||
.instructions()
|
||||
.iter()
|
||||
.rev()
|
||||
.skip(distance_to_end)
|
||||
.take(length)
|
||||
{
|
||||
if let Operation::Close = instruction.operation() {
|
||||
length -= (instruction.c() - instruction.b()) as usize;
|
||||
} else if let Type::Any = item_type {
|
||||
item_type = instruction.yielded_type(self).unwrap_or(Type::Any);
|
||||
}
|
||||
}
|
||||
|
||||
return Some(Type::List {
|
||||
item_type: Box::new(item_type),
|
||||
length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if instruction.yields_value() && instruction.a() == register_index {
|
||||
instruction.yielded_type(self)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn return_type(&self) -> Option<Type> {
|
||||
let returns_value = self
|
||||
.instructions()
|
||||
.last()
|
||||
.map(|(instruction, _)| {
|
||||
debug_assert!(matches!(instruction.operation(), Operation::Return));
|
||||
|
||||
instruction.b_as_boolean()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if returns_value {
|
||||
self.instructions.iter().rev().find_map(|(instruction, _)| {
|
||||
if instruction.yields_value() {
|
||||
instruction.yielded_type(self)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassembler(&self) -> Disassembler {
|
||||
Disassembler::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Chunk {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let disassembler = self.disassembler().styled(false);
|
||||
|
||||
write!(f, "{}", disassembler.disassemble())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Chunk {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let disassembly = self.disassembler().styled(false).disassemble();
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
write!(f, "\n{}", disassembly)
|
||||
} else {
|
||||
write!(f, "{}", disassembly)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Chunk {}
|
||||
|
||||
impl PartialEq for Chunk {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.instructions == other.instructions
|
||||
&& self.constants == other.constants
|
||||
&& self.locals == other.locals
|
||||
}
|
||||
}
|
||||
|
||||
/// A scoped variable.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Local {
|
||||
/// The index of the identifier in the constants table.
|
||||
pub identifier_index: u8,
|
||||
|
||||
/// The expected type of the local's value.
|
||||
pub r#type: Option<Type>,
|
||||
|
||||
/// Whether the local is mutable.
|
||||
pub is_mutable: bool,
|
||||
|
||||
/// Scope where the variable was declared.
|
||||
pub scope: Scope,
|
||||
|
||||
/// Expected location of a local's value.
|
||||
pub register_index: u8,
|
||||
}
|
||||
|
||||
impl Local {
|
||||
/// Creates a new Local instance.
|
||||
pub fn new(
|
||||
identifier_index: u8,
|
||||
r#type: Option<Type>,
|
||||
mutable: bool,
|
||||
scope: Scope,
|
||||
register_index: u8,
|
||||
) -> Self {
|
||||
Self {
|
||||
identifier_index,
|
||||
r#type,
|
||||
is_mutable: mutable,
|
||||
scope,
|
||||
register_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Variable locality, as defined by its depth and block index.
|
||||
///
|
||||
/// The `block index` is a unique identifier for a block within a chunk. It is used to differentiate
|
||||
/// between blocks that are not nested together but have the same depth, i.e. sibling scopes. If the
|
||||
/// `block_index` is 0, then the scope is the root scope of the chunk. The `block_index` is always 0
|
||||
/// when the `depth` is 0. See [Chunk::begin_scope][] and [Chunk::end_scope][] to see how scopes are
|
||||
/// incremented and decremented.
|
||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Scope {
|
||||
/// Level of block nesting.
|
||||
pub depth: u8,
|
||||
/// Index of the block in the chunk.
|
||||
pub block_index: u8,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(depth: u8, block_index: u8) -> Self {
|
||||
Self { depth, block_index }
|
||||
}
|
||||
|
||||
pub fn contains(&self, other: &Self) -> bool {
|
||||
match self.depth.cmp(&other.depth) {
|
||||
Ordering::Less => false,
|
||||
Ordering::Greater => self.block_index >= other.block_index,
|
||||
Ordering::Equal => self.block_index == other.block_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Scope {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.depth, self.block_index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when using a [`Chunk`].
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ChunkError {
|
||||
ConstantIndexOutOfBounds { index: usize },
|
||||
InstructionIndexOutOfBounds { index: usize },
|
||||
LocalIndexOutOfBounds { index: usize },
|
||||
PoisonedChunk,
|
||||
}
|
||||
|
||||
impl Display for ChunkError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ChunkError::ConstantIndexOutOfBounds { index } => {
|
||||
write!(f, "Constant index {} out of bounds", index)
|
||||
}
|
||||
ChunkError::InstructionIndexOutOfBounds { index } => {
|
||||
write!(f, "Instruction index {} out of bounds", index)
|
||||
}
|
||||
ChunkError::LocalIndexOutOfBounds { index } => {
|
||||
write!(f, "Local index {} out of bounds", index)
|
||||
}
|
||||
ChunkError::PoisonedChunk => write!(f, "Chunk is poisoned"),
|
||||
}
|
||||
}
|
||||
}
|
1977
dust-lang/src/compiler.rs
Normal file
1977
dust-lang/src/compiler.rs
Normal file
File diff suppressed because it is too large
Load Diff
366
dust-lang/src/disassembler.rs
Normal file
366
dust-lang/src/disassembler.rs
Normal file
@ -0,0 +1,366 @@
|
||||
//! Tool for disassembling chunks into a human-readable format.
|
||||
//!
|
||||
//! A disassembler can be created by calling [Chunk::disassembler][] or by instantiating one with
|
||||
//! [Disassembler::new][].
|
||||
//!
|
||||
//! # Options
|
||||
//!
|
||||
//! The disassembler can be customized with the 'styled' option, which will apply ANSI color codes
|
||||
//! to the output.
|
||||
//!
|
||||
//! # Output
|
||||
//!
|
||||
//! The output of [Disassembler::disassemble] is a string that can be printed to the console or
|
||||
//! written to a file. Below is an example of the disassembly for a simple "Hello, world!" program.
|
||||
//!
|
||||
//! ```text
|
||||
//! ┌──────────────────────────────────────────────────────────────────────────────┐
|
||||
//! │ <file name omitted> │
|
||||
//! │ │
|
||||
//! │ write_line("Hello, world!") │
|
||||
//! │ │
|
||||
//! │ 3 instructions, 1 constants, 0 locals, returns none │
|
||||
//! │ │
|
||||
//! │ Instructions │
|
||||
//! │ ------------ │
|
||||
//! │ i BYTECODE OPERATION INFO TYPE POSITION │
|
||||
//! │--- -------- ------------- -------------------- ---------------- ------------ │
|
||||
//! │ 0 03 LOAD_CONSTANT R0 = C0 str (11, 26) │
|
||||
//! │ 1 1390117 CALL_NATIVE write_line(R0) (0, 27) │
|
||||
//! │ 2 18 RETURN (27, 27) │
|
||||
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
|
||||
//! │ Locals │
|
||||
//! │ ------ │
|
||||
//! │ i IDENTIFIER TYPE MUTABLE SCOPE REGISTER │
|
||||
//! │ --- ---------- ---------------- ------- ------- -------- │
|
||||
//! │┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈│
|
||||
//! │ Constants │
|
||||
//! │ --------- │
|
||||
//! │ i VALUE │
|
||||
//! │ --- --------------- │
|
||||
//! │ 0 Hello, world! │
|
||||
//! └──────────────────────────────────────────────────────────────────────────────┘
|
||||
//! ```
|
||||
use std::env::current_exe;
|
||||
|
||||
use colored::Colorize;
|
||||
|
||||
use crate::{Chunk, ConcreteValue, Local, Value};
|
||||
|
||||
const INSTRUCTION_HEADER: [&str; 4] = [
|
||||
"Instructions",
|
||||
"------------",
|
||||
" i BYTECODE OPERATION INFO TYPE POSITION ",
|
||||
"--- -------- ------------- -------------------- ---------------- ----------",
|
||||
];
|
||||
|
||||
const CONSTANT_HEADER: [&str; 4] = [
|
||||
"Constants",
|
||||
"---------",
|
||||
" i VALUE ",
|
||||
"--- ---------------",
|
||||
];
|
||||
|
||||
const LOCAL_HEADER: [&str; 4] = [
|
||||
"Locals",
|
||||
"------",
|
||||
" i IDENTIFIER TYPE MUTABLE SCOPE REGISTER",
|
||||
"--- ---------- ---------------- ------- ------- --------",
|
||||
];
|
||||
|
||||
/// Builder that constructs a human-readable representation of a chunk.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
pub struct Disassembler<'a> {
|
||||
output: String,
|
||||
chunk: &'a Chunk,
|
||||
source: Option<&'a str>,
|
||||
|
||||
// Options
|
||||
styled: bool,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
impl<'a> Disassembler<'a> {
|
||||
pub fn new(chunk: &'a Chunk) -> Self {
|
||||
Self {
|
||||
output: String::new(),
|
||||
chunk,
|
||||
source: None,
|
||||
styled: false,
|
||||
indent: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The default width of the disassembly output. To correctly align the output, this should
|
||||
/// return the width of the longest line that the disassembler is guaranteed to produce.
|
||||
pub fn default_width() -> usize {
|
||||
let longest_line = INSTRUCTION_HEADER[3];
|
||||
|
||||
longest_line.chars().count().max(80)
|
||||
}
|
||||
|
||||
pub fn source(mut self, source: &'a str) -> Self {
|
||||
self.source = Some(source);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn styled(mut self, styled: bool) -> Self {
|
||||
self.styled = styled;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn indent(mut self, indent: usize) -> Self {
|
||||
self.indent = indent;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn push(
|
||||
&mut self,
|
||||
text: &str,
|
||||
center: bool,
|
||||
style_bold: bool,
|
||||
style_dim: bool,
|
||||
add_border: bool,
|
||||
) {
|
||||
let width = Disassembler::default_width();
|
||||
let characters = text.chars().collect::<Vec<char>>();
|
||||
let content_width = if add_border { width - 2 } else { width };
|
||||
let (line_characters, remainder) = characters
|
||||
.split_at_checked(content_width)
|
||||
.unwrap_or((characters.as_slice(), &[]));
|
||||
let (left_pad_length, right_pad_length) = {
|
||||
let extra_space = content_width.saturating_sub(characters.len());
|
||||
|
||||
if center {
|
||||
(extra_space / 2, extra_space / 2 + extra_space % 2)
|
||||
} else {
|
||||
(0, extra_space)
|
||||
}
|
||||
};
|
||||
let content = if style_bold {
|
||||
line_characters
|
||||
.iter()
|
||||
.collect::<String>()
|
||||
.bold()
|
||||
.to_string()
|
||||
} else if style_dim {
|
||||
line_characters
|
||||
.iter()
|
||||
.collect::<String>()
|
||||
.dimmed()
|
||||
.to_string()
|
||||
} else {
|
||||
line_characters.iter().collect::<String>()
|
||||
};
|
||||
let length_before_content = self.output.chars().count();
|
||||
|
||||
for _ in 0..self.indent {
|
||||
self.output.push_str("│ ");
|
||||
}
|
||||
|
||||
if add_border {
|
||||
self.output.push('│');
|
||||
}
|
||||
|
||||
self.output.push_str(&" ".repeat(left_pad_length));
|
||||
self.output.push_str(&content);
|
||||
self.output.push_str(&" ".repeat(right_pad_length));
|
||||
|
||||
let length_after_content = self.output.chars().count();
|
||||
let line_length = length_after_content - length_before_content;
|
||||
|
||||
if line_length < content_width - 1 {
|
||||
self.output
|
||||
.push_str(&" ".repeat(content_width - line_length));
|
||||
}
|
||||
|
||||
if add_border {
|
||||
self.output.push('│');
|
||||
}
|
||||
|
||||
self.output.push('\n');
|
||||
|
||||
if !remainder.is_empty() {
|
||||
self.push(
|
||||
remainder.iter().collect::<String>().as_str(),
|
||||
center,
|
||||
style_bold,
|
||||
style_dim,
|
||||
add_border,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_header(&mut self, header: &str) {
|
||||
self.push(header, true, self.styled, false, true);
|
||||
}
|
||||
|
||||
fn push_details(&mut self, details: &str) {
|
||||
self.push(details, true, false, false, true);
|
||||
}
|
||||
|
||||
fn push_border(&mut self, border: &str) {
|
||||
self.push(border, false, false, false, false);
|
||||
}
|
||||
|
||||
fn push_empty(&mut self) {
|
||||
self.push("", false, false, false, true);
|
||||
}
|
||||
|
||||
pub fn disassemble(mut self) -> String {
|
||||
let width = Disassembler::default_width();
|
||||
let top_border = "┌".to_string() + &"─".repeat(width - 2) + "┐";
|
||||
let section_border = "│".to_string() + &"┈".repeat(width - 2) + "│";
|
||||
let bottom_border = "└".to_string() + &"─".repeat(width - 2) + "┘";
|
||||
let name_display = self
|
||||
.chunk
|
||||
.name()
|
||||
.map(|identifier| identifier.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
current_exe()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or("Chunk Disassembly".to_string())
|
||||
});
|
||||
|
||||
self.push_border(&top_border);
|
||||
self.push_header(&name_display);
|
||||
|
||||
if let Some(source) = self.source {
|
||||
self.push_empty();
|
||||
self.push_details(
|
||||
&source
|
||||
.replace(" ", "")
|
||||
.replace("\n\n", " ")
|
||||
.replace('\n', " "),
|
||||
);
|
||||
self.push_empty();
|
||||
}
|
||||
|
||||
let info_line = format!(
|
||||
"{} instructions, {} constants, {} locals, returns {}",
|
||||
self.chunk.len(),
|
||||
self.chunk.constants().len(),
|
||||
self.chunk.locals().len(),
|
||||
self.chunk
|
||||
.return_type()
|
||||
.map(|r#type| r#type.to_string())
|
||||
.unwrap_or("none".to_string())
|
||||
);
|
||||
|
||||
self.push(&info_line, true, false, true, true);
|
||||
self.push_empty();
|
||||
|
||||
for line in INSTRUCTION_HEADER {
|
||||
self.push_header(line);
|
||||
}
|
||||
|
||||
for (index, (instruction, position)) in self.chunk.instructions().iter().enumerate() {
|
||||
let bytecode = format!("{:02X}", u32::from(instruction));
|
||||
let operation = instruction.operation().to_string();
|
||||
let info = instruction.disassembly_info(self.chunk);
|
||||
let type_display = instruction
|
||||
.yielded_type(self.chunk)
|
||||
.map(|r#type| {
|
||||
let type_string = r#type.to_string();
|
||||
|
||||
if type_string.len() > 16 {
|
||||
format!("{type_string:.13}...")
|
||||
} else {
|
||||
type_string
|
||||
}
|
||||
})
|
||||
.unwrap_or(String::with_capacity(0));
|
||||
let position = position.to_string();
|
||||
|
||||
let instruction_display = format!(
|
||||
"{index:^3} {bytecode:>8} {operation:13} {info:^20} {type_display:^16} {position:10}"
|
||||
);
|
||||
|
||||
self.push_details(&instruction_display);
|
||||
}
|
||||
|
||||
self.push_border(§ion_border);
|
||||
|
||||
for line in LOCAL_HEADER {
|
||||
self.push_header(line);
|
||||
}
|
||||
|
||||
for (
|
||||
index,
|
||||
Local {
|
||||
identifier_index,
|
||||
r#type,
|
||||
scope,
|
||||
register_index,
|
||||
is_mutable: mutable,
|
||||
},
|
||||
) in self.chunk.locals().iter().enumerate()
|
||||
{
|
||||
let identifier_display = self
|
||||
.chunk
|
||||
.constants()
|
||||
.get(*identifier_index as usize)
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let type_display = r#type
|
||||
.as_ref()
|
||||
.map(|r#type| {
|
||||
let type_string = r#type.to_string();
|
||||
|
||||
if type_string.len() > 16 {
|
||||
format!("{type_string:.13}...")
|
||||
} else {
|
||||
type_string
|
||||
}
|
||||
})
|
||||
.unwrap_or("unknown".to_string());
|
||||
let local_display = format!(
|
||||
"{index:^3} {identifier_display:10} {type_display:16} {mutable:7} {scope:7} {register_index:8}"
|
||||
);
|
||||
|
||||
self.push_details(&local_display);
|
||||
}
|
||||
|
||||
self.push_border(§ion_border);
|
||||
|
||||
for line in CONSTANT_HEADER {
|
||||
self.push_header(line);
|
||||
}
|
||||
|
||||
for (index, value) in self.chunk.constants().iter().enumerate() {
|
||||
let value_display = {
|
||||
let value_string = value.to_string();
|
||||
|
||||
if value_string.len() > 15 {
|
||||
format!("{value_string:.12}...")
|
||||
} else {
|
||||
value_string
|
||||
}
|
||||
};
|
||||
let constant_display = format!("{index:^3} {value_display:^15}");
|
||||
|
||||
self.push_details(&constant_display);
|
||||
|
||||
if let Value::Concrete(ConcreteValue::Function(function)) = value {
|
||||
let function_disassembly = function
|
||||
.chunk()
|
||||
.disassembler()
|
||||
.styled(self.styled)
|
||||
.indent(self.indent + 1)
|
||||
.disassemble();
|
||||
|
||||
self.output.push_str(&function_disassembly);
|
||||
}
|
||||
}
|
||||
|
||||
self.push_border(&bottom_border);
|
||||
|
||||
let _ = self.output.trim_end_matches('\n');
|
||||
|
||||
self.output
|
||||
}
|
||||
}
|
66
dust-lang/src/dust_error.rs
Normal file
66
dust-lang/src/dust_error.rs
Normal file
@ -0,0 +1,66 @@
|
||||
//! Top-level Dust errors with source code annotations.
|
||||
use annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
||||
use crate::{vm::VmError, CompileError, Span};
|
||||
|
||||
/// A top-level error that can occur during the execution of Dust code.
|
||||
///
|
||||
/// This error can display nicely formatted messages with source code annotations.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DustError<'src> {
|
||||
Compile {
|
||||
error: CompileError,
|
||||
source: &'src str,
|
||||
},
|
||||
Runtime {
|
||||
error: VmError,
|
||||
source: &'src str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'src> DustError<'src> {
|
||||
pub fn report(&self) -> String {
|
||||
let mut report = String::new();
|
||||
let renderer = Renderer::styled();
|
||||
|
||||
match self {
|
||||
DustError::Runtime { error, source } => {
|
||||
let position = error.position();
|
||||
let label = format!("{}: {}", VmError::title(), error.description());
|
||||
let details = error
|
||||
.details()
|
||||
.unwrap_or_else(|| "While running this code".to_string());
|
||||
let message = Level::Error.title(&label).snippet(
|
||||
Snippet::source(source)
|
||||
.fold(false)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
||||
);
|
||||
|
||||
report.push_str(&renderer.render(message).to_string());
|
||||
}
|
||||
DustError::Compile { error, source } => {
|
||||
let position = error.position();
|
||||
let label = format!("{}: {}", CompileError::title(), error.description());
|
||||
let details = error
|
||||
.details()
|
||||
.unwrap_or_else(|| "While parsing this code".to_string());
|
||||
let message = Level::Error.title(&label).snippet(
|
||||
Snippet::source(source)
|
||||
.fold(false)
|
||||
.annotation(Level::Error.span(position.0..position.1).label(&details)),
|
||||
);
|
||||
|
||||
report.push_str(&renderer.render(message).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
report
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnnotatedError {
|
||||
fn title() -> &'static str;
|
||||
fn description(&self) -> &'static str;
|
||||
fn details(&self) -> Option<String>;
|
||||
fn position(&self) -> Span;
|
||||
}
|
224
dust-lang/src/formatter.rs
Normal file
224
dust-lang/src/formatter.rs
Normal file
@ -0,0 +1,224 @@
|
||||
//! Formatting tools
|
||||
use std::mem::replace;
|
||||
|
||||
use colored::{ColoredString, Colorize, CustomColor};
|
||||
|
||||
use crate::{CompileError, DustError, LexError, Lexer, Token};
|
||||
|
||||
pub fn format(source: &str, line_numbers: bool, colored: bool) -> Result<String, DustError> {
|
||||
let lexer = Lexer::new(source);
|
||||
let formatted = Formatter::new(lexer)
|
||||
.line_numbers(line_numbers)
|
||||
.colored(colored)
|
||||
.format()
|
||||
.map_err(|error| DustError::Compile {
|
||||
error: CompileError::Lex(error),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(formatted)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Formatter<'src> {
|
||||
lexer: Lexer<'src>,
|
||||
output_lines: Vec<(String, LineKind, usize)>,
|
||||
next_line: String,
|
||||
indent: usize,
|
||||
|
||||
current_token: Token<'src>,
|
||||
previous_token: Token<'src>,
|
||||
|
||||
// Options
|
||||
line_numbers: bool,
|
||||
colored: bool,
|
||||
}
|
||||
|
||||
impl<'src> Formatter<'src> {
|
||||
pub fn new(mut lexer: Lexer<'src>) -> Self {
|
||||
let (current_token, _) = lexer.next_token().unwrap();
|
||||
|
||||
Self {
|
||||
lexer,
|
||||
output_lines: Vec::new(),
|
||||
next_line: String::new(),
|
||||
indent: 0,
|
||||
current_token,
|
||||
previous_token: Token::Eof,
|
||||
line_numbers: false,
|
||||
colored: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line_numbers(mut self, line_numbers: bool) -> Self {
|
||||
self.line_numbers = line_numbers;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn colored(mut self, colored: bool) -> Self {
|
||||
self.colored = colored;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn format(&mut self) -> Result<String, LexError> {
|
||||
let mut line_kind = LineKind::Empty;
|
||||
|
||||
self.advance()?;
|
||||
|
||||
while self.current_token != Token::Eof {
|
||||
use Token::*;
|
||||
|
||||
if self.current_token.is_expression() && line_kind != LineKind::Assignment {
|
||||
line_kind = LineKind::Expression;
|
||||
}
|
||||
|
||||
match self.current_token {
|
||||
Boolean(boolean) => {
|
||||
self.push_colored(boolean.red());
|
||||
}
|
||||
Byte(byte) => {
|
||||
self.push_colored(byte.green());
|
||||
}
|
||||
Character(character) => {
|
||||
self.push_colored(
|
||||
character
|
||||
.to_string()
|
||||
.custom_color(CustomColor::new(225, 150, 150)),
|
||||
);
|
||||
}
|
||||
Float(float) => {
|
||||
self.push_colored(float.yellow());
|
||||
}
|
||||
Identifier(identifier) => {
|
||||
self.push_colored(identifier.blue());
|
||||
self.next_line.push(' ');
|
||||
}
|
||||
Integer(integer) => {
|
||||
self.push_colored(integer.cyan());
|
||||
}
|
||||
String(string) => {
|
||||
self.push_colored(string.magenta());
|
||||
}
|
||||
LeftCurlyBrace => {
|
||||
self.next_line.push_str(self.current_token.as_str());
|
||||
self.commit_line(LineKind::OpenBlock);
|
||||
|
||||
self.indent += 1;
|
||||
}
|
||||
RightCurlyBrace => {
|
||||
self.commit_line(LineKind::CloseBlock);
|
||||
self.next_line.push_str(self.current_token.as_str());
|
||||
|
||||
self.indent -= 1;
|
||||
}
|
||||
Semicolon => {
|
||||
if line_kind != LineKind::Assignment {
|
||||
line_kind = LineKind::Statement;
|
||||
}
|
||||
|
||||
self.next_line.push_str(self.current_token.as_str());
|
||||
self.commit_line(line_kind);
|
||||
}
|
||||
Let => {
|
||||
line_kind = LineKind::Assignment;
|
||||
|
||||
self.push_colored(self.current_token.as_str().bold());
|
||||
self.next_line.push(' ');
|
||||
}
|
||||
Break | Loop | Return | While => {
|
||||
line_kind = LineKind::Statement;
|
||||
|
||||
self.push_colored(self.current_token.as_str().bold());
|
||||
self.next_line.push(' ');
|
||||
}
|
||||
token => {
|
||||
self.next_line.push_str(token.as_str());
|
||||
self.next_line.push(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut previous_index = 0;
|
||||
let mut current_index = 1;
|
||||
|
||||
while current_index < self.output_lines.len() {
|
||||
let (_, previous, _) = &self.output_lines[previous_index];
|
||||
let (_, current, _) = &self.output_lines[current_index];
|
||||
|
||||
match (previous, current) {
|
||||
(LineKind::Empty, _)
|
||||
| (_, LineKind::Empty)
|
||||
| (LineKind::OpenBlock, _)
|
||||
| (_, LineKind::CloseBlock) => {}
|
||||
(left, right) if left == right => {}
|
||||
_ => {
|
||||
self.output_lines
|
||||
.insert(current_index, ("".to_string(), LineKind::Empty, 0));
|
||||
}
|
||||
}
|
||||
|
||||
previous_index += 1;
|
||||
current_index += 1;
|
||||
}
|
||||
|
||||
let formatted = String::with_capacity(
|
||||
self.output_lines
|
||||
.iter()
|
||||
.fold(0, |total, (line, _, _)| total + line.len()),
|
||||
);
|
||||
|
||||
Ok(self.output_lines.iter().enumerate().fold(
|
||||
formatted,
|
||||
|acc, (index, (line, _, indent))| {
|
||||
let index = if index == 0 {
|
||||
format!("{:<3}| ", index + 1).dimmed()
|
||||
} else {
|
||||
format!("\n{:<3}| ", index + 1).dimmed()
|
||||
};
|
||||
let left_pad = " ".repeat(*indent);
|
||||
|
||||
format!("{}{}{}{}", acc, index, left_pad, line)
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> Result<(), LexError> {
|
||||
if self.lexer.is_eof() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (new_token, position) = self.lexer.next_token()?;
|
||||
|
||||
log::info!(
|
||||
"Parsing {} at {}",
|
||||
new_token.to_string().bold(),
|
||||
position.to_string()
|
||||
);
|
||||
|
||||
self.previous_token = replace(&mut self.current_token, new_token);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_colored(&mut self, colored: ColoredString) {
|
||||
self.next_line.push_str(&format!("{}", colored));
|
||||
}
|
||||
|
||||
fn commit_line(&mut self, line_kind: LineKind) {
|
||||
self.output_lines
|
||||
.push((self.next_line.clone(), line_kind, self.indent));
|
||||
self.next_line.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum LineKind {
|
||||
Empty,
|
||||
Assignment,
|
||||
Expression,
|
||||
Statement,
|
||||
OpenBlock,
|
||||
CloseBlock,
|
||||
}
|
902
dust-lang/src/instruction.rs
Normal file
902
dust-lang/src/instruction.rs
Normal file
@ -0,0 +1,902 @@
|
||||
//! An operation and its arguments for the Dust virtual machine.
|
||||
//!
|
||||
//! Each instruction is a 32-bit unsigned integer that is divided into five fields:
|
||||
//! - Bits 0-6: The operation code.
|
||||
//! - Bit 7: A flag indicating whether the B argument is a constant.
|
||||
//! - Bit 8: A flag indicating whether the C argument is a constant.
|
||||
//! - Bits 9-16: The A argument,
|
||||
//! - Bits 17-24: The B argument.
|
||||
//! - Bits 25-32: The C argument.
|
||||
//!
|
||||
//! Be careful when working with instructions directly. When modifying an instruction, be sure to
|
||||
//! account for the fact that setting the A, B, or C arguments to 0 will have no effect. It is
|
||||
//! usually best to remove instructions and insert new ones in their place instead of mutating them.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Chunk, NativeFunction, Operation, Type};
|
||||
|
||||
/// An operation and its arguments for the Dust virtual machine.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct Instruction(u32);
|
||||
|
||||
impl Instruction {
|
||||
pub fn with_operation(operation: Operation) -> Instruction {
|
||||
Instruction(operation as u32)
|
||||
}
|
||||
|
||||
pub fn r#move(to_register: u8, from_register: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Move as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(from_register);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn close(from_register: u8, to_register: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Close as u32);
|
||||
|
||||
instruction.set_b(from_register);
|
||||
instruction.set_c(to_register);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn load_boolean(to_register: u8, value: bool, skip: bool) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::LoadBoolean as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b_to_boolean(value);
|
||||
instruction.set_c_to_boolean(skip);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn load_constant(to_register: u8, constant_index: u8, skip: bool) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::LoadConstant as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(constant_index);
|
||||
instruction.set_c_to_boolean(skip);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn load_list(to_register: u8, start_register: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::LoadList as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(start_register);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn load_self(to_register: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::LoadSelf as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn define_local(to_register: u8, local_index: u8, is_mutable: bool) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::DefineLocal as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(local_index);
|
||||
instruction.set_c(if is_mutable { 1 } else { 0 });
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn get_local(to_register: u8, local_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::GetLocal as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(local_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn set_local(from_register: u8, local_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::SetLocal as u32);
|
||||
|
||||
instruction.set_a(from_register);
|
||||
instruction.set_b(local_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn add(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Add as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn subtract(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Subtract as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn multiply(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Multiply as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn divide(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Divide as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn modulo(to_register: u8, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Modulo as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn test(to_register: u8, test_value: bool) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Test as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_c_to_boolean(test_value);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn test_set(to_register: u8, argument_index: u8, test_value: bool) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::TestSet as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(argument_index);
|
||||
instruction.set_c_to_boolean(test_value);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Equal as u32);
|
||||
|
||||
instruction.set_a_to_boolean(comparison_boolean);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn less(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Less as u32);
|
||||
|
||||
instruction.set_a_to_boolean(comparison_boolean);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn less_equal(comparison_boolean: bool, left_index: u8, right_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::LessEqual as u32);
|
||||
|
||||
instruction.set_a_to_boolean(comparison_boolean);
|
||||
instruction.set_b(left_index);
|
||||
instruction.set_c(right_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn negate(to_register: u8, from_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Negate as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(from_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn not(to_register: u8, from_index: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Not as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(from_index);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn jump(jump_offset: u8, is_positive: bool) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Jump as u32);
|
||||
|
||||
instruction.set_b(jump_offset);
|
||||
instruction.set_c_to_boolean(is_positive);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn call(to_register: u8, function_register: u8, argument_count: u8) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Call as u32);
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(function_register);
|
||||
instruction.set_c(argument_count);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn call_native(
|
||||
to_register: u8,
|
||||
native_fn: NativeFunction,
|
||||
argument_count: u8,
|
||||
) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::CallNative as u32);
|
||||
let native_fn_byte = native_fn as u8;
|
||||
|
||||
instruction.set_a(to_register);
|
||||
instruction.set_b(native_fn_byte);
|
||||
instruction.set_c(argument_count);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn r#return(should_return_value: bool) -> Instruction {
|
||||
let mut instruction = Instruction(Operation::Return as u32);
|
||||
|
||||
instruction.set_b_to_boolean(should_return_value);
|
||||
|
||||
instruction
|
||||
}
|
||||
|
||||
pub fn operation(&self) -> Operation {
|
||||
Operation::from((self.0 & 0b0000_0000_0011_1111) as u8)
|
||||
}
|
||||
|
||||
pub fn set_operation(&mut self, operation: Operation) {
|
||||
self.0 |= u8::from(operation) as u32;
|
||||
}
|
||||
|
||||
pub fn data(&self) -> (Operation, u8, u8, u8, bool, bool) {
|
||||
(
|
||||
self.operation(),
|
||||
self.a(),
|
||||
self.b(),
|
||||
self.c(),
|
||||
self.b_is_constant(),
|
||||
self.c_is_constant(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn a(&self) -> u8 {
|
||||
(self.0 >> 24) as u8
|
||||
}
|
||||
|
||||
pub fn b(&self) -> u8 {
|
||||
(self.0 >> 16) as u8
|
||||
}
|
||||
|
||||
pub fn c(&self) -> u8 {
|
||||
(self.0 >> 8) as u8
|
||||
}
|
||||
|
||||
pub fn a_as_boolean(&self) -> bool {
|
||||
self.a() != 0
|
||||
}
|
||||
|
||||
pub fn b_as_boolean(&self) -> bool {
|
||||
self.b() != 0
|
||||
}
|
||||
|
||||
pub fn c_as_boolean(&self) -> bool {
|
||||
self.c() != 0
|
||||
}
|
||||
|
||||
pub fn set_a_to_boolean(&mut self, boolean: bool) -> &mut Self {
|
||||
self.set_a(if boolean { 1 } else { 0 });
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_b_to_boolean(&mut self, boolean: bool) -> &mut Self {
|
||||
self.set_b(if boolean { 1 } else { 0 });
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_c_to_boolean(&mut self, boolean: bool) -> &mut Self {
|
||||
self.set_c(if boolean { 1 } else { 0 });
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_a(&mut self, to_register: u8) {
|
||||
self.0 |= (to_register as u32) << 24;
|
||||
}
|
||||
|
||||
pub fn set_b(&mut self, argument: u8) {
|
||||
self.0 |= (argument as u32) << 16;
|
||||
}
|
||||
|
||||
pub fn set_c(&mut self, argument: u8) {
|
||||
self.0 |= (argument as u32) << 8;
|
||||
}
|
||||
|
||||
pub fn b_is_constant(&self) -> bool {
|
||||
self.0 & 0b1000_0000 != 0
|
||||
}
|
||||
|
||||
pub fn c_is_constant(&self) -> bool {
|
||||
self.0 & 0b0100_0000 != 0
|
||||
}
|
||||
|
||||
pub fn set_b_is_constant(&mut self) -> &mut Self {
|
||||
self.0 |= 0b1000_0000;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_c_is_constant(&mut self) -> &mut Self {
|
||||
self.0 |= 0b0100_0000;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn yields_value(&self) -> bool {
|
||||
match self.operation() {
|
||||
Operation::Add
|
||||
| Operation::Call
|
||||
| Operation::Divide
|
||||
| Operation::GetLocal
|
||||
| Operation::LoadBoolean
|
||||
| Operation::LoadConstant
|
||||
| Operation::LoadList
|
||||
| Operation::LoadSelf
|
||||
| Operation::Modulo
|
||||
| Operation::Multiply
|
||||
| Operation::Negate
|
||||
| Operation::Not
|
||||
| Operation::Subtract => true,
|
||||
Operation::CallNative => {
|
||||
let native_function = NativeFunction::from(self.b());
|
||||
|
||||
native_function.r#type().return_type.is_some()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn yielded_type(&self, chunk: &Chunk) -> Option<Type> {
|
||||
use Operation::*;
|
||||
|
||||
match self.operation() {
|
||||
Add | Divide | Modulo | Multiply | Subtract => {
|
||||
if self.b_is_constant() {
|
||||
chunk.get_constant_type(self.b())
|
||||
} else {
|
||||
chunk.get_register_type(self.b())
|
||||
}
|
||||
}
|
||||
LoadBoolean | Not => Some(Type::Boolean),
|
||||
Negate => {
|
||||
if self.b_is_constant() {
|
||||
chunk.get_constant_type(self.b())
|
||||
} else {
|
||||
chunk.get_register_type(self.b())
|
||||
}
|
||||
}
|
||||
LoadConstant => chunk.get_constant_type(self.b()),
|
||||
LoadList => chunk.get_register_type(self.a()),
|
||||
GetLocal => chunk.get_local_type(self.b()),
|
||||
CallNative => {
|
||||
let native_function = NativeFunction::from(self.b());
|
||||
|
||||
native_function.r#type().return_type.map(|boxed| *boxed)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disassembly_info(&self, chunk: &Chunk) -> String {
|
||||
let format_arguments = || {
|
||||
let first_argument = if self.b_is_constant() {
|
||||
format!("C{}", self.b())
|
||||
} else {
|
||||
format!("R{}", self.b())
|
||||
};
|
||||
let second_argument = if self.c_is_constant() {
|
||||
format!("C{}", self.c())
|
||||
} else {
|
||||
format!("R{}", self.c())
|
||||
};
|
||||
|
||||
(first_argument, second_argument)
|
||||
};
|
||||
|
||||
match self.operation() {
|
||||
Operation::Move => format!("R{} = R{}", self.a(), self.b()),
|
||||
Operation::Close => {
|
||||
let from_register = self.b();
|
||||
let to_register = self.c().saturating_sub(1);
|
||||
|
||||
format!("R{from_register}..=R{to_register}")
|
||||
}
|
||||
Operation::LoadBoolean => {
|
||||
let to_register = self.a();
|
||||
let boolean = self.b_as_boolean();
|
||||
let jump = self.c_as_boolean();
|
||||
|
||||
if jump {
|
||||
format!("R{to_register} = {boolean} && SKIP")
|
||||
} else {
|
||||
format!("R{to_register} = {boolean}")
|
||||
}
|
||||
}
|
||||
Operation::LoadConstant => {
|
||||
let register_index = self.a();
|
||||
let constant_index = self.b();
|
||||
let jump = self.c_as_boolean();
|
||||
|
||||
if jump {
|
||||
format!("R{register_index} = C{constant_index} && SKIP")
|
||||
} else {
|
||||
format!("R{register_index} = C{constant_index}")
|
||||
}
|
||||
}
|
||||
Operation::LoadList => {
|
||||
let to_register = self.a();
|
||||
let first_index = self.b();
|
||||
let last_index = self.c();
|
||||
|
||||
format!("R{to_register} = [R{first_index}..=R{last_index}]",)
|
||||
}
|
||||
Operation::LoadSelf => {
|
||||
let to_register = self.a();
|
||||
let name = chunk
|
||||
.name()
|
||||
.map(|idenifier| idenifier.as_str())
|
||||
.unwrap_or("self");
|
||||
|
||||
format!("R{to_register} = {name}")
|
||||
}
|
||||
Operation::DefineLocal => {
|
||||
let to_register = self.a();
|
||||
let local_index = self.b();
|
||||
let identifier_display = match chunk.get_identifier(local_index) {
|
||||
Some(identifier) => identifier.to_string(),
|
||||
None => "???".to_string(),
|
||||
};
|
||||
let mutable_display = if self.c_as_boolean() { "mut" } else { "" };
|
||||
|
||||
format!("R{to_register} = L{local_index} {mutable_display} {identifier_display}")
|
||||
}
|
||||
Operation::GetLocal => {
|
||||
let local_index = self.b();
|
||||
|
||||
format!("R{} = L{}", self.a(), local_index)
|
||||
}
|
||||
Operation::SetLocal => {
|
||||
let local_index = self.b();
|
||||
let identifier_display = match chunk.get_identifier(local_index) {
|
||||
Some(identifier) => identifier.to_string(),
|
||||
None => "???".to_string(),
|
||||
};
|
||||
|
||||
format!("L{} = R{} {}", local_index, self.a(), identifier_display)
|
||||
}
|
||||
Operation::Add => {
|
||||
let to_register = self.a();
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("R{to_register} = {first_argument} + {second_argument}",)
|
||||
}
|
||||
Operation::Subtract => {
|
||||
let to_register = self.a();
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("R{to_register} = {first_argument} - {second_argument}",)
|
||||
}
|
||||
Operation::Multiply => {
|
||||
let to_register = self.a();
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("R{to_register} = {first_argument} * {second_argument}",)
|
||||
}
|
||||
Operation::Divide => {
|
||||
let to_register = self.a();
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("R{to_register} = {first_argument} / {second_argument}",)
|
||||
}
|
||||
Operation::Modulo => {
|
||||
let to_register = self.a();
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("R{to_register} = {first_argument} % {second_argument}",)
|
||||
}
|
||||
Operation::Test => {
|
||||
let to_register = self.a();
|
||||
let test_value = self.c_as_boolean();
|
||||
|
||||
format!("if R{to_register} != {test_value} {{ SKIP }}")
|
||||
}
|
||||
Operation::TestSet => {
|
||||
let to_register = self.a();
|
||||
let argument = format!("R{}", self.b());
|
||||
let test_value = self.c_as_boolean();
|
||||
let bang = if test_value { "" } else { "!" };
|
||||
|
||||
format!("if {bang}R{to_register} {{ R{to_register} = R{argument} }}",)
|
||||
}
|
||||
Operation::Equal => {
|
||||
let comparison_symbol = if self.a_as_boolean() { "==" } else { "!=" };
|
||||
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
|
||||
}
|
||||
Operation::Less => {
|
||||
let comparison_symbol = if self.a_as_boolean() { "<" } else { ">=" };
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
|
||||
}
|
||||
Operation::LessEqual => {
|
||||
let comparison_symbol = if self.a_as_boolean() { "<=" } else { ">" };
|
||||
let (first_argument, second_argument) = format_arguments();
|
||||
|
||||
format!("if {first_argument} {comparison_symbol} {second_argument} {{ SKIP }}")
|
||||
}
|
||||
Operation::Negate => {
|
||||
let to_register = self.a();
|
||||
let argument = if self.b_is_constant() {
|
||||
format!("C{}", self.b())
|
||||
} else {
|
||||
format!("R{}", self.b())
|
||||
};
|
||||
|
||||
format!("R{to_register} = -{argument}")
|
||||
}
|
||||
Operation::Not => {
|
||||
let to_register = self.a();
|
||||
let argument = if self.b_is_constant() {
|
||||
format!("C{}", self.b())
|
||||
} else {
|
||||
format!("R{}", self.b())
|
||||
};
|
||||
|
||||
format!("R{to_register} = !{argument}")
|
||||
}
|
||||
Operation::Jump => {
|
||||
let jump_distance = self.b();
|
||||
let is_positive = self.c_as_boolean();
|
||||
|
||||
if is_positive {
|
||||
format!("JUMP +{jump_distance}")
|
||||
} else {
|
||||
format!("JUMP -{jump_distance}")
|
||||
}
|
||||
}
|
||||
Operation::Call => {
|
||||
let to_register = self.a();
|
||||
let function_register = self.b();
|
||||
let argument_count = self.c();
|
||||
|
||||
let mut output = format!("R{to_register} = R{function_register}(");
|
||||
|
||||
if argument_count != 0 {
|
||||
let first_argument = function_register + 1;
|
||||
|
||||
for (index, register) in
|
||||
(first_argument..first_argument + argument_count).enumerate()
|
||||
{
|
||||
if index > 0 {
|
||||
output.push_str(", ");
|
||||
}
|
||||
|
||||
output.push_str(&format!("R{}", register));
|
||||
}
|
||||
}
|
||||
|
||||
output.push(')');
|
||||
|
||||
output
|
||||
}
|
||||
Operation::CallNative => {
|
||||
let to_register = self.a();
|
||||
let native_function = NativeFunction::from(self.b());
|
||||
let argument_count = self.c();
|
||||
let mut output = String::new();
|
||||
let native_function_name = native_function.as_str();
|
||||
|
||||
if native_function.r#type().return_type.is_some() {
|
||||
output.push_str(&format!("R{} = {}(", to_register, native_function_name));
|
||||
} else {
|
||||
output.push_str(&format!("{}(", native_function_name));
|
||||
}
|
||||
|
||||
if argument_count != 0 {
|
||||
let first_argument = to_register.saturating_sub(argument_count);
|
||||
|
||||
for (index, register) in (first_argument..to_register).enumerate() {
|
||||
if index > 0 {
|
||||
output.push_str(", ");
|
||||
}
|
||||
|
||||
output.push_str(&format!("R{}", register));
|
||||
}
|
||||
}
|
||||
|
||||
output.push(')');
|
||||
|
||||
output
|
||||
}
|
||||
Operation::Return => {
|
||||
let should_return_value = self.b_as_boolean();
|
||||
|
||||
if should_return_value {
|
||||
"->".to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Instruction> for u32 {
|
||||
fn from(instruction: &Instruction) -> Self {
|
||||
instruction.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn r#move() {
|
||||
let mut instruction = Instruction::r#move(0, 1);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Move);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close() {
|
||||
let instruction = Instruction::close(1, 2);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Close);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert_eq!(instruction.c(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_boolean() {
|
||||
let instruction = Instruction::load_boolean(4, true, true);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::LoadBoolean);
|
||||
assert_eq!(instruction.a(), 4);
|
||||
assert!(instruction.a_as_boolean());
|
||||
assert!(instruction.c_as_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_constant() {
|
||||
let mut instruction = Instruction::load_constant(0, 1, true);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::LoadConstant);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.c_as_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_list() {
|
||||
let instruction = Instruction::load_list(0, 1);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::LoadList);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_self() {
|
||||
let instruction = Instruction::load_self(10);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::LoadSelf);
|
||||
assert_eq!(instruction.a(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declare_local() {
|
||||
let mut instruction = Instruction::define_local(0, 1, true);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::DefineLocal);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert_eq!(instruction.c(), true as u8);
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add() {
|
||||
let mut instruction = Instruction::add(1, 1, 0);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Add);
|
||||
assert_eq!(instruction.a(), 1);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert_eq!(instruction.c(), 0);
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract() {
|
||||
let mut instruction = Instruction::subtract(0, 1, 2);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Subtract);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert_eq!(instruction.c(), 2);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply() {
|
||||
let mut instruction = Instruction::multiply(0, 1, 2);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Multiply);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert_eq!(instruction.c(), 2);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn divide() {
|
||||
let mut instruction = Instruction::divide(0, 1, 2);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Divide);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert_eq!(instruction.c(), 2);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and() {
|
||||
let instruction = Instruction::test(4, true);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Test);
|
||||
assert_eq!(instruction.a(), 4);
|
||||
assert!(instruction.c_as_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or() {
|
||||
let instruction = Instruction::test_set(4, 1, true);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::TestSet);
|
||||
assert_eq!(instruction.a(), 4);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert!(instruction.c_as_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equal() {
|
||||
let mut instruction = Instruction::equal(true, 1, 2);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Equal);
|
||||
assert!(instruction.a_as_boolean());
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert_eq!(instruction.c(), 2);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negate() {
|
||||
let mut instruction = Instruction::negate(0, 1);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Negate);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not() {
|
||||
let mut instruction = Instruction::not(0, 1);
|
||||
|
||||
instruction.set_b_is_constant();
|
||||
instruction.set_c_is_constant();
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Not);
|
||||
assert_eq!(instruction.a(), 0);
|
||||
assert_eq!(instruction.b(), 1);
|
||||
assert!(instruction.b_is_constant());
|
||||
assert!(instruction.b_is_constant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jump() {
|
||||
let instruction = Instruction::jump(4, true);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Jump);
|
||||
|
||||
assert_eq!(instruction.b(), 4);
|
||||
assert!(instruction.c_as_boolean());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call() {
|
||||
let instruction = Instruction::call(1, 3, 4);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Call);
|
||||
assert_eq!(instruction.a(), 1);
|
||||
assert_eq!(instruction.b(), 3);
|
||||
assert_eq!(instruction.c(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r#return() {
|
||||
let instruction = Instruction::r#return(true);
|
||||
|
||||
assert_eq!(instruction.operation(), Operation::Return);
|
||||
assert!(instruction.b_as_boolean());
|
||||
}
|
||||
}
|
1476
dust-lang/src/lexer.rs
Normal file
1476
dust-lang/src/lexer.rs
Normal file
File diff suppressed because it is too large
Load Diff
44
dust-lang/src/lib.rs
Normal file
44
dust-lang/src/lib.rs
Normal file
@ -0,0 +1,44 @@
|
||||
//! The Dust programming language library.
|
||||
|
||||
pub mod chunk;
|
||||
pub mod compiler;
|
||||
pub mod disassembler;
|
||||
pub mod dust_error;
|
||||
pub mod formatter;
|
||||
pub mod instruction;
|
||||
pub mod lexer;
|
||||
pub mod native_function;
|
||||
pub mod operation;
|
||||
pub mod optimizer;
|
||||
pub mod token;
|
||||
pub mod r#type;
|
||||
pub mod value;
|
||||
pub mod vm;
|
||||
|
||||
pub use crate::chunk::{Chunk, ChunkError, Local, Scope};
|
||||
pub use crate::compiler::{compile, CompileError, Compiler};
|
||||
pub use crate::disassembler::Disassembler;
|
||||
pub use crate::dust_error::{AnnotatedError, DustError};
|
||||
pub use crate::formatter::{format, Formatter};
|
||||
pub use crate::instruction::Instruction;
|
||||
pub use crate::lexer::{lex, LexError, Lexer};
|
||||
pub use crate::native_function::{NativeFunction, NativeFunctionError};
|
||||
pub use crate::operation::Operation;
|
||||
pub use crate::optimizer::{optimize, Optimizer};
|
||||
pub use crate::r#type::{EnumType, FunctionType, RangeableType, StructType, Type, TypeConflict};
|
||||
pub use crate::token::{Token, TokenKind, TokenOwned};
|
||||
pub use crate::value::{ConcreteValue, Function, Value, ValueError};
|
||||
pub use crate::vm::{run, Vm, VmError};
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct Span(pub usize, pub usize);
|
||||
|
||||
impl Display for Span {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.0, self.1)
|
||||
}
|
||||
}
|
418
dust-lang/src/native_function.rs
Normal file
418
dust-lang/src/native_function.rs
Normal file
@ -0,0 +1,418 @@
|
||||
//! Built-in functions that implement extended functionality.
|
||||
//!
|
||||
//! Native functions are used either to implement features that are not possible to implement in
|
||||
//! Dust itself or that are more efficient to implement in Rust.
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io::{self, stdin, stdout, Write},
|
||||
string::{self},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
AnnotatedError, ConcreteValue, FunctionType, Instruction, Span, Type, Value, Vm, VmError,
|
||||
};
|
||||
|
||||
macro_rules! define_native_function {
|
||||
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
|
||||
/// A dust-native function.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum NativeFunction {
|
||||
$(
|
||||
$name = $byte as isize,
|
||||
)*
|
||||
}
|
||||
|
||||
impl NativeFunction {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
$(
|
||||
NativeFunction::$name => $str,
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn from_str(string: &str) -> Option<Self> {
|
||||
match string {
|
||||
$(
|
||||
$str => Some(NativeFunction::$name),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> FunctionType {
|
||||
match self {
|
||||
$(
|
||||
NativeFunction::$name => $type,
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn returns_value(&self) -> bool {
|
||||
match self {
|
||||
$(
|
||||
NativeFunction::$name => $type.return_type.is_some(),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for NativeFunction {
|
||||
fn from(byte: u8) -> Self {
|
||||
match byte {
|
||||
$(
|
||||
$byte => NativeFunction::$name,
|
||||
)*
|
||||
_ => {
|
||||
if cfg!(test) {
|
||||
panic!("Invalid native function byte: {}", byte)
|
||||
} else {
|
||||
NativeFunction::Panic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NativeFunction> for u8 {
|
||||
fn from(native_function: NativeFunction) -> Self {
|
||||
match native_function {
|
||||
$(
|
||||
NativeFunction::$name => $byte,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_native_function! {
|
||||
// Assertion
|
||||
(
|
||||
Assert,
|
||||
0_u8,
|
||||
"assert",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: None
|
||||
}
|
||||
),
|
||||
// (AssertEqual, 1_u8, "assert_equal", false),
|
||||
// (AssertNotEqual, 2_u8, "assert_not_equal", false),
|
||||
(
|
||||
Panic,
|
||||
3_u8,
|
||||
"panic",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Some(Box::new(Type::Any))
|
||||
}
|
||||
),
|
||||
|
||||
// // Type conversion
|
||||
// (Parse, 4_u8, "parse", true),
|
||||
// (ToByte, 5_u8, "to_byte", true),
|
||||
// (ToFloat, 6_u8, "to_float", true),
|
||||
// (ToInteger, 7_u8, "to_integer", true),
|
||||
(
|
||||
ToString,
|
||||
8_u8,
|
||||
"to_string",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Any)]),
|
||||
return_type: Some(Box::new(Type::String { length: None }))
|
||||
}
|
||||
),
|
||||
|
||||
// // List and string
|
||||
// (All, 9_u8, "all", true),
|
||||
// (Any, 10_u8, "any", true),
|
||||
// (Append, 11_u8, "append", false),
|
||||
// (Contains, 12_u8, "contains", true),
|
||||
// (Dedup, 13_u8, "dedup", false),
|
||||
// (EndsWith, 14_u8, "ends_with", true),
|
||||
// (Find, 15_u8, "find", true),
|
||||
// (Get, 16_u8, "get", true),
|
||||
// (IndexOf, 17_u8, "index_of", true),
|
||||
// (Length, 18_u8, "length", true),
|
||||
// (Prepend, 19_u8, "prepend", false),
|
||||
// (Replace, 20_u8, "replace", false),
|
||||
// (Set, 21_u8, "set", false),
|
||||
// (StartsWith, 22_u8, "starts_with", true),
|
||||
// (Slice, 23_u8, "slice", true),
|
||||
// (Sort, 24_u8, "sort", false),
|
||||
// (Split, 25_u8, "split", true),
|
||||
|
||||
// // List
|
||||
// (Flatten, 26_u8, "flatten", false),
|
||||
// (Join, 27_u8, "join", true),
|
||||
// (Map, 28_u8, "map", true),
|
||||
// (Reduce, 29_u8, "reduce", true),
|
||||
// (Remove, 30_u8, "remove", false),
|
||||
// (Reverse, 31_u8, "reverse", false),
|
||||
// (Unzip, 32_u8, "unzip", true),
|
||||
// (Zip, 33_u8, "zip", true),
|
||||
|
||||
// // String
|
||||
// (Bytes, 34_u8, "bytes", true),
|
||||
// (CharAt, 35_u8, "char_at", true),
|
||||
// (CharCodeAt, 36_u8, "char_code_at", true),
|
||||
// (Chars, 37_u8, "chars", true),
|
||||
// (Format, 38_u8, "format", true),
|
||||
// (Repeat, 39_u8, "repeat", true),
|
||||
// (SplitAt, 40_u8, "split_at", true),
|
||||
// (SplitLines, 41_u8, "split_lines", true),
|
||||
// (SplitWhitespace, 42_u8, "split_whitespace", true),
|
||||
// (ToLowerCase, 43_u8, "to_lower_case", true),
|
||||
// (ToUpperCase, 44_u8, "to_upper_case", true),
|
||||
// (Trim, 45_u8, "trim", true),
|
||||
// (TrimEnd, 46_u8, "trim_end", true),
|
||||
// (TrimStart, 47_u8, "trim_start", true),
|
||||
|
||||
// // I/O
|
||||
// // Read
|
||||
// (Read, 48_u8, "read", true),
|
||||
// (ReadFile, 49_u8, "read_file", true),
|
||||
(
|
||||
ReadLine,
|
||||
50_u8,
|
||||
"read_line",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: Some(Box::new(Type::String { length: None }))
|
||||
}
|
||||
),
|
||||
// (ReadTo, 51_u8, "read_to", false),
|
||||
// (ReadUntil, 52_u8, "read_until", true),
|
||||
// // Write
|
||||
// (AppendFile, 53_u8, "append_file", false),
|
||||
// (PrependFile, 54_u8, "prepend_file", false),
|
||||
(
|
||||
Write,
|
||||
55_u8,
|
||||
"write",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
||||
return_type: None
|
||||
}
|
||||
),
|
||||
// (WriteFile, 56_u8, "write_file", false),
|
||||
(
|
||||
WriteLine,
|
||||
57_u8,
|
||||
"write_line",
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::String { length: None })]),
|
||||
return_type: None
|
||||
}
|
||||
)
|
||||
|
||||
// // Random
|
||||
// (Random, 58_u8, "random", true),
|
||||
// (RandomInRange, 59_u8, "random_in_range", true)
|
||||
}
|
||||
|
||||
impl NativeFunction {
|
||||
pub fn call(
|
||||
&self,
|
||||
instruction: Instruction,
|
||||
vm: &Vm,
|
||||
position: Span,
|
||||
) -> Result<Option<Value>, VmError> {
|
||||
let to_register = instruction.a();
|
||||
let argument_count = instruction.c();
|
||||
|
||||
let return_value = match self {
|
||||
NativeFunction::Panic => {
|
||||
let message = if argument_count == 0 {
|
||||
None
|
||||
} else {
|
||||
let mut message = String::new();
|
||||
|
||||
for argument_index in 0..argument_count {
|
||||
if argument_index != 0 {
|
||||
message.push(' ');
|
||||
}
|
||||
|
||||
let argument = vm.open_register(argument_index, position)?;
|
||||
|
||||
message.push_str(&argument.to_string());
|
||||
}
|
||||
|
||||
Some(message)
|
||||
};
|
||||
|
||||
return Err(VmError::NativeFunction(NativeFunctionError::Panic {
|
||||
message,
|
||||
position,
|
||||
}));
|
||||
}
|
||||
|
||||
// Type conversion
|
||||
NativeFunction::ToString => {
|
||||
let mut string = String::new();
|
||||
|
||||
for argument_index in 0..argument_count {
|
||||
let argument = vm.open_register(argument_index, position)?;
|
||||
|
||||
string.push_str(&argument.to_string());
|
||||
}
|
||||
|
||||
Some(Value::Concrete(ConcreteValue::String(string)))
|
||||
}
|
||||
|
||||
// I/O
|
||||
NativeFunction::ReadLine => {
|
||||
let mut buffer = String::new();
|
||||
|
||||
stdin().read_line(&mut buffer).map_err(|io_error| {
|
||||
VmError::NativeFunction(NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position,
|
||||
})
|
||||
})?;
|
||||
|
||||
buffer = buffer.trim_end_matches('\n').to_string();
|
||||
|
||||
Some(Value::Concrete(ConcreteValue::String(buffer)))
|
||||
}
|
||||
NativeFunction::Write => {
|
||||
let to_register = instruction.a();
|
||||
let mut stdout = stdout();
|
||||
let map_err = |io_error: io::Error| {
|
||||
VmError::NativeFunction(NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position,
|
||||
})
|
||||
};
|
||||
|
||||
let first_argument = to_register.saturating_sub(argument_count);
|
||||
let last_argument = to_register.saturating_sub(1);
|
||||
|
||||
for argument_index in first_argument..=last_argument {
|
||||
if argument_index != first_argument {
|
||||
stdout.write(b" ").map_err(map_err)?;
|
||||
}
|
||||
|
||||
let argument_string = vm.open_register(argument_index, position)?.to_string();
|
||||
|
||||
stdout
|
||||
.write_all(argument_string.as_bytes())
|
||||
.map_err(map_err)?;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
NativeFunction::WriteLine => {
|
||||
let mut stdout = stdout();
|
||||
let map_err = |io_error: io::Error| {
|
||||
VmError::NativeFunction(NativeFunctionError::Io {
|
||||
error: io_error.kind(),
|
||||
position,
|
||||
})
|
||||
};
|
||||
|
||||
let first_index = to_register.saturating_sub(argument_count);
|
||||
let arguments = vm.open_nonempty_registers(first_index..to_register, position)?;
|
||||
|
||||
for (index, argument) in arguments.into_iter().enumerate() {
|
||||
if index != 0 {
|
||||
stdout.write(b" ").map_err(map_err)?;
|
||||
}
|
||||
|
||||
if let Value::Concrete(ConcreteValue::String(string)) = argument {
|
||||
let bytes = string.as_bytes();
|
||||
|
||||
stdout.write_all(bytes).map_err(map_err)?;
|
||||
} else {
|
||||
let bytes = argument.to_string().into_bytes();
|
||||
|
||||
stdout.write_all(&bytes).map_err(map_err)?;
|
||||
}
|
||||
}
|
||||
|
||||
stdout.write(b"\n").map_err(map_err)?;
|
||||
|
||||
None
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
Ok(return_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NativeFunction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum NativeFunctionError {
|
||||
ExpectedArgumentCount {
|
||||
expected: usize,
|
||||
found: usize,
|
||||
position: Span,
|
||||
},
|
||||
Panic {
|
||||
message: Option<String>,
|
||||
position: Span,
|
||||
},
|
||||
Parse {
|
||||
error: string::ParseError,
|
||||
position: Span,
|
||||
},
|
||||
Io {
|
||||
error: io::ErrorKind,
|
||||
position: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl AnnotatedError for NativeFunctionError {
|
||||
fn title() -> &'static str {
|
||||
"Native Function Error"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
NativeFunctionError::ExpectedArgumentCount { .. } => {
|
||||
"Expected a different number of arguments"
|
||||
}
|
||||
NativeFunctionError::Panic { .. } => "Explicit panic",
|
||||
NativeFunctionError::Parse { .. } => "Failed to parse value",
|
||||
NativeFunctionError::Io { .. } => "I/O error",
|
||||
}
|
||||
}
|
||||
|
||||
fn details(&self) -> Option<String> {
|
||||
match self {
|
||||
NativeFunctionError::ExpectedArgumentCount {
|
||||
expected, found, ..
|
||||
} => Some(format!("Expected {} arguments, found {}", expected, found)),
|
||||
NativeFunctionError::Panic { message, .. } => message.clone(),
|
||||
NativeFunctionError::Parse { error, .. } => Some(format!("{}", error)),
|
||||
NativeFunctionError::Io { error, .. } => Some(format!("{}", error)),
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self) -> Span {
|
||||
match self {
|
||||
NativeFunctionError::ExpectedArgumentCount { position, .. } => *position,
|
||||
NativeFunctionError::Panic { position, .. } => *position,
|
||||
NativeFunctionError::Parse { position, .. } => *position,
|
||||
NativeFunctionError::Io { position, .. } => *position,
|
||||
}
|
||||
}
|
||||
}
|
110
dust-lang/src/operation.rs
Normal file
110
dust-lang/src/operation.rs
Normal file
@ -0,0 +1,110 @@
|
||||
//! Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.
|
||||
//!
|
||||
//! !!! Warning !!!
|
||||
//! The byte values of the operations matter. The seventh and eighth bits must be zero so that the
|
||||
//! [Instruction][crate::Instruction] type can use them as flags.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
macro_rules! define_operation {
|
||||
($(($name:ident, $byte:literal, $str:expr, $type:expr)),*) => {
|
||||
/// Part of an [Instruction][crate::Instruction], which can be executed by the Dust virtual machine.)
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Operation {
|
||||
$(
|
||||
$name = $byte as isize,
|
||||
)*
|
||||
}
|
||||
|
||||
impl From<u8> for Operation {
|
||||
fn from(byte: u8) -> Self {
|
||||
match byte {
|
||||
$(
|
||||
$byte => Operation::$name,
|
||||
)*
|
||||
_ => {
|
||||
if cfg!(test) {
|
||||
panic!("Invalid operation byte: {}", byte)
|
||||
} else {
|
||||
Operation::Return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Operation> for u8 {
|
||||
fn from(operation: Operation) -> Self {
|
||||
match operation {
|
||||
$(
|
||||
Operation::$name => $byte,
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Operation {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
$(
|
||||
Operation::$name => write!(f, "{}", $str),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_operation! {
|
||||
(Move, 0b0000_0000, "MOVE", None),
|
||||
(Close, 0b000_0001, "CLOSE", None),
|
||||
(LoadBoolean, 0b0000_0010, "LOAD_BOOLEAN", None),
|
||||
(LoadConstant, 0b0000_0011, "LOAD_CONSTANT", None),
|
||||
(LoadList, 0b0000_0100, "LOAD_LIST", None),
|
||||
(LoadSelf, 0b0000_0101, "LOAD_SELF", None),
|
||||
(DefineLocal, 0b0000_0110, "DEFINE_LOCAL", None),
|
||||
(GetLocal, 0b0000_0111, "GET_LOCAL", None),
|
||||
(SetLocal, 0b0000_1000, "SET_LOCAL", None),
|
||||
(Add, 0b0000_1001, "ADD", None),
|
||||
(Subtract, 0b0000_1010, "SUBTRACT", None),
|
||||
(Multiply, 0b0000_1011, "MULTIPLY", None),
|
||||
(Divide, 0b0000_1100, "DIVIDE", None),
|
||||
(Modulo, 0b0000_1101, "MODULO", None),
|
||||
(Test, 0b0000_1110, "TEST", None),
|
||||
(TestSet, 0b0000_1111, "TEST_SET", None),
|
||||
(Equal, 0b0001_0000, "EQUAL", None),
|
||||
(Less, 0b0001_0001, "LESS", None),
|
||||
(LessEqual, 0b0001_0010, "LESS_EQUAL", None),
|
||||
(Negate, 0b0001_0011, "NEGATE", None),
|
||||
(Not, 0b0001_0100, "NOT", None),
|
||||
(Jump, 0b0001_0101, "JUMP", None),
|
||||
(Call, 0b0001_0110, "CALL", None),
|
||||
(CallNative, 0b0001_0111, "CALL_NATIVE", None),
|
||||
(Return, 0b0001_1000, "RETURN", None)
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn is_math(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Operation::Add
|
||||
| Operation::Subtract
|
||||
| Operation::Multiply
|
||||
| Operation::Divide
|
||||
| Operation::Modulo
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_comparison(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Operation::Equal | Operation::Less | Operation::LessEqual
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_test(&self) -> bool {
|
||||
matches!(self, Operation::Test | Operation::TestSet)
|
||||
}
|
||||
}
|
96
dust-lang/src/optimizer.rs
Normal file
96
dust-lang/src/optimizer.rs
Normal file
@ -0,0 +1,96 @@
|
||||
//! Tools used by the compiler to optimize a chunk's bytecode.
|
||||
use std::{iter::Map, slice::Iter};
|
||||
|
||||
use crate::{Instruction, Operation, Span};
|
||||
|
||||
type MapToOperation = fn(&(Instruction, Span)) -> Operation;
|
||||
|
||||
type OperationIter<'iter> = Map<Iter<'iter, (Instruction, Span)>, MapToOperation>;
|
||||
|
||||
/// Performs optimizations on a subset of instructions.
|
||||
pub fn optimize(instructions: &mut [(Instruction, Span)]) -> usize {
|
||||
Optimizer::new(instructions).optimize()
|
||||
}
|
||||
|
||||
/// An instruction optimizer that mutably borrows instructions from a chunk.
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Optimizer<'chunk> {
|
||||
instructions: &'chunk mut [(Instruction, Span)],
|
||||
}
|
||||
|
||||
impl<'chunk> Optimizer<'chunk> {
|
||||
/// Creates a new optimizer with a mutable reference to some of a chunk's instructions.
|
||||
pub fn new(instructions: &'chunk mut [(Instruction, Span)]) -> Self {
|
||||
Self { instructions }
|
||||
}
|
||||
|
||||
/// Potentially mutates the instructions to optimize them.
|
||||
pub fn optimize(&mut self) -> usize {
|
||||
let mut optimizations = 0;
|
||||
|
||||
if matches!(
|
||||
self.get_operations(),
|
||||
Some([
|
||||
Operation::Equal | Operation::Less | Operation::LessEqual,
|
||||
Operation::Jump,
|
||||
Operation::LoadBoolean | Operation::LoadConstant,
|
||||
Operation::LoadBoolean | Operation::LoadConstant,
|
||||
])
|
||||
) {
|
||||
self.optimize_comparison();
|
||||
|
||||
optimizations += 1;
|
||||
}
|
||||
|
||||
optimizations
|
||||
}
|
||||
|
||||
/// Optimizes a comparison operation.
|
||||
///
|
||||
/// The instructions must be in the following order:
|
||||
/// - `Operation::Equal | Operation::Less | Operation::LessEqual`
|
||||
/// - `Operation::Jump`
|
||||
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
||||
/// - `Operation::LoadBoolean | Operation::LoadConstant`
|
||||
fn optimize_comparison(&mut self) {
|
||||
log::debug!("Optimizing comparison");
|
||||
|
||||
let first_loader_register = {
|
||||
let first_loader = &mut self.instructions[2].0;
|
||||
|
||||
first_loader.set_c_to_boolean(true);
|
||||
first_loader.a()
|
||||
};
|
||||
|
||||
let second_loader = &mut self.instructions[3].0;
|
||||
let mut second_loader_new = Instruction::with_operation(second_loader.operation());
|
||||
|
||||
second_loader_new.set_a(first_loader_register);
|
||||
second_loader_new.set_b(second_loader.b());
|
||||
second_loader_new.set_c(second_loader.c());
|
||||
second_loader_new.set_b_to_boolean(second_loader.b_is_constant());
|
||||
second_loader_new.set_c_to_boolean(second_loader.c_is_constant());
|
||||
|
||||
*second_loader = second_loader_new;
|
||||
}
|
||||
|
||||
fn operations_iter(&self) -> OperationIter {
|
||||
self.instructions
|
||||
.iter()
|
||||
.map(|(instruction, _)| instruction.operation())
|
||||
}
|
||||
|
||||
fn get_operations<const COUNT: usize>(&self) -> Option<[Operation; COUNT]> {
|
||||
if self.instructions.len() < COUNT {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut n_operations = [Operation::Return; COUNT];
|
||||
|
||||
for (nth, operation) in n_operations.iter_mut().zip(self.operations_iter()) {
|
||||
*nth = operation;
|
||||
}
|
||||
|
||||
Some(n_operations)
|
||||
}
|
||||
}
|
642
dust-lang/src/token.rs
Normal file
642
dust-lang/src/token.rs
Normal file
@ -0,0 +1,642 @@
|
||||
//! Token, TokenOwned and TokenKind types.
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
macro_rules! define_tokens {
|
||||
($($variant:ident $(($data_type:ty))?),+ $(,)?) => {
|
||||
/// Source token.
|
||||
///
|
||||
/// This is a borrowed type, i.e. some variants contain references to the source text.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Serialize, Deserialize)]
|
||||
pub enum Token<'src> {
|
||||
#[default]
|
||||
Eof,
|
||||
$(
|
||||
$variant $(($data_type))?,
|
||||
)*
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
/// Data-less representation of a source token.
|
||||
///
|
||||
/// If a [Token] borrows from the source text, its TokenKind omits the data.
|
||||
pub enum TokenKind {
|
||||
Eof,
|
||||
$(
|
||||
$variant,
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_tokens! {
|
||||
// Hard-coded values
|
||||
Boolean(&'src str),
|
||||
Byte(&'src str),
|
||||
Character(char),
|
||||
Float(&'src str),
|
||||
Identifier(&'src str),
|
||||
Integer(&'src str),
|
||||
String(&'src str),
|
||||
|
||||
// Keywords
|
||||
Async,
|
||||
Bool,
|
||||
Break,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
Fn,
|
||||
If,
|
||||
Int,
|
||||
Let,
|
||||
Loop,
|
||||
Map,
|
||||
Mut,
|
||||
Return,
|
||||
Str,
|
||||
Struct,
|
||||
While,
|
||||
|
||||
// Symbols
|
||||
ArrowThin,
|
||||
BangEqual,
|
||||
Bang,
|
||||
Colon,
|
||||
Comma,
|
||||
Dot,
|
||||
DoubleAmpersand,
|
||||
DoubleDot,
|
||||
DoubleEqual,
|
||||
DoublePipe,
|
||||
Equal,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
LeftCurlyBrace,
|
||||
LeftParenthesis,
|
||||
LeftSquareBrace,
|
||||
Less,
|
||||
LessEqual,
|
||||
Minus,
|
||||
MinusEqual,
|
||||
Percent,
|
||||
PercentEqual,
|
||||
Plus,
|
||||
PlusEqual,
|
||||
RightCurlyBrace,
|
||||
RightParenthesis,
|
||||
RightSquareBrace,
|
||||
Semicolon,
|
||||
Slash,
|
||||
SlashEqual,
|
||||
Star,
|
||||
StarEqual,
|
||||
}
|
||||
|
||||
impl<'src> Token<'src> {
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Token::Eof => 0,
|
||||
Token::Boolean(text) => text.len(),
|
||||
Token::Byte(_) => 3,
|
||||
Token::Character(_) => 3,
|
||||
Token::Float(text) => text.len(),
|
||||
Token::Identifier(text) => text.len(),
|
||||
Token::Integer(text) => text.len(),
|
||||
Token::String(text) => text.len() + 2,
|
||||
Token::Async => 5,
|
||||
Token::ArrowThin => 2,
|
||||
Token::Bool => 4,
|
||||
Token::Break => 5,
|
||||
Token::Else => 4,
|
||||
Token::FloatKeyword => 5,
|
||||
Token::Fn => 2,
|
||||
Token::If => 2,
|
||||
Token::Int => 3,
|
||||
Token::Let => 3,
|
||||
Token::Loop => 4,
|
||||
Token::Map => 3,
|
||||
Token::Mut => 3,
|
||||
Token::Str => 3,
|
||||
Token::Struct => 6,
|
||||
Token::While => 5,
|
||||
Token::BangEqual => 2,
|
||||
Token::Bang => 1,
|
||||
Token::Colon => 1,
|
||||
Token::Comma => 1,
|
||||
Token::Dot => 1,
|
||||
Token::DoubleAmpersand => 2,
|
||||
Token::DoubleDot => 2,
|
||||
Token::DoubleEqual => 2,
|
||||
Token::DoublePipe => 2,
|
||||
Token::Equal => 1,
|
||||
Token::Greater => 1,
|
||||
Token::GreaterEqual => 2,
|
||||
Token::LeftCurlyBrace => 1,
|
||||
Token::LeftParenthesis => 1,
|
||||
Token::LeftSquareBrace => 1,
|
||||
Token::Less => 1,
|
||||
Token::LessEqual => 2,
|
||||
Token::Minus => 1,
|
||||
Token::MinusEqual => 2,
|
||||
Token::Percent => 1,
|
||||
Token::PercentEqual => 2,
|
||||
Token::Plus => 1,
|
||||
Token::PlusEqual => 2,
|
||||
Token::Return => 6,
|
||||
Token::RightCurlyBrace => 1,
|
||||
Token::RightParenthesis => 1,
|
||||
Token::RightSquareBrace => 1,
|
||||
Token::Semicolon => 1,
|
||||
Token::Slash => 1,
|
||||
Token::SlashEqual => 2,
|
||||
Token::Star => 1,
|
||||
Token::StarEqual => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Token::Eof => "",
|
||||
Token::Boolean(text) => text,
|
||||
Token::Byte(text) => text,
|
||||
Token::Character(_) => "character token",
|
||||
Token::Float(text) => text,
|
||||
Token::Identifier(text) => text,
|
||||
Token::Integer(text) => text,
|
||||
Token::String(text) => text,
|
||||
Token::Async => "async",
|
||||
Token::ArrowThin => "->",
|
||||
Token::Bool => "bool",
|
||||
Token::Break => "break",
|
||||
Token::Else => "else",
|
||||
Token::FloatKeyword => "float",
|
||||
Token::Fn => "fn",
|
||||
Token::If => "if",
|
||||
Token::Int => "int",
|
||||
Token::Let => "let",
|
||||
Token::Loop => "loop",
|
||||
Token::Map => "map",
|
||||
Token::Mut => "mut",
|
||||
Token::Str => "str",
|
||||
Token::Struct => "struct",
|
||||
Token::While => "while",
|
||||
Token::BangEqual => "!=",
|
||||
Token::Bang => "!",
|
||||
Token::Colon => ":",
|
||||
Token::Comma => ",",
|
||||
Token::Dot => ".",
|
||||
Token::DoubleAmpersand => "&&",
|
||||
Token::DoubleDot => "..",
|
||||
Token::DoubleEqual => "==",
|
||||
Token::DoublePipe => "||",
|
||||
Token::Equal => "=",
|
||||
Token::Greater => ">",
|
||||
Token::GreaterEqual => ">=",
|
||||
Token::LeftCurlyBrace => "{",
|
||||
Token::LeftParenthesis => "(",
|
||||
Token::LeftSquareBrace => "[",
|
||||
Token::Less => "<",
|
||||
Token::LessEqual => "<=",
|
||||
Token::Minus => "-",
|
||||
Token::MinusEqual => "-=",
|
||||
Token::Percent => "%",
|
||||
Token::PercentEqual => "%=",
|
||||
Token::Plus => "+",
|
||||
Token::PlusEqual => "+=",
|
||||
Token::Return => "return",
|
||||
Token::RightCurlyBrace => "}",
|
||||
Token::RightParenthesis => ")",
|
||||
Token::RightSquareBrace => "]",
|
||||
Token::Semicolon => ";",
|
||||
Token::Slash => "/",
|
||||
Token::SlashEqual => "/=",
|
||||
Token::Star => "*",
|
||||
Token::StarEqual => "*=",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_owned(&self) -> TokenOwned {
|
||||
match self {
|
||||
Token::ArrowThin => TokenOwned::ArrowThin,
|
||||
Token::Async => TokenOwned::Async,
|
||||
Token::BangEqual => TokenOwned::BangEqual,
|
||||
Token::Bang => TokenOwned::Bang,
|
||||
Token::Bool => TokenOwned::Bool,
|
||||
Token::Boolean(boolean) => TokenOwned::Boolean(boolean.to_string()),
|
||||
Token::Break => TokenOwned::Break,
|
||||
Token::Byte(byte) => TokenOwned::Byte(byte.to_string()),
|
||||
Token::Character(character) => TokenOwned::Character(*character),
|
||||
Token::Colon => TokenOwned::Colon,
|
||||
Token::Comma => TokenOwned::Comma,
|
||||
Token::Dot => TokenOwned::Dot,
|
||||
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
|
||||
Token::DoubleDot => TokenOwned::DoubleDot,
|
||||
Token::DoubleEqual => TokenOwned::DoubleEqual,
|
||||
Token::DoublePipe => TokenOwned::DoublePipe,
|
||||
Token::Else => TokenOwned::Else,
|
||||
Token::Eof => TokenOwned::Eof,
|
||||
Token::Equal => TokenOwned::Equal,
|
||||
Token::Float(float) => TokenOwned::Float(float.to_string()),
|
||||
Token::FloatKeyword => TokenOwned::FloatKeyword,
|
||||
Token::Fn => TokenOwned::Fn,
|
||||
Token::Greater => TokenOwned::Greater,
|
||||
Token::GreaterEqual => TokenOwned::GreaterOrEqual,
|
||||
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
|
||||
Token::If => TokenOwned::If,
|
||||
Token::Int => TokenOwned::Int,
|
||||
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
|
||||
Token::LeftCurlyBrace => TokenOwned::LeftCurlyBrace,
|
||||
Token::LeftParenthesis => TokenOwned::LeftParenthesis,
|
||||
Token::LeftSquareBrace => TokenOwned::LeftSquareBrace,
|
||||
Token::Let => TokenOwned::Let,
|
||||
Token::Less => TokenOwned::Less,
|
||||
Token::LessEqual => TokenOwned::LessOrEqual,
|
||||
Token::Loop => TokenOwned::Loop,
|
||||
Token::Map => TokenOwned::Map,
|
||||
Token::Minus => TokenOwned::Minus,
|
||||
Token::MinusEqual => TokenOwned::MinusEqual,
|
||||
Token::Mut => TokenOwned::Mut,
|
||||
Token::Percent => TokenOwned::Percent,
|
||||
Token::PercentEqual => TokenOwned::PercentEqual,
|
||||
Token::Plus => TokenOwned::Plus,
|
||||
Token::PlusEqual => TokenOwned::PlusEqual,
|
||||
Token::Return => TokenOwned::Return,
|
||||
Token::RightCurlyBrace => TokenOwned::RightCurlyBrace,
|
||||
Token::RightParenthesis => TokenOwned::RightParenthesis,
|
||||
Token::RightSquareBrace => TokenOwned::RightSquareBrace,
|
||||
Token::Semicolon => TokenOwned::Semicolon,
|
||||
Token::Star => TokenOwned::Star,
|
||||
Token::StarEqual => TokenOwned::StarEqual,
|
||||
Token::Slash => TokenOwned::Slash,
|
||||
Token::SlashEqual => TokenOwned::SlashEqual,
|
||||
Token::String(text) => TokenOwned::String(text.to_string()),
|
||||
Token::Str => TokenOwned::Str,
|
||||
Token::Struct => TokenOwned::Struct,
|
||||
Token::While => TokenOwned::While,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> TokenKind {
|
||||
match self {
|
||||
Token::ArrowThin => TokenKind::ArrowThin,
|
||||
Token::Async => TokenKind::Async,
|
||||
Token::BangEqual => TokenKind::BangEqual,
|
||||
Token::Bang => TokenKind::Bang,
|
||||
Token::Bool => TokenKind::Bool,
|
||||
Token::Boolean(_) => TokenKind::Boolean,
|
||||
Token::Break => TokenKind::Break,
|
||||
Token::Byte(_) => TokenKind::Byte,
|
||||
Token::Character(_) => TokenKind::Character,
|
||||
Token::Colon => TokenKind::Colon,
|
||||
Token::Comma => TokenKind::Comma,
|
||||
Token::Dot => TokenKind::Dot,
|
||||
Token::DoubleAmpersand => TokenKind::DoubleAmpersand,
|
||||
Token::DoubleDot => TokenKind::DoubleDot,
|
||||
Token::DoubleEqual => TokenKind::DoubleEqual,
|
||||
Token::DoublePipe => TokenKind::DoublePipe,
|
||||
Token::Else => TokenKind::Else,
|
||||
Token::Eof => TokenKind::Eof,
|
||||
Token::Equal => TokenKind::Equal,
|
||||
Token::Float(_) => TokenKind::Float,
|
||||
Token::FloatKeyword => TokenKind::FloatKeyword,
|
||||
Token::Fn => TokenKind::Fn,
|
||||
Token::Greater => TokenKind::Greater,
|
||||
Token::GreaterEqual => TokenKind::GreaterEqual,
|
||||
Token::Identifier(_) => TokenKind::Identifier,
|
||||
Token::If => TokenKind::If,
|
||||
Token::Int => TokenKind::Int,
|
||||
Token::Integer(_) => TokenKind::Integer,
|
||||
Token::LeftCurlyBrace => TokenKind::LeftCurlyBrace,
|
||||
Token::LeftParenthesis => TokenKind::LeftParenthesis,
|
||||
Token::LeftSquareBrace => TokenKind::LeftSquareBrace,
|
||||
Token::Let => TokenKind::Let,
|
||||
Token::Less => TokenKind::Less,
|
||||
Token::LessEqual => TokenKind::LessEqual,
|
||||
Token::Loop => TokenKind::Loop,
|
||||
Token::Map => TokenKind::Map,
|
||||
Token::Minus => TokenKind::Minus,
|
||||
Token::MinusEqual => TokenKind::MinusEqual,
|
||||
Token::Mut => TokenKind::Mut,
|
||||
Token::Percent => TokenKind::Percent,
|
||||
Token::PercentEqual => TokenKind::PercentEqual,
|
||||
Token::Plus => TokenKind::Plus,
|
||||
Token::PlusEqual => TokenKind::PlusEqual,
|
||||
Token::Return => TokenKind::Return,
|
||||
Token::RightCurlyBrace => TokenKind::RightCurlyBrace,
|
||||
Token::RightParenthesis => TokenKind::RightParenthesis,
|
||||
Token::RightSquareBrace => TokenKind::RightSquareBrace,
|
||||
Token::Semicolon => TokenKind::Semicolon,
|
||||
Token::Star => TokenKind::Star,
|
||||
Token::StarEqual => TokenKind::StarEqual,
|
||||
Token::Slash => TokenKind::Slash,
|
||||
Token::SlashEqual => TokenKind::SlashEqual,
|
||||
Token::Str => TokenKind::Str,
|
||||
Token::String(_) => TokenKind::String,
|
||||
Token::Struct => TokenKind::Struct,
|
||||
Token::While => TokenKind::While,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the token yields a value, begins an expression or is an expression operator.
|
||||
pub fn is_expression(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Token::Boolean(_)
|
||||
| Token::Byte(_)
|
||||
| Token::Character(_)
|
||||
| Token::Float(_)
|
||||
| Token::Identifier(_)
|
||||
| Token::Integer(_)
|
||||
| Token::String(_)
|
||||
| Token::Break
|
||||
| Token::If
|
||||
| Token::Return
|
||||
| Token::Map
|
||||
| Token::Loop
|
||||
| Token::Struct
|
||||
| Token::BangEqual
|
||||
| Token::DoubleAmpersand
|
||||
| Token::DoubleEqual
|
||||
| Token::DoublePipe
|
||||
| Token::Equal
|
||||
| Token::Greater
|
||||
| Token::GreaterEqual
|
||||
| Token::LeftCurlyBrace
|
||||
| Token::LeftParenthesis
|
||||
| Token::LeftSquareBrace
|
||||
| Token::Less
|
||||
| Token::LessEqual
|
||||
| Token::Minus
|
||||
| Token::MinusEqual
|
||||
| Token::Percent
|
||||
| Token::PercentEqual
|
||||
| Token::Plus
|
||||
| Token::PlusEqual
|
||||
| Token::Slash
|
||||
| Token::SlashEqual
|
||||
| Token::Star
|
||||
| Token::StarEqual
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> Display for Token<'src> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Token::ArrowThin => write!(f, "->"),
|
||||
Token::Async => write!(f, "async"),
|
||||
Token::BangEqual => write!(f, "!="),
|
||||
Token::Bang => write!(f, "!"),
|
||||
Token::Bool => write!(f, "bool"),
|
||||
Token::Boolean(value) => write!(f, "{value}"),
|
||||
Token::Break => write!(f, "break"),
|
||||
Token::Byte(value) => write!(f, "{value}"),
|
||||
Token::Character(value) => write!(f, "{value}"),
|
||||
Token::Colon => write!(f, ":"),
|
||||
Token::Comma => write!(f, ","),
|
||||
Token::Dot => write!(f, "."),
|
||||
Token::DoubleAmpersand => write!(f, "&&"),
|
||||
Token::DoubleDot => write!(f, ".."),
|
||||
Token::DoubleEqual => write!(f, "=="),
|
||||
Token::DoublePipe => write!(f, "||"),
|
||||
Token::Else => write!(f, "else"),
|
||||
Token::Eof => write!(f, "EOF"),
|
||||
Token::Equal => write!(f, "="),
|
||||
Token::Float(value) => write!(f, "{value}"),
|
||||
Token::FloatKeyword => write!(f, "float"),
|
||||
Token::Fn => write!(f, "fn"),
|
||||
Token::Greater => write!(f, ">"),
|
||||
Token::GreaterEqual => write!(f, ">="),
|
||||
Token::Identifier(value) => write!(f, "{value}"),
|
||||
Token::If => write!(f, "if"),
|
||||
Token::Int => write!(f, "int"),
|
||||
Token::Integer(value) => write!(f, "{value}"),
|
||||
Token::LeftCurlyBrace => write!(f, "{{"),
|
||||
Token::LeftParenthesis => write!(f, "("),
|
||||
Token::LeftSquareBrace => write!(f, "["),
|
||||
Token::Let => write!(f, "let"),
|
||||
Token::Less => write!(f, "<"),
|
||||
Token::LessEqual => write!(f, "<="),
|
||||
Token::Loop => write!(f, "loop"),
|
||||
Token::Map => write!(f, "map"),
|
||||
Token::Minus => write!(f, "-"),
|
||||
Token::MinusEqual => write!(f, "-="),
|
||||
Token::Mut => write!(f, "mut"),
|
||||
Token::Percent => write!(f, "%"),
|
||||
Token::PercentEqual => write!(f, "%="),
|
||||
Token::Plus => write!(f, "+"),
|
||||
Token::PlusEqual => write!(f, "+="),
|
||||
Token::Return => write!(f, "return"),
|
||||
Token::RightCurlyBrace => write!(f, "}}"),
|
||||
Token::RightParenthesis => write!(f, ")"),
|
||||
Token::RightSquareBrace => write!(f, "]"),
|
||||
Token::Semicolon => write!(f, ";"),
|
||||
Token::Slash => write!(f, "/"),
|
||||
Token::SlashEqual => write!(f, "/="),
|
||||
Token::Star => write!(f, "*"),
|
||||
Token::StarEqual => write!(f, "*="),
|
||||
Token::Str => write!(f, "str"),
|
||||
Token::String(value) => write!(f, "{value}"),
|
||||
Token::Struct => write!(f, "struct"),
|
||||
Token::While => write!(f, "while"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Owned representation of a source token.
|
||||
///
|
||||
/// If a [Token] borrows from the source text, its TokenOwned omits the data.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TokenOwned {
|
||||
Eof,
|
||||
|
||||
Identifier(String),
|
||||
|
||||
// Hard-coded values
|
||||
Boolean(String),
|
||||
Byte(String),
|
||||
Character(char),
|
||||
Float(String),
|
||||
Integer(String),
|
||||
String(String),
|
||||
|
||||
// Keywords
|
||||
Async,
|
||||
Bool,
|
||||
Break,
|
||||
Else,
|
||||
FloatKeyword,
|
||||
Fn,
|
||||
If,
|
||||
Int,
|
||||
Let,
|
||||
Loop,
|
||||
Map,
|
||||
Mut,
|
||||
Return,
|
||||
Str,
|
||||
While,
|
||||
|
||||
// Symbols
|
||||
ArrowThin,
|
||||
Bang,
|
||||
BangEqual,
|
||||
Colon,
|
||||
Comma,
|
||||
Dot,
|
||||
DoubleAmpersand,
|
||||
DoubleDot,
|
||||
DoubleEqual,
|
||||
DoublePipe,
|
||||
Equal,
|
||||
Greater,
|
||||
GreaterOrEqual,
|
||||
LeftCurlyBrace,
|
||||
LeftParenthesis,
|
||||
LeftSquareBrace,
|
||||
Less,
|
||||
LessOrEqual,
|
||||
Minus,
|
||||
MinusEqual,
|
||||
Percent,
|
||||
PercentEqual,
|
||||
Plus,
|
||||
PlusEqual,
|
||||
RightCurlyBrace,
|
||||
RightParenthesis,
|
||||
RightSquareBrace,
|
||||
Semicolon,
|
||||
Star,
|
||||
StarEqual,
|
||||
Struct,
|
||||
Slash,
|
||||
SlashEqual,
|
||||
}
|
||||
|
||||
impl Display for TokenOwned {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TokenOwned::ArrowThin => Token::ArrowThin.fmt(f),
|
||||
TokenOwned::Async => Token::Async.fmt(f),
|
||||
TokenOwned::Bang => Token::Bang.fmt(f),
|
||||
TokenOwned::BangEqual => Token::BangEqual.fmt(f),
|
||||
TokenOwned::Bool => Token::Bool.fmt(f),
|
||||
TokenOwned::Boolean(boolean) => Token::Boolean(boolean).fmt(f),
|
||||
TokenOwned::Break => Token::Break.fmt(f),
|
||||
TokenOwned::Byte(byte) => Token::Byte(byte).fmt(f),
|
||||
TokenOwned::Character(character) => Token::Character(*character).fmt(f),
|
||||
TokenOwned::Colon => Token::Colon.fmt(f),
|
||||
TokenOwned::Comma => Token::Comma.fmt(f),
|
||||
TokenOwned::Dot => Token::Dot.fmt(f),
|
||||
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
||||
TokenOwned::DoubleDot => Token::DoubleDot.fmt(f),
|
||||
TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f),
|
||||
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
|
||||
TokenOwned::Else => Token::Else.fmt(f),
|
||||
TokenOwned::Eof => Token::Eof.fmt(f),
|
||||
TokenOwned::Equal => Token::Equal.fmt(f),
|
||||
TokenOwned::Float(float) => Token::Float(float).fmt(f),
|
||||
TokenOwned::FloatKeyword => Token::FloatKeyword.fmt(f),
|
||||
TokenOwned::Fn => Token::Fn.fmt(f),
|
||||
TokenOwned::Greater => Token::Greater.fmt(f),
|
||||
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
|
||||
TokenOwned::Identifier(text) => Token::Identifier(text).fmt(f),
|
||||
TokenOwned::If => Token::If.fmt(f),
|
||||
TokenOwned::Int => Token::Int.fmt(f),
|
||||
TokenOwned::Integer(integer) => Token::Integer(integer).fmt(f),
|
||||
TokenOwned::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
|
||||
TokenOwned::LeftParenthesis => Token::LeftParenthesis.fmt(f),
|
||||
TokenOwned::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
|
||||
TokenOwned::Let => Token::Let.fmt(f),
|
||||
TokenOwned::Less => Token::Less.fmt(f),
|
||||
TokenOwned::LessOrEqual => Token::LessEqual.fmt(f),
|
||||
TokenOwned::Loop => Token::Loop.fmt(f),
|
||||
TokenOwned::Map => Token::Map.fmt(f),
|
||||
TokenOwned::Minus => Token::Minus.fmt(f),
|
||||
TokenOwned::MinusEqual => Token::MinusEqual.fmt(f),
|
||||
TokenOwned::Mut => Token::Mut.fmt(f),
|
||||
TokenOwned::Percent => Token::Percent.fmt(f),
|
||||
TokenOwned::PercentEqual => Token::PercentEqual.fmt(f),
|
||||
TokenOwned::Plus => Token::Plus.fmt(f),
|
||||
TokenOwned::PlusEqual => Token::PlusEqual.fmt(f),
|
||||
TokenOwned::Return => Token::Return.fmt(f),
|
||||
TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
||||
TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f),
|
||||
TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
||||
TokenOwned::Semicolon => Token::Semicolon.fmt(f),
|
||||
TokenOwned::Star => Token::Star.fmt(f),
|
||||
TokenOwned::StarEqual => Token::StarEqual.fmt(f),
|
||||
TokenOwned::Slash => Token::Slash.fmt(f),
|
||||
TokenOwned::SlashEqual => Token::SlashEqual.fmt(f),
|
||||
TokenOwned::Str => Token::Str.fmt(f),
|
||||
TokenOwned::String(string) => Token::String(string).fmt(f),
|
||||
TokenOwned::Struct => Token::Struct.fmt(f),
|
||||
TokenOwned::While => Token::While.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TokenKind {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
TokenKind::ArrowThin => Token::ArrowThin.fmt(f),
|
||||
TokenKind::Async => Token::Async.fmt(f),
|
||||
TokenKind::Bang => Token::Bang.fmt(f),
|
||||
TokenKind::BangEqual => Token::BangEqual.fmt(f),
|
||||
TokenKind::Bool => Token::Bool.fmt(f),
|
||||
TokenKind::Boolean => write!(f, "boolean value"),
|
||||
TokenKind::Break => Token::Break.fmt(f),
|
||||
TokenKind::Byte => write!(f, "byte value"),
|
||||
TokenKind::Character => write!(f, "character value"),
|
||||
TokenKind::Colon => Token::Colon.fmt(f),
|
||||
TokenKind::Comma => Token::Comma.fmt(f),
|
||||
TokenKind::Dot => Token::Dot.fmt(f),
|
||||
TokenKind::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
|
||||
TokenKind::DoubleDot => Token::DoubleDot.fmt(f),
|
||||
TokenKind::DoubleEqual => Token::DoubleEqual.fmt(f),
|
||||
TokenKind::DoublePipe => Token::DoublePipe.fmt(f),
|
||||
TokenKind::Else => Token::Else.fmt(f),
|
||||
TokenKind::Eof => Token::Eof.fmt(f),
|
||||
TokenKind::Equal => Token::Equal.fmt(f),
|
||||
TokenKind::Float => write!(f, "float value"),
|
||||
TokenKind::FloatKeyword => Token::FloatKeyword.fmt(f),
|
||||
TokenKind::Fn => Token::Fn.fmt(f),
|
||||
TokenKind::Greater => Token::Greater.fmt(f),
|
||||
TokenKind::GreaterEqual => Token::GreaterEqual.fmt(f),
|
||||
TokenKind::Identifier => write!(f, "identifier"),
|
||||
TokenKind::If => Token::If.fmt(f),
|
||||
TokenKind::Int => Token::Int.fmt(f),
|
||||
TokenKind::Integer => write!(f, "integer value"),
|
||||
TokenKind::LeftCurlyBrace => Token::LeftCurlyBrace.fmt(f),
|
||||
TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f),
|
||||
TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f),
|
||||
TokenKind::Let => Token::Let.fmt(f),
|
||||
TokenKind::Less => Token::Less.fmt(f),
|
||||
TokenKind::LessEqual => Token::LessEqual.fmt(f),
|
||||
TokenKind::Loop => Token::Loop.fmt(f),
|
||||
TokenKind::Map => Token::Map.fmt(f),
|
||||
TokenKind::Minus => Token::Minus.fmt(f),
|
||||
TokenKind::MinusEqual => Token::MinusEqual.fmt(f),
|
||||
TokenKind::Mut => Token::Mut.fmt(f),
|
||||
TokenKind::Percent => Token::Percent.fmt(f),
|
||||
TokenKind::PercentEqual => Token::PercentEqual.fmt(f),
|
||||
TokenKind::Plus => Token::Plus.fmt(f),
|
||||
TokenKind::PlusEqual => Token::PlusEqual.fmt(f),
|
||||
TokenKind::Return => Token::Return.fmt(f),
|
||||
TokenKind::RightCurlyBrace => Token::RightCurlyBrace.fmt(f),
|
||||
TokenKind::RightParenthesis => Token::RightParenthesis.fmt(f),
|
||||
TokenKind::RightSquareBrace => Token::RightSquareBrace.fmt(f),
|
||||
TokenKind::Semicolon => Token::Semicolon.fmt(f),
|
||||
TokenKind::Star => Token::Star.fmt(f),
|
||||
TokenKind::StarEqual => Token::StarEqual.fmt(f),
|
||||
TokenKind::Str => Token::Str.fmt(f),
|
||||
TokenKind::Slash => Token::Slash.fmt(f),
|
||||
TokenKind::SlashEqual => Token::SlashEqual.fmt(f),
|
||||
TokenKind::String => write!(f, "string value"),
|
||||
TokenKind::Struct => Token::Struct.fmt(f),
|
||||
TokenKind::While => Token::While.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
659
dust-lang/src/type.rs
Normal file
659
dust-lang/src/type.rs
Normal file
@ -0,0 +1,659 @@
|
||||
//! Value types and conflict handling.
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
fmt::{self, Display, Formatter},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Description of a kind of value.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Type {
|
||||
Any,
|
||||
Boolean,
|
||||
Byte,
|
||||
Character,
|
||||
Enum(EnumType),
|
||||
Float,
|
||||
Function(FunctionType),
|
||||
Generic {
|
||||
identifier_index: u8,
|
||||
concrete_type: Option<Box<Type>>,
|
||||
},
|
||||
Integer,
|
||||
List {
|
||||
item_type: Box<Type>,
|
||||
length: usize,
|
||||
},
|
||||
ListEmpty,
|
||||
ListOf {
|
||||
item_type: Box<Type>,
|
||||
},
|
||||
Map {
|
||||
pairs: HashMap<u8, Type>,
|
||||
},
|
||||
Number,
|
||||
Range {
|
||||
r#type: RangeableType,
|
||||
},
|
||||
String {
|
||||
length: Option<usize>,
|
||||
},
|
||||
Struct(StructType),
|
||||
Tuple {
|
||||
fields: Option<Vec<Type>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Type {
|
||||
/// Returns a concrete type, either the type itself or the concrete type of a generic type.
|
||||
pub fn concrete_type(&self) -> &Type {
|
||||
if let Type::Generic {
|
||||
concrete_type: Some(concrete_type),
|
||||
..
|
||||
} = self
|
||||
{
|
||||
concrete_type.concrete_type()
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the type is compatible with another type.
|
||||
pub fn check(&self, other: &Type) -> Result<(), TypeConflict> {
|
||||
match (self.concrete_type(), other.concrete_type()) {
|
||||
(Type::Any, _)
|
||||
| (_, Type::Any)
|
||||
| (Type::Boolean, Type::Boolean)
|
||||
| (Type::Byte, Type::Byte)
|
||||
| (Type::Character, Type::Character)
|
||||
| (Type::Float, Type::Float)
|
||||
| (Type::Integer, Type::Integer)
|
||||
| (Type::String { .. }, Type::String { .. }) => return Ok(()),
|
||||
(
|
||||
Type::Generic {
|
||||
concrete_type: left,
|
||||
..
|
||||
},
|
||||
Type::Generic {
|
||||
concrete_type: right,
|
||||
..
|
||||
},
|
||||
) => match (left, right) {
|
||||
(Some(left), Some(right)) => {
|
||||
if left.check(right).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
(None, None) => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
(Type::Generic { concrete_type, .. }, other)
|
||||
| (other, Type::Generic { concrete_type, .. }) => {
|
||||
if let Some(concrete_type) = concrete_type {
|
||||
if other == concrete_type.as_ref() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
(Type::Struct(left_struct_type), Type::Struct(right_struct_type)) => {
|
||||
if left_struct_type == right_struct_type {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
(
|
||||
Type::List {
|
||||
item_type: left_type,
|
||||
length: left_length,
|
||||
},
|
||||
Type::List {
|
||||
item_type: right_type,
|
||||
length: right_length,
|
||||
},
|
||||
) => {
|
||||
if left_length != right_length {
|
||||
return Err(TypeConflict {
|
||||
actual: other.clone(),
|
||||
expected: self.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if left_type.check(right_type).is_err() {
|
||||
return Err(TypeConflict {
|
||||
actual: other.clone(),
|
||||
expected: self.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
(
|
||||
Type::ListOf {
|
||||
item_type: left_type,
|
||||
},
|
||||
Type::ListOf {
|
||||
item_type: right_type,
|
||||
},
|
||||
) => {
|
||||
if left_type.check(right_type).is_err() {
|
||||
return Err(TypeConflict {
|
||||
actual: other.clone(),
|
||||
expected: self.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
Type::List {
|
||||
item_type: list_item_type,
|
||||
..
|
||||
},
|
||||
Type::ListOf {
|
||||
item_type: list_of_item_type,
|
||||
},
|
||||
)
|
||||
| (
|
||||
Type::ListOf {
|
||||
item_type: list_of_item_type,
|
||||
},
|
||||
Type::List {
|
||||
item_type: list_item_type,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
// TODO: This is a hack, remove it.
|
||||
if let Type::Any = **list_of_item_type {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if list_item_type.check(list_of_item_type).is_err() {
|
||||
return Err(TypeConflict {
|
||||
actual: other.clone(),
|
||||
expected: self.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
(
|
||||
Type::Function(FunctionType {
|
||||
type_parameters: left_type_parameters,
|
||||
value_parameters: left_value_parameters,
|
||||
return_type: left_return,
|
||||
}),
|
||||
Type::Function(FunctionType {
|
||||
type_parameters: right_type_parameters,
|
||||
value_parameters: right_value_parameters,
|
||||
return_type: right_return,
|
||||
}),
|
||||
) => {
|
||||
if left_return != right_return
|
||||
|| left_type_parameters != right_type_parameters
|
||||
|| left_value_parameters != right_value_parameters
|
||||
{
|
||||
return Err(TypeConflict {
|
||||
actual: other.clone(),
|
||||
expected: self.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
|
||||
if left_type == right_type {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
(Type::Number, Type::Number | Type::Integer | Type::Float)
|
||||
| (Type::Integer | Type::Float, Type::Number) => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Err(TypeConflict {
|
||||
actual: other.clone(),
|
||||
expected: self.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Type::Any => write!(f, "any"),
|
||||
Type::Boolean => write!(f, "bool"),
|
||||
Type::Byte => write!(f, "byte"),
|
||||
Type::Character => write!(f, "char"),
|
||||
Type::Enum(EnumType { name, .. }) => write!(f, "{name}"),
|
||||
Type::Float => write!(f, "float"),
|
||||
Type::Function(function_type) => write!(f, "{function_type}"),
|
||||
Type::Generic { concrete_type, .. } => {
|
||||
match concrete_type.clone().map(|r#box| *r#box) {
|
||||
Some(Type::Generic {
|
||||
identifier_index: identifier,
|
||||
..
|
||||
}) => write!(f, "{identifier}"),
|
||||
Some(concrete_type) => write!(f, "implied to be {concrete_type}"),
|
||||
None => write!(f, "unknown"),
|
||||
}
|
||||
}
|
||||
Type::Integer => write!(f, "int"),
|
||||
Type::List { item_type, length } => write!(f, "[{item_type}; {length}]"),
|
||||
Type::ListEmpty => write!(f, "[]"),
|
||||
Type::ListOf { item_type } => write!(f, "[{item_type}]"),
|
||||
Type::Map { pairs } => {
|
||||
write!(f, "map ")?;
|
||||
|
||||
write!(f, "{{")?;
|
||||
|
||||
for (index, (key, value)) in pairs.iter().enumerate() {
|
||||
write!(f, "{key}: {value}")?;
|
||||
|
||||
if index != pairs.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, "}}")
|
||||
}
|
||||
Type::Number => write!(f, "num"),
|
||||
Type::Range { r#type } => write!(f, "{type} range"),
|
||||
Type::String { .. } => write!(f, "str"),
|
||||
Type::Struct(struct_type) => write!(f, "{struct_type}"),
|
||||
Type::Tuple { fields } => {
|
||||
if let Some(fields) = fields {
|
||||
write!(f, "(")?;
|
||||
|
||||
for (index, r#type) in fields.iter().enumerate() {
|
||||
write!(f, "{type}")?;
|
||||
|
||||
if index != fields.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
} else {
|
||||
write!(f, "tuple")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Type {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Type {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(Type::Any, Type::Any) => Ordering::Equal,
|
||||
(Type::Any, _) => Ordering::Greater,
|
||||
(Type::Boolean, Type::Boolean) => Ordering::Equal,
|
||||
(Type::Boolean, _) => Ordering::Greater,
|
||||
(Type::Byte, Type::Byte) => Ordering::Equal,
|
||||
(Type::Byte, _) => Ordering::Greater,
|
||||
(Type::Character, Type::Character) => Ordering::Equal,
|
||||
(Type::Character, _) => Ordering::Greater,
|
||||
(Type::Enum(left_enum), Type::Enum(right_enum)) => left_enum.cmp(right_enum),
|
||||
(Type::Enum(_), _) => Ordering::Greater,
|
||||
(Type::Float, Type::Float) => Ordering::Equal,
|
||||
(Type::Float, _) => Ordering::Greater,
|
||||
(Type::Function(left_function), Type::Function(right_function)) => {
|
||||
left_function.cmp(right_function)
|
||||
}
|
||||
(Type::Function(_), _) => Ordering::Greater,
|
||||
(Type::Generic { .. }, Type::Generic { .. }) => Ordering::Equal,
|
||||
(Type::Generic { .. }, _) => Ordering::Greater,
|
||||
(Type::Integer, Type::Integer) => Ordering::Equal,
|
||||
(Type::Integer, _) => Ordering::Greater,
|
||||
(
|
||||
Type::List {
|
||||
item_type: left_item_type,
|
||||
length: left_length,
|
||||
},
|
||||
Type::List {
|
||||
item_type: right_item_type,
|
||||
length: right_length,
|
||||
},
|
||||
) => {
|
||||
if left_length == right_length {
|
||||
left_item_type.cmp(right_item_type)
|
||||
} else {
|
||||
left_length.cmp(right_length)
|
||||
}
|
||||
}
|
||||
(Type::List { .. }, _) => Ordering::Greater,
|
||||
(Type::ListEmpty, Type::ListEmpty) => Ordering::Equal,
|
||||
(Type::ListEmpty, _) => Ordering::Greater,
|
||||
(
|
||||
Type::ListOf {
|
||||
item_type: left_item_type,
|
||||
},
|
||||
Type::ListOf {
|
||||
item_type: right_item_type,
|
||||
},
|
||||
) => left_item_type.cmp(right_item_type),
|
||||
(Type::ListOf { .. }, _) => Ordering::Greater,
|
||||
(Type::Map { pairs: left_pairs }, Type::Map { pairs: right_pairs }) => {
|
||||
left_pairs.iter().cmp(right_pairs.iter())
|
||||
}
|
||||
(Type::Map { .. }, _) => Ordering::Greater,
|
||||
(Type::Number, Type::Number) => Ordering::Equal,
|
||||
(Type::Number, _) => Ordering::Greater,
|
||||
(Type::Range { r#type: left_type }, Type::Range { r#type: right_type }) => {
|
||||
left_type.cmp(right_type)
|
||||
}
|
||||
(Type::Range { .. }, _) => Ordering::Greater,
|
||||
(Type::String { length: left }, Type::String { length: right }) => left.cmp(right),
|
||||
(Type::String { .. }, _) => Ordering::Greater,
|
||||
(Type::Struct(left_struct), Type::Struct(right_struct)) => {
|
||||
left_struct.cmp(right_struct)
|
||||
}
|
||||
(Type::Struct(_), _) => Ordering::Greater,
|
||||
|
||||
(Type::Tuple { fields: left }, Type::Tuple { fields: right }) => left.cmp(right),
|
||||
(Type::Tuple { .. }, _) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct FunctionType {
|
||||
pub type_parameters: Option<Vec<u8>>,
|
||||
pub value_parameters: Option<Vec<(u8, Type)>>,
|
||||
pub return_type: Option<Box<Type>>,
|
||||
}
|
||||
|
||||
impl Display for FunctionType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "fn ")?;
|
||||
|
||||
if let Some(type_parameters) = &self.type_parameters {
|
||||
write!(f, "<")?;
|
||||
|
||||
for (index, type_parameter) in type_parameters.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{type_parameter}")?;
|
||||
}
|
||||
|
||||
write!(f, ">")?;
|
||||
}
|
||||
|
||||
write!(f, "(")?;
|
||||
|
||||
if let Some(value_parameters) = &self.value_parameters {
|
||||
for (index, (identifier, r#type)) in value_parameters.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{identifier}: {type}")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, ")")?;
|
||||
|
||||
if let Some(return_type) = &self.return_type {
|
||||
write!(f, " -> {return_type}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum StructType {
|
||||
Unit { name: u8 },
|
||||
Tuple { name: u8, fields: Vec<Type> },
|
||||
Fields { name: u8, fields: HashMap<u8, Type> },
|
||||
}
|
||||
|
||||
impl StructType {
|
||||
pub fn name(&self) -> u8 {
|
||||
match self {
|
||||
StructType::Unit { name } => *name,
|
||||
StructType::Tuple { name, .. } => *name,
|
||||
StructType::Fields { name, .. } => *name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StructType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
StructType::Unit { name } => write!(f, "{name}"),
|
||||
StructType::Tuple { name, fields } => {
|
||||
write!(f, "{name}(")?;
|
||||
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
write!(f, "{field}")?;
|
||||
|
||||
if index != fields.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
StructType::Fields { name, fields } => {
|
||||
write!(f, "{name} {{")?;
|
||||
|
||||
for (index, (identifier, r#type)) in fields.iter().enumerate() {
|
||||
write!(f, "{identifier}: {type}")?;
|
||||
|
||||
if index != fields.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for StructType {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for StructType {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(StructType::Unit { name: left_name }, StructType::Unit { name: right_name }) => {
|
||||
left_name.cmp(right_name)
|
||||
}
|
||||
(StructType::Unit { .. }, _) => Ordering::Greater,
|
||||
(
|
||||
StructType::Tuple {
|
||||
name: left_name,
|
||||
fields: left_fields,
|
||||
},
|
||||
StructType::Tuple {
|
||||
name: right_name,
|
||||
fields: right_fields,
|
||||
},
|
||||
) => {
|
||||
let name_cmp = left_name.cmp(right_name);
|
||||
|
||||
if name_cmp == Ordering::Equal {
|
||||
left_fields.cmp(right_fields)
|
||||
} else {
|
||||
name_cmp
|
||||
}
|
||||
}
|
||||
(StructType::Tuple { .. }, _) => Ordering::Greater,
|
||||
(
|
||||
StructType::Fields {
|
||||
name: left_name,
|
||||
fields: left_fields,
|
||||
},
|
||||
StructType::Fields {
|
||||
name: right_name,
|
||||
fields: right_fields,
|
||||
},
|
||||
) => {
|
||||
let name_cmp = left_name.cmp(right_name);
|
||||
|
||||
if name_cmp == Ordering::Equal {
|
||||
let len_cmp = left_fields.len().cmp(&right_fields.len());
|
||||
|
||||
if len_cmp == Ordering::Equal {
|
||||
left_fields.iter().cmp(right_fields.iter())
|
||||
} else {
|
||||
len_cmp
|
||||
}
|
||||
} else {
|
||||
name_cmp
|
||||
}
|
||||
}
|
||||
(StructType::Fields { .. }, _) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct EnumType {
|
||||
pub name: u8,
|
||||
pub variants: Vec<StructType>,
|
||||
}
|
||||
|
||||
impl Display for EnumType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let EnumType { name, variants } = self;
|
||||
|
||||
write!(f, "enum {name} {{ ")?;
|
||||
|
||||
for (index, variant) in variants.iter().enumerate() {
|
||||
write!(f, "{variant}")?;
|
||||
|
||||
if index != self.variants.len() - 1 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, " }}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum RangeableType {
|
||||
Byte,
|
||||
Character,
|
||||
Float,
|
||||
Integer,
|
||||
}
|
||||
|
||||
impl Display for RangeableType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RangeableType::Byte => Type::Byte.fmt(f),
|
||||
RangeableType::Character => Type::Character.fmt(f),
|
||||
RangeableType::Float => Type::Float.fmt(f),
|
||||
RangeableType::Integer => Type::Integer.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TypeConflict {
|
||||
pub expected: Type,
|
||||
pub actual: Type,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_type_any() {
|
||||
let foo = Type::Any;
|
||||
let bar = Type::Any;
|
||||
|
||||
foo.check(&bar).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_type_boolean() {
|
||||
let foo = Type::Boolean;
|
||||
let bar = Type::Boolean;
|
||||
|
||||
foo.check(&bar).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_type_byte() {
|
||||
let foo = Type::Byte;
|
||||
let bar = Type::Byte;
|
||||
|
||||
foo.check(&bar).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_type_character() {
|
||||
let foo = Type::Character;
|
||||
let bar = Type::Character;
|
||||
|
||||
foo.check(&bar).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors() {
|
||||
let foo = Type::Integer;
|
||||
let bar = Type::String { length: None };
|
||||
|
||||
assert_eq!(
|
||||
foo.check(&bar),
|
||||
Err(TypeConflict {
|
||||
actual: bar.clone(),
|
||||
expected: foo.clone()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
bar.check(&foo),
|
||||
Err(TypeConflict {
|
||||
actual: foo.clone(),
|
||||
expected: bar.clone()
|
||||
})
|
||||
);
|
||||
|
||||
let types = [
|
||||
Type::Boolean,
|
||||
Type::Float,
|
||||
Type::Integer,
|
||||
Type::List {
|
||||
item_type: Box::new(Type::Integer),
|
||||
length: 42,
|
||||
},
|
||||
Type::Range {
|
||||
r#type: RangeableType::Integer,
|
||||
},
|
||||
Type::String { length: None },
|
||||
];
|
||||
|
||||
for left in types.clone() {
|
||||
for right in types.clone() {
|
||||
if left == right {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
left.check(&right),
|
||||
Err(TypeConflict {
|
||||
actual: right.clone(),
|
||||
expected: left.clone()
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
767
dust-lang/src/value.rs
Normal file
767
dust-lang/src/value.rs
Normal file
@ -0,0 +1,767 @@
|
||||
//! Dust value representation
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Each type of value has a corresponding method for instantiation:
|
||||
//!
|
||||
//! ```
|
||||
//! # use dust_lang::Value;
|
||||
//! let boolean = Value::boolean(true);
|
||||
//! let float = Value::float(3.14);
|
||||
//! let integer = Value::integer(42);
|
||||
//! let string = Value::string("Hello, world!");
|
||||
//! ```
|
||||
//!
|
||||
//! Values have a type, which can be retrieved using the `r#type` method:
|
||||
//!
|
||||
//! ```
|
||||
//! # use dust_lang::*;
|
||||
//! let value = Value::integer(42);
|
||||
//!
|
||||
//! assert_eq!(value.r#type(), Type::Integer);
|
||||
//! ```
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
ops::{Range, RangeInclusive},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Chunk, FunctionType, RangeableType, Type};
|
||||
|
||||
/// Dust value representation
|
||||
///
|
||||
/// See the [module-level documentation][self] for more.
|
||||
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Value {
|
||||
Concrete(ConcreteValue),
|
||||
Abstract(AbstractValue),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn boolean(value: bool) -> Self {
|
||||
Value::Concrete(ConcreteValue::Boolean(value))
|
||||
}
|
||||
|
||||
pub fn byte(value: u8) -> Self {
|
||||
Value::Concrete(ConcreteValue::Byte(value))
|
||||
}
|
||||
|
||||
pub fn character(value: char) -> Self {
|
||||
Value::Concrete(ConcreteValue::Character(value))
|
||||
}
|
||||
|
||||
pub fn float(value: f64) -> Self {
|
||||
Value::Concrete(ConcreteValue::Float(value))
|
||||
}
|
||||
|
||||
pub fn function(body: Chunk, r#type: FunctionType) -> Self {
|
||||
Value::Concrete(ConcreteValue::Function(Function {
|
||||
chunk: body,
|
||||
r#type: Type::Function(r#type),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn integer<T: Into<i64>>(into_i64: T) -> Self {
|
||||
Value::Concrete(ConcreteValue::Integer(into_i64.into()))
|
||||
}
|
||||
|
||||
pub fn list<T: Into<Vec<Value>>>(items: T) -> Self {
|
||||
Value::Concrete(ConcreteValue::List(items.into()))
|
||||
}
|
||||
|
||||
pub fn abstract_list(start: u8, end: u8, item_type: Type) -> Self {
|
||||
Value::Abstract(AbstractValue::List {
|
||||
start,
|
||||
end,
|
||||
item_type,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn string<T: ToString>(to_string: T) -> Self {
|
||||
Value::Concrete(ConcreteValue::String(to_string.to_string()))
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Option<&String> {
|
||||
if let Value::Concrete(ConcreteValue::String(string)) = self {
|
||||
Some(string)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_function(&self) -> bool {
|
||||
matches!(self, Value::Concrete(ConcreteValue::Function(_)))
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Value::Concrete(data) => data.r#type(),
|
||||
Value::Abstract(AbstractValue::List {
|
||||
start,
|
||||
end,
|
||||
item_type,
|
||||
}) => {
|
||||
let length = (end - start + 1) as usize;
|
||||
|
||||
Type::List {
|
||||
length,
|
||||
item_type: Box::new(item_type.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
use Value::*;
|
||||
|
||||
let sum = match (self, other) {
|
||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
||||
Value::byte(left.saturating_add(*right))
|
||||
}
|
||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left + right),
|
||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
||||
Value::integer(left.saturating_add(*right))
|
||||
}
|
||||
(Concrete(String(left)), Concrete(String(right))) => {
|
||||
Value::string(format!("{}{}", left, right))
|
||||
}
|
||||
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(sum)
|
||||
}
|
||||
|
||||
pub fn subtract(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
use Value::*;
|
||||
|
||||
let different = match (self, other) {
|
||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
||||
Value::byte(left.saturating_sub(*right))
|
||||
}
|
||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left - right),
|
||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
||||
Value::integer(left.saturating_sub(*right))
|
||||
}
|
||||
_ => return Err(ValueError::CannotSubtract(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(different)
|
||||
}
|
||||
|
||||
pub fn multiply(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
use Value::*;
|
||||
|
||||
let product = match (self, other) {
|
||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
||||
Value::byte(left.saturating_mul(*right))
|
||||
}
|
||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left * right),
|
||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
||||
Value::integer(left.saturating_mul(*right))
|
||||
}
|
||||
_ => return Err(ValueError::CannotAdd(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(product)
|
||||
}
|
||||
|
||||
pub fn divide(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
use Value::*;
|
||||
|
||||
let product = match (self, other) {
|
||||
(Concrete(Byte(left)), Concrete(Byte(right))) => {
|
||||
Value::byte(left.saturating_div(*right))
|
||||
}
|
||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left / right),
|
||||
(Concrete(Integer(left)), Concrete(Integer(right))) => {
|
||||
Value::integer(left.saturating_div(*right))
|
||||
}
|
||||
_ => return Err(ValueError::CannotDivide(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(product)
|
||||
}
|
||||
|
||||
pub fn modulo(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
use Value::*;
|
||||
|
||||
let product = match (self, other) {
|
||||
(Concrete(Byte(left)), Concrete(Byte(right))) => Value::byte(left % right),
|
||||
(Concrete(Float(left)), Concrete(Float(right))) => Value::float(left % right),
|
||||
(Concrete(Integer(left)), Concrete(Integer(right))) => Value::integer(left % right),
|
||||
_ => return Err(ValueError::CannotModulo(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(product)
|
||||
}
|
||||
|
||||
pub fn less_than(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
let (left, right) = match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
|
||||
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(Value::boolean(left < right))
|
||||
}
|
||||
|
||||
pub fn less_than_or_equal(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
let (left, right) = match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
|
||||
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(Value::boolean(left <= right))
|
||||
}
|
||||
|
||||
pub fn equal(&self, other: &Value) -> Result<Value, ValueError> {
|
||||
let (left, right) = match (self, other) {
|
||||
(Value::Concrete(left), Value::Concrete(right)) => (left, right),
|
||||
_ => return Err(ValueError::CannotCompare(self.clone(), other.clone())),
|
||||
};
|
||||
|
||||
Ok(Value::boolean(left == right))
|
||||
}
|
||||
|
||||
pub fn negate(&self) -> Result<Value, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
use Value::*;
|
||||
|
||||
let negated = match self {
|
||||
Concrete(Integer(integer)) => Value::integer(-integer),
|
||||
Concrete(Float(float)) => Value::float(-float),
|
||||
_ => return Err(ValueError::CannotNot(self.clone())),
|
||||
};
|
||||
|
||||
Ok(negated)
|
||||
}
|
||||
|
||||
pub fn not(&self) -> Result<Value, ValueError> {
|
||||
use ConcreteValue::*;
|
||||
use Value::*;
|
||||
|
||||
let not = match self {
|
||||
Concrete(Boolean(boolean)) => Value::boolean(!boolean),
|
||||
Concrete(Byte(byte)) => Value::byte(!byte),
|
||||
Concrete(Integer(integer)) => Value::integer(!integer),
|
||||
_ => return Err(ValueError::CannotNot(self.clone())),
|
||||
};
|
||||
|
||||
Ok(not)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Value {
|
||||
fn from(value: bool) -> Self {
|
||||
Value::boolean(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Value {
|
||||
fn from(value: u8) -> Self {
|
||||
Value::byte(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<char> for Value {
|
||||
fn from(value: char) -> Self {
|
||||
Value::character(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Value {
|
||||
fn from(value: f64) -> Self {
|
||||
Value::float(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Value {
|
||||
fn from(value: i32) -> Self {
|
||||
Value::integer(value as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Value {
|
||||
fn from(value: i64) -> Self {
|
||||
Value::integer(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Value {
|
||||
fn from(value: String) -> Self {
|
||||
Value::string(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Value {
|
||||
fn from(str: &str) -> Self {
|
||||
Value::string(str)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Value {
|
||||
fn clone(&self) -> Self {
|
||||
log::trace!("Cloning value {self}");
|
||||
|
||||
match self {
|
||||
Value::Abstract(object) => Value::Abstract(object.clone()),
|
||||
Value::Concrete(concrete) => Value::Concrete(concrete.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Value::Abstract(object) => write!(f, "{object}"),
|
||||
Value::Concrete(concrete) => write!(f, "{concrete}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Value representation that can be resolved to a concrete value by the VM.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum AbstractValue {
|
||||
List { start: u8, end: u8, item_type: Type },
|
||||
}
|
||||
|
||||
impl Display for AbstractValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
AbstractValue::List { start, end, .. } => {
|
||||
write!(f, "List [R{}..=R{}]", start, end)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub enum ConcreteValue {
|
||||
Boolean(bool),
|
||||
Byte(u8),
|
||||
Character(char),
|
||||
Float(f64),
|
||||
Function(Function),
|
||||
Integer(i64),
|
||||
List(Vec<Value>),
|
||||
Range(RangeValue),
|
||||
String(String),
|
||||
}
|
||||
|
||||
impl ConcreteValue {
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
ConcreteValue::Boolean(_) => Type::Boolean,
|
||||
ConcreteValue::Byte(_) => Type::Byte,
|
||||
ConcreteValue::Character(_) => Type::Character,
|
||||
ConcreteValue::Float(_) => Type::Float,
|
||||
ConcreteValue::Function(Function { r#type, .. }) => r#type.clone(),
|
||||
ConcreteValue::Integer(_) => Type::Integer,
|
||||
ConcreteValue::List(list) => Type::List {
|
||||
item_type: list
|
||||
.first()
|
||||
.map(|value| Box::new(value.r#type()))
|
||||
.unwrap_or_else(|| Box::new(Type::Any)),
|
||||
length: list.len(),
|
||||
},
|
||||
ConcreteValue::Range(range) => range.r#type(),
|
||||
ConcreteValue::String(string) => Type::String {
|
||||
length: Some(string.len()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_rangeable(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ConcreteValue::Integer(_)
|
||||
| ConcreteValue::Float(_)
|
||||
| ConcreteValue::Character(_)
|
||||
| ConcreteValue::Byte(_)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ConcreteValue {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ConcreteValue::Boolean(boolean) => write!(f, "{boolean}"),
|
||||
ConcreteValue::Byte(byte) => write!(f, "0x{byte:02x}"),
|
||||
ConcreteValue::Character(character) => write!(f, "{character}"),
|
||||
ConcreteValue::Float(float) => {
|
||||
write!(f, "{float}")?;
|
||||
|
||||
if float.fract() == 0.0 {
|
||||
write!(f, ".0")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
ConcreteValue::Function(Function { r#type, .. }) => {
|
||||
write!(f, "{}", r#type)
|
||||
}
|
||||
ConcreteValue::Integer(integer) => write!(f, "{integer}"),
|
||||
ConcreteValue::List(items) => {
|
||||
write!(f, "[")?;
|
||||
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
if index > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
|
||||
write!(f, "{item}")?;
|
||||
}
|
||||
|
||||
write!(f, "]")
|
||||
}
|
||||
ConcreteValue::Range(range_value) => {
|
||||
write!(f, "{range_value}")
|
||||
}
|
||||
ConcreteValue::String(string) => write!(f, "{string}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ConcreteValue {}
|
||||
|
||||
impl PartialOrd for ConcreteValue {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ConcreteValue {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(ConcreteValue::Boolean(left), ConcreteValue::Boolean(right)) => left.cmp(right),
|
||||
(ConcreteValue::Boolean(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::Byte(left), ConcreteValue::Byte(right)) => left.cmp(right),
|
||||
(ConcreteValue::Byte(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::Character(left), ConcreteValue::Character(right)) => left.cmp(right),
|
||||
(ConcreteValue::Character(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::Float(left), ConcreteValue::Float(right)) => {
|
||||
if left.is_nan() && right.is_nan() {
|
||||
Ordering::Equal
|
||||
} else if left.is_nan() {
|
||||
Ordering::Less
|
||||
} else if right.is_nan() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
left.partial_cmp(right).unwrap()
|
||||
}
|
||||
}
|
||||
(ConcreteValue::Float(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::Function(left), ConcreteValue::Function(right)) => left.cmp(right),
|
||||
(ConcreteValue::Function(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::Integer(left), ConcreteValue::Integer(right)) => left.cmp(right),
|
||||
(ConcreteValue::Integer(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::List(left), ConcreteValue::List(right)) => left.cmp(right),
|
||||
(ConcreteValue::List(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::Range(left), ConcreteValue::Range(right)) => left.cmp(right),
|
||||
(ConcreteValue::Range(_), _) => Ordering::Greater,
|
||||
(ConcreteValue::String(left), ConcreteValue::String(right)) => left.cmp(right),
|
||||
(ConcreteValue::String(_), _) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Function {
|
||||
chunk: Chunk,
|
||||
r#type: Type,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn new(chunk: Chunk, r#type: Type) -> Self {
|
||||
Self { chunk, r#type }
|
||||
}
|
||||
|
||||
pub fn chunk(&self) -> &Chunk {
|
||||
&self.chunk
|
||||
}
|
||||
|
||||
pub fn chunk_mut(&mut self) -> &mut Chunk {
|
||||
&mut self.chunk
|
||||
}
|
||||
|
||||
pub fn take_chunk(self) -> Chunk {
|
||||
self.chunk
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> &Type {
|
||||
&self.r#type
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Function {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.r#type)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum RangeValue {
|
||||
ByteRange(Range<u8>),
|
||||
ByteRangeInclusive(RangeInclusive<u8>),
|
||||
CharacterRange(Range<char>),
|
||||
CharacterRangeInclusive(RangeInclusive<char>),
|
||||
FloatRange(Range<f64>),
|
||||
FloatRangeInclusive(RangeInclusive<f64>),
|
||||
IntegerRange(Range<i64>),
|
||||
IntegerRangeInclusive(RangeInclusive<i64>),
|
||||
}
|
||||
|
||||
impl RangeValue {
|
||||
pub fn r#type(&self) -> Type {
|
||||
let inner_type = match self {
|
||||
RangeValue::ByteRange(_) => RangeableType::Byte,
|
||||
RangeValue::ByteRangeInclusive(_) => RangeableType::Byte,
|
||||
RangeValue::CharacterRange(_) => RangeableType::Character,
|
||||
RangeValue::CharacterRangeInclusive(_) => RangeableType::Character,
|
||||
RangeValue::FloatRange(_) => RangeableType::Float,
|
||||
RangeValue::FloatRangeInclusive(_) => RangeableType::Float,
|
||||
RangeValue::IntegerRange(_) => RangeableType::Integer,
|
||||
RangeValue::IntegerRangeInclusive(_) => RangeableType::Integer,
|
||||
};
|
||||
|
||||
Type::Range { r#type: inner_type }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<u8>> for RangeValue {
|
||||
fn from(range: Range<u8>) -> Self {
|
||||
RangeValue::ByteRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<u8>> for RangeValue {
|
||||
fn from(range: RangeInclusive<u8>) -> Self {
|
||||
RangeValue::ByteRangeInclusive(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<char>> for RangeValue {
|
||||
fn from(range: Range<char>) -> Self {
|
||||
RangeValue::CharacterRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<char>> for RangeValue {
|
||||
fn from(range: RangeInclusive<char>) -> Self {
|
||||
RangeValue::CharacterRangeInclusive(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<f64>> for RangeValue {
|
||||
fn from(range: Range<f64>) -> Self {
|
||||
RangeValue::FloatRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<f64>> for RangeValue {
|
||||
fn from(range: RangeInclusive<f64>) -> Self {
|
||||
RangeValue::FloatRangeInclusive(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<i32>> for RangeValue {
|
||||
fn from(range: Range<i32>) -> Self {
|
||||
RangeValue::IntegerRange(range.start as i64..range.end as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<i32>> for RangeValue {
|
||||
fn from(range: RangeInclusive<i32>) -> Self {
|
||||
RangeValue::IntegerRangeInclusive(*range.start() as i64..=*range.end() as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Range<i64>> for RangeValue {
|
||||
fn from(range: Range<i64>) -> Self {
|
||||
RangeValue::IntegerRange(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RangeInclusive<i64>> for RangeValue {
|
||||
fn from(range: RangeInclusive<i64>) -> Self {
|
||||
RangeValue::IntegerRangeInclusive(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RangeValue {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
RangeValue::ByteRange(range) => write!(f, "{}..{}", range.start, range.end),
|
||||
RangeValue::ByteRangeInclusive(range) => {
|
||||
write!(f, "{}..={}", range.start(), range.end())
|
||||
}
|
||||
RangeValue::CharacterRange(range) => write!(f, "{}..{}", range.start, range.end),
|
||||
RangeValue::CharacterRangeInclusive(range) => {
|
||||
write!(f, "{}..={}", range.start(), range.end())
|
||||
}
|
||||
RangeValue::FloatRange(range) => write!(f, "{}..{}", range.start, range.end),
|
||||
RangeValue::FloatRangeInclusive(range) => {
|
||||
write!(f, "{}..={}", range.start(), range.end())
|
||||
}
|
||||
RangeValue::IntegerRange(range) => write!(f, "{}..{}", range.start, range.end),
|
||||
RangeValue::IntegerRangeInclusive(range) => {
|
||||
write!(f, "{}..={}", range.start(), range.end())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for RangeValue {}
|
||||
|
||||
impl PartialOrd for RangeValue {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for RangeValue {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self, other) {
|
||||
(RangeValue::ByteRange(left), RangeValue::ByteRange(right)) => {
|
||||
let start_cmp = left.start.cmp(&right.start);
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end.cmp(&right.end)
|
||||
}
|
||||
}
|
||||
(RangeValue::ByteRange(_), _) => Ordering::Greater,
|
||||
(RangeValue::ByteRangeInclusive(left), RangeValue::ByteRangeInclusive(right)) => {
|
||||
let start_cmp = left.start().cmp(right.start());
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end().cmp(right.end())
|
||||
}
|
||||
}
|
||||
(RangeValue::ByteRangeInclusive(_), _) => Ordering::Greater,
|
||||
(RangeValue::CharacterRange(left), RangeValue::CharacterRange(right)) => {
|
||||
let start_cmp = left.start.cmp(&right.start);
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end.cmp(&right.end)
|
||||
}
|
||||
}
|
||||
(RangeValue::CharacterRange(_), _) => Ordering::Greater,
|
||||
(
|
||||
RangeValue::CharacterRangeInclusive(left),
|
||||
RangeValue::CharacterRangeInclusive(right),
|
||||
) => {
|
||||
let start_cmp = left.start().cmp(right.start());
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end().cmp(right.end())
|
||||
}
|
||||
}
|
||||
(RangeValue::CharacterRangeInclusive(_), _) => Ordering::Greater,
|
||||
(RangeValue::FloatRange(left), RangeValue::FloatRange(right)) => {
|
||||
let start_cmp = left.start.to_bits().cmp(&right.start.to_bits());
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end.to_bits().cmp(&right.end.to_bits())
|
||||
}
|
||||
}
|
||||
(RangeValue::FloatRange(_), _) => Ordering::Greater,
|
||||
(RangeValue::FloatRangeInclusive(left), RangeValue::FloatRangeInclusive(right)) => {
|
||||
let start_cmp = left.start().to_bits().cmp(&right.start().to_bits());
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end().to_bits().cmp(&right.end().to_bits())
|
||||
}
|
||||
}
|
||||
(RangeValue::FloatRangeInclusive(_), _) => Ordering::Greater,
|
||||
(RangeValue::IntegerRange(left), RangeValue::IntegerRange(right)) => {
|
||||
let start_cmp = left.start.cmp(&right.start);
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end.cmp(&right.end)
|
||||
}
|
||||
}
|
||||
(RangeValue::IntegerRange(_), _) => Ordering::Greater,
|
||||
(RangeValue::IntegerRangeInclusive(left), RangeValue::IntegerRangeInclusive(right)) => {
|
||||
let start_cmp = left.start().cmp(right.start());
|
||||
|
||||
if start_cmp != Ordering::Equal {
|
||||
start_cmp
|
||||
} else {
|
||||
left.end().cmp(right.end())
|
||||
}
|
||||
}
|
||||
(RangeValue::IntegerRangeInclusive(_), _) => Ordering::Greater,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ValueError {
|
||||
CannotAdd(Value, Value),
|
||||
CannotAnd(Value, Value),
|
||||
CannotCompare(Value, Value),
|
||||
CannotDivide(Value, Value),
|
||||
CannotModulo(Value, Value),
|
||||
CannotMultiply(Value, Value),
|
||||
CannotNegate(Value),
|
||||
CannotNot(Value),
|
||||
CannotSubtract(Value, Value),
|
||||
CannotOr(Value, Value),
|
||||
}
|
||||
|
||||
impl Display for ValueError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ValueError::CannotAdd(left, right) => {
|
||||
write!(f, "Cannot add {left} and {right}")
|
||||
}
|
||||
ValueError::CannotAnd(left, right) => {
|
||||
write!(f, "Cannot use logical AND operation on {left} and {right}")
|
||||
}
|
||||
ValueError::CannotCompare(left, right) => {
|
||||
write!(f, "Cannot compare {left} and {right}")
|
||||
}
|
||||
ValueError::CannotDivide(left, right) => {
|
||||
write!(f, "Cannot divide {left} by {right}")
|
||||
}
|
||||
ValueError::CannotModulo(left, right) => {
|
||||
write!(f, "Cannot use modulo operation on {left} and {right}")
|
||||
}
|
||||
ValueError::CannotMultiply(left, right) => {
|
||||
write!(f, "Cannot multiply {left} by {right}")
|
||||
}
|
||||
ValueError::CannotNegate(value) => {
|
||||
write!(f, "Cannot negate {value}")
|
||||
}
|
||||
ValueError::CannotNot(value) => {
|
||||
write!(f, "Cannot use logical NOT operation on {value}")
|
||||
}
|
||||
ValueError::CannotSubtract(left, right) => {
|
||||
write!(f, "Cannot subtract {right} from {left}")
|
||||
}
|
||||
ValueError::CannotOr(left, right) => {
|
||||
write!(f, "Cannot use logical OR operation on {left} and {right}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
765
dust-lang/src/vm.rs
Normal file
765
dust-lang/src/vm.rs
Normal file
@ -0,0 +1,765 @@
|
||||
//! Virtual machine and errors
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt::{self, Display, Formatter},
|
||||
ops::Range,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
compile, value::ConcreteValue, AnnotatedError, Chunk, ChunkError, DustError, FunctionType,
|
||||
Instruction, Local, NativeFunction, NativeFunctionError, Operation, Span, Type, Value,
|
||||
ValueError,
|
||||
};
|
||||
|
||||
pub fn run(source: &str) -> Result<Option<Value>, DustError> {
|
||||
let mut chunk = compile(source)?;
|
||||
let mut vm = Vm::new(&mut chunk, None);
|
||||
|
||||
vm.run()
|
||||
.map(|option| option.cloned())
|
||||
.map_err(|error| DustError::Runtime { error, source })
|
||||
}
|
||||
|
||||
pub fn run_and_display_output(source: &str) {
|
||||
match run(source) {
|
||||
Ok(Some(value)) => println!("{}", value),
|
||||
Ok(None) => {}
|
||||
Err(error) => eprintln!("{}", error.report()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Dust virtual machine.
|
||||
///
|
||||
/// See the [module-level documentation](index.html) for more information.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Vm<'chunk, 'parent> {
|
||||
ip: usize,
|
||||
chunk: &'chunk mut Chunk,
|
||||
stack: Vec<Register>,
|
||||
last_assigned_register: Option<u8>,
|
||||
parent: Option<&'parent Vm<'chunk, 'parent>>,
|
||||
}
|
||||
|
||||
impl<'chunk, 'parent> Vm<'chunk, 'parent> {
|
||||
const STACK_LIMIT: usize = u16::MAX as usize;
|
||||
|
||||
pub fn new(chunk: &'chunk mut Chunk, parent: Option<&'parent Vm<'chunk, 'parent>>) -> Self {
|
||||
Self {
|
||||
ip: 0,
|
||||
chunk,
|
||||
stack: Vec::new(),
|
||||
last_assigned_register: None,
|
||||
parent,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> Result<Option<&Value>, VmError> {
|
||||
// DRY helper to get constant or register values for binary operations
|
||||
fn get_arguments<'a>(
|
||||
vm: &'a mut Vm,
|
||||
instruction: Instruction,
|
||||
position: Span,
|
||||
) -> Result<(&'a Value, &'a Value), VmError> {
|
||||
let left = if instruction.b_is_constant() {
|
||||
vm.get_constant(instruction.b(), position)?
|
||||
} else {
|
||||
vm.open_register(instruction.b(), position)?
|
||||
};
|
||||
let right = if instruction.c_is_constant() {
|
||||
vm.get_constant(instruction.c(), position)?
|
||||
} else {
|
||||
vm.open_register(instruction.c(), position)?
|
||||
};
|
||||
|
||||
Ok((left, right))
|
||||
}
|
||||
|
||||
while let Ok((instruction, position)) = self.read(Span(0, 0)).copied() {
|
||||
log::info!(
|
||||
"{} | {} | {} | {}",
|
||||
self.ip - 1,
|
||||
position,
|
||||
instruction.operation(),
|
||||
instruction.disassembly_info(self.chunk)
|
||||
);
|
||||
|
||||
match instruction.operation() {
|
||||
Operation::Move => {
|
||||
let to_register = instruction.a();
|
||||
let from_register = instruction.b();
|
||||
let from_register_has_value = self
|
||||
.stack
|
||||
.get(from_register as usize)
|
||||
.is_some_and(|register| !matches!(register, Register::Empty));
|
||||
|
||||
if from_register_has_value {
|
||||
self.set_register(
|
||||
to_register,
|
||||
Register::StackPointer(from_register),
|
||||
position,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Operation::Close => {
|
||||
let from_register = instruction.b();
|
||||
let to_register = instruction.c();
|
||||
|
||||
if self.stack.len() < to_register as usize {
|
||||
return Err(VmError::StackUnderflow { position });
|
||||
}
|
||||
|
||||
for register_index in from_register..to_register {
|
||||
self.stack[register_index as usize] = Register::Empty;
|
||||
}
|
||||
}
|
||||
Operation::LoadBoolean => {
|
||||
let to_register = instruction.a();
|
||||
let boolean = instruction.b_as_boolean();
|
||||
let jump = instruction.c_as_boolean();
|
||||
let value = Value::boolean(boolean);
|
||||
|
||||
self.set_register(to_register, Register::Value(value), position)?;
|
||||
|
||||
if jump {
|
||||
self.ip += 1;
|
||||
}
|
||||
}
|
||||
Operation::LoadConstant => {
|
||||
let to_register = instruction.a();
|
||||
let from_constant = instruction.b();
|
||||
let jump = instruction.c_as_boolean();
|
||||
|
||||
self.set_register(
|
||||
to_register,
|
||||
Register::ConstantPointer(from_constant),
|
||||
position,
|
||||
)?;
|
||||
|
||||
if jump {
|
||||
self.ip += 1
|
||||
}
|
||||
}
|
||||
Operation::LoadList => {
|
||||
let to_register = instruction.a();
|
||||
let start_register = instruction.b();
|
||||
let item_type = (start_register..to_register)
|
||||
.find_map(|register_index| {
|
||||
if let Ok(value) = self.open_register(register_index, position) {
|
||||
Some(value.r#type())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(Type::Any);
|
||||
let value = Value::abstract_list(start_register, to_register, item_type);
|
||||
|
||||
self.set_register(to_register, Register::Value(value), position)?;
|
||||
}
|
||||
Operation::LoadSelf => {
|
||||
let to_register = instruction.a();
|
||||
let value = Value::function(
|
||||
self.chunk.clone(),
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
return_type: None,
|
||||
},
|
||||
);
|
||||
|
||||
self.set_register(to_register, Register::Value(value), position)?;
|
||||
}
|
||||
Operation::DefineLocal => {
|
||||
let from_register = instruction.a();
|
||||
let to_local = instruction.b();
|
||||
|
||||
self.define_local(to_local, from_register, position)?;
|
||||
}
|
||||
Operation::GetLocal => {
|
||||
let to_register = instruction.a();
|
||||
let local_index = instruction.b();
|
||||
let local = self.get_local(local_index, position)?;
|
||||
|
||||
self.set_register(
|
||||
to_register,
|
||||
Register::StackPointer(local.register_index),
|
||||
position,
|
||||
)?;
|
||||
}
|
||||
Operation::SetLocal => {
|
||||
let register = instruction.a();
|
||||
let local_index = instruction.b();
|
||||
|
||||
self.define_local(local_index, register, position)?;
|
||||
}
|
||||
Operation::Add => {
|
||||
let to_register = instruction.a();
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let sum = left
|
||||
.add(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
|
||||
self.set_register(to_register, Register::Value(sum), position)?;
|
||||
}
|
||||
Operation::Subtract => {
|
||||
let to_register = instruction.a();
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let difference = left
|
||||
.subtract(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
|
||||
self.set_register(to_register, Register::Value(difference), position)?;
|
||||
}
|
||||
Operation::Multiply => {
|
||||
let to_register = instruction.a();
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let product = left
|
||||
.multiply(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
|
||||
self.set_register(to_register, Register::Value(product), position)?;
|
||||
}
|
||||
Operation::Divide => {
|
||||
let to_register = instruction.a();
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let quotient = left
|
||||
.divide(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
|
||||
self.set_register(to_register, Register::Value(quotient), position)?;
|
||||
}
|
||||
Operation::Modulo => {
|
||||
let to_register = instruction.a();
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let remainder = left
|
||||
.modulo(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
|
||||
self.set_register(to_register, Register::Value(remainder), position)?;
|
||||
}
|
||||
Operation::Test => {
|
||||
let register = instruction.a();
|
||||
let test_value = instruction.c_as_boolean();
|
||||
let value = self.open_register(register, position)?;
|
||||
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) = value {
|
||||
*boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: value.clone(),
|
||||
position,
|
||||
});
|
||||
};
|
||||
|
||||
if boolean != test_value {
|
||||
self.ip += 1;
|
||||
}
|
||||
}
|
||||
Operation::TestSet => todo!(),
|
||||
Operation::Equal => {
|
||||
debug_assert_eq!(
|
||||
self.get_instruction(self.ip, position)?.0.operation(),
|
||||
Operation::Jump
|
||||
);
|
||||
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let equal_result = left
|
||||
.equal(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
let boolean =
|
||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = equal_result {
|
||||
boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: equal_result.clone(),
|
||||
position,
|
||||
});
|
||||
};
|
||||
let compare_to = instruction.a_as_boolean();
|
||||
|
||||
if boolean == compare_to {
|
||||
self.ip += 1;
|
||||
} else {
|
||||
let (jump, _) = self.get_instruction(self.ip, position)?;
|
||||
let jump_distance = jump.a();
|
||||
let is_positive = jump.b_as_boolean();
|
||||
let new_ip = if is_positive {
|
||||
self.ip + jump_distance as usize
|
||||
} else {
|
||||
self.ip - jump_distance as usize
|
||||
};
|
||||
|
||||
self.ip = new_ip;
|
||||
}
|
||||
}
|
||||
Operation::Less => {
|
||||
debug_assert_eq!(
|
||||
self.get_instruction(self.ip, position)?.0.operation(),
|
||||
Operation::Jump
|
||||
);
|
||||
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let less_result = left
|
||||
.less_than(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
let boolean =
|
||||
if let Value::Concrete(ConcreteValue::Boolean(boolean)) = less_result {
|
||||
boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: less_result.clone(),
|
||||
position,
|
||||
});
|
||||
};
|
||||
let compare_to = instruction.a_as_boolean();
|
||||
|
||||
if boolean == compare_to {
|
||||
self.ip += 1;
|
||||
} else {
|
||||
let jump = self.get_instruction(self.ip, position)?.0;
|
||||
let jump_distance = jump.a();
|
||||
let is_positive = jump.b_as_boolean();
|
||||
let new_ip = if is_positive {
|
||||
self.ip + jump_distance as usize
|
||||
} else {
|
||||
self.ip - jump_distance as usize
|
||||
};
|
||||
|
||||
self.ip = new_ip;
|
||||
}
|
||||
}
|
||||
Operation::LessEqual => {
|
||||
debug_assert_eq!(
|
||||
self.get_instruction(self.ip, position)?.0.operation(),
|
||||
Operation::Jump
|
||||
);
|
||||
|
||||
let (left, right) = get_arguments(self, instruction, position)?;
|
||||
let less_or_equal_result = left
|
||||
.less_than_or_equal(right)
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
let boolean = if let Value::Concrete(ConcreteValue::Boolean(boolean)) =
|
||||
less_or_equal_result
|
||||
{
|
||||
boolean
|
||||
} else {
|
||||
return Err(VmError::ExpectedBoolean {
|
||||
found: less_or_equal_result.clone(),
|
||||
position,
|
||||
});
|
||||
};
|
||||
let compare_to = instruction.a_as_boolean();
|
||||
|
||||
if boolean == compare_to {
|
||||
self.ip += 1;
|
||||
} else {
|
||||
let jump = self.get_instruction(self.ip, position)?.0;
|
||||
let jump_distance = jump.a();
|
||||
let is_positive = jump.b_as_boolean();
|
||||
let new_ip = if is_positive {
|
||||
self.ip + jump_distance as usize
|
||||
} else {
|
||||
self.ip - jump_distance as usize
|
||||
};
|
||||
|
||||
self.ip = new_ip;
|
||||
}
|
||||
}
|
||||
Operation::Negate => {
|
||||
let value = if instruction.b_is_constant() {
|
||||
self.get_constant(instruction.b(), position)?
|
||||
} else {
|
||||
self.open_register(instruction.b(), position)?
|
||||
};
|
||||
let negated = value
|
||||
.negate()
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
|
||||
self.set_register(instruction.a(), Register::Value(negated), position)?;
|
||||
}
|
||||
Operation::Not => {
|
||||
let value = if instruction.b_is_constant() {
|
||||
self.get_constant(instruction.b(), position)?
|
||||
} else {
|
||||
self.open_register(instruction.b(), position)?
|
||||
};
|
||||
let not = value
|
||||
.not()
|
||||
.map_err(|error| VmError::Value { error, position })?;
|
||||
|
||||
self.set_register(instruction.a(), Register::Value(not), position)?;
|
||||
}
|
||||
Operation::Jump => {
|
||||
let jump_distance = instruction.b();
|
||||
let is_positive = instruction.c_as_boolean();
|
||||
let new_ip = if is_positive {
|
||||
self.ip + jump_distance as usize
|
||||
} else {
|
||||
self.ip - jump_distance as usize - 1
|
||||
};
|
||||
self.ip = new_ip;
|
||||
}
|
||||
Operation::Call => {
|
||||
let to_register = instruction.a();
|
||||
let function_register = instruction.b();
|
||||
let argument_count = instruction.c();
|
||||
let value = self.open_register(function_register, position)?.clone();
|
||||
let mut function =
|
||||
if let Value::Concrete(ConcreteValue::Function(function)) = value {
|
||||
function
|
||||
} else {
|
||||
return Err(VmError::ExpectedFunction {
|
||||
found: value,
|
||||
position,
|
||||
});
|
||||
};
|
||||
let mut function_vm = Vm::new(function.chunk_mut(), Some(self));
|
||||
let first_argument_index = function_register + 1;
|
||||
|
||||
for argument_index in
|
||||
first_argument_index..first_argument_index + argument_count
|
||||
{
|
||||
let top_of_stack = function_vm.stack.len() as u8;
|
||||
|
||||
function_vm.set_register(
|
||||
top_of_stack,
|
||||
Register::ParentStackPointer(argument_index),
|
||||
position,
|
||||
)?
|
||||
}
|
||||
|
||||
let return_value = function_vm.run()?.cloned();
|
||||
|
||||
if let Some(value) = return_value {
|
||||
self.set_register(to_register, Register::Value(value), position)?;
|
||||
}
|
||||
}
|
||||
Operation::CallNative => {
|
||||
let native_function = NativeFunction::from(instruction.b());
|
||||
let return_value = native_function.call(instruction, self, position)?;
|
||||
|
||||
if let Some(value) = return_value {
|
||||
let to_register = instruction.a();
|
||||
|
||||
self.set_register(to_register, Register::Value(value), position)?;
|
||||
}
|
||||
}
|
||||
Operation::Return => {
|
||||
let should_return_value = instruction.b_as_boolean();
|
||||
|
||||
if !should_return_value {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let return_value = if let Some(register_index) = self.last_assigned_register {
|
||||
self.open_register(register_index, position)?
|
||||
} else {
|
||||
return Err(VmError::StackUnderflow { position });
|
||||
};
|
||||
|
||||
return Ok(Some(return_value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn set_register(
|
||||
&mut self,
|
||||
to_register: u8,
|
||||
register: Register,
|
||||
position: Span,
|
||||
) -> Result<(), VmError> {
|
||||
self.last_assigned_register = Some(to_register);
|
||||
|
||||
let length = self.stack.len();
|
||||
let to_register = to_register as usize;
|
||||
|
||||
if length == Self::STACK_LIMIT {
|
||||
return Err(VmError::StackOverflow { position });
|
||||
}
|
||||
|
||||
match to_register.cmp(&length) {
|
||||
Ordering::Less => {
|
||||
log::trace!("Change R{to_register} to {register}");
|
||||
|
||||
self.stack[to_register] = register;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ordering::Equal => {
|
||||
log::trace!("Set R{to_register} to {register}");
|
||||
|
||||
self.stack.push(register);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let difference = to_register - length;
|
||||
|
||||
for index in 0..difference {
|
||||
log::trace!("Set R{index} to {register}");
|
||||
|
||||
self.stack.push(Register::Empty);
|
||||
}
|
||||
|
||||
log::trace!("Set R{to_register} to {register}");
|
||||
|
||||
self.stack.push(register);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_constant(&self, index: u8, position: Span) -> Result<&Value, VmError> {
|
||||
self.chunk
|
||||
.get_constant(index)
|
||||
.map_err(|error| VmError::Chunk { error, position })
|
||||
}
|
||||
|
||||
pub fn open_register(&self, register_index: u8, position: Span) -> Result<&Value, VmError> {
|
||||
let register_index = register_index as usize;
|
||||
let register =
|
||||
self.stack
|
||||
.get(register_index)
|
||||
.ok_or_else(|| VmError::RegisterIndexOutOfBounds {
|
||||
index: register_index,
|
||||
position,
|
||||
})?;
|
||||
|
||||
match register {
|
||||
Register::Value(value) => Ok(value),
|
||||
Register::StackPointer(register_index) => self.open_register(*register_index, position),
|
||||
Register::ConstantPointer(constant_index) => {
|
||||
self.get_constant(*constant_index, position)
|
||||
}
|
||||
Register::ParentStackPointer(register_index) => {
|
||||
let parent = self
|
||||
.parent
|
||||
.as_ref()
|
||||
.ok_or(VmError::ExpectedParent { position })?;
|
||||
|
||||
parent.open_register(*register_index, position)
|
||||
}
|
||||
Register::ParentConstantPointer(constant_index) => {
|
||||
let parent = self
|
||||
.parent
|
||||
.as_ref()
|
||||
.ok_or(VmError::ExpectedParent { position })?;
|
||||
|
||||
parent.get_constant(*constant_index, position)
|
||||
}
|
||||
Register::Empty => Err(VmError::EmptyRegister {
|
||||
index: register_index,
|
||||
position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_nonempty_registers(
|
||||
&self,
|
||||
register_index_range: Range<u8>,
|
||||
position: Span,
|
||||
) -> Result<Vec<&Value>, VmError> {
|
||||
let mut values = Vec::with_capacity(register_index_range.len());
|
||||
|
||||
for register_index in register_index_range.clone() {
|
||||
let register_index = register_index as usize;
|
||||
let register = self.stack.get(register_index).ok_or_else(|| {
|
||||
VmError::RegisterIndexOutOfBounds {
|
||||
index: register_index,
|
||||
position,
|
||||
}
|
||||
})?;
|
||||
|
||||
let value = match register {
|
||||
Register::Value(value) => value,
|
||||
Register::StackPointer(register_index) => {
|
||||
self.open_register(*register_index, position)?
|
||||
}
|
||||
Register::ConstantPointer(constant_index) => {
|
||||
self.get_constant(*constant_index, position)?
|
||||
}
|
||||
Register::ParentStackPointer(register_index) => {
|
||||
let parent = self
|
||||
.parent
|
||||
.as_ref()
|
||||
.ok_or(VmError::ExpectedParent { position })?;
|
||||
|
||||
parent.open_register(*register_index, position)?
|
||||
}
|
||||
Register::ParentConstantPointer(constant_index) => {
|
||||
let parent = self
|
||||
.parent
|
||||
.as_ref()
|
||||
.ok_or(VmError::ExpectedParent { position })?;
|
||||
|
||||
parent.get_constant(*constant_index, position)?
|
||||
}
|
||||
Register::Empty => continue,
|
||||
};
|
||||
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
if values.is_empty() {
|
||||
Err(VmError::EmptyRegisters {
|
||||
indexes: register_index_range,
|
||||
position,
|
||||
})
|
||||
} else {
|
||||
Ok(values)
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self, position: Span) -> Result<&(Instruction, Span), VmError> {
|
||||
self.chunk
|
||||
.expect_not_poisoned()
|
||||
.map_err(|error| VmError::Chunk { error, position })?;
|
||||
|
||||
let max_ip = self.chunk.len() - 1;
|
||||
|
||||
if self.ip > max_ip {
|
||||
return self.get_instruction(max_ip, position);
|
||||
} else {
|
||||
self.ip += 1;
|
||||
}
|
||||
|
||||
self.get_instruction(self.ip - 1, position)
|
||||
}
|
||||
|
||||
fn define_local(
|
||||
&mut self,
|
||||
local_index: u8,
|
||||
register_index: u8,
|
||||
position: Span,
|
||||
) -> Result<(), VmError> {
|
||||
let local = self
|
||||
.chunk
|
||||
.get_local_mut(local_index)
|
||||
.map_err(|error| VmError::Chunk { error, position })?;
|
||||
|
||||
log::debug!("Define local L{}", local_index);
|
||||
|
||||
local.register_index = register_index;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_local(&self, local_index: u8, position: Span) -> Result<&Local, VmError> {
|
||||
self.chunk
|
||||
.get_local(local_index)
|
||||
.map_err(|error| VmError::Chunk { error, position })
|
||||
}
|
||||
|
||||
fn get_instruction(
|
||||
&self,
|
||||
index: usize,
|
||||
position: Span,
|
||||
) -> Result<&(Instruction, Span), VmError> {
|
||||
self.chunk
|
||||
.get_instruction(index)
|
||||
.map_err(|error| VmError::Chunk { error, position })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum Register {
|
||||
Empty,
|
||||
Value(Value),
|
||||
StackPointer(u8),
|
||||
ConstantPointer(u8),
|
||||
ParentStackPointer(u8),
|
||||
ParentConstantPointer(u8),
|
||||
}
|
||||
|
||||
impl Display for Register {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Empty => write!(f, "empty"),
|
||||
Self::Value(value) => write!(f, "{}", value),
|
||||
Self::StackPointer(index) => write!(f, "R{}", index),
|
||||
Self::ConstantPointer(index) => write!(f, "C{}", index),
|
||||
Self::ParentStackPointer(index) => write!(f, "PR{}", index),
|
||||
Self::ParentConstantPointer(index) => write!(f, "PC{}", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum VmError {
|
||||
// Stack errors
|
||||
StackOverflow { position: Span },
|
||||
StackUnderflow { position: Span },
|
||||
|
||||
// Register errors
|
||||
EmptyRegister { index: usize, position: Span },
|
||||
EmptyRegisters { indexes: Range<u8>, position: Span },
|
||||
RegisterIndexOutOfBounds { index: usize, position: Span },
|
||||
|
||||
// Execution errors
|
||||
ExpectedBoolean { found: Value, position: Span },
|
||||
ExpectedFunction { found: Value, position: Span },
|
||||
ExpectedParent { position: Span },
|
||||
|
||||
// Wrappers for foreign errors
|
||||
Chunk { error: ChunkError, position: Span },
|
||||
NativeFunction(NativeFunctionError),
|
||||
Value { error: ValueError, position: Span },
|
||||
}
|
||||
|
||||
impl AnnotatedError for VmError {
|
||||
fn title() -> &'static str {
|
||||
"Runtime Error"
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Chunk { .. } => "Chunk error",
|
||||
Self::EmptyRegister { .. } => "Empty register",
|
||||
Self::EmptyRegisters { .. } => "Empty registers",
|
||||
Self::ExpectedBoolean { .. } => "Expected boolean",
|
||||
Self::ExpectedFunction { .. } => "Expected function",
|
||||
Self::ExpectedParent { .. } => "Expected parent",
|
||||
Self::NativeFunction(error) => error.description(),
|
||||
Self::RegisterIndexOutOfBounds { .. } => "Register index out of bounds",
|
||||
Self::StackOverflow { .. } => "Stack overflow",
|
||||
Self::StackUnderflow { .. } => "Stack underflow",
|
||||
Self::Value { .. } => "Value error",
|
||||
}
|
||||
}
|
||||
|
||||
fn details(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::Chunk { error, .. } => Some(error.to_string()),
|
||||
Self::EmptyRegister { index, .. } => Some(format!("Register R{index} is empty")),
|
||||
Self::EmptyRegisters { indexes: range, .. } => Some(format!(
|
||||
"Registers R{} to R{} are empty",
|
||||
range.start, range.end
|
||||
)),
|
||||
Self::ExpectedFunction { found, .. } => Some(format!("{found} is not a function")),
|
||||
Self::RegisterIndexOutOfBounds { index, .. } => {
|
||||
Some(format!("Register {index} does not exist"))
|
||||
}
|
||||
Self::NativeFunction(error) => error.details(),
|
||||
Self::Value { error, .. } => Some(error.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self) -> Span {
|
||||
match self {
|
||||
Self::Chunk { position, .. } => *position,
|
||||
Self::EmptyRegister { position, .. } => *position,
|
||||
Self::EmptyRegisters { position, .. } => *position,
|
||||
Self::ExpectedBoolean { position, .. } => *position,
|
||||
Self::ExpectedFunction { position, .. } => *position,
|
||||
Self::ExpectedParent { position } => *position,
|
||||
Self::NativeFunction(error) => error.position(),
|
||||
Self::RegisterIndexOutOfBounds { position, .. } => *position,
|
||||
Self::StackOverflow { position } => *position,
|
||||
Self::StackUnderflow { position } => *position,
|
||||
Self::Value { position, .. } => *position,
|
||||
}
|
||||
}
|
||||
}
|
66
dust-lang/tests/basic.rs
Normal file
66
dust-lang/tests/basic.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn constant() {
|
||||
let source = "42";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(0, 2)),
|
||||
(Instruction::r#return(true), Span(2, 2))
|
||||
],
|
||||
vec![Value::integer(42)],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let source = "";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![(Instruction::r#return(false), Span(0, 0))],
|
||||
vec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parentheses_precedence() {
|
||||
let source = "(1 + 2) * 3";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::add(0, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(3, 4)
|
||||
),
|
||||
(
|
||||
*Instruction::multiply(1, 0, 2).set_c_is_constant(),
|
||||
Span(8, 9)
|
||||
),
|
||||
(Instruction::r#return(true), Span(11, 11)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(9))));
|
||||
}
|
169
dust-lang/tests/comparison.rs
Normal file
169
dust-lang/tests/comparison.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn equal() {
|
||||
let source = "1 == 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn greater() {
|
||||
let source = "1 > 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::less_equal(false, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(2, 3)),
|
||||
(Instruction::load_boolean(0, true, true), Span(2, 3)),
|
||||
(Instruction::load_boolean(0, false, false), Span(2, 3)),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn greater_than_or_equal() {
|
||||
let source = "1 >= 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::less(false, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn less_than() {
|
||||
let source = "1 < 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::less(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(2, 3)),
|
||||
(Instruction::load_boolean(0, true, true), Span(2, 3)),
|
||||
(Instruction::load_boolean(0, false, false), Span(2, 3)),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn less_than_or_equal() {
|
||||
let source = "1 <= 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::less_equal(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_equal() {
|
||||
let source = "1 != 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(false, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 4)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, true, true), Span(2, 4)),
|
||||
(Instruction::load_boolean(0, false, false), Span(2, 4)),
|
||||
(Instruction::r#return(true), Span(6, 6)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
420
dust-lang/tests/control_flow.rs
Normal file
420
dust-lang/tests/control_flow.rs
Normal file
@ -0,0 +1,420 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn equality_assignment_long() {
|
||||
let source = "let a = if 4 == 4 { true } else { false }; a";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(13, 15)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(18, 19)),
|
||||
(Instruction::load_boolean(0, true, true), Span(20, 24)),
|
||||
(Instruction::load_boolean(0, false, false), Span(34, 39)),
|
||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
||||
(Instruction::get_local(1, 0), Span(43, 44)),
|
||||
(Instruction::r#return(true), Span(44, 44)),
|
||||
],
|
||||
vec![Value::integer(4), Value::string("a")],
|
||||
vec![Local::new(
|
||||
1,
|
||||
None,
|
||||
false,
|
||||
Scope {
|
||||
depth: 0,
|
||||
block_index: 0
|
||||
},
|
||||
0
|
||||
)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equality_assignment_short() {
|
||||
let source = "let a = 4 == 4 a";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(10, 12)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(10, 12)),
|
||||
(Instruction::load_boolean(0, true, true), Span(10, 12)),
|
||||
(Instruction::load_boolean(0, false, false), Span(10, 12)),
|
||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
||||
(Instruction::get_local(1, 0), Span(15, 16)),
|
||||
(Instruction::r#return(true), Span(16, 16)),
|
||||
],
|
||||
vec![Value::integer(4), Value::string("a")],
|
||||
vec![Local::new(1, None, false, Scope::default(), 0)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_assigment_false() {
|
||||
let source = r#"
|
||||
let a = if 4 == 3 {
|
||||
1; 2; 3; 4;
|
||||
panic()
|
||||
} else {
|
||||
1; 2; 3; 4;
|
||||
42
|
||||
};
|
||||
a"#;
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(22, 24)
|
||||
),
|
||||
(Instruction::jump(6, true), Span(27, 28)),
|
||||
(Instruction::load_constant(0, 2, false), Span(41, 42)),
|
||||
(Instruction::load_constant(1, 3, false), Span(44, 45)),
|
||||
(Instruction::load_constant(2, 1, false), Span(47, 48)),
|
||||
(Instruction::load_constant(3, 0, false), Span(50, 51)),
|
||||
(
|
||||
Instruction::call_native(4, NativeFunction::Panic, 0),
|
||||
Span(65, 72)
|
||||
),
|
||||
(Instruction::jump(5, true), Span(138, 139)),
|
||||
(Instruction::load_constant(5, 2, false), Span(102, 103)),
|
||||
(Instruction::load_constant(6, 3, false), Span(105, 106)),
|
||||
(Instruction::load_constant(7, 1, false), Span(108, 109)),
|
||||
(Instruction::load_constant(8, 0, false), Span(111, 112)),
|
||||
(Instruction::load_constant(9, 4, false), Span(126, 128)),
|
||||
(Instruction::r#move(9, 4), Span(138, 139)),
|
||||
(Instruction::define_local(9, 0, false), Span(13, 14)),
|
||||
(Instruction::get_local(10, 0), Span(148, 149)),
|
||||
(Instruction::r#return(true), Span(149, 149)),
|
||||
],
|
||||
vec![
|
||||
Value::integer(4),
|
||||
Value::integer(3),
|
||||
Value::integer(1),
|
||||
Value::integer(2),
|
||||
Value::integer(42),
|
||||
Value::string("a")
|
||||
],
|
||||
vec![Local::new(5, None, false, Scope::default(), 0)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_assigment_true() {
|
||||
let source = r#"
|
||||
let a = if 4 == 4 {
|
||||
1; 2; 3; 4;
|
||||
42
|
||||
} else {
|
||||
1; 2; 3; 4;
|
||||
panic()
|
||||
};
|
||||
a"#;
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(22, 24)
|
||||
),
|
||||
(Instruction::jump(6, true), Span(27, 28)),
|
||||
(Instruction::load_constant(0, 1, false), Span(41, 42)),
|
||||
(Instruction::load_constant(1, 2, false), Span(44, 45)),
|
||||
(Instruction::load_constant(2, 3, false), Span(47, 48)),
|
||||
(Instruction::load_constant(3, 0, false), Span(50, 51)),
|
||||
(Instruction::load_constant(4, 4, false), Span(65, 67)),
|
||||
(Instruction::jump(5, true), Span(138, 139)),
|
||||
(Instruction::load_constant(5, 1, false), Span(97, 98)),
|
||||
(Instruction::load_constant(6, 2, false), Span(100, 101)),
|
||||
(Instruction::load_constant(7, 3, false), Span(103, 104)),
|
||||
(Instruction::load_constant(8, 0, false), Span(106, 107)),
|
||||
(
|
||||
Instruction::call_native(9, NativeFunction::Panic, 0),
|
||||
Span(121, 128)
|
||||
),
|
||||
(Instruction::r#move(9, 4), Span(138, 139)),
|
||||
(Instruction::define_local(9, 0, false), Span(13, 14)),
|
||||
(Instruction::get_local(10, 0), Span(148, 149)),
|
||||
(Instruction::r#return(true), Span(149, 149)),
|
||||
],
|
||||
vec![
|
||||
Value::integer(4),
|
||||
Value::integer(1),
|
||||
Value::integer(2),
|
||||
Value::integer(3),
|
||||
Value::integer(42),
|
||||
Value::string("a")
|
||||
],
|
||||
vec![Local::new(5, None, false, Scope::default(), 0)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_complex() {
|
||||
let source = "
|
||||
if 1 == 1 {
|
||||
1; 2; 3; 4;
|
||||
} else {
|
||||
1; 2; 3; 4;
|
||||
}";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(14, 16)
|
||||
),
|
||||
(Instruction::jump(5, true), Span(19, 20)),
|
||||
(Instruction::load_constant(0, 0, false), Span(33, 34)),
|
||||
(Instruction::load_constant(1, 1, false), Span(36, 37)),
|
||||
(Instruction::load_constant(2, 2, false), Span(39, 40)),
|
||||
(Instruction::load_constant(3, 3, false), Span(42, 43)),
|
||||
(Instruction::jump(4, true), Span(95, 95)),
|
||||
(Instruction::load_constant(4, 0, false), Span(74, 75)),
|
||||
(Instruction::load_constant(5, 1, false), Span(77, 78)),
|
||||
(Instruction::load_constant(6, 2, false), Span(80, 81)),
|
||||
(Instruction::load_constant(7, 3, false), Span(83, 84)),
|
||||
(Instruction::r#move(7, 3), Span(95, 95)),
|
||||
(Instruction::r#return(false), Span(95, 95)),
|
||||
],
|
||||
vec![
|
||||
Value::integer(1),
|
||||
Value::integer(2),
|
||||
Value::integer(3),
|
||||
Value::integer(4),
|
||||
],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn if_else_nested() {
|
||||
// let source = r#"
|
||||
// if 0 == 1 {
|
||||
// if 0 == 2 {
|
||||
// 1;
|
||||
// } else {
|
||||
// 2;
|
||||
// }
|
||||
// } else {
|
||||
// if 0 == 3 {
|
||||
// 3;
|
||||
// } else {
|
||||
// 4;
|
||||
// }
|
||||
// }"#;
|
||||
|
||||
// assert_eq!(
|
||||
// parse(source),
|
||||
// Ok(Chunk::with_data(
|
||||
// None,
|
||||
// vec![
|
||||
// (
|
||||
// *Instruction::equal(true, 0, 1)
|
||||
// .set_b_is_constant()
|
||||
// .set_c_is_constant(),
|
||||
// Span(14, 16)
|
||||
// ),
|
||||
// (Instruction::jump(7, true), Span(14, 16)),
|
||||
// (
|
||||
// *Instruction::equal(true, 0, 2)
|
||||
// .set_b_is_constant()
|
||||
// .set_c_is_constant(),
|
||||
// Span(38, 41)
|
||||
// ),
|
||||
// (Instruction::jump(3, true), Span(38, 41)),
|
||||
// (Instruction::load_constant(0, 1, false), Span(61, 62)),
|
||||
// (Instruction::jump(1, true1), Span(95, 95)),
|
||||
// (
|
||||
// *Instruction::equal(true, 0, 3)
|
||||
// .set_b_is_constant()
|
||||
// .set_c_is_constant(),
|
||||
// Span(77, 79)
|
||||
// ),
|
||||
// (Instruction::jump(3, true), Span(77, 79)),
|
||||
// (Instruction::load_constant(0, 2, false), Span(94, 95)),
|
||||
// (Instruction::jump(1, true1), Span(95, 95)),
|
||||
// (Instruction::load_constant(0, 3, false), Span(114, 115)),
|
||||
// (Instruction::jump(1, true1), Span(95, 95)),
|
||||
// (Instruction::load_constant(0, 4, false), Span(134, 135)),
|
||||
// (Instruction::r#return(true), Span(146, 146)),
|
||||
// ],
|
||||
// vec![
|
||||
// Value::integer(0),
|
||||
// Value::integer(1),
|
||||
// Value::integer(0),
|
||||
// Value::integer(2),
|
||||
// Value::integer(1),
|
||||
// Value::integer(0),
|
||||
// Value::integer(3),
|
||||
// Value::integer(3),
|
||||
// Value::integer(4)
|
||||
// ],
|
||||
// vec![]
|
||||
// ))
|
||||
// );
|
||||
|
||||
// assert_eq!(run(source), Ok(Some(Value::integer(4))));
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn if_else_false() {
|
||||
let source = "if 1 == 2 { panic() } else { 42 }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(10, 11)),
|
||||
(
|
||||
Instruction::call_native(0, NativeFunction::Panic, 0),
|
||||
Span(12, 19)
|
||||
),
|
||||
(Instruction::load_constant(1, 2, true), Span(29, 31)),
|
||||
(Instruction::r#move(1, 0), Span(33, 33)),
|
||||
(Instruction::r#return(true), Span(33, 33)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2), Value::integer(42)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_else_true() {
|
||||
let source = "if 1 == 1 { 42 } else { panic() }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(10, 11)),
|
||||
(Instruction::load_constant(0, 1, true), Span(12, 14)),
|
||||
(
|
||||
Instruction::call_native(1, NativeFunction::Panic, 0),
|
||||
Span(24, 31)
|
||||
),
|
||||
(Instruction::r#move(1, 0), Span(33, 33)),
|
||||
(Instruction::r#return(true), Span(33, 33))
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(42)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_false() {
|
||||
let source = "if 1 == 2 { 2 }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(10, 11)),
|
||||
(Instruction::load_constant(0, 1, false), Span(12, 13)),
|
||||
(Instruction::r#return(false), Span(15, 15))
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_true() {
|
||||
let source = "if 1 == 1 { 2 }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::equal(true, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(5, 7)
|
||||
),
|
||||
(Instruction::jump(1, true), Span(10, 11)),
|
||||
(Instruction::load_constant(0, 1, false), Span(12, 13)),
|
||||
(Instruction::r#return(false), Span(15, 15))
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
126
dust-lang/tests/functions.rs
Normal file
126
dust-lang/tests/functions.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn function() {
|
||||
let source = "fn(a: int, b: int) -> int { a + b }";
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Ok(Some(Value::function(
|
||||
Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::add(2, 0, 1), Span(30, 31)),
|
||||
(Instruction::r#return(true), Span(35, 35)),
|
||||
],
|
||||
vec![Value::string("a"), Value::string("b"),],
|
||||
vec![
|
||||
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
|
||||
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
|
||||
]
|
||||
),
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Some(Box::new(Type::Integer)),
|
||||
}
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_call() {
|
||||
let source = "fn(a: int, b: int) -> int { a + b }(1, 2)";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(0, 36)),
|
||||
(Instruction::load_constant(1, 1, false), Span(36, 37)),
|
||||
(Instruction::load_constant(2, 2, false), Span(39, 40)),
|
||||
(Instruction::call(3, 0, 2), Span(35, 41)),
|
||||
(Instruction::r#return(true), Span(41, 41)),
|
||||
],
|
||||
vec![
|
||||
Value::function(
|
||||
Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::add(2, 0, 1), Span(30, 31)),
|
||||
(Instruction::r#return(true), Span(35, 36)),
|
||||
],
|
||||
vec![Value::string("a"), Value::string("b"),],
|
||||
vec![
|
||||
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
|
||||
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
|
||||
]
|
||||
),
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Some(Box::new(Type::Integer)),
|
||||
}
|
||||
),
|
||||
Value::integer(1),
|
||||
Value::integer(2)
|
||||
],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(3))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_declaration() {
|
||||
let source = "fn add (a: int, b: int) -> int { a + b }";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 1, false), Span(0, 40)),
|
||||
(Instruction::define_local(0, 0, false), Span(3, 6)),
|
||||
(Instruction::r#return(false), Span(40, 40))
|
||||
],
|
||||
vec![
|
||||
Value::string("add"),
|
||||
Value::function(
|
||||
Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::add(2, 0, 1), Span(35, 36)),
|
||||
(Instruction::r#return(true), Span(40, 40)),
|
||||
],
|
||||
vec![Value::string("a"), Value::string("b")],
|
||||
vec![
|
||||
Local::new(0, Some(Type::Integer), false, Scope::default(), 0),
|
||||
Local::new(1, Some(Type::Integer), false, Scope::default(), 1)
|
||||
]
|
||||
),
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Some(Box::new(Type::Integer)),
|
||||
},
|
||||
)
|
||||
],
|
||||
vec![Local::new(
|
||||
0,
|
||||
Some(Type::Function(FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: Some(vec![(0, Type::Integer), (1, Type::Integer)]),
|
||||
return_type: Some(Box::new(Type::Integer)),
|
||||
})),
|
||||
false,
|
||||
Scope::default(),
|
||||
0
|
||||
),],
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
127
dust-lang/tests/lists.rs
Normal file
127
dust-lang/tests/lists.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn empty_list() {
|
||||
let source = "[]";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_list(0, 0), Span(0, 2)),
|
||||
(Instruction::r#return(true), Span(2, 2)),
|
||||
],
|
||||
vec![],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::abstract_list(0, 0, Type::Any))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
let source = "[1, 2, 3]";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
||||
(Instruction::load_constant(1, 1, false), Span(4, 5)),
|
||||
(Instruction::load_constant(2, 2, false), Span(7, 8)),
|
||||
(Instruction::load_list(3, 0), Span(0, 9)),
|
||||
(Instruction::r#return(true), Span(9, 9)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2), Value::integer(3)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_with_complex_expression() {
|
||||
let source = "[1, 2 + 3 - 4 * 5]";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
||||
(
|
||||
*Instruction::add(1, 1, 2)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(6, 7)
|
||||
),
|
||||
(
|
||||
*Instruction::multiply(2, 3, 4)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(14, 15)
|
||||
),
|
||||
(Instruction::subtract(3, 1, 2), Span(10, 11)),
|
||||
(Instruction::close(1, 3), Span(17, 18)),
|
||||
(Instruction::load_list(4, 0), Span(0, 18)),
|
||||
(Instruction::r#return(true), Span(18, 18)),
|
||||
],
|
||||
vec![
|
||||
Value::integer(1),
|
||||
Value::integer(2),
|
||||
Value::integer(3),
|
||||
Value::integer(4),
|
||||
Value::integer(5)
|
||||
],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Ok(Some(Value::abstract_list(0, 4, Type::Integer)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_with_simple_expression() {
|
||||
let source = "[1, 2 + 3, 4]";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(1, 2)),
|
||||
(
|
||||
*Instruction::add(1, 1, 2)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(6, 7)
|
||||
),
|
||||
(Instruction::load_constant(2, 3, false), Span(11, 12)),
|
||||
(Instruction::load_list(3, 0), Span(0, 13)),
|
||||
(Instruction::r#return(true), Span(13, 13)),
|
||||
],
|
||||
vec![
|
||||
Value::integer(1),
|
||||
Value::integer(2),
|
||||
Value::integer(3),
|
||||
Value::integer(4),
|
||||
],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Ok(Some(Value::abstract_list(0, 3, Type::Integer)))
|
||||
);
|
||||
}
|
77
dust-lang/tests/logic.rs
Normal file
77
dust-lang/tests/logic.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn and() {
|
||||
let source = "true && false";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_boolean(0, true, false), Span(0, 4)),
|
||||
(Instruction::test(0, false), Span(5, 7)),
|
||||
(Instruction::jump(1, true), Span(5, 7)),
|
||||
(Instruction::load_boolean(1, false, false), Span(8, 13)),
|
||||
(Instruction::r#return(true), Span(13, 13)),
|
||||
],
|
||||
vec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or() {
|
||||
let source = "true || false";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_boolean(0, true, false), Span(0, 4)),
|
||||
(Instruction::test(0, true), Span(5, 7)),
|
||||
(Instruction::jump(1, true), Span(5, 7)),
|
||||
(Instruction::load_boolean(1, false, false), Span(8, 13)),
|
||||
(Instruction::r#return(true), Span(13, 13)),
|
||||
],
|
||||
vec![],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(true))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_and() {
|
||||
let source = "let a = true; let b = false; a && b";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_boolean(0, true, false), Span(8, 12)),
|
||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
||||
(Instruction::load_boolean(1, false, false), Span(22, 27)),
|
||||
(Instruction::define_local(1, 1, false), Span(18, 19)),
|
||||
(Instruction::get_local(2, 0), Span(29, 30)),
|
||||
(Instruction::test(2, false), Span(31, 33)),
|
||||
(Instruction::jump(1, true), Span(31, 33)),
|
||||
(Instruction::get_local(3, 1), Span(34, 35)),
|
||||
(Instruction::r#return(true), Span(35, 35)),
|
||||
],
|
||||
vec![Value::string("a"), Value::string("b"),],
|
||||
vec![
|
||||
Local::new(0, None, false, Scope::default(), 0),
|
||||
Local::new(1, None, false, Scope::default(), 1),
|
||||
]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
35
dust-lang/tests/loops.rs
Normal file
35
dust-lang/tests/loops.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn r#while() {
|
||||
let source = "let mut x = 0; while x < 5 { x = x + 1 } x";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
||||
(
|
||||
*Instruction::less(true, 0, 2).set_c_is_constant(),
|
||||
Span(23, 24)
|
||||
),
|
||||
(Instruction::jump(2, true), Span(41, 42)),
|
||||
(*Instruction::add(0, 0, 3).set_c_is_constant(), Span(39, 40)),
|
||||
(Instruction::jump(3, false), Span(41, 42)),
|
||||
(Instruction::get_local(1, 0), Span(41, 42)),
|
||||
(Instruction::r#return(true), Span(42, 42)),
|
||||
],
|
||||
vec![
|
||||
Value::integer(0),
|
||||
Value::string("x"),
|
||||
Value::integer(5),
|
||||
Value::integer(1),
|
||||
],
|
||||
vec![Local::new(1, None, true, Scope::default(), 0),]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(5))));
|
||||
}
|
324
dust-lang/tests/math.rs
Normal file
324
dust-lang/tests/math.rs
Normal file
@ -0,0 +1,324 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn add() {
|
||||
let source = "1 + 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::add(0, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::r#return(true), Span(5, 5))
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(3))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_assign() {
|
||||
let source = "let mut a = 1; a += 2; a";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
||||
(*Instruction::add(0, 0, 2).set_c_is_constant(), Span(17, 19)),
|
||||
(Instruction::get_local(1, 0), Span(23, 24)),
|
||||
(Instruction::r#return(true), Span(24, 24))
|
||||
],
|
||||
vec![Value::integer(1), Value::string("a"), Value::integer(2)],
|
||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(3))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_assign_expects_mutable_variable() {
|
||||
let source = "1 += 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::ExpectedMutableVariable {
|
||||
found: Token::Integer("1").to_owned(),
|
||||
position: Span(0, 1)
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn add_expects_integer_float_or_string() {
|
||||
// let source = "true + false";
|
||||
|
||||
// assert_eq!(
|
||||
// parse(source),
|
||||
// Err(DustError::Parse {
|
||||
// error: ParseError::ExpectedIntegerFloatOrString {
|
||||
// found: Token::True,
|
||||
// position: Span(0, 3)
|
||||
// },
|
||||
// source
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn divide() {
|
||||
let source = "2 / 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::divide(0, 0, 0)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::r#return(true), Span(5, 5))
|
||||
],
|
||||
vec![Value::integer(2)],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn divide_assign() {
|
||||
let source = "let mut a = 2; a /= 2; a";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
||||
(
|
||||
*Instruction::divide(0, 0, 0).set_c_is_constant(),
|
||||
Span(17, 19)
|
||||
),
|
||||
(Instruction::get_local(1, 0), Span(23, 24)),
|
||||
(Instruction::r#return(true), Span(24, 24))
|
||||
],
|
||||
vec![Value::integer(2), Value::string("a")],
|
||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn divide_assign_expects_mutable_variable() {
|
||||
let source = "1 -= 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::ExpectedMutableVariable {
|
||||
found: Token::Integer("1").to_owned(),
|
||||
position: Span(0, 1)
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn math_operator_precedence() {
|
||||
let source = "1 + 2 - 3 * 4 / 5";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::add(0, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 3)
|
||||
),
|
||||
(
|
||||
*Instruction::multiply(1, 2, 3)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(10, 11)
|
||||
),
|
||||
(
|
||||
*Instruction::divide(2, 1, 4).set_c_is_constant(),
|
||||
Span(14, 15)
|
||||
),
|
||||
(Instruction::subtract(3, 0, 2), Span(6, 7)),
|
||||
(Instruction::r#return(true), Span(17, 17)),
|
||||
],
|
||||
vec![
|
||||
Value::integer(1),
|
||||
Value::integer(2),
|
||||
Value::integer(3),
|
||||
Value::integer(4),
|
||||
Value::integer(5),
|
||||
],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply() {
|
||||
let source = "1 * 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::multiply(0, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(2))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_assign() {
|
||||
let source = "let mut a = 2; a *= 3 a";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(12, 13)),
|
||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
||||
(
|
||||
*Instruction::multiply(0, 0, 2).set_c_is_constant(),
|
||||
Span(17, 19)
|
||||
),
|
||||
(Instruction::get_local(1, 0), Span(22, 23)),
|
||||
(Instruction::r#return(true), Span(23, 23))
|
||||
],
|
||||
vec![Value::integer(2), Value::string("a"), Value::integer(3)],
|
||||
vec![Local::new(1, None, true, Scope::default(), 0),]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(6))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_assign_expects_mutable_variable() {
|
||||
let source = "1 *= 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::ExpectedMutableVariable {
|
||||
found: Token::Integer("1").to_owned(),
|
||||
position: Span(0, 1)
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract() {
|
||||
let source = "1 - 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(
|
||||
*Instruction::subtract(0, 0, 1)
|
||||
.set_b_is_constant()
|
||||
.set_c_is_constant(),
|
||||
Span(2, 3)
|
||||
),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![Value::integer(1), Value::integer(2)],
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(-1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract_assign() {
|
||||
let source = "let mut x = 42; x -= 2; x";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(12, 14)),
|
||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
||||
(
|
||||
*Instruction::subtract(0, 0, 2).set_c_is_constant(),
|
||||
Span(18, 20)
|
||||
),
|
||||
(Instruction::get_local(1, 0), Span(24, 25)),
|
||||
(Instruction::r#return(true), Span(25, 25)),
|
||||
],
|
||||
vec![Value::integer(42), Value::string("x"), Value::integer(2)],
|
||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(40))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract_assign_expects_mutable_variable() {
|
||||
let source = "1 -= 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::ExpectedMutableVariable {
|
||||
found: Token::Integer("1").to_owned(),
|
||||
position: Span(0, 1)
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
59
dust-lang/tests/native_functions.rs
Normal file
59
dust-lang/tests/native_functions.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn panic() {
|
||||
let source = "panic(\"Goodbye world!\", 42)";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(6, 22)),
|
||||
(Instruction::load_constant(1, 1, false), Span(24, 26)),
|
||||
(
|
||||
Instruction::call_native(2, NativeFunction::Panic, 2),
|
||||
Span(0, 27)
|
||||
),
|
||||
(Instruction::r#return(true), Span(27, 27))
|
||||
],
|
||||
vec![Value::string("Goodbye world!"), Value::integer(42)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Err(DustError::Runtime {
|
||||
error: VmError::NativeFunction(NativeFunctionError::Panic {
|
||||
message: Some("Goodbye world! 42".to_string()),
|
||||
position: Span(0, 27)
|
||||
}),
|
||||
source
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let source = "to_string(42)";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(10, 12)),
|
||||
(
|
||||
Instruction::call_native(1, NativeFunction::ToString, 1),
|
||||
Span(0, 13)
|
||||
),
|
||||
(Instruction::r#return(true), Span(13, 13))
|
||||
],
|
||||
vec![Value::integer(42)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::string("42"))))
|
||||
}
|
244
dust-lang/tests/scopes.rs
Normal file
244
dust-lang/tests/scopes.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn allow_access_to_parent_scope() {
|
||||
let source = r#"
|
||||
let x = 1;
|
||||
{
|
||||
x
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(1))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_scope() {
|
||||
let source = "
|
||||
let a = 0;
|
||||
{
|
||||
let b = 42;
|
||||
{
|
||||
let c = 1;
|
||||
}
|
||||
let d = 2;
|
||||
}
|
||||
let e = 1;
|
||||
";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(17, 18)),
|
||||
(Instruction::define_local(0, 0, false), Span(13, 14)),
|
||||
(Instruction::load_constant(1, 2, false), Span(50, 52)),
|
||||
(Instruction::define_local(1, 1, false), Span(46, 47)),
|
||||
(Instruction::load_constant(2, 4, false), Span(92, 93)),
|
||||
(Instruction::define_local(2, 2, false), Span(88, 89)),
|
||||
(Instruction::load_constant(3, 6, false), Span(129, 130)),
|
||||
(Instruction::define_local(3, 3, false), Span(125, 126)),
|
||||
(Instruction::load_constant(4, 4, false), Span(158, 159)),
|
||||
(Instruction::define_local(4, 4, false), Span(154, 155)),
|
||||
(Instruction::r#return(false), Span(165, 165))
|
||||
],
|
||||
vec![
|
||||
Value::integer(0),
|
||||
Value::string("a"),
|
||||
Value::integer(42),
|
||||
Value::string("b"),
|
||||
Value::integer(1),
|
||||
Value::string("c"),
|
||||
Value::integer(2),
|
||||
Value::string("d"),
|
||||
Value::string("e"),
|
||||
],
|
||||
vec![
|
||||
Local::new(1, None, false, Scope::new(0, 0), 0),
|
||||
Local::new(3, None, false, Scope::new(1, 1), 1),
|
||||
Local::new(5, None, false, Scope::new(2, 2), 2),
|
||||
Local::new(7, None, false, Scope::new(1, 1), 3),
|
||||
Local::new(8, None, false, Scope::new(0, 0), 4),
|
||||
]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_block_scopes() {
|
||||
let source = "
|
||||
let a = 0;
|
||||
{
|
||||
let b = 42;
|
||||
{
|
||||
let c = 1;
|
||||
}
|
||||
let d = 2;
|
||||
}
|
||||
let q = 42;
|
||||
{
|
||||
let b = 42;
|
||||
{
|
||||
let c = 1;
|
||||
}
|
||||
let d = 2;
|
||||
}
|
||||
let e = 1;
|
||||
";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(17, 18)),
|
||||
(Instruction::define_local(0, 0, false), Span(13, 14)),
|
||||
(Instruction::load_constant(1, 2, false), Span(50, 52)),
|
||||
(Instruction::define_local(1, 1, false), Span(46, 47)),
|
||||
(Instruction::load_constant(2, 4, false), Span(92, 93)),
|
||||
(Instruction::define_local(2, 2, false), Span(88, 89)),
|
||||
(Instruction::load_constant(3, 6, false), Span(129, 130)),
|
||||
(Instruction::define_local(3, 3, false), Span(125, 126)),
|
||||
(Instruction::load_constant(4, 2, false), Span(158, 160)),
|
||||
(Instruction::define_local(4, 4, false), Span(154, 155)),
|
||||
(Instruction::load_constant(5, 2, false), Span(192, 194)),
|
||||
(Instruction::define_local(5, 5, false), Span(188, 189)),
|
||||
(Instruction::load_constant(6, 4, false), Span(234, 235)),
|
||||
(Instruction::define_local(6, 6, false), Span(230, 231)),
|
||||
(Instruction::load_constant(7, 6, false), Span(271, 272)),
|
||||
(Instruction::define_local(7, 7, false), Span(267, 268)),
|
||||
(Instruction::load_constant(8, 4, false), Span(300, 301)),
|
||||
(Instruction::define_local(8, 8, false), Span(296, 297)),
|
||||
(Instruction::r#return(false), Span(307, 307))
|
||||
],
|
||||
vec![
|
||||
Value::integer(0),
|
||||
Value::string("a"),
|
||||
Value::integer(42),
|
||||
Value::string("b"),
|
||||
Value::integer(1),
|
||||
Value::string("c"),
|
||||
Value::integer(2),
|
||||
Value::string("d"),
|
||||
Value::string("q"),
|
||||
Value::string("e"),
|
||||
],
|
||||
vec![
|
||||
Local::new(1, None, false, Scope::new(0, 0), 0),
|
||||
Local::new(3, None, false, Scope::new(1, 1), 1),
|
||||
Local::new(5, None, false, Scope::new(2, 2), 2),
|
||||
Local::new(7, None, false, Scope::new(1, 1), 3),
|
||||
Local::new(8, None, false, Scope::new(0, 0), 4),
|
||||
Local::new(3, None, false, Scope::new(1, 3), 5),
|
||||
Local::new(5, None, false, Scope::new(2, 4), 6),
|
||||
Local::new(7, None, false, Scope::new(1, 3), 7),
|
||||
Local::new(9, None, false, Scope::new(0, 0), 8),
|
||||
]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallow_access_to_child_scope() {
|
||||
let source = r#"
|
||||
{
|
||||
let x = 1;
|
||||
}
|
||||
x
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::VariableOutOfScope {
|
||||
identifier: "x".to_string(),
|
||||
position: Span(52, 53),
|
||||
variable_scope: Scope::new(1, 1),
|
||||
access_scope: Scope::new(0, 0),
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallow_access_to_child_scope_nested() {
|
||||
let source = r#"
|
||||
{
|
||||
{
|
||||
let x = 1;
|
||||
}
|
||||
x
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::VariableOutOfScope {
|
||||
identifier: "x".to_string(),
|
||||
position: Span(78, 79),
|
||||
variable_scope: Scope::new(2, 2),
|
||||
access_scope: Scope::new(1, 1),
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallow_access_to_sibling_scope() {
|
||||
let source = r#"
|
||||
{
|
||||
let x = 1;
|
||||
}
|
||||
{
|
||||
x
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::VariableOutOfScope {
|
||||
identifier: "x".to_string(),
|
||||
variable_scope: Scope::new(1, 1),
|
||||
access_scope: Scope::new(1, 2),
|
||||
position: Span(66, 67),
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallow_access_to_sibling_scope_nested() {
|
||||
let source = r#"
|
||||
{
|
||||
{
|
||||
let x = 1;
|
||||
}
|
||||
{
|
||||
x
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::VariableOutOfScope {
|
||||
identifier: "x".to_string(),
|
||||
variable_scope: Scope::new(2, 2),
|
||||
access_scope: Scope::new(2, 3),
|
||||
position: Span(96, 97),
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
42
dust-lang/tests/unary_operations.rs
Normal file
42
dust-lang/tests/unary_operations.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn negate() {
|
||||
let source = "-(42)";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(*Instruction::negate(0, 0).set_b_is_constant(), Span(0, 1)),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![Value::integer(42)],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(-42))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not() {
|
||||
let source = "!true";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_boolean(0, true, false), Span(1, 5)),
|
||||
(Instruction::not(1, 0), Span(0, 1)),
|
||||
(Instruction::r#return(true), Span(5, 5)),
|
||||
],
|
||||
vec![],
|
||||
vec![]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::boolean(false))));
|
||||
}
|
63
dust-lang/tests/variables.rs
Normal file
63
dust-lang/tests/variables.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use dust_lang::*;
|
||||
|
||||
#[test]
|
||||
fn define_local() {
|
||||
let source = "let x = 42;";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(8, 10)),
|
||||
(Instruction::define_local(0, 0, false), Span(4, 5)),
|
||||
(Instruction::r#return(false), Span(11, 11))
|
||||
],
|
||||
vec![Value::integer(42), Value::string("x")],
|
||||
vec![Local::new(1, None, false, Scope::default(), 0)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_statement_expects_identifier() {
|
||||
let source = "let 1 = 2";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Err(DustError::Compile {
|
||||
error: CompileError::ExpectedToken {
|
||||
expected: TokenKind::Identifier,
|
||||
found: Token::Integer("1").to_owned(),
|
||||
position: Span(4, 5)
|
||||
},
|
||||
source
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_local() {
|
||||
let source = "let mut x = 41; x = 42; x";
|
||||
|
||||
assert_eq!(
|
||||
compile(source),
|
||||
Ok(Chunk::with_data(
|
||||
None,
|
||||
vec![
|
||||
(Instruction::load_constant(0, 0, false), Span(12, 14)),
|
||||
(Instruction::define_local(0, 0, true), Span(8, 9)),
|
||||
(Instruction::load_constant(1, 2, false), Span(20, 22)),
|
||||
(Instruction::set_local(1, 0), Span(16, 17)),
|
||||
(Instruction::get_local(2, 0), Span(24, 25)),
|
||||
(Instruction::r#return(true), Span(25, 25)),
|
||||
],
|
||||
vec![Value::integer(41), Value::string("x"), Value::integer(42)],
|
||||
vec![Local::new(1, None, true, Scope::default(), 0)]
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(run(source), Ok(Some(Value::integer(42))));
|
||||
}
|
15
dust-shell/Cargo.toml
Normal file
15
dust-shell/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "dust-shell"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.14", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
dust-lang = { path = "../dust-lang" }
|
||||
env_logger = "0.11.5"
|
||||
log = "0.4.22"
|
138
dust-shell/src/main.rs
Normal file
138
dust-shell/src/main.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use std::{fs::read_to_string, io::Write};
|
||||
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use dust_lang::{compile, format, run};
|
||||
use log::{Level, LevelFilter};
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Cli {
|
||||
/// Source code sent via command line
|
||||
#[arg(short, long)]
|
||||
command: Option<String>,
|
||||
|
||||
/// Whether to output formatted source code
|
||||
#[arg(short, long)]
|
||||
format: bool,
|
||||
|
||||
/// Whether to output line numbers in formatted source code
|
||||
#[arg(long)]
|
||||
format_line_numbers: Option<bool>,
|
||||
|
||||
/// Whether to output colors in formatted source code
|
||||
#[arg(long)]
|
||||
format_colored: Option<bool>,
|
||||
|
||||
/// Whether to output the disassembled chunk
|
||||
#[arg(short, long)]
|
||||
parse: bool,
|
||||
|
||||
/// Whether to style the disassembled chunk
|
||||
#[arg(long)]
|
||||
style_disassembly: Option<bool>,
|
||||
|
||||
/// Log level
|
||||
#[arg(short, long)]
|
||||
log: Option<LevelFilter>,
|
||||
|
||||
/// Path to a source code file
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Cli::parse();
|
||||
let mut logger = env_logger::builder();
|
||||
|
||||
logger.format(|buf, record| {
|
||||
let level_display = match record.level() {
|
||||
Level::Info => "INFO".bold().white(),
|
||||
Level::Debug => "DEBUG".bold().blue(),
|
||||
Level::Warn => "WARN".bold().yellow(),
|
||||
Level::Error => "ERROR".bold().red(),
|
||||
Level::Trace => "TRACE".bold().purple(),
|
||||
};
|
||||
let module = record
|
||||
.module_path()
|
||||
.map(|path| path.split("::").last().unwrap_or(path))
|
||||
.unwrap_or("unknown")
|
||||
.dimmed();
|
||||
let display = format!("{level_display:5} {module:^6} {args}", args = record.args());
|
||||
|
||||
writeln!(buf, "{display}")
|
||||
});
|
||||
|
||||
if let Some(level) = args.log {
|
||||
logger.filter_level(level).init();
|
||||
} else {
|
||||
logger.parse_env("DUST_LOG").init();
|
||||
}
|
||||
|
||||
let source = if let Some(path) = &args.path {
|
||||
&read_to_string(path).expect("Failed to read file")
|
||||
} else if let Some(command) = &args.command {
|
||||
command
|
||||
} else {
|
||||
eprintln!("No input provided");
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
if args.format {
|
||||
let line_numbers = args.format_line_numbers.unwrap_or(true);
|
||||
let colored = args.format_colored.unwrap_or(true);
|
||||
|
||||
log::info!("Formatting source");
|
||||
|
||||
match format(source, line_numbers, colored) {
|
||||
Ok(formatted) => println!("{}", formatted),
|
||||
Err(error) => {
|
||||
eprintln!("{}", error.report());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.parse {
|
||||
let styled = args.style_disassembly.unwrap_or(true);
|
||||
|
||||
log::info!("Parsing source");
|
||||
|
||||
match compile(source) {
|
||||
Ok(chunk) => {
|
||||
let disassembly = chunk
|
||||
.disassembler()
|
||||
.source(source)
|
||||
.styled(styled)
|
||||
.disassemble();
|
||||
|
||||
println!("{}", disassembly);
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("{}", error.report());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.format || args.parse {
|
||||
return;
|
||||
}
|
||||
|
||||
match run(source) {
|
||||
Ok(Some(value)) => println!("{}", value),
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
eprintln!("{}", error.report());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::CommandFactory;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
Cli::command().debug_assert();
|
||||
}
|
||||
}
|
5
examples/assets/count.js
Normal file
5
examples/assets/count.js
Normal file
@ -0,0 +1,5 @@
|
||||
var i = 0;
|
||||
|
||||
while (i < 10000) {
|
||||
i++;
|
||||
}
|
6
examples/assets/data.json
Normal file
6
examples/assets/data.json
Normal file
@ -0,0 +1,6 @@
|
||||
[
|
||||
{ "name": "Sammy", "type": "shark", "clams": 5 },
|
||||
{ "name": "Bubbles", "type": "orca", "clams": 3 },
|
||||
{ "name": "Splish", "type": "dolphin", "clams": 2 },
|
||||
{ "name": "Splash", "type": "dolphin", "clams": 2 }
|
||||
]
|
11
examples/assets/fibonacci.js
Normal file
11
examples/assets/fibonacci.js
Normal file
@ -0,0 +1,11 @@
|
||||
function fib(n) {
|
||||
if (n <= 0) {
|
||||
return 0;
|
||||
} else if (n === 1) {
|
||||
return 1;
|
||||
} else {
|
||||
return fib(n - 1) + fib(n - 2);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(fib(25));
|
@ -1,19 +0,0 @@
|
||||
create_random_numbers = (count <int>) <none> {
|
||||
numbers = []
|
||||
|
||||
while length(numbers) < count {
|
||||
numbers += random:integer()
|
||||
}
|
||||
|
||||
output("Made " + length(numbers) + " numbers.")
|
||||
}
|
||||
|
||||
output("This will print first.")
|
||||
|
||||
async {
|
||||
create_random_numbers(1000)
|
||||
create_random_numbers(100)
|
||||
create_random_numbers(10)
|
||||
}
|
||||
|
||||
output("This will print last.")
|
@ -1,17 +0,0 @@
|
||||
async {
|
||||
{
|
||||
^echo 'Starting 1...'
|
||||
^sleep 1
|
||||
^echo 'Finished 1.'
|
||||
}
|
||||
{
|
||||
^echo 'Starting 2...'
|
||||
^sleep 2
|
||||
^echo 'Finished 2.'
|
||||
}
|
||||
{
|
||||
^echo 'Starting 3...'
|
||||
^sleep 3
|
||||
^echo 'Finished 3.'
|
||||
}
|
||||
}
|
21
examples/async_count.ds
Normal file
21
examples/async_count.ds
Normal file
@ -0,0 +1,21 @@
|
||||
count_slowly = fn (
|
||||
multiplier: int,
|
||||
) {
|
||||
i = 0
|
||||
|
||||
while i < 10 {
|
||||
sleep_time = i * multiplier;
|
||||
|
||||
thread.sleep(sleep_time)
|
||||
thread.write_line(i as str)
|
||||
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
async {
|
||||
count_slowly(50)
|
||||
count_slowly(100)
|
||||
count_slowly(200)
|
||||
count_slowly(250)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
cast_len = 0
|
||||
characters_len = 0
|
||||
episodes_len = 0
|
||||
|
||||
async {
|
||||
{
|
||||
cast = download("https://api.sampleapis.com/futurama/cast")
|
||||
cast_len = length(from_json(cast))
|
||||
}
|
||||
{
|
||||
characters = download("https://api.sampleapis.com/futurama/characters")
|
||||
characters_len = length(from_json(characters))
|
||||
}
|
||||
{
|
||||
episodes = download("https://api.sampleapis.com/futurama/episodes")
|
||||
episodes_len = length(from_json(episodes))
|
||||
}
|
||||
}
|
||||
|
||||
output ([cast_len, characters_len, episodes_len])
|
@ -1,53 +0,0 @@
|
||||
cards = {
|
||||
rooms = ['Library' 'Kitchen' 'Conservatory']
|
||||
suspects = ['White' 'Green' 'Scarlett']
|
||||
weapons = ['Rope' 'Lead_Pipe' 'Knife']
|
||||
}
|
||||
|
||||
is_ready_to_solve = (cards <map>) <bool> {
|
||||
(length(cards:suspects) == 1)
|
||||
&& (length(cards:rooms) == 1)
|
||||
&& (length(cards:weapons) == 1)
|
||||
}
|
||||
|
||||
remove_card = (cards <map>, opponent_card <str>) <none> {
|
||||
cards:rooms -= opponent_card
|
||||
cards:suspects -= opponent_card
|
||||
cards:weapons -= opponent_card
|
||||
}
|
||||
|
||||
make_guess = (cards <map>, current_room <str>) <none> {
|
||||
if is_ready_to_solve(cards) {
|
||||
output(
|
||||
'I accuse '
|
||||
+ cards:suspects:0
|
||||
+ ' in the '
|
||||
+ cards:rooms:0
|
||||
+ ' with the '
|
||||
+ cards:weapons:0
|
||||
+ '!'
|
||||
)
|
||||
} else {
|
||||
output(
|
||||
'I question '
|
||||
+ random:from(cards:suspects)
|
||||
+ ' in the '
|
||||
+ current_room
|
||||
+ ' with the '
|
||||
+ random:from(cards:weapons)
|
||||
+ '.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
take_turn = (cards <map>, opponent_card <str>, current_room <str>) <none> {
|
||||
remove_card(cards opponent_card)
|
||||
make_guess(cards current_room)
|
||||
}
|
||||
|
||||
take_turn(cards 'Rope' 'Kitchen')
|
||||
take_turn(cards 'Library' 'Kitchen')
|
||||
take_turn(cards 'Conservatory' 'Kitchen')
|
||||
take_turn(cards 'White' 'Kitchen')
|
||||
take_turn(cards 'Green' 'Kitchen')
|
||||
take_turn(cards 'Knife' 'Kitchen')
|
5
examples/count.ds
Normal file
5
examples/count.ds
Normal file
@ -0,0 +1,5 @@
|
||||
let mut i = 0;
|
||||
|
||||
while i < 10000 {
|
||||
i += 1;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
raw_data = download("https://api.sampleapis.com/futurama/cast")
|
||||
cast_data = from_json(raw_data)
|
||||
|
||||
names = []
|
||||
|
||||
for cast_member in cast_data {
|
||||
names += cast_member:name
|
||||
}
|
||||
|
||||
assert_equal("Billy West", names:0)
|
@ -1,9 +1,8 @@
|
||||
fib = (i <int>) <int> {
|
||||
if i <= 1 {
|
||||
1
|
||||
} else {
|
||||
fib(i - 1) + fib(i - 2)
|
||||
}
|
||||
fn fib (n: int) -> int {
|
||||
if n <= 0 { return 0 }
|
||||
if n == 1 { return 1 }
|
||||
|
||||
fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
fib(8)
|
||||
write_line(fib(25))
|
||||
|
@ -1,18 +1,20 @@
|
||||
count = 1
|
||||
let mut count = 1
|
||||
|
||||
while count <= 15 {
|
||||
divides_by_3 = count % 3 == 0
|
||||
divides_by_5 = count % 5 == 0
|
||||
let divides_by_3 = count % 3 == 0
|
||||
let divides_by_5 = count % 5 == 0
|
||||
|
||||
if divides_by_3 && divides_by_5 {
|
||||
output('fizzbuzz')
|
||||
} else if divides_by_3 {
|
||||
output('fizz')
|
||||
let output = if divides_by_3 && divides_by_5 {
|
||||
"fizzbuzz"
|
||||
} else if divides_by_3 {
|
||||
"fizz"
|
||||
} else if divides_by_5 {
|
||||
output('buzz')
|
||||
} else {
|
||||
output(count)
|
||||
"buzz"
|
||||
} else {
|
||||
to_string(count)
|
||||
}
|
||||
|
||||
|
||||
write_line(output)
|
||||
|
||||
count += 1
|
||||
}
|
||||
|
19
examples/guessing_game.ds
Normal file
19
examples/guessing_game.ds
Normal file
@ -0,0 +1,19 @@
|
||||
write_line("Guess the number.")
|
||||
|
||||
let secret_number = random(0..100);
|
||||
|
||||
loop {
|
||||
write_line("Input your guess.")
|
||||
|
||||
let input = io.read_line();
|
||||
let guess = int.parse(input);
|
||||
|
||||
if guess < secret_number {
|
||||
io.write_line("Too low!")
|
||||
} else if guess > secret_number {
|
||||
io.write_line("Too high!")
|
||||
} else {
|
||||
io.write_line("You win!")
|
||||
break
|
||||
}
|
||||
}
|
@ -1 +1,6 @@
|
||||
output('Hello, world!')
|
||||
write_line("Hello, world!")
|
||||
write_line("Enter your name...")
|
||||
|
||||
let name = read_line()
|
||||
|
||||
write_line("Hello " + name + "!")
|
||||
|
@ -1,12 +0,0 @@
|
||||
data = json:parse(fs:read_file('examples/assets/jq_data.json'))
|
||||
|
||||
new_data = []
|
||||
|
||||
for commit_data in data {
|
||||
new_data += {
|
||||
message = commit_data:commit:message
|
||||
name = commit_data:commit:committer:name
|
||||
}
|
||||
}
|
||||
|
||||
new_data
|
4
examples/json_length.ds
Normal file
4
examples/json_length.ds
Normal file
@ -0,0 +1,4 @@
|
||||
input = fs.read_file('examples/assets/data.json')
|
||||
data = json.parse(input)
|
||||
|
||||
length(data)
|
@ -1,13 +0,0 @@
|
||||
stuff = [
|
||||
random:integer()
|
||||
random:integer()
|
||||
random:integer()
|
||||
random:float()
|
||||
random:float()
|
||||
random:float()
|
||||
random:boolean()
|
||||
random:boolean()
|
||||
random:boolean()
|
||||
]
|
||||
|
||||
random:from(stuff)
|
@ -1,19 +0,0 @@
|
||||
raw_data = fs:read_file('examples/assets/seaCreatures.json')
|
||||
sea_creatures = json:parse(raw_data)
|
||||
|
||||
data = {
|
||||
creatures = []
|
||||
total_clams = 0
|
||||
dolphin_clams = 0
|
||||
}
|
||||
|
||||
for creature in sea_creatures {
|
||||
data:creatures += creature:name
|
||||
data:total_clams += creature:clams
|
||||
|
||||
if creature:type == 'dolphin' {
|
||||
data:dolphin_clams += creature:clams
|
||||
}
|
||||
}
|
||||
|
||||
data
|
20
examples/type_inference.ds
Normal file
20
examples/type_inference.ds
Normal file
@ -0,0 +1,20 @@
|
||||
// This function returns its argument.
|
||||
foo = fn <T>(x: T) -> T { x }
|
||||
|
||||
// Use turbofish to supply type information.
|
||||
bar = foo::<str>("hi")
|
||||
|
||||
// Use type annotation
|
||||
baz: str = foo("hi")
|
||||
|
||||
// The `json.parse` function takes a string and returns the specified type
|
||||
|
||||
// Use turbofish
|
||||
x = json.parse::<int>("1")
|
||||
|
||||
// Use type annotation
|
||||
x: int = json.parse("1")
|
||||
|
||||
x: int = {
|
||||
json.parse("1")
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#!/usr/bin/fish
|
||||
# This script is has the following prerequisites (aside from fish):
|
||||
# - hyperfine
|
||||
# - dust (can be installed with "cargo install dust-lang")
|
||||
# - jq
|
||||
# - nodejs
|
||||
# - nushell
|
||||
# - dielectron.json (can be downloaded from https://opendata.cern.ch/record/304)
|
||||
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--parameter-list data_path examples/assets/seaCreatures.json \
|
||||
--warmup 3 \
|
||||
"dust -c 'length(json:parse(input))' -p {data_path}" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
||||
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--parameter-list data_path examples/assets/jq_data.json \
|
||||
--warmup 3 \
|
||||
"dust -c 'length(json:parse(input))' -p {data_path}" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
||||
|
||||
hyperfine \
|
||||
--shell none \
|
||||
--parameter-list data_path dielectron.json \
|
||||
--warmup 3 \
|
||||
"dust -c 'length(json:parse(input))' -p {data_path}" \
|
||||
"jq 'length' {data_path}" \
|
||||
"node --eval \"require('node:fs').readFile('{data_path}', (err, data)=>{console.log(JSON.parse(data).length)})\"" \
|
||||
"nu -c 'open {data_path} | length'"
|
@ -1,8 +0,0 @@
|
||||
#!/usr/bin/fish
|
||||
# Build the project in debug mode.
|
||||
|
||||
cd tree-sitter-dust/
|
||||
tree-sitter generate --debug-build --no-bindings
|
||||
|
||||
cd ..
|
||||
cargo build
|
@ -1,8 +0,0 @@
|
||||
#!/bin/fish
|
||||
# Build the project in release mode.
|
||||
|
||||
cd tree-sitter-dust/
|
||||
tree-sitter generate --no-bindings
|
||||
|
||||
cd ..
|
||||
cargo build --release
|
@ -1,9 +0,0 @@
|
||||
#!/usr/bin/fish
|
||||
# Build the project in debug mode.
|
||||
|
||||
cd tree-sitter-dust/
|
||||
tree-sitter generate --debug-build --no-bindings
|
||||
tree-sitter test
|
||||
|
||||
cd ..
|
||||
cargo test
|
@ -1,180 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, AssignmentOperator, Format, Identifier, Map, SourcePosition, Statement,
|
||||
SyntaxNode, Type, TypeSpecification, Value,
|
||||
};
|
||||
|
||||
/// Variable assignment, including add-assign and subtract-assign operations.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Assignment {
|
||||
identifier: Identifier,
|
||||
type_specification: Option<TypeSpecification>,
|
||||
operator: AssignmentOperator,
|
||||
statement: Statement,
|
||||
|
||||
syntax_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for Assignment {
|
||||
fn from_syntax(
|
||||
syntax_node: SyntaxNode,
|
||||
source: &str,
|
||||
context: &Map,
|
||||
) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "assignment", syntax_node)?;
|
||||
|
||||
let child_count = syntax_node.child_count();
|
||||
|
||||
let identifier_node = syntax_node.child(0).unwrap();
|
||||
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||
|
||||
let type_node = syntax_node.child(1).unwrap();
|
||||
let type_specification = if type_node.kind() == "type_specification" {
|
||||
Some(TypeSpecification::from_syntax(type_node, source, context)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let operator_node = syntax_node.child(child_count - 2).unwrap();
|
||||
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
|
||||
|
||||
let statement_node = syntax_node.child(child_count - 1).unwrap();
|
||||
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||
|
||||
Ok(Assignment {
|
||||
identifier,
|
||||
type_specification,
|
||||
operator,
|
||||
statement,
|
||||
syntax_position: syntax_node.range().into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(&self, source: &str, context: &Map) -> Result<(), ValidationError> {
|
||||
if let AssignmentOperator::Equal = self.operator {
|
||||
let key = self.identifier.inner().clone();
|
||||
let r#type = if let Some(definition) = &self.type_specification {
|
||||
definition.inner().clone()
|
||||
} else {
|
||||
self.statement.expected_type(context)?
|
||||
};
|
||||
|
||||
context.set_type(key, r#type)?;
|
||||
}
|
||||
|
||||
if let Some(type_specification) = &self.type_specification {
|
||||
match self.operator {
|
||||
AssignmentOperator::Equal => {
|
||||
let expected = type_specification.inner();
|
||||
let actual = self.statement.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: expected.clone(),
|
||||
actual,
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
}
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Type::List(expected) = type_specification.inner() {
|
||||
let actual = self.identifier.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: expected.as_ref().clone(),
|
||||
actual,
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let expected = type_specification.inner();
|
||||
let actual = self.identifier.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: expected.clone(),
|
||||
actual,
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
AssignmentOperator::MinusEqual => todo!(),
|
||||
}
|
||||
} else {
|
||||
match self.operator {
|
||||
AssignmentOperator::Equal => {}
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Type::List(expected) = self.identifier.expected_type(context)? {
|
||||
let actual = self.statement.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: expected.as_ref().clone(),
|
||||
actual,
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
AssignmentOperator::MinusEqual => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
self.statement.validate(source, context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let key = self.identifier.inner();
|
||||
let value = self.statement.run(source, context)?;
|
||||
|
||||
let new_value = match self.operator {
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Some((mut previous_value, _)) = context.variables()?.get(key).cloned() {
|
||||
previous_value += value;
|
||||
previous_value
|
||||
} else {
|
||||
return Err(RuntimeError::VariableIdentifierNotFound(key.clone()));
|
||||
}
|
||||
}
|
||||
AssignmentOperator::MinusEqual => {
|
||||
if let Some((mut previous_value, _)) = context.variables()?.get(key).cloned() {
|
||||
previous_value -= value;
|
||||
previous_value
|
||||
} else {
|
||||
return Err(RuntimeError::VariableIdentifierNotFound(key.clone()));
|
||||
}
|
||||
}
|
||||
AssignmentOperator::Equal => value,
|
||||
};
|
||||
|
||||
context.set(key.clone(), new_value)?;
|
||||
|
||||
Ok(Value::none())
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Assignment {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
self.identifier.format(output, indent_level);
|
||||
|
||||
if let Some(type_specification) = &self.type_specification {
|
||||
type_specification.format(output, indent_level);
|
||||
}
|
||||
|
||||
output.push(' ');
|
||||
self.operator.format(output, indent_level);
|
||||
output.push(' ');
|
||||
|
||||
self.statement.format(output, 0);
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Operators that be used in an assignment statement.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum AssignmentOperator {
|
||||
Equal,
|
||||
PlusEqual,
|
||||
MinusEqual,
|
||||
}
|
||||
|
||||
impl AbstractTree for AssignmentOperator {
|
||||
fn from_syntax(
|
||||
node: SyntaxNode,
|
||||
source: &str,
|
||||
_context: &crate::Map,
|
||||
) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "assignment_operator", node)?;
|
||||
|
||||
let operator_node = node.child(0).unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"=" => AssignmentOperator::Equal,
|
||||
"+=" => AssignmentOperator::PlusEqual,
|
||||
"-=" => AssignmentOperator::MinusEqual,
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "=, += or -=".to_string(),
|
||||
actual: operator_node.kind().to_string(),
|
||||
location: operator_node.start_position(),
|
||||
relevant_source: source[operator_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(operator)
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
Ok(Value::none())
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for AssignmentOperator {
|
||||
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||
match self {
|
||||
AssignmentOperator::Equal => output.push('='),
|
||||
AssignmentOperator::PlusEqual => output.push_str("+="),
|
||||
AssignmentOperator::MinusEqual => output.push_str("-="),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{rw_lock_error::RwLockError, RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Map, Statement, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a block.
|
||||
///
|
||||
/// A block is almost identical to the root except that it must have curly
|
||||
/// braces and can optionally be asynchronous. A block evaluates to the value of
|
||||
/// its final statement but an async block will short-circuit if a statement
|
||||
/// results in an error. Note that this will be the first statement to encounter
|
||||
/// an error at runtime, not necessarilly the first statement as they are
|
||||
/// written.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Block {
|
||||
is_async: bool,
|
||||
statements: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl AbstractTree for Block {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "block", node)?;
|
||||
|
||||
let first_child = node.child(0).unwrap();
|
||||
let is_async = first_child.kind() == "async";
|
||||
|
||||
let statement_count = if is_async {
|
||||
node.child_count() - 3
|
||||
} else {
|
||||
node.child_count() - 2
|
||||
};
|
||||
let mut statements = Vec::with_capacity(statement_count);
|
||||
|
||||
for index in 1..node.child_count() - 1 {
|
||||
let child_node = node.child(index).unwrap();
|
||||
|
||||
if child_node.kind() == "statement" {
|
||||
let statement = Statement::from_syntax(child_node, source, context)?;
|
||||
|
||||
statements.push(statement);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Block {
|
||||
is_async,
|
||||
statements,
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.validate(_source, _context);
|
||||
} else {
|
||||
statement.validate(_source, _context)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
if self.is_async {
|
||||
let statements = &self.statements;
|
||||
let final_result = RwLock::new(Ok(Value::none()));
|
||||
|
||||
statements
|
||||
.into_par_iter()
|
||||
.enumerate()
|
||||
.find_map_first(|(index, statement)| {
|
||||
let result = statement.run(source, context);
|
||||
let is_last_statement = index == statements.len() - 1;
|
||||
let is_return_statement = if let Statement::Return(_) = statement {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if is_return_statement || result.is_err() {
|
||||
Some(result)
|
||||
} else if is_last_statement {
|
||||
let get_write_lock = final_result.write();
|
||||
|
||||
match get_write_lock {
|
||||
Ok(mut final_result) => {
|
||||
*final_result = result;
|
||||
None
|
||||
}
|
||||
Err(_error) => Some(Err(RuntimeError::RwLock(RwLockError))),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(final_result.into_inner().map_err(|_| RwLockError)?)
|
||||
} else {
|
||||
let mut prev_result = None;
|
||||
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.run(source, context);
|
||||
}
|
||||
|
||||
prev_result = Some(statement.run(source, context));
|
||||
}
|
||||
|
||||
prev_result.unwrap_or(Ok(Value::none()))
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
if let Some(statement) = self.statements.iter().find(|statement| {
|
||||
if let Statement::Return(_) = statement {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
statement.expected_type(context)
|
||||
} else if let Some(statement) = self.statements.last() {
|
||||
statement.expected_type(context)
|
||||
} else {
|
||||
Ok(Type::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Block {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
if self.is_async {
|
||||
output.push_str("async {\n");
|
||||
} else {
|
||||
output.push_str("{\n");
|
||||
}
|
||||
|
||||
for (index, statement) in self.statements.iter().enumerate() {
|
||||
if index > 0 {
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
statement.format(output, indent_level + 1);
|
||||
}
|
||||
|
||||
output.push('\n');
|
||||
Block::indent(output, indent_level);
|
||||
output.push('}');
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
use std::{collections::BTreeMap, env::args, sync::OnceLock};
|
||||
|
||||
use enum_iterator::{all, Sequence};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
built_in_functions::{fs::fs_functions, json::json_functions, str::string_functions, Callable},
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, BuiltInFunction, Format, Function, List, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
static ARGS: OnceLock<Value> = OnceLock::new();
|
||||
static FS: OnceLock<Value> = OnceLock::new();
|
||||
static JSON: OnceLock<Value> = OnceLock::new();
|
||||
static RANDOM: OnceLock<Value> = OnceLock::new();
|
||||
static STRING: OnceLock<Value> = OnceLock::new();
|
||||
|
||||
/// Returns the entire built-in value API.
|
||||
pub fn built_in_values() -> impl Iterator<Item = BuiltInValue> {
|
||||
all()
|
||||
}
|
||||
|
||||
/// A variable with a hard-coded key that is globally available.
|
||||
#[derive(Sequence, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum BuiltInValue {
|
||||
/// The arguments used to launch the current program.
|
||||
Args,
|
||||
|
||||
/// Create an error if two values are not equal.
|
||||
AssertEqual,
|
||||
|
||||
/// File system tools.
|
||||
Fs,
|
||||
|
||||
/// JSON format tools.
|
||||
Json,
|
||||
|
||||
/// Get the length of a collection.
|
||||
Length,
|
||||
|
||||
/// Print a value to stdout.
|
||||
Output,
|
||||
|
||||
/// Random value generators.
|
||||
Random,
|
||||
|
||||
/// String utilities.
|
||||
Str,
|
||||
}
|
||||
|
||||
impl BuiltInValue {
|
||||
/// Returns the hard-coded key used to identify the value.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
BuiltInValue::Args => "args",
|
||||
BuiltInValue::AssertEqual => "assert_equal",
|
||||
BuiltInValue::Fs => "fs",
|
||||
BuiltInValue::Json => "json",
|
||||
BuiltInValue::Length => BuiltInFunction::Length.name(),
|
||||
BuiltInValue::Output => "output",
|
||||
BuiltInValue::Random => "random",
|
||||
BuiltInValue::Str => "str",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a brief description of the value's features.
|
||||
///
|
||||
/// This is used by the shell when suggesting completions.
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
BuiltInValue::Args => "The command line arguments sent to this program.",
|
||||
BuiltInValue::AssertEqual => "Error if the two values are not equal.",
|
||||
BuiltInValue::Fs => "File and directory tools.",
|
||||
BuiltInValue::Json => "JSON formatting tools.",
|
||||
BuiltInValue::Length => BuiltInFunction::Length.description(),
|
||||
BuiltInValue::Output => "output",
|
||||
BuiltInValue::Random => "random",
|
||||
BuiltInValue::Str => "string",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value's type.
|
||||
///
|
||||
/// This is checked with a unit test to ensure it matches the value.
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
BuiltInValue::Args => Type::list(Type::String),
|
||||
BuiltInValue::AssertEqual => BuiltInFunction::AssertEqual.r#type(),
|
||||
BuiltInValue::Fs => Type::Map(None),
|
||||
BuiltInValue::Json => Type::Map(None),
|
||||
BuiltInValue::Length => BuiltInFunction::Length.r#type(),
|
||||
BuiltInValue::Output => BuiltInFunction::Output.r#type(),
|
||||
BuiltInValue::Random => Type::Map(None),
|
||||
BuiltInValue::Str => Type::Map(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value by creating it or, if it has already been accessed, retrieving it from its
|
||||
/// [OnceLock][].
|
||||
pub fn get(&self) -> &Value {
|
||||
match self {
|
||||
BuiltInValue::Args => ARGS.get_or_init(|| {
|
||||
let args = args().map(|arg| Value::string(arg.to_string())).collect();
|
||||
|
||||
Value::List(List::with_items(args))
|
||||
}),
|
||||
BuiltInValue::AssertEqual => {
|
||||
&Value::Function(Function::BuiltIn(BuiltInFunction::AssertEqual))
|
||||
}
|
||||
BuiltInValue::Fs => FS.get_or_init(|| {
|
||||
let mut fs_context = BTreeMap::new();
|
||||
|
||||
for fs_function in fs_functions() {
|
||||
let key = fs_function.name().to_string();
|
||||
let value =
|
||||
Value::Function(Function::BuiltIn(BuiltInFunction::Fs(fs_function)));
|
||||
let r#type = value.r#type();
|
||||
|
||||
fs_context.insert(key, (value, r#type));
|
||||
}
|
||||
|
||||
Value::Map(Map::with_variables(fs_context))
|
||||
}),
|
||||
BuiltInValue::Json => JSON.get_or_init(|| {
|
||||
let mut json_context = BTreeMap::new();
|
||||
|
||||
for json_function in json_functions() {
|
||||
let key = json_function.name().to_string();
|
||||
let value =
|
||||
Value::Function(Function::BuiltIn(BuiltInFunction::Json(json_function)));
|
||||
let r#type = value.r#type();
|
||||
|
||||
json_context.insert(key, (value, r#type));
|
||||
}
|
||||
|
||||
Value::Map(Map::with_variables(json_context))
|
||||
}),
|
||||
BuiltInValue::Length => &Value::Function(Function::BuiltIn(BuiltInFunction::Length)),
|
||||
BuiltInValue::Output => &Value::Function(Function::BuiltIn(BuiltInFunction::Output)),
|
||||
BuiltInValue::Random => RANDOM.get_or_init(|| {
|
||||
let mut random_context = BTreeMap::new();
|
||||
|
||||
for built_in_function in [
|
||||
BuiltInFunction::RandomBoolean,
|
||||
BuiltInFunction::RandomFloat,
|
||||
BuiltInFunction::RandomFrom,
|
||||
BuiltInFunction::RandomInteger,
|
||||
] {
|
||||
let key = built_in_function.name().to_string();
|
||||
let value = Value::Function(Function::BuiltIn(built_in_function));
|
||||
let r#type = built_in_function.r#type();
|
||||
|
||||
random_context.insert(key, (value, r#type));
|
||||
}
|
||||
|
||||
Value::Map(Map::with_variables(random_context))
|
||||
}),
|
||||
BuiltInValue::Str => STRING.get_or_init(|| {
|
||||
let mut string_context = BTreeMap::new();
|
||||
|
||||
for string_function in string_functions() {
|
||||
let key = string_function.name().to_string();
|
||||
let value = Value::Function(Function::BuiltIn(BuiltInFunction::String(
|
||||
string_function,
|
||||
)));
|
||||
let r#type = string_function.r#type();
|
||||
|
||||
string_context.insert(key, (value, r#type));
|
||||
}
|
||||
|
||||
Value::Map(Map::with_variables(string_context))
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for BuiltInValue {
|
||||
fn from_syntax(node: SyntaxNode, _source: &str, _context: &Map) -> Result<Self, SyntaxError> {
|
||||
let built_in_value = match node.kind() {
|
||||
"args" => BuiltInValue::Args,
|
||||
"assert_equal" => BuiltInValue::AssertEqual,
|
||||
"fs" => BuiltInValue::Fs,
|
||||
"json" => BuiltInValue::Json,
|
||||
"length" => BuiltInValue::Length,
|
||||
"output" => BuiltInValue::Output,
|
||||
"random" => BuiltInValue::Random,
|
||||
"str" => BuiltInValue::Str,
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
Ok(built_in_value)
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(self.r#type())
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
Ok(self.get().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for BuiltInValue {
|
||||
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||
output.push_str(&self.get().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::built_in_values;
|
||||
|
||||
#[test]
|
||||
fn check_built_in_types() {
|
||||
for built_in_value in built_in_values() {
|
||||
let expected = built_in_value.r#type();
|
||||
let actual = built_in_value.get().r#type();
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
use std::process;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Map, Type, Value,
|
||||
};
|
||||
|
||||
/// An external program invokation.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Command {
|
||||
command_text: String,
|
||||
command_arguments: Vec<String>,
|
||||
}
|
||||
|
||||
impl AbstractTree for Command {
|
||||
fn from_syntax(
|
||||
node: tree_sitter::Node,
|
||||
source: &str,
|
||||
_context: &crate::Map,
|
||||
) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "command", node)?;
|
||||
|
||||
let command_text_node = node.child(1).unwrap();
|
||||
let command_text = source[command_text_node.byte_range()].to_string();
|
||||
|
||||
let mut command_arguments = Vec::new();
|
||||
|
||||
for index in 2..node.child_count() {
|
||||
let text_node = node.child(index).unwrap();
|
||||
let mut text = source[text_node.byte_range()].to_string();
|
||||
|
||||
if (text.starts_with('\'') && text.ends_with('\''))
|
||||
|| (text.starts_with('"') && text.ends_with('"'))
|
||||
|| (text.starts_with('`') && text.ends_with('`'))
|
||||
{
|
||||
text = text[1..text.len() - 1].to_string();
|
||||
}
|
||||
|
||||
command_arguments.push(text);
|
||||
}
|
||||
|
||||
Ok(Command {
|
||||
command_text,
|
||||
command_arguments,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::String)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
let output = process::Command::new(&self.command_text)
|
||||
.args(&self.command_arguments)
|
||||
.spawn()?
|
||||
.wait_with_output()?
|
||||
.stdout;
|
||||
let string = String::from_utf8(output)?;
|
||||
|
||||
Ok(Value::String(string))
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Command {
|
||||
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||
todo!()
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
value_node::ValueNode,
|
||||
AbstractTree, Command, Format, FunctionCall, Identifier, Index, Logic, Map, Math, New,
|
||||
SyntaxNode, Type, Value, Yield,
|
||||
};
|
||||
|
||||
/// Abstract representation of an expression statement.
|
||||
///
|
||||
/// Unlike statements, which can involve complex logic, an expression is
|
||||
/// expected to evaluate to a value. However, an expression can still contain
|
||||
/// nested statements and may evaluate to an empty value.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Expression {
|
||||
Value(ValueNode),
|
||||
Identifier(Identifier),
|
||||
Index(Box<Index>),
|
||||
Math(Box<Math>),
|
||||
Logic(Box<Logic>),
|
||||
FunctionCall(Box<FunctionCall>),
|
||||
Yield(Box<Yield>),
|
||||
New(New),
|
||||
Command(Command),
|
||||
}
|
||||
|
||||
impl AbstractTree for Expression {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "expression", node)?;
|
||||
|
||||
let child = if node.child(0).unwrap().is_named() {
|
||||
node.child(0).unwrap()
|
||||
} else {
|
||||
node.child(1).unwrap()
|
||||
};
|
||||
|
||||
let expression = match child.kind() {
|
||||
"value" => Expression::Value(ValueNode::from_syntax(child, source, _context)?),
|
||||
"identifier" => {
|
||||
Expression::Identifier(Identifier::from_syntax(child, source, _context)?)
|
||||
}
|
||||
"index" => Expression::Index(Box::new(Index::from_syntax(child, source, _context)?)),
|
||||
"math" => Expression::Math(Box::new(Math::from_syntax(child, source, _context)?)),
|
||||
"logic" => Expression::Logic(Box::new(Logic::from_syntax(child, source, _context)?)),
|
||||
"function_call" => Expression::FunctionCall(Box::new(FunctionCall::from_syntax(
|
||||
child, source, _context,
|
||||
)?)),
|
||||
"yield" => Expression::Yield(Box::new(Yield::from_syntax(child, source, _context)?)),
|
||||
"new" => Expression::New(New::from_syntax(child, source, _context)?),
|
||||
"command" => Expression::Command(Command::from_syntax(child, source, _context)?),
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected:
|
||||
"value, identifier, index, math, logic, function call, new, context or ->"
|
||||
.to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expression)
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
match self {
|
||||
Expression::Value(value_node) => value_node.expected_type(_context),
|
||||
Expression::Identifier(identifier) => identifier.expected_type(_context),
|
||||
Expression::Math(math) => math.expected_type(_context),
|
||||
Expression::Logic(logic) => logic.expected_type(_context),
|
||||
Expression::FunctionCall(function_call) => function_call.expected_type(_context),
|
||||
Expression::Index(index) => index.expected_type(_context),
|
||||
Expression::Yield(r#yield) => r#yield.expected_type(_context),
|
||||
Expression::New(new) => new.expected_type(_context),
|
||||
Expression::Command(command) => command.expected_type(_context),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
match self {
|
||||
Expression::Value(value_node) => value_node.validate(_source, _context),
|
||||
Expression::Identifier(identifier) => identifier.validate(_source, _context),
|
||||
Expression::Math(math) => math.validate(_source, _context),
|
||||
Expression::Logic(logic) => logic.validate(_source, _context),
|
||||
Expression::FunctionCall(function_call) => function_call.validate(_source, _context),
|
||||
Expression::Index(index) => index.validate(_source, _context),
|
||||
Expression::Yield(r#yield) => r#yield.validate(_source, _context),
|
||||
Expression::New(new) => new.validate(_source, _context),
|
||||
Expression::Command(command) => command.validate(_source, _context),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
Expression::Value(value_node) => value_node.run(_source, _context),
|
||||
Expression::Identifier(identifier) => identifier.run(_source, _context),
|
||||
Expression::Math(math) => math.run(_source, _context),
|
||||
Expression::Logic(logic) => logic.run(_source, _context),
|
||||
Expression::FunctionCall(function_call) => function_call.run(_source, _context),
|
||||
Expression::Index(index) => index.run(_source, _context),
|
||||
Expression::Yield(r#yield) => r#yield.run(_source, _context),
|
||||
Expression::New(new) => new.run(_source, _context),
|
||||
Expression::Command(command) => command.run(_source, _context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Expression {
|
||||
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||
match self {
|
||||
Expression::Value(value_node) => value_node.format(_output, _indent_level),
|
||||
Expression::Identifier(identifier) => identifier.format(_output, _indent_level),
|
||||
Expression::Math(math) => math.format(_output, _indent_level),
|
||||
Expression::Logic(logic) => logic.format(_output, _indent_level),
|
||||
Expression::FunctionCall(function_call) => function_call.format(_output, _indent_level),
|
||||
Expression::Index(index) => index.format(_output, _indent_level),
|
||||
Expression::Yield(r#yield) => r#yield.format(_output, _indent_level),
|
||||
Expression::New(new) => new.format(_output, _indent_level),
|
||||
Expression::Command(command) => command.format(_output, _indent_level),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Block, Expression, Format, Identifier, Map, SourcePosition, SyntaxNode, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a for loop statement.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct For {
|
||||
is_async: bool,
|
||||
item_id: Identifier,
|
||||
collection: Expression,
|
||||
block: Block,
|
||||
source_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for For {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "for", node)?;
|
||||
|
||||
let for_node = node.child(0).unwrap();
|
||||
let is_async = match for_node.kind() {
|
||||
"for" => false,
|
||||
"async for" => true,
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "for or async for".to_string(),
|
||||
actual: for_node.kind().to_string(),
|
||||
location: for_node.start_position(),
|
||||
relevant_source: source[for_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let identifier_node = node.child(1).unwrap();
|
||||
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||
|
||||
let expression_node = node.child(3).unwrap();
|
||||
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||
|
||||
let item_node = node.child(4).unwrap();
|
||||
let item = Block::from_syntax(item_node, source, context)?;
|
||||
|
||||
Ok(For {
|
||||
is_async,
|
||||
item_id: identifier,
|
||||
collection: expression,
|
||||
block: item,
|
||||
source_position: SourcePosition::from(node.range()),
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, context: &Map) -> Result<(), ValidationError> {
|
||||
let collection_type = self.collection.expected_type(context)?;
|
||||
let item_type = if let Type::List(item_type) = collection_type {
|
||||
item_type.as_ref().clone()
|
||||
} else if let Type::Range = collection_type {
|
||||
Type::Integer
|
||||
} else {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: Type::Collection,
|
||||
actual: collection_type,
|
||||
position: self.source_position,
|
||||
});
|
||||
};
|
||||
let key = self.item_id.inner().clone();
|
||||
|
||||
context.set_type(key, item_type)?;
|
||||
self.block.validate(_source, context)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let expression_run = self.collection.run(source, context)?;
|
||||
let key = self.item_id.inner();
|
||||
|
||||
if let Value::Range(range) = expression_run {
|
||||
if self.is_async {
|
||||
range.into_par_iter().try_for_each(|integer| {
|
||||
let iter_context = Map::clone_from(context)?;
|
||||
|
||||
iter_context.set(key.clone(), Value::Integer(integer))?;
|
||||
|
||||
self.block.run(source, &iter_context).map(|_value| ())
|
||||
})?;
|
||||
} else {
|
||||
let loop_context = Map::clone_from(context)?;
|
||||
|
||||
for i in range {
|
||||
loop_context.set(key.clone(), Value::Integer(i))?;
|
||||
|
||||
self.block.run(source, &loop_context)?;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Value::none());
|
||||
}
|
||||
|
||||
let values = expression_run.as_list()?.items();
|
||||
|
||||
if self.is_async {
|
||||
values.par_iter().try_for_each(|value| {
|
||||
let iter_context = Map::clone_from(context)?;
|
||||
|
||||
iter_context.set(key.clone(), value.clone())?;
|
||||
|
||||
self.block.run(source, &iter_context).map(|_value| ())
|
||||
})?;
|
||||
} else {
|
||||
let loop_context = Map::clone_from(context)?;
|
||||
|
||||
for value in values.iter() {
|
||||
loop_context.set(key.clone(), value.clone())?;
|
||||
|
||||
self.block.run(source, &loop_context)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for For {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
if self.is_async {
|
||||
output.push_str("async for ");
|
||||
} else {
|
||||
output.push_str("for ");
|
||||
}
|
||||
|
||||
self.item_id.format(output, indent_level);
|
||||
output.push_str(" in ");
|
||||
self.collection.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.block.format(output, indent_level);
|
||||
}
|
||||
}
|
@ -1,174 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Expression, Format, FunctionExpression, Map, SourcePosition, SyntaxNode, Type,
|
||||
Value,
|
||||
};
|
||||
|
||||
/// A function being invoked and the arguments it is being passed.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct FunctionCall {
|
||||
function_expression: FunctionExpression,
|
||||
arguments: Vec<Expression>,
|
||||
syntax_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl FunctionCall {
|
||||
/// Returns a new FunctionCall.
|
||||
pub fn new(
|
||||
function_expression: FunctionExpression,
|
||||
arguments: Vec<Expression>,
|
||||
syntax_position: SourcePosition,
|
||||
) -> Self {
|
||||
Self {
|
||||
function_expression,
|
||||
arguments,
|
||||
syntax_position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for FunctionCall {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "function_call", node)?;
|
||||
|
||||
let function_node = node.child(0).unwrap();
|
||||
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
|
||||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
for index in 2..node.child_count() - 1 {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.is_named() {
|
||||
let expression = Expression::from_syntax(child, source, context)?;
|
||||
|
||||
arguments.push(expression);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(FunctionCall {
|
||||
function_expression,
|
||||
arguments,
|
||||
syntax_position: node.range().into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
match &self.function_expression {
|
||||
FunctionExpression::Identifier(identifier) => {
|
||||
let identifier_type = identifier.expected_type(context)?;
|
||||
|
||||
if let Type::Function {
|
||||
parameter_types: _,
|
||||
return_type,
|
||||
} = &identifier_type
|
||||
{
|
||||
Ok(*return_type.clone())
|
||||
} else {
|
||||
Ok(identifier_type)
|
||||
}
|
||||
}
|
||||
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
|
||||
FunctionExpression::Value(value_node) => {
|
||||
let value_type = value_node.expected_type(context)?;
|
||||
|
||||
if let Type::Function { return_type, .. } = value_type {
|
||||
Ok(*return_type)
|
||||
} else {
|
||||
Ok(value_type)
|
||||
}
|
||||
}
|
||||
FunctionExpression::Index(index) => index.expected_type(context),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.expected_type(context),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, context: &Map) -> Result<(), ValidationError> {
|
||||
let function_expression_type = self.function_expression.expected_type(context)?;
|
||||
|
||||
let parameter_types = match function_expression_type {
|
||||
Type::Function {
|
||||
parameter_types, ..
|
||||
} => parameter_types,
|
||||
Type::Any => return Ok(()),
|
||||
_ => {
|
||||
return Err(ValidationError::TypeCheckExpectedFunction {
|
||||
actual: function_expression_type,
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if self.arguments.len() != parameter_types.len() {
|
||||
return Err(ValidationError::ExpectedFunctionArgumentAmount {
|
||||
expected: parameter_types.len(),
|
||||
actual: self.arguments.len(),
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
|
||||
for (index, expression) in self.arguments.iter().enumerate() {
|
||||
if let Some(expected) = parameter_types.get(index) {
|
||||
let actual = expression.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: expected.clone(),
|
||||
actual,
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let value = match &self.function_expression {
|
||||
FunctionExpression::Identifier(identifier) => {
|
||||
let key = identifier.inner();
|
||||
let variables = context.variables()?;
|
||||
|
||||
if let Some((value, _)) = variables.get(key) {
|
||||
value.clone()
|
||||
} else {
|
||||
return Err(RuntimeError::VariableIdentifierNotFound(
|
||||
identifier.inner().clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
FunctionExpression::FunctionCall(function_call) => {
|
||||
function_call.run(source, context)?
|
||||
}
|
||||
FunctionExpression::Value(value_node) => value_node.run(source, context)?,
|
||||
FunctionExpression::Index(index) => index.run(source, context)?,
|
||||
FunctionExpression::Yield(r#yield) => r#yield.run(source, context)?,
|
||||
};
|
||||
|
||||
let mut arguments = Vec::with_capacity(self.arguments.len());
|
||||
|
||||
for expression in &self.arguments {
|
||||
let value = expression.run(source, context)?;
|
||||
|
||||
arguments.push(value);
|
||||
}
|
||||
|
||||
value.as_function()?.call(&arguments, source, context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for FunctionCall {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
self.function_expression.format(output, indent_level);
|
||||
output.push('(');
|
||||
|
||||
for expression in &self.arguments {
|
||||
expression.format(output, indent_level);
|
||||
}
|
||||
|
||||
output.push(')');
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, FunctionCall, Identifier, Index, Map, SyntaxNode, Type, Value, ValueNode,
|
||||
Yield,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum FunctionExpression {
|
||||
Identifier(Identifier),
|
||||
FunctionCall(Box<FunctionCall>),
|
||||
Value(ValueNode),
|
||||
Index(Index),
|
||||
Yield(Box<Yield>),
|
||||
}
|
||||
|
||||
impl AbstractTree for FunctionExpression {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "function_expression", node)?;
|
||||
|
||||
let first_child = node.child(0).unwrap();
|
||||
let child = if first_child.is_named() {
|
||||
first_child
|
||||
} else {
|
||||
node.child(1).unwrap()
|
||||
};
|
||||
|
||||
let function_expression = match child.kind() {
|
||||
"identifier" => {
|
||||
FunctionExpression::Identifier(Identifier::from_syntax(child, source, context)?)
|
||||
}
|
||||
|
||||
"function_call" => FunctionExpression::FunctionCall(Box::new(
|
||||
FunctionCall::from_syntax(child, source, context)?,
|
||||
)),
|
||||
"value" => FunctionExpression::Value(ValueNode::from_syntax(child, source, context)?),
|
||||
"index" => FunctionExpression::Index(Index::from_syntax(child, source, context)?),
|
||||
"yield" => {
|
||||
FunctionExpression::Yield(Box::new(Yield::from_syntax(child, source, context)?))
|
||||
}
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "identifier, function call, value, index or yield".to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(function_expression)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
match self {
|
||||
FunctionExpression::Identifier(identifier) => identifier.expected_type(context),
|
||||
FunctionExpression::FunctionCall(function_call) => function_call.expected_type(context),
|
||||
FunctionExpression::Value(value_node) => value_node.expected_type(context),
|
||||
FunctionExpression::Index(index) => index.expected_type(context),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.expected_type(context),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
match self {
|
||||
FunctionExpression::Identifier(identifier) => identifier.validate(_source, _context),
|
||||
FunctionExpression::FunctionCall(function_call) => {
|
||||
function_call.validate(_source, _context)
|
||||
}
|
||||
FunctionExpression::Value(value_node) => value_node.validate(_source, _context),
|
||||
FunctionExpression::Index(index) => index.validate(_source, _context),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.validate(_source, _context),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
FunctionExpression::Identifier(identifier) => identifier.run(source, context),
|
||||
FunctionExpression::FunctionCall(function_call) => function_call.run(source, context),
|
||||
FunctionExpression::Value(value_node) => value_node.run(source, context),
|
||||
FunctionExpression::Index(index) => index.run(source, context),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.run(source, context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for FunctionExpression {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
match self {
|
||||
FunctionExpression::Value(value_node) => value_node.format(output, indent_level),
|
||||
FunctionExpression::Identifier(identifier) => identifier.format(output, indent_level),
|
||||
FunctionExpression::FunctionCall(function_call) => {
|
||||
function_call.format(output, indent_level)
|
||||
}
|
||||
FunctionExpression::Index(index) => index.format(output, indent_level),
|
||||
FunctionExpression::Yield(r#yield) => r#yield.format(output, indent_level),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,230 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Block, Format, Function, Identifier, Map, SourcePosition, SyntaxNode, Type,
|
||||
TypeSpecification, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct FunctionNode {
|
||||
parameters: Vec<Identifier>,
|
||||
body: Block,
|
||||
r#type: Type,
|
||||
syntax_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl FunctionNode {
|
||||
pub fn new(
|
||||
parameters: Vec<Identifier>,
|
||||
body: Block,
|
||||
r#type: Type,
|
||||
syntax_position: SourcePosition,
|
||||
) -> Self {
|
||||
FunctionNode {
|
||||
parameters,
|
||||
body,
|
||||
r#type,
|
||||
syntax_position,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parameters(&self) -> &Vec<Identifier> {
|
||||
&self.parameters
|
||||
}
|
||||
|
||||
pub fn body(&self) -> &Block {
|
||||
&self.body
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> &Type {
|
||||
&self.r#type
|
||||
}
|
||||
|
||||
pub fn syntax_position(&self) -> &SourcePosition {
|
||||
&self.syntax_position
|
||||
}
|
||||
|
||||
pub fn return_type(&self) -> &Type {
|
||||
match &self.r#type {
|
||||
Type::Function {
|
||||
parameter_types: _,
|
||||
return_type,
|
||||
} => return_type.as_ref(),
|
||||
_ => &Type::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
source: &str,
|
||||
outer_context: &Map,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
let function_context = Map::new();
|
||||
let parameter_argument_pairs = self.parameters.iter().zip(arguments.iter());
|
||||
|
||||
for (key, (value, r#type)) in outer_context.variables()?.iter() {
|
||||
if r#type.is_function() {
|
||||
function_context.set(key.clone(), value.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
for (identifier, value) in parameter_argument_pairs {
|
||||
let key = identifier.inner().clone();
|
||||
|
||||
function_context.set(key, value.clone())?;
|
||||
}
|
||||
|
||||
let return_value = self.body.run(source, &function_context)?;
|
||||
|
||||
Ok(return_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for FunctionNode {
|
||||
fn from_syntax(
|
||||
node: SyntaxNode,
|
||||
source: &str,
|
||||
outer_context: &Map,
|
||||
) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "function", node)?;
|
||||
|
||||
let child_count = node.child_count();
|
||||
let mut parameters = Vec::new();
|
||||
let mut parameter_types = Vec::new();
|
||||
|
||||
for index in 1..child_count - 3 {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.kind() == "identifier" {
|
||||
let identifier = Identifier::from_syntax(child, source, outer_context)?;
|
||||
|
||||
parameters.push(identifier);
|
||||
}
|
||||
|
||||
if child.kind() == "type_specification" {
|
||||
let type_specification =
|
||||
TypeSpecification::from_syntax(child, source, outer_context)?;
|
||||
|
||||
parameter_types.push(type_specification.take_inner());
|
||||
}
|
||||
}
|
||||
|
||||
let return_type_node = node.child(child_count - 2).unwrap();
|
||||
let return_type = TypeSpecification::from_syntax(return_type_node, source, outer_context)?;
|
||||
|
||||
let function_context = Map::new();
|
||||
|
||||
for (parameter, parameter_type) in parameters.iter().zip(parameter_types.iter()) {
|
||||
function_context.set_type(parameter.inner().clone(), parameter_type.clone())?;
|
||||
}
|
||||
|
||||
for (key, (value, r#type)) in outer_context.variables()?.iter() {
|
||||
if r#type.is_function() {
|
||||
function_context.set(key.clone(), value.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
let body_node = node.child(child_count - 1).unwrap();
|
||||
let body = Block::from_syntax(body_node, source, &function_context)?;
|
||||
|
||||
let r#type = Type::function(parameter_types, return_type.take_inner());
|
||||
let syntax_position = node.range().into();
|
||||
|
||||
Ok(FunctionNode {
|
||||
parameters,
|
||||
body,
|
||||
r#type,
|
||||
syntax_position,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(self.r#type().clone())
|
||||
}
|
||||
|
||||
fn validate(&self, source: &str, context: &Map) -> Result<(), ValidationError> {
|
||||
let function_context = Map::new();
|
||||
|
||||
for (key, (_value, r#type)) in context.variables()?.iter() {
|
||||
if r#type.is_function() {
|
||||
function_context.set_type(key.clone(), r#type.clone())?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Type::Function {
|
||||
parameter_types,
|
||||
return_type,
|
||||
} = &self.r#type
|
||||
{
|
||||
for (parameter, parameter_type) in self.parameters.iter().zip(parameter_types.iter()) {
|
||||
function_context.set_type(parameter.inner().clone(), parameter_type.clone())?;
|
||||
}
|
||||
|
||||
let actual = self.body.expected_type(&function_context)?;
|
||||
|
||||
if !return_type.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: return_type.as_ref().clone(),
|
||||
actual,
|
||||
position: self.syntax_position,
|
||||
});
|
||||
}
|
||||
|
||||
self.body.validate(source, &function_context)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ValidationError::TypeCheckExpectedFunction {
|
||||
actual: self.r#type.clone(),
|
||||
position: self.syntax_position,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
let self_as_value = Value::Function(Function::ContextDefined(self.clone()));
|
||||
|
||||
Ok(self_as_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for FunctionNode {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
let (parameter_types, return_type) = if let Type::Function {
|
||||
parameter_types,
|
||||
return_type,
|
||||
} = &self.r#type
|
||||
{
|
||||
(parameter_types, return_type)
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
output.push('(');
|
||||
|
||||
for (identifier, r#type) in self.parameters.iter().zip(parameter_types.iter()) {
|
||||
identifier.format(output, indent_level);
|
||||
output.push_str(" <");
|
||||
r#type.format(output, indent_level);
|
||||
output.push('>');
|
||||
}
|
||||
|
||||
output.push_str(") <");
|
||||
return_type.format(output, indent_level);
|
||||
output.push_str("> ");
|
||||
self.body.format(output, indent_level);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FunctionNode {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let mut string = String::new();
|
||||
|
||||
self.format(&mut string, 0);
|
||||
f.write_str(&string)
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// A string by which a variable is known to a context.
|
||||
///
|
||||
/// Every variable is a key-value pair. An identifier holds the key part of that
|
||||
/// pair. Its inner value can be used to retrieve a Value instance from a Map.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Identifier(String);
|
||||
|
||||
impl Identifier {
|
||||
pub fn new<T: Into<String>>(inner: T) -> Self {
|
||||
Identifier(inner.into())
|
||||
}
|
||||
|
||||
pub fn take_inner(self) -> String {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &String {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for Identifier {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "identifier", node)?;
|
||||
|
||||
let text = &source[node.byte_range()];
|
||||
|
||||
debug_assert!(!text.is_empty());
|
||||
|
||||
Ok(Identifier(text.to_string()))
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
if let Some((_value, r#type)) = context.variables()?.get(&self.0) {
|
||||
Ok(r#type.clone())
|
||||
} else {
|
||||
Err(ValidationError::VariableIdentifierNotFound(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
if let Some((value, _)) = context.variables()?.get(&self.0) {
|
||||
Ok(value.clone())
|
||||
} else {
|
||||
Err(RuntimeError::VariableIdentifierNotFound(self.0.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Identifier {
|
||||
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||
output.push_str(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Identifier {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Block, Expression, Format, Map, SourcePosition, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct IfElse {
|
||||
if_expression: Expression,
|
||||
if_block: Block,
|
||||
else_if_expressions: Vec<Expression>,
|
||||
else_if_blocks: Vec<Block>,
|
||||
else_block: Option<Block>,
|
||||
source_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for IfElse {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
let if_expression_node = node.child(0).unwrap().child(1).unwrap();
|
||||
let if_expression = Expression::from_syntax(if_expression_node, source, context)?;
|
||||
|
||||
let if_block_node = node.child(0).unwrap().child(2).unwrap();
|
||||
let if_block = Block::from_syntax(if_block_node, source, context)?;
|
||||
|
||||
let child_count = node.child_count();
|
||||
let mut else_if_expressions = Vec::new();
|
||||
let mut else_if_blocks = Vec::new();
|
||||
let mut else_block = None;
|
||||
|
||||
for index in 1..child_count {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.kind() == "else_if" {
|
||||
let expression_node = child.child(1).unwrap();
|
||||
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||
|
||||
else_if_expressions.push(expression);
|
||||
|
||||
let block_node = child.child(2).unwrap();
|
||||
let block = Block::from_syntax(block_node, source, context)?;
|
||||
|
||||
else_if_blocks.push(block);
|
||||
}
|
||||
|
||||
if child.kind() == "else" {
|
||||
let else_node = child.child(1).unwrap();
|
||||
else_block = Some(Block::from_syntax(else_node, source, context)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(IfElse {
|
||||
if_expression,
|
||||
if_block,
|
||||
else_if_expressions,
|
||||
else_if_blocks,
|
||||
else_block,
|
||||
source_position: SourcePosition::from(node.range()),
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
self.if_block.expected_type(context)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, context: &Map) -> Result<(), ValidationError> {
|
||||
self.if_expression.validate(_source, context)?;
|
||||
self.if_block.validate(_source, context)?;
|
||||
|
||||
let expected = self.if_block.expected_type(context)?;
|
||||
let else_ifs = self
|
||||
.else_if_expressions
|
||||
.iter()
|
||||
.zip(self.else_if_blocks.iter());
|
||||
|
||||
for (expression, block) in else_ifs {
|
||||
expression.validate(_source, context)?;
|
||||
block.validate(_source, context)?;
|
||||
|
||||
let actual = block.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected,
|
||||
actual,
|
||||
position: self.source_position,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(block) = &self.else_block {
|
||||
let actual = block.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected,
|
||||
actual,
|
||||
position: self.source_position,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let if_boolean = self.if_expression.run(source, context)?.as_boolean()?;
|
||||
|
||||
if if_boolean {
|
||||
self.if_block.run(source, context)
|
||||
} else {
|
||||
let expressions = &self.else_if_expressions;
|
||||
|
||||
for (index, expression) in expressions.iter().enumerate() {
|
||||
let if_boolean = expression.run(source, context)?.as_boolean()?;
|
||||
|
||||
if if_boolean {
|
||||
let block = self.else_if_blocks.get(index).unwrap();
|
||||
|
||||
return block.run(source, context);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(block) = &self.else_block {
|
||||
block.run(source, context)
|
||||
} else {
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for IfElse {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
output.push_str("if ");
|
||||
self.if_expression.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.if_block.format(output, indent_level);
|
||||
|
||||
let else_ifs = self
|
||||
.else_if_expressions
|
||||
.iter()
|
||||
.zip(self.else_if_blocks.iter());
|
||||
|
||||
for (expression, block) in else_ifs {
|
||||
output.push_str("else if ");
|
||||
expression.format(output, indent_level);
|
||||
output.push(' ');
|
||||
block.format(output, indent_level);
|
||||
}
|
||||
|
||||
if let Some(block) = &self.else_block {
|
||||
output.push_str("else ");
|
||||
block.format(output, indent_level);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, IndexExpression, List, Map, SourcePosition, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of an index expression.
|
||||
///
|
||||
/// An index is a means of accessing values stored in list, maps and strings.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Index {
|
||||
pub collection: IndexExpression,
|
||||
pub index: IndexExpression,
|
||||
pub index_end: Option<IndexExpression>,
|
||||
source_position: SourcePosition,
|
||||
}
|
||||
|
||||
impl AbstractTree for Index {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "index", node)?;
|
||||
|
||||
let collection_node = node.child(0).unwrap();
|
||||
let collection = IndexExpression::from_syntax(collection_node, source, context)?;
|
||||
|
||||
let index_node = node.child(2).unwrap();
|
||||
let index = IndexExpression::from_syntax(index_node, source, context)?;
|
||||
|
||||
let index_end_node = node.child(4);
|
||||
let index_end = if let Some(index_end_node) = index_end_node {
|
||||
Some(IndexExpression::from_syntax(
|
||||
index_end_node,
|
||||
source,
|
||||
context,
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Index {
|
||||
collection,
|
||||
index,
|
||||
index_end,
|
||||
source_position: SourcePosition::from(node.range()),
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
match self.collection.expected_type(context)? {
|
||||
Type::List(item_type) => Ok(*item_type.clone()),
|
||||
Type::Map(_) => Ok(Type::Any),
|
||||
Type::None => Ok(Type::None),
|
||||
r#type => Ok(r#type),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
self.collection.validate(_source, _context)?;
|
||||
self.index.validate(_source, _context)?;
|
||||
|
||||
if let Some(index_end) = &self.index_end {
|
||||
index_end.validate(_source, _context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let value = self.collection.run(source, context)?;
|
||||
|
||||
match value {
|
||||
Value::List(list) => {
|
||||
let index = self.index.run(source, context)?.as_integer()? as usize;
|
||||
|
||||
let item = if let Some(index_end) = &self.index_end {
|
||||
let index_end = index_end.run(source, context)?.as_integer()? as usize;
|
||||
let sublist = list.items()[index..=index_end].to_vec();
|
||||
|
||||
Value::List(List::with_items(sublist))
|
||||
} else {
|
||||
list.items().get(index).cloned().unwrap_or_default()
|
||||
};
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
Value::Map(map) => {
|
||||
let (key, value) = if let IndexExpression::Identifier(identifier) = &self.index {
|
||||
let key = identifier.inner();
|
||||
let value = map
|
||||
.variables()?
|
||||
.get(key)
|
||||
.map(|(value, _)| value.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
(key.clone(), value)
|
||||
} else {
|
||||
let index_value = self.index.run(source, context)?;
|
||||
let key = index_value.as_string()?;
|
||||
let value = map
|
||||
.variables()?
|
||||
.get(key.as_str())
|
||||
.map(|(value, _)| value.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
(key.clone(), value)
|
||||
};
|
||||
|
||||
if value.is_none() {
|
||||
Err(RuntimeError::VariableIdentifierNotFound(key))
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
Value::String(string) => {
|
||||
let index = self.index.run(source, context)?.as_integer()? as usize;
|
||||
let item = string.chars().nth(index).unwrap_or_default();
|
||||
|
||||
Ok(Value::string(item.to_string()))
|
||||
}
|
||||
_ => Err(RuntimeError::ExpectedCollection { actual: value }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Index {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
self.collection.format(output, indent_level);
|
||||
output.push(':');
|
||||
self.index.format(output, indent_level);
|
||||
|
||||
if let Some(expression) = &self.index_end {
|
||||
output.push_str("..");
|
||||
expression.format(output, indent_level);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, AssignmentOperator, Format, Index, IndexExpression, Map, Statement, SyntaxNode,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct IndexAssignment {
|
||||
index: Index,
|
||||
operator: AssignmentOperator,
|
||||
statement: Statement,
|
||||
}
|
||||
|
||||
impl AbstractTree for IndexAssignment {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "index_assignment", node)?;
|
||||
|
||||
let index_node = node.child(0).unwrap();
|
||||
let index = Index::from_syntax(index_node, source, context)?;
|
||||
|
||||
let operator_node = node.child(1).unwrap();
|
||||
let operator = AssignmentOperator::from_syntax(operator_node, source, context)?;
|
||||
|
||||
let statement_node = node.child(2).unwrap();
|
||||
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||
|
||||
Ok(IndexAssignment {
|
||||
index,
|
||||
operator,
|
||||
statement,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
self.index.validate(_source, _context)?;
|
||||
self.statement.validate(_source, _context)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let index_collection = self.index.collection.run(source, context)?;
|
||||
let index_context = index_collection.as_map().unwrap_or(context);
|
||||
let index_key = if let IndexExpression::Identifier(identifier) = &self.index.index {
|
||||
identifier.inner()
|
||||
} else {
|
||||
return Err(RuntimeError::VariableIdentifierNotFound(
|
||||
self.index.index.run(source, context)?.to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
let value = self.statement.run(source, context)?;
|
||||
|
||||
let new_value = match self.operator {
|
||||
AssignmentOperator::PlusEqual => {
|
||||
if let Some((mut previous_value, _)) =
|
||||
index_context.variables()?.get(index_key).cloned()
|
||||
{
|
||||
previous_value += value;
|
||||
previous_value
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
AssignmentOperator::MinusEqual => {
|
||||
if let Some((mut previous_value, _)) =
|
||||
index_context.variables()?.get(index_key).cloned()
|
||||
{
|
||||
previous_value -= value;
|
||||
previous_value
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
AssignmentOperator::Equal => value,
|
||||
};
|
||||
|
||||
index_context.set(index_key.clone(), new_value)?;
|
||||
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for IndexAssignment {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
self.index.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.operator.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.statement.format(output, indent_level);
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
value_node::ValueNode,
|
||||
AbstractTree, Format, FunctionCall, Identifier, Index, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum IndexExpression {
|
||||
Value(ValueNode),
|
||||
Identifier(Identifier),
|
||||
Index(Box<Index>),
|
||||
FunctionCall(Box<FunctionCall>),
|
||||
}
|
||||
|
||||
impl AbstractTree for IndexExpression {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "index_expression", node)?;
|
||||
|
||||
let first_child = node.child(0).unwrap();
|
||||
let child = if first_child.is_named() {
|
||||
first_child
|
||||
} else {
|
||||
node.child(1).unwrap()
|
||||
};
|
||||
|
||||
let abstract_node = match child.kind() {
|
||||
"value" => IndexExpression::Value(ValueNode::from_syntax(child, source, context)?),
|
||||
"identifier" => {
|
||||
IndexExpression::Identifier(Identifier::from_syntax(child, source, context)?)
|
||||
}
|
||||
"index" => {
|
||||
IndexExpression::Index(Box::new(Index::from_syntax(child, source, context)?))
|
||||
}
|
||||
"function_call" => IndexExpression::FunctionCall(Box::new(FunctionCall::from_syntax(
|
||||
child, source, context,
|
||||
)?)),
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "value, identifier, index or function call".to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(abstract_node)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
match self {
|
||||
IndexExpression::Value(value_node) => value_node.expected_type(context),
|
||||
IndexExpression::Identifier(identifier) => identifier.expected_type(context),
|
||||
IndexExpression::Index(index) => index.expected_type(context),
|
||||
IndexExpression::FunctionCall(function_call) => function_call.expected_type(context),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
match self {
|
||||
IndexExpression::Value(value_node) => value_node.validate(_source, _context),
|
||||
IndexExpression::Identifier(identifier) => identifier.validate(_source, _context),
|
||||
IndexExpression::Index(index) => index.validate(_source, _context),
|
||||
IndexExpression::FunctionCall(function_call) => {
|
||||
function_call.validate(_source, _context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
IndexExpression::Value(value_node) => value_node.run(source, context),
|
||||
IndexExpression::Identifier(identifier) => identifier.run(source, context),
|
||||
IndexExpression::Index(index) => index.run(source, context),
|
||||
IndexExpression::FunctionCall(function_call) => function_call.run(source, context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for IndexExpression {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
match self {
|
||||
IndexExpression::Value(value_node) => {
|
||||
if let ValueNode::BuiltInValue(built_in_value) = value_node {
|
||||
output.push_str(built_in_value.name());
|
||||
} else {
|
||||
value_node.format(output, indent_level);
|
||||
}
|
||||
}
|
||||
IndexExpression::Identifier(identifier) => identifier.format(output, indent_level),
|
||||
IndexExpression::FunctionCall(function_call) => {
|
||||
function_call.format(output, indent_level)
|
||||
}
|
||||
IndexExpression::Index(index) => index.format(output, indent_level),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Expression, Format, LogicOperator, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a logic expression.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Logic {
|
||||
left: Expression,
|
||||
operator: LogicOperator,
|
||||
right: Expression,
|
||||
}
|
||||
|
||||
impl AbstractTree for Logic {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "logic", node)?;
|
||||
|
||||
let first_node = node.child(0).unwrap();
|
||||
let (left_node, operator_node, right_node) = {
|
||||
if first_node.is_named() {
|
||||
(first_node, node.child(1).unwrap(), node.child(2).unwrap())
|
||||
} else {
|
||||
(
|
||||
node.child(1).unwrap(),
|
||||
node.child(2).unwrap(),
|
||||
node.child(3).unwrap(),
|
||||
)
|
||||
}
|
||||
};
|
||||
let left = Expression::from_syntax(left_node, source, context)?;
|
||||
let operator = LogicOperator::from_syntax(operator_node, source, context)?;
|
||||
let right = Expression::from_syntax(right_node, source, context)?;
|
||||
|
||||
Ok(Logic {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::Boolean)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
self.left.validate(_source, _context)?;
|
||||
self.right.validate(_source, _context)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let left = self.left.run(source, context)?;
|
||||
let right = self.right.run(source, context)?;
|
||||
let result = match self.operator {
|
||||
LogicOperator::Equal => {
|
||||
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
|
||||
left_num == right_num
|
||||
} else {
|
||||
left == right
|
||||
}
|
||||
}
|
||||
LogicOperator::NotEqual => {
|
||||
if let (Ok(left_num), Ok(right_num)) = (left.as_number(), right.as_number()) {
|
||||
left_num != right_num
|
||||
} else {
|
||||
left != right
|
||||
}
|
||||
}
|
||||
LogicOperator::And => left.as_boolean()? && right.as_boolean()?,
|
||||
LogicOperator::Or => left.as_boolean()? || right.as_boolean()?,
|
||||
LogicOperator::Greater => left > right,
|
||||
LogicOperator::Less => left < right,
|
||||
LogicOperator::GreaterOrEqual => left >= right,
|
||||
LogicOperator::LessOrEqual => left <= right,
|
||||
};
|
||||
|
||||
Ok(Value::Boolean(result))
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Logic {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
self.left.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.operator.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.right.format(output, indent_level);
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum LogicOperator {
|
||||
Equal,
|
||||
NotEqual,
|
||||
And,
|
||||
Or,
|
||||
Greater,
|
||||
Less,
|
||||
GreaterOrEqual,
|
||||
LessOrEqual,
|
||||
}
|
||||
|
||||
impl AbstractTree for LogicOperator {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "logic_operator", node)?;
|
||||
|
||||
let operator_node = node.child(0).unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"==" => LogicOperator::Equal,
|
||||
"!=" => LogicOperator::NotEqual,
|
||||
"&&" => LogicOperator::And,
|
||||
"||" => LogicOperator::Or,
|
||||
">" => LogicOperator::Greater,
|
||||
"<" => LogicOperator::Less,
|
||||
">=" => LogicOperator::GreaterOrEqual,
|
||||
"<=" => LogicOperator::LessOrEqual,
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "==, !=, &&, ||, >, <, >= or <=".to_string(),
|
||||
actual: operator_node.kind().to_string(),
|
||||
location: operator_node.start_position(),
|
||||
relevant_source: source[operator_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(operator)
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for LogicOperator {
|
||||
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||
match self {
|
||||
LogicOperator::Equal => output.push('='),
|
||||
LogicOperator::NotEqual => output.push_str("!="),
|
||||
LogicOperator::And => output.push_str("&&"),
|
||||
LogicOperator::Or => output.push_str("||"),
|
||||
LogicOperator::Greater => output.push('>'),
|
||||
LogicOperator::Less => output.push('<'),
|
||||
LogicOperator::GreaterOrEqual => output.push_str(">="),
|
||||
LogicOperator::LessOrEqual => output.push_str("<="),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
//! Pattern matching.
|
||||
//!
|
||||
//! Note that this module is called "match" but is escaped as "r#match" because
|
||||
//! "match" is a keyword in Rust.
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Expression, Format, Map, Statement, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a match statement.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Match {
|
||||
matcher: Expression,
|
||||
options: Vec<(Expression, Statement)>,
|
||||
fallback: Option<Box<Statement>>,
|
||||
}
|
||||
|
||||
impl AbstractTree for Match {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "match", node)?;
|
||||
|
||||
let matcher_node = node.child(1).unwrap();
|
||||
let matcher = Expression::from_syntax(matcher_node, source, context)?;
|
||||
|
||||
let mut options = Vec::new();
|
||||
let mut previous_expression = None;
|
||||
let mut next_statement_is_fallback = false;
|
||||
let mut fallback = None;
|
||||
|
||||
for index in 2..node.child_count() {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.kind() == "*" {
|
||||
next_statement_is_fallback = true;
|
||||
}
|
||||
|
||||
if child.kind() == "expression" {
|
||||
previous_expression = Some(Expression::from_syntax(child, source, context)?);
|
||||
}
|
||||
|
||||
if child.kind() == "statement" {
|
||||
let statement = Statement::from_syntax(child, source, context)?;
|
||||
|
||||
if next_statement_is_fallback {
|
||||
fallback = Some(Box::new(statement));
|
||||
next_statement_is_fallback = false;
|
||||
} else if let Some(expression) = &previous_expression {
|
||||
options.push((expression.clone(), statement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Match {
|
||||
matcher,
|
||||
options,
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
let (_, first_statement) = self.options.first().unwrap();
|
||||
|
||||
first_statement.expected_type(context)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
self.matcher.validate(_source, _context)?;
|
||||
|
||||
for (expression, statement) in &self.options {
|
||||
expression.validate(_source, _context)?;
|
||||
statement.validate(_source, _context)?;
|
||||
}
|
||||
|
||||
if let Some(statement) = &self.fallback {
|
||||
statement.validate(_source, _context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let matcher_value = self.matcher.run(source, context)?;
|
||||
|
||||
for (expression, statement) in &self.options {
|
||||
let option_value = expression.run(source, context)?;
|
||||
|
||||
if matcher_value == option_value {
|
||||
return statement.run(source, context);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fallback) = &self.fallback {
|
||||
fallback.run(source, context)
|
||||
} else {
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Match {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
output.push_str("match ");
|
||||
self.matcher.format(output, indent_level);
|
||||
output.push_str(" {");
|
||||
|
||||
for (expression, statement) in &self.options {
|
||||
expression.format(output, indent_level);
|
||||
output.push_str(" => ");
|
||||
statement.format(output, indent_level);
|
||||
}
|
||||
|
||||
if let Some(statement) = &self.fallback {
|
||||
output.push_str("* => ");
|
||||
statement.format(output, indent_level);
|
||||
}
|
||||
|
||||
output.push('}');
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Expression, Format, Map, MathOperator, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a math operation.
|
||||
///
|
||||
/// Dust currently supports the four basic operations and the modulo (or
|
||||
/// remainder) operator.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Math {
|
||||
left: Expression,
|
||||
operator: MathOperator,
|
||||
right: Expression,
|
||||
}
|
||||
|
||||
impl AbstractTree for Math {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "math", node)?;
|
||||
|
||||
let left_node = node.child(0).unwrap();
|
||||
let left = Expression::from_syntax(left_node, source, context)?;
|
||||
|
||||
let operator_node = node.child(1).unwrap();
|
||||
let operator = MathOperator::from_syntax(operator_node, source, context)?;
|
||||
|
||||
let right_node = node.child(2).unwrap();
|
||||
let right = Expression::from_syntax(right_node, source, context)?;
|
||||
|
||||
Ok(Math {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
self.left.expected_type(context)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
self.left.validate(_source, _context)?;
|
||||
self.right.validate(_source, _context)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let left = self.left.run(source, context)?;
|
||||
let right = self.right.run(source, context)?;
|
||||
let value = match self.operator {
|
||||
MathOperator::Add => left + right,
|
||||
MathOperator::Subtract => left - right,
|
||||
MathOperator::Multiply => left * right,
|
||||
MathOperator::Divide => left / right,
|
||||
MathOperator::Modulo => left % right,
|
||||
}?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Math {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
self.left.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.operator.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.right.format(output, indent_level);
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum MathOperator {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
}
|
||||
|
||||
impl AbstractTree for MathOperator {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, _context: &Map) -> Result<Self, SyntaxError> {
|
||||
let operator_node = node.child(0).unwrap();
|
||||
let operator = match operator_node.kind() {
|
||||
"+" => MathOperator::Add,
|
||||
"-" => MathOperator::Subtract,
|
||||
"*" => MathOperator::Multiply,
|
||||
"/" => MathOperator::Divide,
|
||||
"%" => MathOperator::Modulo,
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "+, -, *, / or %".to_string(),
|
||||
actual: operator_node.kind().to_string(),
|
||||
location: operator_node.start_position(),
|
||||
relevant_source: source[operator_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(operator)
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for MathOperator {
|
||||
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||
let char = match self {
|
||||
MathOperator::Add => '+',
|
||||
MathOperator::Subtract => '-',
|
||||
MathOperator::Multiply => '*',
|
||||
MathOperator::Divide => '/',
|
||||
MathOperator::Modulo => '%',
|
||||
};
|
||||
|
||||
output.push(char);
|
||||
}
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
//! Abstract, executable representations of corresponding items found in Dust
|
||||
//! source code. The types that implement [AbstractTree] are inteded to be
|
||||
//! created by an [Evaluator].
|
||||
//!
|
||||
//! When adding new lanugage features, first extend the grammar to recognize new
|
||||
//! syntax nodes. Then add a new AbstractTree type using the existing types as
|
||||
//! examples.
|
||||
|
||||
pub mod assignment;
|
||||
pub mod assignment_operator;
|
||||
pub mod block;
|
||||
pub mod built_in_value;
|
||||
pub mod command;
|
||||
pub mod expression;
|
||||
pub mod r#for;
|
||||
pub mod function_call;
|
||||
pub mod function_expression;
|
||||
pub mod function_node;
|
||||
pub mod identifier;
|
||||
pub mod if_else;
|
||||
pub mod index;
|
||||
pub mod index_assignment;
|
||||
pub mod index_expression;
|
||||
pub mod logic;
|
||||
pub mod logic_operator;
|
||||
pub mod r#match;
|
||||
pub mod math;
|
||||
pub mod math_operator;
|
||||
pub mod new;
|
||||
pub mod statement;
|
||||
pub mod r#type;
|
||||
pub mod type_specification;
|
||||
pub mod value_node;
|
||||
pub mod r#while;
|
||||
pub mod r#yield;
|
||||
|
||||
pub use {
|
||||
assignment::*, assignment_operator::*, block::*, built_in_value::*, command::*, expression::*,
|
||||
function_call::*, function_expression::*, function_node::*, identifier::*, if_else::*,
|
||||
index::*, index_assignment::IndexAssignment, index_expression::*, logic::*, logic_operator::*,
|
||||
math::*, math_operator::*, new::*, r#for::*, r#match::*, r#type::*, r#while::*, r#yield::*,
|
||||
statement::*, type_specification::*, value_node::*,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
Map, SyntaxNode, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct SourcePosition {
|
||||
pub start_byte: usize,
|
||||
pub end_byte: usize,
|
||||
pub start_row: usize,
|
||||
pub start_column: usize,
|
||||
pub end_row: usize,
|
||||
pub end_column: usize,
|
||||
}
|
||||
|
||||
impl From<tree_sitter::Range> for SourcePosition {
|
||||
fn from(range: tree_sitter::Range) -> Self {
|
||||
SourcePosition {
|
||||
start_byte: range.start_byte,
|
||||
end_byte: range.end_byte,
|
||||
start_row: range.start_point.row + 1,
|
||||
start_column: range.start_point.column,
|
||||
end_row: range.end_point.row + 1,
|
||||
end_column: range.end_point.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Root {
|
||||
statements: Vec<Statement>,
|
||||
}
|
||||
|
||||
// TODO Change Root to use tree sitter's cursor to traverse the statements
|
||||
// instead of indexes. This will be more performant when there are a lot of
|
||||
// top-level statements in the tree.
|
||||
impl AbstractTree for Root {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "root", node)?;
|
||||
|
||||
let statement_count = node.child_count();
|
||||
let mut statements = Vec::with_capacity(statement_count);
|
||||
|
||||
for index in 0..statement_count {
|
||||
let statement_node = node.child(index).unwrap();
|
||||
let statement = Statement::from_syntax(statement_node, source, context)?;
|
||||
|
||||
statements.push(statement);
|
||||
}
|
||||
|
||||
Ok(Root { statements })
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.validate(_source, _context);
|
||||
} else {
|
||||
statement.validate(_source, _context)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let mut value = Value::none();
|
||||
|
||||
for statement in &self.statements {
|
||||
if let Statement::Return(inner_statement) = statement {
|
||||
return inner_statement.run(source, context);
|
||||
} else {
|
||||
value = statement.run(source, context)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
self.statements.last().unwrap().expected_type(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Root {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
for (index, statement) in self.statements.iter().enumerate() {
|
||||
if index > 0 {
|
||||
output.push('\n');
|
||||
}
|
||||
statement.format(output, indent_level);
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait is implemented by the Evaluator's internal types to form an
|
||||
/// executable tree that resolves to a single value.
|
||||
pub trait AbstractTree: Sized + Format {
|
||||
/// Interpret the syntax tree at the given node and return the abstraction. Returns a syntax
|
||||
/// error if the source is invalid.
|
||||
///
|
||||
/// This function is used to convert nodes in the Tree Sitter concrete
|
||||
/// syntax tree into executable nodes in an abstract tree. This function is
|
||||
/// where the tree should be traversed by accessing sibling and child nodes.
|
||||
/// Each node in the CST should be traversed only once.
|
||||
///
|
||||
/// If necessary, the source code can be accessed directly by getting the
|
||||
/// node's byte range.
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError>;
|
||||
|
||||
/// Return the type of the value that this abstract node will create when run. Returns a
|
||||
/// validation error if the tree is invalid.
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError>;
|
||||
|
||||
/// Verify the type integrity of the node. Returns a validation error if the tree is invalid.
|
||||
fn validate(&self, source: &str, context: &Map) -> Result<(), ValidationError>;
|
||||
|
||||
/// Execute this node's logic and return a value. Returns a runtime error if the node cannot
|
||||
/// resolve to a value.
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError>;
|
||||
}
|
||||
|
||||
pub trait Format {
|
||||
fn format(&self, output: &mut String, indent_level: u8);
|
||||
|
||||
fn indent(output: &mut String, indent_level: u8) {
|
||||
for _ in 0..indent_level {
|
||||
output.push_str(" ");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::Node;
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Identifier, Map, Type, TypeSpecification, Value, ValueNode,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct New {
|
||||
identifier: Identifier,
|
||||
properties: Vec<(Identifier, ValueNode, Option<TypeSpecification>)>,
|
||||
}
|
||||
|
||||
impl AbstractTree for New {
|
||||
fn from_syntax(node: Node, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
let identifier_node = node.child(1).unwrap();
|
||||
let identifier = Identifier::from_syntax(identifier_node, source, context)?;
|
||||
|
||||
let properties = Vec::new();
|
||||
|
||||
Ok(New {
|
||||
identifier,
|
||||
properties,
|
||||
})
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for New {
|
||||
fn format(&self, _output: &mut String, _indent_level: u8) {
|
||||
todo!()
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Assignment, Block, Expression, For, Format, IfElse, IndexAssignment, Map, Match,
|
||||
SyntaxNode, Type, Value, While,
|
||||
};
|
||||
|
||||
/// Abstract representation of a statement.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Statement {
|
||||
Assignment(Box<Assignment>),
|
||||
Expression(Expression),
|
||||
IfElse(Box<IfElse>),
|
||||
Match(Match),
|
||||
While(Box<While>),
|
||||
Block(Box<Block>),
|
||||
Return(Box<Statement>),
|
||||
For(Box<For>),
|
||||
IndexAssignment(Box<IndexAssignment>),
|
||||
}
|
||||
|
||||
impl AbstractTree for Statement {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "statement", node)?;
|
||||
|
||||
let child = node.child(0).unwrap();
|
||||
|
||||
match child.kind() {
|
||||
"assignment" => Ok(Statement::Assignment(Box::new(
|
||||
Assignment::from_syntax(child, source, context)?,
|
||||
))),
|
||||
"expression" => Ok(Statement::Expression(Expression::from_syntax(
|
||||
child, source, context,
|
||||
)?)),
|
||||
"if_else" => Ok(Statement::IfElse(Box::new(IfElse::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"while" => Ok(Statement::While(Box::new(While::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"block" => Ok(Statement::Block(Box::new(Block::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"for" => Ok(Statement::For(Box::new(For::from_syntax(
|
||||
child, source, context,
|
||||
)?))),
|
||||
"index_assignment" => Ok(Statement::IndexAssignment(Box::new(
|
||||
IndexAssignment::from_syntax(child, source, context)?,
|
||||
))),
|
||||
"match" => Ok(Statement::Match(Match::from_syntax(
|
||||
child, source, context,
|
||||
)?)),
|
||||
"return" => {
|
||||
let statement_node = child.child(1).unwrap();
|
||||
|
||||
Ok(Statement::Return(Box::new(Statement::from_syntax(statement_node, source, context)?)))
|
||||
},
|
||||
_ => Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected:
|
||||
"assignment, index assignment, expression, block, return, if...else, while, for or match".to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.expected_type(context),
|
||||
Statement::Expression(expression) => expression.expected_type(context),
|
||||
Statement::IfElse(if_else) => if_else.expected_type(context),
|
||||
Statement::Match(r#match) => r#match.expected_type(context),
|
||||
Statement::While(r#while) => r#while.expected_type(context),
|
||||
Statement::Block(block) => block.expected_type(context),
|
||||
Statement::For(r#for) => r#for.expected_type(context),
|
||||
Statement::IndexAssignment(index_assignment) => index_assignment.expected_type(context),
|
||||
Statement::Return(statement) => statement.expected_type(context),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.validate(_source, _context),
|
||||
Statement::Expression(expression) => expression.validate(_source, _context),
|
||||
Statement::IfElse(if_else) => if_else.validate(_source, _context),
|
||||
Statement::Match(r#match) => r#match.validate(_source, _context),
|
||||
Statement::While(r#while) => r#while.validate(_source, _context),
|
||||
Statement::Block(block) => block.validate(_source, _context),
|
||||
Statement::For(r#for) => r#for.validate(_source, _context),
|
||||
Statement::IndexAssignment(index_assignment) => {
|
||||
index_assignment.validate(_source, _context)
|
||||
}
|
||||
Statement::Return(statement) => statement.validate(_source, _context),
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.run(source, context),
|
||||
Statement::Expression(expression) => expression.run(source, context),
|
||||
Statement::IfElse(if_else) => if_else.run(source, context),
|
||||
Statement::Match(r#match) => r#match.run(source, context),
|
||||
Statement::While(r#while) => r#while.run(source, context),
|
||||
Statement::Block(block) => block.run(source, context),
|
||||
Statement::For(r#for) => r#for.run(source, context),
|
||||
Statement::IndexAssignment(index_assignment) => index_assignment.run(source, context),
|
||||
Statement::Return(statement) => statement.run(source, context),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Statement {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
Statement::indent(output, indent_level);
|
||||
|
||||
match self {
|
||||
Statement::Assignment(assignment) => assignment.format(output, indent_level),
|
||||
Statement::Expression(expression) => expression.format(output, indent_level),
|
||||
Statement::IfElse(if_else) => if_else.format(output, indent_level),
|
||||
Statement::Match(r#match) => r#match.format(output, indent_level),
|
||||
Statement::While(r#while) => r#while.format(output, indent_level),
|
||||
Statement::Block(block) => block.format(output, indent_level),
|
||||
Statement::For(r#for) => r#for.format(output, indent_level),
|
||||
Statement::IndexAssignment(index_assignment) => {
|
||||
index_assignment.format(output, indent_level)
|
||||
}
|
||||
Statement::Return(statement) => statement.format(output, indent_level),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,297 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Identifier, Map, Structure, SyntaxNode, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum Type {
|
||||
Any,
|
||||
Boolean,
|
||||
Collection,
|
||||
Custom(Identifier),
|
||||
Float,
|
||||
Function {
|
||||
parameter_types: Vec<Type>,
|
||||
return_type: Box<Type>,
|
||||
},
|
||||
Integer,
|
||||
List(Box<Type>),
|
||||
Map(Option<Structure>),
|
||||
None,
|
||||
Number,
|
||||
String,
|
||||
Range,
|
||||
Option(Box<Type>),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn list(item_type: Type) -> Self {
|
||||
Type::List(Box::new(item_type))
|
||||
}
|
||||
|
||||
pub fn function(parameter_types: Vec<Type>, return_type: Type) -> Self {
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
return_type: Box::new(return_type),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn option(optional_type: Type) -> Self {
|
||||
Type::Option(Box::new(optional_type))
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating whether is type is accepting of the other.
|
||||
///
|
||||
/// The types do not need to match exactly. For example, the Any variant matches all of the
|
||||
/// others and the Number variant accepts Number, Integer and Float.
|
||||
pub fn accepts(&self, other: &Type) -> bool {
|
||||
log::info!("Checking type {self} against {other}.");
|
||||
|
||||
match (self, other) {
|
||||
(Type::Any, _)
|
||||
| (_, Type::Any)
|
||||
| (Type::Boolean, Type::Boolean)
|
||||
| (Type::Collection, Type::Collection)
|
||||
| (Type::Collection, Type::List(_))
|
||||
| (Type::List(_), Type::Collection)
|
||||
| (Type::Collection, Type::Map(_))
|
||||
| (Type::Map(_), Type::Collection)
|
||||
| (Type::Collection, Type::String)
|
||||
| (Type::String, Type::Collection)
|
||||
| (Type::Float, Type::Float)
|
||||
| (Type::Integer, Type::Integer)
|
||||
| (Type::Map(_), Type::Map(_))
|
||||
| (Type::Number, Type::Number)
|
||||
| (Type::Number, Type::Integer)
|
||||
| (Type::Number, Type::Float)
|
||||
| (Type::Integer, Type::Number)
|
||||
| (Type::Float, Type::Number)
|
||||
| (Type::None, Type::None)
|
||||
| (Type::String, Type::String) => true,
|
||||
(Type::Custom(left), Type::Custom(right)) => left == right,
|
||||
(Type::Option(_), Type::None) => true,
|
||||
(Type::Option(left), Type::Option(right)) => {
|
||||
if let Type::Any = left.as_ref() {
|
||||
true
|
||||
} else if left == right {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(Type::List(self_item_type), Type::List(other_item_type)) => {
|
||||
self_item_type.accepts(&other_item_type)
|
||||
}
|
||||
(
|
||||
Type::Function {
|
||||
parameter_types: self_parameter_types,
|
||||
return_type: self_return_type,
|
||||
},
|
||||
Type::Function {
|
||||
parameter_types: other_parameter_types,
|
||||
return_type: other_return_type,
|
||||
},
|
||||
) => {
|
||||
let parameter_type_pairs = self_parameter_types
|
||||
.iter()
|
||||
.zip(other_parameter_types.iter());
|
||||
|
||||
for (self_parameter_type, other_parameter_type) in parameter_type_pairs {
|
||||
if self_parameter_type == other_parameter_type {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
self_return_type == other_return_type
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_function(&self) -> bool {
|
||||
matches!(self, Type::Function { .. })
|
||||
}
|
||||
|
||||
pub fn is_list(&self) -> bool {
|
||||
matches!(self, Type::List(_))
|
||||
}
|
||||
|
||||
pub fn is_map(&self) -> bool {
|
||||
matches!(self, Type::Map(_))
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for Type {
|
||||
fn from_syntax(node: SyntaxNode, _source: &str, _context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(_source, "type", node)?;
|
||||
|
||||
let type_node = node.child(0).unwrap();
|
||||
|
||||
let r#type = match type_node.kind() {
|
||||
"[" => {
|
||||
let item_type_node = node.child(1).unwrap();
|
||||
let item_type = Type::from_syntax(item_type_node, _source, _context)?;
|
||||
|
||||
Type::List(Box::new(item_type))
|
||||
}
|
||||
"any" => Type::Any,
|
||||
"bool" => Type::Boolean,
|
||||
"collection" => Type::Collection,
|
||||
"float" => Type::Float,
|
||||
"(" => {
|
||||
let child_count = node.child_count();
|
||||
let mut parameter_types = Vec::new();
|
||||
|
||||
for index in 1..child_count - 2 {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.is_named() {
|
||||
let parameter_type = Type::from_syntax(child, _source, _context)?;
|
||||
|
||||
parameter_types.push(parameter_type);
|
||||
}
|
||||
}
|
||||
|
||||
let final_node = node.child(child_count - 1).unwrap();
|
||||
let return_type = if final_node.is_named() {
|
||||
Type::from_syntax(final_node, _source, _context)?
|
||||
} else {
|
||||
Type::None
|
||||
};
|
||||
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
return_type: Box::new(return_type),
|
||||
}
|
||||
}
|
||||
"int" => Type::Integer,
|
||||
"map" => Type::Map(None),
|
||||
"num" => Type::Number,
|
||||
"none" => Type::None,
|
||||
"str" => Type::String,
|
||||
"option" => {
|
||||
let inner_type_node = node.child(2).unwrap();
|
||||
let inner_type = Type::from_syntax(inner_type_node, _source, _context)?;
|
||||
|
||||
Type::Option(Box::new(inner_type))
|
||||
}
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: "any, bool, float, int, num, str, option, (, [ or {".to_string(),
|
||||
actual: type_node.kind().to_string(),
|
||||
location: type_node.start_position(),
|
||||
relevant_source: _source[type_node.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(r#type)
|
||||
}
|
||||
|
||||
fn expected_type(&self, _context: &Map) -> Result<Type, ValidationError> {
|
||||
Ok(Type::None)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, _source: &str, _context: &Map) -> Result<Value, RuntimeError> {
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Type {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
match self {
|
||||
Type::Any => output.push_str("any"),
|
||||
Type::Boolean => output.push_str("bool"),
|
||||
Type::Collection => output.push_str("collection"),
|
||||
|
||||
Type::Custom(_) => todo!(),
|
||||
Type::Float => output.push_str("float"),
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
return_type,
|
||||
} => {
|
||||
output.push('(');
|
||||
|
||||
for (index, parameter_type) in parameter_types.iter().enumerate() {
|
||||
parameter_type.format(output, indent_level);
|
||||
|
||||
if index != parameter_types.len() - 1 {
|
||||
output.push(' ');
|
||||
}
|
||||
}
|
||||
|
||||
output.push_str(") -> ");
|
||||
return_type.format(output, indent_level);
|
||||
}
|
||||
Type::Integer => output.push_str("int"),
|
||||
Type::List(item_type) => {
|
||||
output.push('[');
|
||||
item_type.format(output, indent_level);
|
||||
output.push(']');
|
||||
}
|
||||
Type::Map(structure_option) => {
|
||||
if let Some(structure) = structure_option {
|
||||
output.push_str(&structure.to_string());
|
||||
} else {
|
||||
output.push_str("map");
|
||||
}
|
||||
}
|
||||
Type::None => output.push_str("none"),
|
||||
Type::Number => output.push_str("num"),
|
||||
Type::String => output.push_str("str"),
|
||||
Type::Option(optional_type) => {
|
||||
output.push_str("option(");
|
||||
optional_type.format(output, indent_level);
|
||||
output.push(')');
|
||||
}
|
||||
Type::Range => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Type::Any => write!(f, "any"),
|
||||
Type::Boolean => write!(f, "bool"),
|
||||
Type::Collection => write!(f, "collection"),
|
||||
Type::Custom(identifier) => write!(f, "{identifier}"),
|
||||
Type::Float => write!(f, "float"),
|
||||
Type::Function {
|
||||
parameter_types,
|
||||
return_type,
|
||||
} => {
|
||||
write!(f, "(")?;
|
||||
|
||||
for (index, parameter_type) in parameter_types.iter().enumerate() {
|
||||
write!(f, "{parameter_type}")?;
|
||||
|
||||
if index != parameter_types.len() - 1 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, ")")?;
|
||||
write!(f, " -> {return_type}")
|
||||
}
|
||||
Type::Integer => write!(f, "int"),
|
||||
Type::List(item_type) => write!(f, "[{item_type}]"),
|
||||
Type::Map(_) => write!(f, "map"),
|
||||
Type::Number => write!(f, "num"),
|
||||
Type::None => write!(f, "none"),
|
||||
Type::String => write!(f, "str"),
|
||||
Type::Option(inner_type) => {
|
||||
write!(f, "option({})", inner_type)
|
||||
}
|
||||
Type::Range => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Format, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct TypeSpecification {
|
||||
r#type: Type,
|
||||
}
|
||||
|
||||
impl TypeSpecification {
|
||||
pub fn new(r#type: Type) -> Self {
|
||||
Self { r#type }
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> &Type {
|
||||
&self.r#type
|
||||
}
|
||||
|
||||
pub fn take_inner(self) -> Type {
|
||||
self.r#type
|
||||
}
|
||||
}
|
||||
|
||||
impl AbstractTree for TypeSpecification {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "type_specification", node)?;
|
||||
|
||||
let type_node = node.child(1).unwrap();
|
||||
let r#type = Type::from_syntax(type_node, source, context)?;
|
||||
|
||||
Ok(TypeSpecification { r#type })
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
self.r#type.expected_type(context)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
self.r#type.run(source, context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for TypeSpecification {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
output.push('<');
|
||||
self.r#type.format(output, indent_level);
|
||||
output.push('>');
|
||||
}
|
||||
}
|
@ -1,447 +0,0 @@
|
||||
use std::{cmp::Ordering, collections::BTreeMap, ops::RangeInclusive};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, BuiltInValue, Expression, Format, Function, FunctionNode, Identifier, List, Map,
|
||||
SourcePosition, Statement, Structure, SyntaxNode, Type, TypeSpecification, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub enum ValueNode {
|
||||
Boolean(String),
|
||||
Float(String),
|
||||
Function(Function),
|
||||
Integer(String),
|
||||
String(String),
|
||||
List(Vec<Expression>),
|
||||
Option(Option<Box<Expression>>),
|
||||
Map(BTreeMap<String, (Statement, Option<Type>)>, SourcePosition),
|
||||
BuiltInValue(BuiltInValue),
|
||||
Structure(BTreeMap<String, (Option<Statement>, Type)>),
|
||||
Range(RangeInclusive<i64>),
|
||||
}
|
||||
|
||||
impl AbstractTree for ValueNode {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "value", node)?;
|
||||
|
||||
let child = node.child(0).unwrap();
|
||||
let value_node = match child.kind() {
|
||||
"boolean" => ValueNode::Boolean(source[child.byte_range()].to_string()),
|
||||
"float" => ValueNode::Float(source[child.byte_range()].to_string()),
|
||||
"function" => {
|
||||
let function_node = FunctionNode::from_syntax(child, source, context)?;
|
||||
|
||||
ValueNode::Function(Function::ContextDefined(function_node))
|
||||
}
|
||||
"integer" => ValueNode::Integer(source[child.byte_range()].to_string()),
|
||||
"string" => {
|
||||
let without_quotes = child.start_byte() + 1..child.end_byte() - 1;
|
||||
|
||||
ValueNode::String(source[without_quotes].to_string())
|
||||
}
|
||||
"list" => {
|
||||
let mut expressions = Vec::new();
|
||||
|
||||
for index in 1..child.child_count() - 1 {
|
||||
let current_node = child.child(index).unwrap();
|
||||
|
||||
if current_node.is_named() {
|
||||
let expression = Expression::from_syntax(current_node, source, context)?;
|
||||
|
||||
expressions.push(expression);
|
||||
}
|
||||
}
|
||||
|
||||
ValueNode::List(expressions)
|
||||
}
|
||||
"map" => {
|
||||
let mut child_nodes = BTreeMap::new();
|
||||
let mut current_key = "".to_string();
|
||||
let mut current_type = None;
|
||||
|
||||
for index in 0..child.child_count() - 1 {
|
||||
let child = child.child(index).unwrap();
|
||||
|
||||
if child.kind() == "identifier" {
|
||||
current_key = Identifier::from_syntax(child, source, context)?.take_inner();
|
||||
current_type = None;
|
||||
}
|
||||
|
||||
if child.kind() == "type_specification" {
|
||||
current_type = Some(
|
||||
TypeSpecification::from_syntax(child, source, context)?.take_inner(),
|
||||
);
|
||||
}
|
||||
|
||||
if child.kind() == "statement" {
|
||||
let statement = Statement::from_syntax(child, source, context)?;
|
||||
|
||||
child_nodes.insert(current_key.clone(), (statement, current_type.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
ValueNode::Map(child_nodes, SourcePosition::from(child.range()))
|
||||
}
|
||||
"option" => {
|
||||
let first_grandchild = child.child(0).unwrap();
|
||||
|
||||
if first_grandchild.kind() == "none" {
|
||||
ValueNode::Option(None)
|
||||
} else {
|
||||
let expression_node = child.child(2).unwrap();
|
||||
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||
|
||||
ValueNode::Option(Some(Box::new(expression)))
|
||||
}
|
||||
}
|
||||
"built_in_value" => {
|
||||
let built_in_value_node = child.child(0).unwrap();
|
||||
|
||||
ValueNode::BuiltInValue(BuiltInValue::from_syntax(
|
||||
built_in_value_node,
|
||||
source,
|
||||
context,
|
||||
)?)
|
||||
}
|
||||
"structure" => {
|
||||
let mut btree_map = BTreeMap::new();
|
||||
let mut current_identifier: Option<Identifier> = None;
|
||||
let mut current_type: Option<Type> = None;
|
||||
let mut current_statement = None;
|
||||
|
||||
for index in 2..child.child_count() - 1 {
|
||||
let child_syntax_node = child.child(index).unwrap();
|
||||
|
||||
if child_syntax_node.kind() == "identifier" {
|
||||
if current_statement.is_none() {
|
||||
if let (Some(identifier), Some(r#type)) =
|
||||
(¤t_identifier, ¤t_type)
|
||||
{
|
||||
btree_map
|
||||
.insert(identifier.inner().clone(), (None, r#type.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
current_type = None;
|
||||
current_identifier =
|
||||
Some(Identifier::from_syntax(child_syntax_node, source, context)?);
|
||||
}
|
||||
|
||||
if child_syntax_node.kind() == "type_specification" {
|
||||
current_type = Some(
|
||||
TypeSpecification::from_syntax(child_syntax_node, source, context)?
|
||||
.take_inner(),
|
||||
);
|
||||
}
|
||||
|
||||
if child_syntax_node.kind() == "statement" {
|
||||
current_statement =
|
||||
Some(Statement::from_syntax(child_syntax_node, source, context)?);
|
||||
|
||||
// if let Some(identifier) = ¤t_identifier {
|
||||
// let r#type = if let Some(r#type) = ¤t_type {
|
||||
// r#type.clone()
|
||||
// } else if let Some(statement) = ¤t_statement {
|
||||
// statement.expected_type(context)?
|
||||
// } else {
|
||||
// Type::None
|
||||
// };
|
||||
|
||||
// btree_map.insert(
|
||||
// identifier.inner().clone(),
|
||||
// (current_statement.clone(), r#type.clone()),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
ValueNode::Structure(btree_map)
|
||||
}
|
||||
"range" => {
|
||||
let mut split = source[child.byte_range()].split("..");
|
||||
let start = split.next().unwrap().parse().unwrap();
|
||||
let end = split.next().unwrap().parse().unwrap();
|
||||
|
||||
ValueNode::Range(start..=end)
|
||||
}
|
||||
_ => {
|
||||
return Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected:
|
||||
"string, integer, float, boolean, range, list, map, option, function or structure"
|
||||
.to_string(),
|
||||
actual: child.kind().to_string(),
|
||||
location: child.start_position(),
|
||||
relevant_source: source[child.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
Ok(value_node)
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
let r#type = match self {
|
||||
ValueNode::Boolean(_) => Type::Boolean,
|
||||
ValueNode::Float(_) => Type::Float,
|
||||
ValueNode::Function(function) => function.r#type().clone(),
|
||||
ValueNode::Integer(_) => Type::Integer,
|
||||
ValueNode::String(_) => Type::String,
|
||||
ValueNode::List(expressions) => {
|
||||
let mut previous_type = None;
|
||||
|
||||
for expression in expressions {
|
||||
let expression_type = expression.expected_type(context)?;
|
||||
|
||||
if let Some(previous) = previous_type {
|
||||
if expression_type != previous {
|
||||
return Ok(Type::List(Box::new(Type::Any)));
|
||||
}
|
||||
}
|
||||
|
||||
previous_type = Some(expression_type);
|
||||
}
|
||||
|
||||
if let Some(previous) = previous_type {
|
||||
Type::List(Box::new(previous))
|
||||
} else {
|
||||
Type::List(Box::new(Type::Any))
|
||||
}
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
if let Some(expression) = option {
|
||||
Type::Option(Box::new(expression.expected_type(context)?))
|
||||
} else {
|
||||
Type::None
|
||||
}
|
||||
}
|
||||
ValueNode::Map(_, _) => Type::Map(None),
|
||||
ValueNode::BuiltInValue(built_in_value) => built_in_value.expected_type(context)?,
|
||||
ValueNode::Structure(node_map) => {
|
||||
let mut value_map = BTreeMap::new();
|
||||
|
||||
for (key, (_statement_option, r#type)) in node_map {
|
||||
value_map.insert(key.to_string(), (None, r#type.clone()));
|
||||
}
|
||||
|
||||
Type::Map(Some(Structure::new(value_map)))
|
||||
}
|
||||
ValueNode::Range(_) => Type::Range,
|
||||
};
|
||||
|
||||
Ok(r#type)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, context: &Map) -> Result<(), ValidationError> {
|
||||
match self {
|
||||
ValueNode::Function(function) => {
|
||||
if let Function::ContextDefined(function_node) = function {
|
||||
function_node.validate(_source, context)?;
|
||||
}
|
||||
}
|
||||
ValueNode::Map(statements, source_position) => {
|
||||
for (key, (statement, r#type)) in statements {
|
||||
if let Some(expected) = r#type {
|
||||
let actual = statement.expected_type(context)?;
|
||||
|
||||
if !expected.accepts(&actual) {
|
||||
return Err(ValidationError::TypeCheck {
|
||||
expected: expected.clone(),
|
||||
actual,
|
||||
position: source_position.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
let value = match self {
|
||||
ValueNode::Boolean(value_source) => Value::Boolean(value_source.parse().unwrap()),
|
||||
ValueNode::Float(value_source) => {
|
||||
let float = value_source.parse()?;
|
||||
|
||||
Value::Float(float)
|
||||
}
|
||||
ValueNode::Function(function) => Value::Function(function.clone()),
|
||||
ValueNode::Integer(value_source) => Value::Integer(value_source.parse().unwrap()),
|
||||
ValueNode::String(value_source) => Value::string(value_source.clone()),
|
||||
ValueNode::List(expressions) => {
|
||||
let mut values = Vec::with_capacity(expressions.len());
|
||||
|
||||
for node in expressions {
|
||||
let value = node.run(source, context)?;
|
||||
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
Value::List(List::with_items(values))
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
let option_value = if let Some(expression) = option {
|
||||
Some(Box::new(expression.run(source, context)?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Value::Option(option_value)
|
||||
}
|
||||
ValueNode::Map(key_statement_pairs, _) => {
|
||||
let map = Map::new();
|
||||
|
||||
{
|
||||
for (key, (statement, _)) in key_statement_pairs {
|
||||
let value = statement.run(source, context)?;
|
||||
|
||||
map.set(key.clone(), value)?;
|
||||
}
|
||||
}
|
||||
|
||||
Value::Map(map)
|
||||
}
|
||||
ValueNode::BuiltInValue(built_in_value) => built_in_value.run(source, context)?,
|
||||
ValueNode::Structure(node_map) => {
|
||||
let mut value_map = BTreeMap::new();
|
||||
|
||||
for (key, (statement_option, r#type)) in node_map {
|
||||
let value_option = if let Some(statement) = statement_option {
|
||||
Some(statement.run(source, context)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
value_map.insert(key.to_string(), (value_option, r#type.clone()));
|
||||
}
|
||||
|
||||
Value::Structure(Structure::new(value_map))
|
||||
}
|
||||
ValueNode::Range(range) => Value::Range(range.clone()),
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for ValueNode {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
match self {
|
||||
ValueNode::Boolean(source) | ValueNode::Float(source) | ValueNode::Integer(source) => {
|
||||
output.push_str(source)
|
||||
}
|
||||
ValueNode::String(source) => {
|
||||
output.push('\'');
|
||||
output.push_str(source);
|
||||
output.push('\'');
|
||||
}
|
||||
ValueNode::Function(function) => function.format(output, indent_level),
|
||||
ValueNode::List(expressions) => {
|
||||
output.push('[');
|
||||
|
||||
for expression in expressions {
|
||||
expression.format(output, indent_level);
|
||||
}
|
||||
|
||||
output.push(']');
|
||||
}
|
||||
ValueNode::Option(option) => {
|
||||
if let Some(expression) = option {
|
||||
output.push_str("some(");
|
||||
expression.format(output, indent_level);
|
||||
output.push(')');
|
||||
} else {
|
||||
output.push_str("none");
|
||||
}
|
||||
}
|
||||
ValueNode::Map(nodes, _) => {
|
||||
output.push_str("{\n");
|
||||
|
||||
for (key, (statement, type_option)) in nodes {
|
||||
if let Some(r#type) = type_option {
|
||||
ValueNode::indent(output, indent_level + 1);
|
||||
output.push_str(key);
|
||||
output.push_str(" <");
|
||||
r#type.format(output, 0);
|
||||
output.push_str("> = ");
|
||||
statement.format(output, 0);
|
||||
} else {
|
||||
ValueNode::indent(output, indent_level + 1);
|
||||
output.push_str(key);
|
||||
output.push_str(" = ");
|
||||
statement.format(output, 0);
|
||||
}
|
||||
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
ValueNode::indent(output, indent_level);
|
||||
output.push('}');
|
||||
}
|
||||
ValueNode::BuiltInValue(built_in_value) => built_in_value.format(output, indent_level),
|
||||
ValueNode::Structure(nodes) => {
|
||||
output.push('{');
|
||||
|
||||
for (key, (value_option, r#type)) in nodes {
|
||||
if let Some(value) = value_option {
|
||||
output.push_str(" ");
|
||||
output.push_str(key);
|
||||
output.push_str(" <");
|
||||
r#type.format(output, indent_level);
|
||||
output.push_str("> = ");
|
||||
value.format(output, indent_level);
|
||||
} else {
|
||||
output.push_str(" ");
|
||||
output.push_str(key);
|
||||
output.push_str(" <");
|
||||
r#type.format(output, indent_level);
|
||||
output.push('>');
|
||||
}
|
||||
}
|
||||
|
||||
output.push('}');
|
||||
}
|
||||
ValueNode::Range(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ValueNode {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match (self, other) {
|
||||
(ValueNode::Boolean(left), ValueNode::Boolean(right)) => left.cmp(right),
|
||||
(ValueNode::Boolean(_), _) => Ordering::Greater,
|
||||
(ValueNode::Float(left), ValueNode::Float(right)) => left.cmp(right),
|
||||
(ValueNode::Float(_), _) => Ordering::Greater,
|
||||
(ValueNode::Function(left), ValueNode::Function(right)) => left.cmp(right),
|
||||
(ValueNode::Function(_), _) => Ordering::Greater,
|
||||
(ValueNode::Integer(left), ValueNode::Integer(right)) => left.cmp(right),
|
||||
(ValueNode::Integer(_), _) => Ordering::Greater,
|
||||
(ValueNode::String(left), ValueNode::String(right)) => left.cmp(right),
|
||||
(ValueNode::String(_), _) => Ordering::Greater,
|
||||
(ValueNode::List(left), ValueNode::List(right)) => left.cmp(right),
|
||||
(ValueNode::List(_), _) => Ordering::Greater,
|
||||
(ValueNode::Option(left), ValueNode::Option(right)) => left.cmp(right),
|
||||
(ValueNode::Option(_), _) => Ordering::Greater,
|
||||
(ValueNode::Map(left, _), ValueNode::Map(right, _)) => left.cmp(right),
|
||||
(ValueNode::Map(_, _), _) => Ordering::Greater,
|
||||
(ValueNode::BuiltInValue(left), ValueNode::BuiltInValue(right)) => left.cmp(right),
|
||||
(ValueNode::BuiltInValue(_), _) => Ordering::Greater,
|
||||
(ValueNode::Structure(left), ValueNode::Structure(right)) => left.cmp(right),
|
||||
(ValueNode::Structure(_), _) => Ordering::Greater,
|
||||
(ValueNode::Range(left), ValueNode::Range(right)) => left.clone().cmp(right.clone()),
|
||||
(ValueNode::Range(_), _) => Ordering::Less,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ValueNode {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
AbstractTree, Block, Expression, Format, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a while loop.
|
||||
///
|
||||
/// While executes its block repeatedly until its expression evaluates to true.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct While {
|
||||
expression: Expression,
|
||||
block: Block,
|
||||
}
|
||||
|
||||
impl AbstractTree for While {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "while", node)?;
|
||||
|
||||
let expression_node = node.child(1).unwrap();
|
||||
let expression = Expression::from_syntax(expression_node, source, context)?;
|
||||
|
||||
let block_node = node.child(2).unwrap();
|
||||
let block = Block::from_syntax(block_node, source, context)?;
|
||||
|
||||
Ok(While { expression, block })
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
self.block.expected_type(context)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, context: &Map) -> Result<(), ValidationError> {
|
||||
self.expression.validate(_source, context)?;
|
||||
self.block.validate(_source, context)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
while self.expression.run(source, context)?.as_boolean()? {
|
||||
self.block.run(source, context)?;
|
||||
}
|
||||
|
||||
Ok(Value::none())
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for While {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
output.push('\n');
|
||||
While::indent(output, indent_level);
|
||||
output.push_str("while ");
|
||||
self.expression.format(output, indent_level);
|
||||
output.push(' ');
|
||||
self.block.format(output, indent_level);
|
||||
output.push('\n');
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
error::{RuntimeError, SyntaxError, ValidationError},
|
||||
function_expression::FunctionExpression,
|
||||
AbstractTree, Expression, Format, FunctionCall, Map, SyntaxNode, Type, Value,
|
||||
};
|
||||
|
||||
/// Abstract representation of a yield expression.
|
||||
///
|
||||
/// Yield is an alternate means of calling and passing values to a function.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct Yield {
|
||||
call: FunctionCall,
|
||||
}
|
||||
|
||||
impl AbstractTree for Yield {
|
||||
fn from_syntax(node: SyntaxNode, source: &str, context: &Map) -> Result<Self, SyntaxError> {
|
||||
SyntaxError::expect_syntax_node(source, "yield", node)?;
|
||||
|
||||
let input_node = node.child(0).unwrap();
|
||||
let input = Expression::from_syntax(input_node, source, context)?;
|
||||
|
||||
let function_node = node.child(2).unwrap();
|
||||
let function_expression = FunctionExpression::from_syntax(function_node, source, context)?;
|
||||
|
||||
let mut arguments = Vec::new();
|
||||
|
||||
arguments.push(input);
|
||||
|
||||
for index in 3..node.child_count() - 1 {
|
||||
let child = node.child(index).unwrap();
|
||||
|
||||
if child.is_named() {
|
||||
let expression = Expression::from_syntax(child, source, context)?;
|
||||
|
||||
arguments.push(expression);
|
||||
}
|
||||
}
|
||||
|
||||
let call = FunctionCall::new(function_expression, arguments, node.range().into());
|
||||
|
||||
Ok(Yield { call })
|
||||
}
|
||||
|
||||
fn expected_type(&self, context: &Map) -> Result<Type, ValidationError> {
|
||||
self.call.expected_type(context)
|
||||
}
|
||||
|
||||
fn validate(&self, _source: &str, _context: &Map) -> Result<(), ValidationError> {
|
||||
self.call.validate(_source, _context)
|
||||
}
|
||||
|
||||
fn run(&self, source: &str, context: &Map) -> Result<Value, RuntimeError> {
|
||||
self.call.run(source, context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Yield {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
self.call.format(output, indent_level);
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use enum_iterator::{all, Sequence};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::RuntimeError, Map, Type, Value};
|
||||
|
||||
use super::Callable;
|
||||
|
||||
pub fn fs_functions() -> impl Iterator<Item = Fs> {
|
||||
all()
|
||||
}
|
||||
|
||||
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Fs {
|
||||
ReadFile,
|
||||
}
|
||||
|
||||
impl Callable for Fs {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Fs::ReadFile => "read_file",
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Fs::ReadFile => "Read the contents of a file to a string.",
|
||||
}
|
||||
}
|
||||
|
||||
fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Fs::ReadFile => Type::function(vec![Type::String], Type::String),
|
||||
}
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
_source: &str,
|
||||
_outer_context: &Map,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
Fs::ReadFile => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let path = arguments.first().unwrap().as_string()?;
|
||||
let file_content = read_to_string(path.as_str())?;
|
||||
|
||||
Ok(Value::string(file_content))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
use enum_iterator::Sequence;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::RuntimeError, Map, Type, Value};
|
||||
|
||||
use super::Callable;
|
||||
|
||||
pub fn json_functions() -> impl Iterator<Item = Json> {
|
||||
enum_iterator::all()
|
||||
}
|
||||
|
||||
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Json {
|
||||
Create,
|
||||
CreatePretty,
|
||||
Parse,
|
||||
}
|
||||
|
||||
impl Callable for Json {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Json::Create => "create",
|
||||
Json::CreatePretty => "create_pretty",
|
||||
Json::Parse => "parse",
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Json::Create => "Convert a value to a JSON string.",
|
||||
Json::CreatePretty => "Convert a value to a formatted JSON string.",
|
||||
Json::Parse => "Convert JSON to a value",
|
||||
}
|
||||
}
|
||||
|
||||
fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Json::Create => Type::function(vec![Type::Any], Type::String),
|
||||
Json::CreatePretty => Type::function(vec![Type::Any], Type::String),
|
||||
Json::Parse => Type::function(vec![Type::String], Type::Any),
|
||||
}
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
_source: &str,
|
||||
_outer_context: &Map,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
Json::Create => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let value = arguments.first().unwrap();
|
||||
let json_string = serde_json::to_string(value)?;
|
||||
|
||||
Ok(Value::String(json_string))
|
||||
}
|
||||
Json::CreatePretty => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let value = arguments.first().unwrap();
|
||||
let json_string = serde_json::to_string_pretty(value)?;
|
||||
|
||||
Ok(Value::String(json_string))
|
||||
}
|
||||
Json::Parse => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let json_string = arguments.first().unwrap().as_string()?;
|
||||
let value = serde_json::from_str(json_string)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
pub mod fs;
|
||||
pub mod json;
|
||||
pub mod str;
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use rand::{random, thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::RuntimeError, Format, Map, Type, Value};
|
||||
|
||||
use self::{fs::Fs, json::Json, str::StrFunction};
|
||||
|
||||
pub trait Callable {
|
||||
fn name(&self) -> &'static str;
|
||||
fn description(&self) -> &'static str;
|
||||
fn r#type(&self) -> Type;
|
||||
fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
source: &str,
|
||||
outer_context: &Map,
|
||||
) -> Result<Value, RuntimeError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum BuiltInFunction {
|
||||
AssertEqual,
|
||||
Fs(Fs),
|
||||
Json(Json),
|
||||
Length,
|
||||
Output,
|
||||
RandomBoolean,
|
||||
RandomFloat,
|
||||
RandomFrom,
|
||||
RandomInteger,
|
||||
String(StrFunction),
|
||||
}
|
||||
|
||||
impl Callable for BuiltInFunction {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
BuiltInFunction::AssertEqual => "assert_equal",
|
||||
BuiltInFunction::Fs(fs_function) => fs_function.name(),
|
||||
BuiltInFunction::Json(json_function) => json_function.name(),
|
||||
BuiltInFunction::Length => "length",
|
||||
BuiltInFunction::Output => "output",
|
||||
BuiltInFunction::RandomBoolean => "boolean",
|
||||
BuiltInFunction::RandomFloat => "float",
|
||||
BuiltInFunction::RandomFrom => "from",
|
||||
BuiltInFunction::RandomInteger => "integer",
|
||||
BuiltInFunction::String(string_function) => string_function.name(),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
BuiltInFunction::AssertEqual => "assert_equal",
|
||||
BuiltInFunction::Fs(fs_function) => fs_function.description(),
|
||||
BuiltInFunction::Json(json_function) => json_function.description(),
|
||||
BuiltInFunction::Length => "length",
|
||||
BuiltInFunction::Output => "output",
|
||||
BuiltInFunction::RandomBoolean => "boolean",
|
||||
BuiltInFunction::RandomFloat => "float",
|
||||
BuiltInFunction::RandomFrom => "from",
|
||||
BuiltInFunction::RandomInteger => "integer",
|
||||
BuiltInFunction::String(string_function) => string_function.description(),
|
||||
}
|
||||
}
|
||||
|
||||
fn r#type(&self) -> Type {
|
||||
match self {
|
||||
BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None),
|
||||
BuiltInFunction::Fs(fs_function) => fs_function.r#type(),
|
||||
BuiltInFunction::Json(json_function) => json_function.r#type(),
|
||||
BuiltInFunction::Length => Type::function(vec![Type::Collection], Type::Integer),
|
||||
BuiltInFunction::Output => Type::function(vec![Type::Any], Type::None),
|
||||
BuiltInFunction::RandomBoolean => Type::function(vec![], Type::Boolean),
|
||||
BuiltInFunction::RandomFloat => Type::function(vec![], Type::Float),
|
||||
BuiltInFunction::RandomFrom => Type::function(vec![Type::Collection], Type::Any),
|
||||
BuiltInFunction::RandomInteger => Type::function(vec![], Type::Integer),
|
||||
BuiltInFunction::String(string_function) => string_function.r#type(),
|
||||
}
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
_source: &str,
|
||||
_outer_context: &Map,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
BuiltInFunction::AssertEqual => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let left = arguments.first().unwrap();
|
||||
let right = arguments.get(1).unwrap();
|
||||
|
||||
Ok(Value::Boolean(left == right))
|
||||
}
|
||||
BuiltInFunction::Fs(fs_function) => {
|
||||
fs_function.call(arguments, _source, _outer_context)
|
||||
}
|
||||
BuiltInFunction::Json(json_function) => {
|
||||
json_function.call(arguments, _source, _outer_context)
|
||||
}
|
||||
BuiltInFunction::Length => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let value = arguments.first().unwrap();
|
||||
let length = if let Ok(list) = value.as_list() {
|
||||
list.items().len()
|
||||
} else if let Ok(map) = value.as_map() {
|
||||
map.variables()?.len()
|
||||
} else if let Ok(str) = value.as_string() {
|
||||
str.chars().count()
|
||||
} else {
|
||||
return Err(RuntimeError::ExpectedCollection {
|
||||
actual: value.clone(),
|
||||
});
|
||||
};
|
||||
|
||||
Ok(Value::Integer(length as i64))
|
||||
}
|
||||
BuiltInFunction::Output => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let value = arguments.first().unwrap();
|
||||
|
||||
println!("{value}");
|
||||
|
||||
Ok(Value::none())
|
||||
}
|
||||
BuiltInFunction::RandomBoolean => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
|
||||
Ok(Value::Boolean(random()))
|
||||
}
|
||||
BuiltInFunction::RandomFloat => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
|
||||
Ok(Value::Float(random()))
|
||||
}
|
||||
BuiltInFunction::RandomFrom => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let value = arguments.first().unwrap();
|
||||
|
||||
if let Ok(list) = value.as_list() {
|
||||
let items = list.items();
|
||||
|
||||
if items.len() == 0 {
|
||||
Ok(Value::none())
|
||||
} else {
|
||||
let random_index = thread_rng().gen_range(0..items.len());
|
||||
let random_value = items.get(random_index).cloned().unwrap_or_default();
|
||||
|
||||
Ok(random_value)
|
||||
}
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
BuiltInFunction::RandomInteger => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 0, arguments.len())?;
|
||||
|
||||
Ok(Value::Integer(random()))
|
||||
}
|
||||
BuiltInFunction::String(string_function) => {
|
||||
string_function.call(arguments, _source, _outer_context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for BuiltInFunction {
|
||||
fn format(&self, output: &mut String, _indent_level: u8) {
|
||||
output.push_str(self.name());
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BuiltInFunction {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
@ -1,564 +0,0 @@
|
||||
use enum_iterator::Sequence;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{error::RuntimeError, List, Map, Type, Value};
|
||||
|
||||
use super::Callable;
|
||||
|
||||
pub fn string_functions() -> impl Iterator<Item = StrFunction> {
|
||||
enum_iterator::all()
|
||||
}
|
||||
|
||||
#[derive(Sequence, Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum StrFunction {
|
||||
AsBytes,
|
||||
EndsWith,
|
||||
Find,
|
||||
Insert,
|
||||
IsAscii,
|
||||
IsEmpty,
|
||||
Lines,
|
||||
Matches,
|
||||
Parse,
|
||||
Remove,
|
||||
ReplaceRange,
|
||||
Retain,
|
||||
Split,
|
||||
SplitAt,
|
||||
SplitInclusive,
|
||||
SplitN,
|
||||
SplitOnce,
|
||||
SplitTerminator,
|
||||
SplitWhitespace,
|
||||
StartsWith,
|
||||
StripPrefix,
|
||||
ToLowercase,
|
||||
ToUppercase,
|
||||
Trim,
|
||||
TrimEnd,
|
||||
TrimEndMatches,
|
||||
TrimMatches,
|
||||
TrimStart,
|
||||
TrimStartMatches,
|
||||
Truncate,
|
||||
}
|
||||
|
||||
impl Callable for StrFunction {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
StrFunction::AsBytes => "as_bytes",
|
||||
StrFunction::EndsWith => "ends_with",
|
||||
StrFunction::Find => "find",
|
||||
StrFunction::Insert => "insert",
|
||||
StrFunction::IsAscii => "is_ascii",
|
||||
StrFunction::IsEmpty => "is_empty",
|
||||
StrFunction::Lines => "lines",
|
||||
StrFunction::Matches => "matches",
|
||||
StrFunction::Parse => "parse",
|
||||
StrFunction::Remove => "remove",
|
||||
StrFunction::ReplaceRange => "replace_range",
|
||||
StrFunction::Retain => "retain",
|
||||
StrFunction::Split => "split",
|
||||
StrFunction::SplitAt => "split_at",
|
||||
StrFunction::SplitInclusive => "split_inclusive",
|
||||
StrFunction::SplitN => "split_n",
|
||||
StrFunction::SplitOnce => "split_once",
|
||||
StrFunction::SplitTerminator => "split_terminator",
|
||||
StrFunction::SplitWhitespace => "split_whitespace",
|
||||
StrFunction::StartsWith => "starts_with",
|
||||
StrFunction::StripPrefix => "strip_prefix",
|
||||
StrFunction::ToLowercase => "to_lowercase",
|
||||
StrFunction::ToUppercase => "to_uppercase",
|
||||
StrFunction::Trim => "trim",
|
||||
StrFunction::TrimEnd => "trim_end",
|
||||
StrFunction::TrimEndMatches => "trim_end_matches",
|
||||
StrFunction::TrimMatches => "trim_matches",
|
||||
StrFunction::TrimStart => "trim_start",
|
||||
StrFunction::TrimStartMatches => "trim_start_matches",
|
||||
StrFunction::Truncate => "truncate",
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
StrFunction::AsBytes => "TODO",
|
||||
StrFunction::EndsWith => "TODO",
|
||||
StrFunction::Find => "TODO",
|
||||
StrFunction::Insert => "TODO",
|
||||
StrFunction::IsAscii => "TODO",
|
||||
StrFunction::IsEmpty => "TODO",
|
||||
StrFunction::Lines => "TODO",
|
||||
StrFunction::Matches => "TODO",
|
||||
StrFunction::Parse => "TODO",
|
||||
StrFunction::Remove => "TODO",
|
||||
StrFunction::ReplaceRange => "TODO",
|
||||
StrFunction::Retain => "TODO",
|
||||
StrFunction::Split => "TODO",
|
||||
StrFunction::SplitAt => "TODO",
|
||||
StrFunction::SplitInclusive => "TODO",
|
||||
StrFunction::SplitN => "TODO",
|
||||
StrFunction::SplitOnce => "TODO",
|
||||
StrFunction::SplitTerminator => "TODO",
|
||||
StrFunction::SplitWhitespace => "TODO",
|
||||
StrFunction::StartsWith => "TODO",
|
||||
StrFunction::StripPrefix => "TODO",
|
||||
StrFunction::ToLowercase => "TODO",
|
||||
StrFunction::ToUppercase => "TODO",
|
||||
StrFunction::Trim => "TODO",
|
||||
StrFunction::TrimEnd => "TODO",
|
||||
StrFunction::TrimEndMatches => "TODO",
|
||||
StrFunction::TrimMatches => "TODO",
|
||||
StrFunction::TrimStart => "TODO",
|
||||
StrFunction::TrimStartMatches => "TODO",
|
||||
StrFunction::Truncate => "TODO",
|
||||
}
|
||||
}
|
||||
|
||||
fn r#type(&self) -> Type {
|
||||
match self {
|
||||
StrFunction::AsBytes => Type::function(vec![Type::String], Type::list(Type::Integer)),
|
||||
StrFunction::EndsWith => {
|
||||
Type::function(vec![Type::String, Type::String], Type::Boolean)
|
||||
}
|
||||
StrFunction::Find => Type::function(
|
||||
vec![Type::String, Type::String],
|
||||
Type::option(Type::Integer),
|
||||
),
|
||||
StrFunction::Insert => Type::function(
|
||||
vec![Type::String, Type::Integer, Type::String],
|
||||
Type::String,
|
||||
),
|
||||
StrFunction::IsAscii => Type::function(vec![Type::String], Type::Boolean),
|
||||
StrFunction::IsEmpty => Type::function(vec![Type::String], Type::Boolean),
|
||||
StrFunction::Lines => Type::function(vec![Type::String], Type::list(Type::String)),
|
||||
StrFunction::Matches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StrFunction::Parse => Type::function(vec![Type::String], Type::Any),
|
||||
StrFunction::Remove => Type::function(
|
||||
vec![Type::String, Type::Integer],
|
||||
Type::option(Type::String),
|
||||
),
|
||||
StrFunction::ReplaceRange => Type::function(
|
||||
vec![Type::String, Type::list(Type::Integer), Type::String],
|
||||
Type::String,
|
||||
),
|
||||
StrFunction::Retain => Type::function(
|
||||
vec![
|
||||
Type::String,
|
||||
Type::function(vec![Type::String], Type::Boolean),
|
||||
],
|
||||
Type::String,
|
||||
),
|
||||
StrFunction::Split => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StrFunction::SplitAt => {
|
||||
Type::function(vec![Type::String, Type::Integer], Type::list(Type::String))
|
||||
}
|
||||
StrFunction::SplitInclusive => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StrFunction::SplitN => Type::function(
|
||||
vec![Type::String, Type::Integer, Type::String],
|
||||
Type::list(Type::String),
|
||||
),
|
||||
StrFunction::SplitOnce => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StrFunction::SplitTerminator => {
|
||||
Type::function(vec![Type::String, Type::String], Type::list(Type::String))
|
||||
}
|
||||
StrFunction::SplitWhitespace => {
|
||||
Type::function(vec![Type::String], Type::list(Type::String))
|
||||
}
|
||||
StrFunction::StartsWith => {
|
||||
Type::function(vec![Type::String, Type::String], Type::Boolean)
|
||||
}
|
||||
StrFunction::StripPrefix => {
|
||||
Type::function(vec![Type::String, Type::String], Type::option(Type::String))
|
||||
}
|
||||
StrFunction::ToLowercase => Type::function(vec![Type::String], Type::String),
|
||||
StrFunction::ToUppercase => Type::function(vec![Type::String], Type::String),
|
||||
StrFunction::Truncate => {
|
||||
Type::function(vec![Type::String, Type::Integer], Type::String)
|
||||
}
|
||||
StrFunction::Trim => Type::function(vec![Type::String], Type::String),
|
||||
StrFunction::TrimEnd => Type::function(vec![Type::String], Type::String),
|
||||
StrFunction::TrimEndMatches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::String)
|
||||
}
|
||||
StrFunction::TrimMatches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::String)
|
||||
}
|
||||
StrFunction::TrimStart => Type::function(vec![Type::String], Type::String),
|
||||
StrFunction::TrimStartMatches => {
|
||||
Type::function(vec![Type::String, Type::String], Type::String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
_source: &str,
|
||||
_outer_context: &Map,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
let value = match self {
|
||||
StrFunction::AsBytes => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let bytes = string
|
||||
.bytes()
|
||||
.map(|byte| Value::Integer(byte as i64))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(bytes))
|
||||
}
|
||||
StrFunction::EndsWith => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
|
||||
Value::Boolean(string.ends_with(pattern))
|
||||
}
|
||||
StrFunction::Find => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let find = string
|
||||
.find(pattern)
|
||||
.map(|index| Box::new(Value::Integer(index as i64)));
|
||||
|
||||
Value::Option(find)
|
||||
}
|
||||
StrFunction::IsAscii => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
|
||||
Value::Boolean(string.is_ascii())
|
||||
}
|
||||
StrFunction::IsEmpty => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
|
||||
Value::Boolean(string.is_empty())
|
||||
}
|
||||
StrFunction::Insert => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||
|
||||
let mut string = arguments.first().unwrap().as_string()?.clone();
|
||||
let index = arguments.get(1).unwrap().as_integer()? as usize;
|
||||
let insertion = arguments.get(2).unwrap().as_string()?;
|
||||
|
||||
string.insert_str(index, insertion);
|
||||
|
||||
Value::String(string)
|
||||
}
|
||||
StrFunction::Lines => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let lines = string
|
||||
.lines()
|
||||
.map(|line| Value::string(line.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(lines))
|
||||
}
|
||||
StrFunction::Matches => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let matches = string
|
||||
.matches(pattern)
|
||||
.map(|pattern| Value::string(pattern.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(matches))
|
||||
}
|
||||
StrFunction::Parse => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
|
||||
if let Ok(integer) = string.parse::<i64>() {
|
||||
Value::option(Some(Value::Integer(integer)))
|
||||
} else if let Ok(float) = string.parse::<f64>() {
|
||||
Value::option(Some(Value::Float(float)))
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
StrFunction::Remove => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let index = arguments.get(1).unwrap().as_integer()? as usize;
|
||||
let chars = string.chars().collect::<Vec<char>>();
|
||||
|
||||
if index < chars.len() {
|
||||
let new_string = chars
|
||||
.iter()
|
||||
.map(|char| char.to_string())
|
||||
.collect::<String>();
|
||||
|
||||
Value::some(Value::string(new_string))
|
||||
} else {
|
||||
Value::none()
|
||||
}
|
||||
}
|
||||
StrFunction::ReplaceRange => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||
|
||||
let mut string = arguments.first().unwrap().as_string()?.clone();
|
||||
let range = arguments.get(1).unwrap().as_list()?.items();
|
||||
let start = range.first().unwrap_or_default().as_integer()? as usize;
|
||||
let end = range.get(1).unwrap_or_default().as_integer()? as usize;
|
||||
let pattern = arguments.get(2).unwrap().as_string()?;
|
||||
|
||||
string.replace_range(start..end, pattern);
|
||||
|
||||
Value::String(string)
|
||||
}
|
||||
StrFunction::Retain => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let mut string = arguments.first().unwrap().as_string()?.clone();
|
||||
let predicate = arguments.get(1).unwrap().as_function()?;
|
||||
|
||||
string.retain(|char| {
|
||||
predicate
|
||||
.call(&[Value::string(char)], _source, _outer_context)
|
||||
.unwrap()
|
||||
.as_boolean()
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
Value::String(string)
|
||||
}
|
||||
StrFunction::Split => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let sections = string
|
||||
.split(pattern)
|
||||
.map(|section| Value::string(section.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(sections))
|
||||
}
|
||||
StrFunction::SplitAt => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let index = arguments.get(1).unwrap().as_integer()?;
|
||||
let (left, right) = string.split_at(index as usize);
|
||||
|
||||
Value::List(List::with_items(vec![
|
||||
Value::string(left.to_string()),
|
||||
Value::string(right.to_string()),
|
||||
]))
|
||||
}
|
||||
StrFunction::SplitInclusive => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let sections = string
|
||||
.split(pattern)
|
||||
.map(|pattern| Value::string(pattern.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(sections))
|
||||
}
|
||||
StrFunction::SplitN => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 3, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let count = arguments.get(1).unwrap().as_integer()?;
|
||||
let pattern_string = arguments.get(2).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let sections = string
|
||||
.splitn(count as usize, pattern)
|
||||
.map(|pattern| Value::string(pattern.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(sections))
|
||||
}
|
||||
StrFunction::SplitOnce => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let sections = string.split_once(pattern).map(|(left, right)| {
|
||||
Value::List(List::with_items(vec![
|
||||
Value::string(left.to_string()),
|
||||
Value::string(right.to_string()),
|
||||
]))
|
||||
});
|
||||
|
||||
Value::option(sections)
|
||||
}
|
||||
StrFunction::SplitTerminator => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let sections = string
|
||||
.split_terminator(pattern)
|
||||
.map(|section| Value::string(section.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(sections))
|
||||
}
|
||||
StrFunction::SplitWhitespace => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let sections = string
|
||||
.split_whitespace()
|
||||
.map(|section| Value::string(section.to_string()))
|
||||
.collect();
|
||||
|
||||
Value::List(List::with_items(sections))
|
||||
}
|
||||
StrFunction::StartsWith => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
|
||||
Value::Boolean(string.starts_with(pattern))
|
||||
}
|
||||
StrFunction::StripPrefix => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let prefix_string = arguments.get(1).unwrap().as_string()?;
|
||||
let prefix = prefix_string.as_str();
|
||||
let stripped = string
|
||||
.strip_prefix(prefix)
|
||||
.map(|remainder| Value::string(remainder.to_string()));
|
||||
|
||||
Value::option(stripped)
|
||||
}
|
||||
StrFunction::ToLowercase => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let lowercase = string.to_lowercase();
|
||||
|
||||
Value::string(lowercase)
|
||||
}
|
||||
StrFunction::ToUppercase => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let uppercase = string.to_uppercase();
|
||||
|
||||
Value::string(uppercase)
|
||||
}
|
||||
StrFunction::Trim => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let trimmed = arguments.first().unwrap().as_string()?.trim().to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StrFunction::TrimEnd => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let trimmed = arguments
|
||||
.first()
|
||||
.unwrap()
|
||||
.as_string()?
|
||||
.trim_end()
|
||||
.to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StrFunction::TrimEndMatches => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern_string = arguments.get(1).unwrap().as_string()?;
|
||||
let pattern = pattern_string.as_str();
|
||||
let trimmed = string.trim_end_matches(pattern).to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StrFunction::TrimMatches => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern = arguments
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_string()?
|
||||
.chars()
|
||||
.collect::<Vec<char>>();
|
||||
let trimmed = string.trim_matches(pattern.as_slice()).to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StrFunction::TrimStart => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 1, arguments.len())?;
|
||||
|
||||
let trimmed = arguments
|
||||
.first()
|
||||
.unwrap()
|
||||
.as_string()?
|
||||
.trim_start()
|
||||
.to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StrFunction::TrimStartMatches => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let string = arguments.first().unwrap().as_string()?;
|
||||
let pattern = arguments
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_string()?
|
||||
.chars()
|
||||
.collect::<Vec<char>>();
|
||||
let trimmed = string.trim_start_matches(pattern.as_slice()).to_string();
|
||||
|
||||
Value::string(trimmed)
|
||||
}
|
||||
StrFunction::Truncate => {
|
||||
RuntimeError::expect_argument_amount(self.name(), 2, arguments.len())?;
|
||||
|
||||
let input_string = arguments.first().unwrap().as_string()?;
|
||||
let new_length = arguments.get(1).unwrap().as_integer()? as usize;
|
||||
|
||||
let new_string = input_string
|
||||
.chars()
|
||||
.take(new_length)
|
||||
.map(|char| char.to_string())
|
||||
.collect();
|
||||
|
||||
Value::String(new_string)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
//! Error and Result types.
|
||||
//!
|
||||
//! To deal with errors from dependencies, either create a new error variant
|
||||
//! or use the ToolFailure variant if the error can only occur inside a tool.
|
||||
mod runtime_error;
|
||||
pub(crate) mod rw_lock_error;
|
||||
mod syntax_error;
|
||||
mod validation_error;
|
||||
|
||||
pub use runtime_error::RuntimeError;
|
||||
pub use syntax_error::SyntaxError;
|
||||
pub use validation_error::ValidationError;
|
||||
|
||||
use tree_sitter::{LanguageError, Point};
|
||||
|
||||
use std::fmt::{self, Formatter};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum Error {
|
||||
Syntax(SyntaxError),
|
||||
|
||||
Validation(ValidationError),
|
||||
|
||||
Runtime(RuntimeError),
|
||||
|
||||
ParserCancelled,
|
||||
|
||||
Language(LanguageError),
|
||||
}
|
||||
|
||||
impl From<SyntaxError> for Error {
|
||||
fn from(error: SyntaxError) -> Self {
|
||||
Error::Syntax(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidationError> for Error {
|
||||
fn from(error: ValidationError) -> Self {
|
||||
Error::Validation(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RuntimeError> for Error {
|
||||
fn from(error: RuntimeError) -> Self {
|
||||
Error::Runtime(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LanguageError> for Error {
|
||||
fn from(error: LanguageError) -> Self {
|
||||
Error::Language(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use Error::*;
|
||||
|
||||
match self {
|
||||
Syntax(error) => write!(f, "Syntax error: {error}"),
|
||||
Validation(error) => write!(f, "Validation error: {error}"),
|
||||
Runtime(error) => write!(f, "Runtime error: {error}"),
|
||||
ParserCancelled => write!(f, "Parsing was cancelled because the parser took too long."),
|
||||
Language(error) => write!(f, "Parser failed to load language grammar."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_position(position: &Point) -> String {
|
||||
format!("column {}, row {}", position.row + 1, position.column)
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
use std::{
|
||||
fmt::{self, Debug, Display, Formatter},
|
||||
io,
|
||||
num::ParseFloatError,
|
||||
string::FromUtf8Error,
|
||||
time,
|
||||
};
|
||||
|
||||
use crate::Value;
|
||||
|
||||
use super::rw_lock_error::RwLockError;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum RuntimeError {
|
||||
Csv(String),
|
||||
|
||||
Io(String),
|
||||
|
||||
Reqwest(String),
|
||||
|
||||
Json(String),
|
||||
|
||||
SystemTime(String),
|
||||
|
||||
Toml(toml::de::Error),
|
||||
|
||||
ExpectedString {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedInteger {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedFloat {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// An integer, floating point or value was expected.
|
||||
ExpectedNumber {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// An integer, floating point or string value was expected.
|
||||
ExpectedNumberOrString {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedBoolean {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedList {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedMinLengthList {
|
||||
minimum_len: usize,
|
||||
actual_len: usize,
|
||||
},
|
||||
|
||||
ExpectedFixedLenList {
|
||||
expected_len: usize,
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedNone {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedMap {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedTable {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedFunction {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
ExpectedOption {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// A string, list, map or table value was expected.
|
||||
ExpectedCollection {
|
||||
actual: Value,
|
||||
},
|
||||
|
||||
/// Failed to read or write a map.
|
||||
///
|
||||
/// See the [MapError] docs for more info.
|
||||
RwLock(RwLockError),
|
||||
|
||||
ParseFloat(ParseFloatError),
|
||||
|
||||
Utf8(FromUtf8Error),
|
||||
|
||||
/// Failed to find a variable with a value for this key.
|
||||
VariableIdentifierNotFound(String),
|
||||
|
||||
/// A built-in function was called with the wrong amount of arguments.
|
||||
ExpectedBuiltInFunctionArgumentAmount {
|
||||
function_name: String,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl RuntimeError {
|
||||
pub fn expect_argument_amount(
|
||||
function_name: &str,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
) -> Result<(), Self> {
|
||||
if expected == actual {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(RuntimeError::ExpectedBuiltInFunctionArgumentAmount {
|
||||
function_name: function_name.to_string(),
|
||||
expected,
|
||||
actual,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<csv::Error> for RuntimeError {
|
||||
fn from(error: csv::Error) -> Self {
|
||||
RuntimeError::Csv(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for RuntimeError {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
RuntimeError::Io(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for RuntimeError {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
RuntimeError::Reqwest(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for RuntimeError {
|
||||
fn from(error: serde_json::Error) -> Self {
|
||||
RuntimeError::Json(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::SystemTimeError> for RuntimeError {
|
||||
fn from(error: time::SystemTimeError) -> Self {
|
||||
RuntimeError::SystemTime(error.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for RuntimeError {
|
||||
fn from(error: toml::de::Error) -> Self {
|
||||
RuntimeError::Toml(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseFloatError> for RuntimeError {
|
||||
fn from(error: ParseFloatError) -> Self {
|
||||
RuntimeError::ParseFloat(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FromUtf8Error> for RuntimeError {
|
||||
fn from(error: FromUtf8Error) -> Self {
|
||||
RuntimeError::Utf8(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RuntimeError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RwLockError> for RuntimeError {
|
||||
fn from(error: RwLockError) -> Self {
|
||||
RuntimeError::RwLock(error)
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
use std::fmt::{self, Debug, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RwLockError;
|
||||
|
||||
impl Display for RwLockError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Map error: failed to acquire a read/write lock because another thread has panicked."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RwLockError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tree_sitter::{Node as SyntaxNode, Point};
|
||||
|
||||
use crate::SourcePosition;
|
||||
|
||||
use super::rw_lock_error::RwLockError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum SyntaxError {
|
||||
/// Invalid user input.
|
||||
InvalidSource {
|
||||
source: String,
|
||||
position: SourcePosition,
|
||||
},
|
||||
|
||||
RwLock(RwLockError),
|
||||
|
||||
UnexpectedSyntaxNode {
|
||||
expected: String,
|
||||
actual: String,
|
||||
|
||||
#[serde(skip)]
|
||||
location: Point,
|
||||
|
||||
relevant_source: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl SyntaxError {
|
||||
pub fn expect_syntax_node(
|
||||
source: &str,
|
||||
expected: &str,
|
||||
actual: SyntaxNode,
|
||||
) -> Result<(), SyntaxError> {
|
||||
log::info!("Converting {} to abstract node", actual.kind());
|
||||
|
||||
if expected == actual.kind() {
|
||||
Ok(())
|
||||
} else if actual.is_error() {
|
||||
Err(SyntaxError::InvalidSource {
|
||||
source: source[actual.byte_range()].to_string(),
|
||||
position: SourcePosition::from(actual.range()),
|
||||
})
|
||||
} else {
|
||||
Err(SyntaxError::UnexpectedSyntaxNode {
|
||||
expected: expected.to_string(),
|
||||
actual: actual.kind().to_string(),
|
||||
location: actual.start_position(),
|
||||
relevant_source: source[actual.byte_range()].to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RwLockError> for SyntaxError {
|
||||
fn from(error: RwLockError) -> Self {
|
||||
SyntaxError::RwLock(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SyntaxError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Identifier, SourcePosition, Type, Value};
|
||||
|
||||
use super::rw_lock_error::RwLockError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ValidationError {
|
||||
/// The 'assert' macro did not resolve successfully.
|
||||
AssertEqualFailed {
|
||||
expected: Value,
|
||||
actual: Value,
|
||||
position: SourcePosition,
|
||||
},
|
||||
|
||||
/// The 'assert' macro did not resolve successfully.
|
||||
AssertFailed { position: SourcePosition },
|
||||
|
||||
/// A built-in function was called with the wrong amount of arguments.
|
||||
ExpectedBuiltInFunctionArgumentAmount {
|
||||
function_name: String,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
},
|
||||
|
||||
/// A function was called with the wrong amount of arguments.
|
||||
ExpectedFunctionArgumentAmount {
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
position: SourcePosition,
|
||||
},
|
||||
|
||||
/// A function was called with the wrong amount of arguments.
|
||||
ExpectedFunctionArgumentMinimum {
|
||||
minumum_expected: usize,
|
||||
actual: usize,
|
||||
position: SourcePosition,
|
||||
},
|
||||
|
||||
/// Failed to read or write a map.
|
||||
///
|
||||
/// See the [MapError] docs for more info.
|
||||
RwLock(RwLockError),
|
||||
|
||||
TypeCheck {
|
||||
expected: Type,
|
||||
actual: Type,
|
||||
position: SourcePosition,
|
||||
},
|
||||
|
||||
TypeCheckExpectedFunction {
|
||||
actual: Type,
|
||||
position: SourcePosition,
|
||||
},
|
||||
|
||||
/// Failed to find a variable with a value for this key.
|
||||
VariableIdentifierNotFound(Identifier),
|
||||
}
|
||||
|
||||
impl ValidationError {
|
||||
pub fn expect_argument_amount(
|
||||
function_name: &str,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
) -> Result<(), Self> {
|
||||
if expected == actual {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ValidationError::ExpectedBuiltInFunctionArgumentAmount {
|
||||
function_name: function_name.to_string(),
|
||||
expected,
|
||||
actual,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RwLockError> for ValidationError {
|
||||
fn from(_error: RwLockError) -> Self {
|
||||
ValidationError::RwLock(RwLockError)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ValidationError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
181
src/interpret.rs
181
src/interpret.rs
@ -1,181 +0,0 @@
|
||||
//! Tools to interpret dust source code.
|
||||
//!
|
||||
//! This module has three tools to run Dust code.
|
||||
//!
|
||||
//! - [interpret] is the simples way to run Dust code inside of an application or library
|
||||
//! - [interpret_with_context] allows you to set variables on the execution context
|
||||
//! - [Interpreter] is an advanced tool that can parse, verify, run and format Dust code
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Run some Dust and get the result.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use dust_lang::*;
|
||||
//! assert_eq!(
|
||||
//! interpret("1 + 2 + 3"),
|
||||
//! Ok(Value::Integer(6))
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! Create a custom context with variables you can use in your code.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use dust_lang::*;
|
||||
//! let context = Map::new();
|
||||
//!
|
||||
//! context.set("one".into(), 1.into());
|
||||
//! context.set("two".into(), 2.into());
|
||||
//! context.set("three".into(), 3.into());
|
||||
//!
|
||||
//! let dust_code = "four = 4; one + two + three + four";
|
||||
//!
|
||||
//! assert_eq!(
|
||||
//! interpret_with_context(dust_code, context),
|
||||
//! Ok(Value::Integer(10))
|
||||
//! );
|
||||
//! ```
|
||||
use tree_sitter::{Node as SyntaxNode, Parser, Tree as SyntaxTree, TreeCursor};
|
||||
|
||||
use crate::{
|
||||
error::SyntaxError, language, AbstractTree, Error, Format, Map, Root, SourcePosition, Value,
|
||||
};
|
||||
|
||||
/// Interpret the given source code. Returns the value of last statement or the
|
||||
/// first error encountered.
|
||||
///
|
||||
/// See the [module-level docs][self] for more info.
|
||||
pub fn interpret(source: &str) -> Result<Value, Error> {
|
||||
interpret_with_context(source, Map::new())
|
||||
}
|
||||
|
||||
/// Interpret the given source code with the given context.
|
||||
///
|
||||
/// A context is a [Map] instance, which is dust's
|
||||
/// [BTreeMap][std::collections::btree_map::BTreeMap] that is used internally
|
||||
/// for the `<map>` type. Any value can be set, including functions and nested
|
||||
/// maps.
|
||||
///
|
||||
/// See the [module-level docs][self] for more info.
|
||||
pub fn interpret_with_context(source: &str, context: Map) -> Result<Value, Error> {
|
||||
let mut interpreter = Interpreter::new(context);
|
||||
let value = interpreter.run(source)?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// A source code interpreter for the Dust language.
|
||||
///
|
||||
/// The interpreter's most important functions are used to parse dust source code, verify it is safe
|
||||
/// and run it and they are written in a way that forces them to be used safely. Each step in this
|
||||
/// process contains the prior steps, meaning that the same code is always used to create the syntax /// tree, abstract tree and final evaluation. This avoids a critical logic error.
|
||||
pub struct Interpreter {
|
||||
parser: Parser,
|
||||
context: Map,
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
/// Creates a new interpreter with the given variable context.
|
||||
pub fn new(context: Map) -> Self {
|
||||
let mut parser = Parser::new();
|
||||
|
||||
parser
|
||||
.set_language(language())
|
||||
.expect("Language version is incompatible with tree sitter version.");
|
||||
|
||||
parser.set_logger(Some(Box::new(|_log_type, message| {
|
||||
log::info!("{}", message)
|
||||
})));
|
||||
|
||||
Interpreter { parser, context }
|
||||
}
|
||||
|
||||
/// Generates a syntax tree from the source. Returns an error if the the parser is cancelled for
|
||||
/// taking too long. The syntax tree may contain error nodes, which represent syntax errors.
|
||||
///
|
||||
/// Tree sitter is designed to be run on every keystroke, so this is generally a lightweight
|
||||
/// function to call.
|
||||
pub fn parse(&mut self, source: &str) -> Result<SyntaxTree, Error> {
|
||||
if let Some(tree) = self.parser.parse(source, None) {
|
||||
Ok(tree)
|
||||
} else {
|
||||
Err(Error::ParserCancelled)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks the source for errors and generates an abstract tree.
|
||||
///
|
||||
/// The order in which this function works is:
|
||||
///
|
||||
/// - parse the source into a syntax tree
|
||||
/// - check the syntax tree for errors
|
||||
/// - generate an abstract tree from the source and syntax tree
|
||||
/// - check the abstract tree for type errors
|
||||
pub fn verify(&mut self, source: &str) -> Result<Root, Error> {
|
||||
fn check_for_error(
|
||||
node: SyntaxNode,
|
||||
source: &str,
|
||||
cursor: &mut TreeCursor,
|
||||
) -> Result<(), Error> {
|
||||
if node.is_error() {
|
||||
Err(Error::Syntax(SyntaxError::InvalidSource {
|
||||
source: source[node.byte_range()].to_string(),
|
||||
position: SourcePosition::from(node.range()),
|
||||
}))
|
||||
} else {
|
||||
for child in node.children(&mut cursor.clone()) {
|
||||
check_for_error(child, source, cursor)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let syntax_tree = self.parse(source)?;
|
||||
let root = syntax_tree.root_node();
|
||||
let mut cursor = syntax_tree.root_node().walk();
|
||||
|
||||
check_for_error(root, source, &mut cursor)?;
|
||||
|
||||
let abstract_tree = Root::from_syntax(syntax_tree.root_node(), source, &self.context)?;
|
||||
|
||||
abstract_tree.validate(source, &self.context)?;
|
||||
|
||||
Ok(abstract_tree)
|
||||
}
|
||||
|
||||
/// Runs the source, returning the final statement's value or first error.
|
||||
///
|
||||
/// This function [parses][Self::parse], [verifies][Self::verify] and [runs][Root::run] using
|
||||
/// the same source code.
|
||||
pub fn run(&mut self, source: &str) -> Result<Value, Error> {
|
||||
self.verify(source)?
|
||||
.run(source, &self.context)
|
||||
.map_err(|error| Error::Runtime(error))
|
||||
}
|
||||
|
||||
/// Return an s-expression displaying a syntax tree of the source, or the ParserCancelled error
|
||||
/// if the parser takes too long.
|
||||
pub fn syntax_tree(&mut self, source: &str) -> Result<String, Error> {
|
||||
Ok(self.parse(source)?.root_node().to_sexp())
|
||||
}
|
||||
|
||||
/// Return formatted Dust code generated from the current abstract tree, or None if no source
|
||||
/// code has been run successfully.
|
||||
///
|
||||
/// You should call [verify][Interpreter::verify] before calling this function. You can only
|
||||
/// create formatted source from a valid abstract tree.
|
||||
pub fn format(&mut self, source: &str) -> Result<String, Error> {
|
||||
let mut formatted_output = String::new();
|
||||
|
||||
self.verify(source)?.format(&mut formatted_output, 0);
|
||||
|
||||
Ok(formatted_output)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Interpreter {
|
||||
fn default() -> Self {
|
||||
Interpreter::new(Map::new())
|
||||
}
|
||||
}
|
42
src/lib.rs
42
src/lib.rs
@ -1,42 +0,0 @@
|
||||
#![warn(missing_docs)]
|
||||
//! The Dust library is used to parse, format and run dust source code.
|
||||
//!
|
||||
//! See the [interpret] module for more information.
|
||||
//!
|
||||
//! You can use this library externally by calling either of the "interpret"
|
||||
//! functions or by constructing your own Interpreter.
|
||||
pub use crate::{
|
||||
abstract_tree::*, built_in_functions::BuiltInFunction, error::Error, interpret::*, value::*,
|
||||
};
|
||||
|
||||
pub use tree_sitter::Node as SyntaxNode;
|
||||
|
||||
pub mod abstract_tree;
|
||||
pub mod built_in_functions;
|
||||
pub mod error;
|
||||
pub mod interpret;
|
||||
pub mod value;
|
||||
|
||||
use tree_sitter::Language;
|
||||
|
||||
extern "C" {
|
||||
fn tree_sitter_dust() -> Language;
|
||||
}
|
||||
|
||||
/// Get the tree-sitter [Language][] for this grammar.
|
||||
///
|
||||
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
|
||||
pub fn language() -> Language {
|
||||
unsafe { tree_sitter_dust() }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_can_load_grammar() {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
parser
|
||||
.set_language(super::language())
|
||||
.expect("Error loading dust language");
|
||||
}
|
||||
}
|
422
src/main.rs
422
src/main.rs
@ -1,422 +0,0 @@
|
||||
//! Command line interface for the dust programming language.
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use crossterm::event::{KeyCode, KeyModifiers};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use reedline::{
|
||||
default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, EditCommand, Emacs,
|
||||
Highlighter, Prompt, Reedline, ReedlineEvent, ReedlineMenu, Signal, Span, SqliteBackedHistory,
|
||||
StyledText, Suggestion,
|
||||
};
|
||||
|
||||
use std::{borrow::Cow, fs::read_to_string, path::PathBuf, process::Command};
|
||||
|
||||
use dust_lang::{built_in_values, Error, Interpreter, Map, Value};
|
||||
|
||||
/// Command-line arguments to be parsed.
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Dust source code to evaluate.
|
||||
#[arg(short, long)]
|
||||
command: Option<String>,
|
||||
|
||||
/// Data to assign to the "input" variable.
|
||||
#[arg(short, long)]
|
||||
input: Option<String>,
|
||||
|
||||
/// File whose contents will be assigned to the "input" variable.
|
||||
#[arg(short = 'p', long)]
|
||||
input_path: Option<String>,
|
||||
|
||||
/// Command for alternate functionality besides running the source.
|
||||
#[command(subcommand)]
|
||||
cli_command: Option<CliCommand>,
|
||||
|
||||
/// Location of the file to run.
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum CliCommand {
|
||||
/// Output a formatted version of the input.
|
||||
Format,
|
||||
|
||||
/// Output a concrete syntax tree of the input.
|
||||
Syntax { path: String },
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
let context = Map::new();
|
||||
|
||||
if let Some(input) = args.input {
|
||||
context
|
||||
.set("input".to_string(), Value::string(input))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if let Some(path) = args.input_path {
|
||||
let file_contents = read_to_string(path).unwrap();
|
||||
|
||||
context
|
||||
.set("input".to_string(), Value::string(file_contents))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if args.path.is_none() && args.command.is_none() {
|
||||
let run_shell_result = run_shell(context);
|
||||
|
||||
match run_shell_result {
|
||||
Ok(_) => {}
|
||||
Err(error) => eprintln!("{error}"),
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let source = if let Some(path) = &args.path {
|
||||
read_to_string(path).unwrap()
|
||||
} else if let Some(command) = args.command {
|
||||
command
|
||||
} else {
|
||||
String::with_capacity(0)
|
||||
};
|
||||
|
||||
let mut interpreter = Interpreter::new(context);
|
||||
|
||||
if let Some(CliCommand::Syntax { path }) = args.cli_command {
|
||||
let source = read_to_string(path).unwrap();
|
||||
let syntax_tree_sexp = interpreter.syntax_tree(&source).unwrap();
|
||||
|
||||
println!("{syntax_tree_sexp}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(CliCommand::Format) = args.cli_command {
|
||||
let formatted = interpreter.format(&source).unwrap();
|
||||
|
||||
println!("{formatted}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let eval_result = interpreter.run(&source);
|
||||
|
||||
match eval_result {
|
||||
Ok(value) => {
|
||||
if !value.is_none() {
|
||||
println!("{value}")
|
||||
}
|
||||
}
|
||||
Err(error) => eprintln!("{error}"),
|
||||
}
|
||||
}
|
||||
|
||||
struct DustHighlighter {
|
||||
context: Map,
|
||||
}
|
||||
|
||||
impl DustHighlighter {
|
||||
fn new(context: Map) -> Self {
|
||||
Self { context }
|
||||
}
|
||||
}
|
||||
|
||||
const HIGHLIGHT_TERMINATORS: [char; 8] = [' ', ':', '(', ')', '{', '}', '[', ']'];
|
||||
|
||||
impl Highlighter for DustHighlighter {
|
||||
fn highlight(&self, line: &str, _cursor: usize) -> reedline::StyledText {
|
||||
fn highlight_identifier(styled: &mut StyledText, word: &str, map: &Map) -> bool {
|
||||
for (key, (value, _type)) in map.variables().unwrap().iter() {
|
||||
if key == &word {
|
||||
styled.push((Style::new().bold(), word.to_string()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Value::Map(nested_map) = value {
|
||||
return highlight_identifier(styled, word, nested_map);
|
||||
}
|
||||
}
|
||||
|
||||
for built_in_value in built_in_values() {
|
||||
if built_in_value.name() == word {
|
||||
styled.push((Style::new().bold(), word.to_string()));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
let mut styled = StyledText::new();
|
||||
|
||||
for word in line.split_inclusive(&HIGHLIGHT_TERMINATORS) {
|
||||
let word_is_highlighted =
|
||||
highlight_identifier(&mut styled, &word[0..word.len() - 1], &self.context);
|
||||
|
||||
if word_is_highlighted {
|
||||
let final_char = word.chars().last().unwrap();
|
||||
|
||||
if HIGHLIGHT_TERMINATORS.contains(&final_char) {
|
||||
let mut terminator_style = Style::new();
|
||||
|
||||
terminator_style.foreground = Some(Color::Cyan);
|
||||
|
||||
styled.push((terminator_style, final_char.to_string()));
|
||||
}
|
||||
} else {
|
||||
styled.push((Style::new(), word.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
styled
|
||||
}
|
||||
}
|
||||
|
||||
struct StarshipPrompt {
|
||||
left: String,
|
||||
right: String,
|
||||
}
|
||||
|
||||
impl StarshipPrompt {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
left: String::new(),
|
||||
right: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reload(&mut self) {
|
||||
let run_starship_left = Command::new("starship").arg("prompt").output();
|
||||
let run_starship_right = Command::new("starship")
|
||||
.args(["prompt", "--right"])
|
||||
.output();
|
||||
let left_prompt = if let Ok(output) = &run_starship_left {
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||
} else {
|
||||
">".to_string()
|
||||
};
|
||||
let right_prompt = if let Ok(output) = &run_starship_right {
|
||||
String::from_utf8_lossy(&output.stdout).trim().to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
self.left = left_prompt;
|
||||
self.right = right_prompt;
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt for StarshipPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
Cow::Borrowed(&self.left)
|
||||
}
|
||||
|
||||
fn render_prompt_right(&self) -> Cow<str> {
|
||||
Cow::Borrowed(&self.right)
|
||||
}
|
||||
|
||||
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
|
||||
Cow::Borrowed(" ")
|
||||
}
|
||||
|
||||
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||
Cow::Borrowed("")
|
||||
}
|
||||
|
||||
fn render_prompt_history_search_indicator(
|
||||
&self,
|
||||
_history_search: reedline::PromptHistorySearch,
|
||||
) -> Cow<str> {
|
||||
Cow::Borrowed("")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DustCompleter {
|
||||
context: Map,
|
||||
}
|
||||
|
||||
impl DustCompleter {
|
||||
fn new(context: Map) -> Self {
|
||||
DustCompleter { context }
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for DustCompleter {
|
||||
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
|
||||
let mut suggestions = Vec::new();
|
||||
let last_word = if let Some(word) = line.rsplit([' ', ':']).next() {
|
||||
word
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
if let Ok(path) = PathBuf::try_from(last_word) {
|
||||
if let Ok(read_dir) = path.read_dir() {
|
||||
for entry in read_dir {
|
||||
if let Ok(entry) = entry {
|
||||
let description = if let Ok(file_type) = entry.file_type() {
|
||||
if file_type.is_dir() {
|
||||
"directory"
|
||||
} else if file_type.is_file() {
|
||||
"file"
|
||||
} else if file_type.is_symlink() {
|
||||
"symlink"
|
||||
} else {
|
||||
"unknown"
|
||||
}
|
||||
} else {
|
||||
"unknown"
|
||||
};
|
||||
|
||||
suggestions.push(Suggestion {
|
||||
value: entry.path().to_string_lossy().to_string(),
|
||||
description: Some(description.to_string()),
|
||||
extra: None,
|
||||
span: Span::new(pos - last_word.len(), pos),
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for built_in_value in built_in_values() {
|
||||
let name = built_in_value.name();
|
||||
let description = built_in_value.description();
|
||||
|
||||
if built_in_value.name().contains(last_word) {
|
||||
suggestions.push(Suggestion {
|
||||
value: name.to_string(),
|
||||
description: Some(description.to_string()),
|
||||
extra: None,
|
||||
span: Span::new(pos - last_word.len(), pos),
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
|
||||
if let Value::Map(map) = built_in_value.get() {
|
||||
for (key, (value, _type)) in map.variables().unwrap().iter() {
|
||||
if key.contains(last_word) {
|
||||
suggestions.push(Suggestion {
|
||||
value: format!("{name}:{key}"),
|
||||
description: Some(value.to_string()),
|
||||
extra: None,
|
||||
span: Span::new(pos - last_word.len(), pos),
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (key, (value, _type)) in self.context.variables().unwrap().iter() {
|
||||
if key.contains(last_word) {
|
||||
suggestions.push(Suggestion {
|
||||
value: key.to_string(),
|
||||
description: Some(value.to_string()),
|
||||
extra: None,
|
||||
span: Span::new(pos - last_word.len(), pos),
|
||||
append_whitespace: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
suggestions
|
||||
}
|
||||
}
|
||||
|
||||
fn run_shell(context: Map) -> Result<(), Error> {
|
||||
let mut interpreter = Interpreter::new(context.clone());
|
||||
let mut keybindings = default_emacs_keybindings();
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::CONTROL,
|
||||
KeyCode::Char(' '),
|
||||
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
|
||||
);
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::NONE,
|
||||
KeyCode::Enter,
|
||||
ReedlineEvent::SubmitOrNewline,
|
||||
);
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::NONE,
|
||||
KeyCode::Tab,
|
||||
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
|
||||
);
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::NONE,
|
||||
KeyCode::Tab,
|
||||
ReedlineEvent::Multiple(vec![
|
||||
ReedlineEvent::Menu("context menu".to_string()),
|
||||
ReedlineEvent::MenuNext,
|
||||
]),
|
||||
);
|
||||
|
||||
let edit_mode = Box::new(Emacs::new(keybindings));
|
||||
let history = Box::new(
|
||||
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
|
||||
.expect("Error loading history."),
|
||||
);
|
||||
let hinter = Box::new(DefaultHinter::default().with_style(Style::new().dimmed()));
|
||||
let completer = DustCompleter::new(context.clone());
|
||||
|
||||
let mut line_editor = Reedline::create()
|
||||
.with_edit_mode(edit_mode)
|
||||
.with_history(history)
|
||||
.with_highlighter(Box::new(DustHighlighter::new(context.clone())))
|
||||
.with_hinter(hinter)
|
||||
.use_kitty_keyboard_enhancement(true)
|
||||
.with_completer(Box::new(completer))
|
||||
.with_menu(ReedlineMenu::EngineCompleter(Box::new(
|
||||
ColumnarMenu::default()
|
||||
.with_name("context menu")
|
||||
.with_text_style(Style::new().fg(Color::White))
|
||||
.with_columns(1)
|
||||
.with_column_padding(10),
|
||||
)));
|
||||
let mut prompt = StarshipPrompt::new();
|
||||
|
||||
prompt.reload();
|
||||
|
||||
loop {
|
||||
let sig = line_editor.read_line(&prompt);
|
||||
|
||||
match sig {
|
||||
Ok(Signal::Success(buffer)) => {
|
||||
if buffer.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let run_result = interpreter.run(&buffer);
|
||||
|
||||
match run_result {
|
||||
Ok(value) => {
|
||||
if !value.is_none() {
|
||||
println!("{value}")
|
||||
}
|
||||
}
|
||||
Err(error) => println!("{error}"),
|
||||
}
|
||||
|
||||
prompt.reload();
|
||||
}
|
||||
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
|
||||
println!("\nLeaving the Dust shell.");
|
||||
break;
|
||||
}
|
||||
x => {
|
||||
println!("Unknown event: {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
built_in_functions::Callable, error::RuntimeError, BuiltInFunction, Format, FunctionNode, Map,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Function {
|
||||
BuiltIn(BuiltInFunction),
|
||||
ContextDefined(FunctionNode),
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn call(
|
||||
&self,
|
||||
arguments: &[Value],
|
||||
source: &str,
|
||||
outer_context: &Map,
|
||||
) -> Result<Value, RuntimeError> {
|
||||
match self {
|
||||
Function::BuiltIn(built_in_function) => {
|
||||
built_in_function.call(arguments, source, outer_context)
|
||||
}
|
||||
Function::ContextDefined(function_node) => {
|
||||
function_node.call(arguments, source, outer_context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn r#type(&self) -> Type {
|
||||
match self {
|
||||
Function::BuiltIn(built_in_function) => built_in_function.r#type(),
|
||||
Function::ContextDefined(context_defined_function) => {
|
||||
context_defined_function.r#type().clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format for Function {
|
||||
fn format(&self, output: &mut String, indent_level: u8) {
|
||||
match self {
|
||||
Function::BuiltIn(built_in_function) => built_in_function.format(output, indent_level),
|
||||
Function::ContextDefined(function_node) => function_node.format(output, indent_level),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Function {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Function::BuiltIn(built_in_function) => write!(f, "{built_in_function}"),
|
||||
Function::ContextDefined(function_node) => write!(f, "{function_node}"),
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user