Compare commits
728 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f19292366 | |||
| 3bc504aae6 | |||
| 1085cfdc3c | |||
| ecd9f79aae | |||
| c934a6c76c | |||
| 018dc31d04 | |||
| 4b90ba77d3 | |||
| 2a6f086350 | |||
| 767e7bd33c | |||
| 6c41e71ade | |||
| 423f3ba22f | |||
| 08eedaf74d | |||
| 88e10158ae | |||
| 93d4aea1b1 | |||
| 810175b6c9 | |||
| b6b0a1e592 | |||
| 00110039e2 | |||
| 15e418d8c1 | |||
| 1c61235b63 | |||
| 4e6103418c | |||
| e805060370 | |||
| eccb2e9a5b | |||
| e574b248e5 | |||
| 43ed297a12 | |||
| 76615df4d4 | |||
| 2caed5b4ae | |||
| cd396952f2 | |||
| 0404202691 | |||
| 92ad52b2aa | |||
| 10246f7268 | |||
| 2edda8a960 | |||
| 11e7a83eb4 | |||
| 9b45bc56d4 | |||
| b9e0b3265e | |||
| 48fa712cb7 | |||
| cad6a0759a | |||
| 6c7fcf6045 | |||
| eb2c1c8aed | |||
| 78ea6e45b2 | |||
| ebafbc2a38 | |||
| 650289f03e | |||
| 5dda7b2186 | |||
| 4931f97496 | |||
| f3bdfef5c5 | |||
| a50a5febb9 | |||
| 6916b3b7b1 | |||
| d8f82d3646 | |||
| e27fb9e79b | |||
| 1f04c3a593 | |||
| 7e05785c09 | |||
| dcad5a2d18 | |||
| 3cd3384006 | |||
| 7f58f7da8b | |||
| 4342c15e38 | |||
| 18e7850b58 | |||
| 70c58f74de | |||
| 185ef7f6f9 | |||
| f3c0901493 | |||
| f84ddf8cad | |||
| cd08801a29 | |||
| 84e21b3832 | |||
| 648b1df547 | |||
| a4443444c1 | |||
| a647f27e8c | |||
| ecd6eba434 | |||
| 50f088842a | |||
| 491a9b2369 | |||
| a9adc77658 | |||
| 16686bfa3d | |||
| 7cf770df7f | |||
| 909d463637 | |||
| 9b1f6d695c | |||
| 838c34b0a1 | |||
| 7d74e59e64 | |||
| c0476f14ae | |||
| bb1886ab9c | |||
| 197373ee27 | |||
| c576c4022e | |||
| 67a8b67dec | |||
| 8d69e4cdd3 | |||
| ec873ea895 | |||
| 45ab5d95a2 | |||
| bb2caeb88a | |||
| b07dbf8eba | |||
| babcce643f | |||
| 6be48aea28 | |||
| f58c01be52 | |||
| 4df7a8905e | |||
| 2be41be609 | |||
| 0a56a61f4f | |||
| fc2fb42e53 | |||
| 9335841fc0 | |||
| e35a576022 | |||
| 1c9700378c | |||
| 56b91d788f | |||
| fc6efafc34 | |||
| a147143fc3 | |||
| a8fd79991c | |||
| a9c85bf017 | |||
| db98e590fc | |||
| e0d4be8db8 | |||
| 0ea05e5836 | |||
| d2c8fdd05c | |||
| 1565376418 | |||
| 566bf41843 | |||
| 45f46b04b0 | |||
| be08308cb2 | |||
| 4f31b712df | |||
| 8607f08093 | |||
| ff99c37219 | |||
| e889a9c49b | |||
| ebc4aacf5b | |||
| 6643c92948 | |||
| 4df8a45a16 | |||
| 3d6a258873 | |||
| 6a9be94601 | |||
| bf62730e79 | |||
| 040be67476 | |||
| 6cd51f3c5f | |||
| ab24c1cfc1 | |||
| e94d438332 | |||
| ef947b8dc6 | |||
| 41116e0b13 | |||
| 98d8edc319 | |||
| ff749a03dd | |||
| daea6cb22a | |||
| 54f69ca786 | |||
| 779aaf3cea | |||
| 8ec7929c1e | |||
| fd8a15db66 | |||
| b1cef5d95f | |||
| 2a6778837f | |||
| 8fa9a579fd | |||
| ce79e16de7 | |||
| cd91f9dc20 | |||
| 24268e498e | |||
| a6dabc27f9 | |||
| 459020126d | |||
| 3a1d9632dc | |||
| 8beb785009 | |||
| 66633be90c | |||
| bc88782938 | |||
| e30f2a74da | |||
| 0e2259bb45 | |||
| f94c562896 | |||
| fb7558576e | |||
| 8d611b6fa6 | |||
| c17a381ba5 | |||
| c45d0215ba | |||
| 0135972e14 | |||
| 24fa83b004 | |||
| b40953a0ec | |||
| cde0eec9aa | |||
| 096da5bdfd | |||
| fe14072392 | |||
| 0947a8de44 | |||
| 278a243804 | |||
| 005b2fcd56 | |||
| 5e93a7afd1 | |||
| c22ac87cd0 | |||
| 8ae6ea3626 | |||
| 8e3b21f807 | |||
| 2ae4111f64 | |||
| b1fc69dacd | |||
| ee02e71b1d | |||
| 80e310010e | |||
| fad3597d7a | |||
| 4cfa431919 | |||
| 6b4ea0e255 | |||
| e2d944d534 | |||
| 81c4f1e46b | |||
| 60ef92bd4c | |||
| 36456ad1bc | |||
| ad8cf0f48f | |||
| 75b48b7000 | |||
| ddf1b57799 | |||
| 802a492be1 | |||
| 552c73f606 | |||
| e26728d27f | |||
| 981a5332e7 | |||
| 1f5e0fe8c1 | |||
| b3ae80e809 | |||
| e5344c034b | |||
| d6f0c470ee | |||
| ef07772ce4 | |||
| 7720a1f629 | |||
| 90d735163b | |||
| 1d90b3c7d7 | |||
| 5d5ffb9c4c | |||
| b9eec89773 | |||
| 64d85c7b70 | |||
| 1d4f958efa | |||
| b441cc1e7b | |||
| 48876a8229 | |||
| c9f453f35e | |||
| c20a5deede | |||
| 496d4aeb01 | |||
| aa3cabe82b | |||
| 2a74389830 | |||
| e898379707 | |||
| dc47a4c8c0 | |||
| 2edbd6c8b3 | |||
| 76eed1153e | |||
| c28ea57777 | |||
| 04420cb7d3 | |||
| a418baa113 | |||
| 27b11c0cdc | |||
| 6f9b6f07d9 | |||
| d2f4dcd8f3 | |||
| 59d38d8c9d | |||
| aa07bee99d | |||
| c93f3916a1 | |||
| 14b36bba1a | |||
| 3297f6671e | |||
| 1fd34731ff | |||
| bd4ed45a45 | |||
| 4987ed0b4d | |||
| 39f62a3d3f | |||
| 367f4c5d34 | |||
| 793314fdd7 | |||
| 5ca80ded92 | |||
| 23052aa6f0 | |||
| 9d43ef75ee | |||
| 418e013dda | |||
| ddafa0935a | |||
| 24b15cfea6 | |||
| bc9f1302b2 | |||
| e4eb8c4e62 | |||
| 222dae2dac | |||
| 39d584bd1b | |||
| d4a7420471 | |||
| 108c89c18e | |||
| f0fbe6440a | |||
| 5890e6188b | |||
| 07ea408d4e | |||
| f06a0e29b9 | |||
| 7600bc28d9 | |||
| a22b6137d5 | |||
| 306b029951 | |||
| 2f2e762d02 | |||
| ff18aae2b6 | |||
| 4166fb8817 | |||
| d3df73e0ec | |||
| 569dd14f59 | |||
| 4b87551702 | |||
| 9d009a1f64 | |||
| 1b92c2cf81 | |||
| 3bc7467c85 | |||
| ed282efdb7 | |||
| 654ba361e2 | |||
| 2d1b2bab43 | |||
| f027a50756 | |||
| 444637bc15 | |||
| f78a71950b | |||
| c4cb1ce273 | |||
| eb9444dad3 | |||
| f93c9b26c8 | |||
| 37aa620f33 | |||
| 158a4220be | |||
| 1fea25ba91 | |||
| 6c7c7c9d99 | |||
| fcf44fd1ec | |||
| ed498a3bc0 | |||
| 159317965d | |||
| 6b9cdcc239 | |||
| 87c2acf650 | |||
| 43a488fb58 | |||
| 14566196a9 | |||
| 63a9b5b540 | |||
| d8714cbc1d | |||
| 199da663e9 | |||
| fb6cbe2c9d | |||
| 9bfc3bf4c6 | |||
| 86145c0975 | |||
| cbfabba8a7 | |||
| 724178ce6e | |||
| 05bd29fc71 | |||
| cd4e9608d0 | |||
| b27684de46 | |||
| 7d93a0abf6 | |||
| cb33628996 | |||
| 22dc2ef175 | |||
| 025302a959 | |||
| 6cd60cc9a6 | |||
| ccb7fe46d0 | |||
| d3d739a24d | |||
| da4f9b8d67 | |||
| 89b7273c2f | |||
| 3d75cdbd02 | |||
| 7d32211e70 | |||
| c3bbbc1536 | |||
| 62cf6deeb6 | |||
| 2c968404ea | |||
| 9d70abc084 | |||
| b8afa6d9c0 | |||
| 42cf7eed67 | |||
| 5448d07815 | |||
| d117e15157 | |||
| c89cbc24fe | |||
| 8b5c6c2d8a | |||
| ab9b0a3fa3 | |||
| 97f149cf42 | |||
| 7ed50c8d6f | |||
| f35ba89cd0 | |||
| 8d2d71be89 | |||
| 59c2ed8502 | |||
| 5d86161b63 | |||
| c7043c2217 | |||
| b78d5acff2 | |||
| f39ba91450 | |||
| 254f3f84c0 | |||
| 73b4643162 | |||
| ab7d72dfe4 | |||
| 76df41f813 | |||
| b743e56c2a | |||
| cf40ab0707 | |||
| 6b56600c8f | |||
| 5f035df827 | |||
| 5b24bdc32b | |||
| 4a5928e268 | |||
| 9bd91b73ab | |||
| ca690b9270 | |||
| 051f96b639 | |||
| 4d4572e36d | |||
| b44e4f3f9d | |||
| 4e385edac8 | |||
| 069371d859 | |||
| cb3e5b6bfb | |||
| 7d2f435d18 | |||
| 203fb4fca4 | |||
| ea0f2ca169 | |||
| 0ca9f5c069 | |||
| c5e75a7182 | |||
| 0d2d7fae02 | |||
| 0c48538888 | |||
| 2932e0c7d6 | |||
| 21ab4f5fca | |||
| b4a886b126 | |||
| 2bc5eb1f49 | |||
| 4895f09f99 | |||
| bf73821dbd | |||
| 2d5a747be6 | |||
| 2571fcde99 | |||
| e6bc09e622 | |||
| 29608f87da | |||
| 07b7571f94 | |||
| d6d7423087 | |||
| 05f31c00a8 | |||
| af1b032a50 | |||
| 88e415e9d5 | |||
| 1593ecef7b | |||
| b0c5130858 | |||
| deae98001d | |||
| a91922e749 | |||
| 51e85f6cf8 | |||
| eacde3a4f9 | |||
| 21e6fb94a1 | |||
| e2eb5bbee1 | |||
| dd87e1338c | |||
| 4d7d9ac33d | |||
| ed2acd2f86 | |||
| c7c56dcb39 | |||
| f8ca067f54 | |||
| f807ced41d | |||
| 52da21b60d | |||
| 92d4916997 | |||
| 35299ea526 | |||
| 6325e46c1c | |||
| 86a45d5e8a | |||
| d6aa28602c | |||
| 0de38082ec | |||
| b2f238314a | |||
| 4d19ad085f | |||
| 74a5527067 | |||
| 92070d142e | |||
| e49fc397d3 | |||
| 0832a948bf | |||
| b5b36c429e | |||
| af9ae75d5c | |||
| 100861dc47 | |||
| fad0a89dc9 | |||
| 4da3e81d3c | |||
| 5e5f112bfe | |||
| df7c45d479 | |||
| 6cf92b66f5 | |||
| a236bb0804 | |||
| e21a865c61 | |||
| b89bed2c94 | |||
| a70aa29701 | |||
| 50cbebd399 | |||
| aa1dd6df0e | |||
| 33c29acfb6 | |||
| 0d149d6fd7 | |||
| 5f6dbf2baf | |||
| 122cafbc2b | |||
| fb95b935d6 | |||
| 483c87b3a3 | |||
| e9b066d180 | |||
| 74e84c7da4 | |||
| 94a25f0aac | |||
| c6424ea315 | |||
| 9a3532bd3e | |||
| 2f9f3614d6 | |||
| b614dde503 | |||
| ba4a455d2e | |||
| ed3d8e766c | |||
| 04b4d71c64 | |||
| b6cc02fcb8 | |||
| b3fc38e7c0 | |||
| bb2d1966b9 | |||
| cf75245820 | |||
| d6a645f93b | |||
| 17e3d1dfbe | |||
| 8ec3649637 | |||
| 823cb44406 | |||
| 1f3f94e24f | |||
| fa252e59ab | |||
| 93c9f42aab | |||
| f04bf32ce2 | |||
| 50fc1d16de | |||
| d724b16cdc | |||
| abbc8e52bf | |||
| 90a505540e | |||
| 92b15eef6c | |||
| 3d89ed08cc | |||
| d7d2d90b20 | |||
| 0bed97da02 | |||
| c347add41c | |||
| 7697bab51a | |||
| 333172d00b | |||
| 5dede3434f | |||
| 4087e12fb9 | |||
| 8ad89623a9 | |||
| 3cf6154e62 | |||
| dc64daa0e0 | |||
| 8a553beaa8 | |||
| c90bb00241 | |||
| 8515bc7350 | |||
| 0fd38baf77 | |||
| 47ca7b98c6 | |||
| 790f1ca6be | |||
| 78050694d6 | |||
| cd8c65e232 | |||
| 9cc015859e | |||
| e32a3d997f | |||
| 4280576f5f | |||
| 7b6c1b8398 | |||
| 6cab43332e | |||
| 7028ec89b6 | |||
| ebfaba0d7d | |||
| a053eeb207 | |||
| 239dcfe5a0 | |||
| a80b6ad74e | |||
| a14df2bfbb | |||
| e14b33657f | |||
| 7e8e46a15d | |||
| 56602e0f17 | |||
| a54feb4522 | |||
| aaff208be2 | |||
| 21f55472fb | |||
| 9cd7877b07 | |||
| 7c1d4790ab | |||
| 673d43a0c8 | |||
| 8140e80c15 | |||
| 9fcfc63935 | |||
| 4f852012ca | |||
| 096cc173db | |||
| 0fef43e13d | |||
| d693132513 | |||
| 28fa7321df | |||
| 0133e87831 | |||
| 62c96ed761 | |||
| 4d2d52b425 | |||
| 8b7ac97e0d | |||
| c38d15fa87 | |||
| 48d8823a4d | |||
| 1aa4a5d290 | |||
| f179997837 | |||
| e95fe53338 | |||
| e44537cebe | |||
| c0ca9108e7 | |||
| befb178291 | |||
| 0f2459d004 | |||
| 28a50325cb | |||
| 683a15a215 | |||
| fa5e32f374 | |||
| 68be179675 | |||
| afebc02706 | |||
| b1777bc77d | |||
| 02370adcd5 | |||
| 7a4b67b538 | |||
| d1aec952e7 | |||
| 88b7cd456e | |||
| 69dfbecce1 | |||
| 879951ed7f | |||
| 89ef8ee800 | |||
| 7e1695e056 | |||
| fc9b3bbfa5 | |||
| db66ff62fb | |||
| 0c44b87797 | |||
| 9e20ba5210 | |||
| 799e7323bc | |||
| 0ae38485ee | |||
| 360785c224 | |||
| 463811ac83 | |||
| 126bec1fc9 | |||
| c323715505 | |||
| f7636ab410 | |||
| decc5939a1 | |||
| eab8163cf1 | |||
| fc88e27abf | |||
| 0eea2b61a3 | |||
| 197fa01a1e | |||
| 40bef8d02c | |||
| 4713e15f28 | |||
| c24957f2e5 | |||
| 66771b3145 | |||
| f659866ae1 | |||
| 4aab296de3 | |||
| b4cb7454b0 | |||
| c0f771db09 | |||
| 5c8749d8cd | |||
| 06759a6e39 | |||
| 192044e074 | |||
| ff3efe646e | |||
| 01526aa914 | |||
| 31cf892a63 | |||
| fa90e50c3b | |||
| 83aefbae07 | |||
| 1f53fc5214 | |||
| 3d6142f496 | |||
| 235252b591 | |||
| 185a6114a6 | |||
| a4c7212709 | |||
| e9a9fc75c6 | |||
| 1b4a0621de | |||
| 7e4abb7bba | |||
| f595a2e742 | |||
| 9a4241cb11 | |||
| 3ca9b5ff50 | |||
| 079a05ccca | |||
| cfe12634ef | |||
| 928fef02cd | |||
| 2f8b3fcad6 | |||
| 7bc24eabe0 | |||
| 839178d1d2 | |||
| f039be92b6 | |||
| c0f004d3b8 | |||
| 658ff9e842 | |||
| 55f0138a64 | |||
| a4fc8746bd | |||
| 64d246a997 | |||
| e188b2f349 | |||
| 8e9b5e0613 | |||
| a423d572ad | |||
| 12ab7dd3e3 | |||
| b6da53afd6 | |||
| c63e077f9a | |||
| 75ec6a2ddc | |||
| 8b45c91239 | |||
| 934af40e4b | |||
| f827bfeb7a | |||
| 32ce14f032 | |||
| ac6a969ac9 | |||
| 07a4895b60 | |||
| cfc894ff29 | |||
| 8dedae5cb9 | |||
| 7035648396 | |||
| c2df4203bd | |||
| 01a3ac2e1b | |||
| 73e2cffda2 | |||
| f12405b3f4 | |||
| d6a5ded758 | |||
| 14a02f5e22 | |||
| 417c7772e4 | |||
| f39edcca0c | |||
| 6fd0d600e2 | |||
| deb2c04650 | |||
| 4114b5c3aa | |||
| effebe5f64 | |||
| cf201a08c6 | |||
| 794c41766c | |||
| 8a6d4599d1 | |||
| 2ce910c54a | |||
| db31c9b80e | |||
| ea697c19dd | |||
| 4878c62531 | |||
| 143aeb5236 | |||
| 3840220582 | |||
| 496ef09154 | |||
| 60f42f8718 | |||
| 5cc14ac04e | |||
| 598e3412be | |||
| 13a36e43ba | |||
| f083d016b4 | |||
| dae023b2af | |||
| e32655e285 | |||
| 3d94027c09 | |||
| 182b361237 | |||
| b750fa3c77 | |||
| 47b3594bbf | |||
| 06df727f7f | |||
| cd688c91af | |||
| a9ff511496 | |||
| 88bc83f277 | |||
| 0645e2b43a | |||
| 6bcbd6aff9 | |||
| b220235a43 | |||
| e55d87d378 | |||
| c4df65d189 | |||
| f72bf533e0 | |||
| 6fa8adf871 | |||
| 91eb3e8e9e | |||
| 9ea5deb23a | |||
| b0b71d7723 | |||
| 0ff4fd9ba4 | |||
| 0a4f63680c | |||
| 414f4595dc | |||
| 93f6894ef0 | |||
| 3d95f41613 | |||
| 900e90d468 | |||
| d16079cbe8 | |||
| b2f8b85969 | |||
| 4a981f3253 | |||
| 6045ed6ee3 | |||
| c780668cd0 | |||
| fb360c3398 | |||
| 30c346c6b7 | |||
| 8b7dcdf70c | |||
| 7d436290c4 | |||
| e06fd099ce | |||
| 2fa3cc1762 | |||
| 6e861c4e52 | |||
| 0e3101b4d0 | |||
| ead5ed3222 | |||
| 8085da442b | |||
| 7f866d102d | |||
| e24b9b8f17 | |||
| 52ac569c06 | |||
| 7dbde0a1d7 | |||
| a18d9ea0de | |||
| 0045b35790 | |||
| eece7479a0 | |||
| 342e9a3a89 | |||
| ffe873a3e4 | |||
| 3f88513dc1 | |||
| ea502dc2bb | |||
| 7b139199c0 | |||
| 31d5317dc8 | |||
| 635add38e6 | |||
| 6b2ed12c90 | |||
| 4fa7848452 | |||
| 3fd00d56eb | |||
| 4c07b9f206 | |||
| df1bc20388 | |||
| ca401442b4 | |||
| 7cf4067167 | |||
| 6ddb32fd5c | |||
| 19a6b73f0e | |||
| 1cd972f50f | |||
| 1d4bcb44a2 | |||
| c709500d58 | |||
| e92377bc95 | |||
| 19b8825f5b | |||
| e5be4dba02 | |||
| d0245e4375 | |||
| 496a8c5e78 | |||
| 85e77c36c6 | |||
| e71b9951e9 | |||
| 2559b88fbb | |||
| 2db97a42b1 | |||
| 38a4cd7aaa | |||
| dc16a17271 | |||
| 31829df4da | |||
| 05b389dd3c | |||
| 3b9ed0c35f | |||
| 7690f683a4 | |||
| 8ce12613e2 | |||
| 54b693620e | |||
| 72941e1ab4 | |||
| ec78adf42d | |||
| 4cbe29e777 | |||
| 3a1d9e20c2 | |||
| e501577555 | |||
| 1c6e485bf7 | |||
| 605fd24228 | |||
| 47beb9e41f | |||
| a2e22d1154 | |||
| af52bbb070 | |||
| dd9f866165 | |||
| fa60f8c814 | |||
| 8cdeab6efa | |||
| 6f14b8a1b3 | |||
| d435a1b1be | |||
| 24af8e0b49 | |||
| ab262207db | |||
| 359441d5fa | |||
| 8751cc7b89 | |||
| b8d573f2cd | |||
| 2018ccead6 | |||
| 68951a3af4 | |||
| 037c59172c | |||
| a6fec780c7 | |||
| c88ba16bdc | |||
| 4da8ecd25f | |||
| 964daf01ed | |||
| 0bcc5e7d46 | |||
| 9a7087ab14 | |||
| ee8eabcfc4 | |||
| 5ac046d5b1 | |||
| 99c09d9b07 | |||
| fc97c3ed49 | |||
| 75c18763a9 | |||
| 30636e18aa | |||
| 92e8205bb9 | |||
| 1302731793 | |||
| cdee4adf1c | |||
| a80492420e | |||
| 1dab6e3805 | |||
| decefce142 | |||
| 8b4244e3cb | |||
| e31ef61e6b | |||
| fcadc0971b | |||
| 091a59c1bd | |||
| 6f40b6216b | |||
| f1e415b038 | |||
| 07bf334689 | |||
| 2373f715ff |
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>.
|
||||
137
README.md
Normal file
137
README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# FSPL
|
||||
|
||||
<img src="assets/fspl.svg" width="128" alt="FSPL logo.">
|
||||
|
||||
[](https://pkg.go.dev/git.tebibyte.media/fspl/fspl)
|
||||
|
||||
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
|
||||
Before installing the compiler with `go install`, you will need to install the
|
||||
[Go programming language](https://go.dev/) on your system. Afterwards, you can
|
||||
install the compiler and associated tooling by running:
|
||||
|
||||
`go install ./cmd/*`
|
||||
|
||||
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)...] ADDRESS`
|
||||
|
||||
The program compiles the unit with the specified address into one output file.
|
||||
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
|
||||
nickname of the input address. For more information on how to use the `fsplc`
|
||||
program, run `fsplc --help`.
|
||||
|
||||
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
|
||||
|
||||
Q4 2023:
|
||||
- [x] Type definitions
|
||||
- [x] Methods
|
||||
- [x] Defined and external functions
|
||||
- [x] Strict, static, bottom-up type inference
|
||||
- [x] Pointers
|
||||
- [x] Arrays
|
||||
- [x] Slices
|
||||
- [x] Structs
|
||||
- [x] Interfaces
|
||||
- [x] Integer, floating point, string, struct, and array Literals
|
||||
- [x] Assignment
|
||||
- [x] Variables
|
||||
- [x] Function, method, and interface behavior calls
|
||||
- [x] Operations
|
||||
- [x] Casting
|
||||
- [x] Blocks
|
||||
- [x] If/else
|
||||
- [x] Loops
|
||||
|
||||
Q1 2024:
|
||||
- [x] Union types (carry type information)
|
||||
- [x] Match statements
|
||||
- [x] Modules
|
||||
- [x] For/range loops
|
||||
- [x] Switch statements
|
||||
|
||||
Afterwards:
|
||||
- [ ] Mutable/immutable variables
|
||||
- [ ] Basic, non-final standard library routines
|
||||
- [ ] Conditional compilation
|
||||
- [ ] Shared library compilation
|
||||
- [ ] Constants
|
||||
- [ ] ABI documentation
|
||||
- [ ] Vararg
|
||||
- [ ] FSPL vararg using Slices
|
||||
- [ ] Optional per-function C-style vararg for compatibility
|
||||
- [ ] Generics
|
||||
- [ ] Ownership system
|
||||
- [ ] Lightweight, modularized (and of course, totally optional) standard library to replace those written previously
|
||||
|
||||
## FAQ
|
||||
|
||||
I have either been asked these questions, or expect to be at some point.
|
||||
|
||||
### What happened to ARF?
|
||||
A couple of years ago, I attempted to create a programming language under the
|
||||
name ARF, with the goal of trying out some new and interesting syntax I'd come
|
||||
up with. I made several attempts to build a working compiler for it, but never
|
||||
quite succeeded. When I entered my senior year at university, I decided to have
|
||||
another crack at it as my senior project. By then, the ideas I had for the
|
||||
language had changed so much, it was basically a Ship of Theseus situation and
|
||||
it really just needed a new name altogether. Plus, ARF never really stood for
|
||||
anything in the first place.
|
||||
|
||||
### Why Go?
|
||||
Out of all the languages I know, I probably know Go the best. It was an obvious
|
||||
choice for making something this complex. However, I'd like to self-host the
|
||||
compiler eventually.
|
||||
|
||||
### I know you as X name from Y place, why is your name here Sasha Koshka?
|
||||
I consider it a bad idea to share your real name over the internet.
|
||||
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,566 @@
|
||||
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/opaque types is not allowed.
|
||||
weak
|
||||
// Full structural equivalence, and named types are always reduced.
|
||||
// Assignment to private/opaque 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/opaque 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.typeOpaque(pos, destination)
|
||||
if err != nil { return err }
|
||||
err = this.typeOpaque(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 union assignment
|
||||
if _, ok := this.isUnion(destination); ok {
|
||||
return this.canAssignUnion(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
|
||||
}
|
||||
`)
|
||||
// canAssignUnion takes in an analyzed union destination type and an
|
||||
// analyzed source type, and determines whether source can be assigned to
|
||||
// destination.
|
||||
func (this *Tree) canAssignUnion (
|
||||
pos errors.Position,
|
||||
destination entity.Type,
|
||||
source entity.Type,
|
||||
) error {
|
||||
sourceHash := source.Hash()
|
||||
union, _ := this.isUnion(destination)
|
||||
|
||||
// check if types are identical
|
||||
if entity.TypesEqual(source, destination) { return nil }
|
||||
// check if type is included within the union
|
||||
if _, ok := union.AllowedMap[sourceHash]; ok { return nil }
|
||||
|
||||
return errors.Errorf (
|
||||
pos, "%v is not included within union %v",
|
||||
source, destination)
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected array", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = 5
|
||||
}
|
||||
`)
|
||||
// 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 TestAssignLiteralErrArrayWrongLength (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected 3 elements", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
// isLocationExpression returns whether or not an expression is a valid location
|
||||
// expression.
|
||||
func (this *Tree) isLocationExpression (expression entity.Expression) error {
|
||||
// TODO: have some Name() method of Expression so we can use it in a
|
||||
// default case here. this will prevent crashes when new features are
|
||||
// added.
|
||||
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.Match:
|
||||
return cannot(expression.Position(), "match")
|
||||
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 TestAssignLiteralErrArrayWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 19,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3.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 TestAssignLiteralErrSliceWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 19,
|
||||
`
|
||||
[main] = {
|
||||
x:*: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 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
|
||||
}
|
||||
`)
|
||||
// isUnion takes in an analyzed type and returns true and a union if that type
|
||||
// refers to a union. If not, it returns nil, false.
|
||||
func (this *Tree) isUnion (ty entity.Type) (*entity.TypeUnion, bool) {
|
||||
ty = ReduceToBase(ty)
|
||||
switch ty.(type) {
|
||||
case *entity.TypeUnion: return ty.(*entity.TypeUnion), true
|
||||
default: return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5
|
||||
}
|
||||
`)
|
||||
// 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 TestAssignLiteralErrInterfaceFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5.5
|
||||
}
|
||||
`)
|
||||
// isInteger returns whether or not the specified type is an integer or word.
|
||||
func isInteger (ty entity.Type) bool {
|
||||
switch ReduceToBase(ty).(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
`)
|
||||
// isWord returns whether or not the specified type is a word.
|
||||
func isWord (ty entity.Type) bool {
|
||||
switch ReduceToBase(ty).(type) {
|
||||
case *entity.TypeWord: 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)
|
||||
}
|
||||
`)
|
||||
// isPointer returns whether or not the specified type is a pointer.
|
||||
func isPointer (ty entity.Type) bool {
|
||||
switch ReduceToBase(ty).(type) {
|
||||
case *entity.TypePointer: return true
|
||||
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)
|
||||
}
|
||||
`)
|
||||
// isOrdered returns whether or not the values of the specified type can be
|
||||
// ordered.
|
||||
func isOrdered (ty entity.Type) bool {
|
||||
return isNumeric(ty)
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
`)
|
||||
// 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 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]
|
||||
}
|
||||
`)
|
||||
// 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 TestCastErrIntPointer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from *Int to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ [@ a:Int] Int]
|
||||
`)
|
||||
// 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 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]
|
||||
`)
|
||||
|
||||
// integerWidth returns the width of the type, if it is an integer.
|
||||
func integerWidth (ty entity.Type) (int, bool) {
|
||||
switch ty := ReduceToBase(ty).(type) {
|
||||
case *entity.TypeInt: return ty.Width, true
|
||||
default: return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
323
analyzer/assignment_test.go
Normal file
323
analyzer/assignment_test.go
Normal file
@@ -0,0 +1,323 @@
|
||||
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, 15,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (1 3.3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrSliceWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 15,
|
||||
`
|
||||
[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 TestAssignmentUnion (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[main] = {
|
||||
x:F64 = 7.7
|
||||
y:U = x
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentUnionToUnion (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[main] = {
|
||||
x:U y:U
|
||||
x = y
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentUnionErrNotListed (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"I64 is not included within union U", 5, 8,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[main] = {
|
||||
x:I64 = 7
|
||||
y:U = x
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
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: 1 }
|
||||
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]]]
|
||||
}
|
||||
`)
|
||||
}
|
||||
237
analyzer/control-flow_test.go
Normal file
237
analyzer/control-flow_test.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMatch (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[matchToInt u:U]:Int = match u
|
||||
| u:Int u
|
||||
| u:F64 [~Int u]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchReturn (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[isInt u:U]:Bool = {
|
||||
match u | u:Int [return true]
|
||||
false
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchReturnValueUsed (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[isInt u:U]:Bool = match u
|
||||
| u:Int [return true]
|
||||
| u:F64 false
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchDefault (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[isInt u:U]:Bool = match u
|
||||
| u:Int [return true]
|
||||
* false
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchUnionUnderComplete (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| Int F64 UInt)
|
||||
[print str:String]
|
||||
[matchToInt u:U] = match u
|
||||
| u:Int [print 'Int']
|
||||
| u:F64 [print 'F64']
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchErrUnionOverComplete (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"UInt is not included within U", 5, 6,
|
||||
`
|
||||
U: (| Int F64)
|
||||
[matchToInt u:U]:Int = match u
|
||||
| u:Int u
|
||||
| u:UInt [~Int u]
|
||||
| u:F64 [~Int u]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchErrNotUnion (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"type U is not a union", 3, 30,
|
||||
`
|
||||
U: Int
|
||||
[matchToInt u:U]:Int = match u
|
||||
| u:Int u
|
||||
| u:F64 [~Int u]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchErrUnionUnderComplete (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"match does not cover all types within U", 3, 24,
|
||||
`
|
||||
U: (| Int F64 UInt)
|
||||
[matchToInt u:U]:Int = match u
|
||||
| u:Int u
|
||||
| u:F64 [~Int u]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMatchErrDuplicate (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"Int already listed in match at stream0.fspl:4:6", 6, 6,
|
||||
`
|
||||
U: (| Int F64 UInt)
|
||||
[matchToInt u:U]:Int = match u
|
||||
| u:Int u
|
||||
| u:F64 [~Int u]
|
||||
| u:Int u
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSwitch (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f x:Int]:Int = switch x
|
||||
| 0 5
|
||||
| 1 4
|
||||
| 2 3
|
||||
* 0
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSwitchReturn (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[is5 x:Int]:Bool = {
|
||||
switch x | 5 [return true]
|
||||
false
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSwitchReturnValueUsed (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[is5 x:Int]:Bool = switch x
|
||||
| 5 [return true]
|
||||
* false
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSwitchErrBadLiteral (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use array literal as Int", 4, 4,
|
||||
`
|
||||
[f x:Int]:Int = switch x
|
||||
| 0 5
|
||||
| (1) 4
|
||||
| 2 3
|
||||
* 0
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSwitchErrNotConstant (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"y cannot represent a constant integer", 7, 4,
|
||||
`
|
||||
[f x:Int]:Int = {
|
||||
y:Int = 1
|
||||
|
||||
switch x
|
||||
| 0 5
|
||||
| y 4
|
||||
| 2 3
|
||||
* 0
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSwitchErrDuplicate (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"65 already listed in match at stream0.fspl:6:2", 7, 4,
|
||||
`
|
||||
[f x:I8]:Int = switch x
|
||||
| 0 5
|
||||
| 1 4
|
||||
| 2 3
|
||||
| 65 2
|
||||
| 'A' 1
|
||||
* 0
|
||||
`)
|
||||
}
|
||||
|
||||
func TestIfElseReturnValueUsed (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[is5 x:Int]:Bool = if [= x 5]
|
||||
then true
|
||||
else [return false]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestIfElseBreakValueUsed (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f x:Int]: Int = loop {
|
||||
y:Int = if [= x 5]
|
||||
then [break 0]
|
||||
else x
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFor (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f str:String] = for c:Byte in str { }
|
||||
[g str:String] = for i:Index c:Byte in str { }
|
||||
`)}
|
||||
|
||||
func TestForBreak (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f str:String]:Byte = for c:Byte in str [break c]
|
||||
`)}
|
||||
|
||||
func TestForBreakBadType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected Byte", 2, 56,
|
||||
`
|
||||
[f str:String]:Byte = for i:Index c:Byte in str [break i]
|
||||
`)}
|
||||
|
||||
func TestForErrBadIndex (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected Index", 2, 22,
|
||||
`
|
||||
[f str:String] = for i:Int c:Byte in str { }
|
||||
`)}
|
||||
|
||||
func TestForErrBadOver (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use U8 as Int", 2, 31,
|
||||
`
|
||||
[f str:String] = for c:Int in str { }
|
||||
`)}
|
||||
|
||||
func TestForErrDeclarationScoped (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no variable named c", 4, 2,
|
||||
`
|
||||
[f str:String] = {
|
||||
for c:Byte in str { }
|
||||
c = 'a'
|
||||
}
|
||||
`)}
|
||||
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
|
||||
79
analyzer/expression-multiplex.go
Normal file
79
analyzer/expression-multiplex.go
Normal file
@@ -0,0 +1,79 @@
|
||||
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.Match:
|
||||
return this.analyzeMatch(into, mode, expression)
|
||||
case *entity.Switch:
|
||||
return this.analyzeSwitch(into, mode, expression)
|
||||
case *entity.Loop:
|
||||
return this.analyzeLoop(into, mode, expression)
|
||||
case *entity.For:
|
||||
return this.analyzeFor(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))
|
||||
}
|
||||
}
|
||||
1040
analyzer/expression.go
Normal file
1040
analyzer/expression.go
Normal file
File diff suppressed because it is too large
Load Diff
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.Unt = key.Unit
|
||||
|
||||
// functions cannot be marked as opaque
|
||||
if function.Acc == entity.AccessOpaque {
|
||||
return nil, errors.Errorf(pos, "cannot mark function as opaque")
|
||||
}
|
||||
|
||||
// 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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.Unt = owner.Unit()
|
||||
|
||||
// methods cannot be marked as opaque
|
||||
if method.Acc == entity.AccessOpaque {
|
||||
return nil, errors.Errorf(pos, "cannot mark method as opaque")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Pos: method.Position(),
|
||||
Name: "this",
|
||||
Ty: &entity.TypePointer {
|
||||
Pos: method.Position(),
|
||||
Referenced: &entity.TypeNamed {
|
||||
Pos: 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 TestUnitAssignOpaque (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[main] = {
|
||||
x:other::OpaqueInt
|
||||
y:other::OpaqueInt
|
||||
x = y
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`# OpaqueInt:Int`,
|
||||
)}
|
||||
|
||||
func TestUnitAssignLiteralOpaqueErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::OpaqueInt is opaque", 1, 27,
|
||||
`[main]:other::OpaqueInt = 5`,
|
||||
|
||||
"other.fspl",
|
||||
`# OpaqueInt:Int`,
|
||||
)}
|
||||
|
||||
func TestAssignLiteralOpaque (test *testing.T) {
|
||||
testString (test,
|
||||
`# OpaqueInt:Int
|
||||
[main]:OpaqueInt = 5`,
|
||||
)}
|
||||
|
||||
func TestUnitMemberAccessOpaqueErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::OpaqueStruct is opaque", 3, 3,
|
||||
`[main] = {
|
||||
x:other::OpaqueStruct
|
||||
x.x = 5
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`# OpaqueStruct:(. x:Int y:Int)`,
|
||||
)}
|
||||
|
||||
func TestMemberAccessOpaque (test *testing.T) {
|
||||
testString (test,
|
||||
`# OpaqueStruct:(. x:Int y:Int)
|
||||
[main] = {
|
||||
x:OpaqueStruct
|
||||
x.x = 5
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestUnitSubscriptOpaqueErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::OpaqueArr is opaque", 3, 4,
|
||||
`[main] = {
|
||||
x:other::OpaqueArr
|
||||
[.x 0] = 5
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`# OpaqueArr:5:Int`,
|
||||
)}
|
||||
|
||||
func TestSubscriptOpaque (test *testing.T) {
|
||||
testString (test,
|
||||
`# OpaqueArr:5:Int
|
||||
[main] = {
|
||||
x:OpaqueArr
|
||||
[.x 0] = 5
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestUnitMathOpaqueErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::OpaqueInt is opaque", 2, 23,
|
||||
`[main] = {
|
||||
x:other::OpaqueInt = [+
|
||||
y:other::OpaqueInt
|
||||
z:other::OpaqueInt]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`# OpaqueInt:Int`,
|
||||
)}
|
||||
|
||||
func TestMathOpaque (test *testing.T) {
|
||||
testString (test,
|
||||
`# OpaqueInt:Int
|
||||
[main] = {
|
||||
x:OpaqueInt = [+
|
||||
y:OpaqueInt
|
||||
z:OpaqueInt]
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestNestedUnitTypedef (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[main]:layer1::X = 5`,
|
||||
|
||||
"layer0.fspl",
|
||||
`+ X:Int`,
|
||||
|
||||
"layer1.fspl",
|
||||
`+ X:layer0::X`,
|
||||
)}
|
||||
|
||||
func TestUnitBehaviorCallOpaqueErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::OpaqueInterface is opaque", 3, 3,
|
||||
`[main] = {
|
||||
x:other::OpaqueInterface
|
||||
x.[y]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`# OpaqueInterface:(& [y])`,
|
||||
)}
|
||||
|
||||
func TestBehaviorCallOpaque (test *testing.T) {
|
||||
testString (test,
|
||||
`[main] = {
|
||||
x:OpaqueInterface
|
||||
x.[y]
|
||||
}
|
||||
# OpaqueInterface:(& [y])`,
|
||||
)}
|
||||
|
||||
func TestUnitCastOpaqueErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::OpaqueInt is opaque", 2, 16,
|
||||
`[main] = {
|
||||
x:Int = [~Int y:other::OpaqueInt]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`# OpaqueInt:Int`,
|
||||
)}
|
||||
|
||||
func TestCastOpaque (test *testing.T) {
|
||||
testString (test,
|
||||
`[main] = {
|
||||
x:Int = [~Int y:OpaqueInt]
|
||||
}
|
||||
# OpaqueInt:Int`,
|
||||
)}
|
||||
|
||||
func TestFunctionOpaqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot mark function as opaque", 1, 1,
|
||||
`# [f]`,
|
||||
)}
|
||||
|
||||
func TestMethodOpaqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot mark method as opaque", 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.Breakable) {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].pushLoop(loop)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) popLoop () {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].popLoop()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) topLoop () (entity.Breakable, 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.Breakable
|
||||
declaration entity.TopLevel
|
||||
}
|
||||
|
||||
func (this *scopeContext) pushLoop (loop entity.Breakable) {
|
||||
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.Breakable, 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
|
||||
}
|
||||
|
||||
195
analyzer/tree.go
195
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()
|
||||
| ||||