Compare commits
571 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89311688a4 | ||
|
|
a4ddab7bb4 | ||
|
|
4aa0a0f304 | ||
|
|
71a1f0a253 | ||
| bd2172b732 | |||
|
|
e2cb3820cf | ||
|
|
eac7d492d6 | ||
|
|
acf8046114 | ||
|
|
69c4294522 | ||
|
|
7acc9c5ab0 | ||
|
|
f92008046b | ||
| f7b590d823 | |||
|
|
c2cdb86b12 | ||
|
|
7225ef7dc3 | ||
|
|
e24d22a24f | ||
|
|
f3e9f33dd3 | ||
|
|
aa82eb190a | ||
|
|
35479e68a8 | ||
|
|
40c5d10d4b | ||
|
|
68be34ddfe | ||
| ba58259eaf | |||
| e2a61e9506 | |||
| 0395df9944 | |||
| 774f36724d | |||
|
|
1c2186e9fc | ||
| f21828794e | |||
|
|
5e740ebf5f | ||
|
|
d5bb1be894 | ||
|
|
0c39d0a1cb | ||
|
|
dda8cea996 | ||
|
|
2c3bc9acce | ||
|
|
60c49a1d9a | ||
|
|
d92aa7c3a9 | ||
|
|
73f70304ed | ||
|
|
2b9c99fff2 | ||
|
|
4c26410274 | ||
|
|
cc2b1315a0 | ||
|
|
785a77b21b | ||
|
|
cc3315280a | ||
|
|
ad828ee01e | ||
|
|
179c0e00c2 | ||
|
|
c1d574b60f | ||
|
|
6bef8aea76 | ||
|
|
818b8058e4 | ||
|
|
e5144fdf6a | ||
|
|
5c8afd52af | ||
|
|
e87717ac21 | ||
|
|
9459f6192b | ||
|
|
c65a307f4a | ||
|
|
da434f00b6 | ||
|
|
f42b76d5c1 | ||
|
|
33995288cc | ||
|
|
a158f26120 | ||
|
|
515eb172a5 | ||
|
|
e55e332e17 | ||
|
|
2fc272fccf | ||
|
|
7ad4bec332 | ||
|
|
0bbeeda319 | ||
|
|
3db6aa211e | ||
|
|
a0f29f36f5 | ||
|
|
779c50e961 | ||
|
|
7e98950574 | ||
|
|
ea20d1d022 | ||
|
|
2a622a1db3 | ||
|
|
623bcabb84 | ||
|
|
7b9b45e5ac | ||
|
|
4c6ff81c18 | ||
|
|
7f31fc1547 | ||
|
|
a580d5d796 | ||
| 7f4e23fb71 | |||
|
|
7d530772bf | ||
|
|
24a7995819 | ||
|
|
1ba6afef23 | ||
|
|
bfee5ecc92 | ||
|
|
c49e963310 | ||
|
|
6f429571ff | ||
|
|
3ef8ecca66 | ||
|
|
2cedeb9c0d | ||
|
|
12bf91d010 | ||
|
|
49b6264ba4 | ||
|
|
c4873915bb | ||
|
|
66ee370f23 | ||
|
|
34c6fcbd57 | ||
|
|
b6edaa0814 | ||
|
|
33b6414647 | ||
|
|
abb326bffc | ||
|
|
74c6d93151 | ||
|
|
dd09b86cb8 | ||
|
|
f5bae2c231 | ||
|
|
f0d42f996c | ||
|
|
a9522b5c20 | ||
|
|
3039bdbaf7 | ||
|
|
6ea10c3783 | ||
|
|
ac56be4468 | ||
|
|
e0cb31a85b | ||
|
|
ef2a2ab059 | ||
|
|
1c6e179f0a | ||
|
|
3ef9f404b1 | ||
|
|
e38c185042 | ||
|
|
745e79157c | ||
|
|
f6eeb011bf | ||
|
|
95db63b1b8 | ||
|
|
d2cb1f78c4 | ||
|
|
6dc958742b | ||
|
|
0684410631 | ||
|
|
e2367b26c2 | ||
|
|
8019a11826 | ||
|
|
9178e55a20 | ||
|
|
78fa0f703d | ||
|
|
afe3683c2a | ||
|
|
699ef84bef | ||
|
|
75d89e9c2a | ||
|
|
49493fe7d0 | ||
|
|
7a4b9850df | ||
|
|
5940d47ad7 | ||
|
|
736f117fac | ||
|
|
ee4dfc3e4a | ||
|
|
7420fac2bd | ||
|
|
bc5f435ea6 | ||
|
|
06b1cc0cb6 | ||
|
|
953e9336db | ||
|
|
04a94a2dc9 | ||
|
|
ab1f25785b | ||
|
|
940346eecf | ||
|
|
832e9232aa | ||
|
|
2985d13131 | ||
|
|
f30e6d839d | ||
|
|
b27d5fe006 | ||
|
|
98789cf779 | ||
|
|
8ad421b1e1 | ||
|
|
794d44787d | ||
|
|
c69091898a | ||
|
|
89500ec5ef | ||
|
|
1636fa0172 | ||
|
|
a46939b3f8 | ||
|
|
87fa994f27 | ||
|
|
a4b60a5eff | ||
|
|
a9d470d46a | ||
|
|
50cbf25c1e | ||
|
|
0bcd41c873 | ||
|
|
a240e26776 | ||
|
|
81bef17d6f | ||
|
|
fb374c5420 | ||
|
|
87d5ca4cee | ||
|
|
a837577217 | ||
|
|
211a6d358a | ||
|
|
9598890aa4 | ||
|
|
b4adef7a1c | ||
|
|
24961269f2 | ||
|
|
c68b3ab4c1 | ||
| bda19ccdcf | |||
| f06ca0250a | |||
| b184056a9d | |||
| f3df9ce15a | |||
|
|
364141ad0d | ||
|
|
27947f7ca4 | ||
|
|
6bc5cc8766 | ||
|
|
b6bd81a244 | ||
|
|
ce6ca856ee | ||
|
|
6bb5846427 | ||
|
|
548c8c4de1 | ||
|
|
7093e65aed | ||
|
|
76d71cd1e8 | ||
|
|
64e5adcf41 | ||
|
|
4385b4cf75 | ||
|
|
c76711cd02 | ||
|
|
66d618a6fc | ||
| 2c127c5170 | |||
|
|
83b58c1ef6 | ||
|
|
f77f341c3d | ||
|
|
3662bda3a8 | ||
| f2f522645f | |||
|
|
eb7a34115b | ||
|
|
58a15d3da0 | ||
|
|
928bf64d9d | ||
|
|
385657a5c1 | ||
|
|
f6b25c8543 | ||
|
|
f90364697c | ||
|
|
8bf97af763 | ||
|
|
20ed736823 | ||
|
|
a68633f061 | ||
|
|
ac16322860 | ||
| e8b8139b20 | |||
|
|
fce7595126 | ||
|
|
6bb61b294b | ||
|
|
5026576878 | ||
|
|
6aa75b80c5 | ||
|
|
4425823404 | ||
|
|
6e4494e7ba | ||
|
|
cbbccea64a | ||
|
|
547b625a3f | ||
|
|
813a95bc8c | ||
|
|
2c6623e763 | ||
| 8c6b7a96af | |||
|
|
9ca1483f82 | ||
|
|
c78deebcc6 | ||
|
|
9d48bcb868 | ||
|
|
803923b3d2 | ||
|
|
bb77db4c80 | ||
|
|
5f804c71da | ||
|
|
341bbb9a6e | ||
|
|
877b6d669a | ||
|
|
a230c5d77b | ||
|
|
2dacebefae | ||
|
|
1804cbbd98 | ||
|
|
2e9f5ee11e | ||
|
|
6e85c6ae19 | ||
| d10eb4e41e | |||
|
|
194617dd34 | ||
|
|
5d09926d30 | ||
|
|
a8a74ec31f | ||
|
|
7d2831109c | ||
|
|
727ccf3660 | ||
|
|
ce66fc879a | ||
| 5e3ef3f57c | |||
|
|
361c4f7289 | ||
|
|
d59703aa5d | ||
|
|
229c88b58a | ||
|
|
503b692104 | ||
|
|
9bb10ef00d | ||
|
|
097d7dfbe3 | ||
|
|
206a9804dd | ||
|
|
cd419cb12d | ||
|
|
3cd512cefb | ||
|
|
862e6b1e25 | ||
|
|
5c2df7f837 | ||
|
|
b6c7a0482a | ||
|
|
678f1f15ab | ||
|
|
2f4522cc6c | ||
|
|
567c59559f | ||
| 8ff47a3076 | |||
|
|
fbcc1f14d2 | ||
|
|
93061188e8 | ||
|
|
df9b96eefc | ||
|
|
85788fca25 | ||
|
|
d2a4d785f6 | ||
|
|
29012110f8 | ||
|
|
e60a6e246f | ||
| 4089765633 | |||
| 993416d3d8 | |||
|
|
24f1044b8f | ||
|
|
ee90077f90 | ||
|
|
0a87781445 | ||
|
|
da4d21a87b | ||
| 16317a9eb8 | |||
|
|
807732b460 | ||
|
|
5fa787dc64 | ||
|
|
775671442b | ||
|
|
498df4fad8 | ||
|
|
19ba849e4b | ||
|
|
ba52a2df2e | ||
|
|
cb7180fa1c | ||
|
|
c9fa465430 | ||
|
|
9e815d0bea | ||
|
|
5bb9508a76 | ||
| 090d52b8f8 | |||
|
|
95e12d6d19 | ||
|
|
202fd1f0bc | ||
|
|
ef7be1f7e8 | ||
|
|
68b144a2df | ||
|
|
be314a1b79 | ||
|
|
95118a183b | ||
|
|
227a329538 | ||
|
|
e1ae32278f | ||
|
|
335060ce84 | ||
|
|
99d4799883 | ||
|
|
de1f873929 | ||
|
|
b715dbcce3 | ||
|
|
e9348ea58f | ||
|
|
5fad8a79dd | ||
|
|
a0a155c7e6 | ||
|
|
1cdc14a313 | ||
|
|
e4c8268e18 | ||
|
|
1bf47a9ebe | ||
|
|
78786f4ea2 | ||
|
|
116a687099 | ||
|
|
e0c62e40bf | ||
|
|
1a4bade4dd | ||
|
|
b17ffdc6d9 | ||
|
|
30d479565b | ||
|
|
f997254270 | ||
|
|
2882a28621 | ||
|
|
ac83753c56 | ||
|
|
be35cda999 | ||
|
|
161ee28e7b | ||
|
|
c1df3f5ae5 | ||
|
|
8cd0871939 | ||
|
|
ea8d5e6b3a | ||
|
|
87c6490183 | ||
|
|
4a3e06362d | ||
|
|
ed239aabb9 | ||
|
|
854abfed1c | ||
|
|
d445743200 | ||
|
|
a47023fc95 | ||
|
|
16f27cd2ee | ||
|
|
cc8426bf85 | ||
|
|
531d3ca22b | ||
|
|
51ef3682f1 | ||
|
|
0762d72486 | ||
|
|
02ec77b855 | ||
|
|
97605d8c2a | ||
|
|
424b621bae | ||
|
|
48fe20b472 | ||
|
|
1529e591e8 | ||
|
|
19910b77a9 | ||
|
|
f8399612f6 | ||
|
|
cb7734afe6 | ||
|
|
23362b058d | ||
|
|
59e3185500 | ||
|
|
6ad56ed4ee | ||
|
|
8adaa0e053 | ||
|
|
9dab29f5cb | ||
|
|
39d8ed088d | ||
|
|
84d88a7644 | ||
|
|
100986327e | ||
|
|
6924d39dcb | ||
|
|
6fc16c7045 | ||
|
|
e6043effe0 | ||
|
|
f3f8b324dc | ||
|
|
c2d9b4d40f | ||
|
|
ef14fc2219 | ||
|
|
825e71a720 | ||
|
|
77783c1004 | ||
|
|
c1c2ab5124 | ||
|
|
a09a0ce34c | ||
|
|
76c129f91c | ||
|
|
f4e5e1ea3d | ||
|
|
bca9de4106 | ||
|
|
1b50c8f879 | ||
|
|
3c67cf576c | ||
|
|
79f51ce4c6 | ||
|
|
3e89953d07 | ||
|
|
08f1027834 | ||
|
|
118e7672bd | ||
|
|
4a2671c601 | ||
|
|
ac433f07ec | ||
|
|
3d1bef3a32 | ||
|
|
eafc834ef3 | ||
|
|
8cfb4f3e6d | ||
|
|
cce8c756fc | ||
|
|
66f0e2d8d9 | ||
|
|
5fe6e32f92 | ||
|
|
7db231a945 | ||
|
|
ec3907d841 | ||
|
|
b6a920af58 | ||
|
|
ff9c275980 | ||
|
|
04e5ead3db | ||
|
|
0d79482568 | ||
|
|
dc6a36b548 | ||
|
|
57d1ab9b2a | ||
|
|
50ec220d08 | ||
|
|
187ed48c4f | ||
| 031f7008fa | |||
|
|
d2ecd7a266 | ||
|
|
6bfc073608 | ||
|
|
db2b7e5f90 | ||
|
|
3e9f3553fe | ||
|
|
7c29d552f2 | ||
|
|
e3f5ffa5c5 | ||
|
|
554fc1f7a7 | ||
|
|
b95df4c23c | ||
|
|
8340dbe7c2 | ||
|
|
0d20f1cbc0 | ||
|
|
af79419809 | ||
|
|
0c1382896e | ||
|
|
1e827a9ea1 | ||
|
|
06a183c7af | ||
|
|
9b80c29a15 | ||
|
|
044e73efda | ||
|
|
8107f82531 | ||
|
|
e42c292433 | ||
|
|
c71cc43a47 | ||
|
|
0d4a86ef02 | ||
|
|
82c05039b7 | ||
|
|
86df537ece | ||
|
|
f057f3b4a4 | ||
|
|
85703f6059 | ||
|
|
fe85582088 | ||
|
|
5940e540b3 | ||
|
|
3c7565861d | ||
|
|
cd6ed93b98 | ||
|
|
56ae34c297 | ||
|
|
57ef5933ee | ||
|
|
f35f3e0df0 | ||
|
|
773e718592 | ||
|
|
41c9f57063 | ||
|
|
d9c6bd644b | ||
|
|
954f6c877e | ||
|
|
05e75cb746 | ||
|
|
3a4a5683b5 | ||
|
|
f994c4dc08 | ||
| 15541b8675 | |||
|
|
805a9a110f | ||
|
|
91d4b92e6b | ||
|
|
021a289623 | ||
|
|
5085ea8502 | ||
|
|
b13130db8e | ||
|
|
21fd6a4130 | ||
|
|
706e2e0286 | ||
|
|
6d28d52cdf | ||
|
|
fc5dda739e | ||
|
|
8bf3938842 | ||
|
|
43130fd0da | ||
|
|
cc5ed3c2ee | ||
|
|
b1799c25e1 | ||
|
|
1f653be8cc | ||
|
|
43aae56b37 | ||
|
|
f9d53d7ab7 | ||
|
|
fbaec1efdb | ||
|
|
10fb780e50 | ||
|
|
6de2a9d37d | ||
|
|
8606c761e8 | ||
|
|
b56dceb3e1 | ||
|
|
7318bfbc88 | ||
| ca6be75cf0 | |||
|
|
9ac9af8bcb | ||
|
|
ed350f94b8 | ||
|
|
709ec56873 | ||
|
|
6b9c165d14 | ||
|
|
b0c62642f5 | ||
|
|
4c66ad4b44 | ||
|
|
35dbbebb8b | ||
|
|
098248da19 | ||
|
|
a80cd600b5 | ||
|
|
ea32fcc7d5 | ||
|
|
d28bfbac16 | ||
|
|
34b9812db4 | ||
|
|
85a582e349 | ||
|
|
8e458111b5 | ||
|
|
f6604dcecd | ||
|
|
67a0157cbd | ||
|
|
2a927b209f | ||
|
|
f1d48d74a2 | ||
|
|
5b5c66d409 | ||
|
|
699cd88000 | ||
|
|
54ac39ee01 | ||
| a0d31fad38 | |||
|
|
b20dbcebbb | ||
|
|
bb59f8de4a | ||
|
|
4b28d40b89 | ||
|
|
9390eb6b2e | ||
| 70c45f6fe1 | |||
| 7757133d43 | |||
| 7d926be83a | |||
|
|
d4f81b0667 | ||
|
|
3cca2dd130 | ||
|
|
eeae7ac1ed | ||
|
|
af73d8e251 | ||
|
|
308efbbd5e | ||
|
|
ae60aa173e | ||
|
|
6bc59ca0d1 | ||
|
|
7572e8bf08 | ||
| da7807d653 | |||
| b7faa727af | |||
| 4a28ee61c0 | |||
| 62a83f5544 | |||
| c9eccdd86f | |||
| e796f055ad | |||
| 895e874e2f | |||
| 5cd1be88ce | |||
|
|
20db37b704 | ||
| 5dc9331668 | |||
| 35ecdd7a8b | |||
|
|
ea934445c6 | ||
|
|
139a66f239 | ||
|
|
8c64c07fff | ||
|
|
019a8732cb | ||
|
|
8d1d87dbb8 | ||
|
|
1176da7cb9 | ||
| 988649098c | |||
| e470fb1f5c | |||
|
|
2353ecd0e1 | ||
|
|
e32d349e2e | ||
|
|
8bc00720ac | ||
|
|
058a985d92 | ||
|
|
f7a6e905f6 | ||
|
|
6074b414b5 | ||
| 588800e02c | |||
| 5205f01877 | |||
| b0c3839ed9 | |||
| a7fe9a592b | |||
| ab8d616e2b | |||
| b1cd82d4ed | |||
| c0997b3e47 | |||
| fa69bd2294 | |||
| 5f30623e8f | |||
| 65ec3c4ad0 | |||
| 3b6aa7fffb | |||
| 55a86ad64c | |||
| 5539f8722f | |||
| 67eb74c66e | |||
|
|
9ccb1490bf | ||
|
|
ccd3ddd82a | ||
| af4827590e | |||
| 1c5a58aa23 | |||
| f87a4a4b78 | |||
| c24113ba51 | |||
| 78d4276a57 | |||
| a58b058025 | |||
| 593683156a | |||
| b96d9c6c4c | |||
| 0b4aef1530 | |||
| c5a61e15f6 | |||
| 37819645cd | |||
| 0afccbe6b1 | |||
|
|
ced43a161d | ||
|
|
c08f5d7706 | ||
|
|
9808fc0e7c | ||
| 905522af56 | |||
| fddccf2967 | |||
| aa107072d8 | |||
| 3a751666ba | |||
| f08a09cee2 | |||
| ae9bace68a | |||
| 36e70c0c97 | |||
| 0a54cbdd63 | |||
| 8258c4f400 | |||
| 94bf21536d | |||
| 15fece8154 | |||
| a2d4252428 | |||
| 0efc4d209e | |||
| 0331b486bf | |||
| ff9fdfeab0 | |||
|
|
05e298583e | ||
|
|
652a6c313b | ||
|
|
729af04963 | ||
| 13b18c0ce0 | |||
| 3f96704723 | |||
| 5e4d2a32e4 | |||
| 9706e844d6 | |||
| 162487490e | |||
| b6a7b237ba | |||
| 58513e7d15 | |||
| 1a09e9ba2a | |||
| b839517e9a | |||
| 45574d3cd5 | |||
| 8ce12b8a71 | |||
| 7b0d2d50bd | |||
| 65a32c3cdb | |||
| e47096c219 | |||
| b884978b87 | |||
| 9e85f869d5 | |||
| 7df9517788 | |||
| 8cb7e2d8b0 | |||
| 8a5d08d7fc | |||
| 24ae32c95a | |||
| 7d8a1cd714 | |||
| 61a04d51a4 | |||
| fb5d17742e | |||
| 2a5ffb12a1 | |||
| bec91323b9 | |||
| 5a48415bd5 | |||
| 4448d3ccb4 | |||
| 5d4977828e | |||
| 84f76f8ba4 | |||
| 13d281388a | |||
| bed87812d5 | |||
| 7c0e96777b | |||
| 87dcc18a8d | |||
| c375c18922 | |||
| f65f9e042c | |||
| 6ee91bee08 | |||
| 416c6b1d59 | |||
| 90796775d3 | |||
| 8cc9b88126 | |||
| 4b2158b1f0 | |||
| fcf4eba79e | |||
| feb0da1172 | |||
| 47fb051caa | |||
| 0113536b0d | |||
|
|
160cbd16ad |
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
charset = utf-8
|
||||
|
||||
[*.md]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
674
LICENSE
Normal file
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
119
README.md
Normal file
119
README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# FSPL
|
||||
|
||||
<img src="assets/fspl.svg" width="128" alt="FSPL logo.">
|
||||
|
||||
Freestanding programming language: a high-ish-level language that has absolutely
|
||||
no need for any runtime support, designed to work well in scenarios where
|
||||
dragging along a language runtime is either not possible or simply unwanted.
|
||||
|
||||
This language is designed for:
|
||||
- Operating system development
|
||||
- Embedded software
|
||||
- Integrating cleanly into existing software
|
||||
|
||||
## Design Principles
|
||||
- Abstractions must happen at compile time unless absolutely necessary
|
||||
- Compiler must not generate any functions that the user does not write
|
||||
- Compiler must avoid generating logic that the user does not write
|
||||
|
||||
## Installation
|
||||
You can install the compiler by running:
|
||||
|
||||
`go install ./cmd/fsplc`
|
||||
|
||||
The `fsplc` program depends on the LLVM IR compiler (`llc`). If it is not found,
|
||||
it will attempt to use `clang` instead but with some features disabled. Please
|
||||
ensure either `llc` or `clang` are installed and accessible from your PATH
|
||||
before using this software.
|
||||
|
||||
## Usage
|
||||
The `fsplc` program may be used as follows:
|
||||
|
||||
`fsplc [ARGUMENT(S)...] FILE(S)...`
|
||||
|
||||
The program compiles all input files into one output. The output file type is
|
||||
determined by the filename extension of the output file:
|
||||
|
||||
| Extension | Type |
|
||||
| --------- | --------------- |
|
||||
| .s | Native assembly |
|
||||
| .o | Object file |
|
||||
| .ll | LLVM IR |
|
||||
|
||||
If no output file is specified, it will default to an object file with the name
|
||||
of the first input file.
|
||||
|
||||
Object files can be linked into an executable binary using the linker of your
|
||||
choice, or by using a C compiler such as `clang`:
|
||||
|
||||
`clang -o OUTPUT INPUT.o`
|
||||
|
||||
Using a C compiler will link the C standard library to your program, which may
|
||||
be useful for building normal user applications.
|
||||
|
||||
## Learning the language
|
||||
|
||||
At this time, there is no guided method of learning how to write FSPL code.
|
||||
However, a good place to start is the `design` directory, which contains a
|
||||
language specification among other things. The language specification goes into
|
||||
detail about the syntax and semantics of the language, and assuming some
|
||||
background in C programming, it should be enough to attain a reasonable grasp
|
||||
of the language.
|
||||
|
||||
## Caveats, Bugs
|
||||
Note that the compiler is still relatively early in development, and has
|
||||
numerous bugs. In addition, language features and syntax are not yet set in
|
||||
stone and may change in the future. Please report any bugs you find to the
|
||||
[issue tracker](https://git.tebibyte.media/sashakoshka/fspl/issues).
|
||||
|
||||
## Roadmap
|
||||
|
||||
Late 2023 (These have been implemented):
|
||||
- Top-level entities
|
||||
- Type definitions
|
||||
- Methods
|
||||
- Defined and external functions
|
||||
- Type system
|
||||
- Strict, static, bottom-up type inference
|
||||
- Pointers
|
||||
- Arrays
|
||||
- Slices
|
||||
- Structs
|
||||
- Interfaces
|
||||
- Expressions and control structures
|
||||
- Literals adapt to types via bottom-up type inference
|
||||
- Assignment
|
||||
- Variable declaration
|
||||
- Variable access
|
||||
- Function calls
|
||||
- Method calls
|
||||
- Interface behavior calls
|
||||
- Operations
|
||||
- Casting
|
||||
- Blocks
|
||||
- If/else
|
||||
- Loops
|
||||
|
||||
Q1 2024:
|
||||
- Union types (carry type information)
|
||||
- Match statements
|
||||
- Modules
|
||||
- Mutable/immutable variables
|
||||
- For/range loops
|
||||
|
||||
Q2 2024:
|
||||
- Basic, non-final standard library routines
|
||||
- Conditional compilation
|
||||
- Shared library compilation
|
||||
- Constants
|
||||
- Vararg
|
||||
- FSPL vararg using Slices
|
||||
- Optional per-function C-style vararg for compatibility
|
||||
|
||||
Q3 2024:
|
||||
- Generics
|
||||
- Ownership system
|
||||
- Lightweight, modularized (and of course, totally optional) standard library to replace those written in Q2
|
||||
|
||||
At the beginning of Q4 2024, a 1.0 version of the language will be released.
|
||||
|
||||
89
analyzer/README.md
Normal file
89
analyzer/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# analyzer
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Define syntax tree type that contains entities
|
||||
- Turn streams of tokens into abstract syntax tree entities
|
||||
|
||||
## Organization
|
||||
|
||||
The entry point for all logic defined in this package is the Tree type. On this
|
||||
type, the Analyze() method is defined. This method checks the semantic
|
||||
correctness of an AST, fills in semantic fields within its data structures, and
|
||||
arranges them into the Tree.
|
||||
|
||||
Tree contains a scopeContextManager. The job of scopeContextManager is to manage
|
||||
a stack of scopeContexts, which are each tied to a function or method that is
|
||||
currently being analyzed. In turn, each scopeContext manages stacks of
|
||||
entity.Scopes and entity.Loops. This allows for greedy/recursive analysis of
|
||||
functions and methods.
|
||||
|
||||
## Operation
|
||||
|
||||
When the analyze method is called, several hidden fields in the Tree are filled
|
||||
out. Tree.ensure() instantiates data that can persist between analyses, which
|
||||
consists of map initialization and merging the data in the builtinTypes map into
|
||||
Tree.Types.
|
||||
|
||||
After Tree.ensure completes, Tree.assembleRawMaps() takes top-level entities
|
||||
from the AST and organizes them into rawTypes, rawFunctions, and rawMethods. It
|
||||
does this so that top-level entites can be indexed by name. While doing this, it
|
||||
ensures that function and type names are unique, and method names are unique
|
||||
within the type they are defined on.
|
||||
|
||||
Next, Tree.analyzeDeclarations() is called. This is the entry point for the
|
||||
actual analysis logic. For each item in the raw top-level entity maps, it calls
|
||||
a specific analysis routine, which is one of:
|
||||
|
||||
- Tree.analyzeTypedef()
|
||||
- Tree.analyzeFunction()
|
||||
- Tree.analyzeMethod()
|
||||
|
||||
These routines all have two crucial properties that make them very useful:
|
||||
|
||||
- They refer to top-level entities by name instead of by memory location
|
||||
- If the entity has already been analyzed, they return that entity instead of
|
||||
analyzing it again
|
||||
|
||||
Because of this, they are also used as accessors for top level entities within
|
||||
more specific analysis routines. For example, the routine Tree.analyzeCall()
|
||||
will call Tree.analyzeFunction() in order to get information about the function
|
||||
that is being called. If the function has not yet been analyzed, it is analyzed
|
||||
(making use of scopeContextManager to push a new scopeContext), and other
|
||||
routines (including Tree.analyzeDeclarations()) will not have to analyze it all
|
||||
over agian. After a top-level entity has been analyzed, these routines will
|
||||
always return the same pointer to the one instance of the analyzed entity.
|
||||
|
||||
## Expression Analysis and Assignment
|
||||
|
||||
Since expressions make up the bulk of FSPL, expression analysis makes up the
|
||||
bulk of the semantic analyzer. Whenever an expression needs to be analyzed,
|
||||
Tree.analyzeExpression() is called. This activates a switch to call one of many
|
||||
specialized analysis routines based on the expression entity's concrete type.
|
||||
|
||||
Much of expression analysis consists of the analyze checking to see if the
|
||||
result of one expression can be assigned to the input of another. To this end,
|
||||
assignment rules are used. There are five different assignment modes:
|
||||
|
||||
- Strict: Structural equivalence, but named types are treated as opaque and are
|
||||
not tested. This applies to the root of the type, and to types enclosed as
|
||||
members, elements, etc. This is the assignment mode most often used.
|
||||
- Weak: Like strict, but the root types specifically are compared as if they
|
||||
were not named. analyzer.ReduceToBase() is used to accomplish this.
|
||||
- Structural: Full structural equivalence, and named types are always reduced.
|
||||
- Coerce: Data of the source type must be convert-able to the destination type.
|
||||
This is used in value casts.
|
||||
- Force: All assignment rules are ignored. This is only used in bit casts.
|
||||
|
||||
|
||||
All expression analysis routines take in as a parameter the type that the result
|
||||
expression is being assigned to, and the assignment mode. To figure out whether
|
||||
or not they can be assigned, they in turn (usually) call Tree.canAssign().
|
||||
Tree.canAssign() is used to determine whether data of a source type can be
|
||||
assigned to a destination type, given an assignment mode. However, it is not
|
||||
called automatically by Tree.analyzeExpression() because:
|
||||
|
||||
- Determining the source type is sometimes non-trivial (see
|
||||
Tree.analyzeOperation())
|
||||
- Literals have their own very weak assignment rules, and are designed to be
|
||||
assignable to a wide range of data types
|
||||
@@ -1,320 +1,500 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
import "fmt"
|
||||
import "math"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/integer"
|
||||
|
||||
func TestAssignLiteralErrUnsignedNegative (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected unsigned integer", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:UInt = -5
|
||||
}
|
||||
`)
|
||||
type strictness int; const (
|
||||
// Structural equivalence, but named types are treated as opaque and are
|
||||
// not tested. This applies to the root of the type, and to types
|
||||
// enclosed as members, elements, etc. This is the assignment mode most
|
||||
// often used.
|
||||
strict strictness = iota
|
||||
// Like strict, but the root types specifically are compared as if they
|
||||
// were not named. analyzer.ReduceToBase() is used to accomplish this.
|
||||
// Assignment to private/restricted types is not allowed.
|
||||
weak
|
||||
// Full structural equivalence, and named types are always reduced.
|
||||
// Assignment to private/restricted types is not allowed.
|
||||
structural
|
||||
// Data of the source type must be convert-able to the destination type.
|
||||
// This is used in value casts. Assignment to private/restricted types
|
||||
// is not allowed.
|
||||
coerce
|
||||
// All assignment rules are ignored. This is only used in bit casts.
|
||||
force
|
||||
)
|
||||
|
||||
// canAssign takes in an analyzed destination type and an analyzed source type,
|
||||
// and determines whether source can be assigned to destination.
|
||||
func (this *Tree) canAssign (
|
||||
pos errors.Position,
|
||||
// assuming both are analyzed already
|
||||
destination entity.Type,
|
||||
mode strictness,
|
||||
source entity.Type,
|
||||
) error {
|
||||
fail := func () error {
|
||||
switch mode {
|
||||
case strict:
|
||||
return errors.Errorf (
|
||||
pos, "expected %v",
|
||||
entity.FormatType(destination))
|
||||
case weak:
|
||||
return errors.Errorf (
|
||||
pos, "cannot use %v as %v",
|
||||
entity.FormatType(source),
|
||||
entity.FormatType(destination))
|
||||
case structural:
|
||||
return errors.Errorf (
|
||||
pos, "cannot convert from %v to %v",
|
||||
entity.FormatType(source),
|
||||
entity.FormatType(destination))
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: analyzer doesnt know about mode", mode))
|
||||
}
|
||||
}
|
||||
|
||||
// check access permissions
|
||||
if destination != nil && source != nil &&
|
||||
mode != strict && mode != force {
|
||||
|
||||
err := this.typeRestricted(pos, destination)
|
||||
if err != nil { return err }
|
||||
err = this.typeRestricted(pos, source)
|
||||
if err != nil { return err }
|
||||
}
|
||||
|
||||
// short circuit if force is selected
|
||||
if mode == force { return nil }
|
||||
|
||||
// check for coerce strictness
|
||||
if mode == coerce {
|
||||
return this.canAssignCoerce(pos, destination, source)
|
||||
}
|
||||
|
||||
// do structural equivalence if structural is selected
|
||||
if mode == structural {
|
||||
if this.areStructurallyEquivalent(destination, source) {
|
||||
return nil
|
||||
} else {
|
||||
return fail()
|
||||
}
|
||||
}
|
||||
|
||||
// first, check if this is interface assignment
|
||||
if destination, ok := this.isInterface(destination); ok {
|
||||
return this.canAssignInterface(pos, destination, source)
|
||||
}
|
||||
|
||||
// then, check for array to slice assignment
|
||||
if destination, ok := ReduceToBase(destination).(*entity.TypeSlice); ok {
|
||||
if source, ok := ReduceToBase(source). (*entity.TypeArray); ok {
|
||||
return this.canAssignSliceArray(pos, destination, source)
|
||||
}}
|
||||
|
||||
// if weak, only compare the first base types
|
||||
if mode == weak {
|
||||
destination = ReduceToBase(destination)
|
||||
source = ReduceToBase(source)
|
||||
}
|
||||
|
||||
switch destination := destination.(type) {
|
||||
// no type
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
// named type
|
||||
case *entity.TypeNamed:
|
||||
if source, ok := source.(*entity.TypeNamed); ok {
|
||||
if destination.Name == source.Name {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// composite base types
|
||||
case *entity.TypePointer:
|
||||
if source, ok := source.(*entity.TypePointer); ok {
|
||||
mode := mode
|
||||
if mode > structural { mode = structural }
|
||||
return this.canAssign (
|
||||
pos, destination.Referenced,
|
||||
mode, source.Referenced)
|
||||
}
|
||||
|
||||
case *entity.TypeSlice:
|
||||
if source, ok := source.(*entity.TypeSlice); ok {
|
||||
mode := mode
|
||||
if mode > structural { mode = structural }
|
||||
return this.canAssign (
|
||||
pos, destination.Element,
|
||||
mode, source.Element)
|
||||
}
|
||||
|
||||
case *entity.TypeArray:
|
||||
if source, ok := source.(*entity.TypeArray); ok {
|
||||
mode := mode
|
||||
if mode > structural { mode = structural }
|
||||
return this.canAssign (
|
||||
pos, destination.Element,
|
||||
mode, source.Element)
|
||||
}
|
||||
|
||||
// other base type
|
||||
case *entity.TypeStruct,
|
||||
*entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
|
||||
if destination.Equals(source) {
|
||||
return nil
|
||||
}
|
||||
|
||||
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", destination))
|
||||
}
|
||||
|
||||
return fail()
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = 5.5
|
||||
}
|
||||
`)
|
||||
// canAssignCoerce determines if data of an analyzed source type can be
|
||||
// converted into data of an analyzed destination type.
|
||||
func (this *Tree) canAssignCoerce (
|
||||
pos errors.Position,
|
||||
destination entity.Type,
|
||||
source entity.Type,
|
||||
) error {
|
||||
fail := func () error {
|
||||
return errors.Errorf (
|
||||
pos, "cannot convert from %v to %v",
|
||||
entity.FormatType(source),
|
||||
entity.FormatType(destination))
|
||||
}
|
||||
|
||||
destination = ReduceToBase(destination)
|
||||
source = ReduceToBase(source)
|
||||
|
||||
switch destination := destination.(type) {
|
||||
// no type
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
// base type
|
||||
case *entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
switch source.(type) {
|
||||
case *entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
// integers, floats, and words may all be assigned to
|
||||
// eachother
|
||||
return nil
|
||||
default:
|
||||
return fail()
|
||||
}
|
||||
|
||||
case *entity.TypePointer:
|
||||
switch source := source.(type) {
|
||||
case *entity.TypeSlice:
|
||||
// a slice may be assigned to a pointer if its element
|
||||
// type can be assigned (structural) to the pointer's
|
||||
// referenced type
|
||||
err := this.canAssign (
|
||||
pos, destination.Referenced,
|
||||
structural, source.Element)
|
||||
if err != nil {
|
||||
return fail()
|
||||
}
|
||||
default:
|
||||
// a pointer may be assigned to another pointer if they
|
||||
// are structurally equivalent
|
||||
if !this.areStructurallyEquivalent(destination, source) {
|
||||
return fail()
|
||||
}
|
||||
}
|
||||
|
||||
case *entity.TypeSlice,
|
||||
*entity.TypeArray,
|
||||
*entity.TypeStruct:
|
||||
// composite types may be assigned to eachother if they are
|
||||
// structurally equivalent
|
||||
if !this.areStructurallyEquivalent(destination, source) {
|
||||
return fail()
|
||||
}
|
||||
|
||||
default: fail()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (x: 5)
|
||||
}
|
||||
`)
|
||||
// canAssignSliceArray takes in an analyzed slice type and an analyzed array
|
||||
// type, and determines whether the array can be assigned to the slice.
|
||||
func (this *Tree) canAssignSliceArray (
|
||||
pos errors.Position,
|
||||
destination *entity.TypeSlice,
|
||||
source *entity.TypeArray,
|
||||
) error {
|
||||
err := this.canAssign(pos, destination.Element, strict, source.Element)
|
||||
if err != nil {
|
||||
return errors.Errorf (
|
||||
pos, "expected %v",
|
||||
entity.FormatType(destination))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (* 5)
|
||||
}
|
||||
`)
|
||||
// canAssignInterface takes in an analyzed interface destination type and an
|
||||
// analyzed source type, and determines whether source can be assigned to
|
||||
// destination.
|
||||
func (this *Tree) canAssignInterface (
|
||||
pos errors.Position,
|
||||
destination *entity.TypeInterface,
|
||||
source entity.Type,
|
||||
) error {
|
||||
for name, behavior := range destination.BehaviorMap {
|
||||
// get method
|
||||
method, err := this.analyzeMethodOrBehavior (
|
||||
pos, source, name)
|
||||
if err != nil { return err }
|
||||
|
||||
// extract signature
|
||||
var signature *entity.Signature
|
||||
switch method.(type) {
|
||||
case *entity.Signature:
|
||||
signature = method.(*entity.Signature)
|
||||
case *entity.Method:
|
||||
signature = method.(*entity.Method).Signature
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"Tree.analyzeMethodOrBehavior returned",
|
||||
method))
|
||||
}
|
||||
|
||||
// check equivalence
|
||||
if !signature.Equals(behavior) {
|
||||
return errors.Errorf (
|
||||
pos, "%v has wrong signature for method %v",
|
||||
source, name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerOverflow (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected smaller number", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:Byte = 1000
|
||||
}
|
||||
`)
|
||||
// areStructurallyEquivalent tests whether two types are structurally equivalent
|
||||
// to eachother.
|
||||
func (this *Tree) areStructurallyEquivalent (left, right entity.Type) bool {
|
||||
left = ReduceToBase(left)
|
||||
right = ReduceToBase(right)
|
||||
|
||||
switch left.(type) {
|
||||
case nil: return false
|
||||
|
||||
// composite
|
||||
case *entity.TypePointer:
|
||||
left := left.(*entity.TypePointer)
|
||||
right, ok := right.(*entity.TypePointer)
|
||||
if !ok { return false }
|
||||
return this.areStructurallyEquivalent(left.Referenced, right.Referenced)
|
||||
|
||||
case *entity.TypeSlice:
|
||||
left := left.(*entity.TypeSlice)
|
||||
right, ok := right.(*entity.TypeSlice)
|
||||
if !ok { return false }
|
||||
return this.areStructurallyEquivalent(left.Element, right.Element)
|
||||
|
||||
case *entity.TypeArray:
|
||||
left := left.(*entity.TypeArray)
|
||||
right, ok := right.(*entity.TypeArray)
|
||||
if !ok { return false }
|
||||
return left.Length == right.Length &&
|
||||
this.areStructurallyEquivalent(left.Element, right.Element)
|
||||
|
||||
case *entity.TypeStruct:
|
||||
left := left.(*entity.TypeStruct)
|
||||
right, ok := right.(*entity.TypeStruct)
|
||||
if !ok { return false }
|
||||
if len(left.MemberMap) != len(right.MemberMap) { return false }
|
||||
for name, member := range left.MemberMap {
|
||||
other, ok := right.MemberMap[name]
|
||||
if !ok { return false }
|
||||
if !this.areStructurallyEquivalent(member.Type(), other.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// terminals
|
||||
case *entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
return left.Equals(right)
|
||||
|
||||
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", left))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected array", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = 5
|
||||
}
|
||||
`)
|
||||
// isLocationExpression returns whether or not an expression is a valid location
|
||||
// expression.
|
||||
func (this *Tree) isLocationExpression (expression entity.Expression) error {
|
||||
cannot := func (pos errors.Position, kind string) error {
|
||||
return errors.Errorf(pos, "cannot assign to %s", kind)
|
||||
}
|
||||
switch expression := expression.(type) {
|
||||
case *entity.Variable:
|
||||
return nil
|
||||
case *entity.Declaration:
|
||||
return nil
|
||||
case *entity.Call:
|
||||
return cannot(expression.Position, "function call")
|
||||
case *entity.MethodCall:
|
||||
return cannot(expression.Position, "method call")
|
||||
case *entity.Subscript:
|
||||
return this.isLocationExpression(expression.Slice)
|
||||
case *entity.Slice:
|
||||
return cannot(expression.Position, "slice operation")
|
||||
case *entity.Dereference:
|
||||
return this.isLocationExpression(expression.Pointer)
|
||||
case *entity.Reference:
|
||||
return cannot(expression.Position, "reference operation")
|
||||
case *entity.ValueCast:
|
||||
return cannot(expression.Position, "value cast")
|
||||
case *entity.BitCast:
|
||||
return cannot(expression.Position, "bit cast")
|
||||
case *entity.Operation:
|
||||
return cannot(expression.Position, fmt.Sprintf (
|
||||
"cannot assign to %v operation",
|
||||
expression.Operator))
|
||||
case *entity.Block:
|
||||
return cannot(expression.Position, "block")
|
||||
case *entity.MemberAccess:
|
||||
return this.isLocationExpression (
|
||||
expression.Source)
|
||||
case *entity.IfElse:
|
||||
return cannot(expression.Position, "if/else")
|
||||
case *entity.Loop:
|
||||
return cannot(expression.Position, "loop")
|
||||
case *entity.Break:
|
||||
return cannot(expression.Position, "break statement")
|
||||
case *entity.Return:
|
||||
return cannot(expression.Position, "return statement")
|
||||
case *entity.LiteralInt:
|
||||
return cannot(expression.Position, "integer literal")
|
||||
case *entity.LiteralFloat:
|
||||
return cannot(expression.Position, "float literal")
|
||||
case *entity.LiteralString:
|
||||
return cannot(expression.Position, "string literal")
|
||||
case *entity.LiteralArray:
|
||||
return cannot(expression.Position, "array literal")
|
||||
case *entity.LiteralStruct:
|
||||
return cannot(expression.Position, "struct literal")
|
||||
case *entity.LiteralBoolean:
|
||||
return cannot(expression.Position, "boolean literal")
|
||||
case *entity.LiteralNil:
|
||||
return cannot(expression.Position, "nil literal")
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: analyzer doesnt know about expression",
|
||||
expression))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrArrayWrongLength (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected 3 elements", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
// ReduceToBase takes in an analyzed type and reduces it to its first non-name
|
||||
// type.
|
||||
func ReduceToBase (ty entity.Type) entity.Type {
|
||||
if namedty, ok := ty.(*entity.TypeNamed); ok {
|
||||
return ReduceToBase(namedty.Type)
|
||||
} else {
|
||||
return ty
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrArrayWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 19,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3.3 4)
|
||||
}
|
||||
`)
|
||||
// isInterface takes in an analyzed type and returns true and an interface if
|
||||
// that type refers to an interface. If not, it returns nil, false.
|
||||
func (this *Tree) isInterface (ty entity.Type) (*entity.TypeInterface, bool) {
|
||||
ty = ReduceToBase(ty)
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInterface: return ty.(*entity.TypeInterface), true
|
||||
default: return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrSliceWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 19,
|
||||
`
|
||||
[main] = {
|
||||
x:*:Int = (* 1 2 3.3 4)
|
||||
}
|
||||
`)
|
||||
// isNumeric returns whether or not the specified type is a number.
|
||||
func isNumeric (ty entity.Type) bool {
|
||||
ty = ReduceToBase(ty)
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeFloat, *entity.TypeWord: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrStructWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 26,
|
||||
`
|
||||
[main] = {
|
||||
x:3:(x:Int y:Int) = (x: 5.5 y: 3)
|
||||
}
|
||||
`)
|
||||
}
|
||||
func TestAssignLiteralErrStructInteger (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected struct", 3, 26,
|
||||
`
|
||||
[main] = {
|
||||
x:3:(x:Int y:Int) = 5
|
||||
}
|
||||
`)
|
||||
// isInteger returns whether or not the specified type is an integer.
|
||||
func isInteger (ty entity.Type) bool {
|
||||
switch ReduceToBase(ty).(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5
|
||||
}
|
||||
`)
|
||||
// isOrdered returns whether or not the values of the specified type can be
|
||||
// ordered.
|
||||
func isOrdered (ty entity.Type) bool {
|
||||
return isNumeric(ty)
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5.5
|
||||
}
|
||||
`)
|
||||
// isBoolean returns whether or not the specified type is a boolean.
|
||||
func isBoolean (ty entity.Type) bool {
|
||||
for {
|
||||
named, ok := ty.(*entity.TypeNamed)
|
||||
if !ok { return false }
|
||||
if named.Name == "Bool" { return true }
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
// isFloat returns whether or not the specified type is a float.
|
||||
func isFloat (ty entity.Type) bool {
|
||||
switch ReduceToBase(ty).(type) {
|
||||
case *entity.TypeFloat: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[land] = { }
|
||||
[main] = {
|
||||
b:Bird = (x: 5 y: 6)
|
||||
}
|
||||
`)
|
||||
// IsUnsigned returns whether or not the specified type is an unsigned integer.
|
||||
func IsUnsigned (ty entity.Type) bool {
|
||||
switch ty := ReduceToBase(ty).(type) {
|
||||
case *entity.TypeInt: return !ty.Signed
|
||||
case *entity.TypeWord: return !ty.Signed
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteral (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
a:F64 = 5.3
|
||||
b:F32 = 5.3
|
||||
c:F64 = -5
|
||||
d:F32 = -5
|
||||
e:Byte = 64
|
||||
z:UInt = 5
|
||||
x:Int = -5
|
||||
arr:4:2:Int = (*
|
||||
(* 1 2)
|
||||
(* 3 4)
|
||||
(* 5 6)
|
||||
(* 7 8))
|
||||
slice:*:Int = (* 3 1 2 3)
|
||||
struct:(x:Int y:Int) = (
|
||||
x: 9
|
||||
y: 10)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignInterfaceErrBadSignature (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"BlueJay has wrong signature for method fly", 7, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[fly] = { }
|
||||
BlueJay::[land] = { }
|
||||
[main] = {
|
||||
b:Bird = [@a:BlueJay]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignInterface (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[fly distance:F64] = { }
|
||||
BlueJay::[fly land] = { }
|
||||
[main] = {
|
||||
a:BlueJay
|
||||
b:Bird = [@a]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntPointer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from *Int to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ [@ a:Int] Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from (x:Int y:Int) to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ a:(x:Int y:Int) Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from 5:Int to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ a:5:Int Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntSlice (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from *:Int to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ a:*:Int Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrPointerInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to *Int", 2, 15,
|
||||
`
|
||||
[main]:*Int = [~ [@ a:Int] *Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrStructInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to (x:Int y:Int)", 2, 24,
|
||||
`
|
||||
[main]:(x:Int y:Int) = [~ a:Int (x:Int y:Int)]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to 5:Int", 2, 13,
|
||||
`
|
||||
[main]:Int = [~ a:5:Int Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrSliceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to *:Int", 2, 16,
|
||||
`
|
||||
[main]:*:Int = [~ a:Int *:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCast (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[fly distance:F64] = { }
|
||||
BlueJay::[fly land] = { }
|
||||
IntDerived: Int
|
||||
[main] = {
|
||||
a:IntDerived = 5
|
||||
b:Int [~ [~ [~ a Byte] F64] Int]
|
||||
c:Int [~~ [~~ [~~ a Byte] F64] Int]
|
||||
d:(x:Int y:Int) = (x: 1 y: 2)
|
||||
e:(z:Int a:Int) = [~~ d (z:Int a:Int)]
|
||||
f:Bird = [@ [~~ 0 BlueJay]]
|
||||
g:*:Int = (~ h:5:Int *:int)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// TODO: complete and test error cases
|
||||
func TestPropagateAssignRules (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f]:Byte = 5
|
||||
A:Int
|
||||
A::[g]:Int = 2
|
||||
B:([g]:Int)
|
||||
[main] = {
|
||||
a:Int
|
||||
b:Int = a
|
||||
c:Int = d:Int
|
||||
d:Int = { a:F64 b }
|
||||
e:Byte = [f]
|
||||
g:(x:Int y:(w:F64 z:F64)) = (x: 1 y: (w: 1.2 z: 78.5))
|
||||
h:F64 = g.x.z
|
||||
i:A
|
||||
j:Int = [i::g]
|
||||
k:B = i
|
||||
l:Int = [k::g]
|
||||
m:5:Int = (* 0 1 2 3 4)
|
||||
n:Int = [. m 3]
|
||||
}
|
||||
`)
|
||||
// inRange returns whether the specified value can fit within the given integer
|
||||
// type.
|
||||
func inRange (ty entity.Type, value int64) bool {
|
||||
base := ReduceToBase(ty)
|
||||
switch base.(type) {
|
||||
case *entity.TypeInt:
|
||||
base := base.(*entity.TypeInt)
|
||||
if base.Signed {
|
||||
return value > integer.SignedMin(base.Width) &&
|
||||
value < integer.SignedMax(base.Width)
|
||||
} else {
|
||||
return value >= 0 &&
|
||||
uint64(value) < integer.UnsignedMax(base.Width)
|
||||
}
|
||||
case *entity.TypeWord:
|
||||
base := base.(*entity.TypeWord)
|
||||
if base.Signed {
|
||||
return value > math.MinInt && value < math.MaxInt
|
||||
} else {
|
||||
return value >= 0 && uint64(value) < math.MaxUint
|
||||
}
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
289
analyzer/assignment_test.go
Normal file
289
analyzer/assignment_test.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAssignmentLiteralErrUnsignedNegative (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"integer literal out of range for type UInt", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:UInt = -5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = 5.5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use struct literal as Int", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (. x: 5)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use array literal as Int", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (* 5)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerOverflow (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"integer literal out of range for type Byte", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:Byte = 1000
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as 3:Int", 3, 12,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrArrayWrongLength (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected 3 elements or less", 3, 12,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrArrayWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 17,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 3.3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrSliceWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 17,
|
||||
`
|
||||
[main] = {
|
||||
x:*:Int = (* 1 3.3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrStructWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 28,
|
||||
`
|
||||
[main] = {
|
||||
x:(. x:Int y:Int) = (. x: 5.5 y: 3)
|
||||
}
|
||||
`)
|
||||
}
|
||||
func TestAssignmentLiteralErrStructInteger (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as (. x:Int y:Int)", 3, 22,
|
||||
`
|
||||
[main] = {
|
||||
x:(. x:Int y:Int) = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5.5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use array literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use struct literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (. x: 5 y: 6)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrDerefByteFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Byte", 4, 17,
|
||||
`
|
||||
[main] = {
|
||||
buffer:8:Byte
|
||||
[. buffer 7] = 0.0
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteral (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
a:F64 = 5.3
|
||||
b:F32 = 5.3
|
||||
c:F64 = -5
|
||||
d:F32 = -5
|
||||
e:Byte = 64
|
||||
z:UInt = 5
|
||||
x:Int = -5
|
||||
arr:4:2:Int = (*
|
||||
(* 1 2)
|
||||
(* 3 4)
|
||||
(* 5 6)
|
||||
(* 7 8))
|
||||
slice:*:Int = (* 3 1 2 3)
|
||||
struct:(. x:Int y:Int) = (.
|
||||
x: 9
|
||||
y: 10)
|
||||
|
||||
[.[.arr 1] 0] = 6
|
||||
[.slice 4] = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterfaceErrBadSignature (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"BlueJay has wrong signature for method fly", 7, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly] = { }
|
||||
BlueJay.[land] = { }
|
||||
[main] = {
|
||||
b:Bird = a:BlueJay
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterfaceErrMissingMethod (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named fly defined on this type", 6, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[land] = { }
|
||||
[main] = {
|
||||
b:Bird = a:BlueJay
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterfaceErrBadLayer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named fly defined on this type", 7, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJayRef: *BlueJay
|
||||
[main] = {
|
||||
b:Bird = a:BlueJayRef
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterface (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJay.[land] = { }
|
||||
[main] = {
|
||||
b:Bird = a:BlueJay
|
||||
ref:*Bird = [@ a]
|
||||
c:Bird = ref
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentErrByteSliceString (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected *:Byte", 4, 13,
|
||||
`
|
||||
[main] = {
|
||||
a:String = 'hello'
|
||||
b:*:Byte = a
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// TODO: complete and test error cases
|
||||
func TestAssignmentPropagateRules (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f]:Byte = 5
|
||||
A:Int
|
||||
A.[g]:Int = 2
|
||||
B:(~ [g]:Int)
|
||||
[main] = {
|
||||
a:Int
|
||||
b:Int = a
|
||||
c:Int = d:Int
|
||||
d = { a:F64 b }
|
||||
e:Byte = [f]
|
||||
g:(. x:Int y:(. w:F64 z:F64)) = (. x: 1 y: (. w: 1.2 z: 78.5))
|
||||
gx:(. w:F64 z:F64) = g.y
|
||||
h:F64 = gx.z
|
||||
i:A
|
||||
j:Int = i.[g]
|
||||
k:B = i
|
||||
l:Int = k.[g]
|
||||
m:5:Int = (* 0 1 2 3 4)
|
||||
n:Int = [. m 3]
|
||||
o:*:Int = m
|
||||
}
|
||||
`)
|
||||
}
|
||||
51
analyzer/builtin.go
Normal file
51
analyzer/builtin.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package analyzer
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
var primitiveTypes = map[string] entity.Type {
|
||||
"Int": &entity.TypeWord { Acc: entity.AccessPublic, Signed: true },
|
||||
"UInt": &entity.TypeWord { Acc: entity.AccessPublic, },
|
||||
"I8": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 8 },
|
||||
"I16": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 16 },
|
||||
"I32": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 32 },
|
||||
"I64": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 64 },
|
||||
"U8": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 },
|
||||
"U16": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 16 },
|
||||
"U32": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 32 },
|
||||
"U64": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 64 },
|
||||
"F32": &entity.TypeFloat { Acc: entity.AccessPublic, Width: 32 },
|
||||
"F64": &entity.TypeFloat { Acc: entity.AccessPublic, Width: 64 },
|
||||
}
|
||||
|
||||
var builtinTypes = map[string] entity.Type { }
|
||||
|
||||
func builtinType (name string) *entity.TypeNamed {
|
||||
ty, ok := builtinTypes[name]
|
||||
if !ok { panic("BUG: compiler tried to reference missing builtin " + name) }
|
||||
return &entity.TypeNamed {
|
||||
Name: name,
|
||||
Type: ty,
|
||||
Acc: entity.AccessPublic,
|
||||
}
|
||||
}
|
||||
|
||||
func primitiveType (name string) *entity.TypeNamed {
|
||||
ty, ok := primitiveTypes[name]
|
||||
if !ok { panic("BUG: compiler tried to reference missing primitive " + name) }
|
||||
return &entity.TypeNamed {
|
||||
Name: name,
|
||||
Type: ty,
|
||||
Acc: entity.AccessPublic,
|
||||
}
|
||||
}
|
||||
|
||||
func init () {
|
||||
builtinTypes["Index"] = &entity.TypeWord { Acc: entity.AccessPublic }
|
||||
builtinTypes["Byte"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
|
||||
builtinTypes["Bool"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
|
||||
builtinTypes["Rune"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 32 }
|
||||
builtinTypes["String"] = &entity.TypeSlice {
|
||||
Acc: entity.AccessPublic,
|
||||
Element: &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 },
|
||||
}
|
||||
}
|
||||
122
analyzer/cast_test.go
Normal file
122
analyzer/cast_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCastErrIntPointer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from *Int to Int", 4, 9,
|
||||
`
|
||||
[main]:Int = {
|
||||
a:*Int
|
||||
[~ Int a]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from (. x:Int y:Int) to Int", 2, 21,
|
||||
`
|
||||
[main]:Int = [~ Int a:(. x:Int y:Int)]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from 5:Int to Int", 2, 21,
|
||||
`
|
||||
[main]:Int = [~ Int a:5:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntSlice (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from *:Int to Int", 2, 21,
|
||||
`
|
||||
[main]:Int = [~ Int a:*:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrPointerInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to *Int", 2, 23,
|
||||
`
|
||||
[main]:*Int = [~ *Int a:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrStructInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to (. x:Int y:Int)", 2, 45,
|
||||
`
|
||||
[main]:(. x:Int y:Int) = [~ (. x:Int y:Int) a:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from 5:Int to Int", 2, 21,
|
||||
`
|
||||
[main]:Int = [~ Int a:5:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrSliceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to *:Int", 2, 25,
|
||||
`
|
||||
[main]:*:Int = [~ *:Int a:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCast (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJay.[land] = { }
|
||||
IntDerived: Int
|
||||
[main] = {
|
||||
a:IntDerived = 5
|
||||
b:Int [~ Int [~ F64 [~ Byte a]]]
|
||||
c:Int [~~ Int [~~ F64 [~~ Byte a]]]
|
||||
d:(. x:Int y:Int) = (. x: 1 y: 2)
|
||||
e:(. z:Int a:Int) = [~~ (. z:Int a:Int) d]
|
||||
f:Bird = [~~ BlueJay 0]
|
||||
g:String = 'hello'
|
||||
h:*:Byte = [~ *:Byte g]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBitCastPointer (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Struct: (. x:Int y:Int)
|
||||
Array: 4:Int
|
||||
[main] = {
|
||||
ptr:*Byte
|
||||
stc:Struct
|
||||
arr:Array
|
||||
str:String
|
||||
idx:Index
|
||||
|
||||
; struct
|
||||
ptr = [~~*Byte [~~Struct ptr]]
|
||||
stc = [~~Struct [~~*Byte stc]]
|
||||
; array
|
||||
ptr = [~~*Byte [~~Array ptr]]
|
||||
arr = [~~Array [~~*Byte arr]]
|
||||
; slice
|
||||
ptr = [~~*Byte [~~String ptr]]
|
||||
str = [~~String [~~*Byte str]]
|
||||
; int
|
||||
ptr = [~~*Byte [~~Index ptr]]
|
||||
idx = [~~Index [~~*Byte idx]]
|
||||
|
||||
; arithmetic
|
||||
ptr = [~~*Byte [+ 1 [~~Index ptr]]]
|
||||
}
|
||||
`)
|
||||
}
|
||||
5
analyzer/doc.go
Normal file
5
analyzer/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package analyzer implements the semantic analysis stage of the FSPL compiler.
|
||||
// The analyzer takes in a well-formed abstract syntax tree, ensures its
|
||||
// semantic correctness, and fills in the semantic information stored within
|
||||
// the tree.
|
||||
package analyzer
|
||||
73
analyzer/expression-multiplex.go
Normal file
73
analyzer/expression-multiplex.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeExpression (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
expression entity.Expression,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
switch expression := expression.(type) {
|
||||
case *entity.Assignment:
|
||||
return this.analyzeAssignment(expression)
|
||||
case *entity.Variable:
|
||||
return this.analyzeVariable(into, mode, expression)
|
||||
case *entity.Declaration:
|
||||
return this.analyzeDeclaration(into, mode, expression)
|
||||
case *entity.Call:
|
||||
return this.analyzeCall(into, mode, expression)
|
||||
case *entity.MethodCall:
|
||||
return this.analyzeMethodCall(into, mode, expression)
|
||||
case *entity.Subscript:
|
||||
return this.analyzeSubscript(into, mode, expression)
|
||||
case *entity.Slice:
|
||||
return this.analyzeSlice(into, mode, expression)
|
||||
case *entity.Length:
|
||||
return this.analyzeLength(into, mode, expression)
|
||||
case *entity.Dereference:
|
||||
return this.analyzeDereference(into, mode, expression)
|
||||
case *entity.Reference:
|
||||
return this.analyzeReference(into, mode, expression)
|
||||
case *entity.ValueCast:
|
||||
return this.analyzeValueCast(into, mode, expression)
|
||||
case *entity.BitCast:
|
||||
return this.analyzeBitCast(into, mode, expression)
|
||||
case *entity.Operation:
|
||||
return this.analyzeOperation(into, mode, expression)
|
||||
case *entity.Block:
|
||||
return this.analyzeBlock(into, mode, expression)
|
||||
case *entity.MemberAccess:
|
||||
return this.analyzeMemberAccess(into, mode, expression)
|
||||
case *entity.IfElse:
|
||||
return this.analyzeIfElse(into, mode, expression)
|
||||
case *entity.Loop:
|
||||
return this.analyzeLoop(into, mode, expression)
|
||||
case *entity.Break:
|
||||
return this.analyzeBreak(into, mode, expression)
|
||||
case *entity.Return:
|
||||
return this.analyzeReturn(into, mode, expression)
|
||||
|
||||
case *entity.LiteralInt:
|
||||
return this.analyzeLiteralInt(into, mode, expression)
|
||||
case *entity.LiteralFloat:
|
||||
return this.analyzeLiteralFloat(into, mode, expression)
|
||||
case *entity.LiteralArray:
|
||||
return this.analyzeLiteralArray(into, mode, expression)
|
||||
case *entity.LiteralString:
|
||||
return this.analyzeLiteralString(into, mode, expression)
|
||||
case *entity.LiteralStruct:
|
||||
return this.analyzeLiteralStruct(into, mode, expression)
|
||||
case *entity.LiteralBoolean:
|
||||
return this.analyzeLiteralBoolean(into, mode, expression)
|
||||
case *entity.LiteralNil:
|
||||
return this.analyzeLiteralNil(into, mode, expression)
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: analyzer doesnt know about expression %v, ty: %T",
|
||||
expression, expression))
|
||||
}
|
||||
}
|
||||
805
analyzer/expression.go
Normal file
805
analyzer/expression.go
Normal file
@@ -0,0 +1,805 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
// All expression analysis routines must take in the type they are being
|
||||
// assigned to and return an error if they can't be assigned to it. if the type
|
||||
// is nil, the expression must ignore it unless it can do upwards type
|
||||
// inference.
|
||||
|
||||
func (this *Tree) analyzeAssignment (
|
||||
assignment *entity.Assignment,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// analyze location
|
||||
location, err := this.analyzeExpression(nil, strict, assignment.Location)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Location = location
|
||||
|
||||
// ensure location is location expression
|
||||
err = this.isLocationExpression(location)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// analyze value
|
||||
value, err := this.analyzeExpression(location.Type(), strict, assignment.Value)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Value = value
|
||||
|
||||
return assignment, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeVariable (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
variable *entity.Variable,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
declaration := this.variable(variable.Name)
|
||||
if declaration == nil {
|
||||
return nil, errors.Errorf (
|
||||
variable.Position, "no variable named %s",
|
||||
variable.Name)
|
||||
}
|
||||
|
||||
err := this.canAssign(variable.Position, into, mode, declaration.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
variable.Declaration = declaration
|
||||
return variable, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDeclaration (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
declaration *entity.Declaration,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
scope, _ := this.topScope()
|
||||
existing := scope.Variable(declaration.Name)
|
||||
if existing != nil {
|
||||
return nil, errors.Errorf (
|
||||
declaration.Position,
|
||||
"%s already declared in block at %v",
|
||||
declaration.Name, existing.Position)
|
||||
}
|
||||
|
||||
ty, err := this.analyzeType(declaration.Ty, false)
|
||||
declaration.Ty = ty
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.canAssign(declaration.Position, into, mode, declaration.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
this.addVariable(declaration)
|
||||
return declaration, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeCall (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
call *entity.Call,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// get function
|
||||
unit, err := this.resolveNickname(call.Position, call.UnitNickname)
|
||||
if err != nil { return nil, err }
|
||||
function, err := this.analyzeFunction(call.Position, entity.Key {
|
||||
Unit: unit,
|
||||
Name: call.Name,
|
||||
})
|
||||
if err != nil { return nil, err }
|
||||
call.Function = function
|
||||
call.Unit = function.Unit
|
||||
|
||||
// check access permissions
|
||||
if function.Acc == entity.AccessPrivate && function.Unit != this.unit {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "function %v::[%v] is private",
|
||||
call.UnitNickname, call.Name)
|
||||
}
|
||||
|
||||
// check return result
|
||||
err = this.canAssign(call.Position, into, mode, function.Signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// check arg count
|
||||
if len(call.Arguments) > len(function.Signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too many arguments in call to %s",
|
||||
call.Name)
|
||||
} else if len(call.Arguments) < len(function.Signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too few arguments in call to %s",
|
||||
call.Name)
|
||||
}
|
||||
|
||||
// check arg types
|
||||
for index, argument := range call.Arguments {
|
||||
signature := function.Signature
|
||||
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
|
||||
argument, err := this.analyzeExpression(correct.Type(), strict, argument)
|
||||
if err != nil { return nil, err }
|
||||
call.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return call, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeMethodCall (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
call *entity.MethodCall,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// get method
|
||||
source, err := this.analyzeExpression(nil, strict, call.Source)
|
||||
if err != nil { return nil, err }
|
||||
method, err := this.analyzeMethodOrBehavior (
|
||||
call.Position, source.Type(), call.Name)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// extract signature
|
||||
var signature *entity.Signature
|
||||
switch method := method.(type) {
|
||||
case *entity.Signature:
|
||||
signature = method
|
||||
call.Behavior = signature
|
||||
|
||||
// since this is a behavior, check access permissions of the
|
||||
// interface
|
||||
err = this.typeRestricted(call.Position, source.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
case *entity.Method:
|
||||
signature = method.Signature
|
||||
call.Method = method
|
||||
|
||||
// since this is a method, check access permissions of the
|
||||
// method
|
||||
if method.Acc == entity.AccessPrivate && method.Unit != this.unit {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "method %v.[%v] is private",
|
||||
method.TypeName, method.Signature.Name)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"Tree.analyzeMethodOrBehavior returned ",
|
||||
method))
|
||||
}
|
||||
|
||||
// check return result
|
||||
err = this.canAssign(call.Position, into, mode, signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// check arg count
|
||||
if len(call.Arguments) > len(signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too many arguments in call to %s",
|
||||
call.Name)
|
||||
} else if len(call.Arguments) < len(signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too few arguments in call to %s",
|
||||
call.Name)
|
||||
}
|
||||
|
||||
// check arg types
|
||||
for index, argument := range call.Arguments {
|
||||
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
|
||||
argument, err := this.analyzeExpression(correct.Type(), strict, argument)
|
||||
if err != nil { return nil, err }
|
||||
call.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return call, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeSubscript (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
subscript *entity.Subscript,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
slice, err := this.analyzeExpression (
|
||||
&entity.TypeSlice {
|
||||
Position: subscript.Position,
|
||||
Element: into,
|
||||
}, weak,
|
||||
subscript.Slice)
|
||||
if err != nil { return nil, err }
|
||||
subscript.Slice = slice
|
||||
subscript.Ty = into
|
||||
|
||||
// check permissions
|
||||
err = this.typeRestricted(subscript.Position, slice.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
offset, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
subscript.Offset)
|
||||
if err != nil { return nil, err }
|
||||
subscript.Offset = offset
|
||||
|
||||
var frailType bool
|
||||
switch into := into.(type) {
|
||||
case nil: frailType = true
|
||||
case *entity.TypeSlice: frailType = into.Element == nil
|
||||
}
|
||||
|
||||
if frailType {
|
||||
ty := ReduceToBase(subscript.Slice.Type())
|
||||
switch ty := ty.(type) {
|
||||
case *entity.TypeSlice: subscript.Ty = ty.Element
|
||||
case *entity.TypeArray: subscript.Ty = ty.Element
|
||||
}
|
||||
}
|
||||
|
||||
return subscript, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeSlice (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
slice *entity.Slice,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
value, err := this.analyzeExpression(into, weak, slice.Slice)
|
||||
if err != nil { return nil, err }
|
||||
slice.Slice = value
|
||||
|
||||
// check permissions
|
||||
err = this.typeRestricted(slice.Position, value.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if slice.Start != nil {
|
||||
start, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
slice.Start)
|
||||
if err != nil { return nil, err }
|
||||
slice.Start = start
|
||||
}
|
||||
|
||||
if slice.End != nil {
|
||||
end, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
slice.End)
|
||||
if err != nil { return nil, err }
|
||||
slice.End = end
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLength (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
length *entity.Length,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
value, err := this.analyzeExpression(nil, strict, length.Slice)
|
||||
if err != nil { return nil, err }
|
||||
length.Slice = value
|
||||
length.Ty = builtinType("Index")
|
||||
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDereference (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
dereference *entity.Dereference,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
pointer, err := this.analyzeExpression (
|
||||
&entity.TypePointer {
|
||||
Position: dereference.Position,
|
||||
Referenced: into,
|
||||
}, weak,
|
||||
dereference.Pointer)
|
||||
if err != nil { return nil, err }
|
||||
dereference.Pointer = pointer
|
||||
dereference.Ty = into
|
||||
|
||||
var frailType bool
|
||||
switch into := into.(type) {
|
||||
case nil: frailType = true
|
||||
case *entity.TypePointer: frailType = into.Referenced == nil
|
||||
}
|
||||
|
||||
if frailType {
|
||||
ty := ReduceToBase(dereference.Pointer.Type())
|
||||
switch ty := ty.(type) {
|
||||
case *entity.TypePointer: dereference.Ty = ty.Referenced
|
||||
}
|
||||
}
|
||||
|
||||
return dereference, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeReference (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
reference *entity.Reference,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
referenced, ok := into.(*entity.TypePointer)
|
||||
if !ok {
|
||||
return nil, errors.Errorf(reference.Position, "expected %v", into)
|
||||
}
|
||||
|
||||
value, err := this.analyzeExpression (
|
||||
referenced.Referenced, weak,
|
||||
reference.Value)
|
||||
if err != nil { return nil, err }
|
||||
err = this.isLocationExpression(reference.Value)
|
||||
if err != nil { return nil, err }
|
||||
reference.Value = value
|
||||
reference.Ty = referenced.Referenced
|
||||
|
||||
return reference, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeValueCast (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
cast *entity.ValueCast,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
ty, err := this.analyzeType(cast.Ty, false)
|
||||
if err != nil { return nil, err }
|
||||
cast.Ty = ty
|
||||
|
||||
err = this.canAssign(cast.Position, into, mode, cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
value, err := this.analyzeExpression(cast.Ty, coerce, cast.Value)
|
||||
if err != nil { return nil, err }
|
||||
cast.Value = value
|
||||
|
||||
return cast, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeBitCast (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
cast *entity.BitCast,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
ty, err := this.analyzeType(cast.Ty, false)
|
||||
if err != nil { return nil, err }
|
||||
cast.Ty = ty
|
||||
|
||||
err = this.canAssign(cast.Position, into, mode, cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
value, err := this.analyzeExpression(cast.Ty, force, cast.Value)
|
||||
if err != nil { return nil, err }
|
||||
cast.Value = value
|
||||
|
||||
return cast, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeOperation (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
operation *entity.Operation,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// check permissions
|
||||
err := this.typeRestricted(operation.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
intoUntrustworthy := into == nil || mode == force || mode == coerce
|
||||
|
||||
undefined := func (ty entity.Type) (entity.Expression, error) {
|
||||
return nil, errors.Errorf (
|
||||
operation.Position, "operator %v undefined for %v",
|
||||
operation.Operator, entity.FormatType(ty))
|
||||
}
|
||||
|
||||
wrongInto := func () (entity.Expression, error) {
|
||||
return nil, errors.Errorf (
|
||||
operation.Position, "expected %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
wrongArgCount := func () (entity.Expression, error) {
|
||||
return nil, errors.Errorf (
|
||||
operation.Position, "wrong argument count for %v",
|
||||
operation.Operator)
|
||||
}
|
||||
|
||||
determineArgumentType := func () (entity.Type, int, error) {
|
||||
// find the first argument that has explicit type information.
|
||||
boss := -1
|
||||
var argumentType entity.Type
|
||||
for index, argument := range operation.Arguments {
|
||||
if argument.HasExplicitType() {
|
||||
argument, err := this.analyzeExpression(nil, strict, argument)
|
||||
if err != nil { return nil, -1, err }
|
||||
boss = index
|
||||
operation.Arguments[index] = argument
|
||||
argumentType = argument.Type()
|
||||
break
|
||||
}
|
||||
}
|
||||
if argumentType == nil {
|
||||
return nil, -1, errors.Errorf (
|
||||
operation.Position,
|
||||
"operation arguments have ambiguous type")
|
||||
}
|
||||
return argumentType, boss, err
|
||||
}
|
||||
|
||||
nSameType := func (n int, constraint func (entity.Type) bool) (entity.Expression, error) {
|
||||
if n > 0 {
|
||||
if len(operation.Arguments) != n { return wrongArgCount() }
|
||||
} else {
|
||||
if len(operation.Arguments) < 1 { return wrongArgCount() }
|
||||
}
|
||||
n = len(operation.Arguments)
|
||||
|
||||
boss := -1
|
||||
var argumentType entity.Type
|
||||
if intoUntrustworthy {
|
||||
// determine the type of all arguments (and the return
|
||||
// type) using determineArgumentType
|
||||
var err error
|
||||
argumentType, boss, err = determineArgumentType()
|
||||
if err != nil { return nil, err }
|
||||
if !constraint(argumentType) { return undefined(argumentType) }
|
||||
} else {
|
||||
// into is trustworthy, so we don't need to determine
|
||||
// anything
|
||||
argumentType = into
|
||||
}
|
||||
if !constraint(argumentType) { return undefined(argumentType) }
|
||||
operation.Ty = argumentType
|
||||
|
||||
for index, argument := range operation.Arguments {
|
||||
if index == boss { continue }
|
||||
argument, err := this.analyzeExpression (
|
||||
operation.Ty, strict,
|
||||
argument)
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
comparison := func (argConstraint func (entity.Type) bool) (entity.Expression, error) {
|
||||
if len(operation.Arguments) < 2 { return wrongArgCount() }
|
||||
if !isBoolean(into) && !intoUntrustworthy { return wrongInto() }
|
||||
operation.Ty = builtinType("Bool")
|
||||
|
||||
// determine argument type
|
||||
argumentType, boss, err := determineArgumentType()
|
||||
if err != nil { return nil, err }
|
||||
if !argConstraint(argumentType) { return undefined(argumentType) }
|
||||
|
||||
// analyze all remaining arguments
|
||||
for index, argument := range operation.Arguments {
|
||||
if index == boss { continue }
|
||||
argument, err := this.analyzeExpression (
|
||||
argumentType, strict, argument)
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
switch operation.Operator {
|
||||
// math
|
||||
case entity.OperatorAdd,
|
||||
entity.OperatorSubtract,
|
||||
entity.OperatorMultiply,
|
||||
entity.OperatorDivide,
|
||||
entity.OperatorIncrement,
|
||||
entity.OperatorDecrement:
|
||||
return nSameType(-1, isNumeric)
|
||||
|
||||
case entity.OperatorModulo:
|
||||
return nSameType(2, isNumeric)
|
||||
|
||||
// logic
|
||||
case entity.OperatorLogicalNot:
|
||||
return nSameType(1, isBoolean)
|
||||
|
||||
case entity.OperatorLogicalOr,
|
||||
entity.OperatorLogicalAnd,
|
||||
entity.OperatorLogicalXor:
|
||||
return nSameType(-1, isBoolean)
|
||||
|
||||
// bit manipulation
|
||||
case entity.OperatorNot:
|
||||
return nSameType(1, isInteger)
|
||||
|
||||
case entity.OperatorOr, entity.OperatorAnd, entity.OperatorXor:
|
||||
return nSameType(-1, isInteger)
|
||||
|
||||
case entity.OperatorLeftShift, entity.OperatorRightShift:
|
||||
if len(operation.Arguments) != 2 { return wrongArgCount() }
|
||||
if !isInteger(into) { return wrongInto() }
|
||||
|
||||
arg, err := this.analyzeExpression(into, mode, operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[0] = arg
|
||||
operation.Ty = arg.Type()
|
||||
|
||||
offset, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
operation.Arguments[1])
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[1] = offset
|
||||
|
||||
return operation, nil
|
||||
|
||||
// comparison
|
||||
case entity.OperatorLess,
|
||||
entity.OperatorGreater,
|
||||
entity.OperatorLessEqual,
|
||||
entity.OperatorGreaterEqual:
|
||||
return comparison(isOrdered)
|
||||
|
||||
case entity.OperatorEqual:
|
||||
return comparison(isOrdered)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: analyzer doesnt know about operator ",
|
||||
operation.Operator))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeBlock (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
block *entity.Block,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
this.pushScope(block)
|
||||
defer this.popScope()
|
||||
|
||||
if len(block.Steps) == 0 && into != nil {
|
||||
return nil, errors.Errorf (
|
||||
block.Position, "block must have at least one statement")
|
||||
}
|
||||
|
||||
final := len(block.Steps) - 1
|
||||
for index, step := range block.Steps {
|
||||
if index == final && into != nil {
|
||||
expression, ok := step.(entity.Expression)
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
block.Position, "expected expression")
|
||||
}
|
||||
|
||||
step, err := this.analyzeExpression(into, strict, expression)
|
||||
if err != nil { return nil, err }
|
||||
block.Steps[index] = step
|
||||
block.Ty = step.Type()
|
||||
} else {
|
||||
step, err := this.analyzeExpression(nil, strict, step)
|
||||
if err != nil { return nil, err }
|
||||
block.Steps[index] = step
|
||||
}
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeMemberAccess (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
access *entity.MemberAccess,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
source, err := this.analyzeExpression(nil, strict, access.Source)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// determine type with the members
|
||||
var sourceType *entity.TypeStruct
|
||||
var qualifiedSourceType entity.Type
|
||||
switch sourceTypeAny := ReduceToBase(source.Type()).(type) {
|
||||
case *entity.TypeStruct:
|
||||
sourceType = sourceTypeAny
|
||||
qualifiedSourceType = source.Type()
|
||||
case *entity.TypePointer:
|
||||
referenced, ok := ReduceToBase(sourceTypeAny.Referenced).(*entity.TypeStruct)
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "cannot access members of %v",
|
||||
source)
|
||||
}
|
||||
sourceType = referenced
|
||||
qualifiedSourceType = sourceTypeAny.Referenced
|
||||
default:
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "cannot access members of %v",
|
||||
source)
|
||||
}
|
||||
|
||||
// check permissions
|
||||
err = this.typeRestricted(access.Position, qualifiedSourceType)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// get member
|
||||
member, ok := sourceType.MemberMap[access.Member]
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "no member %v",
|
||||
access)
|
||||
}
|
||||
|
||||
err = this.canAssign(access.Position, into, mode, member.Type())
|
||||
if err != nil { return nil, err }
|
||||
access.Ty = member.Type()
|
||||
|
||||
return access, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeIfElse (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
ifelse *entity.IfElse,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
condition, err := this.analyzeExpression (
|
||||
&entity.TypeNamed {
|
||||
Name: "Bool",
|
||||
Type: builtinType("Bool"),
|
||||
},
|
||||
weak, ifelse.Condition)
|
||||
if err != nil { return nil, err }
|
||||
ifelse.Condition = condition
|
||||
|
||||
trueBranch, err := this.analyzeExpression(into, strict, ifelse.True)
|
||||
if err != nil { return nil, err }
|
||||
ifelse.True = trueBranch
|
||||
|
||||
if ifelse.False == nil {
|
||||
if into != nil {
|
||||
return nil, errors.Errorf (
|
||||
ifelse.Position,
|
||||
"else case required when using value of if ")
|
||||
}
|
||||
} else {
|
||||
falseBranch, err := this.analyzeExpression(into, strict, ifelse.False)
|
||||
if err != nil { return nil, err }
|
||||
ifelse.False = falseBranch
|
||||
}
|
||||
|
||||
ifelse.Ty = into
|
||||
|
||||
return ifelse, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLoop (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
loop *entity.Loop,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
loop.Ty = into
|
||||
this.pushLoop(loop)
|
||||
defer this.popLoop()
|
||||
|
||||
body, err := this.analyzeExpression(nil, strict, loop.Body)
|
||||
if err != nil { return nil, err }
|
||||
loop.Body = body
|
||||
|
||||
return loop, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeBreak (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
brk *entity.Break,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
if into != nil {
|
||||
return nil, errors.Errorf (
|
||||
brk.Position, "expected %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
loop, ok := this.topLoop()
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
brk.Position,
|
||||
"break statement must be within loop")
|
||||
}
|
||||
brk.Loop = loop
|
||||
|
||||
if loop.Type() != nil && brk.Value == nil {
|
||||
return nil, errors.Errorf (
|
||||
brk.Position,
|
||||
"break statement must have value")
|
||||
}
|
||||
|
||||
if brk.Value != nil {
|
||||
value, err := this.analyzeExpression(loop.Type(), strict, brk.Value)
|
||||
if err != nil { return nil, err }
|
||||
brk.Value = value
|
||||
}
|
||||
|
||||
return brk, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeReturn (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
ret *entity.Return,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
if into != nil {
|
||||
return nil, errors.Errorf (
|
||||
ret.Position, "expected %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
ret.Declaration, _ = this.topDeclaration()
|
||||
var ty entity.Type
|
||||
switch ret.Declaration.(type) {
|
||||
case *entity.Function:
|
||||
ty = ret.Declaration.(*entity.Function).Signature.Return
|
||||
case *entity.Method:
|
||||
ty = ret.Declaration.(*entity.Method).Signature.Return
|
||||
}
|
||||
|
||||
if ty != nil && ret.Value == nil {
|
||||
return nil, errors.Errorf (
|
||||
ret.Position,
|
||||
"break statement must have value")
|
||||
}
|
||||
|
||||
if ret.Value != nil {
|
||||
value, err := this.analyzeExpression(ty, strict, ret.Value)
|
||||
if err != nil { return nil, err }
|
||||
ret.Value = value
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
66
analyzer/function.go
Normal file
66
analyzer/function.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package analyzer
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeFunction (
|
||||
pos errors.Position,
|
||||
key entity.Key,
|
||||
) (
|
||||
*entity.Function,
|
||||
error,
|
||||
) {
|
||||
var err error
|
||||
|
||||
// return if exists already
|
||||
if function, exists := this.Functions[key]; exists {
|
||||
return function, nil
|
||||
}
|
||||
|
||||
// error if function is missing
|
||||
function, exists := this.rawFunctions[key]
|
||||
if !exists {
|
||||
return nil, errors.Errorf(pos, "no function named %s", key.Name)
|
||||
}
|
||||
|
||||
// set unit
|
||||
function.Unit = key.Unit
|
||||
|
||||
// functions cannot be marked as restricted
|
||||
if function.Acc == entity.AccessRestricted {
|
||||
return nil, errors.Errorf(pos, "cannot mark function as restricted")
|
||||
}
|
||||
|
||||
// create a new scope context for this function
|
||||
this.pushScopeContext(function)
|
||||
this.pushScope(function)
|
||||
defer this.popScopeContext()
|
||||
defer this.popScope()
|
||||
|
||||
// analyze signature and add arguments to root scope of function
|
||||
function.Signature, err = this.assembleSignatureMap(function.Signature)
|
||||
if err != nil { return function, err }
|
||||
for name, argument := range function.Signature.ArgumentMap {
|
||||
argument.Ty, err = this.analyzeType(argument.Ty, false)
|
||||
this.addVariable(argument)
|
||||
function.Signature.ArgumentMap[name] = argument
|
||||
if err != nil { return function, err }
|
||||
}
|
||||
function.Signature.Return, err =
|
||||
this.analyzeType(function.Signature.Return, false)
|
||||
if err != nil { return function, err }
|
||||
|
||||
// add incomplete function to complete functions because there is enough
|
||||
// information for it to be complete from the point of view of other
|
||||
// parts of the code
|
||||
this.Functions[key] = function
|
||||
|
||||
// analyze function body
|
||||
if function.Body != nil {
|
||||
body, err := this.analyzeExpression (
|
||||
function.Signature.Return, strict, function.Body)
|
||||
if err != nil { return nil, err }
|
||||
function.Body = body
|
||||
}
|
||||
return function, nil
|
||||
}
|
||||
35
analyzer/function_test.go
Normal file
35
analyzer/function_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFunctionUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
[hello] = { }
|
||||
[hello] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[hello] = { }
|
||||
[world] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionArgumentUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed as argument at stream0.fspl:2:7", 2, 13,
|
||||
`
|
||||
[main x:Int x:U8 y:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionArgumentUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main x:Int y:U8 z:Int] = { }
|
||||
`)
|
||||
}
|
||||
297
analyzer/literal.go
Normal file
297
analyzer/literal.go
Normal file
@@ -0,0 +1,297 @@
|
||||
package analyzer
|
||||
|
||||
import "unicode/utf16"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeLiteralInt (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralInt,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if !isNumeric(into) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use integer literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
if isInteger(into) && !inRange(into, int64(literal.Value)) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "integer literal out of range for type %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralFloat (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralFloat,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if !isFloat(into) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use float literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralString (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralString,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
base := ReduceToBase(into)
|
||||
|
||||
errCantUse := func () error {
|
||||
return errors.Errorf (
|
||||
literal.Position, "cannot use string literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
fillUTF32 := func () {
|
||||
literal.ValueUTF32 = []rune(literal.ValueUTF8)
|
||||
}
|
||||
|
||||
fillUTF16 := func () {
|
||||
literal.ValueUTF16 = utf16.Encode([]rune(literal.ValueUTF8))
|
||||
}
|
||||
switch base := base.(type) {
|
||||
case *entity.TypeInt:
|
||||
var length int; switch {
|
||||
case base.Width >= 32:
|
||||
fillUTF32()
|
||||
length = len(literal.ValueUTF32)
|
||||
case base.Width >= 16:
|
||||
fillUTF16()
|
||||
length = len(literal.ValueUTF16)
|
||||
default:
|
||||
length = len(literal.ValueUTF8)
|
||||
}
|
||||
if length > 1 {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"cannot fit string literal of length " +
|
||||
"%v into %v",
|
||||
length, entity.FormatType(into))
|
||||
} else if length < 1 {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"string literal must have data when " +
|
||||
"assigning to %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
case *entity.TypeArray:
|
||||
element := ReduceToBase(base.Element)
|
||||
if element, ok := element.(*entity.TypeInt); ok {
|
||||
var length int; switch {
|
||||
case element.Width >= 32:
|
||||
fillUTF32()
|
||||
length = len(literal.ValueUTF32)
|
||||
case element.Width >= 16:
|
||||
fillUTF16()
|
||||
length = len(literal.ValueUTF16)
|
||||
default:
|
||||
length = len(literal.ValueUTF8)
|
||||
}
|
||||
if length > base.Length {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"cannot fit string literal of length " +
|
||||
"%v into array of length %v",
|
||||
length, base.Length)
|
||||
}
|
||||
} else {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
case *entity.TypeSlice:
|
||||
element := ReduceToBase(base.Element)
|
||||
if element, ok := element.(*entity.TypeInt); ok {
|
||||
if element.Width >= 32 {
|
||||
fillUTF32()
|
||||
} else if element.Width >= 16 {
|
||||
fillUTF16()
|
||||
}
|
||||
} else {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
case *entity.TypePointer:
|
||||
referenced := ReduceToBase(base.Referenced)
|
||||
if referenced, ok := referenced.(*entity.TypeInt); ok {
|
||||
if referenced.Width != 8 {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
} else {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
default:
|
||||
return nil, errCantUse()
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
|
||||
func (this *Tree) analyzeLiteralArray (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralArray,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
base := ReduceToBase(into)
|
||||
var elementType entity.Type
|
||||
switch base.(type) {
|
||||
case *entity.TypeArray:
|
||||
base := base.(*entity.TypeArray)
|
||||
if base.Length < len(literal.Elements) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "expected %v elements or less",
|
||||
base.Length)
|
||||
}
|
||||
elementType = base.Element
|
||||
|
||||
case *entity.TypeSlice:
|
||||
base := base.(*entity.TypeSlice)
|
||||
elementType = base.Element
|
||||
|
||||
case *entity.TypePointer:
|
||||
base := base.(*entity.TypePointer)
|
||||
elementType = base.Referenced
|
||||
default:
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use array literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
for index, element := range literal.Elements {
|
||||
element, err := this.analyzeExpression(elementType, strict, element)
|
||||
if err != nil { return nil, err }
|
||||
literal.Elements[index] = element
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralStruct (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralStruct,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
base, ok := ReduceToBase(into).(*entity.TypeStruct)
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use struct literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal, err = this.assembleStructLiteralMap(literal)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
for name, member := range literal.MemberMap {
|
||||
correct, ok := base.MemberMap[name]
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "no member %v.%s",
|
||||
into, name)
|
||||
}
|
||||
|
||||
value, err := this.analyzeExpression(correct.Type(), strict, member.Value)
|
||||
if err != nil { return nil, err }
|
||||
member.Value = value
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralBoolean (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralBoolean,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if !isBoolean(into) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use boolean literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralNil (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralNil,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) assembleStructLiteralMap (
|
||||
literal *entity.LiteralStruct,
|
||||
) (
|
||||
*entity.LiteralStruct,
|
||||
error,
|
||||
) {
|
||||
literal.MemberMap = make(map[string] *entity.Member)
|
||||
literal.MemberOrder = make([]string, len(literal.Members))
|
||||
for index, member := range literal.Members {
|
||||
if previous, exists := literal.MemberMap[member.Name]; exists {
|
||||
return literal, errors.Errorf (
|
||||
member.Position, "%s already listed in struct literal at %v",
|
||||
member.Name, previous.Position)
|
||||
}
|
||||
literal.MemberMap [member.Name] = member
|
||||
literal.MemberOrder[index] = member.Name
|
||||
literal.Members [index] = member
|
||||
}
|
||||
return literal, nil
|
||||
}
|
||||
53
analyzer/literal_test.go
Normal file
53
analyzer/literal_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLiteralStructMemberUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed in struct literal at stream0.fspl:5:3", 7, 3,
|
||||
`
|
||||
Point: (. x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (.
|
||||
x: 5
|
||||
y: 0
|
||||
x: 32)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralStructMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Point: (. x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (.
|
||||
x: 5
|
||||
y: 0
|
||||
z: 32)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralStructNested (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
g:(. x:Int y:(. w:F64 z:F64)) = (.
|
||||
x: 1
|
||||
y: (.
|
||||
w: 1.2
|
||||
z: 78.5))
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralReferenceErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign to string literal", 3, 20,
|
||||
`
|
||||
[main] = {
|
||||
byteptr:*Byte = [@'\n']
|
||||
}
|
||||
`)
|
||||
}
|
||||
178
analyzer/method.go
Normal file
178
analyzer/method.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
|
||||
// analyzeMethod analyzes a method that is directly owned by the specified type.
|
||||
func (this *Tree) analyzeMethod (
|
||||
pos errors.Position,
|
||||
key entity.Key,
|
||||
) (
|
||||
*entity.Method,
|
||||
error,
|
||||
) {
|
||||
// get parent typedef
|
||||
typeKey := key.StripMethod()
|
||||
owner, err := this.analyzeTypedef(pos, typeKey, false)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// return if exists already
|
||||
if method, exists := owner.Methods[key.Method]; exists {
|
||||
return method, nil
|
||||
}
|
||||
|
||||
// error if method is missing
|
||||
method, exists := this.rawMethods[key]
|
||||
if !exists {
|
||||
return nil, errors.Errorf(pos, "no method named %v", key)
|
||||
}
|
||||
|
||||
// set method's unit, very important information yes
|
||||
method.Unit = owner.Unit
|
||||
|
||||
// methods cannot be marked as restricted
|
||||
if method.Acc == entity.AccessRestricted {
|
||||
return nil, errors.Errorf(pos, "cannot mark method as restricted")
|
||||
}
|
||||
|
||||
// create a new scope context for this method
|
||||
this.pushScopeContext(method)
|
||||
this.pushScope(method)
|
||||
defer this.popScopeContext()
|
||||
defer this.popScope()
|
||||
|
||||
// analyze signature and add arguments to root scope of method
|
||||
method.Signature, err = this.assembleSignatureMap(method.Signature)
|
||||
if err != nil { return method, err }
|
||||
for name, argument := range method.Signature.ArgumentMap {
|
||||
argument.Ty, err = this.analyzeType(argument.Ty, false)
|
||||
this.addVariable(argument)
|
||||
method.Signature.ArgumentMap[name] = argument
|
||||
if err != nil { return method, err }
|
||||
}
|
||||
method.Signature.Return, err =
|
||||
this.analyzeType(method.Signature.Return, false)
|
||||
if err != nil { return method, err }
|
||||
|
||||
// add owner to method
|
||||
method.This = &entity.Declaration {
|
||||
Position: method.Position,
|
||||
Name: "this",
|
||||
Ty: &entity.TypePointer {
|
||||
Position: method.Position,
|
||||
Referenced: &entity.TypeNamed {
|
||||
Position: method.Position,
|
||||
Unt: key.Unit,
|
||||
Name: key.Name,
|
||||
Type: owner.Type,
|
||||
},
|
||||
},
|
||||
}
|
||||
this.addVariable(method.This)
|
||||
|
||||
// add incomplete method to complete methods because there is enough
|
||||
// information for it to be complete from the point of view of other
|
||||
// parts of the code
|
||||
owner.Methods[key.Method] = method
|
||||
|
||||
// analyze method body
|
||||
if method.Body != nil {
|
||||
body, err := this.analyzeExpression (
|
||||
method.Signature.Return, strict, method.Body)
|
||||
if err != nil { return nil, err }
|
||||
method.Body = body
|
||||
}
|
||||
return method, nil
|
||||
}
|
||||
|
||||
func (this *Tree) methodExists (pos errors.Position, key entity.Key) (bool, error) {
|
||||
// check raw map
|
||||
_, existsInRaw := this.rawMethods[key]
|
||||
if existsInRaw { return true, nil }
|
||||
|
||||
// check parent typedef
|
||||
typeKey := key.StripMethod()
|
||||
owner, err := this.analyzeTypedef(pos, typeKey, false)
|
||||
if err != nil { return false, err }
|
||||
_, existsInType := owner.Methods[key.Method]
|
||||
return existsInType, nil
|
||||
}
|
||||
|
||||
// analyzeMethodOrBehavior returns *entity.Signature if it found an interface
|
||||
// behavior, and *entity.Method if it found a method.
|
||||
func (this *Tree) analyzeMethodOrBehavior (
|
||||
pos errors.Position,
|
||||
ty entity.Type,
|
||||
name string,
|
||||
) (
|
||||
any,
|
||||
error,
|
||||
) {
|
||||
return this.analyzeMethodOrBehaviorInternal(pos, ty, name, true)
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeMethodOrBehaviorInternal (
|
||||
pos errors.Position,
|
||||
ty entity.Type,
|
||||
name string,
|
||||
pierceReference bool,
|
||||
) (
|
||||
any,
|
||||
error,
|
||||
) {
|
||||
switch ty.(type) {
|
||||
case *entity.TypeNamed:
|
||||
ty := ty.(*entity.TypeNamed)
|
||||
key := entity.Key {
|
||||
Unit: ty.Type.Unit(),
|
||||
Name: ty.Name,
|
||||
Method: name,
|
||||
}
|
||||
exists, err := this.methodExists(pos, key)
|
||||
if err != nil { return nil, err }
|
||||
if exists {
|
||||
method, err := this.analyzeMethod(pos, key)
|
||||
if err != nil { return nil, err }
|
||||
return method, nil
|
||||
} else {
|
||||
return this.analyzeMethodOrBehaviorInternal (
|
||||
pos, ty.Type, name, false)
|
||||
}
|
||||
|
||||
case *entity.TypeInterface:
|
||||
ty := ty.(*entity.TypeInterface)
|
||||
if behavior, ok := ty.BehaviorMap[name]; ok {
|
||||
return behavior, nil
|
||||
} else {
|
||||
return nil, errors.Errorf (
|
||||
pos, "no behavior or method named %s",
|
||||
name)
|
||||
}
|
||||
|
||||
case *entity.TypePointer:
|
||||
if pierceReference {
|
||||
ty := ty.(*entity.TypePointer)
|
||||
return this.analyzeMethodOrBehaviorInternal (
|
||||
pos, ty.Referenced, name, false)
|
||||
} else {
|
||||
return nil, errors.Errorf (
|
||||
pos, "no method named %s defined on this type",
|
||||
name)
|
||||
}
|
||||
|
||||
case *entity.TypeSlice,
|
||||
*entity.TypeArray,
|
||||
*entity.TypeStruct,
|
||||
*entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
|
||||
return nil, errors.Errorf (
|
||||
pos, "no method named %s defined on this type",
|
||||
name)
|
||||
|
||||
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
|
||||
}
|
||||
}
|
||||
69
analyzer/method_test.go
Normal file
69
analyzer/method_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMethodUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"Bird.fly already declared at stream0.fspl:3:1", 4, 1,
|
||||
`
|
||||
Bird: Int
|
||||
Bird.[fly] = { }
|
||||
Bird.[fly distance:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: Int
|
||||
Bird.[fly] = { }
|
||||
Bird.[land] = { }
|
||||
Bird.[walk distance:Int] = { }
|
||||
Bat: Int
|
||||
Bat.[fly] = { }
|
||||
[fly] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodArgumentUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed as argument at stream0.fspl:3:12", 3, 18,
|
||||
`
|
||||
Bird: Int
|
||||
Bird.[main x:Int x:U8 y:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodArgumentUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: Int
|
||||
Bird.[main x:Int y:U8 z:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodThis (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Number: Int
|
||||
Number.[add x:Number]:Number = [++[.this] x]
|
||||
StringHolder: (. string:String)
|
||||
StringHolder.[setString string:String] = {
|
||||
this.string = string
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodChained (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Number: Int
|
||||
|
||||
Number.[add x:Number]:Number = [+ [.this] x]
|
||||
Number.[sub x:Number]:Number = [- [.this] x]
|
||||
Number.[mul x:Number]:Number = [* [.this] x]
|
||||
Number.[div x:Number]:Number = [/ [.this] x]
|
||||
|
||||
[main]: Number = [~Number 5].[add 8].[mul 3]
|
||||
`)
|
||||
}
|
||||
20
analyzer/misc.go
Normal file
20
analyzer/misc.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package analyzer
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) assembleSignatureMap (signature *entity.Signature) (*entity.Signature, error) {
|
||||
signature.ArgumentMap = make(map[string] *entity.Declaration)
|
||||
signature.ArgumentOrder = make([]string, len(signature.Arguments))
|
||||
for index, member := range signature.Arguments {
|
||||
if previous, exists := signature.ArgumentMap[member.Name]; exists {
|
||||
return signature, errors.Errorf (
|
||||
member.Position, "%s already listed as argument at %v",
|
||||
member.Name, previous.Position)
|
||||
}
|
||||
signature.ArgumentMap [member.Name] = member
|
||||
signature.ArgumentOrder[index] = member.Name
|
||||
signature.Arguments [index] = member
|
||||
}
|
||||
return signature, nil
|
||||
}
|
||||
33
analyzer/modules-plan.md
Normal file
33
analyzer/modules-plan.md
Normal file
@@ -0,0 +1,33 @@
|
||||
note to self clean this up afterward and move it into design/modules because
|
||||
this realllllly sucks.
|
||||
|
||||
semantic tree: index top-level declarations by a key:
|
||||
|
||||
Key struct {
|
||||
UUID
|
||||
Name
|
||||
}
|
||||
|
||||
Tree.Analyze should take in:
|
||||
⁃ UUID
|
||||
⁃ Syntax tree
|
||||
|
||||
only analyze one unit at a time.
|
||||
|
||||
parser should have no idea about what a unit is, and UUID fields in top level declarations are to be considered semantic.
|
||||
|
||||
important: in order for the compiler to make sure everything is analyzed, it does this
|
||||
|
||||
module:
|
||||
take in semantic tree, address, skimming book as parameters
|
||||
parse metadata file
|
||||
if currently parsing a unit with this uuid, error
|
||||
push this units uuid onto a stack
|
||||
for each dependency unit, recurse, passing in semantic tree and true to skimming
|
||||
parse unit files into one syntax tree. if skimming, do a skim parse
|
||||
analyze syntax tree into semantic tree
|
||||
pop this units uuid off of the stack
|
||||
|
||||
have a simpler version for source files
|
||||
|
||||
this routine analyzes leaf units (no dependencies) first, which makes them available in the semantic tree for their dependents to depend on. this travels upward the root is reached.
|
||||
219
analyzer/multiunit_test.go
Normal file
219
analyzer/multiunit_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTwoUnit (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[main]:something::X = 5`,
|
||||
|
||||
"something.fspl",
|
||||
`+ X:Int`,
|
||||
)}
|
||||
|
||||
func TestUnitPrivateTypeErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::X is private", 1, 8,
|
||||
`[main]:other::X = 5`,
|
||||
|
||||
"other.fspl",
|
||||
`- X:Int`,
|
||||
)}
|
||||
|
||||
func TestUnitPrivateFunctionErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "function other::[x] is private", 1, 11,
|
||||
`[y]:Int = other::[x]`,
|
||||
|
||||
"other.fspl",
|
||||
`- [x]:Int = 5`,
|
||||
)}
|
||||
|
||||
func TestUnitPrivateMethodErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "method T.[x] is private", 1, 21,
|
||||
`[y]:Int = z:other::T.[x]`,
|
||||
|
||||
"other.fspl",
|
||||
`
|
||||
+ T:Int
|
||||
- T.[x]:Int = 5`,
|
||||
)}
|
||||
|
||||
func TestUnitAssignRestricted (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[main] = {
|
||||
x:other::RestrictedInt
|
||||
y:other::RestrictedInt
|
||||
x = y
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestUnitAssignLiteralRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInt is restricted", 1, 31,
|
||||
`[main]:other::RestrictedInt = 5`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestAssignLiteralRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedInt:Int
|
||||
[main]:RestrictedInt = 5`,
|
||||
)}
|
||||
|
||||
func TestUnitMemberAccessRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedStruct is restricted", 3, 3,
|
||||
`[main] = {
|
||||
x:other::RestrictedStruct
|
||||
x.x = 5
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedStruct:(. x:Int y:Int)`,
|
||||
)}
|
||||
|
||||
func TestMemberAccessRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedStruct:(. x:Int y:Int)
|
||||
[main] = {
|
||||
x:RestrictedStruct
|
||||
x.x = 5
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestUnitSubscriptRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedArr is restricted", 3, 4,
|
||||
`[main] = {
|
||||
x:other::RestrictedArr
|
||||
[.x 0] = 5
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedArr:5:Int`,
|
||||
)}
|
||||
|
||||
func TestSubscriptRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedArr:5:Int
|
||||
[main] = {
|
||||
x:RestrictedArr
|
||||
[.x 0] = 5
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestUnitMathRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInt is restricted", 2, 27,
|
||||
`[main] = {
|
||||
x:other::RestrictedInt = [+
|
||||
y:other::RestrictedInt
|
||||
z:other::RestrictedInt]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestMathRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedInt:Int
|
||||
[main] = {
|
||||
x:RestrictedInt = [+
|
||||
y:RestrictedInt
|
||||
z:RestrictedInt]
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestNestedUnitTypedef (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[main]:layer1::X = 5`,
|
||||
|
||||
"layer0.fspl",
|
||||
`+ X:Int`,
|
||||
|
||||
"layer1.fspl",
|
||||
`+ X:layer0::X`,
|
||||
)}
|
||||
|
||||
func TestUnitBehaviorCallRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInterface is restricted", 3, 3,
|
||||
`[main] = {
|
||||
x:other::RestrictedInterface
|
||||
x.[y]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInterface:(~ [y])`,
|
||||
)}
|
||||
|
||||
func TestBehaviorCallRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`[main] = {
|
||||
x:RestrictedInterface
|
||||
x.[y]
|
||||
}
|
||||
~ RestrictedInterface:(~ [y])`,
|
||||
)}
|
||||
|
||||
func TestUnitCastRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInt is restricted", 2, 16,
|
||||
`[main] = {
|
||||
x:Int = [~Int y:other::RestrictedInt]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestCastRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`[main] = {
|
||||
x:Int = [~Int y:RestrictedInt]
|
||||
}
|
||||
~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestFunctionRestrictedErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot mark function as restricted", 1, 1,
|
||||
`~ [f]`,
|
||||
)}
|
||||
|
||||
func TestMethodRestrictedErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot mark method as restricted", 2, 1,
|
||||
`T:Int
|
||||
~ T.[f]`,
|
||||
)}
|
||||
|
||||
func TestUnitInterfaceSatisfaction (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[sayHello writer:io::Writer] = writer.[write 'well hello their\n']
|
||||
|
||||
[main]: I32 'main' = {
|
||||
stdout:io::File = 1
|
||||
[sayHello stdout]
|
||||
0
|
||||
}`,
|
||||
|
||||
"cstdio.fspl",
|
||||
`+ FileDescriptor: Int
|
||||
+ [write file:FileDescriptor buffer:*Byte count:Index]: Index 'write'`,
|
||||
|
||||
"io.fspl",
|
||||
`+ Writer: (~ [write buffer:*:Byte]: Index)
|
||||
+ File: cstdio::FileDescriptor
|
||||
+ File.[write buffer:*:Byte]:Index =
|
||||
cstdio::[write
|
||||
[~cstdio::FileDescriptor [.this]]
|
||||
[~*Byte buffer] [#buffer]]`,
|
||||
)}
|
||||
@@ -2,141 +2,29 @@ package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFunctionUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
[hello] = { }
|
||||
[hello] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUniqueErrShadowBuiltin (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow builtin true ", 2, 2,
|
||||
`
|
||||
[true]:Bool = false
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[hello] = { }
|
||||
[world] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
hello: *Int
|
||||
hello: (x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUniqueErrShadowBuiltin (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow builtin true ", 2, 2,
|
||||
`
|
||||
true:Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
hello: *Int
|
||||
world: (x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no type named example", 2, 20,
|
||||
`
|
||||
[main] = { example:Example }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Example: Int
|
||||
AllBuiltin: (
|
||||
int: Int
|
||||
uint: UInt
|
||||
byte: Byte
|
||||
rune: Rune
|
||||
string: String
|
||||
i8: I8
|
||||
i8: I16
|
||||
i8: I32
|
||||
i8: I64
|
||||
u8: U8
|
||||
u8: U16
|
||||
u8: U32
|
||||
u8: U64
|
||||
f32: F32
|
||||
f64: F64
|
||||
bool: Bool)
|
||||
[main] = {
|
||||
example:Example
|
||||
allBuiltin:AllBuiltin
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
hello: *Int
|
||||
[hello] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
hello: *Int
|
||||
[world] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"Bird.fly already declared at stream0.fspl:3:1", 4, 1,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[fly] = { }
|
||||
Bird::[fly distance:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[fly] = { }
|
||||
Bird::[land] = { }
|
||||
Bird::[walk distance:Int] = { }
|
||||
Bat: Int
|
||||
Bat::[fly] = { }
|
||||
[fly] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method Type::world", 5, 3,
|
||||
"no method named world defined on this type", 6, 10,
|
||||
`
|
||||
Type: Int
|
||||
Type::[something] = { }
|
||||
Type.[something] = { }
|
||||
[main] = {
|
||||
instance:Type
|
||||
[instance::world]
|
||||
instance.[world]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodNameErrBadLayer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named something defined on this type", 7, 10,
|
||||
`
|
||||
Type: Int
|
||||
Type.[something] = { }
|
||||
RefType: *Type
|
||||
[main] = {
|
||||
instance:RefType
|
||||
instance.[something]
|
||||
}
|
||||
`)
|
||||
}
|
||||
@@ -145,90 +33,16 @@ func TestMethodName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Type: Int
|
||||
Type::[world] = { }
|
||||
Type.[world] = { }
|
||||
[main] = {
|
||||
instance:Type
|
||||
[instance::world]
|
||||
instance.[world]
|
||||
ref:*Type = [@ instance]
|
||||
ref.[world]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"fly already listed in interface at stream0.fspl:2:8", 2, 14,
|
||||
`
|
||||
Bird: ([fly] [fly])
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no behavior Bird::swim", 8, 3,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl::[fly] = { }
|
||||
BirdImpl::[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = [@impl:BirdImpl]
|
||||
[bird::swim]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl::[fly] = { }
|
||||
BirdImpl::[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = [@impl:BirdImpl]
|
||||
[bird::fly]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionArgumentUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already declared in block at stream0.fspl:2:8", 2, 14,
|
||||
`
|
||||
[main x:Int x:U8 y:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionArgumentUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main x:Int y:U8 z:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodArgumentUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already declared in block at stream0.fspl:3:13", 3, 19,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[main x:Int x:U8 y:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodArgumentUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[main x:Int y:U8 z:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already declared in block at stream0.fspl:3:2", 5, 2,
|
||||
@@ -241,20 +55,10 @@ testStringErr (test,
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableUniqueErrShadowBuiltin (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow builtin false ", 3, 2,
|
||||
`
|
||||
[main] = {
|
||||
false:Bool = true
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
x: Int
|
||||
[x] = { }
|
||||
[y] = { }
|
||||
[main x:Int] = {
|
||||
x:Int
|
||||
@@ -268,20 +72,9 @@ x: Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableNameErrType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"named type example cannot be used as location expression", 4, 2,
|
||||
`
|
||||
example: Int
|
||||
[main] = {
|
||||
example = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableNameErrFunction (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"function example cannot be used as location expression", 4, 2,
|
||||
"no variable named example", 4, 2,
|
||||
`
|
||||
[example] = { }
|
||||
[main] = {
|
||||
@@ -292,7 +85,7 @@ testStringErr (test,
|
||||
|
||||
func TestVariableNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no variable named example", 4, 2,
|
||||
"no variable named example", 3, 2,
|
||||
`
|
||||
[main] = {
|
||||
example = 5
|
||||
@@ -328,96 +121,13 @@ testString (test,
|
||||
example = 3
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed in struct at stream0.fspl:3:2", 6, 2,
|
||||
`
|
||||
Bird: (
|
||||
x:Int
|
||||
y:Int
|
||||
z:Int
|
||||
x:U8)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (
|
||||
x:Int
|
||||
y:Int
|
||||
z:Int
|
||||
a:U8)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no member Type.world", 5, 2,
|
||||
`
|
||||
Type: (something:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Type: (world:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructLiteralMemberUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed in struct literal at stream0.fspl:5:2", 7, 2,
|
||||
`
|
||||
Point: (x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (
|
||||
x: 5
|
||||
y: 0
|
||||
x: 32)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructLiteralMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Point: (x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (
|
||||
x: 5
|
||||
y: 0
|
||||
z: 32)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionNameErrType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot call named type example", 3, 3,
|
||||
`
|
||||
example: Int
|
||||
[main] = [example]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionNameErrVar (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot call example:Int", 4, 3,
|
||||
"no function named example", 4, 2,
|
||||
`
|
||||
[main] = {
|
||||
example:Int
|
||||
@@ -428,7 +138,7 @@ testStringErr (test,
|
||||
|
||||
func TestFunctionNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no function named example", 2, 3,
|
||||
"no function named example", 2, 10,
|
||||
`
|
||||
[main] = [example]
|
||||
`)
|
||||
@@ -441,3 +151,55 @@ testString (test,
|
||||
[main] = [example]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceBehaviorNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no behavior or method named swim", 8, 6,
|
||||
`
|
||||
Bird: (~ [fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl.[fly] = { }
|
||||
BirdImpl.[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = impl:BirdImpl
|
||||
bird.[swim]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceBehaviorName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (~ [fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl.[fly] = { }
|
||||
BirdImpl.[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = impl:BirdImpl
|
||||
bird.[fly]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no member instance.world", 5, 10,
|
||||
`
|
||||
Type: (. something:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Type: (. world:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -2,112 +2,82 @@ package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOperatorModUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationModUnderArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for %", 10, 2,
|
||||
"wrong argument count for %", 2, 10,
|
||||
`
|
||||
[main] = [% 2]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorModOverArgCountErr (test *testing.T) {
|
||||
func TestOperationModOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for %", 10, 2,
|
||||
"wrong argument count for %", 2, 10,
|
||||
`
|
||||
[main] = [% 2 3 1]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorLogicalNegationUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationLogicalNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !", 10, 2,
|
||||
`
|
||||
[main] = [!]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorLogicalNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !", 10, 2,
|
||||
"wrong argument count for !", 2, 10,
|
||||
`
|
||||
[main] = [! 348 92]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitwiseNegationUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationBitwiseNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !!", 10, 2,
|
||||
`
|
||||
[main] = [!!]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitwiseNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !!", 10, 2,
|
||||
"wrong argument count for !!", 2, 10,
|
||||
`
|
||||
[main] = [!! 348 92]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftLeftUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationBitShiftLeftOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for <<", 10, 2,
|
||||
`
|
||||
[main] = [<<]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftLeftOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for <<", 10, 2,
|
||||
"wrong argument count for <<", 2, 10,
|
||||
`
|
||||
[main] = [<< 348 92 324]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftRightUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationBitShiftRightOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for >>", 10, 2,
|
||||
`
|
||||
[main] = [>>]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftRightOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for >>", 10, 2,
|
||||
"wrong argument count for >>", 2, 10,
|
||||
`
|
||||
[main] = [>> 348 92 324]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorArgCount (test *testing.T) {
|
||||
func TestOperationArgCount (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
[+ 1 2 3 4]
|
||||
[++ 1]
|
||||
[- 1 2 3 4]
|
||||
[-- 1]
|
||||
[* 5 6]
|
||||
[/ 32 16]
|
||||
[% 5 6]
|
||||
[!! true]
|
||||
[|| false]
|
||||
[&& true true false true]
|
||||
[^^ true true false true]
|
||||
[! 1]
|
||||
[| 1 2]
|
||||
[& 3 1]
|
||||
[^ 1 2 3 4]
|
||||
[<< 1 8]
|
||||
[>> 8 1]
|
||||
[< 1 2 3 4]
|
||||
[> 1 2 3 4]
|
||||
[<= 10 5 3]
|
||||
[>= 1 2 3 3 4]
|
||||
[= 2 2 2 3 9]
|
||||
x:Int
|
||||
b:Bool
|
||||
x = [+ 1 2 3 4]
|
||||
x = [++ 1]
|
||||
x = [- 1 2 3 4]
|
||||
x = [-- 1]
|
||||
x = [* 5 6]
|
||||
x = [/ 32 16]
|
||||
x = [% 5 6]
|
||||
x = [!! 1]
|
||||
x = [|| 1 2]
|
||||
x = [&& 3 1 5]
|
||||
x = [^^ 1 2 3 4]
|
||||
b = [! true]
|
||||
b = [| true false]
|
||||
b = [& true true]
|
||||
b = [^ true true false true]
|
||||
x = [<< 1 8]
|
||||
x = [>> 8 1]
|
||||
b = [< x 2 3 4]
|
||||
b = [> x 2 3 4]
|
||||
b = [<= x 5 3]
|
||||
b = [>= x 2 3 3 4]
|
||||
b = [= x 2 2 3 9]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
132
analyzer/scope.go
Normal file
132
analyzer/scope.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package analyzer
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
// scopeContextManager is a stack of scopeContexts allowing multiple stacks of
|
||||
// scopes to be managed at the same time in case the analysis of one scoped
|
||||
// entity causes the analysis of another.
|
||||
type scopeContextManager []scopeContext
|
||||
|
||||
func (this *scopeContextManager) pushScopeContext (declaration entity.TopLevel) {
|
||||
*this = append(*this, scopeContext { declaration: declaration })
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) popScopeContext () {
|
||||
this.assertPopulated()
|
||||
*this = (*this)[:len(*this) - 1]
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) pushLoop (loop *entity.Loop) {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].pushLoop(loop)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) popLoop () {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].popLoop()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) topLoop () (*entity.Loop, bool) {
|
||||
this.assertPopulated()
|
||||
return (*this)[len(*this) - 1].topLoop()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) topDeclaration () (entity.TopLevel, bool) {
|
||||
if len(*this) < 1 { return nil, false }
|
||||
return (*this)[len(*this) - 1].declaration, true
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) pushScope (scope entity.Scoped) {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].pushScope(scope)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) popScope () {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].popScope()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) topScope () (entity.Scoped, bool) {
|
||||
this.assertPopulated()
|
||||
return (*this)[len(*this) - 1].topScope()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) variable (name string) *entity.Declaration {
|
||||
this.assertPopulated()
|
||||
return (*this)[len(*this) - 1].variable(name)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) addVariable (declaration *entity.Declaration) {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].addVariable(declaration)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) assertPopulated () {
|
||||
if len(*this) < 1 {
|
||||
panic("scopeContextManager must have at least 1 scope context")
|
||||
}
|
||||
}
|
||||
|
||||
// scopeContext is a stack of scopes and loops used when analyzing scoped
|
||||
// entities.
|
||||
type scopeContext struct {
|
||||
scopes []entity.Scoped
|
||||
loops []*entity.Loop
|
||||
declaration entity.TopLevel
|
||||
}
|
||||
|
||||
func (this *scopeContext) pushLoop (loop *entity.Loop) {
|
||||
this.loops = append(this.loops, loop)
|
||||
}
|
||||
|
||||
func (this *scopeContext) popLoop () {
|
||||
this.assertLoopPopulated()
|
||||
this.loops = this.loops[:len(this.loops) - 1]
|
||||
}
|
||||
|
||||
func (this *scopeContext) topLoop () (*entity.Loop, bool) {
|
||||
if len(this.loops) < 1 { return nil, false }
|
||||
return this.loops[len(this.loops) - 1], true
|
||||
}
|
||||
|
||||
func (this *scopeContext) pushScope (scope entity.Scoped) {
|
||||
this.scopes = append(this.scopes, scope)
|
||||
}
|
||||
|
||||
func (this *scopeContext) popScope () {
|
||||
this.assertScopePopulated()
|
||||
this.scopes = this.scopes[:len(this.scopes) - 1]
|
||||
}
|
||||
|
||||
func (this *scopeContext) topScope () (entity.Scoped, bool) {
|
||||
if len(this.scopes) < 1 { return nil, false }
|
||||
return this.scopes[len(this.scopes) - 1], true
|
||||
}
|
||||
|
||||
func (this *scopeContext) variable (name string) *entity.Declaration {
|
||||
this.assertScopePopulated()
|
||||
for index := len(this.scopes) - 1; index >= 0; index -- {
|
||||
variable := this.scopes[index].Variable(name)
|
||||
if variable != nil {
|
||||
return variable
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *scopeContext) addVariable (declaration *entity.Declaration) {
|
||||
this.assertScopePopulated()
|
||||
this.scopes[len(this.scopes) - 1].AddVariable(declaration)
|
||||
}
|
||||
|
||||
func (this *scopeContext) assertScopePopulated () {
|
||||
if len(this.scopes) < 1 {
|
||||
panic("scopeContext must have at least 1 scope")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *scopeContext) assertLoopPopulated () {
|
||||
if len(this.scopes) < 1 {
|
||||
panic("scopeContext must have at least 1 loop")
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,199 @@
|
||||
package analyzer
|
||||
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "testing"
|
||||
import "strings"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/parser"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/lexer"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/fspl"
|
||||
|
||||
func testStringErr (
|
||||
test *testing.T,
|
||||
errMessage string,
|
||||
errLine int,
|
||||
errColumn int,
|
||||
errRow int,
|
||||
errStart int,
|
||||
input string,
|
||||
) {
|
||||
testReaderErr (
|
||||
test,
|
||||
errMessage, errLine, errColumn,
|
||||
strings.NewReader(input))
|
||||
}
|
||||
|
||||
func testReaderErr (
|
||||
test *testing.T,
|
||||
errMessage string,
|
||||
errLine int,
|
||||
errColumn int,
|
||||
inputs ...io.Reader,
|
||||
) {
|
||||
ast := parser.Tree { }
|
||||
for index, stream := range inputs {
|
||||
err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
|
||||
if err != nil {
|
||||
test.Error("parser returned error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
address := entity.Address("stream0.fspl")
|
||||
ast, ok := treeOf(test, address, input, false)
|
||||
if !ok { return }
|
||||
|
||||
tree := Tree { }
|
||||
err := tree.Analyze(ast)
|
||||
err := tree.Analyze(address.UUID(), nil, ast)
|
||||
if err == nil {
|
||||
test.Error("analyzer did not return error")
|
||||
return
|
||||
}
|
||||
got := err.(participle.Error)
|
||||
gotMessage := got.Message()
|
||||
gotLine := got.Position().Line
|
||||
gotColumn := got.Position().Column
|
||||
correct :=
|
||||
gotMessage == errMessage &&
|
||||
gotLine == errLine &&
|
||||
gotColumn == errColumn
|
||||
if !correct {
|
||||
test.Log("errors do not match")
|
||||
test.Logf("got:\n%v:%v: %v", gotLine, gotColumn, gotMessage)
|
||||
test.Logf("correct:\n%v:%v: %v", errLine, errColumn, errMessage)
|
||||
test.Fail()
|
||||
return
|
||||
}
|
||||
compareErr(test, address, string(address), errMessage, errRow, errStart, err)
|
||||
}
|
||||
|
||||
func testString (test *testing.T, input string) {
|
||||
testReader(test, strings.NewReader(input))
|
||||
}
|
||||
|
||||
func testReader (test *testing.T, inputs ...io.Reader) {
|
||||
ast := parser.Tree { }
|
||||
for index, stream := range inputs {
|
||||
err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
|
||||
if err != nil {
|
||||
test.Error("parser returned error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
address := entity.Address("stream0.fspl")
|
||||
ast, ok := treeOf(test, address, input, false)
|
||||
if !ok { return }
|
||||
|
||||
tree := Tree { }
|
||||
err := tree.Analyze(ast)
|
||||
err := tree.Analyze(address.UUID(), nil, ast)
|
||||
if err != nil {
|
||||
test.Error("analyzer returned error: ", err)
|
||||
test.Error("analyzer returned error:\n" + errors.Format(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testUnits (
|
||||
test *testing.T,
|
||||
main string,
|
||||
dependencies ...string,
|
||||
) {
|
||||
if len(dependencies) % 2 != 0 {
|
||||
panic("dependencies len must be even. forget an address?")
|
||||
}
|
||||
tree := Tree { }
|
||||
nicknames := make(map[string] uuid.UUID)
|
||||
|
||||
// dependencies
|
||||
for index := 0; index < len(dependencies) - 1; index += 2 {
|
||||
address := entity.Address(dependencies[index])
|
||||
source := dependencies[index + 1]
|
||||
ast, ok := treeOf(test, address, source, true)
|
||||
if !ok { return }
|
||||
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
test.Error("analyzer returned error:\n" + errors.Format(err))
|
||||
return
|
||||
}
|
||||
|
||||
unit := address.UUID()
|
||||
nickname, ok := address.Nickname()
|
||||
if !ok {
|
||||
test.Errorf("could not generate nickname for %v", address)
|
||||
return
|
||||
}
|
||||
nicknames[nickname] = unit
|
||||
}
|
||||
|
||||
// main
|
||||
address := entity.Address("main.fspl")
|
||||
ast, ok := treeOf(test, address, main, false)
|
||||
if !ok { return }
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
test.Error("analyzer returned error:\n" + errors.Format(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testUnitsErr (
|
||||
test *testing.T,
|
||||
errFile string,
|
||||
errMessage string,
|
||||
errRow int,
|
||||
errStart int,
|
||||
|
||||
main string,
|
||||
dependencies ...string,
|
||||
) {
|
||||
if len(dependencies) % 2 != 0 {
|
||||
panic("dependencies len must be even. forget an address?")
|
||||
}
|
||||
tree := Tree { }
|
||||
nicknames := make(map[string] uuid.UUID)
|
||||
|
||||
// dependencies
|
||||
for index := 0; index < len(dependencies) - 1; index += 2 {
|
||||
address := entity.Address(dependencies[index])
|
||||
source := dependencies[index + 1]
|
||||
ast, ok := treeOf(test, address, source, true)
|
||||
if !ok { return }
|
||||
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
compareErr(test, address, errFile, errMessage, errRow, errStart, err)
|
||||
return
|
||||
}
|
||||
|
||||
unit := address.UUID()
|
||||
nickname, ok := address.Nickname()
|
||||
if !ok {
|
||||
test.Errorf("could not generate nickname for %v", address)
|
||||
return
|
||||
}
|
||||
nicknames[nickname] = unit
|
||||
}
|
||||
|
||||
// main
|
||||
address := entity.Address("main.fspl")
|
||||
ast, ok := treeOf(test, address, main, false)
|
||||
if !ok { return }
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
compareErr(test, address, errFile, errMessage, errRow, errStart, err)
|
||||
return
|
||||
}
|
||||
|
||||
test.Error("analyzer did not return error")
|
||||
}
|
||||
|
||||
func treeOf (test *testing.T, address entity.Address, input string, skim bool) (fsplParser.Tree, bool) {
|
||||
ast := fsplParser.Tree { }
|
||||
lx, err := lexer.LexReader (
|
||||
string(address),
|
||||
strings.NewReader(input))
|
||||
if err != nil {
|
||||
test.Error("lexer returned error:\n" + errors.Format(err))
|
||||
return ast, false
|
||||
}
|
||||
if skim {
|
||||
err = ast.Skim(lx)
|
||||
} else {
|
||||
err = ast.Parse(lx)
|
||||
}
|
||||
if err != nil {
|
||||
test.Error("parser returned error:\n" + errors.Format(err))
|
||||
return ast, false
|
||||
}
|
||||
|
||||
return ast, true
|
||||
}
|
||||
|
||||
func compareErr (
|
||||
test *testing.T,
|
||||
address entity.Address,
|
||||
errFile string,
|
||||
errMessage string,
|
||||
errRow int,
|
||||
errStart int,
|
||||
err error,
|
||||
) bool {
|
||||
got := err.(errors.Error)
|
||||
gotMessage := got.Error()
|
||||
gotFile := got.Position().File
|
||||
gotRow := got.Position().Row + 1
|
||||
gotStart := got.Position().Start + 1
|
||||
|
||||
correct :=
|
||||
gotFile == errFile &&
|
||||
gotMessage == errMessage &&
|
||||
gotRow == errRow &&
|
||||
gotStart == errStart
|
||||
if correct { return true }
|
||||
|
||||
test.Log("errors do not match:")
|
||||
if gotMessage != errMessage {
|
||||
test.Log("- messages do not match:")
|
||||
test.Logf(" [%s]", gotMessage)
|
||||
test.Logf(" [%s]", errMessage)
|
||||
}
|
||||
if gotRow != errRow {
|
||||
test.Logf("- rows do not match: (%d, %d)", gotRow, errRow)
|
||||
}
|
||||
if gotStart != errStart {
|
||||
test.Logf("- columns do not match: (%d, %d)", gotStart, errStart)
|
||||
}
|
||||
test.Log("got:\n" + errors.Format(got))
|
||||
test.Logf("correct:\n%v:%v: %v", errRow, errStart, errMessage)
|
||||
test.Fail()
|
||||
return false
|
||||
}
|
||||
|
||||
192
analyzer/tree.go
192
analyzer/tree.go
@@ -1,27 +1,39 @@
|
||||
package analyzer
|
||||
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/parser"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/fspl"
|
||||
|
||||
// Tree is a semantic tree. It contains the same constructs as the syntax tree,
|
||||
// but with their semantic information filled in.
|
||||
type Tree struct {
|
||||
rawTypes map[string] *entity.Typedef
|
||||
rawFunctions map[string] *entity.Function
|
||||
rawMethods map[string] *entity.Method
|
||||
ast parser.Tree
|
||||
Types map[entity.Key] *entity.Typedef
|
||||
Functions map[entity.Key] *entity.Function
|
||||
|
||||
Types map[string] entity.Type
|
||||
Functions map[string] *entity.Function
|
||||
unit uuid.UUID
|
||||
ast fsplParser.Tree
|
||||
nicknames map[string] uuid.UUID
|
||||
rawTypes map[entity.Key] *entity.Typedef
|
||||
rawFunctions map[entity.Key] *entity.Function
|
||||
rawMethods map[entity.Key] *entity.Method
|
||||
|
||||
scopeContextManager
|
||||
incompleteTypes map[entity.Key] *entity.Typedef
|
||||
}
|
||||
|
||||
// Analyze takes in an AST and analyzes its contents within the context of this
|
||||
// semantic tree. Analyzed entities will be added to the tree.
|
||||
func (this *Tree) Analyze (ast parser.Tree) error {
|
||||
func (this *Tree) Analyze (
|
||||
unit uuid.UUID,
|
||||
nicknames map[string] uuid.UUID,
|
||||
ast fsplParser.Tree,
|
||||
) error {
|
||||
this.ensure()
|
||||
this.ast = ast
|
||||
this.resetRawMaps()
|
||||
this.ast = ast
|
||||
this.unit = unit
|
||||
this.nicknames = nicknames
|
||||
err := this.assembleRawMaps()
|
||||
if err != nil { return err }
|
||||
return this.analyzeDeclarations()
|
||||
@@ -29,72 +41,142 @@ func (this *Tree) Analyze (ast parser.Tree) error {
|
||||
|
||||
func (this *Tree) assembleRawMaps () error {
|
||||
for _, declaration := range this.ast.Declarations {
|
||||
switch declaration.(type) {
|
||||
switch declaration := declaration.(type) {
|
||||
case *entity.Typedef:
|
||||
ty := declaration.(*entity.Typedef)
|
||||
err := this.rawNameUnique(ty.Pos, ty.Name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position, key)
|
||||
if err != nil { return err }
|
||||
this.rawTypes[ty.Name] = ty
|
||||
declaration.Methods = make(map[string] *entity.Method)
|
||||
this.rawTypes[key] = declaration
|
||||
|
||||
case *entity.Function:
|
||||
function := declaration.(*entity.Function)
|
||||
err := this.rawNameUnique (
|
||||
function.Pos,
|
||||
function.Signature.Name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.Signature.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position, key)
|
||||
if err != nil { return err }
|
||||
this.rawFunctions[function.Signature.Name] = function
|
||||
this.rawFunctions[key] = declaration
|
||||
|
||||
case *entity.Method:
|
||||
method := declaration.(*entity.Method)
|
||||
name := method.TypeName + "." + method.Signature.Name
|
||||
err := this.rawNameUnique(method.Pos, name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.TypeName,
|
||||
Method: declaration.Signature.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position, key)
|
||||
if err != nil { return err }
|
||||
this.rawMethods[name] = method
|
||||
this.rawMethods[key] = declaration
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) rawNameUnique (currentPos lexer.Position, name string) error {
|
||||
var pos lexer.Position
|
||||
var unique = true
|
||||
|
||||
if ty, isType := this.rawTypes[name]; isType {
|
||||
pos = ty.Pos
|
||||
unique = false
|
||||
func (this *Tree) topLevelNameAvailable (currentPos errors.Position, key entity.Key) error {
|
||||
if _, isPrimitive := primitiveTypes[key.Name]; isPrimitive {
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow primitive %s",
|
||||
key.Name)
|
||||
}
|
||||
if function, isFunction := this.rawFunctions[name]; isFunction {
|
||||
pos = function.Pos
|
||||
unique = false
|
||||
if _, isBuiltin := builtinTypes[key.Name]; isBuiltin {
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow builtin %s",
|
||||
key.Name)
|
||||
}
|
||||
if method, isMethod := this.rawMethods[name]; isMethod {
|
||||
pos = method.Pos
|
||||
unique = false
|
||||
}
|
||||
|
||||
if unique {
|
||||
return nil
|
||||
} else {
|
||||
return participle.Errorf (
|
||||
if ty, isType := this.rawTypes[key]; isType {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
name, pos)
|
||||
key.Name, ty.Position)
|
||||
}
|
||||
if function, isFunction := this.rawFunctions[key]; isFunction {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
key.Name, function.Position)
|
||||
}
|
||||
if method, isMethod := this.rawMethods[key]; isMethod {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s.%s already declared at %v",
|
||||
key.Name, key.Method, method.Position)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDeclarations () error {
|
||||
for name := range this.rawTypes {
|
||||
ty, err := this.analyzeTypedef(name)
|
||||
for key, rawType := range this.rawTypes {
|
||||
ty, err := this.analyzeTypedef(rawType.Position, key, false)
|
||||
if err != nil { return err }
|
||||
this.Types[key] = ty
|
||||
}
|
||||
for key, rawFunction := range this.rawFunctions {
|
||||
_, err := this.analyzeFunction(rawFunction.Position, key)
|
||||
if err != nil { return err }
|
||||
}
|
||||
for _, rawMethod := range this.rawMethods {
|
||||
_, err := this.analyzeMethod (
|
||||
rawMethod.Position,
|
||||
entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: rawMethod.TypeName,
|
||||
Method: rawMethod.Signature.Name,
|
||||
})
|
||||
if err != nil { return err }
|
||||
this.Types[name] = ty
|
||||
}
|
||||
// TODO functions
|
||||
// TODO methods
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) resolveNickname (pos errors.Position, nickname string) (uuid.UUID, error) {
|
||||
if nickname == "" { return this.unit, nil }
|
||||
|
||||
fail := func () (uuid.UUID, error) {
|
||||
return uuid.UUID { }, errors.Errorf (
|
||||
pos, "no unit named %s",
|
||||
nickname)
|
||||
}
|
||||
|
||||
if this.nicknames == nil { return fail() }
|
||||
unit, ok := this.nicknames[nickname]
|
||||
if !ok { return fail() }
|
||||
|
||||
return unit, nil
|
||||
}
|
||||
|
||||
func (this *Tree) ensure () {
|
||||
if this.rawTypes != nil { return }
|
||||
this.rawTypes = make(map[string] *entity.Typedef)
|
||||
this.rawFunctions = make(map[string] *entity.Function)
|
||||
this.rawMethods = make(map[string] *entity.Method)
|
||||
this.Types = make(map[string] entity.Type)
|
||||
this.Functions = make(map[string] *entity.Function)
|
||||
this.resetRawMaps()
|
||||
this.incompleteTypes = make(map[entity.Key] *entity.Typedef)
|
||||
this.Types = make(map[entity.Key] *entity.Typedef)
|
||||
this.Functions = make(map[entity.Key] *entity.Function)
|
||||
|
||||
for name, ty := range builtinTypes {
|
||||
// builtin types have a zero UUID in their key
|
||||
this.Types[entity.Key { Name: name }] = &entity.Typedef {
|
||||
Acc: entity.AccessPublic,
|
||||
Name: name,
|
||||
Type: ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Tree) resetRawMaps () {
|
||||
if this.rawTypes != nil &&
|
||||
this.rawFunctions != nil &&
|
||||
this.rawMethods != nil {
|
||||
|
||||
if len(this.rawTypes) > 0 {
|
||||
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
||||
}
|
||||
if len(this.rawFunctions) > 0 {
|
||||
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
||||
}
|
||||
if len(this.rawMethods) > 0 {
|
||||
this.rawMethods = make(map[entity.Key] *entity.Method)
|
||||
}
|
||||
} else {
|
||||
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
||||
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
||||
this.rawMethods = make(map[entity.Key] *entity.Method)
|
||||
}
|
||||
}
|
||||
|
||||
290
analyzer/type.go
290
analyzer/type.go
@@ -1,100 +1,278 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeTypedef (pos lexer.Position, name string) (entity.Type, error) {
|
||||
if ty, exists := this.Types[name]; exists {
|
||||
return ty
|
||||
func (this *Tree) analyzeTypedef (
|
||||
pos errors.Position,
|
||||
key entity.Key,
|
||||
acceptIncomplete bool,
|
||||
) (
|
||||
*entity.Typedef,
|
||||
error,
|
||||
) {
|
||||
// return a builtin if it exists (search types with zero uuid)
|
||||
if definition, exists := this.Types[entity.Key { Name: key.Name }]; exists {
|
||||
return definition, nil
|
||||
}
|
||||
|
||||
// return if exists already
|
||||
if definition, exists := this.Types[key]; exists {
|
||||
return definition, nil
|
||||
}
|
||||
|
||||
definition, exists := this.RawTypes[name]
|
||||
if !exists {
|
||||
return participle.Errorf(pos, "no type named %s", name)
|
||||
// check if incomplete
|
||||
if definition, incomplete := this. incompleteTypes[key]; incomplete {
|
||||
if acceptIncomplete {
|
||||
return definition, nil
|
||||
} else {
|
||||
return nil, errors.Errorf (
|
||||
pos, "type %s cannot be used in this context",
|
||||
key.Name)
|
||||
}
|
||||
}
|
||||
return this.analyzeType(definition.Type)
|
||||
|
||||
// analyze if it still needs to be analyzed
|
||||
if definition, exists := this.rawTypes[key]; exists {
|
||||
// update unit
|
||||
definition.Unit = key.Unit
|
||||
|
||||
// analyze
|
||||
var err error
|
||||
definition.Type, err = this.analyzeTypeInternal (
|
||||
definition.Type, definition, false)
|
||||
return definition, err
|
||||
}
|
||||
|
||||
// if we couldn't get the type, error
|
||||
return nil, errors.Errorf(pos, "no type named %s", key.Name)
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeType (ty entity.Type) (entity.Type, error) {
|
||||
if ty == entity.Void() { return ty }
|
||||
var err error
|
||||
func (this *Tree) analyzeType (
|
||||
ty entity.Type,
|
||||
| ||||