Compare commits
727 Commits
Author | SHA1 | Date |
---|---|---|
Sasha Koshka | 3bc504aae6 | |
Sasha Koshka | 1085cfdc3c | |
Sasha Koshka | ecd9f79aae | |
Sasha Koshka | c934a6c76c | |
Sasha Koshka | 018dc31d04 | |
Sasha Koshka | 4b90ba77d3 | |
Sasha Koshka | 2a6f086350 | |
Sasha Koshka | 767e7bd33c | |
Sasha Koshka | 6c41e71ade | |
Sasha Koshka | 423f3ba22f | |
Sasha Koshka | 08eedaf74d | |
Sasha Koshka | 88e10158ae | |
Sasha Koshka | 93d4aea1b1 | |
Sasha Koshka | 810175b6c9 | |
Sasha Koshka | b6b0a1e592 | |
Sasha Koshka | 00110039e2 | |
Sasha Koshka | 15e418d8c1 | |
Sasha Koshka | 1c61235b63 | |
Sasha Koshka | 4e6103418c | |
Sasha Koshka | e805060370 | |
Sasha Koshka | eccb2e9a5b | |
Sasha Koshka | e574b248e5 | |
Sasha Koshka | 43ed297a12 | |
Sasha Koshka | 76615df4d4 | |
Sasha Koshka | 2caed5b4ae | |
Sasha Koshka | cd396952f2 | |
Sasha Koshka | 0404202691 | |
Sasha Koshka | 92ad52b2aa | |
Sasha Koshka | 10246f7268 | |
Sasha Koshka | 2edda8a960 | |
Sasha Koshka | 11e7a83eb4 | |
Sasha Koshka | 9b45bc56d4 | |
Sasha Koshka | b9e0b3265e | |
Sasha Koshka | 48fa712cb7 | |
Sasha Koshka | cad6a0759a | |
Sasha Koshka | 6c7fcf6045 | |
Sasha Koshka | eb2c1c8aed | |
Sasha Koshka | 78ea6e45b2 | |
Sasha Koshka | ebafbc2a38 | |
Sasha Koshka | 650289f03e | |
Sasha Koshka | 5dda7b2186 | |
Sasha Koshka | 4931f97496 | |
Sasha Koshka | f3bdfef5c5 | |
Sasha Koshka | a50a5febb9 | |
Sasha Koshka | 6916b3b7b1 | |
Sasha Koshka | d8f82d3646 | |
Sasha Koshka | e27fb9e79b | |
Sasha Koshka | 1f04c3a593 | |
Sasha Koshka | 7e05785c09 | |
Sasha Koshka | dcad5a2d18 | |
Sasha Koshka | 3cd3384006 | |
Sasha Koshka | 7f58f7da8b | |
Sasha Koshka | 4342c15e38 | |
Sasha Koshka | 18e7850b58 | |
Sasha Koshka | 70c58f74de | |
Sasha Koshka | 185ef7f6f9 | |
Sasha Koshka | f3c0901493 | |
Sasha Koshka | f84ddf8cad | |
Sasha Koshka | cd08801a29 | |
Sasha Koshka | 84e21b3832 | |
Sasha Koshka | 648b1df547 | |
Sasha Koshka | a4443444c1 | |
Sasha Koshka | a647f27e8c | |
Sasha Koshka | ecd6eba434 | |
Sasha Koshka | 50f088842a | |
Sasha Koshka | 491a9b2369 | |
Sasha Koshka | a9adc77658 | |
Sasha Koshka | 16686bfa3d | |
Sasha Koshka | 7cf770df7f | |
Sasha Koshka | 909d463637 | |
Sasha Koshka | 9b1f6d695c | |
Sasha Koshka | 838c34b0a1 | |
Sasha Koshka | 7d74e59e64 | |
Sasha Koshka | c0476f14ae | |
Sasha Koshka | bb1886ab9c | |
Sasha Koshka | 197373ee27 | |
Sasha Koshka | c576c4022e | |
Sasha Koshka | 67a8b67dec | |
Sasha Koshka | 8d69e4cdd3 | |
Sasha Koshka | ec873ea895 | |
Sasha Koshka | 45ab5d95a2 | |
Sasha Koshka | bb2caeb88a | |
Sasha Koshka | b07dbf8eba | |
Sasha Koshka | babcce643f | |
Sasha Koshka | 6be48aea28 | |
Sasha Koshka | f58c01be52 | |
Sasha Koshka | 4df7a8905e | |
Sasha Koshka | 2be41be609 | |
Sasha Koshka | 0a56a61f4f | |
Sasha Koshka | fc2fb42e53 | |
Sasha Koshka | 9335841fc0 | |
Sasha Koshka | e35a576022 | |
Sasha Koshka | 1c9700378c | |
Sasha Koshka | 56b91d788f | |
Sasha Koshka | fc6efafc34 | |
Sasha Koshka | a147143fc3 | |
Sasha Koshka | a8fd79991c | |
Sasha Koshka | a9c85bf017 | |
Sasha Koshka | db98e590fc | |
Sasha Koshka | e0d4be8db8 | |
Sasha Koshka | 0ea05e5836 | |
Sasha Koshka | d2c8fdd05c | |
Sasha Koshka | 1565376418 | |
Sasha Koshka | 566bf41843 | |
Sasha Koshka | 45f46b04b0 | |
Sasha Koshka | be08308cb2 | |
Sasha Koshka | 4f31b712df | |
Sasha Koshka | 8607f08093 | |
Sasha Koshka | ff99c37219 | |
Sasha Koshka | e889a9c49b | |
Sasha Koshka | ebc4aacf5b | |
Sasha Koshka | 6643c92948 | |
Sasha Koshka | 4df8a45a16 | |
Sasha Koshka | 3d6a258873 | |
Sasha Koshka | 6a9be94601 | |
Sasha Koshka | bf62730e79 | |
Sasha Koshka | 040be67476 | |
Sasha Koshka | 6cd51f3c5f | |
Sasha Koshka | ab24c1cfc1 | |
Sasha Koshka | e94d438332 | |
Sasha Koshka | ef947b8dc6 | |
Sasha Koshka | 41116e0b13 | |
Sasha Koshka | 98d8edc319 | |
Sasha Koshka | ff749a03dd | |
Sasha Koshka | daea6cb22a | |
Sasha Koshka | 54f69ca786 | |
Sasha Koshka | 779aaf3cea | |
Sasha Koshka | 8ec7929c1e | |
Sasha Koshka | fd8a15db66 | |
Sasha Koshka | b1cef5d95f | |
Sasha Koshka | 2a6778837f | |
Sasha Koshka | 8fa9a579fd | |
Sasha Koshka | ce79e16de7 | |
Sasha Koshka | cd91f9dc20 | |
Sasha Koshka | 24268e498e | |
Sasha Koshka | a6dabc27f9 | |
Sasha Koshka | 459020126d | |
Sasha Koshka | 3a1d9632dc | |
Sasha Koshka | 8beb785009 | |
Sasha Koshka | 66633be90c | |
Sasha Koshka | bc88782938 | |
Sasha Koshka | e30f2a74da | |
Sasha Koshka | 0e2259bb45 | |
Sasha Koshka | f94c562896 | |
Sasha Koshka | fb7558576e | |
Sasha Koshka | 8d611b6fa6 | |
Sasha Koshka | c17a381ba5 | |
Sasha Koshka | c45d0215ba | |
Sasha Koshka | 0135972e14 | |
Sasha Koshka | 24fa83b004 | |
Sasha Koshka | b40953a0ec | |
Sasha Koshka | cde0eec9aa | |
Sasha Koshka | 096da5bdfd | |
Sasha Koshka | fe14072392 | |
Sasha Koshka | 0947a8de44 | |
Sasha Koshka | 278a243804 | |
Sasha Koshka | 005b2fcd56 | |
Sasha Koshka | 5e93a7afd1 | |
Sasha Koshka | c22ac87cd0 | |
Sasha Koshka | 8ae6ea3626 | |
Sasha Koshka | 8e3b21f807 | |
Sasha Koshka | 2ae4111f64 | |
Sasha Koshka | b1fc69dacd | |
Sasha Koshka | ee02e71b1d | |
Sasha Koshka | 80e310010e | |
Sasha Koshka | fad3597d7a | |
Sasha Koshka | 4cfa431919 | |
Sasha Koshka | 6b4ea0e255 | |
Sasha Koshka | e2d944d534 | |
Sasha Koshka | 81c4f1e46b | |
Sasha Koshka | 60ef92bd4c | |
Sasha Koshka | 36456ad1bc | |
Sasha Koshka | ad8cf0f48f | |
Sasha Koshka | 75b48b7000 | |
Sasha Koshka | ddf1b57799 | |
Sasha Koshka | 802a492be1 | |
Sasha Koshka | 552c73f606 | |
Sasha Koshka | e26728d27f | |
Sasha Koshka | 981a5332e7 | |
Sasha Koshka | 1f5e0fe8c1 | |
Sasha Koshka | b3ae80e809 | |
Sasha Koshka | e5344c034b | |
Sasha Koshka | d6f0c470ee | |
Sasha Koshka | ef07772ce4 | |
Sasha Koshka | 7720a1f629 | |
Sasha Koshka | 90d735163b | |
Sasha Koshka | 1d90b3c7d7 | |
Sasha Koshka | 5d5ffb9c4c | |
Sasha Koshka | b9eec89773 | |
Sasha Koshka | 64d85c7b70 | |
Sasha Koshka | 1d4f958efa | |
Sasha Koshka | b441cc1e7b | |
Sasha Koshka | 48876a8229 | |
Sasha Koshka | c9f453f35e | |
Sasha Koshka | c20a5deede | |
Sasha Koshka | 496d4aeb01 | |
Sasha Koshka | aa3cabe82b | |
Sasha Koshka | 2a74389830 | |
Sasha Koshka | e898379707 | |
Sasha Koshka | dc47a4c8c0 | |
Sasha Koshka | 2edbd6c8b3 | |
Sasha Koshka | 76eed1153e | |
Sasha Koshka | c28ea57777 | |
Sasha Koshka | 04420cb7d3 | |
Sasha Koshka | a418baa113 | |
Sasha Koshka | 27b11c0cdc | |
Sasha Koshka | 6f9b6f07d9 | |
Sasha Koshka | d2f4dcd8f3 | |
Sasha Koshka | 59d38d8c9d | |
Sasha Koshka | aa07bee99d | |
Sasha Koshka | c93f3916a1 | |
Sasha Koshka | 14b36bba1a | |
Sasha Koshka | 3297f6671e | |
Sasha Koshka | 1fd34731ff | |
Sasha Koshka | bd4ed45a45 | |
Sasha Koshka | 4987ed0b4d | |
Sasha Koshka | 39f62a3d3f | |
Sasha Koshka | 367f4c5d34 | |
Sasha Koshka | 793314fdd7 | |
Sasha Koshka | 5ca80ded92 | |
Sasha Koshka | 23052aa6f0 | |
Sasha Koshka | 9d43ef75ee | |
Sasha Koshka | 418e013dda | |
Sasha Koshka | ddafa0935a | |
Sasha Koshka | 24b15cfea6 | |
Sasha Koshka | bc9f1302b2 | |
Sasha Koshka | e4eb8c4e62 | |
Sasha Koshka | 222dae2dac | |
Sasha Koshka | 39d584bd1b | |
Sasha Koshka | d4a7420471 | |
Sasha Koshka | 108c89c18e | |
Sasha Koshka | f0fbe6440a | |
Sasha Koshka | 5890e6188b | |
Sasha Koshka | 07ea408d4e | |
Sasha Koshka | f06a0e29b9 | |
Sasha Koshka | 7600bc28d9 | |
Sasha Koshka | a22b6137d5 | |
Sasha Koshka | 306b029951 | |
Sasha Koshka | 2f2e762d02 | |
Sasha Koshka | ff18aae2b6 | |
Sasha Koshka | 4166fb8817 | |
Sasha Koshka | d3df73e0ec | |
Sasha Koshka | 569dd14f59 | |
Sasha Koshka | 4b87551702 | |
Sasha Koshka | 9d009a1f64 | |
Sasha Koshka | 1b92c2cf81 | |
Sasha Koshka | 3bc7467c85 | |
Sasha Koshka | ed282efdb7 | |
Sasha Koshka | 654ba361e2 | |
Sasha Koshka | 2d1b2bab43 | |
Sasha Koshka | f027a50756 | |
Sasha Koshka | 444637bc15 | |
Sasha Koshka | f78a71950b | |
Sasha Koshka | c4cb1ce273 | |
Sasha Koshka | eb9444dad3 | |
Sasha Koshka | f93c9b26c8 | |
Sasha Koshka | 37aa620f33 | |
Sasha Koshka | 158a4220be | |
Sasha Koshka | 1fea25ba91 | |
Sasha Koshka | 6c7c7c9d99 | |
Sasha Koshka | fcf44fd1ec | |
Sasha Koshka | ed498a3bc0 | |
Sasha Koshka | 159317965d | |
Sasha Koshka | 6b9cdcc239 | |
Sasha Koshka | 87c2acf650 | |
Sasha Koshka | 43a488fb58 | |
Sasha Koshka | 14566196a9 | |
Sasha Koshka | 63a9b5b540 | |
Sasha Koshka | d8714cbc1d | |
Sasha Koshka | 199da663e9 | |
Sasha Koshka | fb6cbe2c9d | |
Sasha Koshka | 9bfc3bf4c6 | |
Sasha Koshka | 86145c0975 | |
Sasha Koshka | cbfabba8a7 | |
Sasha Koshka | 724178ce6e | |
Sasha Koshka | 05bd29fc71 | |
Sasha Koshka | cd4e9608d0 | |
Sasha Koshka | b27684de46 | |
Sasha Koshka | 7d93a0abf6 | |
Sasha Koshka | cb33628996 | |
Sasha Koshka | 22dc2ef175 | |
Sasha Koshka | 025302a959 | |
Sasha Koshka | 6cd60cc9a6 | |
Sasha Koshka | ccb7fe46d0 | |
Sasha Koshka | d3d739a24d | |
Sasha Koshka | da4f9b8d67 | |
Sasha Koshka | 89b7273c2f | |
Sasha Koshka | 3d75cdbd02 | |
Sasha Koshka | 7d32211e70 | |
Sasha Koshka | c3bbbc1536 | |
Sasha Koshka | 62cf6deeb6 | |
Sasha Koshka | 2c968404ea | |
Sasha Koshka | 9d70abc084 | |
Sasha Koshka | b8afa6d9c0 | |
Sasha Koshka | 42cf7eed67 | |
Sasha Koshka | 5448d07815 | |
Sasha Koshka | d117e15157 | |
Sasha Koshka | c89cbc24fe | |
Sasha Koshka | 8b5c6c2d8a | |
Sasha Koshka | ab9b0a3fa3 | |
Sasha Koshka | 97f149cf42 | |
Sasha Koshka | 7ed50c8d6f | |
Sasha Koshka | f35ba89cd0 | |
Sasha Koshka | 8d2d71be89 | |
Sasha Koshka | 59c2ed8502 | |
Sasha Koshka | 5d86161b63 | |
Sasha Koshka | c7043c2217 | |
Sasha Koshka | b78d5acff2 | |
Sasha Koshka | f39ba91450 | |
Sasha Koshka | 254f3f84c0 | |
Sasha Koshka | 73b4643162 | |
Sasha Koshka | ab7d72dfe4 | |
Sasha Koshka | 76df41f813 | |
Sasha Koshka | b743e56c2a | |
Sasha Koshka | cf40ab0707 | |
Sasha Koshka | 6b56600c8f | |
Sasha Koshka | 5f035df827 | |
Sasha Koshka | 5b24bdc32b | |
Sasha Koshka | 4a5928e268 | |
Sasha Koshka | 9bd91b73ab | |
Sasha Koshka | ca690b9270 | |
Sasha Koshka | 051f96b639 | |
Sasha Koshka | 4d4572e36d | |
Sasha Koshka | b44e4f3f9d | |
Sasha Koshka | 4e385edac8 | |
Sasha Koshka | 069371d859 | |
Sasha Koshka | cb3e5b6bfb | |
Sasha Koshka | 7d2f435d18 | |
Sasha Koshka | 203fb4fca4 | |
Sasha Koshka | ea0f2ca169 | |
Sasha Koshka | 0ca9f5c069 | |
Sasha Koshka | c5e75a7182 | |
Sasha Koshka | 0d2d7fae02 | |
Sasha Koshka | 0c48538888 | |
Sasha Koshka | 2932e0c7d6 | |
Sasha Koshka | 21ab4f5fca | |
Sasha Koshka | b4a886b126 | |
Sasha Koshka | 2bc5eb1f49 | |
Sasha Koshka | 4895f09f99 | |
Sasha Koshka | bf73821dbd | |
Sasha Koshka | 2d5a747be6 | |
Sasha Koshka | 2571fcde99 | |
Sasha Koshka | e6bc09e622 | |
Sasha Koshka | 29608f87da | |
Sasha Koshka | 07b7571f94 | |
Sasha Koshka | d6d7423087 | |
Sasha Koshka | 05f31c00a8 | |
Sasha Koshka | af1b032a50 | |
Sasha Koshka | 88e415e9d5 | |
Sasha Koshka | 1593ecef7b | |
Sasha Koshka | b0c5130858 | |
Sasha Koshka | deae98001d | |
Sasha Koshka | a91922e749 | |
Sasha Koshka | 51e85f6cf8 | |
Sasha Koshka | eacde3a4f9 | |
Sasha Koshka | 21e6fb94a1 | |
Sasha Koshka | e2eb5bbee1 | |
Sasha Koshka | dd87e1338c | |
Sasha Koshka | 4d7d9ac33d | |
Sasha Koshka | ed2acd2f86 | |
Sasha Koshka | c7c56dcb39 | |
Sasha Koshka | f8ca067f54 | |
Sasha Koshka | f807ced41d | |
Sasha Koshka | 52da21b60d | |
Sasha Koshka | 92d4916997 | |
Sasha Koshka | 35299ea526 | |
Sasha Koshka | 6325e46c1c | |
Sasha Koshka | 86a45d5e8a | |
Sasha Koshka | d6aa28602c | |
Sasha Koshka | 0de38082ec | |
Sasha Koshka | b2f238314a | |
Sasha Koshka | 4d19ad085f | |
Sasha Koshka | 74a5527067 | |
Sasha Koshka | 92070d142e | |
Sasha Koshka | e49fc397d3 | |
Sasha Koshka | 0832a948bf | |
Sasha Koshka | b5b36c429e | |
Sasha Koshka | af9ae75d5c | |
Sasha Koshka | 100861dc47 | |
Sasha Koshka | fad0a89dc9 | |
Sasha Koshka | 4da3e81d3c | |
Sasha Koshka | 5e5f112bfe | |
Sasha Koshka | df7c45d479 | |
Sasha Koshka | 6cf92b66f5 | |
Sasha Koshka | a236bb0804 | |
Sasha Koshka | e21a865c61 | |
Sasha Koshka | b89bed2c94 | |
Sasha Koshka | a70aa29701 | |
Sasha Koshka | 50cbebd399 | |
Sasha Koshka | aa1dd6df0e | |
Sasha Koshka | 33c29acfb6 | |
Sasha Koshka | 0d149d6fd7 | |
Sasha Koshka | 5f6dbf2baf | |
Sasha Koshka | 122cafbc2b | |
Sasha Koshka | fb95b935d6 | |
Sasha Koshka | 483c87b3a3 | |
Sasha Koshka | e9b066d180 | |
Sasha Koshka | 74e84c7da4 | |
Sasha Koshka | 94a25f0aac | |
Sasha Koshka | c6424ea315 | |
Sasha Koshka | 9a3532bd3e | |
Sasha Koshka | 2f9f3614d6 | |
Sasha Koshka | b614dde503 | |
Sasha Koshka | ba4a455d2e | |
Sasha Koshka | ed3d8e766c | |
Sasha Koshka | 04b4d71c64 | |
Sasha Koshka | b6cc02fcb8 | |
Sasha Koshka | b3fc38e7c0 | |
Sasha Koshka | bb2d1966b9 | |
Sasha Koshka | cf75245820 | |
Sasha Koshka | d6a645f93b | |
Sasha Koshka | 17e3d1dfbe | |
Sasha Koshka | 8ec3649637 | |
Sasha Koshka | 823cb44406 | |
Sasha Koshka | 1f3f94e24f | |
Sasha Koshka | fa252e59ab | |
Sasha Koshka | 93c9f42aab | |
Sasha Koshka | f04bf32ce2 | |
Sasha Koshka | 50fc1d16de | |
Sasha Koshka | d724b16cdc | |
Sasha Koshka | abbc8e52bf | |
Sasha Koshka | 90a505540e | |
Sasha Koshka | 92b15eef6c | |
Sasha Koshka | 3d89ed08cc | |
Sasha Koshka | d7d2d90b20 | |
Sasha Koshka | 0bed97da02 | |
Sasha Koshka | c347add41c | |
Sasha Koshka | 7697bab51a | |
Sasha Koshka | 333172d00b | |
Sasha Koshka | 5dede3434f | |
Sasha Koshka | 4087e12fb9 | |
Sasha Koshka | 8ad89623a9 | |
Sasha Koshka | 3cf6154e62 | |
Sasha Koshka | dc64daa0e0 | |
Sasha Koshka | 8a553beaa8 | |
Sasha Koshka | c90bb00241 | |
Sasha Koshka | 8515bc7350 | |
Sasha Koshka | 0fd38baf77 | |
Sasha Koshka | 47ca7b98c6 | |
Sasha Koshka | 790f1ca6be | |
Sasha Koshka | 78050694d6 | |
Sasha Koshka | cd8c65e232 | |
Sasha Koshka | 9cc015859e | |
Sasha Koshka | e32a3d997f | |
Sasha Koshka | 4280576f5f | |
Sasha Koshka | 7b6c1b8398 | |
Sasha Koshka | 6cab43332e | |
Sasha Koshka | 7028ec89b6 | |
Sasha Koshka | ebfaba0d7d | |
Sasha Koshka | a053eeb207 | |
Sasha Koshka | 239dcfe5a0 | |
Sasha Koshka | a80b6ad74e | |
Sasha Koshka | a14df2bfbb | |
Sasha Koshka | e14b33657f | |
Sasha Koshka | 7e8e46a15d | |
Sasha Koshka | 56602e0f17 | |
Sasha Koshka | a54feb4522 | |
Sasha Koshka | aaff208be2 | |
Sasha Koshka | 21f55472fb | |
Sasha Koshka | 9cd7877b07 | |
Sasha Koshka | 7c1d4790ab | |
Sasha Koshka | 673d43a0c8 | |
Sasha Koshka | 8140e80c15 | |
Sasha Koshka | 9fcfc63935 | |
Sasha Koshka | 4f852012ca | |
Sasha Koshka | 096cc173db | |
Sasha Koshka | 0fef43e13d | |
Sasha Koshka | d693132513 | |
Sasha Koshka | 28fa7321df | |
Sasha Koshka | 0133e87831 | |
Sasha Koshka | 62c96ed761 | |
Sasha Koshka | 4d2d52b425 | |
Sasha Koshka | 8b7ac97e0d | |
Sasha Koshka | c38d15fa87 | |
Sasha Koshka | 48d8823a4d | |
Sasha Koshka | 1aa4a5d290 | |
Sasha Koshka | f179997837 | |
Sasha Koshka | e95fe53338 | |
Sasha Koshka | e44537cebe | |
Sasha Koshka | c0ca9108e7 | |
Sasha Koshka | befb178291 | |
Sasha Koshka | 0f2459d004 | |
Sasha Koshka | 28a50325cb | |
Sasha Koshka | 683a15a215 | |
Sasha Koshka | fa5e32f374 | |
Sasha Koshka | 68be179675 | |
Sasha Koshka | afebc02706 | |
Sasha Koshka | b1777bc77d | |
Sasha Koshka | 02370adcd5 | |
Sasha Koshka | 7a4b67b538 | |
Sasha Koshka | d1aec952e7 | |
Sasha Koshka | 88b7cd456e | |
Sasha Koshka | 69dfbecce1 | |
Sasha Koshka | 879951ed7f | |
Sasha Koshka | 89ef8ee800 | |
Sasha Koshka | 7e1695e056 | |
Sasha Koshka | fc9b3bbfa5 | |
Sasha Koshka | db66ff62fb | |
Sasha Koshka | 0c44b87797 | |
Sasha Koshka | 9e20ba5210 | |
Sasha Koshka | 799e7323bc | |
Sasha Koshka | 0ae38485ee | |
Sasha Koshka | 360785c224 | |
Sasha Koshka | 463811ac83 | |
Sasha Koshka | 126bec1fc9 | |
Sasha Koshka | c323715505 | |
Sasha Koshka | f7636ab410 | |
Sasha Koshka | decc5939a1 | |
Sasha Koshka | eab8163cf1 | |
Sasha Koshka | fc88e27abf | |
Sasha Koshka | 0eea2b61a3 | |
Sasha Koshka | 197fa01a1e | |
Sasha Koshka | 40bef8d02c | |
Sasha Koshka | 4713e15f28 | |
Sasha Koshka | c24957f2e5 | |
Sasha Koshka | 66771b3145 | |
Sasha Koshka | f659866ae1 | |
Sasha Koshka | 4aab296de3 | |
Sasha Koshka | b4cb7454b0 | |
Sasha Koshka | c0f771db09 | |
Sasha Koshka | 5c8749d8cd | |
Sasha Koshka | 06759a6e39 | |
Sasha Koshka | 192044e074 | |
Sasha Koshka | ff3efe646e | |
Sasha Koshka | 01526aa914 | |
Sasha Koshka | 31cf892a63 | |
Sasha Koshka | fa90e50c3b | |
Sasha Koshka | 83aefbae07 | |
Sasha Koshka | 1f53fc5214 | |
Sasha Koshka | 3d6142f496 | |
Sasha Koshka | 235252b591 | |
Sasha Koshka | 185a6114a6 | |
Sasha Koshka | a4c7212709 | |
Sasha Koshka | e9a9fc75c6 | |
Sasha Koshka | 1b4a0621de | |
Sasha Koshka | 7e4abb7bba | |
Sasha Koshka | f595a2e742 | |
Sasha Koshka | 9a4241cb11 | |
Sasha Koshka | 3ca9b5ff50 | |
Sasha Koshka | 079a05ccca | |
Sasha Koshka | cfe12634ef | |
Sasha Koshka | 928fef02cd | |
Sasha Koshka | 2f8b3fcad6 | |
Sasha Koshka | 7bc24eabe0 | |
Sasha Koshka | 839178d1d2 | |
Sasha Koshka | f039be92b6 | |
Sasha Koshka | c0f004d3b8 | |
Sasha Koshka | 658ff9e842 | |
Sasha Koshka | 55f0138a64 | |
Sasha Koshka | a4fc8746bd | |
Sasha Koshka | 64d246a997 | |
Sasha Koshka | e188b2f349 | |
Sasha Koshka | 8e9b5e0613 | |
Sasha Koshka | a423d572ad | |
Sasha Koshka | 12ab7dd3e3 | |
Sasha Koshka | b6da53afd6 | |
Sasha Koshka | c63e077f9a | |
Sasha Koshka | 75ec6a2ddc | |
Sasha Koshka | 8b45c91239 | |
Sasha Koshka | 934af40e4b | |
Sasha Koshka | f827bfeb7a | |
Sasha Koshka | 32ce14f032 | |
Sasha Koshka | ac6a969ac9 | |
Sasha Koshka | 07a4895b60 | |
Sasha Koshka | cfc894ff29 | |
Sasha Koshka | 8dedae5cb9 | |
Sasha Koshka | 7035648396 | |
Sasha Koshka | c2df4203bd | |
Sasha Koshka | 01a3ac2e1b | |
Sasha Koshka | 73e2cffda2 | |
Sasha Koshka | f12405b3f4 | |
Sasha Koshka | d6a5ded758 | |
Sasha Koshka | 14a02f5e22 | |
Sasha Koshka | 417c7772e4 | |
Sasha Koshka | f39edcca0c | |
Sasha Koshka | 6fd0d600e2 | |
Sasha Koshka | deb2c04650 | |
Sasha Koshka | 4114b5c3aa | |
Sasha Koshka | effebe5f64 | |
Sasha Koshka | cf201a08c6 | |
Sasha Koshka | 794c41766c | |
Sasha Koshka | 8a6d4599d1 | |
Sasha Koshka | 2ce910c54a | |
Sasha Koshka | db31c9b80e | |
Sasha Koshka | ea697c19dd | |
Sasha Koshka | 4878c62531 | |
Sasha Koshka | 143aeb5236 | |
Sasha Koshka | 3840220582 | |
Sasha Koshka | 496ef09154 | |
Sasha Koshka | 60f42f8718 | |
Sasha Koshka | 5cc14ac04e | |
Sasha Koshka | 598e3412be | |
Sasha Koshka | 13a36e43ba | |
Sasha Koshka | f083d016b4 | |
Sasha Koshka | dae023b2af | |
Sasha Koshka | e32655e285 | |
Sasha Koshka | 3d94027c09 | |
Sasha Koshka | 182b361237 | |
Sasha Koshka | b750fa3c77 | |
Sasha Koshka | 47b3594bbf | |
Sasha Koshka | 06df727f7f | |
Sasha Koshka | cd688c91af | |
Sasha Koshka | a9ff511496 | |
Sasha Koshka | 88bc83f277 | |
Sasha Koshka | 0645e2b43a | |
Sasha Koshka | 6bcbd6aff9 | |
Sasha Koshka | b220235a43 | |
Sasha Koshka | e55d87d378 | |
Sasha Koshka | c4df65d189 | |
Sasha Koshka | f72bf533e0 | |
Sasha Koshka | 6fa8adf871 | |
Sasha Koshka | 91eb3e8e9e | |
Sasha Koshka | 9ea5deb23a | |
Sasha Koshka | b0b71d7723 | |
Sasha Koshka | 0ff4fd9ba4 | |
Sasha Koshka | 0a4f63680c | |
Sasha Koshka | 414f4595dc | |
Sasha Koshka | 93f6894ef0 | |
Sasha Koshka | 3d95f41613 | |
Sasha Koshka | 900e90d468 | |
Sasha Koshka | d16079cbe8 | |
Sasha Koshka | b2f8b85969 | |
Sasha Koshka | 4a981f3253 | |
Sasha Koshka | 6045ed6ee3 | |
Sasha Koshka | c780668cd0 | |
Sasha Koshka | fb360c3398 | |
Sasha Koshka | 30c346c6b7 | |
Sasha Koshka | 8b7dcdf70c | |
Sasha Koshka | 7d436290c4 | |
Sasha Koshka | e06fd099ce | |
Sasha Koshka | 2fa3cc1762 | |
Sasha Koshka | 6e861c4e52 | |
Sasha Koshka | 0e3101b4d0 | |
Sasha Koshka | ead5ed3222 | |
Sasha Koshka | 8085da442b | |
Sasha Koshka | 7f866d102d | |
Sasha Koshka | e24b9b8f17 | |
Sasha Koshka | 52ac569c06 | |
Sasha Koshka | 7dbde0a1d7 | |
Sasha Koshka | a18d9ea0de | |
Sasha Koshka | 0045b35790 | |
Sasha Koshka | eece7479a0 | |
Sasha Koshka | 342e9a3a89 | |
Sasha Koshka | ffe873a3e4 | |
Sasha Koshka | 3f88513dc1 | |
Sasha Koshka | ea502dc2bb | |
Sasha Koshka | 7b139199c0 | |
Sasha Koshka | 31d5317dc8 | |
Sasha Koshka | 635add38e6 | |
Sasha Koshka | 6b2ed12c90 | |
Sasha Koshka | 4fa7848452 | |
Sasha Koshka | 3fd00d56eb | |
Sasha Koshka | 4c07b9f206 | |
Sasha Koshka | df1bc20388 | |
Sasha Koshka | ca401442b4 | |
Sasha Koshka | 7cf4067167 | |
Sasha Koshka | 6ddb32fd5c | |
Sasha Koshka | 19a6b73f0e | |
Sasha Koshka | 1cd972f50f | |
Sasha Koshka | 1d4bcb44a2 | |
Sasha Koshka | c709500d58 | |
Sasha Koshka | e92377bc95 | |
Sasha Koshka | 19b8825f5b | |
Sasha Koshka | e5be4dba02 | |
Sasha Koshka | d0245e4375 | |
Sasha Koshka | 496a8c5e78 | |
Sasha Koshka | 85e77c36c6 | |
Sasha Koshka | e71b9951e9 | |
Sasha Koshka | 2559b88fbb | |
Sasha Koshka | 2db97a42b1 | |
Sasha Koshka | 38a4cd7aaa | |
Sasha Koshka | dc16a17271 | |
Sasha Koshka | 31829df4da | |
Sasha Koshka | 05b389dd3c | |
Sasha Koshka | 3b9ed0c35f | |
Sasha Koshka | 7690f683a4 | |
Sasha Koshka | 8ce12613e2 | |
Sasha Koshka | 54b693620e | |
Sasha Koshka | 72941e1ab4 | |
Sasha Koshka | ec78adf42d | |
Sasha Koshka | 4cbe29e777 | |
Sasha Koshka | 3a1d9e20c2 | |
Sasha Koshka | e501577555 | |
Sasha Koshka | 1c6e485bf7 | |
Sasha Koshka | 605fd24228 | |
Sasha Koshka | 47beb9e41f | |
Sasha Koshka | a2e22d1154 | |
Sasha Koshka | af52bbb070 | |
Sasha Koshka | dd9f866165 | |
Sasha Koshka | fa60f8c814 | |
Sasha Koshka | 8cdeab6efa | |
Sasha Koshka | 6f14b8a1b3 | |
Sasha Koshka | d435a1b1be | |
Sasha Koshka | 24af8e0b49 | |
Sasha Koshka | ab262207db | |
Sasha Koshka | 359441d5fa | |
Sasha Koshka | 8751cc7b89 | |
Sasha Koshka | b8d573f2cd | |
Sasha Koshka | 2018ccead6 | |
Sasha Koshka | 68951a3af4 | |
Sasha Koshka | 037c59172c | |
Sasha Koshka | a6fec780c7 | |
Sasha Koshka | c88ba16bdc | |
Sasha Koshka | 4da8ecd25f | |
Sasha Koshka | 964daf01ed | |
Sasha Koshka | 0bcc5e7d46 | |
Sasha Koshka | 9a7087ab14 | |
Sasha Koshka | ee8eabcfc4 | |
Sasha Koshka | 5ac046d5b1 | |
Sasha Koshka | 99c09d9b07 | |
Sasha Koshka | fc97c3ed49 | |
Sasha Koshka | 75c18763a9 | |
Sasha Koshka | 30636e18aa | |
Sasha Koshka | 92e8205bb9 | |
Sasha Koshka | 1302731793 | |
Sasha Koshka | cdee4adf1c | |
Sasha Koshka | a80492420e | |
Sasha Koshka | 1dab6e3805 | |
Sasha Koshka | decefce142 | |
Sasha Koshka | 8b4244e3cb | |
Sasha Koshka | e31ef61e6b | |
Sasha Koshka | fcadc0971b | |
Sasha Koshka | 091a59c1bd | |
Sasha Koshka | 6f40b6216b | |
Sasha Koshka | f1e415b038 | |
Sasha Koshka | 07bf334689 | |
Sasha Koshka | 2373f715ff |
|
@ -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
|
|
@ -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>.
|
|
@ -0,0 +1,141 @@
|
|||
# FSPL
|
||||
|
||||
<img src="assets/fspl.svg" width="128" alt="FSPL logo.">
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/fspl/fspl.svg)](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
|
||||
|
||||
Q2 2024:
|
||||
- [ ] Mutable/immutable variables
|
||||
- [ ] Basic, non-final standard library routines
|
||||
- [ ] Conditional compilation
|
||||
- [ ] Shared library compilation
|
||||
- [ ] Constants
|
||||
- [ ] ABI documentation
|
||||
|
||||
Q3 2024
|
||||
- [ ] 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 in Q2
|
||||
|
||||
At the beginning of Q4 2024, a 1.0 version of the language will be released.
|
||||
|
||||
## 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.
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -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 },
|
||||
}
|
||||
}
|
|
@ -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]]]
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
`)}
|
|
@ -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
|
|
@ -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))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
|
@ -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] = { }
|
||||
`)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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']
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
`)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
|
@ -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]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
this.ast = ast
|
||||
this.resetRawMaps()
|
||||
this.ast = ast
|
||||
this.unit = unit
|
||||
this.nicknames = nicknames
|
||||
err := this.assembleRawMaps()
|
||||
if err != nil { return err }
|
||||
return this.analyzeDeclarations()
|
||||
|
@ -29,72 +41,147 @@ func (this *Tree) Analyze (ast parser.Tree) error {
|
|||
|
||||
func (this *Tree) assembleRawMaps () error {
|
||||
for _, declaration := range this.ast.Declarations {
|
||||
switch declaration.(type) {
|
||||
switch declaration := declaration.(type) {
|
||||
case *entity.Typedef:
|
||||
ty := declaration.(*entity.Typedef)
|
||||
err := this.rawNameUnique(ty.Pos, ty.Name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position(), key)
|
||||
if err != nil { return err }
|
||||
this.rawTypes[ty.Name] = ty
|
||||
declaration.Methods = make(map[string] *entity.Method)
|
||||
this.rawTypes[key] = declaration
|
||||
|
||||
case *entity.Function:
|
||||
function := declaration.(*entity.Function)
|
||||
err := this.rawNameUnique (
|
||||
function.Pos,
|
||||
function.Signature.Name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.Signature.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position(), key)
|
||||
if err != nil { return err }
|
||||
this.rawFunctions[function.Signature.Name] = function
|
||||
this.rawFunctions[key] = declaration
|
||||
|
||||
case *entity.Method:
|
||||
method := declaration.(*entity.Method)
|
||||
name := method.TypeName + "." + method.Signature.Name
|
||||
err := this.rawNameUnique(method.Pos, name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.TypeName,
|
||||
Method: declaration.Signature.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position(), key)
|
||||
if err != nil { return err }
|
||||
this.rawMethods[name] = method
|
||||
this.rawMethods[key] = declaration
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) rawNameUnique (currentPos lexer.Position, name string) error {
|
||||
var pos lexer.Position
|
||||
var unique = true
|
||||
|
||||
if ty, isType := this.rawTypes[name]; isType {
|
||||
pos = ty.Pos
|
||||
unique = false
|
||||
func (this *Tree) topLevelNameAvailable (currentPos errors.Position, key entity.Key) error {
|
||||
if fsplParser.IsReserved(key.Name) {
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow reserved identifier %s",
|
||||
key.Name)
|
||||
}
|
||||
if function, isFunction := this.rawFunctions[name]; isFunction {
|
||||
pos = function.Pos
|
||||
unique = false
|
||||
if _, isPrimitive := primitiveTypes[key.Name]; isPrimitive {
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow primitive %s",
|
||||
key.Name)
|
||||
}
|
||||
if method, isMethod := this.rawMethods[name]; isMethod {
|
||||
pos = method.Pos
|
||||
unique = false
|
||||
if _, isBuiltin := builtinTypes[key.Name]; isBuiltin {
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow builtin %s",
|
||||
key.Name)
|
||||
}
|
||||
|
||||
if unique {
|
||||
return nil
|
||||
} else {
|
||||
return participle.Errorf (
|
||||
if ty, isType := this.rawTypes[key]; isType {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
name, pos)
|
||||
key.Name, ty.Position())
|
||||
}
|
||||
if function, isFunction := this.rawFunctions[key]; isFunction {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
key.Name, function.Position())
|
||||
}
|
||||
if method, isMethod := this.rawMethods[key]; isMethod {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s.%s already declared at %v",
|
||||
key.Name, key.Method, method.Position())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDeclarations () error {
|
||||
for name := range this.rawTypes {
|
||||
ty, err := this.analyzeTypedef(name)
|
||||
for key, rawType := range this.rawTypes {
|
||||
ty, err := this.analyzeTypedef(rawType.Position(), key, false)
|
||||
if err != nil { return err }
|
||||
this.Types[key] = ty
|
||||
}
|
||||
for key, rawFunction := range this.rawFunctions {
|
||||
_, err := this.analyzeFunction(rawFunction.Position(), key)
|
||||
if err != nil { return err }
|
||||
}
|
||||
for _, rawMethod := range this.rawMethods {
|
||||
_, err := this.analyzeMethod (
|
||||
rawMethod.Position(),
|
||||
entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: rawMethod.TypeName,
|
||||
Method: rawMethod.Signature.Name,
|
||||
})
|
||||
if err != nil { return err }
|
||||
this.Types[name] = ty
|
||||
}
|
||||
// TODO functions
|
||||
// TODO methods
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) resolveNickname (pos errors.Position, nickname string) (uuid.UUID, error) {
|
||||
if nickname == "" { return this.unit, nil }
|
||||
|
||||
fail := func () (uuid.UUID, error) {
|
||||
return uuid.UUID { }, errors.Errorf (
|
||||
pos, "no unit named %s",
|
||||
nickname)
|
||||
}
|
||||
|
||||
if this.nicknames == nil { return fail() }
|
||||
unit, ok := this.nicknames[nickname]
|
||||
if !ok { return fail() }
|
||||
|
||||
return unit, nil
|
||||
}
|
||||
|
||||
func (this *Tree) ensure () {
|
||||
if this.rawTypes != nil { return }
|
||||
this.rawTypes = make(map[string] *entity.Typedef)
|
||||
this.rawFunctions = make(map[string] *entity.Function)
|
||||
this.rawMethods = make(map[string] *entity.Method)
|
||||
this.Types = make(map[string] entity.Type)
|
||||
this.Functions = make(map[string] *entity.Function)
|
||||
this.resetRawMaps()
|
||||
this.incompleteTypes = make(map[entity.Key] *entity.Typedef)
|
||||
this.Types = make(map[entity.Key] *entity.Typedef)
|
||||
this.Functions = make(map[entity.Key] *entity.Function)
|
||||
|
||||
for name, ty := range builtinTypes {
|
||||
// builtin types have a zero UUID in their key
|
||||
this.Types[entity.Key { Name: name }] = &entity.Typedef {
|
||||
Acc: entity.AccessPublic,
|
||||
Name: name,
|
||||
Type: ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Tree) resetRawMaps () {
|
||||
if this.rawTypes != nil &&
|
||||
this.rawFunctions != nil &&
|
||||
this.rawMethods != nil {
|
||||
|
||||
if len(this.rawTypes) > 0 {
|
||||
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
||||
}
|
||||
if len(this.rawFunctions) > 0 {
|
||||
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
||||
}
|
||||
if len(this.rawMethods) > 0 {
|
||||
this.rawMethods = make(map[entity.Key] *entity.Method)
|
||||
}
|
||||
} else {
|
||||
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
||||
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
||||
this.rawMethods = make(map[entity.Key] *entity.Method)
|
||||
}
|
||||
}
|
||||
|
|
321
analyzer/type.go
321
analyzer/type.go
|
@ -1,100 +1,305 @@
|
|||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeTypedef (pos lexer.Position, name string) (entity.Type, error) {
|
||||
if ty, exists := this.Types[name]; exists {
|
||||
return ty
|
||||
func (this *Tree) analyzeTypedef (
|
||||
pos errors.Position,
|
||||
key entity.Key,
|
||||
acceptIncomplete bool,
|
||||
) (
|
||||
*entity.Typedef,
|
||||
error,
|
||||
) {
|
||||
// return a builtin if it exists (search types with zero uuid)
|
||||
if definition, exists := this.Types[entity.Key { Name: key.Name }]; exists {
|
||||
return definition, nil
|
||||
}
|
||||
|
||||
// return if exists already
|
||||
if definition, exists := this.Types[key]; exists {
|
||||
return definition, nil
|
||||
}
|
||||
|
||||
definition, exists := this.RawTypes[name]
|
||||
if !exists {
|
||||
return participle.Errorf(pos, "no type named %s", name)
|
||||
// check if incomplete
|
||||
if definition, incomplete := this. incompleteTypes[key]; incomplete {
|
||||
if acceptIncomplete {
|
||||
return definition, nil
|
||||
} else {
|
||||
return nil, errors.Errorf (
|
||||
pos, "type %s cannot be used in this context",
|
||||
key.Name)
|
||||
}
|
||||
}
|
||||
return this.analyzeType(definition.Type)
|
||||
|
||||
// analyze if it still needs to be analyzed
|
||||
if definition, exists := this.rawTypes[key]; exists {
|
||||
// update unit
|
||||
definition.Unt = key.Unit
|
||||
|
||||
// analyze
|
||||
var err error
|
||||
definition.Type, err = this.analyzeTypeInternal (
|
||||
definition.Type, definition, false)
|
||||
return definition, err
|
||||
}
|
||||
|
||||
// if we couldn't get the type, error
|
||||
return nil, errors.Errorf(pos, "no type named %s", key.Name)
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeType (ty entity.Type) (entity.Type, error) {
|
||||
if ty == entity.Void() { return ty }
|
||||
var err error
|
||||
func (this *Tree) analyzeType (
|
||||
ty entity.Type,
|
||||
acceptIncomplete bool,
|
||||
) (
|
||||
entity.Type,
|
||||
error,
|
||||
) {
|
||||
return this.analyzeTypeInternal (ty, nil, acceptIncomplete)
|
||||
}
|
||||
|
||||
switch ty.(type) {
|
||||
case *entity.TypeNamed:
|
||||
ty := ty.(*entity.TypeNamed)
|
||||
defined, err := this.analyzeTypedef(ty.Pos, ty.Name)
|
||||
ty.Type = defined
|
||||
return ty, err
|
||||
case *entity.TypePointer:
|
||||
ty := ty.(*entity.TypePointer)
|
||||
ty.Referenced, err = this.analyzeType(ty.Referenced)
|
||||
return ty, err
|
||||
case *entity.TypeSlice:
|
||||
ty := ty.(*entity.TypeSlice)
|
||||
ty.Element, err = this.analyzeType(ty.Element)
|
||||
return ty, err
|
||||
case *entity.TypeArray:
|
||||
ty := ty.(*entity.TypeArray)
|
||||
if ty.Length < 1 {
|
||||
return ty, participle.Errorf (
|
||||
ty.Pos, "array length must be > 1", name)
|
||||
func (this *Tree) analyzeTypeInternal (
|
||||
ty entity.Type,
|
||||
root *entity.Typedef,
|
||||
acceptIncomplete bool,
|
||||
) (
|
||||
entity.Type,
|
||||
error,
|
||||
) {
|
||||
// edge cases
|
||||
if ty == nil { return nil, nil }
|
||||
var err error
|
||||
|
||||
// if we are analyzing a typedef, we will need to update incomplete type
|
||||
// information as we go in order for recursive type definitions to work
|
||||
rootKey := func () entity.Key {
|
||||
return entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: root.Name,
|
||||
}
|
||||
ty.Element, err = this.analyzeType(ty.Element)
|
||||
}
|
||||
updateIncompleteInfo := func () {
|
||||
if root != nil { this.incompleteTypes[rootKey()] = root }
|
||||
}
|
||||
updatePseudoCompleteInfo := func () {
|
||||
if root != nil { this.Types[rootKey()] = root }
|
||||
}
|
||||
if root != nil { defer delete(this.incompleteTypes, rootKey()) }
|
||||
// ---------
|
||||
|
||||
// determine the access permission for the type
|
||||
access := entity.AccessPublic; if root != nil {
|
||||
access = root.Acc
|
||||
}
|
||||
|
||||
switch ty := ty.(type) {
|
||||
// named type
|
||||
case *entity.TypeNamed:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
updateIncompleteInfo()
|
||||
|
||||
// resolve nickname of the unit where the typedef is
|
||||
if primitive, isPrimitive := primitiveTypes[ty.Name]; isPrimitive {
|
||||
return primitive, nil
|
||||
}
|
||||
unit, err := this.resolveNickname(ty.Position(), ty.UnitNickname)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// analyze the typedef
|
||||
def, err := this.analyzeTypedef(ty.Position(), entity.Key {
|
||||
Unit: unit,
|
||||
Name: ty.Name,
|
||||
}, acceptIncomplete)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
ty.Type = def.Type
|
||||
|
||||
// check access permissions
|
||||
err = this.typePrivate(ty.Position(), ty)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return ty, nil
|
||||
|
||||
// pointer type
|
||||
case *entity.TypePointer:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
updateIncompleteInfo()
|
||||
ty.Referenced, err = this.analyzeType(ty.Referenced, true)
|
||||
return ty, err
|
||||
|
||||
// slice type
|
||||
case *entity.TypeSlice:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
updateIncompleteInfo()
|
||||
ty.Element, err = this.analyzeType(ty.Element, false)
|
||||
return ty, err
|
||||
|
||||
// array type
|
||||
case *entity.TypeArray:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
updateIncompleteInfo()
|
||||
if ty.Length < 1 {
|
||||
return ty, errors.Errorf (
|
||||
ty.Position(), "array length must be > 0")
|
||||
}
|
||||
ty.Element, err = this.analyzeType(ty.Element, false)
|
||||
return ty, err
|
||||
|
||||
// struct type
|
||||
case *entity.TypeStruct:
|
||||
ty := ty.(*entity.TypeStruct)
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
ty, err = this.assembleStructMap(ty)
|
||||
if err != nil { return err }
|
||||
updateIncompleteInfo()
|
||||
if err != nil { return nil, err }
|
||||
for name, member := range ty.MemberMap {
|
||||
ty.MemberMap[name], err = this.analyzeType(member.Type)
|
||||
if err != nil { return ty, err }
|
||||
ty.MemberMap[name].Ty,
|
||||
err = this.analyzeType(member.Ty, false)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
return ty, nil
|
||||
|
||||
// interface type
|
||||
case *entity.TypeInterface:
|
||||
ty := ty.(*entity.TypeInterface)
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
ty, err = this.assembleInterfaceMap(ty)
|
||||
if err != nil { return err }
|
||||
for name, method := range ty.MethodMap {
|
||||
ty.MethodMap[name], err = this.analyzeSignature(method)
|
||||
if err != nil { return ty, err }
|
||||
updatePseudoCompleteInfo()
|
||||
if err != nil { return nil, err }
|
||||
for name, behavior := range ty.BehaviorMap {
|
||||
ty.BehaviorMap[name], err = this.analyzeBehavior(behavior)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
return ty, nil
|
||||
|
||||
// union type
|
||||
case *entity.TypeUnion:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
updateIncompleteInfo()
|
||||
return this.analyzeTypeUnion(ty)
|
||||
|
||||
// integer type
|
||||
case *entity.TypeInt:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
updateIncompleteInfo()
|
||||
if ty.Width < 1 {
|
||||
return ty, errors.Errorf (
|
||||
ty.Position(), "integer width must be > 0")
|
||||
}
|
||||
return ty, nil
|
||||
|
||||
// floating point and word types
|
||||
case *entity.TypeFloat:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
return ty, nil
|
||||
case *entity.TypeWord:
|
||||
ty.Unt = this.unit
|
||||
ty.Acc = access
|
||||
return ty, nil
|
||||
|
||||
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
|
||||
}
|
||||
}
|
||||
|
||||
// typePrivate checks if the given type is private.
|
||||
func (this *Tree) typePrivate (pos errors.Position, ty entity.Type) error {
|
||||
if ty == nil { return nil }
|
||||
|
||||
original := ty
|
||||
if named, ok := ty.(*entity.TypeNamed); ok {
|
||||
ty = named.Type
|
||||
}
|
||||
if ty.Unit() != this.unit && ty.Access() == entity.AccessPrivate {
|
||||
return errors.Errorf(pos, "type %v is private", entity.FormatType(original))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// typeOpaque checks if the given type is opaque or private.
|
||||
func (this *Tree) typeOpaque (pos errors.Position, ty entity.Type) error {
|
||||
if ty == nil { return nil }
|
||||
|
||||
err := this.typePrivate(pos, ty)
|
||||
if err != nil { return err }
|
||||
|
||||
original := ty
|
||||
if named, ok := ty.(*entity.TypeNamed); ok {
|
||||
ty = named.Type
|
||||
}
|
||||
if ty.Unit() != this.unit && ty.Access() == entity.AccessOpaque {
|
||||
return errors.Errorf(pos, "type %v is opaque", entity.FormatType(original))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) assembleStructMap (ty *entity.TypeStruct) (*entity.TypeStruct, error) {
|
||||
ty.MemberMap = make(map[string] *entity.Declaration)
|
||||
ty.MemberOrder = make([]string, len(ty.Members))
|
||||
for index, member := range ty.Members {
|
||||
if previous, exists := ty.MemberMap[member.Name]; exists {
|
||||
return ty, participle.Errorf (
|
||||
member.Pos, "%s already listed in struct at %v",
|
||||
member.Name, previous.Pos)
|
||||
return ty, errors.Errorf (
|
||||
member.Position(), "%s already listed in struct at %v",
|
||||
member.Name, previous.Position())
|
||||
}
|
||||
ty.MemberMap [member.Name] = member
|
||||
ty.MemberOrder[index] = member.Name
|
||||
ty.Members [index] = member
|
||||
}
|
||||
|
||||
ty.Members = nil
|
||||
return ty, nil
|
||||
}
|
||||
|
||||
func (this *Tree) assembleInterfaceMap (ty *entity.TypeStruct) (*entity.TypeStruct, error) {
|
||||
ty.MethodMap = make(map[string] *entity.Signature)
|
||||
ty.MethodOrder = make([]string, len(ty.Methods))
|
||||
for index, method := range ty.Methods {
|
||||
if previous, exists := ty.MethodMap[method.Name]; exists {
|
||||
return ty, participle.Errorf (
|
||||
method.Pos, "%s already listed in interface at %v",
|
||||
method.Name, previous.Pos)
|
||||
func (this *Tree) assembleInterfaceMap (ty *entity.TypeInterface) (*entity.TypeInterface, error) {
|
||||
ty.BehaviorMap = make(map[string] *entity.Signature)
|
||||
ty.BehaviorOrder = make([]string, len(ty.Behaviors))
|
||||
for index, method := range ty.Behaviors {
|
||||
if previous, exists := ty.BehaviorMap[method.Name]; exists {
|
||||
return ty, errors.Errorf (
|
||||
method.Position(), "%s already listed in interface at %v",
|
||||
method.Name, previous.Position())
|
||||
}
|
||||
ty.MethodMap [method.Name] = member
|
||||
ty.MethodOrder[index] = method.Name
|
||||
ty.BehaviorMap [method.Name] = method
|
||||
ty.BehaviorOrder[index] = method.Name
|
||||
ty.Behaviors [index] = method
|
||||
}
|
||||
|
||||
ty.Methods = nil
|
||||
return ty, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeTypeUnion (ty *entity.TypeUnion) (*entity.TypeUnion, error) {
|
||||
ty.AllowedMap = make(map[entity.Hash] entity.Type)
|
||||
ty.AllowedOrder = make([]entity.Hash, len(ty.Allowed))
|
||||
for index, allowed := range ty.Allowed {
|
||||
allowed, err := this.analyzeType(allowed, true)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
hash := allowed.Hash()
|
||||
if previous, exists := ty.AllowedMap[hash]; exists {
|
||||
return ty, errors.Errorf (
|
||||
allowed.Position(), "%v already listed in union at %v",
|
||||
allowed, previous.Position())
|
||||
}
|
||||
ty.AllowedMap [hash] = allowed
|
||||
ty.AllowedOrder[index] = hash
|
||||
ty.Allowed [index] = allowed
|
||||
}
|
||||
return ty, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeBehavior (behavior *entity.Signature) (*entity.Signature, error) {
|
||||
behavior, err := this.assembleSignatureMap(behavior)
|
||||
behavior.Unt = this.unit
|
||||
if err != nil { return behavior, nil }
|
||||
for name, argument := range behavior.ArgumentMap {
|
||||
behavior.ArgumentMap[name].Ty, err = this.analyzeType(argument.Ty, false)
|
||||
if err != nil { return behavior, err }
|
||||
}
|
||||
behavior.Return, err = this.analyzeType(behavior.Return, false)
|
||||
return behavior, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTypedefUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"Hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
Hello: *Int
|
||||
Hello: (. x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedefUniqueErrShadowPrimitive (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow primitive U8", 2, 1,
|
||||
`
|
||||
U8: Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedefUniqueErrShadowBuiltin (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow builtin String", 2, 1,
|
||||
`
|
||||
String: Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedefUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Hello: *Int
|
||||
World: (. x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedefRecursiveErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"type List cannot be used in this context", 4, 9,
|
||||
`
|
||||
List: (.
|
||||
value: Int
|
||||
next: List)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedefRecursive (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
List: (.
|
||||
value: Int
|
||||
next: *List)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeNamedErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no type named Missing", 2, 10,
|
||||
`
|
||||
Present: Missing
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeNamed (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Example: Int
|
||||
AllBuiltin: (.
|
||||
int: Int
|
||||
uint: UInt
|
||||
byte: Byte
|
||||
rune: Rune
|
||||
string: String
|
||||
i8: I8
|
||||
i16: I16
|
||||
i32: I32
|
||||
i64: I64
|
||||
u8: U8
|
||||
u16: U16
|
||||
u32: U32
|
||||
u64: U64
|
||||
f32: F32
|
||||
f64: F64
|
||||
bool: Bool)
|
||||
[main] = {
|
||||
example:Example
|
||||
allBuiltin:AllBuiltin
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypePointer (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Ptr: *Int
|
||||
PtrPtr: **Int
|
||||
PtrPtrPtr: *PtrPtr
|
||||
ArrPtr: *5:Int
|
||||
SlicePtr: **:Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeSlice (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Slice: *:Int
|
||||
RaggedSlice: *:*:Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeArrayErrSizeZero (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"array length must be > 0", 2, 8,
|
||||
`
|
||||
Array: 0:Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeArray (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Array: 5:Int
|
||||
Matrix: 5:6:Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeStructMemberUniqueErr (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 TestTypeStructMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (.
|
||||
x:Int
|
||||
y:Int
|
||||
z:Int
|
||||
a:U8)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUnion (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
U: (| I8 I16 I32 I64 Int)
|
||||
Point: (. x:Int y:Int)
|
||||
Error: (& [error]:String)
|
||||
PointOrError: (| Point Error)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUnionAllowedUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
SomeInt: Int
|
||||
U: (|
|
||||
U8
|
||||
U16
|
||||
(. x:Int)
|
||||
Int
|
||||
(. x:SomeInt))
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUnionAllowedUniqueErrPrimitive (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"Int already listed in union at stream0.fspl:2:10", 2, 26,
|
||||
`
|
||||
U: (| I8 Int I16 I32 I64 Int I64)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUnionAllowedUniqueErrComposite (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"(. x:Int) already listed in union at stream0.fspl:5:3", 7, 3,
|
||||
`
|
||||
U: (|
|
||||
U8
|
||||
U16
|
||||
(. x:Int)
|
||||
Int
|
||||
(. x:Int))
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeInterfaceBehaviorUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"fly already listed in interface at stream0.fspl:2:10", 2, 16,
|
||||
`
|
||||
Bird: (& [fly] [fly])
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeInterfaceBehaviorUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (& [fly] [land])
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeIntegerLiteralVoid (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as Void", 2, 10,
|
||||
`
|
||||
[main] = 5
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeStringLiteralVoid (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use string literal as Void", 2, 10,
|
||||
`
|
||||
[main] = 'hello'
|
||||
`)
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="1024"
|
||||
height="1024"
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="fspl.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#FF0000"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#646464"
|
||||
inkscape:document-units="mm"
|
||||
labelstyle="default"
|
||||
showgrid="true"
|
||||
inkscape:zoom="0.69531497"
|
||||
inkscape:cx="342.29092"
|
||||
inkscape:cy="574.55976"
|
||||
inkscape:window-width="1886"
|
||||
inkscape:window-height="1000"
|
||||
inkscape:window-x="16"
|
||||
inkscape:window-y="1119"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="32"
|
||||
spacingy="32"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="4"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect6"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="128"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect5"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="128"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
d="M 592.50809,272 H 369.86667 A 33.866667,33.866667 135 0 0 336,305.86667 v 28.26666 96 96 A 33.866667,33.866667 45 0 0 369.86667,560 H 654.13333 A 33.866667,33.866667 45 0 1 688,593.86667 v 28.26666 96 A 33.866667,33.866667 135 0 1 654.13333,752 H 336"
|
||||
id="path1"
|
||||
inkscape:path-effect="#path-effect5"
|
||||
inkscape:original-d="M 592.50809,272 H 336 v 96 96 96 h 352 v 96 96 H 336"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill;stroke-linejoin:miter"
|
||||
d="M 336.03852,368.00001 H 495.98905"
|
||||
id="path2" />
|
||||
<path
|
||||
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
d="M 336.02172,464 H 623.97828"
|
||||
id="path3" />
|
||||
<path
|
||||
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
d="M 688,656 H 369.86667 A 33.866667,33.866667 135 0 0 336,689.86667 V 814.13333 A 33.866667,33.866667 45 0 0 369.86667,848 h 257.05452"
|
||||
id="path4"
|
||||
inkscape:path-effect="#path-effect6"
|
||||
inkscape:original-d="M 688,656 H 336 v 192 h 290.92119"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill;stroke-linejoin:miter"
|
||||
d="M 560.08272,176 H 463.94476"
|
||||
id="path5" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
|
@ -0,0 +1,418 @@
|
|||
package cli
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "sort"
|
||||
import "errors"
|
||||
import "strings"
|
||||
import "strconv"
|
||||
|
||||
// Cli represents a command line interface. It is not tied to os.Args, and it is
|
||||
// possible to create multiple Cli's to have so many sub-commands in one
|
||||
// program.
|
||||
type Cli struct {
|
||||
Logger
|
||||
|
||||
// Description describes the application.
|
||||
Description string
|
||||
|
||||
// The syntax description of the command. It appears in Usage after the
|
||||
// name of the program (os.Args[0]). If not specified, "[OPTIONS]..."
|
||||
// will be used instead.
|
||||
Syntax string
|
||||
|
||||
// Flag is a set of flags to parse.
|
||||
Flags []*Flag
|
||||
|
||||
// Args is a list of leftover arguments that were not parsed.
|
||||
Args []string
|
||||
|
||||
// Sub contains sub-commands.
|
||||
Sub map[string] *Cli
|
||||
|
||||
// Super contains the string of commands used to invoke this Cli.
|
||||
// For example, a command invoked using
|
||||
// <program> foo bar
|
||||
// Would have Super set to
|
||||
// "foo bar"
|
||||
// This is automatically filled out by Cli.AddSub.
|
||||
Super string
|
||||
}
|
||||
|
||||
// New creates a new Cli from a command description and a set of flags. These
|
||||
// flags should be created as variables before passing them to New(), so that
|
||||
// their values can be extracted after Parse is called.
|
||||
func New (description string, flags ...*Flag) *Cli {
|
||||
return &Cli {
|
||||
Description: description,
|
||||
Flags: flags,
|
||||
Sub: make(map[string] *Cli),
|
||||
}
|
||||
}
|
||||
|
||||
// AddSub adds a sub-command to this command. It automatically fills out the
|
||||
// sub-command's Super field, and alters the prefix of its Logger to match.
|
||||
func (this *Cli) AddSub (name string, sub *Cli) {
|
||||
super := this.Super
|
||||
if super != "" { super += " " }
|
||||
super += name
|
||||
sub.Super = super
|
||||
sub.Prefix = os.Args[0] + " " + super
|
||||
this.Sub[name] = sub
|
||||
}
|
||||
|
||||
// Parse parses the given set of command line arguments according to the flags
|
||||
// defined within the Cli. The first element of the argument slice is always
|
||||
// dropped, because it is assumed that it just contains the command name. If the
|
||||
// second element matches up with the name of a sub-command, parsing is
|
||||
// deferred to that sub-command instead. The sub-command will be given all but
|
||||
// the first element of args. This method will return the address of the
|
||||
// command that did the parsing.
|
||||
func (this *Cli) Parse (args []string) (*Cli, error) {
|
||||
// try to find a sub-command
|
||||
if this.Sub != nil && len(args) > 1 {
|
||||
if sub, ok := this.Sub[args[1]]; ok {
|
||||
return sub.Parse(args[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// strip out program name
|
||||
if len(args) > 0 { args = args[1:] }
|
||||
|
||||
next := func () string {
|
||||
args = args[1:]
|
||||
if len(args) > 0 {
|
||||
return args[0]
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
processFlag := func (flag *Flag, allowInput bool) error {
|
||||
if flag.Validate == nil {
|
||||
flag.Value = "true"
|
||||
} else {
|
||||
if len(args) < 1 || !allowInput {
|
||||
return errors.New(
|
||||
flag.String() +
|
||||
" requires a value")
|
||||
}
|
||||
value := next()
|
||||
err := flag.Validate(value)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprint (
|
||||
"bad value for ", flag.String(), ": ",
|
||||
err))
|
||||
}
|
||||
flag.Value = value
|
||||
}
|
||||
if flag.Found != nil { flag.Found(this, flag.Value) }
|
||||
return nil
|
||||
}
|
||||
|
||||
// process args one by one
|
||||
for ; len(args) > 0; next() {
|
||||
arg := args[0]
|
||||
|
||||
switch {
|
||||
case arg == "--":
|
||||
// halt parsing
|
||||
this.Args = append(this.Args, args[1:]...)
|
||||
return nil, nil
|
||||
|
||||
case strings.HasPrefix(arg, "--"):
|
||||
// long flag
|
||||
flag, ok := this.LongFlag(arg[2:])
|
||||
if !ok {
|
||||
return nil, errors.New("unknown flag: " + arg)
|
||||
}
|
||||
err := processFlag(flag, true)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
case strings.HasPrefix(arg, "-") && len(arg) > 1:
|
||||
// one or more short flags
|
||||
arg := arg[1:]
|
||||
for _, part := range arg {
|
||||
flag, ok := this.ShortFlag(part)
|
||||
if !ok {
|
||||
return nil, errors.New (
|
||||
"unknown flag: -" +
|
||||
string(part))
|
||||
}
|
||||
err := processFlag(flag, len(arg) == 1)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
default:
|
||||
// not a flag
|
||||
this.Args = append(this.Args, arg)
|
||||
}
|
||||
}
|
||||
return this, nil
|
||||
}
|
||||
|
||||
// ParseOrExit is like Parse, but if an error occurs while parsing the argument
|
||||
// list, it prints out Usage and exits with error status 2.
|
||||
func (this *Cli) ParseOrExit (args []string) *Cli {
|
||||
command, err := this.Parse(args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(this, "%s: %v\n\n", os.Args[0], err)
|
||||
this.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// ShortFlag searches for and returns the flag with the given short form. If it
|
||||
// was found, it returns true. If it was not found, it returns nil, false.
|
||||
func (this *Cli) ShortFlag (short rune) (*Flag, bool) {
|
||||
if short == 0 { return nil, false }
|
||||
for _, flag := range this.Flags {
|
||||
if flag.Short == short { return flag, true }
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// LongFlag searches for and returns the flag with the given long form. If it
|
||||
// was found, it returns true. If it was not found, it returns nil, false.
|
||||
func (this *Cli) LongFlag (long string) (*Flag, bool) {
|
||||
if long == "" { return nil, false }
|
||||
for _, flag := range this.Flags {
|
||||
if flag.Long == long { return flag, true }
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Usage prints out usage/help information.
|
||||
func (this *Cli) Usage () {
|
||||
// syntax
|
||||
fmt.Fprint(this, "Usage:")
|
||||
fmt.Fprint(this, " ", os.Args[0])
|
||||
if this.Super != "" {
|
||||
fmt.Fprint(this, " ", this.Super)
|
||||
}
|
||||
hasSubCommands := this.Sub != nil && len(this.Sub) > 0
|
||||
if hasSubCommands {
|
||||
fmt.Fprint(this, " [COMMAND]")
|
||||
}
|
||||
if this.Syntax == "" {
|
||||
fmt.Fprint(this, " [OPTION]...")
|
||||
} else {
|
||||
fmt.Fprint(this, " ", this.Syntax)
|
||||
}
|
||||
fmt.Fprint(this, "\n\n")
|
||||
|
||||
// description
|
||||
if this.Description != "" {
|
||||
fmt.Fprintf(this, "%s\n\n", this.Description)
|
||||
}
|
||||
|
||||
// fit the longest flag
|
||||
longest := 0
|
||||
for _, flag := range this.Flags {
|
||||
if len(flag.Long) > longest {
|
||||
longest = len(flag.Long)
|
||||
}
|
||||
}
|
||||
format := fmt.Sprint("\t%-", longest + 8, "s%s\n")
|
||||
|
||||
// flags
|
||||
for _, flag := range this.Flags {
|
||||
shortLong := ""
|
||||
if flag.Short == 0 {
|
||||
shortLong += " "
|
||||
} else {
|
||||
shortLong += "-" + string(flag.Short)
|
||||
}
|
||||
if flag.Short != 0 && flag.Long != "" {
|
||||
shortLong += ", "
|
||||
}
|
||||
if flag.Long != "" {
|
||||
shortLong += "--" + flag.Long
|
||||
}
|
||||
|
||||
fmt.Fprintf(this, format, shortLong, flag.Help)
|
||||
}
|
||||
|
||||
// commands
|
||||
if hasSubCommands {
|
||||
fmt.Fprint(this, "\nCommands:\n\n")
|
||||
names := sortMapKeys(this.Sub)
|
||||
|
||||
// fit the longest command
|
||||
longest := 0
|
||||
for _, name := range names {
|
||||
if len(name) > longest {
|
||||
longest = len(name)
|
||||
}
|
||||
}
|
||||
format := fmt.Sprint("\t%-", longest + 2, "s%s\n")
|
||||
|
||||
for _, name := range names {
|
||||
fmt.Fprintf(this, format, name, this.Sub[name].Description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logger prints messages to an output writer.
|
||||
type Logger struct {
|
||||
// Writer specifies the writer to output to. If it is nil, the Logger
|
||||
// will output to os.Stderr. If you wish to silence the output, set it
|
||||
// to io.Discard.
|
||||
io.Writer
|
||||
|
||||
// Debug determines whether or not debug messages will be logged.
|
||||
Debug bool
|
||||
|
||||
// Prefix is printed before all messages. If this is an empty string,
|
||||
// os.Args[0] will be used.
|
||||
Prefix string
|
||||
}
|
||||
|
||||
// Println logs a normal message.
|
||||
func (this *Logger) Println (v ...any) {
|
||||
fmt.Fprintf(this, "%s: %s", this.prefix(), fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Errorln logs an error message.
|
||||
func (this *Logger) Errorln (v ...any) {
|
||||
fmt.Fprintf(this, "%s: %s", this.prefix(), fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Warnln logs a warning.
|
||||
func (this *Logger) Warnln (v ...any) {
|
||||
fmt.Fprintf(this, "%s: warning: %s", this.prefix(), fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Debugln logs debugging information. It will only print the message if the
|
||||
// Debug field is set to true.
|
||||
func (this *Logger) Debugln (v ...any) {
|
||||
if !this.Debug { return }
|
||||
fmt.Fprintf(this, "%s: debug: %s", this.prefix(), fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
func (this *Logger) prefix () string {
|
||||
if this.Prefix == "" {
|
||||
return os.Args[0]
|
||||
} else {
|
||||
return this.Prefix
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes to the Logger's writer. If it is nil, it writes to os.Stderr.
|
||||
func (this *Logger) Write (data []byte) (n int, err error) {
|
||||
if this.Writer == nil {
|
||||
return os.Stderr.Write(data)
|
||||
} else {
|
||||
return this.Writer.Write(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Flag is a command line option.
|
||||
type Flag struct {
|
||||
Short rune // The short form of the flag (-l)
|
||||
Long string // Long form of the flag (--long-form)
|
||||
Help string // Help text to display by the flag
|
||||
|
||||
// Validate is an optional function that is called when an input is
|
||||
// passed to the flag. It checks whether or not the input is valid, and
|
||||
// returns an error if it isn't. If this function is nil, the flag is
|
||||
// assumed to have no input.
|
||||
Validate func (string) error
|
||||
|
||||
// Value contains the input given to the flag, and is filled out when
|
||||
// the flags are parsed. If this is a non-input flag (if Validate is
|
||||
// nil), this will be set to true. If the flag was not specified, Value
|
||||
// will be unchanged.
|
||||
Value string
|
||||
|
||||
// Found is an optional function that is called each time the flag is
|
||||
// found, and has a valid argument (if applicable). The value is passed
|
||||
// to the function, as well as the command that it was found in.
|
||||
Found func (*Cli, string)
|
||||
}
|
||||
|
||||
// String returns --<LongForm> if specified, and if not returns -<ShortForm>
|
||||
func (this *Flag) String () string {
|
||||
if this.Long == "" {
|
||||
return fmt.Sprintf("-%c", this.Short)
|
||||
} else {
|
||||
return fmt.Sprintf("--%s", this.Long)
|
||||
}
|
||||
}
|
||||
|
||||
// NewFlag creates a new flag that does not take in a value.
|
||||
func NewFlag (short rune, long string, help string) *Flag {
|
||||
return &Flag {
|
||||
Short: short,
|
||||
Long: long,
|
||||
Help: help,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInputFlag creates a new flag that does take in a value. This function will
|
||||
// panic if the given validation function is nil.
|
||||
func NewInputFlag (short rune, long string, help string, defaul string, validate func (string) error) *Flag {
|
||||
if validate == nil {
|
||||
panic("validate must be non-nil for a flag to take in a value")
|
||||
}
|
||||
return &Flag {
|
||||
Short: short,
|
||||
Long: long,
|
||||
Help: help,
|
||||
Value: defaul,
|
||||
Validate: validate,
|
||||
}
|
||||
}
|
||||
|
||||
// NewHelp creates a new help flag activated by --help or -h. It shows help
|
||||
// information for the command that it is a part of, and causes the program to
|
||||
// exit with a status of 2.
|
||||
func NewHelp () *Flag {
|
||||
flag := NewFlag('h', "help", "Display usage information and exit")
|
||||
flag.Found = func (command *Cli, value string) {
|
||||
command.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
// ValString is a validation function that always returns nil, accepting any
|
||||
// string.
|
||||
func ValString (value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValInt is a validation function that returns an error if the value is not an
|
||||
// integer.
|
||||
func ValInt (value string) error {
|
||||
_, err := strconv.Atoi(value)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewValSet returns a validation function that accepts a set of strings.
|
||||
func NewValSet (allowed ...string) func (value string) error {
|
||||
return func (value string) error {
|
||||
allowedFmt := ""
|
||||
for index, test := range allowed {
|
||||
if test == value { return nil }
|
||||
|
||||
if index > 0 { allowedFmt += ", " }
|
||||
allowedFmt += test
|
||||
}
|
||||
return errors.New (
|
||||
"value must be one of (" + allowedFmt + ")")
|
||||
}
|
||||
}
|
||||
|
||||
func sortMapKeys[T any] (unsorted map[string] T) []string {
|
||||
keys := make([]string, len(unsorted))
|
||||
index := 0
|
||||
for key := range unsorted {
|
||||
keys[index] = key
|
||||
index ++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// fsplc compiles FSPL programs.
|
||||
//
|
||||
// Its job is to take in FSPL code, and compile it to one of the supported
|
||||
// output formats. Currently it supports native object files, native ASM, and
|
||||
// LLVM IR code.
|
||||
//
|
||||
// Usage: fsplc [OPTION]... ADDRESS
|
||||
//
|
||||
// -h, --help Display usage information and exit
|
||||
// --debug Print extra debug information while compiling
|
||||
// -q, --quiet Don't print warnings, errors, etc.
|
||||
// -t, --filetype Output format (asm, obj, ir)
|
||||
// -o, --output Output filename
|
||||
// -O, --optimization Optimization level (0-3)
|
||||
package main
|
|
@ -0,0 +1,88 @@
|
|||
package main
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/cli"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/compiler"
|
||||
import ferrors "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/compiler/native"
|
||||
|
||||
func main () {
|
||||
// instantiate the compiler
|
||||
comp := new(compiler.Compiler)
|
||||
comp.Writer = os.Stderr
|
||||
|
||||
resolver, err := native.NativeResolver()
|
||||
if err != nil {
|
||||
comp.Errorln(err)
|
||||
os.Exit(2)
|
||||
}
|
||||
comp.Resolver = resolver
|
||||
|
||||
// take in CLI flags
|
||||
debug := cli.NewFlag (
|
||||
0, "debug",
|
||||
"Print extra debug information while compiling")
|
||||
quiet := cli.NewFlag (
|
||||
'q', "quiet",
|
||||
"Don't print warnings, errors, etc.")
|
||||
filetype := cli.NewInputFlag (
|
||||
't', "filetype",
|
||||
fmt.Sprintf (
|
||||
"Output format (%s, %s, %s)",
|
||||
compiler.FiletypeAssembly,
|
||||
compiler.FiletypeObject,
|
||||
compiler.FiletypeIR), "",
|
||||
cli.NewValSet (
|
||||
compiler.FiletypeAssembly.String(),
|
||||
compiler.FiletypeObject.String(),
|
||||
compiler.FiletypeIR.String()))
|
||||
output := cli.NewInputFlag (
|
||||
'o', "output",
|
||||
"Output filename", "",
|
||||
cli.ValString)
|
||||
optimization := cli.NewInputFlag (
|
||||
'O', "optimization",
|
||||
"Optimization level (0-3)", "0",
|
||||
cli.NewValSet("0", "1", "2", "3"))
|
||||
includePath := cli.NewInputFlag (
|
||||
'u', "unit-directory",
|
||||
"Extra directory(s) to search for units in", "",
|
||||
cli.ValString)
|
||||
includePath.Found = func (application *cli.Cli, value string) {
|
||||
comp.Resolver.AddPathFront(value)
|
||||
}
|
||||
|
||||
application := cli.New (
|
||||
"Compile FSPL source files",
|
||||
cli.NewHelp(),
|
||||
debug,
|
||||
quiet,
|
||||
filetype,
|
||||
output,
|
||||
optimization,
|
||||
includePath)
|
||||
|
||||
application.Syntax = "[OPTION]... ADDRESS"
|
||||
application.ParseOrExit(os.Args)
|
||||
if len(application.Args) != 1 {
|
||||
comp.Errorln("please specify one unit address")
|
||||
application.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// configure the compiler based on user input
|
||||
comp.Output = output.Value
|
||||
comp.Optimization = optimization.Value
|
||||
comp.Filetype, _ = compiler.FiletypeFromString(filetype.Value)
|
||||
comp.Debug = debug.Value != ""
|
||||
if quiet.Value != "" { comp.Writer = io.Discard }
|
||||
|
||||
err = comp.CompileUnit(entity.Address(application.Args[0]))
|
||||
if err != nil {
|
||||
comp.Errorln(ferrors.Format(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// fsplmod manages FSPL modules.
|
||||
// Usage: fsplmod [COMMAND] [OPTION]...
|
||||
//
|
||||
// -h, --help Display usage information and exit
|
||||
//
|
||||
// Commands:
|
||||
//
|
||||
// bump Re-generate the UUID of a module
|
||||
// new Create a new module in the specified directory
|
||||
package main
|
|
@ -0,0 +1,113 @@
|
|||
package main
|
||||
|
||||
import "io"
|
||||
import "os"
|
||||
import "path/filepath"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/cli"
|
||||
import "git.tebibyte.media/fspl/fspl/lexer"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/meta"
|
||||
import ferrors "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
func main () {
|
||||
// CLI: application
|
||||
applicationCli := cli.New (
|
||||
"Manage FSPL modules",
|
||||
cli.NewHelp())
|
||||
|
||||
// CLI: new
|
||||
newCli := cli.New (
|
||||
"Create a new module in the specified directory",
|
||||
cli.NewHelp())
|
||||
newCli.Syntax = "[OPTION]... DIRECTORY"
|
||||
applicationCli.AddSub("new", newCli)
|
||||
|
||||
// CLI: bump
|
||||
bumpCli := cli.New (
|
||||
"Re-generate the UUID of a module",
|
||||
cli.NewHelp())
|
||||
newCli.Syntax = "[OPTION]... DIRECTORY"
|
||||
applicationCli.AddSub("bump", bumpCli)
|
||||
|
||||
handleErr := func (cli *cli.Cli, err error, code int) {
|
||||
if err == nil { return }
|
||||
cli.Errorln(ferrors.Format(err))
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
switch cli := applicationCli.ParseOrExit(os.Args); cli {
|
||||
case newCli:
|
||||
// Create a new module in the specified directory
|
||||
if len(cli.Args) != 1 {
|
||||
cli.Errorln("please specify one module directory")
|
||||
cli.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
dir := cli.Args[0]
|
||||
if abs, err := filepath.Abs(dir); err == nil {
|
||||
dir = abs
|
||||
}
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
err := os.Mkdir(dir, 0755)
|
||||
handleErr(cli, err, 1)
|
||||
}
|
||||
|
||||
moduleId, err := uuid.NewRandom()
|
||||
handleErr(cli, err, 1)
|
||||
cli.Println("creating module", moduleId, "in", dir)
|
||||
metadataPath := filepath.Join(dir, "fspl.mod")
|
||||
|
||||
file, err := os.OpenFile (
|
||||
metadataPath,
|
||||
os.O_CREATE | os.O_WRONLY | os.O_TRUNC,
|
||||
0644)
|
||||
handleErr(cli, err, 1)
|
||||
defer file.Close()
|
||||
|
||||
meta := entity.Metadata {
|
||||
UUID: moduleId,
|
||||
}
|
||||
_, err = io.WriteString(file, meta.String() + "\n")
|
||||
handleErr(cli, err, 1)
|
||||
|
||||
case bumpCli:
|
||||
// Re-generate the UUID of a module
|
||||
if len(cli.Args) != 1 {
|
||||
cli.Errorln("please specify one module directory")
|
||||
cli.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
dir := cli.Args[0]
|
||||
if abs, err := filepath.Abs(dir); err == nil {
|
||||
dir = abs
|
||||
}
|
||||
metadataPath := filepath.Join(dir, "fspl.mod")
|
||||
file, err := os.OpenFile(metadataPath, os.O_RDWR, 0644)
|
||||
handleErr(cli, err, 1)
|
||||
defer file.Close()
|
||||
|
||||
lx, err := lexer.LexReader(metadataPath, file)
|
||||
handleErr(cli, err, 1)
|
||||
meta := metaParser.Tree { }
|
||||
err = meta.Parse(lx)
|
||||
handleErr(cli, err, 1)
|
||||
|
||||
newModuleId, err := uuid.NewRandom()
|
||||
handleErr(cli, err, 1)
|
||||
cli.Println("changing uuid of", dir + ":", meta.UUID, "->", newModuleId)
|
||||
meta.UUID = newModuleId
|
||||
|
||||
file.Truncate(0)
|
||||
file.Seek(0, 0)
|
||||
_, err = io.WriteString(file, meta.String() + "\n")
|
||||
handleErr(cli, err, 1)
|
||||
|
||||
default:
|
||||
applicationCli.Usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
package compiler
|
||||
|
||||
import "time"
|
||||
import "io/fs"
|
||||
import "embed"
|
||||
import "strings"
|
||||
import "testing"
|
||||
import "context"
|
||||
import "os/exec"
|
||||
import "path/filepath"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/testcommon"
|
||||
import "git.tebibyte.media/fspl/fspl/generator/native"
|
||||
|
||||
//go:embed all:test-data/*
|
||||
var testData embed.FS
|
||||
|
||||
func defaultCompiler () *Compiler {
|
||||
// instantiate and configure the compiler
|
||||
comp := new(Compiler)
|
||||
comp.Prefix = "compiler"
|
||||
comp.Resolver = NewResolver (
|
||||
"/test-data/usr/local/src/fspl",
|
||||
"/test-data/usr/src/fspl",
|
||||
"/test-data/usr/local/incude/fspl",
|
||||
"/test-data/usr/include/fspl")
|
||||
comp.Resolver.FS = testData
|
||||
comp.Filetype = FiletypeObject
|
||||
comp.Debug = true
|
||||
nativeTarget := native.NativeTarget()
|
||||
comp.Target = &nativeTarget
|
||||
return comp
|
||||
}
|
||||
|
||||
func compileDependency (
|
||||
test *testing.T,
|
||||
address entity.Address,
|
||||
) string {
|
||||
// create temporary directory
|
||||
temp := test.TempDir()
|
||||
|
||||
// instantiate and configure the compiler
|
||||
compOutputBuilder := new(strings.Builder)
|
||||
comp := defaultCompiler()
|
||||
defer func () {
|
||||
test.Logf (
|
||||
"COMPILER LOG (dependency %s):\n%s",
|
||||
address, compOutputBuilder)
|
||||
} ()
|
||||
comp.Writer = compOutputBuilder
|
||||
nickname, ok := address.Nickname()
|
||||
if !ok {
|
||||
test.Fatal("could not generate nickname for", address)
|
||||
}
|
||||
comp.Output = filepath.Join(temp, FiletypeObject.Extend(*comp.Target, nickname))
|
||||
|
||||
// compile to object file
|
||||
err := comp.CompileUnit(address)
|
||||
if err != nil {
|
||||
test.Fatal("compiler returned error:", errors.Format(err))
|
||||
}
|
||||
|
||||
return comp.Output
|
||||
}
|
||||
|
||||
func testUnit (
|
||||
test *testing.T,
|
||||
address entity.Address,
|
||||
clangArgs []string,
|
||||
stdin, stdout string,
|
||||
exit int,
|
||||
args ...string,
|
||||
) {
|
||||
// create temporary directory
|
||||
temp := test.TempDir()
|
||||
|
||||
// instantiate and configure the compiler
|
||||
compOutputBuilder := new(strings.Builder)
|
||||
comp := defaultCompiler()
|
||||
outputCompilerLog := func () {
|
||||
test.Log("COMPILER LOG (main unit):\n" + compOutputBuilder.String())
|
||||
}
|
||||
comp.Writer = compOutputBuilder
|
||||
comp.Output = filepath.Join(temp, FiletypeObject.Extend(*comp.Target, "output"))
|
||||
|
||||
// compile to object file
|
||||
err := comp.CompileUnit(address)
|
||||
if err != nil {
|
||||
outputCompilerLog()
|
||||
test.Fatal("compiler returned error:", errors.Format(err))
|
||||
}
|
||||
outputCompilerLog()
|
||||
|
||||
// link the object file into an executable
|
||||
linkOutputBuilder := new(strings.Builder)
|
||||
executablePath := FiletypeExecutable.Extend(*comp.Target, filepath.Join(temp, "output"))
|
||||
linkCommand := exec.Command("clang", append (
|
||||
clangArgs,
|
||||
comp.Output,
|
||||
"-v",
|
||||
"-o",
|
||||
executablePath)...)
|
||||
linkCommand.Stdout = linkOutputBuilder
|
||||
linkCommand.Stderr = linkOutputBuilder
|
||||
test.Log("running link command: ", linkCommand)
|
||||
err = linkCommand.Run()
|
||||
test.Log("LINKER LOG (main unit):\n" + linkOutputBuilder.String())
|
||||
if err != nil {
|
||||
test.Fatal("error linking executable:", err)
|
||||
}
|
||||
|
||||
// run the executable file (with timeout) and check its output
|
||||
timeoutDuration := 4000 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
|
||||
defer cancel()
|
||||
executableCommand := exec.CommandContext(ctx, executablePath, args...)
|
||||
stdoutBuilder := new(strings.Builder)
|
||||
executableCommand.Stdin = strings.NewReader(stdin)
|
||||
executableCommand.Stdout = stdoutBuilder
|
||||
test.Log("running executable command: ", executableCommand)
|
||||
err = executableCommand.Run()
|
||||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
test.Errorf("timeout of %v exceeded!", timeoutDuration)
|
||||
}
|
||||
|
||||
// check error, compare exit code
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
code := exitErr.ExitCode()
|
||||
if code != exit {
|
||||
test.Errorf (
|
||||
"expecting exit code %d, got %d",
|
||||
exit, code)
|
||||
}
|
||||
} else if err != nil {
|
||||
test.Fatalf("error running %s: %v", executablePath, err)
|
||||
} else {
|
||||
if 0 != exit {
|
||||
test.Errorf("expecting exit code %d, got 0", exit)
|
||||
}
|
||||
}
|
||||
|
||||
// compare stdout
|
||||
gotStdout := stdoutBuilder.String()
|
||||
if gotStdout != stdout {
|
||||
testcommon.CompareHex(test, stdout, gotStdout)
|
||||
test.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedOk (test *testing.T) {
|
||||
err := fs.WalkDir(testData, ".", func (path string, d fs.DirEntry, err error) error {
|
||||
test.Log("file:", path)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
test.Error("walk dir failed: ", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package compiler
|
||||
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "errors"
|
||||
import "os/exec"
|
||||
import "path/filepath"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
import "git.tebibyte.media/fspl/fspl/generator/native"
|
||||
|
||||
func (this *Compiler) CompileUnit (address entity.Address) error {
|
||||
this.Debugln("using search path", this.Resolver.Path)
|
||||
|
||||
path, err := this.ResolveCwd(address)
|
||||
if err != nil { return err }
|
||||
|
||||
this.Debugln("compiling unit", path)
|
||||
|
||||
var semanticTree analyzer.Tree
|
||||
_, err = this.AnalyzeUnit(&semanticTree, path, false)
|
||||
if err != nil { return err }
|
||||
|
||||
if this.Target == nil {
|
||||
target := native.NativeTarget()
|
||||
this.Target = &target
|
||||
}
|
||||
irModule, err := this.Target.Generate(semanticTree)
|
||||
if err != nil {
|
||||
return this.bug(err)
|
||||
}
|
||||
|
||||
// if the format isn't specified, try to get it from the filename
|
||||
// extension of the input. if that isn't specified, default to .o
|
||||
if this.Filetype == FiletypeUnknown {
|
||||
this.Debugln("filetype was not specified")
|
||||
if this.Output == "" {
|
||||
this.Filetype = FiletypeObject
|
||||
} else {
|
||||
var ok bool
|
||||
this.Filetype, ok = FiletypeFromExt (
|
||||
*this.Target,
|
||||
filepath.Ext(this.Output))
|
||||
if !ok {
|
||||
this.Filetype = FiletypeObject
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the output file is unspecified, generate a nickname from the
|
||||
// input address. if that doesn't work, default to "output"
|
||||
if this.Output == "" {
|
||||
nickname, ok := entity.Address(path).Nickname()
|
||||
if !ok { nickname = "output" }
|
||||
this.Output = this.Filetype.Extend(*this.Target, nickname)
|
||||
}
|
||||
|
||||
// do something based on the output extension
|
||||
// TODO: add .so
|
||||
switch this.Filetype {
|
||||
case FiletypeAssembly, FiletypeObject:
|
||||
return this.CompileIRModule(irModule, this.Filetype)
|
||||
case FiletypeIR:
|
||||
file, err := os.Create(this.Output)
|
||||
if err != nil { return err }
|
||||
defer file.Close()
|
||||
_, err = irModule.WriteTo(file)
|
||||
return err
|
||||
default:
|
||||
return errors.New(fmt.Sprintf (
|
||||
"unknown output type %s", this.Filetype))
|
||||
}
|
||||
}
|
||||
func (this *Compiler) CompileIRModule (module *llvm.Module, filetype Filetype) error {
|
||||
this.Debugln("compiling ir module to filetype", filetype)
|
||||
|
||||
commandName, args, err := this.FindBackend(filetype)
|
||||
if err != nil { return err }
|
||||
|
||||
command := exec.Command(commandName, args...)
|
||||
this.Debugln("compiling ir using:", command)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
pipe, err := command.StdinPipe()
|
||||
if err != nil { return err }
|
||||
|
||||
err = command.Start()
|
||||
if err != nil { return err }
|
||||
_, err = module.WriteTo(pipe)
|
||||
if err != nil { return err }
|
||||
pipe.Close()
|
||||
return command.Wait()
|
||||
}
|
||||
|
||||
// FindBackend returns the name of an LLVM backend command, and a list of
|
||||
// arguments to pass to it. It tries commands in this order:
|
||||
// - llc
|
||||
// - llc-<latest> -> llc-14
|
||||
// - clang
|
||||
// If none were found, it returns an error.
|
||||
func (this *Compiler) FindBackend (filetype Filetype) (string, []string, error) {
|
||||
optimization := "0"
|
||||
if this.Optimization != "" { optimization = this.Optimization }
|
||||
|
||||
llcArgs := []string {
|
||||
"-",
|
||||
fmt.Sprintf("-filetype=%s", filetype),
|
||||
"-o", this.Output,
|
||||
fmt.Sprintf("-O=%s", optimization),
|
||||
}
|
||||
|
||||
// attempt to use llc command
|
||||
commandName := "llc"
|
||||
_, err := exec.LookPath(commandName)
|
||||
this.Debugln("trying", commandName)
|
||||
if err == nil {
|
||||
return commandName, llcArgs, nil
|
||||
}
|
||||
|
||||
// attempt to find a versioned llc command, counting down from the
|
||||
// latest known version
|
||||
llcVersion := 17 // TODO change this number to the latest llc version in
|
||||
// the future
|
||||
for err != nil && llcVersion >= 14 {
|
||||
commandName := fmt.Sprintf("llc-%d", llcVersion)
|
||||
this.Debugln("trying", commandName)
|
||||
_, err = exec.LookPath(commandName)
|
||||
if err == nil {
|
||||
if llcVersion == 14 {
|
||||
// -opaque-pointers is needed in version 14
|
||||
llcArgs = append(llcArgs, "-opaque-pointers")
|
||||
this.Warnln (
|
||||
"using llvm llc version 14, which",
|
||||
"does not have proper support for",
|
||||
"opaque pointers. expect bugs")
|
||||
}
|
||||
return commandName, llcArgs, nil
|
||||
}
|
||||
llcVersion --
|
||||
}
|
||||
|
||||
// attempt to use clang
|
||||
commandName = "clang"
|
||||
_, err = exec.LookPath(commandName)
|
||||
this.Debugln("trying", commandName)
|
||||
if err == nil {
|
||||
if filetype != FiletypeObject {
|
||||
return "", nil, errors.New(fmt.Sprint (
|
||||
"need 'llc' to compile to ", filetype))
|
||||
}
|
||||
this.Warnln("falling back to clang to compile llvm ir. expect bugs")
|
||||
return commandName, []string {
|
||||
"-c",
|
||||
"-x", "ir",
|
||||
"-o", this.Output,
|
||||
fmt.Sprintf("-O%s", this.Optimization),
|
||||
"-",
|
||||
}, nil
|
||||
}
|
||||
|
||||
return "", nil, errors.New (
|
||||
"no suitable backends found: please make sure either 'llc' " +
|
||||
"or 'clang' are accessable from your PATH")
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
package compiler
|
||||
|
||||
import "fmt"
|
||||
import "sort"
|
||||
import "io/fs"
|
||||
import "errors"
|
||||
import "path/filepath"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/cli"
|
||||
import "git.tebibyte.media/fspl/fspl/lexer"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/fspl"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/meta"
|
||||
import ferrors "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
type Compiler struct {
|
||||
*Resolver
|
||||
cli.Logger
|
||||
|
||||
Target *generator.Target
|
||||
Output string
|
||||
Optimization string
|
||||
Filetype Filetype
|
||||
}
|
||||
|
||||
func (this *Compiler) bug (err error) error {
|
||||
return errors.New(fmt.Sprintln (
|
||||
"Bug detected in the compiler!\n" +
|
||||
"The FSPL compiler has experienced an error that should not",
|
||||
"happen.\n" +
|
||||
"Please submit a report with this info and the code you were",
|
||||
"compiling to:",
|
||||
"https://git.tebibyte.media/sashakoshka/fspl/issues\n" +
|
||||
"The error is:", err))
|
||||
}
|
||||
|
||||
func (this *Compiler) AnalyzeUnit (
|
||||
semanticTree *analyzer.Tree,
|
||||
path string,
|
||||
skim bool,
|
||||
) (
|
||||
uuid.UUID,
|
||||
error,
|
||||
) {
|
||||
this.Debugln("entering unit analysis", path)
|
||||
filePath, isFile := entity.Address(path).SourceFile()
|
||||
modulePath, isModule := entity.Address(path).Module()
|
||||
if !isFile && !isModule {
|
||||
return uuid.UUID { }, errors.New(fmt.Sprintf (
|
||||
"%v is not a module, nor a source file",
|
||||
path))
|
||||
}
|
||||
|
||||
if isModule {
|
||||
return this.AnalyzeModule(semanticTree, modulePath, skim)
|
||||
} else {
|
||||
return this.AnalyzeSourceFile(semanticTree, filePath, skim)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Compiler) AnalyzeModule (
|
||||
semanticTree *analyzer.Tree,
|
||||
path string,
|
||||
skim bool,
|
||||
) (
|
||||
uuid.UUID,
|
||||
error,
|
||||
) {
|
||||
this.Debugln("entering module analysis", path)
|
||||
|
||||
// parse module metadata file
|
||||
var metaTree metaParser.Tree
|
||||
metaPath := filepath.Join(path, "fspl.mod")
|
||||
metaFile, err := openAbsolute(this.FS, metaPath)
|
||||
if err != nil { return uuid.UUID { }, err }
|
||||
defer metaFile.Close()
|
||||
lx, err := lexer.LexReader(metaPath, metaFile)
|
||||
if err != nil { return uuid.UUID { }, err }
|
||||
err = metaTree.Parse(lx)
|
||||
if err != nil { return uuid.UUID { }, err }
|
||||
|
||||
// ensure metadata is well formed
|
||||
dependencies := make(map[string] *entity.Dependency)
|
||||
for _, dependency := range metaTree.Dependencies {
|
||||
this.Debugln(dependency)
|
||||
nickname := dependency.Nickname
|
||||
if nickname == "" {
|
||||
newNickname, ok := dependency.Address.Nickname()
|
||||
if !ok {
|
||||
return uuid.UUID { }, ferrors.Errorf (
|
||||
dependency.Position(),
|
||||
"cannot generate nickname for %v, " +
|
||||
"please add one after the address",
|
||||
dependency.Address)
|
||||
}
|
||||
nickname = newNickname
|
||||
}
|
||||
if previous, exists := dependencies[nickname]; exists {
|
||||
return uuid.UUID { }, ferrors.Errorf (
|
||||
dependency.Position(),
|
||||
"unit with nickname %v already listed at %v",
|
||||
nickname, previous.Position())
|
||||
}
|
||||
dependencies[nickname] = dependency
|
||||
}
|
||||
|
||||
// analyze dependency units, building a nickname translation table
|
||||
nicknames := make(map[string] uuid.UUID)
|
||||
dependencyKeys := sortMapKeys(dependencies)
|
||||
for _, nickname := range dependencyKeys {
|
||||
dependency := dependencies[nickname]
|
||||
resolved, err := this.Resolve(path, dependency.Address)
|
||||
if err != nil { return uuid.UUID { }, err }
|
||||
dependencyUUID, err := this.AnalyzeUnit(semanticTree, resolved, true)
|
||||
if err != nil { return uuid.UUID { }, err }
|
||||
nicknames[nickname] = dependencyUUID
|
||||
}
|
||||
|
||||
// parse this unit
|
||||
var syntaxTree fsplParser.Tree
|
||||
err = this.ParseUnit(&syntaxTree, path, skim)
|
||||
if err != nil { return uuid.UUID { }, err}
|
||||
|
||||
// analyze this unit
|
||||
this.Debugln("analyzing", path, metaTree.UUID)
|
||||
err = semanticTree.Analyze(metaTree.UUID, nicknames, syntaxTree)
|
||||
if err != nil { return uuid.UUID { }, err}
|
||||
|
||||
return metaTree.UUID, nil
|
||||
}
|
||||
|
||||
func (this *Compiler) AnalyzeSourceFile (
|
||||
semanticTree *analyzer.Tree,
|
||||
path string,
|
||||
skim bool,
|
||||
) (
|
||||
uuid.UUID,
|
||||
error,
|
||||
) {
|
||||
this.Debugln("entering source file analysis", path)
|
||||
|
||||
// parse this unit
|
||||
var syntaxTree fsplParser.Tree
|
||||
err := this.ParseUnit(&syntaxTree, path, skim)
|
||||
if err != nil { return uuid.UUID { }, err}
|
||||
|
||||
// analyze this unit
|
||||
unitId := entity.Address(path).UUID()
|
||||
this.Debugln("analyzing", path, unitId)
|
||||
err = semanticTree.Analyze(unitId, nil, syntaxTree)
|
||||
if err != nil { return uuid.UUID { }, err}
|
||||
|
||||
return unitId, nil
|
||||
}
|
||||
|
||||
func (this *Compiler) ParseUnit (
|
||||
syntaxTree *fsplParser.Tree,
|
||||
path string,
|
||||
skim bool,
|
||||
) (
|
||||
error,
|
||||
) {
|
||||
filePath, isFile := entity.Address(path).SourceFile()
|
||||
modulePath, isModule := entity.Address(path).Module()
|
||||
if !isFile && !isModule {
|
||||
return errors.New(fmt.Sprintf (
|
||||
"%v is not a module, nor a source file",
|
||||
path))
|
||||
}
|
||||
|
||||
if isModule {
|
||||
return this.ParseModule(syntaxTree, modulePath, skim)
|
||||
} else {
|
||||
return this.ParseSourceFile(syntaxTree, filePath, skim)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Compiler) ParseModule (
|
||||
syntaxTree *fsplParser.Tree,
|
||||
path string,
|
||||
skim bool,
|
||||
) (
|
||||
error,
|
||||
) {
|
||||
this.Debugln("parsing module", path)
|
||||
|
||||
// parse all files in the module
|
||||
file, err := openAbsolute(this.FS, path)
|
||||
if err != nil { return err }
|
||||
defer file.Close()
|
||||
dir, ok := file.(fs.ReadDirFile)
|
||||
if !ok { return errors.New(fmt.Sprintf("%s is not a directory", path)) }
|
||||
entries, err := dir.ReadDir(0)
|
||||
if err != nil { return err }
|
||||
|
||||
for _, entry := range entries {
|
||||
if filepath.Ext(entry.Name()) != ".fspl" { continue }
|
||||
|
||||
filePath := filepath.Join(path, entry.Name())
|
||||
file, err := openAbsolute(this.FS, filePath)
|
||||
if err != nil { return err }
|
||||
defer file.Close()
|
||||
lx, err := lexer.LexReader(filePath, file)
|
||||
if err != nil { return err }
|
||||
|
||||
if skim {
|
||||
err = syntaxTree.Skim(lx)
|
||||
} else {
|
||||
err = syntaxTree.Parse(lx)
|
||||
}
|
||||
if err != nil { return err }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Compiler) ParseSourceFile (
|
||||
syntaxTree *fsplParser.Tree,
|
||||
path string,
|
||||
skim bool,
|
||||
) (
|
||||
error,
|
||||
) {
|
||||
this.Debugln("parsing source file", path)
|
||||
|
||||
file, err := openAbsolute(this.FS, path)
|
||||
if err != nil { return err }
|
||||
defer file.Close()
|
||||
lx, err := lexer.LexReader(path, file)
|
||||
if err != nil { return err }
|
||||
if skim {
|
||||
err = syntaxTree.Skim(lx)
|
||||
} else {
|
||||
err = syntaxTree.Parse(lx)
|
||||
}
|
||||
if err != nil { return err }
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortMapKeys[T any] (unsorted map[string] T) []string {
|
||||
keys := make([]string, len(unsorted))
|
||||
index := 0
|
||||
for key := range unsorted {
|
||||
keys[index] = key
|
||||
index ++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package compiler
|
||||
|
||||
import "testing"
|
||||
import "runtime"
|
||||
|
||||
var nativeLineBreak = "\n"
|
||||
func init () {
|
||||
if runtime.GOOS == "windows" {
|
||||
nativeLineBreak = "\r\n"
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelloWorld (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/hello", nil,
|
||||
"", "Hello, world!" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestPuts (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/puts", nil,
|
||||
"", "hello" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestExitCode13 (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/exit-code-13", nil,
|
||||
"", "",
|
||||
13,
|
||||
)}
|
||||
|
||||
func TestSystemInclude (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/system-include", nil,
|
||||
"", "Hello, /usr/include!" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestSystemSrc (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/system-src", dependencies,
|
||||
"", "Hello, /usr/src!" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestArgC (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/argc", nil,
|
||||
"", "",
|
||||
2, "arg1", "arg2",
|
||||
)}
|
||||
|
||||
// TODO: uncomment once #47 has been dealt with
|
||||
// func TestArgV (test *testing.T) {
|
||||
// testUnit (test,
|
||||
// "/test-data/data/argv", nil,
|
||||
// "", "This is an argument\n",
|
||||
// 0, "This is an argument",
|
||||
// )}
|
||||
|
||||
func TestSimpleInterface (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/simple-interface", nil,
|
||||
"", "",
|
||||
9,
|
||||
)}
|
||||
|
||||
func TestWriterInterface (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/writer", dependencies,
|
||||
"", "well hello their" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestMatchExitCode (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/match-exit-code", nil,
|
||||
"", "",
|
||||
7,
|
||||
)}
|
||||
|
||||
func TestMatchPrint (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/match-print", dependencies,
|
||||
"", "F64" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestMatchDefaultPrint (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/match-default-print", dependencies,
|
||||
"", "something else" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestSwitchExitCode (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/switch-exit-code", nil,
|
||||
"", "",
|
||||
4,
|
||||
)}
|
||||
|
||||
func TestReturnAssign (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/return-assign", dependencies,
|
||||
"", "false" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestLoopBreakExitCode (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/loop-break-exit-code", nil,
|
||||
"", "",
|
||||
5,
|
||||
)}
|
||||
|
||||
func TestLoopBreakBranchExitCode (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/loop-break-branch-exit-code", nil,
|
||||
"", "",
|
||||
2,
|
||||
)}
|
||||
|
||||
func TestForSimple (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/for-simple", nil,
|
||||
"", "",
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestForStringArray (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/for-string-array", dependencies,
|
||||
"", "a" + nativeLineBreak + "b" + nativeLineBreak + "c" + nativeLineBreak +
|
||||
"a" + nativeLineBreak + "b" + nativeLineBreak + "c" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestForStringArrayOnce (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/for-string-array-once", dependencies,
|
||||
"", "abc" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestForBreakBranch (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/for-break-branch", dependencies,
|
||||
"", "iter" + nativeLineBreak + "iter" + nativeLineBreak,
|
||||
0,
|
||||
)}
|
|
@ -0,0 +1,3 @@
|
|||
// Package compiler is responsible for orchestrating the different FSPL
|
||||
// compilation stages, as well as invoking the LLVM IR compiler.
|
||||
package compiler
|
|
@ -0,0 +1,95 @@
|
|||
package compiler
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
// Filetype represents an output filetype.
|
||||
type Filetype int; const (
|
||||
FiletypeUnknown Filetype = iota
|
||||
FiletypeObject
|
||||
FiletypeLibrary
|
||||
FiletypeAssembly
|
||||
FiletypeIR
|
||||
FiletypeExecutable
|
||||
)
|
||||
|
||||
// FiletypeFromString returns a filetype based on the given name.
|
||||
func FiletypeFromString (ext string) (Filetype, bool) {
|
||||
switch ext {
|
||||
case "": return FiletypeUnknown, true
|
||||
case "obj": return FiletypeObject, true
|
||||
case "lib": return FiletypeLibrary, true
|
||||
case "asm": return FiletypeAssembly, true
|
||||
case "ir": return FiletypeIR, true
|
||||
case "exe": return FiletypeExecutable, true
|
||||
default: return FiletypeUnknown, false
|
||||
}
|
||||
}
|
||||
|
||||
// FiletypeFromExt returns a filetype based on the given filename extension.
|
||||
func FiletypeFromExt (target generator.Target, ext string) (Filetype, bool) {
|
||||
switch ext {
|
||||
case targetObjExt(target): return FiletypeObject, true
|
||||
case targetSoExt(target): return FiletypeLibrary, true
|
||||
case ".s": return FiletypeAssembly, true
|
||||
case ".ll": return FiletypeIR, true
|
||||
case targetExeExt(target): return FiletypeExecutable, true
|
||||
default: return FiletypeUnknown, false
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of the filetype.
|
||||
func (filetype Filetype) String () string {
|
||||
switch filetype {
|
||||
case FiletypeUnknown: return ""
|
||||
case FiletypeObject: return "obj"
|
||||
case FiletypeLibrary: return "lib"
|
||||
case FiletypeAssembly: return "asm"
|
||||
case FiletypeIR: return "ir"
|
||||
case FiletypeExecutable: return "exe"
|
||||
default: return fmt.Sprintf("Filetype(%d)", filetype)
|
||||
}
|
||||
}
|
||||
|
||||
// Ext returns the standard filename extension of the filetype.
|
||||
func (filetype Filetype) Ext (target generator.Target) string {
|
||||
switch filetype {
|
||||
case FiletypeUnknown: return ""
|
||||
case FiletypeObject: return targetObjExt(target)
|
||||
case FiletypeLibrary: return targetSoExt(target)
|
||||
case FiletypeAssembly: return ".s"
|
||||
case FiletypeIR: return ".ll"
|
||||
case FiletypeExecutable: return targetExeExt(target)
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Extend adds the extension of the filetype onto the specified path.
|
||||
func (filetype Filetype) Extend (target generator.Target, path string) string {
|
||||
return path + filetype.Ext(target)
|
||||
}
|
||||
|
||||
func targetSoExt (target generator.Target) string {
|
||||
// TODO: more research is required here
|
||||
switch target.OS {
|
||||
case "win32": return ".dll"
|
||||
default: return ".so"
|
||||
}
|
||||
}
|
||||
|
||||
func targetObjExt (target generator.Target) string {
|
||||
// TODO: more research is required here
|
||||
switch target.OS {
|
||||
case "win32": return ".obj"
|
||||
default: return ".o"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func targetExeExt (target generator.Target) string {
|
||||
// TODO: more research is required here
|
||||
switch target.OS {
|
||||
case "win32": return ".exe"
|
||||
default: return ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package compiler
|
||||
|
||||
import "os"
|
||||
import "io/fs"
|
||||
import "runtime"
|
||||
import "strings"
|
||||
import "path/filepath"
|
||||
|
||||
// OsFS returns an fs.FS that represents the operating system's filesystem. In
|
||||
// Windows, volume names (A:, B:, C:, etc.) are treated as subdirectories within
|
||||
// the root.
|
||||
type OsFS struct { }
|
||||
|
||||
// win
|
||||
// C:/Something -> C:\Something
|
||||
// "" -> ""
|
||||
|
||||
// unix
|
||||
// C:/Something -> /C:/Something
|
||||
// "" -> /
|
||||
|
||||
func (this OsFS) Open (name string) (fs.File, error) {
|
||||
fullname, err := this.nativize(name)
|
||||
if err != nil {
|
||||
return nil, this.err("open", name, err)
|
||||
}
|
||||
file, err := os.Open(fullname)
|
||||
if err != nil {
|
||||
return nil, this.err("open", name, err.(*os.PathError).Err)
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (this OsFS) nativize (name string) (string, error) {
|
||||
if !fs.ValidPath(name) {
|
||||
return "", fs.ErrInvalid
|
||||
}
|
||||
if filepath.Separator != '/' {
|
||||
if strings.Contains(name, string(filepath.Separator)) {
|
||||
return "", fs.ErrInvalid
|
||||
}
|
||||
}
|
||||
if runtime.GOOS != "windows" {
|
||||
// TODO is this correct for all non-windows systems?
|
||||
name = "/" + name
|
||||
}
|
||||
name = filepath.FromSlash(name)
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (this OsFS) err (op, path string, err error) error {
|
||||
return &fs.PathError {
|
||||
Op: "open",
|
||||
Path: path,
|
||||
Err: err,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// Package native provides OS native parameters for the compilation process.
|
||||
package native
|
||||
|
||||
import "os"
|
||||
import "errors"
|
||||
import "runtime"
|
||||
import "path/filepath"
|
||||
import "git.tebibyte.media/fspl/fspl/compiler"
|
||||
|
||||
// NativeResolver returns a resolver that resolves paths native to the operating
|
||||
// system.
|
||||
func NativeResolver () (*compiler.Resolver, error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows": return windowsNativeResolver()
|
||||
default: return unixNativeResolver()
|
||||
}
|
||||
}
|
||||
|
||||
func unixNativeResolver () (*compiler.Resolver, error) {
|
||||
resolver := compiler.NewResolver (
|
||||
"/usr/local/src/fspl",
|
||||
"/usr/src/fspl",
|
||||
"/usr/local/incude/fspl",
|
||||
"/usr/include/fspl")
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil { return nil, err }
|
||||
resolver.AddPathFront (
|
||||
filepath.Join(homeDir, ".local/src/fspl"),
|
||||
filepath.Join(homeDir, ".local/include/fspl"))
|
||||
return resolver, nil
|
||||
}
|
||||
|
||||
func windowsNativeResolver () (*compiler.Resolver, error) {
|
||||
localAppData := os.Getenv("LOCALAPPDATA")
|
||||
if localAppData == "" {
|
||||
return nil, errors.New("could not get %LOCALAPPDATA%")
|
||||
}
|
||||
allUsersProfile := os.Getenv("ALLUSERSPROFILE")
|
||||
if allUsersProfile == "" {
|
||||
return nil, errors.New("could not get %ALLUSERSPROFILE%")
|
||||
}
|
||||
programFiles := os.Getenv("ProgramFiles")
|
||||
if programFiles == "" {
|
||||
return nil, errors.New("could not get %ProgramFiles%")
|
||||
}
|
||||
|
||||
resolver := compiler.NewResolver (
|
||||
filepath.Join(localAppData, "fspl\\src"),
|
||||
filepath.Join(localAppData, "fspl\\include"),
|
||||
filepath.Join(allUsersProfile, "fspl\\src"),
|
||||
filepath.Join(allUsersProfile, "fspl\\include"),
|
||||
filepath.Join(programFiles, "fspl\\src"),
|
||||
filepath.Join(programFiles, "fspl\\include"))
|
||||
return resolver, nil
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package compiler
|
||||
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "io/fs"
|
||||
import "errors"
|
||||
import "strings"
|
||||
import "path/filepath"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
// Resolver turns addresses into absolute filepaths.
|
||||
type Resolver struct {
|
||||
// FS specifies a filesystem to search.
|
||||
FS fs.FS
|
||||
|
||||
// Path specifies a list of paths that a unit may exist directly in. The
|
||||
// Resolver will search the FS for each path listed, starting at the
|
||||
// first and ending at the last. Thus, paths nearer the start will have
|
||||
// a higher priority.
|
||||
Path []string
|
||||
}
|
||||
|
||||
// NewResolver creates a new resolver with OsFS.
|
||||
func NewResolver (path ...string) *Resolver {
|
||||
return &Resolver {
|
||||
FS: OsFS { },
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// AddPath adds one or more items to the resolver's search path.
|
||||
func (resolver *Resolver) AddPath (path ...string) {
|
||||
resolver.Path = append(resolver.Path, path...)
|
||||
}
|
||||
|
||||
// AddPathFront adds one or more items to the beginning of the resolver's search
|
||||
// path.
|
||||
func (resolver *Resolver) AddPathFront (path ...string) {
|
||||
// i know how memory works in go babyyyyyy
|
||||
newPath := make([]string, len(path))
|
||||
copy(newPath, path)
|
||||
resolver.Path = append(newPath, resolver.Path...)
|
||||
}
|
||||
|
||||
// Resolve resolves an address into an absolute filepath starting at the
|
||||
// filesystem root. It follows these rules:
|
||||
// - If the address starts with '.', '..', it is joined with context
|
||||
// - If the address starts with '/', it is treated as an absolute path from
|
||||
// the fs root
|
||||
// - Else, the address is searched for in the resolver's paths
|
||||
func (resolver *Resolver) Resolve (context string, address entity.Address) (string, error) {
|
||||
strAddr := string(address)
|
||||
switch {
|
||||
case strings.HasPrefix(strAddr, "."):
|
||||
return filepath.Join(context, strAddr), nil
|
||||
case strings.HasPrefix(strAddr, "/"):
|
||||
return strAddr, nil
|
||||
default:
|
||||
if resolver.Path == nil || resolver.FS == nil {
|
||||
return "", errors.New(fmt.Sprintf (
|
||||
"could not find unit %v: %v",
|
||||
address, "no search path specified"))
|
||||
}
|
||||
location, err := resolver.search(strAddr)
|
||||
if err != nil {
|
||||
return "", errors.New(fmt.Sprintf (
|
||||
"could not find unit %v: %v",
|
||||
address, err))
|
||||
}
|
||||
return location, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (resolver *Resolver) search (needle string) (string, error) {
|
||||
for _, dirPath := range resolver.Path {
|
||||
// attempt to open the file as dir.
|
||||
// if we can't open the dir, just skip it, because it is
|
||||
// perfectly reasonable that the user might not have
|
||||
// /usr/local/include/fspl etc.
|
||||
file, err := openAbsolute(resolver.FS, dirPath)
|
||||
if err != nil { continue }
|
||||
dir, ok := file.(fs.ReadDirFile)
|
||||
if !ok { continue }
|
||||
entries, err := dir.ReadDir(0)
|
||||
if err != nil { continue }
|
||||
dir.Close()
|
||||
|
||||
// search through the entries
|
||||
for _, entry := range entries {
|
||||
if entry.Name() == needle {
|
||||
return filepath.Join(dirPath, entry.Name()), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fs.ErrNotExist
|
||||
}
|
||||
|
||||
// ResolveCwd resolves the address within the context of the current working
|
||||
// directory.
|
||||
func (resolver *Resolver) ResolveCwd (address entity.Address) (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil { return "", err }
|
||||
return resolver.Resolve(wd, address)
|
||||
}
|
||||
|
||||
// openAbsolute exists because fs.FS implementations do not understand absolute
|
||||
// paths, which the FSPL compiler runs on. It converts an absolute path to a
|
||||
// slash path relative to "/" and opens the file.
|
||||
func openAbsolute (filesystem fs.FS, path string) (fs.File, error) {
|
||||
path = strings.TrimPrefix(filepath.ToSlash(path), "/")
|
||||
return filesystem.Open(path)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'2b4a9e61-1f87-4173-b6d5-c0174b0abe43'
|
|
@ -0,0 +1 @@
|
|||
[main argc:I32 argv:**Byte]: I32 'main' = [- argc 1]
|
|
@ -0,0 +1,2 @@
|
|||
'ad9c62ae-c580-4c53-86e1-d5bf3ce251e5'
|
||||
+ 'cstdio'
|
|
@ -0,0 +1,5 @@
|
|||
[main argc:I32 argv:**Byte]: I32 'main' = {
|
||||
argv = [~~ **Byte [+ [~~Index argv] 1]]
|
||||
cstdio::[puts [.argv]]
|
||||
0
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'2380d7e1-00a4-44a8-ae4a-30285a158788'
|
|
@ -0,0 +1 @@
|
|||
[main]: Int 'main' = 13
|
|
@ -0,0 +1,2 @@
|
|||
'61b23750-b3c5-4cbd-85f7-71322a23f980'
|
||||
+ 'io'
|
|
@ -0,0 +1,8 @@
|
|||
[main]:I32 'main' = {
|
||||
arr:3:Int = (5 6 7)
|
||||
for i:Index e:Int in arr {
|
||||
if [>= i 2] then [break]
|
||||
io::[println 'iter']
|
||||
}
|
||||
0
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
'11371094-76a7-424f-b9ae-14636962073b'
|
||||
+ 'io'
|
|
@ -0,0 +1,5 @@
|
|||
[main]:I32 'main' = {
|
||||
str:String = 'a'
|
||||
for c:Byte in str { }
|
||||
0
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
'c9024148-f4ec-4d69-84c1-c838f9c68073'
|
||||
+ 'io'
|
||||
+ 'cstdio'
|
|
@ -0,0 +1,5 @@
|
|||
[main]:I32 'main' = {
|
||||
arr:5:String = ('abc' 'def' 'ghi')
|
||||
for e:String in arr { io::[println e] [break] }
|
||||
0
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
'cfb45339-4c4d-4519-9578-3abf0c698867'
|
||||
+ 'io'
|
|
@ -0,0 +1,10 @@
|
|||
[main]:I32 'main' = {
|
||||
; array
|
||||
arr:3:String = ('a' 'b' 'c')
|
||||
for e:String in arr io::[println e]
|
||||
|
||||
; slice
|
||||
slice:*:String = arr
|
||||
for e:String in slice io::[println e]
|
||||
0
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'241467f1-219a-4f13-8cdf-32ef2ad88277'
|
|
@ -0,0 +1,6 @@
|
|||
[puts string:*Byte]: Index 'puts'
|
||||
|
||||
[main]: I32 'main' = {
|
||||
[puts 'Hello, world!']
|
||||
0
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'e8962449-6214-4622-afcf-f6b58046fab2'
|
|
@ -0,0 +1,10 @@
|
|||
[main]:I32 'main' = {
|
||||
y:I32 = 6
|
||||
loop {
|
||||
if [< y 3]
|
||||
then [break y]
|
||||
else {
|
||||
y = [-- y]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'779e632d-88a6-46c4-bfa2-7db4fc919f07'
|
|
@ -0,0 +1,3 @@
|
|||
[main]:I32 'main' = loop {
|
||||
[break 5]
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
'4f3d6ccb-c233-4648-abd2-72e3f9a82efd'
|
||||
+ 'io'
|
|
@ -0,0 +1,11 @@
|
|||
[main]: I32 'main' = {
|
||||
x:UInt = 7
|
||||
[matchToInt x]
|
||||
0
|
||||
}
|
||||
|
||||
U: (| Int F64 UInt)
|
||||
[matchToInt u:U] = match u
|
||||
| u:Int io::[println 'Int']
|
||||
| u:F64 io::[println 'F64']
|
||||
* io::[println 'something else']
|
|
@ -0,0 +1 @@
|
|||
'839f0104-91f5-4db0-be52-6a17c571ebb1'
|
|
@ -0,0 +1,9 @@
|
|||
[main]: I32 'main' = {
|
||||
x:F32 = 7.7
|
||||
[matchToInt x]
|
||||
}
|
||||
|
||||
U: (| I32 F32)
|
||||
[matchToInt u:U]:I32 = match u
|
||||
| u:I32 u
|
||||
| u:F32 [~I32 u]
|
|
@ -0,0 +1,2 @@
|
|||
'624d4557-5291-4ad7-9283-7c200b9c2942'
|
||||
+ 'io'
|
|
@ -0,0 +1,10 @@
|
|||
[main]: I32 'main' = {
|
||||
x:F64 = 7.7
|
||||
[matchToInt x]
|
||||
0
|
||||
}
|
||||
|
||||
U: (| Int F64 UInt)
|
||||
[matchToInt u:U] = match u
|
||||
| u:Int io::[println 'Int']
|
||||
| u:F64 io::[println 'F64']
|
|
@ -0,0 +1,2 @@
|
|||
'aaad83b9-6610-45c4-ad89-4c28cf3a3b26'
|
||||
+ 'cstdio'
|
|
@ -0,0 +1,4 @@
|
|||
[main]: I32 'main' = {
|
||||
cstdio::[puts 'hello']
|
||||
0
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
'678748d4-ce9e-45db-bccb-07eecf067770'
|
||||
+ 'io'
|
|
@ -0,0 +1,10 @@
|
|||
[main]:I32 'main' = {
|
||||
if [is5 7]
|
||||
then io::[println 'true']
|
||||
else io::[println 'false']
|
||||
0
|
||||
}
|
||||
|
||||
[is5 x:Int]:Bool = if [= x 5]
|
||||
then true
|
||||
else [return false]
|
|
@ -0,0 +1 @@
|
|||
'1cabcb3c-ebac-4bc1-8610-76cbc47fde3a'
|
|
@ -0,0 +1,9 @@
|
|||
Numbered: (& [number]:Int)
|
||||
Number: Int
|
||||
Number.[number]: Int = [.this]
|
||||
|
||||
[main]: I32 'main' = {
|
||||
num:Number = 9
|
||||
ifa:Numbered = num
|
||||
[~I32 ifa.[number]]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'433ed4ee-be14-4d0f-baef-a13919ec3b6c'
|
|
@ -0,0 +1,10 @@
|
|||
[main]: I32 'main' = {
|
||||
x:I32 = 1
|
||||
[switch x]
|
||||
}
|
||||
|
||||
[switch x:I32]:I32 = switch x
|
||||
| 0 5
|
||||
| 1 4
|
||||
| 2 3
|
||||
* 0
|
|
@ -0,0 +1,2 @@
|
|||
'15059679-90a0-468a-be57-62f8b958d46b'
|
||||
+ 'cstdio'
|
|
@ -0,0 +1,4 @@
|
|||
[main]: I32 'main' = {
|
||||
cstdio::[puts 'Hello, /usr/include!']
|
||||
0
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
'fb8bddc7-2db1-45f6-b81c-d58a28298ab0'
|
||||
+ 'io'
|
|
@ -0,0 +1,4 @@
|
|||
[main]: I32 'main' = {
|
||||
io::[println 'Hello, /usr/src!']
|
||||
0
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
'7e7ee20f-30fc-441a-943c-2acdb40c9df1'
|
||||
+ 'io'
|
|
@ -0,0 +1,7 @@
|
|||
[sayHello writer:io::Writer] = writer.[write 'well hello their\n']
|
||||
|
||||
[main]: I32 'main' = {
|
||||
stdout:io::File = 1
|
||||
[sayHello stdout]
|
||||
0
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
'f95aaa14-612c-45cd-b3ae-fd24049cc81b'
|
|
@ -0,0 +1,4 @@
|
|||
+ FileDescriptor: Int
|
||||
+ [write file:FileDescriptor buffer:*Byte count:Index]: Index 'write'
|
||||
+ [read file:FileDescriptor buffer:*Byte count:Index]: Index 'read'
|
||||
+ [puts string:*Byte]: Index 'puts'
|
|
@ -0,0 +1,2 @@
|
|||
'431b60dd-c990-4086-ae30-aad6246b207d'
|
||||
+ 'cstdio'
|
|
@ -0,0 +1,9 @@
|
|||
+ [print string:String]: Index = cstdio::[write 1 [~*Byte string] [#string]]
|
||||
+ [println string:String]: Index = [+ [print string] cstdio::[write 1 '\n' 1]]
|
||||
|
||||
+ Writer: (& [write buffer:*:Byte]: Index)
|
||||
+ File: cstdio::FileDescriptor
|
||||
+ File.[write buffer:*:Byte]:Index =
|
||||
cstdio::[write
|
||||
[~cstdio::FileDescriptor [.this]]
|
||||
[~*Byte buffer] [#buffer]]
|
192
design/spec.md
192
design/spec.md
|
@ -1,8 +1,18 @@
|
|||
# Semantic entities
|
||||
|
||||
## Top level
|
||||
Top level entities are defined directly within the source file(s) of a unit, and
|
||||
can be made available to other units via access control modes. The three modes
|
||||
are:
|
||||
- Public access: Allows other modules to access an entity normally.
|
||||
- Opaque access: Causes a top-level entity to appear opaque to other units.
|
||||
Values of opaque types can be passed around, assigned to each-other, and their
|
||||
methods can be called, but the implementation of the type is entirely hidden.
|
||||
This access mode cannot be applied to functions or methods.
|
||||
- Private access: Disallows other modules from accessing a top-level entity.
|
||||
This mode is the default when one isn't supplied.
|
||||
### Type definition
|
||||
Type definitions bind a type to a global identifier.
|
||||
Type definitions bind a type to a global type identifier.
|
||||
### Function
|
||||
Functions bind a global identifier and argument list to an expression which is
|
||||
evaluated each time the function is called. If no expression is specified, the
|
||||
|
@ -17,7 +27,7 @@ the type they are defined on.
|
|||
|
||||
## Types
|
||||
### Named
|
||||
Named refers to a user-defined or built in named type.
|
||||
Named refers to a user-defined, primitive, or built-in named type.
|
||||
### Pointer
|
||||
Pointer is a pointer to another type.
|
||||
### Array
|
||||
|
@ -37,6 +47,44 @@ except it must have at least the methods defined within the interface.
|
|||
Interfaces are always passed by reference. When assigning a value to an
|
||||
interface, it will be referenced automatically. When assigning a pointer to an
|
||||
interface, the pointer's reference will be used instead.
|
||||
### Union
|
||||
Union is a polymorphic type that can hold any value as long as it is one of a
|
||||
list of allowed types. It is not a pointer. It holds the hash of the actual type
|
||||
of the value stored within it, followed by the value. The hash field is computed
|
||||
using the type's name, and the UUID that it was defined in. If it is not named,
|
||||
then the hash is computed using the structure of the type. The value field is
|
||||
always big enough to hold the largest type in the allowed list. The value of a
|
||||
union can only be extracted using a match expression.
|
||||
|
||||
## Primitive types
|
||||
### Int
|
||||
Int is defined as a signed system word.
|
||||
### UInt
|
||||
UInt is defined as an unsigned system word.
|
||||
### I8, I16, I32, I64
|
||||
I8-I64 are defined as signed 8, 16, 32, and 64 bit integers respectively.
|
||||
### U8, U16, U32, U64
|
||||
U8-U64 are defined as unsigned 8, 16, 32, and 64 bit integers respectively.
|
||||
### F32, F64
|
||||
F32 and F64 are defined as single-precision and double-precision floating point
|
||||
types respectively.
|
||||
|
||||
## Built-in types
|
||||
### Index
|
||||
Index is defined as an unsigned system word. It is used to describe the size of
|
||||
chunks of memory, and to index arrays and such.
|
||||
### Byte
|
||||
Byte is defined as the smallest addressable integer. It is unsigned. It is
|
||||
usually equivalent to U8.
|
||||
### Bool
|
||||
Bool is a boolean type. It is equivalent to U1. For now, since arbitrary width
|
||||
integers are not supported, it is the only way to get a U1 type.
|
||||
### Rune
|
||||
Rune is defined as a U32. It represents a single UTF-32 code point.
|
||||
### String
|
||||
String is defined as a slice of U8's. It represents a UTF-8 string. It is not
|
||||
conventionally null-terminated, but a null can be added at the end manually if
|
||||
desired.
|
||||
|
||||
## Expressions
|
||||
### Location expressions
|
||||
|
@ -47,7 +95,8 @@ with a star (*).
|
|||
### Literals
|
||||
#### Integer
|
||||
An integer literal specifies an integer value. It can be assigned to any type
|
||||
that is derived from an integer, or a float.
|
||||
that is derived from an integer or a float, as long as the value of the literal
|
||||
can fit within the range of the type.
|
||||
It cannot be directly assigned to an interface because it contains no inherent
|
||||
type information. A value cast may be used for this purpose.
|
||||
#### Float
|
||||
|
@ -55,6 +104,22 @@ A float literal specifies a floating point value. It can be assigned to any type
|
|||
that is derived from a float.
|
||||
It cannot be directly assigned to an interface because it contains no inherent
|
||||
type information. A value cast may be used for this purpose.
|
||||
#### String
|
||||
A string literal specifies a string value. It takes on different data
|
||||
representations depending on what the base type of what it is assigned to is
|
||||
structurally equivalent to:
|
||||
- Integer: Single unicode code point. When assigning to an integer, the
|
||||
string literal may not be longer than one code point, and that code point
|
||||
must fit in the integer.
|
||||
- Slice of 8 bit integers: UTF-8 string.
|
||||
- Slice of 16 bit integers: UTF-16 string.
|
||||
- Slice of 32 bit (or larger) integers: UTF-32 string.
|
||||
- Array of integers: The same as slices of integers, but the string literal
|
||||
must fit inside of the array.
|
||||
- Pointer to 8 bit integer: Null-terminated UTF-8 string (AKA C-string).
|
||||
A string literal cannot be directly assigned to an interface because it
|
||||
contains no inherent type information. A value cast may be used for this
|
||||
purpose.
|
||||
#### Array
|
||||
Array is a composite array literal. It can contain any number of values. It can
|
||||
be assigned to any array type that:
|
||||
|
@ -73,6 +138,11 @@ pairs. It can be assigned to any struct type that:
|
|||
|
||||
It cannot be directly assigned to an interface because it contains no inherent
|
||||
type information. A value cast may be used for this purpose.
|
||||
#### Boolean
|
||||
Boolean is a boolean literal. It may be either true or false. It can be assigned
|
||||
to any type derived from a boolean. It cannot be directly assigned to an
|
||||
interface because it contains no inherent type information. A value cast may be
|
||||
used for this purpose.
|
||||
### Variable *
|
||||
Variable specifies a named variable. It can be assigned to a type matching the
|
||||
variable declaration's type. Since it contains inherent type information, it may
|
||||
|
@ -89,19 +159,22 @@ value, and any assignment rules of the block are equivalent to those of its last
|
|||
expression.
|
||||
### Call
|
||||
Call calls upon the function specified by the first argument, and passes the
|
||||
rest of that argument to the function. The first argument must be a function
|
||||
type, usually the name of a function. The result of a call may be assigned to
|
||||
rest of that argument to the function. The first argument must be an identifier
|
||||
referring to usually the name of a function. The result of a call may be assigned to
|
||||
any type matching the function's return type. Since it contains inherent type
|
||||
information, it may be directly assigned to an interface.
|
||||
### Method call
|
||||
Method call calls upon the method (of the expression before the dot) that is
|
||||
specified by the first argument, passing the rest of the arguments to the
|
||||
method. The first argument must be a method name. The result of a call may be
|
||||
assigned to any type matching the method's return type. Since it contains
|
||||
inherent type information, it may be directly assigned to an interface.
|
||||
### Member access *
|
||||
Member access allows referring to a specific member of a value with a struct
|
||||
type. It accepts any struct type that contains the specified member name, and
|
||||
may be assigned to any type that matches the type of the selected member. Since
|
||||
it contains inherent type information, it may be directly assigned to an
|
||||
interface.
|
||||
### Method access
|
||||
Method access allows referring to a specific method of a type, or a behavior of
|
||||
an interface. It can only be assigned to the first argument of a call.
|
||||
### Array subscript *
|
||||
Array subscripting allows referring to a specific element of an array. It
|
||||
accepts any array, and any offset of type Size. It may be assigned to any type
|
||||
|
@ -112,6 +185,9 @@ Slice adjusts the start and end points of a slice relative to its current
|
|||
starting index, and returns an adjusted copy pointing to the same data. Any
|
||||
assignment rules of this expression are equivalent to those of the slice it is
|
||||
operating on.
|
||||
### Length
|
||||
Length returns the length of an array or a slice. It always returns a value
|
||||
of type Index.
|
||||
### Pointer dereference *
|
||||
Pointer dereferencing allows retrieving the value of a pointer. It accepts any
|
||||
pointer. It may be assigned to any type matching the pointer's pointed type.
|
||||
|
@ -183,6 +259,22 @@ If/else is a control flow branching expression that executes one of two
|
|||
expressions depending on a boolean value. If the value of the if/else is unused,
|
||||
the else expression need not be specified. It may be assigned to any type that
|
||||
satisfies the assignment rules of both the true and false expressions.
|
||||
### Match
|
||||
Match is a control flow branching expression that executes one of several case
|
||||
expressions depending on the input. It can be used to check the type of a union
|
||||
value. Each case takes the form of a declaration, and an associated expression.
|
||||
If the type of the union matches the type of the declaration in the case, the
|
||||
expression is executed and the value of the union is made available to it
|
||||
through the declaration. If the value of the match expression is used, all
|
||||
possible types in the union must be accounted for, or it must have a default
|
||||
case. It may be assigned to any type that satisfies the assignment rules of its
|
||||
first case.
|
||||
### Switch
|
||||
Switch is a control flow branching expression that executes one of several case
|
||||
expressions depending on the value of the input. It accepts any pointer or
|
||||
integer type. If the value of the switch expression is used, a default case must
|
||||
be present. It may be assigned to any type that satisfies the assignment rules
|
||||
of its first case.
|
||||
### Loop
|
||||
Loop is a control flow expression that repeats an expression until a break
|
||||
statement is called from within it. The break statement must be given a value
|
||||
|
@ -191,26 +283,40 @@ statement. The result of the loop may be assigned to any type that satisfies the
|
|||
assignment rules of all of its break statements. Loops may be nested, and break
|
||||
statements only apply to the closest containing loop. The value of the loop's
|
||||
expression is never used.
|
||||
### For
|
||||
For is a special kind of loop that evaluates an expression for each element of
|
||||
an array or slice. It accepts an index declaration and an element declaration,
|
||||
which are scoped to the loop's body and are set to the index of the current
|
||||
element and the element itself respectively at the beginning of each iteration.
|
||||
The assignment rules of a for statement are identical to that of a normal loop.
|
||||
### Break
|
||||
Break allows breaking out of loops. It has no value and may not be assigned to
|
||||
anything.
|
||||
Break allows breaking out of loops. It may be assigned to anything, but the
|
||||
assignment will have no effect as execution of the code after and surrounding it
|
||||
will cease.
|
||||
### Return
|
||||
Return allows terminating functions before they have reached their end. It
|
||||
accepts values that may be assigned to the function's return type. If a function
|
||||
does not return anything, the return statement does not accept a value. In all
|
||||
cases, return statements have no value and may not be assigned to anything.
|
||||
does not return anything, the return statement does not accept a value. It may
|
||||
be assigned to anything, but the assignment will have no effect as execution of
|
||||
the function or method will cease.
|
||||
### Assignment
|
||||
Assignment allows assigning the result of one expression to one or more location
|
||||
expressions. The assignment statement itself has no value and may not be
|
||||
expressions. The assignment expression itself has no value and may not be
|
||||
assigned to anything.
|
||||
|
||||
# Syntax entities
|
||||
|
||||
Below is a rough syntax description of the language. Note that `<assignment>`
|
||||
is right-associative, and `<memberAccess>` and `<methodCall>` are
|
||||
left-associative. I invite you to torture yourself by attempting to implement
|
||||
this without hand-writing a parser.
|
||||
|
||||
```
|
||||
<file> -> (<typedef> | <function> | <method>)*
|
||||
<typedef> -> <identifier> ":" <type>
|
||||
<function> -> <signature> ["=" <expression>]
|
||||
<method> -> <identifier> "." <function>
|
||||
<access> -> "+" | "#" | "-"
|
||||
<typedef> -> [<access>] <typeIdentifier> ":" <type>
|
||||
<function> -> [<access>] <signature> ["=" <expression>]
|
||||
<method> -> [<access>] <typeIdentifier> "." <function>
|
||||
|
||||
<type> -> <namedType>
|
||||
| <pointerType>
|
||||
|
@ -218,21 +324,25 @@ assigned to anything.
|
|||
| <arrayType>
|
||||
| <structType>
|
||||
| <interfaceType>
|
||||
<namedType> -> <identifier>
|
||||
<namedType> -> <typeIdentifier>
|
||||
<pointerType> -> "*" <type>
|
||||
<sliceType> -> "*" ":" <type>
|
||||
<arrayType> -> <intLiteral> ":" <type>
|
||||
<structType> -> "(" <declaration>* ")"
|
||||
<interfaceType> -> "(" <signature> ")"
|
||||
<structType> -> "(" "." <declaration>* ")"
|
||||
<interfaceType> -> "(" "&" <signature>* ")"
|
||||
<unionType> -> "(" "|" <type>* ")"
|
||||
|
||||
<expression> -> <intLiteral>
|
||||
| <floatLiteral>
|
||||
| <stringLiteral>
|
||||
| <arrayLiteral>
|
||||
| <structLiteral>
|
||||
| <booleanLiteral>
|
||||
| <variable>
|
||||
| <declaration>
|
||||
| <call>
|
||||
| <subscript>
|
||||
| <length>
|
||||
| <dereference>
|
||||
| <reference>
|
||||
| <valueCast>
|
||||
|
@ -240,28 +350,33 @@ assigned to anything.
|
|||
| <operation>
|
||||
| <block>
|
||||
| <memberAccess>
|
||||
| <methodCall>
|
||||
| <ifelse>
|
||||
| <loop>
|
||||
| <break>
|
||||
| <return>
|
||||
<statement> -> <expression> | <assignment>
|
||||
| <assignment>
|
||||
<variable> -> <identifier>
|
||||
<declaration> -> <identifier> ":" <type>
|
||||
<call> -> "[" <expression>+ "]"
|
||||
<subscript> -> "[" "." <expression> <expression> "]"
|
||||
<slice> -> "[" "\" <expression> <expression>? ":" <expression>? "]"
|
||||
<slice> -> "[" "\" <expression> <expression>? "/" <expression>? "]"
|
||||
<length> -> "[" "#" <expression> "]"
|
||||
<dereference> -> "[" "." <expression> "]"
|
||||
<reference> -> "[" "@" <expression> "]"
|
||||
<valueCast> -> "[" "~" <type> <expression> "]"
|
||||
<valueCast> -> "[" "~" <type> <expression> "]"
|
||||
<bitCast> -> "[" "~~" <type> <expression> "]"
|
||||
<operation> -> "[" <operator> <expression>* "]"
|
||||
<block> -> "{" <statement>* "}"
|
||||
<memberAccess> -> <variable> "." <identifier>
|
||||
<methodAccess> -> <variable> "::" <identifier>
|
||||
<block> -> "{" <expression>* "}"
|
||||
<memberAccess> -> <expression> "." <identifier>
|
||||
<methodCall> -> <expression> "." <call>
|
||||
<ifelse> -> "if" <expression>
|
||||
"then" <expression>
|
||||
["else" <expression>]
|
||||
<match> -> "match" <expression> <matchCase>* [<defaultCase>]
|
||||
<switch> -> "switch" <expression> <switchCase>* [<defaultCase>]
|
||||
<loop> -> "loop" <expression>
|
||||
<for> -> "for" <expression> [<expression>] in <expression> <expression>
|
||||
<break> -> "[" "break" [<expression>] "]"
|
||||
<return> -> "[" "return" [<expression>] "]"
|
||||
<assignment> -> <expression> "=" <expression>
|
||||
|
@ -270,16 +385,21 @@ assigned to anything.
|
|||
| /-?0[0-7]*/
|
||||
| /-?0x[0-9a-fA-F]*/
|
||||
| /-?0b[0-1]*/
|
||||
<floatLiteral> -> /-?[0-9]*\.[0-9]+/
|
||||
<arrayLiteral> -> "(*" <expression>* ")"
|
||||
<structLiteral> -> "(" <member>* ")"
|
||||
<floatLiteral> -> /-?[0-9]*\.[0-9]+/
|
||||
<stringLiteral> -> /'.*'/
|
||||
<arrayLiteral> -> "(*" <expression>* ")"
|
||||
<structLiteral> -> "(." <member>* ")"
|
||||
<booleanLiteral> -> "true" | "false"
|
||||
|
||||
<member> -> <identifier> ":" <expression>
|
||||
<signature> -> "[" <identifier> <declaration>* "]" [":" <type>]
|
||||
<identifier> -> /[A-Za-z]+/
|
||||
<operator> -> "+" | "++" | "-" | "--" | "*" | "/" | "%"
|
||||
| "!!" | "||" | "&&" | "^^"
|
||||
| "!" | "|" | "&" | "^" | "<<" | ">>"
|
||||
| "<" | ">" | "<=" | ">=" | "="
|
||||
<member> -> <identifier> ":" <expression>
|
||||
<matchCase> -> "|" <declaration> <expression>
|
||||
<switchCase> -> "|" <expression> <expression>
|
||||
<defaultCase> -> "*" <expression>
|
||||
<signature> -> "[" <identifier> <declaration>* "]" [":" <type>]
|
||||
<identifier> -> /[a-z][A-Za-z]*/
|
||||
<typeIdentifier> -> /[A-Z][A-Za-z]*/
|
||||
<operator> -> "+" | "++" | "-" | "--" | "*" | "/" | "%"
|
||||
| "!!" | "||" | "&&" | "^^"
|
||||
| "!" | "|" | "&" | "^" | "<<" | ">>"
|
||||
| "<" | ">" | "<=" | ">=" | "="
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
# Units
|
||||
|
||||
## Modules - Concept
|
||||
|
||||
- Equivalent to a package in Go
|
||||
- Contains one or more FSPL source files
|
||||
- Uniqued by a UUIDv4
|
||||
- Depends on zero or more other units
|
||||
- Source files in a module can access functionality of dependencies
|
||||
|
||||
## Addressing
|
||||
|
||||
When compiling source files, depending on a module, etc. an *address* is used to
|
||||
refer to a *unit*, which is a module or file. An *addresser* is anything that
|
||||
addresses a unit, the *addressee*. An addresser can be a module, a user invoking
|
||||
the compiler, or something else. An address is represented by a string. If the
|
||||
string ends in `.fspl`, the address refers to an FSPL source file. If not, the
|
||||
address refers to a module.
|
||||
|
||||
If the address begins in a `/`, `./` or `../`, the address is interpreted as an
|
||||
absolute path beginning from the filesystem root, the current directory of the
|
||||
addressee, or the directory above that respectively. Otherwise, the unit is
|
||||
searched for within a set of standard or configured paths.
|
||||
|
||||
For example, if the search path is `/usr/include/fspl`, and the address is
|
||||
`foo`, then the unit will be located at `/usr/include/fspl/foo`. If the address
|
||||
is `foo/bar`, then the unit will be located at `/usr/include/fspl/foo/bar`. If
|
||||
there is an additional directory in the search path, such as
|
||||
`/usr/local/include/fspl`, then the unit will be searched for in each one (in
|
||||
order) until it is found.
|
||||
|
||||
There are standard paths that the compiler will search for units in. These are,
|
||||
in order of preference:
|
||||
- `$HOME/.local/src/fspl`
|
||||
- `$HOME/.local/include/fspl`
|
||||
- `/usr/local/src/fspl`
|
||||
- `/usr/local/include/fspl`
|
||||
- `/usr/src/fspl`
|
||||
- `/usr/include/fspl`
|
||||
|
||||
On Windows, these are used instead:
|
||||
- `%LOCALAPPDATA%\fspl\src`
|
||||
- `%LOCALAPPDATA%\fspl\include`
|
||||
- `%ALLUSERSPROFILE%\fspl\src`
|
||||
- `%ALLUSERSPROFILE%\fspl\include`
|
||||
- `%ProgramFiles%\fspl\src`
|
||||
- `%ProgramFiles%\fspl\include`
|
||||
|
||||
Files in `include` directories *should not* include program code, and should
|
||||
only define types and external functions and methods, similar to header files in
|
||||
C. They may have a corresponding shared object file that programs can
|
||||
dynamically link against.
|
||||
|
||||
Files in `src` directories *may* contain program code, and may be compiled into
|
||||
an object file if the user wishes to link them statically. Because of FSPL's
|
||||
ability to "skim" units (discussed later in this document), files in `src` may
|
||||
be used in the same way that files in `include` are. Thus, `src` files are
|
||||
effectively more "complete" versions of `include` files with extended
|
||||
capability, and that is why they are searched first.
|
||||
|
||||
## Uniqueness
|
||||
|
||||
Each unit is uniqued by a UUID. Most users will never directly use UUIDs, but
|
||||
they are essential in order to prevent name collisions within the compiler or
|
||||
linker. For modules, the UUID is specified in the metadata file. For other
|
||||
units, the UUID is a UUIDv3 (md5) generated using the zero-UUID as a namespace
|
||||
and the basename of the file (with the extension) as the data.
|
||||
|
||||
When creating a module, a UUID should be randomly generated for it. Keep in mind
|
||||
that altering the UUID of a library will cause programs that used to dynamically
|
||||
load it to no longer function until they are re-compiled. Therefore, UUIDs
|
||||
should only be altered if you are introducing breaking ABI changes to your
|
||||
library. If you are forking an existing module and making changes to it, a
|
||||
similar rule applies: only keep the same UUID if you intend on keeping an
|
||||
entirely backwards compatible ABI.
|
||||
|
||||
Built-in entities that can be accessed globally from any module (such as the
|
||||
`String` type) are given a zero-UUID, which represents the "global" unit.
|
||||
Anything that is a part of this unit is accesisble from any other unit, without
|
||||
having to use a nickname to refer to it.
|
||||
|
||||
When generating code, top-level entities must be named like this if their link
|
||||
name was not specified manually:
|
||||
|
||||
`<uuid>::<name>`
|
||||
|
||||
Where `<uuid>` is the base64 encoding of the UUID. For example, the built-in
|
||||
String type would be assigned the following link name:
|
||||
|
||||
`AAAAAAAAAAAAAAAAAAAAAA==::String`
|
||||
|
||||
And a type `Bird` in a lone source file with the name `bird.fspl` would be:
|
||||
|
||||
`eT/CnSopFDlFwpDCnSEAThjDsBw=::Bird`
|
||||
|
||||
Methods are named as follows:
|
||||
|
||||
`<uuid>::<name>.<method>`
|
||||
|
||||
Where `<uuid>` and `<name>` correspond to the base64 UUID of the unit and the
|
||||
name of the method's owner type respectively, and `<method>` corresponds to the
|
||||
method name.
|
||||
|
||||
## Module Structure
|
||||
|
||||
Each module is represented by a directory, which contains source files along
|
||||
with a metadata file called `fspl.mod`. The metadata file is of the form:
|
||||
|
||||
```
|
||||
<file> -> <UUIDv4> <directive>*
|
||||
<directive> -> <depedency>
|
||||
<dependency> -> "+" <stringLiteral> [<ident>]
|
||||
<UUIDv4> -> <stringLiteral>
|
||||
```
|
||||
|
||||
Metadata files only make use of tokens defined in the FSPL lexer, and are
|
||||
designed to make use of the same parsing and lexing infrastructure used to parse
|
||||
and tokenize source files. A sample metadata file might look like:
|
||||
|
||||
```
|
||||
'5a8353f8-cad8-4604-be60-29a2575996bc'
|
||||
+ 'io'
|
||||
+ '../io' customIo
|
||||
```
|
||||
|
||||
The UUID is represented as a string, and so are addresses. When depending on a
|
||||
unit, it may be "nicknamed" by supplying an identifier after the address. This
|
||||
changes how the unit is referred to within the module.
|
||||
|
||||
## Referencing Units
|
||||
|
||||
Compiled by itself, an FSPL source file has no access to other units. However,
|
||||
when compiling a module as a whole, all source files within the module have
|
||||
access to units depended on by the module's metadata file. Note that no actual
|
||||
data or code is imported into the module from the units it depends on, because
|
||||
all methods and functions defined within them are automatically turned into
|
||||
prototypes. The module must be linked either statically or dynamically to the
|
||||
unit's object code after compilation. This is why the FSPL compiler outputs
|
||||
object files by default.
|
||||
|
||||
FSPL source files may reference functions or types from dependencies by
|
||||
prefixing them with a unit name and a double colon (`::`), like this:
|
||||
|
||||
```
|
||||
reader: io::Reader = x
|
||||
data: *:Byte = io::[readAll reader]
|
||||
```
|
||||
|
||||
The name of a unit depends on the associated dependency directive used in the
|
||||
module metadata file. If a nickname is listed, then that is used as the unit
|
||||
name. Otherwise, the unit name is the basename of the address, which is
|
||||
normalized and formatted into a valid identifier by the the following rules:
|
||||
|
||||
- If the name contains at least one dot, the last dot and everything after it
|
||||
are removed
|
||||
- All non-alphabetical and non-numeric characters are removed, and any
|
||||
alphabetical characters that were directly after them are converted to
|
||||
uppercase
|
||||
- All numeric digits at the start of the string are removed
|
||||
- The first character is converted to lowercase
|
||||
|
||||
For example:
|
||||
|
||||
- `100-bottles-of-glue_test`
|
||||
- `Picture.jpg`
|
||||
- `Just a straight up sentence`
|
||||
|
||||
Would become:
|
||||
|
||||
- `bottlesOfGlueTest`
|
||||
- `picture`
|
||||
- `justAStraightUpSentence`
|
||||
|
||||
If the unit name is still not a valid identifier or is empty, the compiler will
|
||||
refuse to process the module and it is up to the user to either nickname the
|
||||
unit, or change the unit's basename to something workable.
|
||||
|
||||
The compiler will also refuse to process the module if one or more units end up
|
||||
with the same unit name. However, a function or a variable may have the same
|
||||
name as a unit because units are only ever used within the context of their own
|
||||
special syntax (`::`).
|
||||
|
||||
## Future Work
|
||||
|
||||
Addresses do not necessarily have to refer to units. They could also refer to
|
||||
arbitrary blobs to embed into a compiled program, similarly to how Go's embed
|
||||
system works. There of course would need to be a distinction between depending
|
||||
on units and embedding data, because someone might want to embed an FSPL source
|
||||
file. Thus, there would need to be a separate metadata file directive, possibly
|
||||
starting with an `!` or something like that.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Package entity provides data representations of language concepts.
|
||||
// They are used to construct syntax trees, and semantic trees. It additionally
|
||||
// provides some utility functions to compare certain entities.
|
||||
package entity
|
|
@ -1,125 +1,235 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// Expression is any construct that can be evaluated.
|
||||
type Expression interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Position returns the position of the expression within its source
|
||||
// file.
|
||||
Position () errors.Position
|
||||
|
||||
// Type returns the type of the expression. The return value of this is
|
||||
// only considered valid if the expression has its semantic information
|
||||
// fields filled.
|
||||
Type () Type
|
||||
|
||||
// HasExplicitType returns true if the expression carries inherent type
|
||||
// information. If it returns false, the expression must be given a type
|
||||
// to conform to. The value of this does not depend on semantic
|
||||
// information fields.
|
||||
HasExplicitType () bool
|
||||
|
||||
expression ()
|
||||
Statement
|
||||
}
|
||||
|
||||
// Statement is any construct that can be placed inside of a block expression.
|
||||
type Statement interface {
|
||||
statement()
|
||||
}
|
||||
|
||||
var _ Expression = &Variable { }
|
||||
// Variable specifies a named variable. It can be assigned to a type matching
|
||||
// the variable declaration's type. Since it contains inherent type information,
|
||||
// it may be directly assigned to an interface. A variable is always a valid
|
||||
// location expression.
|
||||
type Variable struct {
|
||||
Pos lexer.Position
|
||||
Name string `parser:" @Ident "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Name string
|
||||
|
||||
// Semantics
|
||||
Declaration *Declaration
|
||||
}
|
||||
func (*Variable) expression(){}
|
||||
func (*Variable) statement(){}
|
||||
func (this *Variable) Position () errors.Position { return this.Pos }
|
||||
func (this *Variable) Type () Type { return this.Declaration.Type() }
|
||||
func (this *Variable) HasExplicitType () bool { return true }
|
||||
func (this *Variable) String () string {
|
||||
return this.Name
|
||||
}
|
||||
|
||||
var _ Expression = &Declaration { }
|
||||
// Declaration binds a local identifier to a typed variable, but also acts as a
|
||||
// variable expression allowing the variable to be used the moment it is
|
||||
// defined. Since it contains inherent type information, it may be directly
|
||||
// assigned to an interface. A declaration is always a valid location
|
||||
// expression.
|
||||
type Declaration struct {
|
||||
Pos lexer.Position
|
||||
Name string `parser:" @Ident "`
|
||||
Type Type `parser:" ':' @@ "`
|
||||
Pos errors.Position
|
||||
Name string
|
||||
Ty Type
|
||||
}
|
||||
func (*Declaration) expression(){}
|
||||
func (*Declaration) statement(){}
|
||||
func (this *Declaration) Position () errors.Position { return this.Pos }
|
||||
func (this *Declaration) Type () Type { return this.Ty }
|
||||
func (this *Declaration) HasExplicitType () bool { return true }
|
||||
func (this *Declaration) String () string {
|
||||
return fmt.Sprint(this.Name, ":", this.Type)
|
||||
return fmt.Sprint(this.Name, ":", this.Ty)
|
||||
}
|
||||
|
||||
// Call calls upon the function specified by the first argument, and passes the
|
||||
// rest of that argument to the function. The first argument must be a function
|
||||
// type, usually the name of a function. The result of a call may be assigned to
|
||||
// any type matching the function's return type. Since it contains inherent type
|
||||
// information, it may be directly assigned to an interface. A call is never a
|
||||
// valid location expression.
|
||||
var _ Expression = &Call { }
|
||||
// Call calls upon the function specified by the first argument, passing the
|
||||
// rest of the arguments to that function. The first argument must be a function
|
||||
// name. The result of a call may be assigned to any type matching the
|
||||
// function's return type. Since it contains inherent type information, it may
|
||||
// be directly assigned to an interface. A call is never a valid location
|
||||
// expression.
|
||||
type Call struct {
|
||||
Pos lexer.Position
|
||||
Function Expression `parser:" '[' @@ "`
|
||||
Arguments []Expression `parser:" @@* ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
UnitNickname string
|
||||
Name string
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Function *Function
|
||||
Unit uuid.UUID
|
||||
}
|
||||
func (*Call) expression(){}
|
||||
func (*Call) statement(){}
|
||||
func (this *Call) Position () errors.Position { return this.Pos }
|
||||
func (this *Call) Type () Type { return this.Function.Signature.Return }
|
||||
func (this *Call) HasExplicitType () bool { return true }
|
||||
func (this *Call) String () string {
|
||||
out := fmt.Sprint("[", this.Function)
|
||||
out := ""
|
||||
if this.UnitNickname != "" {
|
||||
out += fmt.Sprint(this.UnitNickname, "::")
|
||||
}
|
||||
out += fmt.Sprint("[", this.Name)
|
||||
for _, argument := range this.Arguments {
|
||||
out += fmt.Sprint(" ", argument)
|
||||
}
|
||||
return out + "]"
|
||||
}
|
||||
|
||||
// Array subscripting allows referring to a specific element of an array. It
|
||||
// accepts any array, and any offset of type Size. It may be assigned to any
|
||||
// type matching the array's element type. Since it contains inherent type
|
||||
var _ Expression = &MethodCall { }
|
||||
// MethodCall calls upon the method of the variable before the dot that is
|
||||
// specified by the first argument, passing the rest of the arguments to the
|
||||
// method. The first argument must be a method name. The result of a call may be
|
||||
// assigned to any type matching the method's return type. Since it contains
|
||||
// inherent type information, it may be directly assigned to an interface.
|
||||
// A method call is never a valid location expression.
|
||||
type MethodCall struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Source Expression
|
||||
Name string
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Method *Method
|
||||
Behavior *Signature
|
||||
Ty Type
|
||||
}
|
||||
func (*MethodCall) expression(){}
|
||||
func (this *MethodCall) Position () errors.Position { return this.Pos }
|
||||
func (this *MethodCall) Type () Type {
|
||||
if this.Method != nil {
|
||||
return this.Method.Signature.Return
|
||||
} else {
|
||||
return this.Behavior.Return
|
||||
}
|
||||
}
|
||||
func (this *MethodCall) HasExplicitType () bool { return true }
|
||||
func (this *MethodCall) String () string {
|
||||
out := fmt.Sprint(this.Source, ".[", this.Name)
|
||||
for _, argument := range this.Arguments {
|
||||
out += fmt.Sprint(" ", argument)
|
||||
}
|
||||
return out + "]"
|
||||
}
|
||||
|
||||
var _ Expression = &Subscript { }
|
||||
// Slice subscripting allows referring to a specific element of a slice. It
|
||||
// accepts any slice, and any offset of type Size. It may be assigned to any
|
||||
// type matching the slice's element type. Since it contains inherent type
|
||||
// information, it may be directly assigned to an interface. A subscript is a
|
||||
// valid location expression only if the array being subscripted is.
|
||||
type Subscript struct {
|
||||
Pos lexer.Position
|
||||
Array Expression `parser:" '[' '.' @@ "`
|
||||
Offset Expression `parser:" @@ ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Slice Expression
|
||||
Offset Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Subscript) expression(){}
|
||||
func (*Subscript) statement(){}
|
||||
func (this *Subscript) Position () errors.Position { return this.Pos }
|
||||
func (this *Subscript) Type () Type { return this.Ty }
|
||||
func (this *Subscript) HasExplicitType () bool { return true }
|
||||
func (this *Subscript) String () string {
|
||||
return fmt.Sprint("[.", this.Array, " ", this.Offset, "]")
|
||||
return fmt.Sprint("[.", this.Slice, " ", this.Offset, "]")
|
||||
}
|
||||
|
||||
var _ Expression = &Slice { }
|
||||
// Slice adjusts the start and end points of a slice relative to its current
|
||||
// starting index, and returns an adjusted copy pointing to the same data. Any
|
||||
// assignment rules of this expression are equivalent to those of the slice it
|
||||
// is operating on. A slice is never a valid location expression.
|
||||
type Slice struct {
|
||||
Pos lexer.Position
|
||||
Slice Expression `parser:" '[' '\\\\' @@ "`
|
||||
Start Expression `parser:" @@? "`
|
||||
End Expression `parser:" ':' @@? ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Slice Expression
|
||||
Start Expression
|
||||
End Expression
|
||||
}
|
||||
func (*Slice) expression(){}
|
||||
func (*Slice) statement(){}
|
||||
func (this *Slice) Position () errors.Position { return this.Pos }
|
||||
func (this *Slice) Type () Type { return this.Slice.Type() }
|
||||
func (this *Slice) HasExplicitType () bool { return true }
|
||||
func (this *Slice) String () string {
|
||||
out := fmt.Sprint("[\\", this.Slice, " ")
|
||||
if this.Start != nil {
|
||||
out += fmt.Sprint(this.Start)
|
||||
}
|
||||
out += ":"
|
||||
out += "/" // TODO have trailing decimals pop off of numbers, replace this with ..
|
||||
if this.End != nil {
|
||||
out += fmt.Sprint(this.End)
|
||||
}
|
||||
return out + "]"
|
||||
}
|
||||
|
||||
var _ Expression = &Length { }
|
||||
// Length returns the length of an array or a slice. It always returns a value
|
||||
// of type Index. A length is never a valid location expression.
|
||||
type Length struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Slice Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Length) expression(){}
|
||||
func (this *Length) Position () errors.Position { return this.Pos }
|
||||
func (this *Length) Type () Type { return this.Ty }
|
||||
func (this *Length) HasExplicitType () bool { return true }
|
||||
func (this *Length) String () string {
|
||||
return fmt.Sprint("[#", this.Slice, "]")
|
||||
}
|
||||
|
||||
var _ Expression = &Dereference { }
|
||||
// Pointer dereferencing allows retrieving the value of a pointer. It accepts
|
||||
// any pointer. It may be assigned to any type matching the pointer's pointed
|
||||
// type. Since it contains inherent type information, it may be directly
|
||||
// assigned to an interface. A dereference is a valid location expression only
|
||||
// if the pointer being dereferenced is.
|
||||
type Dereference struct {
|
||||
Pos lexer.Position
|
||||
Pointer Expression `parser:" '[' '.' @@ ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Pointer Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Dereference) expression(){}
|
||||
func (*Dereference) statement(){}
|
||||
func (this *Dereference) Position () errors.Position { return this.Pos }
|
||||
func (this *Dereference) Type () Type { return this.Ty }
|
||||
func (this *Dereference) HasExplicitType () bool { return true }
|
||||
func (this *Dereference) String () string {
|
||||
return fmt.Sprint("[.", this.Pointer, "]")
|
||||
}
|
||||
|
||||
var _ Expression = &Reference { }
|
||||
// Value referencing allows retrieving the location of a value in memory. It
|
||||
// accepts any location expression, and can be assigned to any type that is a
|
||||
// pointer to the location expression's type. Since it contains inherent type
|
||||
|
@ -128,56 +238,76 @@ func (this *Dereference) String () string {
|
|||
// automatically references it anyway. A reference is never a valid location
|
||||
// expression.
|
||||
type Reference struct {
|
||||
Pos lexer.Position
|
||||
Value Expression `parser:" '[' '@' @@ ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Reference) expression(){}
|
||||
func (*Reference) statement(){}
|
||||
func (this *Reference) Position () errors.Position { return this.Pos }
|
||||
func (this *Reference) Type () Type { return this.Ty }
|
||||
func (this *Reference) HasExplicitType () bool { return true }
|
||||
func (this *Reference) String () string {
|
||||
return fmt.Sprint("[@", this.Value, "]")
|
||||
}
|
||||
|
||||
var _ Expression = &ValueCast { }
|
||||
// Vaue casting converts a value of a certain type to another type. Since it
|
||||
// contains inherent type information, it may be directly assigned to an
|
||||
// interface. A value cast is never a valid location expression.
|
||||
type ValueCast struct {
|
||||
Pos lexer.Position
|
||||
Type Type `parser:" '[' '~' @@ "`
|
||||
Value Expression `parser:" @@ ']' "`
|
||||
Pos errors.Position
|
||||
Ty Type
|
||||
Value Expression
|
||||
}
|
||||
func (*ValueCast) expression(){}
|
||||
func (*ValueCast) statement(){}
|
||||
func (this *ValueCast) Position () errors.Position { return this.Pos }
|
||||
func (this *ValueCast) Type () Type { return this.Ty }
|
||||
func (this *ValueCast) HasExplicitType () bool { return true }
|
||||
func (this *ValueCast) String () string {
|
||||
return fmt.Sprint("[~ ", this.Type, this.Value, "]")
|
||||
return fmt.Sprint("[~ ", this.Ty, this.Value, "]")
|
||||
}
|
||||
|
||||
var _ Expression = &BitCast { }
|
||||
// Bit casting takes the raw data in memory of a certain value and re-interprets
|
||||
// it as a value of another type. Since it contains inherent type information,
|
||||
// it may be directly assigned to an interface. A bit cast is never a valid
|
||||
// location expression.
|
||||
type BitCast struct {
|
||||
Pos lexer.Position
|
||||
Type Type `parser:" '[' '~''~' @@ "`
|
||||
Value Expression `parser:" @@ ']' "`
|
||||
Pos errors.Position
|
||||
Ty Type
|
||||
Value Expression
|
||||
}
|
||||
func (*BitCast) expression(){}
|
||||
func (*BitCast) statement(){}
|
||||
func (this *BitCast) Position () errors.Position { return this.Pos }
|
||||
func (this *BitCast) Type () Type { return this.Ty }
|
||||
func (this *BitCast) HasExplicitType () bool { return true }
|
||||
func (this *BitCast) String () string {
|
||||
return fmt.Sprint("[~~ ", this.Type, this.Value, "]")
|
||||
return fmt.Sprint("[~~ ", this.Ty, this.Value, "]")
|
||||
}
|
||||
|
||||
var _ Expression = &Operation { }
|
||||
// Operations perform math, logic, or bit manipulation on values. They accept
|
||||
// values of the same type as the type they are being assigned to, except in
|
||||
// special cases. Since they contain no inherent type information, they may not
|
||||
// be assigned to interfaces. An operation is never a valid location expression.
|
||||
type Operation struct {
|
||||
Pos lexer.Position
|
||||
// FIXME super janky, need to make a custom lexer at some point
|
||||
Operator Operator `parser:" '[' @('+''+' | '+' | '-''-' | '-' | '*' | '/' | '%' | '!''!' | '|''|' | '&''&' | '^''^' | '!' | '|' | '&' | '^' | '<''<' | '>''>' | '<' | '>' | '<''=' | '>''=' | '=') "`
|
||||
Arguments []Expression `parser:" @@+ ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Operator Operator
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Operation) expression(){}
|
||||
func (*Operation) statement(){}
|
||||
func (this *Operation) Position () errors.Position { return this.Pos }
|
||||
func (this *Operation) Type () Type { return this.Ty }
|
||||
func (this *Operation) HasExplicitType () bool {
|
||||
return this.Operator.ResultsInBoolean()
|
||||
}
|
||||
func (this *Operation) String () string {
|
||||
out := fmt.Sprint("[", this.Operator)
|
||||
for _, argument := range this.Arguments {
|
||||
|
@ -186,6 +316,7 @@ func (this *Operation) String () string {
|
|||
return out + "]"
|
||||
}
|
||||
|
||||
var _ Expression = &Block { }
|
||||
// Block is an ordered collection of expressions that are evaluated
|
||||
// sequentially. It has its own scope. The last expression in the block
|
||||
// specifies the block's value, and any assignment rules of the block are
|
||||
|
@ -193,14 +324,20 @@ func (this *Operation) String () string {
|
|||
// expression.
|
||||
type Block struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Steps []Statement `parser:" '{' @@* '}' "`
|
||||
Pos errors.Position
|
||||
Steps []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
Scope
|
||||
}
|
||||
func (*Block) expression(){}
|
||||
func (*Block) statement(){}
|
||||
func (this *Block) Position () errors.Position { return this.Pos }
|
||||
func (this *Block) Type () Type { return this.Ty }
|
||||
func (this *Block) HasExplicitType () bool {
|
||||
if len(this.Steps) == 0 { return false }
|
||||
return this.Steps[len(this.Steps) - 1].HasExplicitType()
|
||||
}
|
||||
func (this *Block) String () string {
|
||||
out := "{"
|
||||
for index, step := range this.Steps {
|
||||
|
@ -210,6 +347,7 @@ func (this *Block) String () string {
|
|||
return out + "}"
|
||||
}
|
||||
|
||||
var _ Expression = &MemberAccess { }
|
||||
// Member access allows referring to a specific member of a value with a struct
|
||||
// type. It accepts any struct type that contains the specified member name, and
|
||||
// may be assigned to any type that matches the type of the selected member.
|
||||
|
@ -217,43 +355,44 @@ func (this *Block) String () string {
|
|||
// an interface. A member access is a valid location expression only when the
|
||||
// struct being accessed is.
|
||||
type MemberAccess struct {
|
||||
Pos lexer.Position
|
||||
Source *Variable `parser:" @@ "`
|
||||
Member string `parser:" '.' @Ident "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Source Expression
|
||||
Member string
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*MemberAccess) expression(){}
|
||||
func (*MemberAccess) statement(){}
|
||||
func (this *MemberAccess) Position () errors.Position { return this.Pos }
|
||||
func (this *MemberAccess) Type () Type { return this.Ty }
|
||||
func (this *MemberAccess) HasExplicitType () bool { return true }
|
||||
func (this *MemberAccess) String () string {
|
||||
return fmt.Sprint(this.Source, ".", this.Member)
|
||||
}
|
||||
|
||||
// Method access allows referring to a specific method of a type, or a behavior
|
||||
// of an interface. It can only be assigned to the first argument of a call. A
|
||||
// method access is never a valid location expression.
|
||||
type MethodAccess struct {
|
||||
Pos lexer.Position
|
||||
Source *Variable `parser:" @@ "`
|
||||
Member string `parser:" ':' ':' @Ident "`
|
||||
}
|
||||
func (*MethodAccess) expression(){}
|
||||
func (*MethodAccess) statement(){}
|
||||
func (this *MethodAccess) String () string {
|
||||
return fmt.Sprint(this.Source, "::", this.Member)
|
||||
}
|
||||
|
||||
var _ Expression = &IfElse { }
|
||||
// If/else is a control flow branching expression that executes one of two
|
||||
// expressions depending on a boolean value. If the value of the if/else is
|
||||
// unused, the else expression need not be specified. It may be assigned to any
|
||||
// type that satisfies the assignment rules of both the true and false
|
||||
// expressions. An If/else is never a valid location expression.
|
||||
type IfElse struct {
|
||||
Pos lexer.Position
|
||||
Condition Expression `parser:" 'if' @@ "`
|
||||
True Expression `parser:" 'then' @@ "`
|
||||
False Expression `parser:" ('else' @@)? "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Condition Expression
|
||||
True Expression
|
||||
False Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*IfElse) expression(){}
|
||||
func (*IfElse) statement(){}
|
||||
func (this *IfElse) Position () errors.Position { return this.Pos }
|
||||
func (this *IfElse) Type () Type { return this.Ty }
|
||||
func (this *IfElse) HasExplicitType () bool {
|
||||
return this.True.HasExplicitType()
|
||||
}
|
||||
func (this *IfElse) String () string {
|
||||
out := fmt.Sprint("if ", this.Condition, " then ", this.True)
|
||||
if this.False != nil {
|
||||
|
@ -262,6 +401,95 @@ func (this *IfElse) String () string {
|
|||
return out
|
||||
}
|
||||
|
||||
var _ Expression = &Match { }
|
||||
// Match is a control flow branching expression that executes one of several
|
||||
// case expressions depending on the input. It can be used to check the type of
|
||||
// a union value. Each case takes the form of a declaration, and an associated
|
||||
// expression. If the type of the union matches the type of the declaration in
|
||||
// the case, the expression is executed and the value of the union is made
|
||||
// available to it through the declaration. If the value of the match expression
|
||||
// is used, all possible types in the union must be accounted for. It may be
|
||||
// assigned to any type that satisfies the assignment rules of its first case.
|
||||
type Match struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value Expression
|
||||
Cases []*MatchCase
|
||||
Default *DefaultCase
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
CaseOrder []Hash
|
||||
CaseMap map[Hash] *MatchCase
|
||||
}
|
||||
func (*Match) expression(){}
|
||||
func (this *Match) Position () errors.Position { return this.Pos }
|
||||
func (this *Match) Type () Type { return this.Ty }
|
||||
func (this *Match) HasExplicitType () bool {
|
||||
if len(this.Cases) == 0 {
|
||||
return true
|
||||
} else {
|
||||
return this.Cases[0].HasExplicitType()
|
||||
}
|
||||
}
|
||||
func (this *Match) String () string {
|
||||
out := fmt.Sprint("match ", this.Value)
|
||||
for _, cas := range this.Cases {
|
||||
out += fmt.Sprint(" ", cas)
|
||||
}
|
||||
if this.Default != nil {
|
||||
out += fmt.Sprint(" ", this.Default)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
var _ Expression = &Switch { }
|
||||
// Switch is a control flow branching expression that executes one of several
|
||||
// case expressions depending on the value of the input. It accepts any pointer
|
||||
// or integer type. If the value of the switch expression is used, a default
|
||||
// case must be present. It may be assigned to any type that satisfies the
|
||||
// assignment rules of its first case.
|
||||
type Switch struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value Expression
|
||||
Cases []*SwitchCase
|
||||
Default *DefaultCase
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
CaseOrder []int64
|
||||
CaseMap map[int64] *SwitchCase
|
||||
}
|
||||
func (*Switch) expression(){}
|
||||
func (this *Switch) Position () errors.Position { return this.Pos }
|
||||
func (this *Switch) Type () Type { return this.Ty }
|
||||
func (this *Switch) HasExplicitType () bool {
|
||||
if len(this.Cases) == 0 {
|
||||
return true
|
||||
} else {
|
||||
return this.Cases[0].HasExplicitType()
|
||||
}
|
||||
}
|
||||
func (this *Switch) String () string {
|
||||
out := fmt.Sprint("switch ", this.Value)
|
||||
for _, cas := range this.Cases {
|
||||
out += fmt.Sprint(" ", cas)
|
||||
}
|
||||
if this.Default != nil {
|
||||
out += fmt.Sprint(" ", this.Default)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Breakable is any expression that can be halted using a break.
|
||||
type Breakable interface {
|
||||
Expression
|
||||
breakable()
|
||||
}
|
||||
|
||||
var _ Expression = &Loop { }
|
||||
var _ Breakable = &Loop { }
|
||||
// Loop is a control flow expression that repeats an expression until a break
|
||||
// statement is called from within it. The break statement must be given a value
|
||||
// if the value of the loop is used. Otherwise, it need not even have a break
|
||||
|
@ -270,23 +498,79 @@ func (this *IfElse) String () string {
|
|||
// break statements only apply to the closest containing loop. The value of the
|
||||
// loop's expression is never used. A loop is never a valid location expression.
|
||||
type Loop struct {
|
||||
Pos lexer.Position
|
||||
Body Expression `parser:" 'loop' @@ "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Loop) expression(){}
|
||||
func (*Loop) statement(){}
|
||||
func (*Loop) breakable(){}
|
||||
func (this *Loop) Position () errors.Position { return this.Pos }
|
||||
func (this *Loop) Type () Type { return this.Ty }
|
||||
func (this *Loop) HasExplicitType () bool {
|
||||
// this is as accurate as possible without doing a full analysis of the
|
||||
// loop
|
||||
return false
|
||||
}
|
||||
func (this *Loop) String () string {
|
||||
return fmt.Sprint("loop ", this.Body)
|
||||
}
|
||||
|
||||
var _ Expression = &For { }
|
||||
var _ Breakable = &For { }
|
||||
// For is a special kind of loop that evaluates an expression for each element
|
||||
// of an array or slice. It accepts an index declaration and an element
|
||||
// declaration, which are scoped to the loop's body and are set to the index of
|
||||
// the current element and the element itself respectively at the beginning of
|
||||
// each iteration. The assignment rules of a for statement are identical to that
|
||||
// of a normal loop.
|
||||
type For struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Index *Declaration
|
||||
Element *Declaration
|
||||
Over Expression
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
Scope
|
||||
}
|
||||
func (*For) expression(){}
|
||||
func (*For) breakable(){}
|
||||
func (this *For) Position () errors.Position { return this.Pos }
|
||||
func (this *For) Type () Type { return this.Ty }
|
||||
func (this *For) HasExplicitType () bool {
|
||||
// this is as accurate as possible without doing a full analysis of the
|
||||
// loop
|
||||
return false
|
||||
}
|
||||
func (this *For) String () string {
|
||||
out := "for"
|
||||
if this.Index != nil {
|
||||
out += " " + this.Index.String()
|
||||
}
|
||||
out += fmt.Sprintf(" %v in %v %v", this.Element, this.Over, this.Body)
|
||||
return out
|
||||
}
|
||||
|
||||
var _ Expression = &Break { }
|
||||
// Break allows breaking out of loops. It has no value and may not be assigned
|
||||
// to anything. It is never a valid location expression.
|
||||
type Break struct {
|
||||
Pos lexer.Position
|
||||
Value Expression `parser:" '[' 'break' @@? ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Loop Breakable
|
||||
}
|
||||
func (*Break) expression(){}
|
||||
func (*Break) statement(){}
|
||||
func (this *Break) Position () errors.Position { return this.Pos }
|
||||
func (this *Break) Type () Type { return nil }
|
||||
func (this *Break) HasExplicitType () bool { return false }
|
||||
func (this *Break) String () string {
|
||||
if this.Value == nil {
|
||||
return "[break]"
|
||||
|
@ -294,17 +578,25 @@ func (this *Break) String () string {
|
|||
return fmt.Sprint("[break ", this.Value, "]")
|
||||
}
|
||||
}
|
||||
|
||||
var _ Expression = &Return { }
|
||||
// Return allows terminating functions before they have reached their end. It
|
||||
// accepts values that may be assigned to the function's return type. If a
|
||||
// function does not return anything, the return statement does not accept a
|
||||
// value. In all cases, return statements have no value and may not be assigned
|
||||
// to anything. A return statement is never a valid location expression.
|
||||
type Return struct {
|
||||
Pos lexer.Position
|
||||
Value Expression `parser:" '[' 'return' @@? ']' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Declaration TopLevel
|
||||
}
|
||||
func (*Return) expression(){}
|
||||
func (*Return) statement(){}
|
||||
func (this *Return) Position () errors.Position { return this.Pos }
|
||||
func (this *Return) Type () Type { return nil }
|
||||
func (this *Return) HasExplicitType () bool { return false }
|
||||
func (this *Return) String () string {
|
||||
if this.Value == nil {
|
||||
return "[return]"
|
||||
|
@ -313,16 +605,20 @@ func (this *Return) String () string {
|
|||
}
|
||||
}
|
||||
|
||||
var _ Expression = &Assignment { }
|
||||
// Assignment allows assigning the result of one expression to one or more
|
||||
// location expressions. The assignment statement itself has no value and may
|
||||
// not be assigned to anything. An assignment statement is never a valid
|
||||
// location expression.
|
||||
type Assignment struct {
|
||||
Pos lexer.Position
|
||||
Location Expression `parser:" @@ "`
|
||||
Value Expression `parser:" '=' @@ "`
|
||||
Pos errors.Position
|
||||
Location Expression
|
||||
Value Expression
|
||||
}
|
||||
func (*Assignment) statement(){}
|
||||
func (*Assignment) expression(){}
|
||||
func (this *Assignment) Position () errors.Position { return this.Pos }
|
||||
func (this *Assignment) Type () Type { return nil }
|
||||
func (this *Assignment) HasExplicitType () bool { return false }
|
||||
func (this *Assignment) String () string {
|
||||
return fmt.Sprint(this.Location, "=", this.Value)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "crypto/md5"
|
||||
import "encoding/base64"
|
||||
import "github.com/google/uuid"
|
||||
|
||||
// A hash represents a hash sum that fits within a uint64.
|
||||
type Hash [8]byte
|
||||
|
||||
// NewHash creates a new truncated md5 hash using the specified data.
|
||||
func NewHash (data []byte) Hash {
|
||||
sum := md5.Sum(data)
|
||||
hash := Hash { }
|
||||
copy(hash[:], sum[:8])
|
||||
return hash
|
||||
}
|
||||
|
||||
// Number converts the hash into a uint64.
|
||||
func (hash Hash) Number () uint64 {
|
||||
var number uint64
|
||||
for _, part := range hash {
|
||||
number <<= 8
|
||||
number |= uint64(part)
|
||||
}
|
||||
return number
|
||||
}
|
||||
|
||||
// Key globally indexes top level entities in contexts where modules matter.
|
||||
type Key struct {
|
||||
Unit uuid.UUID
|
||||
Name string
|
||||
Method string
|
||||
}
|
||||
|
||||
func (key Key) String () string {
|
||||
out := fmt.Sprintf("%v::%v", key.Unit, key.Name)
|
||||
if key.Method != "" {
|
||||
out = fmt.Sprintf("%s.%s", out, key.Method)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Hash returns a representation of the hash of this key that fits within a
|
||||
// uint64.
|
||||
func (key Key) Hash () Hash {
|
||||
return NewHash([]byte("Key:" + key.String()))
|
||||
}
|
||||
|
||||
// LinkName returns the name that the entity it refers to will be given when
|
||||
// compiled.
|
||||
func (key Key) LinkName () string {
|
||||
data := [16]byte(key.Unit)
|
||||
out := fmt.Sprintf(
|
||||
"%s::%s",
|
||||
base64.StdEncoding.EncodeToString(data[:]),
|
||||
key.Name)
|
||||
if key.Method != "" {
|
||||
out = fmt.Sprintf("%s.%s", out, key.Method)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// StripMethod returns a copy of the key that refers to a type instead of a
|
||||
// method.
|
||||
func (key Key) StripMethod () Key {
|
||||
key.Method = ""
|
||||
return key
|
||||
}
|
||||
|
|
@ -1,58 +1,116 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
var _ Expression = &LiteralInt { }
|
||||
// LiteralInt specifies an integer value. It can be assigned to any type that is
|
||||
// derived from an integer, or a float. It cannot be directly assigned to an
|
||||
// derived from an integer or a float, as long as the value fo the literal can
|
||||
// fit within the range of the type. It cannot be directly assigned to an
|
||||
// interface because it contains no inherent type information. A value cast may
|
||||
// be used for this purpose.
|
||||
type LiteralInt struct {
|
||||
Pos lexer.Position
|
||||
Value int `parser:" @Int "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value int
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralInt) expression(){}
|
||||
func (*LiteralInt) statement(){}
|
||||
func (this *LiteralInt) Position () errors.Position { return this.Pos }
|
||||
func (this *LiteralInt) Type () Type { return this.Ty }
|
||||
func (this *LiteralInt) HasExplicitType () bool { return false }
|
||||
func (this *LiteralInt) String () string {
|
||||
return fmt.Sprint(this.Value)
|
||||
}
|
||||
|
||||
var _ Expression = &LiteralFloat { }
|
||||
// LiteralFloat specifies a floating point value. It can be assigned to any type
|
||||
// that is derived from a float. It cannot be directly assigned to an interface
|
||||
// because it contains no inherent type information. A value cast may be used
|
||||
// for this purpose.
|
||||
type LiteralFloat struct {
|
||||
Pos lexer.Position
|
||||
Value float64 `parser:" @Float "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value float64
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralFloat) expression(){}
|
||||
func (*LiteralFloat) statement(){}
|
||||
func (this *LiteralFloat) Position () errors.Position { return this.Pos }
|
||||
func (this *LiteralFloat) Type () Type { return this.Ty }
|
||||
func (this *LiteralFloat) HasExplicitType () bool { return false }
|
||||
func (this *LiteralFloat) String () string {
|
||||
return fmt.Sprint(this.Value)
|
||||
}
|
||||
|
||||
// Array is a composite array literal. It can contain any number of values. It
|
||||
// can be assigned to any array type that:
|
||||
var _ Expression = &LiteralString { }
|
||||
// LiteralString specifies a string value. It takes on different data
|
||||
// representations depending on what the base type of what it is assigned to is
|
||||
// structurally equivalent to:
|
||||
// - Integer: Single unicode code point. When assigning to an integer, the
|
||||
// string literal may not be longer than one code point, and that code point
|
||||
// must fit in the integer.
|
||||
// - Slice of 8 bit integers: UTF-8 string.
|
||||
// - Slice of 16 bit integers: UTF-16 string.
|
||||
// - Slice of 32 bit (or larger) integers: UTF-32 string.
|
||||
// - Array of integers: The same as slices of integers, but the string literal
|
||||
// must fit inside of the array.
|
||||
// - Pointer to 8 bit integer: Null-terminated UTF-8 string (AKA C-string).
|
||||
// A string literal cannot be directly assigned to an interface because it
|
||||
// contains no inherent type information. A value cast may be used for this
|
||||
// purpose.
|
||||
type LiteralString struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
ValueUTF8 string
|
||||
ValueUTF16 []uint16
|
||||
ValueUTF32 []rune
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralString) expression(){}
|
||||
func (this *LiteralString) Position () errors.Position { return this.Pos }
|
||||
func (this *LiteralString) Type () Type { return this.Ty }
|
||||
func (this *LiteralString) HasExplicitType () bool { return false }
|
||||
func (this *LiteralString) String () string {
|
||||
return Quote(this.ValueUTF8)
|
||||
}
|
||||
|
||||
var _ Expression = &LiteralArray { }
|
||||
// LiteralArray is a composite array literal. It can contain any number of
|
||||
// values. It can be assigned to any array type that:
|
||||
// 1. has an identical length, and
|
||||
// 2. who's element type can be assigned to by all the element values in the
|
||||
// literal.
|
||||
// It cannot be directly assigned to an interface because it contains no
|
||||
// inherent type information. A value cast may be used for this purpose.
|
||||
type LiteralArray struct {
|
||||
Pos lexer.Position
|
||||
Elements []Expression `parser:" '(' '*' @@* ')' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Elements []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralArray) expression(){}
|
||||
func (*LiteralArray) statement(){}
|
||||
func (this *LiteralArray) Position () errors.Position { return this.Pos }
|
||||
func (this *LiteralArray) Type () Type { return this.Ty }
|
||||
func (this *LiteralArray) HasExplicitType () bool { return false }
|
||||
func (this *LiteralArray) String () string {
|
||||
out := "(*"
|
||||
for _, element := range this.Elements {
|
||||
out += fmt.Sprint(" ", element)
|
||||
out := "("
|
||||
for index, element := range this.Elements {
|
||||
if index > 0 { out += " " }
|
||||
out += fmt.Sprint(element)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
|
||||
// Struct is a composite structure literal. It can contain any number of
|
||||
var _ Expression = &LiteralStruct { }
|
||||
// LiteralStruct is a composite structure literal. It can contain any number of
|
||||
// name:value pairs. It can be assigned to any struct type that:
|
||||
// 1. has at least the members specified in the literal
|
||||
// 2. who's member types can be assigned to by the corresponding member
|
||||
|
@ -60,16 +118,63 @@ func (this *LiteralArray) String () string {
|
|||
// It cannot be directly assigned to an interface because it contains no
|
||||
// inherent type information. A value cast may be used for this purpose.
|
||||
type LiteralStruct struct {
|
||||
Pos lexer.Position
|
||||
Members []Member `parser:" '(' @@* ')' "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Members []*Member
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
MemberOrder []string
|
||||
MemberMap map[string] *Member
|
||||
}
|
||||
func (*LiteralStruct) expression(){}
|
||||
func (*LiteralStruct) statement(){}
|
||||
func (this *LiteralStruct) Position () errors.Position { return this.Pos }
|
||||
func (this *LiteralStruct) Type () Type { return this.Ty }
|
||||
func (this *LiteralStruct) HasExplicitType () bool { return false }
|
||||
func (this *LiteralStruct) String () string {
|
||||
out := "("
|
||||
for index, member := range this.Members {
|
||||
if index > 1 { out += " " }
|
||||
out += fmt.Sprint(member)
|
||||
out := "(."
|
||||
for _, member := range this.Members {
|
||||
out += fmt.Sprint(" ", member)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
|
||||
var _ Expression = &LiteralBoolean { }
|
||||
// LiteralBoolean is a boolean literal. It may be either true or false. It can
|
||||
// be assigned to any type derived from a boolean. It cannot be directly
|
||||
// assigned to an interface because it contains no inherent type information. A
|
||||
// value cast may be used for this purpose.
|
||||
type LiteralBoolean struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Value bool
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralBoolean) expression(){}
|
||||
func (this *LiteralBoolean) Position () errors.Position { return this.Pos }
|
||||
func (this *LiteralBoolean) Type () Type { return this.Ty }
|
||||
func (this *LiteralBoolean) HasExplicitType () bool { return false }
|
||||
func (this *LiteralBoolean) String () string {
|
||||
if this.Value {
|
||||
return "true"
|
||||
} else {
|
||||
return "false"
|
||||
}
|
||||
}
|
||||
|
||||
var _ Expression = &LiteralNil { }
|
||||
// TODO: document, put in spec, fully implement nil for slices interfaces etc
|
||||
type LiteralNil struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralNil) expression(){}
|
||||
func (this *LiteralNil) Position () errors.Position { return this.Pos }
|
||||
func (this *LiteralNil) Type () Type { return this.Ty }
|
||||
func (this *LiteralNil) HasExplicitType () bool { return false }
|
||||
func (this *LiteralNil) String () string { return "nil" }
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
import "unicode"
|
||||
import "unicode/utf8"
|
||||
import "path/filepath"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// Metadata represents a module metadata file.
|
||||
type Metadata struct {
|
||||
Pos errors.Position
|
||||
UUID uuid.UUID
|
||||
Dependencies []*Dependency
|
||||
}
|
||||
func (this *Metadata) Position () errors.Position { return this.Pos }
|
||||
func (this *Metadata) String () string {
|
||||
out := fmt.Sprint(Quote(this.UUID.String()))
|
||||
for _, dependency := range this.Dependencies {
|
||||
out += fmt.Sprint("\n", dependency)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Directive is a declaration within a module metadata file.
|
||||
type Directive interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Position returns the position of the directive within its metadata
|
||||
// file.
|
||||
Position () errors.Position
|
||||
|
||||
directive()
|
||||
}
|
||||
|
||||
var _ Directive = &Dependency { }
|
||||
// Dependency is a metadata dependency listing.
|
||||
type Dependency struct {
|
||||
Pos errors.Position
|
||||
Address Address
|
||||
Nickname string
|
||||
}
|
||||
func (*Dependency) directive () { }
|
||||
func (this *Dependency) Position () errors.Position { return this.Pos }
|
||||
func (this *Dependency) String () string {
|
||||
out := fmt.Sprint("+ ", this.Address)
|
||||
if this.Nickname != "" {
|
||||
out += fmt.Sprint(" ", this.Nickname)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Address is the address of a unit.
|
||||
type Address string
|
||||
func (addr Address) String () string {
|
||||
return Quote(string(addr))
|
||||
}
|
||||
// SourceFile returns the FSPL source file associated with the address. If the
|
||||
// address does not point to an FSPL source file, it returns "", false.
|
||||
func (addr Address) SourceFile () (string, bool) {
|
||||
ext := filepath.Ext(string(addr))
|
||||
if ext == ".fspl" {
|
||||
return filepath.Clean(string(addr)), true
|
||||
} else {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
// Module returns the module associated with the address. If the address does
|
||||
// does not point to a module, it returns "", false.
|
||||
func (addr Address) Module () (string, bool) {
|
||||
path := string(addr)
|
||||
switch {
|
||||
case filepath.Base(path) == "fspl.mod":
|
||||
return filepath.Dir(path), true
|
||||
case filepath.Ext(path) == "" || filepath.Ext(path) == ".":
|
||||
return filepath.Clean(path), true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
// Nickname automatically generates a nickname from the address, which is a
|
||||
// valid Ident token. On failure "", false is returned. The nickname is
|
||||
// generated according to the folowing rules:
|
||||
//
|
||||
// - If the name contains at least one dot, the last dot and everything after
|
||||
// it is removed
|
||||
// - All non-letter, non-numeric characters are removed, and any letters that
|
||||
// were directly after them are converted to uppercase
|
||||
// - All numeric digits at the start of the string are removed
|
||||
// - The first character is converted to lowercase
|
||||
func (addr Address) Nickname () (string, bool) {
|
||||
// get the normalized basename with no extension
|
||||
base := filepath.Base(string(addr))
|
||||
base = strings.TrimSuffix(base, filepath.Ext(base))
|
||||
|
||||
// remove all non-letter, non-digit characters and convert to camel case
|
||||
nickname := ""
|
||||
uppercase := false
|
||||
for _, char := range base {
|
||||
if unicode.IsLetter(char) || unicode.IsDigit(char) {
|
||||
if uppercase {
|
||||
uppercase = false
|
||||
char = unicode.ToUpper(char)
|
||||
}
|
||||
nickname += string(char)
|
||||
} else {
|
||||
uppercase = true
|
||||
}
|
||||
}
|
||||
|
||||
// remove numeric digits
|
||||
for len(nickname) > 0 {
|
||||
char, size := utf8.DecodeRuneInString(nickname)
|
||||
nickname = nickname[size:]
|
||||
if unicode.IsLetter(char) {
|
||||
// lowercase the first letter
|
||||
nickname = string(unicode.ToLower(char)) + nickname
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nickname, nickname != ""
|
||||
}
|
||||
// UUID automatically generates a UUID from the address. It is the MD5 hash of
|
||||
// the basename, with a space of the zero UUID.
|
||||
func (addr Address) UUID () uuid.UUID {
|
||||
return uuid.NewMD5(uuid.UUID { }, []byte(filepath.Base(string(addr))))
|
||||
}
|
155
entity/misc.go
155
entity/misc.go
|
@ -2,18 +2,55 @@ package entity
|
|||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "unicode"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// Access determines the external access control mode for a top-level entity.
|
||||
type Access int; const (
|
||||
// AccessPublic allows other modules to access an entity normally.
|
||||
AccessPublic Access = iota
|
||||
|
||||
// AccessOpaque causes a top-level entity to appear opaque to other
|
||||
// units. Values of opaque types can be passed around, assigned to each-
|
||||
// other, and their methods can be called, but the implementation of the
|
||||
// type is entirely hidden. This access mode cannot be applied to
|
||||
// functions or methods.
|
||||
AccessOpaque
|
||||
|
||||
// AccessPrivate disallows other modules from accessing a top-level
|
||||
// entity.
|
||||
AccessPrivate
|
||||
)
|
||||
func (this Access) String () string {
|
||||
switch this {
|
||||
case AccessPrivate: return "-"
|
||||
case AccessOpaque: return "#"
|
||||
case AccessPublic: return "+"
|
||||
default: return fmt.Sprintf("entity.Access(%d)", this)
|
||||
}
|
||||
}
|
||||
|
||||
// Signature is a function or method signature that is used in functions,
|
||||
// methods, and specifying interface behaviors. It defines the type of a
|
||||
// function.
|
||||
type Signature struct {
|
||||
Pos lexer.Position
|
||||
Name string `parser:" '[' @Ident "`
|
||||
Arguments []*Declaration `parser:" @@* ']' "`
|
||||
Return Type `parser:" ( ':' @@ )? "`
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Name string
|
||||
Arguments []*Declaration
|
||||
Return Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
ArgumentOrder []string
|
||||
ArgumentMap map[string] *Declaration
|
||||
}
|
||||
func (*Signature) ty(){}
|
||||
func (this *Signature) Position () errors.Position { return this.Pos }
|
||||
func (this *Signature) Access () Access { return this.Acc }
|
||||
func (this *Signature) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *Signature) String () string {
|
||||
out := "[" + this.Name
|
||||
for _, argument := range this.Arguments {
|
||||
|
@ -25,18 +62,80 @@ func (this *Signature) String () string {
|
|||
}
|
||||
return out
|
||||
}
|
||||
func (this *Signature) Hash () Hash {
|
||||
data := new(strings.Builder)
|
||||
data.WriteString("Signature:")
|
||||
for _, argument := range this.Arguments {
|
||||
argumentHash := HashType(argument.Type())
|
||||
data.Write(argumentHash[:])
|
||||
}
|
||||
data.WriteString(":")
|
||||
returnHash := HashType(this.Return)
|
||||
data.Write(returnHash[:])
|
||||
return NewHash([]byte(data.String()))
|
||||
}
|
||||
func (this *Signature) Equals (ty Type) bool {
|
||||
real, ok := ty.(*Signature)
|
||||
if !ok ||
|
||||
len(real.Arguments) != len(this.Arguments) ||
|
||||
!TypesEqual(real.Return, this.Return) ||
|
||||
real.Name != this.Name { return false }
|
||||
|
||||
for index, argument := range this.Arguments {
|
||||
if !argument.Type().Equals(real.Arguments[index].Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Member is a syntactical construct that is used to list members in struct
|
||||
// literals.
|
||||
type Member struct {
|
||||
Pos lexer.Position
|
||||
Name string `parser:" @Ident "`
|
||||
Value Expression `parser:" ':' @@ "`
|
||||
Pos errors.Position
|
||||
Name string
|
||||
Value Expression
|
||||
}
|
||||
func (this *Member) Position () errors.Position { return this.Pos }
|
||||
func (this *Member) String () string {
|
||||
return fmt.Sprint(this.Name, ":", this.Value)
|
||||
}
|
||||
|
||||
// MatchCase represents a case in a match expression.
|
||||
type MatchCase struct {
|
||||
Pos errors.Position
|
||||
Declaration *Declaration
|
||||
Expression
|
||||
Scope
|
||||
}
|
||||
func (this *MatchCase) Position () errors.Position { return this.Pos }
|
||||
func (this *MatchCase) String () string {
|
||||
return fmt.Sprint("| ", this.Declaration, this.Expression)
|
||||
}
|
||||
|
||||
// SwitchCase represents a case in a switch expression.
|
||||
type SwitchCase struct {
|
||||
Pos errors.Position
|
||||
Key Expression
|
||||
Expression
|
||||
Scope
|
||||
}
|
||||
func (this *SwitchCase) Position () errors.Position { return this.Pos }
|
||||
func (this *SwitchCase) String () string {
|
||||
return fmt.Sprint("| ", this.Key, this.Expression)
|
||||
}
|
||||
|
||||
// DefaultCase represents the default case in a switch or match expression.
|
||||
type DefaultCase struct {
|
||||
Pos errors.Position
|
||||
Expression
|
||||
Scope
|
||||
}
|
||||
func (this *DefaultCase) Position () errors.Position { return this.Pos }
|
||||
func (this *DefaultCase) String () string {
|
||||
return fmt.Sprint("* ", this.Expression)
|
||||
}
|
||||
|
||||
// Operator determines which operation to apply to a value in an operation
|
||||
// expression.
|
||||
type Operator int; const (
|
||||
|
@ -71,6 +170,25 @@ type Operator int; const (
|
|||
OperatorEqual
|
||||
)
|
||||
|
||||
// ResultsInBoolean determines whether or not this operation will always result
|
||||
// in a boolean value.
|
||||
func (operator Operator) ResultsInBoolean () bool {
|
||||
switch operator {
|
||||
case OperatorLogicalNot,
|
||||
OperatorLogicalOr,
|
||||
OperatorLogicalAnd,
|
||||
OperatorLogicalXor,
|
||||
OperatorLess,
|
||||
OperatorGreater,
|
||||
OperatorLessEqual,
|
||||
OperatorGreaterEqual,
|
||||
OperatorEqual:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var operatorToString = []string {
|
||||
// Math
|
||||
"+",
|
||||
|
@ -86,6 +204,7 @@ var operatorToString = []string {
|
|||
"|",
|
||||
"&",
|
||||
"^",
|
||||
|
||||
// Bit manipulation
|
||||
"!!",
|
||||
"||",
|
||||
|
@ -111,11 +230,25 @@ func init () {
|
|||
}
|
||||
}
|
||||
|
||||
func (this *Operator) Capture (str []string) error {
|
||||
*this = stringToOperator[strings.Join(str, "")]
|
||||
return nil
|
||||
func OperatorFromString (str string) Operator {
|
||||
return stringToOperator[str]
|
||||
}
|
||||
|
||||
func (this Operator) String () string {
|
||||
return operatorToString[this]
|
||||
}
|
||||
|
||||
// Quote puts quotes around a string to turn it into a string literal.
|
||||
func Quote (in string) string {
|
||||
out := "'"
|
||||
for _, char := range in {
|
||||
if char == '\'' {
|
||||
out += "\\'"
|
||||
} else if unicode.IsPrint(char) {
|
||||
out += string(char)
|
||||
} else {
|
||||
out += fmt.Sprintf("\\%03o", char)
|
||||
}
|
||||
}
|
||||
return out + "'"
|
||||
}
|
||||
|
|
|
@ -8,7 +8,11 @@ type Scoped interface {
|
|||
Variable (name string) *Declaration
|
||||
|
||||
// AddVariable adds a variable to this entity's scope.
|
||||
AddVariable (name string, declaration *Declaration)
|
||||
AddVariable (declaration *Declaration)
|
||||
|
||||
// OverVariables calls callback for each defined variable, stopping if
|
||||
// returns false.
|
||||
OverVariables (callback func (*Declaration) bool)
|
||||
}
|
||||
|
||||
// Scope implements a scope.
|
||||
|
@ -23,9 +27,15 @@ func (this *Scope) Variable (name string) *Declaration {
|
|||
return this.variables[name]
|
||||
}
|
||||
|
||||
func (this *Scope) AddVariable (name string, declaration *Declaration) {
|
||||
func (this *Scope) AddVariable (declaration *Declaration) {
|
||||
if this.variables == nil {
|
||||
this.variables = make(map[string] *Declaration)
|
||||
}
|
||||
this.variables[name] = declaration
|
||||
this.variables[declaration.Name] = declaration
|
||||
}
|
||||
|
||||
func (this *Scope) OverVariables (callback func (*Declaration) bool) {
|
||||
for _, declaration := range this.variables {
|
||||
if !callback(declaration) { break }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,55 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// TopLevel is any construct that is placed at the root of a file.
|
||||
type TopLevel interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Position returns the position of the entity within its source file.
|
||||
Position () errors.Position
|
||||
|
||||
// Access returns the access control mode of the entity.
|
||||
Access () Access
|
||||
|
||||
// Unit returns the unit that the entity was defined in.
|
||||
Unit () uuid.UUID
|
||||
|
||||
topLevel ()
|
||||
}
|
||||
|
||||
var _ TopLevel = &Typedef { }
|
||||
// Typedef binds a type to a global identifier.
|
||||
type Typedef struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Public bool `parser:" @'+'? "`
|
||||
Name string `parser:" @Ident "`
|
||||
Type Type `parser:" ':' @@ "`
|
||||
Pos errors.Position
|
||||
Acc Access
|
||||
Name string
|
||||
Type Type
|
||||
|
||||
// Semantics
|
||||
Methods map[string] Method
|
||||
Unt uuid.UUID
|
||||
Methods map[string] *Method
|
||||
}
|
||||
func (*Typedef) topLevel(){}
|
||||
func (this *Typedef) Position () errors.Position { return this.Pos }
|
||||
func (this *Typedef) Access () Access { return this.Acc }
|
||||
func (this *Typedef) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *Typedef) String () string {
|
||||
out := fmt.Sprint(this.Name, ": ", this.Type)
|
||||
output := ""
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
output += fmt.Sprint(this.Name, ": ", this.Type)
|
||||
if this.Methods != nil {
|
||||
for _, method := range this.Methods {
|
||||
out += fmt.Sprint("\n", method)
|
||||
output += fmt.Sprint("\n", method)
|
||||
}
|
||||
}
|
||||
return out
|
||||
return output
|
||||
}
|
||||
|
||||
var _ TopLevel = &Function { }
|
||||
// Function binds a global identifier and argument list to an expression which
|
||||
// is evaluated each time the function is called. If no expression is specified,
|
||||
// the function is marked as external. Functions have an argument list, where
|
||||
|
@ -37,46 +57,66 @@ func (this *Typedef) String () string {
|
|||
// these are typed.
|
||||
type Function struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Public bool `parser:" @'+'? "`
|
||||
Signature *Signature `parser:" @@ "`
|
||||
Body Expression `parser:" ( '=' @@ )? "`
|
||||
Pos errors.Position
|
||||
Acc Access
|
||||
LinkName string
|
||||
Signature *Signature
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Unt uuid.UUID
|
||||
Scope
|
||||
}
|
||||
func (*Function) topLevel(){}
|
||||
func (this *Function) Position () errors.Position { return this.Pos }
|
||||
func (this *Function) Access () Access { return this.Acc }
|
||||
func (this *Function) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *Function) String () string {
|
||||
if this.Body == nil {
|
||||
return fmt.Sprint(this.Signature)
|
||||
} else {
|
||||
return fmt.Sprint(this.Signature, " = ", this.Body)
|
||||
output := ""
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
output += this.Signature.String()
|
||||
if this.LinkName != "" {
|
||||
output += fmt.Sprint(" '", this.LinkName, "'")
|
||||
}
|
||||
if this.Body != nil {
|
||||
output += fmt.Sprint(" = ", this.Body)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
var _ TopLevel = &Method { }
|
||||
// Method is like a function, except localized to a defined type. Methods are
|
||||
// called on an instance of that type, and receive a pointer to that instance
|
||||
// via the "this" variable. Method names are not globally unique, bur are unique
|
||||
// within the type they are defined on.
|
||||
// via the "this" variable when they are run. Method names are not globally
|
||||
// unique, but are unique within the type they are defined on.
|
||||
type Method struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Public bool `parser:" @'+'? "`
|
||||
TypeName string `parser:" @Ident "`
|
||||
Signature *Signature `parser:" ':' ':' @@ "`
|
||||
Body Expression `parser:" ( '=' @@ )? "`
|
||||
Pos errors.Position
|
||||
Acc Access
|
||||
TypeName string
|
||||
LinkName string
|
||||
Signature *Signature
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Unt uuid.UUID
|
||||
Type Type
|
||||
This *Declaration
|
||||
Scope
|
||||
}
|
||||
func (*Method) topLevel(){}
|
||||
func (this *Method) Position () errors.Position { return this.Pos }
|
||||
func (this *Method) Access () Access { return this.Acc }
|
||||
func (this *Method) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *Method) String () string {
|
||||
if this.Body == nil {
|
||||
return fmt.Sprint(this.TypeName, ".", this.Signature)
|
||||
} else {
|
||||
return fmt.Sprint (
|
||||
this.TypeName, "::",
|
||||
this.Signature, " = ",
|
||||
this.Body)
|
||||
output := ""
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
output += fmt.Sprint(this.TypeName, ".", this.Signature)
|
||||
if this.LinkName != "" {
|
||||
output += fmt.Sprint(" '", this.LinkName, "'")
|
||||
}
|
||||
if this.Body != nil {
|
||||
output += fmt.Sprint(" = ", this.Body)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
|
377
entity/type.go
377
entity/type.go
|
@ -1,108 +1,403 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "strings"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// Type is any type notation.
|
||||
type Type interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Position returns the position of the type within its source file.
|
||||
Position () errors.Position
|
||||
|
||||
// Equals returns whether this type is equivalent to another type.
|
||||
Equals (ty Type) bool
|
||||
|
||||
// Access returns the access control mode of the type.
|
||||
Access () Access
|
||||
|
||||
// Unit returns the unit that the type was defined in.
|
||||
Unit () uuid.UUID
|
||||
|
||||
// Hash returns a hash of this type that fits within a uint64.
|
||||
Hash () Hash
|
||||
|
||||
ty ()
|
||||
}
|
||||
|
||||
type void { }
|
||||
func (void) ty(){}
|
||||
|
||||
// Void returns the absence of a type. It can be compared to a Type to find out
|
||||
// if it is filled in, but has no type.
|
||||
func Void () Type {
|
||||
return void { }
|
||||
}
|
||||
|
||||
// TypeNamed refers to a user-defined or built in named type.
|
||||
type TypeNamed struct {
|
||||
Pos lexer.Position
|
||||
Name string `parser:" @Ident "`
|
||||
Type Type
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
UnitNickname string
|
||||
Name string
|
||||
Type Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeNamed) ty(){}
|
||||
func (this *TypeNamed) String () string { return this.Name }
|
||||
func (this *TypeNamed) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeNamed) Access () Access { return this.Acc }
|
||||
func (this *TypeNamed) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeNamed) String () string {
|
||||
if this.UnitNickname == "" {
|
||||
return this.Name
|
||||
} else {
|
||||
return fmt.Sprint(this.UnitNickname, "::", this.Name)
|
||||
}
|
||||
}
|
||||
func (this *TypeNamed) Hash () Hash {
|
||||
return Key {
|
||||
Unit: this.Type.Unit(),
|
||||
Name: this.Name,
|
||||
}.Hash()
|
||||
}
|
||||
func (this *TypeNamed) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeNamed)
|
||||
return ok && TypesEqual(real.Type, this.Type)
|
||||
}
|
||||
|
||||
// TypePointer is a pointer to another type.
|
||||
type TypePointer struct {
|
||||
Pos lexer.Position
|
||||
Referenced Type `parser:" '*' @@ "`
|
||||
Pos errors.Position
|
||||
Referenced Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypePointer) ty(){}
|
||||
func (this *TypePointer) Position () errors.Position { return this.Pos }
|
||||
func (this *TypePointer) Access () Access { return this.Acc }
|
||||
func (this *TypePointer) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypePointer) String () string {
|
||||
return fmt.Sprint("*", this.Referenced)
|
||||
}
|
||||
func (this *TypePointer) Hash () Hash {
|
||||
referenced := HashType(this.Referenced)
|
||||
return NewHash(append([]byte("TypePointer:"), referenced[:]...))
|
||||
}
|
||||
func (this *TypePointer) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypePointer)
|
||||
return ok && TypesEqual(real.Referenced, this.Referenced)
|
||||
}
|
||||
|
||||
// TypeSlice is a pointer to several values of a given type stored next to
|
||||
// eachother. Its length is not built into its type and can be changed at
|
||||
// runtime.
|
||||
type TypeSlice struct {
|
||||
Pos lexer.Position
|
||||
Element Type `parser:" '*' ':' @@ "`
|
||||
Pos errors.Position
|
||||
Element Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeSlice) ty(){}
|
||||
func (this *TypeSlice) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeSlice) Access () Access { return this.Acc }
|
||||
func (this *TypeSlice) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeSlice) String () string {
|
||||
return fmt.Sprint("*:", this.Element)
|
||||
}
|
||||
func (this *TypeSlice) Hash () Hash {
|
||||
referenced := HashType(this.Element)
|
||||
return NewHash(append([]byte("TypeSlice:"), referenced[:]...))
|
||||
}
|
||||
func (this *TypeSlice) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeSlice)
|
||||
return ok && TypesEqual(real.Element, this.Element)
|
||||
}
|
||||
|
||||
// TypeArray is a group of values of a given type stored next to eachother. The
|
||||
// length of an array is fixed and is part of its type. Arrays are passed by
|
||||
// value unless a pointer is used.
|
||||
type TypeArray struct {
|
||||
Pos lexer.Position
|
||||
Length int `parser:" @Int "`
|
||||
Element Type `parser:" ':' @@ "`
|
||||
Pos errors.Position
|
||||
Length int
|
||||
Element Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeArray) ty(){}
|
||||
func (this *TypeArray) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeArray) Access () Access { return this.Acc }
|
||||
func (this *TypeArray) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeArray) String () string {
|
||||
return fmt.Sprint(this.Length, ":", this.Element)
|
||||
}
|
||||
func (this *TypeArray) Hash () Hash {
|
||||
referenced := HashType(this.Element)
|
||||
return NewHash(append (
|
||||
[]byte(fmt.Sprintf("TypeArray:%d:", this.Length)),
|
||||
referenced[:]...))
|
||||
}
|
||||
func (this *TypeArray) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeArray)
|
||||
return ok &&
|
||||
real.Length == this.Length &&
|
||||
TypesEqual(real.Element, this.Element)
|
||||
}
|
||||
|
||||
// Struct is a composite type that stores keyed values. The positions of the
|
||||
// TypeStruct is a composite type that stores keyed values. The positions of the
|
||||
// values within the struct are decided at compile time, based on the order they
|
||||
// are specified in. Structs are passed by value unless a pointer is used.
|
||||
type TypeStruct struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Members []*Declaration `parser:" '(' @@+ ')' "`
|
||||
Pos errors.Position
|
||||
Members []*Declaration
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
MemberOrder []string
|
||||
MemberMap map[string] *Declaration
|
||||
MemberMap map[string] *Declaration
|
||||
}
|
||||
func (*TypeStruct) ty(){}
|
||||
func (this *TypeStruct) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeStruct) Access () Access { return this.Acc }
|
||||
func (this *TypeStruct) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeStruct) String () string {
|
||||
out := "("
|
||||
for index, member := range this.Members {
|
||||
if index > 0 { out += " " }
|
||||
out += fmt.Sprint(member)
|
||||
out := "(."
|
||||
for _, member := range this.Members {
|
||||
out += fmt.Sprint(" ", member)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
func (this *TypeStruct) Hash () Hash {
|
||||
data := new(strings.Builder)
|
||||
data.WriteString("TypeStruct:")
|
||||
for _, member := range this.Members {
|
||||
memberHash := HashType(member.Type())
|
||||
data.Write(memberHash[:])
|
||||
}
|
||||
return NewHash([]byte(data.String()))
|
||||
}
|
||||
func (this *TypeStruct) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeStruct)
|
||||
if !ok || len(real.Members) != len(this.Members) { return false }
|
||||
|
||||
// Interface is a polymorphic pointer that allows any value of any type through,
|
||||
// except it must have at least the methods defined within the interface.
|
||||
// Interfaces are always passed by reference. When assigning a value to an
|
||||
// interface, it will be referenced automatically. When assigning a pointer to
|
||||
// an interface, the pointer's reference will be used instead.
|
||||
for index, member := range this.Members {
|
||||
if !TypesEqual(member.Type(), real.Members[index].Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TypeInterface is a polymorphic pointer that allows any value of any type
|
||||
// through, except it must have at least the methods defined within the
|
||||
// interface. Interfaces are always passed by reference. When assigning a value
|
||||
// to an interface, it will be referenced automatically. When assigning a
|
||||
// pointer to an interface, the pointer's reference will be used instead.
|
||||
type TypeInterface struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Behaviors []*Signature `parser:" '(' @@+ ')' "`
|
||||
Pos errors.Position
|
||||
Behaviors []*Signature
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
BehaviorOrder []string
|
||||
BehaviorMap map[string] *Signature
|
||||
BehaviorMap map[string] *Signature
|
||||
}
|
||||
func (*TypeInterface) ty(){}
|
||||
func (this *TypeInterface) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeInterface) Access () Access { return this.Acc }
|
||||
func (this *TypeInterface) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeInterface) String () string {
|
||||
out := "("
|
||||
for index, behavior := range this.Behaviors {
|
||||
if index > 0 { out += " " }
|
||||
out += fmt.Sprint(behavior)
|
||||
out := "(&"
|
||||
for _, behavior := range this.Behaviors {
|
||||
out += fmt.Sprint(" ", behavior)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
func (this *TypeInterface) Hash () Hash {
|
||||
data := new(strings.Builder)
|
||||
data.WriteString("TypeInterface:")
|
||||
for _, behavior := range this.Behaviors {
|
||||
behaviorHash := HashType(behavior)
|
||||
data.Write(behaviorHash[:])
|
||||
}
|
||||
return NewHash([]byte(data.String()))
|
||||
}
|
||||
func (this *TypeInterface) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeInterface)
|
||||
if !ok || len(real.Behaviors) != len(this.Behaviors) { return false }
|
||||
|
||||
for index, behavior := range this.Behaviors {
|
||||
if !behavior.Equals(real.Behaviors[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TypeUnion is a polymorphic type that can hold any value as long as it is one
|
||||
// of a list of allowed types. It is not a pointer. It holds the hash of the
|
||||
// actual type of the value stored within it, followed by the value. The hash
|
||||
// field is computed using the type's name, and the UUID that it was defined in.
|
||||
// If it is not named, then the hash is computed using the structure of the
|
||||
// type. The value field is always big enough to hold the largest type in the
|
||||
// allowed list.
|
||||
type TypeUnion struct {
|
||||
// Syntax
|
||||
Pos errors.Position
|
||||
Allowed []Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
AllowedOrder []Hash
|
||||
AllowedMap map[Hash] Type
|
||||
}
|
||||
func (*TypeUnion) ty(){}
|
||||
func (this *TypeUnion) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeUnion) Access () Access { return this.Acc }
|
||||
func (this *TypeUnion) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeUnion) String () string {
|
||||
out := "(|"
|
||||
for _, ty := range this.Allowed {
|
||||
out += fmt.Sprint(" ", ty)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
func (this *TypeUnion) Hash () Hash {
|
||||
data := new(strings.Builder)
|
||||
data.WriteString("TypeUnion:")
|
||||
for _, ty := range this.Allowed {
|
||||
typeHash := HashType(ty)
|
||||
data.Write(typeHash[:])
|
||||
}
|
||||
return NewHash([]byte(data.String()))
|
||||
}
|
||||
func (this *TypeUnion) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeUnion)
|
||||
if !ok || len(real.Allowed) != len(this.Allowed) { return false }
|
||||
|
||||
for index, ty := range this.Allowed {
|
||||
if !ty.Equals(real.Allowed[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TypeInt represents any signed or unsigned integer type.
|
||||
type TypeInt struct {
|
||||
Pos errors.Position
|
||||
Width int
|
||||
Signed bool
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeInt) ty(){}
|
||||
func (this *TypeInt) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeInt) Access () Access { return this.Acc }
|
||||
func (this *TypeInt) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeInt) String () string {
|
||||
if this.Signed {
|
||||
return fmt.Sprint("I", this.Width)
|
||||
} else {
|
||||
return fmt.Sprint("U", this.Width)
|
||||
}
|
||||
}
|
||||
func (this *TypeInt) Hash () Hash {
|
||||
return NewHash([]byte(fmt.Sprintf (
|
||||
"TypeInt:%d:%t",
|
||||
this.Width, this.Signed)))
|
||||
}
|
||||
func (this *TypeInt) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeInt)
|
||||
return ok && real.Width == this.Width && real.Signed == this.Signed
|
||||
}
|
||||
|
||||
// TypeFloat represents any floating point type.
|
||||
type TypeFloat struct {
|
||||
Pos errors.Position
|
||||
Width int
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeFloat) ty(){}
|
||||
func (this *TypeFloat) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeFloat) Access () Access { return this.Acc }
|
||||
func (this *TypeFloat) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeFloat) String () string {
|
||||
return fmt.Sprint("F", this.Width)
|
||||
}
|
||||
func (this *TypeFloat) Hash () Hash {
|
||||
return NewHash([]byte(fmt.Sprintf("TypeFloat:%d", this.Width)))
|
||||
}
|
||||
func (this *TypeFloat) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeFloat)
|
||||
return ok && real.Width == this.Width
|
||||
}
|
||||
|
||||
// TypeWord represents an integer type of unspecified width. The optimal width
|
||||
// is chosen based on the machine word size (32 on 32 bit systems, 64 on 64 bit
|
||||
// systems, etc)
|
||||
type TypeWord struct {
|
||||
Pos errors.Position
|
||||
Signed bool
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeWord) ty(){}
|
||||
func (this *TypeWord) Position () errors.Position { return this.Pos }
|
||||
func (this *TypeWord) Access () Access { return this.Acc }
|
||||
func (this *TypeWord) Unit () uuid.UUID { return this.Unt }
|
||||
func (this *TypeWord) String () string {
|
||||
if this.Signed {
|
||||
return "Int"
|
||||
} else {
|
||||
return "UInt"
|
||||
}
|
||||
}
|
||||
func (this *TypeWord) Hash () Hash {
|
||||
return NewHash([]byte(fmt.Sprintf("TypeWord:%t", this.Signed)))
|
||||
}
|
||||
func (this *TypeWord) Equals (ty Type) bool {
|
||||
real, ok := ty.(*TypeWord)
|
||||
return ok && real.Signed == this.Signed
|
||||
}
|
||||
|
||||
// TypesEqual checks if two types are equal to eachother, even if one or both
|
||||
// are nil.
|
||||
func TypesEqual (left, right Type) bool {
|
||||
if (left == nil) != (right == nil) { return false }
|
||||
if left == nil { return true }
|
||||
return left.Equals(right)
|
||||
}
|
||||
|
||||
// FormatType returns a string representing a type. If the type is nil, it
|
||||
// returns "Void".
|
||||
func FormatType (ty Type) string {
|
||||
if ty == nil {
|
||||
return "Void"
|
||||
} else {
|
||||
return ty.String()
|
||||
}
|
||||
}
|
||||
|
||||
// HashType returns a hash representing a type. If the type is nil, it returns
|
||||
// NewHash([]byte("TypeVoid"))
|
||||
func HashType (ty Type) Hash {
|
||||
if ty == nil {
|
||||
return NewHash([]byte("TypeVoid"))
|
||||
} else {
|
||||
return ty.Hash()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Package errors provides a location tracking and error formatting system.
|
||||
// It provides a way to keep track of the in-file position of the physical text
|
||||
// that in-memory data structures were derived from, and a way to use this
|
||||
// positional information to create informative and easy to read errors.
|
||||
package errors
|
|
@ -0,0 +1,62 @@
|
|||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Error is an error that contains positional information.
|
||||
type Error interface {
|
||||
error
|
||||
Position () Position
|
||||
}
|
||||
|
||||
type errInternal struct {
|
||||
position Position
|
||||
message string
|
||||
}
|
||||
var _ Error = new(errInternal)
|
||||
|
||||
func (err *errInternal) Error () string {
|
||||
return err.String()
|
||||
}
|
||||
|
||||
func (err *errInternal) String () string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
func (err *errInternal) Position () Position {
|
||||
return err.position
|
||||
}
|
||||
|
||||
// Errorf produces an error with a fmt'd message.
|
||||
func Errorf (position Position, format string, variables ...any) Error {
|
||||
return &errInternal {
|
||||
position: position,
|
||||
message: fmt.Sprintf(format, variables...),
|
||||
}
|
||||
}
|
||||
|
||||
// Format returns a formatted string representing an error. This string may take
|
||||
// up multiple lines and contain ANSI escape codes. If err does not fulfill the
|
||||
// Error interface, err.Error() is returned instead.
|
||||
//
|
||||
// When the error fulfills the Error interface, the formatted output will
|
||||
// be of the general form:
|
||||
// FILE:ROW+1:START+1: MESSAGE
|
||||
// ROW+1 | LINE
|
||||
// ^^^^
|
||||
// Where the position of the underline (^^^^) corresponds to the start and end
|
||||
// fields in the error's position. Note that the row and column numbers as
|
||||
// displayed are increased by one from their normal zero-indexed state, because
|
||||
// most editors display row and column numbers starting at 1:1. Additionally,
|
||||
// because normal error messages do not produce trailing line breaks, neither
|
||||
// does this function.
|
||||
func Format (err error) string {
|
||||
if e, ok := err.(Error); ok {
|
||||
return fmt.Sprintf (
|
||||
"%v: %v\n%v",
|
||||
e.Position(),
|
||||
e.Error(),
|
||||
e.Position().Format())
|
||||
} else {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue