forked from M-Labs/nac3
Compare commits
636 Commits
42511334f7
...
aebfd0a37d
Author | SHA1 | Date | |
---|---|---|---|
aebfd0a37d | |||
91d697c327 | |||
d18292dd28 | |||
29da2d1a9a | |||
34b460757b | |||
925f1c2cc4 | |||
65110a2cf2 | |||
3d39a80df6 | |||
55db05fdbb | |||
24a26b53ae | |||
1084ba2158 | |||
be75fa7368 | |||
ec52128a4a | |||
b10b49e39a | |||
d92ce201d3 | |||
8b485f552b | |||
d9be8d3978 | |||
41d62f7325 | |||
4400d9b57d | |||
8ee5db7462 | |||
6d9b3abcd7 | |||
f11a0776e7 | |||
f2dc03dfa1 | |||
1c807ebe08 | |||
9e0b5187dd | |||
1887a337ff | |||
03f5b80153 | |||
1114d11b34 | |||
a7a188da76 | |||
eb6ceefdcd | |||
9332d1643c | |||
718b076e50 | |||
9d86b46e86 | |||
263bc82434 | |||
3f890f183c | |||
234823c51a | |||
b97c016629 | |||
14a5c7981e | |||
35ac5cb6f6 | |||
93af337ed3 | |||
0ca2797428 | |||
9ccdc0180d | |||
c5993c2a58 | |||
fb8553311c | |||
04e7a7eb4b | |||
642e3b2bad | |||
e126fef012 | |||
8fd868a673 | |||
94aac16cc5 | |||
2f85bb3837 | |||
e266d3c2b0 | |||
60b3807ab3 | |||
5006028e2d | |||
1cc276cb43 | |||
8241a29908 | |||
e9a17cf8f8 | |||
adb5c69e67 | |||
d848c2284e | |||
f7e62ab5b7 | |||
9f6c7b3359 | |||
142e99a0f1 | |||
79c469301a | |||
8602852241 | |||
42fbe8e383 | |||
63b0f29728 | |||
a5e1da0b92 | |||
294943e303 | |||
84b4bd920b | |||
317eb80005 | |||
59ac5aae8a | |||
da039e3acf | |||
d1e172501d | |||
323d77a455 | |||
d41c923cfd | |||
5d8e87d923 | |||
a9c73a4915 | |||
804d5db27e | |||
cbc77dddb0 | |||
846d1726ef | |||
0686e83f4c | |||
e710b6c320 | |||
cc769a7006 | |||
5cd4fe6507 | |||
aa79c8d8b7 | |||
75fde1bbf7 | |||
17792b76b7 | |||
6ae770d5eb | |||
d3cb5d6e52 | |||
bb7c0a2d79 | |||
3ad25c8f07 | |||
ede3706ca8 | |||
f97f93d92c | |||
d9cb506f6a | |||
352831b2ca | |||
21d9182ba2 | |||
91f41052fe | |||
14d25b3b9d | |||
265d234266 | |||
2e44745933 | |||
4b8e70f746 | |||
31e76ca3b6 | |||
343f6fd067 | |||
f1ebf8f96e | |||
b18626b149 | |||
750d912eb4 | |||
bf52e294ee | |||
e303248261 | |||
7ea5a5f84d | |||
b267a656a8 | |||
050c862c1a | |||
ffe89eec86 | |||
d6ab73afb0 | |||
6f9f455152 | |||
e50f1017fa | |||
77608346b1 | |||
f5ce7376e3 | |||
1288624218 | |||
0124bcd26c | |||
de065cfa14 | |||
304181fd8c | |||
43048eb8d8 | |||
ace0e2a2c6 | |||
e891683f2e | |||
8e01a20ac3 | |||
465514ca7a | |||
9c34dd9c80 | |||
ced7acd871 | |||
6ea40809b3 | |||
f8e3f7a4ca | |||
ba997ae094 | |||
2a0caf931f | |||
64b94955fe | |||
f478c6afcc | |||
0439bf6aef | |||
fd4bf12808 | |||
d7b14dd705 | |||
9d342d9f0f | |||
ae8f82ccb0 | |||
4a1a4dc076 | |||
eba9fc8a69 | |||
4976e89ae2 | |||
82509d60ec | |||
2579ecbd19 | |||
44f4c4f028 | |||
8ef9e74aaf | |||
9c20e84c84 | |||
b88f17ed42 | |||
096193f7ab | |||
4760851638 | |||
1ee857de6a | |||
4a65d82db5 | |||
b638d1b4b0 | |||
52ccf31bb1 | |||
4904610dc6 | |||
7193e3f328 | |||
2822c613ef | |||
a0bf6da6c2 | |||
9cc9a0284a | |||
85e06d431a | |||
9b3b47ce50 | |||
88f0da7bdd | |||
1bd966965e | |||
521f136f2e | |||
fa04768a77 | |||
6162d21a5b | |||
8101483ebd | |||
dc5e42c5eb | |||
86005da8e1 | |||
3b5328d3cd | |||
5aa6749241 | |||
80d3ab1b0f | |||
ec986dfdf3 | |||
d2a5cd6d57 | |||
9e3f75255e | |||
53f13b44cf | |||
34cabe0e55 | |||
6e85f549f6 | |||
0902d8adf4 | |||
66320679be | |||
0ff995722c | |||
e2b44a066b | |||
2008db8097 | |||
cb450372d6 | |||
ff27a1697e | |||
91625dd327 | |||
7420ce185b | |||
69b9ac5152 | |||
ccfcba4066 | |||
b5637a04e9 | |||
2c6601d97c | |||
82359b81a2 | |||
4d2fd9582a | |||
b7892ce952 | |||
01d3249646 | |||
d2ffdeeb47 | |||
ae902aac2f | |||
3f73896477 | |||
ddb4c548ae | |||
6d00d4dabb | |||
baa713a3ca | |||
d2919b9620 | |||
9ee2168932 | |||
65bc1e5fa4 | |||
2938eacd16 | |||
e8e1499478 | |||
e4f35372d3 | |||
41f88095a5 | |||
c98f367f90 | |||
1f3aa48361 | |||
8c05d8431d | |||
0ae2aae645 | |||
b0eb7815da | |||
26e60fca6e | |||
22a509e7ce | |||
4526c28edb | |||
25fc9db66d | |||
6315027a8b | |||
c0f8d5c602 | |||
998f49261d | |||
aab43b1c07 | |||
a6275fbb57 | |||
8a46032f4c | |||
1c31aa6e8e | |||
b030aec191 | |||
aa2d79fea6 | |||
1e6848ab92 | |||
a91b2d602c | |||
c683958e4a | |||
142f82f987 | |||
dfd3548ed2 | |||
31fba04cee | |||
fa2fe8ed5d | |||
7ede4f15b6 | |||
0fe346106d | |||
681d85d3be | |||
14119a2c80 | |||
b35075245b | |||
4b17511b4a | |||
7ee82de312 | |||
701ca36e99 | |||
5e1b0a10a0 | |||
9f316a3294 | |||
0ae1fe82b4 | |||
de8fc264d7 | |||
970f075490 | |||
4587088835 | |||
49476d06e1 | |||
664e02cec4 | |||
c6f75c8bde | |||
01b51b62ee | |||
aae9925014 | |||
d336200bf4 | |||
a50df6560e | |||
a9635f0979 | |||
c2706fa720 | |||
f5ec103c82 | |||
ba08deada6 | |||
439cef636f | |||
1e47b364c5 | |||
8ab3ee9cce | |||
9ae08d6e3d | |||
d6b92adf70 | |||
aa84fefa56 | |||
5ad7aa5a93 | |||
b64d2399f2 | |||
ebca596be6 | |||
4aeea87702 | |||
e25a9bbcda | |||
978eaf16a4 | |||
4547eee82a | |||
96607432c1 | |||
dba1a8b3d4 | |||
612b6768c0 | |||
c004da85f7 | |||
7fc04936cb | |||
b57b869c49 | |||
50f1aca1aa | |||
ffa89e9308 | |||
34cf303e6c | |||
b1e83a1fd4 | |||
7385b91113 | |||
016cbf2b90 | |||
37eae090e5 | |||
204baabfd2 | |||
597857ccd0 | |||
efc9edbc14 | |||
7d66195eae | |||
1fea51d9b3 | |||
99b29d8ded | |||
3db95b120b | |||
8dbb4ad58a | |||
ee67b22ebc | |||
afb94dd299 | |||
d6f0607ff0 | |||
610448fa73 | |||
e8228710e7 | |||
032e1d84cf | |||
b239806558 | |||
694c7e945c | |||
3b1cc02d06 | |||
32d1fe811b | |||
36e4028f5b | |||
b6ff46c39e | |||
bf7e2c295a | |||
48ce6bb6c5 | |||
80c7bc1cbd | |||
e89bc93b5f | |||
47f563908a | |||
0e914ab7e9 | |||
613020a717 | |||
ee2c0d8bab | |||
0d1e9262af | |||
bc0f82cad8 | |||
624dfe8cd1 | |||
e47597bb8a | |||
98d9f73afb | |||
da2886565b | |||
b37cf6de08 | |||
083eacc268 | |||
137efebb33 | |||
443b95d909 | |||
8b73a123cc | |||
84c5201243 | |||
558c3f03ef | |||
45673b0ecc | |||
181607008d | |||
fb92b6d364 | |||
2f6ba69770 | |||
cc83bbc63a | |||
279f47f633 | |||
9850cbe313 | |||
1f5bea2448 | |||
c4259d14d1 | |||
26076c37ba | |||
fd0b11087e | |||
3a1dd893a1 | |||
a4ccac2329 | |||
77542170fd | |||
a3ce5be10b | |||
a22552a012 | |||
6ba74ed9f6 | |||
8b32c8270d | |||
5749141efb | |||
3b10172810 | |||
82efb0e720 | |||
d3a21d75fa | |||
a07674a042 | |||
c5bcd352a5 | |||
79d3c5caae | |||
c697e522d3 | |||
08947d20c2 | |||
62673cf608 | |||
11144301ca | |||
4fcb54e463 | |||
24b2111c64 | |||
f5ce1afe0b | |||
915460ecb7 | |||
b2c7f51d57 | |||
248d8cbece | |||
c429a86586 | |||
c5e731f16d | |||
0cbe4778d2 | |||
c93305739d | |||
ba93931758 | |||
3dd916b6ac | |||
8447aa3000 | |||
1d2a32b140 | |||
07a9229d52 | |||
f0fdfe42cb | |||
928b5bafb5 | |||
dceaf42500 | |||
bfd041d361 | |||
6141f01180 | |||
8d839db553 | |||
316db42940 | |||
64404bba20 | |||
d4ed76d76e | |||
3c121dfcda | |||
693ac7d336 | |||
dd998c8afc | |||
7ab762a174 | |||
7ab2114882 | |||
4535b60fc0 | |||
bf48151748 | |||
bed8ce1f26 | |||
c26689c7a7 | |||
ac17bf50f8 | |||
13bd18bfcb | |||
5c236271c3 | |||
14662a66dc | |||
c4fbfeaca9 | |||
20a752fd3a | |||
6a69211c55 | |||
59dac8bdf5 | |||
edd60e3f9a | |||
799ed58d21 | |||
105d605e6d | |||
97f5b7c324 | |||
7d48883583 | |||
084efe92af | |||
891056631f | |||
1b5ac3cd25 | |||
5ed2b450d3 | |||
a508baae20 | |||
013e7cfc2a | |||
db14b4b635 | |||
8acb39f31f | |||
d561450bf5 | |||
956cca6ac8 | |||
4a5f2d495e | |||
4fe643f45b | |||
1c170f5c41 | |||
df6c9c8a35 | |||
20905a9b67 | |||
e66693282c | |||
dd1be541b8 | |||
3c930ae9ab | |||
35a94a8fc0 | |||
4939ff4dbd | |||
bf1769cef6 | |||
2b74895b71 | |||
1b0f3d07cc | |||
ed5dfd4100 | |||
41e63f24d0 | |||
d0df705c5a | |||
a0662c58e6 | |||
526c18bda0 | |||
a10ab81ee7 | |||
f5353419ac | |||
180392e2ab | |||
471547855e | |||
2ac3f9a176 | |||
cb310965b8 | |||
118f19762a | |||
b419634f8a | |||
147298ff40 | |||
c7cb02b0f3 | |||
03b5e51822 | |||
4eacd1aa9e | |||
9eef51f29f | |||
917d447605 | |||
f1013d9a17 | |||
2ce507964c | |||
5a1a8ecee3 | |||
1300b5ebdd | |||
87f25e1c5d | |||
55335fc05d | |||
247b364191 | |||
bbcec6ae6f | |||
235b6e34d1 | |||
54b4572c5f | |||
dc7c014b10 | |||
1ae6acc061 | |||
98d032b72a | |||
7bbd608492 | |||
4a9593efa3 | |||
098bd1e6e6 | |||
82c2edcf8d | |||
40e58d02ed | |||
e2a9bdd8bc | |||
236989defc | |||
22a728833d | |||
2223c86d9b | |||
72aebed559 | |||
8c1c7fcfc3 | |||
6633eabb89 | |||
d81249cabe | |||
bf4e0009c0 | |||
52dd792b3e | |||
a24e204824 | |||
35ef0386db | |||
b9a580d271 | |||
018d6643e1 | |||
935e7410fd | |||
1a21fb1072 | |||
35a331552b | |||
0bab477ab0 | |||
862d205f67 | |||
e2b11c3fee | |||
0608fd9659 | |||
173102fc56 | |||
93270d7227 | |||
1ffa1a8bb0 | |||
01f7a31aae | |||
32773c14e0 | |||
c356062239 | |||
56f082ca7c | |||
39f300b62a | |||
7b1fe36e90 | |||
fb5b4697a9 | |||
364054331c | |||
40b062ce0f | |||
f5b8b58826 | |||
c4d6b3691a | |||
957ceb74e4 | |||
e47d063efc | |||
0e2da0d180 | |||
39545c0005 | |||
3279f7a776 | |||
f205a8282a | |||
d1215bf5ac | |||
6e424a6a3e | |||
9a07ef3301 | |||
c238c264e7 | |||
f8a697e3d4 | |||
4b38fe66a2 | |||
9cb07e6f04 | |||
6279dbb589 | |||
529442590f | |||
4fcd48e4c8 | |||
619963dc8c | |||
276daa03f7 | |||
a94145348a | |||
fa40fd73c6 | |||
79ce13722a | |||
eb814dd8c3 | |||
3734663188 | |||
d8c3c063ec | |||
d3ad894521 | |||
784111fdbe | |||
d30918bea0 | |||
e2adf82229 | |||
33391c55c2 | |||
3f65e1b133 | |||
ba5bb78f11 | |||
e176aa660d | |||
cb01c79603 | |||
1db8378f60 | |||
8c7ccb626b | |||
1f6c16e08b | |||
77943a8117 | |||
3a93e2b048 | |||
824a5cb01a | |||
17ee8fe6d0 | |||
d46a4b2d38 | |||
de8b67b605 | |||
0af4e95914 | |||
99276c8f31 | |||
42a636b4ce | |||
e112354d25 | |||
43236db9bd | |||
1bec6cf2db | |||
a73ab922e2 | |||
82ce816177 | |||
6ad953f877 | |||
4db871c244 | |||
cc0692a34c | |||
7a90ff5791 | |||
d8c713ce3d | |||
1ffb792000 | |||
057fcfe3df | |||
86ca02796b | |||
711482d09c | |||
7a38ab3119 | |||
34d3317ea0 | |||
c405e46b00 | |||
18db2ddd53 | |||
fe26070364 | |||
095f28468b | |||
29286210b5 | |||
b01d0f6fbb | |||
3dcd846302 | |||
c0227210df | |||
99c71687a6 | |||
d052f007fb | |||
8452579c67 | |||
f00c1813e3 | |||
d4d12a9d1d | |||
a3acf09bda | |||
52dc112410 | |||
d4807293b0 | |||
d4721db4a3 | |||
a7e3eeea0d | |||
f7bbc3e10d | |||
7e0d55443a | |||
197a72c658 | |||
eba92ed8bd | |||
b87c627c41 | |||
ae79533cfd | |||
9983aa62e6 | |||
7ad8e2d81d | |||
743a9384a3 | |||
f2c5a9b352 | |||
09e76efcf7 | |||
832513e210 | |||
f665ea358b | |||
e15473d2c9 | |||
5f0490cd84 | |||
1d13b16f94 | |||
8d0856a58d | |||
bf31c48bba | |||
0941de9ee1 | |||
8618837816 | |||
53ebe8d8b2 | |||
d7df93bef1 | |||
d140164a38 | |||
ddcf4b7e39 | |||
88c45172b2 | |||
c315227a28 | |||
d484fa1e5c | |||
09c9218852 | |||
4f81690128 | |||
b3d849ea7a | |||
3e03398d9b | |||
2f5c3b3cb7 | |||
25ff24a320 | |||
0296844d5f | |||
e95bfe1d31 | |||
bc9b453b3e | |||
fa31e8f336 | |||
22455e43ac | |||
016166de46 | |||
eb4b2bb7f6 | |||
e732f7e089 | |||
d4b85d0bac | |||
c913fb28bd | |||
f51603f6da | |||
d67407716c | |||
f4121b570d | |||
8b078dfa1b | |||
62736bd4bf | |||
c2d00aa762 | |||
d94f25583b | |||
1df3f4e757 | |||
97fe450a0b | |||
e8c5189fce | |||
291e642699 | |||
e554737b68 | |||
84c980fed3 | |||
2985b88351 | |||
52a82e8a39 | |||
eb12f6f082 | |||
f43e225845 | |||
e72d96f165 | |||
ed04cef431 | |||
7c9919256c |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
__pycache__
|
||||
/target
|
||||
windows/msys2
|
||||
|
963
Cargo.lock
generated
963
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,12 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"nac3ast",
|
||||
"nac3parser",
|
||||
"nac3core",
|
||||
"nac3standalone",
|
||||
"nac3embedded",
|
||||
"nac3artiq",
|
||||
"runkernel",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
93
README.md
93
README.md
@ -1,34 +1,67 @@
|
||||
# nac3 compiler
|
||||
# NAC3
|
||||
|
||||
NAC3 is a major, backward-incompatible rewrite of the compiler for the [ARTIQ](https://m-labs.hk/artiq) physics experiment control and data acquisition system. It features greatly improved compilation speeds, a much better type system, and more predictable and transparent operation.
|
||||
|
||||
NAC3 has a modular design and its applicability reaches beyond ARTIQ. The ``nac3core`` module does not contain anything specific to ARTIQ, and can be used in any project that requires compiling Python to machine code.
|
||||
|
||||
**WARNING: NAC3 is currently experimental software and several important features are not implemented yet.**
|
||||
|
||||
## Packaging
|
||||
|
||||
NAC3 is packaged using the [Nix](https://nixos.org) Flakes system. Install Nix 2.4+ and enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
|
||||
|
||||
## Try NAC3
|
||||
|
||||
### Linux
|
||||
|
||||
After setting up Nix as above, use ``nix shell git+https://github.com/m-labs/artiq.git?ref=nac3`` to get a shell with the NAC3 version of ARTIQ. See the ``examples`` directory in ARTIQ (``nac3`` Git branch) for some samples of NAC3 kernel code.
|
||||
|
||||
### Windows (work in progress)
|
||||
|
||||
NAC3 ARTIQ packaging for MSYS2/Windows is not yet complete so installation involves many manual steps. It is also less tested and you may encounter problems.
|
||||
|
||||
Install [MSYS2](https://www.msys2.org/) and run the following commands:
|
||||
```
|
||||
pacman -S mingw-w64-x86_64-python-h5py mingw-w64-x86_64-python-pyqt5 mingw-w64-x86_64-python-scipy mingw-w64-x86_64-python-prettytable mingw-w64-x86_64-python-pygit2
|
||||
pacman -S mingw-w64-x86_64-python-pip
|
||||
pip install qasync
|
||||
pip install pyqtgraph
|
||||
pacman -S patch git
|
||||
git clone https://github.com/m-labs/sipyco
|
||||
cd sipyco
|
||||
git show 20c946aad78872fe60b78d9b57a624d69f3eea47 | patch -p1 -R
|
||||
python setup.py install
|
||||
cd ..
|
||||
git clone -b nac3 https://github.com/m-labs/artiq
|
||||
cd artiq
|
||||
python setup.py install
|
||||
```
|
||||
|
||||
Locate a recent build of ``nac3artiq-msys2`` from [Hydra](https://nixbld.m-labs.hk) and download ``nac3artiq.zip``. Then extract the contents in the appropriate location:
|
||||
```
|
||||
pacman -S unzip
|
||||
wget https://nixbld.m-labs.hk/build/97899/download/1/nac3artiq.zip # edit the build number
|
||||
unzip nac3artiq.zip -d C:/msys64/mingw64/lib/python3.9/site-packages
|
||||
```
|
||||
|
||||
Install additional NAC3 dependencies:
|
||||
```
|
||||
pacman -S mingw-w64-x86_64-lld
|
||||
```
|
||||
|
||||
And you should be good to go.
|
||||
|
||||
## For developers
|
||||
|
||||
This repository contains:
|
||||
- nac3core: Core compiler library, containing type-checking, static analysis (in
|
||||
the future) and code generation.
|
||||
- nac3embedded: Integration with CPython runtime.
|
||||
- nac3standalone: Standalone compiler tool.
|
||||
|
||||
The core compiler would know nothing about symbol resolution, host variables
|
||||
etc. The nac3embedded/nac3standalone library would provide (implement) the
|
||||
symbol resolver to the core compiler for resolving the type and value for
|
||||
unknown symbols. The core compiler would only type check classes and functions
|
||||
requested by the nac3embedded/nac3standalone lib (the API should allow the
|
||||
caller to specify which methods should be compiled). After type checking, the
|
||||
compiler would analyse the set of functions/classes that are used and perform
|
||||
code generation.
|
||||
|
||||
|
||||
Symbol resolver:
|
||||
- Str -> Nac3Type
|
||||
- Str -> Value
|
||||
|
||||
value could be integer values, boolean values, bytes (for memcpy), function ID
|
||||
(full name + concrete type)
|
||||
|
||||
## Current Plan
|
||||
|
||||
1. Write out the syntax-directed type checking/inferencing rules. Fix the rule
|
||||
for type variable instantiation.
|
||||
2. Update the library dependencies and rewrite some of the type checking code.
|
||||
3. Design the symbol resolver API.
|
||||
4. Move tests from code to external files to cleanup the code.
|
||||
- ``nac3ast``: Python abstract syntax tree definition (based on RustPython).
|
||||
- ``nac3parser``: Python parser (based on RustPython).
|
||||
- ``nac3core``: Core compiler library, containing type-checking and code generation.
|
||||
- ``nac3standalone``: Standalone compiler tool (core language only).
|
||||
- ``nac3artiq``: Integration with ARTIQ and implementation of ARTIQ-specific extensions to the core language.
|
||||
- ``runkernel``: Simple program that runs compiled ARTIQ kernels on the host and displays RTIO operations. Useful for testing without hardware.
|
||||
|
||||
Use ``nix develop`` in this repository to enter a development shell.
|
||||
If you are using a different shell than bash you can use e.g. ``nix develop --command fish``.
|
||||
|
||||
Build NAC3 with ``cargo build --release``. See the demonstrations in ``nac3artiq`` and ``nac3standalone``.
|
||||
|
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1647992509,
|
||||
"narHash": "sha256-AG40Nt5OWz0LBs5p457emOuwLKOvTtcv/2fUdnEN3Ws=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d2caa9377539e3b5ff1272ac3aa2d15f3081069f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-21.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
152
flake.nix
Normal file
152
flake.nix
Normal file
@ -0,0 +1,152 @@
|
||||
{
|
||||
description = "The third-generation ARTIQ compiler";
|
||||
|
||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
in rec {
|
||||
packages.x86_64-linux = rec {
|
||||
llvm-nac3 = pkgs.callPackage "${self}/llvm" {};
|
||||
nac3artiq = pkgs.python3Packages.toPythonModule (
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
name = "nac3artiq";
|
||||
outputs = [ "out" "runkernel" "standalone" ];
|
||||
src = self;
|
||||
cargoLock = { lockFile = ./Cargo.lock; };
|
||||
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_13.clang-unwrapped llvm-nac3 ];
|
||||
buildInputs = [ pkgs.python3 llvm-nac3 ];
|
||||
checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ])) ];
|
||||
checkPhase =
|
||||
''
|
||||
echo "Checking nac3standalone demos..."
|
||||
pushd nac3standalone/demo
|
||||
patchShebangs .
|
||||
./check_demos.sh
|
||||
popd
|
||||
echo "Running Cargo tests..."
|
||||
cargoCheckHook
|
||||
'';
|
||||
installPhase =
|
||||
''
|
||||
PYTHON_SITEPACKAGES=$out/${pkgs.python3Packages.python.sitePackages}
|
||||
mkdir -p $PYTHON_SITEPACKAGES
|
||||
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $PYTHON_SITEPACKAGES/nac3artiq.so
|
||||
|
||||
mkdir -p $runkernel/bin
|
||||
cp target/x86_64-unknown-linux-gnu/release/runkernel $runkernel/bin
|
||||
|
||||
mkdir -p $standalone/bin
|
||||
cp target/x86_64-unknown-linux-gnu/release/nac3standalone $standalone/bin
|
||||
'';
|
||||
}
|
||||
);
|
||||
python3-mimalloc = pkgs.python3 // rec {
|
||||
withMimalloc = pkgs.python3.buildEnv.override({ makeWrapperArgs = [ "--set LD_PRELOAD ${pkgs.mimalloc}/lib/libmimalloc.so" ]; });
|
||||
withPackages = f: let packages = f pkgs.python3.pkgs; in withMimalloc.override { extraLibs = packages; };
|
||||
};
|
||||
|
||||
# LLVM PGO support
|
||||
llvm-nac3-instrumented = pkgs.callPackage "${self}/llvm" {
|
||||
stdenv = pkgs.llvmPackages_13.stdenv;
|
||||
extraCmakeFlags = [ "-DLLVM_BUILD_INSTRUMENTED=IR" ];
|
||||
};
|
||||
nac3artiq-instrumented = pkgs.python3Packages.toPythonModule (
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
name = "nac3artiq-instrumented";
|
||||
src = self;
|
||||
cargoLock = { lockFile = ./Cargo.lock; };
|
||||
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_13.clang-unwrapped llvm-nac3-instrumented ];
|
||||
buildInputs = [ pkgs.python3 llvm-nac3-instrumented ];
|
||||
cargoBuildFlags = [ "--package" "nac3artiq" "--features" "init-llvm-profile" ];
|
||||
doCheck = false;
|
||||
configurePhase =
|
||||
''
|
||||
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-L${pkgs.llvmPackages_13.compiler-rt}/lib/linux -C link-arg=-lclang_rt.profile-x86_64"
|
||||
'';
|
||||
installPhase =
|
||||
''
|
||||
TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}
|
||||
mkdir -p $TARGET_DIR
|
||||
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $TARGET_DIR/nac3artiq.so
|
||||
'';
|
||||
}
|
||||
);
|
||||
nac3artiq-profile = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "nac3artiq-profile";
|
||||
src = self;
|
||||
buildInputs = [ (python3-mimalloc.withPackages(ps: [ ps.numpy nac3artiq-instrumented ])) pkgs.lld_13 pkgs.llvmPackages_13.libllvm ];
|
||||
phases = [ "buildPhase" "installPhase" ];
|
||||
# TODO: get more representative code.
|
||||
buildPhase = "python $src/nac3artiq/demo/demo.py";
|
||||
installPhase =
|
||||
''
|
||||
mkdir $out
|
||||
llvm-profdata merge -o $out/llvm.profdata /build/llvm/build/profiles/*
|
||||
'';
|
||||
};
|
||||
llvm-nac3-pgo = pkgs.callPackage "${self}/llvm" {
|
||||
stdenv = pkgs.llvmPackages_13.stdenv;
|
||||
extraCmakeFlags = [ "-DLLVM_PROFDATA_FILE=${nac3artiq-profile}/llvm.profdata" ];
|
||||
};
|
||||
nac3artiq-pgo = pkgs.python3Packages.toPythonModule (
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
name = "nac3artiq-pgo";
|
||||
src = self;
|
||||
cargoLock = { lockFile = ./Cargo.lock; };
|
||||
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_13.clang-unwrapped llvm-nac3-pgo ];
|
||||
buildInputs = [ pkgs.python3 llvm-nac3-pgo ];
|
||||
cargoBuildFlags = [ "--package" "nac3artiq" ];
|
||||
cargoTestFlags = [ "--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq" ];
|
||||
installPhase =
|
||||
''
|
||||
TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}
|
||||
mkdir -p $TARGET_DIR
|
||||
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $TARGET_DIR/nac3artiq.so
|
||||
'';
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
packages.x86_64-w64-mingw32 = import ./windows { inherit pkgs; };
|
||||
|
||||
devShell.x86_64-linux = pkgs.mkShell {
|
||||
name = "nac3-dev-shell";
|
||||
buildInputs = with pkgs; [
|
||||
# build dependencies
|
||||
packages.x86_64-linux.llvm-nac3
|
||||
llvmPackages_13.clang-unwrapped # IRRT
|
||||
cargo
|
||||
rustc
|
||||
# runtime dependencies
|
||||
lld_13
|
||||
(packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ]))
|
||||
# development tools
|
||||
cargo-insta
|
||||
clippy
|
||||
rustfmt
|
||||
];
|
||||
};
|
||||
devShells.x86_64-linux.msys2 = pkgs.mkShell {
|
||||
name = "nac3-dev-shell";
|
||||
buildInputs = with pkgs; [
|
||||
curl
|
||||
pacman
|
||||
fakeroot
|
||||
packages.x86_64-w64-mingw32.wine-msys2
|
||||
];
|
||||
};
|
||||
|
||||
hydraJobs = {
|
||||
inherit (packages.x86_64-linux) llvm-nac3 nac3artiq;
|
||||
llvm-nac3-msys2 = packages.x86_64-w64-mingw32.llvm-nac3;
|
||||
nac3artiq-msys2 = packages.x86_64-w64-mingw32.nac3artiq;
|
||||
};
|
||||
};
|
||||
|
||||
nixConfig = {
|
||||
binaryCachePublicKeys = ["nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc="];
|
||||
binaryCaches = ["https://nixbld.m-labs.hk" "https://cache.nixos.org"];
|
||||
};
|
||||
}
|
35
llvm/TLI-musl.patch
Normal file
35
llvm/TLI-musl.patch
Normal file
@ -0,0 +1,35 @@
|
||||
From 5c571082fdaf61f6df19d9b7137dc26d71334058 Mon Sep 17 00:00:00 2001
|
||||
From: Natanael Copa <ncopa@alpinelinux.org>
|
||||
Date: Thu, 18 Feb 2016 10:33:04 +0100
|
||||
Subject: [PATCH 2/3] Fix build with musl libc
|
||||
|
||||
On musl libc the fopen64 and fopen are the same thing, but for
|
||||
compatibility they have a `#define fopen64 fopen`. Same applies for
|
||||
fseek64, fstat64, fstatvfs64, ftello64, lstat64, stat64 and tmpfile64.
|
||||
---
|
||||
include/llvm/Analysis/TargetLibraryInfo.h | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
|
||||
diff --git a/include/llvm/Analysis/TargetLibraryInfo.h b/include/llvm/Analysis/TargetLibraryInfo.h
|
||||
index 7becdf0..7f14427 100644
|
||||
--- a/include/llvm/Analysis/TargetLibraryInfo.h
|
||||
+++ b/include/llvm/Analysis/TargetLibraryInfo.h
|
||||
@@ -18,6 +18,15 @@
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/Pass.h"
|
||||
|
||||
+#undef fopen64
|
||||
+#undef fseeko64
|
||||
+#undef fstat64
|
||||
+#undef fstatvfs64
|
||||
+#undef ftello64
|
||||
+#undef lstat64
|
||||
+#undef stat64
|
||||
+#undef tmpfile64
|
||||
+
|
||||
namespace llvm {
|
||||
/// VecDesc - Describes a possible vectorization of a function.
|
||||
/// Function 'VectorFnName' is equivalent to 'ScalarFnName' vectorized
|
||||
--
|
||||
2.7.3
|
||||
|
228
llvm/default.nix
Normal file
228
llvm/default.nix
Normal file
@ -0,0 +1,228 @@
|
||||
{ lib, stdenv
|
||||
, pkgsBuildBuild
|
||||
, fetchurl
|
||||
, fetchpatch
|
||||
, cmake
|
||||
, python3
|
||||
, libbfd
|
||||
, ncurses
|
||||
, zlib
|
||||
, which
|
||||
, llvmPackages_13
|
||||
, debugVersion ? false
|
||||
, enableManpages ? false
|
||||
, enableSharedLibraries ? false
|
||||
, extraCmakeFlags ? []
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib) optional optionals optionalString;
|
||||
|
||||
release_version = "13.0.1";
|
||||
candidate = ""; # empty or "rcN"
|
||||
dash-candidate = lib.optionalString (candidate != "") "-${candidate}";
|
||||
version = "${release_version}${dash-candidate}"; # differentiating these (variables) is important for RCs
|
||||
fetch = name: sha256: fetchurl {
|
||||
url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-${version}/${name}-${release_version}${candidate}.src.tar.xz";
|
||||
inherit sha256;
|
||||
};
|
||||
|
||||
# Used when creating a version-suffixed symlink of libLLVM.dylib
|
||||
shortVersion = with lib;
|
||||
concatStringsSep "." (take 1 (splitString "." release_version));
|
||||
|
||||
in stdenv.mkDerivation (rec {
|
||||
pname = "llvm";
|
||||
inherit version;
|
||||
|
||||
src = fetch pname "sha256-7GuA2Cw4SsrS3BkpA6bPLNuv+4ibhL+5janXHmMPyDQ=";
|
||||
|
||||
unpackPhase = ''
|
||||
unpackFile $src
|
||||
mv llvm-${release_version}* llvm
|
||||
sourceRoot=$PWD/llvm
|
||||
'';
|
||||
|
||||
outputs = [ "out" "lib" "dev" "python" ];
|
||||
|
||||
nativeBuildInputs = [ cmake python3 ]
|
||||
++ optionals enableManpages [ python3.pkgs.sphinx python3.pkgs.recommonmark ];
|
||||
|
||||
buildInputs = [ ];
|
||||
|
||||
propagatedBuildInputs = optionals (stdenv.buildPlatform == stdenv.hostPlatform) [ ncurses ]
|
||||
++ [ zlib ];
|
||||
|
||||
checkInputs = [ which ];
|
||||
|
||||
patches = [
|
||||
./gnu-install-dirs.patch
|
||||
# Fix random compiler crashes: https://bugs.llvm.org/show_bug.cgi?id=50611
|
||||
(fetchpatch {
|
||||
url = "https://raw.githubusercontent.com/archlinux/svntogit-packages/4764a4f8c920912a2bfd8b0eea57273acfe0d8a8/trunk/no-strict-aliasing-DwarfCompileUnit.patch";
|
||||
sha256 = "18l6mrvm2vmwm77ckcnbjvh6ybvn72rhrb799d4qzwac4x2ifl7g";
|
||||
stripLen = 1;
|
||||
})
|
||||
./llvm-future-riscv-abi.diff
|
||||
];
|
||||
|
||||
postPatch = optionalString stdenv.isDarwin ''
|
||||
substituteInPlace cmake/modules/AddLLVM.cmake \
|
||||
--replace 'set(_install_name_dir INSTALL_NAME_DIR "@rpath")' "set(_install_name_dir)" \
|
||||
--replace 'set(_install_rpath "@loader_path/../''${CMAKE_INSTALL_LIBDIR}''${LLVM_LIBDIR_SUFFIX}" ''${extra_libdir})' ""
|
||||
''
|
||||
# Patch llvm-config to return correct library path based on --link-{shared,static}.
|
||||
+ ''
|
||||
substitute '${./outputs.patch}' ./outputs.patch --subst-var lib
|
||||
patch -p1 < ./outputs.patch
|
||||
'' + ''
|
||||
# FileSystem permissions tests fail with various special bits
|
||||
substituteInPlace unittests/Support/CMakeLists.txt \
|
||||
--replace "Path.cpp" ""
|
||||
rm unittests/Support/Path.cpp
|
||||
substituteInPlace unittests/IR/CMakeLists.txt \
|
||||
--replace "PassBuilderCallbacksTest.cpp" ""
|
||||
rm unittests/IR/PassBuilderCallbacksTest.cpp
|
||||
rm test/tools/llvm-objcopy/ELF/mirror-permissions-unix.test
|
||||
'' + optionalString stdenv.hostPlatform.isMusl ''
|
||||
patch -p1 -i ${./TLI-musl.patch}
|
||||
substituteInPlace unittests/Support/CMakeLists.txt \
|
||||
--replace "add_subdirectory(DynamicLibrary)" ""
|
||||
rm unittests/Support/DynamicLibrary/DynamicLibraryTest.cpp
|
||||
# valgrind unhappy with musl or glibc, but fails w/musl only
|
||||
rm test/CodeGen/AArch64/wineh4.mir
|
||||
'' + optionalString stdenv.hostPlatform.isAarch32 ''
|
||||
# skip failing X86 test cases on 32-bit ARM
|
||||
rm test/DebugInfo/X86/convert-debugloc.ll
|
||||
rm test/DebugInfo/X86/convert-inlined.ll
|
||||
rm test/DebugInfo/X86/convert-linked.ll
|
||||
rm test/tools/dsymutil/X86/op-convert.test
|
||||
'' + optionalString (stdenv.hostPlatform.system == "armv6l-linux") ''
|
||||
# Seems to require certain floating point hardware (NEON?)
|
||||
rm test/ExecutionEngine/frem.ll
|
||||
'' + ''
|
||||
patchShebangs test/BugPoint/compile-custom.ll.py
|
||||
'';
|
||||
|
||||
# hacky fix: created binaries need to be run before installation
|
||||
preBuild = ''
|
||||
mkdir -p $out/
|
||||
ln -sv $PWD/lib $out
|
||||
'';
|
||||
|
||||
# E.g. mesa.drivers use the build-id as a cache key (see #93946):
|
||||
LDFLAGS = optionalString (enableSharedLibraries && !stdenv.isDarwin) "-Wl,--build-id=sha1";
|
||||
|
||||
cmakeFlags = with stdenv; [
|
||||
"-DLLVM_INSTALL_CMAKE_DIR=${placeholder "dev"}/lib/cmake/llvm/"
|
||||
"-DCMAKE_BUILD_TYPE=${if debugVersion then "Debug" else "Release"}"
|
||||
"-DLLVM_BUILD_TESTS=${if stdenv.targetPlatform.isMinGW then "OFF" else "ON"}"
|
||||
"-DLLVM_HOST_TRIPLE=${stdenv.hostPlatform.config}"
|
||||
"-DLLVM_DEFAULT_TARGET_TRIPLE=${stdenv.hostPlatform.config}"
|
||||
"-DLLVM_ENABLE_UNWIND_TABLES=OFF"
|
||||
"-DLLVM_ENABLE_THREADS=OFF"
|
||||
"-DLLVM_TARGETS_TO_BUILD=X86;ARM;RISCV"
|
||||
] ++ optionals enableSharedLibraries [
|
||||
"-DLLVM_LINK_LLVM_DYLIB=ON"
|
||||
] ++ optionals enableManpages [
|
||||
"-DLLVM_BUILD_DOCS=ON"
|
||||
"-DLLVM_ENABLE_SPHINX=ON"
|
||||
"-DSPHINX_OUTPUT_MAN=ON"
|
||||
"-DSPHINX_OUTPUT_HTML=OFF"
|
||||
"-DSPHINX_WARNINGS_AS_ERRORS=OFF"
|
||||
] ++ optionals (!isDarwin && !stdenv.targetPlatform.isMinGW) [
|
||||
"-DLLVM_BINUTILS_INCDIR=${libbfd.dev}/include"
|
||||
] ++ optionals isDarwin [
|
||||
"-DLLVM_ENABLE_LIBCXX=ON"
|
||||
"-DCAN_TARGET_i386=false"
|
||||
] ++ optionals (stdenv.hostPlatform != stdenv.buildPlatform) [
|
||||
"-DCMAKE_CROSSCOMPILING=True"
|
||||
"-DLLVM_TABLEGEN=${llvmPackages_13.tools.llvm}/bin/llvm-tblgen"
|
||||
(
|
||||
let
|
||||
nativeCC = pkgsBuildBuild.targetPackages.stdenv.cc;
|
||||
nativeBintools = nativeCC.bintools.bintools;
|
||||
nativeToolchainFlags = [
|
||||
"-DCMAKE_C_COMPILER=${nativeCC}/bin/${nativeCC.targetPrefix}cc"
|
||||
"-DCMAKE_CXX_COMPILER=${nativeCC}/bin/${nativeCC.targetPrefix}c++"
|
||||
"-DCMAKE_AR=${nativeBintools}/bin/${nativeBintools.targetPrefix}ar"
|
||||
"-DCMAKE_STRIP=${nativeBintools}/bin/${nativeBintools.targetPrefix}strip"
|
||||
"-DCMAKE_RANLIB=${nativeBintools}/bin/${nativeBintools.targetPrefix}ranlib"
|
||||
];
|
||||
in "-DCROSS_TOOLCHAIN_FLAGS_NATIVE:list=${lib.concatStringsSep ";" nativeToolchainFlags}"
|
||||
)
|
||||
] ++ extraCmakeFlags;
|
||||
|
||||
postBuild = ''
|
||||
rm -fR $out
|
||||
'';
|
||||
|
||||
preCheck = ''
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}$PWD/lib
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $python/share
|
||||
mv $out/share/opt-viewer $python/share/opt-viewer
|
||||
moveToOutput "bin/llvm-config*" "$dev"
|
||||
substituteInPlace "$dev/lib/cmake/llvm/LLVMExports-${if debugVersion then "debug" else "release"}.cmake" \
|
||||
--replace "\''${_IMPORT_PREFIX}/lib/lib" "$lib/lib/lib" \
|
||||
--replace "$out/bin/llvm-config" "$dev/bin/llvm-config"
|
||||
substituteInPlace "$dev/lib/cmake/llvm/LLVMConfig.cmake" \
|
||||
--replace 'set(LLVM_BINARY_DIR "''${LLVM_INSTALL_PREFIX}")' 'set(LLVM_BINARY_DIR "''${LLVM_INSTALL_PREFIX}'"$lib"'")'
|
||||
''
|
||||
+ optionalString (stdenv.isDarwin && enableSharedLibraries) ''
|
||||
ln -s $lib/lib/libLLVM.dylib $lib/lib/libLLVM-${shortVersion}.dylib
|
||||
ln -s $lib/lib/libLLVM.dylib $lib/lib/libLLVM-${release_version}.dylib
|
||||
''
|
||||
+ optionalString (stdenv.buildPlatform != stdenv.hostPlatform) ''
|
||||
cp NATIVE/bin/llvm-config $dev/bin/llvm-config-native
|
||||
'';
|
||||
|
||||
doCheck = false; # the ABI change breaks RISC-V FP tests
|
||||
|
||||
checkTarget = "check-all";
|
||||
|
||||
requiredSystemFeatures = [ "big-parallel" ];
|
||||
meta = {
|
||||
homepage = "https://llvm.org/";
|
||||
description = "A collection of modular and reusable compiler and toolchain technologies";
|
||||
longDescription = ''
|
||||
The LLVM Project is a collection of modular and reusable compiler and
|
||||
toolchain technologies. Despite its name, LLVM has little to do with
|
||||
traditional virtual machines. The name "LLVM" itself is not an acronym; it
|
||||
is the full name of the project.
|
||||
LLVM began as a research project at the University of Illinois, with the
|
||||
goal of providing a modern, SSA-based compilation strategy capable of
|
||||
supporting both static and dynamic compilation of arbitrary programming
|
||||
languages. Since then, LLVM has grown to be an umbrella project consisting
|
||||
of a number of subprojects, many of which are being used in production by
|
||||
a wide variety of commercial and open source projects as well as being
|
||||
widely used in academic research. Code in the LLVM project is licensed
|
||||
under the "Apache 2.0 License with LLVM exceptions".
|
||||
'';
|
||||
};
|
||||
} // lib.optionalAttrs enableManpages {
|
||||
pname = "llvm-manpages";
|
||||
|
||||
buildPhase = ''
|
||||
make docs-llvm-man
|
||||
'';
|
||||
|
||||
propagatedBuildInputs = [];
|
||||
|
||||
installPhase = ''
|
||||
make -C docs install
|
||||
'';
|
||||
|
||||
postPatch = null;
|
||||
postInstall = null;
|
||||
|
||||
outputs = [ "out" ];
|
||||
|
||||
doCheck = false;
|
||||
|
||||
meta = {
|
||||
description = "man pages for LLVM ${version}";
|
||||
};
|
||||
})
|
381
llvm/gnu-install-dirs.patch
Normal file
381
llvm/gnu-install-dirs.patch
Normal file
@ -0,0 +1,381 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 135036f509d2..265c36f8211b 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -270,15 +270,21 @@ if (CMAKE_BUILD_TYPE AND
|
||||
message(FATAL_ERROR "Invalid value for CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||
endif()
|
||||
|
||||
+include(GNUInstallDirs)
|
||||
+
|
||||
set(LLVM_LIBDIR_SUFFIX "" CACHE STRING "Define suffix of library directory name (32/64)" )
|
||||
|
||||
-set(LLVM_TOOLS_INSTALL_DIR "bin" CACHE STRING "Path for binary subdirectory (defaults to 'bin')")
|
||||
+set(LLVM_TOOLS_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}" CACHE STRING
|
||||
+ "Path for binary subdirectory (defaults to 'bin')")
|
||||
mark_as_advanced(LLVM_TOOLS_INSTALL_DIR)
|
||||
|
||||
set(LLVM_UTILS_INSTALL_DIR "${LLVM_TOOLS_INSTALL_DIR}" CACHE STRING
|
||||
"Path to install LLVM utilities (enabled by LLVM_INSTALL_UTILS=ON) (defaults to LLVM_TOOLS_INSTALL_DIR)")
|
||||
mark_as_advanced(LLVM_UTILS_INSTALL_DIR)
|
||||
|
||||
+set(LLVM_INSTALL_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}${LLVM_LIBDIR_SUFFIX}/cmake/llvm" CACHE STRING
|
||||
+ "Path for CMake subdirectory (defaults to lib/cmake/llvm)" )
|
||||
+
|
||||
# They are used as destination of target generators.
|
||||
set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)
|
||||
set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib${LLVM_LIBDIR_SUFFIX})
|
||||
@@ -581,9 +587,9 @@ option (LLVM_ENABLE_SPHINX "Use Sphinx to generate llvm documentation." OFF)
|
||||
option (LLVM_ENABLE_OCAMLDOC "Build OCaml bindings documentation." ON)
|
||||
option (LLVM_ENABLE_BINDINGS "Build bindings." ON)
|
||||
|
||||
-set(LLVM_INSTALL_DOXYGEN_HTML_DIR "share/doc/llvm/doxygen-html"
|
||||
+set(LLVM_INSTALL_DOXYGEN_HTML_DIR "${CMAKE_INSTALL_DOCDIR}/${project}/doxygen-html"
|
||||
CACHE STRING "Doxygen-generated HTML documentation install directory")
|
||||
-set(LLVM_INSTALL_OCAMLDOC_HTML_DIR "share/doc/llvm/ocaml-html"
|
||||
+set(LLVM_INSTALL_OCAMLDOC_HTML_DIR "${CMAKE_INSTALL_DOCDIR}/${project}/ocaml-html"
|
||||
CACHE STRING "OCamldoc-generated HTML documentation install directory")
|
||||
|
||||
option (LLVM_BUILD_EXTERNAL_COMPILER_RT
|
||||
@@ -1048,7 +1054,7 @@ endif()
|
||||
|
||||
if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
|
||||
install(DIRECTORY include/llvm include/llvm-c
|
||||
- DESTINATION include
|
||||
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
COMPONENT llvm-headers
|
||||
FILES_MATCHING
|
||||
PATTERN "*.def"
|
||||
@@ -1059,7 +1065,7 @@ if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
|
||||
)
|
||||
|
||||
install(DIRECTORY ${LLVM_INCLUDE_DIR}/llvm ${LLVM_INCLUDE_DIR}/llvm-c
|
||||
- DESTINATION include
|
||||
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
COMPONENT llvm-headers
|
||||
FILES_MATCHING
|
||||
PATTERN "*.def"
|
||||
@@ -1073,13 +1079,13 @@ if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
|
||||
|
||||
if (LLVM_INSTALL_MODULEMAPS)
|
||||
install(DIRECTORY include/llvm include/llvm-c
|
||||
- DESTINATION include
|
||||
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
COMPONENT llvm-headers
|
||||
FILES_MATCHING
|
||||
PATTERN "module.modulemap"
|
||||
)
|
||||
install(FILES include/llvm/module.install.modulemap
|
||||
- DESTINATION include/llvm
|
||||
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/llvm
|
||||
COMPONENT llvm-headers
|
||||
RENAME "module.extern.modulemap"
|
||||
)
|
||||
diff --git a/cmake/modules/AddLLVM.cmake b/cmake/modules/AddLLVM.cmake
|
||||
index 9c2b85374307..5531ceeb2eeb 100644
|
||||
--- a/cmake/modules/AddLLVM.cmake
|
||||
+++ b/cmake/modules/AddLLVM.cmake
|
||||
@@ -818,9 +818,9 @@ macro(add_llvm_library name)
|
||||
get_target_export_arg(${name} LLVM export_to_llvmexports ${umbrella})
|
||||
install(TARGETS ${name}
|
||||
${export_to_llvmexports}
|
||||
- LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
|
||||
- ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
|
||||
- RUNTIME DESTINATION bin COMPONENT ${name})
|
||||
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
|
||||
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
|
||||
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${name})
|
||||
|
||||
if (NOT LLVM_ENABLE_IDE)
|
||||
add_llvm_install_targets(install-${name}
|
||||
@@ -1036,7 +1036,7 @@ function(process_llvm_pass_plugins)
|
||||
"set(LLVM_STATIC_EXTENSIONS ${LLVM_STATIC_EXTENSIONS})")
|
||||
install(FILES
|
||||
${llvm_cmake_builddir}/LLVMConfigExtensions.cmake
|
||||
- DESTINATION ${LLVM_INSTALL_PACKAGE_DIR}
|
||||
+ DESTINATION ${LLVM_INSTALL_CMAKE_DIR}
|
||||
COMPONENT cmake-exports)
|
||||
|
||||
set(ExtensionDef "${LLVM_BINARY_DIR}/include/llvm/Support/Extension.def")
|
||||
@@ -1250,7 +1250,7 @@ macro(add_llvm_example name)
|
||||
endif()
|
||||
add_llvm_executable(${name} ${ARGN})
|
||||
if( LLVM_BUILD_EXAMPLES )
|
||||
- install(TARGETS ${name} RUNTIME DESTINATION examples)
|
||||
+ install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples)
|
||||
endif()
|
||||
set_target_properties(${name} PROPERTIES FOLDER "Examples")
|
||||
endmacro(add_llvm_example name)
|
||||
@@ -1868,7 +1868,7 @@ function(llvm_install_library_symlink name dest type)
|
||||
set(full_name ${CMAKE_${type}_LIBRARY_PREFIX}${name}${CMAKE_${type}_LIBRARY_SUFFIX})
|
||||
set(full_dest ${CMAKE_${type}_LIBRARY_PREFIX}${dest}${CMAKE_${type}_LIBRARY_SUFFIX})
|
||||
|
||||
- set(output_dir lib${LLVM_LIBDIR_SUFFIX})
|
||||
+ set(output_dir ${CMAKE_INSTALL_FULL_LIBDIR}${LLVM_LIBDIR_SUFFIX})
|
||||
if(WIN32 AND "${type}" STREQUAL "SHARED")
|
||||
set(output_dir bin)
|
||||
endif()
|
||||
@@ -1879,7 +1879,7 @@ function(llvm_install_library_symlink name dest type)
|
||||
|
||||
endfunction()
|
||||
|
||||
-function(llvm_install_symlink name dest)
|
||||
+function(llvm_install_symlink name dest output_dir)
|
||||
cmake_parse_arguments(ARG "ALWAYS_GENERATE" "COMPONENT" "" ${ARGN})
|
||||
foreach(path ${CMAKE_MODULE_PATH})
|
||||
if(EXISTS ${path}/LLVMInstallSymlink.cmake)
|
||||
@@ -1902,7 +1902,7 @@ function(llvm_install_symlink name dest)
|
||||
set(full_dest ${dest}${CMAKE_EXECUTABLE_SUFFIX})
|
||||
|
||||
install(SCRIPT ${INSTALL_SYMLINK}
|
||||
- CODE "install_symlink(${full_name} ${full_dest} ${LLVM_TOOLS_INSTALL_DIR})"
|
||||
+ CODE "install_symlink(${full_name} ${full_dest} ${output_dir})"
|
||||
COMPONENT ${component})
|
||||
|
||||
if (NOT LLVM_ENABLE_IDE AND NOT ARG_ALWAYS_GENERATE)
|
||||
@@ -1985,7 +1985,8 @@ function(add_llvm_tool_symlink link_name target)
|
||||
endif()
|
||||
|
||||
if ((TOOL_IS_TOOLCHAIN OR NOT LLVM_INSTALL_TOOLCHAIN_ONLY) AND LLVM_BUILD_TOOLS)
|
||||
- llvm_install_symlink(${link_name} ${target})
|
||||
+ GNUInstallDirs_get_absolute_install_dir(output_dir LLVM_TOOLS_INSTALL_DIR)
|
||||
+ llvm_install_symlink(${link_name} ${target} ${output_dir})
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
@@ -2114,9 +2115,9 @@ function(llvm_setup_rpath name)
|
||||
# Since BUILD_SHARED_LIBS is only recommended for use by developers,
|
||||
# hardcode the rpath to build/install lib dir first in this mode.
|
||||
# FIXME: update this when there is better solution.
|
||||
- set(_install_rpath "${LLVM_LIBRARY_OUTPUT_INTDIR}" "${CMAKE_INSTALL_PREFIX}/lib${LLVM_LIBDIR_SUFFIX}" ${extra_libdir})
|
||||
+ set(_install_rpath "${LLVM_LIBRARY_OUTPUT_INTDIR}" "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}${LLVM_LIBDIR_SUFFIX}" ${extra_libdir})
|
||||
elseif(UNIX)
|
||||
- set(_install_rpath "\$ORIGIN/../lib${LLVM_LIBDIR_SUFFIX}" ${extra_libdir})
|
||||
+ set(_install_rpath "\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}${LLVM_LIBDIR_SUFFIX}" ${extra_libdir})
|
||||
if(${CMAKE_SYSTEM_NAME} MATCHES "(FreeBSD|DragonFly)")
|
||||
set_property(TARGET ${name} APPEND_STRING PROPERTY
|
||||
LINK_FLAGS " -Wl,-z,origin ")
|
||||
diff --git a/cmake/modules/AddOCaml.cmake b/cmake/modules/AddOCaml.cmake
|
||||
index 554046b20edf..4d1ad980641e 100644
|
||||
--- a/cmake/modules/AddOCaml.cmake
|
||||
+++ b/cmake/modules/AddOCaml.cmake
|
||||
@@ -144,9 +144,9 @@ function(add_ocaml_library name)
|
||||
endforeach()
|
||||
|
||||
if( APPLE )
|
||||
- set(ocaml_rpath "@executable_path/../../../lib${LLVM_LIBDIR_SUFFIX}")
|
||||
+ set(ocaml_rpath "@executable_path/../../../${CMAKE_INSTALL_LIBDIR}${LLVM_LIBDIR_SUFFIX}")
|
||||
elseif( UNIX )
|
||||
- set(ocaml_rpath "\\$ORIGIN/../../../lib${LLVM_LIBDIR_SUFFIX}")
|
||||
+ set(ocaml_rpath "\\$ORIGIN/../../../${CMAKE_INSTALL_LIBDIR}${LLVM_LIBDIR_SUFFIX}")
|
||||
endif()
|
||||
list(APPEND ocaml_flags "-ldopt" "-Wl,-rpath,${ocaml_rpath}")
|
||||
|
||||
diff --git a/cmake/modules/AddSphinxTarget.cmake b/cmake/modules/AddSphinxTarget.cmake
|
||||
index e80c3b5c1cac..482f6d715ef5 100644
|
||||
--- a/cmake/modules/AddSphinxTarget.cmake
|
||||
+++ b/cmake/modules/AddSphinxTarget.cmake
|
||||
@@ -90,7 +90,7 @@ function (add_sphinx_target builder project)
|
||||
endif()
|
||||
elseif (builder STREQUAL html)
|
||||
string(TOUPPER "${project}" project_upper)
|
||||
- set(${project_upper}_INSTALL_SPHINX_HTML_DIR "share/doc/${project}/html"
|
||||
+ set(${project_upper}_INSTALL_SPHINX_HTML_DIR "${CMAKE_INSTALL_DOCDIR}/${project}/html"
|
||||
CACHE STRING "HTML documentation install directory for ${project}")
|
||||
|
||||
# '/.' indicates: copy the contents of the directory directly into
|
||||
diff --git a/cmake/modules/CMakeLists.txt b/cmake/modules/CMakeLists.txt
|
||||
index 51b6a4fdc284..4adc2acfc074 100644
|
||||
--- a/cmake/modules/CMakeLists.txt
|
||||
+++ b/cmake/modules/CMakeLists.txt
|
||||
@@ -1,6 +1,6 @@
|
||||
include(LLVMDistributionSupport)
|
||||
|
||||
-set(LLVM_INSTALL_PACKAGE_DIR lib${LLVM_LIBDIR_SUFFIX}/cmake/llvm)
|
||||
+set(LLVM_INSTALL_PACKAGE_DIR ${LLVM_INSTALL_CMAKE_DIR} CACHE STRING "Path for CMake subdirectory (defaults to 'cmake/llvm')")
|
||||
set(llvm_cmake_builddir "${LLVM_BINARY_DIR}/${LLVM_INSTALL_PACKAGE_DIR}")
|
||||
|
||||
# First for users who use an installed LLVM, create the LLVMExports.cmake file.
|
||||
@@ -109,13 +109,13 @@ foreach(p ${_count})
|
||||
set(LLVM_CONFIG_CODE "${LLVM_CONFIG_CODE}
|
||||
get_filename_component(LLVM_INSTALL_PREFIX \"\${LLVM_INSTALL_PREFIX}\" PATH)")
|
||||
endforeach(p)
|
||||
-set(LLVM_CONFIG_INCLUDE_DIRS "\${LLVM_INSTALL_PREFIX}/include")
|
||||
+set(LLVM_CONFIG_INCLUDE_DIRS "\${LLVM_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
set(LLVM_CONFIG_INCLUDE_DIR "${LLVM_CONFIG_INCLUDE_DIRS}")
|
||||
set(LLVM_CONFIG_MAIN_INCLUDE_DIR "${LLVM_CONFIG_INCLUDE_DIRS}")
|
||||
-set(LLVM_CONFIG_LIBRARY_DIRS "\${LLVM_INSTALL_PREFIX}/lib\${LLVM_LIBDIR_SUFFIX}")
|
||||
+set(LLVM_CONFIG_LIBRARY_DIRS "\${LLVM_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}\${LLVM_LIBDIR_SUFFIX}")
|
||||
set(LLVM_CONFIG_CMAKE_DIR "\${LLVM_INSTALL_PREFIX}/${LLVM_INSTALL_PACKAGE_DIR}")
|
||||
set(LLVM_CONFIG_BINARY_DIR "\${LLVM_INSTALL_PREFIX}")
|
||||
-set(LLVM_CONFIG_TOOLS_BINARY_DIR "\${LLVM_INSTALL_PREFIX}/bin")
|
||||
+set(LLVM_CONFIG_TOOLS_BINARY_DIR "\${LLVM_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
|
||||
|
||||
# Generate a default location for lit
|
||||
if (LLVM_INSTALL_UTILS AND LLVM_BUILD_UTILS)
|
||||
diff --git a/cmake/modules/LLVMInstallSymlink.cmake b/cmake/modules/LLVMInstallSymlink.cmake
|
||||
index 3e6a2c9a2648..52e14d955c60 100644
|
||||
--- a/cmake/modules/LLVMInstallSymlink.cmake
|
||||
+++ b/cmake/modules/LLVMInstallSymlink.cmake
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
function(install_symlink name target outdir)
|
||||
set(DESTDIR $ENV{DESTDIR})
|
||||
- set(bindir "${DESTDIR}${CMAKE_INSTALL_PREFIX}/${outdir}/")
|
||||
+ set(bindir "${DESTDIR}${outdir}/")
|
||||
|
||||
message(STATUS "Creating ${name}")
|
||||
|
||||
diff --git a/docs/CMake.rst b/docs/CMake.rst
|
||||
index f1ac2c7d4934..c6e1469b5e13 100644
|
||||
--- a/docs/CMake.rst
|
||||
+++ b/docs/CMake.rst
|
||||
@@ -202,7 +202,7 @@ CMake manual, or execute ``cmake --help-variable VARIABLE_NAME``.
|
||||
**LLVM_LIBDIR_SUFFIX**:STRING
|
||||
Extra suffix to append to the directory where libraries are to be
|
||||
installed. On a 64-bit architecture, one could use ``-DLLVM_LIBDIR_SUFFIX=64``
|
||||
- to install libraries to ``/usr/lib64``.
|
||||
+ to install libraries to ``/usr/lib64``. See also ``CMAKE_INSTALL_LIBDIR``.
|
||||
|
||||
Rarely-used CMake variables
|
||||
---------------------------
|
||||
@@ -551,8 +551,8 @@ LLVM-specific variables
|
||||
|
||||
**LLVM_INSTALL_DOXYGEN_HTML_DIR**:STRING
|
||||
The path to install Doxygen-generated HTML documentation to. This path can
|
||||
- either be absolute or relative to the CMAKE_INSTALL_PREFIX. Defaults to
|
||||
- `share/doc/llvm/doxygen-html`.
|
||||
+ either be absolute or relative to the ``CMAKE_INSTALL_PREFIX``. Defaults to
|
||||
+ `${CMAKE_INSTALL_DOCDIR}/${project}/doxygen-html`.
|
||||
|
||||
**LLVM_LINK_LLVM_DYLIB**:BOOL
|
||||
If enabled, tools will be linked with the libLLVM shared library. Defaults
|
||||
@@ -792,9 +792,11 @@ the ``cmake`` command or by setting it directly in ``ccmake`` or ``cmake-gui``).
|
||||
|
||||
This file is available in two different locations.
|
||||
|
||||
-* ``<INSTALL_PREFIX>/lib/cmake/llvm/LLVMConfig.cmake`` where
|
||||
- ``<INSTALL_PREFIX>`` is the install prefix of an installed version of LLVM.
|
||||
- On Linux typically this is ``/usr/lib/cmake/llvm/LLVMConfig.cmake``.
|
||||
+* ``<LLVM_INSTALL_PACKAGE_DIR>LLVMConfig.cmake`` where
|
||||
+ ``<LLVM_INSTALL_PACKAGE_DIR>`` is the location where LLVM CMake modules are
|
||||
+ installed as part of an installed version of LLVM. This is typically
|
||||
+ ``cmake/llvm/`` within the lib directory. On Linux, this is typically
|
||||
+ ``/usr/lib/cmake/llvm/LLVMConfig.cmake``.
|
||||
|
||||
* ``<LLVM_BUILD_ROOT>/lib/cmake/llvm/LLVMConfig.cmake`` where
|
||||
``<LLVM_BUILD_ROOT>`` is the root of the LLVM build tree. **Note: this is only
|
||||
diff --git a/examples/Bye/CMakeLists.txt b/examples/Bye/CMakeLists.txt
|
||||
index bb96edb4b4bf..678c22fb43c8 100644
|
||||
--- a/examples/Bye/CMakeLists.txt
|
||||
+++ b/examples/Bye/CMakeLists.txt
|
||||
@@ -14,6 +14,6 @@ if (NOT WIN32)
|
||||
BUILDTREE_ONLY
|
||||
)
|
||||
|
||||
- install(TARGETS ${name} RUNTIME DESTINATION examples)
|
||||
+ install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_DOCDIR}/examples)
|
||||
set_target_properties(${name} PROPERTIES FOLDER "Examples")
|
||||
endif()
|
||||
diff --git a/include/llvm/CMakeLists.txt b/include/llvm/CMakeLists.txt
|
||||
index b46319f24fc8..2feabd1954e4 100644
|
||||
--- a/include/llvm/CMakeLists.txt
|
||||
+++ b/include/llvm/CMakeLists.txt
|
||||
@@ -5,5 +5,5 @@ add_subdirectory(Frontend)
|
||||
# If we're doing an out-of-tree build, copy a module map for generated
|
||||
# header files into the build area.
|
||||
if (NOT "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
||||
- configure_file(module.modulemap.build module.modulemap COPYONLY)
|
||||
+ configure_file(module.modulemap.build ${LLVM_INCLUDE_DIR}/module.modulemap COPYONLY)
|
||||
endif (NOT "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
||||
diff --git a/tools/llvm-config/BuildVariables.inc.in b/tools/llvm-config/BuildVariables.inc.in
|
||||
index ebe5b73a5c65..70c497be12f5 100644
|
||||
--- a/tools/llvm-config/BuildVariables.inc.in
|
||||
+++ b/tools/llvm-config/BuildVariables.inc.in
|
||||
@@ -23,6 +23,10 @@
|
||||
#define LLVM_CXXFLAGS "@LLVM_CXXFLAGS@"
|
||||
#define LLVM_BUILDMODE "@LLVM_BUILDMODE@"
|
||||
#define LLVM_LIBDIR_SUFFIX "@LLVM_LIBDIR_SUFFIX@"
|
||||
+#define LLVM_INSTALL_BINDIR "@CMAKE_INSTALL_BINDIR@"
|
||||
+#define LLVM_INSTALL_LIBDIR "@CMAKE_INSTALL_LIBDIR@"
|
||||
+#define LLVM_INSTALL_INCLUDEDIR "@CMAKE_INSTALL_INCLUDEDIR@"
|
||||
+#define LLVM_INSTALL_CMAKEDIR "@LLVM_INSTALL_CMAKE_DIR@"
|
||||
#define LLVM_TARGETS_BUILT "@LLVM_TARGETS_BUILT@"
|
||||
#define LLVM_SYSTEM_LIBS "@LLVM_SYSTEM_LIBS@"
|
||||
#define LLVM_BUILD_SYSTEM "@LLVM_BUILD_SYSTEM@"
|
||||
diff --git a/tools/llvm-config/llvm-config.cpp b/tools/llvm-config/llvm-config.cpp
|
||||
index 1a2f04552d13..44fa7d3eec6b 100644
|
||||
--- a/tools/llvm-config/llvm-config.cpp
|
||||
+++ b/tools/llvm-config/llvm-config.cpp
|
||||
@@ -357,12 +357,26 @@ int main(int argc, char **argv) {
|
||||
("-I" + ActiveIncludeDir + " " + "-I" + ActiveObjRoot + "/include");
|
||||
} else {
|
||||
ActivePrefix = CurrentExecPrefix;
|
||||
- ActiveIncludeDir = ActivePrefix + "/include";
|
||||
- SmallString<256> path(StringRef(LLVM_TOOLS_INSTALL_DIR));
|
||||
- sys::fs::make_absolute(ActivePrefix, path);
|
||||
- ActiveBinDir = std::string(path.str());
|
||||
- ActiveLibDir = ActivePrefix + "/lib" + LLVM_LIBDIR_SUFFIX;
|
||||
- ActiveCMakeDir = ActiveLibDir + "/cmake/llvm";
|
||||
+ {
|
||||
+ SmallString<256> path(StringRef(LLVM_INSTALL_INCLUDEDIR));
|
||||
+ sys::fs::make_absolute(ActivePrefix, path);
|
||||
+ ActiveIncludeDir = std::string(path.str());
|
||||
+ }
|
||||
+ {
|
||||
+ SmallString<256> path(StringRef(LLVM_INSTALL_BINDIR));
|
||||
+ sys::fs::make_absolute(ActivePrefix, path);
|
||||
+ ActiveBinDir = std::string(path.str());
|
||||
+ }
|
||||
+ {
|
||||
+ SmallString<256> path(StringRef(LLVM_INSTALL_LIBDIR LLVM_LIBDIR_SUFFIX));
|
||||
+ sys::fs::make_absolute(ActivePrefix, path);
|
||||
+ ActiveLibDir = std::string(path.str());
|
||||
+ }
|
||||
+ {
|
||||
+ SmallString<256> path(StringRef(LLVM_INSTALL_CMAKEDIR));
|
||||
+ sys::fs::make_absolute(ActivePrefix, path);
|
||||
+ ActiveCMakeDir = std::string(path.str());
|
||||
+ }
|
||||
ActiveIncludeOption = "-I" + ActiveIncludeDir;
|
||||
}
|
||||
|
||||
diff --git a/tools/lto/CMakeLists.txt b/tools/lto/CMakeLists.txt
|
||||
index 0af29ad762c5..37b99b83e35c 100644
|
||||
--- a/tools/lto/CMakeLists.txt
|
||||
+++ b/tools/lto/CMakeLists.txt
|
||||
@@ -33,7 +33,7 @@ add_llvm_library(${LTO_LIBRARY_NAME} ${LTO_LIBRARY_TYPE} INSTALL_WITH_TOOLCHAIN
|
||||
${SOURCES} DEPENDS intrinsics_gen)
|
||||
|
||||
install(FILES ${LLVM_MAIN_INCLUDE_DIR}/llvm-c/lto.h
|
||||
- DESTINATION include/llvm-c
|
||||
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/llvm-c
|
||||
COMPONENT LTO)
|
||||
|
||||
if (APPLE)
|
||||
diff --git a/tools/opt-viewer/CMakeLists.txt b/tools/opt-viewer/CMakeLists.txt
|
||||
index ead73ec13a8f..250362021f17 100644
|
||||
--- a/tools/opt-viewer/CMakeLists.txt
|
||||
+++ b/tools/opt-viewer/CMakeLists.txt
|
||||
@@ -8,7 +8,7 @@ set (files
|
||||
|
||||
foreach (file ${files})
|
||||
install(PROGRAMS ${file}
|
||||
- DESTINATION share/opt-viewer
|
||||
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/opt-viewer
|
||||
COMPONENT opt-viewer)
|
||||
endforeach (file)
|
||||
|
||||
diff --git a/tools/remarks-shlib/CMakeLists.txt b/tools/remarks-shlib/CMakeLists.txt
|
||||
index 865436247270..ce1daa62f6ab 100644
|
||||
--- a/tools/remarks-shlib/CMakeLists.txt
|
||||
+++ b/tools/remarks-shlib/CMakeLists.txt
|
||||
@@ -19,7 +19,7 @@ if(LLVM_ENABLE_PIC)
|
||||
endif()
|
||||
|
||||
install(FILES ${LLVM_MAIN_INCLUDE_DIR}/llvm-c/Remarks.h
|
||||
- DESTINATION include/llvm-c
|
||||
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/llvm-c
|
||||
COMPONENT Remarks)
|
||||
|
||||
if (APPLE)
|
28
llvm/llvm-future-riscv-abi.diff
Normal file
28
llvm/llvm-future-riscv-abi.diff
Normal file
@ -0,0 +1,28 @@
|
||||
diff --git a/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp b/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
|
||||
index 0aba18b20..9bb75e7f4 100644
|
||||
--- a/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
|
||||
+++ b/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
|
||||
@@ -33,6 +33,8 @@ ABI computeTargetABI(const Triple &TT, FeatureBitset FeatureBits,
|
||||
auto TargetABI = getTargetABI(ABIName);
|
||||
bool IsRV64 = TT.isArch64Bit();
|
||||
bool IsRV32E = FeatureBits[RISCV::FeatureRV32E];
|
||||
+ bool IsRV32D = FeatureBits[RISCV::FeatureStdExtD];
|
||||
+ bool IsRV32F = FeatureBits[RISCV::FeatureStdExtF];
|
||||
|
||||
if (!ABIName.empty() && TargetABI == ABI_Unknown) {
|
||||
errs()
|
||||
@@ -56,10 +58,10 @@ ABI computeTargetABI(const Triple &TT, FeatureBitset FeatureBits,
|
||||
if (TargetABI != ABI_Unknown)
|
||||
return TargetABI;
|
||||
|
||||
- // For now, default to the ilp32/ilp32e/lp64 ABI if no explicit ABI is given
|
||||
- // or an invalid/unrecognised string is given. In the future, it might be
|
||||
- // worth changing this to default to ilp32f/lp64f and ilp32d/lp64d when
|
||||
- // hardware support for floating point is present.
|
||||
+ if (IsRV32D)
|
||||
+ return ABI_ILP32D;
|
||||
+ if (IsRV32F)
|
||||
+ return ABI_ILP32F;
|
||||
if (IsRV32E)
|
||||
return ABI_ILP32E;
|
||||
if (IsRV64)
|
16
llvm/outputs.patch
Normal file
16
llvm/outputs.patch
Normal file
@ -0,0 +1,16 @@
|
||||
diff --git a/tools/llvm-config/llvm-config.cpp b/tools/llvm-config/llvm-config.cpp
|
||||
index 94d426b..37f7794 100644
|
||||
--- a/tools/llvm-config/llvm-config.cpp
|
||||
+++ b/tools/llvm-config/llvm-config.cpp
|
||||
@@ -333,6 +333,11 @@ int main(int argc, char **argv) {
|
||||
ActiveIncludeOption = "-I" + ActiveIncludeDir;
|
||||
}
|
||||
|
||||
+ /// Nix-specific multiple-output handling: override ActiveLibDir
|
||||
+ if (!IsInDevelopmentTree) {
|
||||
+ ActiveLibDir = std::string("@lib@") + "/lib" + LLVM_LIBDIR_SUFFIX;
|
||||
+ }
|
||||
+
|
||||
/// We only use `shared library` mode in cases where the static library form
|
||||
/// of the components provided are not available; note however that this is
|
||||
/// skipped if we're run from within the build dir. However, once installed,
|
24
nac3artiq/Cargo.toml
Normal file
24
nac3artiq/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "nac3artiq"
|
||||
version = "0.1.0"
|
||||
authors = ["M-Labs"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "nac3artiq"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.14", features = ["extension-module"] }
|
||||
parking_lot = "0.11"
|
||||
tempfile = "3"
|
||||
nac3parser = { path = "../nac3parser" }
|
||||
nac3core = { path = "../nac3core" }
|
||||
|
||||
[dependencies.inkwell]
|
||||
version = "0.1.0-beta.4"
|
||||
default-features = false
|
||||
features = ["llvm13-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]
|
||||
|
||||
[features]
|
||||
init-llvm-profile = []
|
32
nac3artiq/demo/demo.py
Normal file
32
nac3artiq/demo/demo.py
Normal file
@ -0,0 +1,32 @@
|
||||
from min_artiq import *
|
||||
from numpy import int32, int64
|
||||
|
||||
|
||||
@extern
|
||||
def output_int(x: int32):
|
||||
...
|
||||
|
||||
|
||||
@nac3
|
||||
class Demo:
|
||||
core: KernelInvariant[Core]
|
||||
led0: KernelInvariant[TTLOut]
|
||||
led1: KernelInvariant[TTLOut]
|
||||
|
||||
def __init__(self):
|
||||
self.core = Core()
|
||||
self.led0 = TTLOut(self.core, 18)
|
||||
self.led1 = TTLOut(self.core, 19)
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
while True:
|
||||
with parallel:
|
||||
self.led0.pulse(100.*ms)
|
||||
self.led1.pulse(100.*ms)
|
||||
self.core.delay(100.*ms)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Demo().run()
|
16
nac3artiq/demo/device_db.py
Normal file
16
nac3artiq/demo/device_db.py
Normal file
@ -0,0 +1,16 @@
|
||||
# python demo.py
|
||||
# artiq_run module.elf
|
||||
|
||||
device_db = {
|
||||
"core": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {
|
||||
"host": "kc705",
|
||||
"ref_period": 1e-9,
|
||||
"ref_multiplier": 8,
|
||||
"target": "rv32g"
|
||||
}
|
||||
},
|
||||
}
|
57
nac3artiq/demo/embedding_map.py
Normal file
57
nac3artiq/demo/embedding_map.py
Normal file
@ -0,0 +1,57 @@
|
||||
class EmbeddingMap:
|
||||
def __init__(self):
|
||||
self.object_inverse_map = {}
|
||||
self.object_map = {}
|
||||
self.string_map = {}
|
||||
self.string_reverse_map = {}
|
||||
self.function_map = {}
|
||||
|
||||
# preallocate exception names
|
||||
self.preallocate_runtime_exception_names(["RuntimeError",
|
||||
"RTIOUnderflow",
|
||||
"RTIOOverflow",
|
||||
"RTIODestinationUnreachable",
|
||||
"DMAError",
|
||||
"I2CError",
|
||||
"CacheError",
|
||||
"SPIError",
|
||||
"0:ZeroDivisionError",
|
||||
"0:IndexError"])
|
||||
|
||||
def preallocate_runtime_exception_names(self, names):
|
||||
for i, name in enumerate(names):
|
||||
if ":" not in name:
|
||||
name = "0:artiq.coredevice.exceptions." + name
|
||||
exn_id = self.store_str(name)
|
||||
assert exn_id == i
|
||||
|
||||
def store_function(self, key, fun):
|
||||
self.function_map[key] = fun
|
||||
return key
|
||||
|
||||
def store_object(self, obj):
|
||||
obj_id = id(obj)
|
||||
if obj_id in self.object_inverse_map:
|
||||
return self.object_inverse_map[obj_id]
|
||||
key = len(self.object_map) + 1
|
||||
self.object_map[key] = obj
|
||||
self.object_inverse_map[obj_id] = key
|
||||
return key
|
||||
|
||||
def store_str(self, s):
|
||||
if s in self.string_reverse_map:
|
||||
return self.string_reverse_map[s]
|
||||
key = len(self.string_map)
|
||||
self.string_map[key] = s
|
||||
self.string_reverse_map[s] = key
|
||||
return key
|
||||
|
||||
def retrieve_function(self, key):
|
||||
return self.function_map[key]
|
||||
|
||||
def retrieve_object(self, key):
|
||||
return self.object_map[key]
|
||||
|
||||
def retrieve_str(self, key):
|
||||
return self.string_map[key]
|
||||
|
278
nac3artiq/demo/min_artiq.py
Normal file
278
nac3artiq/demo/min_artiq.py
Normal file
@ -0,0 +1,278 @@
|
||||
from inspect import getfullargspec
|
||||
from functools import wraps
|
||||
from types import SimpleNamespace
|
||||
from numpy import int32, int64
|
||||
from typing import Generic, TypeVar
|
||||
from math import floor, ceil
|
||||
|
||||
import nac3artiq
|
||||
from embedding_map import EmbeddingMap
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Kernel", "KernelInvariant", "virtual",
|
||||
"Option", "Some", "none",
|
||||
"round64", "floor64", "ceil64",
|
||||
"extern", "kernel", "portable", "nac3",
|
||||
"rpc", "ms", "us", "ns",
|
||||
"print_int32", "print_int64",
|
||||
"Core", "TTLOut",
|
||||
"parallel", "sequential"
|
||||
]
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class Kernel(Generic[T]):
|
||||
pass
|
||||
|
||||
class KernelInvariant(Generic[T]):
|
||||
pass
|
||||
|
||||
# The virtual class must exist before nac3artiq.NAC3 is created.
|
||||
class virtual(Generic[T]):
|
||||
pass
|
||||
|
||||
class Option(Generic[T]):
|
||||
_nac3_option: T
|
||||
|
||||
def __init__(self, v: T):
|
||||
self._nac3_option = v
|
||||
|
||||
def is_none(self):
|
||||
return self._nac3_option is None
|
||||
|
||||
def is_some(self):
|
||||
return not self.is_none()
|
||||
|
||||
def unwrap(self):
|
||||
if self.is_none():
|
||||
raise ValueError("unwrap on none")
|
||||
return self._nac3_option
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if self.is_none():
|
||||
return "none"
|
||||
else:
|
||||
return "Some({})".format(repr(self._nac3_option))
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.is_none():
|
||||
return "none"
|
||||
else:
|
||||
return "Some({})".format(str(self._nac3_option))
|
||||
|
||||
def Some(v: T) -> Option[T]:
|
||||
return Option(v)
|
||||
|
||||
none = Option(None)
|
||||
|
||||
def round64(x):
|
||||
return round(x)
|
||||
|
||||
def floor64(x):
|
||||
return floor(x)
|
||||
|
||||
def ceil64(x):
|
||||
return ceil(x)
|
||||
|
||||
|
||||
import device_db
|
||||
core_arguments = device_db.device_db["core"]["arguments"]
|
||||
|
||||
compiler = nac3artiq.NAC3(core_arguments["target"])
|
||||
allow_registration = True
|
||||
# Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side.
|
||||
registered_functions = set()
|
||||
registered_classes = set()
|
||||
|
||||
def register_function(fun):
|
||||
assert allow_registration
|
||||
registered_functions.add(fun)
|
||||
|
||||
def register_class(cls):
|
||||
assert allow_registration
|
||||
registered_classes.add(cls)
|
||||
|
||||
|
||||
def extern(function):
|
||||
"""Decorates a function declaration defined by the core device runtime."""
|
||||
register_function(function)
|
||||
return function
|
||||
|
||||
def rpc(function):
|
||||
"""Decorates a function declaration defined by the core device runtime."""
|
||||
register_function(function)
|
||||
return function
|
||||
|
||||
def kernel(function_or_method):
|
||||
"""Decorates a function or method to be executed on the core device."""
|
||||
register_function(function_or_method)
|
||||
argspec = getfullargspec(function_or_method)
|
||||
if argspec.args and argspec.args[0] == "self":
|
||||
@wraps(function_or_method)
|
||||
def run_on_core(self, *args, **kwargs):
|
||||
fake_method = SimpleNamespace(__self__=self, __name__=function_or_method.__name__)
|
||||
self.core.run(fake_method, *args, **kwargs)
|
||||
else:
|
||||
@wraps(function_or_method)
|
||||
def run_on_core(*args, **kwargs):
|
||||
raise RuntimeError("Kernel functions need explicit core.run()")
|
||||
return run_on_core
|
||||
|
||||
|
||||
def portable(function):
|
||||
"""Decorates a function or method to be executed on the same device (host/core device) as the caller."""
|
||||
register_function(function)
|
||||
return function
|
||||
|
||||
|
||||
def nac3(cls):
|
||||
"""
|
||||
Decorates a class to be analyzed by NAC3.
|
||||
All classes containing kernels or portable methods must use this decorator.
|
||||
"""
|
||||
register_class(cls)
|
||||
return cls
|
||||
|
||||
|
||||
ms = 1e-3
|
||||
us = 1e-6
|
||||
ns = 1e-9
|
||||
|
||||
@extern
|
||||
def rtio_init():
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@extern
|
||||
def rtio_get_counter() -> int64:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@extern
|
||||
def rtio_output(target: int32, data: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@extern
|
||||
def rtio_input_timestamp(timeout_mu: int64, channel: int32) -> int64:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@extern
|
||||
def rtio_input_data(channel: int32) -> int32:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
# These is not part of ARTIQ and only available in runkernel. Defined here for convenience.
|
||||
@extern
|
||||
def print_int32(x: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@extern
|
||||
def print_int64(x: int64):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@nac3
|
||||
class Core:
|
||||
ref_period: KernelInvariant[float]
|
||||
|
||||
def __init__(self):
|
||||
self.ref_period = core_arguments["ref_period"]
|
||||
|
||||
def run(self, method, *args, **kwargs):
|
||||
global allow_registration
|
||||
|
||||
embedding = EmbeddingMap()
|
||||
|
||||
if allow_registration:
|
||||
compiler.analyze(registered_functions, registered_classes)
|
||||
allow_registration = False
|
||||
|
||||
if hasattr(method, "__self__"):
|
||||
obj = method.__self__
|
||||
name = method.__name__
|
||||
else:
|
||||
obj = method
|
||||
name = ""
|
||||
|
||||
compiler.compile_method_to_file(obj, name, args, "module.elf", embedding)
|
||||
|
||||
@kernel
|
||||
def reset(self):
|
||||
rtio_init()
|
||||
at_mu(rtio_get_counter() + int64(125000))
|
||||
|
||||
@kernel
|
||||
def break_realtime(self):
|
||||
min_now = rtio_get_counter() + int64(125000)
|
||||
if now_mu() < min_now:
|
||||
at_mu(min_now)
|
||||
|
||||
@portable
|
||||
def seconds_to_mu(self, seconds: float) -> int64:
|
||||
return int64(round(seconds/self.ref_period))
|
||||
|
||||
@portable
|
||||
def mu_to_seconds(self, mu: int64) -> float:
|
||||
return float(mu)*self.ref_period
|
||||
|
||||
@kernel
|
||||
def delay(self, dt: float):
|
||||
delay_mu(self.seconds_to_mu(dt))
|
||||
|
||||
|
||||
@nac3
|
||||
class TTLOut:
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
target_o: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, core: Core, channel: int32):
|
||||
self.core = core
|
||||
self.channel = channel
|
||||
self.target_o = channel << 8
|
||||
|
||||
@kernel
|
||||
def output(self):
|
||||
pass
|
||||
|
||||
@kernel
|
||||
def set_o(self, o: bool):
|
||||
rtio_output(self.target_o, 1 if o else 0)
|
||||
|
||||
@kernel
|
||||
def on(self):
|
||||
self.set_o(True)
|
||||
|
||||
@kernel
|
||||
def off(self):
|
||||
self.set_o(False)
|
||||
|
||||
@kernel
|
||||
def pulse_mu(self, duration: int64):
|
||||
self.on()
|
||||
delay_mu(duration)
|
||||
self.off()
|
||||
|
||||
@kernel
|
||||
def pulse(self, duration: float):
|
||||
self.on()
|
||||
self.core.delay(duration)
|
||||
self.off()
|
||||
|
||||
@nac3
|
||||
class KernelContextManager:
|
||||
@kernel
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
@kernel
|
||||
def __exit__(self):
|
||||
pass
|
||||
|
||||
parallel = KernelContextManager()
|
||||
sequential = KernelContextManager()
|
1
nac3artiq/demo/nac3artiq.so
Symbolic link
1
nac3artiq/demo/nac3artiq.so
Symbolic link
@ -0,0 +1 @@
|
||||
../../target/release/libnac3artiq.so
|
493
nac3artiq/src/codegen.rs
Normal file
493
nac3artiq/src/codegen.rs
Normal file
@ -0,0 +1,493 @@
|
||||
use nac3core::{
|
||||
codegen::{
|
||||
expr::gen_call,
|
||||
stmt::{gen_block, gen_with},
|
||||
CodeGenContext, CodeGenerator,
|
||||
},
|
||||
symbol_resolver::ValueEnum,
|
||||
toplevel::{DefinitionId, GenCall},
|
||||
typecheck::typedef::{FunSignature, Type},
|
||||
};
|
||||
|
||||
use nac3parser::ast::{Expr, ExprKind, Located, Stmt, StmtKind, StrRef};
|
||||
|
||||
use inkwell::{
|
||||
context::Context, module::Linkage, types::IntType, values::BasicValueEnum, AddressSpace,
|
||||
};
|
||||
|
||||
use crate::timeline::TimeFns;
|
||||
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
collections::HashMap,
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub struct ArtiqCodeGenerator<'a> {
|
||||
name: String,
|
||||
size_t: u32,
|
||||
name_counter: u32,
|
||||
start: Option<Expr<Option<Type>>>,
|
||||
end: Option<Expr<Option<Type>>>,
|
||||
timeline: &'a (dyn TimeFns + Sync),
|
||||
}
|
||||
|
||||
impl<'a> ArtiqCodeGenerator<'a> {
|
||||
pub fn new(
|
||||
name: String,
|
||||
size_t: u32,
|
||||
timeline: &'a (dyn TimeFns + Sync),
|
||||
) -> ArtiqCodeGenerator<'a> {
|
||||
assert!(size_t == 32 || size_t == 64);
|
||||
ArtiqCodeGenerator { name, size_t, name_counter: 0, start: None, end: None, timeline }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> {
|
||||
fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx> {
|
||||
if self.size_t == 32 {
|
||||
ctx.i32_type()
|
||||
} else {
|
||||
ctx.i64_type()
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_call<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
fun: (&FunSignature, DefinitionId),
|
||||
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
) -> Result<Option<BasicValueEnum<'ctx>>, String> {
|
||||
let result = gen_call(self, ctx, obj, fun, params)?;
|
||||
if let Some(end) = self.end.clone() {
|
||||
let old_end = self.gen_expr(ctx, &end)?.unwrap().to_basic_value_enum(ctx, self)?;
|
||||
let now = self.timeline.emit_now_mu(ctx);
|
||||
let smax = ctx.module.get_function("llvm.smax.i64").unwrap_or_else(|| {
|
||||
let i64 = ctx.ctx.i64_type();
|
||||
ctx.module.add_function(
|
||||
"llvm.smax.i64",
|
||||
i64.fn_type(&[i64.into(), i64.into()], false),
|
||||
None,
|
||||
)
|
||||
});
|
||||
let max = ctx
|
||||
.builder
|
||||
.build_call(smax, &[old_end.into(), now.into()], "smax")
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap();
|
||||
let end_store = self.gen_store_target(ctx, &end)?;
|
||||
ctx.builder.build_store(end_store, max);
|
||||
}
|
||||
if let Some(start) = self.start.clone() {
|
||||
let start_val = self.gen_expr(ctx, &start)?.unwrap().to_basic_value_enum(ctx, self)?;
|
||||
self.timeline.emit_at_mu(ctx, start_val);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn gen_with<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String> {
|
||||
if let StmtKind::With { items, body, .. } = &stmt.node {
|
||||
if items.len() == 1 && items[0].optional_vars.is_none() {
|
||||
let item = &items[0];
|
||||
// Behavior of parallel and sequential:
|
||||
// Each function call (indirectly, can be inside a sequential block) within a parallel
|
||||
// block will update the end variable to the maximum now_mu in the block.
|
||||
// Each function call directly inside a parallel block will reset the timeline after
|
||||
// execution. A parallel block within a sequential block (or not within any block) will
|
||||
// set the timeline to the max now_mu within the block (and the outer max now_mu will also
|
||||
// be updated).
|
||||
//
|
||||
// Implementation: We track the start and end separately.
|
||||
// - If there is a start variable, it indicates that we are directly inside a
|
||||
// parallel block and we have to reset the timeline after every function call.
|
||||
// - If there is a end variable, it indicates that we are (indirectly) inside a
|
||||
// parallel block, and we should update the max end value.
|
||||
if let ExprKind::Name { id, ctx: name_ctx } = &item.context_expr.node {
|
||||
if id == &"parallel".into() {
|
||||
let old_start = self.start.take();
|
||||
let old_end = self.end.take();
|
||||
let now = if let Some(old_start) = &old_start {
|
||||
self.gen_expr(ctx, old_start)?.unwrap().to_basic_value_enum(ctx, self)?
|
||||
} else {
|
||||
self.timeline.emit_now_mu(ctx)
|
||||
};
|
||||
// Emulate variable allocation, as we need to use the CodeGenContext
|
||||
// HashMap to store our variable due to lifetime limitation
|
||||
// Note: we should be able to store variables directly if generic
|
||||
// associative type is used by limiting the lifetime of CodeGenerator to
|
||||
// the LLVM Context.
|
||||
// The name is guaranteed to be unique as users cannot use this as variable
|
||||
// name.
|
||||
self.start = old_start.clone().map_or_else(
|
||||
|| {
|
||||
let start = format!("with-{}-start", self.name_counter).into();
|
||||
let start_expr = Located {
|
||||
// location does not matter at this point
|
||||
location: stmt.location,
|
||||
node: ExprKind::Name { id: start, ctx: name_ctx.clone() },
|
||||
custom: Some(ctx.primitives.int64),
|
||||
};
|
||||
let start = self.gen_store_target(ctx, &start_expr)?;
|
||||
ctx.builder.build_store(start, now);
|
||||
Ok(Some(start_expr)) as Result<_, String>
|
||||
},
|
||||
|v| Ok(Some(v)),
|
||||
)?;
|
||||
let end = format!("with-{}-end", self.name_counter).into();
|
||||
let end_expr = Located {
|
||||
// location does not matter at this point
|
||||
location: stmt.location,
|
||||
node: ExprKind::Name { id: end, ctx: name_ctx.clone() },
|
||||
custom: Some(ctx.primitives.int64),
|
||||
};
|
||||
let end = self.gen_store_target(ctx, &end_expr)?;
|
||||
ctx.builder.build_store(end, now);
|
||||
self.end = Some(end_expr);
|
||||
self.name_counter += 1;
|
||||
gen_block(self, ctx, body.iter())?;
|
||||
let current = ctx.builder.get_insert_block().unwrap();
|
||||
// if the current block is terminated, move before the terminator
|
||||
// we want to set the timeline before reaching the terminator
|
||||
// TODO: This may be unsound if there are multiple exit paths in the
|
||||
// block... e.g.
|
||||
// if ...:
|
||||
// return
|
||||
// Perhaps we can fix this by using actual with block?
|
||||
let reset_position = if let Some(terminator) = current.get_terminator() {
|
||||
ctx.builder.position_before(&terminator);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// set duration
|
||||
let end_expr = self.end.take().unwrap();
|
||||
let end_val =
|
||||
self.gen_expr(ctx, &end_expr)?.unwrap().to_basic_value_enum(ctx, self)?;
|
||||
|
||||
// inside a sequential block
|
||||
if old_start.is_none() {
|
||||
self.timeline.emit_at_mu(ctx, end_val);
|
||||
}
|
||||
// inside a parallel block, should update the outer max now_mu
|
||||
if let Some(old_end) = &old_end {
|
||||
let outer_end_val = self
|
||||
.gen_expr(ctx, old_end)?
|
||||
.unwrap()
|
||||
.to_basic_value_enum(ctx, self)?;
|
||||
let smax =
|
||||
ctx.module.get_function("llvm.smax.i64").unwrap_or_else(|| {
|
||||
let i64 = ctx.ctx.i64_type();
|
||||
ctx.module.add_function(
|
||||
"llvm.smax.i64",
|
||||
i64.fn_type(&[i64.into(), i64.into()], false),
|
||||
None,
|
||||
)
|
||||
});
|
||||
let max = ctx
|
||||
.builder
|
||||
.build_call(smax, &[end_val.into(), outer_end_val.into()], "smax")
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap();
|
||||
let outer_end = self.gen_store_target(ctx, old_end)?;
|
||||
ctx.builder.build_store(outer_end, max);
|
||||
}
|
||||
self.start = old_start;
|
||||
self.end = old_end;
|
||||
if reset_position {
|
||||
ctx.builder.position_at_end(current);
|
||||
}
|
||||
return Ok(());
|
||||
} else if id == &"sequential".into() {
|
||||
let start = self.start.take();
|
||||
for stmt in body.iter() {
|
||||
self.gen_stmt(ctx, stmt)?;
|
||||
if ctx.is_terminated() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.start = start;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
// not parallel/sequential
|
||||
gen_with(self, ctx, stmt)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_rpc_tag<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ty: Type,
|
||||
buffer: &mut Vec<u8>,
|
||||
) -> Result<(), String> {
|
||||
use nac3core::typecheck::typedef::TypeEnum::*;
|
||||
|
||||
let int32 = ctx.primitives.int32;
|
||||
let int64 = ctx.primitives.int64;
|
||||
let float = ctx.primitives.float;
|
||||
let bool = ctx.primitives.bool;
|
||||
let str = ctx.primitives.str;
|
||||
let none = ctx.primitives.none;
|
||||
|
||||
if ctx.unifier.unioned(ty, int32) {
|
||||
buffer.push(b'i');
|
||||
} else if ctx.unifier.unioned(ty, int64) {
|
||||
buffer.push(b'I');
|
||||
} else if ctx.unifier.unioned(ty, float) {
|
||||
buffer.push(b'f');
|
||||
} else if ctx.unifier.unioned(ty, bool) {
|
||||
buffer.push(b'b');
|
||||
} else if ctx.unifier.unioned(ty, str) {
|
||||
buffer.push(b's');
|
||||
} else if ctx.unifier.unioned(ty, none) {
|
||||
buffer.push(b'n');
|
||||
} else {
|
||||
let ty_enum = ctx.unifier.get_ty(ty);
|
||||
match &*ty_enum {
|
||||
TTuple { ty } => {
|
||||
buffer.push(b't');
|
||||
buffer.push(ty.len() as u8);
|
||||
for ty in ty {
|
||||
gen_rpc_tag(ctx, *ty, buffer)?;
|
||||
}
|
||||
}
|
||||
TList { ty } => {
|
||||
buffer.push(b'l');
|
||||
gen_rpc_tag(ctx, *ty, buffer)?;
|
||||
}
|
||||
// we should return an error, this will be fixed after improving error message
|
||||
// as this requires returning an error during codegen
|
||||
_ => return Err(format!("Unsupported type: {:?}", ctx.unifier.stringify(ty))),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rpc_codegen_callback_fn<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
fun: (&FunSignature, DefinitionId),
|
||||
args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> Result<Option<BasicValueEnum<'ctx>>, String> {
|
||||
let ptr_type = ctx.ctx.i8_type().ptr_type(inkwell::AddressSpace::Generic);
|
||||
let size_type = generator.get_size_type(ctx.ctx);
|
||||
let int8 = ctx.ctx.i8_type();
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false);
|
||||
|
||||
let service_id = int32.const_int(fun.1 .0 as u64, false);
|
||||
// -- setup rpc tags
|
||||
let mut tag = Vec::new();
|
||||
if obj.is_some() {
|
||||
tag.push(b'O');
|
||||
}
|
||||
for arg in fun.0.args.iter() {
|
||||
gen_rpc_tag(ctx, arg.ty, &mut tag)?;
|
||||
}
|
||||
tag.push(b':');
|
||||
gen_rpc_tag(ctx, fun.0.ret, &mut tag)?;
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
tag.hash(&mut hasher);
|
||||
let hash = format!("{}", hasher.finish());
|
||||
|
||||
let tag_ptr = ctx
|
||||
.module
|
||||
.get_global(hash.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
let tag_arr_ptr = ctx.module.add_global(
|
||||
int8.array_type(tag.len() as u32),
|
||||
None,
|
||||
format!("tagptr{}", fun.1 .0).as_str(),
|
||||
);
|
||||
tag_arr_ptr.set_initializer(&int8.const_array(
|
||||
&tag.iter().map(|v| int8.const_int(*v as u64, false)).collect::<Vec<_>>(),
|
||||
));
|
||||
tag_arr_ptr.set_linkage(Linkage::Private);
|
||||
let tag_ptr = ctx.module.add_global(tag_ptr_type, None, &hash);
|
||||
tag_ptr.set_linkage(Linkage::Private);
|
||||
tag_ptr.set_initializer(&ctx.ctx.const_struct(
|
||||
&[
|
||||
tag_arr_ptr.as_pointer_value().const_cast(ptr_type).into(),
|
||||
size_type.const_int(tag.len() as u64, false).into(),
|
||||
],
|
||||
false,
|
||||
));
|
||||
tag_ptr
|
||||
})
|
||||
.as_pointer_value();
|
||||
|
||||
let arg_length = args.len() + if obj.is_some() { 1 } else { 0 };
|
||||
|
||||
let stacksave = ctx.module.get_function("llvm.stacksave").unwrap_or_else(|| {
|
||||
ctx.module.add_function("llvm.stacksave", ptr_type.fn_type(&[], false), None)
|
||||
});
|
||||
let stackrestore = ctx.module.get_function("llvm.stackrestore").unwrap_or_else(|| {
|
||||
ctx.module.add_function(
|
||||
"llvm.stackrestore",
|
||||
ctx.ctx.void_type().fn_type(&[ptr_type.into()], false),
|
||||
None,
|
||||
)
|
||||
});
|
||||
|
||||
let stackptr = ctx.builder.build_call(stacksave, &[], "rpc.stack");
|
||||
let args_ptr = ctx.builder.build_array_alloca(
|
||||
ptr_type,
|
||||
ctx.ctx.i32_type().const_int(arg_length as u64, false),
|
||||
"argptr",
|
||||
);
|
||||
|
||||
// -- rpc args handling
|
||||
let mut keys = fun.0.args.clone();
|
||||
let mut mapping = HashMap::new();
|
||||
for (key, value) in args.into_iter() {
|
||||
mapping.insert(key.unwrap_or_else(|| keys.remove(0).name), value);
|
||||
}
|
||||
// default value handling
|
||||
for k in keys.into_iter() {
|
||||
mapping.insert(k.name, ctx.gen_symbol_val(generator, &k.default_value.unwrap()).into());
|
||||
}
|
||||
// reorder the parameters
|
||||
let mut real_params = fun
|
||||
.0
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| mapping.remove(&arg.name).unwrap().to_basic_value_enum(ctx, generator))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if let Some(obj) = obj {
|
||||
if let ValueEnum::Static(obj) = obj.1 {
|
||||
real_params.insert(0, obj.get_const_obj(ctx, generator));
|
||||
} else {
|
||||
// should be an error here...
|
||||
panic!("only host object is allowed");
|
||||
}
|
||||
}
|
||||
|
||||
for (i, arg) in real_params.iter().enumerate() {
|
||||
let arg_slot = ctx.builder.build_alloca(arg.get_type(), &format!("rpc.arg{}", i));
|
||||
ctx.builder.build_store(arg_slot, *arg);
|
||||
let arg_slot = ctx.builder.build_bitcast(arg_slot, ptr_type, "rpc.arg");
|
||||
let arg_ptr = unsafe {
|
||||
ctx.builder.build_gep(
|
||||
args_ptr,
|
||||
&[int32.const_int(i as u64, false)],
|
||||
&format!("rpc.arg{}", i),
|
||||
)
|
||||
};
|
||||
ctx.builder.build_store(arg_ptr, arg_slot);
|
||||
}
|
||||
|
||||
// call
|
||||
let rpc_send = ctx.module.get_function("rpc_send").unwrap_or_else(|| {
|
||||
ctx.module.add_function(
|
||||
"rpc_send",
|
||||
ctx.ctx.void_type().fn_type(
|
||||
&[
|
||||
int32.into(),
|
||||
tag_ptr_type.ptr_type(AddressSpace::Generic).into(),
|
||||
ptr_type.ptr_type(AddressSpace::Generic).into(),
|
||||
],
|
||||
false,
|
||||
),
|
||||
None,
|
||||
)
|
||||
});
|
||||
ctx.builder.build_call(
|
||||
rpc_send,
|
||||
&[service_id.into(), tag_ptr.into(), args_ptr.into()],
|
||||
"rpc.send",
|
||||
);
|
||||
|
||||
// reclaim stack space used by arguments
|
||||
ctx.builder.build_call(
|
||||
stackrestore,
|
||||
&[stackptr.try_as_basic_value().unwrap_left().into()],
|
||||
"rpc.stackrestore",
|
||||
);
|
||||
|
||||
// -- receive value:
|
||||
// T result = {
|
||||
// void *ret_ptr = alloca(sizeof(T));
|
||||
// void *ptr = ret_ptr;
|
||||
// loop: int size = rpc_recv(ptr);
|
||||
// // Non-zero: Provide `size` bytes of extra storage for variable-length data.
|
||||
// if(size) { ptr = alloca(size); goto loop; }
|
||||
// else *(T*)ret_ptr
|
||||
// }
|
||||
let rpc_recv = ctx.module.get_function("rpc_recv").unwrap_or_else(|| {
|
||||
ctx.module.add_function("rpc_recv", int32.fn_type(&[ptr_type.into()], false), None)
|
||||
});
|
||||
|
||||
if ctx.unifier.unioned(fun.0.ret, ctx.primitives.none) {
|
||||
ctx.build_call_or_invoke(rpc_recv, &[ptr_type.const_null().into()], "rpc_recv");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let prehead_bb = ctx.builder.get_insert_block().unwrap();
|
||||
let current_function = prehead_bb.get_parent().unwrap();
|
||||
let head_bb = ctx.ctx.append_basic_block(current_function, "rpc.head");
|
||||
let alloc_bb = ctx.ctx.append_basic_block(current_function, "rpc.continue");
|
||||
let tail_bb = ctx.ctx.append_basic_block(current_function, "rpc.tail");
|
||||
|
||||
let ret_ty = ctx.get_llvm_type(generator, fun.0.ret);
|
||||
let need_load = !ret_ty.is_pointer_type();
|
||||
let slot = ctx.builder.build_alloca(ret_ty, "rpc.ret.slot");
|
||||
let slotgen = ctx.builder.build_bitcast(slot, ptr_type, "rpc.ret.ptr");
|
||||
ctx.builder.build_unconditional_branch(head_bb);
|
||||
ctx.builder.position_at_end(head_bb);
|
||||
|
||||
let phi = ctx.builder.build_phi(ptr_type, "rpc.ptr");
|
||||
phi.add_incoming(&[(&slotgen, prehead_bb)]);
|
||||
let alloc_size = ctx
|
||||
.build_call_or_invoke(rpc_recv, &[phi.as_basic_value()], "rpc.size.next")
|
||||
.unwrap()
|
||||
.into_int_value();
|
||||
let is_done = ctx.builder.build_int_compare(
|
||||
inkwell::IntPredicate::EQ,
|
||||
int32.const_zero(),
|
||||
alloc_size,
|
||||
"rpc.done",
|
||||
);
|
||||
|
||||
ctx.builder.build_conditional_branch(is_done, tail_bb, alloc_bb);
|
||||
ctx.builder.position_at_end(alloc_bb);
|
||||
|
||||
let alloc_ptr = ctx.builder.build_array_alloca(ptr_type, alloc_size, "rpc.alloc");
|
||||
let alloc_ptr = ctx.builder.build_bitcast(alloc_ptr, ptr_type, "rpc.alloc.ptr");
|
||||
phi.add_incoming(&[(&alloc_ptr, alloc_bb)]);
|
||||
ctx.builder.build_unconditional_branch(head_bb);
|
||||
|
||||
ctx.builder.position_at_end(tail_bb);
|
||||
|
||||
let result = ctx.builder.build_load(slot, "rpc.result");
|
||||
if need_load {
|
||||
ctx.builder.build_call(
|
||||
stackrestore,
|
||||
&[stackptr.try_as_basic_value().unwrap_left().into()],
|
||||
"rpc.stackrestore",
|
||||
);
|
||||
}
|
||||
Ok(Some(result))
|
||||
}
|
||||
|
||||
pub fn rpc_codegen_callback() -> Arc<GenCall> {
|
||||
Arc::new(GenCall::new(Box::new(|ctx, obj, fun, args, generator| {
|
||||
rpc_codegen_callback_fn(ctx, obj, fun, args, generator)
|
||||
})))
|
||||
}
|
56
nac3artiq/src/kernel.ld
Normal file
56
nac3artiq/src/kernel.ld
Normal file
@ -0,0 +1,56 @@
|
||||
/* Force ld to make the ELF header as loadable. */
|
||||
PHDRS
|
||||
{
|
||||
headers PT_LOAD FILEHDR PHDRS ;
|
||||
text PT_LOAD ;
|
||||
data PT_LOAD ;
|
||||
dynamic PT_DYNAMIC ;
|
||||
eh_frame PT_GNU_EH_FRAME ;
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Push back .text section enough so that ld.lld not complain */
|
||||
. = SIZEOF_HEADERS;
|
||||
|
||||
.text :
|
||||
{
|
||||
*(.text .text.*)
|
||||
} : text
|
||||
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
}
|
||||
|
||||
.eh_frame :
|
||||
{
|
||||
KEEP(*(.eh_frame))
|
||||
} : text
|
||||
|
||||
.eh_frame_hdr :
|
||||
{
|
||||
KEEP(*(.eh_frame_hdr))
|
||||
} : text : eh_frame
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data)
|
||||
} : data
|
||||
|
||||
.dynamic :
|
||||
{
|
||||
*(.dynamic)
|
||||
} : data : dynamic
|
||||
|
||||
.bss (NOLOAD) : ALIGN(4)
|
||||
{
|
||||
__bss_start = .;
|
||||
*(.sbss .sbss.* .bss .bss.*);
|
||||
. = ALIGN(4);
|
||||
_end = .;
|
||||
}
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
_sstack_guard = .;
|
||||
}
|
874
nac3artiq/src/lib.rs
Normal file
874
nac3artiq/src/lib.rs
Normal file
@ -0,0 +1,874 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use inkwell::{
|
||||
memory_buffer::MemoryBuffer,
|
||||
passes::{PassManager, PassManagerBuilder},
|
||||
targets::*,
|
||||
OptimizationLevel,
|
||||
};
|
||||
use nac3core::toplevel::builtins::get_exn_constructor;
|
||||
use nac3core::typecheck::typedef::{TypeEnum, Unifier};
|
||||
use nac3parser::{
|
||||
ast::{self, ExprKind, Stmt, StmtKind, StrRef},
|
||||
parser::{self, parse_program},
|
||||
};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::{exceptions, types::PyBytes, types::PyDict, types::PySet};
|
||||
use pyo3::create_exception;
|
||||
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
|
||||
use nac3core::{
|
||||
codegen::irrt::load_irrt,
|
||||
codegen::{concrete_type::ConcreteTypeStore, CodeGenTask, WithCall, WorkerRegistry},
|
||||
symbol_resolver::SymbolResolver,
|
||||
toplevel::{
|
||||
composer::{ComposerConfig, TopLevelComposer},
|
||||
DefinitionId, GenCall, TopLevelDef,
|
||||
},
|
||||
typecheck::typedef::{FunSignature, FuncArg},
|
||||
typecheck::{type_inferencer::PrimitiveStore, typedef::Type},
|
||||
};
|
||||
|
||||
use tempfile::{self, TempDir};
|
||||
|
||||
use crate::{
|
||||
codegen::{rpc_codegen_callback, ArtiqCodeGenerator},
|
||||
symbol_resolver::{InnerResolver, PythonHelper, Resolver, DeferredEvaluationStore},
|
||||
};
|
||||
|
||||
mod codegen;
|
||||
mod symbol_resolver;
|
||||
mod timeline;
|
||||
|
||||
use timeline::TimeFns;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
enum Isa {
|
||||
Host,
|
||||
RiscV32G,
|
||||
RiscV32IMA,
|
||||
CortexA9,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PrimitivePythonId {
|
||||
int: u64,
|
||||
int32: u64,
|
||||
int64: u64,
|
||||
uint32: u64,
|
||||
uint64: u64,
|
||||
float: u64,
|
||||
bool: u64,
|
||||
list: u64,
|
||||
tuple: u64,
|
||||
typevar: u64,
|
||||
none: u64,
|
||||
exception: u64,
|
||||
generic_alias: (u64, u64),
|
||||
virtual_id: u64,
|
||||
option: u64,
|
||||
}
|
||||
|
||||
type TopLevelComponent = (Stmt, String, PyObject);
|
||||
|
||||
// TopLevelComposer is unsendable as it holds the unification table, which is
|
||||
// unsendable due to Rc. Arc would cause a performance hit.
|
||||
#[pyclass(unsendable, name = "NAC3")]
|
||||
struct Nac3 {
|
||||
isa: Isa,
|
||||
time_fns: &'static (dyn TimeFns + Sync),
|
||||
primitive: PrimitiveStore,
|
||||
builtins: Vec<(StrRef, FunSignature, Arc<GenCall>)>,
|
||||
pyid_to_def: Arc<RwLock<HashMap<u64, DefinitionId>>>,
|
||||
primitive_ids: PrimitivePythonId,
|
||||
working_directory: TempDir,
|
||||
top_levels: Vec<TopLevelComponent>,
|
||||
string_store: Arc<RwLock<HashMap<String, i32>>>,
|
||||
exception_ids: Arc<RwLock<HashMap<usize, usize>>>,
|
||||
deferred_eval_store: DeferredEvaluationStore
|
||||
}
|
||||
|
||||
create_exception!(nac3artiq, CompileError, exceptions::PyException);
|
||||
|
||||
impl Nac3 {
|
||||
fn register_module(
|
||||
&mut self,
|
||||
module: PyObject,
|
||||
registered_class_ids: &HashSet<u64>,
|
||||
) -> PyResult<()> {
|
||||
let (module_name, source_file) = Python::with_gil(|py| -> PyResult<(String, String)> {
|
||||
let module: &PyAny = module.extract(py)?;
|
||||
Ok((module.getattr("__name__")?.extract()?, module.getattr("__file__")?.extract()?))
|
||||
})?;
|
||||
|
||||
let source = fs::read_to_string(&source_file).map_err(|e| {
|
||||
exceptions::PyIOError::new_err(format!("failed to read input file: {}", e))
|
||||
})?;
|
||||
let parser_result = parser::parse_program(&source, source_file.into())
|
||||
.map_err(|e| exceptions::PySyntaxError::new_err(format!("parse error: {}", e)))?;
|
||||
|
||||
for mut stmt in parser_result.into_iter() {
|
||||
let include = match stmt.node {
|
||||
ast::StmtKind::ClassDef {
|
||||
ref decorator_list, ref mut body, ref mut bases, ..
|
||||
} => {
|
||||
let nac3_class = decorator_list.iter().any(|decorator| {
|
||||
if let ast::ExprKind::Name { id, .. } = decorator.node {
|
||||
id.to_string() == "nac3"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if !nac3_class {
|
||||
continue;
|
||||
}
|
||||
// Drop unregistered (i.e. host-only) base classes.
|
||||
bases.retain(|base| {
|
||||
Python::with_gil(|py| -> PyResult<bool> {
|
||||
let id_fn = PyModule::import(py, "builtins")?.getattr("id")?;
|
||||
match &base.node {
|
||||
ast::ExprKind::Name { id, .. } => {
|
||||
if *id == "Exception".into() {
|
||||
Ok(true)
|
||||
} else {
|
||||
let base_obj = module.getattr(py, id.to_string())?;
|
||||
let base_id = id_fn.call1((base_obj,))?.extract()?;
|
||||
Ok(registered_class_ids.contains(&base_id))
|
||||
}
|
||||
}
|
||||
_ => Ok(true),
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
});
|
||||
body.retain(|stmt| {
|
||||
if let ast::StmtKind::FunctionDef { ref decorator_list, .. } = stmt.node {
|
||||
decorator_list.iter().any(|decorator| {
|
||||
if let ast::ExprKind::Name { id, .. } = decorator.node {
|
||||
id.to_string() == "kernel"
|
||||
|| id.to_string() == "portable"
|
||||
|| id.to_string() == "rpc"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
true
|
||||
}
|
||||
ast::StmtKind::FunctionDef { ref decorator_list, .. } => {
|
||||
decorator_list.iter().any(|decorator| {
|
||||
if let ast::ExprKind::Name { id, .. } = decorator.node {
|
||||
let id = id.to_string();
|
||||
id == "extern" || id == "portable" || id == "kernel" || id == "rpc"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if include {
|
||||
self.top_levels.push((stmt, module_name.clone(), module.clone()));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn report_modinit(
|
||||
arg_names: &[String],
|
||||
method_name: &str,
|
||||
resolver: Arc<dyn SymbolResolver + Send + Sync>,
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
) -> Option<String> {
|
||||
let base_ty =
|
||||
match resolver.get_symbol_type(unifier, top_level_defs, primitives, "base".into()) {
|
||||
Ok(ty) => ty,
|
||||
Err(e) => return Some(format!("type error inside object launching kernel: {}", e)),
|
||||
};
|
||||
|
||||
let fun_ty = if method_name.is_empty() {
|
||||
base_ty
|
||||
} else if let TypeEnum::TObj { fields, .. } = &*unifier.get_ty(base_ty) {
|
||||
match fields.get(&(*method_name).into()) {
|
||||
Some(t) => t.0,
|
||||
None => {
|
||||
return Some(format!(
|
||||
"object launching kernel does not have method `{}`",
|
||||
method_name
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Some("cannot launch kernel by calling a non-callable".into());
|
||||
};
|
||||
|
||||
if let TypeEnum::TFunc(FunSignature { args, .. }) = &*unifier.get_ty(fun_ty) {
|
||||
if arg_names.len() > args.len() {
|
||||
return Some(format!(
|
||||
"launching kernel function with too many arguments (expect {}, found {})",
|
||||
args.len(),
|
||||
arg_names.len(),
|
||||
));
|
||||
}
|
||||
for (i, FuncArg { ty, default_value, name }) in args.iter().enumerate() {
|
||||
let in_name = match arg_names.get(i) {
|
||||
Some(n) => n,
|
||||
None if default_value.is_none() => {
|
||||
return Some(format!(
|
||||
"argument `{}` not provided when launching kernel function",
|
||||
name
|
||||
))
|
||||
}
|
||||
_ => break,
|
||||
};
|
||||
let in_ty = match resolver.get_symbol_type(
|
||||
unifier,
|
||||
top_level_defs,
|
||||
primitives,
|
||||
in_name.clone().into(),
|
||||
) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return Some(format!(
|
||||
"type error ({}) at parameter #{} when calling kernel function",
|
||||
e, i
|
||||
))
|
||||
}
|
||||
};
|
||||
if let Err(e) = unifier.unify(in_ty, *ty) {
|
||||
return Some(format!(
|
||||
"type error ({}) at parameter #{} when calling kernel function",
|
||||
e.to_display(unifier).to_string(),
|
||||
i
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Some("cannot launch kernel by calling a non-callable".into());
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn add_exceptions(
|
||||
composer: &mut TopLevelComposer,
|
||||
builtin_def: &mut HashMap<StrRef, DefinitionId>,
|
||||
builtin_ty: &mut HashMap<StrRef, Type>,
|
||||
error_names: &[&str]
|
||||
) -> Vec<Type> {
|
||||
let mut types = Vec::new();
|
||||
// note: this is only for builtin exceptions, i.e. the exception name is "0:{exn}"
|
||||
for name in error_names {
|
||||
let def_id = composer.definition_ast_list.len();
|
||||
let (exception_fn, exception_class, exception_cons, exception_type) = get_exn_constructor(
|
||||
name,
|
||||
// class id
|
||||
def_id,
|
||||
// constructor id
|
||||
def_id + 1,
|
||||
&mut composer.unifier,
|
||||
&composer.primitives_ty
|
||||
);
|
||||
composer.definition_ast_list.push((Arc::new(RwLock::new(exception_class)), None));
|
||||
composer.definition_ast_list.push((Arc::new(RwLock::new(exception_fn)), None));
|
||||
builtin_ty.insert((*name).into(), exception_cons);
|
||||
builtin_def.insert((*name).into(), DefinitionId(def_id));
|
||||
types.push(exception_type);
|
||||
}
|
||||
types
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Nac3 {
|
||||
#[new]
|
||||
fn new(isa: &str, py: Python) -> PyResult<Self> {
|
||||
let isa = match isa {
|
||||
"host" => Isa::Host,
|
||||
"rv32g" => Isa::RiscV32G,
|
||||
"rv32ima" => Isa::RiscV32IMA,
|
||||
"cortexa9" => Isa::CortexA9,
|
||||
_ => return Err(exceptions::PyValueError::new_err("invalid ISA")),
|
||||
};
|
||||
let time_fns: &(dyn TimeFns + Sync) = match isa {
|
||||
Isa::Host => &timeline::EXTERN_TIME_FNS,
|
||||
Isa::RiscV32G => &timeline::NOW_PINNING_TIME_FNS_64,
|
||||
Isa::RiscV32IMA => &timeline::NOW_PINNING_TIME_FNS,
|
||||
Isa::CortexA9 => &timeline::EXTERN_TIME_FNS,
|
||||
};
|
||||
let primitive: PrimitiveStore = TopLevelComposer::make_primitives().0;
|
||||
let builtins = vec![
|
||||
(
|
||||
"now_mu".into(),
|
||||
FunSignature { args: vec![], ret: primitive.int64, vars: HashMap::new() },
|
||||
Arc::new(GenCall::new(Box::new(move |ctx, _, _, _, _| {
|
||||
Ok(Some(time_fns.emit_now_mu(ctx)))
|
||||
}))),
|
||||
),
|
||||
(
|
||||
"at_mu".into(),
|
||||
FunSignature {
|
||||
args: vec![FuncArg {
|
||||
name: "t".into(),
|
||||
ty: primitive.int64,
|
||||
default_value: None,
|
||||
}],
|
||||
ret: primitive.none,
|
||||
vars: HashMap::new(),
|
||||
},
|
||||
Arc::new(GenCall::new(Box::new(move |ctx, _, _, args, generator| {
|
||||
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator).unwrap();
|
||||
time_fns.emit_at_mu(ctx, arg);
|
||||
Ok(None)
|
||||
}))),
|
||||
),
|
||||
(
|
||||
"delay_mu".into(),
|
||||
FunSignature {
|
||||
args: vec![FuncArg {
|
||||
name: "dt".into(),
|
||||
ty: primitive.int64,
|
||||
default_value: None,
|
||||
}],
|
||||
ret: primitive.none,
|
||||
vars: HashMap::new(),
|
||||
},
|
||||
Arc::new(GenCall::new(Box::new(move |ctx, _, _, args, generator| {
|
||||
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator).unwrap();
|
||||
time_fns.emit_delay_mu(ctx, arg);
|
||||
Ok(None)
|
||||
}))),
|
||||
),
|
||||
];
|
||||
|
||||
let builtins_mod = PyModule::import(py, "builtins").unwrap();
|
||||
let id_fn = builtins_mod.getattr("id").unwrap();
|
||||
let numpy_mod = PyModule::import(py, "numpy").unwrap();
|
||||
let typing_mod = PyModule::import(py, "typing").unwrap();
|
||||
let types_mod = PyModule::import(py, "types").unwrap();
|
||||
|
||||
let get_id = |x| id_fn.call1((x,)).unwrap().extract().unwrap();
|
||||
let get_attr_id = |obj: &PyModule, attr| id_fn.call1((obj.getattr(attr).unwrap(),))
|
||||
.unwrap().extract().unwrap();
|
||||
let primitive_ids = PrimitivePythonId {
|
||||
virtual_id: get_id(
|
||||
builtins_mod
|
||||
.getattr("globals")
|
||||
.unwrap()
|
||||
.call0()
|
||||
.unwrap()
|
||||
.get_item("virtual")
|
||||
.unwrap(
|
||||
)),
|
||||
generic_alias: (
|
||||
get_attr_id(typing_mod, "_GenericAlias"),
|
||||
get_attr_id(types_mod, "GenericAlias"),
|
||||
),
|
||||
none: id_fn
|
||||
.call1((builtins_mod
|
||||
.getattr("globals")
|
||||
.unwrap()
|
||||
.call0()
|
||||
.unwrap()
|
||||
.get_item("none")
|
||||
.unwrap(),))
|
||||
.unwrap()
|
||||
.extract()
|
||||
.unwrap(),
|
||||
typevar: get_attr_id(typing_mod, "TypeVar"),
|
||||
int: get_attr_id(builtins_mod, "int"),
|
||||
int32: get_attr_id(numpy_mod, "int32"),
|
||||
int64: get_attr_id(numpy_mod, "int64"),
|
||||
uint32: get_attr_id(numpy_mod, "uint32"),
|
||||
uint64: get_attr_id(numpy_mod, "uint64"),
|
||||
bool: get_attr_id(builtins_mod, "bool"),
|
||||
float: get_attr_id(builtins_mod, "float"),
|
||||
list: get_attr_id(builtins_mod, "list"),
|
||||
tuple: get_attr_id(builtins_mod, "tuple"),
|
||||
exception: get_attr_id(builtins_mod, "Exception"),
|
||||
option: id_fn
|
||||
.call1((builtins_mod
|
||||
.getattr("globals")
|
||||
.unwrap()
|
||||
.call0()
|
||||
.unwrap()
|
||||
.get_item("Option")
|
||||
.unwrap(),))
|
||||
.unwrap()
|
||||
.extract()
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap();
|
||||
fs::write(working_directory.path().join("kernel.ld"), include_bytes!("kernel.ld")).unwrap();
|
||||
|
||||
Ok(Nac3 {
|
||||
isa,
|
||||
time_fns,
|
||||
primitive,
|
||||
builtins,
|
||||
primitive_ids,
|
||||
top_levels: Default::default(),
|
||||
pyid_to_def: Default::default(),
|
||||
working_directory,
|
||||
string_store: Default::default(),
|
||||
exception_ids: Default::default(),
|
||||
deferred_eval_store: DeferredEvaluationStore::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn analyze(&mut self, functions: &PySet, classes: &PySet) -> PyResult<()> {
|
||||
let (modules, class_ids) =
|
||||
Python::with_gil(|py| -> PyResult<(HashMap<u64, PyObject>, HashSet<u64>)> {
|
||||
let mut modules: HashMap<u64, PyObject> = HashMap::new();
|
||||
let mut class_ids: HashSet<u64> = HashSet::new();
|
||||
|
||||
let id_fn = PyModule::import(py, "builtins")?.getattr("id")?;
|
||||
let getmodule_fn = PyModule::import(py, "inspect")?.getattr("getmodule")?;
|
||||
|
||||
for function in functions.iter() {
|
||||
let module = getmodule_fn.call1((function,))?.extract()?;
|
||||
modules.insert(id_fn.call1((&module,))?.extract()?, module);
|
||||
}
|
||||
for class in classes.iter() {
|
||||
let module = getmodule_fn.call1((class,))?.extract()?;
|
||||
modules.insert(id_fn.call1((&module,))?.extract()?, module);
|
||||
class_ids.insert(id_fn.call1((class,))?.extract()?);
|
||||
}
|
||||
Ok((modules, class_ids))
|
||||
})?;
|
||||
|
||||
for module in modules.into_values() {
|
||||
self.register_module(module, &class_ids)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_method_to_file(
|
||||
&mut self,
|
||||
obj: &PyAny,
|
||||
method_name: &str,
|
||||
args: Vec<&PyAny>,
|
||||
filename: &str,
|
||||
embedding_map: &PyAny,
|
||||
py: Python,
|
||||
) -> PyResult<()> {
|
||||
let (mut composer, mut builtins_def, mut builtins_ty) = TopLevelComposer::new(
|
||||
self.builtins.clone(),
|
||||
ComposerConfig { kernel_ann: Some("Kernel"), kernel_invariant_ann: "KernelInvariant" },
|
||||
);
|
||||
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
let typings = PyModule::import(py, "typing")?;
|
||||
let id_fn = builtins.getattr("id")?;
|
||||
let issubclass = builtins.getattr("issubclass")?;
|
||||
let exn_class = builtins.getattr("Exception")?;
|
||||
let store_obj = embedding_map.getattr("store_object").unwrap().to_object(py);
|
||||
let store_str = embedding_map.getattr("store_str").unwrap().to_object(py);
|
||||
let store_fun = embedding_map.getattr("store_function").unwrap().to_object(py);
|
||||
let helper = PythonHelper {
|
||||
id_fn: builtins.getattr("id").unwrap().to_object(py),
|
||||
len_fn: builtins.getattr("len").unwrap().to_object(py),
|
||||
type_fn: builtins.getattr("type").unwrap().to_object(py),
|
||||
origin_ty_fn: typings.getattr("get_origin").unwrap().to_object(py),
|
||||
args_ty_fn: typings.getattr("get_args").unwrap().to_object(py),
|
||||
store_obj: store_obj.clone(),
|
||||
store_str,
|
||||
};
|
||||
|
||||
let pyid_to_type = Arc::new(RwLock::new(HashMap::<u64, Type>::new()));
|
||||
let exception_names = [
|
||||
"ZeroDivisionError",
|
||||
"IndexError",
|
||||
"ValueError",
|
||||
"RuntimeError",
|
||||
"AssertionError",
|
||||
"KeyError",
|
||||
"NotImplementedError",
|
||||
"OverflowError",
|
||||
"IOError"
|
||||
];
|
||||
add_exceptions(&mut composer, &mut builtins_def, &mut builtins_ty, &exception_names);
|
||||
|
||||
let mut module_to_resolver_cache: HashMap<u64, _> = HashMap::new();
|
||||
|
||||
let global_value_ids = Arc::new(RwLock::new(HashSet::<u64>::new()));
|
||||
let mut rpc_ids = vec![];
|
||||
for (stmt, path, module) in self.top_levels.iter() {
|
||||
let py_module: &PyAny = module.extract(py)?;
|
||||
let module_id: u64 = id_fn.call1((py_module,))?.extract()?;
|
||||
let helper = helper.clone();
|
||||
let class_obj;
|
||||
if let StmtKind::ClassDef { name, .. } = &stmt.node {
|
||||
let class = py_module.getattr(name.to_string()).unwrap();
|
||||
if issubclass.call1((class, exn_class)).unwrap().extract().unwrap() &&
|
||||
class.getattr("artiq_builtin").is_err() {
|
||||
class_obj = Some(class);
|
||||
} else {
|
||||
class_obj = None;
|
||||
}
|
||||
} else {
|
||||
class_obj = None;
|
||||
}
|
||||
let (name_to_pyid, resolver) =
|
||||
module_to_resolver_cache.get(&module_id).cloned().unwrap_or_else(|| {
|
||||
let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new();
|
||||
let members: &PyDict =
|
||||
py_module.getattr("__dict__").unwrap().cast_as().unwrap();
|
||||
for (key, val) in members.iter() {
|
||||
let key: &str = key.extract().unwrap();
|
||||
let val = id_fn.call1((val,)).unwrap().extract().unwrap();
|
||||
name_to_pyid.insert(key.into(), val);
|
||||
}
|
||||
let resolver = Arc::new(Resolver(Arc::new(InnerResolver {
|
||||
id_to_type: builtins_ty.clone().into(),
|
||||
id_to_def: builtins_def.clone().into(),
|
||||
pyid_to_def: self.pyid_to_def.clone(),
|
||||
pyid_to_type: pyid_to_type.clone(),
|
||||
primitive_ids: self.primitive_ids.clone(),
|
||||
global_value_ids: global_value_ids.clone(),
|
||||
class_names: Default::default(),
|
||||
name_to_pyid: name_to_pyid.clone(),
|
||||
module: module.clone(),
|
||||
id_to_pyval: Default::default(),
|
||||
id_to_primitive: Default::default(),
|
||||
field_to_val: Default::default(),
|
||||
helper,
|
||||
string_store: self.string_store.clone(),
|
||||
exception_ids: self.exception_ids.clone(),
|
||||
deferred_eval_store: self.deferred_eval_store.clone(),
|
||||
})))
|
||||
as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
let name_to_pyid = Rc::new(name_to_pyid);
|
||||
module_to_resolver_cache
|
||||
.insert(module_id, (name_to_pyid.clone(), resolver.clone()));
|
||||
(name_to_pyid, resolver)
|
||||
});
|
||||
|
||||
let (name, def_id, ty) = composer
|
||||
.register_top_level(stmt.clone(), Some(resolver.clone()), path.clone())
|
||||
.map_err(|e| {
|
||||
CompileError::new_err(format!(
|
||||
"compilation failed\n----------\n{}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
if let Some(class_obj) = class_obj {
|
||||
self.exception_ids.write().insert(def_id.0, store_obj.call1(py, (class_obj, ))?.extract(py)?);
|
||||
}
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { decorator_list, .. } => {
|
||||
if decorator_list.iter().any(|decorator| matches!(decorator.node, ExprKind::Name { id, .. } if id == "rpc".into())) {
|
||||
store_fun.call1(py, (def_id.0.into_py(py), module.getattr(py, name.to_string()).unwrap())).unwrap();
|
||||
rpc_ids.push((None, def_id));
|
||||
}
|
||||
}
|
||||
StmtKind::ClassDef { name, body, .. } => {
|
||||
let class_obj = module.getattr(py, name.to_string()).unwrap();
|
||||
for stmt in body.iter() {
|
||||
if let StmtKind::FunctionDef { name, decorator_list, .. } = &stmt.node {
|
||||
if decorator_list.iter().any(|decorator| matches!(decorator.node, ExprKind::Name { id, .. } if id == "rpc".into())) {
|
||||
rpc_ids.push((Some((class_obj.clone(), *name)), def_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
|
||||
let id = *name_to_pyid.get(&name).unwrap();
|
||||
self.pyid_to_def.write().insert(id, def_id);
|
||||
{
|
||||
let mut pyid_to_ty = pyid_to_type.write();
|
||||
if let Some(ty) = ty {
|
||||
pyid_to_ty.insert(id, ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id_fun = PyModule::import(py, "builtins")?.getattr("id")?;
|
||||
let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new();
|
||||
let module = PyModule::new(py, "tmp")?;
|
||||
module.add("base", obj)?;
|
||||
name_to_pyid.insert("base".into(), id_fun.call1((obj,))?.extract()?);
|
||||
let mut arg_names = vec![];
|
||||
for (i, arg) in args.into_iter().enumerate() {
|
||||
let name = format!("tmp{}", i);
|
||||
module.add(&name, arg)?;
|
||||
name_to_pyid.insert(name.clone().into(), id_fun.call1((arg,))?.extract()?);
|
||||
arg_names.push(name);
|
||||
}
|
||||
let synthesized = if method_name.is_empty() {
|
||||
format!("def __modinit__():\n base({})", arg_names.join(", "))
|
||||
} else {
|
||||
format!("def __modinit__():\n base.{}({})", method_name, arg_names.join(", "))
|
||||
};
|
||||
let mut synthesized =
|
||||
parse_program(&synthesized, "__nac3_synthesized_modinit__".to_string().into()).unwrap();
|
||||
let resolver = Arc::new(Resolver(Arc::new(InnerResolver {
|
||||
id_to_type: builtins_ty.clone().into(),
|
||||
id_to_def: builtins_def.clone().into(),
|
||||
pyid_to_def: self.pyid_to_def.clone(),
|
||||
pyid_to_type: pyid_to_type.clone(),
|
||||
primitive_ids: self.primitive_ids.clone(),
|
||||
global_value_ids: global_value_ids.clone(),
|
||||
class_names: Default::default(),
|
||||
id_to_pyval: Default::default(),
|
||||
id_to_primitive: Default::default(),
|
||||
field_to_val: Default::default(),
|
||||
name_to_pyid,
|
||||
module: module.to_object(py),
|
||||
helper,
|
||||
string_store: self.string_store.clone(),
|
||||
exception_ids: self.exception_ids.clone(),
|
||||
deferred_eval_store: self.deferred_eval_store.clone(),
|
||||
}))) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
let (_, def_id, _) = composer
|
||||
.register_top_level(synthesized.pop().unwrap(), Some(resolver.clone()), "".into())
|
||||
.unwrap();
|
||||
|
||||
let signature =
|
||||
FunSignature { args: vec![], ret: self.primitive.none, vars: HashMap::new() };
|
||||
let mut store = ConcreteTypeStore::new();
|
||||
let mut cache = HashMap::new();
|
||||
let signature =
|
||||
store.from_signature(&mut composer.unifier, &self.primitive, &signature, &mut cache);
|
||||
let signature = store.add_cty(signature);
|
||||
|
||||
if let Err(e) = composer.start_analysis(true) {
|
||||
// report error of __modinit__ separately
|
||||
if !e.contains("__nac3_synthesized_modinit__") {
|
||||
return Err(CompileError::new_err(format!(
|
||||
"compilation failed\n----------\n{}",
|
||||
e
|
||||
)));
|
||||
} else {
|
||||
let msg = Self::report_modinit(
|
||||
&arg_names,
|
||||
method_name,
|
||||
resolver.clone(),
|
||||
&composer.extract_def_list(),
|
||||
&mut composer.unifier,
|
||||
&self.primitive,
|
||||
);
|
||||
return Err(CompileError::new_err(msg.unwrap_or(e)));
|
||||
}
|
||||
}
|
||||
let top_level = Arc::new(composer.make_top_level_context());
|
||||
|
||||
{
|
||||
let rpc_codegen = rpc_codegen_callback();
|
||||
let defs = top_level.definitions.read();
|
||||
for (class_data, id) in rpc_ids.iter() {
|
||||
let mut def = defs[id.0].write();
|
||||
match &mut *def {
|
||||
TopLevelDef::Function { codegen_callback, .. } => {
|
||||
*codegen_callback = Some(rpc_codegen.clone());
|
||||
}
|
||||
TopLevelDef::Class { methods, .. } => {
|
||||
let (class_def, method_name) = class_data.as_ref().unwrap();
|
||||
for (name, _, id) in methods.iter() {
|
||||
if name != method_name {
|
||||
continue;
|
||||
}
|
||||
if let TopLevelDef::Function { codegen_callback, .. } =
|
||||
&mut *defs[id.0].write()
|
||||
{
|
||||
*codegen_callback = Some(rpc_codegen.clone());
|
||||
store_fun
|
||||
.call1(
|
||||
py,
|
||||
(
|
||||
id.0.into_py(py),
|
||||
class_def.getattr(py, name.to_string()).unwrap(),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instance = {
|
||||
let defs = top_level.definitions.read();
|
||||
let mut definition = defs[def_id.0].write();
|
||||
if let TopLevelDef::Function { instance_to_stmt, instance_to_symbol, .. } =
|
||||
&mut *definition
|
||||
{
|
||||
instance_to_symbol.insert("".to_string(), "__modinit__".into());
|
||||
instance_to_stmt[""].clone()
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let task = CodeGenTask {
|
||||
subst: Default::default(),
|
||||
symbol_name: "__modinit__".to_string(),
|
||||
body: instance.body,
|
||||
signature,
|
||||
resolver,
|
||||
store,
|
||||
unifier_index: instance.unifier_id,
|
||||
calls: instance.calls,
|
||||
id: 0,
|
||||
};
|
||||
let isa = self.isa;
|
||||
let working_directory = self.working_directory.path().to_owned();
|
||||
|
||||
let membuffers: Arc<Mutex<Vec<Vec<u8>>>> = Default::default();
|
||||
|
||||
let membuffer = membuffers.clone();
|
||||
|
||||
let f = Arc::new(WithCall::new(Box::new(move |module| {
|
||||
let buffer = module.write_bitcode_to_memory();
|
||||
let buffer = buffer.as_slice().into();
|
||||
membuffer.lock().push(buffer);
|
||||
})));
|
||||
let size_t = if self.isa == Isa::Host { 64 } else { 32 };
|
||||
let thread_names: Vec<String> = (0..4).map(|_| "main".to_string()).collect();
|
||||
let threads: Vec<_> = thread_names
|
||||
.iter()
|
||||
.map(|s| Box::new(ArtiqCodeGenerator::new(s.to_string(), size_t, self.time_fns)))
|
||||
.collect();
|
||||
|
||||
py.allow_threads(|| {
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level.clone(), f);
|
||||
registry.add_task(task);
|
||||
registry.wait_tasks_complete(handles);
|
||||
});
|
||||
|
||||
let buffers = membuffers.lock();
|
||||
let context = inkwell::context::Context::create();
|
||||
let main = context
|
||||
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
|
||||
.unwrap();
|
||||
for buffer in buffers.iter().skip(1) {
|
||||
let other = context
|
||||
.create_module_from_ir(MemoryBuffer::create_from_memory_range(buffer, "main"))
|
||||
.unwrap();
|
||||
|
||||
main.link_in_module(other)
|
||||
.map_err(|err| CompileError::new_err(err.to_string()))?;
|
||||
}
|
||||
main.link_in_module(load_irrt(&context))
|
||||
.map_err(|err| CompileError::new_err(err.to_string()))?;
|
||||
|
||||
let mut function_iter = main.get_first_function();
|
||||
while let Some(func) = function_iter {
|
||||
if func.count_basic_blocks() > 0 && func.get_name().to_str().unwrap() != "__modinit__" {
|
||||
func.set_linkage(inkwell::module::Linkage::Private);
|
||||
}
|
||||
function_iter = func.get_next_function();
|
||||
}
|
||||
|
||||
let builder = PassManagerBuilder::create();
|
||||
builder.set_optimization_level(OptimizationLevel::Aggressive);
|
||||
let passes = PassManager::create(());
|
||||
builder.set_inliner_with_threshold(255);
|
||||
builder.populate_module_pass_manager(&passes);
|
||||
passes.run_on(&main);
|
||||
|
||||
let (triple, features) = match isa {
|
||||
Isa::Host => (
|
||||
TargetMachine::get_default_triple(),
|
||||
TargetMachine::get_host_cpu_features().to_string(),
|
||||
),
|
||||
Isa::RiscV32G => {
|
||||
(TargetTriple::create("riscv32-unknown-linux"), "+a,+m,+f,+d".to_string())
|
||||
}
|
||||
Isa::RiscV32IMA => (TargetTriple::create("riscv32-unknown-linux"), "+a,+m".to_string()),
|
||||
Isa::CortexA9 => (
|
||||
TargetTriple::create("armv7-unknown-linux-gnueabihf"),
|
||||
"+dsp,+fp16,+neon,+vfp3".to_string(),
|
||||
),
|
||||
};
|
||||
let target =
|
||||
Target::from_triple(&triple).expect("couldn't create target from target triple");
|
||||
let target_machine = target
|
||||
.create_target_machine(
|
||||
&triple,
|
||||
"",
|
||||
&features,
|
||||
OptimizationLevel::Default,
|
||||
RelocMode::PIC,
|
||||
CodeModel::Default,
|
||||
)
|
||||
.expect("couldn't create target machine");
|
||||
target_machine
|
||||
.write_to_file(&main, FileType::Object, &working_directory.join("module.o"))
|
||||
.expect("couldn't write module to file");
|
||||
|
||||
let mut linker_args = vec![
|
||||
"-shared".to_string(),
|
||||
"--eh-frame-hdr".to_string(),
|
||||
"-x".to_string(),
|
||||
"-o".to_string(),
|
||||
filename.to_string(),
|
||||
working_directory.join("module.o").to_string_lossy().to_string(),
|
||||
];
|
||||
if isa != Isa::Host {
|
||||
linker_args.push(
|
||||
"-T".to_string()
|
||||
+ self.working_directory.path().join("kernel.ld").to_str().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(linker_status) = Command::new("ld.lld").args(linker_args).status() {
|
||||
if !linker_status.success() {
|
||||
return Err(CompileError::new_err("failed to start linker"));
|
||||
}
|
||||
} else {
|
||||
return Err(CompileError::new_err(
|
||||
"linker returned non-zero status code",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_method_to_mem(
|
||||
&mut self,
|
||||
obj: &PyAny,
|
||||
method_name: &str,
|
||||
args: Vec<&PyAny>,
|
||||
embedding_map: &PyAny,
|
||||
py: Python,
|
||||
) -> PyResult<PyObject> {
|
||||
let filename_path = self.working_directory.path().join("module.elf");
|
||||
let filename = filename_path.to_str().unwrap();
|
||||
self.compile_method_to_file(obj, method_name, args, filename, embedding_map, py)?;
|
||||
Ok(PyBytes::new(py, &fs::read(filename).unwrap()).into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "init-llvm-profile")]
|
||||
extern "C" {
|
||||
fn __llvm_profile_initialize();
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn nac3artiq(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
#[cfg(feature = "init-llvm-profile")]
|
||||
unsafe {
|
||||
__llvm_profile_initialize();
|
||||
}
|
||||
|
||||
Target::initialize_all(&InitializationConfig::default());
|
||||
m.add("CompileError", py.get_type::<CompileError>())?;
|
||||
m.add_class::<Nac3>()?;
|
||||
Ok(())
|
||||
}
|
1151
nac3artiq/src/symbol_resolver.rs
Normal file
1151
nac3artiq/src/symbol_resolver.rs
Normal file
File diff suppressed because it is too large
Load Diff
304
nac3artiq/src/timeline.rs
Normal file
304
nac3artiq/src/timeline.rs
Normal file
@ -0,0 +1,304 @@
|
||||
use inkwell::{values::BasicValueEnum, AddressSpace, AtomicOrdering};
|
||||
use nac3core::codegen::CodeGenContext;
|
||||
|
||||
pub trait TimeFns {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx>;
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>);
|
||||
fn emit_delay_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, dt: BasicValueEnum<'ctx>);
|
||||
}
|
||||
|
||||
pub struct NowPinningTimeFns64 {}
|
||||
|
||||
// For FPGA design reasons, on VexRiscv with 64-bit data bus, the "now" CSR is split into two 32-bit
|
||||
// values that are each padded to 64-bits.
|
||||
impl TimeFns for NowPinningTimeFns64 {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr =
|
||||
ctx.builder.build_bitcast(now, i32_type.ptr_type(AddressSpace::Generic), "now_hiptr");
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_gep")
|
||||
};
|
||||
if let (BasicValueEnum::IntValue(now_hi), BasicValueEnum::IntValue(now_lo)) = (
|
||||
ctx.builder.build_load(now_hiptr, "now_hi"),
|
||||
ctx.builder.build_load(now_loptr, "now_lo"),
|
||||
) {
|
||||
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "now_zext_hi");
|
||||
let shifted_hi = ctx.builder.build_left_shift(
|
||||
zext_hi,
|
||||
i64_type.const_int(32, false),
|
||||
"now_shifted_zext_hi",
|
||||
);
|
||||
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "now_zext_lo");
|
||||
ctx.builder.build_or(shifted_hi, zext_lo, "now_or").into()
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
if let BasicValueEnum::IntValue(time) = t {
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr = ctx.builder.build_bitcast(
|
||||
now,
|
||||
i32_type.ptr_type(AddressSpace::Generic),
|
||||
"now_bitcast",
|
||||
);
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_gep")
|
||||
};
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_delay_mu<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
dt: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr =
|
||||
ctx.builder.build_bitcast(now, i32_type.ptr_type(AddressSpace::Generic), "now_hiptr");
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_loptr")
|
||||
};
|
||||
if let (
|
||||
BasicValueEnum::IntValue(now_hi),
|
||||
BasicValueEnum::IntValue(now_lo),
|
||||
BasicValueEnum::IntValue(dt),
|
||||
) = (
|
||||
ctx.builder.build_load(now_hiptr, "now_hi"),
|
||||
ctx.builder.build_load(now_loptr, "now_lo"),
|
||||
dt,
|
||||
) {
|
||||
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "now_zext_hi");
|
||||
let shifted_hi = ctx.builder.build_left_shift(
|
||||
zext_hi,
|
||||
i64_type.const_int(32, false),
|
||||
"now_shifted_zext_hi",
|
||||
);
|
||||
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "now_zext_lo");
|
||||
let now_val = ctx.builder.build_or(shifted_hi, zext_lo, "now_or");
|
||||
|
||||
let time = ctx.builder.build_int_add(now_val, dt, "now_add");
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(
|
||||
time,
|
||||
i64_type.const_int(32, false),
|
||||
false,
|
||||
"now_lshr",
|
||||
),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub static NOW_PINNING_TIME_FNS_64: NowPinningTimeFns64 = NowPinningTimeFns64 {};
|
||||
|
||||
pub struct NowPinningTimeFns {}
|
||||
|
||||
impl TimeFns for NowPinningTimeFns {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_raw = ctx.builder.build_load(now.as_pointer_value(), "now");
|
||||
if let BasicValueEnum::IntValue(now_raw) = now_raw {
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now_shl");
|
||||
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now_lshr");
|
||||
ctx.builder.build_or(now_lo, now_hi, "now_or").into()
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
if let BasicValueEnum::IntValue(time) = t {
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr = ctx.builder.build_bitcast(
|
||||
now,
|
||||
i32_type.ptr_type(AddressSpace::Generic),
|
||||
"now_bitcast",
|
||||
);
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now_gep")
|
||||
};
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_delay_mu<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
dt: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_raw = ctx.builder.build_load(now.as_pointer_value(), "now");
|
||||
if let (BasicValueEnum::IntValue(now_raw), BasicValueEnum::IntValue(dt)) = (now_raw, dt) {
|
||||
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now_shl");
|
||||
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now_lshr");
|
||||
let now_val = ctx.builder.build_or(now_lo, now_hi, "now_or");
|
||||
let time = ctx.builder.build_int_add(now_val, dt, "now_add");
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
let now_hiptr = ctx.builder.build_bitcast(
|
||||
now,
|
||||
i32_type.ptr_type(AddressSpace::Generic),
|
||||
"now_bitcast",
|
||||
);
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now_gep")
|
||||
};
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub static NOW_PINNING_TIME_FNS: NowPinningTimeFns = NowPinningTimeFns {};
|
||||
|
||||
pub struct ExternTimeFns {}
|
||||
|
||||
impl TimeFns for ExternTimeFns {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
|
||||
let now_mu = ctx.module.get_function("now_mu").unwrap_or_else(|| {
|
||||
ctx.module.add_function("now_mu", ctx.ctx.i64_type().fn_type(&[], false), None)
|
||||
});
|
||||
ctx.builder.build_call(now_mu, &[], "now_mu").try_as_basic_value().left().unwrap()
|
||||
}
|
||||
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
|
||||
let at_mu = ctx.module.get_function("at_mu").unwrap_or_else(|| {
|
||||
ctx.module.add_function(
|
||||
"at_mu",
|
||||
ctx.ctx.void_type().fn_type(&[ctx.ctx.i64_type().into()], false),
|
||||
None,
|
||||
)
|
||||
});
|
||||
ctx.builder.build_call(at_mu, &[t.into()], "at_mu");
|
||||
}
|
||||
|
||||
fn emit_delay_mu<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
dt: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
let delay_mu = ctx.module.get_function("delay_mu").unwrap_or_else(|| {
|
||||
ctx.module.add_function(
|
||||
"delay_mu",
|
||||
ctx.ctx.void_type().fn_type(&[ctx.ctx.i64_type().into()], false),
|
||||
None,
|
||||
)
|
||||
});
|
||||
ctx.builder.build_call(delay_mu, &[dt.into()], "delay_mu");
|
||||
}
|
||||
}
|
||||
|
||||
pub static EXTERN_TIME_FNS: ExternTimeFns = ExternTimeFns {};
|
16
nac3ast/Cargo.toml
Normal file
16
nac3ast/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "nac3ast"
|
||||
version = "0.1.0"
|
||||
authors = ["RustPython Team", "M-Labs"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["constant-optimization", "fold"]
|
||||
constant-optimization = ["fold"]
|
||||
fold = []
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
parking_lot = "0.11.1"
|
||||
string-interner = "0.13.0"
|
||||
fxhash = "0.2.1"
|
127
nac3ast/Python.asdl
Normal file
127
nac3ast/Python.asdl
Normal file
@ -0,0 +1,127 @@
|
||||
-- ASDL's 4 builtin types are:
|
||||
-- identifier, int, string, constant
|
||||
|
||||
module Python
|
||||
{
|
||||
mod = Module(stmt* body, type_ignore* type_ignores)
|
||||
| Interactive(stmt* body)
|
||||
| Expression(expr body)
|
||||
| FunctionType(expr* argtypes, expr returns)
|
||||
|
||||
stmt = FunctionDef(identifier name, arguments args,
|
||||
stmt* body, expr* decorator_list, expr? returns,
|
||||
string? type_comment, identifier* config_comment)
|
||||
| AsyncFunctionDef(identifier name, arguments args,
|
||||
stmt* body, expr* decorator_list, expr? returns,
|
||||
string? type_comment, identifier* config_comment)
|
||||
|
||||
| ClassDef(identifier name,
|
||||
expr* bases,
|
||||
keyword* keywords,
|
||||
stmt* body,
|
||||
expr* decorator_list, identifier* config_comment)
|
||||
| Return(expr? value, identifier* config_comment)
|
||||
|
||||
| Delete(expr* targets, identifier* config_comment)
|
||||
| Assign(expr* targets, expr value, string? type_comment, identifier* config_comment)
|
||||
| AugAssign(expr target, operator op, expr value, identifier* config_comment)
|
||||
-- 'simple' indicates that we annotate simple name without parens
|
||||
| AnnAssign(expr target, expr annotation, expr? value, bool simple, identifier* config_comment)
|
||||
|
||||
-- use 'orelse' because else is a keyword in target languages
|
||||
| For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment, identifier* config_comment)
|
||||
| AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment, identifier* config_comment)
|
||||
| While(expr test, stmt* body, stmt* orelse, identifier* config_comment)
|
||||
| If(expr test, stmt* body, stmt* orelse, identifier* config_comment)
|
||||
| With(withitem* items, stmt* body, string? type_comment, identifier* config_comment)
|
||||
| AsyncWith(withitem* items, stmt* body, string? type_comment, identifier* config_comment)
|
||||
|
||||
| Raise(expr? exc, expr? cause, identifier* config_comment)
|
||||
| Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody, identifier* config_comment)
|
||||
| Assert(expr test, expr? msg, identifier* config_comment)
|
||||
|
||||
| Import(alias* names, identifier* config_comment)
|
||||
| ImportFrom(identifier? module, alias* names, int level, identifier* config_comment)
|
||||
|
||||
| Global(identifier* names, identifier* config_comment)
|
||||
| Nonlocal(identifier* names, identifier* config_comment)
|
||||
| Expr(expr value, identifier* config_comment)
|
||||
| Pass(identifier* config_comment)
|
||||
| Break(identifier* config_comment)
|
||||
| Continue(identifier* config_comment)
|
||||
|
||||
-- col_offset is the byte offset in the utf8 string the parser uses
|
||||
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
|
||||
|
||||
-- BoolOp() can use left & right?
|
||||
expr = BoolOp(boolop op, expr* values)
|
||||
| NamedExpr(expr target, expr value)
|
||||
| BinOp(expr left, operator op, expr right)
|
||||
| UnaryOp(unaryop op, expr operand)
|
||||
| Lambda(arguments args, expr body)
|
||||
| IfExp(expr test, expr body, expr orelse)
|
||||
| Dict(expr?* keys, expr* values)
|
||||
| Set(expr* elts)
|
||||
| ListComp(expr elt, comprehension* generators)
|
||||
| SetComp(expr elt, comprehension* generators)
|
||||
| DictComp(expr key, expr value, comprehension* generators)
|
||||
| GeneratorExp(expr elt, comprehension* generators)
|
||||
-- the grammar constrains where yield expressions can occur
|
||||
| Await(expr value)
|
||||
| Yield(expr? value)
|
||||
| YieldFrom(expr value)
|
||||
-- need sequences for compare to distinguish between
|
||||
-- x < 4 < 3 and (x < 4) < 3
|
||||
| Compare(expr left, cmpop* ops, expr* comparators)
|
||||
| Call(expr func, expr* args, keyword* keywords)
|
||||
| FormattedValue(expr value, conversion_flag? conversion, expr? format_spec)
|
||||
| JoinedStr(expr* values)
|
||||
| Constant(constant value, string? kind)
|
||||
|
||||
-- the following expression can appear in assignment context
|
||||
| Attribute(expr value, identifier attr, expr_context ctx)
|
||||
| Subscript(expr value, expr slice, expr_context ctx)
|
||||
| Starred(expr value, expr_context ctx)
|
||||
| Name(identifier id, expr_context ctx)
|
||||
| List(expr* elts, expr_context ctx)
|
||||
| Tuple(expr* elts, expr_context ctx)
|
||||
|
||||
-- can appear only in Subscript
|
||||
| Slice(expr? lower, expr? upper, expr? step)
|
||||
|
||||
-- col_offset is the byte offset in the utf8 string the parser uses
|
||||
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
|
||||
|
||||
expr_context = Load | Store | Del
|
||||
|
||||
boolop = And | Or
|
||||
|
||||
operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift
|
||||
| RShift | BitOr | BitXor | BitAnd | FloorDiv
|
||||
|
||||
unaryop = Invert | Not | UAdd | USub
|
||||
|
||||
cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn
|
||||
|
||||
comprehension = (expr target, expr iter, expr* ifs, bool is_async)
|
||||
|
||||
excepthandler = ExceptHandler(expr? type, identifier? name, stmt* body)
|
||||
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
|
||||
|
||||
arguments = (arg* posonlyargs, arg* args, arg? vararg, arg* kwonlyargs,
|
||||
expr?* kw_defaults, arg? kwarg, expr* defaults)
|
||||
|
||||
arg = (identifier arg, expr? annotation, string? type_comment)
|
||||
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
|
||||
|
||||
-- keyword arguments supplied to call (NULL identifier for **kwargs)
|
||||
keyword = (identifier? arg, expr value)
|
||||
attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset)
|
||||
|
||||
-- import name with optional 'as' alias.
|
||||
alias = (identifier name, identifier? asname)
|
||||
|
||||
withitem = (expr context_expr, expr? optional_vars)
|
||||
|
||||
type_ignore = TypeIgnore(int lineno, string tag)
|
||||
}
|
385
nac3ast/asdl.py
Normal file
385
nac3ast/asdl.py
Normal file
@ -0,0 +1,385 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# Parser for ASDL [1] definition files. Reads in an ASDL description and parses
|
||||
# it into an AST that describes it.
|
||||
#
|
||||
# The EBNF we're parsing here: Figure 1 of the paper [1]. Extended to support
|
||||
# modules and attributes after a product. Words starting with Capital letters
|
||||
# are terminals. Literal tokens are in "double quotes". Others are
|
||||
# non-terminals. Id is either TokenId or ConstructorId.
|
||||
#
|
||||
# module ::= "module" Id "{" [definitions] "}"
|
||||
# definitions ::= { TypeId "=" type }
|
||||
# type ::= product | sum
|
||||
# product ::= fields ["attributes" fields]
|
||||
# fields ::= "(" { field, "," } field ")"
|
||||
# field ::= TypeId ["?" | "*"] [Id]
|
||||
# sum ::= constructor { "|" constructor } ["attributes" fields]
|
||||
# constructor ::= ConstructorId [fields]
|
||||
#
|
||||
# [1] "The Zephyr Abstract Syntax Description Language" by Wang, et. al. See
|
||||
# http://asdl.sourceforge.net/
|
||||
#-------------------------------------------------------------------------------
|
||||
from collections import namedtuple
|
||||
import re
|
||||
|
||||
__all__ = [
|
||||
'builtin_types', 'parse', 'AST', 'Module', 'Type', 'Constructor',
|
||||
'Field', 'Sum', 'Product', 'VisitorBase', 'Check', 'check']
|
||||
|
||||
# The following classes define nodes into which the ASDL description is parsed.
|
||||
# Note: this is a "meta-AST". ASDL files (such as Python.asdl) describe the AST
|
||||
# structure used by a programming language. But ASDL files themselves need to be
|
||||
# parsed. This module parses ASDL files and uses a simple AST to represent them.
|
||||
# See the EBNF at the top of the file to understand the logical connection
|
||||
# between the various node types.
|
||||
|
||||
builtin_types = {'identifier', 'string', 'int', 'constant', 'bool', 'conversion_flag'}
|
||||
|
||||
class AST:
|
||||
def __repr__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
class Module(AST):
|
||||
def __init__(self, name, dfns):
|
||||
self.name = name
|
||||
self.dfns = dfns
|
||||
self.types = {type.name: type.value for type in dfns}
|
||||
|
||||
def __repr__(self):
|
||||
return 'Module({0.name}, {0.dfns})'.format(self)
|
||||
|
||||
class Type(AST):
|
||||
def __init__(self, name, value):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return 'Type({0.name}, {0.value})'.format(self)
|
||||
|
||||
class Constructor(AST):
|
||||
def __init__(self, name, fields=None):
|
||||
self.name = name
|
||||
self.fields = fields or []
|
||||
|
||||
def __repr__(self):
|
||||
return 'Constructor({0.name}, {0.fields})'.format(self)
|
||||
|
||||
class Field(AST):
|
||||
def __init__(self, type, name=None, seq=False, opt=False):
|
||||
self.type = type
|
||||
self.name = name
|
||||
self.seq = seq
|
||||
self.opt = opt
|
||||
|
||||
def __str__(self):
|
||||
if self.seq:
|
||||
extra = "*"
|
||||
elif self.opt:
|
||||
extra = "?"
|
||||
else:
|
||||
extra = ""
|
||||
|
||||
return "{}{} {}".format(self.type, extra, self.name)
|
||||
|
||||
def __repr__(self):
|
||||
if self.seq:
|
||||
extra = ", seq=True"
|
||||
elif self.opt:
|
||||
extra = ", opt=True"
|
||||
else:
|
||||
extra = ""
|
||||
if self.name is None:
|
||||
return 'Field({0.type}{1})'.format(self, extra)
|
||||
else:
|
||||
return 'Field({0.type}, {0.name}{1})'.format(self, extra)
|
||||
|
||||
class Sum(AST):
|
||||
def __init__(self, types, attributes=None):
|
||||
self.types = types
|
||||
self.attributes = attributes or []
|
||||
|
||||
def __repr__(self):
|
||||
if self.attributes:
|
||||
return 'Sum({0.types}, {0.attributes})'.format(self)
|
||||
else:
|
||||
return 'Sum({0.types})'.format(self)
|
||||
|
||||
class Product(AST):
|
||||
def __init__(self, fields, attributes=None):
|
||||
self.fields = fields
|
||||
self.attributes = attributes or []
|
||||
|
||||
def __repr__(self):
|
||||
if self.attributes:
|
||||
return 'Product({0.fields}, {0.attributes})'.format(self)
|
||||
else:
|
||||
return 'Product({0.fields})'.format(self)
|
||||
|
||||
# A generic visitor for the meta-AST that describes ASDL. This can be used by
|
||||
# emitters. Note that this visitor does not provide a generic visit method, so a
|
||||
# subclass needs to define visit methods from visitModule to as deep as the
|
||||
# interesting node.
|
||||
# We also define a Check visitor that makes sure the parsed ASDL is well-formed.
|
||||
|
||||
class VisitorBase(object):
|
||||
"""Generic tree visitor for ASTs."""
|
||||
def __init__(self):
|
||||
self.cache = {}
|
||||
|
||||
def visit(self, obj, *args):
|
||||
klass = obj.__class__
|
||||
meth = self.cache.get(klass)
|
||||
if meth is None:
|
||||
methname = "visit" + klass.__name__
|
||||
meth = getattr(self, methname, None)
|
||||
self.cache[klass] = meth
|
||||
if meth:
|
||||
try:
|
||||
meth(obj, *args)
|
||||
except Exception as e:
|
||||
print("Error visiting %r: %s" % (obj, e))
|
||||
raise
|
||||
|
||||
class Check(VisitorBase):
|
||||
"""A visitor that checks a parsed ASDL tree for correctness.
|
||||
|
||||
Errors are printed and accumulated.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(Check, self).__init__()
|
||||
self.cons = {}
|
||||
self.errors = 0
|
||||
self.types = {}
|
||||
|
||||
def visitModule(self, mod):
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn)
|
||||
|
||||
def visitType(self, type):
|
||||
self.visit(type.value, str(type.name))
|
||||
|
||||
def visitSum(self, sum, name):
|
||||
for t in sum.types:
|
||||
self.visit(t, name)
|
||||
|
||||
def visitConstructor(self, cons, name):
|
||||
key = str(cons.name)
|
||||
conflict = self.cons.get(key)
|
||||
if conflict is None:
|
||||
self.cons[key] = name
|
||||
else:
|
||||
print('Redefinition of constructor {}'.format(key))
|
||||
print('Defined in {} and {}'.format(conflict, name))
|
||||
self.errors += 1
|
||||
for f in cons.fields:
|
||||
self.visit(f, key)
|
||||
|
||||
def visitField(self, field, name):
|
||||
key = str(field.type)
|
||||
l = self.types.setdefault(key, [])
|
||||
l.append(name)
|
||||
|
||||
def visitProduct(self, prod, name):
|
||||
for f in prod.fields:
|
||||
self.visit(f, name)
|
||||
|
||||
def check(mod):
|
||||
"""Check the parsed ASDL tree for correctness.
|
||||
|
||||
Return True if success. For failure, the errors are printed out and False
|
||||
is returned.
|
||||
"""
|
||||
v = Check()
|
||||
v.visit(mod)
|
||||
|
||||
for t in v.types:
|
||||
if t not in mod.types and not t in builtin_types:
|
||||
v.errors += 1
|
||||
uses = ", ".join(v.types[t])
|
||||
print('Undefined type {}, used in {}'.format(t, uses))
|
||||
return not v.errors
|
||||
|
||||
# The ASDL parser itself comes next. The only interesting external interface
|
||||
# here is the top-level parse function.
|
||||
|
||||
def parse(filename):
|
||||
"""Parse ASDL from the given file and return a Module node describing it."""
|
||||
with open(filename) as f:
|
||||
parser = ASDLParser()
|
||||
return parser.parse(f.read())
|
||||
|
||||
# Types for describing tokens in an ASDL specification.
|
||||
class TokenKind:
|
||||
"""TokenKind is provides a scope for enumerated token kinds."""
|
||||
(ConstructorId, TypeId, Equals, Comma, Question, Pipe, Asterisk,
|
||||
LParen, RParen, LBrace, RBrace) = range(11)
|
||||
|
||||
operator_table = {
|
||||
'=': Equals, ',': Comma, '?': Question, '|': Pipe, '(': LParen,
|
||||
')': RParen, '*': Asterisk, '{': LBrace, '}': RBrace}
|
||||
|
||||
Token = namedtuple('Token', 'kind value lineno')
|
||||
|
||||
class ASDLSyntaxError(Exception):
|
||||
def __init__(self, msg, lineno=None):
|
||||
self.msg = msg
|
||||
self.lineno = lineno or '<unknown>'
|
||||
|
||||
def __str__(self):
|
||||
return 'Syntax error on line {0.lineno}: {0.msg}'.format(self)
|
||||
|
||||
def tokenize_asdl(buf):
|
||||
"""Tokenize the given buffer. Yield Token objects."""
|
||||
for lineno, line in enumerate(buf.splitlines(), 1):
|
||||
for m in re.finditer(r'\s*(\w+|--.*|.)', line.strip()):
|
||||
c = m.group(1)
|
||||
if c[0].isalpha():
|
||||
# Some kind of identifier
|
||||
if c[0].isupper():
|
||||
yield Token(TokenKind.ConstructorId, c, lineno)
|
||||
else:
|
||||
yield Token(TokenKind.TypeId, c, lineno)
|
||||
elif c[:2] == '--':
|
||||
# Comment
|
||||
break
|
||||
else:
|
||||
# Operators
|
||||
try:
|
||||
op_kind = TokenKind.operator_table[c]
|
||||
except KeyError:
|
||||
raise ASDLSyntaxError('Invalid operator %s' % c, lineno)
|
||||
yield Token(op_kind, c, lineno)
|
||||
|
||||
class ASDLParser:
|
||||
"""Parser for ASDL files.
|
||||
|
||||
Create, then call the parse method on a buffer containing ASDL.
|
||||
This is a simple recursive descent parser that uses tokenize_asdl for the
|
||||
lexing.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._tokenizer = None
|
||||
self.cur_token = None
|
||||
|
||||
def parse(self, buf):
|
||||
"""Parse the ASDL in the buffer and return an AST with a Module root.
|
||||
"""
|
||||
self._tokenizer = tokenize_asdl(buf)
|
||||
self._advance()
|
||||
return self._parse_module()
|
||||
|
||||
def _parse_module(self):
|
||||
if self._at_keyword('module'):
|
||||
self._advance()
|
||||
else:
|
||||
raise ASDLSyntaxError(
|
||||
'Expected "module" (found {})'.format(self.cur_token.value),
|
||||
self.cur_token.lineno)
|
||||
name = self._match(self._id_kinds)
|
||||
self._match(TokenKind.LBrace)
|
||||
defs = self._parse_definitions()
|
||||
self._match(TokenKind.RBrace)
|
||||
return Module(name, defs)
|
||||
|
||||
def _parse_definitions(self):
|
||||
defs = []
|
||||
while self.cur_token.kind == TokenKind.TypeId:
|
||||
typename = self._advance()
|
||||
self._match(TokenKind.Equals)
|
||||
type = self._parse_type()
|
||||
defs.append(Type(typename, type))
|
||||
return defs
|
||||
|
||||
def _parse_type(self):
|
||||
if self.cur_token.kind == TokenKind.LParen:
|
||||
# If we see a (, it's a product
|
||||
return self._parse_product()
|
||||
else:
|
||||
# Otherwise it's a sum. Look for ConstructorId
|
||||
sumlist = [Constructor(self._match(TokenKind.ConstructorId),
|
||||
self._parse_optional_fields())]
|
||||
while self.cur_token.kind == TokenKind.Pipe:
|
||||
# More constructors
|
||||
self._advance()
|
||||
sumlist.append(Constructor(
|
||||
self._match(TokenKind.ConstructorId),
|
||||
self._parse_optional_fields()))
|
||||
return Sum(sumlist, self._parse_optional_attributes())
|
||||
|
||||
def _parse_product(self):
|
||||
return Product(self._parse_fields(), self._parse_optional_attributes())
|
||||
|
||||
def _parse_fields(self):
|
||||
fields = []
|
||||
self._match(TokenKind.LParen)
|
||||
while self.cur_token.kind == TokenKind.TypeId:
|
||||
typename = self._advance()
|
||||
is_seq, is_opt = self._parse_optional_field_quantifier()
|
||||
id = (self._advance() if self.cur_token.kind in self._id_kinds
|
||||
else None)
|
||||
fields.append(Field(typename, id, seq=is_seq, opt=is_opt))
|
||||
if self.cur_token.kind == TokenKind.RParen:
|
||||
break
|
||||
elif self.cur_token.kind == TokenKind.Comma:
|
||||
self._advance()
|
||||
self._match(TokenKind.RParen)
|
||||
return fields
|
||||
|
||||
def _parse_optional_fields(self):
|
||||
if self.cur_token.kind == TokenKind.LParen:
|
||||
return self._parse_fields()
|
||||
else:
|
||||
return None
|
||||
|
||||
def _parse_optional_attributes(self):
|
||||
if self._at_keyword('attributes'):
|
||||
self._advance()
|
||||
return self._parse_fields()
|
||||
else:
|
||||
return None
|
||||
|
||||
def _parse_optional_field_quantifier(self):
|
||||
is_seq, is_opt = False, False
|
||||
if self.cur_token.kind == TokenKind.Question:
|
||||
is_opt = True
|
||||
self._advance()
|
||||
if self.cur_token.kind == TokenKind.Asterisk:
|
||||
is_seq = True
|
||||
self._advance()
|
||||
return is_seq, is_opt
|
||||
|
||||
def _advance(self):
|
||||
""" Return the value of the current token and read the next one into
|
||||
self.cur_token.
|
||||
"""
|
||||
cur_val = None if self.cur_token is None else self.cur_token.value
|
||||
try:
|
||||
self.cur_token = next(self._tokenizer)
|
||||
except StopIteration:
|
||||
self.cur_token = None
|
||||
return cur_val
|
||||
|
||||
_id_kinds = (TokenKind.ConstructorId, TokenKind.TypeId)
|
||||
|
||||
def _match(self, kind):
|
||||
"""The 'match' primitive of RD parsers.
|
||||
|
||||
* Verifies that the current token is of the given kind (kind can
|
||||
be a tuple, in which the kind must match one of its members).
|
||||
* Returns the value of the current token
|
||||
* Reads in the next token
|
||||
"""
|
||||
if (isinstance(kind, tuple) and self.cur_token.kind in kind or
|
||||
self.cur_token.kind == kind
|
||||
):
|
||||
value = self.cur_token.value
|
||||
self._advance()
|
||||
return value
|
||||
else:
|
||||
raise ASDLSyntaxError(
|
||||
'Unmatched {} (found {})'.format(kind, self.cur_token.kind),
|
||||
self.cur_token.lineno)
|
||||
|
||||
def _at_keyword(self, keyword):
|
||||
return (self.cur_token.kind == TokenKind.TypeId and
|
||||
self.cur_token.value == keyword)
|
609
nac3ast/asdl_rs.py
Executable file
609
nac3ast/asdl_rs.py
Executable file
@ -0,0 +1,609 @@
|
||||
#! /usr/bin/env python
|
||||
"""Generate Rust code from an ASDL description."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import json
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from pathlib import Path
|
||||
|
||||
import asdl
|
||||
|
||||
TABSIZE = 4
|
||||
AUTOGEN_MESSAGE = "// File automatically generated by {}.\n\n"
|
||||
|
||||
builtin_type_mapping = {
|
||||
'identifier': 'Ident',
|
||||
'string': 'String',
|
||||
'int': 'usize',
|
||||
'constant': 'Constant',
|
||||
'bool': 'bool',
|
||||
'conversion_flag': 'ConversionFlag',
|
||||
}
|
||||
assert builtin_type_mapping.keys() == asdl.builtin_types
|
||||
|
||||
def get_rust_type(name):
|
||||
"""Return a string for the C name of the type.
|
||||
|
||||
This function special cases the default types provided by asdl.
|
||||
"""
|
||||
if name in asdl.builtin_types:
|
||||
return builtin_type_mapping[name]
|
||||
else:
|
||||
return "".join(part.capitalize() for part in name.split("_"))
|
||||
|
||||
def is_simple(sum):
|
||||
"""Return True if a sum is a simple.
|
||||
|
||||
A sum is simple if its types have no fields, e.g.
|
||||
unaryop = Invert | Not | UAdd | USub
|
||||
"""
|
||||
for t in sum.types:
|
||||
if t.fields:
|
||||
return False
|
||||
return True
|
||||
|
||||
def asdl_of(name, obj):
|
||||
if isinstance(obj, asdl.Product) or isinstance(obj, asdl.Constructor):
|
||||
fields = ", ".join(map(str, obj.fields))
|
||||
if fields:
|
||||
fields = "({})".format(fields)
|
||||
return "{}{}".format(name, fields)
|
||||
else:
|
||||
if is_simple(obj):
|
||||
types = " | ".join(type.name for type in obj.types)
|
||||
else:
|
||||
sep = "\n{}| ".format(" " * (len(name) + 1))
|
||||
types = sep.join(
|
||||
asdl_of(type.name, type) for type in obj.types
|
||||
)
|
||||
return "{} = {}".format(name, types)
|
||||
|
||||
class EmitVisitor(asdl.VisitorBase):
|
||||
"""Visit that emits lines"""
|
||||
|
||||
def __init__(self, file):
|
||||
self.file = file
|
||||
self.identifiers = set()
|
||||
super(EmitVisitor, self).__init__()
|
||||
|
||||
def emit_identifier(self, name):
|
||||
name = str(name)
|
||||
if name in self.identifiers:
|
||||
return
|
||||
self.emit("_Py_IDENTIFIER(%s);" % name, 0)
|
||||
self.identifiers.add(name)
|
||||
|
||||
def emit(self, line, depth):
|
||||
if line:
|
||||
line = (" " * TABSIZE * depth) + line
|
||||
self.file.write(line + "\n")
|
||||
|
||||
class TypeInfo:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.has_userdata = None
|
||||
self.children = set()
|
||||
self.boxed = False
|
||||
|
||||
def __repr__(self):
|
||||
return f"<TypeInfo: {self.name}>"
|
||||
|
||||
def determine_userdata(self, typeinfo, stack):
|
||||
if self.name in stack:
|
||||
return None
|
||||
stack.add(self.name)
|
||||
for child, child_seq in self.children:
|
||||
if child in asdl.builtin_types:
|
||||
continue
|
||||
childinfo = typeinfo[child]
|
||||
child_has_userdata = childinfo.determine_userdata(typeinfo, stack)
|
||||
if self.has_userdata is None and child_has_userdata is True:
|
||||
self.has_userdata = True
|
||||
|
||||
stack.remove(self.name)
|
||||
return self.has_userdata
|
||||
|
||||
class FindUserdataTypesVisitor(asdl.VisitorBase):
|
||||
def __init__(self, typeinfo):
|
||||
self.typeinfo = typeinfo
|
||||
super().__init__()
|
||||
|
||||
def visitModule(self, mod):
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn)
|
||||
stack = set()
|
||||
for info in self.typeinfo.values():
|
||||
info.determine_userdata(self.typeinfo, stack)
|
||||
|
||||
def visitType(self, type):
|
||||
self.typeinfo[type.name] = TypeInfo(type.name)
|
||||
self.visit(type.value, type.name)
|
||||
|
||||
def visitSum(self, sum, name):
|
||||
info = self.typeinfo[name]
|
||||
if is_simple(sum):
|
||||
info.has_userdata = False
|
||||
else:
|
||||
if len(sum.types) > 1:
|
||||
info.boxed = True
|
||||
if sum.attributes:
|
||||
# attributes means Located, which has the `custom: U` field
|
||||
info.has_userdata = True
|
||||
for variant in sum.types:
|
||||
self.add_children(name, variant.fields)
|
||||
|
||||
def visitProduct(self, product, name):
|
||||
info = self.typeinfo[name]
|
||||
if product.attributes:
|
||||
# attributes means Located, which has the `custom: U` field
|
||||
info.has_userdata = True
|
||||
if len(product.fields) > 2:
|
||||
info.boxed = True
|
||||
self.add_children(name, product.fields)
|
||||
|
||||
def add_children(self, name, fields):
|
||||
self.typeinfo[name].children.update((field.type, field.seq) for field in fields)
|
||||
|
||||
def rust_field(field_name):
|
||||
if field_name == 'type':
|
||||
return 'type_'
|
||||
else:
|
||||
return field_name
|
||||
|
||||
class TypeInfoEmitVisitor(EmitVisitor):
|
||||
def __init__(self, file, typeinfo):
|
||||
self.typeinfo = typeinfo
|
||||
super().__init__(file)
|
||||
|
||||
def has_userdata(self, typ):
|
||||
return self.typeinfo[typ].has_userdata
|
||||
|
||||
def get_generics(self, typ, *generics):
|
||||
if self.has_userdata(typ):
|
||||
return [f"<{g}>" for g in generics]
|
||||
else:
|
||||
return ["" for g in generics]
|
||||
|
||||
class StructVisitor(TypeInfoEmitVisitor):
|
||||
"""Visitor to generate typedefs for AST."""
|
||||
|
||||
def visitModule(self, mod):
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn)
|
||||
|
||||
def visitType(self, type, depth=0):
|
||||
self.visit(type.value, type.name, depth)
|
||||
|
||||
def visitSum(self, sum, name, depth):
|
||||
if is_simple(sum):
|
||||
self.simple_sum(sum, name, depth)
|
||||
else:
|
||||
self.sum_with_constructors(sum, name, depth)
|
||||
|
||||
def emit_attrs(self, depth):
|
||||
self.emit("#[derive(Clone, Debug, PartialEq)]", depth)
|
||||
|
||||
def simple_sum(self, sum, name, depth):
|
||||
rustname = get_rust_type(name)
|
||||
self.emit_attrs(depth)
|
||||
self.emit(f"pub enum {rustname} {{", depth)
|
||||
for variant in sum.types:
|
||||
self.emit(f"{variant.name},", depth + 1)
|
||||
self.emit("}", depth)
|
||||
self.emit("", depth)
|
||||
|
||||
def sum_with_constructors(self, sum, name, depth):
|
||||
typeinfo = self.typeinfo[name]
|
||||
generics, generics_applied = self.get_generics(name, "U = ()", "U")
|
||||
enumname = rustname = get_rust_type(name)
|
||||
# all the attributes right now are for location, so if it has attrs we
|
||||
# can just wrap it in Located<>
|
||||
if sum.attributes:
|
||||
enumname = rustname + "Kind"
|
||||
self.emit_attrs(depth)
|
||||
self.emit(f"pub enum {enumname}{generics} {{", depth)
|
||||
for t in sum.types:
|
||||
self.visit(t, typeinfo, depth + 1)
|
||||
self.emit("}", depth)
|
||||
if sum.attributes:
|
||||
self.emit(f"pub type {rustname}<U = ()> = Located<{enumname}{generics_applied}, U>;", depth)
|
||||
self.emit("", depth)
|
||||
|
||||
def visitConstructor(self, cons, parent, depth):
|
||||
if cons.fields:
|
||||
self.emit(f"{cons.name} {{", depth)
|
||||
for f in cons.fields:
|
||||
self.visit(f, parent, "", depth + 1)
|
||||
self.emit("},", depth)
|
||||
else:
|
||||
self.emit(f"{cons.name},", depth)
|
||||
|
||||
def visitField(self, field, parent, vis, depth):
|
||||
typ = get_rust_type(field.type)
|
||||
fieldtype = self.typeinfo.get(field.type)
|
||||
if fieldtype and fieldtype.has_userdata:
|
||||
typ = f"{typ}<U>"
|
||||
# don't box if we're doing Vec<T>, but do box if we're doing Vec<Option<Box<T>>>
|
||||
if fieldtype and fieldtype.boxed and (not field.seq or field.opt):
|
||||
typ = f"Box<{typ}>"
|
||||
if field.opt:
|
||||
typ = f"Option<{typ}>"
|
||||
if field.seq:
|
||||
typ = f"Vec<{typ}>"
|
||||
name = rust_field(field.name)
|
||||
self.emit(f"{vis}{name}: {typ},", depth)
|
||||
|
||||
def visitProduct(self, product, name, depth):
|
||||
typeinfo = self.typeinfo[name]
|
||||
generics, generics_applied = self.get_generics(name, "U = ()", "U")
|
||||
dataname = rustname = get_rust_type(name)
|
||||
if product.attributes:
|
||||
dataname = rustname + "Data"
|
||||
self.emit_attrs(depth)
|
||||
self.emit(f"pub struct {dataname}{generics} {{", depth)
|
||||
for f in product.fields:
|
||||
self.visit(f, typeinfo, "pub ", depth + 1)
|
||||
self.emit("}", depth)
|
||||
if product.attributes:
|
||||
# attributes should just be location info
|
||||
self.emit(f"pub type {rustname}<U = ()> = Located<{dataname}{generics_applied}, U>;", depth);
|
||||
self.emit("", depth)
|
||||
|
||||
|
||||
class FoldTraitDefVisitor(TypeInfoEmitVisitor):
|
||||
def visitModule(self, mod, depth):
|
||||
self.emit("pub trait Fold<U> {", depth)
|
||||
self.emit("type TargetU;", depth + 1)
|
||||
self.emit("type Error;", depth + 1)
|
||||
self.emit("fn map_user(&mut self, user: U) -> Result<Self::TargetU, Self::Error>;", depth + 2)
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn, depth + 2)
|
||||
self.emit("}", depth)
|
||||
|
||||
def visitType(self, type, depth):
|
||||
name = type.name
|
||||
apply_u, apply_target_u = self.get_generics(name, "U", "Self::TargetU")
|
||||
enumname = get_rust_type(name)
|
||||
self.emit(f"fn fold_{name}(&mut self, node: {enumname}{apply_u}) -> Result<{enumname}{apply_target_u}, Self::Error> {{", depth)
|
||||
self.emit(f"fold_{name}(self, node)", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
|
||||
class FoldImplVisitor(TypeInfoEmitVisitor):
|
||||
def visitModule(self, mod, depth):
|
||||
self.emit("fn fold_located<U, F: Fold<U> + ?Sized, T, MT>(folder: &mut F, node: Located<T, U>, f: impl FnOnce(&mut F, T) -> Result<MT, F::Error>) -> Result<Located<MT, F::TargetU>, F::Error> {", depth)
|
||||
self.emit("Ok(Located { custom: folder.map_user(node.custom)?, location: node.location, node: f(folder, node.node)? })", depth + 1)
|
||||
self.emit("}", depth)
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn, depth)
|
||||
|
||||
def visitType(self, type, depth=0):
|
||||
self.visit(type.value, type.name, depth)
|
||||
|
||||
def visitSum(self, sum, name, depth):
|
||||
apply_t, apply_u, apply_target_u = self.get_generics(name, "T", "U", "F::TargetU")
|
||||
enumname = get_rust_type(name)
|
||||
is_located = bool(sum.attributes)
|
||||
|
||||
self.emit(f"impl<T, U> Foldable<T, U> for {enumname}{apply_t} {{", depth)
|
||||
self.emit(f"type Mapped = {enumname}{apply_u};", depth + 1)
|
||||
self.emit("fn fold<F: Fold<T, TargetU = U> + ?Sized>(self, folder: &mut F) -> Result<Self::Mapped, F::Error> {", depth + 1)
|
||||
self.emit(f"folder.fold_{name}(self)", depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
self.emit(f"pub fn fold_{name}<U, F: Fold<U> + ?Sized>(#[allow(unused)] folder: &mut F, node: {enumname}{apply_u}) -> Result<{enumname}{apply_target_u}, F::Error> {{", depth)
|
||||
if is_located:
|
||||
self.emit("fold_located(folder, node, |folder, node| {", depth)
|
||||
enumname += "Kind"
|
||||
self.emit("match node {", depth + 1)
|
||||
for cons in sum.types:
|
||||
fields_pattern = self.make_pattern(cons.fields)
|
||||
self.emit(f"{enumname}::{cons.name} {{ {fields_pattern} }} => {{", depth + 2)
|
||||
self.gen_construction(f"{enumname}::{cons.name}", cons.fields, depth + 3)
|
||||
self.emit("}", depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
if is_located:
|
||||
self.emit("})", depth)
|
||||
self.emit("}", depth)
|
||||
|
||||
|
||||
def visitProduct(self, product, name, depth):
|
||||
apply_t, apply_u, apply_target_u = self.get_generics(name, "T", "U", "F::TargetU")
|
||||
structname = get_rust_type(name)
|
||||
is_located = bool(product.attributes)
|
||||
|
||||
self.emit(f"impl<T, U> Foldable<T, U> for {structname}{apply_t} {{", depth)
|
||||
self.emit(f"type Mapped = {structname}{apply_u};", depth + 1)
|
||||
self.emit("fn fold<F: Fold<T, TargetU = U> + ?Sized>(self, folder: &mut F) -> Result<Self::Mapped, F::Error> {", depth + 1)
|
||||
self.emit(f"folder.fold_{name}(self)", depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
self.emit(f"pub fn fold_{name}<U, F: Fold<U> + ?Sized>(#[allow(unused)] folder: &mut F, node: {structname}{apply_u}) -> Result<{structname}{apply_target_u}, F::Error> {{", depth)
|
||||
if is_located:
|
||||
self.emit("fold_located(folder, node, |folder, node| {", depth)
|
||||
structname += "Data"
|
||||
fields_pattern = self.make_pattern(product.fields)
|
||||
self.emit(f"let {structname} {{ {fields_pattern} }} = node;", depth + 1)
|
||||
self.gen_construction(structname, product.fields, depth + 1)
|
||||
if is_located:
|
||||
self.emit("})", depth)
|
||||
self.emit("}", depth)
|
||||
|
||||
def make_pattern(self, fields):
|
||||
return ",".join(rust_field(f.name) for f in fields)
|
||||
|
||||
def gen_construction(self, cons_path, fields, depth):
|
||||
self.emit(f"Ok({cons_path} {{", depth)
|
||||
for field in fields:
|
||||
name = rust_field(field.name)
|
||||
self.emit(f"{name}: Foldable::fold({name}, folder)?,", depth + 1)
|
||||
self.emit("})", depth)
|
||||
|
||||
|
||||
class FoldModuleVisitor(TypeInfoEmitVisitor):
|
||||
def visitModule(self, mod):
|
||||
depth = 0
|
||||
self.emit('#[cfg(feature = "fold")]', depth)
|
||||
self.emit("pub mod fold {", depth)
|
||||
self.emit("use super::*;", depth + 1)
|
||||
self.emit("use crate::fold_helpers::Foldable;", depth + 1)
|
||||
FoldTraitDefVisitor(self.file, self.typeinfo).visit(mod, depth + 1)
|
||||
FoldImplVisitor(self.file, self.typeinfo).visit(mod, depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
|
||||
class ClassDefVisitor(EmitVisitor):
|
||||
|
||||
def visitModule(self, mod):
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn)
|
||||
|
||||
def visitType(self, type, depth=0):
|
||||
self.visit(type.value, type.name, depth)
|
||||
|
||||
def visitSum(self, sum, name, depth):
|
||||
for cons in sum.types:
|
||||
self.visit(cons, sum.attributes, depth)
|
||||
|
||||
def visitConstructor(self, cons, attrs, depth):
|
||||
self.gen_classdef(cons.name, cons.fields, attrs, depth)
|
||||
|
||||
def visitProduct(self, product, name, depth):
|
||||
self.gen_classdef(name, product.fields, product.attributes, depth)
|
||||
|
||||
def gen_classdef(self, name, fields, attrs, depth):
|
||||
structname = "Node" + name
|
||||
self.emit(f'#[pyclass(module = "_ast", name = {json.dumps(name)}, base = "AstNode")]', depth)
|
||||
self.emit(f"struct {structname};", depth)
|
||||
self.emit("#[pyimpl(flags(HAS_DICT, BASETYPE))]", depth)
|
||||
self.emit(f"impl {structname} {{", depth)
|
||||
self.emit(f"#[extend_class]", depth + 1)
|
||||
self.emit("fn extend_class_with_fields(ctx: &PyContext, class: &PyTypeRef) {", depth + 1)
|
||||
fields = ",".join(f"ctx.new_str({json.dumps(f.name)})" for f in fields)
|
||||
self.emit(f'class.set_str_attr("_fields", ctx.new_list(vec![{fields}]));', depth + 2)
|
||||
attrs = ",".join(f"ctx.new_str({json.dumps(attr.name)})" for attr in attrs)
|
||||
self.emit(f'class.set_str_attr("_attributes", ctx.new_list(vec![{attrs}]));', depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
class ExtendModuleVisitor(EmitVisitor):
|
||||
|
||||
def visitModule(self, mod):
|
||||
depth = 0
|
||||
self.emit("pub fn extend_module_nodes(vm: &VirtualMachine, module: &PyObjectRef) {", depth)
|
||||
self.emit("extend_module!(vm, module, {", depth + 1)
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn, depth + 2)
|
||||
self.emit("})", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
def visitType(self, type, depth):
|
||||
self.visit(type.value, type.name, depth)
|
||||
|
||||
def visitSum(self, sum, name, depth):
|
||||
for cons in sum.types:
|
||||
self.visit(cons, depth)
|
||||
|
||||
def visitConstructor(self, cons, depth):
|
||||
self.gen_extension(cons.name, depth)
|
||||
|
||||
def visitProduct(self, product, name, depth):
|
||||
self.gen_extension(name, depth)
|
||||
|
||||
def gen_extension(self, name, depth):
|
||||
self.emit(f"{json.dumps(name)} => Node{name}::make_class(&vm.ctx),", depth)
|
||||
|
||||
|
||||
class TraitImplVisitor(EmitVisitor):
|
||||
|
||||
def visitModule(self, mod):
|
||||
for dfn in mod.dfns:
|
||||
self.visit(dfn)
|
||||
|
||||
def visitType(self, type, depth=0):
|
||||
self.visit(type.value, type.name, depth)
|
||||
|
||||
def visitSum(self, sum, name, depth):
|
||||
enumname = get_rust_type(name)
|
||||
if sum.attributes:
|
||||
enumname += "Kind"
|
||||
|
||||
|
||||
self.emit(f"impl NamedNode for ast::{enumname} {{", depth)
|
||||
self.emit(f"const NAME: &'static str = {json.dumps(name)};", depth + 1)
|
||||
self.emit("}", depth)
|
||||
self.emit(f"impl Node for ast::{enumname} {{", depth)
|
||||
self.emit("fn ast_to_object(self, _vm: &VirtualMachine) -> PyObjectRef {", depth + 1)
|
||||
self.emit("match self {", depth + 2)
|
||||
for variant in sum.types:
|
||||
self.constructor_to_object(variant, enumname, depth + 3)
|
||||
self.emit("}", depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
self.emit("fn ast_from_object(_vm: &VirtualMachine, _object: PyObjectRef) -> PyResult<Self> {", depth + 1)
|
||||
self.gen_sum_fromobj(sum, name, enumname, depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
def constructor_to_object(self, cons, enumname, depth):
|
||||
fields_pattern = self.make_pattern(cons.fields)
|
||||
self.emit(f"ast::{enumname}::{cons.name} {{ {fields_pattern} }} => {{", depth)
|
||||
self.make_node(cons.name, cons.fields, depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
def visitProduct(self, product, name, depth):
|
||||
structname = get_rust_type(name)
|
||||
if product.attributes:
|
||||
structname += "Data"
|
||||
|
||||
self.emit(f"impl NamedNode for ast::{structname} {{", depth)
|
||||
self.emit(f"const NAME: &'static str = {json.dumps(name)};", depth + 1)
|
||||
self.emit("}", depth)
|
||||
self.emit(f"impl Node for ast::{structname} {{", depth)
|
||||
self.emit("fn ast_to_object(self, _vm: &VirtualMachine) -> PyObjectRef {", depth + 1)
|
||||
fields_pattern = self.make_pattern(product.fields)
|
||||
self.emit(f"let ast::{structname} {{ {fields_pattern} }} = self;", depth + 2)
|
||||
self.make_node(name, product.fields, depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
self.emit("fn ast_from_object(_vm: &VirtualMachine, _object: PyObjectRef) -> PyResult<Self> {", depth + 1)
|
||||
self.gen_product_fromobj(product, name, structname, depth + 2)
|
||||
self.emit("}", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
def make_node(self, variant, fields, depth):
|
||||
lines = []
|
||||
self.emit(f"let _node = AstNode.into_ref_with_type(_vm, Node{variant}::static_type().clone()).unwrap();", depth)
|
||||
if fields:
|
||||
self.emit("let _dict = _node.as_object().dict().unwrap();", depth)
|
||||
for f in fields:
|
||||
self.emit(f"_dict.set_item({json.dumps(f.name)}, {rust_field(f.name)}.ast_to_object(_vm), _vm).unwrap();", depth)
|
||||
self.emit("_node.into_object()", depth)
|
||||
|
||||
def make_pattern(self, fields):
|
||||
return ",".join(rust_field(f.name) for f in fields)
|
||||
|
||||
def gen_sum_fromobj(self, sum, sumname, enumname, depth):
|
||||
if sum.attributes:
|
||||
self.extract_location(sumname, depth)
|
||||
|
||||
self.emit("let _cls = _object.class();", depth)
|
||||
self.emit("Ok(", depth)
|
||||
for cons in sum.types:
|
||||
self.emit(f"if _cls.is(Node{cons.name}::static_type()) {{", depth)
|
||||
self.gen_construction(f"{enumname}::{cons.name}", cons, sumname, depth + 1)
|
||||
self.emit("} else", depth)
|
||||
|
||||
self.emit("{", depth)
|
||||
msg = f'format!("expected some sort of {sumname}, but got {{}}",_vm.to_repr(&_object)?)'
|
||||
self.emit(f"return Err(_vm.new_type_error({msg}));", depth + 1)
|
||||
self.emit("})", depth)
|
||||
|
||||
def gen_product_fromobj(self, product, prodname, structname, depth):
|
||||
if product.attributes:
|
||||
self.extract_location(prodname, depth)
|
||||
|
||||
self.emit("Ok(", depth)
|
||||
self.gen_construction(structname, product, prodname, depth + 1)
|
||||
self.emit(")", depth)
|
||||
|
||||
def gen_construction(self, cons_path, cons, name, depth):
|
||||
self.emit(f"ast::{cons_path} {{", depth)
|
||||
for field in cons.fields:
|
||||
self.emit(f"{rust_field(field.name)}: {self.decode_field(field, name)},", depth + 1)
|
||||
self.emit("}", depth)
|
||||
|
||||
def extract_location(self, typename, depth):
|
||||
row = self.decode_field(asdl.Field('int', 'lineno'), typename)
|
||||
column = self.decode_field(asdl.Field('int', 'col_offset'), typename)
|
||||
self.emit(f"let _location = ast::Location::new({row}, {column});", depth)
|
||||
|
||||
def wrap_located_node(self, depth):
|
||||
self.emit(f"let node = ast::Located::new(_location, node);", depth)
|
||||
|
||||
def decode_field(self, field, typename):
|
||||
name = json.dumps(field.name)
|
||||
if field.opt and not field.seq:
|
||||
return f"get_node_field_opt(_vm, &_object, {name})?.map(|obj| Node::ast_from_object(_vm, obj)).transpose()?"
|
||||
else:
|
||||
return f"Node::ast_from_object(_vm, get_node_field(_vm, &_object, {name}, {json.dumps(typename)})?)?"
|
||||
|
||||
class ChainOfVisitors:
|
||||
def __init__(self, *visitors):
|
||||
self.visitors = visitors
|
||||
|
||||
def visit(self, object):
|
||||
for v in self.visitors:
|
||||
v.visit(object)
|
||||
v.emit("", 0)
|
||||
|
||||
|
||||
def write_ast_def(mod, typeinfo, f):
|
||||
f.write('pub use crate::location::Location;\n')
|
||||
f.write('pub use crate::constant::*;\n')
|
||||
f.write('\n')
|
||||
f.write('type Ident = String;\n')
|
||||
f.write('\n')
|
||||
StructVisitor(f, typeinfo).emit_attrs(0)
|
||||
f.write('pub struct Located<T, U = ()> {\n')
|
||||
f.write(' pub location: Location,\n')
|
||||
f.write(' pub custom: U,\n')
|
||||
f.write(' pub node: T,\n')
|
||||
f.write('}\n')
|
||||
f.write('\n')
|
||||
f.write('impl<T> Located<T> {\n')
|
||||
f.write(' pub fn new(location: Location, node: T) -> Self {\n')
|
||||
f.write(' Self { location, custom: (), node }\n')
|
||||
f.write(' }\n')
|
||||
f.write('}\n')
|
||||
f.write('\n')
|
||||
|
||||
c = ChainOfVisitors(StructVisitor(f, typeinfo),
|
||||
FoldModuleVisitor(f, typeinfo))
|
||||
c.visit(mod)
|
||||
|
||||
|
||||
def write_ast_mod(mod, f):
|
||||
f.write('use super::*;\n')
|
||||
f.write('\n')
|
||||
|
||||
c = ChainOfVisitors(ClassDefVisitor(f),
|
||||
TraitImplVisitor(f),
|
||||
ExtendModuleVisitor(f))
|
||||
c.visit(mod)
|
||||
|
||||
def main(input_filename, ast_mod_filename, ast_def_filename, dump_module=False):
|
||||
auto_gen_msg = AUTOGEN_MESSAGE.format("/".join(Path(__file__).parts[-2:]))
|
||||
mod = asdl.parse(input_filename)
|
||||
if dump_module:
|
||||
print('Parsed Module:')
|
||||
print(mod)
|
||||
if not asdl.check(mod):
|
||||
sys.exit(1)
|
||||
|
||||
typeinfo = {}
|
||||
FindUserdataTypesVisitor(typeinfo).visit(mod)
|
||||
|
||||
with ast_def_filename.open("w") as def_file, \
|
||||
ast_mod_filename.open("w") as mod_file:
|
||||
def_file.write(auto_gen_msg)
|
||||
write_ast_def(mod, typeinfo, def_file)
|
||||
|
||||
mod_file.write(auto_gen_msg)
|
||||
write_ast_mod(mod, mod_file)
|
||||
|
||||
print(f"{ast_def_filename}, {ast_mod_filename} regenerated.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("input_file", type=Path)
|
||||
parser.add_argument("-M", "--mod-file", type=Path, required=True)
|
||||
parser.add_argument("-D", "--def-file", type=Path, required=True)
|
||||
parser.add_argument("-d", "--dump-module", action="store_true")
|
||||
|
||||
args = parser.parse_args()
|
||||
main(args.input_file, args.mod_file, args.def_file, args.dump_module)
|
1265
nac3ast/src/ast_gen.rs
Normal file
1265
nac3ast/src/ast_gen.rs
Normal file
File diff suppressed because it is too large
Load Diff
213
nac3ast/src/constant.rs
Normal file
213
nac3ast/src/constant.rs
Normal file
@ -0,0 +1,213 @@
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Constant {
|
||||
None,
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
Bytes(Vec<u8>),
|
||||
Int(i128),
|
||||
Tuple(Vec<Constant>),
|
||||
Float(f64),
|
||||
Complex { real: f64, imag: f64 },
|
||||
Ellipsis,
|
||||
}
|
||||
|
||||
impl From<String> for Constant {
|
||||
fn from(s: String) -> Constant {
|
||||
Self::Str(s)
|
||||
}
|
||||
}
|
||||
impl From<Vec<u8>> for Constant {
|
||||
fn from(b: Vec<u8>) -> Constant {
|
||||
Self::Bytes(b)
|
||||
}
|
||||
}
|
||||
impl From<bool> for Constant {
|
||||
fn from(b: bool) -> Constant {
|
||||
Self::Bool(b)
|
||||
}
|
||||
}
|
||||
impl From<i32> for Constant {
|
||||
fn from(i: i32) -> Constant {
|
||||
Self::Int(i as i128)
|
||||
}
|
||||
}
|
||||
impl From<i64> for Constant {
|
||||
fn from(i: i64) -> Constant {
|
||||
Self::Int(i as i128)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms a value prior to formatting it.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum ConversionFlag {
|
||||
/// Converts by calling `str(<value>)`.
|
||||
Str = b's',
|
||||
/// Converts by calling `ascii(<value>)`.
|
||||
Ascii = b'a',
|
||||
/// Converts by calling `repr(<value>)`.
|
||||
Repr = b'r',
|
||||
}
|
||||
|
||||
impl ConversionFlag {
|
||||
pub fn try_from_byte(b: u8) -> Option<Self> {
|
||||
match b {
|
||||
b's' => Some(Self::Str),
|
||||
b'a' => Some(Self::Ascii),
|
||||
b'r' => Some(Self::Repr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "constant-optimization")]
|
||||
#[derive(Default)]
|
||||
pub struct ConstantOptimizer {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "constant-optimization")]
|
||||
impl ConstantOptimizer {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self { _priv: () }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "constant-optimization")]
|
||||
impl<U> crate::fold::Fold<U> for ConstantOptimizer {
|
||||
type TargetU = U;
|
||||
type Error = std::convert::Infallible;
|
||||
#[inline]
|
||||
fn map_user(&mut self, user: U) -> Result<Self::TargetU, Self::Error> {
|
||||
Ok(user)
|
||||
}
|
||||
fn fold_expr(&mut self, node: crate::Expr<U>) -> Result<crate::Expr<U>, Self::Error> {
|
||||
match node.node {
|
||||
crate::ExprKind::Tuple { elts, ctx } => {
|
||||
let elts = elts
|
||||
.into_iter()
|
||||
.map(|x| self.fold_expr(x))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let expr = if elts
|
||||
.iter()
|
||||
.all(|e| matches!(e.node, crate::ExprKind::Constant { .. }))
|
||||
{
|
||||
let tuple = elts
|
||||
.into_iter()
|
||||
.map(|e| match e.node {
|
||||
crate::ExprKind::Constant { value, .. } => value,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
crate::ExprKind::Constant {
|
||||
value: Constant::Tuple(tuple),
|
||||
kind: None,
|
||||
}
|
||||
} else {
|
||||
crate::ExprKind::Tuple { elts, ctx }
|
||||
};
|
||||
Ok(crate::Expr {
|
||||
node: expr,
|
||||
custom: node.custom,
|
||||
location: node.location,
|
||||
})
|
||||
}
|
||||
_ => crate::fold::fold_expr(self, node),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(feature = "constant-optimization")]
|
||||
#[test]
|
||||
fn test_constant_opt() {
|
||||
use super::*;
|
||||
use crate::fold::Fold;
|
||||
use crate::*;
|
||||
|
||||
let location = Location::new(0, 0, Default::default());
|
||||
let custom = ();
|
||||
let ast = Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Tuple {
|
||||
ctx: ExprContext::Load,
|
||||
elts: vec![
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 1.into(),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 2.into(),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Tuple {
|
||||
ctx: ExprContext::Load,
|
||||
elts: vec![
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 3.into(),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 4.into(),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 5.into(),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
let new_ast = ConstantOptimizer::new()
|
||||
.fold_expr(ast)
|
||||
.unwrap_or_else(|e| match e {});
|
||||
assert_eq!(
|
||||
new_ast,
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: Constant::Tuple(vec![
|
||||
1.into(),
|
||||
2.into(),
|
||||
Constant::Tuple(vec![
|
||||
3.into(),
|
||||
4.into(),
|
||||
5.into(),
|
||||
])
|
||||
]),
|
||||
kind: None
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
74
nac3ast/src/fold_helpers.rs
Normal file
74
nac3ast/src/fold_helpers.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use crate::constant;
|
||||
use crate::fold::Fold;
|
||||
use crate::StrRef;
|
||||
|
||||
pub(crate) trait Foldable<T, U> {
|
||||
type Mapped;
|
||||
fn fold<F: Fold<T, TargetU = U> + ?Sized>(
|
||||
self,
|
||||
folder: &mut F,
|
||||
) -> Result<Self::Mapped, F::Error>;
|
||||
}
|
||||
|
||||
impl<T, U, X> Foldable<T, U> for Vec<X>
|
||||
where
|
||||
X: Foldable<T, U>,
|
||||
{
|
||||
type Mapped = Vec<X::Mapped>;
|
||||
fn fold<F: Fold<T, TargetU = U> + ?Sized>(
|
||||
self,
|
||||
folder: &mut F,
|
||||
) -> Result<Self::Mapped, F::Error> {
|
||||
self.into_iter().map(|x| x.fold(folder)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, X> Foldable<T, U> for Option<X>
|
||||
where
|
||||
X: Foldable<T, U>,
|
||||
{
|
||||
type Mapped = Option<X::Mapped>;
|
||||
fn fold<F: Fold<T, TargetU = U> + ?Sized>(
|
||||
self,
|
||||
folder: &mut F,
|
||||
) -> Result<Self::Mapped, F::Error> {
|
||||
self.map(|x| x.fold(folder)).transpose()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, X> Foldable<T, U> for Box<X>
|
||||
where
|
||||
X: Foldable<T, U>,
|
||||
{
|
||||
type Mapped = Box<X::Mapped>;
|
||||
fn fold<F: Fold<T, TargetU = U> + ?Sized>(
|
||||
self,
|
||||
folder: &mut F,
|
||||
) -> Result<Self::Mapped, F::Error> {
|
||||
(*self).fold(folder).map(Box::new)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! simple_fold {
|
||||
($($t:ty),+$(,)?) => {
|
||||
$(impl<T, U> $crate::fold_helpers::Foldable<T, U> for $t {
|
||||
type Mapped = Self;
|
||||
#[inline]
|
||||
fn fold<F: Fold<T, TargetU = U> + ?Sized>(
|
||||
self,
|
||||
_folder: &mut F,
|
||||
) -> Result<Self::Mapped, F::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
})+
|
||||
};
|
||||
}
|
||||
|
||||
simple_fold!(
|
||||
usize,
|
||||
String,
|
||||
bool,
|
||||
StrRef,
|
||||
constant::Constant,
|
||||
constant::ConversionFlag
|
||||
);
|
53
nac3ast/src/impls.rs
Normal file
53
nac3ast/src/impls.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use crate::{Constant, ExprKind};
|
||||
|
||||
impl<U> ExprKind<U> {
|
||||
/// Returns a short name for the node suitable for use in error messages.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
ExprKind::BoolOp { .. } | ExprKind::BinOp { .. } | ExprKind::UnaryOp { .. } => {
|
||||
"operator"
|
||||
}
|
||||
ExprKind::Subscript { .. } => "subscript",
|
||||
ExprKind::Await { .. } => "await expression",
|
||||
ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } => "yield expression",
|
||||
ExprKind::Compare { .. } => "comparison",
|
||||
ExprKind::Attribute { .. } => "attribute",
|
||||
ExprKind::Call { .. } => "function call",
|
||||
ExprKind::Constant { value, .. } => match value {
|
||||
Constant::Str(_)
|
||||
| Constant::Int(_)
|
||||
| Constant::Float(_)
|
||||
| Constant::Complex { .. }
|
||||
| Constant::Bytes(_) => "literal",
|
||||
Constant::Tuple(_) => "tuple",
|
||||
Constant::Bool(_) | Constant::None => "keyword",
|
||||
Constant::Ellipsis => "ellipsis",
|
||||
},
|
||||
ExprKind::List { .. } => "list",
|
||||
ExprKind::Tuple { .. } => "tuple",
|
||||
ExprKind::Dict { .. } => "dict display",
|
||||
ExprKind::Set { .. } => "set display",
|
||||
ExprKind::ListComp { .. } => "list comprehension",
|
||||
ExprKind::DictComp { .. } => "dict comprehension",
|
||||
ExprKind::SetComp { .. } => "set comprehension",
|
||||
ExprKind::GeneratorExp { .. } => "generator expression",
|
||||
ExprKind::Starred { .. } => "starred",
|
||||
ExprKind::Slice { .. } => "slice",
|
||||
ExprKind::JoinedStr { values } => {
|
||||
if values
|
||||
.iter()
|
||||
.any(|e| matches!(e.node, ExprKind::JoinedStr { .. }))
|
||||
{
|
||||
"f-string expression"
|
||||
} else {
|
||||
"literal"
|
||||
}
|
||||
}
|
||||
ExprKind::FormattedValue { .. } => "f-string expression",
|
||||
ExprKind::Name { .. } => "name",
|
||||
ExprKind::Lambda { .. } => "lambda",
|
||||
ExprKind::IfExp { .. } => "conditional expression",
|
||||
ExprKind::NamedExpr { .. } => "named expression",
|
||||
}
|
||||
}
|
||||
}
|
14
nac3ast/src/lib.rs
Normal file
14
nac3ast/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
mod ast_gen;
|
||||
mod constant;
|
||||
#[cfg(feature = "fold")]
|
||||
mod fold_helpers;
|
||||
mod impls;
|
||||
mod location;
|
||||
|
||||
pub use ast_gen::*;
|
||||
pub use location::{Location, FileName};
|
||||
|
||||
pub type Suite<U = ()> = Vec<Stmt<U>>;
|
94
nac3ast/src/location.rs
Normal file
94
nac3ast/src/location.rs
Normal file
@ -0,0 +1,94 @@
|
||||
//! Datatypes to support source location information.
|
||||
use crate::ast_gen::StrRef;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct FileName(pub StrRef);
|
||||
impl Default for FileName {
|
||||
fn default() -> Self {
|
||||
FileName("unknown".into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for FileName {
|
||||
fn from(s: String) -> Self {
|
||||
FileName(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A location somewhere in the sourcecode.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Location {
|
||||
pub row: usize,
|
||||
pub column: usize,
|
||||
pub file: FileName
|
||||
}
|
||||
|
||||
impl fmt::Display for Location {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: line {} column {}", self.file.0, self.row, self.column)
|
||||
}
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn visualize<'a>(
|
||||
&self,
|
||||
line: &'a str,
|
||||
desc: impl fmt::Display + 'a,
|
||||
) -> impl fmt::Display + 'a {
|
||||
struct Visualize<'a, D: fmt::Display> {
|
||||
loc: Location,
|
||||
line: &'a str,
|
||||
desc: D,
|
||||
}
|
||||
impl<D: fmt::Display> fmt::Display for Visualize<'_, D> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}\n{}\n{arrow:>pad$}",
|
||||
self.desc,
|
||||
self.line,
|
||||
pad = self.loc.column,
|
||||
arrow = "↑",
|
||||
)
|
||||
}
|
||||
}
|
||||
Visualize {
|
||||
loc: *self,
|
||||
line,
|
||||
desc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn new(row: usize, column: usize, file: FileName) -> Self {
|
||||
Location { row, column, file }
|
||||
}
|
||||
|
||||
pub fn row(&self) -> usize {
|
||||
self.row
|
||||
}
|
||||
|
||||
pub fn column(&self) -> usize {
|
||||
self.column
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.row = 1;
|
||||
self.column = 1;
|
||||
}
|
||||
|
||||
pub fn go_right(&mut self) {
|
||||
self.column += 1;
|
||||
}
|
||||
|
||||
pub fn go_left(&mut self) {
|
||||
self.column -= 1;
|
||||
}
|
||||
|
||||
pub fn newline(&mut self) {
|
||||
self.row += 1;
|
||||
self.column = 1;
|
||||
}
|
||||
}
|
@ -5,7 +5,21 @@ authors = ["M-Labs"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
num-bigint = "0.3"
|
||||
num-traits = "0.2"
|
||||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm10-0"] }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/RustPython", branch = "master" }
|
||||
itertools = "0.10.1"
|
||||
crossbeam = "0.8.1"
|
||||
parking_lot = "0.11.1"
|
||||
rayon = "1.5.1"
|
||||
nac3parser = { path = "../nac3parser" }
|
||||
|
||||
[dependencies.inkwell]
|
||||
version = "0.1.0-beta.4"
|
||||
default-features = false
|
||||
features = ["llvm13-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]
|
||||
|
||||
[dev-dependencies]
|
||||
test-case = "1.2.0"
|
||||
indoc = "1.0"
|
||||
insta = "=1.11.0"
|
||||
|
||||
[build-dependencies]
|
||||
regex = "1"
|
||||
|
72
nac3core/build.rs
Normal file
72
nac3core/build.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
io::Write,
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
const FILE: &str = "src/codegen/irrt/irrt.c";
|
||||
println!("cargo:rerun-if-changed={}", FILE);
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_path = Path::new(&out_dir);
|
||||
|
||||
/*
|
||||
* HACK: Sadly, clang doesn't let us emit generic LLVM bitcode.
|
||||
* Compiling for WASM32 and filtering the output with regex is the closest we can get.
|
||||
*/
|
||||
|
||||
const FLAG: &[&str] = &[
|
||||
"--target=wasm32",
|
||||
FILE,
|
||||
"-O3",
|
||||
"-emit-llvm",
|
||||
"-S",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-o",
|
||||
"-",
|
||||
];
|
||||
let output = Command::new("clang")
|
||||
.args(FLAG)
|
||||
.output()
|
||||
.map(|o| {
|
||||
assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap());
|
||||
o
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// https://github.com/rust-lang/regex/issues/244
|
||||
let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n");
|
||||
let mut filtered_output = String::with_capacity(output.len());
|
||||
|
||||
let regex_filter = regex::Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)").unwrap();
|
||||
for f in regex_filter.captures_iter(&output) {
|
||||
assert!(f.len() == 1);
|
||||
filtered_output.push_str(&f[0]);
|
||||
filtered_output.push('\n');
|
||||
}
|
||||
|
||||
let filtered_output = Regex::new("(#\\d+)|(, *![0-9A-Za-z.]+)|(![0-9A-Za-z.]+)|(!\".*?\")")
|
||||
.unwrap()
|
||||
.replace_all(&filtered_output, "");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT");
|
||||
if env::var("DEBUG_DUMP_IRRT").is_ok() {
|
||||
let mut file = File::create(out_path.join("irrt.ll")).unwrap();
|
||||
file.write_all(output.as_bytes()).unwrap();
|
||||
let mut file = File::create(out_path.join("irrt-filtered.ll")).unwrap();
|
||||
file.write_all(filtered_output.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
let mut llvm_as = Command::new("llvm-as")
|
||||
.stdin(Stdio::piped())
|
||||
.arg("-o")
|
||||
.arg(out_path.join("irrt.bc"))
|
||||
.spawn()
|
||||
.unwrap();
|
||||
llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap();
|
||||
assert!(llvm_as.wait().unwrap().success())
|
||||
}
|
305
nac3core/src/codegen/concrete_type.rs
Normal file
305
nac3core/src/codegen/concrete_type.rs
Normal file
@ -0,0 +1,305 @@
|
||||
use crate::{
|
||||
symbol_resolver::SymbolValue,
|
||||
toplevel::DefinitionId,
|
||||
typecheck::{
|
||||
type_inferencer::PrimitiveStore,
|
||||
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
|
||||
},
|
||||
};
|
||||
|
||||
use nac3parser::ast::StrRef;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct ConcreteTypeStore {
|
||||
store: Vec<ConcreteTypeEnum>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub struct ConcreteType(usize);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConcreteFuncArg {
|
||||
pub name: StrRef,
|
||||
pub ty: ConcreteType,
|
||||
pub default_value: Option<SymbolValue>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Primitive {
|
||||
Int32,
|
||||
Int64,
|
||||
UInt32,
|
||||
UInt64,
|
||||
Float,
|
||||
Bool,
|
||||
None,
|
||||
Range,
|
||||
Str,
|
||||
Exception,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ConcreteTypeEnum {
|
||||
TPrimitive(Primitive),
|
||||
TTuple {
|
||||
ty: Vec<ConcreteType>,
|
||||
},
|
||||
TList {
|
||||
ty: ConcreteType,
|
||||
},
|
||||
TObj {
|
||||
obj_id: DefinitionId,
|
||||
fields: HashMap<StrRef, (ConcreteType, bool)>,
|
||||
params: HashMap<u32, ConcreteType>,
|
||||
},
|
||||
TVirtual {
|
||||
ty: ConcreteType,
|
||||
},
|
||||
TFunc {
|
||||
args: Vec<ConcreteFuncArg>,
|
||||
ret: ConcreteType,
|
||||
vars: HashMap<u32, ConcreteType>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ConcreteTypeStore {
|
||||
pub fn new() -> ConcreteTypeStore {
|
||||
ConcreteTypeStore {
|
||||
store: vec![
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::Int32),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::Int64),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::Float),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::Bool),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::None),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::Range),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::Str),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::Exception),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::UInt32),
|
||||
ConcreteTypeEnum::TPrimitive(Primitive::UInt64),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, cty: ConcreteType) -> &ConcreteTypeEnum {
|
||||
&self.store[cty.0]
|
||||
}
|
||||
|
||||
pub fn from_signature(
|
||||
&mut self,
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
signature: &FunSignature,
|
||||
cache: &mut HashMap<Type, Option<ConcreteType>>,
|
||||
) -> ConcreteTypeEnum {
|
||||
ConcreteTypeEnum::TFunc {
|
||||
args: signature
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| ConcreteFuncArg {
|
||||
name: arg.name,
|
||||
ty: self.from_unifier_type(unifier, primitives, arg.ty, cache),
|
||||
default_value: arg.default_value.clone(),
|
||||
})
|
||||
.collect(),
|
||||
ret: self.from_unifier_type(unifier, primitives, signature.ret, cache),
|
||||
vars: signature
|
||||
.vars
|
||||
.iter()
|
||||
.map(|(id, ty)| (*id, self.from_unifier_type(unifier, primitives, *ty, cache)))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_unifier_type(
|
||||
&mut self,
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
ty: Type,
|
||||
cache: &mut HashMap<Type, Option<ConcreteType>>,
|
||||
) -> ConcreteType {
|
||||
let ty = unifier.get_representative(ty);
|
||||
if unifier.unioned(ty, primitives.int32) {
|
||||
ConcreteType(0)
|
||||
} else if unifier.unioned(ty, primitives.int64) {
|
||||
ConcreteType(1)
|
||||
} else if unifier.unioned(ty, primitives.float) {
|
||||
ConcreteType(2)
|
||||
} else if unifier.unioned(ty, primitives.bool) {
|
||||
ConcreteType(3)
|
||||
} else if unifier.unioned(ty, primitives.none) {
|
||||
ConcreteType(4)
|
||||
} else if unifier.unioned(ty, primitives.range) {
|
||||
ConcreteType(5)
|
||||
} else if unifier.unioned(ty, primitives.str) {
|
||||
ConcreteType(6)
|
||||
} else if unifier.unioned(ty, primitives.exception) {
|
||||
ConcreteType(7)
|
||||
} else if unifier.unioned(ty, primitives.uint32) {
|
||||
ConcreteType(8)
|
||||
} else if unifier.unioned(ty, primitives.uint64) {
|
||||
ConcreteType(9)
|
||||
} else if let Some(cty) = cache.get(&ty) {
|
||||
if let Some(cty) = cty {
|
||||
*cty
|
||||
} else {
|
||||
let index = self.store.len();
|
||||
// placeholder
|
||||
self.store.push(ConcreteTypeEnum::TPrimitive(Primitive::Int32));
|
||||
let result = ConcreteType(index);
|
||||
cache.insert(ty, Some(result));
|
||||
result
|
||||
}
|
||||
} else {
|
||||
cache.insert(ty, None);
|
||||
let ty_enum = unifier.get_ty(ty);
|
||||
let result = match &*ty_enum {
|
||||
TypeEnum::TTuple { ty } => ConcreteTypeEnum::TTuple {
|
||||
ty: ty
|
||||
.iter()
|
||||
.map(|t| self.from_unifier_type(unifier, primitives, *t, cache))
|
||||
.collect(),
|
||||
},
|
||||
TypeEnum::TList { ty } => ConcreteTypeEnum::TList {
|
||||
ty: self.from_unifier_type(unifier, primitives, *ty, cache),
|
||||
},
|
||||
TypeEnum::TObj { obj_id, fields, params } => ConcreteTypeEnum::TObj {
|
||||
obj_id: *obj_id,
|
||||
fields: fields
|
||||
.iter()
|
||||
.filter_map(|(name, ty)| {
|
||||
// here we should not have type vars, but some partial instantiated
|
||||
// class methods can still have uninstantiated type vars, so
|
||||
// filter out all the methods, as this will not affect codegen
|
||||
if let TypeEnum::TFunc(..) = &*unifier.get_ty(ty.0) {
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
*name,
|
||||
(
|
||||
self.from_unifier_type(unifier, primitives, ty.0, cache),
|
||||
ty.1,
|
||||
),
|
||||
))
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
params: params
|
||||
.iter()
|
||||
.map(|(id, ty)| {
|
||||
(*id, self.from_unifier_type(unifier, primitives, *ty, cache))
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
TypeEnum::TVirtual { ty } => ConcreteTypeEnum::TVirtual {
|
||||
ty: self.from_unifier_type(unifier, primitives, *ty, cache),
|
||||
},
|
||||
TypeEnum::TFunc(signature) => {
|
||||
self.from_signature(unifier, primitives, &*signature, cache)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let index = if let Some(ConcreteType(index)) = cache.get(&ty).unwrap() {
|
||||
self.store[*index] = result;
|
||||
*index
|
||||
} else {
|
||||
self.store.push(result);
|
||||
self.store.len() - 1
|
||||
};
|
||||
cache.insert(ty, Some(ConcreteType(index)));
|
||||
ConcreteType(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_unifier_type(
|
||||
&self,
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
cty: ConcreteType,
|
||||
cache: &mut HashMap<ConcreteType, Option<Type>>,
|
||||
) -> Type {
|
||||
if let Some(ty) = cache.get_mut(&cty) {
|
||||
return if let Some(ty) = ty {
|
||||
*ty
|
||||
} else {
|
||||
*ty = Some(unifier.get_dummy_var().0);
|
||||
ty.unwrap()
|
||||
};
|
||||
}
|
||||
cache.insert(cty, None);
|
||||
let result = match &self.store[cty.0] {
|
||||
ConcreteTypeEnum::TPrimitive(primitive) => {
|
||||
let ty = match primitive {
|
||||
Primitive::Int32 => primitives.int32,
|
||||
Primitive::Int64 => primitives.int64,
|
||||
Primitive::UInt32 => primitives.uint32,
|
||||
Primitive::UInt64 => primitives.uint64,
|
||||
Primitive::Float => primitives.float,
|
||||
Primitive::Bool => primitives.bool,
|
||||
Primitive::None => primitives.none,
|
||||
Primitive::Range => primitives.range,
|
||||
Primitive::Str => primitives.str,
|
||||
Primitive::Exception => primitives.exception,
|
||||
};
|
||||
*cache.get_mut(&cty).unwrap() = Some(ty);
|
||||
return ty;
|
||||
}
|
||||
ConcreteTypeEnum::TTuple { ty } => TypeEnum::TTuple {
|
||||
ty: ty
|
||||
.iter()
|
||||
.map(|cty| self.to_unifier_type(unifier, primitives, *cty, cache))
|
||||
.collect(),
|
||||
},
|
||||
ConcreteTypeEnum::TList { ty } => {
|
||||
TypeEnum::TList { ty: self.to_unifier_type(unifier, primitives, *ty, cache) }
|
||||
}
|
||||
ConcreteTypeEnum::TVirtual { ty } => {
|
||||
TypeEnum::TVirtual { ty: self.to_unifier_type(unifier, primitives, *ty, cache) }
|
||||
}
|
||||
ConcreteTypeEnum::TObj { obj_id, fields, params } => TypeEnum::TObj {
|
||||
obj_id: *obj_id,
|
||||
fields: fields
|
||||
.iter()
|
||||
.map(|(name, cty)| {
|
||||
(*name, (self.to_unifier_type(unifier, primitives, cty.0, cache), cty.1))
|
||||
})
|
||||
.collect::<HashMap<_, _>>(),
|
||||
params: params
|
||||
.iter()
|
||||
.map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache)))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
},
|
||||
ConcreteTypeEnum::TFunc { args, ret, vars } => TypeEnum::TFunc(FunSignature {
|
||||
args: args
|
||||
.iter()
|
||||
.map(|arg| FuncArg {
|
||||
name: arg.name,
|
||||
ty: self.to_unifier_type(unifier, primitives, arg.ty, cache),
|
||||
default_value: arg.default_value.clone(),
|
||||
})
|
||||
.collect(),
|
||||
ret: self.to_unifier_type(unifier, primitives, *ret, cache),
|
||||
vars: vars
|
||||
.iter()
|
||||
.map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache)))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
}),
|
||||
};
|
||||
let result = unifier.add_ty(result);
|
||||
if let Some(ty) = cache.get(&cty).unwrap() {
|
||||
unifier.unify(*ty, result).unwrap();
|
||||
}
|
||||
cache.insert(cty, Some(result));
|
||||
result
|
||||
}
|
||||
|
||||
pub fn add_cty(&mut self, cty: ConcreteTypeEnum) -> ConcreteType {
|
||||
self.store.push(cty);
|
||||
ConcreteType(self.store.len() - 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConcreteTypeStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
1453
nac3core/src/codegen/expr.rs
Normal file
1453
nac3core/src/codegen/expr.rs
Normal file
File diff suppressed because it is too large
Load Diff
209
nac3core/src/codegen/generator.rs
Normal file
209
nac3core/src/codegen/generator.rs
Normal file
@ -0,0 +1,209 @@
|
||||
use crate::{
|
||||
codegen::{expr::*, stmt::*, CodeGenContext},
|
||||
symbol_resolver::ValueEnum,
|
||||
toplevel::{DefinitionId, TopLevelDef},
|
||||
typecheck::typedef::{FunSignature, Type},
|
||||
};
|
||||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicTypeEnum, IntType},
|
||||
values::{BasicValueEnum, PointerValue},
|
||||
};
|
||||
use nac3parser::ast::{Expr, Stmt, StrRef};
|
||||
|
||||
pub trait CodeGenerator {
|
||||
/// Return the module name for the code generator.
|
||||
fn get_name(&self) -> &str;
|
||||
|
||||
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx>;
|
||||
|
||||
/// Generate function call and returns the function return value.
|
||||
/// - obj: Optional object for method call.
|
||||
/// - fun: Function signature and definition ID.
|
||||
/// - params: Function parameters. Note that this does not include the object even if the
|
||||
/// function is a class method.
|
||||
fn gen_call<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
fun: (&FunSignature, DefinitionId),
|
||||
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
) -> Result<Option<BasicValueEnum<'ctx>>, String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_call(self, ctx, obj, fun, params)
|
||||
}
|
||||
|
||||
/// Generate object constructor and returns the constructed object.
|
||||
/// - signature: Function signature of the contructor.
|
||||
/// - def: Class definition for the constructor class.
|
||||
/// - params: Function parameters.
|
||||
fn gen_constructor<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
signature: &FunSignature,
|
||||
def: &TopLevelDef,
|
||||
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
) -> Result<BasicValueEnum<'ctx>, String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_constructor(self, ctx, signature, def, params)
|
||||
}
|
||||
|
||||
/// Generate a function instance.
|
||||
/// - obj: Optional object for method call.
|
||||
/// - fun: Function signature, definition ID and the substitution key.
|
||||
/// - params: Function parameters. Note that this does not include the object even if the
|
||||
/// function is a class method.
|
||||
/// Note that this function should check if the function is generated in another thread (due to
|
||||
/// possible race condition), see the default implementation for an example.
|
||||
fn gen_func_instance<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
fun: (&FunSignature, &mut TopLevelDef, String),
|
||||
id: usize,
|
||||
) -> Result<String, String> {
|
||||
gen_func_instance(ctx, obj, fun, id)
|
||||
}
|
||||
|
||||
/// Generate the code for an expression.
|
||||
fn gen_expr<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
expr: &Expr<Option<Type>>,
|
||||
) -> Result<Option<ValueEnum<'ctx>>, String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_expr(self, ctx, expr)
|
||||
}
|
||||
|
||||
/// Allocate memory for a variable and return a pointer pointing to it.
|
||||
/// The default implementation places the allocations at the start of the function.
|
||||
fn gen_var_alloc<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<PointerValue<'ctx>, String> {
|
||||
gen_var(ctx, ty)
|
||||
}
|
||||
|
||||
/// Return a pointer pointing to the target of the expression.
|
||||
fn gen_store_target<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
pattern: &Expr<Option<Type>>,
|
||||
) -> Result<PointerValue<'ctx>, String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_store_target(self, ctx, pattern)
|
||||
}
|
||||
|
||||
/// Generate code for an assignment expression.
|
||||
fn gen_assign<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
target: &Expr<Option<Type>>,
|
||||
value: ValueEnum<'ctx>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_assign(self, ctx, target, value)
|
||||
}
|
||||
|
||||
/// Generate code for a while expression.
|
||||
/// Return true if the while loop must early return
|
||||
fn gen_while<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_while(self, ctx, stmt)
|
||||
}
|
||||
|
||||
/// Generate code for a while expression.
|
||||
/// Return true if the while loop must early return
|
||||
fn gen_for<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_for(self, ctx, stmt)
|
||||
}
|
||||
|
||||
/// Generate code for an if expression.
|
||||
/// Return true if the statement must early return
|
||||
fn gen_if<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_if(self, ctx, stmt)
|
||||
}
|
||||
|
||||
fn gen_with<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_with(self, ctx, stmt)
|
||||
}
|
||||
|
||||
/// Generate code for a statement
|
||||
/// Return true if the statement must early return
|
||||
fn gen_stmt<'ctx, 'a>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_stmt(self, ctx, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultCodeGenerator {
|
||||
name: String,
|
||||
size_t: u32,
|
||||
}
|
||||
|
||||
impl DefaultCodeGenerator {
|
||||
pub fn new(name: String, size_t: u32) -> DefaultCodeGenerator {
|
||||
assert!(size_t == 32 || size_t == 64);
|
||||
DefaultCodeGenerator { name, size_t }
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeGenerator for DefaultCodeGenerator {
|
||||
fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx> {
|
||||
// it should be unsigned, but we don't really need unsigned and this could save us from
|
||||
// having to do a bit cast...
|
||||
if self.size_t == 32 {
|
||||
ctx.i32_type()
|
||||
} else {
|
||||
ctx.i64_type()
|
||||
}
|
||||
}
|
||||
}
|
140
nac3core/src/codegen/irrt/irrt.c
Normal file
140
nac3core/src/codegen/irrt/irrt.c
Normal file
@ -0,0 +1,140 @@
|
||||
typedef _ExtInt(8) int8_t;
|
||||
typedef unsigned _ExtInt(8) uint8_t;
|
||||
typedef _ExtInt(32) int32_t;
|
||||
typedef unsigned _ExtInt(32) uint32_t;
|
||||
typedef _ExtInt(64) int64_t;
|
||||
typedef unsigned _ExtInt(64) uint64_t;
|
||||
|
||||
# define MAX(a, b) (a > b ? a : b)
|
||||
# define MIN(a, b) (a > b ? b : a)
|
||||
|
||||
// adapted from GNU Scientific Library: https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
// need to make sure `exp >= 0` before calling this function
|
||||
#define DEF_INT_EXP(T) T __nac3_int_exp_##T( \
|
||||
T base, \
|
||||
T exp \
|
||||
) { \
|
||||
T res = (T)1; \
|
||||
/* repeated squaring method */ \
|
||||
do { \
|
||||
if (exp & 1) res *= base; /* for n odd */ \
|
||||
exp >>= 1; \
|
||||
base *= base; \
|
||||
} while (exp); \
|
||||
return res; \
|
||||
} \
|
||||
|
||||
DEF_INT_EXP(int32_t)
|
||||
DEF_INT_EXP(int64_t)
|
||||
DEF_INT_EXP(uint32_t)
|
||||
DEF_INT_EXP(uint64_t)
|
||||
|
||||
|
||||
int32_t __nac3_slice_index_bound(int32_t i, const int32_t len) {
|
||||
if (i < 0) {
|
||||
i = len + i;
|
||||
}
|
||||
if (i < 0) {
|
||||
return 0;
|
||||
} else if (i > len) {
|
||||
return len;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int32_t __nac3_range_slice_len(const int32_t start, const int32_t end, const int32_t step) {
|
||||
int32_t diff = end - start;
|
||||
if (diff > 0 && step > 0) {
|
||||
return ((diff - 1) / step) + 1;
|
||||
} else if (diff < 0 && step < 0) {
|
||||
return ((diff + 1) / step) + 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle list assignment and dropping part of the list when
|
||||
// both dest_step and src_step are +1.
|
||||
// - All the index must *not* be out-of-bound or negative,
|
||||
// - The end index is *inclusive*,
|
||||
// - The length of src and dest slice size should already
|
||||
// be checked: if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest)
|
||||
int32_t __nac3_list_slice_assign_var_size(
|
||||
int32_t dest_start,
|
||||
int32_t dest_end,
|
||||
int32_t dest_step,
|
||||
uint8_t *dest_arr,
|
||||
int32_t dest_arr_len,
|
||||
int32_t src_start,
|
||||
int32_t src_end,
|
||||
int32_t src_step,
|
||||
uint8_t *src_arr,
|
||||
int32_t src_arr_len,
|
||||
const int32_t size
|
||||
) {
|
||||
/* if dest_arr_len == 0, do nothing since we do not support extending list */
|
||||
if (dest_arr_len == 0) return dest_arr_len;
|
||||
/* if both step is 1, memmove directly, handle the dropping of the list, and shrink size */
|
||||
if (src_step == dest_step && dest_step == 1) {
|
||||
const int32_t src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
|
||||
const int32_t dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
|
||||
if (src_len > 0) {
|
||||
__builtin_memmove(
|
||||
dest_arr + dest_start * size,
|
||||
src_arr + src_start * size,
|
||||
src_len * size
|
||||
);
|
||||
}
|
||||
if (dest_len > 0) {
|
||||
/* dropping */
|
||||
__builtin_memmove(
|
||||
dest_arr + (dest_start + src_len) * size,
|
||||
dest_arr + (dest_end + 1) * size,
|
||||
(dest_arr_len - dest_end - 1) * size
|
||||
);
|
||||
}
|
||||
/* shrink size */
|
||||
return dest_arr_len - (dest_len - src_len);
|
||||
}
|
||||
/* if two range overlaps, need alloca */
|
||||
uint8_t need_alloca =
|
||||
(dest_arr == src_arr)
|
||||
&& !(
|
||||
MAX(dest_start, dest_end) < MIN(src_start, src_end)
|
||||
|| MAX(src_start, src_end) < MIN(dest_start, dest_end)
|
||||
);
|
||||
if (need_alloca) {
|
||||
uint8_t *tmp = __builtin_alloca(src_arr_len * size);
|
||||
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
|
||||
src_arr = tmp;
|
||||
}
|
||||
int32_t src_ind = src_start;
|
||||
int32_t dest_ind = dest_start;
|
||||
for (;
|
||||
(src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end);
|
||||
src_ind += src_step, dest_ind += dest_step
|
||||
) {
|
||||
/* for constant optimization */
|
||||
if (size == 1) {
|
||||
__builtin_memcpy(dest_arr + dest_ind, src_arr + src_ind, 1);
|
||||
} else if (size == 4) {
|
||||
__builtin_memcpy(dest_arr + dest_ind * 4, src_arr + src_ind * 4, 4);
|
||||
} else if (size == 8) {
|
||||
__builtin_memcpy(dest_arr + dest_ind * 8, src_arr + src_ind * 8, 8);
|
||||
} else {
|
||||
/* memcpy for var size, cannot overlap after previous alloca */
|
||||
__builtin_memcpy(dest_arr + dest_ind * size, src_arr + src_ind * size, size);
|
||||
}
|
||||
}
|
||||
/* only dest_step == 1 can we shrink the dest list. */
|
||||
/* size should be ensured prior to calling this function */
|
||||
if (dest_step == 1 && dest_end >= dest_start) {
|
||||
__builtin_memmove(
|
||||
dest_arr + dest_ind * size,
|
||||
dest_arr + (dest_end + 1) * size,
|
||||
(dest_arr_len - dest_end - 1) * size
|
||||
);
|
||||
return dest_arr_len - (dest_end - dest_ind) - 1;
|
||||
}
|
||||
return dest_arr_len;
|
||||
}
|
330
nac3core/src/codegen/irrt/mod.rs
Normal file
330
nac3core/src/codegen/irrt/mod.rs
Normal file
@ -0,0 +1,330 @@
|
||||
use crate::typecheck::typedef::Type;
|
||||
|
||||
use super::{CodeGenContext, CodeGenerator};
|
||||
use inkwell::{
|
||||
attributes::{Attribute, AttributeLoc},
|
||||
context::Context,
|
||||
memory_buffer::MemoryBuffer,
|
||||
module::Module,
|
||||
types::{BasicTypeEnum, IntType},
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace, IntPredicate,
|
||||
};
|
||||
use nac3parser::ast::Expr;
|
||||
|
||||
pub fn load_irrt(ctx: &Context) -> Module {
|
||||
let bitcode_buf = MemoryBuffer::create_from_memory_range(
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/irrt.bc")),
|
||||
"irrt_bitcode_buffer",
|
||||
);
|
||||
let irrt_mod = Module::parse_bitcode_from_buffer(&bitcode_buf, ctx).unwrap();
|
||||
let inline_attr = Attribute::get_named_enum_kind_id("alwaysinline");
|
||||
for symbol in &[
|
||||
"__nac3_int_exp_int32_t",
|
||||
"__nac3_int_exp_int64_t",
|
||||
"__nac3_range_slice_len",
|
||||
"__nac3_slice_index_bound",
|
||||
] {
|
||||
let function = irrt_mod.get_function(symbol).unwrap();
|
||||
function.add_attribute(AttributeLoc::Function, ctx.create_enum_attribute(inline_attr, 0));
|
||||
}
|
||||
irrt_mod
|
||||
}
|
||||
|
||||
// repeated squaring method adapted from GNU Scientific Library:
|
||||
// https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
pub fn integer_power<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
base: IntValue<'ctx>,
|
||||
exp: IntValue<'ctx>,
|
||||
signed: bool,
|
||||
) -> IntValue<'ctx> {
|
||||
let symbol = match (base.get_type().get_bit_width(), exp.get_type().get_bit_width(), signed) {
|
||||
(32, 32, true) => "__nac3_int_exp_int32_t",
|
||||
(64, 64, true) => "__nac3_int_exp_int64_t",
|
||||
(32, 32, false) => "__nac3_int_exp_uint32_t",
|
||||
(64, 64, false) => "__nac3_int_exp_uint64_t",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let base_type = base.get_type();
|
||||
let pow_fun = ctx.module.get_function(symbol).unwrap_or_else(|| {
|
||||
let fn_type = base_type.fn_type(&[base_type.into(), base_type.into()], false);
|
||||
ctx.module.add_function(symbol, fn_type, None)
|
||||
});
|
||||
// TODO: throw exception when exp < 0
|
||||
ctx.builder
|
||||
.build_call(pow_fun, &[base.into(), exp.into()], "call_int_pow")
|
||||
.try_as_basic_value()
|
||||
.unwrap_left()
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
pub fn calculate_len_for_slice_range<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
start: IntValue<'ctx>,
|
||||
end: IntValue<'ctx>,
|
||||
step: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
const SYMBOL: &str = "__nac3_range_slice_len";
|
||||
let len_func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
|
||||
let i32_t = ctx.ctx.i32_type();
|
||||
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into(), i32_t.into()], false);
|
||||
ctx.module.add_function(SYMBOL, fn_t, None)
|
||||
});
|
||||
|
||||
// TODO: assert step != 0, throw exception if not
|
||||
ctx.builder
|
||||
.build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len")
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap()
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
/// NOTE: the output value of the end index of this function should be compared ***inclusively***,
|
||||
/// because python allows `a[2::-1]`, whose semantic is `[a[2], a[1], a[0]]`, which is equivalent to
|
||||
/// NO numeric slice in python.
|
||||
///
|
||||
/// equivalent code:
|
||||
/// ```pseudo_code
|
||||
/// match (start, end, step):
|
||||
/// case (s, e, None | Some(step)) if step > 0:
|
||||
/// return (
|
||||
/// match s:
|
||||
/// case None:
|
||||
/// 0
|
||||
/// case Some(s):
|
||||
/// handle_in_bound(s)
|
||||
/// ,match e:
|
||||
/// case None:
|
||||
/// length - 1
|
||||
/// case Some(e):
|
||||
/// handle_in_bound(e) - 1
|
||||
/// ,step == None ? 1 : step
|
||||
/// )
|
||||
/// case (s, e, Some(step)) if step < 0:
|
||||
/// return (
|
||||
/// match s:
|
||||
/// case None:
|
||||
/// length - 1
|
||||
/// case Some(s):
|
||||
/// s = handle_in_bound(s)
|
||||
/// if s == length:
|
||||
/// s - 1
|
||||
/// else:
|
||||
/// s
|
||||
/// ,match e:
|
||||
/// case None:
|
||||
/// 0
|
||||
/// case Some(e):
|
||||
/// handle_in_bound(e) + 1
|
||||
/// ,step
|
||||
/// )
|
||||
/// ```
|
||||
pub fn handle_slice_indices<'a, 'ctx, G: CodeGenerator>(
|
||||
start: &Option<Box<Expr<Option<Type>>>>,
|
||||
end: &Option<Box<Expr<Option<Type>>>>,
|
||||
step: &Option<Box<Expr<Option<Type>>>>,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
generator: &mut G,
|
||||
list: PointerValue<'ctx>,
|
||||
) -> Result<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>), String> {
|
||||
// TODO: throw exception when step is 0
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let zero = int32.const_zero();
|
||||
let one = int32.const_int(1, false);
|
||||
let length = ctx.build_gep_and_load(list, &[zero, one]).into_int_value();
|
||||
let length = ctx.builder.build_int_truncate_or_bit_cast(length, int32, "leni32");
|
||||
Ok(match (start, end, step) {
|
||||
(s, e, None) => (
|
||||
s.as_ref().map_or_else(
|
||||
|| Ok(int32.const_zero()),
|
||||
|s| handle_slice_index_bound(s, ctx, generator, length),
|
||||
)?,
|
||||
{
|
||||
let e = e.as_ref().map_or_else(
|
||||
|| Ok(length),
|
||||
|e| handle_slice_index_bound(e, ctx, generator, length),
|
||||
)?;
|
||||
ctx.builder.build_int_sub(e, one, "final_end")
|
||||
},
|
||||
one,
|
||||
),
|
||||
(s, e, Some(step)) => {
|
||||
let step = generator
|
||||
.gen_expr(ctx, step)?
|
||||
.unwrap()
|
||||
.to_basic_value_enum(ctx, generator)?
|
||||
.into_int_value();
|
||||
let len_id = ctx.builder.build_int_sub(length, one, "lenmin1");
|
||||
let neg = ctx.builder.build_int_compare(IntPredicate::SLT, step, zero, "step_is_neg");
|
||||
(
|
||||
match s {
|
||||
Some(s) => {
|
||||
let s = handle_slice_index_bound(s, ctx, generator, length)?;
|
||||
ctx.builder
|
||||
.build_select(
|
||||
ctx.builder.build_and(
|
||||
ctx.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
s,
|
||||
length,
|
||||
"s_eq_len",
|
||||
),
|
||||
neg,
|
||||
"should_minus_one",
|
||||
),
|
||||
ctx.builder.build_int_sub(s, one, "s_min"),
|
||||
s,
|
||||
"final_start",
|
||||
)
|
||||
.into_int_value()
|
||||
}
|
||||
None => ctx.builder.build_select(neg, len_id, zero, "stt").into_int_value(),
|
||||
},
|
||||
match e {
|
||||
Some(e) => {
|
||||
let e = handle_slice_index_bound(e, ctx, generator, length)?;
|
||||
ctx.builder
|
||||
.build_select(
|
||||
neg,
|
||||
ctx.builder.build_int_add(e, one, "end_add_one"),
|
||||
ctx.builder.build_int_sub(e, one, "end_sub_one"),
|
||||
"final_end",
|
||||
)
|
||||
.into_int_value()
|
||||
}
|
||||
None => ctx.builder.build_select(neg, zero, len_id, "end").into_int_value(),
|
||||
},
|
||||
step,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// this function allows index out of range, since python
|
||||
/// allows index out of range in slice (`a = [1,2,3]; a[1:10] == [2,3]`).
|
||||
pub fn handle_slice_index_bound<'a, 'ctx, G: CodeGenerator>(
|
||||
i: &Expr<Option<Type>>,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
generator: &mut G,
|
||||
length: IntValue<'ctx>,
|
||||
) -> Result<IntValue<'ctx>, String> {
|
||||
const SYMBOL: &str = "__nac3_slice_index_bound";
|
||||
let func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
|
||||
let i32_t = ctx.ctx.i32_type();
|
||||
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into()], false);
|
||||
ctx.module.add_function(SYMBOL, fn_t, None)
|
||||
});
|
||||
|
||||
let i = generator.gen_expr(ctx, i)?.unwrap().to_basic_value_enum(ctx, generator)?;
|
||||
Ok(ctx
|
||||
.builder
|
||||
.build_call(func, &[i.into(), length.into()], "bounded_ind")
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap()
|
||||
.into_int_value())
|
||||
}
|
||||
|
||||
/// This function handles 'end' **inclusively**.
|
||||
/// Order of tuples assign_idx and value_idx is ('start', 'end', 'step').
|
||||
/// Negative index should be handled before entering this function
|
||||
pub fn list_slice_assignment<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
size_ty: IntType<'ctx>,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
dest_arr: PointerValue<'ctx>,
|
||||
dest_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
|
||||
src_arr: PointerValue<'ctx>,
|
||||
src_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
|
||||
) {
|
||||
let int8_ptr = ctx.ctx.i8_type().ptr_type(AddressSpace::Generic);
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let (fun_symbol, elem_ptr_type) = ("__nac3_list_slice_assign_var_size", int8_ptr);
|
||||
let slice_assign_fun = {
|
||||
let ty_vec = vec![
|
||||
int32.into(), // dest start idx
|
||||
int32.into(), // dest end idx
|
||||
int32.into(), // dest step
|
||||
elem_ptr_type.into(), // dest arr ptr
|
||||
int32.into(), // dest arr len
|
||||
int32.into(), // src start idx
|
||||
int32.into(), // src end idx
|
||||
int32.into(), // src step
|
||||
elem_ptr_type.into(), // src arr ptr
|
||||
int32.into(), // src arr len
|
||||
int32.into(), // size
|
||||
];
|
||||
ctx.module.get_function(fun_symbol).unwrap_or_else(|| {
|
||||
let fn_t = int32.fn_type(ty_vec.as_slice(), false);
|
||||
ctx.module.add_function(fun_symbol, fn_t, None)
|
||||
})
|
||||
};
|
||||
|
||||
let zero = int32.const_zero();
|
||||
let one = int32.const_int(1, false);
|
||||
let dest_arr_ptr = ctx.build_gep_and_load(dest_arr, &[zero, zero]);
|
||||
let dest_arr_ptr = ctx.builder.build_pointer_cast(
|
||||
dest_arr_ptr.into_pointer_value(),
|
||||
elem_ptr_type,
|
||||
"dest_arr_ptr_cast",
|
||||
);
|
||||
let dest_len = ctx.build_gep_and_load(dest_arr, &[zero, one]).into_int_value();
|
||||
let dest_len = ctx.builder.build_int_truncate_or_bit_cast(dest_len, int32, "srclen32");
|
||||
let src_arr_ptr = ctx.build_gep_and_load(src_arr, &[zero, zero]);
|
||||
let src_arr_ptr = ctx.builder.build_pointer_cast(
|
||||
src_arr_ptr.into_pointer_value(),
|
||||
elem_ptr_type,
|
||||
"src_arr_ptr_cast",
|
||||
);
|
||||
let src_len = ctx.build_gep_and_load(src_arr, &[zero, one]).into_int_value();
|
||||
let src_len = ctx.builder.build_int_truncate_or_bit_cast(src_len, int32, "srclen32");
|
||||
|
||||
// index in bound and positive should be done
|
||||
// TODO: assert if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest), and
|
||||
// throw exception if not satisfied
|
||||
let new_len = {
|
||||
let args = vec![
|
||||
dest_idx.0.into(), // dest start idx
|
||||
dest_idx.1.into(), // dest end idx
|
||||
dest_idx.2.into(), // dest step
|
||||
dest_arr_ptr.into(), // dest arr ptr
|
||||
dest_len.into(), // dest arr len
|
||||
src_idx.0.into(), // src start idx
|
||||
src_idx.1.into(), // src end idx
|
||||
src_idx.2.into(), // src step
|
||||
src_arr_ptr.into(), // src arr ptr
|
||||
src_len.into(), // src arr len
|
||||
{
|
||||
let s = match ty {
|
||||
BasicTypeEnum::FloatType(t) => t.size_of(),
|
||||
BasicTypeEnum::IntType(t) => t.size_of(),
|
||||
BasicTypeEnum::PointerType(t) => t.size_of(),
|
||||
BasicTypeEnum::StructType(t) => t.size_of().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ctx.builder.build_int_truncate_or_bit_cast(s, int32, "size")
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
ctx.builder
|
||||
.build_call(slice_assign_fun, args.as_slice(), "slice_assign")
|
||||
.try_as_basic_value()
|
||||
.unwrap_left()
|
||||
.into_int_value()
|
||||
};
|
||||
// update length
|
||||
let need_update =
|
||||
ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update");
|
||||
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let update_bb = ctx.ctx.append_basic_block(current, "update");
|
||||
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
|
||||
ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb);
|
||||
ctx.builder.position_at_end(update_bb);
|
||||
let dest_len_ptr = unsafe { ctx.builder.build_gep(dest_arr, &[zero, one], "dest_len_ptr") };
|
||||
let new_len = ctx.builder.build_int_z_extend_or_bit_cast(new_len, size_ty, "new_len");
|
||||
ctx.builder.build_store(dest_len_ptr, new_len);
|
||||
ctx.builder.build_unconditional_branch(cont_bb);
|
||||
ctx.builder.position_at_end(cont_bb);
|
||||
}
|
596
nac3core/src/codegen/mod.rs
Normal file
596
nac3core/src/codegen/mod.rs
Normal file
@ -0,0 +1,596 @@
|
||||
use crate::{
|
||||
symbol_resolver::{StaticValue, SymbolResolver},
|
||||
toplevel::{TopLevelContext, TopLevelDef},
|
||||
typecheck::{
|
||||
type_inferencer::{CodeLocation, PrimitiveStore},
|
||||
typedef::{CallId, FuncArg, Type, TypeEnum, Unifier},
|
||||
},
|
||||
};
|
||||
use crossbeam::channel::{unbounded, Receiver, Sender};
|
||||
use inkwell::{
|
||||
AddressSpace,
|
||||
OptimizationLevel,
|
||||
attributes::{Attribute, AttributeLoc},
|
||||
basic_block::BasicBlock,
|
||||
builder::Builder,
|
||||
context::Context,
|
||||
module::Module,
|
||||
passes::{PassManager, PassManagerBuilder},
|
||||
types::{AnyType, BasicType, BasicTypeEnum},
|
||||
values::{BasicValueEnum, FunctionValue, PhiValue, PointerValue}
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nac3parser::ast::{Stmt, StrRef};
|
||||
use parking_lot::{Condvar, Mutex};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use std::thread;
|
||||
|
||||
pub mod concrete_type;
|
||||
pub mod expr;
|
||||
mod generator;
|
||||
pub mod irrt;
|
||||
pub mod stmt;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use concrete_type::{ConcreteType, ConcreteTypeEnum, ConcreteTypeStore};
|
||||
pub use generator::{CodeGenerator, DefaultCodeGenerator};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StaticValueStore {
|
||||
pub lookup: HashMap<Vec<(usize, u64)>, usize>,
|
||||
pub store: Vec<HashMap<usize, Arc<dyn StaticValue + Send + Sync>>>,
|
||||
}
|
||||
|
||||
pub type VarValue<'ctx> = (PointerValue<'ctx>, Option<Arc<dyn StaticValue + Send + Sync>>, i64);
|
||||
|
||||
pub struct CodeGenContext<'ctx, 'a> {
|
||||
pub ctx: &'ctx Context,
|
||||
pub builder: Builder<'ctx>,
|
||||
pub module: Module<'ctx>,
|
||||
pub top_level: &'a TopLevelContext,
|
||||
pub unifier: Unifier,
|
||||
pub resolver: Arc<dyn SymbolResolver + Send + Sync>,
|
||||
pub static_value_store: Arc<Mutex<StaticValueStore>>,
|
||||
pub var_assignment: HashMap<StrRef, VarValue<'ctx>>,
|
||||
pub type_cache: HashMap<Type, BasicTypeEnum<'ctx>>,
|
||||
pub primitives: PrimitiveStore,
|
||||
pub calls: Arc<HashMap<CodeLocation, CallId>>,
|
||||
pub registry: &'a WorkerRegistry,
|
||||
// const string cache
|
||||
pub const_strings: HashMap<String, BasicValueEnum<'ctx>>,
|
||||
// stores the alloca for variables
|
||||
pub init_bb: BasicBlock<'ctx>,
|
||||
// the first one is the test_bb, and the second one is bb after the loop
|
||||
pub loop_target: Option<(BasicBlock<'ctx>, BasicBlock<'ctx>)>,
|
||||
// unwind target bb
|
||||
pub unwind_target: Option<BasicBlock<'ctx>>,
|
||||
// return target bb, just emit ret if no such target
|
||||
pub return_target: Option<BasicBlock<'ctx>>,
|
||||
pub return_buffer: Option<PointerValue<'ctx>>,
|
||||
// outer catch clauses
|
||||
pub outer_catch_clauses:
|
||||
Option<(Vec<Option<BasicValueEnum<'ctx>>>, BasicBlock<'ctx>, PhiValue<'ctx>)>,
|
||||
pub need_sret: bool,
|
||||
}
|
||||
|
||||
impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
|
||||
pub fn is_terminated(&self) -> bool {
|
||||
self.builder.get_insert_block().unwrap().get_terminator().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
type Fp = Box<dyn Fn(&Module) + Send + Sync>;
|
||||
|
||||
pub struct WithCall {
|
||||
fp: Fp,
|
||||
}
|
||||
|
||||
impl WithCall {
|
||||
pub fn new(fp: Fp) -> WithCall {
|
||||
WithCall { fp }
|
||||
}
|
||||
|
||||
pub fn run<'ctx>(&self, m: &Module<'ctx>) {
|
||||
(self.fp)(m)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkerRegistry {
|
||||
sender: Arc<Sender<Option<CodeGenTask>>>,
|
||||
receiver: Arc<Receiver<Option<CodeGenTask>>>,
|
||||
panicked: AtomicBool,
|
||||
task_count: Mutex<usize>,
|
||||
thread_count: usize,
|
||||
wait_condvar: Condvar,
|
||||
top_level_ctx: Arc<TopLevelContext>,
|
||||
static_value_store: Arc<Mutex<StaticValueStore>>,
|
||||
}
|
||||
|
||||
impl WorkerRegistry {
|
||||
pub fn create_workers<G: CodeGenerator + Send + 'static>(
|
||||
generators: Vec<Box<G>>,
|
||||
top_level_ctx: Arc<TopLevelContext>,
|
||||
f: Arc<WithCall>,
|
||||
) -> (Arc<WorkerRegistry>, Vec<thread::JoinHandle<()>>) {
|
||||
let (sender, receiver) = unbounded();
|
||||
let task_count = Mutex::new(0);
|
||||
let wait_condvar = Condvar::new();
|
||||
|
||||
// init: 0 to be empty
|
||||
let mut static_value_store: StaticValueStore = Default::default();
|
||||
static_value_store.lookup.insert(Default::default(), 0);
|
||||
static_value_store.store.push(Default::default());
|
||||
|
||||
let registry = Arc::new(WorkerRegistry {
|
||||
sender: Arc::new(sender),
|
||||
receiver: Arc::new(receiver),
|
||||
thread_count: generators.len(),
|
||||
panicked: AtomicBool::new(false),
|
||||
static_value_store: Arc::new(Mutex::new(static_value_store)),
|
||||
task_count,
|
||||
wait_condvar,
|
||||
top_level_ctx,
|
||||
});
|
||||
|
||||
let mut handles = Vec::new();
|
||||
for mut generator in generators.into_iter() {
|
||||
let registry = registry.clone();
|
||||
let registry2 = registry.clone();
|
||||
let f = f.clone();
|
||||
let handle = thread::spawn(move || {
|
||||
registry.worker_thread(generator.as_mut(), f);
|
||||
});
|
||||
let handle = thread::spawn(move || {
|
||||
if let Err(e) = handle.join() {
|
||||
if let Some(e) = e.downcast_ref::<&'static str>() {
|
||||
eprintln!("Got an error: {}", e);
|
||||
} else {
|
||||
eprintln!("Got an unknown error: {:?}", e);
|
||||
}
|
||||
registry2.panicked.store(true, Ordering::SeqCst);
|
||||
registry2.wait_condvar.notify_all();
|
||||
}
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
(registry, handles)
|
||||
}
|
||||
|
||||
pub fn wait_tasks_complete(&self, handles: Vec<thread::JoinHandle<()>>) {
|
||||
{
|
||||
let mut count = self.task_count.lock();
|
||||
while *count != 0 {
|
||||
if self.panicked.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
self.wait_condvar.wait(&mut count);
|
||||
}
|
||||
}
|
||||
for _ in 0..self.thread_count {
|
||||
self.sender.send(None).unwrap();
|
||||
}
|
||||
{
|
||||
let mut count = self.task_count.lock();
|
||||
while *count != self.thread_count {
|
||||
if self.panicked.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
self.wait_condvar.wait(&mut count);
|
||||
}
|
||||
}
|
||||
for handle in handles {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
if self.panicked.load(Ordering::SeqCst) {
|
||||
panic!("tasks panicked");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_task(&self, task: CodeGenTask) {
|
||||
*self.task_count.lock() += 1;
|
||||
self.sender.send(Some(task)).unwrap();
|
||||
}
|
||||
|
||||
fn worker_thread<G: CodeGenerator>(&self, generator: &mut G, f: Arc<WithCall>) {
|
||||
let context = Context::create();
|
||||
let mut builder = context.create_builder();
|
||||
let module = context.create_module(generator.get_name());
|
||||
|
||||
let pass_builder = PassManagerBuilder::create();
|
||||
pass_builder.set_optimization_level(OptimizationLevel::Default);
|
||||
let passes = PassManager::create(&module);
|
||||
pass_builder.populate_function_pass_manager(&passes);
|
||||
|
||||
let mut errors = HashSet::new();
|
||||
while let Some(task) = self.receiver.recv().unwrap() {
|
||||
let tmp_module = context.create_module("tmp");
|
||||
match gen_func(&context, generator, self, builder, tmp_module, task) {
|
||||
Ok(result) => {
|
||||
builder = result.0;
|
||||
passes.run_on(&result.2);
|
||||
module.link_in_module(result.1).unwrap();
|
||||
}
|
||||
Err((old_builder, e)) => {
|
||||
builder = old_builder;
|
||||
errors.insert(e);
|
||||
}
|
||||
}
|
||||
*self.task_count.lock() -= 1;
|
||||
self.wait_condvar.notify_all();
|
||||
}
|
||||
if !errors.is_empty() {
|
||||
panic!("Codegen error: {}", errors.into_iter().sorted().join("\n----------\n"));
|
||||
}
|
||||
|
||||
let result = module.verify();
|
||||
if let Err(err) = result {
|
||||
println!("{}", module.print_to_string().to_str().unwrap());
|
||||
println!("{}", err);
|
||||
panic!()
|
||||
}
|
||||
f.run(&module);
|
||||
let mut lock = self.task_count.lock();
|
||||
*lock += 1;
|
||||
self.wait_condvar.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CodeGenTask {
|
||||
pub subst: Vec<(Type, ConcreteType)>,
|
||||
pub store: ConcreteTypeStore,
|
||||
pub symbol_name: String,
|
||||
pub signature: ConcreteType,
|
||||
pub body: Arc<Vec<Stmt<Option<Type>>>>,
|
||||
pub calls: Arc<HashMap<CodeLocation, CallId>>,
|
||||
pub unifier_index: usize,
|
||||
pub resolver: Arc<dyn SymbolResolver + Send + Sync>,
|
||||
pub id: usize,
|
||||
}
|
||||
|
||||
fn get_llvm_type<'ctx>(
|
||||
ctx: &'ctx Context,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
unifier: &mut Unifier,
|
||||
top_level: &TopLevelContext,
|
||||
type_cache: &mut HashMap<Type, BasicTypeEnum<'ctx>>,
|
||||
primitives: &PrimitiveStore,
|
||||
ty: Type,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use TypeEnum::*;
|
||||
// we assume the type cache should already contain primitive types,
|
||||
// and they should be passed by value instead of passing as pointer.
|
||||
type_cache.get(&unifier.get_representative(ty)).cloned().unwrap_or_else(|| {
|
||||
let ty_enum = unifier.get_ty(ty);
|
||||
let result = match &*ty_enum {
|
||||
TObj { obj_id, fields, .. } => {
|
||||
// check to avoid treating primitives other than Option as classes
|
||||
if obj_id.0 <= 10 {
|
||||
match (unifier.get_ty(ty).as_ref(), unifier.get_ty(primitives.option).as_ref())
|
||||
{
|
||||
(
|
||||
TypeEnum::TObj { obj_id, params, .. },
|
||||
TypeEnum::TObj { obj_id: opt_id, .. },
|
||||
) if *obj_id == *opt_id => {
|
||||
return get_llvm_type(
|
||||
ctx,
|
||||
generator,
|
||||
unifier,
|
||||
top_level,
|
||||
type_cache,
|
||||
primitives,
|
||||
*params.iter().next().unwrap().1,
|
||||
)
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into();
|
||||
}
|
||||
_ => unreachable!("must be option type"),
|
||||
}
|
||||
}
|
||||
// a struct with fields in the order of declaration
|
||||
let top_level_defs = top_level.definitions.read();
|
||||
let definition = top_level_defs.get(obj_id.0).unwrap();
|
||||
let ty = if let TopLevelDef::Class { name, fields: fields_list, .. } =
|
||||
&*definition.read()
|
||||
{
|
||||
let struct_type = ctx.opaque_struct_type(&name.to_string());
|
||||
type_cache.insert(unifier.get_representative(ty), struct_type.ptr_type(AddressSpace::Generic).into());
|
||||
let fields = fields_list
|
||||
.iter()
|
||||
.map(|f| {
|
||||
get_llvm_type(
|
||||
ctx,
|
||||
generator,
|
||||
unifier,
|
||||
top_level,
|
||||
type_cache,
|
||||
primitives,
|
||||
fields[&f.0].0,
|
||||
)
|
||||
})
|
||||
.collect_vec();
|
||||
struct_type.set_body(&fields, false);
|
||||
struct_type.ptr_type(AddressSpace::Generic).into()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
return ty;
|
||||
}
|
||||
TTuple { ty } => {
|
||||
// a struct with fields in the order present in the tuple
|
||||
let fields = ty
|
||||
.iter()
|
||||
.map(|ty| get_llvm_type(ctx, generator, unifier, top_level, type_cache, primitives, *ty))
|
||||
.collect_vec();
|
||||
ctx.struct_type(&fields, false).into()
|
||||
}
|
||||
TList { ty } => {
|
||||
// a struct with an integer and a pointer to an array
|
||||
let element_type =
|
||||
get_llvm_type(ctx, generator, unifier, top_level, type_cache, primitives, *ty);
|
||||
let fields = [
|
||||
element_type.ptr_type(AddressSpace::Generic).into(),
|
||||
generator.get_size_type(ctx).into(),
|
||||
];
|
||||
ctx.struct_type(&fields, false).ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
TVirtual { .. } => unimplemented!(),
|
||||
_ => unreachable!("{}", ty_enum.get_type_name()),
|
||||
};
|
||||
type_cache.insert(unifier.get_representative(ty), result);
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
fn need_sret<'ctx>(ctx: &'ctx Context, ty: BasicTypeEnum<'ctx>) -> bool {
|
||||
fn need_sret_impl<'ctx>(ctx: &'ctx Context, ty: BasicTypeEnum<'ctx>, maybe_large: bool) -> bool {
|
||||
match ty {
|
||||
BasicTypeEnum::IntType(_) | BasicTypeEnum::PointerType(_) => false,
|
||||
BasicTypeEnum::FloatType(_) if maybe_large => false,
|
||||
BasicTypeEnum::StructType(ty) if maybe_large && ty.count_fields() <= 2 =>
|
||||
ty.get_field_types().iter().any(|ty| need_sret_impl(ctx, *ty, false)),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
need_sret_impl(ctx, ty, true)
|
||||
}
|
||||
|
||||
pub fn gen_func<'ctx, G: CodeGenerator>(
|
||||
context: &'ctx Context,
|
||||
generator: &mut G,
|
||||
registry: &WorkerRegistry,
|
||||
builder: Builder<'ctx>,
|
||||
module: Module<'ctx>,
|
||||
task: CodeGenTask,
|
||||
) -> Result<(Builder<'ctx>, Module<'ctx>, FunctionValue<'ctx>), (Builder<'ctx>, String)> {
|
||||
let top_level_ctx = registry.top_level_ctx.clone();
|
||||
let static_value_store = registry.static_value_store.clone();
|
||||
let (mut unifier, primitives) = {
|
||||
let (unifier, primitives) = &top_level_ctx.unifiers.read()[task.unifier_index];
|
||||
(Unifier::from_shared_unifier(unifier), *primitives)
|
||||
};
|
||||
unifier.top_level = Some(top_level_ctx.clone());
|
||||
|
||||
let mut cache = HashMap::new();
|
||||
for (a, b) in task.subst.iter() {
|
||||
// this should be unification between variables and concrete types
|
||||
// and should not cause any problem...
|
||||
let b = task.store.to_unifier_type(&mut unifier, &primitives, *b, &mut cache);
|
||||
unifier
|
||||
.unify(*a, b)
|
||||
.or_else(|err| {
|
||||
if matches!(&*unifier.get_ty(*a), TypeEnum::TRigidVar { .. }) {
|
||||
unifier.replace_rigid_var(*a, b);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// rebuild primitive store with unique representatives
|
||||
let primitives = PrimitiveStore {
|
||||
int32: unifier.get_representative(primitives.int32),
|
||||
int64: unifier.get_representative(primitives.int64),
|
||||
uint32: unifier.get_representative(primitives.uint32),
|
||||
uint64: unifier.get_representative(primitives.uint64),
|
||||
float: unifier.get_representative(primitives.float),
|
||||
bool: unifier.get_representative(primitives.bool),
|
||||
none: unifier.get_representative(primitives.none),
|
||||
range: unifier.get_representative(primitives.range),
|
||||
str: unifier.get_representative(primitives.str),
|
||||
exception: unifier.get_representative(primitives.exception),
|
||||
option: unifier.get_representative(primitives.option),
|
||||
};
|
||||
|
||||
let mut type_cache: HashMap<_, _> = [
|
||||
(primitives.int32, context.i32_type().into()),
|
||||
(primitives.int64, context.i64_type().into()),
|
||||
(primitives.uint32, context.i32_type().into()),
|
||||
(primitives.uint64, context.i64_type().into()),
|
||||
(primitives.float, context.f64_type().into()),
|
||||
(primitives.bool, context.bool_type().into()),
|
||||
(primitives.str, {
|
||||
let str_type = context.opaque_struct_type("str");
|
||||
let fields = [
|
||||
context.i8_type().ptr_type(AddressSpace::Generic).into(),
|
||||
generator.get_size_type(context).into(),
|
||||
];
|
||||
str_type.set_body(&fields, false);
|
||||
str_type.into()
|
||||
}),
|
||||
(primitives.range, context.i32_type().array_type(3).ptr_type(AddressSpace::Generic).into()),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
type_cache.insert(primitives.exception, {
|
||||
let exception = context.opaque_struct_type("Exception");
|
||||
let int32 = context.i32_type().into();
|
||||
let int64 = context.i64_type().into();
|
||||
let str_ty = *type_cache.get(&primitives.str).unwrap();
|
||||
let fields = [int32, str_ty, int32, int32, str_ty, str_ty, int64, int64, int64];
|
||||
exception.set_body(&fields, false);
|
||||
exception.ptr_type(AddressSpace::Generic).into()
|
||||
});
|
||||
// NOTE: special handling of option cannot use this type cache since it contains type var,
|
||||
// handled inside get_llvm_type instead
|
||||
|
||||
let (args, ret) = if let ConcreteTypeEnum::TFunc { args, ret, .. } =
|
||||
task.store.get(task.signature)
|
||||
{
|
||||
(
|
||||
args.iter()
|
||||
.map(|arg| FuncArg {
|
||||
name: arg.name,
|
||||
ty: task.store.to_unifier_type(&mut unifier, &primitives, arg.ty, &mut cache),
|
||||
default_value: arg.default_value.clone(),
|
||||
})
|
||||
.collect_vec(),
|
||||
task.store.to_unifier_type(&mut unifier, &primitives, *ret, &mut cache),
|
||||
)
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
let ret_type = if unifier.unioned(ret, primitives.none) {
|
||||
None
|
||||
} else {
|
||||
Some(get_llvm_type(context, generator, &mut unifier, top_level_ctx.as_ref(), &mut type_cache, &primitives, ret))
|
||||
};
|
||||
|
||||
let has_sret = ret_type.map_or(false, |ty| need_sret(context, ty));
|
||||
let mut params = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
get_llvm_type(
|
||||
context,
|
||||
generator,
|
||||
&mut unifier,
|
||||
top_level_ctx.as_ref(),
|
||||
&mut type_cache,
|
||||
&primitives,
|
||||
arg.ty,
|
||||
)
|
||||
.into()
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
if has_sret {
|
||||
params.insert(0, ret_type.unwrap().ptr_type(AddressSpace::Generic).into());
|
||||
}
|
||||
|
||||
let fn_type = match ret_type {
|
||||
Some(ret_type) if !has_sret => ret_type.fn_type(¶ms, false),
|
||||
_ => context.void_type().fn_type(¶ms, false)
|
||||
};
|
||||
|
||||
let symbol = &task.symbol_name;
|
||||
let fn_val =
|
||||
module.get_function(symbol).unwrap_or_else(|| module.add_function(symbol, fn_type, None));
|
||||
|
||||
if let Some(personality) = &top_level_ctx.personality_symbol {
|
||||
let personality = module.get_function(personality).unwrap_or_else(|| {
|
||||
let ty = context.i32_type().fn_type(&[], true);
|
||||
module.add_function(personality, ty, None)
|
||||
});
|
||||
fn_val.set_personality_function(personality);
|
||||
}
|
||||
if has_sret {
|
||||
fn_val.add_attribute(AttributeLoc::Param(0),
|
||||
context.create_type_attribute(Attribute::get_named_enum_kind_id("sret"),
|
||||
ret_type.unwrap().as_any_type_enum()));
|
||||
}
|
||||
|
||||
let init_bb = context.append_basic_block(fn_val, "init");
|
||||
builder.position_at_end(init_bb);
|
||||
let body_bb = context.append_basic_block(fn_val, "body");
|
||||
|
||||
let mut var_assignment = HashMap::new();
|
||||
let offset = if has_sret { 1 } else { 0 };
|
||||
for (n, arg) in args.iter().enumerate() {
|
||||
let param = fn_val.get_nth_param((n as u32) + offset).unwrap();
|
||||
let alloca = builder.build_alloca(
|
||||
get_llvm_type(
|
||||
context,
|
||||
generator,
|
||||
&mut unifier,
|
||||
top_level_ctx.as_ref(),
|
||||
&mut type_cache,
|
||||
&primitives,
|
||||
arg.ty,
|
||||
),
|
||||
&arg.name.to_string(),
|
||||
);
|
||||
builder.build_store(alloca, param);
|
||||
var_assignment.insert(arg.name, (alloca, None, 0));
|
||||
}
|
||||
|
||||
let return_buffer = if has_sret {
|
||||
Some(fn_val.get_nth_param(0).unwrap().into_pointer_value())
|
||||
} else {
|
||||
fn_type.get_return_type().map(|v| builder.build_alloca(v, "$ret"))
|
||||
};
|
||||
|
||||
let static_values = {
|
||||
let store = registry.static_value_store.lock();
|
||||
store.store[task.id].clone()
|
||||
};
|
||||
for (k, v) in static_values.into_iter() {
|
||||
let (_, static_val, _) = var_assignment.get_mut(&args[k].name).unwrap();
|
||||
*static_val = Some(v);
|
||||
}
|
||||
|
||||
builder.build_unconditional_branch(body_bb);
|
||||
builder.position_at_end(body_bb);
|
||||
|
||||
let mut code_gen_context = CodeGenContext {
|
||||
ctx: context,
|
||||
resolver: task.resolver,
|
||||
top_level: top_level_ctx.as_ref(),
|
||||
calls: task.calls,
|
||||
loop_target: None,
|
||||
return_target: None,
|
||||
return_buffer,
|
||||
unwind_target: None,
|
||||
outer_catch_clauses: None,
|
||||
const_strings: Default::default(),
|
||||
registry,
|
||||
var_assignment,
|
||||
type_cache,
|
||||
primitives,
|
||||
init_bb,
|
||||
builder,
|
||||
module,
|
||||
unifier,
|
||||
static_value_store,
|
||||
need_sret: has_sret
|
||||
};
|
||||
|
||||
let mut err = None;
|
||||
for stmt in task.body.iter() {
|
||||
if let Err(e) = generator.gen_stmt(&mut code_gen_context, stmt) {
|
||||
err = Some(e);
|
||||
break;
|
||||
}
|
||||
if code_gen_context.is_terminated() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// after static analysis, only void functions can have no return at the end.
|
||||
if !code_gen_context.is_terminated() {
|
||||
code_gen_context.builder.build_return(None);
|
||||
}
|
||||
|
||||
let CodeGenContext { builder, module, .. } = code_gen_context;
|
||||
if let Some(e) = err {
|
||||
return Err((builder, e));
|
||||
}
|
||||
|
||||
Ok((builder, module, fn_val))
|
||||
}
|
993
nac3core/src/codegen/stmt.rs
Normal file
993
nac3core/src/codegen/stmt.rs
Normal file
@ -0,0 +1,993 @@
|
||||
use super::{
|
||||
super::symbol_resolver::ValueEnum,
|
||||
expr::destructure_range,
|
||||
irrt::{handle_slice_indices, list_slice_assignment},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
use crate::{
|
||||
codegen::expr::gen_binop_expr,
|
||||
toplevel::{DefinitionId, TopLevelDef},
|
||||
typecheck::typedef::{FunSignature, Type, TypeEnum},
|
||||
};
|
||||
use inkwell::{
|
||||
attributes::{Attribute, AttributeLoc},
|
||||
basic_block::BasicBlock,
|
||||
types::BasicTypeEnum,
|
||||
values::{BasicValue, BasicValueEnum, FunctionValue, PointerValue},
|
||||
IntPredicate::EQ,
|
||||
};
|
||||
use nac3parser::ast::{
|
||||
Constant, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind, StrRef,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
pub fn gen_var<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<PointerValue<'ctx>, String> {
|
||||
// put the alloca in init block
|
||||
let current = ctx.builder.get_insert_block().unwrap();
|
||||
// position before the last branching instruction...
|
||||
ctx.builder.position_before(&ctx.init_bb.get_last_instruction().unwrap());
|
||||
let ptr = ctx.builder.build_alloca(ty, "tmp");
|
||||
ctx.builder.position_at_end(current);
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
pub fn gen_store_target<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
pattern: &Expr<Option<Type>>,
|
||||
) -> Result<PointerValue<'ctx>, String> {
|
||||
// very similar to gen_expr, but we don't do an extra load at the end
|
||||
// and we flatten nested tuples
|
||||
Ok(match &pattern.node {
|
||||
ExprKind::Name { id, .. } => {
|
||||
ctx.var_assignment.get(id).map(|v| Ok(v.0) as Result<_, String>).unwrap_or_else(
|
||||
|| {
|
||||
let ptr_ty = ctx.get_llvm_type(generator, pattern.custom.unwrap());
|
||||
let ptr = generator.gen_var_alloc(ctx, ptr_ty)?;
|
||||
ctx.var_assignment.insert(*id, (ptr, None, 0));
|
||||
Ok(ptr)
|
||||
},
|
||||
)?
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. } => {
|
||||
let index = ctx.get_attr_index(value.custom.unwrap(), *attr);
|
||||
let val = generator.gen_expr(ctx, value)?.unwrap().to_basic_value_enum(ctx, generator)?;
|
||||
let ptr = if let BasicValueEnum::PointerValue(v) = val {
|
||||
v
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
unsafe {
|
||||
ctx.builder.build_in_bounds_gep(
|
||||
ptr,
|
||||
&[
|
||||
ctx.ctx.i32_type().const_zero(),
|
||||
ctx.ctx.i32_type().const_int(index as u64, false),
|
||||
],
|
||||
"attr",
|
||||
)
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let v = generator
|
||||
.gen_expr(ctx, value)?
|
||||
.unwrap()
|
||||
.to_basic_value_enum(ctx, generator)?
|
||||
.into_pointer_value();
|
||||
let index = generator
|
||||
.gen_expr(ctx, slice)?
|
||||
.unwrap()
|
||||
.to_basic_value_enum(ctx, generator)?
|
||||
.into_int_value();
|
||||
unsafe {
|
||||
let arr_ptr = ctx
|
||||
.build_gep_and_load(v, &[i32_type.const_zero(), i32_type.const_zero()])
|
||||
.into_pointer_value();
|
||||
ctx.builder.build_gep(arr_ptr, &[index], "loadarrgep")
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn gen_assign<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
target: &Expr<Option<Type>>,
|
||||
value: ValueEnum<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
match &target.node {
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
if let BasicValueEnum::StructValue(v) = value.to_basic_value_enum(ctx, generator)? {
|
||||
for (i, elt) in elts.iter().enumerate() {
|
||||
let v = ctx
|
||||
.builder
|
||||
.build_extract_value(v, u32::try_from(i).unwrap(), "struct_elem")
|
||||
.unwrap();
|
||||
generator.gen_assign(ctx, elt, v.into())?;
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value: ls, slice, .. }
|
||||
if matches!(&slice.node, ExprKind::Slice { .. }) =>
|
||||
{
|
||||
if let ExprKind::Slice { lower, upper, step } = &slice.node {
|
||||
let ls = generator
|
||||
.gen_expr(ctx, ls)?
|
||||
.unwrap()
|
||||
.to_basic_value_enum(ctx, generator)?
|
||||
.into_pointer_value();
|
||||
let (start, end, step) =
|
||||
handle_slice_indices(lower, upper, step, ctx, generator, ls)?;
|
||||
let value = value.to_basic_value_enum(ctx, generator)?.into_pointer_value();
|
||||
let ty =
|
||||
if let TypeEnum::TList { ty } = &*ctx.unifier.get_ty(target.custom.unwrap()) {
|
||||
ctx.get_llvm_type(generator, *ty)
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
let src_ind = handle_slice_indices(&None, &None, &None, ctx, generator, value)?;
|
||||
list_slice_assignment(
|
||||
ctx,
|
||||
generator.get_size_type(ctx.ctx),
|
||||
ty,
|
||||
ls,
|
||||
(start, end, step),
|
||||
value,
|
||||
src_ind,
|
||||
)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let ptr = generator.gen_store_target(ctx, target)?;
|
||||
if let ExprKind::Name { id, .. } = &target.node {
|
||||
let (_, static_value, counter) = ctx.var_assignment.get_mut(id).unwrap();
|
||||
*counter += 1;
|
||||
if let ValueEnum::Static(s) = &value {
|
||||
*static_value = Some(s.clone());
|
||||
}
|
||||
}
|
||||
let val = value.to_basic_value_enum(ctx, generator)?;
|
||||
ctx.builder.build_store(ptr, val);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_for<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String> {
|
||||
if let StmtKind::For { iter, target, body, orelse, .. } = &stmt.node {
|
||||
// var_assignment static values may be changed in another branch
|
||||
// if so, remove the static value as it may not be correct in this branch
|
||||
let var_assignment = ctx.var_assignment.clone();
|
||||
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let size_t = generator.get_size_type(ctx.ctx);
|
||||
let zero = int32.const_zero();
|
||||
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let test_bb = ctx.ctx.append_basic_block(current, "test");
|
||||
let body_bb = ctx.ctx.append_basic_block(current, "body");
|
||||
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
|
||||
// if there is no orelse, we just go to cont_bb
|
||||
let orelse_bb =
|
||||
if orelse.is_empty() { cont_bb } else { ctx.ctx.append_basic_block(current, "orelse") };
|
||||
// store loop bb information and restore it later
|
||||
let loop_bb = ctx.loop_target.replace((test_bb, cont_bb));
|
||||
|
||||
let iter_val = generator.gen_expr(ctx, iter)?.unwrap().to_basic_value_enum(ctx, generator)?;
|
||||
if ctx.unifier.unioned(iter.custom.unwrap(), ctx.primitives.range) {
|
||||
// setup
|
||||
let iter_val = iter_val.into_pointer_value();
|
||||
let i = generator.gen_store_target(ctx, target)?;
|
||||
let (start, end, step) = destructure_range(ctx, iter_val);
|
||||
ctx.builder.build_store(i, ctx.builder.build_int_sub(start, step, "start_init"));
|
||||
ctx.builder.build_unconditional_branch(test_bb);
|
||||
ctx.builder.position_at_end(test_bb);
|
||||
let sign = ctx.builder.build_int_compare(
|
||||
inkwell::IntPredicate::SGT,
|
||||
step,
|
||||
int32.const_zero(),
|
||||
"sign",
|
||||
);
|
||||
// add and test
|
||||
let tmp = ctx.builder.build_int_add(
|
||||
ctx.builder.build_load(i, "i").into_int_value(),
|
||||
step,
|
||||
"start_loop",
|
||||
);
|
||||
ctx.builder.build_store(i, tmp);
|
||||
// // if step > 0, continue when i < end
|
||||
let cmp1 = ctx.builder.build_int_compare(inkwell::IntPredicate::SLT, tmp, end, "cmp1");
|
||||
// if step < 0, continue when i > end
|
||||
let cmp2 = ctx.builder.build_int_compare(inkwell::IntPredicate::SGT, tmp, end, "cmp2");
|
||||
let pos = ctx.builder.build_and(sign, cmp1, "pos");
|
||||
let neg = ctx.builder.build_and(ctx.builder.build_not(sign, "inv"), cmp2, "neg");
|
||||
ctx.builder.build_conditional_branch(
|
||||
ctx.builder.build_or(pos, neg, "or"),
|
||||
body_bb,
|
||||
orelse_bb,
|
||||
);
|
||||
ctx.builder.position_at_end(body_bb);
|
||||
} else {
|
||||
let counter = generator.gen_var_alloc(ctx, size_t.into())?;
|
||||
// counter = -1
|
||||
ctx.builder.build_store(counter, size_t.const_int(u64::max_value(), true));
|
||||
let len = ctx
|
||||
.build_gep_and_load(
|
||||
iter_val.into_pointer_value(),
|
||||
&[zero, int32.const_int(1, false)],
|
||||
)
|
||||
.into_int_value();
|
||||
ctx.builder.build_unconditional_branch(test_bb);
|
||||
ctx.builder.position_at_end(test_bb);
|
||||
let tmp = ctx.builder.build_load(counter, "i").into_int_value();
|
||||
let tmp = ctx.builder.build_int_add(tmp, size_t.const_int(1, false), "inc");
|
||||
ctx.builder.build_store(counter, tmp);
|
||||
let cmp = ctx.builder.build_int_compare(inkwell::IntPredicate::SLT, tmp, len, "cmp");
|
||||
ctx.builder.build_conditional_branch(cmp, body_bb, orelse_bb);
|
||||
ctx.builder.position_at_end(body_bb);
|
||||
let arr_ptr = ctx
|
||||
.build_gep_and_load(iter_val.into_pointer_value(), &[zero, zero])
|
||||
.into_pointer_value();
|
||||
let val = ctx.build_gep_and_load(arr_ptr, &[tmp]);
|
||||
generator.gen_assign(ctx, target, val.into())?;
|
||||
}
|
||||
|
||||
gen_block(generator, ctx, body.iter())?;
|
||||
for (k, (_, _, counter)) in var_assignment.iter() {
|
||||
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
|
||||
if counter != counter2 {
|
||||
*static_val = None;
|
||||
}
|
||||
}
|
||||
if !ctx.is_terminated() {
|
||||
ctx.builder.build_unconditional_branch(test_bb);
|
||||
}
|
||||
if !orelse.is_empty() {
|
||||
ctx.builder.position_at_end(orelse_bb);
|
||||
gen_block(generator, ctx, orelse.iter())?;
|
||||
if !ctx.is_terminated() {
|
||||
ctx.builder.build_unconditional_branch(cont_bb);
|
||||
}
|
||||
}
|
||||
for (k, (_, _, counter)) in var_assignment.iter() {
|
||||
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
|
||||
if counter != counter2 {
|
||||
*static_val = None;
|
||||
}
|
||||
}
|
||||
ctx.builder.position_at_end(cont_bb);
|
||||
ctx.loop_target = loop_bb;
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_while<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String> {
|
||||
if let StmtKind::While { test, body, orelse, .. } = &stmt.node {
|
||||
// var_assignment static values may be changed in another branch
|
||||
// if so, remove the static value as it may not be correct in this branch
|
||||
let var_assignment = ctx.var_assignment.clone();
|
||||
|
||||
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let test_bb = ctx.ctx.append_basic_block(current, "test");
|
||||
let body_bb = ctx.ctx.append_basic_block(current, "body");
|
||||
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
|
||||
// if there is no orelse, we just go to cont_bb
|
||||
let orelse_bb =
|
||||
if orelse.is_empty() { cont_bb } else { ctx.ctx.append_basic_block(current, "orelse") };
|
||||
// store loop bb information and restore it later
|
||||
let loop_bb = ctx.loop_target.replace((test_bb, cont_bb));
|
||||
ctx.builder.build_unconditional_branch(test_bb);
|
||||
ctx.builder.position_at_end(test_bb);
|
||||
let test = generator.gen_expr(ctx, test)?.unwrap().to_basic_value_enum(ctx, generator)?;
|
||||
if let BasicValueEnum::IntValue(test) = test {
|
||||
ctx.builder.build_conditional_branch(test, body_bb, orelse_bb);
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
ctx.builder.position_at_end(body_bb);
|
||||
gen_block(generator, ctx, body.iter())?;
|
||||
for (k, (_, _, counter)) in var_assignment.iter() {
|
||||
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
|
||||
if counter != counter2 {
|
||||
*static_val = None;
|
||||
}
|
||||
}
|
||||
if !ctx.is_terminated() {
|
||||
ctx.builder.build_unconditional_branch(test_bb);
|
||||
}
|
||||
if !orelse.is_empty() {
|
||||
ctx.builder.position_at_end(orelse_bb);
|
||||
gen_block(generator, ctx, orelse.iter())?;
|
||||
if !ctx.is_terminated() {
|
||||
ctx.builder.build_unconditional_branch(cont_bb);
|
||||
}
|
||||
}
|
||||
for (k, (_, _, counter)) in var_assignment.iter() {
|
||||
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
|
||||
if counter != counter2 {
|
||||
*static_val = None;
|
||||
}
|
||||
}
|
||||
ctx.builder.position_at_end(cont_bb);
|
||||
ctx.loop_target = loop_bb;
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_if<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String> {
|
||||
if let StmtKind::If { test, body, orelse, .. } = &stmt.node {
|
||||
// var_assignment static values may be changed in another branch
|
||||
// if so, remove the static value as it may not be correct in this branch
|
||||
let var_assignment = ctx.var_assignment.clone();
|
||||
|
||||
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let test_bb = ctx.ctx.append_basic_block(current, "test");
|
||||
let body_bb = ctx.ctx.append_basic_block(current, "body");
|
||||
let mut cont_bb = None;
|
||||
// if there is no orelse, we just go to cont_bb
|
||||
let orelse_bb = if orelse.is_empty() {
|
||||
cont_bb = Some(ctx.ctx.append_basic_block(current, "cont"));
|
||||
cont_bb.unwrap()
|
||||
} else {
|
||||
ctx.ctx.append_basic_block(current, "orelse")
|
||||
};
|
||||
ctx.builder.build_unconditional_branch(test_bb);
|
||||
ctx.builder.position_at_end(test_bb);
|
||||
let test = generator.gen_expr(ctx, test)?.unwrap().to_basic_value_enum(ctx, generator)?;
|
||||
if let BasicValueEnum::IntValue(test) = test {
|
||||
ctx.builder.build_conditional_branch(test, body_bb, orelse_bb);
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
ctx.builder.position_at_end(body_bb);
|
||||
gen_block(generator, ctx, body.iter())?;
|
||||
for (k, (_, _, counter)) in var_assignment.iter() {
|
||||
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
|
||||
if counter != counter2 {
|
||||
*static_val = None;
|
||||
}
|
||||
}
|
||||
|
||||
if !ctx.is_terminated() {
|
||||
if cont_bb.is_none() {
|
||||
cont_bb = Some(ctx.ctx.append_basic_block(current, "cont"));
|
||||
}
|
||||
ctx.builder.build_unconditional_branch(cont_bb.unwrap());
|
||||
}
|
||||
if !orelse.is_empty() {
|
||||
ctx.builder.position_at_end(orelse_bb);
|
||||
gen_block(generator, ctx, orelse.iter())?;
|
||||
if !ctx.is_terminated() {
|
||||
if cont_bb.is_none() {
|
||||
cont_bb = Some(ctx.ctx.append_basic_block(current, "cont"));
|
||||
}
|
||||
ctx.builder.build_unconditional_branch(cont_bb.unwrap());
|
||||
}
|
||||
}
|
||||
if let Some(cont_bb) = cont_bb {
|
||||
ctx.builder.position_at_end(cont_bb);
|
||||
}
|
||||
for (k, (_, _, counter)) in var_assignment.iter() {
|
||||
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
|
||||
if counter != counter2 {
|
||||
*static_val = None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn final_proxy<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
target: BasicBlock<'ctx>,
|
||||
block: BasicBlock<'ctx>,
|
||||
final_data: &mut (PointerValue, Vec<BasicBlock<'ctx>>, Vec<BasicBlock<'ctx>>),
|
||||
) {
|
||||
let (final_state, final_targets, final_paths) = final_data;
|
||||
let prev = ctx.builder.get_insert_block().unwrap();
|
||||
ctx.builder.position_at_end(block);
|
||||
unsafe {
|
||||
ctx.builder.build_store(*final_state, target.get_address().unwrap());
|
||||
}
|
||||
ctx.builder.position_at_end(prev);
|
||||
final_targets.push(target);
|
||||
final_paths.push(block);
|
||||
}
|
||||
|
||||
pub fn get_builtins<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
symbol: &str,
|
||||
) -> FunctionValue<'ctx> {
|
||||
ctx.module.get_function(symbol).unwrap_or_else(|| {
|
||||
let ty = match symbol {
|
||||
"__nac3_raise" => ctx
|
||||
.ctx
|
||||
.void_type()
|
||||
.fn_type(&[ctx.get_llvm_type(generator, ctx.primitives.exception).into()], false),
|
||||
"__nac3_resume" => ctx.ctx.void_type().fn_type(&[], false),
|
||||
"__nac3_end_catch" => ctx.ctx.void_type().fn_type(&[], false),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
let fun = ctx.module.add_function(symbol, ty, None);
|
||||
if symbol == "__nac3_raise" || symbol == "__nac3_resume" {
|
||||
fun.add_attribute(
|
||||
AttributeLoc::Function,
|
||||
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("noreturn"), 1),
|
||||
);
|
||||
}
|
||||
fun
|
||||
})
|
||||
}
|
||||
|
||||
pub fn exn_constructor<'ctx, 'a>(
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
_fun: (&FunSignature, DefinitionId),
|
||||
mut args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> Result<Option<BasicValueEnum<'ctx>>, String> {
|
||||
let (zelf_ty, zelf) = obj.unwrap();
|
||||
let zelf = zelf.to_basic_value_enum(ctx, generator)?.into_pointer_value();
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let zero = int32.const_zero();
|
||||
let zelf_id = {
|
||||
if let TypeEnum::TObj { obj_id, .. } = &*ctx.unifier.get_ty(zelf_ty) {
|
||||
obj_id.0
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let defs = ctx.top_level.definitions.read();
|
||||
let def = defs[zelf_id].read();
|
||||
let zelf_name =
|
||||
if let TopLevelDef::Class { name, .. } = &*def { *name } else { unreachable!() };
|
||||
let exception_name = format!("{}:{}", ctx.resolver.get_exception_id(zelf_id), zelf_name);
|
||||
unsafe {
|
||||
let id_ptr = ctx.builder.build_in_bounds_gep(zelf, &[zero, zero], "exn.id");
|
||||
let id = ctx.resolver.get_string_id(&exception_name);
|
||||
ctx.builder.build_store(id_ptr, int32.const_int(id as u64, false));
|
||||
let empty_string = ctx.gen_const(generator, &Constant::Str("".into()), ctx.primitives.str);
|
||||
let ptr =
|
||||
ctx.builder.build_in_bounds_gep(zelf, &[zero, int32.const_int(5, false)], "exn.msg");
|
||||
let msg = if !args.is_empty() {
|
||||
args.remove(0).1.to_basic_value_enum(ctx, generator)?
|
||||
} else {
|
||||
empty_string
|
||||
};
|
||||
ctx.builder.build_store(ptr, msg);
|
||||
for i in [6, 7, 8].iter() {
|
||||
let value = if !args.is_empty() {
|
||||
args.remove(0).1.to_basic_value_enum(ctx, generator)?
|
||||
} else {
|
||||
ctx.ctx.i64_type().const_zero().into()
|
||||
};
|
||||
let ptr = ctx.builder.build_in_bounds_gep(
|
||||
zelf,
|
||||
&[zero, int32.const_int(*i, false)],
|
||||
"exn.param",
|
||||
);
|
||||
ctx.builder.build_store(ptr, value);
|
||||
}
|
||||
// set file, func to empty string
|
||||
for i in [1, 4].iter() {
|
||||
let ptr = ctx.builder.build_in_bounds_gep(
|
||||
zelf,
|
||||
&[zero, int32.const_int(*i, false)],
|
||||
"exn.str",
|
||||
);
|
||||
ctx.builder.build_store(ptr, empty_string);
|
||||
}
|
||||
// set ints to zero
|
||||
for i in [2, 3].iter() {
|
||||
let ptr = ctx.builder.build_in_bounds_gep(
|
||||
zelf,
|
||||
&[zero, int32.const_int(*i, false)],
|
||||
"exn.ints",
|
||||
);
|
||||
ctx.builder.build_store(ptr, zero);
|
||||
}
|
||||
}
|
||||
Ok(Some(zelf.into()))
|
||||
}
|
||||
|
||||
pub fn gen_raise<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
exception: Option<&BasicValueEnum<'ctx>>,
|
||||
loc: Location,
|
||||
) {
|
||||
if let Some(exception) = exception {
|
||||
unsafe {
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let zero = int32.const_zero();
|
||||
let exception = exception.into_pointer_value();
|
||||
let file_ptr = ctx.builder.build_in_bounds_gep(
|
||||
exception,
|
||||
&[zero, int32.const_int(1, false)],
|
||||
"file_ptr",
|
||||
);
|
||||
let filename = ctx.gen_string(generator, loc.file.0);
|
||||
ctx.builder.build_store(file_ptr, filename);
|
||||
let row_ptr = ctx.builder.build_in_bounds_gep(
|
||||
exception,
|
||||
&[zero, int32.const_int(2, false)],
|
||||
"row_ptr",
|
||||
);
|
||||
ctx.builder.build_store(row_ptr, int32.const_int(loc.row as u64, false));
|
||||
let col_ptr = ctx.builder.build_in_bounds_gep(
|
||||
exception,
|
||||
&[zero, int32.const_int(3, false)],
|
||||
"col_ptr",
|
||||
);
|
||||
ctx.builder.build_store(col_ptr, int32.const_int(loc.column as u64, false));
|
||||
|
||||
let current_fun = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let fun_name = ctx.gen_string(generator, current_fun.get_name().to_str().unwrap());
|
||||
let name_ptr = ctx.builder.build_in_bounds_gep(
|
||||
exception,
|
||||
&[zero, int32.const_int(4, false)],
|
||||
"name_ptr",
|
||||
);
|
||||
ctx.builder.build_store(name_ptr, fun_name);
|
||||
}
|
||||
|
||||
let raise = get_builtins(generator, ctx, "__nac3_raise");
|
||||
let exception = *exception;
|
||||
ctx.build_call_or_invoke(raise, &[exception], "raise");
|
||||
} else {
|
||||
let resume = get_builtins(generator, ctx, "__nac3_resume");
|
||||
ctx.build_call_or_invoke(resume, &[], "resume");
|
||||
}
|
||||
ctx.builder.build_unreachable();
|
||||
}
|
||||
|
||||
pub fn gen_try<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
target: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String> {
|
||||
if let StmtKind::Try { body, handlers, orelse, finalbody, .. } = &target.node {
|
||||
// if we need to generate anything related to exception, we must have personality defined
|
||||
let personality_symbol = ctx.top_level.personality_symbol.as_ref().unwrap();
|
||||
let personality = ctx.module.get_function(personality_symbol).unwrap_or_else(|| {
|
||||
let ty = ctx.ctx.i32_type().fn_type(&[], true);
|
||||
ctx.module.add_function(personality_symbol, ty, None)
|
||||
});
|
||||
let exception_type = ctx.get_llvm_type(generator, ctx.primitives.exception);
|
||||
let ptr_type = ctx.ctx.i8_type().ptr_type(inkwell::AddressSpace::Generic);
|
||||
let current_block = ctx.builder.get_insert_block().unwrap();
|
||||
let current_fun = current_block.get_parent().unwrap();
|
||||
let landingpad = ctx.ctx.append_basic_block(current_fun, "try.landingpad");
|
||||
let dispatcher = ctx.ctx.append_basic_block(current_fun, "try.dispatch");
|
||||
let mut dispatcher_end = dispatcher;
|
||||
ctx.builder.position_at_end(dispatcher);
|
||||
let exn = ctx.builder.build_phi(exception_type, "exn");
|
||||
ctx.builder.position_at_end(current_block);
|
||||
|
||||
let mut cleanup = None;
|
||||
let mut old_loop_target = None;
|
||||
let mut old_return = None;
|
||||
let mut final_data = None;
|
||||
let has_cleanup = !finalbody.is_empty();
|
||||
if has_cleanup {
|
||||
let final_state = generator.gen_var_alloc(ctx, ptr_type.into())?;
|
||||
final_data = Some((final_state, Vec::new(), Vec::new()));
|
||||
if let Some((continue_target, break_target)) = ctx.loop_target {
|
||||
let break_proxy = ctx.ctx.append_basic_block(current_fun, "try.break");
|
||||
let continue_proxy = ctx.ctx.append_basic_block(current_fun, "try.continue");
|
||||
final_proxy(ctx, break_target, break_proxy, final_data.as_mut().unwrap());
|
||||
final_proxy(ctx, continue_target, continue_proxy, final_data.as_mut().unwrap());
|
||||
old_loop_target = ctx.loop_target.replace((continue_proxy, break_proxy));
|
||||
}
|
||||
let return_proxy = ctx.ctx.append_basic_block(current_fun, "try.return");
|
||||
if let Some(return_target) = ctx.return_target {
|
||||
final_proxy(ctx, return_target, return_proxy, final_data.as_mut().unwrap());
|
||||
} else {
|
||||
let return_target = ctx.ctx.append_basic_block(current_fun, "try.return_target");
|
||||
ctx.builder.position_at_end(return_target);
|
||||
let return_value = ctx.return_buffer.map(|v| ctx.builder.build_load(v, "$ret"));
|
||||
ctx.builder.build_return(return_value.as_ref().map(|v| v as &dyn BasicValue));
|
||||
ctx.builder.position_at_end(current_block);
|
||||
final_proxy(ctx, return_target, return_proxy, final_data.as_mut().unwrap());
|
||||
}
|
||||
old_return = ctx.return_target.replace(return_proxy);
|
||||
cleanup = Some(ctx.ctx.append_basic_block(current_fun, "try.cleanup"));
|
||||
}
|
||||
|
||||
let mut clauses = Vec::new();
|
||||
let mut found_catch_all = false;
|
||||
for handler_node in handlers.iter() {
|
||||
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler_node.node;
|
||||
// none or Exception
|
||||
if type_.is_none()
|
||||
|| ctx
|
||||
.unifier
|
||||
.unioned(type_.as_ref().unwrap().custom.unwrap(), ctx.primitives.exception)
|
||||
{
|
||||
clauses.push(None);
|
||||
found_catch_all = true;
|
||||
break;
|
||||
} else {
|
||||
let type_ = type_.as_ref().unwrap();
|
||||
let exn_name = ctx.resolver.get_type_name(
|
||||
&ctx.top_level.definitions.read(),
|
||||
&mut ctx.unifier,
|
||||
type_.custom.unwrap(),
|
||||
);
|
||||
let obj_id = if let TypeEnum::TObj { obj_id, .. } = &*ctx.unifier.get_ty(type_.custom.unwrap()) {
|
||||
*obj_id
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
let exception_name = format!("{}:{}", ctx.resolver.get_exception_id(obj_id.0), exn_name);
|
||||
let exn_id = ctx.resolver.get_string_id(&exception_name);
|
||||
let exn_id_global =
|
||||
ctx.module.add_global(ctx.ctx.i32_type(), None, &format!("exn.{}", exn_id));
|
||||
exn_id_global.set_initializer(&ctx.ctx.i32_type().const_int(exn_id as u64, false));
|
||||
clauses.push(Some(exn_id_global.as_pointer_value().as_basic_value_enum()));
|
||||
}
|
||||
}
|
||||
let mut all_clauses = clauses.clone();
|
||||
if let Some(old_clauses) = &ctx.outer_catch_clauses {
|
||||
if !found_catch_all {
|
||||
all_clauses.extend_from_slice(&old_clauses.0)
|
||||
}
|
||||
}
|
||||
let old_clauses = ctx.outer_catch_clauses.replace((all_clauses, dispatcher, exn));
|
||||
let old_unwind = ctx.unwind_target.replace(landingpad);
|
||||
gen_block(generator, ctx, body.iter())?;
|
||||
if ctx.builder.get_insert_block().unwrap().get_terminator().is_none() {
|
||||
gen_block(generator, ctx, orelse.iter())?;
|
||||
}
|
||||
let body = ctx.builder.get_insert_block().unwrap();
|
||||
// reset old_clauses and old_unwind
|
||||
let (all_clauses, _, _) = ctx.outer_catch_clauses.take().unwrap();
|
||||
ctx.outer_catch_clauses = old_clauses;
|
||||
ctx.unwind_target = old_unwind;
|
||||
ctx.return_target = old_return;
|
||||
ctx.loop_target = old_loop_target;
|
||||
old_loop_target = None;
|
||||
|
||||
let old_unwind = if !finalbody.is_empty() {
|
||||
let final_landingpad = ctx.ctx.append_basic_block(current_fun, "try.catch.final");
|
||||
ctx.builder.position_at_end(final_landingpad);
|
||||
ctx.builder.build_landing_pad(
|
||||
ctx.ctx.struct_type(&[ptr_type.into(), exception_type], false),
|
||||
personality,
|
||||
&[],
|
||||
true,
|
||||
"try.catch.final",
|
||||
);
|
||||
ctx.builder.build_unconditional_branch(cleanup.unwrap());
|
||||
ctx.builder.position_at_end(body);
|
||||
ctx.unwind_target.replace(final_landingpad)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// run end_catch before continue/break/return
|
||||
let mut final_proxy_lambda =
|
||||
|ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
target: BasicBlock<'ctx>,
|
||||
block: BasicBlock<'ctx>| final_proxy(ctx, target, block, final_data.as_mut().unwrap());
|
||||
let mut redirect_lambda = |ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
target: BasicBlock<'ctx>,
|
||||
block: BasicBlock<'ctx>| {
|
||||
ctx.builder.position_at_end(block);
|
||||
ctx.builder.build_unconditional_branch(target);
|
||||
ctx.builder.position_at_end(body);
|
||||
};
|
||||
let redirect = if has_cleanup {
|
||||
&mut final_proxy_lambda
|
||||
as &mut dyn FnMut(&mut CodeGenContext<'ctx, 'a>, BasicBlock<'ctx>, BasicBlock<'ctx>)
|
||||
} else {
|
||||
&mut redirect_lambda
|
||||
as &mut dyn FnMut(&mut CodeGenContext<'ctx, 'a>, BasicBlock<'ctx>, BasicBlock<'ctx>)
|
||||
};
|
||||
let resume = get_builtins(generator, ctx, "__nac3_resume");
|
||||
let end_catch = get_builtins(generator, ctx, "__nac3_end_catch");
|
||||
if let Some((continue_target, break_target)) = ctx.loop_target.take() {
|
||||
let break_proxy = ctx.ctx.append_basic_block(current_fun, "try.break");
|
||||
let continue_proxy = ctx.ctx.append_basic_block(current_fun, "try.continue");
|
||||
ctx.builder.position_at_end(break_proxy);
|
||||
ctx.builder.build_call(end_catch, &[], "end_catch");
|
||||
ctx.builder.position_at_end(continue_proxy);
|
||||
ctx.builder.build_call(end_catch, &[], "end_catch");
|
||||
ctx.builder.position_at_end(body);
|
||||
redirect(ctx, break_target, break_proxy);
|
||||
redirect(ctx, continue_target, continue_proxy);
|
||||
ctx.loop_target = Some((continue_proxy, break_proxy));
|
||||
old_loop_target = Some((continue_target, break_target));
|
||||
}
|
||||
let return_proxy = ctx.ctx.append_basic_block(current_fun, "try.return");
|
||||
ctx.builder.position_at_end(return_proxy);
|
||||
ctx.builder.build_call(end_catch, &[], "end_catch");
|
||||
let return_target = ctx.return_target.take().unwrap_or_else(|| {
|
||||
let doreturn = ctx.ctx.append_basic_block(current_fun, "try.doreturn");
|
||||
ctx.builder.position_at_end(doreturn);
|
||||
let return_value = ctx.return_buffer.map(|v| ctx.builder.build_load(v, "$ret"));
|
||||
ctx.builder.build_return(return_value.as_ref().map(|v| v as &dyn BasicValue));
|
||||
doreturn
|
||||
});
|
||||
redirect(ctx, return_target, return_proxy);
|
||||
ctx.return_target = Some(return_proxy);
|
||||
old_return = Some(return_target);
|
||||
|
||||
let mut post_handlers = Vec::new();
|
||||
|
||||
let exnid = if !handlers.is_empty() {
|
||||
ctx.builder.position_at_end(dispatcher);
|
||||
unsafe {
|
||||
let zero = ctx.ctx.i32_type().const_zero();
|
||||
let exnid_ptr = ctx.builder.build_gep(
|
||||
exn.as_basic_value().into_pointer_value(),
|
||||
&[zero, zero],
|
||||
"exnidptr",
|
||||
);
|
||||
Some(ctx.builder.build_load(exnid_ptr, "exnid"))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for (handler_node, exn_type) in handlers.iter().zip(clauses.iter()) {
|
||||
let ExcepthandlerKind::ExceptHandler { type_, name, body } = &handler_node.node;
|
||||
let handler_bb = ctx.ctx.append_basic_block(current_fun, "try.handler");
|
||||
ctx.builder.position_at_end(handler_bb);
|
||||
if let Some(name) = name {
|
||||
let exn_ty = ctx.get_llvm_type(generator, type_.as_ref().unwrap().custom.unwrap());
|
||||
let exn_store = generator.gen_var_alloc(ctx, exn_ty)?;
|
||||
ctx.var_assignment.insert(*name, (exn_store, None, 0));
|
||||
ctx.builder.build_store(exn_store, exn.as_basic_value());
|
||||
}
|
||||
gen_block(generator, ctx, body.iter())?;
|
||||
let current = ctx.builder.get_insert_block().unwrap();
|
||||
// only need to call end catch if not terminated
|
||||
// otherwise, we already handled in return/break/continue/raise
|
||||
if current.get_terminator().is_none() {
|
||||
ctx.builder.build_call(end_catch, &[], "end_catch");
|
||||
}
|
||||
post_handlers.push(current);
|
||||
ctx.builder.position_at_end(dispatcher_end);
|
||||
if let Some(exn_type) = exn_type {
|
||||
let dispatcher_cont =
|
||||
ctx.ctx.append_basic_block(current_fun, "try.dispatcher_cont");
|
||||
let actual_id = exnid.unwrap().into_int_value();
|
||||
let expected_id = ctx
|
||||
.builder
|
||||
.build_load(exn_type.into_pointer_value(), "expected_id")
|
||||
.into_int_value();
|
||||
let result = ctx.builder.build_int_compare(EQ, actual_id, expected_id, "exncheck");
|
||||
ctx.builder.build_conditional_branch(result, handler_bb, dispatcher_cont);
|
||||
dispatcher_end = dispatcher_cont;
|
||||
} else {
|
||||
ctx.builder.build_unconditional_branch(handler_bb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.unwind_target = old_unwind;
|
||||
ctx.loop_target = old_loop_target;
|
||||
ctx.return_target = old_return;
|
||||
|
||||
ctx.builder.position_at_end(landingpad);
|
||||
let clauses: Vec<_> = if finalbody.is_empty() { &all_clauses } else { &clauses }
|
||||
.iter()
|
||||
.map(|v| v.unwrap_or(ptr_type.const_zero().into()))
|
||||
.collect();
|
||||
let landingpad_value = ctx
|
||||
.builder
|
||||
.build_landing_pad(
|
||||
ctx.ctx.struct_type(&[ptr_type.into(), exception_type], false),
|
||||
personality,
|
||||
&clauses,
|
||||
has_cleanup,
|
||||
"try.landingpad",
|
||||
)
|
||||
.into_struct_value();
|
||||
let exn_val = ctx.builder.build_extract_value(landingpad_value, 1, "exn").unwrap();
|
||||
ctx.builder.build_unconditional_branch(dispatcher);
|
||||
exn.add_incoming(&[(&exn_val, landingpad)]);
|
||||
|
||||
if dispatcher_end.get_terminator().is_none() {
|
||||
ctx.builder.position_at_end(dispatcher_end);
|
||||
if let Some(cleanup) = cleanup {
|
||||
ctx.builder.build_unconditional_branch(cleanup);
|
||||
} else if let Some((_, outer_dispatcher, phi)) = ctx.outer_catch_clauses {
|
||||
phi.add_incoming(&[(&exn_val, dispatcher_end)]);
|
||||
ctx.builder.build_unconditional_branch(outer_dispatcher);
|
||||
} else {
|
||||
ctx.build_call_or_invoke(resume, &[], "resume");
|
||||
ctx.builder.build_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
if finalbody.is_empty() {
|
||||
let tail = ctx.ctx.append_basic_block(current_fun, "try.tail");
|
||||
if body.get_terminator().is_none() {
|
||||
ctx.builder.position_at_end(body);
|
||||
ctx.builder.build_unconditional_branch(tail);
|
||||
}
|
||||
if matches!(cleanup, Some(cleanup) if cleanup.get_terminator().is_none()) {
|
||||
ctx.builder.position_at_end(cleanup.unwrap());
|
||||
ctx.builder.build_unconditional_branch(tail);
|
||||
}
|
||||
for post_handler in post_handlers {
|
||||
if post_handler.get_terminator().is_none() {
|
||||
ctx.builder.position_at_end(post_handler);
|
||||
ctx.builder.build_unconditional_branch(tail);
|
||||
}
|
||||
}
|
||||
ctx.builder.position_at_end(tail);
|
||||
} else {
|
||||
// exception path
|
||||
let cleanup = cleanup.unwrap();
|
||||
ctx.builder.position_at_end(cleanup);
|
||||
gen_block(generator, ctx, finalbody.iter())?;
|
||||
if !ctx.is_terminated() {
|
||||
ctx.build_call_or_invoke(resume, &[], "resume");
|
||||
ctx.builder.build_unreachable();
|
||||
}
|
||||
|
||||
// normal path
|
||||
let (final_state, mut final_targets, final_paths) = final_data.unwrap();
|
||||
let tail = ctx.ctx.append_basic_block(current_fun, "try.tail");
|
||||
final_targets.push(tail);
|
||||
let finalizer = ctx.ctx.append_basic_block(current_fun, "try.finally");
|
||||
ctx.builder.position_at_end(finalizer);
|
||||
gen_block(generator, ctx, finalbody.iter())?;
|
||||
if !ctx.is_terminated() {
|
||||
let dest = ctx.builder.build_load(final_state, "final_dest");
|
||||
ctx.builder.build_indirect_branch(dest, &final_targets);
|
||||
}
|
||||
for block in final_paths.iter() {
|
||||
if block.get_terminator().is_none() {
|
||||
ctx.builder.position_at_end(*block);
|
||||
ctx.builder.build_unconditional_branch(finalizer);
|
||||
}
|
||||
}
|
||||
for block in [body].iter().chain(post_handlers.iter()) {
|
||||
if block.get_terminator().is_none() {
|
||||
ctx.builder.position_at_end(*block);
|
||||
unsafe {
|
||||
ctx.builder.build_store(final_state, tail.get_address().unwrap());
|
||||
}
|
||||
ctx.builder.build_unconditional_branch(finalizer);
|
||||
}
|
||||
}
|
||||
ctx.builder.position_at_end(tail);
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_with<'ctx, 'a, G: CodeGenerator>(
|
||||
_: &mut G,
|
||||
_: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String> {
|
||||
// TODO: Implement with statement after finishing exceptions
|
||||
Err(format!("With statement with custom types is not yet supported (at {})", stmt.location))
|
||||
}
|
||||
|
||||
pub fn gen_return<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
value: &Option<Box<Expr<Option<Type>>>>,
|
||||
) -> Result<(), String> {
|
||||
let value = value
|
||||
.as_ref()
|
||||
.map(|v| generator.gen_expr(ctx, v).and_then(|v| v.unwrap().to_basic_value_enum(ctx, generator)))
|
||||
.transpose()?;
|
||||
if let Some(return_target) = ctx.return_target {
|
||||
if let Some(value) = value {
|
||||
ctx.builder.build_store(ctx.return_buffer.unwrap(), value);
|
||||
}
|
||||
ctx.builder.build_unconditional_branch(return_target);
|
||||
} else if ctx.need_sret {
|
||||
// sret
|
||||
ctx.builder.build_store(ctx.return_buffer.unwrap(), value.unwrap());
|
||||
ctx.builder.build_return(None);
|
||||
} else {
|
||||
let value = value.as_ref().map(|v| v as &dyn BasicValue);
|
||||
ctx.builder.build_return(value);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_stmt<'ctx, 'a, G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String> {
|
||||
match &stmt.node {
|
||||
StmtKind::Pass { .. } => {}
|
||||
StmtKind::Expr { value, .. } => {
|
||||
generator.gen_expr(ctx, value)?;
|
||||
}
|
||||
StmtKind::Return { value, .. } => {
|
||||
gen_return(generator, ctx, value)?;
|
||||
}
|
||||
StmtKind::AnnAssign { target, value, .. } => {
|
||||
if let Some(value) = value {
|
||||
let value = generator.gen_expr(ctx, value)?.unwrap();
|
||||
generator.gen_assign(ctx, target, value)?;
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
let value = generator.gen_expr(ctx, value)?.unwrap();
|
||||
for target in targets.iter() {
|
||||
generator.gen_assign(ctx, target, value.clone())?;
|
||||
}
|
||||
}
|
||||
StmtKind::Continue { .. } => {
|
||||
ctx.builder.build_unconditional_branch(ctx.loop_target.unwrap().0);
|
||||
}
|
||||
StmtKind::Break { .. } => {
|
||||
ctx.builder.build_unconditional_branch(ctx.loop_target.unwrap().1);
|
||||
}
|
||||
StmtKind::If { .. } => generator.gen_if(ctx, stmt)?,
|
||||
StmtKind::While { .. } => generator.gen_while(ctx, stmt)?,
|
||||
StmtKind::For { .. } => generator.gen_for(ctx, stmt)?,
|
||||
StmtKind::With { .. } => generator.gen_with(ctx, stmt)?,
|
||||
StmtKind::AugAssign { target, op, value, .. } => {
|
||||
let value = gen_binop_expr(generator, ctx, target, op, value)?;
|
||||
generator.gen_assign(ctx, target, value)?;
|
||||
}
|
||||
StmtKind::Try { .. } => gen_try(generator, ctx, stmt)?,
|
||||
StmtKind::Raise { exc, .. } => {
|
||||
if let Some(exc) = exc {
|
||||
let exc =
|
||||
generator.gen_expr(ctx, exc)?.unwrap().to_basic_value_enum(ctx, generator)?;
|
||||
gen_raise(generator, ctx, Some(&exc), stmt.location);
|
||||
} else {
|
||||
gen_raise(generator, ctx, None, stmt.location);
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gen_block<'ctx, 'a, 'b, G: CodeGenerator, I: Iterator<Item = &'b Stmt<Option<Type>>>>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
stmts: I,
|
||||
) -> Result<(), String> {
|
||||
for stmt in stmts {
|
||||
generator.gen_stmt(ctx, stmt)?;
|
||||
if ctx.is_terminated() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
355
nac3core/src/codegen/test.rs
Normal file
355
nac3core/src/codegen/test.rs
Normal file
@ -0,0 +1,355 @@
|
||||
use crate::{
|
||||
codegen::{
|
||||
concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenTask, DefaultCodeGenerator,
|
||||
WithCall, WorkerRegistry,
|
||||
},
|
||||
symbol_resolver::{SymbolResolver, ValueEnum},
|
||||
toplevel::{
|
||||
composer::TopLevelComposer, DefinitionId, FunInstance, TopLevelContext, TopLevelDef,
|
||||
},
|
||||
typecheck::{
|
||||
type_inferencer::{FunctionData, Inferencer, PrimitiveStore},
|
||||
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
|
||||
},
|
||||
};
|
||||
use indoc::indoc;
|
||||
use nac3parser::{
|
||||
ast::{fold::Fold, StrRef},
|
||||
parser::parse_program,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
struct Resolver {
|
||||
id_to_type: HashMap<StrRef, Type>,
|
||||
id_to_def: RwLock<HashMap<StrRef, DefinitionId>>,
|
||||
class_names: HashMap<StrRef, Type>,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub fn add_id_def(&self, id: StrRef, def: DefinitionId) {
|
||||
self.id_to_def.write().insert(id, def);
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolResolver for Resolver {
|
||||
fn get_default_param_value(
|
||||
&self,
|
||||
_: &nac3parser::ast::Expr,
|
||||
) -> Option<crate::symbol_resolver::SymbolValue> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_symbol_type(
|
||||
&self,
|
||||
_: &mut Unifier,
|
||||
_: &[Arc<RwLock<TopLevelDef>>],
|
||||
_: &PrimitiveStore,
|
||||
str: StrRef,
|
||||
) -> Result<Type, String> {
|
||||
self.id_to_type.get(&str).cloned().ok_or_else(|| format!("cannot find symbol `{}`", str))
|
||||
}
|
||||
|
||||
fn get_symbol_value<'ctx, 'a>(
|
||||
&self,
|
||||
_: StrRef,
|
||||
_: &mut CodeGenContext<'ctx, 'a>,
|
||||
) -> Option<ValueEnum<'ctx>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
|
||||
self.id_to_def
|
||||
.read()
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("cannot find symbol `{}`", id))
|
||||
}
|
||||
|
||||
fn get_string_id(&self, _: &str) -> i32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_exception_id(&self, tyid: usize) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primitives() {
|
||||
let source = indoc! { "
|
||||
c = a + b
|
||||
d = a if c == 1 else 0
|
||||
return d
|
||||
"};
|
||||
let statements = parse_program(source, Default::default()).unwrap();
|
||||
|
||||
let composer: TopLevelComposer = Default::default();
|
||||
let mut unifier = composer.unifier.clone();
|
||||
let primitives = composer.primitives_ty;
|
||||
let top_level = Arc::new(composer.make_top_level_context());
|
||||
unifier.top_level = Some(top_level.clone());
|
||||
|
||||
let resolver = Arc::new(Resolver {
|
||||
id_to_type: HashMap::new(),
|
||||
id_to_def: RwLock::new(HashMap::new()),
|
||||
class_names: Default::default(),
|
||||
}) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
|
||||
let signature = FunSignature {
|
||||
args: vec![
|
||||
FuncArg { name: "a".into(), ty: primitives.int32, default_value: None },
|
||||
FuncArg { name: "b".into(), ty: primitives.int32, default_value: None },
|
||||
],
|
||||
ret: primitives.int32,
|
||||
vars: HashMap::new(),
|
||||
};
|
||||
|
||||
let mut store = ConcreteTypeStore::new();
|
||||
let mut cache = HashMap::new();
|
||||
let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache);
|
||||
let signature = store.add_cty(signature);
|
||||
|
||||
let mut function_data = FunctionData {
|
||||
resolver: resolver.clone(),
|
||||
bound_variables: Vec::new(),
|
||||
return_type: Some(primitives.int32),
|
||||
};
|
||||
let mut virtual_checks = Vec::new();
|
||||
let mut calls = HashMap::new();
|
||||
let mut identifiers: HashSet<_> = ["a".into(), "b".into()].iter().cloned().collect();
|
||||
let mut inferencer = Inferencer {
|
||||
top_level: &top_level,
|
||||
function_data: &mut function_data,
|
||||
unifier: &mut unifier,
|
||||
variable_mapping: Default::default(),
|
||||
primitives: &primitives,
|
||||
virtual_checks: &mut virtual_checks,
|
||||
calls: &mut calls,
|
||||
defined_identifiers: identifiers.clone(),
|
||||
in_handler: false,
|
||||
};
|
||||
inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32);
|
||||
inferencer.variable_mapping.insert("b".into(), inferencer.primitives.int32);
|
||||
|
||||
let statements = statements
|
||||
.into_iter()
|
||||
.map(|v| inferencer.fold_stmt(v))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
inferencer.check_block(&statements, &mut identifiers).unwrap();
|
||||
let top_level = Arc::new(TopLevelContext {
|
||||
definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))),
|
||||
unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])),
|
||||
personality_symbol: None,
|
||||
});
|
||||
|
||||
let task = CodeGenTask {
|
||||
subst: Default::default(),
|
||||
symbol_name: "testing".into(),
|
||||
body: Arc::new(statements),
|
||||
unifier_index: 0,
|
||||
calls: Arc::new(calls),
|
||||
resolver,
|
||||
store,
|
||||
signature,
|
||||
id: 0,
|
||||
};
|
||||
let f = Arc::new(WithCall::new(Box::new(|module| {
|
||||
// the following IR is equivalent to
|
||||
// ```
|
||||
// ; ModuleID = 'test.ll'
|
||||
// source_filename = "test"
|
||||
//
|
||||
// ; Function Attrs: norecurse nounwind readnone
|
||||
// define i32 @testing(i32 %0, i32 %1) local_unnamed_addr #0 {
|
||||
// init:
|
||||
// %add = add i32 %1, %0
|
||||
// %cmp = icmp eq i32 %add, 1
|
||||
// %ifexpr = select i1 %cmp, i32 %0, i32 0
|
||||
// ret i32 %ifexpr
|
||||
// }
|
||||
//
|
||||
// attributes #0 = { norecurse nounwind readnone }
|
||||
// ```
|
||||
// after O2 optimization
|
||||
|
||||
let expected = indoc! {"
|
||||
; ModuleID = 'test'
|
||||
source_filename = \"test\"
|
||||
|
||||
define i32 @testing(i32 %0, i32 %1) {
|
||||
init:
|
||||
%add = add i32 %0, %1
|
||||
%cmp = icmp eq i32 %add, 1
|
||||
%ifexpr = select i1 %cmp, i32 %0, i32 0
|
||||
ret i32 %ifexpr
|
||||
}
|
||||
"}
|
||||
.trim();
|
||||
assert_eq!(expected, module.print_to_string().to_str().unwrap().trim());
|
||||
})));
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, f);
|
||||
registry.add_task(task);
|
||||
registry.wait_tasks_complete(handles);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_call() {
|
||||
let source_1 = indoc! { "
|
||||
a = foo(a)
|
||||
return a * 2
|
||||
"};
|
||||
let statements_1 = parse_program(source_1, Default::default()).unwrap();
|
||||
|
||||
let source_2 = indoc! { "
|
||||
return a + 1
|
||||
"};
|
||||
let statements_2 = parse_program(source_2, Default::default()).unwrap();
|
||||
|
||||
let composer: TopLevelComposer = Default::default();
|
||||
let mut unifier = composer.unifier.clone();
|
||||
let primitives = composer.primitives_ty;
|
||||
let top_level = Arc::new(composer.make_top_level_context());
|
||||
unifier.top_level = Some(top_level.clone());
|
||||
|
||||
let signature = FunSignature {
|
||||
args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }],
|
||||
ret: primitives.int32,
|
||||
vars: HashMap::new(),
|
||||
};
|
||||
let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone()));
|
||||
let mut store = ConcreteTypeStore::new();
|
||||
let mut cache = HashMap::new();
|
||||
let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache);
|
||||
let signature = store.add_cty(signature);
|
||||
|
||||
let foo_id = top_level.definitions.read().len();
|
||||
top_level.definitions.write().push(Arc::new(RwLock::new(TopLevelDef::Function {
|
||||
name: "foo".to_string(),
|
||||
simple_name: "foo".into(),
|
||||
signature: fun_ty,
|
||||
var_id: vec![],
|
||||
instance_to_stmt: HashMap::new(),
|
||||
instance_to_symbol: HashMap::new(),
|
||||
resolver: None,
|
||||
codegen_callback: None,
|
||||
loc: None,
|
||||
})));
|
||||
|
||||
let resolver = Resolver {
|
||||
id_to_type: HashMap::new(),
|
||||
id_to_def: RwLock::new(HashMap::new()),
|
||||
class_names: Default::default(),
|
||||
};
|
||||
resolver.add_id_def("foo".into(), DefinitionId(foo_id));
|
||||
let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
if let TopLevelDef::Function { resolver: r, .. } =
|
||||
&mut *top_level.definitions.read()[foo_id].write()
|
||||
{
|
||||
*r = Some(resolver.clone());
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
|
||||
let mut function_data = FunctionData {
|
||||
resolver: resolver.clone(),
|
||||
bound_variables: Vec::new(),
|
||||
return_type: Some(primitives.int32),
|
||||
};
|
||||
let mut virtual_checks = Vec::new();
|
||||
let mut calls = HashMap::new();
|
||||
let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].iter().cloned().collect();
|
||||
let mut inferencer = Inferencer {
|
||||
top_level: &top_level,
|
||||
function_data: &mut function_data,
|
||||
unifier: &mut unifier,
|
||||
variable_mapping: Default::default(),
|
||||
primitives: &primitives,
|
||||
virtual_checks: &mut virtual_checks,
|
||||
calls: &mut calls,
|
||||
defined_identifiers: identifiers.clone(),
|
||||
in_handler: false,
|
||||
};
|
||||
inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32);
|
||||
inferencer.variable_mapping.insert("foo".into(), fun_ty);
|
||||
|
||||
let statements_1 = statements_1
|
||||
.into_iter()
|
||||
.map(|v| inferencer.fold_stmt(v))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
let calls1 = inferencer.calls.clone();
|
||||
inferencer.calls.clear();
|
||||
|
||||
let statements_2 = statements_2
|
||||
.into_iter()
|
||||
.map(|v| inferencer.fold_stmt(v))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
if let TopLevelDef::Function { instance_to_stmt, .. } =
|
||||
&mut *top_level.definitions.read()[foo_id].write()
|
||||
{
|
||||
instance_to_stmt.insert(
|
||||
"".to_string(),
|
||||
FunInstance {
|
||||
body: Arc::new(statements_2),
|
||||
calls: Arc::new(inferencer.calls.clone()),
|
||||
subst: Default::default(),
|
||||
unifier_id: 0,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
inferencer.check_block(&statements_1, &mut identifiers).unwrap();
|
||||
let top_level = Arc::new(TopLevelContext {
|
||||
definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))),
|
||||
unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])),
|
||||
personality_symbol: None,
|
||||
});
|
||||
|
||||
let task = CodeGenTask {
|
||||
subst: Default::default(),
|
||||
symbol_name: "testing".to_string(),
|
||||
body: Arc::new(statements_1),
|
||||
calls: Arc::new(calls1),
|
||||
unifier_index: 0,
|
||||
resolver,
|
||||
signature,
|
||||
store,
|
||||
id: 0,
|
||||
};
|
||||
let f = Arc::new(WithCall::new(Box::new(|module| {
|
||||
let expected = indoc! {"
|
||||
; ModuleID = 'test'
|
||||
source_filename = \"test\"
|
||||
|
||||
define i32 @testing(i32 %0) {
|
||||
init:
|
||||
%call = call i32 @foo.0(i32 %0)
|
||||
%mul = mul i32 %call, 2
|
||||
ret i32 %mul
|
||||
}
|
||||
|
||||
define i32 @foo.0(i32 %0) {
|
||||
init:
|
||||
%add = add i32 %0, 1
|
||||
ret i32 %add
|
||||
}
|
||||
"}
|
||||
.trim();
|
||||
assert_eq!(expected, module.print_to_string().to_str().unwrap().trim());
|
||||
})));
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, f);
|
||||
registry.add_task(task);
|
||||
registry.wait_tasks_complete(handles);
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
use super::TopLevelContext;
|
||||
use crate::typedef::*;
|
||||
use std::boxed::Box;
|
||||
use std::collections::HashMap;
|
||||
|
||||
struct ContextStack<'a> {
|
||||
/// stack level, starts from 0
|
||||
level: u32,
|
||||
/// stack of variable definitions containing (id, def, level) where `def` is the original
|
||||
/// definition in `level-1`.
|
||||
var_defs: Vec<(usize, VarDef<'a>, u32)>,
|
||||
/// stack of symbol definitions containing (name, level) where `level` is the smallest level
|
||||
/// where the name is assigned a value
|
||||
sym_def: Vec<(&'a str, u32)>,
|
||||
}
|
||||
|
||||
pub struct InferenceContext<'a> {
|
||||
/// top level context
|
||||
top_level: TopLevelContext<'a>,
|
||||
|
||||
/// list of primitive instances
|
||||
primitives: Vec<Type>,
|
||||
/// list of variable instances
|
||||
variables: Vec<Type>,
|
||||
/// identifier to (type, readable) mapping.
|
||||
/// an identifier might be defined earlier but has no value (for some code path), thus not
|
||||
/// readable.
|
||||
sym_table: HashMap<&'a str, (Type, bool)>,
|
||||
/// resolution function reference, that may resolve unbounded identifiers to some type
|
||||
resolution_fn: Box<dyn FnMut(&str) -> Result<Type, String>>,
|
||||
/// stack
|
||||
stack: ContextStack<'a>,
|
||||
}
|
||||
|
||||
// non-trivial implementations here
|
||||
impl<'a> InferenceContext<'a> {
|
||||
/// return a new `InferenceContext` from `TopLevelContext` and resolution function.
|
||||
pub fn new(
|
||||
top_level: TopLevelContext,
|
||||
resolution_fn: Box<dyn FnMut(&str) -> Result<Type, String>>,
|
||||
) -> InferenceContext {
|
||||
let primitives = (0..top_level.primitive_defs.len())
|
||||
.map(|v| TypeEnum::PrimitiveType(PrimitiveId(v)).into())
|
||||
.collect();
|
||||
let variables = (0..top_level.var_defs.len())
|
||||
.map(|v| TypeEnum::TypeVariable(VariableId(v)).into())
|
||||
.collect();
|
||||
InferenceContext {
|
||||
top_level,
|
||||
primitives,
|
||||
variables,
|
||||
sym_table: HashMap::new(),
|
||||
resolution_fn,
|
||||
stack: ContextStack {
|
||||
level: 0,
|
||||
var_defs: Vec::new(),
|
||||
sym_def: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// execute the function with new scope.
|
||||
/// variable assignment would be limited within the scope (not readable outside), and type
|
||||
/// variable type guard would be limited within the scope.
|
||||
/// returns the list of variables assigned within the scope, and the result of the function
|
||||
pub fn with_scope<F, R>(&mut self, f: F) -> (Vec<&'a str>, R)
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.stack.level += 1;
|
||||
let result = f(self);
|
||||
self.stack.level -= 1;
|
||||
while !self.stack.var_defs.is_empty() {
|
||||
let (_, _, level) = self.stack.var_defs.last().unwrap();
|
||||
if *level > self.stack.level {
|
||||
let (id, def, _) = self.stack.var_defs.pop().unwrap();
|
||||
self.top_level.var_defs[id] = def;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut poped_names = Vec::new();
|
||||
while !self.stack.sym_def.is_empty() {
|
||||
let (_, level) = self.stack.sym_def.last().unwrap();
|
||||
if *level > self.stack.level {
|
||||
let (name, _) = self.stack.sym_def.pop().unwrap();
|
||||
self.sym_table.remove(name).unwrap();
|
||||
poped_names.push(name);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
(poped_names, result)
|
||||
}
|
||||
|
||||
/// assign a type to an identifier.
|
||||
/// may return error if the identifier was defined but with different type
|
||||
pub fn assign(&mut self, name: &'a str, ty: Type) -> Result<Type, String> {
|
||||
if let Some((t, x)) = self.sym_table.get_mut(name) {
|
||||
if t == &ty {
|
||||
if !*x {
|
||||
self.stack.sym_def.push((name, self.stack.level));
|
||||
}
|
||||
*x = true;
|
||||
Ok(ty)
|
||||
} else {
|
||||
Err("different types".into())
|
||||
}
|
||||
} else {
|
||||
self.stack.sym_def.push((name, self.stack.level));
|
||||
self.sym_table.insert(name, (ty.clone(), true));
|
||||
Ok(ty)
|
||||
}
|
||||
}
|
||||
|
||||
/// check if an identifier is already defined
|
||||
pub fn defined(&self, name: &str) -> bool {
|
||||
self.sym_table.get(name).is_some()
|
||||
}
|
||||
|
||||
/// get the type of an identifier
|
||||
/// may return error if the identifier is not defined, and cannot be resolved with the
|
||||
/// resolution function.
|
||||
pub fn resolve(&mut self, name: &str) -> Result<Type, String> {
|
||||
if let Some((t, x)) = self.sym_table.get(name) {
|
||||
if *x {
|
||||
Ok(t.clone())
|
||||
} else {
|
||||
Err("may not have value".into())
|
||||
}
|
||||
} else {
|
||||
self.resolution_fn.as_mut()(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// restrict the bound of a type variable by replacing its definition.
|
||||
/// used for implementing type guard
|
||||
pub fn restrict(&mut self, id: VariableId, mut def: VarDef<'a>) {
|
||||
std::mem::swap(self.top_level.var_defs.get_mut(id.0).unwrap(), &mut def);
|
||||
self.stack.var_defs.push((id.0, def, self.stack.level));
|
||||
}
|
||||
}
|
||||
|
||||
// trivial getters:
|
||||
impl<'a> InferenceContext<'a> {
|
||||
pub fn get_primitive(&self, id: PrimitiveId) -> Type {
|
||||
self.primitives.get(id.0).unwrap().clone()
|
||||
}
|
||||
pub fn get_variable(&self, id: VariableId) -> Type {
|
||||
self.variables.get(id.0).unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn get_fn_def(&self, name: &str) -> Option<&FnDef> {
|
||||
self.top_level.fn_table.get(name)
|
||||
}
|
||||
pub fn get_primitive_def(&self, id: PrimitiveId) -> &TypeDef {
|
||||
self.top_level.primitive_defs.get(id.0).unwrap()
|
||||
}
|
||||
pub fn get_class_def(&self, id: ClassId) -> &ClassDef {
|
||||
self.top_level.class_defs.get(id.0).unwrap()
|
||||
}
|
||||
pub fn get_parametric_def(&self, id: ParamId) -> &ParametricDef {
|
||||
self.top_level.parametric_defs.get(id.0).unwrap()
|
||||
}
|
||||
pub fn get_variable_def(&self, id: VariableId) -> &VarDef {
|
||||
self.top_level.var_defs.get(id.0).unwrap()
|
||||
}
|
||||
pub fn get_type(&self, name: &str) -> Option<Type> {
|
||||
self.top_level.get_type(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeEnum {
|
||||
pub fn subst(&self, map: &HashMap<VariableId, Type>) -> TypeEnum {
|
||||
match self {
|
||||
TypeEnum::TypeVariable(id) => map.get(id).map(|v| v.as_ref()).unwrap_or(self).clone(),
|
||||
TypeEnum::ParametricType(id, params) => TypeEnum::ParametricType(
|
||||
*id,
|
||||
params
|
||||
.iter()
|
||||
.map(|v| v.as_ref().subst(map).into())
|
||||
.collect(),
|
||||
),
|
||||
_ => self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_subst(&self, ctx: &InferenceContext) -> HashMap<VariableId, Type> {
|
||||
match self {
|
||||
TypeEnum::ParametricType(id, params) => {
|
||||
let vars = &ctx.get_parametric_def(*id).params;
|
||||
vars.iter()
|
||||
.zip(params)
|
||||
.map(|(v, p)| (*v, p.as_ref().clone().into()))
|
||||
.collect()
|
||||
}
|
||||
// if this proves to be slow, we can use option type
|
||||
_ => HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_base<'b: 'a, 'a>(&'a self, ctx: &'b InferenceContext) -> Option<&'b TypeDef> {
|
||||
match self {
|
||||
TypeEnum::PrimitiveType(id) => Some(ctx.get_primitive_def(*id)),
|
||||
TypeEnum::ClassType(id) | TypeEnum::VirtualClassType(id) => {
|
||||
Some(&ctx.get_class_def(*id).base)
|
||||
}
|
||||
TypeEnum::ParametricType(id, _) => Some(&ctx.get_parametric_def(*id).base),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
mod inference_context;
|
||||
mod top_level_context;
|
||||
pub use inference_context::InferenceContext;
|
||||
pub use top_level_context::TopLevelContext;
|
@ -1,136 +0,0 @@
|
||||
use crate::typedef::*;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Structure for storing top-level type definitions.
|
||||
/// Used for collecting type signature from source code.
|
||||
/// Can be converted to `InferenceContext` for type inference in functions.
|
||||
pub struct TopLevelContext<'a> {
|
||||
/// List of primitive definitions.
|
||||
pub(super) primitive_defs: Vec<TypeDef<'a>>,
|
||||
/// List of class definitions.
|
||||
pub(super) class_defs: Vec<ClassDef<'a>>,
|
||||
/// List of parametric type definitions.
|
||||
pub(super) parametric_defs: Vec<ParametricDef<'a>>,
|
||||
/// List of type variable definitions.
|
||||
pub(super) var_defs: Vec<VarDef<'a>>,
|
||||
/// Function name to signature mapping.
|
||||
pub(super) fn_table: HashMap<&'a str, FnDef>,
|
||||
/// Type name to type mapping.
|
||||
pub(super) sym_table: HashMap<&'a str, Type>,
|
||||
|
||||
primitives: Vec<Type>,
|
||||
variables: Vec<Type>,
|
||||
}
|
||||
|
||||
impl<'a> TopLevelContext<'a> {
|
||||
pub fn new(primitive_defs: Vec<TypeDef<'a>>) -> TopLevelContext {
|
||||
let mut sym_table = HashMap::new();
|
||||
let mut primitives = Vec::new();
|
||||
for (i, t) in primitive_defs.iter().enumerate() {
|
||||
primitives.push(TypeEnum::PrimitiveType(PrimitiveId(i)).into());
|
||||
sym_table.insert(t.name, TypeEnum::PrimitiveType(PrimitiveId(i)).into());
|
||||
}
|
||||
TopLevelContext {
|
||||
primitive_defs,
|
||||
class_defs: Vec::new(),
|
||||
parametric_defs: Vec::new(),
|
||||
var_defs: Vec::new(),
|
||||
fn_table: HashMap::new(),
|
||||
sym_table,
|
||||
primitives,
|
||||
variables: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_class(&mut self, def: ClassDef<'a>) -> ClassId {
|
||||
self.sym_table.insert(
|
||||
def.base.name,
|
||||
TypeEnum::ClassType(ClassId(self.class_defs.len())).into(),
|
||||
);
|
||||
self.class_defs.push(def);
|
||||
ClassId(self.class_defs.len() - 1)
|
||||
}
|
||||
|
||||
pub fn add_parametric(&mut self, def: ParametricDef<'a>) -> ParamId {
|
||||
let params = def
|
||||
.params
|
||||
.iter()
|
||||
.map(|&v| Rc::new(TypeEnum::TypeVariable(v)))
|
||||
.collect();
|
||||
self.sym_table.insert(
|
||||
def.base.name,
|
||||
TypeEnum::ParametricType(ParamId(self.parametric_defs.len()), params).into(),
|
||||
);
|
||||
self.parametric_defs.push(def);
|
||||
ParamId(self.parametric_defs.len() - 1)
|
||||
}
|
||||
|
||||
pub fn add_variable(&mut self, def: VarDef<'a>) -> VariableId {
|
||||
self.sym_table.insert(
|
||||
def.name,
|
||||
TypeEnum::TypeVariable(VariableId(self.var_defs.len())).into(),
|
||||
);
|
||||
self.add_variable_private(def)
|
||||
}
|
||||
|
||||
pub fn add_variable_private(&mut self, def: VarDef<'a>) -> VariableId {
|
||||
self.var_defs.push(def);
|
||||
self.variables
|
||||
.push(TypeEnum::TypeVariable(VariableId(self.var_defs.len() - 1)).into());
|
||||
VariableId(self.var_defs.len() - 1)
|
||||
}
|
||||
|
||||
pub fn add_fn(&mut self, name: &'a str, def: FnDef) {
|
||||
self.fn_table.insert(name, def);
|
||||
}
|
||||
|
||||
pub fn get_fn_def(&self, name: &str) -> Option<&FnDef> {
|
||||
self.fn_table.get(name)
|
||||
}
|
||||
|
||||
pub fn get_primitive_def_mut(&mut self, id: PrimitiveId) -> &mut TypeDef<'a> {
|
||||
self.primitive_defs.get_mut(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_primitive_def(&self, id: PrimitiveId) -> &TypeDef {
|
||||
self.primitive_defs.get(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_class_def_mut(&mut self, id: ClassId) -> &mut ClassDef<'a> {
|
||||
self.class_defs.get_mut(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_class_def(&self, id: ClassId) -> &ClassDef {
|
||||
self.class_defs.get(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_parametric_def_mut(&mut self, id: ParamId) -> &mut ParametricDef<'a> {
|
||||
self.parametric_defs.get_mut(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_parametric_def(&self, id: ParamId) -> &ParametricDef {
|
||||
self.parametric_defs.get(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_variable_def_mut(&mut self, id: VariableId) -> &mut VarDef<'a> {
|
||||
self.var_defs.get_mut(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_variable_def(&self, id: VariableId) -> &VarDef {
|
||||
self.var_defs.get(id.0).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_primitive(&self, id: PrimitiveId) -> Type {
|
||||
self.primitives.get(id.0).unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn get_variable(&self, id: VariableId) -> Type {
|
||||
self.variables.get(id.0).unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn get_type(&self, name: &str) -> Option<Type> {
|
||||
// TODO: handle parametric types
|
||||
self.sym_table.get(name).cloned()
|
||||
}
|
||||
}
|
@ -1,922 +0,0 @@
|
||||
use crate::context::InferenceContext;
|
||||
use crate::inference_core::resolve_call;
|
||||
use crate::magic_methods::*;
|
||||
use crate::primitives::*;
|
||||
use crate::typedef::{Type, TypeEnum::*};
|
||||
use rustpython_parser::ast::{
|
||||
Comparison, Comprehension, ComprehensionKind, Expression, ExpressionType, Operator,
|
||||
UnaryOperator,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
type ParserResult = Result<Option<Type>, String>;
|
||||
|
||||
pub fn infer_expr<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
expr: &'b Expression,
|
||||
) -> ParserResult {
|
||||
match &expr.node {
|
||||
ExpressionType::Number { value } => infer_constant(ctx, value),
|
||||
ExpressionType::Identifier { name } => infer_identifier(ctx, name),
|
||||
ExpressionType::List { elements } => infer_list(ctx, elements),
|
||||
ExpressionType::Tuple { elements } => infer_tuple(ctx, elements),
|
||||
ExpressionType::Attribute { value, name } => infer_attribute(ctx, value, name),
|
||||
ExpressionType::BoolOp { values, .. } => infer_bool_ops(ctx, values),
|
||||
ExpressionType::Binop { a, b, op } => infer_bin_ops(ctx, op, a, b),
|
||||
ExpressionType::Unop { op, a } => infer_unary_ops(ctx, op, a),
|
||||
ExpressionType::Compare { vals, ops } => infer_compare(ctx, vals, ops),
|
||||
ExpressionType::Call {
|
||||
args,
|
||||
function,
|
||||
keywords,
|
||||
} => {
|
||||
if !keywords.is_empty() {
|
||||
Err("keyword is not supported".into())
|
||||
} else {
|
||||
infer_call(ctx, &args, &function)
|
||||
}
|
||||
}
|
||||
ExpressionType::Subscript { a, b } => infer_subscript(ctx, a, b),
|
||||
ExpressionType::IfExpression { test, body, orelse } => {
|
||||
infer_if_expr(ctx, &test, &body, orelse)
|
||||
}
|
||||
ExpressionType::Comprehension { kind, generators } => match kind.as_ref() {
|
||||
ComprehensionKind::List { element } => {
|
||||
if generators.len() == 1 {
|
||||
infer_list_comprehension(ctx, element, &generators[0])
|
||||
} else {
|
||||
Err("only 1 generator statement is supported".into())
|
||||
}
|
||||
}
|
||||
_ => Err("only list comprehension is supported".into()),
|
||||
},
|
||||
ExpressionType::True | ExpressionType::False => Ok(Some(ctx.get_primitive(BOOL_TYPE))),
|
||||
_ => Err("not supported".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_constant(
|
||||
ctx: &mut InferenceContext,
|
||||
value: &rustpython_parser::ast::Number,
|
||||
) -> ParserResult {
|
||||
use rustpython_parser::ast::Number;
|
||||
match value {
|
||||
Number::Integer { value } => {
|
||||
let int32: Result<i32, _> = value.try_into();
|
||||
if int32.is_ok() {
|
||||
Ok(Some(ctx.get_primitive(INT32_TYPE)))
|
||||
} else {
|
||||
Err("integer out of range".into())
|
||||
}
|
||||
}
|
||||
Number::Float { .. } => Ok(Some(ctx.get_primitive(FLOAT_TYPE))),
|
||||
_ => Err("not supported".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_identifier(ctx: &mut InferenceContext, name: &str) -> ParserResult {
|
||||
Ok(Some(ctx.resolve(name)?))
|
||||
}
|
||||
|
||||
fn infer_list<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
elements: &'b [Expression],
|
||||
) -> ParserResult {
|
||||
if elements.is_empty() {
|
||||
return Ok(Some(ParametricType(LIST_TYPE, vec![BotType.into()]).into()));
|
||||
}
|
||||
|
||||
let mut types = elements.iter().map(|v| infer_expr(ctx, v));
|
||||
|
||||
let head = types.next().unwrap()?;
|
||||
if head.is_none() {
|
||||
return Err("list elements must have some type".into());
|
||||
}
|
||||
for v in types {
|
||||
// TODO: try virtual type...
|
||||
if v? != head {
|
||||
return Err("inhomogeneous list is not allowed".into());
|
||||
}
|
||||
}
|
||||
Ok(Some(ParametricType(LIST_TYPE, vec![head.unwrap()]).into()))
|
||||
}
|
||||
|
||||
fn infer_tuple<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
elements: &'b [Expression],
|
||||
) -> ParserResult {
|
||||
let types: Result<Option<Vec<_>>, String> =
|
||||
elements.iter().map(|v| infer_expr(ctx, v)).collect();
|
||||
if let Some(t) = types? {
|
||||
Ok(Some(ParametricType(TUPLE_TYPE, t).into()))
|
||||
} else {
|
||||
Err("tuple elements must have some type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_attribute<'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
value: &'a Expression,
|
||||
name: &str,
|
||||
) -> ParserResult {
|
||||
let value = infer_expr(ctx, value)?.ok_or_else(|| "no value".to_string())?;
|
||||
if let TypeVariable(_) = value.as_ref() {
|
||||
return Err("no fields for type variable".into());
|
||||
}
|
||||
|
||||
value
|
||||
.get_base(ctx)
|
||||
.and_then(|b| b.fields.get(name).cloned())
|
||||
.map_or_else(|| Err("no such field".to_string()), |v| Ok(Some(v)))
|
||||
}
|
||||
|
||||
fn infer_bool_ops<'a>(ctx: &mut InferenceContext<'a>, values: &'a [Expression]) -> ParserResult {
|
||||
assert_eq!(values.len(), 2);
|
||||
let left = infer_expr(ctx, &values[0])?.ok_or_else(|| "no value".to_string())?;
|
||||
let right = infer_expr(ctx, &values[1])?.ok_or_else(|| "no value".to_string())?;
|
||||
|
||||
let b = ctx.get_primitive(BOOL_TYPE);
|
||||
if left == b && right == b {
|
||||
Ok(Some(b))
|
||||
} else {
|
||||
Err("bool operands must be bool".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_bin_ops<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
op: &Operator,
|
||||
left: &'b Expression,
|
||||
right: &'b Expression,
|
||||
) -> ParserResult {
|
||||
let left = infer_expr(ctx, left)?.ok_or_else(|| "no value".to_string())?;
|
||||
let right = infer_expr(ctx, right)?.ok_or_else(|| "no value".to_string())?;
|
||||
let fun = binop_name(op);
|
||||
resolve_call(ctx, Some(left), fun, &[right])
|
||||
}
|
||||
|
||||
fn infer_unary_ops<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
op: &UnaryOperator,
|
||||
obj: &'b Expression,
|
||||
) -> ParserResult {
|
||||
let ty = infer_expr(ctx, obj)?.ok_or_else(|| "no value".to_string())?;
|
||||
if let UnaryOperator::Not = op {
|
||||
if ty == ctx.get_primitive(BOOL_TYPE) {
|
||||
Ok(Some(ty))
|
||||
} else {
|
||||
Err("logical not must be applied to bool".into())
|
||||
}
|
||||
} else {
|
||||
resolve_call(ctx, Some(ty), unaryop_name(op), &[])
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_compare<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
vals: &'b [Expression],
|
||||
ops: &'b [Comparison],
|
||||
) -> ParserResult {
|
||||
let types: Result<Option<Vec<_>>, _> = vals.iter().map(|v| infer_expr(ctx, v)).collect();
|
||||
let types = types?;
|
||||
if types.is_none() {
|
||||
return Err("comparison operands must have type".into());
|
||||
}
|
||||
let types = types.unwrap();
|
||||
let boolean = ctx.get_primitive(BOOL_TYPE);
|
||||
let left = &types[..types.len() - 1];
|
||||
let right = &types[1..];
|
||||
|
||||
for ((a, b), op) in left.iter().zip(right.iter()).zip(ops.iter()) {
|
||||
let fun = comparison_name(op).ok_or_else(|| "unsupported comparison".to_string())?;
|
||||
let ty = resolve_call(ctx, Some(a.clone()), fun, &[b.clone()])?;
|
||||
if ty.is_none() || ty.unwrap() != boolean {
|
||||
return Err("comparison result must be boolean".into());
|
||||
}
|
||||
}
|
||||
Ok(Some(boolean))
|
||||
}
|
||||
|
||||
fn infer_call<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
args: &'b [Expression],
|
||||
function: &'b Expression,
|
||||
) -> ParserResult {
|
||||
// TODO: special handling for int64 constant
|
||||
let types: Result<Option<Vec<_>>, _> = args.iter().map(|v| infer_expr(ctx, v)).collect();
|
||||
let types = types?;
|
||||
if types.is_none() {
|
||||
return Err("function params must have type".into());
|
||||
}
|
||||
|
||||
let (obj, fun) = match &function.node {
|
||||
ExpressionType::Identifier { name } => (None, name),
|
||||
ExpressionType::Attribute { value, name } => (
|
||||
Some(infer_expr(ctx, &value)?.ok_or_else(|| "no value".to_string())?),
|
||||
name,
|
||||
),
|
||||
_ => return Err("not supported".into()),
|
||||
};
|
||||
resolve_call(ctx, obj, fun.as_str(), &types.unwrap())
|
||||
}
|
||||
|
||||
fn infer_subscript<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
a: &'b Expression,
|
||||
b: &'b Expression,
|
||||
) -> ParserResult {
|
||||
let a = infer_expr(ctx, a)?.ok_or_else(|| "no value".to_string())?;
|
||||
let t = if let ParametricType(LIST_TYPE, ls) = a.as_ref() {
|
||||
ls[0].clone()
|
||||
} else {
|
||||
return Err("subscript is not supported for types other than list".into());
|
||||
};
|
||||
|
||||
match &b.node {
|
||||
ExpressionType::Slice { elements } => {
|
||||
let int32 = ctx.get_primitive(INT32_TYPE);
|
||||
let types: Result<Option<Vec<_>>, _> = elements
|
||||
.iter()
|
||||
.map(|v| {
|
||||
if let ExpressionType::None = v.node {
|
||||
Ok(Some(int32.clone()))
|
||||
} else {
|
||||
infer_expr(ctx, v)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let types = types?.ok_or_else(|| "slice must have type".to_string())?;
|
||||
if types.iter().all(|v| v == &int32) {
|
||||
Ok(Some(a))
|
||||
} else {
|
||||
Err("slice must be int32 type".into())
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let b = infer_expr(ctx, b)?.ok_or_else(|| "no value".to_string())?;
|
||||
if b == ctx.get_primitive(INT32_TYPE) {
|
||||
Ok(Some(t))
|
||||
} else {
|
||||
Err("index must be either slice or int32".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_if_expr<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
test: &'b Expression,
|
||||
body: &'b Expression,
|
||||
orelse: &'b Expression,
|
||||
) -> ParserResult {
|
||||
let test = infer_expr(ctx, test)?.ok_or_else(|| "no value".to_string())?;
|
||||
if test != ctx.get_primitive(BOOL_TYPE) {
|
||||
return Err("test should be bool".into());
|
||||
}
|
||||
|
||||
let body = infer_expr(ctx, body)?;
|
||||
let orelse = infer_expr(ctx, orelse)?;
|
||||
if body.as_ref() == orelse.as_ref() {
|
||||
Ok(body)
|
||||
} else {
|
||||
Err("divergent type".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_simple_binding<'a: 'b, 'b>(
|
||||
ctx: &mut InferenceContext<'b>,
|
||||
name: &'a Expression,
|
||||
ty: Type,
|
||||
) -> Result<(), String> {
|
||||
match &name.node {
|
||||
ExpressionType::Identifier { name } => {
|
||||
if name == "_" {
|
||||
Ok(())
|
||||
} else if ctx.defined(name.as_str()) {
|
||||
Err("duplicated naming".into())
|
||||
} else {
|
||||
ctx.assign(name.as_str(), ty)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
ExpressionType::Tuple { elements } => {
|
||||
if let ParametricType(TUPLE_TYPE, ls) = ty.as_ref() {
|
||||
if elements.len() == ls.len() {
|
||||
for (a, b) in elements.iter().zip(ls.iter()) {
|
||||
infer_simple_binding(ctx, a, b.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Err("different length".into())
|
||||
}
|
||||
} else {
|
||||
Err("not supported".into())
|
||||
}
|
||||
}
|
||||
_ => Err("not supported".into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_list_comprehension<'b: 'a, 'a>(
|
||||
ctx: &mut InferenceContext<'a>,
|
||||
element: &'b Expression,
|
||||
comprehension: &'b Comprehension,
|
||||
) -> ParserResult {
|
||||
if comprehension.is_async {
|
||||
return Err("async is not supported".into());
|
||||
}
|
||||
|
||||
let iter = infer_expr(ctx, &comprehension.iter)?.ok_or_else(|| "no value".to_string())?;
|
||||
if let ParametricType(LIST_TYPE, ls) = iter.as_ref() {
|
||||
ctx.with_scope(|ctx| {
|
||||
infer_simple_binding(ctx, &comprehension.target, ls[0].clone())?;
|
||||
|
||||
let boolean = ctx.get_primitive(BOOL_TYPE);
|
||||
for test in comprehension.ifs.iter() {
|
||||
let result =
|
||||
infer_expr(ctx, test)?.ok_or_else(|| "no value in test".to_string())?;
|
||||
if result != boolean {
|
||||
return Err("test must be bool".into());
|
||||
}
|
||||
}
|
||||
let result = infer_expr(ctx, element)?.ok_or_else(|| "no value")?;
|
||||
Ok(Some(ParametricType(LIST_TYPE, vec![result]).into()))
|
||||
})
|
||||
.1
|
||||
} else {
|
||||
Err("iteration is supported for list only".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::context::*;
|
||||
use crate::typedef::*;
|
||||
use rustpython_parser::parser::parse_expression;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn get_inference_context(ctx: TopLevelContext) -> InferenceContext {
|
||||
InferenceContext::new(ctx, Box::new(|_| Err("unbounded identifier".into())))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constants() {
|
||||
let ctx = basic_ctx();
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
|
||||
let ast = parse_expression("123").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("2147483647").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("2147483648").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("integer out of range".into()));
|
||||
//
|
||||
// let ast = parse_expression("2147483648").unwrap();
|
||||
// let result = infer_expr(&mut ctx, &ast);
|
||||
// assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT64_TYPE));
|
||||
|
||||
// let ast = parse_expression("9223372036854775807").unwrap();
|
||||
// let result = infer_expr(&mut ctx, &ast);
|
||||
// assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT64_TYPE));
|
||||
|
||||
// let ast = parse_expression("9223372036854775808").unwrap();
|
||||
// let result = infer_expr(&mut ctx, &ast);
|
||||
// assert_eq!(result, Err("integer out of range".into()));
|
||||
|
||||
let ast = parse_expression("123.456").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(FLOAT_TYPE));
|
||||
|
||||
let ast = parse_expression("True").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(BOOL_TYPE));
|
||||
|
||||
let ast = parse_expression("False").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(BOOL_TYPE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identifier() {
|
||||
let ctx = basic_ctx();
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("abc", ctx.get_primitive(INT32_TYPE)).unwrap();
|
||||
|
||||
let ast = parse_expression("abc").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("ab").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("unbounded identifier".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"foo",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("abc", ctx.get_primitive(INT32_TYPE)).unwrap();
|
||||
// def is reserved...
|
||||
ctx.assign("efg", ctx.get_primitive(INT32_TYPE)).unwrap();
|
||||
ctx.assign("xyz", ctx.get_primitive(FLOAT_TYPE)).unwrap();
|
||||
|
||||
let ast = parse_expression("[]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result.unwrap().unwrap(),
|
||||
ParametricType(LIST_TYPE, vec![BotType.into()]).into()
|
||||
);
|
||||
|
||||
let ast = parse_expression("[abc]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result.unwrap().unwrap(),
|
||||
ParametricType(LIST_TYPE, vec![ctx.get_primitive(INT32_TYPE)]).into()
|
||||
);
|
||||
|
||||
let ast = parse_expression("[abc, efg]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result.unwrap().unwrap(),
|
||||
ParametricType(LIST_TYPE, vec![ctx.get_primitive(INT32_TYPE)]).into()
|
||||
);
|
||||
|
||||
let ast = parse_expression("[abc, efg, xyz]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("inhomogeneous list is not allowed".into()));
|
||||
|
||||
let ast = parse_expression("[foo()]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("list elements must have some type".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"foo",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("abc", ctx.get_primitive(INT32_TYPE)).unwrap();
|
||||
ctx.assign("efg", ctx.get_primitive(FLOAT_TYPE)).unwrap();
|
||||
|
||||
let ast = parse_expression("(abc, efg)").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result.unwrap().unwrap(),
|
||||
ParametricType(
|
||||
TUPLE_TYPE,
|
||||
vec![ctx.get_primitive(INT32_TYPE), ctx.get_primitive(FLOAT_TYPE)]
|
||||
)
|
||||
.into()
|
||||
);
|
||||
|
||||
let ast = parse_expression("(abc, efg, foo())").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("tuple elements must have some type".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attribute() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"none",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let int32 = ctx.get_primitive(INT32_TYPE);
|
||||
let float = ctx.get_primitive(FLOAT_TYPE);
|
||||
|
||||
let foo = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "Foo",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
parents: vec![],
|
||||
});
|
||||
let foo_def = ctx.get_class_def_mut(foo);
|
||||
foo_def.base.fields.insert("a", int32.clone());
|
||||
foo_def.base.fields.insert("b", ClassType(foo).into());
|
||||
foo_def.base.fields.insert("c", int32.clone());
|
||||
|
||||
let bar = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "Bar",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
parents: vec![],
|
||||
});
|
||||
let bar_def = ctx.get_class_def_mut(bar);
|
||||
bar_def.base.fields.insert("a", int32);
|
||||
bar_def.base.fields.insert("b", ClassType(bar).into());
|
||||
bar_def.base.fields.insert("c", float);
|
||||
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "v0",
|
||||
bound: vec![],
|
||||
});
|
||||
|
||||
let v1 = ctx.add_variable(VarDef {
|
||||
name: "v1",
|
||||
bound: vec![ClassType(foo).into(), ClassType(bar).into()],
|
||||
});
|
||||
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("foo", Rc::new(ClassType(foo))).unwrap();
|
||||
ctx.assign("bar", Rc::new(ClassType(bar))).unwrap();
|
||||
ctx.assign("foobar", Rc::new(VirtualClassType(foo)))
|
||||
.unwrap();
|
||||
ctx.assign("v0", ctx.get_variable(v0)).unwrap();
|
||||
ctx.assign("v1", ctx.get_variable(v1)).unwrap();
|
||||
ctx.assign("bot", Rc::new(BotType)).unwrap();
|
||||
|
||||
let ast = parse_expression("foo.a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("foo.d").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no such field".into()));
|
||||
|
||||
let ast = parse_expression("foobar.a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("v0.a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no fields for type variable".into()));
|
||||
|
||||
let ast = parse_expression("v1.a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no fields for type variable".into()));
|
||||
|
||||
let ast = parse_expression("none().a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no value".into()));
|
||||
|
||||
let ast = parse_expression("bot.a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no such field".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bool_ops() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"none",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
|
||||
let ast = parse_expression("True and False").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(BOOL_TYPE));
|
||||
|
||||
let ast = parse_expression("True and none()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no value".into()));
|
||||
|
||||
let ast = parse_expression("True and 123").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("bool operands must be bool".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bin_ops() {
|
||||
let mut ctx = basic_ctx();
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "v0",
|
||||
bound: vec![ctx.get_primitive(INT32_TYPE), ctx.get_primitive(INT64_TYPE)],
|
||||
});
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("a", TypeVariable(v0).into()).unwrap();
|
||||
|
||||
let ast = parse_expression("1 + 2 + 3").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("a + a + a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("not supported".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unary_ops() {
|
||||
let mut ctx = basic_ctx();
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "v0",
|
||||
bound: vec![ctx.get_primitive(INT32_TYPE), ctx.get_primitive(INT64_TYPE)],
|
||||
});
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("a", TypeVariable(v0).into()).unwrap();
|
||||
|
||||
let ast = parse_expression("-(123)").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("-a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("not supported".into()));
|
||||
|
||||
let ast = parse_expression("not True").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(BOOL_TYPE));
|
||||
|
||||
let ast = parse_expression("not (1)").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("logical not must be applied to bool".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compare() {
|
||||
let mut ctx = basic_ctx();
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "v0",
|
||||
bound: vec![ctx.get_primitive(INT32_TYPE), ctx.get_primitive(INT64_TYPE)],
|
||||
});
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("a", TypeVariable(v0).into()).unwrap();
|
||||
|
||||
let ast = parse_expression("a == a == a").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("not supported".into()));
|
||||
|
||||
let ast = parse_expression("a == a == 1").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("not supported".into()));
|
||||
|
||||
let ast = parse_expression("True > False").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no such function".into()));
|
||||
|
||||
let ast = parse_expression("True in False").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("unsupported comparison".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"none",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
|
||||
let foo = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "Foo",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
parents: vec![],
|
||||
});
|
||||
let foo_def = ctx.get_class_def_mut(foo);
|
||||
foo_def.base.methods.insert(
|
||||
"a",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: Some(Rc::new(ClassType(foo))),
|
||||
},
|
||||
);
|
||||
|
||||
let bar = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "Bar",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
parents: vec![],
|
||||
});
|
||||
let bar_def = ctx.get_class_def_mut(bar);
|
||||
bar_def.base.methods.insert(
|
||||
"a",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: Some(Rc::new(ClassType(bar))),
|
||||
},
|
||||
);
|
||||
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "v0",
|
||||
bound: vec![],
|
||||
});
|
||||
let v1 = ctx.add_variable(VarDef {
|
||||
name: "v1",
|
||||
bound: vec![ClassType(foo).into(), ClassType(bar).into()],
|
||||
});
|
||||
let v2 = ctx.add_variable(VarDef {
|
||||
name: "v2",
|
||||
bound: vec![
|
||||
ClassType(foo).into(),
|
||||
ClassType(bar).into(),
|
||||
ctx.get_primitive(INT32_TYPE),
|
||||
],
|
||||
});
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("foo", Rc::new(ClassType(foo))).unwrap();
|
||||
ctx.assign("bar", Rc::new(ClassType(bar))).unwrap();
|
||||
ctx.assign("foobar", Rc::new(VirtualClassType(foo)))
|
||||
.unwrap();
|
||||
ctx.assign("v0", ctx.get_variable(v0)).unwrap();
|
||||
ctx.assign("v1", ctx.get_variable(v1)).unwrap();
|
||||
ctx.assign("v2", ctx.get_variable(v2)).unwrap();
|
||||
ctx.assign("bot", Rc::new(BotType)).unwrap();
|
||||
|
||||
let ast = parse_expression("foo.a()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ClassType(foo).into());
|
||||
|
||||
let ast = parse_expression("v1.a()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("not supported".into()));
|
||||
|
||||
let ast = parse_expression("foobar.a()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ClassType(foo).into());
|
||||
|
||||
let ast = parse_expression("none().a()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no value".into()));
|
||||
|
||||
let ast = parse_expression("bot.a()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("not supported".into()));
|
||||
|
||||
let ast = parse_expression("[][0].a()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("not supported".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_subscript() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"none",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
|
||||
let ast = parse_expression("[1, 2, 3][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("[[1]][0][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("[1, 2, 3][1:2]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result.unwrap().unwrap(),
|
||||
ParametricType(LIST_TYPE, vec![ctx.get_primitive(INT32_TYPE)]).into()
|
||||
);
|
||||
|
||||
let ast = parse_expression("[1, 2, 3][1:2:2]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result.unwrap().unwrap(),
|
||||
ParametricType(LIST_TYPE, vec![ctx.get_primitive(INT32_TYPE)]).into()
|
||||
);
|
||||
|
||||
let ast = parse_expression("[1, 2, 3][1:1.2]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("slice must be int32 type".into()));
|
||||
|
||||
let ast = parse_expression("[1, 2, 3][1:none()]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("slice must have type".into()));
|
||||
|
||||
let ast = parse_expression("[1, 2, 3][1.2]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("index must be either slice or int32".into()));
|
||||
|
||||
let ast = parse_expression("[1, 2, 3][none()]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no value".into()));
|
||||
|
||||
let ast = parse_expression("none()[1.2]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no value".into()));
|
||||
|
||||
let ast = parse_expression("123[1]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err("subscript is not supported for types other than list".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if_expr() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"none",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
|
||||
let ast = parse_expression("1 if True else 0").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), ctx.get_primitive(INT32_TYPE));
|
||||
|
||||
let ast = parse_expression("none() if True else none()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap(), None);
|
||||
|
||||
let ast = parse_expression("none() if 1 else none()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("test should be bool".into()));
|
||||
|
||||
let ast = parse_expression("1 if True else none()").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("divergent type".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_comp() {
|
||||
let mut ctx = basic_ctx();
|
||||
ctx.add_fn(
|
||||
"none",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let int32 = ctx.get_primitive(INT32_TYPE);
|
||||
let mut ctx = get_inference_context(ctx);
|
||||
ctx.assign("z", int32.clone()).unwrap();
|
||||
|
||||
let ast = parse_expression("[x for x in [(1, 2), (2, 3), (3, 4)]][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result.unwrap().unwrap(),
|
||||
ParametricType(TUPLE_TYPE, vec![int32.clone(), int32.clone()]).into()
|
||||
);
|
||||
|
||||
let ast = parse_expression("[x for (x, y) in [(1, 2), (2, 3), (3, 4)]][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), int32);
|
||||
|
||||
let ast =
|
||||
parse_expression("[x for (x, y) in [(1, 2), (2, 3), (3, 4)] if x > 0][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result.unwrap().unwrap(), int32);
|
||||
|
||||
let ast = parse_expression("[x for (x, y) in [(1, 2), (2, 3), (3, 4)] if x][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("test must be bool".into()));
|
||||
|
||||
let ast = parse_expression("[y for x in []][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("unbounded identifier".into()));
|
||||
|
||||
let ast = parse_expression("[none() for x in []][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("no value".into()));
|
||||
|
||||
let ast = parse_expression("[z for z in []][0]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(result, Err("duplicated naming".into()));
|
||||
|
||||
let ast = parse_expression("[x for x in [] for y in []]").unwrap();
|
||||
let result = infer_expr(&mut ctx, &ast);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err("only 1 generator statement is supported".into())
|
||||
);
|
||||
}
|
||||
}
|
@ -1,525 +0,0 @@
|
||||
use crate::context::InferenceContext;
|
||||
use crate::typedef::{TypeEnum::*, *};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn find_subst(
|
||||
ctx: &InferenceContext,
|
||||
valuation: &Option<(VariableId, Type)>,
|
||||
sub: &mut HashMap<VariableId, Type>,
|
||||
mut a: Type,
|
||||
mut b: Type,
|
||||
) -> Result<(), String> {
|
||||
// TODO: fix error messages later
|
||||
if let TypeVariable(id) = a.as_ref() {
|
||||
if let Some((assumption_id, t)) = valuation {
|
||||
if assumption_id == id {
|
||||
a = t.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut substituted = false;
|
||||
if let TypeVariable(id) = b.as_ref() {
|
||||
if let Some(c) = sub.get(&id) {
|
||||
b = c.clone();
|
||||
substituted = true;
|
||||
}
|
||||
}
|
||||
|
||||
match (a.as_ref(), b.as_ref()) {
|
||||
(BotType, _) => Ok(()),
|
||||
(TypeVariable(id_a), TypeVariable(id_b)) => {
|
||||
if substituted {
|
||||
return if id_a == id_b {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("different variables".to_string())
|
||||
};
|
||||
}
|
||||
let v_a = ctx.get_variable_def(*id_a);
|
||||
let v_b = ctx.get_variable_def(*id_b);
|
||||
if !v_b.bound.is_empty() {
|
||||
if v_a.bound.is_empty() {
|
||||
return Err("unbounded a".to_string());
|
||||
} else {
|
||||
let diff: Vec<_> = v_a
|
||||
.bound
|
||||
.iter()
|
||||
.filter(|x| !v_b.bound.contains(x))
|
||||
.collect();
|
||||
if !diff.is_empty() {
|
||||
return Err("different domain".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
sub.insert(*id_b, a.clone());
|
||||
Ok(())
|
||||
}
|
||||
(TypeVariable(id_a), _) => {
|
||||
let v_a = ctx.get_variable_def(*id_a);
|
||||
if v_a.bound.len() == 1 && v_a.bound[0].as_ref() == b.as_ref() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("different domain".to_string())
|
||||
}
|
||||
}
|
||||
(_, TypeVariable(id_b)) => {
|
||||
let v_b = ctx.get_variable_def(*id_b);
|
||||
if v_b.bound.is_empty() || v_b.bound.contains(&a) {
|
||||
sub.insert(*id_b, a.clone());
|
||||
Ok(())
|
||||
} else {
|
||||
Err("different domain".to_string())
|
||||
}
|
||||
}
|
||||
(_, VirtualClassType(id_b)) => {
|
||||
let mut parents;
|
||||
match a.as_ref() {
|
||||
ClassType(id_a) => {
|
||||
parents = [*id_a].to_vec();
|
||||
}
|
||||
VirtualClassType(id_a) => {
|
||||
parents = [*id_a].to_vec();
|
||||
}
|
||||
_ => {
|
||||
return Err("cannot substitute non-class type into virtual class".to_string());
|
||||
}
|
||||
};
|
||||
while !parents.is_empty() {
|
||||
if *id_b == parents[0] {
|
||||
return Ok(());
|
||||
}
|
||||
let c = ctx.get_class_def(parents.remove(0));
|
||||
parents.extend_from_slice(&c.parents);
|
||||
}
|
||||
Err("not subtype".to_string())
|
||||
}
|
||||
(ParametricType(id_a, param_a), ParametricType(id_b, param_b)) => {
|
||||
if id_a != id_b || param_a.len() != param_b.len() {
|
||||
Err("different parametric types".to_string())
|
||||
} else {
|
||||
for (x, y) in param_a.iter().zip(param_b.iter()) {
|
||||
find_subst(ctx, valuation, sub, x.clone(), y.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
(_, _) => {
|
||||
if a == b {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("not equal".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_call_rec(
|
||||
ctx: &InferenceContext,
|
||||
valuation: &Option<(VariableId, Type)>,
|
||||
obj: Option<Type>,
|
||||
func: &str,
|
||||
args: &[Type],
|
||||
) -> Result<Option<Type>, String> {
|
||||
let mut subst = obj
|
||||
.as_ref()
|
||||
.map(|v| v.get_subst(ctx))
|
||||
.unwrap_or_else(HashMap::new);
|
||||
|
||||
let fun = match &obj {
|
||||
Some(obj) => {
|
||||
let base = match obj.as_ref() {
|
||||
PrimitiveType(id) => &ctx.get_primitive_def(*id),
|
||||
ClassType(id) | VirtualClassType(id) => &ctx.get_class_def(*id).base,
|
||||
ParametricType(id, _) => &ctx.get_parametric_def(*id).base,
|
||||
_ => return Err("not supported".to_string()),
|
||||
};
|
||||
base.methods.get(func)
|
||||
}
|
||||
None => ctx.get_fn_def(func),
|
||||
}
|
||||
.ok_or_else(|| "no such function".to_string())?;
|
||||
|
||||
if args.len() != fun.args.len() {
|
||||
return Err("incorrect parameter number".to_string());
|
||||
}
|
||||
for (a, b) in args.iter().zip(fun.args.iter()) {
|
||||
find_subst(ctx, valuation, &mut subst, a.clone(), b.clone())?;
|
||||
}
|
||||
let result = fun.result.as_ref().map(|v| v.subst(&subst));
|
||||
Ok(result.map(|result| {
|
||||
if let SelfType = result {
|
||||
obj.unwrap()
|
||||
} else {
|
||||
result.into()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn resolve_call(
|
||||
ctx: &InferenceContext,
|
||||
obj: Option<Type>,
|
||||
func: &str,
|
||||
args: &[Type],
|
||||
) -> Result<Option<Type>, String> {
|
||||
resolve_call_rec(ctx, &None, obj, func, args)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::context::TopLevelContext;
|
||||
use crate::primitives::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
fn get_inference_context(ctx: TopLevelContext) -> InferenceContext {
|
||||
InferenceContext::new(ctx, Box::new(|_| Err("unbounded identifier".into())))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_generic() {
|
||||
let mut ctx = basic_ctx();
|
||||
let v1 = ctx.add_variable(VarDef {
|
||||
name: "V1",
|
||||
bound: vec![ctx.get_primitive(INT32_TYPE), ctx.get_primitive(FLOAT_TYPE)],
|
||||
});
|
||||
let v1 = ctx.get_variable(v1);
|
||||
let v2 = ctx.add_variable(VarDef {
|
||||
name: "V2",
|
||||
bound: vec![
|
||||
ctx.get_primitive(BOOL_TYPE),
|
||||
ctx.get_primitive(INT32_TYPE),
|
||||
ctx.get_primitive(FLOAT_TYPE),
|
||||
],
|
||||
});
|
||||
let v2 = ctx.get_variable(v2);
|
||||
let ctx = get_inference_context(ctx);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "int32", &[ctx.get_primitive(FLOAT_TYPE)]),
|
||||
Ok(Some(ctx.get_primitive(INT32_TYPE)))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "int32", &[ctx.get_primitive(INT32_TYPE)],),
|
||||
Ok(Some(ctx.get_primitive(INT32_TYPE)))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "float", &[ctx.get_primitive(INT32_TYPE)]),
|
||||
Ok(Some(ctx.get_primitive(FLOAT_TYPE)))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "float", &[ctx.get_primitive(BOOL_TYPE)]),
|
||||
Err("different domain".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "float", &[]),
|
||||
Err("incorrect parameter number".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "float", &[v1]),
|
||||
Ok(Some(ctx.get_primitive(FLOAT_TYPE)))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "float", &[v2]),
|
||||
Err("different domain".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_methods() {
|
||||
let mut ctx = basic_ctx();
|
||||
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "V0",
|
||||
bound: vec![],
|
||||
});
|
||||
let v0 = ctx.get_variable(v0);
|
||||
|
||||
let int32 = ctx.get_primitive(INT32_TYPE);
|
||||
let int64 = ctx.get_primitive(INT64_TYPE);
|
||||
let ctx = get_inference_context(ctx);
|
||||
|
||||
// simple cases
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, Some(int32.clone()), "__add__", &[int32.clone()]),
|
||||
Ok(Some(int32.clone()))
|
||||
);
|
||||
|
||||
assert_ne!(
|
||||
resolve_call(&ctx, Some(int32.clone()), "__add__", &[int32.clone()]),
|
||||
Ok(Some(int64.clone()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, Some(int32), "__add__", &[int64]),
|
||||
Err("not equal".to_string())
|
||||
);
|
||||
|
||||
// with type variables
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, Some(v0.clone()), "__add__", &[v0.clone()]),
|
||||
Err("not supported".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_generic() {
|
||||
let mut ctx = basic_ctx();
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "V0",
|
||||
bound: vec![],
|
||||
});
|
||||
let v0 = ctx.get_variable(v0);
|
||||
let v1 = ctx.add_variable(VarDef {
|
||||
name: "V1",
|
||||
bound: vec![],
|
||||
});
|
||||
let v1 = ctx.get_variable(v1);
|
||||
let v2 = ctx.add_variable(VarDef {
|
||||
name: "V2",
|
||||
bound: vec![],
|
||||
});
|
||||
let v2 = ctx.get_variable(v2);
|
||||
let v3 = ctx.add_variable(VarDef {
|
||||
name: "V3",
|
||||
bound: vec![],
|
||||
});
|
||||
let v3 = ctx.get_variable(v3);
|
||||
|
||||
ctx.add_fn(
|
||||
"foo",
|
||||
FnDef {
|
||||
args: vec![v0.clone(), v0.clone(), v1.clone()],
|
||||
result: Some(v0.clone()),
|
||||
},
|
||||
);
|
||||
|
||||
ctx.add_fn(
|
||||
"foo1",
|
||||
FnDef {
|
||||
args: vec![ParametricType(TUPLE_TYPE, vec![v0.clone(), v0.clone(), v1]).into()],
|
||||
result: Some(v0),
|
||||
},
|
||||
);
|
||||
let ctx = get_inference_context(ctx);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[v2.clone(), v2.clone(), v2.clone()]),
|
||||
Ok(Some(v2.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[v2.clone(), v2.clone(), v3.clone()]),
|
||||
Ok(Some(v2.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[v2.clone(), v3.clone(), v3.clone()]),
|
||||
Err("different variables".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(
|
||||
&ctx,
|
||||
None,
|
||||
"foo1",
|
||||
&[ParametricType(TUPLE_TYPE, vec![v2.clone(), v2.clone(), v2.clone()]).into()]
|
||||
),
|
||||
Ok(Some(v2.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(
|
||||
&ctx,
|
||||
None,
|
||||
"foo1",
|
||||
&[ParametricType(TUPLE_TYPE, vec![v2.clone(), v2.clone(), v3.clone()]).into()]
|
||||
),
|
||||
Ok(Some(v2.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(
|
||||
&ctx,
|
||||
None,
|
||||
"foo1",
|
||||
&[ParametricType(TUPLE_TYPE, vec![v2, v3.clone(), v3]).into()]
|
||||
),
|
||||
Err("different variables".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_class_generics() {
|
||||
let mut ctx = basic_ctx();
|
||||
|
||||
let list = ctx.get_parametric_def_mut(LIST_TYPE);
|
||||
let t = Rc::new(TypeVariable(list.params[0]));
|
||||
list.base.methods.insert(
|
||||
"head",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result: Some(t.clone()),
|
||||
},
|
||||
);
|
||||
list.base.methods.insert(
|
||||
"append",
|
||||
FnDef {
|
||||
args: vec![t],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
|
||||
let v0 = ctx.add_variable(VarDef {
|
||||
name: "V0",
|
||||
bound: vec![],
|
||||
});
|
||||
let v0 = ctx.get_variable(v0);
|
||||
let v1 = ctx.add_variable(VarDef {
|
||||
name: "V1",
|
||||
bound: vec![],
|
||||
});
|
||||
let v1 = ctx.get_variable(v1);
|
||||
let ctx = get_inference_context(ctx);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(
|
||||
&ctx,
|
||||
Some(ParametricType(LIST_TYPE, vec![v0.clone()]).into()),
|
||||
"head",
|
||||
&[]
|
||||
),
|
||||
Ok(Some(v0.clone()))
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(
|
||||
&ctx,
|
||||
Some(ParametricType(LIST_TYPE, vec![v0.clone()]).into()),
|
||||
"append",
|
||||
&[v0.clone()]
|
||||
),
|
||||
Ok(None)
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(
|
||||
&ctx,
|
||||
Some(ParametricType(LIST_TYPE, vec![v0]).into()),
|
||||
"append",
|
||||
&[v1]
|
||||
),
|
||||
Err("different variables".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtual_class() {
|
||||
let mut ctx = basic_ctx();
|
||||
|
||||
let foo = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "Foo",
|
||||
methods: HashMap::new(),
|
||||
fields: HashMap::new(),
|
||||
},
|
||||
parents: vec![],
|
||||
});
|
||||
|
||||
let foo1 = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "Foo1",
|
||||
methods: HashMap::new(),
|
||||
fields: HashMap::new(),
|
||||
},
|
||||
parents: vec![foo],
|
||||
});
|
||||
|
||||
let foo2 = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "Foo2",
|
||||
methods: HashMap::new(),
|
||||
fields: HashMap::new(),
|
||||
},
|
||||
parents: vec![foo1],
|
||||
});
|
||||
|
||||
let bar = ctx.add_class(ClassDef {
|
||||
base: TypeDef {
|
||||
name: "bar",
|
||||
methods: HashMap::new(),
|
||||
fields: HashMap::new(),
|
||||
},
|
||||
parents: vec![],
|
||||
});
|
||||
|
||||
ctx.add_fn(
|
||||
"foo",
|
||||
FnDef {
|
||||
args: vec![VirtualClassType(foo).into()],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
ctx.add_fn(
|
||||
"foo1",
|
||||
FnDef {
|
||||
args: vec![VirtualClassType(foo1).into()],
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
let ctx = get_inference_context(ctx);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[ClassType(foo).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[ClassType(foo1).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[ClassType(foo2).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[ClassType(bar).into()]),
|
||||
Err("not subtype".to_string())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo1", &[ClassType(foo1).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo1", &[ClassType(foo2).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo1", &[ClassType(foo).into()]),
|
||||
Err("not subtype".to_string())
|
||||
);
|
||||
|
||||
// virtual class substitution
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[VirtualClassType(foo).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[VirtualClassType(foo1).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[VirtualClassType(foo2).into()]),
|
||||
Ok(None)
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_call(&ctx, None, "foo", &[VirtualClassType(bar).into()]),
|
||||
Err("not subtype".to_string())
|
||||
);
|
||||
}
|
||||
}
|
@ -1,593 +1,7 @@
|
||||
#![warn(clippy::all)]
|
||||
#![allow(clippy::clone_double_ref)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
extern crate num_bigint;
|
||||
extern crate inkwell;
|
||||
extern crate rustpython_parser;
|
||||
|
||||
pub mod expression_inference;
|
||||
pub mod inference_core;
|
||||
mod magic_methods;
|
||||
pub mod primitives;
|
||||
pub mod typedef;
|
||||
pub mod context;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
use rustpython_parser::ast;
|
||||
|
||||
use inkwell::OptimizationLevel;
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Module;
|
||||
use inkwell::targets::*;
|
||||
use inkwell::types;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::values;
|
||||
use inkwell::{IntPredicate, FloatPredicate};
|
||||
use inkwell::basic_block;
|
||||
use inkwell::passes;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CompileErrorKind {
|
||||
Unsupported(&'static str),
|
||||
MissingTypeAnnotation,
|
||||
UnknownTypeAnnotation,
|
||||
IncompatibleTypes,
|
||||
UnboundIdentifier,
|
||||
BreakOutsideLoop,
|
||||
Internal(&'static str)
|
||||
}
|
||||
|
||||
impl fmt::Display for CompileErrorKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
CompileErrorKind::Unsupported(feature)
|
||||
=> write!(f, "The following Python feature is not supported by NAC3: {}", feature),
|
||||
CompileErrorKind::MissingTypeAnnotation
|
||||
=> write!(f, "Missing type annotation"),
|
||||
CompileErrorKind::UnknownTypeAnnotation
|
||||
=> write!(f, "Unknown type annotation"),
|
||||
CompileErrorKind::IncompatibleTypes
|
||||
=> write!(f, "Incompatible types"),
|
||||
CompileErrorKind::UnboundIdentifier
|
||||
=> write!(f, "Unbound identifier"),
|
||||
CompileErrorKind::BreakOutsideLoop
|
||||
=> write!(f, "Break outside loop"),
|
||||
CompileErrorKind::Internal(details)
|
||||
=> write!(f, "Internal compiler error: {}", details),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CompileError {
|
||||
location: ast::Location,
|
||||
kind: CompileErrorKind,
|
||||
}
|
||||
|
||||
impl fmt::Display for CompileError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}, at {}", self.kind, self.location)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CompileError {}
|
||||
|
||||
type CompileResult<T> = Result<T, CompileError>;
|
||||
|
||||
pub struct CodeGen<'ctx> {
|
||||
context: &'ctx Context,
|
||||
module: Module<'ctx>,
|
||||
pass_manager: passes::PassManager<values::FunctionValue<'ctx>>,
|
||||
builder: Builder<'ctx>,
|
||||
current_source_location: ast::Location,
|
||||
namespace: HashMap<String, values::PointerValue<'ctx>>,
|
||||
break_bb: Option<basic_block::BasicBlock<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> CodeGen<'ctx> {
|
||||
pub fn new(context: &'ctx Context) -> CodeGen<'ctx> {
|
||||
let module = context.create_module("kernel");
|
||||
|
||||
let pass_manager = passes::PassManager::create(&module);
|
||||
pass_manager.add_instruction_combining_pass();
|
||||
pass_manager.add_reassociate_pass();
|
||||
pass_manager.add_gvn_pass();
|
||||
pass_manager.add_cfg_simplification_pass();
|
||||
pass_manager.add_basic_alias_analysis_pass();
|
||||
pass_manager.add_promote_memory_to_register_pass();
|
||||
pass_manager.add_instruction_combining_pass();
|
||||
pass_manager.add_reassociate_pass();
|
||||
pass_manager.initialize();
|
||||
|
||||
let i32_type = context.i32_type();
|
||||
let fn_type = i32_type.fn_type(&[i32_type.into()], false);
|
||||
module.add_function("output", fn_type, None);
|
||||
|
||||
CodeGen {
|
||||
context, module, pass_manager,
|
||||
builder: context.create_builder(),
|
||||
current_source_location: ast::Location::default(),
|
||||
namespace: HashMap::new(),
|
||||
break_bb: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_source_location(&mut self, location: ast::Location) {
|
||||
self.current_source_location = location;
|
||||
}
|
||||
|
||||
fn compile_error(&self, kind: CompileErrorKind) -> CompileError {
|
||||
CompileError {
|
||||
location: self.current_source_location,
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
||||
fn get_basic_type(&self, name: &str) -> CompileResult<types::BasicTypeEnum<'ctx>> {
|
||||
match name {
|
||||
"bool" => Ok(self.context.bool_type().into()),
|
||||
"int32" => Ok(self.context.i32_type().into()),
|
||||
"int64" => Ok(self.context.i64_type().into()),
|
||||
"float32" => Ok(self.context.f32_type().into()),
|
||||
"float64" => Ok(self.context.f64_type().into()),
|
||||
_ => Err(self.compile_error(CompileErrorKind::UnknownTypeAnnotation))
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_function_def(
|
||||
&mut self,
|
||||
name: &str,
|
||||
args: &ast::Parameters,
|
||||
body: &ast::Suite,
|
||||
decorator_list: &[ast::Expression],
|
||||
returns: &Option<ast::Expression>,
|
||||
is_async: bool,
|
||||
) -> CompileResult<values::FunctionValue<'ctx>> {
|
||||
if is_async {
|
||||
return Err(self.compile_error(CompileErrorKind::Unsupported("async functions")))
|
||||
}
|
||||
for decorator in decorator_list.iter() {
|
||||
self.set_source_location(decorator.location);
|
||||
if let ast::ExpressionType::Identifier { name } = &decorator.node {
|
||||
if name != "kernel" && name != "portable" {
|
||||
return Err(self.compile_error(CompileErrorKind::Unsupported("custom decorators")))
|
||||
}
|
||||
} else {
|
||||
return Err(self.compile_error(CompileErrorKind::Unsupported("decorator must be an identifier")))
|
||||
}
|
||||
}
|
||||
|
||||
let args_type = args.args.iter().map(|val| {
|
||||
self.set_source_location(val.location);
|
||||
if let Some(annotation) = &val.annotation {
|
||||
if let ast::ExpressionType::Identifier { name } = &annotation.node {
|
||||
Ok(self.get_basic_type(&name)?)
|
||||
} else {
|
||||
Err(self.compile_error(CompileErrorKind::Unsupported("type annotation must be an identifier")))
|
||||
}
|
||||
} else {
|
||||
Err(self.compile_error(CompileErrorKind::MissingTypeAnnotation))
|
||||
}
|
||||
}).collect::<CompileResult<Vec<types::BasicTypeEnum>>>()?;
|
||||
let return_type = if let Some(returns) = returns {
|
||||
self.set_source_location(returns.location);
|
||||
if let ast::ExpressionType::Identifier { name } = &returns.node {
|
||||
if name == "None" { None } else { Some(self.get_basic_type(name)?) }
|
||||
} else {
|
||||
return Err(self.compile_error(CompileErrorKind::Unsupported("type annotation must be an identifier")))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let fn_type = match return_type {
|
||||
Some(ty) => ty.fn_type(&args_type, false),
|
||||
None => self.context.void_type().fn_type(&args_type, false)
|
||||
};
|
||||
|
||||
let function = self.module.add_function(name, fn_type, None);
|
||||
let basic_block = self.context.append_basic_block(function, "entry");
|
||||
self.builder.position_at_end(basic_block);
|
||||
|
||||
for (n, arg) in args.args.iter().enumerate() {
|
||||
let param = function.get_nth_param(n as u32).unwrap();
|
||||
let alloca = self.builder.build_alloca(param.get_type(), &arg.arg);
|
||||
self.builder.build_store(alloca, param);
|
||||
self.namespace.insert(arg.arg.clone(), alloca);
|
||||
}
|
||||
|
||||
self.compile_suite(body, return_type)?;
|
||||
|
||||
Ok(function)
|
||||
}
|
||||
|
||||
fn compile_expression(
|
||||
&mut self,
|
||||
expression: &ast::Expression
|
||||
) -> CompileResult<values::BasicValueEnum<'ctx>> {
|
||||
self.set_source_location(expression.location);
|
||||
|
||||
match &expression.node {
|
||||
ast::ExpressionType::True => Ok(self.context.bool_type().const_int(1, false).into()),
|
||||
ast::ExpressionType::False => Ok(self.context.bool_type().const_int(0, false).into()),
|
||||
ast::ExpressionType::Number { value: ast::Number::Integer { value } } => {
|
||||
let mut bits = value.bits();
|
||||
if value.sign() == num_bigint::Sign::Minus {
|
||||
bits += 1;
|
||||
}
|
||||
match bits {
|
||||
0..=32 => Ok(self.context.i32_type().const_int(value.to_i32().unwrap() as _, true).into()),
|
||||
33..=64 => Ok(self.context.i64_type().const_int(value.to_i64().unwrap() as _, true).into()),
|
||||
_ => Err(self.compile_error(CompileErrorKind::Unsupported("integers larger than 64 bits")))
|
||||
}
|
||||
},
|
||||
ast::ExpressionType::Number { value: ast::Number::Float { value } } => {
|
||||
Ok(self.context.f64_type().const_float(*value).into())
|
||||
},
|
||||
ast::ExpressionType::Identifier { name } => {
|
||||
match self.namespace.get(name) {
|
||||
Some(value) => Ok(self.builder.build_load(*value, name).into()),
|
||||
None => Err(self.compile_error(CompileErrorKind::UnboundIdentifier))
|
||||
}
|
||||
},
|
||||
ast::ExpressionType::Unop { op, a } => {
|
||||
let a = self.compile_expression(&a)?;
|
||||
match (op, a) {
|
||||
(ast::UnaryOperator::Pos, values::BasicValueEnum::IntValue(a))
|
||||
=> Ok(a.into()),
|
||||
(ast::UnaryOperator::Pos, values::BasicValueEnum::FloatValue(a))
|
||||
=> Ok(a.into()),
|
||||
(ast::UnaryOperator::Neg, values::BasicValueEnum::IntValue(a))
|
||||
=> Ok(self.builder.build_int_neg(a, "tmpneg").into()),
|
||||
(ast::UnaryOperator::Neg, values::BasicValueEnum::FloatValue(a))
|
||||
=> Ok(self.builder.build_float_neg(a, "tmpneg").into()),
|
||||
(ast::UnaryOperator::Inv, values::BasicValueEnum::IntValue(a))
|
||||
=> Ok(self.builder.build_not(a, "tmpnot").into()),
|
||||
(ast::UnaryOperator::Not, values::BasicValueEnum::IntValue(a)) => {
|
||||
// boolean "not"
|
||||
if a.get_type().get_bit_width() != 1 {
|
||||
Err(self.compile_error(CompileErrorKind::Unsupported("unimplemented unary operation")))
|
||||
} else {
|
||||
Ok(self.builder.build_not(a, "tmpnot").into())
|
||||
}
|
||||
},
|
||||
_ => Err(self.compile_error(CompileErrorKind::Unsupported("unimplemented unary operation"))),
|
||||
}
|
||||
},
|
||||
ast::ExpressionType::Binop { a, op, b } => {
|
||||
let a = self.compile_expression(&a)?;
|
||||
let b = self.compile_expression(&b)?;
|
||||
if a.get_type() != b.get_type() {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
use ast::Operator::*;
|
||||
match (op, a, b) {
|
||||
(Add, values::BasicValueEnum::IntValue(a), values::BasicValueEnum::IntValue(b))
|
||||
=> Ok(self.builder.build_int_add(a, b, "tmpadd").into()),
|
||||
(Sub, values::BasicValueEnum::IntValue(a), values::BasicValueEnum::IntValue(b))
|
||||
=> Ok(self.builder.build_int_sub(a, b, "tmpsub").into()),
|
||||
(Mult, values::BasicValueEnum::IntValue(a), values::BasicValueEnum::IntValue(b))
|
||||
=> Ok(self.builder.build_int_mul(a, b, "tmpmul").into()),
|
||||
|
||||
(Add, values::BasicValueEnum::FloatValue(a), values::BasicValueEnum::FloatValue(b))
|
||||
=> Ok(self.builder.build_float_add(a, b, "tmpadd").into()),
|
||||
(Sub, values::BasicValueEnum::FloatValue(a), values::BasicValueEnum::FloatValue(b))
|
||||
=> Ok(self.builder.build_float_sub(a, b, "tmpsub").into()),
|
||||
(Mult, values::BasicValueEnum::FloatValue(a), values::BasicValueEnum::FloatValue(b))
|
||||
=> Ok(self.builder.build_float_mul(a, b, "tmpmul").into()),
|
||||
|
||||
(Div, values::BasicValueEnum::FloatValue(a), values::BasicValueEnum::FloatValue(b))
|
||||
=> Ok(self.builder.build_float_div(a, b, "tmpdiv").into()),
|
||||
(FloorDiv, values::BasicValueEnum::IntValue(a), values::BasicValueEnum::IntValue(b))
|
||||
=> Ok(self.builder.build_int_signed_div(a, b, "tmpdiv").into()),
|
||||
_ => Err(self.compile_error(CompileErrorKind::Unsupported("unimplemented binary operation"))),
|
||||
}
|
||||
},
|
||||
ast::ExpressionType::Compare { vals, ops } => {
|
||||
let mut vals = vals.iter();
|
||||
let mut ops = ops.iter();
|
||||
|
||||
let mut result = None;
|
||||
let mut a = self.compile_expression(vals.next().unwrap())?;
|
||||
loop {
|
||||
if let Some(op) = ops.next() {
|
||||
let b = self.compile_expression(vals.next().unwrap())?;
|
||||
if a.get_type() != b.get_type() {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
let this_result = match (a, b) {
|
||||
(values::BasicValueEnum::IntValue(a), values::BasicValueEnum::IntValue(b)) => {
|
||||
match op {
|
||||
ast::Comparison::Equal
|
||||
=> self.builder.build_int_compare(IntPredicate::EQ, a, b, "tmpeq"),
|
||||
ast::Comparison::NotEqual
|
||||
=> self.builder.build_int_compare(IntPredicate::NE, a, b, "tmpne"),
|
||||
ast::Comparison::Less
|
||||
=> self.builder.build_int_compare(IntPredicate::SLT, a, b, "tmpslt"),
|
||||
ast::Comparison::LessOrEqual
|
||||
=> self.builder.build_int_compare(IntPredicate::SLE, a, b, "tmpsle"),
|
||||
ast::Comparison::Greater
|
||||
=> self.builder.build_int_compare(IntPredicate::SGT, a, b, "tmpsgt"),
|
||||
ast::Comparison::GreaterOrEqual
|
||||
=> self.builder.build_int_compare(IntPredicate::SGE, a, b, "tmpsge"),
|
||||
_ => return Err(self.compile_error(CompileErrorKind::Unsupported("special comparison"))),
|
||||
}
|
||||
},
|
||||
(values::BasicValueEnum::FloatValue(a), values::BasicValueEnum::FloatValue(b)) => {
|
||||
match op {
|
||||
ast::Comparison::Equal
|
||||
=> self.builder.build_float_compare(FloatPredicate::OEQ, a, b, "tmpoeq"),
|
||||
ast::Comparison::NotEqual
|
||||
=> self.builder.build_float_compare(FloatPredicate::UNE, a, b, "tmpune"),
|
||||
ast::Comparison::Less
|
||||
=> self.builder.build_float_compare(FloatPredicate::OLT, a, b, "tmpolt"),
|
||||
ast::Comparison::LessOrEqual
|
||||
=> self.builder.build_float_compare(FloatPredicate::OLE, a, b, "tmpole"),
|
||||
ast::Comparison::Greater
|
||||
=> self.builder.build_float_compare(FloatPredicate::OGT, a, b, "tmpogt"),
|
||||
ast::Comparison::GreaterOrEqual
|
||||
=> self.builder.build_float_compare(FloatPredicate::OGE, a, b, "tmpoge"),
|
||||
_ => return Err(self.compile_error(CompileErrorKind::Unsupported("special comparison"))),
|
||||
}
|
||||
},
|
||||
_ => return Err(self.compile_error(CompileErrorKind::Unsupported("comparison of non-numerical types"))),
|
||||
};
|
||||
match result {
|
||||
Some(last) => {
|
||||
result = Some(self.builder.build_and(last, this_result, "tmpand"));
|
||||
}
|
||||
None => {
|
||||
result = Some(this_result);
|
||||
}
|
||||
}
|
||||
a = b;
|
||||
} else {
|
||||
return Ok(result.unwrap().into())
|
||||
}
|
||||
}
|
||||
},
|
||||
ast::ExpressionType::Call { function, args, keywords } => {
|
||||
if !keywords.is_empty() {
|
||||
return Err(self.compile_error(CompileErrorKind::Unsupported("keyword arguments")))
|
||||
}
|
||||
let args = args.iter().map(|val| self.compile_expression(val))
|
||||
.collect::<CompileResult<Vec<values::BasicValueEnum>>>()?;
|
||||
self.set_source_location(expression.location);
|
||||
if let ast::ExpressionType::Identifier { name } = &function.node {
|
||||
match (name.as_str(), args[0]) {
|
||||
("int32", values::BasicValueEnum::IntValue(a)) => {
|
||||
let nbits = a.get_type().get_bit_width();
|
||||
if nbits < 32 {
|
||||
Ok(self.builder.build_int_s_extend(a, self.context.i32_type(), "tmpsext").into())
|
||||
} else if nbits > 32 {
|
||||
Ok(self.builder.build_int_truncate(a, self.context.i32_type(), "tmptrunc").into())
|
||||
} else {
|
||||
Ok(a.into())
|
||||
}
|
||||
},
|
||||
("int64", values::BasicValueEnum::IntValue(a)) => {
|
||||
let nbits = a.get_type().get_bit_width();
|
||||
if nbits < 64 {
|
||||
Ok(self.builder.build_int_s_extend(a, self.context.i64_type(), "tmpsext").into())
|
||||
} else {
|
||||
Ok(a.into())
|
||||
}
|
||||
},
|
||||
("int32", values::BasicValueEnum::FloatValue(a)) => {
|
||||
Ok(self.builder.build_float_to_signed_int(a, self.context.i32_type(), "tmpfptosi").into())
|
||||
},
|
||||
("int64", values::BasicValueEnum::FloatValue(a)) => {
|
||||
Ok(self.builder.build_float_to_signed_int(a, self.context.i64_type(), "tmpfptosi").into())
|
||||
},
|
||||
("float32", values::BasicValueEnum::IntValue(a)) => {
|
||||
Ok(self.builder.build_signed_int_to_float(a, self.context.f32_type(), "tmpsitofp").into())
|
||||
},
|
||||
("float64", values::BasicValueEnum::IntValue(a)) => {
|
||||
Ok(self.builder.build_signed_int_to_float(a, self.context.f64_type(), "tmpsitofp").into())
|
||||
},
|
||||
("float32", values::BasicValueEnum::FloatValue(a)) => {
|
||||
if a.get_type() == self.context.f64_type() {
|
||||
Ok(self.builder.build_float_trunc(a, self.context.f32_type(), "tmptrunc").into())
|
||||
} else {
|
||||
Ok(a.into())
|
||||
}
|
||||
},
|
||||
("float64", values::BasicValueEnum::FloatValue(a)) => {
|
||||
if a.get_type() == self.context.f32_type() {
|
||||
Ok(self.builder.build_float_ext(a, self.context.f64_type(), "tmpext").into())
|
||||
} else {
|
||||
Ok(a.into())
|
||||
}
|
||||
},
|
||||
|
||||
("output", values::BasicValueEnum::IntValue(a)) => {
|
||||
let fn_value = self.module.get_function("output").unwrap();
|
||||
Ok(self.builder.build_call(fn_value, &[a.into()], "call")
|
||||
.try_as_basic_value().left().unwrap())
|
||||
},
|
||||
_ => Err(self.compile_error(CompileErrorKind::Unsupported("unrecognized call")))
|
||||
}
|
||||
} else {
|
||||
return Err(self.compile_error(CompileErrorKind::Unsupported("function must be an identifier")))
|
||||
}
|
||||
},
|
||||
_ => return Err(self.compile_error(CompileErrorKind::Unsupported("unimplemented expression"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_statement(
|
||||
&mut self,
|
||||
statement: &ast::Statement,
|
||||
return_type: Option<types::BasicTypeEnum>
|
||||
) -> CompileResult<()> {
|
||||
self.set_source_location(statement.location);
|
||||
|
||||
use ast::StatementType::*;
|
||||
match &statement.node {
|
||||
Assign { targets, value } => {
|
||||
let value = self.compile_expression(value)?;
|
||||
for target in targets.iter() {
|
||||
self.set_source_location(target.location);
|
||||
if let ast::ExpressionType::Identifier { name } = &target.node {
|
||||
let builder = &self.builder;
|
||||
let target = self.namespace.entry(name.clone()).or_insert_with(
|
||||
|| builder.build_alloca(value.get_type(), name));
|
||||
if target.get_type() != value.get_type().ptr_type(inkwell::AddressSpace::Generic) {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
builder.build_store(*target, value);
|
||||
} else {
|
||||
return Err(self.compile_error(CompileErrorKind::Unsupported("assignment target must be an identifier")))
|
||||
}
|
||||
}
|
||||
},
|
||||
Expression { expression } => { self.compile_expression(expression)?; },
|
||||
If { test, body, orelse } => {
|
||||
let test = self.compile_expression(test)?;
|
||||
if test.get_type() != self.context.bool_type().into() {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
|
||||
let parent = self.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let then_bb = self.context.append_basic_block(parent, "then");
|
||||
let else_bb = self.context.append_basic_block(parent, "else");
|
||||
let cont_bb = self.context.append_basic_block(parent, "ifcont");
|
||||
self.builder.build_conditional_branch(test.into_int_value(), then_bb, else_bb);
|
||||
|
||||
self.builder.position_at_end(then_bb);
|
||||
self.compile_suite(body, return_type)?;
|
||||
self.builder.build_unconditional_branch(cont_bb);
|
||||
|
||||
self.builder.position_at_end(else_bb);
|
||||
if let Some(orelse) = orelse {
|
||||
self.compile_suite(orelse, return_type)?;
|
||||
}
|
||||
self.builder.build_unconditional_branch(cont_bb);
|
||||
self.builder.position_at_end(cont_bb);
|
||||
},
|
||||
While { test, body, orelse } => {
|
||||
let parent = self.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let test_bb = self.context.append_basic_block(parent, "test");
|
||||
self.builder.build_unconditional_branch(test_bb);
|
||||
self.builder.position_at_end(test_bb);
|
||||
let test = self.compile_expression(test)?;
|
||||
if test.get_type() != self.context.bool_type().into() {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
|
||||
let then_bb = self.context.append_basic_block(parent, "then");
|
||||
let else_bb = self.context.append_basic_block(parent, "else");
|
||||
let cont_bb = self.context.append_basic_block(parent, "ifcont");
|
||||
self.builder.build_conditional_branch(test.into_int_value(), then_bb, else_bb);
|
||||
|
||||
self.break_bb = Some(cont_bb);
|
||||
|
||||
self.builder.position_at_end(then_bb);
|
||||
self.compile_suite(body, return_type)?;
|
||||
self.builder.build_unconditional_branch(test_bb);
|
||||
|
||||
self.builder.position_at_end(else_bb);
|
||||
if let Some(orelse) = orelse {
|
||||
self.compile_suite(orelse, return_type)?;
|
||||
}
|
||||
self.builder.build_unconditional_branch(cont_bb);
|
||||
self.builder.position_at_end(cont_bb);
|
||||
|
||||
self.break_bb = None;
|
||||
},
|
||||
Break => {
|
||||
if let Some(bb) = self.break_bb {
|
||||
self.builder.build_unconditional_branch(bb);
|
||||
let parent = self.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let unreachable_bb = self.context.append_basic_block(parent, "unreachable");
|
||||
self.builder.position_at_end(unreachable_bb);
|
||||
} else {
|
||||
return Err(self.compile_error(CompileErrorKind::BreakOutsideLoop));
|
||||
}
|
||||
}
|
||||
Return { value: Some(value) } => {
|
||||
if let Some(return_type) = return_type {
|
||||
let value = self.compile_expression(value)?;
|
||||
if value.get_type() != return_type {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
self.builder.build_return(Some(&value));
|
||||
} else {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
},
|
||||
Return { value: None } => {
|
||||
if !return_type.is_none() {
|
||||
return Err(self.compile_error(CompileErrorKind::IncompatibleTypes));
|
||||
}
|
||||
self.builder.build_return(None);
|
||||
},
|
||||
Pass => (),
|
||||
_ => return Err(self.compile_error(CompileErrorKind::Unsupported("special statement"))),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_suite(
|
||||
&mut self,
|
||||
suite: &ast::Suite,
|
||||
return_type: Option<types::BasicTypeEnum>
|
||||
) -> CompileResult<()> {
|
||||
for statement in suite.iter() {
|
||||
self.compile_statement(statement, return_type)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_toplevel(&mut self, statement: &ast::Statement) -> CompileResult<()> {
|
||||
self.set_source_location(statement.location);
|
||||
if let ast::StatementType::FunctionDef {
|
||||
is_async,
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
returns,
|
||||
} = &statement.node {
|
||||
let function = self.compile_function_def(name, args, body, decorator_list, returns, *is_async)?;
|
||||
self.pass_manager.run_on(&function);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self.compile_error(CompileErrorKind::Internal("top-level is not a function definition")))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_ir(&self) {
|
||||
self.module.print_to_stderr();
|
||||
}
|
||||
|
||||
pub fn output(&self, filename: &str) {
|
||||
//let triple = TargetTriple::create("riscv32-none-linux-gnu");
|
||||
let triple = TargetMachine::get_default_triple();
|
||||
let target = Target::from_triple(&triple)
|
||||
.expect("couldn't create target from target triple");
|
||||
|
||||
let target_machine = target
|
||||
.create_target_machine(
|
||||
&triple,
|
||||
"",
|
||||
"",
|
||||
OptimizationLevel::Default,
|
||||
RelocMode::Default,
|
||||
CodeModel::Default,
|
||||
)
|
||||
.expect("couldn't create target machine");
|
||||
|
||||
target_machine
|
||||
.write_to_file(&self.module, FileType::Object, Path::new(filename))
|
||||
.expect("couldn't write module to file");
|
||||
}
|
||||
}
|
||||
pub mod codegen;
|
||||
pub mod symbol_resolver;
|
||||
pub mod toplevel;
|
||||
pub mod typecheck;
|
||||
|
@ -1,58 +0,0 @@
|
||||
use rustpython_parser::ast::{Comparison, Operator, UnaryOperator};
|
||||
|
||||
pub fn binop_name(op: &Operator) -> &'static str {
|
||||
match op {
|
||||
Operator::Add => "__add__",
|
||||
Operator::Sub => "__sub__",
|
||||
Operator::Div => "__truediv__",
|
||||
Operator::Mod => "__mod__",
|
||||
Operator::Mult => "__mul__",
|
||||
Operator::Pow => "__pow__",
|
||||
Operator::BitOr => "__or__",
|
||||
Operator::BitXor => "__xor__",
|
||||
Operator::BitAnd => "__and__",
|
||||
Operator::LShift => "__lshift__",
|
||||
Operator::RShift => "__rshift__",
|
||||
Operator::FloorDiv => "__floordiv__",
|
||||
Operator::MatMult => "__matmul__",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binop_assign_name(op: &Operator) -> &'static str {
|
||||
match op {
|
||||
Operator::Add => "__iadd__",
|
||||
Operator::Sub => "__isub__",
|
||||
Operator::Div => "__itruediv__",
|
||||
Operator::Mod => "__imod__",
|
||||
Operator::Mult => "__imul__",
|
||||
Operator::Pow => "__ipow__",
|
||||
Operator::BitOr => "__ior__",
|
||||
Operator::BitXor => "__ixor__",
|
||||
Operator::BitAnd => "__iand__",
|
||||
Operator::LShift => "__ilshift__",
|
||||
Operator::RShift => "__irshift__",
|
||||
Operator::FloorDiv => "__ifloordiv__",
|
||||
Operator::MatMult => "__imatmul__",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unaryop_name(op: &UnaryOperator) -> &'static str {
|
||||
match op {
|
||||
UnaryOperator::Pos => "__pos__",
|
||||
UnaryOperator::Neg => "__neg__",
|
||||
UnaryOperator::Not => "__not__",
|
||||
UnaryOperator::Inv => "__inv__",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn comparison_name(op: &Comparison) -> Option<&'static str> {
|
||||
match op {
|
||||
Comparison::Less => Some("__lt__"),
|
||||
Comparison::LessOrEqual => Some("__le__"),
|
||||
Comparison::Greater => Some("__gt__"),
|
||||
Comparison::GreaterOrEqual => Some("__ge__"),
|
||||
Comparison::Equal => Some("__eq__"),
|
||||
Comparison::NotEqual => Some("__ne__"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
use super::typedef::{TypeEnum::*, *};
|
||||
use crate::context::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const TUPLE_TYPE: ParamId = ParamId(0);
|
||||
pub const LIST_TYPE: ParamId = ParamId(1);
|
||||
|
||||
pub const BOOL_TYPE: PrimitiveId = PrimitiveId(0);
|
||||
pub const INT32_TYPE: PrimitiveId = PrimitiveId(1);
|
||||
pub const INT64_TYPE: PrimitiveId = PrimitiveId(2);
|
||||
pub const FLOAT_TYPE: PrimitiveId = PrimitiveId(3);
|
||||
|
||||
fn impl_math(def: &mut TypeDef, ty: &Type) {
|
||||
let result = Some(ty.clone());
|
||||
let fun = FnDef {
|
||||
args: vec![ty.clone()],
|
||||
result: result.clone(),
|
||||
};
|
||||
def.methods.insert("__add__", fun.clone());
|
||||
def.methods.insert("__sub__", fun.clone());
|
||||
def.methods.insert("__mul__", fun.clone());
|
||||
def.methods.insert(
|
||||
"__neg__",
|
||||
FnDef {
|
||||
args: vec![],
|
||||
result,
|
||||
},
|
||||
);
|
||||
def.methods.insert(
|
||||
"__truediv__",
|
||||
FnDef {
|
||||
args: vec![ty.clone()],
|
||||
result: Some(PrimitiveType(FLOAT_TYPE).into()),
|
||||
},
|
||||
);
|
||||
def.methods.insert("__floordiv__", fun.clone());
|
||||
def.methods.insert("__mod__", fun.clone());
|
||||
def.methods.insert("__pow__", fun);
|
||||
}
|
||||
|
||||
fn impl_bits(def: &mut TypeDef, ty: &Type) {
|
||||
let result = Some(ty.clone());
|
||||
let fun = FnDef {
|
||||
args: vec![PrimitiveType(INT32_TYPE).into()],
|
||||
result,
|
||||
};
|
||||
|
||||
def.methods.insert("__lshift__", fun.clone());
|
||||
def.methods.insert("__rshift__", fun);
|
||||
def.methods.insert(
|
||||
"__xor__",
|
||||
FnDef {
|
||||
args: vec![ty.clone()],
|
||||
result: Some(ty.clone()),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn impl_eq(def: &mut TypeDef, ty: &Type) {
|
||||
let fun = FnDef {
|
||||
args: vec![ty.clone()],
|
||||
result: Some(PrimitiveType(BOOL_TYPE).into()),
|
||||
};
|
||||
|
||||
def.methods.insert("__eq__", fun.clone());
|
||||
def.methods.insert("__ne__", fun);
|
||||
}
|
||||
|
||||
fn impl_order(def: &mut TypeDef, ty: &Type) {
|
||||
let fun = FnDef {
|
||||
args: vec![ty.clone()],
|
||||
result: Some(PrimitiveType(BOOL_TYPE).into()),
|
||||
};
|
||||
|
||||
def.methods.insert("__lt__", fun.clone());
|
||||
def.methods.insert("__gt__", fun.clone());
|
||||
def.methods.insert("__le__", fun.clone());
|
||||
def.methods.insert("__ge__", fun);
|
||||
}
|
||||
|
||||
pub fn basic_ctx() -> TopLevelContext<'static> {
|
||||
let primitives = [
|
||||
TypeDef {
|
||||
name: "bool",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
TypeDef {
|
||||
name: "int32",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
TypeDef {
|
||||
name: "int64",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
TypeDef {
|
||||
name: "float",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
]
|
||||
.to_vec();
|
||||
let mut ctx = TopLevelContext::new(primitives);
|
||||
|
||||
let b = ctx.get_primitive(BOOL_TYPE);
|
||||
let b_def = ctx.get_primitive_def_mut(BOOL_TYPE);
|
||||
impl_eq(b_def, &b);
|
||||
let int32 = ctx.get_primitive(INT32_TYPE);
|
||||
let int32_def = ctx.get_primitive_def_mut(INT32_TYPE);
|
||||
impl_math(int32_def, &int32);
|
||||
impl_bits(int32_def, &int32);
|
||||
impl_order(int32_def, &int32);
|
||||
impl_eq(int32_def, &int32);
|
||||
let int64 = ctx.get_primitive(INT64_TYPE);
|
||||
let int64_def = ctx.get_primitive_def_mut(INT64_TYPE);
|
||||
impl_math(int64_def, &int64);
|
||||
impl_bits(int64_def, &int64);
|
||||
impl_order(int64_def, &int64);
|
||||
impl_eq(int64_def, &int64);
|
||||
let float = ctx.get_primitive(FLOAT_TYPE);
|
||||
let float_def = ctx.get_primitive_def_mut(FLOAT_TYPE);
|
||||
impl_math(float_def, &float);
|
||||
impl_order(float_def, &float);
|
||||
impl_eq(float_def, &float);
|
||||
|
||||
let t = ctx.add_variable_private(VarDef {
|
||||
name: "T",
|
||||
bound: vec![],
|
||||
});
|
||||
|
||||
ctx.add_parametric(ParametricDef {
|
||||
base: TypeDef {
|
||||
name: "tuple",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
// we have nothing for tuple, so no param def
|
||||
params: vec![],
|
||||
});
|
||||
|
||||
ctx.add_parametric(ParametricDef {
|
||||
base: TypeDef {
|
||||
name: "list",
|
||||
fields: HashMap::new(),
|
||||
methods: HashMap::new(),
|
||||
},
|
||||
params: vec![t],
|
||||
});
|
||||
|
||||
let i = ctx.add_variable_private(VarDef {
|
||||
name: "I",
|
||||
bound: vec![
|
||||
PrimitiveType(INT32_TYPE).into(),
|
||||
PrimitiveType(INT64_TYPE).into(),
|
||||
PrimitiveType(FLOAT_TYPE).into(),
|
||||
],
|
||||
});
|
||||
let args = vec![TypeVariable(i).into()];
|
||||
ctx.add_fn(
|
||||
"int32",
|
||||
FnDef {
|
||||
args: args.clone(),
|
||||
result: Some(PrimitiveType(INT32_TYPE).into()),
|
||||
},
|
||||
);
|
||||
ctx.add_fn(
|
||||
"int64",
|
||||
FnDef {
|
||||
args: args.clone(),
|
||||
result: Some(PrimitiveType(INT64_TYPE).into()),
|
||||
},
|
||||
);
|
||||
ctx.add_fn(
|
||||
"float",
|
||||
FnDef {
|
||||
args,
|
||||
result: Some(PrimitiveType(FLOAT_TYPE).into()),
|
||||
},
|
||||
);
|
||||
|
||||
ctx
|
||||
}
|
366
nac3core/src/symbol_resolver.rs
Normal file
366
nac3core/src/symbol_resolver.rs
Normal file
@ -0,0 +1,366 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use crate::typecheck::typedef::TypeEnum;
|
||||
use crate::{
|
||||
codegen::CodeGenContext,
|
||||
toplevel::{DefinitionId, TopLevelDef},
|
||||
};
|
||||
use crate::{
|
||||
codegen::CodeGenerator,
|
||||
typecheck::{
|
||||
type_inferencer::PrimitiveStore,
|
||||
typedef::{Type, Unifier},
|
||||
},
|
||||
};
|
||||
use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue};
|
||||
use itertools::{chain, izip};
|
||||
use nac3parser::ast::{Expr, Location, StrRef};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum SymbolValue {
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
Str(String),
|
||||
Double(f64),
|
||||
Bool(bool),
|
||||
Tuple(Vec<SymbolValue>),
|
||||
}
|
||||
|
||||
impl Display for SymbolValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SymbolValue::I32(i) => write!(f, "{}", i),
|
||||
SymbolValue::I64(i) => write!(f, "int64({})", i),
|
||||
SymbolValue::U32(i) => write!(f, "uint32({})", i),
|
||||
SymbolValue::U64(i) => write!(f, "uint64({})", i),
|
||||
SymbolValue::Str(s) => write!(f, "\"{}\"", s),
|
||||
SymbolValue::Double(d) => write!(f, "{}", d),
|
||||
SymbolValue::Bool(b) => {
|
||||
if *b {
|
||||
write!(f, "True")
|
||||
} else {
|
||||
write!(f, "False")
|
||||
}
|
||||
}
|
||||
SymbolValue::Tuple(t) => {
|
||||
write!(f, "({})", t.iter().map(|v| format!("{}", v)).collect::<Vec<_>>().join(", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StaticValue {
|
||||
fn get_unique_identifier(&self) -> u64;
|
||||
|
||||
fn get_const_obj<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> BasicValueEnum<'ctx>;
|
||||
|
||||
fn to_basic_value_enum<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> Result<BasicValueEnum<'ctx>, String>;
|
||||
|
||||
fn get_field<'ctx, 'a>(
|
||||
&self,
|
||||
name: StrRef,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
) -> Option<ValueEnum<'ctx>>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ValueEnum<'ctx> {
|
||||
Static(Arc<dyn StaticValue + Send + Sync>),
|
||||
Dynamic(BasicValueEnum<'ctx>),
|
||||
}
|
||||
|
||||
impl<'ctx> From<BasicValueEnum<'ctx>> for ValueEnum<'ctx> {
|
||||
fn from(v: BasicValueEnum<'ctx>) -> Self {
|
||||
ValueEnum::Dynamic(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<PointerValue<'ctx>> for ValueEnum<'ctx> {
|
||||
fn from(v: PointerValue<'ctx>) -> Self {
|
||||
ValueEnum::Dynamic(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<IntValue<'ctx>> for ValueEnum<'ctx> {
|
||||
fn from(v: IntValue<'ctx>) -> Self {
|
||||
ValueEnum::Dynamic(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<FloatValue<'ctx>> for ValueEnum<'ctx> {
|
||||
fn from(v: FloatValue<'ctx>) -> Self {
|
||||
ValueEnum::Dynamic(v.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ValueEnum<'ctx> {
|
||||
pub fn to_basic_value_enum<'a>(
|
||||
self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> Result<BasicValueEnum<'ctx>, String> {
|
||||
match self {
|
||||
ValueEnum::Static(v) => v.to_basic_value_enum(ctx, generator),
|
||||
ValueEnum::Dynamic(v) => Ok(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SymbolResolver {
|
||||
// get type of type variable identifier or top-level function type
|
||||
fn get_symbol_type(
|
||||
&self,
|
||||
unifier: &mut Unifier,
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
primitives: &PrimitiveStore,
|
||||
str: StrRef,
|
||||
) -> Result<Type, String>;
|
||||
|
||||
// get the top-level definition of identifiers
|
||||
fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, String>;
|
||||
|
||||
fn get_symbol_value<'ctx, 'a>(
|
||||
&self,
|
||||
str: StrRef,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
) -> Option<ValueEnum<'ctx>>;
|
||||
|
||||
fn get_default_param_value(&self, expr: &nac3parser::ast::Expr) -> Option<SymbolValue>;
|
||||
fn get_string_id(&self, s: &str) -> i32;
|
||||
fn get_exception_id(&self, tyid: usize) -> usize;
|
||||
|
||||
fn handle_deferred_eval(
|
||||
&self,
|
||||
_unifier: &mut Unifier,
|
||||
_top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
_primitives: &PrimitiveStore
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static IDENTIFIER_ID: [StrRef; 11] = [
|
||||
"int32".into(),
|
||||
"int64".into(),
|
||||
"float".into(),
|
||||
"bool".into(),
|
||||
"virtual".into(),
|
||||
"list".into(),
|
||||
"tuple".into(),
|
||||
"str".into(),
|
||||
"Exception".into(),
|
||||
"uint32".into(),
|
||||
"uint64".into(),
|
||||
];
|
||||
}
|
||||
|
||||
// convert type annotation into type
|
||||
pub fn parse_type_annotation<T>(
|
||||
resolver: &dyn SymbolResolver,
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
expr: &Expr<T>,
|
||||
) -> Result<Type, String> {
|
||||
use nac3parser::ast::ExprKind::*;
|
||||
let ids = IDENTIFIER_ID.with(|ids| *ids);
|
||||
let int32_id = ids[0];
|
||||
let int64_id = ids[1];
|
||||
let float_id = ids[2];
|
||||
let bool_id = ids[3];
|
||||
let virtual_id = ids[4];
|
||||
let list_id = ids[5];
|
||||
let tuple_id = ids[6];
|
||||
let str_id = ids[7];
|
||||
let exn_id = ids[8];
|
||||
let uint32_id = ids[9];
|
||||
let uint64_id = ids[10];
|
||||
|
||||
let name_handling = |id: &StrRef, loc: Location, unifier: &mut Unifier| {
|
||||
if *id == int32_id {
|
||||
Ok(primitives.int32)
|
||||
} else if *id == int64_id {
|
||||
Ok(primitives.int64)
|
||||
} else if *id == uint32_id {
|
||||
Ok(primitives.uint32)
|
||||
} else if *id == uint64_id {
|
||||
Ok(primitives.uint64)
|
||||
} else if *id == float_id {
|
||||
Ok(primitives.float)
|
||||
} else if *id == bool_id {
|
||||
Ok(primitives.bool)
|
||||
} else if *id == str_id {
|
||||
Ok(primitives.str)
|
||||
} else if *id == exn_id {
|
||||
Ok(primitives.exception)
|
||||
} else {
|
||||
let obj_id = resolver.get_identifier_def(*id);
|
||||
match obj_id {
|
||||
Ok(obj_id) => {
|
||||
let def = top_level_defs[obj_id.0].read();
|
||||
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
|
||||
if !type_vars.is_empty() {
|
||||
return Err(format!(
|
||||
"Unexpected number of type parameters: expected {} but got 0",
|
||||
type_vars.len()
|
||||
));
|
||||
}
|
||||
let fields = chain(
|
||||
fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
|
||||
methods.iter().map(|(k, v, _)| (*k, (*v, false))),
|
||||
)
|
||||
.collect();
|
||||
Ok(unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id,
|
||||
fields,
|
||||
params: Default::default(),
|
||||
}))
|
||||
} else {
|
||||
Err(format!("Cannot use function name as type at {}", loc))
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
let ty = resolver
|
||||
.get_symbol_type(unifier, top_level_defs, primitives, *id)
|
||||
.map_err(|e| format!("Unknown type annotation at {}: {}", loc, e))?;
|
||||
if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) {
|
||||
Ok(ty)
|
||||
} else {
|
||||
Err(format!("Unknown type annotation {} at {}", id, loc))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let subscript_name_handle = |id: &StrRef, slice: &Expr<T>, unifier: &mut Unifier| {
|
||||
if *id == virtual_id {
|
||||
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
|
||||
Ok(unifier.add_ty(TypeEnum::TVirtual { ty }))
|
||||
} else if *id == list_id {
|
||||
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
|
||||
Ok(unifier.add_ty(TypeEnum::TList { ty }))
|
||||
} else if *id == tuple_id {
|
||||
if let Tuple { elts, .. } = &slice.node {
|
||||
let ty = elts
|
||||
.iter()
|
||||
.map(|elt| {
|
||||
parse_type_annotation(resolver, top_level_defs, unifier, primitives, elt)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(unifier.add_ty(TypeEnum::TTuple { ty }))
|
||||
} else {
|
||||
Err("Expected multiple elements for tuple".into())
|
||||
}
|
||||
} else {
|
||||
let types = if let Tuple { elts, .. } = &slice.node {
|
||||
elts.iter()
|
||||
.map(|v| {
|
||||
parse_type_annotation(resolver, top_level_defs, unifier, primitives, v)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
} else {
|
||||
vec![parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?]
|
||||
};
|
||||
|
||||
let obj_id = resolver.get_identifier_def(*id)?;
|
||||
let def = top_level_defs[obj_id.0].read();
|
||||
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
|
||||
if types.len() != type_vars.len() {
|
||||
return Err(format!(
|
||||
"Unexpected number of type parameters: expected {} but got {}",
|
||||
type_vars.len(),
|
||||
types.len()
|
||||
));
|
||||
}
|
||||
let mut subst = HashMap::new();
|
||||
for (var, ty) in izip!(type_vars.iter(), types.iter()) {
|
||||
let id = if let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*var) {
|
||||
*id
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
subst.insert(id, *ty);
|
||||
}
|
||||
let mut fields = fields
|
||||
.iter()
|
||||
.map(|(attr, ty, is_mutable)| {
|
||||
let ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
|
||||
(*attr, (ty, *is_mutable))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
fields.extend(methods.iter().map(|(attr, ty, _)| {
|
||||
let ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
|
||||
(*attr, (ty, false))
|
||||
}));
|
||||
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst }))
|
||||
} else {
|
||||
Err("Cannot use function name as type".into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match &expr.node {
|
||||
Name { id, .. } => name_handling(id, expr.location, unifier),
|
||||
Subscript { value, slice, .. } => {
|
||||
if let Name { id, .. } = &value.node {
|
||||
subscript_name_handle(id, slice, unifier)
|
||||
} else {
|
||||
Err(format!("unsupported type expression at {}", expr.location))
|
||||
}
|
||||
}
|
||||
_ => Err(format!("unsupported type expression at {}", expr.location)),
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn SymbolResolver + Send + Sync {
|
||||
pub fn parse_type_annotation<T>(
|
||||
&self,
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
expr: &Expr<T>,
|
||||
) -> Result<Type, String> {
|
||||
parse_type_annotation(self, top_level_defs, unifier, primitives, expr)
|
||||
}
|
||||
|
||||
pub fn get_type_name(
|
||||
&self,
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
unifier: &mut Unifier,
|
||||
ty: Type,
|
||||
) -> String {
|
||||
unifier.internal_stringify(
|
||||
ty,
|
||||
&mut |id| {
|
||||
if let TopLevelDef::Class { name, .. } = &*top_level_defs[id].read() {
|
||||
name.to_string()
|
||||
} else {
|
||||
unreachable!("expected class definition")
|
||||
}
|
||||
},
|
||||
&mut |id| format!("var{}", id),
|
||||
&mut None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for dyn SymbolResolver + Send + Sync {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "")
|
||||
}
|
||||
}
|
1239
nac3core/src/toplevel/builtins.rs
Normal file
1239
nac3core/src/toplevel/builtins.rs
Normal file
File diff suppressed because it is too large
Load Diff
1949
nac3core/src/toplevel/composer.rs
Normal file
1949
nac3core/src/toplevel/composer.rs
Normal file
File diff suppressed because it is too large
Load Diff
570
nac3core/src/toplevel/helper.rs
Normal file
570
nac3core/src/toplevel/helper.rs
Normal file
@ -0,0 +1,570 @@
|
||||
use std::convert::TryInto;
|
||||
|
||||
use crate::symbol_resolver::SymbolValue;
|
||||
use nac3parser::ast::{Constant, Location};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl TopLevelDef {
|
||||
pub fn to_string(&self, unifier: &mut Unifier) -> String {
|
||||
match self {
|
||||
TopLevelDef::Class { name, ancestors, fields, methods, type_vars, .. } => {
|
||||
let fields_str = fields
|
||||
.iter()
|
||||
.map(|(n, ty, _)| (n.to_string(), unifier.stringify(*ty)))
|
||||
.collect_vec();
|
||||
|
||||
let methods_str = methods
|
||||
.iter()
|
||||
.map(|(n, ty, id)| (n.to_string(), unifier.stringify(*ty), *id))
|
||||
.collect_vec();
|
||||
format!(
|
||||
"Class {{\nname: {:?},\nancestors: {:?},\nfields: {:?},\nmethods: {:?},\ntype_vars: {:?}\n}}",
|
||||
name,
|
||||
ancestors.iter().map(|ancestor| ancestor.stringify(unifier)).collect_vec(),
|
||||
fields_str.iter().map(|(a, _)| a).collect_vec(),
|
||||
methods_str.iter().map(|(a, b, _)| (a, b)).collect_vec(),
|
||||
type_vars.iter().map(|id| unifier.stringify(*id)).collect_vec(),
|
||||
)
|
||||
}
|
||||
TopLevelDef::Function { name, signature, var_id, .. } => format!(
|
||||
"Function {{\nname: {:?},\nsig: {:?},\nvar_id: {:?}\n}}",
|
||||
name,
|
||||
unifier.stringify(*signature),
|
||||
{
|
||||
// preserve the order for debug output and test
|
||||
let mut r = var_id.clone();
|
||||
r.sort_unstable();
|
||||
r
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TopLevelComposer {
|
||||
pub fn make_primitives() -> (PrimitiveStore, Unifier) {
|
||||
let mut unifier = Unifier::new();
|
||||
let int32 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(0),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let int64 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(1),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let float = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(2),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let bool = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(3),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let none = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(4),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let range = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(5),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let str = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(6),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let exception = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(7),
|
||||
fields: vec![
|
||||
("__name__".into(), (int32, true)),
|
||||
("__file__".into(), (int32, true)),
|
||||
("__line__".into(), (int32, true)),
|
||||
("__col__".into(), (int32, true)),
|
||||
("__func__".into(), (str, true)),
|
||||
("__message__".into(), (str, true)),
|
||||
("__param0__".into(), (int64, true)),
|
||||
("__param1__".into(), (int64, true)),
|
||||
("__param2__".into(), (int64, true)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let uint32 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(8),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let uint64 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(9),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
|
||||
let option_type_var = unifier.get_fresh_var(Some("option_type_var".into()), None);
|
||||
let is_some_type_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![],
|
||||
ret: bool,
|
||||
vars: HashMap::from([(option_type_var.1, option_type_var.0)]),
|
||||
}));
|
||||
let unwrap_fun_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![],
|
||||
ret: option_type_var.0,
|
||||
vars: HashMap::from([(option_type_var.1, option_type_var.0)]),
|
||||
}));
|
||||
let option = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(10),
|
||||
fields: vec![
|
||||
("is_some".into(), (is_some_type_fun_ty, true)),
|
||||
("is_none".into(), (is_some_type_fun_ty, true)),
|
||||
("unwrap".into(), (unwrap_fun_ty, true)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
params: HashMap::from([(option_type_var.1, option_type_var.0)]),
|
||||
});
|
||||
|
||||
let primitives = PrimitiveStore {
|
||||
int32,
|
||||
int64,
|
||||
float,
|
||||
bool,
|
||||
none,
|
||||
range,
|
||||
str,
|
||||
exception,
|
||||
uint32,
|
||||
uint64,
|
||||
option,
|
||||
};
|
||||
crate::typecheck::magic_methods::set_primitives_magic_methods(&primitives, &mut unifier);
|
||||
(primitives, unifier)
|
||||
}
|
||||
|
||||
/// already include the definition_id of itself inside the ancestors vector
|
||||
/// when first regitering, the type_vars, fields, methods, ancestors are invalid
|
||||
pub fn make_top_level_class_def(
|
||||
index: usize,
|
||||
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
|
||||
name: StrRef,
|
||||
constructor: Option<Type>,
|
||||
loc: Option<Location>,
|
||||
) -> TopLevelDef {
|
||||
TopLevelDef::Class {
|
||||
name,
|
||||
object_id: DefinitionId(index),
|
||||
type_vars: Default::default(),
|
||||
fields: Default::default(),
|
||||
methods: Default::default(),
|
||||
ancestors: Default::default(),
|
||||
constructor,
|
||||
resolver,
|
||||
loc,
|
||||
}
|
||||
}
|
||||
|
||||
/// when first registering, the type is a invalid value
|
||||
pub fn make_top_level_function_def(
|
||||
name: String,
|
||||
simple_name: StrRef,
|
||||
ty: Type,
|
||||
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
|
||||
loc: Option<Location>,
|
||||
) -> TopLevelDef {
|
||||
TopLevelDef::Function {
|
||||
name,
|
||||
simple_name,
|
||||
signature: ty,
|
||||
var_id: Default::default(),
|
||||
instance_to_symbol: Default::default(),
|
||||
instance_to_stmt: Default::default(),
|
||||
resolver,
|
||||
codegen_callback: None,
|
||||
loc,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_class_method_name(mut class_name: String, method_name: &str) -> String {
|
||||
class_name.push('.');
|
||||
class_name.push_str(method_name);
|
||||
class_name
|
||||
}
|
||||
|
||||
pub fn get_class_method_def_info(
|
||||
class_methods_def: &[(StrRef, Type, DefinitionId)],
|
||||
method_name: StrRef,
|
||||
) -> Result<(Type, DefinitionId), String> {
|
||||
for (name, ty, def_id) in class_methods_def {
|
||||
if name == &method_name {
|
||||
return Ok((*ty, *def_id));
|
||||
}
|
||||
}
|
||||
Err(format!("no method {} in the current class", method_name))
|
||||
}
|
||||
|
||||
/// get all base class def id of a class, excluding itself. \
|
||||
/// this function should called only after the direct parent is set
|
||||
/// and before all the ancestors are set
|
||||
/// and when we allow single inheritance \
|
||||
/// the order of the returned list is from the child to the deepest ancestor
|
||||
pub fn get_all_ancestors_helper(
|
||||
child: &TypeAnnotation,
|
||||
temp_def_list: &[Arc<RwLock<TopLevelDef>>],
|
||||
) -> Result<Vec<TypeAnnotation>, String> {
|
||||
let mut result: Vec<TypeAnnotation> = Vec::new();
|
||||
let mut parent = Self::get_parent(child, temp_def_list);
|
||||
while let Some(p) = parent {
|
||||
parent = Self::get_parent(&p, temp_def_list);
|
||||
let p_id = if let TypeAnnotation::CustomClass { id, .. } = &p {
|
||||
*id
|
||||
} else {
|
||||
unreachable!("must be class kind annotation")
|
||||
};
|
||||
// check cycle
|
||||
let no_cycle = result.iter().all(|x| {
|
||||
if let TypeAnnotation::CustomClass { id, .. } = x {
|
||||
id.0 != p_id.0
|
||||
} else {
|
||||
unreachable!("must be class kind annotation")
|
||||
}
|
||||
});
|
||||
if no_cycle {
|
||||
result.push(p);
|
||||
} else {
|
||||
return Err("cyclic inheritance detected".into());
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// should only be called when finding all ancestors, so panic when wrong
|
||||
fn get_parent(
|
||||
child: &TypeAnnotation,
|
||||
temp_def_list: &[Arc<RwLock<TopLevelDef>>],
|
||||
) -> Option<TypeAnnotation> {
|
||||
let child_id = if let TypeAnnotation::CustomClass { id, .. } = child {
|
||||
*id
|
||||
} else {
|
||||
unreachable!("should be class type annotation")
|
||||
};
|
||||
let child_def = temp_def_list.get(child_id.0).unwrap();
|
||||
let child_def = child_def.read();
|
||||
if let TopLevelDef::Class { ancestors, .. } = &*child_def {
|
||||
if !ancestors.is_empty() {
|
||||
Some(ancestors[0].clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
unreachable!("child must be top level class def")
|
||||
}
|
||||
}
|
||||
|
||||
/// get the var_id of a given TVar type
|
||||
pub fn get_var_id(var_ty: Type, unifier: &mut Unifier) -> Result<u32, String> {
|
||||
if let TypeEnum::TVar { id, .. } = unifier.get_ty(var_ty).as_ref() {
|
||||
Ok(*id)
|
||||
} else {
|
||||
Err("not type var".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_overload_function_type(
|
||||
this: Type,
|
||||
other: Type,
|
||||
unifier: &mut Unifier,
|
||||
type_var_to_concrete_def: &HashMap<Type, TypeAnnotation>,
|
||||
) -> bool {
|
||||
let this = unifier.get_ty(this);
|
||||
let this = this.as_ref();
|
||||
let other = unifier.get_ty(other);
|
||||
let other = other.as_ref();
|
||||
if let (
|
||||
TypeEnum::TFunc(FunSignature { args: this_args, ret: this_ret, .. }),
|
||||
TypeEnum::TFunc(FunSignature { args: other_args, ret: other_ret, .. }),
|
||||
) = (this, other)
|
||||
{
|
||||
// check args
|
||||
let args_ok = this_args
|
||||
.iter()
|
||||
.map(|FuncArg { name, ty, .. }| (name, type_var_to_concrete_def.get(ty).unwrap()))
|
||||
.zip(other_args.iter().map(|FuncArg { name, ty, .. }| {
|
||||
(name, type_var_to_concrete_def.get(ty).unwrap())
|
||||
}))
|
||||
.all(|(this, other)| {
|
||||
if this.0 == &"self".into() && this.0 == other.0 {
|
||||
true
|
||||
} else {
|
||||
this.0 == other.0
|
||||
&& check_overload_type_annotation_compatible(this.1, other.1, unifier)
|
||||
}
|
||||
});
|
||||
|
||||
// check rets
|
||||
let ret_ok = check_overload_type_annotation_compatible(
|
||||
type_var_to_concrete_def.get(this_ret).unwrap(),
|
||||
type_var_to_concrete_def.get(other_ret).unwrap(),
|
||||
unifier,
|
||||
);
|
||||
|
||||
// return
|
||||
args_ok && ret_ok
|
||||
} else {
|
||||
unreachable!("this function must be called with function type")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_overload_field_type(
|
||||
this: Type,
|
||||
other: Type,
|
||||
unifier: &mut Unifier,
|
||||
type_var_to_concrete_def: &HashMap<Type, TypeAnnotation>,
|
||||
) -> bool {
|
||||
check_overload_type_annotation_compatible(
|
||||
type_var_to_concrete_def.get(&this).unwrap(),
|
||||
type_var_to_concrete_def.get(&other).unwrap(),
|
||||
unifier,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_all_assigned_field(stmts: &[ast::Stmt<()>]) -> Result<HashSet<StrRef>, String> {
|
||||
let mut result = HashSet::new();
|
||||
for s in stmts {
|
||||
match &s.node {
|
||||
ast::StmtKind::AnnAssign { target, .. }
|
||||
if {
|
||||
if let ast::ExprKind::Attribute { value, .. } = &target.node {
|
||||
if let ast::ExprKind::Name { id, .. } = &value.node {
|
||||
id == &"self".into()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} =>
|
||||
{
|
||||
return Err(format!(
|
||||
"redundant type annotation for class fields at {}",
|
||||
s.location
|
||||
))
|
||||
}
|
||||
ast::StmtKind::Assign { targets, .. } => {
|
||||
for t in targets {
|
||||
if let ast::ExprKind::Attribute { value, attr, .. } = &t.node {
|
||||
if let ast::ExprKind::Name { id, .. } = &value.node {
|
||||
if id == &"self".into() {
|
||||
result.insert(*attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: do not check for For and While?
|
||||
ast::StmtKind::For { body, orelse, .. }
|
||||
| ast::StmtKind::While { body, orelse, .. } => {
|
||||
result.extend(Self::get_all_assigned_field(body.as_slice())?);
|
||||
result.extend(Self::get_all_assigned_field(orelse.as_slice())?);
|
||||
}
|
||||
ast::StmtKind::If { body, orelse, .. } => {
|
||||
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())?
|
||||
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?)
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
result.extend(inited_for_sure);
|
||||
}
|
||||
ast::StmtKind::Try { body, orelse, finalbody, .. } => {
|
||||
let inited_for_sure = Self::get_all_assigned_field(body.as_slice())?
|
||||
.intersection(&Self::get_all_assigned_field(orelse.as_slice())?)
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
result.extend(inited_for_sure);
|
||||
result.extend(Self::get_all_assigned_field(finalbody.as_slice())?);
|
||||
}
|
||||
ast::StmtKind::With { body, .. } => {
|
||||
result.extend(Self::get_all_assigned_field(body.as_slice())?);
|
||||
}
|
||||
ast::StmtKind::Pass { .. } => {}
|
||||
ast::StmtKind::Assert { .. } => {}
|
||||
ast::StmtKind::Expr { .. } => {}
|
||||
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn parse_parameter_default_value(
|
||||
default: &ast::Expr,
|
||||
resolver: &(dyn SymbolResolver + Send + Sync),
|
||||
) -> Result<SymbolValue, String> {
|
||||
parse_parameter_default_value(default, resolver)
|
||||
}
|
||||
|
||||
pub fn check_default_param_type(
|
||||
val: &SymbolValue,
|
||||
ty: &TypeAnnotation,
|
||||
primitive: &PrimitiveStore,
|
||||
unifier: &mut Unifier,
|
||||
) -> Result<(), String> {
|
||||
let res = match val {
|
||||
SymbolValue::Bool(..) => {
|
||||
if matches!(ty, TypeAnnotation::Primitive(t) if *t == primitive.bool) {
|
||||
None
|
||||
} else {
|
||||
Some("bool".to_string())
|
||||
}
|
||||
}
|
||||
SymbolValue::Double(..) => {
|
||||
if matches!(ty, TypeAnnotation::Primitive(t) if *t == primitive.float) {
|
||||
None
|
||||
} else {
|
||||
Some("float".to_string())
|
||||
}
|
||||
}
|
||||
SymbolValue::I32(..) => {
|
||||
if matches!(ty, TypeAnnotation::Primitive(t) if *t == primitive.int32) {
|
||||
None
|
||||
} else {
|
||||
Some("int32".to_string())
|
||||
}
|
||||
}
|
||||
SymbolValue::I64(..) => {
|
||||
if matches!(ty, TypeAnnotation::Primitive(t) if *t == primitive.int64) {
|
||||
None
|
||||
} else {
|
||||
Some("int64".to_string())
|
||||
}
|
||||
}
|
||||
SymbolValue::U32(..) => {
|
||||
if matches!(ty, TypeAnnotation::Primitive(t) if *t == primitive.uint32) {
|
||||
None
|
||||
} else {
|
||||
Some("uint32".to_string())
|
||||
}
|
||||
}
|
||||
SymbolValue::U64(..) => {
|
||||
if matches!(ty, TypeAnnotation::Primitive(t) if *t == primitive.uint64) {
|
||||
None
|
||||
} else {
|
||||
Some("uint64".to_string())
|
||||
}
|
||||
}
|
||||
SymbolValue::Str(..) => {
|
||||
if matches!(ty, TypeAnnotation::Primitive(t) if *t == primitive.str) {
|
||||
None
|
||||
} else {
|
||||
Some("str".to_string())
|
||||
}
|
||||
}
|
||||
SymbolValue::Tuple(elts) => {
|
||||
if let TypeAnnotation::Tuple(elts_ty) = ty {
|
||||
for (e, t) in elts.iter().zip(elts_ty.iter()) {
|
||||
Self::check_default_param_type(e, t, primitive, unifier)?
|
||||
}
|
||||
if elts.len() != elts_ty.len() {
|
||||
Some(format!("tuple of length {}", elts.len()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some("tuple".to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(found) = res {
|
||||
Err(format!(
|
||||
"incompatible default parameter type, expect {}, found {}",
|
||||
ty.stringify(unifier),
|
||||
found
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_parameter_default_value(
|
||||
default: &ast::Expr,
|
||||
resolver: &(dyn SymbolResolver + Send + Sync),
|
||||
) -> Result<SymbolValue, String> {
|
||||
fn handle_constant(val: &Constant, loc: &Location) -> Result<SymbolValue, String> {
|
||||
match val {
|
||||
Constant::Int(v) => {
|
||||
if let Ok(v) = (*v).try_into() {
|
||||
Ok(SymbolValue::I32(v))
|
||||
} else {
|
||||
Err(format!("integer value out of range at {}", loc))
|
||||
}
|
||||
}
|
||||
Constant::Float(v) => Ok(SymbolValue::Double(*v)),
|
||||
Constant::Bool(v) => Ok(SymbolValue::Bool(*v)),
|
||||
Constant::Tuple(tuple) => Ok(SymbolValue::Tuple(
|
||||
tuple.iter().map(|x| handle_constant(x, loc)).collect::<Result<Vec<_>, _>>()?,
|
||||
)),
|
||||
_ => unimplemented!("this constant is not supported at {}", loc),
|
||||
}
|
||||
}
|
||||
match &default.node {
|
||||
ast::ExprKind::Constant { value, .. } => handle_constant(value, &default.location),
|
||||
ast::ExprKind::Call { func, args, .. } if args.len() == 1 => {
|
||||
match &func.node {
|
||||
ast::ExprKind::Name { id, .. } if *id == "int64".into() => match &args[0].node {
|
||||
ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
|
||||
let v: Result<i64, _> = (*v).try_into();
|
||||
match v {
|
||||
Ok(v) => Ok(SymbolValue::I64(v)),
|
||||
_ => Err(format!("default param value out of range at {}", default.location)),
|
||||
}
|
||||
}
|
||||
_ => Err(format!("only allow constant integer here at {}", default.location))
|
||||
}
|
||||
ast::ExprKind::Name { id, .. } if *id == "uint32".into() => match &args[0].node {
|
||||
ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
|
||||
let v: Result<u32, _> = (*v).try_into();
|
||||
match v {
|
||||
Ok(v) => Ok(SymbolValue::U32(v)),
|
||||
_ => Err(format!("default param value out of range at {}", default.location)),
|
||||
}
|
||||
}
|
||||
_ => Err(format!("only allow constant integer here at {}", default.location))
|
||||
}
|
||||
ast::ExprKind::Name { id, .. } if *id == "uint64".into() => match &args[0].node {
|
||||
ast::ExprKind::Constant { value: Constant::Int(v), .. } => {
|
||||
let v: Result<u64, _> = (*v).try_into();
|
||||
match v {
|
||||
Ok(v) => Ok(SymbolValue::U64(v)),
|
||||
_ => Err(format!("default param value out of range at {}", default.location)),
|
||||
}
|
||||
}
|
||||
_ => Err(format!("only allow constant integer here at {}", default.location))
|
||||
}
|
||||
_ => Err(format!("unsupported default parameter at {}", default.location)),
|
||||
}
|
||||
}
|
||||
ast::ExprKind::Tuple { elts, .. } => Ok(SymbolValue::Tuple(elts
|
||||
.iter()
|
||||
.map(|x| parse_parameter_default_value(x, resolver))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
)),
|
||||
ast::ExprKind::Name { id, .. } => {
|
||||
resolver.get_default_param_value(default).ok_or_else(
|
||||
|| format!(
|
||||
"`{}` cannot be used as a default parameter at {} (not primitive type or tuple / not defined?)",
|
||||
id,
|
||||
default.location
|
||||
)
|
||||
)
|
||||
}
|
||||
_ => Err(format!("unsupported default parameter at {}", default.location))
|
||||
}
|
||||
}
|
138
nac3core/src/toplevel/mod.rs
Normal file
138
nac3core/src/toplevel/mod.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Debug,
|
||||
iter::FromIterator,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use super::codegen::CodeGenContext;
|
||||
use super::typecheck::type_inferencer::PrimitiveStore;
|
||||
use super::typecheck::typedef::{FunSignature, FuncArg, SharedUnifier, Type, TypeEnum, Unifier};
|
||||
use crate::{
|
||||
codegen::CodeGenerator,
|
||||
symbol_resolver::{SymbolResolver, ValueEnum},
|
||||
typecheck::{type_inferencer::CodeLocation, typedef::CallId},
|
||||
};
|
||||
use inkwell::values::BasicValueEnum;
|
||||
use itertools::{izip, Itertools};
|
||||
use nac3parser::ast::{self, Location, Stmt, StrRef};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
|
||||
pub struct DefinitionId(pub usize);
|
||||
|
||||
pub mod builtins;
|
||||
pub mod composer;
|
||||
pub mod helper;
|
||||
pub mod type_annotation;
|
||||
use composer::*;
|
||||
use type_annotation::*;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
type GenCallCallback = Box<
|
||||
dyn for<'ctx, 'a> Fn(
|
||||
&mut CodeGenContext<'ctx, 'a>,
|
||||
Option<(Type, ValueEnum<'ctx>)>,
|
||||
(&FunSignature, DefinitionId),
|
||||
Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
&mut dyn CodeGenerator,
|
||||
) -> Result<Option<BasicValueEnum<'ctx>>, String>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
|
||||
pub struct GenCall {
|
||||
fp: GenCallCallback,
|
||||
}
|
||||
|
||||
impl GenCall {
|
||||
pub fn new(fp: GenCallCallback) -> GenCall {
|
||||
GenCall { fp }
|
||||
}
|
||||
|
||||
pub fn run<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
fun: (&FunSignature, DefinitionId),
|
||||
args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> Result<Option<BasicValueEnum<'ctx>>, String> {
|
||||
(self.fp)(ctx, obj, fun, args, generator)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for GenCall {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FunInstance {
|
||||
pub body: Arc<Vec<Stmt<Option<Type>>>>,
|
||||
pub calls: Arc<HashMap<CodeLocation, CallId>>,
|
||||
pub subst: HashMap<u32, Type>,
|
||||
pub unifier_id: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TopLevelDef {
|
||||
Class {
|
||||
// name for error messages and symbols
|
||||
name: StrRef,
|
||||
// object ID used for TypeEnum
|
||||
object_id: DefinitionId,
|
||||
/// type variables bounded to the class.
|
||||
type_vars: Vec<Type>,
|
||||
// class fields
|
||||
// name, type, is mutable
|
||||
fields: Vec<(StrRef, Type, bool)>,
|
||||
// class methods, pointing to the corresponding function definition.
|
||||
methods: Vec<(StrRef, Type, DefinitionId)>,
|
||||
// ancestor classes, including itself.
|
||||
ancestors: Vec<TypeAnnotation>,
|
||||
// symbol resolver of the module defined the class, none if it is built-in type
|
||||
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
|
||||
// constructor type
|
||||
constructor: Option<Type>,
|
||||
// definition location
|
||||
loc: Option<Location>,
|
||||
},
|
||||
Function {
|
||||
// prefix for symbol, should be unique globally
|
||||
name: String,
|
||||
// simple name, the same as in method/function definition
|
||||
simple_name: StrRef,
|
||||
// function signature.
|
||||
signature: Type,
|
||||
// instantiated type variable IDs
|
||||
var_id: Vec<u32>,
|
||||
/// Function instance to symbol mapping
|
||||
/// Key: string representation of type variable values, sorted by variable ID in ascending
|
||||
/// order, including type variables associated with the class.
|
||||
/// Value: function symbol name.
|
||||
instance_to_symbol: HashMap<String, String>,
|
||||
/// Function instances to annotated AST mapping
|
||||
/// Key: string representation of type variable values, sorted by variable ID in ascending
|
||||
/// order, including type variables associated with the class. Excluding rigid type
|
||||
/// variables.
|
||||
/// rigid type variables that would be substituted when the function is instantiated.
|
||||
instance_to_stmt: HashMap<String, FunInstance>,
|
||||
// symbol resolver of the module defined the class
|
||||
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
|
||||
// custom codegen callback
|
||||
codegen_callback: Option<Arc<GenCall>>,
|
||||
// definition location
|
||||
loc: Option<Location>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct TopLevelContext {
|
||||
pub definitions: Arc<RwLock<Vec<Arc<RwLock<TopLevelDef>>>>>,
|
||||
pub unifiers: Arc<RwLock<Vec<(SharedUnifier, PrimitiveStore)>>>,
|
||||
pub personality_symbol: Option<String>,
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
---
|
||||
source: nac3core/src/toplevel/test.rs
|
||||
assertion_line: 549
|
||||
expression: res_vec
|
||||
|
||||
---
|
||||
[
|
||||
"Class {\nname: \"Generic_A\",\nancestors: [\"{class: Generic_A, params: [\\\"V\\\"]}\", \"{class: B, params: []}\"],\nfields: [\"aa\", \"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\"), (\"fun\", \"fn[[a:int32], V]\")],\ntype_vars: [\"V\"]\n}\n",
|
||||
"Function {\nname: \"Generic_A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [18]\n}\n",
|
||||
"Class {\nname: \"B\",\nancestors: [\"{class: B, params: []}\"],\nfields: [\"aa\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\")],\ntype_vars: []\n}\n",
|
||||
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"B.foo\",\nsig: \"fn[[b:T], none]\",\nvar_id: []\n}\n",
|
||||
]
|
@ -0,0 +1,17 @@
|
||||
---
|
||||
source: nac3core/src/toplevel/test.rs
|
||||
assertion_line: 549
|
||||
expression: res_vec
|
||||
|
||||
---
|
||||
[
|
||||
"Class {\nname: \"A\",\nancestors: [\"{class: A, params: [\\\"T\\\"]}\"],\nfields: [\"a\", \"b\", \"c\"],\nmethods: [(\"__init__\", \"fn[[t:T], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"T\"]\n}\n",
|
||||
"Function {\nname: \"A.__init__\",\nsig: \"fn[[t:T], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"A.foo\",\nsig: \"fn[[c:C], none]\",\nvar_id: []\n}\n",
|
||||
"Class {\nname: \"B\",\nancestors: [\"{class: B, params: [\\\"var7\\\"]}\", \"{class: A, params: [\\\"float\\\"]}\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"var7\"]\n}\n",
|
||||
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"B.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n",
|
||||
"Class {\nname: \"C\",\nancestors: [\"{class: C, params: []}\", \"{class: B, params: [\\\"bool\\\"]}\", \"{class: A, params: [\\\"float\\\"]}\"],\nfields: [\"a\", \"b\", \"c\", \"d\", \"e\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: []\n}\n",
|
||||
"Function {\nname: \"C.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
]
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
source: nac3core/src/toplevel/test.rs
|
||||
assertion_line: 549
|
||||
expression: res_vec
|
||||
|
||||
---
|
||||
[
|
||||
"Function {\nname: \"foo\",\nsig: \"fn[[a:list[int32], b:tuple[T, float]], A[B, bool]]\",\nvar_id: []\n}\n",
|
||||
"Class {\nname: \"A\",\nancestors: [\"{class: A, params: [\\\"T\\\", \\\"V\\\"]}\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[v:V], none]\"), (\"fun\", \"fn[[a:T], V]\")],\ntype_vars: [\"T\", \"V\"]\n}\n",
|
||||
"Function {\nname: \"A.__init__\",\nsig: \"fn[[v:V], none]\",\nvar_id: [20]\n}\n",
|
||||
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:T], V]\",\nvar_id: [25]\n}\n",
|
||||
"Function {\nname: \"gfun\",\nsig: \"fn[[a:A[int32, list[float]]], none]\",\nvar_id: []\n}\n",
|
||||
"Class {\nname: \"B\",\nancestors: [\"{class: B, params: []}\"],\nfields: [],\nmethods: [(\"__init__\", \"fn[[], none]\")],\ntype_vars: []\n}\n",
|
||||
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
]
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
source: nac3core/src/toplevel/test.rs
|
||||
assertion_line: 549
|
||||
expression: res_vec
|
||||
|
||||
---
|
||||
[
|
||||
"Class {\nname: \"A\",\nancestors: [\"{class: A, params: [\\\"var6\\\", \\\"var7\\\"]}\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[a:A[bool, float], b:B], none]\"), (\"fun\", \"fn[[a:A[bool, float]], A[bool, int32]]\")],\ntype_vars: [\"var6\", \"var7\"]\n}\n",
|
||||
"Function {\nname: \"A.__init__\",\nsig: \"fn[[a:A[bool, float], b:B], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:A[bool, float]], A[bool, int32]]\",\nvar_id: []\n}\n",
|
||||
"Class {\nname: \"B\",\nancestors: [\"{class: B, params: []}\", \"{class: A, params: [\\\"int64\\\", \\\"bool\\\"]}\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:A[bool, float]], A[bool, int32]]\"), (\"foo\", \"fn[[b:B], B]\"), (\"bar\", \"fn[[a:A[int32, list[B]]], tuple[A[bool, virtual[A[B, int32]]], B]]\")],\ntype_vars: []\n}\n",
|
||||
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"B.foo\",\nsig: \"fn[[b:B], B]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"B.bar\",\nsig: \"fn[[a:A[int32, list[B]]], tuple[A[bool, virtual[A[B, int32]]], B]]\",\nvar_id: []\n}\n",
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
source: nac3core/src/toplevel/test.rs
|
||||
assertion_line: 549
|
||||
expression: res_vec
|
||||
|
||||
---
|
||||
[
|
||||
"Class {\nname: \"A\",\nancestors: [\"{class: A, params: []}\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
|
||||
"Function {\nname: \"A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"A.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"A.foo\",\nsig: \"fn[[a:T, b:V], none]\",\nvar_id: [26]\n}\n",
|
||||
"Class {\nname: \"B\",\nancestors: [\"{class: B, params: []}\", \"{class: C, params: []}\", \"{class: A, params: []}\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
|
||||
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
"Class {\nname: \"C\",\nancestors: [\"{class: C, params: []}\", \"{class: A, params: []}\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
|
||||
"Function {\nname: \"C.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"C.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"foo\",\nsig: \"fn[[a:A], none]\",\nvar_id: []\n}\n",
|
||||
"Function {\nname: \"ff\",\nsig: \"fn[[a:T], V]\",\nvar_id: [34]\n}\n",
|
||||
]
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
source: nac3core/src/toplevel/test.rs
|
||||
assertion_line: 541
|
||||
expression: res_vec
|
||||
|
||||
---
|
||||
[
|
||||
"Class {\nname: \"A\",\nancestors: [\"{class: A, params: []}\"],\nfields: [],\nmethods: [],\ntype_vars: []\n}\n",
|
||||
]
|
801
nac3core/src/toplevel/test.rs
Normal file
801
nac3core/src/toplevel/test.rs
Normal file
@ -0,0 +1,801 @@
|
||||
use crate::{
|
||||
codegen::CodeGenContext,
|
||||
symbol_resolver::{SymbolResolver, ValueEnum},
|
||||
toplevel::DefinitionId,
|
||||
typecheck::{
|
||||
type_inferencer::PrimitiveStore,
|
||||
typedef::{Type, Unifier},
|
||||
},
|
||||
};
|
||||
use indoc::indoc;
|
||||
use nac3parser::{ast::fold::Fold, parser::parse_program};
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use test_case::test_case;
|
||||
|
||||
use super::*;
|
||||
|
||||
struct ResolverInternal {
|
||||
id_to_type: Mutex<HashMap<StrRef, Type>>,
|
||||
id_to_def: Mutex<HashMap<StrRef, DefinitionId>>,
|
||||
class_names: Mutex<HashMap<StrRef, Type>>,
|
||||
}
|
||||
|
||||
impl ResolverInternal {
|
||||
fn add_id_def(&self, id: StrRef, def: DefinitionId) {
|
||||
self.id_to_def.lock().insert(id, def);
|
||||
}
|
||||
|
||||
fn add_id_type(&self, id: StrRef, ty: Type) {
|
||||
self.id_to_type.lock().insert(id, ty);
|
||||
}
|
||||
}
|
||||
|
||||
struct Resolver(Arc<ResolverInternal>);
|
||||
|
||||
impl SymbolResolver for Resolver {
|
||||
fn get_default_param_value(
|
||||
&self,
|
||||
_: &nac3parser::ast::Expr,
|
||||
) -> Option<crate::symbol_resolver::SymbolValue> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_symbol_type(
|
||||
&self,
|
||||
_: &mut Unifier,
|
||||
_: &[Arc<RwLock<TopLevelDef>>],
|
||||
_: &PrimitiveStore,
|
||||
str: StrRef,
|
||||
) -> Result<Type, String> {
|
||||
self.0
|
||||
.id_to_type
|
||||
.lock()
|
||||
.get(&str)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("cannot find symbol `{}`", str))
|
||||
}
|
||||
|
||||
fn get_symbol_value<'ctx, 'a>(
|
||||
&self,
|
||||
_: StrRef,
|
||||
_: &mut CodeGenContext<'ctx, 'a>,
|
||||
) -> Option<ValueEnum<'ctx>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
|
||||
self.0.id_to_def.lock().get(&id).cloned().ok_or("Unknown identifier".to_string())
|
||||
}
|
||||
|
||||
fn get_string_id(&self, _: &str) -> i32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_exception_id(&self, tyid: usize) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
def fun(a: int32) -> int32:
|
||||
return a
|
||||
"},
|
||||
indoc! {"
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.a: int32 = 3
|
||||
"},
|
||||
indoc! {"
|
||||
class B:
|
||||
def __init__(self):
|
||||
self.b: float = 4.3
|
||||
|
||||
def fun(self):
|
||||
self.b = self.b + 3.0
|
||||
"},
|
||||
indoc! {"
|
||||
def foo(a: float):
|
||||
a + 1.0
|
||||
"},
|
||||
indoc! {"
|
||||
class C(B):
|
||||
def __init__(self):
|
||||
self.c: int32 = 4
|
||||
self.a: bool = True
|
||||
"}
|
||||
];
|
||||
"register"
|
||||
)]
|
||||
fn test_simple_register(source: Vec<&str>) {
|
||||
let mut composer: TopLevelComposer = Default::default();
|
||||
|
||||
for s in source {
|
||||
let ast = parse_program(s, Default::default()).unwrap();
|
||||
let ast = ast[0].clone();
|
||||
|
||||
composer.register_top_level(ast, None, "".into()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
def fun(a: int32) -> int32:
|
||||
return a
|
||||
"},
|
||||
indoc! {"
|
||||
def foo(a: float):
|
||||
a + 1.0
|
||||
"},
|
||||
indoc! {"
|
||||
def f(b: int64) -> int32:
|
||||
return 3
|
||||
"},
|
||||
],
|
||||
vec![
|
||||
"fn[[a:0], 0]",
|
||||
"fn[[a:2], 4]",
|
||||
"fn[[b:1], 0]",
|
||||
],
|
||||
vec![
|
||||
"fun",
|
||||
"foo",
|
||||
"f"
|
||||
];
|
||||
"function compose"
|
||||
)]
|
||||
fn test_simple_function_analyze(source: Vec<&str>, tys: Vec<&str>, names: Vec<&str>) {
|
||||
let mut composer: TopLevelComposer = Default::default();
|
||||
|
||||
let internal_resolver = Arc::new(ResolverInternal {
|
||||
id_to_def: Default::default(),
|
||||
id_to_type: Default::default(),
|
||||
class_names: Default::default(),
|
||||
});
|
||||
let resolver =
|
||||
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
for s in source {
|
||||
let ast = parse_program(s, Default::default()).unwrap();
|
||||
let ast = ast[0].clone();
|
||||
|
||||
let (id, def_id, ty) =
|
||||
composer.register_top_level(ast, Some(resolver.clone()), "".into()).unwrap();
|
||||
internal_resolver.add_id_def(id, def_id);
|
||||
if let Some(ty) = ty {
|
||||
internal_resolver.add_id_type(id, ty);
|
||||
}
|
||||
}
|
||||
|
||||
composer.start_analysis(true).unwrap();
|
||||
|
||||
for (i, (def, _)) in composer.definition_ast_list.iter().skip(composer.builtin_num).enumerate()
|
||||
{
|
||||
let def = &*def.read();
|
||||
if let TopLevelDef::Function { signature, name, .. } = def {
|
||||
let ty_str = composer.unifier.internal_stringify(
|
||||
*signature,
|
||||
&mut |id| id.to_string(),
|
||||
&mut |id| id.to_string(),
|
||||
&mut None,
|
||||
);
|
||||
assert_eq!(ty_str, tys[i]);
|
||||
assert_eq!(name, names[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A():
|
||||
a: int32
|
||||
def __init__(self):
|
||||
self.a = 3
|
||||
def fun(self, b: B):
|
||||
pass
|
||||
def foo(self, a: T, b: V):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B(C):
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class C(A):
|
||||
def __init__(self):
|
||||
pass
|
||||
def fun(self, b: B):
|
||||
a = 1
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
def foo(a: A):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
def ff(a: T) -> V:
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"simple class compose"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class Generic_A(Generic[V], B):
|
||||
a: int64
|
||||
def __init__(self):
|
||||
self.a = 123123123123
|
||||
def fun(self, a: int32) -> V:
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B:
|
||||
aa: bool
|
||||
def __init__(self):
|
||||
self.aa = False
|
||||
def foo(self, b: T):
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"generic class"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
def foo(a: list[int32], b: tuple[T, float]) -> A[B, bool]:
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class A(Generic[T, V]):
|
||||
a: T
|
||||
b: V
|
||||
def __init__(self, v: V):
|
||||
self.a = 1
|
||||
self.b = v
|
||||
def fun(self, a: T) -> V:
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
def gfun(a: A[list[float], int32]):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B:
|
||||
def __init__(self):
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"list tuple generic"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(Generic[T, V]):
|
||||
a: A[float, bool]
|
||||
b: B
|
||||
def __init__(self, a: A[float, bool], b: B):
|
||||
self.a = a
|
||||
self.b = b
|
||||
def fun(self, a: A[float, bool]) -> A[bool, int32]:
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B(A[int64, bool]):
|
||||
def __init__(self):
|
||||
pass
|
||||
def foo(self, b: B) -> B:
|
||||
pass
|
||||
def bar(self, a: A[list[B], int32]) -> tuple[A[virtual[A[B, int32]], bool], B]:
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"self1"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(Generic[T]):
|
||||
a: int32
|
||||
b: T
|
||||
c: A[int64]
|
||||
def __init__(self, t: T):
|
||||
self.a = 3
|
||||
self.b = T
|
||||
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
|
||||
pass
|
||||
def foo(self, c: C):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B(Generic[V], A[float]):
|
||||
d: C
|
||||
def __init__(self):
|
||||
pass
|
||||
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
|
||||
# override
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class C(B[bool]):
|
||||
e: int64
|
||||
def __init__(self):
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"inheritance_override"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(Generic[T]):
|
||||
def __init__(self):
|
||||
pass
|
||||
def fun(self, a: A[T]) -> A[T]:
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec!["application of type vars to generic class is not currently supported (at unknown: line 4 column 24)"];
|
||||
"err no type var in generic app"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(B):
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B(A):
|
||||
def __init__(self):
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec!["cyclic inheritance detected"];
|
||||
"cyclic1"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(B[bool, int64]):
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B(Generic[V, T], C[int32]):
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class C(Generic[T], A):
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
],
|
||||
vec!["cyclic inheritance detected"];
|
||||
"cyclic2"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A:
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec!["5: Class {\nname: \"A\",\ndef_id: DefinitionId(5),\nancestors: [CustomClassKind { id: DefinitionId(5), params: [] }],\nfields: [],\nmethods: [],\ntype_vars: []\n}"];
|
||||
"simple pass in class"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![indoc! {"
|
||||
class A:
|
||||
def __init__():
|
||||
pass
|
||||
"}],
|
||||
vec!["__init__ method must have a `self` parameter (at unknown: line 2 column 5)"];
|
||||
"err no self_1"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(B, Generic[T], C):
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B:
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class C:
|
||||
def __init__(self):
|
||||
pass
|
||||
"}
|
||||
|
||||
],
|
||||
vec!["a class definition can only have at most one base class declaration and one generic declaration (at unknown: line 1 column 24)"];
|
||||
"err multiple inheritance"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(Generic[T]):
|
||||
a: int32
|
||||
b: T
|
||||
c: A[int64]
|
||||
def __init__(self, t: T):
|
||||
self.a = 3
|
||||
self.b = T
|
||||
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B(Generic[V], A[float]):
|
||||
def __init__(self):
|
||||
pass
|
||||
def fun(self, a: int32, b: T) -> list[virtual[B[int32]]]:
|
||||
# override
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec!["method fun has same name as ancestors' method, but incompatible type"];
|
||||
"err_incompatible_inheritance_method"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(Generic[T]):
|
||||
a: int32
|
||||
b: T
|
||||
c: A[int64]
|
||||
def __init__(self, t: T):
|
||||
self.a = 3
|
||||
self.b = T
|
||||
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class B(Generic[V], A[float]):
|
||||
a: int32
|
||||
def __init__(self):
|
||||
pass
|
||||
def fun(self, a: int32, b: T) -> list[virtual[B[bool]]]:
|
||||
# override
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec!["field `a` has already declared in the ancestor classes"];
|
||||
"err_incompatible_inheritance_field"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A:
|
||||
def __init__(self):
|
||||
pass
|
||||
"},
|
||||
indoc! {"
|
||||
class A:
|
||||
a: int32
|
||||
def __init__(self):
|
||||
pass
|
||||
"}
|
||||
],
|
||||
vec!["duplicate definition of class `A` (at unknown: line 1 column 1)"];
|
||||
"class same name"
|
||||
)]
|
||||
fn test_analyze(source: Vec<&str>, res: Vec<&str>) {
|
||||
let print = false;
|
||||
let mut composer: TopLevelComposer = Default::default();
|
||||
|
||||
let internal_resolver = make_internal_resolver_with_tvar(
|
||||
vec![
|
||||
("T".into(), vec![]),
|
||||
("V".into(), vec![composer.primitives_ty.bool, composer.primitives_ty.int32]),
|
||||
("G".into(), vec![composer.primitives_ty.bool, composer.primitives_ty.int64]),
|
||||
],
|
||||
&mut composer.unifier,
|
||||
print,
|
||||
);
|
||||
let resolver =
|
||||
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
for s in source {
|
||||
let ast = parse_program(s, Default::default()).unwrap();
|
||||
let ast = ast[0].clone();
|
||||
|
||||
let (id, def_id, ty) = {
|
||||
match composer.register_top_level(ast, Some(resolver.clone()), "".into()) {
|
||||
Ok(x) => x,
|
||||
Err(msg) => {
|
||||
if print {
|
||||
println!("{}", msg);
|
||||
} else {
|
||||
assert_eq!(res[0], msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
internal_resolver.add_id_def(id, def_id);
|
||||
if let Some(ty) = ty {
|
||||
internal_resolver.add_id_type(id, ty);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(msg) = composer.start_analysis(false) {
|
||||
if print {
|
||||
println!("{}", msg);
|
||||
} else {
|
||||
assert_eq!(res[0], msg);
|
||||
}
|
||||
} else {
|
||||
// skip 5 to skip primitives
|
||||
let mut res_vec: Vec<String> = Vec::new();
|
||||
for (def, _) in composer.definition_ast_list.iter().skip(composer.builtin_num) {
|
||||
let def = &*def.read();
|
||||
res_vec.push(format!("{}\n", def.to_string(composer.unifier.borrow_mut())));
|
||||
}
|
||||
insta::assert_debug_snapshot!(res_vec);
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
def fun(a: int32, b: int32) -> int32:
|
||||
return a + b
|
||||
"},
|
||||
indoc! {"
|
||||
def fib(n: int32) -> int32:
|
||||
if n <= 2:
|
||||
return 1
|
||||
a = fib(n - 1)
|
||||
b = fib(n - 2)
|
||||
return fib(n - 1)
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"simple function"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A:
|
||||
a: int32
|
||||
def __init__(self):
|
||||
self.a = 3
|
||||
def fun(self) -> int32:
|
||||
b = self.a + 3
|
||||
return b * self.a
|
||||
def clone(self) -> A:
|
||||
SELF = self
|
||||
return SELF
|
||||
def sum(self) -> int32:
|
||||
if self.a == 0:
|
||||
return self.a
|
||||
else:
|
||||
a = self.a
|
||||
self.a = self.a - 1
|
||||
return a + self.sum()
|
||||
def fib(self, a: int32) -> int32:
|
||||
if a <= 2:
|
||||
return 1
|
||||
return self.fib(a - 1) + self.fib(a - 2)
|
||||
"},
|
||||
indoc! {"
|
||||
def fun(a: A) -> int32:
|
||||
return a.fun() + 2
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"simple class body"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
def fun(a: V, c: G, t: T) -> V:
|
||||
b = a
|
||||
cc = c
|
||||
ret = fun(b, cc, t)
|
||||
return ret * ret
|
||||
"},
|
||||
indoc! {"
|
||||
def sum_three(l: list[V]) -> V:
|
||||
return l[0] + l[1] + l[2]
|
||||
"},
|
||||
indoc! {"
|
||||
def sum_sq_pair(p: tuple[V, V]) -> list[V]:
|
||||
a = p[0]
|
||||
b = p[1]
|
||||
a = a**a
|
||||
b = b**b
|
||||
return [a, b]
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"type var fun"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A(Generic[G]):
|
||||
a: G
|
||||
b: bool
|
||||
def __init__(self, aa: G):
|
||||
self.a = aa
|
||||
if 2 > 1:
|
||||
self.b = True
|
||||
else:
|
||||
# self.b = False
|
||||
pass
|
||||
def fun(self, a: G) -> list[G]:
|
||||
ret = [a, self.a]
|
||||
return ret if self.b else self.fun(self.a)
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"type var class"
|
||||
)]
|
||||
#[test_case(
|
||||
vec![
|
||||
indoc! {"
|
||||
class A:
|
||||
def fun(self):
|
||||
pass
|
||||
"},
|
||||
indoc!{"
|
||||
class B:
|
||||
a: int32
|
||||
b: bool
|
||||
def __init__(self):
|
||||
# self.b = False
|
||||
if 3 > 2:
|
||||
self.a = 3
|
||||
self.b = False
|
||||
else:
|
||||
self.a = 4
|
||||
self.b = True
|
||||
"}
|
||||
],
|
||||
vec![];
|
||||
"no_init_inst_check"
|
||||
)]
|
||||
fn test_inference(source: Vec<&str>, res: Vec<&str>) {
|
||||
let print = true;
|
||||
let mut composer: TopLevelComposer = Default::default();
|
||||
|
||||
let internal_resolver = make_internal_resolver_with_tvar(
|
||||
vec![
|
||||
("T".into(), vec![]),
|
||||
(
|
||||
"V".into(),
|
||||
vec![
|
||||
composer.primitives_ty.float,
|
||||
composer.primitives_ty.int32,
|
||||
composer.primitives_ty.int64,
|
||||
],
|
||||
),
|
||||
("G".into(), vec![composer.primitives_ty.bool, composer.primitives_ty.int64]),
|
||||
],
|
||||
&mut composer.unifier,
|
||||
print,
|
||||
);
|
||||
let resolver =
|
||||
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
for s in source {
|
||||
let ast = parse_program(s, Default::default()).unwrap();
|
||||
let ast = ast[0].clone();
|
||||
|
||||
let (id, def_id, ty) = {
|
||||
match composer.register_top_level(ast, Some(resolver.clone()), "".into()) {
|
||||
Ok(x) => x,
|
||||
Err(msg) => {
|
||||
if print {
|
||||
println!("{}", msg);
|
||||
} else {
|
||||
assert_eq!(res[0], msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
internal_resolver.add_id_def(id, def_id);
|
||||
if let Some(ty) = ty {
|
||||
internal_resolver.add_id_type(id, ty);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(msg) = composer.start_analysis(true) {
|
||||
if print {
|
||||
println!("{}", msg);
|
||||
} else {
|
||||
assert_eq!(res[0], msg);
|
||||
}
|
||||
} else {
|
||||
// skip 5 to skip primitives
|
||||
let mut stringify_folder = TypeToStringFolder { unifier: &mut composer.unifier };
|
||||
for (_i, (def, _)) in
|
||||
composer.definition_ast_list.iter().skip(composer.builtin_num).enumerate()
|
||||
{
|
||||
let def = &*def.read();
|
||||
|
||||
if let TopLevelDef::Function { instance_to_stmt, name, .. } = def {
|
||||
println!(
|
||||
"=========`{}`: number of instances: {}===========",
|
||||
name,
|
||||
instance_to_stmt.len()
|
||||
);
|
||||
for inst in instance_to_stmt.iter() {
|
||||
let ast = &inst.1.body;
|
||||
for b in ast.iter() {
|
||||
println!("{:?}", stringify_folder.fold_stmt(b.clone()).unwrap());
|
||||
println!("--------------------");
|
||||
}
|
||||
println!("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_internal_resolver_with_tvar(
|
||||
tvars: Vec<(StrRef, Vec<Type>)>,
|
||||
unifier: &mut Unifier,
|
||||
print: bool,
|
||||
) -> Arc<ResolverInternal> {
|
||||
let res: Arc<ResolverInternal> = ResolverInternal {
|
||||
id_to_def: Default::default(),
|
||||
id_to_type: tvars
|
||||
.into_iter()
|
||||
.map(|(name, range)| {
|
||||
(name, {
|
||||
let (ty, id) = unifier.get_fresh_var_with_range(range.as_slice(), None, None);
|
||||
if print {
|
||||
println!("{}: {:?}, tvar{}", name, ty, id);
|
||||
}
|
||||
ty
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
.into(),
|
||||
class_names: Default::default(),
|
||||
}
|
||||
.into();
|
||||
if print {
|
||||
println!();
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
struct TypeToStringFolder<'a> {
|
||||
unifier: &'a mut Unifier,
|
||||
}
|
||||
|
||||
impl<'a> Fold<Option<Type>> for TypeToStringFolder<'a> {
|
||||
type TargetU = String;
|
||||
type Error = String;
|
||||
fn map_user(&mut self, user: Option<Type>) -> Result<Self::TargetU, Self::Error> {
|
||||
Ok(if let Some(ty) = user {
|
||||
self.unifier.internal_stringify(
|
||||
ty,
|
||||
&mut |id| format!("class{}", id.to_string()),
|
||||
&mut |id| format!("tvar{}", id.to_string()),
|
||||
&mut None,
|
||||
)
|
||||
} else {
|
||||
"None".into()
|
||||
})
|
||||
}
|
||||
}
|
521
nac3core/src/toplevel/type_annotation.rs
Normal file
521
nac3core/src/toplevel/type_annotation.rs
Normal file
@ -0,0 +1,521 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TypeAnnotation {
|
||||
Primitive(Type),
|
||||
// we use type vars kind at params to represent self type
|
||||
CustomClass {
|
||||
id: DefinitionId,
|
||||
// params can also be type var
|
||||
params: Vec<TypeAnnotation>,
|
||||
},
|
||||
// can only be CustomClassKind
|
||||
Virtual(Box<TypeAnnotation>),
|
||||
TypeVar(Type),
|
||||
List(Box<TypeAnnotation>),
|
||||
Tuple(Vec<TypeAnnotation>),
|
||||
}
|
||||
|
||||
impl TypeAnnotation {
|
||||
pub fn stringify(&self, unifier: &mut Unifier) -> String {
|
||||
use TypeAnnotation::*;
|
||||
match self {
|
||||
Primitive(ty) | TypeVar(ty) => unifier.stringify(*ty),
|
||||
CustomClass { id, params } => {
|
||||
let class_name = match unifier.top_level {
|
||||
Some(ref top) => {
|
||||
if let TopLevelDef::Class { name, .. } =
|
||||
&*top.definitions.read()[id.0].read()
|
||||
{
|
||||
(*name).into()
|
||||
} else {
|
||||
format!("def_{}", id.0)
|
||||
}
|
||||
}
|
||||
None => format!("def_{}", id.0),
|
||||
};
|
||||
format!(
|
||||
"{{class: {}, params: {:?}}}",
|
||||
class_name,
|
||||
params.iter().map(|p| p.stringify(unifier)).collect_vec()
|
||||
)
|
||||
}
|
||||
Virtual(ty) | List(ty) => ty.stringify(unifier),
|
||||
Tuple(types) => {
|
||||
format!("({:?})", types.iter().map(|p| p.stringify(unifier)).collect_vec())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_ast_to_type_annotation_kinds<T>(
|
||||
resolver: &(dyn SymbolResolver + Send + Sync),
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
expr: &ast::Expr<T>,
|
||||
// the key stores the type_var of this topleveldef::class, we only need this field here
|
||||
locked: HashMap<DefinitionId, Vec<Type>>,
|
||||
) -> Result<TypeAnnotation, String> {
|
||||
let name_handle = |id: &StrRef,
|
||||
unifier: &mut Unifier,
|
||||
locked: HashMap<DefinitionId, Vec<Type>>| {
|
||||
if id == &"int32".into() {
|
||||
Ok(TypeAnnotation::Primitive(primitives.int32))
|
||||
} else if id == &"int64".into() {
|
||||
Ok(TypeAnnotation::Primitive(primitives.int64))
|
||||
} else if id == &"uint32".into() {
|
||||
Ok(TypeAnnotation::Primitive(primitives.uint32))
|
||||
} else if id == &"uint64".into() {
|
||||
Ok(TypeAnnotation::Primitive(primitives.uint64))
|
||||
} else if id == &"float".into() {
|
||||
Ok(TypeAnnotation::Primitive(primitives.float))
|
||||
} else if id == &"bool".into() {
|
||||
Ok(TypeAnnotation::Primitive(primitives.bool))
|
||||
} else if id == &"str".into() {
|
||||
Ok(TypeAnnotation::Primitive(primitives.str))
|
||||
} else if id == &"Exception".into() {
|
||||
Ok(TypeAnnotation::CustomClass { id: DefinitionId(7), params: Default::default() })
|
||||
} else if let Ok(obj_id) = resolver.get_identifier_def(*id) {
|
||||
let type_vars = {
|
||||
let def_read = top_level_defs[obj_id.0].try_read();
|
||||
if let Some(def_read) = def_read {
|
||||
if let TopLevelDef::Class { type_vars, .. } = &*def_read {
|
||||
type_vars.clone()
|
||||
} else {
|
||||
return Err(format!(
|
||||
"function cannot be used as a type (at {})",
|
||||
expr.location
|
||||
));
|
||||
}
|
||||
} else {
|
||||
locked.get(&obj_id).unwrap().clone()
|
||||
}
|
||||
};
|
||||
// check param number here
|
||||
if !type_vars.is_empty() {
|
||||
return Err(format!(
|
||||
"expect {} type variable parameter but got 0 (at {})",
|
||||
type_vars.len(),
|
||||
expr.location,
|
||||
));
|
||||
}
|
||||
Ok(TypeAnnotation::CustomClass { id: obj_id, params: vec![] })
|
||||
} else if let Ok(ty) = resolver.get_symbol_type(unifier, top_level_defs, primitives, *id) {
|
||||
if let TypeEnum::TVar { .. } = unifier.get_ty(ty).as_ref() {
|
||||
let var = unifier.get_fresh_var(Some(*id), Some(expr.location)).0;
|
||||
unifier.unify(var, ty).unwrap();
|
||||
Ok(TypeAnnotation::TypeVar(ty))
|
||||
} else {
|
||||
Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location))
|
||||
}
|
||||
} else {
|
||||
Err(format!("`{}` is not a valid type annotation (at {})", id, expr.location))
|
||||
}
|
||||
};
|
||||
|
||||
let class_name_handle =
|
||||
|id: &StrRef,
|
||||
slice: &ast::Expr<T>,
|
||||
unifier: &mut Unifier,
|
||||
mut locked: HashMap<DefinitionId, Vec<Type>>| {
|
||||
if vec!["virtual".into(), "Generic".into(), "list".into(), "tuple".into()].contains(id)
|
||||
{
|
||||
return Err(format!("keywords cannot be class name (at {})", expr.location));
|
||||
}
|
||||
let obj_id = resolver.get_identifier_def(*id)?;
|
||||
let type_vars = {
|
||||
let def_read = top_level_defs[obj_id.0].try_read();
|
||||
if let Some(def_read) = def_read {
|
||||
if let TopLevelDef::Class { type_vars, .. } = &*def_read {
|
||||
type_vars.clone()
|
||||
} else {
|
||||
unreachable!("must be class here")
|
||||
}
|
||||
} else {
|
||||
locked.get(&obj_id).unwrap().clone()
|
||||
}
|
||||
};
|
||||
// we do not check whether the application of type variables are compatible here
|
||||
let param_type_infos = {
|
||||
let params_ast = if let ast::ExprKind::Tuple { elts, .. } = &slice.node {
|
||||
elts.iter().collect_vec()
|
||||
} else {
|
||||
vec![slice]
|
||||
};
|
||||
if type_vars.len() != params_ast.len() {
|
||||
return Err(format!(
|
||||
"expect {} type parameters but got {} (at {})",
|
||||
type_vars.len(),
|
||||
params_ast.len(),
|
||||
params_ast[0].location,
|
||||
));
|
||||
}
|
||||
let result = params_ast
|
||||
.iter()
|
||||
.map(|x| {
|
||||
parse_ast_to_type_annotation_kinds(
|
||||
resolver,
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
x,
|
||||
{
|
||||
locked.insert(obj_id, type_vars.clone());
|
||||
locked.clone()
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
// make sure the result do not contain any type vars
|
||||
let no_type_var =
|
||||
result.iter().all(|x| get_type_var_contained_in_type_annotation(x).is_empty());
|
||||
if no_type_var {
|
||||
result
|
||||
} else {
|
||||
return Err(format!(
|
||||
"application of type vars to generic class \
|
||||
is not currently supported (at {})",
|
||||
params_ast[0].location
|
||||
));
|
||||
}
|
||||
};
|
||||
Ok(TypeAnnotation::CustomClass { id: obj_id, params: param_type_infos })
|
||||
};
|
||||
match &expr.node {
|
||||
ast::ExprKind::Name { id, .. } => name_handle(id, unifier, locked),
|
||||
// virtual
|
||||
ast::ExprKind::Subscript { value, slice, .. }
|
||||
if {
|
||||
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"virtual".into())
|
||||
} =>
|
||||
{
|
||||
let def = parse_ast_to_type_annotation_kinds(
|
||||
resolver,
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
slice.as_ref(),
|
||||
locked,
|
||||
)?;
|
||||
if !matches!(def, TypeAnnotation::CustomClass { .. }) {
|
||||
unreachable!("must be concretized custom class kind in the virtual")
|
||||
}
|
||||
Ok(TypeAnnotation::Virtual(def.into()))
|
||||
}
|
||||
|
||||
// list
|
||||
ast::ExprKind::Subscript { value, slice, .. }
|
||||
if {
|
||||
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"list".into())
|
||||
} =>
|
||||
{
|
||||
let def_ann = parse_ast_to_type_annotation_kinds(
|
||||
resolver,
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
slice.as_ref(),
|
||||
locked,
|
||||
)?;
|
||||
Ok(TypeAnnotation::List(def_ann.into()))
|
||||
}
|
||||
|
||||
// option
|
||||
ast::ExprKind::Subscript { value, slice, .. }
|
||||
if {
|
||||
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"Option".into())
|
||||
} =>
|
||||
{
|
||||
let def_ann = parse_ast_to_type_annotation_kinds(
|
||||
resolver,
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
slice.as_ref(),
|
||||
locked,
|
||||
)?;
|
||||
let id =
|
||||
if let TypeEnum::TObj { obj_id, .. } = unifier.get_ty(primitives.option).as_ref() {
|
||||
*obj_id
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
Ok(TypeAnnotation::CustomClass { id, params: vec![def_ann] })
|
||||
}
|
||||
|
||||
// tuple
|
||||
ast::ExprKind::Subscript { value, slice, .. }
|
||||
if {
|
||||
matches!(&value.node, ast::ExprKind::Name { id, .. } if id == &"tuple".into())
|
||||
} =>
|
||||
{
|
||||
let tup_elts = {
|
||||
if let ast::ExprKind::Tuple { elts, .. } = &slice.node {
|
||||
elts.as_slice()
|
||||
} else {
|
||||
std::slice::from_ref(slice.as_ref())
|
||||
}
|
||||
};
|
||||
let type_annotations = tup_elts
|
||||
.iter()
|
||||
.map(|e| {
|
||||
parse_ast_to_type_annotation_kinds(
|
||||
resolver,
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
e,
|
||||
locked.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(TypeAnnotation::Tuple(type_annotations))
|
||||
}
|
||||
|
||||
// custom class
|
||||
ast::ExprKind::Subscript { value, slice, .. } => {
|
||||
if let ast::ExprKind::Name { id, .. } = &value.node {
|
||||
class_name_handle(id, slice, unifier, locked)
|
||||
} else {
|
||||
Err(format!("unsupported expression type for class name (at {})", value.location))
|
||||
}
|
||||
}
|
||||
|
||||
_ => Err(format!("unsupported expression for type annotation (at {})", expr.location)),
|
||||
}
|
||||
}
|
||||
|
||||
// no need to have the `locked` parameter, unlike the `parse_ast_to_type_annotation_kinds`, since
|
||||
// when calling this function, there should be no topleveldef::class being write, and this function
|
||||
// also only read the toplevedefs
|
||||
pub fn get_type_from_type_annotation_kinds(
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
ann: &TypeAnnotation,
|
||||
subst_list: &mut Option<Vec<Type>>
|
||||
) -> Result<Type, String> {
|
||||
match ann {
|
||||
TypeAnnotation::CustomClass { id: obj_id, params } => {
|
||||
let def_read = top_level_defs[obj_id.0].read();
|
||||
let class_def: &TopLevelDef = def_read.deref();
|
||||
if let TopLevelDef::Class { fields, methods, type_vars, .. } = class_def {
|
||||
if type_vars.len() != params.len() {
|
||||
Err(format!(
|
||||
"unexpected number of type parameters: expected {} but got {}",
|
||||
type_vars.len(),
|
||||
params.len()
|
||||
))
|
||||
} else {
|
||||
let param_ty = params
|
||||
.iter()
|
||||
.map(|x| {
|
||||
get_type_from_type_annotation_kinds(
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
x,
|
||||
subst_list
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let subst = {
|
||||
// check for compatible range
|
||||
// TODO: if allow type var to be applied(now this disallowed in the parse_to_type_annotation), need more check
|
||||
let mut result: HashMap<u32, Type> = HashMap::new();
|
||||
for (tvar, p) in type_vars.iter().zip(param_ty) {
|
||||
if let TypeEnum::TVar { id, range, fields: None, name, loc } =
|
||||
unifier.get_ty(*tvar).as_ref()
|
||||
{
|
||||
let ok: bool = {
|
||||
// create a temp type var and unify to check compatibility
|
||||
p == *tvar || {
|
||||
let temp = unifier.get_fresh_var_with_range(
|
||||
range.as_slice(),
|
||||
*name,
|
||||
*loc,
|
||||
);
|
||||
unifier.unify(temp.0, p).is_ok()
|
||||
}
|
||||
};
|
||||
if ok {
|
||||
result.insert(*id, p);
|
||||
} else {
|
||||
return Err(format!(
|
||||
"cannot apply type {} to type variable with id {:?}",
|
||||
unifier.internal_stringify(
|
||||
p,
|
||||
&mut |id| format!("class{}", id),
|
||||
&mut |id| format!("tvar{}", id),
|
||||
&mut None
|
||||
),
|
||||
*id
|
||||
));
|
||||
}
|
||||
} else {
|
||||
unreachable!("must be generic type var")
|
||||
}
|
||||
}
|
||||
result
|
||||
};
|
||||
let mut tobj_fields = methods
|
||||
.iter()
|
||||
.map(|(name, ty, _)| {
|
||||
let subst_ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
|
||||
// methods are immutable
|
||||
(*name, (subst_ty, false))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
tobj_fields.extend(fields.iter().map(|(name, ty, mutability)| {
|
||||
let subst_ty = unifier.subst(*ty, &subst).unwrap_or(*ty);
|
||||
(*name, (subst_ty, *mutability))
|
||||
}));
|
||||
let need_subst = !subst.is_empty();
|
||||
let ty = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: *obj_id,
|
||||
fields: tobj_fields,
|
||||
params: subst,
|
||||
});
|
||||
if need_subst {
|
||||
subst_list.as_mut().map(|wl| wl.push(ty));
|
||||
}
|
||||
Ok(ty)
|
||||
}
|
||||
} else {
|
||||
unreachable!("should be class def here")
|
||||
}
|
||||
}
|
||||
TypeAnnotation::Primitive(ty) | TypeAnnotation::TypeVar(ty) => Ok(*ty),
|
||||
TypeAnnotation::Virtual(ty) => {
|
||||
let ty = get_type_from_type_annotation_kinds(
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
ty.as_ref(),
|
||||
subst_list
|
||||
)?;
|
||||
Ok(unifier.add_ty(TypeEnum::TVirtual { ty }))
|
||||
}
|
||||
TypeAnnotation::List(ty) => {
|
||||
let ty = get_type_from_type_annotation_kinds(
|
||||
top_level_defs,
|
||||
unifier,
|
||||
primitives,
|
||||
ty.as_ref(),
|
||||
subst_list
|
||||
)?;
|
||||
Ok(unifier.add_ty(TypeEnum::TList { ty }))
|
||||
}
|
||||
TypeAnnotation::Tuple(tys) => {
|
||||
let tys = tys
|
||||
.iter()
|
||||
.map(|x| {
|
||||
get_type_from_type_annotation_kinds(top_level_defs, unifier, primitives, x, subst_list)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(unifier.add_ty(TypeEnum::TTuple { ty: tys }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// given an def id, return a type annotation of self \
|
||||
/// ```python
|
||||
/// class A(Generic[T, V]):
|
||||
/// def fun(self):
|
||||
/// ```
|
||||
/// the type of `self` should be similar to `A[T, V]`, where `T`, `V`
|
||||
/// considered to be type variables associated with the class \
|
||||
/// \
|
||||
/// But note that here we do not make a duplication of `T`, `V`, we direclty
|
||||
/// use them as they are in the TopLevelDef::Class since those in the
|
||||
/// TopLevelDef::Class.type_vars will be substitute later when seeing applications/instantiations
|
||||
/// the Type of their fields and methods will also be subst when application/instantiation
|
||||
pub fn make_self_type_annotation(type_vars: &[Type], object_id: DefinitionId) -> TypeAnnotation {
|
||||
TypeAnnotation::CustomClass {
|
||||
id: object_id,
|
||||
params: type_vars.iter().map(|ty| TypeAnnotation::TypeVar(*ty)).collect_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// get all the occurences of type vars contained in a type annotation
|
||||
/// e.g. `A[int, B[T], V, virtual[C[G]]]` => [T, V, G]
|
||||
/// this function will not make a duplicate of type var
|
||||
pub fn get_type_var_contained_in_type_annotation(ann: &TypeAnnotation) -> Vec<TypeAnnotation> {
|
||||
let mut result: Vec<TypeAnnotation> = Vec::new();
|
||||
match ann {
|
||||
TypeAnnotation::TypeVar(..) => result.push(ann.clone()),
|
||||
TypeAnnotation::Virtual(ann) => {
|
||||
result.extend(get_type_var_contained_in_type_annotation(ann.as_ref()))
|
||||
}
|
||||
TypeAnnotation::CustomClass { params, .. } => {
|
||||
for p in params {
|
||||
result.extend(get_type_var_contained_in_type_annotation(p));
|
||||
}
|
||||
}
|
||||
TypeAnnotation::List(ann) => {
|
||||
result.extend(get_type_var_contained_in_type_annotation(ann.as_ref()))
|
||||
}
|
||||
TypeAnnotation::Tuple(anns) => {
|
||||
for a in anns {
|
||||
result.extend(get_type_var_contained_in_type_annotation(a));
|
||||
}
|
||||
}
|
||||
TypeAnnotation::Primitive(..) => {}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// check the type compatibility for overload
|
||||
pub fn check_overload_type_annotation_compatible(
|
||||
this: &TypeAnnotation,
|
||||
other: &TypeAnnotation,
|
||||
unifier: &mut Unifier,
|
||||
) -> bool {
|
||||
match (this, other) {
|
||||
(TypeAnnotation::Primitive(a), TypeAnnotation::Primitive(b)) => a == b,
|
||||
(TypeAnnotation::TypeVar(a), TypeAnnotation::TypeVar(b)) => {
|
||||
let a = unifier.get_ty(*a);
|
||||
let a = a.deref();
|
||||
let b = unifier.get_ty(*b);
|
||||
let b = b.deref();
|
||||
if let (
|
||||
TypeEnum::TVar { id: a, fields: None, .. },
|
||||
TypeEnum::TVar { id: b, fields: None, .. },
|
||||
) = (a, b)
|
||||
{
|
||||
a == b
|
||||
} else {
|
||||
unreachable!("must be type var")
|
||||
}
|
||||
}
|
||||
(TypeAnnotation::Virtual(a), TypeAnnotation::Virtual(b))
|
||||
| (TypeAnnotation::List(a), TypeAnnotation::List(b)) => {
|
||||
check_overload_type_annotation_compatible(a.as_ref(), b.as_ref(), unifier)
|
||||
}
|
||||
|
||||
(TypeAnnotation::Tuple(a), TypeAnnotation::Tuple(b)) => {
|
||||
a.len() == b.len() && {
|
||||
a.iter()
|
||||
.zip(b)
|
||||
.all(|(a, b)| check_overload_type_annotation_compatible(a, b, unifier))
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
TypeAnnotation::CustomClass { id: a, params: a_p },
|
||||
TypeAnnotation::CustomClass { id: b, params: b_p },
|
||||
) => {
|
||||
a.0 == b.0 && {
|
||||
a_p.len() == b_p.len() && {
|
||||
a_p.iter()
|
||||
.zip(b_p)
|
||||
.all(|(a, b)| check_overload_type_annotation_compatible(a, b, unifier))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
306
nac3core/src/typecheck/function_check.rs
Normal file
306
nac3core/src/typecheck/function_check.rs
Normal file
@ -0,0 +1,306 @@
|
||||
use crate::typecheck::typedef::TypeEnum;
|
||||
|
||||
use super::type_inferencer::Inferencer;
|
||||
use super::typedef::Type;
|
||||
use nac3parser::ast::{self, Expr, ExprKind, Stmt, StmtKind, StrRef};
|
||||
use std::{collections::HashSet, iter::once};
|
||||
|
||||
impl<'a> Inferencer<'a> {
|
||||
fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), String> {
|
||||
if matches!(expr.custom, Some(ty) if self.unifier.unioned(ty, self.primitives.none)) {
|
||||
Err(format!("Error at {}: cannot have value none", expr.location))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check_pattern(
|
||||
&mut self,
|
||||
pattern: &Expr<Option<Type>>,
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<(), String> {
|
||||
match &pattern.node {
|
||||
ast::ExprKind::Name { id, .. } if id == &"none".into() =>
|
||||
Err(format!("cannot assign to a `none` (at {})", pattern.location)),
|
||||
ExprKind::Name { id, .. } => {
|
||||
if !defined_identifiers.contains(id) {
|
||||
defined_identifiers.insert(*id);
|
||||
}
|
||||
self.should_have_value(pattern)?;
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
for elt in elts.iter() {
|
||||
self.check_pattern(elt, defined_identifiers)?;
|
||||
self.should_have_value(elt)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
self.check_expr(slice, defined_identifiers)?;
|
||||
if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty(value.custom.unwrap()) {
|
||||
return Err(format!(
|
||||
"Error at {}: cannot assign to tuple element",
|
||||
value.location
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
ExprKind::Constant { .. } => {
|
||||
Err(format!("cannot assign to a constant (at {})", pattern.location))
|
||||
}
|
||||
_ => self.check_expr(pattern, defined_identifiers),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_expr(
|
||||
&mut self,
|
||||
expr: &Expr<Option<Type>>,
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<(), String> {
|
||||
// there are some cases where the custom field is None
|
||||
if let Some(ty) = &expr.custom {
|
||||
if !self.unifier.is_concrete(*ty, &self.function_data.bound_variables) {
|
||||
return Err(format!(
|
||||
"expected concrete type at {} but got {}",
|
||||
expr.location,
|
||||
self.unifier.get_ty(*ty).get_type_name()
|
||||
));
|
||||
}
|
||||
}
|
||||
match &expr.node {
|
||||
ExprKind::Name { id, .. } => {
|
||||
if id == &"none".into() {
|
||||
return Ok(());
|
||||
}
|
||||
self.should_have_value(expr)?;
|
||||
if !defined_identifiers.contains(id) {
|
||||
match self.function_data.resolver.get_symbol_type(
|
||||
self.unifier,
|
||||
&self.top_level.definitions.read(),
|
||||
self.primitives,
|
||||
*id,
|
||||
) {
|
||||
Ok(_) => {
|
||||
self.defined_identifiers.insert(*id);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"type error at identifier `{}` ({}) at {}",
|
||||
id, e, expr.location
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::List { elts, .. }
|
||||
| ExprKind::Tuple { elts, .. }
|
||||
| ExprKind::BoolOp { values: elts, .. } => {
|
||||
for elt in elts.iter() {
|
||||
self.check_expr(elt, defined_identifiers)?;
|
||||
self.should_have_value(elt)?;
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { value, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
}
|
||||
ExprKind::BinOp { left, right, .. } => {
|
||||
self.check_expr(left, defined_identifiers)?;
|
||||
self.check_expr(right, defined_identifiers)?;
|
||||
self.should_have_value(left)?;
|
||||
self.should_have_value(right)?;
|
||||
}
|
||||
ExprKind::UnaryOp { operand, .. } => {
|
||||
self.check_expr(operand, defined_identifiers)?;
|
||||
self.should_have_value(operand)?;
|
||||
}
|
||||
ExprKind::Compare { left, comparators, .. } => {
|
||||
for elt in once(left.as_ref()).chain(comparators.iter()) {
|
||||
self.check_expr(elt, defined_identifiers)?;
|
||||
self.should_have_value(elt)?;
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
self.should_have_value(value)?;
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.check_expr(slice, defined_identifiers)?;
|
||||
}
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
self.check_expr(test, defined_identifiers)?;
|
||||
self.check_expr(body, defined_identifiers)?;
|
||||
self.check_expr(orelse, defined_identifiers)?;
|
||||
}
|
||||
ExprKind::Slice { lower, upper, step } => {
|
||||
for elt in [lower.as_ref(), upper.as_ref(), step.as_ref()].iter().flatten() {
|
||||
self.should_have_value(elt)?;
|
||||
self.check_expr(elt, defined_identifiers)?;
|
||||
}
|
||||
}
|
||||
ExprKind::Lambda { args, body } => {
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
for arg in args.args.iter() {
|
||||
// TODO: should we check the types here?
|
||||
if !defined_identifiers.contains(&arg.node.arg) {
|
||||
defined_identifiers.insert(arg.node.arg);
|
||||
}
|
||||
}
|
||||
self.check_expr(body, &mut defined_identifiers)?;
|
||||
}
|
||||
ExprKind::ListComp { elt, generators, .. } => {
|
||||
// in our type inference stage, we already make sure that there is only 1 generator
|
||||
let ast::Comprehension { target, iter, ifs, .. } = &generators[0];
|
||||
self.check_expr(iter, defined_identifiers)?;
|
||||
self.should_have_value(iter)?;
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
self.check_pattern(target, &mut defined_identifiers)?;
|
||||
self.should_have_value(target)?;
|
||||
for term in once(elt.as_ref()).chain(ifs.iter()) {
|
||||
self.check_expr(term, &mut defined_identifiers)?;
|
||||
self.should_have_value(term)?;
|
||||
}
|
||||
}
|
||||
ExprKind::Call { func, args, keywords } => {
|
||||
for expr in once(func.as_ref())
|
||||
.chain(args.iter())
|
||||
.chain(keywords.iter().map(|v| v.node.value.as_ref()))
|
||||
{
|
||||
self.check_expr(expr, defined_identifiers)?;
|
||||
self.should_have_value(expr)?;
|
||||
}
|
||||
}
|
||||
ExprKind::Constant { .. } => {}
|
||||
_ => {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// check statements for proper identifier def-use and return on all paths
|
||||
fn check_stmt(
|
||||
&mut self,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<bool, String> {
|
||||
match &stmt.node {
|
||||
StmtKind::For { target, iter, body, orelse, .. } => {
|
||||
self.check_expr(iter, defined_identifiers)?;
|
||||
self.should_have_value(iter)?;
|
||||
let mut local_defined_identifiers = defined_identifiers.clone();
|
||||
for stmt in orelse.iter() {
|
||||
self.check_stmt(stmt, &mut local_defined_identifiers)?;
|
||||
}
|
||||
let mut local_defined_identifiers = defined_identifiers.clone();
|
||||
self.check_pattern(target, &mut local_defined_identifiers)?;
|
||||
self.should_have_value(target)?;
|
||||
for stmt in body.iter() {
|
||||
self.check_stmt(stmt, &mut local_defined_identifiers)?;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::If { test, body, orelse, .. } => {
|
||||
self.check_expr(test, defined_identifiers)?;
|
||||
self.should_have_value(test)?;
|
||||
let mut body_identifiers = defined_identifiers.clone();
|
||||
let mut orelse_identifiers = defined_identifiers.clone();
|
||||
let body_returned = self.check_block(body, &mut body_identifiers)?;
|
||||
let orelse_returned = self.check_block(orelse, &mut orelse_identifiers)?;
|
||||
|
||||
for ident in body_identifiers.iter() {
|
||||
if !defined_identifiers.contains(ident) && orelse_identifiers.contains(ident) {
|
||||
defined_identifiers.insert(*ident);
|
||||
}
|
||||
}
|
||||
Ok(body_returned && orelse_returned)
|
||||
}
|
||||
StmtKind::While { test, body, orelse, .. } => {
|
||||
self.check_expr(test, defined_identifiers)?;
|
||||
self.should_have_value(test)?;
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
self.check_block(body, &mut defined_identifiers)?;
|
||||
self.check_block(orelse, &mut defined_identifiers)?;
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::With { items, body, .. } => {
|
||||
let mut new_defined_identifiers = defined_identifiers.clone();
|
||||
for item in items.iter() {
|
||||
self.check_expr(&item.context_expr, defined_identifiers)?;
|
||||
if let Some(var) = item.optional_vars.as_ref() {
|
||||
self.check_pattern(var, &mut new_defined_identifiers)?;
|
||||
}
|
||||
}
|
||||
self.check_block(body, &mut new_defined_identifiers)?;
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::Try { body, handlers, orelse, finalbody, .. } => {
|
||||
self.check_block(body, &mut defined_identifiers.clone())?;
|
||||
self.check_block(orelse, &mut defined_identifiers.clone())?;
|
||||
for handler in handlers.iter() {
|
||||
let mut defined_identifiers = defined_identifiers.clone();
|
||||
let ast::ExcepthandlerKind::ExceptHandler { name, body, .. } = &handler.node;
|
||||
if let Some(name) = name {
|
||||
defined_identifiers.insert(*name);
|
||||
}
|
||||
self.check_block(body, &mut defined_identifiers)?;
|
||||
}
|
||||
self.check_block(finalbody, defined_identifiers)?;
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::Expr { value, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
for target in targets {
|
||||
self.check_pattern(target, defined_identifiers)?;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::AnnAssign { target, value, .. } => {
|
||||
if let Some(value) = value {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
self.check_pattern(target, defined_identifiers)?;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
StmtKind::Return { value, .. } => {
|
||||
if let Some(value) = value {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
self.should_have_value(value)?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
StmtKind::Raise { exc, .. } => {
|
||||
if let Some(value) = exc {
|
||||
self.check_expr(value, defined_identifiers)?;
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
// break, raise, etc.
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_block(
|
||||
&mut self,
|
||||
block: &[Stmt<Option<Type>>],
|
||||
defined_identifiers: &mut HashSet<StrRef>,
|
||||
) -> Result<bool, String> {
|
||||
let mut ret = false;
|
||||
for stmt in block {
|
||||
if ret {
|
||||
return Err(format!("dead code at {:?}", stmt.location));
|
||||
}
|
||||
if self.check_stmt(stmt, defined_identifiers)? {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
330
nac3core/src/typecheck/magic_methods.rs
Normal file
330
nac3core/src/typecheck/magic_methods.rs
Normal file
@ -0,0 +1,330 @@
|
||||
use crate::typecheck::{
|
||||
type_inferencer::*,
|
||||
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
|
||||
};
|
||||
use nac3parser::ast::{self, StrRef};
|
||||
use nac3parser::ast::{Cmpop, Operator, Unaryop};
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn binop_name(op: &Operator) -> &'static str {
|
||||
match op {
|
||||
Operator::Add => "__add__",
|
||||
Operator::Sub => "__sub__",
|
||||
Operator::Div => "__truediv__",
|
||||
Operator::Mod => "__mod__",
|
||||
Operator::Mult => "__mul__",
|
||||
Operator::Pow => "__pow__",
|
||||
Operator::BitOr => "__or__",
|
||||
Operator::BitXor => "__xor__",
|
||||
Operator::BitAnd => "__and__",
|
||||
Operator::LShift => "__lshift__",
|
||||
Operator::RShift => "__rshift__",
|
||||
Operator::FloorDiv => "__floordiv__",
|
||||
Operator::MatMult => "__matmul__",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binop_assign_name(op: &Operator) -> &'static str {
|
||||
match op {
|
||||
Operator::Add => "__iadd__",
|
||||
Operator::Sub => "__isub__",
|
||||
Operator::Div => "__itruediv__",
|
||||
Operator::Mod => "__imod__",
|
||||
Operator::Mult => "__imul__",
|
||||
Operator::Pow => "__ipow__",
|
||||
Operator::BitOr => "__ior__",
|
||||
Operator::BitXor => "__ixor__",
|
||||
Operator::BitAnd => "__iand__",
|
||||
Operator::LShift => "__ilshift__",
|
||||
Operator::RShift => "__irshift__",
|
||||
Operator::FloorDiv => "__ifloordiv__",
|
||||
Operator::MatMult => "__imatmul__",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unaryop_name(op: &Unaryop) -> &'static str {
|
||||
match op {
|
||||
Unaryop::UAdd => "__pos__",
|
||||
Unaryop::USub => "__neg__",
|
||||
Unaryop::Not => "__not__",
|
||||
Unaryop::Invert => "__inv__",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn comparison_name(op: &Cmpop) -> Option<&'static str> {
|
||||
match op {
|
||||
Cmpop::Lt => Some("__lt__"),
|
||||
Cmpop::LtE => Some("__le__"),
|
||||
Cmpop::Gt => Some("__gt__"),
|
||||
Cmpop::GtE => Some("__ge__"),
|
||||
Cmpop::Eq => Some("__eq__"),
|
||||
Cmpop::NotEq => Some("__ne__"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn with_fields<F>(unifier: &mut Unifier, ty: Type, f: F)
|
||||
where
|
||||
F: FnOnce(&mut Unifier, &mut HashMap<StrRef, (Type, bool)>),
|
||||
{
|
||||
let (id, mut fields, params) =
|
||||
if let TypeEnum::TObj { obj_id, fields, params } = &*unifier.get_ty(ty) {
|
||||
(*obj_id, fields.clone(), params.clone())
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
f(unifier, &mut fields);
|
||||
unsafe {
|
||||
let unification_table = unifier.get_unification_table();
|
||||
unification_table.set_value(ty, Rc::new(TypeEnum::TObj { obj_id: id, fields, params }));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_binop(
|
||||
unifier: &mut Unifier,
|
||||
store: &PrimitiveStore,
|
||||
ty: Type,
|
||||
other_ty: &[Type],
|
||||
ret_ty: Type,
|
||||
ops: &[ast::Operator],
|
||||
) {
|
||||
with_fields(unifier, ty, |unifier, fields| {
|
||||
let (other_ty, other_var_id) = if other_ty.len() == 1 {
|
||||
(other_ty[0], None)
|
||||
} else {
|
||||
let (ty, var_id) = unifier.get_fresh_var_with_range(other_ty, Some("N".into()), None);
|
||||
(ty, Some(var_id))
|
||||
};
|
||||
let function_vars = if let Some(var_id) = other_var_id {
|
||||
vec![(var_id, other_ty)].into_iter().collect::<HashMap<_, _>>()
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
for op in ops {
|
||||
fields.insert(binop_name(op).into(), {
|
||||
(
|
||||
unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
ret: ret_ty,
|
||||
vars: function_vars.clone(),
|
||||
args: vec![FuncArg {
|
||||
ty: other_ty,
|
||||
default_value: None,
|
||||
name: "other".into(),
|
||||
}],
|
||||
})),
|
||||
false,
|
||||
)
|
||||
});
|
||||
|
||||
fields.insert(binop_assign_name(op).into(), {
|
||||
(
|
||||
unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
ret: store.none,
|
||||
vars: function_vars.clone(),
|
||||
args: vec![FuncArg {
|
||||
ty: other_ty,
|
||||
default_value: None,
|
||||
name: "other".into(),
|
||||
}],
|
||||
})),
|
||||
false,
|
||||
)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn impl_unaryop(unifier: &mut Unifier, ty: Type, ret_ty: Type, ops: &[ast::Unaryop]) {
|
||||
with_fields(unifier, ty, |unifier, fields| {
|
||||
for op in ops {
|
||||
fields.insert(
|
||||
unaryop_name(op).into(),
|
||||
(
|
||||
unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
ret: ret_ty,
|
||||
vars: HashMap::new(),
|
||||
args: vec![],
|
||||
})),
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn impl_cmpop(
|
||||
unifier: &mut Unifier,
|
||||
store: &PrimitiveStore,
|
||||
ty: Type,
|
||||
other_ty: Type,
|
||||
ops: &[ast::Cmpop],
|
||||
) {
|
||||
with_fields(unifier, ty, |unifier, fields| {
|
||||
for op in ops {
|
||||
fields.insert(
|
||||
comparison_name(op).unwrap().into(),
|
||||
(
|
||||
unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
ret: store.bool,
|
||||
vars: HashMap::new(),
|
||||
args: vec![FuncArg {
|
||||
ty: other_ty,
|
||||
default_value: None,
|
||||
name: "other".into(),
|
||||
}],
|
||||
})),
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Add, Sub, Mult
|
||||
pub fn impl_basic_arithmetic(
|
||||
unifier: &mut Unifier,
|
||||
store: &PrimitiveStore,
|
||||
ty: Type,
|
||||
other_ty: &[Type],
|
||||
ret_ty: Type,
|
||||
) {
|
||||
impl_binop(
|
||||
unifier,
|
||||
store,
|
||||
ty,
|
||||
other_ty,
|
||||
ret_ty,
|
||||
&[ast::Operator::Add, ast::Operator::Sub, ast::Operator::Mult],
|
||||
)
|
||||
}
|
||||
|
||||
/// Pow
|
||||
pub fn impl_pow(
|
||||
unifier: &mut Unifier,
|
||||
store: &PrimitiveStore,
|
||||
ty: Type,
|
||||
other_ty: &[Type],
|
||||
ret_ty: Type,
|
||||
) {
|
||||
impl_binop(unifier, store, ty, other_ty, ret_ty, &[ast::Operator::Pow])
|
||||
}
|
||||
|
||||
/// BitOr, BitXor, BitAnd
|
||||
pub fn impl_bitwise_arithmetic(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
|
||||
impl_binop(
|
||||
unifier,
|
||||
store,
|
||||
ty,
|
||||
&[ty],
|
||||
ty,
|
||||
&[ast::Operator::BitAnd, ast::Operator::BitOr, ast::Operator::BitXor],
|
||||
)
|
||||
}
|
||||
|
||||
/// LShift, RShift
|
||||
pub fn impl_bitwise_shift(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
|
||||
impl_binop(unifier, store, ty, &[ty], ty, &[ast::Operator::LShift, ast::Operator::RShift])
|
||||
}
|
||||
|
||||
/// Div
|
||||
pub fn impl_div(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type, other_ty: &[Type]) {
|
||||
impl_binop(unifier, store, ty, other_ty, store.float, &[ast::Operator::Div])
|
||||
}
|
||||
|
||||
/// FloorDiv
|
||||
pub fn impl_floordiv(
|
||||
unifier: &mut Unifier,
|
||||
store: &PrimitiveStore,
|
||||
ty: Type,
|
||||
other_ty: &[Type],
|
||||
ret_ty: Type,
|
||||
) {
|
||||
impl_binop(unifier, store, ty, other_ty, ret_ty, &[ast::Operator::FloorDiv])
|
||||
}
|
||||
|
||||
/// Mod
|
||||
pub fn impl_mod(
|
||||
unifier: &mut Unifier,
|
||||
store: &PrimitiveStore,
|
||||
ty: Type,
|
||||
other_ty: &[Type],
|
||||
ret_ty: Type,
|
||||
) {
|
||||
impl_binop(unifier, store, ty, other_ty, ret_ty, &[ast::Operator::Mod])
|
||||
}
|
||||
|
||||
/// UAdd, USub
|
||||
pub fn impl_sign(unifier: &mut Unifier, _store: &PrimitiveStore, ty: Type) {
|
||||
impl_unaryop(unifier, ty, ty, &[ast::Unaryop::UAdd, ast::Unaryop::USub])
|
||||
}
|
||||
|
||||
/// Invert
|
||||
pub fn impl_invert(unifier: &mut Unifier, _store: &PrimitiveStore, ty: Type) {
|
||||
impl_unaryop(unifier, ty, ty, &[ast::Unaryop::Invert])
|
||||
}
|
||||
|
||||
/// Not
|
||||
pub fn impl_not(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
|
||||
impl_unaryop(unifier, ty, store.bool, &[ast::Unaryop::Not])
|
||||
}
|
||||
|
||||
/// Lt, LtE, Gt, GtE
|
||||
pub fn impl_comparison(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type, other_ty: Type) {
|
||||
impl_cmpop(
|
||||
unifier,
|
||||
store,
|
||||
ty,
|
||||
other_ty,
|
||||
&[ast::Cmpop::Lt, ast::Cmpop::Gt, ast::Cmpop::LtE, ast::Cmpop::GtE],
|
||||
)
|
||||
}
|
||||
|
||||
/// Eq, NotEq
|
||||
pub fn impl_eq(unifier: &mut Unifier, store: &PrimitiveStore, ty: Type) {
|
||||
impl_cmpop(unifier, store, ty, ty, &[ast::Cmpop::Eq, ast::Cmpop::NotEq])
|
||||
}
|
||||
|
||||
pub fn set_primitives_magic_methods(store: &PrimitiveStore, unifier: &mut Unifier) {
|
||||
let PrimitiveStore {
|
||||
int32: int32_t,
|
||||
int64: int64_t,
|
||||
float: float_t,
|
||||
bool: bool_t,
|
||||
uint32: uint32_t,
|
||||
uint64: uint64_t,
|
||||
..
|
||||
} = *store;
|
||||
/* int ======== */
|
||||
for t in [int32_t, int64_t, uint32_t, uint64_t] {
|
||||
impl_basic_arithmetic(unifier, store, t, &[t], t);
|
||||
impl_pow(unifier, store, t, &[t], t);
|
||||
impl_bitwise_arithmetic(unifier, store, t);
|
||||
impl_bitwise_shift(unifier, store, t);
|
||||
impl_div(unifier, store, t, &[t]);
|
||||
impl_floordiv(unifier, store, t, &[t], t);
|
||||
impl_mod(unifier, store, t, &[t], t);
|
||||
impl_invert(unifier, store, t);
|
||||
impl_not(unifier, store, t);
|
||||
impl_comparison(unifier, store, t, t);
|
||||
impl_eq(unifier, store, t);
|
||||
}
|
||||
for t in [int32_t, int64_t] {
|
||||
impl_sign(unifier, store, t);
|
||||
}
|
||||
|
||||
/* float ======== */
|
||||
impl_basic_arithmetic(unifier, store, float_t, &[float_t], float_t);
|
||||
impl_pow(unifier, store, float_t, &[int32_t, float_t], float_t);
|
||||
impl_div(unifier, store, float_t, &[float_t]);
|
||||
impl_floordiv(unifier, store, float_t, &[float_t], float_t);
|
||||
impl_mod(unifier, store, float_t, &[float_t], float_t);
|
||||
impl_sign(unifier, store, float_t);
|
||||
impl_not(unifier, store, float_t);
|
||||
impl_comparison(unifier, store, float_t, float_t);
|
||||
impl_eq(unifier, store, float_t);
|
||||
|
||||
/* bool ======== */
|
||||
impl_not(unifier, store, bool_t);
|
||||
impl_eq(unifier, store, bool_t);
|
||||
}
|
6
nac3core/src/typecheck/mod.rs
Normal file
6
nac3core/src/typecheck/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
mod function_check;
|
||||
pub mod magic_methods;
|
||||
pub mod type_error;
|
||||
pub mod type_inferencer;
|
||||
pub mod typedef;
|
||||
mod unification_table;
|
186
nac3core/src/typecheck/type_error.rs
Normal file
186
nac3core/src/typecheck/type_error.rs
Normal file
@ -0,0 +1,186 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::typecheck::typedef::TypeEnum;
|
||||
|
||||
use super::typedef::{RecordKey, Type, Unifier};
|
||||
use nac3parser::ast::{Location, StrRef};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TypeErrorKind {
|
||||
TooManyArguments {
|
||||
expected: usize,
|
||||
got: usize,
|
||||
},
|
||||
MissingArgs(String),
|
||||
UnknownArgName(StrRef),
|
||||
IncorrectArgType {
|
||||
name: StrRef,
|
||||
expected: Type,
|
||||
got: Type,
|
||||
},
|
||||
FieldUnificationError {
|
||||
field: RecordKey,
|
||||
types: (Type, Type),
|
||||
loc: (Option<Location>, Option<Location>),
|
||||
},
|
||||
IncompatibleRange(Type, Vec<Type>),
|
||||
IncompatibleTypes(Type, Type),
|
||||
MutationError(RecordKey, Type),
|
||||
NoSuchField(RecordKey, Type),
|
||||
TupleIndexOutOfBounds {
|
||||
index: i32,
|
||||
len: i32,
|
||||
},
|
||||
RequiresTypeAnn,
|
||||
PolymorphicFunctionPointer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeError {
|
||||
pub kind: TypeErrorKind,
|
||||
pub loc: Option<Location>,
|
||||
}
|
||||
|
||||
impl TypeError {
|
||||
pub fn new(kind: TypeErrorKind, loc: Option<Location>) -> TypeError {
|
||||
TypeError { kind, loc }
|
||||
}
|
||||
|
||||
pub fn at(mut self, loc: Option<Location>) -> TypeError {
|
||||
self.loc = self.loc.or(loc);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_display(self, unifier: &Unifier) -> DisplayTypeError {
|
||||
DisplayTypeError { err: self, unifier }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplayTypeError<'a> {
|
||||
pub err: TypeError,
|
||||
pub unifier: &'a Unifier,
|
||||
}
|
||||
|
||||
fn loc_to_str(loc: Option<Location>) -> String {
|
||||
match loc {
|
||||
Some(loc) => format!("(in {})", loc),
|
||||
None => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for DisplayTypeError<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use TypeErrorKind::*;
|
||||
let mut notes = Some(HashMap::new());
|
||||
match &self.err.kind {
|
||||
TooManyArguments { expected, got } => {
|
||||
write!(f, "Too many arguments. Expected {} but got {}", expected, got)
|
||||
}
|
||||
MissingArgs(args) => {
|
||||
write!(f, "Missing arguments: {}", args)
|
||||
}
|
||||
UnknownArgName(name) => {
|
||||
write!(f, "Unknown argument name: {}", name)
|
||||
}
|
||||
IncorrectArgType { name, expected, got } => {
|
||||
let expected = self.unifier.stringify_with_notes(*expected, &mut notes);
|
||||
let got = self.unifier.stringify_with_notes(*got, &mut notes);
|
||||
write!(
|
||||
f,
|
||||
"Incorrect argument type for {}. Expected {}, but got {}",
|
||||
name, expected, got
|
||||
)
|
||||
}
|
||||
FieldUnificationError { field, types, loc } => {
|
||||
let lhs = self.unifier.stringify_with_notes(types.0, &mut notes);
|
||||
let rhs = self.unifier.stringify_with_notes(types.1, &mut notes);
|
||||
write!(
|
||||
f,
|
||||
"Unable to unify field {}: Got types {}{} and {}{}",
|
||||
field,
|
||||
lhs,
|
||||
loc_to_str(loc.0),
|
||||
rhs,
|
||||
loc_to_str(loc.1)
|
||||
)
|
||||
}
|
||||
IncompatibleRange(t, ts) => {
|
||||
let t = self.unifier.stringify_with_notes(*t, &mut notes);
|
||||
let ts = ts
|
||||
.iter()
|
||||
.map(|t| self.unifier.stringify_with_notes(*t, &mut notes))
|
||||
.collect::<Vec<_>>();
|
||||
write!(f, "Expected any one of these types: {}, but got {}", ts.join(", "), t)
|
||||
}
|
||||
IncompatibleTypes(t1, t2) => {
|
||||
let type1 = self.unifier.get_ty_immutable(*t1);
|
||||
let type2 = self.unifier.get_ty_immutable(*t2);
|
||||
match (&*type1, &*type2) {
|
||||
(TypeEnum::TCall(calls), _) => {
|
||||
let loc = self.unifier.calls[calls[0].0].loc;
|
||||
let result = write!(
|
||||
f,
|
||||
"{} is not callable",
|
||||
self.unifier.stringify_with_notes(*t2, &mut notes)
|
||||
);
|
||||
if let Some(loc) = loc {
|
||||
result?;
|
||||
write!(f, " (in {})", loc)?;
|
||||
return Ok(());
|
||||
}
|
||||
result
|
||||
}
|
||||
(TypeEnum::TTuple { ty: ty1 }, TypeEnum::TTuple { ty: ty2 })
|
||||
if ty1.len() != ty2.len() =>
|
||||
{
|
||||
let t1 = self.unifier.stringify_with_notes(*t1, &mut notes);
|
||||
let t2 = self.unifier.stringify_with_notes(*t2, &mut notes);
|
||||
write!(f, "Tuple length mismatch: got {} and {}", t1, t2)
|
||||
}
|
||||
_ => {
|
||||
let t1 = self.unifier.stringify_with_notes(*t1, &mut notes);
|
||||
let t2 = self.unifier.stringify_with_notes(*t2, &mut notes);
|
||||
write!(f, "Incompatible types: {} and {}", t1, t2)
|
||||
}
|
||||
}
|
||||
}
|
||||
MutationError(name, t) => {
|
||||
if let TypeEnum::TTuple { .. } = &*self.unifier.get_ty_immutable(*t) {
|
||||
write!(f, "Cannot assign to an element of a tuple")
|
||||
} else {
|
||||
let t = self.unifier.stringify_with_notes(*t, &mut notes);
|
||||
write!(f, "Cannot assign to field {} of {}, which is immutable", name, t)
|
||||
}
|
||||
}
|
||||
NoSuchField(name, t) => {
|
||||
let t = self.unifier.stringify_with_notes(*t, &mut notes);
|
||||
write!(f, "`{}::{}` field/method does not exist", t, name)
|
||||
}
|
||||
TupleIndexOutOfBounds { index, len } => {
|
||||
write!(
|
||||
f,
|
||||
"Tuple index out of bounds. Got {} but tuple has only {} elements",
|
||||
index, len
|
||||
)
|
||||
}
|
||||
RequiresTypeAnn => {
|
||||
write!(f, "Unable to infer virtual object type: Type annotation required")
|
||||
}
|
||||
PolymorphicFunctionPointer => {
|
||||
write!(f, "Polymorphic function pointers is not supported")
|
||||
}
|
||||
}?;
|
||||
if let Some(loc) = self.err.loc {
|
||||
write!(f, " at {}", loc)?;
|
||||
}
|
||||
let notes = notes.unwrap();
|
||||
if !notes.is_empty() {
|
||||
write!(f, "\n\nNotes:")?;
|
||||
for line in notes.values() {
|
||||
write!(f, "\n {}", line)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
1118
nac3core/src/typecheck/type_inferencer/mod.rs
Normal file
1118
nac3core/src/typecheck/type_inferencer/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
713
nac3core/src/typecheck/type_inferencer/test.rs
Normal file
713
nac3core/src/typecheck/type_inferencer/test.rs
Normal file
@ -0,0 +1,713 @@
|
||||
use super::super::{magic_methods::with_fields, typedef::*};
|
||||
use super::*;
|
||||
use crate::{
|
||||
codegen::CodeGenContext,
|
||||
symbol_resolver::ValueEnum,
|
||||
toplevel::{DefinitionId, TopLevelDef},
|
||||
};
|
||||
use indoc::indoc;
|
||||
use itertools::zip;
|
||||
use nac3parser::parser::parse_program;
|
||||
use parking_lot::RwLock;
|
||||
use test_case::test_case;
|
||||
|
||||
struct Resolver {
|
||||
id_to_type: HashMap<StrRef, Type>,
|
||||
id_to_def: HashMap<StrRef, DefinitionId>,
|
||||
class_names: HashMap<StrRef, Type>,
|
||||
}
|
||||
|
||||
impl SymbolResolver for Resolver {
|
||||
fn get_default_param_value(
|
||||
&self,
|
||||
_: &nac3parser::ast::Expr,
|
||||
) -> Option<crate::symbol_resolver::SymbolValue> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_symbol_type(
|
||||
&self,
|
||||
_: &mut Unifier,
|
||||
_: &[Arc<RwLock<TopLevelDef>>],
|
||||
_: &PrimitiveStore,
|
||||
str: StrRef,
|
||||
) -> Result<Type, String> {
|
||||
self.id_to_type.get(&str).cloned().ok_or_else(|| format!("cannot find symbol `{}`", str))
|
||||
}
|
||||
|
||||
fn get_symbol_value<'ctx, 'a>(
|
||||
&self,
|
||||
_: StrRef,
|
||||
_: &mut CodeGenContext<'ctx, 'a>,
|
||||
) -> Option<ValueEnum<'ctx>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
|
||||
self.id_to_def.get(&id).cloned().ok_or("Unknown identifier".to_string())
|
||||
}
|
||||
|
||||
fn get_string_id(&self, _: &str) -> i32 {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_exception_id(&self, tyid: usize) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
struct TestEnvironment {
|
||||
pub unifier: Unifier,
|
||||
pub function_data: FunctionData,
|
||||
pub primitives: PrimitiveStore,
|
||||
pub id_to_name: HashMap<usize, StrRef>,
|
||||
pub identifier_mapping: HashMap<StrRef, Type>,
|
||||
pub virtual_checks: Vec<(Type, Type, nac3parser::ast::Location)>,
|
||||
pub calls: HashMap<CodeLocation, CallId>,
|
||||
pub top_level: TopLevelContext,
|
||||
}
|
||||
|
||||
impl TestEnvironment {
|
||||
pub fn basic_test_env() -> TestEnvironment {
|
||||
let mut unifier = Unifier::new();
|
||||
|
||||
let int32 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(0),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
with_fields(&mut unifier, int32, |unifier, fields| {
|
||||
let add_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![FuncArg { name: "other".into(), ty: int32, default_value: None }],
|
||||
ret: int32,
|
||||
vars: HashMap::new(),
|
||||
}));
|
||||
fields.insert("__add__".into(), (add_ty, false));
|
||||
});
|
||||
let int64 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(1),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let float = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(2),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let bool = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(3),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let none = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(4),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let range = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(5),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let str = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(6),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let exception = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(7),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let uint32 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(8),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let uint64 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(9),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let option = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(10),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let primitives = PrimitiveStore {
|
||||
int32,
|
||||
int64,
|
||||
float,
|
||||
bool,
|
||||
none,
|
||||
range,
|
||||
str,
|
||||
exception,
|
||||
uint32,
|
||||
uint64,
|
||||
option,
|
||||
};
|
||||
set_primitives_magic_methods(&primitives, &mut unifier);
|
||||
|
||||
let id_to_name = [
|
||||
(0, "int32".into()),
|
||||
(1, "int64".into()),
|
||||
(2, "float".into()),
|
||||
(3, "bool".into()),
|
||||
(4, "none".into()),
|
||||
(5, "range".into()),
|
||||
(6, "str".into()),
|
||||
(7, "exception".into()),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let mut identifier_mapping = HashMap::new();
|
||||
identifier_mapping.insert("None".into(), none);
|
||||
|
||||
let resolver = Arc::new(Resolver {
|
||||
id_to_type: identifier_mapping.clone(),
|
||||
id_to_def: Default::default(),
|
||||
class_names: Default::default(),
|
||||
}) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
TestEnvironment {
|
||||
top_level: TopLevelContext {
|
||||
definitions: Default::default(),
|
||||
unifiers: Default::default(),
|
||||
personality_symbol: None,
|
||||
},
|
||||
unifier,
|
||||
function_data: FunctionData {
|
||||
resolver,
|
||||
bound_variables: Vec::new(),
|
||||
return_type: None,
|
||||
},
|
||||
primitives,
|
||||
id_to_name,
|
||||
identifier_mapping,
|
||||
virtual_checks: Vec::new(),
|
||||
calls: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new() -> TestEnvironment {
|
||||
let mut unifier = Unifier::new();
|
||||
let mut identifier_mapping = HashMap::new();
|
||||
let mut top_level_defs: Vec<Arc<RwLock<TopLevelDef>>> = Vec::new();
|
||||
let int32 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(0),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
with_fields(&mut unifier, int32, |unifier, fields| {
|
||||
let add_ty = unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![FuncArg { name: "other".into(), ty: int32, default_value: None }],
|
||||
ret: int32,
|
||||
vars: HashMap::new(),
|
||||
}));
|
||||
fields.insert("__add__".into(), (add_ty, false));
|
||||
});
|
||||
let int64 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(1),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let float = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(2),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let bool = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(3),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let none = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(4),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let range = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(5),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let str = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(6),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let exception = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(7),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let uint32 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(8),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let uint64 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(9),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let option = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(10),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
identifier_mapping.insert("None".into(), none);
|
||||
for (i, name) in ["int32", "int64", "float", "bool", "none", "range", "str", "Exception"]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
top_level_defs.push(
|
||||
RwLock::new(TopLevelDef::Class {
|
||||
name: (*name).into(),
|
||||
object_id: DefinitionId(i),
|
||||
type_vars: Default::default(),
|
||||
fields: Default::default(),
|
||||
methods: Default::default(),
|
||||
ancestors: Default::default(),
|
||||
resolver: None,
|
||||
constructor: None,
|
||||
loc: None,
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
let defs = 7;
|
||||
|
||||
let primitives = PrimitiveStore {
|
||||
int32,
|
||||
int64,
|
||||
float,
|
||||
bool,
|
||||
none,
|
||||
range,
|
||||
str,
|
||||
exception,
|
||||
uint32,
|
||||
uint64,
|
||||
option,
|
||||
};
|
||||
|
||||
let (v0, id) = unifier.get_dummy_var();
|
||||
|
||||
let foo_ty = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(defs + 1),
|
||||
fields: [("a".into(), (v0, true))].iter().cloned().collect::<HashMap<_, _>>(),
|
||||
params: [(id, v0)].iter().cloned().collect::<HashMap<_, _>>(),
|
||||
});
|
||||
top_level_defs.push(
|
||||
RwLock::new(TopLevelDef::Class {
|
||||
name: "Foo".into(),
|
||||
object_id: DefinitionId(defs + 1),
|
||||
type_vars: vec![v0],
|
||||
fields: [("a".into(), v0, true)].into(),
|
||||
methods: Default::default(),
|
||||
ancestors: Default::default(),
|
||||
resolver: None,
|
||||
constructor: None,
|
||||
loc: None,
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
|
||||
identifier_mapping.insert(
|
||||
"Foo".into(),
|
||||
unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![],
|
||||
ret: foo_ty,
|
||||
vars: [(id, v0)].iter().cloned().collect(),
|
||||
})),
|
||||
);
|
||||
|
||||
let fun = unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![],
|
||||
ret: int32,
|
||||
vars: Default::default(),
|
||||
}));
|
||||
let bar = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(defs + 2),
|
||||
fields: [("a".into(), (int32, true)), ("b".into(), (fun, true))]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
params: Default::default(),
|
||||
});
|
||||
top_level_defs.push(
|
||||
RwLock::new(TopLevelDef::Class {
|
||||
name: "Bar".into(),
|
||||
object_id: DefinitionId(defs + 2),
|
||||
type_vars: Default::default(),
|
||||
fields: [("a".into(), int32, true), ("b".into(), fun, true)].into(),
|
||||
methods: Default::default(),
|
||||
ancestors: Default::default(),
|
||||
resolver: None,
|
||||
constructor: None,
|
||||
loc: None,
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
identifier_mapping.insert(
|
||||
"Bar".into(),
|
||||
unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![],
|
||||
ret: bar,
|
||||
vars: Default::default(),
|
||||
})),
|
||||
);
|
||||
|
||||
let bar2 = unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(defs + 3),
|
||||
fields: [("a".into(), (bool, true)), ("b".into(), (fun, false))]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
params: Default::default(),
|
||||
});
|
||||
top_level_defs.push(
|
||||
RwLock::new(TopLevelDef::Class {
|
||||
name: "Bar2".into(),
|
||||
object_id: DefinitionId(defs + 3),
|
||||
type_vars: Default::default(),
|
||||
fields: [("a".into(), bool, true), ("b".into(), fun, false)].into(),
|
||||
methods: Default::default(),
|
||||
ancestors: Default::default(),
|
||||
resolver: None,
|
||||
constructor: None,
|
||||
loc: None,
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
identifier_mapping.insert(
|
||||
"Bar2".into(),
|
||||
unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![],
|
||||
ret: bar2,
|
||||
vars: Default::default(),
|
||||
})),
|
||||
);
|
||||
let class_names = [("Bar".into(), bar), ("Bar2".into(), bar2)].iter().cloned().collect();
|
||||
|
||||
let id_to_name = [
|
||||
"int32".into(),
|
||||
"int64".into(),
|
||||
"float".into(),
|
||||
"bool".into(),
|
||||
"none".into(),
|
||||
"range".into(),
|
||||
"str".into(),
|
||||
"exception".into(),
|
||||
"Foo".into(),
|
||||
"Bar".into(),
|
||||
"Bar2".into(),
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(a, b)| (a, *b))
|
||||
.collect();
|
||||
|
||||
let top_level = TopLevelContext {
|
||||
definitions: Arc::new(top_level_defs.into()),
|
||||
unifiers: Default::default(),
|
||||
personality_symbol: None,
|
||||
};
|
||||
|
||||
let resolver = Arc::new(Resolver {
|
||||
id_to_type: identifier_mapping.clone(),
|
||||
id_to_def: [
|
||||
("Foo".into(), DefinitionId(defs + 1)),
|
||||
("Bar".into(), DefinitionId(defs + 2)),
|
||||
("Bar2".into(), DefinitionId(defs + 3)),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
class_names,
|
||||
}) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
TestEnvironment {
|
||||
unifier,
|
||||
top_level,
|
||||
function_data: FunctionData {
|
||||
resolver,
|
||||
bound_variables: Vec::new(),
|
||||
return_type: None,
|
||||
},
|
||||
primitives,
|
||||
id_to_name,
|
||||
identifier_mapping,
|
||||
virtual_checks: Vec::new(),
|
||||
calls: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_inferencer(&mut self) -> Inferencer {
|
||||
Inferencer {
|
||||
top_level: &self.top_level,
|
||||
function_data: &mut self.function_data,
|
||||
unifier: &mut self.unifier,
|
||||
variable_mapping: Default::default(),
|
||||
primitives: &mut self.primitives,
|
||||
virtual_checks: &mut self.virtual_checks,
|
||||
calls: &mut self.calls,
|
||||
defined_identifiers: Default::default(),
|
||||
in_handler: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(indoc! {"
|
||||
a = 1234
|
||||
b = int64(2147483648)
|
||||
c = 1.234
|
||||
d = True
|
||||
"},
|
||||
[("a", "int32"), ("b", "int64"), ("c", "float"), ("d", "bool")].iter().cloned().collect(),
|
||||
&[]
|
||||
; "primitives test")]
|
||||
#[test_case(indoc! {"
|
||||
a = lambda x, y: x
|
||||
b = lambda x: a(x, x)
|
||||
c = 1.234
|
||||
d = b(c)
|
||||
"},
|
||||
[("a", "fn[[x:float, y:float], float]"), ("b", "fn[[x:float], float]"), ("c", "float"), ("d", "float")].iter().cloned().collect(),
|
||||
&[]
|
||||
; "lambda test")]
|
||||
#[test_case(indoc! {"
|
||||
a = lambda x: x + x
|
||||
b = lambda x: a(x) + x
|
||||
a = b
|
||||
c = b(1)
|
||||
"},
|
||||
[("a", "fn[[x:int32], int32]"), ("b", "fn[[x:int32], int32]"), ("c", "int32")].iter().cloned().collect(),
|
||||
&[]
|
||||
; "lambda test 2")]
|
||||
#[test_case(indoc! {"
|
||||
a = lambda x: x
|
||||
b = lambda x: x
|
||||
|
||||
foo1 = Foo()
|
||||
foo2 = Foo()
|
||||
c = a(foo1.a)
|
||||
d = b(foo2.a)
|
||||
|
||||
a(True)
|
||||
b(123)
|
||||
|
||||
"},
|
||||
[("a", "fn[[x:bool], bool]"), ("b", "fn[[x:int32], int32]"), ("c", "bool"),
|
||||
("d", "int32"), ("foo1", "Foo[bool]"), ("foo2", "Foo[int32]")].iter().cloned().collect(),
|
||||
&[]
|
||||
; "obj test")]
|
||||
#[test_case(indoc! {"
|
||||
a = [1, 2, 3]
|
||||
b = [x + x for x in a]
|
||||
"},
|
||||
[("a", "list[int32]"), ("b", "list[int32]")].iter().cloned().collect(),
|
||||
&[]
|
||||
; "listcomp test")]
|
||||
#[test_case(indoc! {"
|
||||
a = virtual(Bar(), Bar)
|
||||
b = a.b()
|
||||
a = virtual(Bar2())
|
||||
"},
|
||||
[("a", "virtual[Bar]"), ("b", "int32")].iter().cloned().collect(),
|
||||
&[("Bar", "Bar"), ("Bar2", "Bar")]
|
||||
; "virtual test")]
|
||||
#[test_case(indoc! {"
|
||||
a = [virtual(Bar(), Bar), virtual(Bar2())]
|
||||
b = [x.b() for x in a]
|
||||
"},
|
||||
[("a", "list[virtual[Bar]]"), ("b", "list[int32]")].iter().cloned().collect(),
|
||||
&[("Bar", "Bar"), ("Bar2", "Bar")]
|
||||
; "virtual list test")]
|
||||
fn test_basic(source: &str, mapping: HashMap<&str, &str>, virtuals: &[(&str, &str)]) {
|
||||
println!("source:\n{}", source);
|
||||
let mut env = TestEnvironment::new();
|
||||
let id_to_name = std::mem::take(&mut env.id_to_name);
|
||||
let mut defined_identifiers: HashSet<_> = env.identifier_mapping.keys().cloned().collect();
|
||||
defined_identifiers.insert("virtual".into());
|
||||
let mut inferencer = env.get_inferencer();
|
||||
inferencer.defined_identifiers = defined_identifiers.clone();
|
||||
let statements = parse_program(source, Default::default()).unwrap();
|
||||
let statements = statements
|
||||
.into_iter()
|
||||
.map(|v| inferencer.fold_stmt(v))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
inferencer.check_block(&statements, &mut defined_identifiers).unwrap();
|
||||
|
||||
for (k, v) in inferencer.variable_mapping.iter() {
|
||||
let name = inferencer.unifier.internal_stringify(
|
||||
*v,
|
||||
&mut |v| (*id_to_name.get(&v).unwrap()).into(),
|
||||
&mut |v| format!("v{}", v),
|
||||
&mut None,
|
||||
);
|
||||
println!("{}: {}", k, name);
|
||||
}
|
||||
for (k, v) in mapping.iter() {
|
||||
let ty = inferencer.variable_mapping.get(&(*k).into()).unwrap();
|
||||
let name = inferencer.unifier.internal_stringify(
|
||||
*ty,
|
||||
&mut |v| (*id_to_name.get(&v).unwrap()).into(),
|
||||
&mut |v| format!("v{}", v),
|
||||
&mut None,
|
||||
);
|
||||
assert_eq!(format!("{}: {}", k, v), format!("{}: {}", k, name));
|
||||
}
|
||||
assert_eq!(inferencer.virtual_checks.len(), virtuals.len());
|
||||
for ((a, b, _), (x, y)) in zip(inferencer.virtual_checks.iter(), virtuals) {
|
||||
let a = inferencer.unifier.internal_stringify(
|
||||
*a,
|
||||
&mut |v| (*id_to_name.get(&v).unwrap()).into(),
|
||||
&mut |v| format!("v{}", v),
|
||||
&mut None,
|
||||
);
|
||||
let b = inferencer.unifier.internal_stringify(
|
||||
*b,
|
||||
&mut |v| (*id_to_name.get(&v).unwrap()).into(),
|
||||
&mut |v| format!("v{}", v),
|
||||
&mut None,
|
||||
);
|
||||
|
||||
assert_eq!(&a, x);
|
||||
assert_eq!(&b, y);
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(indoc! {"
|
||||
a = 2
|
||||
b = 2
|
||||
c = a + b
|
||||
d = a - b
|
||||
e = a * b
|
||||
f = a / b
|
||||
g = a // b
|
||||
h = a % b
|
||||
"},
|
||||
[("a", "int32"),
|
||||
("b", "int32"),
|
||||
("c", "int32"),
|
||||
("d", "int32"),
|
||||
("e", "int32"),
|
||||
("f", "float"),
|
||||
("g", "int32"),
|
||||
("h", "int32")].iter().cloned().collect()
|
||||
; "int32")]
|
||||
#[test_case(
|
||||
indoc! {"
|
||||
a = 2.4
|
||||
b = 3.6
|
||||
c = a + b
|
||||
d = a - b
|
||||
e = a * b
|
||||
f = a / b
|
||||
g = a // b
|
||||
h = a % b
|
||||
i = a ** b
|
||||
ii = 3
|
||||
j = a ** b
|
||||
"},
|
||||
[("a", "float"),
|
||||
("b", "float"),
|
||||
("c", "float"),
|
||||
("d", "float"),
|
||||
("e", "float"),
|
||||
("f", "float"),
|
||||
("g", "float"),
|
||||
("h", "float"),
|
||||
("i", "float"),
|
||||
("ii", "int32"),
|
||||
("j", "float")].iter().cloned().collect()
|
||||
; "float"
|
||||
)]
|
||||
#[test_case(
|
||||
indoc! {"
|
||||
a = int64(12312312312)
|
||||
b = int64(24242424424)
|
||||
c = a + b
|
||||
d = a - b
|
||||
e = a * b
|
||||
f = a / b
|
||||
g = a // b
|
||||
h = a % b
|
||||
i = a == b
|
||||
j = a > b
|
||||
k = a < b
|
||||
l = a != b
|
||||
"},
|
||||
[("a", "int64"),
|
||||
("b", "int64"),
|
||||
("c", "int64"),
|
||||
("d", "int64"),
|
||||
("e", "int64"),
|
||||
("f", "float"),
|
||||
("g", "int64"),
|
||||
("h", "int64"),
|
||||
("i", "bool"),
|
||||
("j", "bool"),
|
||||
("k", "bool"),
|
||||
("l", "bool")].iter().cloned().collect()
|
||||
; "int64"
|
||||
)]
|
||||
#[test_case(
|
||||
indoc! {"
|
||||
a = True
|
||||
b = False
|
||||
c = a == b
|
||||
d = not a
|
||||
e = a != b
|
||||
"},
|
||||
[("a", "bool"),
|
||||
("b", "bool"),
|
||||
("c", "bool"),
|
||||
("d", "bool"),
|
||||
("e", "bool")].iter().cloned().collect()
|
||||
; "boolean"
|
||||
)]
|
||||
fn test_primitive_magic_methods(source: &str, mapping: HashMap<&str, &str>) {
|
||||
println!("source:\n{}", source);
|
||||
let mut env = TestEnvironment::basic_test_env();
|
||||
let id_to_name = std::mem::take(&mut env.id_to_name);
|
||||
let mut defined_identifiers: HashSet<_> = env.identifier_mapping.keys().cloned().collect();
|
||||
defined_identifiers.insert("virtual".into());
|
||||
let mut inferencer = env.get_inferencer();
|
||||
inferencer.defined_identifiers = defined_identifiers.clone();
|
||||
let statements = parse_program(source, Default::default()).unwrap();
|
||||
let statements = statements
|
||||
.into_iter()
|
||||
.map(|v| inferencer.fold_stmt(v))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
|
||||
inferencer.check_block(&statements, &mut defined_identifiers).unwrap();
|
||||
|
||||
for (k, v) in inferencer.variable_mapping.iter() {
|
||||
let name = inferencer.unifier.internal_stringify(
|
||||
*v,
|
||||
&mut |v| (*id_to_name.get(&v).unwrap()).into(),
|
||||
&mut |v| format!("v{}", v),
|
||||
&mut None,
|
||||
);
|
||||
println!("{}: {}", k, name);
|
||||
}
|
||||
for (k, v) in mapping.iter() {
|
||||
let ty = inferencer.variable_mapping.get(&(*k).into()).unwrap();
|
||||
let name = inferencer.unifier.internal_stringify(
|
||||
*ty,
|
||||
&mut |v| (*id_to_name.get(&v).unwrap()).into(),
|
||||
&mut |v| format!("v{}", v),
|
||||
&mut None,
|
||||
);
|
||||
assert_eq!(format!("{}: {}", k, v), format!("{}: {}", k, name));
|
||||
}
|
||||
}
|
1229
nac3core/src/typecheck/typedef/mod.rs
Normal file
1229
nac3core/src/typecheck/typedef/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
571
nac3core/src/typecheck/typedef/test.rs
Normal file
571
nac3core/src/typecheck/typedef/test.rs
Normal file
@ -0,0 +1,571 @@
|
||||
use super::super::magic_methods::with_fields;
|
||||
use super::*;
|
||||
use indoc::indoc;
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use test_case::test_case;
|
||||
|
||||
impl Unifier {
|
||||
/// Check whether two types are equal.
|
||||
fn eq(&mut self, a: Type, b: Type) -> bool {
|
||||
if a == b {
|
||||
return true;
|
||||
}
|
||||
let (ty_a, ty_b) = {
|
||||
let table = &mut self.unification_table;
|
||||
if table.unioned(a, b) {
|
||||
return true;
|
||||
}
|
||||
(table.probe_value(a).clone(), table.probe_value(b).clone())
|
||||
};
|
||||
|
||||
match (&*ty_a, &*ty_b) {
|
||||
(
|
||||
TypeEnum::TVar { fields: None, id: id1, .. },
|
||||
TypeEnum::TVar { fields: None, id: id2, .. },
|
||||
) => id1 == id2,
|
||||
(
|
||||
TypeEnum::TVar { fields: Some(map1), .. },
|
||||
TypeEnum::TVar { fields: Some(map2), .. },
|
||||
) => self.map_eq2(map1, map2),
|
||||
(TypeEnum::TTuple { ty: ty1 }, TypeEnum::TTuple { ty: ty2 }) => {
|
||||
ty1.len() == ty2.len()
|
||||
&& ty1.iter().zip(ty2.iter()).all(|(t1, t2)| self.eq(*t1, *t2))
|
||||
}
|
||||
(TypeEnum::TList { ty: ty1 }, TypeEnum::TList { ty: ty2 })
|
||||
| (TypeEnum::TVirtual { ty: ty1 }, TypeEnum::TVirtual { ty: ty2 }) => {
|
||||
self.eq(*ty1, *ty2)
|
||||
}
|
||||
(
|
||||
TypeEnum::TObj { obj_id: id1, params: params1, .. },
|
||||
TypeEnum::TObj { obj_id: id2, params: params2, .. },
|
||||
) => id1 == id2 && self.map_eq(params1, params2),
|
||||
// TCall and TFunc are not yet implemented
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_eq<K>(&mut self, map1: &Mapping<K>, map2: &Mapping<K>) -> bool
|
||||
where
|
||||
K: std::hash::Hash + std::cmp::Eq + std::clone::Clone,
|
||||
{
|
||||
if map1.len() != map2.len() {
|
||||
return false;
|
||||
}
|
||||
for (k, v) in map1.iter() {
|
||||
if !map2.get(k).map(|v1| self.eq(*v, *v1)).unwrap_or(false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn map_eq2<K>(&mut self, map1: &Mapping<K, RecordField>, map2: &Mapping<K, RecordField>) -> bool
|
||||
where
|
||||
K: std::hash::Hash + std::cmp::Eq + std::clone::Clone,
|
||||
{
|
||||
if map1.len() != map2.len() {
|
||||
return false;
|
||||
}
|
||||
for (k, v) in map1.iter() {
|
||||
if !map2.get(k).map(|v1| self.eq(v.ty, v1.ty)).unwrap_or(false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct TestEnvironment {
|
||||
pub unifier: Unifier,
|
||||
pub type_mapping: HashMap<String, Type>,
|
||||
}
|
||||
|
||||
impl TestEnvironment {
|
||||
fn new() -> TestEnvironment {
|
||||
let mut unifier = Unifier::new();
|
||||
let mut type_mapping = HashMap::new();
|
||||
|
||||
type_mapping.insert(
|
||||
"int".into(),
|
||||
unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(0),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
}),
|
||||
);
|
||||
type_mapping.insert(
|
||||
"float".into(),
|
||||
unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(1),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
}),
|
||||
);
|
||||
type_mapping.insert(
|
||||
"bool".into(),
|
||||
unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(2),
|
||||
fields: HashMap::new(),
|
||||
params: HashMap::new(),
|
||||
}),
|
||||
);
|
||||
let (v0, id) = unifier.get_dummy_var();
|
||||
type_mapping.insert(
|
||||
"Foo".into(),
|
||||
unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(3),
|
||||
fields: [("a".into(), (v0, true))].iter().cloned().collect::<HashMap<_, _>>(),
|
||||
params: [(id, v0)].iter().cloned().collect::<HashMap<_, _>>(),
|
||||
}),
|
||||
);
|
||||
|
||||
TestEnvironment { unifier, type_mapping }
|
||||
}
|
||||
|
||||
fn parse(&mut self, typ: &str, mapping: &Mapping<String>) -> Type {
|
||||
let result = self.internal_parse(typ, mapping);
|
||||
assert!(result.1.is_empty());
|
||||
result.0
|
||||
}
|
||||
|
||||
fn internal_parse<'a, 'b>(
|
||||
&'a mut self,
|
||||
typ: &'b str,
|
||||
mapping: &Mapping<String>,
|
||||
) -> (Type, &'b str) {
|
||||
// for testing only, so we can just panic when the input is malformed
|
||||
let end = typ.find(|c| ['[', ',', ']', '='].contains(&c)).unwrap_or_else(|| typ.len());
|
||||
match &typ[..end] {
|
||||
"tuple" => {
|
||||
let mut s = &typ[end..];
|
||||
assert!(&s[0..1] == "[");
|
||||
let mut ty = Vec::new();
|
||||
while &s[0..1] != "]" {
|
||||
let result = self.internal_parse(&s[1..], mapping);
|
||||
ty.push(result.0);
|
||||
s = result.1;
|
||||
}
|
||||
(self.unifier.add_ty(TypeEnum::TTuple { ty }), &s[1..])
|
||||
}
|
||||
"list" => {
|
||||
assert!(&typ[end..end + 1] == "[");
|
||||
let (ty, s) = self.internal_parse(&typ[end + 1..], mapping);
|
||||
assert!(&s[0..1] == "]");
|
||||
(self.unifier.add_ty(TypeEnum::TList { ty }), &s[1..])
|
||||
}
|
||||
"Record" => {
|
||||
let mut s = &typ[end..];
|
||||
assert!(&s[0..1] == "[");
|
||||
let mut fields = HashMap::new();
|
||||
while &s[0..1] != "]" {
|
||||
let eq = s.find('=').unwrap();
|
||||
let key = s[1..eq].into();
|
||||
let result = self.internal_parse(&s[eq + 1..], mapping);
|
||||
fields.insert(key, RecordField::new(result.0, true, None));
|
||||
s = result.1;
|
||||
}
|
||||
(self.unifier.add_record(fields), &s[1..])
|
||||
}
|
||||
x => {
|
||||
let mut s = &typ[end..];
|
||||
let ty = mapping.get(x).cloned().unwrap_or_else(|| {
|
||||
// mapping should be type variables, type_mapping should be concrete types
|
||||
// we should not resolve the type of type variables.
|
||||
let mut ty = *self.type_mapping.get(x).unwrap();
|
||||
let te = self.unifier.get_ty(ty);
|
||||
if let TypeEnum::TObj { params, .. } = &*te.as_ref() {
|
||||
if !params.is_empty() {
|
||||
assert!(&s[0..1] == "[");
|
||||
let mut p = Vec::new();
|
||||
while &s[0..1] != "]" {
|
||||
let result = self.internal_parse(&s[1..], mapping);
|
||||
p.push(result.0);
|
||||
s = result.1;
|
||||
}
|
||||
s = &s[1..];
|
||||
ty = self
|
||||
.unifier
|
||||
.subst(ty, ¶ms.keys().cloned().zip(p.into_iter()).collect())
|
||||
.unwrap_or(ty);
|
||||
}
|
||||
}
|
||||
ty
|
||||
});
|
||||
(ty, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unify(&mut self, typ1: Type, typ2: Type) -> Result<(), String> {
|
||||
self.unifier.unify(typ1, typ2).map_err(|e| e.to_display(&self.unifier).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(2,
|
||||
&[("v1", "v2"), ("v2", "float")],
|
||||
&[("v1", "float"), ("v2", "float")]
|
||||
; "simple variable"
|
||||
)]
|
||||
#[test_case(2,
|
||||
&[("v1", "list[v2]"), ("v1", "list[float]")],
|
||||
&[("v1", "list[float]"), ("v2", "float")]
|
||||
; "list element"
|
||||
)]
|
||||
#[test_case(3,
|
||||
&[
|
||||
("v1", "Record[a=v3,b=v3]"),
|
||||
("v2", "Record[b=float,c=v3]"),
|
||||
("v1", "v2")
|
||||
],
|
||||
&[
|
||||
("v1", "Record[a=float,b=float,c=float]"),
|
||||
("v2", "Record[a=float,b=float,c=float]"),
|
||||
("v3", "float")
|
||||
]
|
||||
; "record merge"
|
||||
)]
|
||||
#[test_case(3,
|
||||
&[
|
||||
("v1", "Record[a=float]"),
|
||||
("v2", "Foo[v3]"),
|
||||
("v1", "v2")
|
||||
],
|
||||
&[
|
||||
("v1", "Foo[float]"),
|
||||
("v3", "float")
|
||||
]
|
||||
; "record obj merge"
|
||||
)]
|
||||
/// Test cases for valid unifications.
|
||||
fn test_unify(
|
||||
variable_count: u32,
|
||||
unify_pairs: &[(&'static str, &'static str)],
|
||||
verify_pairs: &[(&'static str, &'static str)],
|
||||
) {
|
||||
let unify_count = unify_pairs.len();
|
||||
// test all permutations...
|
||||
for perm in unify_pairs.iter().permutations(unify_count) {
|
||||
let mut env = TestEnvironment::new();
|
||||
let mut mapping = HashMap::new();
|
||||
for i in 1..=variable_count {
|
||||
let v = env.unifier.get_dummy_var();
|
||||
mapping.insert(format!("v{}", i), v.0);
|
||||
}
|
||||
// unification may have side effect when we do type resolution, so freeze the types
|
||||
// before doing unification.
|
||||
let mut pairs = Vec::new();
|
||||
for (a, b) in perm.iter() {
|
||||
let t1 = env.parse(a, &mapping);
|
||||
let t2 = env.parse(b, &mapping);
|
||||
pairs.push((t1, t2));
|
||||
}
|
||||
for (t1, t2) in pairs {
|
||||
env.unifier.unify(t1, t2).unwrap();
|
||||
}
|
||||
for (a, b) in verify_pairs.iter() {
|
||||
println!("{} = {}", a, b);
|
||||
let t1 = env.parse(a, &mapping);
|
||||
let t2 = env.parse(b, &mapping);
|
||||
println!("a = {}, b = {}", env.unifier.stringify(t1), env.unifier.stringify(t2));
|
||||
assert!(env.unifier.eq(t1, t2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test_case(2,
|
||||
&[
|
||||
("v1", "tuple[int]"),
|
||||
("v2", "list[int]"),
|
||||
],
|
||||
(("v1", "v2"), "Incompatible types: list[0] and tuple[0]")
|
||||
; "type mismatch"
|
||||
)]
|
||||
#[test_case(2,
|
||||
&[
|
||||
("v1", "tuple[int]"),
|
||||
("v2", "tuple[float]"),
|
||||
],
|
||||
(("v1", "v2"), "Incompatible types: 0 and 1")
|
||||
; "tuple parameter mismatch"
|
||||
)]
|
||||
#[test_case(2,
|
||||
&[
|
||||
("v1", "tuple[int,int]"),
|
||||
("v2", "tuple[int]"),
|
||||
],
|
||||
(("v1", "v2"), "Tuple length mismatch: got tuple[0, 0] and tuple[0]")
|
||||
; "tuple length mismatch"
|
||||
)]
|
||||
#[test_case(3,
|
||||
&[
|
||||
("v1", "Record[a=float,b=int]"),
|
||||
("v2", "Foo[v3]"),
|
||||
],
|
||||
(("v1", "v2"), "`3[var4]::b` field/method does not exist")
|
||||
; "record obj merge"
|
||||
)]
|
||||
/// Test cases for invalid unifications.
|
||||
fn test_invalid_unification(
|
||||
variable_count: u32,
|
||||
unify_pairs: &[(&'static str, &'static str)],
|
||||
errornous_pair: ((&'static str, &'static str), &'static str),
|
||||
) {
|
||||
let mut env = TestEnvironment::new();
|
||||
let mut mapping = HashMap::new();
|
||||
for i in 1..=variable_count {
|
||||
let v = env.unifier.get_dummy_var();
|
||||
mapping.insert(format!("v{}", i), v.0);
|
||||
}
|
||||
// unification may have side effect when we do type resolution, so freeze the types
|
||||
// before doing unification.
|
||||
let mut pairs = Vec::new();
|
||||
for (a, b) in unify_pairs.iter() {
|
||||
let t1 = env.parse(a, &mapping);
|
||||
let t2 = env.parse(b, &mapping);
|
||||
pairs.push((t1, t2));
|
||||
}
|
||||
let (t1, t2) =
|
||||
(env.parse(errornous_pair.0 .0, &mapping), env.parse(errornous_pair.0 .1, &mapping));
|
||||
for (a, b) in pairs {
|
||||
env.unifier.unify(a, b).unwrap();
|
||||
}
|
||||
assert_eq!(env.unify(t1, t2), Err(errornous_pair.1.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recursive_subst() {
|
||||
let mut env = TestEnvironment::new();
|
||||
let int = *env.type_mapping.get("int").unwrap();
|
||||
let foo_id = *env.type_mapping.get("Foo").unwrap();
|
||||
let foo_ty = env.unifier.get_ty(foo_id);
|
||||
let mapping: HashMap<_, _>;
|
||||
with_fields(&mut env.unifier, foo_id, |_unifier, fields| {
|
||||
fields.insert("rec".into(), (foo_id, true));
|
||||
});
|
||||
if let TypeEnum::TObj { params, .. } = &*foo_ty {
|
||||
mapping = params.iter().map(|(id, _)| (*id, int)).collect();
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
let instantiated = env.unifier.subst(foo_id, &mapping).unwrap();
|
||||
let instantiated_ty = env.unifier.get_ty(instantiated);
|
||||
if let TypeEnum::TObj { fields, .. } = &*instantiated_ty {
|
||||
assert!(env.unifier.unioned(fields.get(&"a".into()).unwrap().0, int));
|
||||
assert!(env.unifier.unioned(fields.get(&"rec".into()).unwrap().0, instantiated));
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtual() {
|
||||
let mut env = TestEnvironment::new();
|
||||
let int = env.parse("int", &HashMap::new());
|
||||
let fun = env.unifier.add_ty(TypeEnum::TFunc(FunSignature {
|
||||
args: vec![],
|
||||
ret: int,
|
||||
vars: HashMap::new(),
|
||||
}));
|
||||
let bar = env.unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id: DefinitionId(5),
|
||||
fields: [("f".into(), (fun, false)), ("a".into(), (int, false))]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashMap<StrRef, _>>(),
|
||||
params: HashMap::new(),
|
||||
});
|
||||
let v0 = env.unifier.get_dummy_var().0;
|
||||
let v1 = env.unifier.get_dummy_var().0;
|
||||
|
||||
let a = env.unifier.add_ty(TypeEnum::TVirtual { ty: bar });
|
||||
let b = env.unifier.add_ty(TypeEnum::TVirtual { ty: v0 });
|
||||
let c = env
|
||||
.unifier
|
||||
.add_record([("f".into(), RecordField::new(v1, false, None))].iter().cloned().collect());
|
||||
env.unifier.unify(a, b).unwrap();
|
||||
env.unifier.unify(b, c).unwrap();
|
||||
assert!(env.unifier.eq(v1, fun));
|
||||
|
||||
let d = env
|
||||
.unifier
|
||||
.add_record([("a".into(), RecordField::new(v1, true, None))].iter().cloned().collect());
|
||||
assert_eq!(env.unify(b, d), Err("`virtual[5]::a` field/method does not exist".to_string()));
|
||||
|
||||
let d = env
|
||||
.unifier
|
||||
.add_record([("b".into(), RecordField::new(v1, true, None))].iter().cloned().collect());
|
||||
assert_eq!(env.unify(b, d), Err("`virtual[5]::b` field/method does not exist".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_typevar_range() {
|
||||
let mut env = TestEnvironment::new();
|
||||
let int = env.parse("int", &HashMap::new());
|
||||
let boolean = env.parse("bool", &HashMap::new());
|
||||
let float = env.parse("float", &HashMap::new());
|
||||
let int_list = env.parse("list[int]", &HashMap::new());
|
||||
let float_list = env.parse("list[float]", &HashMap::new());
|
||||
|
||||
// unification between v and int
|
||||
// where v in (int, bool)
|
||||
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
|
||||
env.unifier.unify(int, v).unwrap();
|
||||
|
||||
// unification between v and list[int]
|
||||
// where v in (int, bool)
|
||||
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
|
||||
assert_eq!(
|
||||
env.unify(int_list, v),
|
||||
Err("Expected any one of these types: 0, 2, but got list[0]".to_string())
|
||||
);
|
||||
|
||||
// unification between v and float
|
||||
// where v in (int, bool)
|
||||
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
|
||||
assert_eq!(
|
||||
env.unify(float, v),
|
||||
Err("Expected any one of these types: 0, 2, but got 1".to_string())
|
||||
);
|
||||
|
||||
let v1 = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
|
||||
let v1_list = env.unifier.add_ty(TypeEnum::TList { ty: v1 });
|
||||
let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).0;
|
||||
// unification between v and int
|
||||
// where v in (int, list[v1]), v1 in (int, bool)
|
||||
env.unifier.unify(int, v).unwrap();
|
||||
|
||||
let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).0;
|
||||
// unification between v and list[int]
|
||||
// where v in (int, list[v1]), v1 in (int, bool)
|
||||
env.unifier.unify(int_list, v).unwrap();
|
||||
|
||||
let v = env.unifier.get_fresh_var_with_range(&[int, v1_list], None, None).0;
|
||||
// unification between v and list[float]
|
||||
// where v in (int, list[v1]), v1 in (int, bool)
|
||||
assert_eq!(
|
||||
env.unify(float_list, v),
|
||||
Err("Expected any one of these types: 0, list[var5], but got list[1]\n\nNotes:\n var5 ∈ {0, 2}".to_string())
|
||||
);
|
||||
|
||||
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
|
||||
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
|
||||
env.unifier.unify(a, b).unwrap();
|
||||
env.unifier.unify(a, float).unwrap();
|
||||
|
||||
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
|
||||
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
|
||||
env.unifier.unify(a, b).unwrap();
|
||||
assert_eq!(env.unify(a, int), Err("Expected any one of these types: 1, but got 0".into()));
|
||||
|
||||
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
|
||||
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
|
||||
let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a });
|
||||
let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).0;
|
||||
let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b });
|
||||
let b_list = env.unifier.get_fresh_var_with_range(&[b_list], None, None).0;
|
||||
env.unifier.unify(a_list, b_list).unwrap();
|
||||
let float_list = env.unifier.add_ty(TypeEnum::TList { ty: float });
|
||||
env.unifier.unify(a_list, float_list).unwrap();
|
||||
// previous unifications should not affect a and b
|
||||
env.unifier.unify(a, int).unwrap();
|
||||
|
||||
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
|
||||
let b = env.unifier.get_fresh_var_with_range(&[boolean, float], None, None).0;
|
||||
let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a });
|
||||
let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b });
|
||||
env.unifier.unify(a_list, b_list).unwrap();
|
||||
let int_list = env.unifier.add_ty(TypeEnum::TList { ty: int });
|
||||
assert_eq!(
|
||||
env.unify(a_list, int_list),
|
||||
Err("Expected any one of these types: 1, but got 0".into())
|
||||
);
|
||||
|
||||
let a = env.unifier.get_fresh_var_with_range(&[int, float], None, None).0;
|
||||
let b = env.unifier.get_dummy_var().0;
|
||||
let a_list = env.unifier.add_ty(TypeEnum::TList { ty: a });
|
||||
let a_list = env.unifier.get_fresh_var_with_range(&[a_list], None, None).0;
|
||||
let b_list = env.unifier.add_ty(TypeEnum::TList { ty: b });
|
||||
env.unifier.unify(a_list, b_list).unwrap();
|
||||
assert_eq!(
|
||||
env.unify(b, boolean),
|
||||
Err("Expected any one of these types: 0, 1, but got 2".into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rigid_var() {
|
||||
let mut env = TestEnvironment::new();
|
||||
let a = env.unifier.get_fresh_rigid_var(None, None).0;
|
||||
let b = env.unifier.get_fresh_rigid_var(None, None).0;
|
||||
let x = env.unifier.get_dummy_var().0;
|
||||
let list_a = env.unifier.add_ty(TypeEnum::TList { ty: a });
|
||||
let list_x = env.unifier.add_ty(TypeEnum::TList { ty: x });
|
||||
let int = env.parse("int", &HashMap::new());
|
||||
let list_int = env.parse("list[int]", &HashMap::new());
|
||||
|
||||
assert_eq!(env.unify(a, b), Err("Incompatible types: var3 and var2".to_string()));
|
||||
env.unifier.unify(list_a, list_x).unwrap();
|
||||
assert_eq!(env.unify(list_x, list_int), Err("Incompatible types: 0 and var2".to_string()));
|
||||
|
||||
env.unifier.replace_rigid_var(a, int);
|
||||
env.unifier.unify(list_x, list_int).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instantiation() {
|
||||
let mut env = TestEnvironment::new();
|
||||
let int = env.parse("int", &HashMap::new());
|
||||
let boolean = env.parse("bool", &HashMap::new());
|
||||
let float = env.parse("float", &HashMap::new());
|
||||
let list_int = env.parse("list[int]", &HashMap::new());
|
||||
|
||||
let obj_map: HashMap<_, _> =
|
||||
[(0usize, "int"), (1, "float"), (2, "bool")].iter().cloned().collect();
|
||||
|
||||
let v = env.unifier.get_fresh_var_with_range(&[int, boolean], None, None).0;
|
||||
let list_v = env.unifier.add_ty(TypeEnum::TList { ty: v });
|
||||
let v1 = env.unifier.get_fresh_var_with_range(&[list_v, int], None, None).0;
|
||||
let v2 = env.unifier.get_fresh_var_with_range(&[list_int, float], None, None).0;
|
||||
let t = env.unifier.get_dummy_var().0;
|
||||
let tuple = env.unifier.add_ty(TypeEnum::TTuple { ty: vec![v, v1, v2] });
|
||||
let v3 = env.unifier.get_fresh_var_with_range(&[tuple, t], None, None).0;
|
||||
// t = TypeVar('t')
|
||||
// v = TypeVar('v', int, bool)
|
||||
// v1 = TypeVar('v1', 'list[v]', int)
|
||||
// v2 = TypeVar('v2', 'list[int]', float)
|
||||
// v3 = TypeVar('v3', tuple[v, v1, v2], t)
|
||||
// what values can v3 take?
|
||||
|
||||
let types = env.unifier.get_instantiations(v3).unwrap();
|
||||
let expected_types = indoc! {"
|
||||
tuple[bool, int, float]
|
||||
tuple[bool, int, list[int]]
|
||||
tuple[bool, list[bool], float]
|
||||
tuple[bool, list[bool], list[int]]
|
||||
tuple[bool, list[int], float]
|
||||
tuple[bool, list[int], list[int]]
|
||||
tuple[int, int, float]
|
||||
tuple[int, int, list[int]]
|
||||
tuple[int, list[bool], float]
|
||||
tuple[int, list[bool], list[int]]
|
||||
tuple[int, list[int], float]
|
||||
tuple[int, list[int], list[int]]
|
||||
v5"
|
||||
}
|
||||
.split('\n')
|
||||
.collect_vec();
|
||||
let types = types
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
env.unifier.internal_stringify(
|
||||
*ty,
|
||||
&mut |i| obj_map.get(&i).unwrap().to_string(),
|
||||
&mut |i| format!("v{}", i),
|
||||
&mut None,
|
||||
)
|
||||
})
|
||||
.sorted()
|
||||
.collect_vec();
|
||||
assert_eq!(expected_types, types);
|
||||
}
|
169
nac3core/src/typecheck/unification_table.rs
Normal file
169
nac3core/src/typecheck/unification_table.rs
Normal file
@ -0,0 +1,169 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct UnificationKey(usize);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UnificationTable<V> {
|
||||
parents: Vec<usize>,
|
||||
ranks: Vec<u32>,
|
||||
values: Vec<Option<V>>,
|
||||
log: Vec<Action<V>>,
|
||||
generation: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Action<V> {
|
||||
Parent {
|
||||
key: usize,
|
||||
original_parent: usize,
|
||||
},
|
||||
Value {
|
||||
key: usize,
|
||||
original_value: Option<V>,
|
||||
},
|
||||
Rank {
|
||||
key: usize,
|
||||
original_rank: u32,
|
||||
},
|
||||
Marker {
|
||||
generation: u32,
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for UnificationTable<V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> UnificationTable<V> {
|
||||
pub fn new() -> UnificationTable<V> {
|
||||
UnificationTable { parents: Vec::new(), ranks: Vec::new(), values: Vec::new(), log: Vec::new(), generation: 0 }
|
||||
}
|
||||
|
||||
pub fn new_key(&mut self, v: V) -> UnificationKey {
|
||||
let index = self.parents.len();
|
||||
self.parents.push(index);
|
||||
self.ranks.push(0);
|
||||
self.values.push(Some(v));
|
||||
UnificationKey(index)
|
||||
}
|
||||
|
||||
pub fn unify(&mut self, a: UnificationKey, b: UnificationKey) {
|
||||
let mut a = self.find(a);
|
||||
let mut b = self.find(b);
|
||||
if a == b {
|
||||
return;
|
||||
}
|
||||
if self.ranks[a] < self.ranks[b] {
|
||||
std::mem::swap(&mut a, &mut b);
|
||||
}
|
||||
self.log.push(Action::Parent { key: b, original_parent: self.parents[b] });
|
||||
self.parents[b] = a;
|
||||
if self.ranks[a] == self.ranks[b] {
|
||||
self.log.push(Action::Rank { key: a, original_rank: self.ranks[a] });
|
||||
self.ranks[a] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn probe_value_immutable(&self, key: UnificationKey) -> &V {
|
||||
let mut root = key.0;
|
||||
let mut parent = self.parents[root];
|
||||
while root != parent {
|
||||
root = parent;
|
||||
// parent = root.parent
|
||||
parent = self.parents[parent];
|
||||
}
|
||||
self.values[parent].as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn probe_value(&mut self, a: UnificationKey) -> &V {
|
||||
let index = self.find(a);
|
||||
self.values[index].as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_value(&mut self, a: UnificationKey, v: V) {
|
||||
let index = self.find(a);
|
||||
let original_value = self.values[index].replace(v);
|
||||
self.log.push(Action::Value { key: index, original_value });
|
||||
}
|
||||
|
||||
pub fn unioned(&mut self, a: UnificationKey, b: UnificationKey) -> bool {
|
||||
self.find(a) == self.find(b)
|
||||
}
|
||||
|
||||
pub fn get_representative(&mut self, key: UnificationKey) -> UnificationKey {
|
||||
UnificationKey(self.find(key))
|
||||
}
|
||||
|
||||
fn find(&mut self, key: UnificationKey) -> usize {
|
||||
let mut root = key.0;
|
||||
let mut parent = self.parents[root];
|
||||
while root != parent {
|
||||
// a = parent.parent
|
||||
let a = self.parents[parent];
|
||||
// root.parent = parent.parent
|
||||
self.log.push(Action::Parent { key: root, original_parent: self.parents[root] });
|
||||
self.parents[root] = a;
|
||||
root = parent;
|
||||
// parent = root.parent
|
||||
parent = a;
|
||||
}
|
||||
parent
|
||||
}
|
||||
|
||||
pub fn get_snapshot(&mut self) -> (usize, u32) {
|
||||
let generation = self.generation;
|
||||
self.log.push(Action::Marker { generation });
|
||||
self.generation += 1;
|
||||
(self.log.len(), generation)
|
||||
}
|
||||
|
||||
pub fn restore_snapshot(&mut self, snapshot: (usize, u32)) {
|
||||
let (log_len, generation) = snapshot;
|
||||
assert!(self.log.len() >= log_len, "snapshot restoration error");
|
||||
assert!(matches!(self.log[log_len - 1], Action::Marker { generation: gen } if gen == generation), "snapshot restoration error");
|
||||
for action in self.log.drain(log_len - 1..).rev() {
|
||||
match action {
|
||||
Action::Parent { key, original_parent } => {
|
||||
self.parents[key] = original_parent;
|
||||
}
|
||||
Action::Value { key, original_value } => {
|
||||
self.values[key] = original_value;
|
||||
}
|
||||
Action::Rank { key, original_rank } => {
|
||||
self.ranks[key] = original_rank;
|
||||
}
|
||||
Action::Marker { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn discard_snapshot(&mut self, snapshot: (usize, u32)) {
|
||||
let (log_len, generation) = snapshot;
|
||||
assert!(self.log.len() >= log_len, "snapshot discard error");
|
||||
assert!(matches!(self.log[log_len - 1], Action::Marker { generation: gen } if gen == generation), "snapshot discard error");
|
||||
self.log.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> UnificationTable<Rc<V>>
|
||||
where
|
||||
V: Clone,
|
||||
{
|
||||
pub fn get_send(&self) -> UnificationTable<V> {
|
||||
let values = izip!(self.values.iter(), self.parents.iter())
|
||||
.enumerate()
|
||||
.map(|(i, (v, p))| if *p == i { v.as_ref().map(|v| v.as_ref().clone()) } else { None })
|
||||
.collect();
|
||||
UnificationTable { parents: self.parents.clone(), ranks: self.ranks.clone(), values, log: Vec::new(), generation: 0 }
|
||||
}
|
||||
|
||||
pub fn from_send(table: &UnificationTable<V>) -> UnificationTable<Rc<V>> {
|
||||
let values = table.values.iter().cloned().map(|v| v.map(Rc::new)).collect();
|
||||
UnificationTable { parents: table.parents.clone(), ranks: table.ranks.clone(), values, log: Vec::new(), generation: 0 }
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
|
||||
pub struct PrimitiveId(pub(crate) usize);
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
|
||||
pub struct ClassId(pub(crate) usize);
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
|
||||
pub struct ParamId(pub(crate) usize);
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Hash, Debug)]
|
||||
pub struct VariableId(pub(crate) usize);
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
|
||||
pub enum TypeEnum {
|
||||
BotType,
|
||||
SelfType,
|
||||
PrimitiveType(PrimitiveId),
|
||||
ClassType(ClassId),
|
||||
VirtualClassType(ClassId),
|
||||
ParametricType(ParamId, Vec<Rc<TypeEnum>>),
|
||||
TypeVariable(VariableId),
|
||||
}
|
||||
|
||||
pub type Type = Rc<TypeEnum>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FnDef {
|
||||
// we assume methods first argument to be SelfType,
|
||||
// so the first argument is not contained here
|
||||
pub args: Vec<Type>,
|
||||
pub result: Option<Type>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TypeDef<'a> {
|
||||
pub name: &'a str,
|
||||
pub fields: HashMap<&'a str, Type>,
|
||||
pub methods: HashMap<&'a str, FnDef>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClassDef<'a> {
|
||||
pub base: TypeDef<'a>,
|
||||
pub parents: Vec<ClassId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ParametricDef<'a> {
|
||||
pub base: TypeDef<'a>,
|
||||
pub params: Vec<VariableId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VarDef<'a> {
|
||||
pub name: &'a str,
|
||||
pub bound: Vec<Type>,
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
[package]
|
||||
name = "nac3embedded"
|
||||
version = "0.1.0"
|
||||
authors = ["M-Labs"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "nac3embedded"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.12.4", features = ["extension-module"] }
|
||||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm10-0"] }
|
||||
rustpython-parser = { git = "https://github.com/RustPython/RustPython", branch = "master" }
|
||||
nac3core = { path = "../nac3core" }
|
@ -1,11 +0,0 @@
|
||||
from language import *
|
||||
|
||||
|
||||
class Demo:
|
||||
@kernel
|
||||
def run(self: bool) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Demo().run()
|
@ -1,19 +0,0 @@
|
||||
from functools import wraps
|
||||
|
||||
import nac3embedded
|
||||
|
||||
|
||||
__all__ = ["kernel", "portable"]
|
||||
|
||||
|
||||
def kernel(function):
|
||||
@wraps(function)
|
||||
def run_on_core(self, *args, **kwargs):
|
||||
nac3 = nac3embedded.NAC3()
|
||||
nac3.register_host_object(self)
|
||||
nac3.compile_method(self, function.__name__)
|
||||
return run_on_core
|
||||
|
||||
|
||||
def portable(function):
|
||||
return fn
|
@ -1 +0,0 @@
|
||||
../target/release/libnac3embedded.so
|
@ -1,116 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::exceptions;
|
||||
use rustpython_parser::{ast, parser};
|
||||
use inkwell::context::Context;
|
||||
use inkwell::targets::*;
|
||||
|
||||
use nac3core::CodeGen;
|
||||
|
||||
fn runs_on_core(decorator_list: &[ast::Expression]) -> bool {
|
||||
for decorator in decorator_list.iter() {
|
||||
if let ast::ExpressionType::Identifier { name } = &decorator.node {
|
||||
if name == "kernel" || name == "portable" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[pyclass(name=NAC3)]
|
||||
struct Nac3 {
|
||||
type_definitions: HashMap<i64, ast::Program>,
|
||||
host_objects: HashMap<i64, i64>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl Nac3 {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
Nac3 {
|
||||
type_definitions: HashMap::new(),
|
||||
host_objects: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn register_host_object(&mut self, obj: PyObject) -> PyResult<()> {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
let obj: &PyAny = obj.extract(py)?;
|
||||
let obj_type = obj.get_type();
|
||||
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
let type_id = builtins.call1("id", (obj_type, ))?.extract()?;
|
||||
|
||||
let entry = self.type_definitions.entry(type_id);
|
||||
if let Entry::Vacant(entry) = entry {
|
||||
let source = PyModule::import(py, "inspect")?.call1("getsource", (obj_type, ))?;
|
||||
let ast = parser::parse_program(source.extract()?).map_err(|e|
|
||||
exceptions::PySyntaxError::new_err(format!("failed to parse host object source: {}", e)))?;
|
||||
entry.insert(ast);
|
||||
// TODO: examine AST and recursively register dependencies
|
||||
};
|
||||
|
||||
let obj_id = builtins.call1("id", (obj, ))?.extract()?;
|
||||
match self.host_objects.entry(obj_id) {
|
||||
Entry::Vacant(entry) => entry.insert(type_id),
|
||||
Entry::Occupied(_) => return Err(
|
||||
exceptions::PyValueError::new_err("host object registered twice")),
|
||||
};
|
||||
// TODO: collect other information about host object, e.g. value of fields
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn compile_method(&self, obj: PyObject, name: String) -> PyResult<()> {
|
||||
Python::with_gil(|py| -> PyResult<()> {
|
||||
let obj: &PyAny = obj.extract(py)?;
|
||||
let builtins = PyModule::import(py, "builtins")?;
|
||||
let obj_id = builtins.call1("id", (obj, ))?.extract()?;
|
||||
|
||||
let type_id = self.host_objects.get(&obj_id).ok_or_else(||
|
||||
exceptions::PyKeyError::new_err("type of host object not found"))?;
|
||||
let ast = self.type_definitions.get(&type_id).ok_or_else(||
|
||||
exceptions::PyKeyError::new_err("type definition not found"))?;
|
||||
|
||||
if let ast::StatementType::ClassDef {
|
||||
name: _,
|
||||
body,
|
||||
bases: _,
|
||||
keywords: _,
|
||||
decorator_list: _ } = &ast.statements[0].node {
|
||||
for statement in body.iter() {
|
||||
if let ast::StatementType::FunctionDef {
|
||||
is_async: _,
|
||||
name: funcdef_name,
|
||||
args: _,
|
||||
body: _,
|
||||
decorator_list,
|
||||
returns: _ } = &statement.node {
|
||||
if runs_on_core(decorator_list) && funcdef_name == &name {
|
||||
let context = Context::create();
|
||||
let mut codegen = CodeGen::new(&context);
|
||||
codegen.compile_toplevel(&body[0]).map_err(|e|
|
||||
exceptions::PyRuntimeError::new_err(format!("compilation failed: {}", e)))?;
|
||||
codegen.print_ir();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(exceptions::PyValueError::new_err("expected ClassDef for type definition"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[pymodule]
|
||||
fn nac3embedded(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
Target::initialize_all(&InitializationConfig::default());
|
||||
m.add_class::<Nac3>()?;
|
||||
Ok(())
|
||||
}
|
24
nac3parser/Cargo.toml
Normal file
24
nac3parser/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "nac3parser"
|
||||
version = "0.1.2"
|
||||
description = "Parser for python code."
|
||||
authors = [ "RustPython Team", "M-Labs" ]
|
||||
build = "build.rs"
|
||||
license = "MIT"
|
||||
edition = "2018"
|
||||
|
||||
[build-dependencies]
|
||||
lalrpop = "0.19.6"
|
||||
|
||||
[dependencies]
|
||||
nac3ast = { path = "../nac3ast" }
|
||||
lalrpop-util = "0.19.6"
|
||||
log = "0.4.1"
|
||||
unic-emoji-char = "0.9"
|
||||
unic-ucd-ident = "0.9"
|
||||
unicode_names2 = "0.4"
|
||||
phf = { version = "0.9", features = ["macros"] }
|
||||
ahash = "0.7.2"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "=1.11.0"
|
56
nac3parser/README.md
Normal file
56
nac3parser/README.md
Normal file
@ -0,0 +1,56 @@
|
||||
# nac3parser
|
||||
|
||||
This directory has the code for python lexing, parsing and generating Abstract Syntax Trees (AST).
|
||||
|
||||
This is the RustPython parser with modifications for NAC3.
|
||||
|
||||
The steps are:
|
||||
- Lexical analysis: splits the source code into tokens.
|
||||
- Parsing and generating the AST: transforms those tokens into an AST. Uses `LALRPOP`, a Rust parser generator framework.
|
||||
|
||||
The RustPython team wrote [a blog post](https://rustpython.github.io/2020/04/02/thing-explainer-parser.html) with screenshots and an explanation to help you understand the steps by seeing them in action.
|
||||
|
||||
For more information on LALRPOP, here is a link to the [LALRPOP book](https://github.com/lalrpop/lalrpop).
|
||||
|
||||
There is a readme in the `src` folder with the details of each file.
|
||||
|
||||
|
||||
## Directory content
|
||||
|
||||
`build.rs`: The build script.
|
||||
`Cargo.toml`: The config file.
|
||||
|
||||
The `src` directory has:
|
||||
|
||||
**lib.rs**
|
||||
This is the crate's root.
|
||||
|
||||
**lexer.rs**
|
||||
This module takes care of lexing python source text. This means source code is translated into separate tokens.
|
||||
|
||||
**parser.rs**
|
||||
A python parsing module. Use this module to parse python code into an AST. There are three ways to parse python code. You could parse a whole program, a single statement, or a single expression.
|
||||
|
||||
**ast.rs**
|
||||
Implements abstract syntax tree (AST) nodes for the python language. Roughly equivalent to [the python AST](https://docs.python.org/3/library/ast.html).
|
||||
|
||||
**python.lalrpop**
|
||||
Python grammar.
|
||||
|
||||
**token.rs**
|
||||
Different token definitions. Loosely based on token.h from CPython source.
|
||||
|
||||
**errors.rs**
|
||||
Define internal parse error types. The goal is to provide a matching and a safe error API, masking errors from LALR.
|
||||
|
||||
**fstring.rs**
|
||||
Format strings.
|
||||
|
||||
**function.rs**
|
||||
Collection of functions for parsing parameters, arguments.
|
||||
|
||||
**location.rs**
|
||||
Datatypes to support source location information.
|
||||
|
||||
**mode.rs**
|
||||
Execution mode check. Allowed modes are `exec`, `eval` or `single`.
|
3
nac3parser/build.rs
Normal file
3
nac3parser/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
lalrpop::process_root().unwrap()
|
||||
}
|
85
nac3parser/src/config_comment_helper.rs
Normal file
85
nac3parser/src/config_comment_helper.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use lalrpop_util::ParseError;
|
||||
use nac3ast::*;
|
||||
use crate::ast::Ident;
|
||||
use crate::ast::Location;
|
||||
use crate::token::Tok;
|
||||
use crate::error::*;
|
||||
|
||||
pub fn make_config_comment(
|
||||
com_loc: Location,
|
||||
stmt_loc: Location,
|
||||
nac3com_above: Vec<(Ident, Tok)>,
|
||||
nac3com_end: Option<Ident>
|
||||
) -> Result<Vec<Ident>, ParseError<Location, Tok, LexicalError>> {
|
||||
if com_loc.column() != stmt_loc.column() && !nac3com_above.is_empty() {
|
||||
return Err(ParseError::User {
|
||||
error: LexicalError {
|
||||
location: com_loc,
|
||||
error: LexicalErrorType::OtherError(
|
||||
format!(
|
||||
"config comment at top must have the same indentation with what it applies (comment at {}, statement at {})",
|
||||
com_loc,
|
||||
stmt_loc,
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
};
|
||||
Ok(
|
||||
nac3com_above
|
||||
.into_iter()
|
||||
.map(|(com, _)| com)
|
||||
.chain(nac3com_end.map_or_else(|| vec![].into_iter(), |com| vec![com].into_iter()))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handle_small_stmt<U>(stmts: &mut [Stmt<U>], nac3com_above: Vec<(Ident, Tok)>, nac3com_end: Option<Ident>, com_above_loc: Location) -> Result<(), ParseError<Location, Tok, LexicalError>> {
|
||||
if com_above_loc.column() != stmts[0].location.column() && !nac3com_above.is_empty() {
|
||||
return Err(ParseError::User {
|
||||
error: LexicalError {
|
||||
location: com_above_loc,
|
||||
error: LexicalErrorType::OtherError(
|
||||
format!(
|
||||
"config comment at top must have the same indentation with what it applies (comment at {}, statement at {})",
|
||||
com_above_loc,
|
||||
stmts[0].location,
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
apply_config_comments(
|
||||
&mut stmts[0],
|
||||
nac3com_above
|
||||
.into_iter()
|
||||
.map(|(com, _)| com).collect()
|
||||
);
|
||||
apply_config_comments(
|
||||
stmts.last_mut().unwrap(),
|
||||
nac3com_end.map_or_else(Vec::new, |com| vec![com])
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_config_comments<U>(stmt: &mut Stmt<U>, comments: Vec<Ident>) {
|
||||
match &mut stmt.node {
|
||||
StmtKind::Pass { config_comment, .. }
|
||||
| StmtKind::Delete { config_comment, .. }
|
||||
| StmtKind::Expr { config_comment, .. }
|
||||
| StmtKind::Assign { config_comment, .. }
|
||||
| StmtKind::AugAssign { config_comment, .. }
|
||||
| StmtKind::AnnAssign { config_comment, .. }
|
||||
| StmtKind::Break { config_comment, .. }
|
||||
| StmtKind::Continue { config_comment, .. }
|
||||
| StmtKind::Return { config_comment, .. }
|
||||
| StmtKind::Raise { config_comment, .. }
|
||||
| StmtKind::Import { config_comment, .. }
|
||||
| StmtKind::ImportFrom { config_comment, .. }
|
||||
| StmtKind::Global { config_comment, .. }
|
||||
| StmtKind::Nonlocal { config_comment, .. }
|
||||
| StmtKind::Assert { config_comment, .. } => config_comment.extend(comments),
|
||||
|
||||
_ => { unreachable!("only small statements should call this function") }
|
||||
}
|
||||
}
|
239
nac3parser/src/error.rs
Normal file
239
nac3parser/src/error.rs
Normal file
@ -0,0 +1,239 @@
|
||||
//! Define internal parse error types
|
||||
//! The goal is to provide a matching and a safe error API, maksing errors from LALR
|
||||
use lalrpop_util::ParseError as LalrpopError;
|
||||
|
||||
use crate::ast::Location;
|
||||
use crate::token::Tok;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
/// Represents an error during lexical scanning.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LexicalError {
|
||||
pub error: LexicalErrorType,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum LexicalErrorType {
|
||||
StringError,
|
||||
UnicodeError,
|
||||
NestingError,
|
||||
IndentationError,
|
||||
TabError,
|
||||
TabsAfterSpaces,
|
||||
DefaultArgumentError,
|
||||
PositionalArgumentError,
|
||||
DuplicateKeywordArgumentError,
|
||||
UnrecognizedToken { tok: char },
|
||||
FStringError(FStringErrorType),
|
||||
LineContinuationError,
|
||||
Eof,
|
||||
OtherError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for LexicalErrorType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
LexicalErrorType::StringError => write!(f, "Got unexpected string"),
|
||||
LexicalErrorType::FStringError(error) => write!(f, "Got error in f-string: {}", error),
|
||||
LexicalErrorType::UnicodeError => write!(f, "Got unexpected unicode"),
|
||||
LexicalErrorType::NestingError => write!(f, "Got unexpected nesting"),
|
||||
LexicalErrorType::IndentationError => {
|
||||
write!(f, "unindent does not match any outer indentation level")
|
||||
}
|
||||
LexicalErrorType::TabError => {
|
||||
write!(f, "inconsistent use of tabs and spaces in indentation")
|
||||
}
|
||||
LexicalErrorType::TabsAfterSpaces => {
|
||||
write!(f, "Tabs not allowed as part of indentation after spaces")
|
||||
}
|
||||
LexicalErrorType::DefaultArgumentError => {
|
||||
write!(f, "non-default argument follows default argument")
|
||||
}
|
||||
LexicalErrorType::DuplicateKeywordArgumentError => {
|
||||
write!(f, "keyword argument repeated")
|
||||
}
|
||||
LexicalErrorType::PositionalArgumentError => {
|
||||
write!(f, "positional argument follows keyword argument")
|
||||
}
|
||||
LexicalErrorType::UnrecognizedToken { tok } => {
|
||||
write!(f, "Got unexpected token {}", tok)
|
||||
}
|
||||
LexicalErrorType::LineContinuationError => {
|
||||
write!(f, "unexpected character after line continuation character")
|
||||
}
|
||||
LexicalErrorType::Eof => write!(f, "unexpected EOF while parsing"),
|
||||
LexicalErrorType::OtherError(msg) => write!(f, "{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consolidate these with ParseError
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct FStringError {
|
||||
pub error: FStringErrorType,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FStringErrorType {
|
||||
UnclosedLbrace,
|
||||
UnopenedRbrace,
|
||||
ExpectedRbrace,
|
||||
InvalidExpression(Box<ParseErrorType>),
|
||||
InvalidConversionFlag,
|
||||
EmptyExpression,
|
||||
MismatchedDelimiter,
|
||||
ExpressionNestedTooDeeply,
|
||||
}
|
||||
|
||||
impl fmt::Display for FStringErrorType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
FStringErrorType::UnclosedLbrace => write!(f, "Unclosed '{{'"),
|
||||
FStringErrorType::UnopenedRbrace => write!(f, "Unopened '}}'"),
|
||||
FStringErrorType::ExpectedRbrace => write!(f, "Expected '}}' after conversion flag."),
|
||||
FStringErrorType::InvalidExpression(error) => {
|
||||
write!(f, "Invalid expression: {}", error)
|
||||
}
|
||||
FStringErrorType::InvalidConversionFlag => write!(f, "Invalid conversion flag"),
|
||||
FStringErrorType::EmptyExpression => write!(f, "Empty expression"),
|
||||
FStringErrorType::MismatchedDelimiter => write!(f, "Mismatched delimiter"),
|
||||
FStringErrorType::ExpressionNestedTooDeeply => {
|
||||
write!(f, "expressions nested too deeply")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FStringError> for LalrpopError<Location, Tok, LexicalError> {
|
||||
fn from(err: FStringError) -> Self {
|
||||
lalrpop_util::ParseError::User {
|
||||
error: LexicalError {
|
||||
error: LexicalErrorType::FStringError(err.error),
|
||||
location: err.location,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an error during parsing
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ParseError {
|
||||
pub error: ParseErrorType,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseErrorType {
|
||||
/// Parser encountered an unexpected end of input
|
||||
Eof,
|
||||
/// Parser encountered an extra token
|
||||
ExtraToken(Tok),
|
||||
/// Parser encountered an invalid token
|
||||
InvalidToken,
|
||||
/// Parser encountered an unexpected token
|
||||
UnrecognizedToken(Tok, Option<String>),
|
||||
/// Maps to `User` type from `lalrpop-util`
|
||||
Lexical(LexicalErrorType),
|
||||
}
|
||||
|
||||
/// Convert `lalrpop_util::ParseError` to our internal type
|
||||
impl From<LalrpopError<Location, Tok, LexicalError>> for ParseError {
|
||||
fn from(err: LalrpopError<Location, Tok, LexicalError>) -> Self {
|
||||
match err {
|
||||
// TODO: Are there cases where this isn't an EOF?
|
||||
LalrpopError::InvalidToken { location } => ParseError {
|
||||
error: ParseErrorType::Eof,
|
||||
location,
|
||||
},
|
||||
LalrpopError::ExtraToken { token } => ParseError {
|
||||
error: ParseErrorType::ExtraToken(token.1),
|
||||
location: token.0,
|
||||
},
|
||||
LalrpopError::User { error } => ParseError {
|
||||
error: ParseErrorType::Lexical(error.error),
|
||||
location: error.location,
|
||||
},
|
||||
LalrpopError::UnrecognizedToken { token, expected } => {
|
||||
// Hacky, but it's how CPython does it. See PyParser_AddToken,
|
||||
// in particular "Only one possible expected token" comment.
|
||||
let expected = if expected.len() == 1 {
|
||||
Some(expected[0].clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
ParseError {
|
||||
error: ParseErrorType::UnrecognizedToken(token.1, expected),
|
||||
location: token.0,
|
||||
}
|
||||
}
|
||||
LalrpopError::UnrecognizedEOF { location, .. } => ParseError {
|
||||
error: ParseErrorType::Eof,
|
||||
location,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} at {}", self.error, self.location)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseErrorType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
ParseErrorType::Eof => write!(f, "Got unexpected EOF"),
|
||||
ParseErrorType::ExtraToken(ref tok) => write!(f, "Got extraneous token: {:?}", tok),
|
||||
ParseErrorType::InvalidToken => write!(f, "Got invalid token"),
|
||||
ParseErrorType::UnrecognizedToken(ref tok, ref expected) => {
|
||||
if *tok == Tok::Indent {
|
||||
write!(f, "unexpected indent")
|
||||
} else if expected.as_deref() == Some("Indent") {
|
||||
write!(f, "expected an indented block")
|
||||
} else {
|
||||
write!(f, "Got unexpected token {}", tok)
|
||||
}
|
||||
}
|
||||
ParseErrorType::Lexical(ref error) => write!(f, "{}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseErrorType {}
|
||||
|
||||
impl ParseErrorType {
|
||||
pub fn is_indentation_error(&self) -> bool {
|
||||
match self {
|
||||
ParseErrorType::Lexical(LexicalErrorType::IndentationError) => true,
|
||||
ParseErrorType::UnrecognizedToken(token, expected) => {
|
||||
*token == Tok::Indent || expected.clone() == Some("Indent".to_owned())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_tab_error(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ParseErrorType::Lexical(LexicalErrorType::TabError)
|
||||
| ParseErrorType::Lexical(LexicalErrorType::TabsAfterSpaces)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ParseError {
|
||||
type Target = ParseErrorType;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.error
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
405
nac3parser/src/fstring.rs
Normal file
405
nac3parser/src/fstring.rs
Normal file
@ -0,0 +1,405 @@
|
||||
use std::iter;
|
||||
use std::mem;
|
||||
use std::str;
|
||||
|
||||
use crate::ast::{Constant, ConversionFlag, Expr, ExprKind, Location};
|
||||
use crate::error::{FStringError, FStringErrorType, ParseError};
|
||||
use crate::parser::parse_expression;
|
||||
|
||||
use self::FStringErrorType::*;
|
||||
|
||||
struct FStringParser<'a> {
|
||||
chars: iter::Peekable<str::Chars<'a>>,
|
||||
str_location: Location,
|
||||
}
|
||||
|
||||
impl<'a> FStringParser<'a> {
|
||||
fn new(source: &'a str, str_location: Location) -> Self {
|
||||
Self {
|
||||
chars: source.chars().peekable(),
|
||||
str_location,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn expr(&self, node: ExprKind) -> Expr {
|
||||
Expr::new(self.str_location, node)
|
||||
}
|
||||
|
||||
fn parse_formatted_value(&mut self) -> Result<Vec<Expr>, FStringErrorType> {
|
||||
let mut expression = String::new();
|
||||
let mut spec = None;
|
||||
let mut delims = Vec::new();
|
||||
let mut conversion = None;
|
||||
let mut pred_expression_text = String::new();
|
||||
let mut trailing_seq = String::new();
|
||||
|
||||
while let Some(ch) = self.chars.next() {
|
||||
match ch {
|
||||
// can be integrated better with the remainign code, but as a starting point ok
|
||||
// in general I would do here a tokenizing of the fstrings to omit this peeking.
|
||||
'!' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push_str("!=");
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'=' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push_str("==");
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'>' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push_str(">=");
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'<' if self.chars.peek() == Some(&'=') => {
|
||||
expression.push_str("<=");
|
||||
self.chars.next();
|
||||
}
|
||||
|
||||
'!' if delims.is_empty() && self.chars.peek() != Some(&'=') => {
|
||||
if expression.trim().is_empty() {
|
||||
return Err(EmptyExpression);
|
||||
}
|
||||
|
||||
conversion = Some(match self.chars.next() {
|
||||
Some('s') => ConversionFlag::Str,
|
||||
Some('a') => ConversionFlag::Ascii,
|
||||
Some('r') => ConversionFlag::Repr,
|
||||
Some(_) => {
|
||||
return Err(InvalidConversionFlag);
|
||||
}
|
||||
None => {
|
||||
return Err(ExpectedRbrace);
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(&peek) = self.chars.peek() {
|
||||
if peek != '}' && peek != ':' {
|
||||
return Err(ExpectedRbrace);
|
||||
}
|
||||
} else {
|
||||
return Err(ExpectedRbrace);
|
||||
}
|
||||
}
|
||||
|
||||
// match a python 3.8 self documenting expression
|
||||
// format '{' PYTHON_EXPRESSION '=' FORMAT_SPECIFIER? '}'
|
||||
'=' if self.chars.peek() != Some(&'=') && delims.is_empty() => {
|
||||
pred_expression_text = expression.to_string(); // safe expression before = to print it
|
||||
}
|
||||
|
||||
':' if delims.is_empty() => {
|
||||
let mut nested = false;
|
||||
let mut in_nested = false;
|
||||
let mut spec_expression = String::new();
|
||||
while let Some(&next) = self.chars.peek() {
|
||||
match next {
|
||||
'{' => {
|
||||
if in_nested {
|
||||
return Err(ExpressionNestedTooDeeply);
|
||||
}
|
||||
in_nested = true;
|
||||
nested = true;
|
||||
self.chars.next();
|
||||
continue;
|
||||
}
|
||||
'}' => {
|
||||
if in_nested {
|
||||
in_nested = false;
|
||||
self.chars.next();
|
||||
}
|
||||
break;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
spec_expression.push(next);
|
||||
self.chars.next();
|
||||
}
|
||||
if in_nested {
|
||||
return Err(UnclosedLbrace);
|
||||
}
|
||||
spec = Some(if nested {
|
||||
Box::new(
|
||||
self.expr(ExprKind::FormattedValue {
|
||||
value: Box::new(
|
||||
parse_fstring_expr(&spec_expression)
|
||||
.map_err(|e| InvalidExpression(Box::new(e.error)))?,
|
||||
),
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
Box::new(self.expr(ExprKind::Constant {
|
||||
value: spec_expression.to_owned().into(),
|
||||
kind: None,
|
||||
}))
|
||||
})
|
||||
}
|
||||
'(' | '{' | '[' => {
|
||||
expression.push(ch);
|
||||
delims.push(ch);
|
||||
}
|
||||
')' => {
|
||||
if delims.pop() != Some('(') {
|
||||
return Err(MismatchedDelimiter);
|
||||
}
|
||||
expression.push(ch);
|
||||
}
|
||||
']' => {
|
||||
if delims.pop() != Some('[') {
|
||||
return Err(MismatchedDelimiter);
|
||||
}
|
||||
expression.push(ch);
|
||||
}
|
||||
'}' if !delims.is_empty() => {
|
||||
if delims.pop() != Some('{') {
|
||||
return Err(MismatchedDelimiter);
|
||||
}
|
||||
expression.push(ch);
|
||||
}
|
||||
'}' => {
|
||||
if expression.is_empty() {
|
||||
return Err(EmptyExpression);
|
||||
}
|
||||
let ret = if pred_expression_text.is_empty() {
|
||||
vec![self.expr(ExprKind::FormattedValue {
|
||||
value: Box::new(
|
||||
parse_fstring_expr(&expression)
|
||||
.map_err(|e| InvalidExpression(Box::new(e.error)))?,
|
||||
),
|
||||
conversion,
|
||||
format_spec: spec,
|
||||
})]
|
||||
} else {
|
||||
vec![
|
||||
self.expr(ExprKind::Constant {
|
||||
value: Constant::Str(pred_expression_text + "="),
|
||||
kind: None,
|
||||
}),
|
||||
self.expr(ExprKind::Constant {
|
||||
value: trailing_seq.into(),
|
||||
kind: None,
|
||||
}),
|
||||
self.expr(ExprKind::FormattedValue {
|
||||
value: Box::new(
|
||||
parse_fstring_expr(&expression)
|
||||
.map_err(|e| InvalidExpression(Box::new(e.error)))?,
|
||||
),
|
||||
conversion,
|
||||
format_spec: spec,
|
||||
}),
|
||||
]
|
||||
};
|
||||
return Ok(ret);
|
||||
}
|
||||
'"' | '\'' => {
|
||||
expression.push(ch);
|
||||
for next in &mut self.chars {
|
||||
expression.push(next);
|
||||
if next == ch {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
' ' if !pred_expression_text.is_empty() => {
|
||||
trailing_seq.push(ch);
|
||||
}
|
||||
_ => {
|
||||
expression.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(UnclosedLbrace)
|
||||
}
|
||||
|
||||
fn parse(mut self) -> Result<Expr, FStringErrorType> {
|
||||
let mut content = String::new();
|
||||
let mut values = vec![];
|
||||
|
||||
while let Some(ch) = self.chars.next() {
|
||||
match ch {
|
||||
'{' => {
|
||||
if let Some('{') = self.chars.peek() {
|
||||
self.chars.next();
|
||||
content.push('{');
|
||||
} else {
|
||||
if !content.is_empty() {
|
||||
values.push(self.expr(ExprKind::Constant {
|
||||
value: mem::take(&mut content).into(),
|
||||
kind: None,
|
||||
}));
|
||||
}
|
||||
|
||||
values.extend(self.parse_formatted_value()?);
|
||||
}
|
||||
}
|
||||
'}' => {
|
||||
if let Some('}') = self.chars.peek() {
|
||||
self.chars.next();
|
||||
content.push('}');
|
||||
} else {
|
||||
return Err(UnopenedRbrace);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
content.push(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !content.is_empty() {
|
||||
values.push(self.expr(ExprKind::Constant {
|
||||
value: content.into(),
|
||||
kind: None,
|
||||
}))
|
||||
}
|
||||
|
||||
let s = match values.len() {
|
||||
0 => self.expr(ExprKind::Constant {
|
||||
value: String::new().into(),
|
||||
kind: None,
|
||||
}),
|
||||
1 => values.into_iter().next().unwrap(),
|
||||
_ => self.expr(ExprKind::JoinedStr { values }),
|
||||
};
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_fstring_expr(source: &str) -> Result<Expr, ParseError> {
|
||||
let fstring_body = format!("({})", source);
|
||||
parse_expression(&fstring_body)
|
||||
}
|
||||
|
||||
/// Parse an fstring from a string, located at a certain position in the sourcecode.
|
||||
/// In case of errors, we will get the location and the error returned.
|
||||
pub fn parse_located_fstring(source: &str, location: Location) -> Result<Expr, FStringError> {
|
||||
FStringParser::new(source, location)
|
||||
.parse()
|
||||
.map_err(|error| FStringError { error, location })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn parse_fstring(source: &str) -> Result<Expr, FStringErrorType> {
|
||||
FStringParser::new(source, Location::default()).parse()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring() {
|
||||
let source = "{a}{ b }{{foo}}";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_nested_spec() {
|
||||
let source = "{foo:{spec}}";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_not_nested_spec() {
|
||||
let source = "{foo:spec}";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty_fstring() {
|
||||
insta::assert_debug_snapshot!(parse_fstring("").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fstring_parse_selfdocumenting_base() {
|
||||
let src = "{user=}";
|
||||
let parse_ast = parse_fstring(&src).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fstring_parse_selfdocumenting_base_more() {
|
||||
let src = "mix {user=} with text and {second=}";
|
||||
let parse_ast = parse_fstring(&src).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fstring_parse_selfdocumenting_format() {
|
||||
let src = "{user=:>10}";
|
||||
let parse_ast = parse_fstring(&src).unwrap();
|
||||
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_fstring() {
|
||||
assert_eq!(parse_fstring("{5!a"), Err(ExpectedRbrace));
|
||||
assert_eq!(parse_fstring("{5!a1}"), Err(ExpectedRbrace));
|
||||
assert_eq!(parse_fstring("{5!"), Err(ExpectedRbrace));
|
||||
assert_eq!(parse_fstring("abc{!a 'cat'}"), Err(EmptyExpression));
|
||||
assert_eq!(parse_fstring("{!a"), Err(EmptyExpression));
|
||||
assert_eq!(parse_fstring("{ !a}"), Err(EmptyExpression));
|
||||
|
||||
assert_eq!(parse_fstring("{5!}"), Err(InvalidConversionFlag));
|
||||
assert_eq!(parse_fstring("{5!x}"), Err(InvalidConversionFlag));
|
||||
|
||||
assert_eq!(parse_fstring("{a:{a:{b}}"), Err(ExpressionNestedTooDeeply));
|
||||
|
||||
assert_eq!(parse_fstring("{a:b}}"), Err(UnopenedRbrace));
|
||||
assert_eq!(parse_fstring("}"), Err(UnopenedRbrace));
|
||||
assert_eq!(parse_fstring("{a:{b}"), Err(UnclosedLbrace));
|
||||
assert_eq!(parse_fstring("{"), Err(UnclosedLbrace));
|
||||
|
||||
assert_eq!(parse_fstring("{}"), Err(EmptyExpression));
|
||||
|
||||
// TODO: check for InvalidExpression enum?
|
||||
assert!(parse_fstring("{class}").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_not_equals() {
|
||||
let source = "{1 != 2}";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_equals() {
|
||||
let source = "{42 == 42}";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_selfdoc_prec_space() {
|
||||
let source = "{x =}";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_selfdoc_trailing_space() {
|
||||
let source = "{x= }";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fstring_yield_expr() {
|
||||
let source = "{yield}";
|
||||
let parse_ast = parse_fstring(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
}
|
96
nac3parser/src/function.rs
Normal file
96
nac3parser/src/function.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use ahash::RandomState;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::ast;
|
||||
use crate::error::{LexicalError, LexicalErrorType};
|
||||
|
||||
pub struct ArgumentList {
|
||||
pub args: Vec<ast::Expr>,
|
||||
pub keywords: Vec<ast::Keyword>,
|
||||
}
|
||||
|
||||
type ParameterDefs = (Vec<ast::Arg>, Vec<ast::Arg>, Vec<ast::Expr>);
|
||||
type ParameterDef = (ast::Arg, Option<ast::Expr>);
|
||||
|
||||
pub fn parse_params(
|
||||
params: (Vec<ParameterDef>, Vec<ParameterDef>),
|
||||
) -> Result<ParameterDefs, LexicalError> {
|
||||
let mut posonly = Vec::with_capacity(params.0.len());
|
||||
let mut names = Vec::with_capacity(params.1.len());
|
||||
let mut defaults = vec![];
|
||||
|
||||
let mut try_default = |name: &ast::Arg, default| {
|
||||
if let Some(default) = default {
|
||||
defaults.push(default);
|
||||
} else if !defaults.is_empty() {
|
||||
// Once we have started with defaults, all remaining arguments must
|
||||
// have defaults
|
||||
return Err(LexicalError {
|
||||
error: LexicalErrorType::DefaultArgumentError,
|
||||
location: name.location,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (name, default) in params.0 {
|
||||
try_default(&name, default)?;
|
||||
posonly.push(name);
|
||||
}
|
||||
|
||||
for (name, default) in params.1 {
|
||||
try_default(&name, default)?;
|
||||
names.push(name);
|
||||
}
|
||||
|
||||
Ok((posonly, names, defaults))
|
||||
}
|
||||
|
||||
type FunctionArgument = (Option<(ast::Location, Option<String>)>, ast::Expr);
|
||||
|
||||
pub fn parse_args(func_args: Vec<FunctionArgument>) -> Result<ArgumentList, LexicalError> {
|
||||
let mut args = vec![];
|
||||
let mut keywords = vec![];
|
||||
|
||||
let mut keyword_names = HashSet::with_capacity_and_hasher(func_args.len(), RandomState::new());
|
||||
for (name, value) in func_args {
|
||||
match name {
|
||||
Some((location, name)) => {
|
||||
if let Some(keyword_name) = &name {
|
||||
if keyword_names.contains(keyword_name) {
|
||||
return Err(LexicalError {
|
||||
error: LexicalErrorType::DuplicateKeywordArgumentError,
|
||||
location,
|
||||
});
|
||||
}
|
||||
|
||||
keyword_names.insert(keyword_name.clone());
|
||||
}
|
||||
|
||||
keywords.push(ast::Keyword::new(
|
||||
location,
|
||||
ast::KeywordData {
|
||||
arg: name.map(|name| name.into()),
|
||||
value: Box::new(value),
|
||||
},
|
||||
));
|
||||
}
|
||||
None => {
|
||||
// Allow starred args after keyword arguments.
|
||||
if !keywords.is_empty() && !is_starred(&value) {
|
||||
return Err(LexicalError {
|
||||
error: LexicalErrorType::PositionalArgumentError,
|
||||
location: value.location,
|
||||
});
|
||||
}
|
||||
|
||||
args.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ArgumentList { args, keywords })
|
||||
}
|
||||
|
||||
fn is_starred(exp: &ast::Expr) -> bool {
|
||||
matches!(exp.node, ast::ExprKind::Starred { .. })
|
||||
}
|
1895
nac3parser/src/lexer.rs
Normal file
1895
nac3parser/src/lexer.rs
Normal file
File diff suppressed because it is too large
Load Diff
35
nac3parser/src/lib.rs
Normal file
35
nac3parser/src/lib.rs
Normal file
@ -0,0 +1,35 @@
|
||||
//! This crate can be used to parse python sourcecode into a so
|
||||
//! called AST (abstract syntax tree).
|
||||
//!
|
||||
//! The stages involved in this process are lexical analysis and
|
||||
//! parsing. The lexical analysis splits the sourcecode into
|
||||
//! tokens, and the parsing transforms those tokens into an AST.
|
||||
//!
|
||||
//! For example, one could do this:
|
||||
//!
|
||||
//! ```
|
||||
//! use nac3parser::{parser, ast};
|
||||
//!
|
||||
//! let python_source = "print('Hello world')";
|
||||
//! let python_ast = parser::parse_expression(python_source).unwrap();
|
||||
//!
|
||||
//! ```
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use lalrpop_util::lalrpop_mod;
|
||||
pub use nac3ast as ast;
|
||||
|
||||
pub mod error;
|
||||
mod fstring;
|
||||
mod function;
|
||||
pub mod lexer;
|
||||
pub mod mode;
|
||||
pub mod parser;
|
||||
lalrpop_mod!(
|
||||
#[allow(clippy::all)]
|
||||
#[allow(unused)]
|
||||
python
|
||||
);
|
||||
pub mod token;
|
||||
pub mod config_comment_helper;
|
40
nac3parser/src/mode.rs
Normal file
40
nac3parser/src/mode.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use crate::token::Tok;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Mode {
|
||||
Module,
|
||||
Interactive,
|
||||
Expression,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub(crate) fn to_marker(self) -> Tok {
|
||||
match self {
|
||||
Self::Module => Tok::StartModule,
|
||||
Self::Interactive => Tok::StartInteractive,
|
||||
Self::Expression => Tok::StartExpression,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Mode {
|
||||
type Err = ModeParseError;
|
||||
fn from_str(s: &str) -> Result<Self, ModeParseError> {
|
||||
match s {
|
||||
"exec" | "single" => Ok(Mode::Module),
|
||||
"eval" => Ok(Mode::Expression),
|
||||
_ => Err(ModeParseError { _priv: () }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModeParseError {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ModeParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, r#"mode should be "exec", "eval", or "single""#)
|
||||
}
|
||||
}
|
229
nac3parser/src/parser.rs
Normal file
229
nac3parser/src/parser.rs
Normal file
@ -0,0 +1,229 @@
|
||||
//! Python parsing.
|
||||
//!
|
||||
//! Use this module to parse python code into an AST.
|
||||
//! There are three ways to parse python code. You could
|
||||
//! parse a whole program, a single statement, or a single
|
||||
//! expression.
|
||||
|
||||
use std::iter;
|
||||
|
||||
use crate::ast::{self, FileName};
|
||||
use crate::error::ParseError;
|
||||
use crate::lexer;
|
||||
pub use crate::mode::Mode;
|
||||
use crate::python;
|
||||
|
||||
/*
|
||||
* Parse python code.
|
||||
* Grammar may be inspired by antlr grammar for python:
|
||||
* https://github.com/antlr/grammars-v4/tree/master/python3
|
||||
*/
|
||||
|
||||
/// Parse a full python program, containing usually multiple lines.
|
||||
pub fn parse_program(source: &str, file: FileName) -> Result<ast::Suite, ParseError> {
|
||||
parse(source, Mode::Module, file).map(|top| match top {
|
||||
ast::Mod::Module { body, .. } => body,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses a python expression
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use nac3parser::{parser, ast};
|
||||
/// let expr = parser::parse_expression("1 + 2").unwrap();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// expr,
|
||||
/// ast::Expr {
|
||||
/// location: ast::Location::new(1, 3, Default::default()),
|
||||
/// custom: (),
|
||||
/// node: ast::ExprKind::BinOp {
|
||||
/// left: Box::new(ast::Expr {
|
||||
/// location: ast::Location::new(1, 1, Default::default()),
|
||||
/// custom: (),
|
||||
/// node: ast::ExprKind::Constant {
|
||||
/// value: ast::Constant::Int(1.into()),
|
||||
/// kind: None,
|
||||
/// }
|
||||
/// }),
|
||||
/// op: ast::Operator::Add,
|
||||
/// right: Box::new(ast::Expr {
|
||||
/// location: ast::Location::new(1, 5, Default::default()),
|
||||
/// custom: (),
|
||||
/// node: ast::ExprKind::Constant {
|
||||
/// value: ast::Constant::Int(2.into()),
|
||||
/// kind: None,
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// },
|
||||
/// );
|
||||
///
|
||||
/// ```
|
||||
pub fn parse_expression(source: &str) -> Result<ast::Expr, ParseError> {
|
||||
parse(source, Mode::Expression, Default::default()).map(|top| match top {
|
||||
ast::Mod::Expression { body } => *body,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
// Parse a given source code
|
||||
pub fn parse(source: &str, mode: Mode, file: FileName) -> Result<ast::Mod, ParseError> {
|
||||
let lxr = lexer::make_tokenizer(source, file);
|
||||
let marker_token = (Default::default(), mode.to_marker(), Default::default());
|
||||
let tokenizer = iter::once(Ok(marker_token)).chain(lxr);
|
||||
|
||||
python::TopParser::new()
|
||||
.parse(tokenizer)
|
||||
.map_err(ParseError::from)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty() {
|
||||
let parse_ast = parse_program("", Default::default()).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_print_hello() {
|
||||
let source = String::from("print('Hello world')");
|
||||
let parse_ast = parse_program(&source, Default::default()).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_print_2() {
|
||||
let source = String::from("print('Hello world', 2)");
|
||||
let parse_ast = parse_program(&source, Default::default()).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_kwargs() {
|
||||
let source = String::from("my_func('positional', keyword=2)");
|
||||
let parse_ast = parse_program(&source, Default::default()).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_if_elif_else() {
|
||||
let source = String::from("if 1: 10\nelif 2: 20\nelse: 30");
|
||||
let parse_ast = parse_program(&source, Default::default()).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_lambda() {
|
||||
let source = "lambda x, y: x * y"; // lambda(x, y): x * y";
|
||||
let parse_ast = parse_program(&source, Default::default()).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tuples() {
|
||||
let source = "a, b = 4, 5";
|
||||
|
||||
insta::assert_debug_snapshot!(parse_program(&source, Default::default()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_class() {
|
||||
let source = "\
|
||||
class Foo(A, B):
|
||||
def __init__(self):
|
||||
pass
|
||||
def method_with_default(self, arg='default'):
|
||||
pass";
|
||||
insta::assert_debug_snapshot!(parse_program(&source, Default::default()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_dict_comprehension() {
|
||||
let source = String::from("{x1: x2 for y in z}");
|
||||
let parse_ast = parse_expression(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_list_comprehension() {
|
||||
let source = String::from("[x for y in z]");
|
||||
let parse_ast = parse_expression(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_double_list_comprehension() {
|
||||
let source = String::from("[x for y, y2 in z for a in b if a < 5 if a > 10]");
|
||||
let parse_ast = parse_expression(&source).unwrap();
|
||||
insta::assert_debug_snapshot!(parse_ast);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_more_comment() {
|
||||
let source = "\
|
||||
a: int # nac3: sf1
|
||||
# nac3: sdf4
|
||||
for i in (1, '12'): # nac3: sf2
|
||||
a: int
|
||||
# nac3: 3
|
||||
# nac3: 5
|
||||
while i < 2: # nac3: 4
|
||||
# nac3: real pass
|
||||
pass
|
||||
# nac3: expr1
|
||||
# nac3: expr3
|
||||
1 + 2 # nac3: expr2
|
||||
# nac3: if3
|
||||
# nac3: if1
|
||||
if 1: # nac3: if2
|
||||
3";
|
||||
insta::assert_debug_snapshot!(parse_program(&source, Default::default()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample_comment() {
|
||||
let source = "\
|
||||
# nac3: while1
|
||||
# nac3: while2
|
||||
# normal comment
|
||||
while test: # nac3: while3
|
||||
# nac3: simple assign0
|
||||
a = 3 # nac3: simple assign1
|
||||
";
|
||||
insta::assert_debug_snapshot!(parse_program(&source, Default::default()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_ambiguity() {
|
||||
let source = "\
|
||||
if a: d; # nac3: for d
|
||||
if b: c # nac3: for c
|
||||
if d: # nac3: for if d
|
||||
b; b + 3; # nac3: for b + 3
|
||||
a = 3; a + 3; b = a; # nac3: notif
|
||||
# nac3: smallsingle1
|
||||
# nac3: smallsingle3
|
||||
aa = 3 # nac3: smallsingle2
|
||||
if a: # nac3: small2
|
||||
a
|
||||
for i in a: # nac3: for1
|
||||
pass
|
||||
";
|
||||
insta::assert_debug_snapshot!(parse_program(&source, Default::default()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment_should_fail() {
|
||||
let source = "\
|
||||
if a: # nac3: something
|
||||
a = 3
|
||||
";
|
||||
assert!(parse_program(&source, Default::default()).is_err());
|
||||
}
|
||||
}
|
1377
nac3parser/src/python.lalrpop
Normal file
1377
nac3parser/src/python.lalrpop
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,80 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 327
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: JoinedStr {
|
||||
values: [
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"user=",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "user",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 335
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: JoinedStr {
|
||||
values: [
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"mix ",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"user=",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "user",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
" with text and ",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"second=",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "second",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 343
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: JoinedStr {
|
||||
values: [
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"user=",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "user",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
">10",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 319
|
||||
expression: "parse_fstring(\"\").unwrap()"
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 298
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: JoinedStr {
|
||||
values: [
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "a",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 3,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "b",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"{foo}",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 382
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 5,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Compare {
|
||||
left: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
ops: [
|
||||
Eq,
|
||||
],
|
||||
comparators: [
|
||||
Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 8,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Int(
|
||||
42,
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 306
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "foo",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "spec",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 375
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 4,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Compare {
|
||||
left: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
ops: [
|
||||
NotEq,
|
||||
],
|
||||
comparators: [
|
||||
Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 7,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Int(
|
||||
2,
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 314
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "foo",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"spec",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
---
|
||||
source: nac3parser/src/fstring.rs
|
||||
assertion_line: 389
|
||||
expression: parse_ast
|
||||
|
||||
---
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: JoinedStr {
|
||||
values: [
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"x =",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Constant {
|
||||
value: Str(
|
||||
"",
|
||||
),
|
||||
kind: None,
|
||||
},
|
||||
},
|
||||
Located {
|
||||
location: Location {
|
||||
row: 0,
|
||||
column: 0,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: FormattedValue {
|
||||
value: Located {
|
||||
location: Location {
|
||||
row: 1,
|
||||
column: 2,
|
||||
file: FileName(
|
||||
"unknown",
|
||||
),
|
||||
},
|
||||
custom: (),
|
||||
node: Name {
|
||||
id: "x",
|
||||
ctx: Load,
|
||||
},
|
||||
},
|
||||
conversion: None,
|
||||
format_spec: None,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user