Compare commits
571 Commits
Author | SHA1 | Date |
---|---|---|
Sasha Koshka | 89311688a4 | |
Sasha Koshka | a4ddab7bb4 | |
Sasha Koshka | 4aa0a0f304 | |
Sasha Koshka | 71a1f0a253 | |
Sasha Koshka | bd2172b732 | |
Sasha Koshka | e2cb3820cf | |
Sasha Koshka | eac7d492d6 | |
Sasha Koshka | acf8046114 | |
Sasha Koshka | 69c4294522 | |
Sasha Koshka | 7acc9c5ab0 | |
Sasha Koshka | f92008046b | |
Sasha Koshka | f7b590d823 | |
Sasha Koshka | c2cdb86b12 | |
Sasha Koshka | 7225ef7dc3 | |
Sasha Koshka | e24d22a24f | |
Sasha Koshka | f3e9f33dd3 | |
Sasha Koshka | aa82eb190a | |
Sasha Koshka | 35479e68a8 | |
Sasha Koshka | 40c5d10d4b | |
Sasha Koshka | 68be34ddfe | |
Sasha Koshka | ba58259eaf | |
Sasha Koshka | e2a61e9506 | |
Sasha Koshka | 0395df9944 | |
Sasha Koshka | 774f36724d | |
Sasha Koshka | 1c2186e9fc | |
Sasha Koshka | f21828794e | |
Sasha Koshka | 5e740ebf5f | |
Sasha Koshka | d5bb1be894 | |
Sasha Koshka | 0c39d0a1cb | |
Sasha Koshka | dda8cea996 | |
Sasha Koshka | 2c3bc9acce | |
Sasha Koshka | 60c49a1d9a | |
Sasha Koshka | d92aa7c3a9 | |
Sasha Koshka | 73f70304ed | |
Sasha Koshka | 2b9c99fff2 | |
Sasha Koshka | 4c26410274 | |
Sasha Koshka | cc2b1315a0 | |
Sasha Koshka | 785a77b21b | |
Sasha Koshka | cc3315280a | |
Sasha Koshka | ad828ee01e | |
Sasha Koshka | 179c0e00c2 | |
Sasha Koshka | c1d574b60f | |
Sasha Koshka | 6bef8aea76 | |
Sasha Koshka | 818b8058e4 | |
Sasha Koshka | e5144fdf6a | |
Sasha Koshka | 5c8afd52af | |
Sasha Koshka | e87717ac21 | |
Sasha Koshka | 9459f6192b | |
Sasha Koshka | c65a307f4a | |
Sasha Koshka | da434f00b6 | |
Sasha Koshka | f42b76d5c1 | |
Sasha Koshka | 33995288cc | |
Sasha Koshka | a158f26120 | |
Sasha Koshka | 515eb172a5 | |
Sasha Koshka | e55e332e17 | |
Sasha Koshka | 2fc272fccf | |
Sasha Koshka | 7ad4bec332 | |
Sasha Koshka | 0bbeeda319 | |
Sasha Koshka | 3db6aa211e | |
Sasha Koshka | a0f29f36f5 | |
Sasha Koshka | 779c50e961 | |
Sasha Koshka | 7e98950574 | |
Sasha Koshka | ea20d1d022 | |
Sasha Koshka | 2a622a1db3 | |
Sasha Koshka | 623bcabb84 | |
Sasha Koshka | 7b9b45e5ac | |
Sasha Koshka | 4c6ff81c18 | |
Sasha Koshka | 7f31fc1547 | |
Sasha Koshka | a580d5d796 | |
Sasha Koshka | 7f4e23fb71 | |
Sasha Koshka | 7d530772bf | |
Sasha Koshka | 24a7995819 | |
Sasha Koshka | 1ba6afef23 | |
Sasha Koshka | bfee5ecc92 | |
Sasha Koshka | c49e963310 | |
Sasha Koshka | 6f429571ff | |
Sasha Koshka | 3ef8ecca66 | |
Sasha Koshka | 2cedeb9c0d | |
Sasha Koshka | 12bf91d010 | |
Sasha Koshka | 49b6264ba4 | |
Sasha Koshka | c4873915bb | |
Sasha Koshka | 66ee370f23 | |
Sasha Koshka | 34c6fcbd57 | |
Sasha Koshka | b6edaa0814 | |
Sasha Koshka | 33b6414647 | |
Sasha Koshka | abb326bffc | |
Sasha Koshka | 74c6d93151 | |
Sasha Koshka | dd09b86cb8 | |
Sasha Koshka | f5bae2c231 | |
Sasha Koshka | f0d42f996c | |
Sasha Koshka | a9522b5c20 | |
Sasha Koshka | 3039bdbaf7 | |
Sasha Koshka | 6ea10c3783 | |
Sasha Koshka | ac56be4468 | |
Sasha Koshka | e0cb31a85b | |
Sasha Koshka | ef2a2ab059 | |
Sasha Koshka | 1c6e179f0a | |
Sasha Koshka | 3ef9f404b1 | |
Sasha Koshka | e38c185042 | |
Sasha Koshka | 745e79157c | |
Sasha Koshka | f6eeb011bf | |
Sasha Koshka | 95db63b1b8 | |
Sasha Koshka | d2cb1f78c4 | |
Sasha Koshka | 6dc958742b | |
Sasha Koshka | 0684410631 | |
Sasha Koshka | e2367b26c2 | |
Sasha Koshka | 8019a11826 | |
Sasha Koshka | 9178e55a20 | |
Sasha Koshka | 78fa0f703d | |
Sasha Koshka | afe3683c2a | |
Sasha Koshka | 699ef84bef | |
Sasha Koshka | 75d89e9c2a | |
Sasha Koshka | 49493fe7d0 | |
Sasha Koshka | 7a4b9850df | |
Sasha Koshka | 5940d47ad7 | |
Sasha Koshka | 736f117fac | |
Sasha Koshka | ee4dfc3e4a | |
Sasha Koshka | 7420fac2bd | |
Sasha Koshka | bc5f435ea6 | |
Sasha Koshka | 06b1cc0cb6 | |
Sasha Koshka | 953e9336db | |
Sasha Koshka | 04a94a2dc9 | |
Sasha Koshka | ab1f25785b | |
Sasha Koshka | 940346eecf | |
Sasha Koshka | 832e9232aa | |
Sasha Koshka | 2985d13131 | |
Sasha Koshka | f30e6d839d | |
Sasha Koshka | b27d5fe006 | |
Sasha Koshka | 98789cf779 | |
Sasha Koshka | 8ad421b1e1 | |
Sasha Koshka | 794d44787d | |
Sasha Koshka | c69091898a | |
Sasha Koshka | 89500ec5ef | |
Sasha Koshka | 1636fa0172 | |
Sasha Koshka | a46939b3f8 | |
Sasha Koshka | 87fa994f27 | |
Sasha Koshka | a4b60a5eff | |
Sasha Koshka | a9d470d46a | |
Sasha Koshka | 50cbf25c1e | |
Sasha Koshka | 0bcd41c873 | |
Sasha Koshka | a240e26776 | |
Sasha Koshka | 81bef17d6f | |
Sasha Koshka | fb374c5420 | |
Sasha Koshka | 87d5ca4cee | |
Sasha Koshka | a837577217 | |
Sasha Koshka | 211a6d358a | |
Sasha Koshka | 9598890aa4 | |
Sasha Koshka | b4adef7a1c | |
Sasha Koshka | 24961269f2 | |
Sasha Koshka | c68b3ab4c1 | |
Sasha Koshka | bda19ccdcf | |
Sasha Koshka | f06ca0250a | |
Sasha Koshka | b184056a9d | |
Sasha Koshka | f3df9ce15a | |
Sasha Koshka | 364141ad0d | |
Sasha Koshka | 27947f7ca4 | |
Sasha Koshka | 6bc5cc8766 | |
Sasha Koshka | b6bd81a244 | |
Sasha Koshka | ce6ca856ee | |
Sasha Koshka | 6bb5846427 | |
Sasha Koshka | 548c8c4de1 | |
Sasha Koshka | 7093e65aed | |
Sasha Koshka | 76d71cd1e8 | |
Sasha Koshka | 64e5adcf41 | |
Sasha Koshka | 4385b4cf75 | |
Sasha Koshka | c76711cd02 | |
Sasha Koshka | 66d618a6fc | |
Sasha Koshka | 2c127c5170 | |
Sasha Koshka | 83b58c1ef6 | |
Sasha Koshka | f77f341c3d | |
Sasha Koshka | 3662bda3a8 | |
Sasha Koshka | f2f522645f | |
Sasha Koshka | eb7a34115b | |
Sasha Koshka | 58a15d3da0 | |
Sasha Koshka | 928bf64d9d | |
Sasha Koshka | 385657a5c1 | |
Sasha Koshka | f6b25c8543 | |
Sasha Koshka | f90364697c | |
Sasha Koshka | 8bf97af763 | |
Sasha Koshka | 20ed736823 | |
Sasha Koshka | a68633f061 | |
Sasha Koshka | ac16322860 | |
Sasha Koshka | e8b8139b20 | |
Sasha Koshka | fce7595126 | |
Sasha Koshka | 6bb61b294b | |
Sasha Koshka | 5026576878 | |
Sasha Koshka | 6aa75b80c5 | |
Sasha Koshka | 4425823404 | |
Sasha Koshka | 6e4494e7ba | |
Sasha Koshka | cbbccea64a | |
Sasha Koshka | 547b625a3f | |
Sasha Koshka | 813a95bc8c | |
Sasha Koshka | 2c6623e763 | |
Sasha Koshka | 8c6b7a96af | |
Sasha Koshka | 9ca1483f82 | |
Sasha Koshka | c78deebcc6 | |
Sasha Koshka | 9d48bcb868 | |
Sasha Koshka | 803923b3d2 | |
Sasha Koshka | bb77db4c80 | |
Sasha Koshka | 5f804c71da | |
Sasha Koshka | 341bbb9a6e | |
Sasha Koshka | 877b6d669a | |
Sasha Koshka | a230c5d77b | |
Sasha Koshka | 2dacebefae | |
Sasha Koshka | 1804cbbd98 | |
Sasha Koshka | 2e9f5ee11e | |
Sasha Koshka | 6e85c6ae19 | |
Sasha Koshka | d10eb4e41e | |
Sasha Koshka | 194617dd34 | |
Sasha Koshka | 5d09926d30 | |
Sasha Koshka | a8a74ec31f | |
Sasha Koshka | 7d2831109c | |
Sasha Koshka | 727ccf3660 | |
Sasha Koshka | ce66fc879a | |
Sasha Koshka | 5e3ef3f57c | |
Sasha Koshka | 361c4f7289 | |
Sasha Koshka | d59703aa5d | |
Sasha Koshka | 229c88b58a | |
Sasha Koshka | 503b692104 | |
Sasha Koshka | 9bb10ef00d | |
Sasha Koshka | 097d7dfbe3 | |
Sasha Koshka | 206a9804dd | |
Sasha Koshka | cd419cb12d | |
Sasha Koshka | 3cd512cefb | |
Sasha Koshka | 862e6b1e25 | |
Sasha Koshka | 5c2df7f837 | |
Sasha Koshka | b6c7a0482a | |
Sasha Koshka | 678f1f15ab | |
Sasha Koshka | 2f4522cc6c | |
Sasha Koshka | 567c59559f | |
Sasha Koshka | 8ff47a3076 | |
Sasha Koshka | fbcc1f14d2 | |
Sasha Koshka | 93061188e8 | |
Sasha Koshka | df9b96eefc | |
Sasha Koshka | 85788fca25 | |
Sasha Koshka | d2a4d785f6 | |
Sasha Koshka | 29012110f8 | |
Sasha Koshka | e60a6e246f | |
Sasha Koshka | 4089765633 | |
Sasha Koshka | 993416d3d8 | |
Sasha Koshka | 24f1044b8f | |
Sasha Koshka | ee90077f90 | |
Sasha Koshka | 0a87781445 | |
Sasha Koshka | da4d21a87b | |
Sasha Koshka | 16317a9eb8 | |
Sasha Koshka | 807732b460 | |
Sasha Koshka | 5fa787dc64 | |
Sasha Koshka | 775671442b | |
Sasha Koshka | 498df4fad8 | |
Sasha Koshka | 19ba849e4b | |
Sasha Koshka | ba52a2df2e | |
Sasha Koshka | cb7180fa1c | |
Sasha Koshka | c9fa465430 | |
Sasha Koshka | 9e815d0bea | |
Sasha Koshka | 5bb9508a76 | |
Sasha Koshka | 090d52b8f8 | |
Sasha Koshka | 95e12d6d19 | |
Sasha Koshka | 202fd1f0bc | |
Sasha Koshka | ef7be1f7e8 | |
Sasha Koshka | 68b144a2df | |
Sasha Koshka | be314a1b79 | |
Sasha Koshka | 95118a183b | |
Sasha Koshka | 227a329538 | |
Sasha Koshka | e1ae32278f | |
Sasha Koshka | 335060ce84 | |
Sasha Koshka | 99d4799883 | |
Sasha Koshka | de1f873929 | |
Sasha Koshka | b715dbcce3 | |
Sasha Koshka | e9348ea58f | |
Sasha Koshka | 5fad8a79dd | |
Sasha Koshka | a0a155c7e6 | |
Sasha Koshka | 1cdc14a313 | |
Sasha Koshka | e4c8268e18 | |
Sasha Koshka | 1bf47a9ebe | |
Sasha Koshka | 78786f4ea2 | |
Sasha Koshka | 116a687099 | |
Sasha Koshka | e0c62e40bf | |
Sasha Koshka | 1a4bade4dd | |
Sasha Koshka | b17ffdc6d9 | |
Sasha Koshka | 30d479565b | |
Sasha Koshka | f997254270 | |
Sasha Koshka | 2882a28621 | |
Sasha Koshka | ac83753c56 | |
Sasha Koshka | be35cda999 | |
Sasha Koshka | 161ee28e7b | |
Sasha Koshka | c1df3f5ae5 | |
Sasha Koshka | 8cd0871939 | |
Sasha Koshka | ea8d5e6b3a | |
Sasha Koshka | 87c6490183 | |
Sasha Koshka | 4a3e06362d | |
Sasha Koshka | ed239aabb9 | |
Sasha Koshka | 854abfed1c | |
Sasha Koshka | d445743200 | |
Sasha Koshka | a47023fc95 | |
Sasha Koshka | 16f27cd2ee | |
Sasha Koshka | cc8426bf85 | |
Sasha Koshka | 531d3ca22b | |
Sasha Koshka | 51ef3682f1 | |
Sasha Koshka | 0762d72486 | |
Sasha Koshka | 02ec77b855 | |
Sasha Koshka | 97605d8c2a | |
Sasha Koshka | 424b621bae | |
Sasha Koshka | 48fe20b472 | |
Sasha Koshka | 1529e591e8 | |
Sasha Koshka | 19910b77a9 | |
Sasha Koshka | f8399612f6 | |
Sasha Koshka | cb7734afe6 | |
Sasha Koshka | 23362b058d | |
Sasha Koshka | 59e3185500 | |
Sasha Koshka | 6ad56ed4ee | |
Sasha Koshka | 8adaa0e053 | |
Sasha Koshka | 9dab29f5cb | |
Sasha Koshka | 39d8ed088d | |
Sasha Koshka | 84d88a7644 | |
Sasha Koshka | 100986327e | |
Sasha Koshka | 6924d39dcb | |
Sasha Koshka | 6fc16c7045 | |
Sasha Koshka | e6043effe0 | |
Sasha Koshka | f3f8b324dc | |
Sasha Koshka | c2d9b4d40f | |
Sasha Koshka | ef14fc2219 | |
Sasha Koshka | 825e71a720 | |
Sasha Koshka | 77783c1004 | |
Sasha Koshka | c1c2ab5124 | |
Sasha Koshka | a09a0ce34c | |
Sasha Koshka | 76c129f91c | |
Sasha Koshka | f4e5e1ea3d | |
Sasha Koshka | bca9de4106 | |
Sasha Koshka | 1b50c8f879 | |
Sasha Koshka | 3c67cf576c | |
Sasha Koshka | 79f51ce4c6 | |
Sasha Koshka | 3e89953d07 | |
Sasha Koshka | 08f1027834 | |
Sasha Koshka | 118e7672bd | |
Sasha Koshka | 4a2671c601 | |
Sasha Koshka | ac433f07ec | |
Sasha Koshka | 3d1bef3a32 | |
Sasha Koshka | eafc834ef3 | |
Sasha Koshka | 8cfb4f3e6d | |
Sasha Koshka | cce8c756fc | |
Sasha Koshka | 66f0e2d8d9 | |
Sasha Koshka | 5fe6e32f92 | |
Sasha Koshka | 7db231a945 | |
Sasha Koshka | ec3907d841 | |
Sasha Koshka | b6a920af58 | |
Sasha Koshka | ff9c275980 | |
Sasha Koshka | 04e5ead3db | |
Sasha Koshka | 0d79482568 | |
Sasha Koshka | dc6a36b548 | |
Sasha Koshka | 57d1ab9b2a | |
Sasha Koshka | 50ec220d08 | |
Sasha Koshka | 187ed48c4f | |
Sasha Koshka | 031f7008fa | |
Sasha Koshka | d2ecd7a266 | |
Sasha Koshka | 6bfc073608 | |
Sasha Koshka | db2b7e5f90 | |
Sasha Koshka | 3e9f3553fe | |
Sasha Koshka | 7c29d552f2 | |
Sasha Koshka | e3f5ffa5c5 | |
Sasha Koshka | 554fc1f7a7 | |
Sasha Koshka | b95df4c23c | |
Sasha Koshka | 8340dbe7c2 | |
Sasha Koshka | 0d20f1cbc0 | |
Sasha Koshka | af79419809 | |
Sasha Koshka | 0c1382896e | |
Sasha Koshka | 1e827a9ea1 | |
Sasha Koshka | 06a183c7af | |
Sasha Koshka | 9b80c29a15 | |
Sasha Koshka | 044e73efda | |
Sasha Koshka | 8107f82531 | |
Sasha Koshka | e42c292433 | |
Sasha Koshka | c71cc43a47 | |
Sasha Koshka | 0d4a86ef02 | |
Sasha Koshka | 82c05039b7 | |
Sasha Koshka | 86df537ece | |
Sasha Koshka | f057f3b4a4 | |
Sasha Koshka | 85703f6059 | |
Sasha Koshka | fe85582088 | |
Sasha Koshka | 5940e540b3 | |
Sasha Koshka | 3c7565861d | |
Sasha Koshka | cd6ed93b98 | |
Sasha Koshka | 56ae34c297 | |
Sasha Koshka | 57ef5933ee | |
Sasha Koshka | f35f3e0df0 | |
Sasha Koshka | 773e718592 | |
Sasha Koshka | 41c9f57063 | |
Sasha Koshka | d9c6bd644b | |
Sasha Koshka | 954f6c877e | |
Sasha Koshka | 05e75cb746 | |
Sasha Koshka | 3a4a5683b5 | |
Sasha Koshka | f994c4dc08 | |
Sasha Koshka | 15541b8675 | |
Sasha Koshka | 805a9a110f | |
Sasha Koshka | 91d4b92e6b | |
Sasha Koshka | 021a289623 | |
Sasha Koshka | 5085ea8502 | |
Sasha Koshka | b13130db8e | |
Sasha Koshka | 21fd6a4130 | |
Sasha Koshka | 706e2e0286 | |
Sasha Koshka | 6d28d52cdf | |
Sasha Koshka | fc5dda739e | |
Sasha Koshka | 8bf3938842 | |
Sasha Koshka | 43130fd0da | |
Sasha Koshka | cc5ed3c2ee | |
Sasha Koshka | b1799c25e1 | |
Sasha Koshka | 1f653be8cc | |
Sasha Koshka | 43aae56b37 | |
Sasha Koshka | f9d53d7ab7 | |
Sasha Koshka | fbaec1efdb | |
Sasha Koshka | 10fb780e50 | |
Sasha Koshka | 6de2a9d37d | |
Sasha Koshka | 8606c761e8 | |
Sasha Koshka | b56dceb3e1 | |
Sasha Koshka | 7318bfbc88 | |
Sasha Koshka | ca6be75cf0 | |
Sasha Koshka | 9ac9af8bcb | |
Sasha Koshka | ed350f94b8 | |
Sasha Koshka | 709ec56873 | |
Sasha Koshka | 6b9c165d14 | |
Sasha Koshka | b0c62642f5 | |
Sasha Koshka | 4c66ad4b44 | |
Sasha Koshka | 35dbbebb8b | |
Sasha Koshka | 098248da19 | |
Sasha Koshka | a80cd600b5 | |
Sasha Koshka | ea32fcc7d5 | |
Sasha Koshka | d28bfbac16 | |
Sasha Koshka | 34b9812db4 | |
Sasha Koshka | 85a582e349 | |
Sasha Koshka | 8e458111b5 | |
Sasha Koshka | f6604dcecd | |
Sasha Koshka | 67a0157cbd | |
Sasha Koshka | 2a927b209f | |
Sasha Koshka | f1d48d74a2 | |
Sasha Koshka | 5b5c66d409 | |
Sasha Koshka | 699cd88000 | |
Sasha Koshka | 54ac39ee01 | |
Sasha Koshka | a0d31fad38 | |
Sasha Koshka | b20dbcebbb | |
Sasha Koshka | bb59f8de4a | |
Sasha Koshka | 4b28d40b89 | |
Sasha Koshka | 9390eb6b2e | |
Sasha Koshka | 70c45f6fe1 | |
Sasha Koshka | 7757133d43 | |
Sasha Koshka | 7d926be83a | |
Sasha Koshka | d4f81b0667 | |
Sasha Koshka | 3cca2dd130 | |
Sasha Koshka | eeae7ac1ed | |
Sasha Koshka | af73d8e251 | |
Sasha Koshka | 308efbbd5e | |
Sasha Koshka | ae60aa173e | |
Sasha Koshka | 6bc59ca0d1 | |
Sasha Koshka | 7572e8bf08 | |
Sasha Koshka | da7807d653 | |
Sasha Koshka | b7faa727af | |
Sasha Koshka | 4a28ee61c0 | |
Sasha Koshka | 62a83f5544 | |
Sasha Koshka | c9eccdd86f | |
Sasha Koshka | e796f055ad | |
Sasha Koshka | 895e874e2f | |
Sasha Koshka | 5cd1be88ce | |
Sasha Koshka | 20db37b704 | |
Sasha Koshka | 5dc9331668 | |
Sasha Koshka | 35ecdd7a8b | |
Sasha Koshka | ea934445c6 | |
Sasha Koshka | 139a66f239 | |
Sasha Koshka | 8c64c07fff | |
Sasha Koshka | 019a8732cb | |
Sasha Koshka | 8d1d87dbb8 | |
Sasha Koshka | 1176da7cb9 | |
Sasha Koshka | 988649098c | |
Sasha Koshka | e470fb1f5c | |
Sasha Koshka | 2353ecd0e1 | |
Sasha Koshka | e32d349e2e | |
Sasha Koshka | 8bc00720ac | |
Sasha Koshka | 058a985d92 | |
Sasha Koshka | f7a6e905f6 | |
Sasha Koshka | 6074b414b5 | |
Sasha Koshka | 588800e02c | |
Sasha Koshka | 5205f01877 | |
Sasha Koshka | b0c3839ed9 | |
Sasha Koshka | a7fe9a592b | |
Sasha Koshka | ab8d616e2b | |
Sasha Koshka | b1cd82d4ed | |
Sasha Koshka | c0997b3e47 | |
Sasha Koshka | fa69bd2294 | |
Sasha Koshka | 5f30623e8f | |
Sasha Koshka | 65ec3c4ad0 | |
Sasha Koshka | 3b6aa7fffb | |
Sasha Koshka | 55a86ad64c | |
Sasha Koshka | 5539f8722f | |
Sasha Koshka | 67eb74c66e | |
Sasha Koshka | 9ccb1490bf | |
Sasha Koshka | ccd3ddd82a | |
Sasha Koshka | af4827590e | |
Sasha Koshka | 1c5a58aa23 | |
Sasha Koshka | f87a4a4b78 | |
Sasha Koshka | c24113ba51 | |
Sasha Koshka | 78d4276a57 | |
Sasha Koshka | a58b058025 | |
Sasha Koshka | 593683156a | |
Sasha Koshka | b96d9c6c4c | |
Sasha Koshka | 0b4aef1530 | |
Sasha Koshka | c5a61e15f6 | |
Sasha Koshka | 37819645cd | |
Sasha Koshka | 0afccbe6b1 | |
Sasha Koshka | ced43a161d | |
Sasha Koshka | c08f5d7706 | |
Sasha Koshka | 9808fc0e7c | |
Sasha Koshka | 905522af56 | |
Sasha Koshka | fddccf2967 | |
Sasha Koshka | aa107072d8 | |
Sasha Koshka | 3a751666ba | |
Sasha Koshka | f08a09cee2 | |
Sasha Koshka | ae9bace68a | |
Sasha Koshka | 36e70c0c97 | |
Sasha Koshka | 0a54cbdd63 | |
Sasha Koshka | 8258c4f400 | |
Sasha Koshka | 94bf21536d | |
Sasha Koshka | 15fece8154 | |
Sasha Koshka | a2d4252428 | |
Sasha Koshka | 0efc4d209e | |
Sasha Koshka | 0331b486bf | |
Sasha Koshka | ff9fdfeab0 | |
Sasha Koshka | 05e298583e | |
Sasha Koshka | 652a6c313b | |
Sasha Koshka | 729af04963 | |
Sasha Koshka | 13b18c0ce0 | |
Sasha Koshka | 3f96704723 | |
Sasha Koshka | 5e4d2a32e4 | |
Sasha Koshka | 9706e844d6 | |
Sasha Koshka | 162487490e | |
Sasha Koshka | b6a7b237ba | |
Sasha Koshka | 58513e7d15 | |
Sasha Koshka | 1a09e9ba2a | |
Sasha Koshka | b839517e9a | |
Sasha Koshka | 45574d3cd5 | |
Sasha Koshka | 8ce12b8a71 | |
Sasha Koshka | 7b0d2d50bd | |
Sasha Koshka | 65a32c3cdb | |
Sasha Koshka | e47096c219 | |
Sasha Koshka | b884978b87 | |
Sasha Koshka | 9e85f869d5 | |
Sasha Koshka | 7df9517788 | |
Sasha Koshka | 8cb7e2d8b0 | |
Sasha Koshka | 8a5d08d7fc | |
Sasha Koshka | 24ae32c95a | |
Sasha Koshka | 7d8a1cd714 | |
Sasha Koshka | 61a04d51a4 | |
Sasha Koshka | fb5d17742e | |
Sasha Koshka | 2a5ffb12a1 | |
Sasha Koshka | bec91323b9 | |
Sasha Koshka | 5a48415bd5 | |
Sasha Koshka | 4448d3ccb4 | |
Sasha Koshka | 5d4977828e | |
Sasha Koshka | 84f76f8ba4 | |
Sasha Koshka | 13d281388a | |
Sasha Koshka | bed87812d5 | |
Sasha Koshka | 7c0e96777b | |
Sasha Koshka | 87dcc18a8d | |
Sasha Koshka | c375c18922 | |
Sasha Koshka | f65f9e042c | |
Sasha Koshka | 6ee91bee08 | |
Sasha Koshka | 416c6b1d59 | |
Sasha Koshka | 90796775d3 | |
Sasha Koshka | 8cc9b88126 | |
Sasha Koshka | 4b2158b1f0 | |
Sasha Koshka | fcf4eba79e | |
Sasha Koshka | feb0da1172 | |
Sasha Koshka | 47fb051caa | |
Sasha Koshka | 0113536b0d | |
Sasha Koshka | 160cbd16ad |
|
@ -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,119 @@
|
|||
# FSPL
|
||||
|
||||
<img src="assets/fspl.svg" width="128" alt="FSPL logo.">
|
||||
|
||||
Freestanding programming language: a high-ish-level language that has absolutely
|
||||
no need for any runtime support, designed to work well in scenarios where
|
||||
dragging along a language runtime is either not possible or simply unwanted.
|
||||
|
||||
This language is designed for:
|
||||
- Operating system development
|
||||
- Embedded software
|
||||
- Integrating cleanly into existing software
|
||||
|
||||
## Design Principles
|
||||
- Abstractions must happen at compile time unless absolutely necessary
|
||||
- Compiler must not generate any functions that the user does not write
|
||||
- Compiler must avoid generating logic that the user does not write
|
||||
|
||||
## Installation
|
||||
You can install the compiler by running:
|
||||
|
||||
`go install ./cmd/fsplc`
|
||||
|
||||
The `fsplc` program depends on the LLVM IR compiler (`llc`). If it is not found,
|
||||
it will attempt to use `clang` instead but with some features disabled. Please
|
||||
ensure either `llc` or `clang` are installed and accessible from your PATH
|
||||
before using this software.
|
||||
|
||||
## Usage
|
||||
The `fsplc` program may be used as follows:
|
||||
|
||||
`fsplc [ARGUMENT(S)...] FILE(S)...`
|
||||
|
||||
The program compiles all input files into one output. The output file type is
|
||||
determined by the filename extension of the output file:
|
||||
|
||||
| Extension | Type |
|
||||
| --------- | --------------- |
|
||||
| .s | Native assembly |
|
||||
| .o | Object file |
|
||||
| .ll | LLVM IR |
|
||||
|
||||
If no output file is specified, it will default to an object file with the name
|
||||
of the first input file.
|
||||
|
||||
Object files can be linked into an executable binary using the linker of your
|
||||
choice, or by using a C compiler such as `clang`:
|
||||
|
||||
`clang -o OUTPUT INPUT.o`
|
||||
|
||||
Using a C compiler will link the C standard library to your program, which may
|
||||
be useful for building normal user applications.
|
||||
|
||||
## Learning the language
|
||||
|
||||
At this time, there is no guided method of learning how to write FSPL code.
|
||||
However, a good place to start is the `design` directory, which contains a
|
||||
language specification among other things. The language specification goes into
|
||||
detail about the syntax and semantics of the language, and assuming some
|
||||
background in C programming, it should be enough to attain a reasonable grasp
|
||||
of the language.
|
||||
|
||||
## Caveats, Bugs
|
||||
Note that the compiler is still relatively early in development, and has
|
||||
numerous bugs. In addition, language features and syntax are not yet set in
|
||||
stone and may change in the future. Please report any bugs you find to the
|
||||
[issue tracker](https://git.tebibyte.media/sashakoshka/fspl/issues).
|
||||
|
||||
## Roadmap
|
||||
|
||||
Late 2023 (These have been implemented):
|
||||
- Top-level entities
|
||||
- Type definitions
|
||||
- Methods
|
||||
- Defined and external functions
|
||||
- Type system
|
||||
- Strict, static, bottom-up type inference
|
||||
- Pointers
|
||||
- Arrays
|
||||
- Slices
|
||||
- Structs
|
||||
- Interfaces
|
||||
- Expressions and control structures
|
||||
- Literals adapt to types via bottom-up type inference
|
||||
- Assignment
|
||||
- Variable declaration
|
||||
- Variable access
|
||||
- Function calls
|
||||
- Method calls
|
||||
- Interface behavior calls
|
||||
- Operations
|
||||
- Casting
|
||||
- Blocks
|
||||
- If/else
|
||||
- Loops
|
||||
|
||||
Q1 2024:
|
||||
- Union types (carry type information)
|
||||
- Match statements
|
||||
- Modules
|
||||
- Mutable/immutable variables
|
||||
- For/range loops
|
||||
|
||||
Q2 2024:
|
||||
- Basic, non-final standard library routines
|
||||
- Conditional compilation
|
||||
- Shared library compilation
|
||||
- Constants
|
||||
- Vararg
|
||||
- FSPL vararg using Slices
|
||||
- Optional per-function C-style vararg for compatibility
|
||||
|
||||
Q3 2024:
|
||||
- Generics
|
||||
- Ownership system
|
||||
- Lightweight, modularized (and of course, totally optional) standard library to replace those written in Q2
|
||||
|
||||
At the beginning of Q4 2024, a 1.0 version of the language will be released.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# analyzer
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Define syntax tree type that contains entities
|
||||
- Turn streams of tokens into abstract syntax tree entities
|
||||
|
||||
## Organization
|
||||
|
||||
The entry point for all logic defined in this package is the Tree type. On this
|
||||
type, the Analyze() method is defined. This method checks the semantic
|
||||
correctness of an AST, fills in semantic fields within its data structures, and
|
||||
arranges them into the Tree.
|
||||
|
||||
Tree contains a scopeContextManager. The job of scopeContextManager is to manage
|
||||
a stack of scopeContexts, which are each tied to a function or method that is
|
||||
currently being analyzed. In turn, each scopeContext manages stacks of
|
||||
entity.Scopes and entity.Loops. This allows for greedy/recursive analysis of
|
||||
functions and methods.
|
||||
|
||||
## Operation
|
||||
|
||||
When the analyze method is called, several hidden fields in the Tree are filled
|
||||
out. Tree.ensure() instantiates data that can persist between analyses, which
|
||||
consists of map initialization and merging the data in the builtinTypes map into
|
||||
Tree.Types.
|
||||
|
||||
After Tree.ensure completes, Tree.assembleRawMaps() takes top-level entities
|
||||
from the AST and organizes them into rawTypes, rawFunctions, and rawMethods. It
|
||||
does this so that top-level entites can be indexed by name. While doing this, it
|
||||
ensures that function and type names are unique, and method names are unique
|
||||
within the type they are defined on.
|
||||
|
||||
Next, Tree.analyzeDeclarations() is called. This is the entry point for the
|
||||
actual analysis logic. For each item in the raw top-level entity maps, it calls
|
||||
a specific analysis routine, which is one of:
|
||||
|
||||
- Tree.analyzeTypedef()
|
||||
- Tree.analyzeFunction()
|
||||
- Tree.analyzeMethod()
|
||||
|
||||
These routines all have two crucial properties that make them very useful:
|
||||
|
||||
- They refer to top-level entities by name instead of by memory location
|
||||
- If the entity has already been analyzed, they return that entity instead of
|
||||
analyzing it again
|
||||
|
||||
Because of this, they are also used as accessors for top level entities within
|
||||
more specific analysis routines. For example, the routine Tree.analyzeCall()
|
||||
will call Tree.analyzeFunction() in order to get information about the function
|
||||
that is being called. If the function has not yet been analyzed, it is analyzed
|
||||
(making use of scopeContextManager to push a new scopeContext), and other
|
||||
routines (including Tree.analyzeDeclarations()) will not have to analyze it all
|
||||
over agian. After a top-level entity has been analyzed, these routines will
|
||||
always return the same pointer to the one instance of the analyzed entity.
|
||||
|
||||
## Expression Analysis and Assignment
|
||||
|
||||
Since expressions make up the bulk of FSPL, expression analysis makes up the
|
||||
bulk of the semantic analyzer. Whenever an expression needs to be analyzed,
|
||||
Tree.analyzeExpression() is called. This activates a switch to call one of many
|
||||
specialized analysis routines based on the expression entity's concrete type.
|
||||
|
||||
Much of expression analysis consists of the analyze checking to see if the
|
||||
result of one expression can be assigned to the input of another. To this end,
|
||||
assignment rules are used. There are five different assignment modes:
|
||||
|
||||
- Strict: Structural equivalence, but named types are treated as opaque and are
|
||||
not tested. This applies to the root of the type, and to types enclosed as
|
||||
members, elements, etc. This is the assignment mode most often used.
|
||||
- Weak: Like strict, but the root types specifically are compared as if they
|
||||
were not named. analyzer.ReduceToBase() is used to accomplish this.
|
||||
- Structural: Full structural equivalence, and named types are always reduced.
|
||||
- Coerce: Data of the source type must be convert-able to the destination type.
|
||||
This is used in value casts.
|
||||
- Force: All assignment rules are ignored. This is only used in bit casts.
|
||||
|
||||
|
||||
All expression analysis routines take in as a parameter the type that the result
|
||||
expression is being assigned to, and the assignment mode. To figure out whether
|
||||
or not they can be assigned, they in turn (usually) call Tree.canAssign().
|
||||
Tree.canAssign() is used to determine whether data of a source type can be
|
||||
assigned to a destination type, given an assignment mode. However, it is not
|
||||
called automatically by Tree.analyzeExpression() because:
|
||||
|
||||
- Determining the source type is sometimes non-trivial (see
|
||||
Tree.analyzeOperation())
|
||||
- Literals have their own very weak assignment rules, and are designed to be
|
||||
assignable to a wide range of data types
|
|
@ -1,320 +1,500 @@
|
|||
package analyzer
|
||||
|
||||
import "testing"
|
||||
import "fmt"
|
||||
import "math"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/integer"
|
||||
|
||||
func TestAssignLiteralErrUnsignedNegative (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected unsigned integer", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:UInt = -5
|
||||
}
|
||||
`)
|
||||
type strictness int; const (
|
||||
// Structural equivalence, but named types are treated as opaque and are
|
||||
// not tested. This applies to the root of the type, and to types
|
||||
// enclosed as members, elements, etc. This is the assignment mode most
|
||||
// often used.
|
||||
strict strictness = iota
|
||||
// Like strict, but the root types specifically are compared as if they
|
||||
// were not named. analyzer.ReduceToBase() is used to accomplish this.
|
||||
// Assignment to private/restricted types is not allowed.
|
||||
weak
|
||||
// Full structural equivalence, and named types are always reduced.
|
||||
// Assignment to private/restricted types is not allowed.
|
||||
structural
|
||||
// Data of the source type must be convert-able to the destination type.
|
||||
// This is used in value casts. Assignment to private/restricted types
|
||||
// is not allowed.
|
||||
coerce
|
||||
// All assignment rules are ignored. This is only used in bit casts.
|
||||
force
|
||||
)
|
||||
|
||||
// canAssign takes in an analyzed destination type and an analyzed source type,
|
||||
// and determines whether source can be assigned to destination.
|
||||
func (this *Tree) canAssign (
|
||||
pos errors.Position,
|
||||
// assuming both are analyzed already
|
||||
destination entity.Type,
|
||||
mode strictness,
|
||||
source entity.Type,
|
||||
) error {
|
||||
fail := func () error {
|
||||
switch mode {
|
||||
case strict:
|
||||
return errors.Errorf (
|
||||
pos, "expected %v",
|
||||
entity.FormatType(destination))
|
||||
case weak:
|
||||
return errors.Errorf (
|
||||
pos, "cannot use %v as %v",
|
||||
entity.FormatType(source),
|
||||
entity.FormatType(destination))
|
||||
case structural:
|
||||
return errors.Errorf (
|
||||
pos, "cannot convert from %v to %v",
|
||||
entity.FormatType(source),
|
||||
entity.FormatType(destination))
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: analyzer doesnt know about mode", mode))
|
||||
}
|
||||
}
|
||||
|
||||
// check access permissions
|
||||
if destination != nil && source != nil &&
|
||||
mode != strict && mode != force {
|
||||
|
||||
err := this.typeRestricted(pos, destination)
|
||||
if err != nil { return err }
|
||||
err = this.typeRestricted(pos, source)
|
||||
if err != nil { return err }
|
||||
}
|
||||
|
||||
// short circuit if force is selected
|
||||
if mode == force { return nil }
|
||||
|
||||
// check for coerce strictness
|
||||
if mode == coerce {
|
||||
return this.canAssignCoerce(pos, destination, source)
|
||||
}
|
||||
|
||||
// do structural equivalence if structural is selected
|
||||
if mode == structural {
|
||||
if this.areStructurallyEquivalent(destination, source) {
|
||||
return nil
|
||||
} else {
|
||||
return fail()
|
||||
}
|
||||
}
|
||||
|
||||
// first, check if this is interface assignment
|
||||
if destination, ok := this.isInterface(destination); ok {
|
||||
return this.canAssignInterface(pos, destination, source)
|
||||
}
|
||||
|
||||
// then, check for array to slice assignment
|
||||
if destination, ok := ReduceToBase(destination).(*entity.TypeSlice); ok {
|
||||
if source, ok := ReduceToBase(source). (*entity.TypeArray); ok {
|
||||
return this.canAssignSliceArray(pos, destination, source)
|
||||
}}
|
||||
|
||||
// if weak, only compare the first base types
|
||||
if mode == weak {
|
||||
destination = ReduceToBase(destination)
|
||||
source = ReduceToBase(source)
|
||||
}
|
||||
|
||||
switch destination := destination.(type) {
|
||||
// no type
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
// named type
|
||||
case *entity.TypeNamed:
|
||||
if source, ok := source.(*entity.TypeNamed); ok {
|
||||
if destination.Name == source.Name {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// composite base types
|
||||
case *entity.TypePointer:
|
||||
if source, ok := source.(*entity.TypePointer); ok {
|
||||
mode := mode
|
||||
if mode > structural { mode = structural }
|
||||
return this.canAssign (
|
||||
pos, destination.Referenced,
|
||||
mode, source.Referenced)
|
||||
}
|
||||
|
||||
case *entity.TypeSlice:
|
||||
if source, ok := source.(*entity.TypeSlice); ok {
|
||||
mode := mode
|
||||
if mode > structural { mode = structural }
|
||||
return this.canAssign (
|
||||
pos, destination.Element,
|
||||
mode, source.Element)
|
||||
}
|
||||
|
||||
case *entity.TypeArray:
|
||||
if source, ok := source.(*entity.TypeArray); ok {
|
||||
mode := mode
|
||||
if mode > structural { mode = structural }
|
||||
return this.canAssign (
|
||||
pos, destination.Element,
|
||||
mode, source.Element)
|
||||
}
|
||||
|
||||
// other base type
|
||||
case *entity.TypeStruct,
|
||||
*entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
|
||||
if destination.Equals(source) {
|
||||
return nil
|
||||
}
|
||||
|
||||
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", destination))
|
||||
}
|
||||
|
||||
return fail()
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = 5.5
|
||||
}
|
||||
`)
|
||||
// canAssignCoerce determines if data of an analyzed source type can be
|
||||
// converted into data of an analyzed destination type.
|
||||
func (this *Tree) canAssignCoerce (
|
||||
pos errors.Position,
|
||||
destination entity.Type,
|
||||
source entity.Type,
|
||||
) error {
|
||||
fail := func () error {
|
||||
return errors.Errorf (
|
||||
pos, "cannot convert from %v to %v",
|
||||
entity.FormatType(source),
|
||||
entity.FormatType(destination))
|
||||
}
|
||||
|
||||
destination = ReduceToBase(destination)
|
||||
source = ReduceToBase(source)
|
||||
|
||||
switch destination := destination.(type) {
|
||||
// no type
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
// base type
|
||||
case *entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
switch source.(type) {
|
||||
case *entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
// integers, floats, and words may all be assigned to
|
||||
// eachother
|
||||
return nil
|
||||
default:
|
||||
return fail()
|
||||
}
|
||||
|
||||
case *entity.TypePointer:
|
||||
switch source := source.(type) {
|
||||
case *entity.TypeSlice:
|
||||
// a slice may be assigned to a pointer if its element
|
||||
// type can be assigned (structural) to the pointer's
|
||||
// referenced type
|
||||
err := this.canAssign (
|
||||
pos, destination.Referenced,
|
||||
structural, source.Element)
|
||||
if err != nil {
|
||||
return fail()
|
||||
}
|
||||
default:
|
||||
// a pointer may be assigned to another pointer if they
|
||||
// are structurally equivalent
|
||||
if !this.areStructurallyEquivalent(destination, source) {
|
||||
return fail()
|
||||
}
|
||||
}
|
||||
|
||||
case *entity.TypeSlice,
|
||||
*entity.TypeArray,
|
||||
*entity.TypeStruct:
|
||||
// composite types may be assigned to eachother if they are
|
||||
// structurally equivalent
|
||||
if !this.areStructurallyEquivalent(destination, source) {
|
||||
return fail()
|
||||
}
|
||||
|
||||
default: fail()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (x: 5)
|
||||
}
|
||||
`)
|
||||
// canAssignSliceArray takes in an analyzed slice type and an analyzed array
|
||||
// type, and determines whether the array can be assigned to the slice.
|
||||
func (this *Tree) canAssignSliceArray (
|
||||
pos errors.Position,
|
||||
destination *entity.TypeSlice,
|
||||
source *entity.TypeArray,
|
||||
) error {
|
||||
err := this.canAssign(pos, destination.Element, strict, source.Element)
|
||||
if err != nil {
|
||||
return errors.Errorf (
|
||||
pos, "expected %v",
|
||||
entity.FormatType(destination))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (* 5)
|
||||
}
|
||||
`)
|
||||
// canAssignInterface takes in an analyzed interface destination type and an
|
||||
// analyzed source type, and determines whether source can be assigned to
|
||||
// destination.
|
||||
func (this *Tree) canAssignInterface (
|
||||
pos errors.Position,
|
||||
destination *entity.TypeInterface,
|
||||
source entity.Type,
|
||||
) error {
|
||||
for name, behavior := range destination.BehaviorMap {
|
||||
// get method
|
||||
method, err := this.analyzeMethodOrBehavior (
|
||||
pos, source, name)
|
||||
if err != nil { return err }
|
||||
|
||||
// extract signature
|
||||
var signature *entity.Signature
|
||||
switch method.(type) {
|
||||
case *entity.Signature:
|
||||
signature = method.(*entity.Signature)
|
||||
case *entity.Method:
|
||||
signature = method.(*entity.Method).Signature
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"Tree.analyzeMethodOrBehavior returned",
|
||||
method))
|
||||
}
|
||||
|
||||
// check equivalence
|
||||
if !signature.Equals(behavior) {
|
||||
return errors.Errorf (
|
||||
pos, "%v has wrong signature for method %v",
|
||||
source, name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrIntegerOverflow (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected smaller number", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:Byte = 1000
|
||||
}
|
||||
`)
|
||||
// areStructurallyEquivalent tests whether two types are structurally equivalent
|
||||
// to eachother.
|
||||
func (this *Tree) areStructurallyEquivalent (left, right entity.Type) bool {
|
||||
left = ReduceToBase(left)
|
||||
right = ReduceToBase(right)
|
||||
|
||||
switch left.(type) {
|
||||
case nil: return false
|
||||
|
||||
// composite
|
||||
case *entity.TypePointer:
|
||||
left := left.(*entity.TypePointer)
|
||||
right, ok := right.(*entity.TypePointer)
|
||||
if !ok { return false }
|
||||
return this.areStructurallyEquivalent(left.Referenced, right.Referenced)
|
||||
|
||||
case *entity.TypeSlice:
|
||||
left := left.(*entity.TypeSlice)
|
||||
right, ok := right.(*entity.TypeSlice)
|
||||
if !ok { return false }
|
||||
return this.areStructurallyEquivalent(left.Element, right.Element)
|
||||
|
||||
case *entity.TypeArray:
|
||||
left := left.(*entity.TypeArray)
|
||||
right, ok := right.(*entity.TypeArray)
|
||||
if !ok { return false }
|
||||
return left.Length == right.Length &&
|
||||
this.areStructurallyEquivalent(left.Element, right.Element)
|
||||
|
||||
case *entity.TypeStruct:
|
||||
left := left.(*entity.TypeStruct)
|
||||
right, ok := right.(*entity.TypeStruct)
|
||||
if !ok { return false }
|
||||
if len(left.MemberMap) != len(right.MemberMap) { return false }
|
||||
for name, member := range left.MemberMap {
|
||||
other, ok := right.MemberMap[name]
|
||||
if !ok { return false }
|
||||
if !this.areStructurallyEquivalent(member.Type(), other.Type()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// terminals
|
||||
case *entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
return left.Equals(right)
|
||||
|
||||
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", left))
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected array", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = 5
|
||||
}
|
||||
`)
|
||||
// isLocationExpression returns whether or not an expression is a valid location
|
||||
// expression.
|
||||
func (this *Tree) isLocationExpression (expression entity.Expression) error {
|
||||
cannot := func (pos errors.Position, kind string) error {
|
||||
return errors.Errorf(pos, "cannot assign to %s", kind)
|
||||
}
|
||||
switch expression := expression.(type) {
|
||||
case *entity.Variable:
|
||||
return nil
|
||||
case *entity.Declaration:
|
||||
return nil
|
||||
case *entity.Call:
|
||||
return cannot(expression.Position, "function call")
|
||||
case *entity.MethodCall:
|
||||
return cannot(expression.Position, "method call")
|
||||
case *entity.Subscript:
|
||||
return this.isLocationExpression(expression.Slice)
|
||||
case *entity.Slice:
|
||||
return cannot(expression.Position, "slice operation")
|
||||
case *entity.Dereference:
|
||||
return this.isLocationExpression(expression.Pointer)
|
||||
case *entity.Reference:
|
||||
return cannot(expression.Position, "reference operation")
|
||||
case *entity.ValueCast:
|
||||
return cannot(expression.Position, "value cast")
|
||||
case *entity.BitCast:
|
||||
return cannot(expression.Position, "bit cast")
|
||||
case *entity.Operation:
|
||||
return cannot(expression.Position, fmt.Sprintf (
|
||||
"cannot assign to %v operation",
|
||||
expression.Operator))
|
||||
case *entity.Block:
|
||||
return cannot(expression.Position, "block")
|
||||
case *entity.MemberAccess:
|
||||
return this.isLocationExpression (
|
||||
expression.Source)
|
||||
case *entity.IfElse:
|
||||
return cannot(expression.Position, "if/else")
|
||||
case *entity.Loop:
|
||||
return cannot(expression.Position, "loop")
|
||||
case *entity.Break:
|
||||
return cannot(expression.Position, "break statement")
|
||||
case *entity.Return:
|
||||
return cannot(expression.Position, "return statement")
|
||||
case *entity.LiteralInt:
|
||||
return cannot(expression.Position, "integer literal")
|
||||
case *entity.LiteralFloat:
|
||||
return cannot(expression.Position, "float literal")
|
||||
case *entity.LiteralString:
|
||||
return cannot(expression.Position, "string literal")
|
||||
case *entity.LiteralArray:
|
||||
return cannot(expression.Position, "array literal")
|
||||
case *entity.LiteralStruct:
|
||||
return cannot(expression.Position, "struct literal")
|
||||
case *entity.LiteralBoolean:
|
||||
return cannot(expression.Position, "boolean literal")
|
||||
case *entity.LiteralNil:
|
||||
return cannot(expression.Position, "nil literal")
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: analyzer doesnt know about expression",
|
||||
expression))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrArrayWrongLength (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected 3 elements", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
// ReduceToBase takes in an analyzed type and reduces it to its first non-name
|
||||
// type.
|
||||
func ReduceToBase (ty entity.Type) entity.Type {
|
||||
if namedty, ok := ty.(*entity.TypeNamed); ok {
|
||||
return ReduceToBase(namedty.Type)
|
||||
} else {
|
||||
return ty
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrArrayWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 19,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3.3 4)
|
||||
}
|
||||
`)
|
||||
// isInterface takes in an analyzed type and returns true and an interface if
|
||||
// that type refers to an interface. If not, it returns nil, false.
|
||||
func (this *Tree) isInterface (ty entity.Type) (*entity.TypeInterface, bool) {
|
||||
ty = ReduceToBase(ty)
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInterface: return ty.(*entity.TypeInterface), true
|
||||
default: return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrSliceWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 19,
|
||||
`
|
||||
[main] = {
|
||||
x:*:Int = (* 1 2 3.3 4)
|
||||
}
|
||||
`)
|
||||
// isNumeric returns whether or not the specified type is a number.
|
||||
func isNumeric (ty entity.Type) bool {
|
||||
ty = ReduceToBase(ty)
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeFloat, *entity.TypeWord: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrStructWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected integer", 3, 26,
|
||||
`
|
||||
[main] = {
|
||||
x:3:(x:Int y:Int) = (x: 5.5 y: 3)
|
||||
}
|
||||
`)
|
||||
}
|
||||
func TestAssignLiteralErrStructInteger (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected struct", 3, 26,
|
||||
`
|
||||
[main] = {
|
||||
x:3:(x:Int y:Int) = 5
|
||||
}
|
||||
`)
|
||||
// isInteger returns whether or not the specified type is an integer.
|
||||
func isInteger (ty entity.Type) bool {
|
||||
switch ReduceToBase(ty).(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5
|
||||
}
|
||||
`)
|
||||
// isOrdered returns whether or not the values of the specified type can be
|
||||
// ordered.
|
||||
func isOrdered (ty entity.Type) bool {
|
||||
return isNumeric(ty)
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5.5
|
||||
}
|
||||
`)
|
||||
// isBoolean returns whether or not the specified type is a boolean.
|
||||
func isBoolean (ty entity.Type) bool {
|
||||
for {
|
||||
named, ok := ty.(*entity.TypeNamed)
|
||||
if !ok { return false }
|
||||
if named.Name == "Bool" { return true }
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
// isFloat returns whether or not the specified type is a float.
|
||||
func isFloat (ty entity.Type) bool {
|
||||
switch ReduceToBase(ty).(type) {
|
||||
case *entity.TypeFloat: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteralErrInterfaceStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot assign literal to interface", 6, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[land] = { }
|
||||
[main] = {
|
||||
b:Bird = (x: 5 y: 6)
|
||||
}
|
||||
`)
|
||||
// IsUnsigned returns whether or not the specified type is an unsigned integer.
|
||||
func IsUnsigned (ty entity.Type) bool {
|
||||
switch ty := ReduceToBase(ty).(type) {
|
||||
case *entity.TypeInt: return !ty.Signed
|
||||
case *entity.TypeWord: return !ty.Signed
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignLiteral (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
a:F64 = 5.3
|
||||
b:F32 = 5.3
|
||||
c:F64 = -5
|
||||
d:F32 = -5
|
||||
e:Byte = 64
|
||||
z:UInt = 5
|
||||
x:Int = -5
|
||||
arr:4:2:Int = (*
|
||||
(* 1 2)
|
||||
(* 3 4)
|
||||
(* 5 6)
|
||||
(* 7 8))
|
||||
slice:*:Int = (* 3 1 2 3)
|
||||
struct:(x:Int y:Int) = (
|
||||
x: 9
|
||||
y: 10)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignInterfaceErrBadSignature (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"BlueJay has wrong signature for method fly", 7, 2,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[fly] = { }
|
||||
BlueJay::[land] = { }
|
||||
[main] = {
|
||||
b:Bird = [@a:BlueJay]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignInterface (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[fly distance:F64] = { }
|
||||
BlueJay::[fly land] = { }
|
||||
[main] = {
|
||||
a:BlueJay
|
||||
b:Bird = [@a]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntPointer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from *Int to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ [@ a:Int] Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from (x:Int y:Int) to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ a:(x:Int y:Int) Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from 5:Int to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ a:5:Int Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrIntSlice (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from *:Int to Int", 2, 14,
|
||||
`
|
||||
[main]:Int = [~ a:*:Int Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrPointerInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to *Int", 2, 15,
|
||||
`
|
||||
[main]:*Int = [~ [@ a:Int] *Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrStructInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to (x:Int y:Int)", 2, 24,
|
||||
`
|
||||
[main]:(x:Int y:Int) = [~ a:Int (x:Int y:Int)]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to 5:Int", 2, 13,
|
||||
`
|
||||
[main]:Int = [~ a:5:Int Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCastErrSliceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to *:Int", 2, 16,
|
||||
`
|
||||
[main]:*:Int = [~ a:Int *:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCast (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay::[fly distance:F64] = { }
|
||||
BlueJay::[fly land] = { }
|
||||
IntDerived: Int
|
||||
[main] = {
|
||||
a:IntDerived = 5
|
||||
b:Int [~ [~ [~ a Byte] F64] Int]
|
||||
c:Int [~~ [~~ [~~ a Byte] F64] Int]
|
||||
d:(x:Int y:Int) = (x: 1 y: 2)
|
||||
e:(z:Int a:Int) = [~~ d (z:Int a:Int)]
|
||||
f:Bird = [@ [~~ 0 BlueJay]]
|
||||
g:*:Int = (~ h:5:Int *:int)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// TODO: complete and test error cases
|
||||
func TestPropagateAssignRules (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f]:Byte = 5
|
||||
A:Int
|
||||
A::[g]:Int = 2
|
||||
B:([g]:Int)
|
||||
[main] = {
|
||||
a:Int
|
||||
b:Int = a
|
||||
c:Int = d:Int
|
||||
d:Int = { a:F64 b }
|
||||
e:Byte = [f]
|
||||
g:(x:Int y:(w:F64 z:F64)) = (x: 1 y: (w: 1.2 z: 78.5))
|
||||
h:F64 = g.x.z
|
||||
i:A
|
||||
j:Int = [i::g]
|
||||
k:B = i
|
||||
l:Int = [k::g]
|
||||
m:5:Int = (* 0 1 2 3 4)
|
||||
n:Int = [. m 3]
|
||||
}
|
||||
`)
|
||||
// inRange returns whether the specified value can fit within the given integer
|
||||
// type.
|
||||
func inRange (ty entity.Type, value int64) bool {
|
||||
base := ReduceToBase(ty)
|
||||
switch base.(type) {
|
||||
case *entity.TypeInt:
|
||||
base := base.(*entity.TypeInt)
|
||||
if base.Signed {
|
||||
return value > integer.SignedMin(base.Width) &&
|
||||
value < integer.SignedMax(base.Width)
|
||||
} else {
|
||||
return value >= 0 &&
|
||||
uint64(value) < integer.UnsignedMax(base.Width)
|
||||
}
|
||||
case *entity.TypeWord:
|
||||
base := base.(*entity.TypeWord)
|
||||
if base.Signed {
|
||||
return value > math.MinInt && value < math.MaxInt
|
||||
} else {
|
||||
return value >= 0 && uint64(value) < math.MaxUint
|
||||
}
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
package analyzer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAssignmentLiteralErrUnsignedNegative (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"integer literal out of range for type UInt", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:UInt = -5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = 5.5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use struct literal as Int", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (. x: 5)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use array literal as Int", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (* 5)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrIntegerOverflow (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"integer literal out of range for type Byte", 3, 11,
|
||||
`
|
||||
[main] = {
|
||||
x:Byte = 1000
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrArrayInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as 3:Int", 3, 12,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrArrayWrongLength (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected 3 elements or less", 3, 12,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrArrayWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 17,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 3.3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrSliceWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 17,
|
||||
`
|
||||
[main] = {
|
||||
x:*:Int = (* 1 3.3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrStructWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 28,
|
||||
`
|
||||
[main] = {
|
||||
x:(. x:Int y:Int) = (. x: 5.5 y: 3)
|
||||
}
|
||||
`)
|
||||
}
|
||||
func TestAssignmentLiteralErrStructInteger (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as (. x:Int y:Int)", 3, 22,
|
||||
`
|
||||
[main] = {
|
||||
x:(. x:Int y:Int) = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5.5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use array literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (* 1 2 3 4)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrInterfaceStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use struct literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (. x: 5 y: 6)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteralErrDerefByteFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Byte", 4, 17,
|
||||
`
|
||||
[main] = {
|
||||
buffer:8:Byte
|
||||
[. buffer 7] = 0.0
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentLiteral (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
a:F64 = 5.3
|
||||
b:F32 = 5.3
|
||||
c:F64 = -5
|
||||
d:F32 = -5
|
||||
e:Byte = 64
|
||||
z:UInt = 5
|
||||
x:Int = -5
|
||||
arr:4:2:Int = (*
|
||||
(* 1 2)
|
||||
(* 3 4)
|
||||
(* 5 6)
|
||||
(* 7 8))
|
||||
slice:*:Int = (* 3 1 2 3)
|
||||
struct:(. x:Int y:Int) = (.
|
||||
x: 9
|
||||
y: 10)
|
||||
|
||||
[.[.arr 1] 0] = 6
|
||||
[.slice 4] = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterfaceErrBadSignature (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"BlueJay has wrong signature for method fly", 7, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly] = { }
|
||||
BlueJay.[land] = { }
|
||||
[main] = {
|
||||
b:Bird = a:BlueJay
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterfaceErrMissingMethod (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named fly defined on this type", 6, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[land] = { }
|
||||
[main] = {
|
||||
b:Bird = a:BlueJay
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterfaceErrBadLayer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named fly defined on this type", 7, 11,
|
||||
`
|
||||
Bird: (~ [fly distance:F64])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJayRef: *BlueJay
|
||||
[main] = {
|
||||
b:Bird = a:BlueJayRef
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentInterface (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJay.[land] = { }
|
||||
[main] = {
|
||||
b:Bird = a:BlueJay
|
||||
ref:*Bird = [@ a]
|
||||
c:Bird = ref
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestAssignmentErrByteSliceString (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected *:Byte", 4, 13,
|
||||
`
|
||||
[main] = {
|
||||
a:String = 'hello'
|
||||
b:*:Byte = a
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// TODO: complete and test error cases
|
||||
func TestAssignmentPropagateRules (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[f]:Byte = 5
|
||||
A:Int
|
||||
A.[g]:Int = 2
|
||||
B:(~ [g]:Int)
|
||||
[main] = {
|
||||
a:Int
|
||||
b:Int = a
|
||||
c:Int = d:Int
|
||||
d = { a:F64 b }
|
||||
e:Byte = [f]
|
||||
g:(. x:Int y:(. w:F64 z:F64)) = (. x: 1 y: (. w: 1.2 z: 78.5))
|
||||
gx:(. w:F64 z:F64) = g.y
|
||||
h:F64 = gx.z
|
||||
i:A
|
||||
j:Int = i.[g]
|
||||
k:B = i
|
||||
l:Int = k.[g]
|
||||
m:5:Int = (* 0 1 2 3 4)
|
||||
n:Int = [. m 3]
|
||||
o:*:Int = m
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package analyzer
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
var primitiveTypes = map[string] entity.Type {
|
||||
"Int": &entity.TypeWord { Acc: entity.AccessPublic, Signed: true },
|
||||
"UInt": &entity.TypeWord { Acc: entity.AccessPublic, },
|
||||
"I8": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 8 },
|
||||
"I16": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 16 },
|
||||
"I32": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 32 },
|
||||
"I64": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 64 },
|
||||
"U8": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 },
|
||||
"U16": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 16 },
|
||||
"U32": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 32 },
|
||||
"U64": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 64 },
|
||||
"F32": &entity.TypeFloat { Acc: entity.AccessPublic, Width: 32 },
|
||||
"F64": &entity.TypeFloat { Acc: entity.AccessPublic, Width: 64 },
|
||||
}
|
||||
|
||||
var builtinTypes = map[string] entity.Type { }
|
||||
|
||||
func builtinType (name string) *entity.TypeNamed {
|
||||
ty, ok := builtinTypes[name]
|
||||
if !ok { panic("BUG: compiler tried to reference missing builtin " + name) }
|
||||
return &entity.TypeNamed {
|
||||
Name: name,
|
||||
Type: ty,
|
||||
Acc: entity.AccessPublic,
|
||||
}
|
||||
}
|
||||
|
||||
func primitiveType (name string) *entity.TypeNamed {
|
||||
ty, ok := primitiveTypes[name]
|
||||
if !ok { panic("BUG: compiler tried to reference missing primitive " + name) }
|
||||
return &entity.TypeNamed {
|
||||
Name: name,
|
||||
Type: ty,
|
||||
Acc: entity.AccessPublic,
|
||||
}
|
||||
}
|
||||
|
||||
func init () {
|
||||
builtinTypes["Index"] = &entity.TypeWord { Acc: entity.AccessPublic }
|
||||
builtinTypes["Byte"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
|
||||
builtinTypes["Bool"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
|
||||
builtinTypes["Rune"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 32 }
|
||||
builtinTypes["String"] = &entity.TypeSlice {
|
||||
Acc: entity.AccessPublic,
|
||||
Element: &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 },
|
||||
}
|
||||
}
|
|
@ -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,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,73 @@
|
|||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeExpression (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
expression entity.Expression,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
switch expression := expression.(type) {
|
||||
case *entity.Assignment:
|
||||
return this.analyzeAssignment(expression)
|
||||
case *entity.Variable:
|
||||
return this.analyzeVariable(into, mode, expression)
|
||||
case *entity.Declaration:
|
||||
return this.analyzeDeclaration(into, mode, expression)
|
||||
case *entity.Call:
|
||||
return this.analyzeCall(into, mode, expression)
|
||||
case *entity.MethodCall:
|
||||
return this.analyzeMethodCall(into, mode, expression)
|
||||
case *entity.Subscript:
|
||||
return this.analyzeSubscript(into, mode, expression)
|
||||
case *entity.Slice:
|
||||
return this.analyzeSlice(into, mode, expression)
|
||||
case *entity.Length:
|
||||
return this.analyzeLength(into, mode, expression)
|
||||
case *entity.Dereference:
|
||||
return this.analyzeDereference(into, mode, expression)
|
||||
case *entity.Reference:
|
||||
return this.analyzeReference(into, mode, expression)
|
||||
case *entity.ValueCast:
|
||||
return this.analyzeValueCast(into, mode, expression)
|
||||
case *entity.BitCast:
|
||||
return this.analyzeBitCast(into, mode, expression)
|
||||
case *entity.Operation:
|
||||
return this.analyzeOperation(into, mode, expression)
|
||||
case *entity.Block:
|
||||
return this.analyzeBlock(into, mode, expression)
|
||||
case *entity.MemberAccess:
|
||||
return this.analyzeMemberAccess(into, mode, expression)
|
||||
case *entity.IfElse:
|
||||
return this.analyzeIfElse(into, mode, expression)
|
||||
case *entity.Loop:
|
||||
return this.analyzeLoop(into, mode, expression)
|
||||
case *entity.Break:
|
||||
return this.analyzeBreak(into, mode, expression)
|
||||
case *entity.Return:
|
||||
return this.analyzeReturn(into, mode, expression)
|
||||
|
||||
case *entity.LiteralInt:
|
||||
return this.analyzeLiteralInt(into, mode, expression)
|
||||
case *entity.LiteralFloat:
|
||||
return this.analyzeLiteralFloat(into, mode, expression)
|
||||
case *entity.LiteralArray:
|
||||
return this.analyzeLiteralArray(into, mode, expression)
|
||||
case *entity.LiteralString:
|
||||
return this.analyzeLiteralString(into, mode, expression)
|
||||
case *entity.LiteralStruct:
|
||||
return this.analyzeLiteralStruct(into, mode, expression)
|
||||
case *entity.LiteralBoolean:
|
||||
return this.analyzeLiteralBoolean(into, mode, expression)
|
||||
case *entity.LiteralNil:
|
||||
return this.analyzeLiteralNil(into, mode, expression)
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: analyzer doesnt know about expression %v, ty: %T",
|
||||
expression, expression))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,805 @@
|
|||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
// All expression analysis routines must take in the type they are being
|
||||
// assigned to and return an error if they can't be assigned to it. if the type
|
||||
// is nil, the expression must ignore it unless it can do upwards type
|
||||
// inference.
|
||||
|
||||
func (this *Tree) analyzeAssignment (
|
||||
assignment *entity.Assignment,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// analyze location
|
||||
location, err := this.analyzeExpression(nil, strict, assignment.Location)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Location = location
|
||||
|
||||
// ensure location is location expression
|
||||
err = this.isLocationExpression(location)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// analyze value
|
||||
value, err := this.analyzeExpression(location.Type(), strict, assignment.Value)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Value = value
|
||||
|
||||
return assignment, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeVariable (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
variable *entity.Variable,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
declaration := this.variable(variable.Name)
|
||||
if declaration == nil {
|
||||
return nil, errors.Errorf (
|
||||
variable.Position, "no variable named %s",
|
||||
variable.Name)
|
||||
}
|
||||
|
||||
err := this.canAssign(variable.Position, into, mode, declaration.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
variable.Declaration = declaration
|
||||
return variable, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDeclaration (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
declaration *entity.Declaration,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
scope, _ := this.topScope()
|
||||
existing := scope.Variable(declaration.Name)
|
||||
if existing != nil {
|
||||
return nil, errors.Errorf (
|
||||
declaration.Position,
|
||||
"%s already declared in block at %v",
|
||||
declaration.Name, existing.Position)
|
||||
}
|
||||
|
||||
ty, err := this.analyzeType(declaration.Ty, false)
|
||||
declaration.Ty = ty
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.canAssign(declaration.Position, into, mode, declaration.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
this.addVariable(declaration)
|
||||
return declaration, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeCall (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
call *entity.Call,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// get function
|
||||
unit, err := this.resolveNickname(call.Position, call.UnitNickname)
|
||||
if err != nil { return nil, err }
|
||||
function, err := this.analyzeFunction(call.Position, entity.Key {
|
||||
Unit: unit,
|
||||
Name: call.Name,
|
||||
})
|
||||
if err != nil { return nil, err }
|
||||
call.Function = function
|
||||
call.Unit = function.Unit
|
||||
|
||||
// check access permissions
|
||||
if function.Acc == entity.AccessPrivate && function.Unit != this.unit {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "function %v::[%v] is private",
|
||||
call.UnitNickname, call.Name)
|
||||
}
|
||||
|
||||
// check return result
|
||||
err = this.canAssign(call.Position, into, mode, function.Signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// check arg count
|
||||
if len(call.Arguments) > len(function.Signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too many arguments in call to %s",
|
||||
call.Name)
|
||||
} else if len(call.Arguments) < len(function.Signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too few arguments in call to %s",
|
||||
call.Name)
|
||||
}
|
||||
|
||||
// check arg types
|
||||
for index, argument := range call.Arguments {
|
||||
signature := function.Signature
|
||||
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
|
||||
argument, err := this.analyzeExpression(correct.Type(), strict, argument)
|
||||
if err != nil { return nil, err }
|
||||
call.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return call, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeMethodCall (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
call *entity.MethodCall,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// get method
|
||||
source, err := this.analyzeExpression(nil, strict, call.Source)
|
||||
if err != nil { return nil, err }
|
||||
method, err := this.analyzeMethodOrBehavior (
|
||||
call.Position, source.Type(), call.Name)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// extract signature
|
||||
var signature *entity.Signature
|
||||
switch method := method.(type) {
|
||||
case *entity.Signature:
|
||||
signature = method
|
||||
call.Behavior = signature
|
||||
|
||||
// since this is a behavior, check access permissions of the
|
||||
// interface
|
||||
err = this.typeRestricted(call.Position, source.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
case *entity.Method:
|
||||
signature = method.Signature
|
||||
call.Method = method
|
||||
|
||||
// since this is a method, check access permissions of the
|
||||
// method
|
||||
if method.Acc == entity.AccessPrivate && method.Unit != this.unit {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "method %v.[%v] is private",
|
||||
method.TypeName, method.Signature.Name)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"Tree.analyzeMethodOrBehavior returned ",
|
||||
method))
|
||||
}
|
||||
|
||||
// check return result
|
||||
err = this.canAssign(call.Position, into, mode, signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// check arg count
|
||||
if len(call.Arguments) > len(signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too many arguments in call to %s",
|
||||
call.Name)
|
||||
} else if len(call.Arguments) < len(signature.Arguments) {
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too few arguments in call to %s",
|
||||
call.Name)
|
||||
}
|
||||
|
||||
// check arg types
|
||||
for index, argument := range call.Arguments {
|
||||
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
|
||||
argument, err := this.analyzeExpression(correct.Type(), strict, argument)
|
||||
if err != nil { return nil, err }
|
||||
call.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return call, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeSubscript (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
subscript *entity.Subscript,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
slice, err := this.analyzeExpression (
|
||||
&entity.TypeSlice {
|
||||
Position: subscript.Position,
|
||||
Element: into,
|
||||
}, weak,
|
||||
subscript.Slice)
|
||||
if err != nil { return nil, err }
|
||||
subscript.Slice = slice
|
||||
subscript.Ty = into
|
||||
|
||||
// check permissions
|
||||
err = this.typeRestricted(subscript.Position, slice.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
offset, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
subscript.Offset)
|
||||
if err != nil { return nil, err }
|
||||
subscript.Offset = offset
|
||||
|
||||
var frailType bool
|
||||
switch into := into.(type) {
|
||||
case nil: frailType = true
|
||||
case *entity.TypeSlice: frailType = into.Element == nil
|
||||
}
|
||||
|
||||
if frailType {
|
||||
ty := ReduceToBase(subscript.Slice.Type())
|
||||
switch ty := ty.(type) {
|
||||
case *entity.TypeSlice: subscript.Ty = ty.Element
|
||||
case *entity.TypeArray: subscript.Ty = ty.Element
|
||||
}
|
||||
}
|
||||
|
||||
return subscript, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeSlice (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
slice *entity.Slice,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
value, err := this.analyzeExpression(into, weak, slice.Slice)
|
||||
if err != nil { return nil, err }
|
||||
slice.Slice = value
|
||||
|
||||
// check permissions
|
||||
err = this.typeRestricted(slice.Position, value.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if slice.Start != nil {
|
||||
start, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
slice.Start)
|
||||
if err != nil { return nil, err }
|
||||
slice.Start = start
|
||||
}
|
||||
|
||||
if slice.End != nil {
|
||||
end, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
slice.End)
|
||||
if err != nil { return nil, err }
|
||||
slice.End = end
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLength (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
length *entity.Length,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
value, err := this.analyzeExpression(nil, strict, length.Slice)
|
||||
if err != nil { return nil, err }
|
||||
length.Slice = value
|
||||
length.Ty = builtinType("Index")
|
||||
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDereference (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
dereference *entity.Dereference,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
pointer, err := this.analyzeExpression (
|
||||
&entity.TypePointer {
|
||||
Position: dereference.Position,
|
||||
Referenced: into,
|
||||
}, weak,
|
||||
dereference.Pointer)
|
||||
if err != nil { return nil, err }
|
||||
dereference.Pointer = pointer
|
||||
dereference.Ty = into
|
||||
|
||||
var frailType bool
|
||||
switch into := into.(type) {
|
||||
case nil: frailType = true
|
||||
case *entity.TypePointer: frailType = into.Referenced == nil
|
||||
}
|
||||
|
||||
if frailType {
|
||||
ty := ReduceToBase(dereference.Pointer.Type())
|
||||
switch ty := ty.(type) {
|
||||
case *entity.TypePointer: dereference.Ty = ty.Referenced
|
||||
}
|
||||
}
|
||||
|
||||
return dereference, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeReference (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
reference *entity.Reference,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
referenced, ok := into.(*entity.TypePointer)
|
||||
if !ok {
|
||||
return nil, errors.Errorf(reference.Position, "expected %v", into)
|
||||
}
|
||||
|
||||
value, err := this.analyzeExpression (
|
||||
referenced.Referenced, weak,
|
||||
reference.Value)
|
||||
if err != nil { return nil, err }
|
||||
err = this.isLocationExpression(reference.Value)
|
||||
if err != nil { return nil, err }
|
||||
reference.Value = value
|
||||
reference.Ty = referenced.Referenced
|
||||
|
||||
return reference, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeValueCast (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
cast *entity.ValueCast,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
ty, err := this.analyzeType(cast.Ty, false)
|
||||
if err != nil { return nil, err }
|
||||
cast.Ty = ty
|
||||
|
||||
err = this.canAssign(cast.Position, into, mode, cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
value, err := this.analyzeExpression(cast.Ty, coerce, cast.Value)
|
||||
if err != nil { return nil, err }
|
||||
cast.Value = value
|
||||
|
||||
return cast, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeBitCast (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
cast *entity.BitCast,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
ty, err := this.analyzeType(cast.Ty, false)
|
||||
if err != nil { return nil, err }
|
||||
cast.Ty = ty
|
||||
|
||||
err = this.canAssign(cast.Position, into, mode, cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
value, err := this.analyzeExpression(cast.Ty, force, cast.Value)
|
||||
if err != nil { return nil, err }
|
||||
cast.Value = value
|
||||
|
||||
return cast, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeOperation (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
operation *entity.Operation,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// check permissions
|
||||
err := this.typeRestricted(operation.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
intoUntrustworthy := into == nil || mode == force || mode == coerce
|
||||
|
||||
undefined := func (ty entity.Type) (entity.Expression, error) {
|
||||
return nil, errors.Errorf (
|
||||
operation.Position, "operator %v undefined for %v",
|
||||
operation.Operator, entity.FormatType(ty))
|
||||
}
|
||||
|
||||
wrongInto := func () (entity.Expression, error) {
|
||||
return nil, errors.Errorf (
|
||||
operation.Position, "expected %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
wrongArgCount := func () (entity.Expression, error) {
|
||||
return nil, errors.Errorf (
|
||||
operation.Position, "wrong argument count for %v",
|
||||
operation.Operator)
|
||||
}
|
||||
|
||||
determineArgumentType := func () (entity.Type, int, error) {
|
||||
// find the first argument that has explicit type information.
|
||||
boss := -1
|
||||
var argumentType entity.Type
|
||||
for index, argument := range operation.Arguments {
|
||||
if argument.HasExplicitType() {
|
||||
argument, err := this.analyzeExpression(nil, strict, argument)
|
||||
if err != nil { return nil, -1, err }
|
||||
boss = index
|
||||
operation.Arguments[index] = argument
|
||||
argumentType = argument.Type()
|
||||
break
|
||||
}
|
||||
}
|
||||
if argumentType == nil {
|
||||
return nil, -1, errors.Errorf (
|
||||
operation.Position,
|
||||
"operation arguments have ambiguous type")
|
||||
}
|
||||
return argumentType, boss, err
|
||||
}
|
||||
|
||||
nSameType := func (n int, constraint func (entity.Type) bool) (entity.Expression, error) {
|
||||
if n > 0 {
|
||||
if len(operation.Arguments) != n { return wrongArgCount() }
|
||||
} else {
|
||||
if len(operation.Arguments) < 1 { return wrongArgCount() }
|
||||
}
|
||||
n = len(operation.Arguments)
|
||||
|
||||
boss := -1
|
||||
var argumentType entity.Type
|
||||
if intoUntrustworthy {
|
||||
// determine the type of all arguments (and the return
|
||||
// type) using determineArgumentType
|
||||
var err error
|
||||
argumentType, boss, err = determineArgumentType()
|
||||
if err != nil { return nil, err }
|
||||
if !constraint(argumentType) { return undefined(argumentType) }
|
||||
} else {
|
||||
// into is trustworthy, so we don't need to determine
|
||||
// anything
|
||||
argumentType = into
|
||||
}
|
||||
if !constraint(argumentType) { return undefined(argumentType) }
|
||||
operation.Ty = argumentType
|
||||
|
||||
for index, argument := range operation.Arguments {
|
||||
if index == boss { continue }
|
||||
argument, err := this.analyzeExpression (
|
||||
operation.Ty, strict,
|
||||
argument)
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
comparison := func (argConstraint func (entity.Type) bool) (entity.Expression, error) {
|
||||
if len(operation.Arguments) < 2 { return wrongArgCount() }
|
||||
if !isBoolean(into) && !intoUntrustworthy { return wrongInto() }
|
||||
operation.Ty = builtinType("Bool")
|
||||
|
||||
// determine argument type
|
||||
argumentType, boss, err := determineArgumentType()
|
||||
if err != nil { return nil, err }
|
||||
if !argConstraint(argumentType) { return undefined(argumentType) }
|
||||
|
||||
// analyze all remaining arguments
|
||||
for index, argument := range operation.Arguments {
|
||||
if index == boss { continue }
|
||||
argument, err := this.analyzeExpression (
|
||||
argumentType, strict, argument)
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[index] = argument
|
||||
}
|
||||
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
switch operation.Operator {
|
||||
// math
|
||||
case entity.OperatorAdd,
|
||||
entity.OperatorSubtract,
|
||||
entity.OperatorMultiply,
|
||||
entity.OperatorDivide,
|
||||
entity.OperatorIncrement,
|
||||
entity.OperatorDecrement:
|
||||
return nSameType(-1, isNumeric)
|
||||
|
||||
case entity.OperatorModulo:
|
||||
return nSameType(2, isNumeric)
|
||||
|
||||
// logic
|
||||
case entity.OperatorLogicalNot:
|
||||
return nSameType(1, isBoolean)
|
||||
|
||||
case entity.OperatorLogicalOr,
|
||||
entity.OperatorLogicalAnd,
|
||||
entity.OperatorLogicalXor:
|
||||
return nSameType(-1, isBoolean)
|
||||
|
||||
// bit manipulation
|
||||
case entity.OperatorNot:
|
||||
return nSameType(1, isInteger)
|
||||
|
||||
case entity.OperatorOr, entity.OperatorAnd, entity.OperatorXor:
|
||||
return nSameType(-1, isInteger)
|
||||
|
||||
case entity.OperatorLeftShift, entity.OperatorRightShift:
|
||||
if len(operation.Arguments) != 2 { return wrongArgCount() }
|
||||
if !isInteger(into) { return wrongInto() }
|
||||
|
||||
arg, err := this.analyzeExpression(into, mode, operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[0] = arg
|
||||
operation.Ty = arg.Type()
|
||||
|
||||
offset, err := this.analyzeExpression (
|
||||
builtinType("Index"), weak,
|
||||
operation.Arguments[1])
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments[1] = offset
|
||||
|
||||
return operation, nil
|
||||
|
||||
// comparison
|
||||
case entity.OperatorLess,
|
||||
entity.OperatorGreater,
|
||||
entity.OperatorLessEqual,
|
||||
entity.OperatorGreaterEqual:
|
||||
return comparison(isOrdered)
|
||||
|
||||
case entity.OperatorEqual:
|
||||
return comparison(isOrdered)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: analyzer doesnt know about operator ",
|
||||
operation.Operator))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeBlock (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
block *entity.Block,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
this.pushScope(block)
|
||||
defer this.popScope()
|
||||
|
||||
if len(block.Steps) == 0 && into != nil {
|
||||
return nil, errors.Errorf (
|
||||
block.Position, "block must have at least one statement")
|
||||
}
|
||||
|
||||
final := len(block.Steps) - 1
|
||||
for index, step := range block.Steps {
|
||||
if index == final && into != nil {
|
||||
expression, ok := step.(entity.Expression)
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
block.Position, "expected expression")
|
||||
}
|
||||
|
||||
step, err := this.analyzeExpression(into, strict, expression)
|
||||
if err != nil { return nil, err }
|
||||
block.Steps[index] = step
|
||||
block.Ty = step.Type()
|
||||
} else {
|
||||
step, err := this.analyzeExpression(nil, strict, step)
|
||||
if err != nil { return nil, err }
|
||||
block.Steps[index] = step
|
||||
}
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeMemberAccess (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
access *entity.MemberAccess,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
source, err := this.analyzeExpression(nil, strict, access.Source)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// determine type with the members
|
||||
var sourceType *entity.TypeStruct
|
||||
var qualifiedSourceType entity.Type
|
||||
switch sourceTypeAny := ReduceToBase(source.Type()).(type) {
|
||||
case *entity.TypeStruct:
|
||||
sourceType = sourceTypeAny
|
||||
qualifiedSourceType = source.Type()
|
||||
case *entity.TypePointer:
|
||||
referenced, ok := ReduceToBase(sourceTypeAny.Referenced).(*entity.TypeStruct)
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "cannot access members of %v",
|
||||
source)
|
||||
}
|
||||
sourceType = referenced
|
||||
qualifiedSourceType = sourceTypeAny.Referenced
|
||||
default:
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "cannot access members of %v",
|
||||
source)
|
||||
}
|
||||
|
||||
// check permissions
|
||||
err = this.typeRestricted(access.Position, qualifiedSourceType)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// get member
|
||||
member, ok := sourceType.MemberMap[access.Member]
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "no member %v",
|
||||
access)
|
||||
}
|
||||
|
||||
err = this.canAssign(access.Position, into, mode, member.Type())
|
||||
if err != nil { return nil, err }
|
||||
access.Ty = member.Type()
|
||||
|
||||
return access, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeIfElse (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
ifelse *entity.IfElse,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
condition, err := this.analyzeExpression (
|
||||
&entity.TypeNamed {
|
||||
Name: "Bool",
|
||||
Type: builtinType("Bool"),
|
||||
},
|
||||
weak, ifelse.Condition)
|
||||
if err != nil { return nil, err }
|
||||
ifelse.Condition = condition
|
||||
|
||||
trueBranch, err := this.analyzeExpression(into, strict, ifelse.True)
|
||||
if err != nil { return nil, err }
|
||||
ifelse.True = trueBranch
|
||||
|
||||
if ifelse.False == nil {
|
||||
if into != nil {
|
||||
return nil, errors.Errorf (
|
||||
ifelse.Position,
|
||||
"else case required when using value of if ")
|
||||
}
|
||||
} else {
|
||||
falseBranch, err := this.analyzeExpression(into, strict, ifelse.False)
|
||||
if err != nil { return nil, err }
|
||||
ifelse.False = falseBranch
|
||||
}
|
||||
|
||||
ifelse.Ty = into
|
||||
|
||||
return ifelse, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLoop (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
loop *entity.Loop,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
loop.Ty = into
|
||||
this.pushLoop(loop)
|
||||
defer this.popLoop()
|
||||
|
||||
body, err := this.analyzeExpression(nil, strict, loop.Body)
|
||||
if err != nil { return nil, err }
|
||||
loop.Body = body
|
||||
|
||||
return loop, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeBreak (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
brk *entity.Break,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
if into != nil {
|
||||
return nil, errors.Errorf (
|
||||
brk.Position, "expected %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
loop, ok := this.topLoop()
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
brk.Position,
|
||||
"break statement must be within loop")
|
||||
}
|
||||
brk.Loop = loop
|
||||
|
||||
if loop.Type() != nil && brk.Value == nil {
|
||||
return nil, errors.Errorf (
|
||||
brk.Position,
|
||||
"break statement must have value")
|
||||
}
|
||||
|
||||
if brk.Value != nil {
|
||||
value, err := this.analyzeExpression(loop.Type(), strict, brk.Value)
|
||||
if err != nil { return nil, err }
|
||||
brk.Value = value
|
||||
}
|
||||
|
||||
return brk, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeReturn (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
ret *entity.Return,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
if into != nil {
|
||||
return nil, errors.Errorf (
|
||||
ret.Position, "expected %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
ret.Declaration, _ = this.topDeclaration()
|
||||
var ty entity.Type
|
||||
switch ret.Declaration.(type) {
|
||||
case *entity.Function:
|
||||
ty = ret.Declaration.(*entity.Function).Signature.Return
|
||||
case *entity.Method:
|
||||
ty = ret.Declaration.(*entity.Method).Signature.Return
|
||||
}
|
||||
|
||||
if ty != nil && ret.Value == nil {
|
||||
return nil, errors.Errorf (
|
||||
ret.Position,
|
||||
"break statement must have value")
|
||||
}
|
||||
|
||||
if ret.Value != nil {
|
||||
value, err := this.analyzeExpression(ty, strict, ret.Value)
|
||||
if err != nil { return nil, err }
|
||||
ret.Value = value
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package analyzer
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeFunction (
|
||||
pos errors.Position,
|
||||
key entity.Key,
|
||||
) (
|
||||
*entity.Function,
|
||||
error,
|
||||
) {
|
||||
var err error
|
||||
|
||||
// return if exists already
|
||||
if function, exists := this.Functions[key]; exists {
|
||||
return function, nil
|
||||
}
|
||||
|
||||
// error if function is missing
|
||||
function, exists := this.rawFunctions[key]
|
||||
if !exists {
|
||||
return nil, errors.Errorf(pos, "no function named %s", key.Name)
|
||||
}
|
||||
|
||||
// set unit
|
||||
function.Unit = key.Unit
|
||||
|
||||
// functions cannot be marked as restricted
|
||||
if function.Acc == entity.AccessRestricted {
|
||||
return nil, errors.Errorf(pos, "cannot mark function as restricted")
|
||||
}
|
||||
|
||||
// create a new scope context for this function
|
||||
this.pushScopeContext(function)
|
||||
this.pushScope(function)
|
||||
defer this.popScopeContext()
|
||||
defer this.popScope()
|
||||
|
||||
// analyze signature and add arguments to root scope of function
|
||||
function.Signature, err = this.assembleSignatureMap(function.Signature)
|
||||
if err != nil { return function, err }
|
||||
for name, argument := range function.Signature.ArgumentMap {
|
||||
argument.Ty, err = this.analyzeType(argument.Ty, false)
|
||||
this.addVariable(argument)
|
||||
function.Signature.ArgumentMap[name] = argument
|
||||
if err != nil { return function, err }
|
||||
}
|
||||
function.Signature.Return, err =
|
||||
this.analyzeType(function.Signature.Return, false)
|
||||
if err != nil { return function, err }
|
||||
|
||||
// add incomplete function to complete functions because there is enough
|
||||
// information for it to be complete from the point of view of other
|
||||
// parts of the code
|
||||
this.Functions[key] = function
|
||||
|
||||
// analyze function body
|
||||
if function.Body != nil {
|
||||
body, err := this.analyzeExpression (
|
||||
function.Signature.Return, strict, function.Body)
|
||||
if err != nil { return nil, err }
|
||||
function.Body = body
|
||||
}
|
||||
return function, nil
|
||||
}
|
|
@ -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.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if !isNumeric(into) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use integer literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
if isInteger(into) && !inRange(into, int64(literal.Value)) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "integer literal out of range for type %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralFloat (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralFloat,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if !isFloat(into) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use float literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralString (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralString,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
base := ReduceToBase(into)
|
||||
|
||||
errCantUse := func () error {
|
||||
return errors.Errorf (
|
||||
literal.Position, "cannot use string literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
fillUTF32 := func () {
|
||||
literal.ValueUTF32 = []rune(literal.ValueUTF8)
|
||||
}
|
||||
|
||||
fillUTF16 := func () {
|
||||
literal.ValueUTF16 = utf16.Encode([]rune(literal.ValueUTF8))
|
||||
}
|
||||
switch base := base.(type) {
|
||||
case *entity.TypeInt:
|
||||
var length int; switch {
|
||||
case base.Width >= 32:
|
||||
fillUTF32()
|
||||
length = len(literal.ValueUTF32)
|
||||
case base.Width >= 16:
|
||||
fillUTF16()
|
||||
length = len(literal.ValueUTF16)
|
||||
default:
|
||||
length = len(literal.ValueUTF8)
|
||||
}
|
||||
if length > 1 {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"cannot fit string literal of length " +
|
||||
"%v into %v",
|
||||
length, entity.FormatType(into))
|
||||
} else if length < 1 {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"string literal must have data when " +
|
||||
"assigning to %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
case *entity.TypeArray:
|
||||
element := ReduceToBase(base.Element)
|
||||
if element, ok := element.(*entity.TypeInt); ok {
|
||||
var length int; switch {
|
||||
case element.Width >= 32:
|
||||
fillUTF32()
|
||||
length = len(literal.ValueUTF32)
|
||||
case element.Width >= 16:
|
||||
fillUTF16()
|
||||
length = len(literal.ValueUTF16)
|
||||
default:
|
||||
length = len(literal.ValueUTF8)
|
||||
}
|
||||
if length > base.Length {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"cannot fit string literal of length " +
|
||||
"%v into array of length %v",
|
||||
length, base.Length)
|
||||
}
|
||||
} else {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
case *entity.TypeSlice:
|
||||
element := ReduceToBase(base.Element)
|
||||
if element, ok := element.(*entity.TypeInt); ok {
|
||||
if element.Width >= 32 {
|
||||
fillUTF32()
|
||||
} else if element.Width >= 16 {
|
||||
fillUTF16()
|
||||
}
|
||||
} else {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
case *entity.TypePointer:
|
||||
referenced := ReduceToBase(base.Referenced)
|
||||
if referenced, ok := referenced.(*entity.TypeInt); ok {
|
||||
if referenced.Width != 8 {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
} else {
|
||||
return nil, errCantUse()
|
||||
}
|
||||
default:
|
||||
return nil, errCantUse()
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
|
||||
func (this *Tree) analyzeLiteralArray (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralArray,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
base := ReduceToBase(into)
|
||||
var elementType entity.Type
|
||||
switch base.(type) {
|
||||
case *entity.TypeArray:
|
||||
base := base.(*entity.TypeArray)
|
||||
if base.Length < len(literal.Elements) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "expected %v elements or less",
|
||||
base.Length)
|
||||
}
|
||||
elementType = base.Element
|
||||
|
||||
case *entity.TypeSlice:
|
||||
base := base.(*entity.TypeSlice)
|
||||
elementType = base.Element
|
||||
|
||||
case *entity.TypePointer:
|
||||
base := base.(*entity.TypePointer)
|
||||
elementType = base.Referenced
|
||||
default:
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use array literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
for index, element := range literal.Elements {
|
||||
element, err := this.analyzeExpression(elementType, strict, element)
|
||||
if err != nil { return nil, err }
|
||||
literal.Elements[index] = element
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralStruct (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralStruct,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
base, ok := ReduceToBase(into).(*entity.TypeStruct)
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use struct literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal, err = this.assembleStructLiteralMap(literal)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
for name, member := range literal.MemberMap {
|
||||
correct, ok := base.MemberMap[name]
|
||||
if !ok {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "no member %v.%s",
|
||||
into, name)
|
||||
}
|
||||
|
||||
value, err := this.analyzeExpression(correct.Type(), strict, member.Value)
|
||||
if err != nil { return nil, err }
|
||||
member.Value = value
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralBoolean (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralBoolean,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if !isBoolean(into) {
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use boolean literal as %v",
|
||||
entity.FormatType(into))
|
||||
}
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeLiteralNil (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
literal *entity.LiteralNil,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
err := this.typeRestricted(literal.Position, into)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
literal.Ty = into
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Tree) assembleStructLiteralMap (
|
||||
literal *entity.LiteralStruct,
|
||||
) (
|
||||
*entity.LiteralStruct,
|
||||
error,
|
||||
) {
|
||||
literal.MemberMap = make(map[string] *entity.Member)
|
||||
literal.MemberOrder = make([]string, len(literal.Members))
|
||||
for index, member := range literal.Members {
|
||||
if previous, exists := literal.MemberMap[member.Name]; exists {
|
||||
return literal, errors.Errorf (
|
||||
member.Position, "%s already listed in struct literal at %v",
|
||||
member.Name, previous.Position)
|
||||
}
|
||||
literal.MemberMap [member.Name] = member
|
||||
literal.MemberOrder[index] = member.Name
|
||||
literal.Members [index] = member
|
||||
}
|
||||
return literal, nil
|
||||
}
|
|
@ -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.Unit = owner.Unit
|
||||
|
||||
// methods cannot be marked as restricted
|
||||
if method.Acc == entity.AccessRestricted {
|
||||
return nil, errors.Errorf(pos, "cannot mark method as restricted")
|
||||
}
|
||||
|
||||
// create a new scope context for this method
|
||||
this.pushScopeContext(method)
|
||||
this.pushScope(method)
|
||||
defer this.popScopeContext()
|
||||
defer this.popScope()
|
||||
|
||||
// analyze signature and add arguments to root scope of method
|
||||
method.Signature, err = this.assembleSignatureMap(method.Signature)
|
||||
if err != nil { return method, err }
|
||||
for name, argument := range method.Signature.ArgumentMap {
|
||||
argument.Ty, err = this.analyzeType(argument.Ty, false)
|
||||
this.addVariable(argument)
|
||||
method.Signature.ArgumentMap[name] = argument
|
||||
if err != nil { return method, err }
|
||||
}
|
||||
method.Signature.Return, err =
|
||||
this.analyzeType(method.Signature.Return, false)
|
||||
if err != nil { return method, err }
|
||||
|
||||
// add owner to method
|
||||
method.This = &entity.Declaration {
|
||||
Position: method.Position,
|
||||
Name: "this",
|
||||
Ty: &entity.TypePointer {
|
||||
Position: method.Position,
|
||||
Referenced: &entity.TypeNamed {
|
||||
Position: method.Position,
|
||||
Unt: key.Unit,
|
||||
Name: key.Name,
|
||||
Type: owner.Type,
|
||||
},
|
||||
},
|
||||
}
|
||||
this.addVariable(method.This)
|
||||
|
||||
// add incomplete method to complete methods because there is enough
|
||||
// information for it to be complete from the point of view of other
|
||||
// parts of the code
|
||||
owner.Methods[key.Method] = method
|
||||
|
||||
// analyze method body
|
||||
if method.Body != nil {
|
||||
body, err := this.analyzeExpression (
|
||||
method.Signature.Return, strict, method.Body)
|
||||
if err != nil { return nil, err }
|
||||
method.Body = body
|
||||
}
|
||||
return method, nil
|
||||
}
|
||||
|
||||
func (this *Tree) methodExists (pos errors.Position, key entity.Key) (bool, error) {
|
||||
// check raw map
|
||||
_, existsInRaw := this.rawMethods[key]
|
||||
if existsInRaw { return true, nil }
|
||||
|
||||
// check parent typedef
|
||||
typeKey := key.StripMethod()
|
||||
owner, err := this.analyzeTypedef(pos, typeKey, false)
|
||||
if err != nil { return false, err }
|
||||
_, existsInType := owner.Methods[key.Method]
|
||||
return existsInType, nil
|
||||
}
|
||||
|
||||
// analyzeMethodOrBehavior returns *entity.Signature if it found an interface
|
||||
// behavior, and *entity.Method if it found a method.
|
||||
func (this *Tree) analyzeMethodOrBehavior (
|
||||
pos errors.Position,
|
||||
ty entity.Type,
|
||||
name string,
|
||||
) (
|
||||
any,
|
||||
error,
|
||||
) {
|
||||
return this.analyzeMethodOrBehaviorInternal(pos, ty, name, true)
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeMethodOrBehaviorInternal (
|
||||
pos errors.Position,
|
||||
ty entity.Type,
|
||||
name string,
|
||||
pierceReference bool,
|
||||
) (
|
||||
any,
|
||||
error,
|
||||
) {
|
||||
switch ty.(type) {
|
||||
case *entity.TypeNamed:
|
||||
ty := ty.(*entity.TypeNamed)
|
||||
key := entity.Key {
|
||||
Unit: ty.Type.Unit(),
|
||||
Name: ty.Name,
|
||||
Method: name,
|
||||
}
|
||||
exists, err := this.methodExists(pos, key)
|
||||
if err != nil { return nil, err }
|
||||
if exists {
|
||||
method, err := this.analyzeMethod(pos, key)
|
||||
if err != nil { return nil, err }
|
||||
return method, nil
|
||||
} else {
|
||||
return this.analyzeMethodOrBehaviorInternal (
|
||||
pos, ty.Type, name, false)
|
||||
}
|
||||
|
||||
case *entity.TypeInterface:
|
||||
ty := ty.(*entity.TypeInterface)
|
||||
if behavior, ok := ty.BehaviorMap[name]; ok {
|
||||
return behavior, nil
|
||||
} else {
|
||||
return nil, errors.Errorf (
|
||||
pos, "no behavior or method named %s",
|
||||
name)
|
||||
}
|
||||
|
||||
case *entity.TypePointer:
|
||||
if pierceReference {
|
||||
ty := ty.(*entity.TypePointer)
|
||||
return this.analyzeMethodOrBehaviorInternal (
|
||||
pos, ty.Referenced, name, false)
|
||||
} else {
|
||||
return nil, errors.Errorf (
|
||||
pos, "no method named %s defined on this type",
|
||||
name)
|
||||
}
|
||||
|
||||
case *entity.TypeSlice,
|
||||
*entity.TypeArray,
|
||||
*entity.TypeStruct,
|
||||
*entity.TypeInt,
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
|
||||
return nil, errors.Errorf (
|
||||
pos, "no method named %s defined on this type",
|
||||
name)
|
||||
|
||||
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
|
||||
}
|
||||
}
|
|
@ -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 TestUnitAssignRestricted (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[main] = {
|
||||
x:other::RestrictedInt
|
||||
y:other::RestrictedInt
|
||||
x = y
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestUnitAssignLiteralRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInt is restricted", 1, 31,
|
||||
`[main]:other::RestrictedInt = 5`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestAssignLiteralRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedInt:Int
|
||||
[main]:RestrictedInt = 5`,
|
||||
)}
|
||||
|
||||
func TestUnitMemberAccessRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedStruct is restricted", 3, 3,
|
||||
`[main] = {
|
||||
x:other::RestrictedStruct
|
||||
x.x = 5
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedStruct:(. x:Int y:Int)`,
|
||||
)}
|
||||
|
||||
func TestMemberAccessRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedStruct:(. x:Int y:Int)
|
||||
[main] = {
|
||||
x:RestrictedStruct
|
||||
x.x = 5
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestUnitSubscriptRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedArr is restricted", 3, 4,
|
||||
`[main] = {
|
||||
x:other::RestrictedArr
|
||||
[.x 0] = 5
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedArr:5:Int`,
|
||||
)}
|
||||
|
||||
func TestSubscriptRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedArr:5:Int
|
||||
[main] = {
|
||||
x:RestrictedArr
|
||||
[.x 0] = 5
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestUnitMathRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInt is restricted", 2, 27,
|
||||
`[main] = {
|
||||
x:other::RestrictedInt = [+
|
||||
y:other::RestrictedInt
|
||||
z:other::RestrictedInt]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestMathRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`~ RestrictedInt:Int
|
||||
[main] = {
|
||||
x:RestrictedInt = [+
|
||||
y:RestrictedInt
|
||||
z:RestrictedInt]
|
||||
}`,
|
||||
)}
|
||||
|
||||
func TestNestedUnitTypedef (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[main]:layer1::X = 5`,
|
||||
|
||||
"layer0.fspl",
|
||||
`+ X:Int`,
|
||||
|
||||
"layer1.fspl",
|
||||
`+ X:layer0::X`,
|
||||
)}
|
||||
|
||||
func TestUnitBehaviorCallRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInterface is restricted", 3, 3,
|
||||
`[main] = {
|
||||
x:other::RestrictedInterface
|
||||
x.[y]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInterface:(~ [y])`,
|
||||
)}
|
||||
|
||||
func TestBehaviorCallRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`[main] = {
|
||||
x:RestrictedInterface
|
||||
x.[y]
|
||||
}
|
||||
~ RestrictedInterface:(~ [y])`,
|
||||
)}
|
||||
|
||||
func TestUnitCastRestrictedErr (test *testing.T) {
|
||||
testUnitsErr (test,
|
||||
"main.fspl", "type other::RestrictedInt is restricted", 2, 16,
|
||||
`[main] = {
|
||||
x:Int = [~Int y:other::RestrictedInt]
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestCastRestricted (test *testing.T) {
|
||||
testString (test,
|
||||
`[main] = {
|
||||
x:Int = [~Int y:RestrictedInt]
|
||||
}
|
||||
~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestFunctionRestrictedErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot mark function as restricted", 1, 1,
|
||||
`~ [f]`,
|
||||
)}
|
||||
|
||||
func TestMethodRestrictedErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot mark method as restricted", 2, 1,
|
||||
`T:Int
|
||||
~ T.[f]`,
|
||||
)}
|
||||
|
||||
func TestUnitInterfaceSatisfaction (test *testing.T) {
|
||||
testUnits (test,
|
||||
`[sayHello writer:io::Writer] = writer.[write 'well hello their\n']
|
||||
|
||||
[main]: I32 'main' = {
|
||||
stdout:io::File = 1
|
||||
[sayHello stdout]
|
||||
0
|
||||
}`,
|
||||
|
||||
"cstdio.fspl",
|
||||
`+ FileDescriptor: Int
|
||||
+ [write file:FileDescriptor buffer:*Byte count:Index]: Index 'write'`,
|
||||
|
||||
"io.fspl",
|
||||
`+ Writer: (~ [write buffer:*:Byte]: Index)
|
||||
+ File: cstdio::FileDescriptor
|
||||
+ File.[write buffer:*:Byte]:Index =
|
||||
cstdio::[write
|
||||
[~cstdio::FileDescriptor [.this]]
|
||||
[~*Byte buffer] [#buffer]]`,
|
||||
)}
|
|
@ -2,141 +2,29 @@ package analyzer
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestFunctionUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
[hello] = { }
|
||||
[hello] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUniqueErrShadowBuiltin (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow builtin true ", 2, 2,
|
||||
`
|
||||
[true]:Bool = false
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[hello] = { }
|
||||
[world] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
hello: *Int
|
||||
hello: (x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUniqueErrShadowBuiltin (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow builtin true ", 2, 2,
|
||||
`
|
||||
true:Int
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
hello: *Int
|
||||
world: (x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no type named example", 2, 20,
|
||||
`
|
||||
[main] = { example:Example }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Example: Int
|
||||
AllBuiltin: (
|
||||
int: Int
|
||||
uint: UInt
|
||||
byte: Byte
|
||||
rune: Rune
|
||||
string: String
|
||||
i8: I8
|
||||
i8: I16
|
||||
i8: I32
|
||||
i8: I64
|
||||
u8: U8
|
||||
u8: U16
|
||||
u8: U32
|
||||
u8: U64
|
||||
f32: F32
|
||||
f64: F64
|
||||
bool: Bool)
|
||||
[main] = {
|
||||
example:Example
|
||||
allBuiltin:AllBuiltin
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
hello: *Int
|
||||
[hello] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
hello: *Int
|
||||
[world] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"Bird.fly already declared at stream0.fspl:3:1", 4, 1,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[fly] = { }
|
||||
Bird::[fly distance:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[fly] = { }
|
||||
Bird::[land] = { }
|
||||
Bird::[walk distance:Int] = { }
|
||||
Bat: Int
|
||||
Bat::[fly] = { }
|
||||
[fly] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method Type::world", 5, 3,
|
||||
"no method named world defined on this type", 6, 10,
|
||||
`
|
||||
Type: Int
|
||||
Type::[something] = { }
|
||||
Type.[something] = { }
|
||||
[main] = {
|
||||
instance:Type
|
||||
[instance::world]
|
||||
instance.[world]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodNameErrBadLayer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named something defined on this type", 7, 10,
|
||||
`
|
||||
Type: Int
|
||||
Type.[something] = { }
|
||||
RefType: *Type
|
||||
[main] = {
|
||||
instance:RefType
|
||||
instance.[something]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
@ -145,90 +33,16 @@ func TestMethodName (test *testing.T) {
|
|||
testString (test,
|
||||
`
|
||||
Type: Int
|
||||
Type::[world] = { }
|
||||
Type.[world] = { }
|
||||
[main] = {
|
||||
instance:Type
|
||||
[instance::world]
|
||||
instance.[world]
|
||||
ref:*Type = [@ instance]
|
||||
ref.[world]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"fly already listed in interface at stream0.fspl:2:8", 2, 14,
|
||||
`
|
||||
Bird: ([fly] [fly])
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no behavior Bird::swim", 8, 3,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl::[fly] = { }
|
||||
BirdImpl::[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = [@impl:BirdImpl]
|
||||
[bird::swim]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBehaviorName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl::[fly] = { }
|
||||
BirdImpl::[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = [@impl:BirdImpl]
|
||||
[bird::fly]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionArgumentUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already declared in block at stream0.fspl:2:8", 2, 14,
|
||||
`
|
||||
[main x:Int x:U8 y:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionArgumentUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main x:Int y:U8 z:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodArgumentUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already declared in block at stream0.fspl:3:13", 3, 19,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[main x:Int x:U8 y:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodArgumentUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: Int
|
||||
Bird::[main x:Int y:U8 z:Int] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already declared in block at stream0.fspl:3:2", 5, 2,
|
||||
|
@ -241,20 +55,10 @@ testStringErr (test,
|
|||
`)
|
||||
}
|
||||
|
||||
func TestVariableUniqueErrShadowBuiltin (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot shadow builtin false ", 3, 2,
|
||||
`
|
||||
[main] = {
|
||||
false:Bool = true
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
x: Int
|
||||
[x] = { }
|
||||
[y] = { }
|
||||
[main x:Int] = {
|
||||
x:Int
|
||||
|
@ -268,20 +72,9 @@ x: Int
|
|||
`)
|
||||
}
|
||||
|
||||
func TestVariableNameErrType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"named type example cannot be used as location expression", 4, 2,
|
||||
`
|
||||
example: Int
|
||||
[main] = {
|
||||
example = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestVariableNameErrFunction (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"function example cannot be used as location expression", 4, 2,
|
||||
"no variable named example", 4, 2,
|
||||
`
|
||||
[example] = { }
|
||||
[main] = {
|
||||
|
@ -292,7 +85,7 @@ testStringErr (test,
|
|||
|
||||
func TestVariableNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no variable named example", 4, 2,
|
||||
"no variable named example", 3, 2,
|
||||
`
|
||||
[main] = {
|
||||
example = 5
|
||||
|
@ -328,96 +121,13 @@ testString (test,
|
|||
example = 3
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed in struct at stream0.fspl:3:2", 6, 2,
|
||||
`
|
||||
Bird: (
|
||||
x:Int
|
||||
y:Int
|
||||
z:Int
|
||||
x:U8)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (
|
||||
x:Int
|
||||
y:Int
|
||||
z:Int
|
||||
a:U8)
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no member Type.world", 5, 2,
|
||||
`
|
||||
Type: (something:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Type: (world:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructLiteralMemberUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed in struct literal at stream0.fspl:5:2", 7, 2,
|
||||
`
|
||||
Point: (x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (
|
||||
x: 5
|
||||
y: 0
|
||||
x: 32)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructLiteralMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Point: (x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (
|
||||
x: 5
|
||||
y: 0
|
||||
z: 32)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionNameErrType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot call named type example", 3, 3,
|
||||
`
|
||||
example: Int
|
||||
[main] = [example]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionNameErrVar (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot call example:Int", 4, 3,
|
||||
"no function named example", 4, 2,
|
||||
`
|
||||
[main] = {
|
||||
example:Int
|
||||
|
@ -428,7 +138,7 @@ testStringErr (test,
|
|||
|
||||
func TestFunctionNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no function named example", 2, 3,
|
||||
"no function named example", 2, 10,
|
||||
`
|
||||
[main] = [example]
|
||||
`)
|
||||
|
@ -441,3 +151,55 @@ testString (test,
|
|||
[main] = [example]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceBehaviorNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no behavior or method named swim", 8, 6,
|
||||
`
|
||||
Bird: (~ [fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl.[fly] = { }
|
||||
BirdImpl.[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = impl:BirdImpl
|
||||
bird.[swim]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceBehaviorName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (~ [fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl.[fly] = { }
|
||||
BirdImpl.[land] = { }
|
||||
[main] = {
|
||||
bird:Bird = impl:BirdImpl
|
||||
bird.[fly]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no member instance.world", 5, 10,
|
||||
`
|
||||
Type: (. something:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStructMemberName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Type: (. world:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
|
|
@ -2,112 +2,82 @@ package analyzer
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestOperatorModUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationModUnderArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for %", 10, 2,
|
||||
"wrong argument count for %", 2, 10,
|
||||
`
|
||||
[main] = [% 2]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorModOverArgCountErr (test *testing.T) {
|
||||
func TestOperationModOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for %", 10, 2,
|
||||
"wrong argument count for %", 2, 10,
|
||||
`
|
||||
[main] = [% 2 3 1]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorLogicalNegationUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationLogicalNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !", 10, 2,
|
||||
`
|
||||
[main] = [!]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorLogicalNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !", 10, 2,
|
||||
"wrong argument count for !", 2, 10,
|
||||
`
|
||||
[main] = [! 348 92]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitwiseNegationUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationBitwiseNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !!", 10, 2,
|
||||
`
|
||||
[main] = [!!]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitwiseNegationOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for !!", 10, 2,
|
||||
"wrong argument count for !!", 2, 10,
|
||||
`
|
||||
[main] = [!! 348 92]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftLeftUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationBitShiftLeftOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for <<", 10, 2,
|
||||
`
|
||||
[main] = [<<]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftLeftOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for <<", 10, 2,
|
||||
"wrong argument count for <<", 2, 10,
|
||||
`
|
||||
[main] = [<< 348 92 324]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftRightUnderArgCountErr (test *testing.T) {
|
||||
func TestOperationBitShiftRightOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for >>", 10, 2,
|
||||
`
|
||||
[main] = [>>]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorBitShiftRightOverArgCountErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"wrong argument count for >>", 10, 2,
|
||||
"wrong argument count for >>", 2, 10,
|
||||
`
|
||||
[main] = [>> 348 92 324]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestOperatorArgCount (test *testing.T) {
|
||||
func TestOperationArgCount (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
[+ 1 2 3 4]
|
||||
[++ 1]
|
||||
[- 1 2 3 4]
|
||||
[-- 1]
|
||||
[* 5 6]
|
||||
[/ 32 16]
|
||||
[% 5 6]
|
||||
[!! true]
|
||||
[|| false]
|
||||
[&& true true false true]
|
||||
[^^ true true false true]
|
||||
[! 1]
|
||||
[| 1 2]
|
||||
[& 3 1]
|
||||
[^ 1 2 3 4]
|
||||
[<< 1 8]
|
||||
[>> 8 1]
|
||||
[< 1 2 3 4]
|
||||
[> 1 2 3 4]
|
||||
[<= 10 5 3]
|
||||
[>= 1 2 3 3 4]
|
||||
[= 2 2 2 3 9]
|
||||
x:Int
|
||||
b:Bool
|
||||
x = [+ 1 2 3 4]
|
||||
x = [++ 1]
|
||||
x = [- 1 2 3 4]
|
||||
x = [-- 1]
|
||||
x = [* 5 6]
|
||||
x = [/ 32 16]
|
||||
x = [% 5 6]
|
||||
x = [!! 1]
|
||||
x = [|| 1 2]
|
||||
x = [&& 3 1 5]
|
||||
x = [^^ 1 2 3 4]
|
||||
b = [! true]
|
||||
b = [| true false]
|
||||
b = [& true true]
|
||||
b = [^ true true false true]
|
||||
x = [<< 1 8]
|
||||
x = [>> 8 1]
|
||||
b = [< x 2 3 4]
|
||||
b = [> x 2 3 4]
|
||||
b = [<= x 5 3]
|
||||
b = [>= x 2 3 3 4]
|
||||
b = [= x 2 2 3 9]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package analyzer
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
// scopeContextManager is a stack of scopeContexts allowing multiple stacks of
|
||||
// scopes to be managed at the same time in case the analysis of one scoped
|
||||
// entity causes the analysis of another.
|
||||
type scopeContextManager []scopeContext
|
||||
|
||||
func (this *scopeContextManager) pushScopeContext (declaration entity.TopLevel) {
|
||||
*this = append(*this, scopeContext { declaration: declaration })
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) popScopeContext () {
|
||||
this.assertPopulated()
|
||||
*this = (*this)[:len(*this) - 1]
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) pushLoop (loop *entity.Loop) {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].pushLoop(loop)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) popLoop () {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].popLoop()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) topLoop () (*entity.Loop, bool) {
|
||||
this.assertPopulated()
|
||||
return (*this)[len(*this) - 1].topLoop()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) topDeclaration () (entity.TopLevel, bool) {
|
||||
if len(*this) < 1 { return nil, false }
|
||||
return (*this)[len(*this) - 1].declaration, true
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) pushScope (scope entity.Scoped) {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].pushScope(scope)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) popScope () {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].popScope()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) topScope () (entity.Scoped, bool) {
|
||||
this.assertPopulated()
|
||||
return (*this)[len(*this) - 1].topScope()
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) variable (name string) *entity.Declaration {
|
||||
this.assertPopulated()
|
||||
return (*this)[len(*this) - 1].variable(name)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) addVariable (declaration *entity.Declaration) {
|
||||
this.assertPopulated()
|
||||
(*this)[len(*this) - 1].addVariable(declaration)
|
||||
}
|
||||
|
||||
func (this *scopeContextManager) assertPopulated () {
|
||||
if len(*this) < 1 {
|
||||
panic("scopeContextManager must have at least 1 scope context")
|
||||
}
|
||||
}
|
||||
|
||||
// scopeContext is a stack of scopes and loops used when analyzing scoped
|
||||
// entities.
|
||||
type scopeContext struct {
|
||||
scopes []entity.Scoped
|
||||
loops []*entity.Loop
|
||||
declaration entity.TopLevel
|
||||
}
|
||||
|
||||
func (this *scopeContext) pushLoop (loop *entity.Loop) {
|
||||
this.loops = append(this.loops, loop)
|
||||
}
|
||||
|
||||
func (this *scopeContext) popLoop () {
|
||||
this.assertLoopPopulated()
|
||||
this.loops = this.loops[:len(this.loops) - 1]
|
||||
}
|
||||
|
||||
func (this *scopeContext) topLoop () (*entity.Loop, bool) {
|
||||
if len(this.loops) < 1 { return nil, false }
|
||||
return this.loops[len(this.loops) - 1], true
|
||||
}
|
||||
|
||||
func (this *scopeContext) pushScope (scope entity.Scoped) {
|
||||
this.scopes = append(this.scopes, scope)
|
||||
}
|
||||
|
||||
func (this *scopeContext) popScope () {
|
||||
this.assertScopePopulated()
|
||||
this.scopes = this.scopes[:len(this.scopes) - 1]
|
||||
}
|
||||
|
||||
func (this *scopeContext) topScope () (entity.Scoped, bool) {
|
||||
if len(this.scopes) < 1 { return nil, false }
|
||||
return this.scopes[len(this.scopes) - 1], true
|
||||
}
|
||||
|
||||
func (this *scopeContext) variable (name string) *entity.Declaration {
|
||||
this.assertScopePopulated()
|
||||
for index := len(this.scopes) - 1; index >= 0; index -- {
|
||||
variable := this.scopes[index].Variable(name)
|
||||
if variable != nil {
|
||||
return variable
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *scopeContext) addVariable (declaration *entity.Declaration) {
|
||||
this.assertScopePopulated()
|
||||
this.scopes[len(this.scopes) - 1].AddVariable(declaration)
|
||||
}
|
||||
|
||||
func (this *scopeContext) assertScopePopulated () {
|
||||
if len(this.scopes) < 1 {
|
||||
panic("scopeContext must have at least 1 scope")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *scopeContext) assertLoopPopulated () {
|
||||
if len(this.scopes) < 1 {
|
||||
panic("scopeContext must have at least 1 loop")
|
||||
}
|
||||
}
|
|
@ -1,82 +1,199 @@
|
|||
package analyzer
|
||||
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "testing"
|
||||
import "strings"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/parser"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/lexer"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/fspl"
|
||||
|
||||
func testStringErr (
|
||||
test *testing.T,
|
||||
errMessage string,
|
||||
errLine int,
|
||||
errColumn int,
|
||||
errRow int,
|
||||
errStart int,
|
||||
input string,
|
||||
) {
|
||||
testReaderErr (
|
||||
test,
|
||||
errMessage, errLine, errColumn,
|
||||
strings.NewReader(input))
|
||||
}
|
||||
|
||||
func testReaderErr (
|
||||
test *testing.T,
|
||||
errMessage string,
|
||||
errLine int,
|
||||
errColumn int,
|
||||
inputs ...io.Reader,
|
||||
) {
|
||||
ast := parser.Tree { }
|
||||
for index, stream := range inputs {
|
||||
err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
|
||||
if err != nil {
|
||||
test.Error("parser returned error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
address := entity.Address("stream0.fspl")
|
||||
ast, ok := treeOf(test, address, input, false)
|
||||
if !ok { return }
|
||||
|
||||
tree := Tree { }
|
||||
err := tree.Analyze(ast)
|
||||
err := tree.Analyze(address.UUID(), nil, ast)
|
||||
if err == nil {
|
||||
test.Error("analyzer did not return error")
|
||||
return
|
||||
}
|
||||
got := err.(participle.Error)
|
||||
gotMessage := got.Message()
|
||||
gotLine := got.Position().Line
|
||||
gotColumn := got.Position().Column
|
||||
correct :=
|
||||
gotMessage == errMessage &&
|
||||
gotLine == errLine &&
|
||||
gotColumn == errColumn
|
||||
if !correct {
|
||||
test.Log("errors do not match")
|
||||
test.Logf("got:\n%v:%v: %v", gotLine, gotColumn, gotMessage)
|
||||
test.Logf("correct:\n%v:%v: %v", errLine, errColumn, errMessage)
|
||||
test.Fail()
|
||||
return
|
||||
}
|
||||
compareErr(test, address, string(address), errMessage, errRow, errStart, err)
|
||||
}
|
||||
|
||||
func testString (test *testing.T, input string) {
|
||||
testReader(test, strings.NewReader(input))
|
||||
}
|
||||
|
||||
func testReader (test *testing.T, inputs ...io.Reader) {
|
||||
ast := parser.Tree { }
|
||||
for index, stream := range inputs {
|
||||
err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
|
||||
if err != nil {
|
||||
test.Error("parser returned error: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
address := entity.Address("stream0.fspl")
|
||||
ast, ok := treeOf(test, address, input, false)
|
||||
if !ok { return }
|
||||
|
||||
tree := Tree { }
|
||||
err := tree.Analyze(ast)
|
||||
err := tree.Analyze(address.UUID(), nil, ast)
|
||||
if err != nil {
|
||||
test.Error("analyzer returned error: ", err)
|
||||
test.Error("analyzer returned error:\n" + errors.Format(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testUnits (
|
||||
test *testing.T,
|
||||
main string,
|
||||
dependencies ...string,
|
||||
) {
|
||||
if len(dependencies) % 2 != 0 {
|
||||
panic("dependencies len must be even. forget an address?")
|
||||
}
|
||||
tree := Tree { }
|
||||
nicknames := make(map[string] uuid.UUID)
|
||||
|
||||
// dependencies
|
||||
for index := 0; index < len(dependencies) - 1; index += 2 {
|
||||
address := entity.Address(dependencies[index])
|
||||
source := dependencies[index + 1]
|
||||
ast, ok := treeOf(test, address, source, true)
|
||||
if !ok { return }
|
||||
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
test.Error("analyzer returned error:\n" + errors.Format(err))
|
||||
return
|
||||
}
|
||||
|
||||
unit := address.UUID()
|
||||
nickname, ok := address.Nickname()
|
||||
if !ok {
|
||||
test.Errorf("could not generate nickname for %v", address)
|
||||
return
|
||||
}
|
||||
nicknames[nickname] = unit
|
||||
}
|
||||
|
||||
// main
|
||||
address := entity.Address("main.fspl")
|
||||
ast, ok := treeOf(test, address, main, false)
|
||||
if !ok { return }
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
test.Error("analyzer returned error:\n" + errors.Format(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func testUnitsErr (
|
||||
test *testing.T,
|
||||
errFile string,
|
||||
errMessage string,
|
||||
errRow int,
|
||||
errStart int,
|
||||
|
||||
main string,
|
||||
dependencies ...string,
|
||||
) {
|
||||
if len(dependencies) % 2 != 0 {
|
||||
panic("dependencies len must be even. forget an address?")
|
||||
}
|
||||
tree := Tree { }
|
||||
nicknames := make(map[string] uuid.UUID)
|
||||
|
||||
// dependencies
|
||||
for index := 0; index < len(dependencies) - 1; index += 2 {
|
||||
address := entity.Address(dependencies[index])
|
||||
source := dependencies[index + 1]
|
||||
ast, ok := treeOf(test, address, source, true)
|
||||
if !ok { return }
|
||||
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
compareErr(test, address, errFile, errMessage, errRow, errStart, err)
|
||||
return
|
||||
}
|
||||
|
||||
unit := address.UUID()
|
||||
nickname, ok := address.Nickname()
|
||||
if !ok {
|
||||
test.Errorf("could not generate nickname for %v", address)
|
||||
return
|
||||
}
|
||||
nicknames[nickname] = unit
|
||||
}
|
||||
|
||||
// main
|
||||
address := entity.Address("main.fspl")
|
||||
ast, ok := treeOf(test, address, main, false)
|
||||
if !ok { return }
|
||||
err := tree.Analyze(address.UUID(), nicknames, ast)
|
||||
if err != nil {
|
||||
compareErr(test, address, errFile, errMessage, errRow, errStart, err)
|
||||
return
|
||||
}
|
||||
|
||||
test.Error("analyzer did not return error")
|
||||
}
|
||||
|
||||
func treeOf (test *testing.T, address entity.Address, input string, skim bool) (fsplParser.Tree, bool) {
|
||||
ast := fsplParser.Tree { }
|
||||
lx, err := lexer.LexReader (
|
||||
string(address),
|
||||
strings.NewReader(input))
|
||||
if err != nil {
|
||||
test.Error("lexer returned error:\n" + errors.Format(err))
|
||||
return ast, false
|
||||
}
|
||||
if skim {
|
||||
err = ast.Skim(lx)
|
||||
} else {
|
||||
err = ast.Parse(lx)
|
||||
}
|
||||
if err != nil {
|
||||
test.Error("parser returned error:\n" + errors.Format(err))
|
||||
return ast, false
|
||||
}
|
||||
|
||||
return ast, true
|
||||
}
|
||||
|
||||
func compareErr (
|
||||
test *testing.T,
|
||||
address entity.Address,
|
||||
errFile string,
|
||||
errMessage string,
|
||||
errRow int,
|
||||
errStart int,
|
||||
err error,
|
||||
) bool {
|
||||
got := err.(errors.Error)
|
||||
gotMessage := got.Error()
|
||||
gotFile := got.Position().File
|
||||
gotRow := got.Position().Row + 1
|
||||
gotStart := got.Position().Start + 1
|
||||
|
||||
correct :=
|
||||
gotFile == errFile &&
|
||||
gotMessage == errMessage &&
|
||||
gotRow == errRow &&
|
||||
gotStart == errStart
|
||||
if correct { return true }
|
||||
|
||||
test.Log("errors do not match:")
|
||||
if gotMessage != errMessage {
|
||||
test.Log("- messages do not match:")
|
||||
test.Logf(" [%s]", gotMessage)
|
||||
test.Logf(" [%s]", errMessage)
|
||||
}
|
||||
if gotRow != errRow {
|
||||
test.Logf("- rows do not match: (%d, %d)", gotRow, errRow)
|
||||
}
|
||||
if gotStart != errStart {
|
||||
test.Logf("- columns do not match: (%d, %d)", gotStart, errStart)
|
||||
}
|
||||
test.Log("got:\n" + errors.Format(got))
|
||||
test.Logf("correct:\n%v:%v: %v", errRow, errStart, errMessage)
|
||||
test.Fail()
|
||||
return false
|
||||
}
|
||||
|
|
192
analyzer/tree.go
192
analyzer/tree.go
|
@ -1,27 +1,39 @@
|
|||
package analyzer
|
||||
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/parser"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/fspl"
|
||||
|
||||
// Tree is a semantic tree. It contains the same constructs as the syntax tree,
|
||||
// but with their semantic information filled in.
|
||||
type Tree struct {
|
||||
rawTypes map[string] *entity.Typedef
|
||||
rawFunctions map[string] *entity.Function
|
||||
rawMethods map[string] *entity.Method
|
||||
ast parser.Tree
|
||||
Types map[entity.Key] *entity.Typedef
|
||||
Functions map[entity.Key] *entity.Function
|
||||
|
||||
Types map[string] entity.Type
|
||||
Functions map[string] *entity.Function
|
||||
unit uuid.UUID
|
||||
ast fsplParser.Tree
|
||||
nicknames map[string] uuid.UUID
|
||||
rawTypes map[entity.Key] *entity.Typedef
|
||||
rawFunctions map[entity.Key] *entity.Function
|
||||
rawMethods map[entity.Key] *entity.Method
|
||||
|
||||
scopeContextManager
|
||||
incompleteTypes map[entity.Key] *entity.Typedef
|
||||
}
|
||||
|
||||
// Analyze takes in an AST and analyzes its contents within the context of this
|
||||
// semantic tree. Analyzed entities will be added to the tree.
|
||||
func (this *Tree) Analyze (ast parser.Tree) error {
|
||||
func (this *Tree) Analyze (
|
||||
unit uuid.UUID,
|
||||
nicknames map[string] uuid.UUID,
|
||||
ast fsplParser.Tree,
|
||||
) error {
|
||||
this.ensure()
|
||||
this.ast = ast
|
||||
this.resetRawMaps()
|
||||
this.ast = ast
|
||||
this.unit = unit
|
||||
this.nicknames = nicknames
|
||||
err := this.assembleRawMaps()
|
||||
if err != nil { return err }
|
||||
return this.analyzeDeclarations()
|
||||
|
@ -29,72 +41,142 @@ func (this *Tree) Analyze (ast parser.Tree) error {
|
|||
|
||||
func (this *Tree) assembleRawMaps () error {
|
||||
for _, declaration := range this.ast.Declarations {
|
||||
switch declaration.(type) {
|
||||
switch declaration := declaration.(type) {
|
||||
case *entity.Typedef:
|
||||
ty := declaration.(*entity.Typedef)
|
||||
err := this.rawNameUnique(ty.Pos, ty.Name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position, key)
|
||||
if err != nil { return err }
|
||||
this.rawTypes[ty.Name] = ty
|
||||
declaration.Methods = make(map[string] *entity.Method)
|
||||
this.rawTypes[key] = declaration
|
||||
|
||||
case *entity.Function:
|
||||
function := declaration.(*entity.Function)
|
||||
err := this.rawNameUnique (
|
||||
function.Pos,
|
||||
function.Signature.Name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.Signature.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position, key)
|
||||
if err != nil { return err }
|
||||
this.rawFunctions[function.Signature.Name] = function
|
||||
this.rawFunctions[key] = declaration
|
||||
|
||||
case *entity.Method:
|
||||
method := declaration.(*entity.Method)
|
||||
name := method.TypeName + "." + method.Signature.Name
|
||||
err := this.rawNameUnique(method.Pos, name)
|
||||
key := entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: declaration.TypeName,
|
||||
Method: declaration.Signature.Name,
|
||||
}
|
||||
err := this.topLevelNameAvailable(declaration.Position, key)
|
||||
if err != nil { return err }
|
||||
this.rawMethods[name] = method
|
||||
this.rawMethods[key] = declaration
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) rawNameUnique (currentPos lexer.Position, name string) error {
|
||||
var pos lexer.Position
|
||||
var unique = true
|
||||
|
||||
if ty, isType := this.rawTypes[name]; isType {
|
||||
pos = ty.Pos
|
||||
unique = false
|
||||
func (this *Tree) topLevelNameAvailable (currentPos errors.Position, key entity.Key) error {
|
||||
if _, isPrimitive := primitiveTypes[key.Name]; isPrimitive {
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow primitive %s",
|
||||
key.Name)
|
||||
}
|
||||
if function, isFunction := this.rawFunctions[name]; isFunction {
|
||||
pos = function.Pos
|
||||
unique = false
|
||||
if _, isBuiltin := builtinTypes[key.Name]; isBuiltin {
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow builtin %s",
|
||||
key.Name)
|
||||
}
|
||||
if method, isMethod := this.rawMethods[name]; isMethod {
|
||||
pos = method.Pos
|
||||
unique = false
|
||||
}
|
||||
|
||||
if unique {
|
||||
return nil
|
||||
} else {
|
||||
return participle.Errorf (
|
||||
if ty, isType := this.rawTypes[key]; isType {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
name, pos)
|
||||
key.Name, ty.Position)
|
||||
}
|
||||
if function, isFunction := this.rawFunctions[key]; isFunction {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
key.Name, function.Position)
|
||||
}
|
||||
if method, isMethod := this.rawMethods[key]; isMethod {
|
||||
return errors.Errorf (
|
||||
currentPos, "%s.%s already declared at %v",
|
||||
key.Name, key.Method, method.Position)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDeclarations () error {
|
||||
for name := range this.rawTypes {
|
||||
ty, err := this.analyzeTypedef(name)
|
||||
for key, rawType := range this.rawTypes {
|
||||
ty, err := this.analyzeTypedef(rawType.Position, key, false)
|
||||
if err != nil { return err }
|
||||
this.Types[key] = ty
|
||||
}
|
||||
for key, rawFunction := range this.rawFunctions {
|
||||
_, err := this.analyzeFunction(rawFunction.Position, key)
|
||||
if err != nil { return err }
|
||||
}
|
||||
for _, rawMethod := range this.rawMethods {
|
||||
_, err := this.analyzeMethod (
|
||||
rawMethod.Position,
|
||||
entity.Key {
|
||||
Unit: this.unit,
|
||||
Name: rawMethod.TypeName,
|
||||
Method: rawMethod.Signature.Name,
|
||||
})
|
||||
if err != nil { return err }
|
||||
this.Types[name] = ty
|
||||
}
|
||||
// TODO functions
|
||||
// TODO methods
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) resolveNickname (pos errors.Position, nickname string) (uuid.UUID, error) {
|
||||
if nickname == "" { return this.unit, nil }
|
||||
|
||||
fail := func () (uuid.UUID, error) {
|
||||
return uuid.UUID { }, errors.Errorf (
|
||||
pos, "no unit named %s",
|
||||
nickname)
|
||||
}
|
||||
|
||||
if this.nicknames == nil { return fail() }
|
||||
unit, ok := this.nicknames[nickname]
|
||||
if !ok { return fail() }
|
||||
|
||||
return unit, nil
|
||||
}
|
||||
|
||||
func (this *Tree) ensure () {
|
||||
if this.rawTypes != nil { return }
|
||||
this.rawTypes = make(map[string] *entity.Typedef)
|
||||
this.rawFunctions = make(map[string] *entity.Function)
|
||||
this.rawMethods = make(map[string] *entity.Method)
|
||||
this.Types = make(map[string] entity.Type)
|
||||
this.Functions = make(map[string] *entity.Function)
|
||||
this.resetRawMaps()
|
||||
this.incompleteTypes = make(map[entity.Key] *entity.Typedef)
|
||||
this.Types = make(map[entity.Key] *entity.Typedef)
|
||||
this.Functions = make(map[entity.Key] *entity.Function)
|
||||
|
||||
for name, ty := range builtinTypes {
|
||||
// builtin types have a zero UUID in their key
|
||||
this.Types[entity.Key { Name: name }] = &entity.Typedef {
|
||||
Acc: entity.AccessPublic,
|
||||
Name: name,
|
||||
Type: ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Tree) resetRawMaps () {
|
||||
if this.rawTypes != nil &&
|
||||
this.rawFunctions != nil &&
|
||||
this.rawMethods != nil {
|
||||
|
||||
if len(this.rawTypes) > 0 {
|
||||
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
||||
}
|
||||
if len(this.rawFunctions) > 0 {
|
||||
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
||||
}
|
||||
if len(this.rawMethods) > 0 {
|
||||
this.rawMethods = make(map[entity.Key] *entity.Method)
|
||||
}
|
||||
} else {
|
||||
this.rawTypes = make(map[entity.Key] *entity.Typedef)
|
||||
this.rawFunctions = make(map[entity.Key] *entity.Function)
|
||||
this.rawMethods = make(map[entity.Key] *entity.Method)
|
||||
}
|
||||
}
|
||||
|
|
290
analyzer/type.go
290
analyzer/type.go
|
@ -1,100 +1,278 @@
|
|||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeTypedef (pos lexer.Position, name string) (entity.Type, error) {
|
||||
if ty, exists := this.Types[name]; exists {
|
||||
return ty
|
||||
func (this *Tree) analyzeTypedef (
|
||||
pos errors.Position,
|
||||
key entity.Key,
|
||||
acceptIncomplete bool,
|
||||
) (
|
||||
*entity.Typedef,
|
||||
error,
|
||||
) {
|
||||
// return a builtin if it exists (search types with zero uuid)
|
||||
if definition, exists := this.Types[entity.Key { Name: key.Name }]; exists {
|
||||
return definition, nil
|
||||
}
|
||||
|
||||
// return if exists already
|
||||
if definition, exists := this.Types[key]; exists {
|
||||
return definition, nil
|
||||
}
|
||||
|
||||
definition, exists := this.RawTypes[name]
|
||||
if !exists {
|
||||
return participle.Errorf(pos, "no type named %s", name)
|
||||
// check if incomplete
|
||||
if definition, incomplete := this. incompleteTypes[key]; incomplete {
|
||||
if acceptIncomplete {
|
||||
return definition, nil
|
||||
} else {
|
||||
return nil, errors.Errorf (
|
||||
pos, "type %s cannot be used in this context",
|
||||
key.Name)
|
||||
}
|
||||
}
|
||||
return this.analyzeType(definition.Type)
|
||||
|
||||
// analyze if it still needs to be analyzed
|
||||
if definition, exists := this.rawTypes[key]; exists {
|
||||
// update unit
|
||||
definition.Unit = key.Unit
|
||||
|
||||
// analyze
|
||||
var err error
|
||||
definition.Type, err = this.analyzeTypeInternal (
|
||||
definition.Type, definition, false)
|
||||
return definition, err
|
||||
}
|
||||
|
||||
// if we couldn't get the type, error
|
||||
return nil, errors.Errorf(pos, "no type named %s", key.Name)
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeType (ty entity.Type) (entity.Type, error) {
|
||||
if ty == entity.Void() { return ty }
|
||||
var err error
|
||||
func (this *Tree) analyzeType (
|
||||
ty entity.Type,
|
||||
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 ty, err }
|
||||
for name, member := range ty.MemberMap {
|
||||
ty.MemberMap[name], err = this.analyzeType(member.Type)
|
||||
ty.MemberMap[name].Ty,
|
||||
err = this.analyzeType(member.Ty, false)
|
||||
if err != nil { return ty, 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)
|
||||
updatePseudoCompleteInfo()
|
||||
if err != nil { return ty, err }
|
||||
for name, behavior := range ty.BehaviorMap {
|
||||
ty.BehaviorMap[name], err = this.analyzeBehavior(behavior)
|
||||
if err != nil { return ty, err }
|
||||
}
|
||||
return ty, nil
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// typeRestricted checks if the given type is restricted or private.
|
||||
func (this *Tree) typeRestricted (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.AccessRestricted {
|
||||
return errors.Errorf(pos, "type %v is restricted", 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) 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,180 @@
|
|||
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 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.
|
||||
// -m, --format Output format (.s, .o, .ll)
|
||||
// -o, --output Output filename
|
||||
// -O, --optimization Optimization level (0-3)
|
||||
package main
|
|
@ -0,0 +1,79 @@
|
|||
package main
|
||||
|
||||
import "os"
|
||||
import "io"
|
||||
import "path/filepath"
|
||||
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"
|
||||
|
||||
func main () {
|
||||
// instantiate the compiler
|
||||
// FIXME: perhaps we want different paths on Windows?
|
||||
comp := new(compiler.Compiler)
|
||||
comp.Writer = os.Stderr
|
||||
comp.Resolver = compiler.NewResolver (
|
||||
"/usr/local/src/fspl",
|
||||
"/usr/src/fspl",
|
||||
"/usr/local/incude/fspl",
|
||||
"/usr/include/fspl")
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
comp.Errorln(err)
|
||||
os.Exit(2)
|
||||
}
|
||||
comp.Resolver.AddPathFront (
|
||||
filepath.Join(homeDir, ".local/src/fspl"),
|
||||
filepath.Join(homeDir, ".local/include/fspl"))
|
||||
|
||||
// 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.")
|
||||
format := cli.NewInputFlag (
|
||||
'm', "format",
|
||||
"Output format (.s, .o, .ll)", "",
|
||||
cli.NewValSet(".s", ".o", ".ll"))
|
||||
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"))
|
||||
|
||||
application := cli.New (
|
||||
"Compile FSPL source files",
|
||||
cli.NewHelp(),
|
||||
debug,
|
||||
quiet,
|
||||
format,
|
||||
output,
|
||||
optimization)
|
||||
|
||||
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.Format = format.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,107 @@
|
|||
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]
|
||||
_, 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]
|
||||
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,144 @@
|
|||
package compiler
|
||||
|
||||
import "io/fs"
|
||||
import "embed"
|
||||
import "strings"
|
||||
import "testing"
|
||||
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"
|
||||
|
||||
//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.Format = ".o"
|
||||
comp.Debug = true
|
||||
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, 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()
|
||||
defer func () {
|
||||
test.Log("COMPILER LOG (main unit):\n" + compOutputBuilder.String())
|
||||
} ()
|
||||
comp.Writer = compOutputBuilder
|
||||
comp.Output = filepath.Join(temp, "output.o")
|
||||
|
||||
|
||||
// compile to object file
|
||||
err := comp.CompileUnit(address)
|
||||
if err != nil {
|
||||
test.Fatal("compiler returned error:", errors.Format(err))
|
||||
}
|
||||
|
||||
// link the object file into an executable
|
||||
executablePath := filepath.Join(temp, "output")
|
||||
linkCommand := exec.Command("clang", append (
|
||||
clangArgs,
|
||||
comp.Output,
|
||||
"-o",
|
||||
executablePath)...)
|
||||
linkCommand.Stdout = compOutputBuilder
|
||||
linkCommand.Stderr = compOutputBuilder
|
||||
test.Log("running link command: ", linkCommand)
|
||||
err = linkCommand.Run()
|
||||
if err != nil {
|
||||
test.Fatal("error linking executable:", err)
|
||||
}
|
||||
|
||||
// run the executable file and check its output
|
||||
executableCommand := exec.Command(executablePath, args...)
|
||||
stdoutBuilder := new(strings.Builder)
|
||||
executableCommand.Stdin = strings.NewReader(stdin)
|
||||
executableCommand.Stdout = stdoutBuilder
|
||||
test.Log("running executable command: ", executableCommand)
|
||||
err = executableCommand.Run()
|
||||
|
||||
// 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,158 @@
|
|||
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 }
|
||||
|
||||
irModule, err := native.NativeTarget().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.Format == "" {
|
||||
this.Format = filepath.Ext(this.Output)
|
||||
}
|
||||
if this.Format == "" {
|
||||
this.Format = ".o"
|
||||
}
|
||||
|
||||
// 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 := address.Nickname()
|
||||
if !ok { nickname = "output" }
|
||||
this.Output = nickname + this.Format
|
||||
}
|
||||
|
||||
// do something based on the output extension
|
||||
// TODO: add .so
|
||||
switch this.Format {
|
||||
case ".s":
|
||||
return this.CompileIRModule(irModule, "asm")
|
||||
case ".o":
|
||||
return this.CompileIRModule(irModule, "obj")
|
||||
case ".ll":
|
||||
file, err := os.Create(this.Output)
|
||||
if err != nil { return err }
|
||||
defer file.Close()
|
||||
_, err = irModule.WriteTo(file)
|
||||
return err
|
||||
case "":
|
||||
return errors.New(fmt.Sprint (
|
||||
"output file has no extension, ",
|
||||
"could not determine output type"))
|
||||
default:
|
||||
return errors.New(fmt.Sprintf (
|
||||
"unknown output type %s", this.Format))
|
||||
}
|
||||
}
|
||||
func (this *Compiler) CompileIRModule (module *llvm.Module, filetype string) 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 string) (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 != "obj" {
|
||||
return "", nil, errors.New("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,248 @@
|
|||
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/parser/fspl"
|
||||
import "git.tebibyte.media/fspl/fspl/parser/meta"
|
||||
import ferrors "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
type Compiler struct {
|
||||
Resolver
|
||||
cli.Logger
|
||||
|
||||
Output string
|
||||
Optimization string
|
||||
Format string
|
||||
}
|
||||
|
||||
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,66 @@
|
|||
package compiler
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHelloWorld (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/hello", nil,
|
||||
"", "Hello, world!\n",
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestExitCode13 (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/exitcode13", nil,
|
||||
"", "",
|
||||
13,
|
||||
)}
|
||||
|
||||
func TestSystemInclude (test *testing.T) {
|
||||
testUnit (test,
|
||||
"/test-data/data/systeminclude", nil,
|
||||
"", "Hello, /usr/include!\n",
|
||||
0,
|
||||
)}
|
||||
|
||||
func TestSystemSrc (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/systemsrc", dependencies,
|
||||
"", "Hello, /usr/src!\n",
|
||||
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/simpleinterface", nil,
|
||||
"", "",
|
||||
9,
|
||||
)}
|
||||
|
||||
func TestWriterInterface (test *testing.T) {
|
||||
dependencies := []string {
|
||||
compileDependency(test, "io"),
|
||||
}
|
||||
testUnit (test,
|
||||
"/test-data/data/writer", dependencies,
|
||||
"", "well hello their\n",
|
||||
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,114 @@
|
|||
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 os.DirFS("/").
|
||||
func NewResolver (path ...string) Resolver {
|
||||
return Resolver {
|
||||
FS: os.DirFS("/"),
|
||||
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
|
||||
// path relative to "/" and opens the file.
|
||||
func openAbsolute (filesystem fs.FS, path string) (fs.File, error) {
|
||||
path, err := filepath.Rel("/", path)
|
||||
if err != nil { return nil, err }
|
||||
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 @@
|
|||
'241467f1-219a-4f13-8cdf-32ef2ad88277'
|
|
@ -0,0 +1,6 @@
|
|||
[puts string:*Byte]: Index 'puts'
|
||||
|
||||
[main]: I32 'main' = {
|
||||
[puts 'Hello, world!']
|
||||
0
|
||||
}
|
|
@ -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,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]]
|
129
design/spec.md
129
design/spec.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Top level
|
||||
### 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 +17,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
|
||||
|
@ -38,6 +38,35 @@ 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.
|
||||
|
||||
## 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 Byte.
|
||||
### 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
|
||||
Location expressions are special expressions that only refer to the location of
|
||||
|
@ -47,7 +76,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 +85,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 +119,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 +140,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 +166,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.
|
||||
|
@ -201,16 +258,21 @@ 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.
|
||||
### 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>
|
||||
<typedef> -> <typeIdentifier> ":" <type>
|
||||
<function> -> <signature> ["=" <expression>]
|
||||
<method> -> <identifier> "." <function>
|
||||
<method> -> <typeIdentifier> "." <function>
|
||||
|
||||
<type> -> <namedType>
|
||||
| <pointerType>
|
||||
|
@ -218,21 +280,24 @@ 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>* ")"
|
||||
|
||||
<expression> -> <intLiteral>
|
||||
| <floatLiteral>
|
||||
| <stringLiteral>
|
||||
| <arrayLiteral>
|
||||
| <structLiteral>
|
||||
| <booleanLiteral>
|
||||
| <variable>
|
||||
| <declaration>
|
||||
| <call>
|
||||
| <subscript>
|
||||
| <length>
|
||||
| <dereference>
|
||||
| <reference>
|
||||
| <valueCast>
|
||||
|
@ -240,24 +305,26 @@ 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> "]"
|
||||
<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>]
|
||||
|
@ -270,16 +337,18 @@ 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>
|
||||
<signature> -> "[" <identifier> <declaration>* "]" [":" <type>]
|
||||
<identifier> -> /[a-z][A-Za-z]*/
|
||||
<typeIdentifier> -> /[A-Z][A-Za-z]*/
|
||||
<operator> -> "+" | "++" | "-" | "--" | "*" | "/" | "%"
|
||||
| "!!" | "||" | "&&" | "^^"
|
||||
| "!" | "|" | "&" | "^" | "<<" | ">>"
|
||||
| "<" | ">" | "<=" | ">=" | "="
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
# 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`
|
||||
|
||||
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,17 +1,14 @@
|
|||
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 {
|
||||
Type () Type
|
||||
HasExplicitType () bool
|
||||
expression ()
|
||||
Statement
|
||||
}
|
||||
|
||||
// Statement is any construct that can be placed inside of a block expression.
|
||||
type Statement interface {
|
||||
statement()
|
||||
}
|
||||
|
||||
// Variable specifies a named variable. It can be assigned to a type matching
|
||||
|
@ -19,11 +16,16 @@ type Statement interface {
|
|||
// 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
|
||||
Position errors.Position
|
||||
Name string
|
||||
|
||||
// Semantics
|
||||
Declaration *Declaration
|
||||
}
|
||||
func (*Variable) expression(){}
|
||||
func (*Variable) statement(){}
|
||||
func (this *Variable) Type () Type { return this.Declaration.Type() }
|
||||
func (this *Variable) HasExplicitType () bool { return true }
|
||||
func (this *Variable) String () string {
|
||||
return this.Name
|
||||
}
|
||||
|
@ -34,51 +36,103 @@ func (this *Variable) String () string {
|
|||
// 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:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Name string
|
||||
Ty Type
|
||||
}
|
||||
func (*Declaration) expression(){}
|
||||
func (*Declaration) statement(){}
|
||||
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.
|
||||
// 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
|
||||
Position errors.Position
|
||||
UnitNickname string
|
||||
Name string
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Function *Function
|
||||
Unit uuid.UUID
|
||||
}
|
||||
func (*Call) expression(){}
|
||||
func (*Call) statement(){}
|
||||
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
|
||||
// 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
|
||||
Position errors.Position
|
||||
Source Expression
|
||||
Name string
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Method *Method
|
||||
Behavior *Signature
|
||||
Ty Type
|
||||
}
|
||||
func (*MethodCall) expression(){}
|
||||
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 + "]"
|
||||
}
|
||||
|
||||
// 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
|
||||
Position errors.Position
|
||||
Slice Expression
|
||||
Offset Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Subscript) expression(){}
|
||||
func (*Subscript) statement(){}
|
||||
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, "]")
|
||||
}
|
||||
|
||||
// Slice adjusts the start and end points of a slice relative to its current
|
||||
|
@ -86,36 +140,60 @@ func (this *Subscript) String () string {
|
|||
// 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
|
||||
Position errors.Position
|
||||
Slice Expression
|
||||
Start Expression
|
||||
End Expression
|
||||
}
|
||||
func (*Slice) expression(){}
|
||||
func (*Slice) statement(){}
|
||||
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 + "]"
|
||||
}
|
||||
|
||||
// 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
|
||||
Position errors.Position
|
||||
Slice Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Length) expression(){}
|
||||
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, "]")
|
||||
}
|
||||
|
||||
// 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
|
||||
Position errors.Position
|
||||
Pointer Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Dereference) expression(){}
|
||||
func (*Dereference) statement(){}
|
||||
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, "]")
|
||||
}
|
||||
|
@ -128,11 +206,16 @@ 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
|
||||
Position errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Reference) expression(){}
|
||||
func (*Reference) statement(){}
|
||||
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, "]")
|
||||
}
|
||||
|
@ -141,14 +224,15 @@ func (this *Reference) String () string {
|
|||
// 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:" @@ ']' "`
|
||||
Position errors.Position
|
||||
Ty Type
|
||||
Value Expression
|
||||
}
|
||||
func (*ValueCast) expression(){}
|
||||
func (*ValueCast) statement(){}
|
||||
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, "]")
|
||||
}
|
||||
|
||||
// Bit casting takes the raw data in memory of a certain value and re-interprets
|
||||
|
@ -156,14 +240,15 @@ func (this *ValueCast) String () string {
|
|||
// 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:" @@ ']' "`
|
||||
Position errors.Position
|
||||
Ty Type
|
||||
Value Expression
|
||||
}
|
||||
func (*BitCast) expression(){}
|
||||
func (*BitCast) statement(){}
|
||||
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, "]")
|
||||
}
|
||||
|
||||
// Operations perform math, logic, or bit manipulation on values. They accept
|
||||
|
@ -171,13 +256,19 @@ func (this *BitCast) String () string {
|
|||
// 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
|
||||
Position errors.Position
|
||||
Operator Operator
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Operation) expression(){}
|
||||
func (*Operation) statement(){}
|
||||
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 {
|
||||
|
@ -193,14 +284,19 @@ func (this *Operation) String () string {
|
|||
// expression.
|
||||
type Block struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Steps []Statement `parser:" '{' @@* '}' "`
|
||||
Position errors.Position
|
||||
Steps []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
Scope
|
||||
}
|
||||
func (*Block) expression(){}
|
||||
func (*Block) statement(){}
|
||||
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 {
|
||||
|
@ -217,43 +313,41 @@ 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
|
||||
Position errors.Position
|
||||
Source Expression
|
||||
Member string
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*MemberAccess) expression(){}
|
||||
func (*MemberAccess) statement(){}
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
Position errors.Position
|
||||
Condition Expression
|
||||
True Expression
|
||||
False Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*IfElse) expression(){}
|
||||
func (*IfElse) statement(){}
|
||||
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 {
|
||||
|
@ -270,11 +364,20 @@ 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
|
||||
Position errors.Position
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Loop) expression(){}
|
||||
func (*Loop) statement(){}
|
||||
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)
|
||||
}
|
||||
|
@ -282,11 +385,16 @@ func (this *Loop) String () string {
|
|||
// 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
|
||||
Position errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Loop *Loop
|
||||
}
|
||||
func (*Break) expression(){}
|
||||
func (*Break) statement(){}
|
||||
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]"
|
||||
|
@ -300,11 +408,16 @@ func (this *Break) String () string {
|
|||
// 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
|
||||
Position errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Declaration TopLevel
|
||||
}
|
||||
func (*Return) expression(){}
|
||||
func (*Return) statement(){}
|
||||
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]"
|
||||
|
@ -318,11 +431,13 @@ func (this *Return) String () string {
|
|||
// 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:" '=' @@ "`
|
||||
Position errors.Position
|
||||
Location Expression
|
||||
Value Expression
|
||||
}
|
||||
func (*Assignment) statement(){}
|
||||
func (*Assignment) expression(){}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// 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
|
||||
Position errors.Position
|
||||
Value int
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralInt) expression(){}
|
||||
func (*LiteralInt) statement(){}
|
||||
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)
|
||||
}
|
||||
|
@ -22,28 +28,70 @@ func (this *LiteralInt) String () string {
|
|||
// 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
|
||||
Position errors.Position
|
||||
Value float64
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralFloat) expression(){}
|
||||
func (*LiteralFloat) statement(){}
|
||||
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:
|
||||
// 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
|
||||
Position errors.Position
|
||||
ValueUTF8 string
|
||||
ValueUTF16 []uint16
|
||||
ValueUTF32 []rune
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralString) expression(){}
|
||||
func (this *LiteralString) Type () Type { return this.Ty }
|
||||
func (this *LiteralString) HasExplicitType () bool { return false }
|
||||
func (this *LiteralString) String () string {
|
||||
return Quote(this.ValueUTF8)
|
||||
}
|
||||
|
||||
// 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
|
||||
Position errors.Position
|
||||
Elements []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralArray) expression(){}
|
||||
func (*LiteralArray) statement(){}
|
||||
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 {
|
||||
|
@ -52,7 +100,7 @@ func (this *LiteralArray) String () string {
|
|||
return out + ")"
|
||||
}
|
||||
|
||||
// Struct is a composite structure literal. It can contain any number of
|
||||
// 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 +108,57 @@ 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
|
||||
Position errors.Position
|
||||
Members []*Member
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
MemberOrder []string
|
||||
MemberMap map[string] *Member
|
||||
}
|
||||
func (*LiteralStruct) expression(){}
|
||||
func (*LiteralStruct) statement(){}
|
||||
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 + ")"
|
||||
}
|
||||
|
||||
// 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
|
||||
Position errors.Position
|
||||
Value bool
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralBoolean) expression(){}
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
type LiteralNil struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralNil) expression(){}
|
||||
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,120 @@
|
|||
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 {
|
||||
Position errors.Position
|
||||
UUID uuid.UUID
|
||||
Dependencies []*Dependency
|
||||
}
|
||||
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 {
|
||||
directive()
|
||||
}
|
||||
|
||||
// Dependency is a metadata dependency listing.
|
||||
type Dependency struct {
|
||||
Position errors.Position
|
||||
Address Address
|
||||
Nickname string
|
||||
}
|
||||
func (*Dependency) directive () { }
|
||||
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))))
|
||||
}
|
|
@ -1,19 +1,29 @@
|
|||
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"
|
||||
|
||||
// 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
|
||||
Position 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) 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,13 +35,27 @@ func (this *Signature) String () string {
|
|||
}
|
||||
return out
|
||||
}
|
||||
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:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Name string
|
||||
Value Expression
|
||||
}
|
||||
func (this *Member) String () string {
|
||||
return fmt.Sprint(this.Name, ":", this.Value)
|
||||
|
@ -71,6 +95,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 +129,7 @@ var operatorToString = []string {
|
|||
"|",
|
||||
"&",
|
||||
"^",
|
||||
|
||||
// Bit manipulation
|
||||
"!!",
|
||||
"||",
|
||||
|
@ -111,11 +155,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,33 +1,100 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "encoding/base64"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// TopLevel is any construct that is placed at the root of a file.
|
||||
type TopLevel interface {
|
||||
topLevel ()
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// AccessRestricted causes a top-level entity to appear opaque to other
|
||||
// modules. Values of restricted types can be passed around, assigned
|
||||
// to eachother, and their methods can be called, but the implementation
|
||||
// of the type is entirely hidden. This access mode cannot be applied to
|
||||
// functions.
|
||||
AccessRestricted
|
||||
|
||||
// AccessPrivate disallows other modules from accessing a top-level
|
||||
// entity.
|
||||
AccessPrivate
|
||||
)
|
||||
|
||||
func (this Access) String () string {
|
||||
switch this {
|
||||
case AccessPrivate: return "-"
|
||||
case AccessRestricted: return "~"
|
||||
case AccessPublic: return "+"
|
||||
default: return fmt.Sprintf("entity.Access(%d)", this)
|
||||
}
|
||||
}
|
||||
|
||||
// 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:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Acc Access
|
||||
Name string
|
||||
Type Type
|
||||
|
||||
// Semantics
|
||||
Methods map[string] Method
|
||||
Unit uuid.UUID
|
||||
Methods map[string] *Method
|
||||
}
|
||||
func (*Typedef) topLevel(){}
|
||||
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
|
||||
}
|
||||
|
||||
// Function binds a global identifier and argument list to an expression which
|
||||
|
@ -37,46 +104,59 @@ func (this *Typedef) String () string {
|
|||
// these are typed.
|
||||
type Function struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Public bool `parser:" @'+'? "`
|
||||
Signature *Signature `parser:" @@ "`
|
||||
Body Expression `parser:" ( '=' @@ )? "`
|
||||
Position errors.Position
|
||||
Acc Access
|
||||
LinkName string
|
||||
Signature *Signature
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Unit uuid.UUID
|
||||
Scope
|
||||
}
|
||||
func (*Function) topLevel(){}
|
||||
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
|
||||
}
|
||||
|
||||
// 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:" ( '=' @@ )? "`
|
||||
Position errors.Position
|
||||
Acc Access
|
||||
TypeName string
|
||||
LinkName string
|
||||
Signature *Signature
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Unit uuid.UUID
|
||||
Type Type
|
||||
This *Declaration
|
||||
Scope
|
||||
}
|
||||
func (*Method) topLevel(){}
|
||||
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
|
||||
}
|
||||
|
|
252
entity/type.go
252
entity/type.go
|
@ -1,108 +1,278 @@
|
|||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "github.com/google/uuid"
|
||||
import "git.tebibyte.media/fspl/fspl/errors"
|
||||
|
||||
// Type is any type notation.
|
||||
type Type interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Equals reports whether this type is equivalent to another type.
|
||||
Equals (ty Type) bool
|
||||
|
||||
// Access reports the access permission of the type.
|
||||
Access () Access
|
||||
|
||||
// Unit reports the unit that the type was defined in.
|
||||
Unit () uuid.UUID
|
||||
|
||||
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
|
||||
Position 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) 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) 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:" '*' @@ "`
|
||||
Position errors.Position
|
||||
Referenced Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypePointer) ty(){}
|
||||
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) 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:" '*' ':' @@ "`
|
||||
Position errors.Position
|
||||
Element Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeSlice) ty(){}
|
||||
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) 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:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Length int
|
||||
Element Type
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeArray) ty(){}
|
||||
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) 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:" '(' @@+ ')' "`
|
||||
Position 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) 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) 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:" '(' @@+ ')' "`
|
||||
Position 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) 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) 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
|
||||
}
|
||||
|
||||
// TypeInt represents any signed or unsigned integer type.
|
||||
type TypeInt struct {
|
||||
Position errors.Position
|
||||
Width int
|
||||
Signed bool
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeInt) ty(){}
|
||||
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) 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 {
|
||||
Position errors.Position
|
||||
Width int
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeFloat) ty(){}
|
||||
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) 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 {
|
||||
Position errors.Position
|
||||
Signed bool
|
||||
|
||||
// Semantics
|
||||
Acc Access
|
||||
Unt uuid.UUID
|
||||
}
|
||||
func (*TypeWord) ty(){}
|
||||
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) 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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package errors
|
||||
|
||||
import "testing"
|
||||
import "strings"
|
||||
|
||||
func TestError (test *testing.T) {
|
||||
testError (test,
|
||||
`example.fspl:11:7: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^`,
|
||||
Errorf (
|
||||
Position {
|
||||
File: "example.fspl",
|
||||
Line: "lorem ipsum dolor",
|
||||
Row: 10,
|
||||
Start: 6,
|
||||
End: 11,
|
||||
},
|
||||
"some error"))
|
||||
}
|
||||
|
||||
func TestErrorTab (test *testing.T) {
|
||||
testError (test,
|
||||
`example.fspl:11:8: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^`,
|
||||
Errorf (
|
||||
Position {
|
||||
File: "example.fspl",
|
||||
Line: "\tlorem\tipsum\tdolor",
|
||||
Row: 10,
|
||||
Start: 7,
|
||||
End: 12,
|
||||
},
|
||||
"some error"))
|
||||
}
|
||||
|
||||
func TestErrorTabInBetween (test *testing.T) {
|
||||
testError (test,
|
||||
`example.fspl:11:8: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^^^^^`,
|
||||
Errorf (
|
||||
Position {
|
||||
File: "example.fspl",
|
||||
Line: "\tlorem\tipsum\tdolor",
|
||||
Row: 10,
|
||||
Start: 7,
|
||||
End: 14,
|
||||
},
|
||||
"some error"))
|
||||
}
|
||||
|
||||
func TestGetXInTabbedString (test *testing.T) {
|
||||
getXCase := func (line string, column, correct int) {
|
||||
x := getXInTabbedString(line, column)
|
||||
if x != correct {
|
||||
test.Logf("[%s]: %d", formatTabs(line), column)
|
||||
test.Log("expecting", correct, "got", x)
|
||||
test.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
getXCase("\t", 1, 8)
|
||||
getXCase("x\ty", 1, 1)
|
||||
getXCase("x\ty", 2, 8)
|
||||
getXCase("x\tyyy", 3, 9)
|
||||
getXCase("x\ty", 3, 9)
|
||||
getXCase("x\tyyyy\tzy", 7, 16)
|
||||
}
|
||||
|
||||
|
||||
func TestFormatTabs (test *testing.T) {
|
||||
fmtCase := func (line string, correct string) {
|
||||
got := formatTabs(line)
|
||||
if got != correct {
|
||||
test.Logf("[%s]", strings.ReplaceAll(line, "\t", "\\t"))
|
||||
test.Logf("expecting [%s] got [%s]", correct, got)
|
||||
test.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
fmtCase("\t", " ")
|
||||
fmtCase("\ty", " y")
|
||||
fmtCase("x\ty", "x y")
|
||||
fmtCase("x\tyyyy\tz", "x yyyy z")
|
||||
fmtCase("x\tyyyyyyy\tz", "x yyyyyyy z")
|
||||
fmtCase("\tjustice!\tz", " justice! z")
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package errors
|
||||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
|
||||
// Position stores the position of something within a file. For memory's sake,
|
||||
// positions in the same file should be created using the same File string, and
|
||||
// positions on the same line should be created using the same Line string.
|
||||
type Position struct {
|
||||
File string // The name of the file
|
||||
Line string // the text of the line the position is on
|
||||
|
||||
Row int // Line number, starting at 0
|
||||
Start int // Starting column number, starting at 0
|
||||
End int // Terminating column number, starting at 0
|
||||
}
|
||||
|
||||
// String returns a string representation of the position of the form:
|
||||
// FILE:ROW+1:START+1
|
||||
// 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.
|
||||
func (pos Position) String () string {
|
||||
return fmt.Sprintf("%s:%d:%d", pos.File, pos.Row + 1, pos.Start + 1)
|
||||
}
|
||||
|
||||
// Format produces a formatted printout of where the position is in its file.
|
||||
func (pos Position) Format () string {
|
||||
line := formatTabs(pos.Line)
|
||||
output := fmt.Sprintf("%-4d | %s\n ", pos.Row + 1, line)
|
||||
|
||||
start := pos.Start
|
||||
end := pos.End
|
||||
if end <= start { end = start + 1 }
|
||||
start = getXInTabbedString(pos.Line, start)
|
||||
end = getXInTabbedString(pos.Line, end)
|
||||
|
||||
for index := range line {
|
||||
if index >= end { break }
|
||||
if index >= start{
|
||||
output += "^"
|
||||
} else {
|
||||
output += " "
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func getXInTabbedString (tabbed string, column int) int {
|
||||
var x int
|
||||
for index, ch := range tabbed {
|
||||
if index >= column { return x }
|
||||
if ch == '\t' {
|
||||
nextStop := ((x / 8) + 1) * 8
|
||||
x = nextStop - 1
|
||||
}
|
||||
x ++
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func formatTabs (tabbed string) string {
|
||||
var result string
|
||||
var x int
|
||||
for _, ch := range tabbed {
|
||||
if ch == '\t' {
|
||||
nextStop := ((x / 8) + 1) * 8
|
||||
result += strings.Repeat(" ", nextStop - x)
|
||||
x = nextStop - 1
|
||||
} else {
|
||||
result += string(ch)
|
||||
}
|
||||
x ++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Union attempts to return the union of two positions. Since a position cannot
|
||||
// span multiple lines or files, the resulting position will be on the same line
|
||||
// and in the same file as the method reciever.
|
||||
func (pos Position) Union (other Position) Position {
|
||||
switch {
|
||||
case other.File != pos.File:
|
||||
case other.Row < pos.Row: pos.Start = 0
|
||||
case other.Row > pos.Row: pos.End = len(pos.Line)
|
||||
default:
|
||||
if other.Start < pos.Start { pos.Start = other.Start }
|
||||
if other.End > pos.End { pos.End = other.End }
|
||||
}
|
||||
return pos
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package errors
|
||||
|
||||
import "testing"
|
||||
import "git.tebibyte.media/fspl/fspl/testcommon"
|
||||
|
||||
func testString (test *testing.T, correct string, got string) {
|
||||
if got != correct {
|
||||
test.Logf("results do not match")
|
||||
test.Fail()
|
||||
}
|
||||
testcommon.Compare(test, correct, got)
|
||||
}
|
||||
|
||||
func testError (test *testing.T, correct string, err Error) {
|
||||
testString(test, correct, Format(err))
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
# generator
|
||||
|
||||
## Responsibilities
|
||||
|
||||
Given a compilation target, turn a well-formed FSPL semantic tree into an LLVM
|
||||
IR module tree.
|
||||
|
||||
## Organization
|
||||
|
||||
Generator defines the Target type, which contains information about the system
|
||||
that the program is being compiled for. The native sub-package uses Go's
|
||||
conditional compilation directives to provide a default Target that matches the
|
||||
system the compiler has been natively built for.
|
||||
|
||||
The entry point for all logic defined in this package is Target.Generate(). This
|
||||
method creates a new generator, and uses it to recursively generate and return an
|
||||
LLVM module. The details of the generator are hidden from other packages, and
|
||||
instances of it only last for the duration of Target.Generate().
|
||||
|
||||
The generator contains a stack of blockManagers, which plays a similar role to
|
||||
analyzer.scopeContextManager, except that the stack of blockManagers is managed
|
||||
directly by the generator, which contains appropriate methods for
|
||||
pushing/popping them.
|
||||
|
||||
Like the analyzer, the generator greedily generates code, and one function may
|
||||
be generated in the middle of the generation process of another function. Thus,
|
||||
each blockManager is tied to a specific LLVM function, and is in charge of
|
||||
variables/stack allocations and to a degree, control flow flattening
|
||||
(specifically loops). It also embeds the current active block, allowing for
|
||||
generator routines to call its methods to add new instructions to the current
|
||||
block, and switch between different blocks when necessary.
|
||||
|
||||
## Operation
|
||||
|
||||
When Target.Generate() is called, a new generator is created. It is given the
|
||||
semantic tree to generate, as well as a copy of the Target. All data structure
|
||||
initialization within the generator happens at this point.
|
||||
|
||||
Then, the generate() method on the newly created generator is called. This is
|
||||
the entry point for the actual generation logic. This routine is comprised of
|
||||
two phases:
|
||||
|
||||
- Function generation
|
||||
- Method generation
|
||||
|
||||
You'll notice that there is no step for type generation. This is because types
|
||||
are generated on-demand in order to reduce IR clutter.
|
||||
|
||||
## Expression Generation
|
||||
|
||||
Since expressions make up the bulk of FSPL, expression generation makes up the
|
||||
bulk of the code generator. The generator is able to produce expressions in one
|
||||
of three modes:
|
||||
|
||||
- Location: The generator will return an IR register that contains a pointer to
|
||||
the result of the expression.
|
||||
- Value: The generator will return an IR register that directly contains the
|
||||
result of the expression.
|
||||
- Any: The generator will decide which of these two options is best for the
|
||||
specific expression, and will let the caller know which was chosen, in case it
|
||||
cares. Some expressions are better suited to returning a pointer, such as
|
||||
array subscripting or member access. Other expressions are better suited to
|
||||
returning a value, such as arithmetic operators and function calls.
|
||||
|
||||
It is important to note that generating a Value expression may return a pointer,
|
||||
because *FSPL pointers are first-class values*. The distinction between location
|
||||
and value generation modes is purely to do with LLVM. It is similar to the
|
||||
concept of location expressions within the analyzer, but not 100% identical all
|
||||
of the time.
|
||||
|
||||
Whenever an expression needs to be generated, one of the following routines is
|
||||
called:
|
||||
|
||||
- generator.generateExpression()
|
||||
- generator.generateAny()
|
||||
- generator.generateVal()
|
||||
- generator.generateLoc()
|
||||
|
||||
The generator.generateExpression() routine takes in a mode value and depending
|
||||
on it, calls one of the other more specific routines. Each of these routines, in
|
||||
turn, calls a more specialized generation routine depending on the specific
|
||||
expression.
|
||||
|
||||
If it is specifically requested to generate a value for an expression with only
|
||||
its location component defined or vice versa, generator.generateVal/Loc() will
|
||||
automatically perform the conversion.
|
|
@ -0,0 +1,245 @@
|
|||
package generator
|
||||
|
||||
import "fmt"
|
||||
import "errors"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
|
||||
func (this *generator) generateAssignment (assignment *entity.Assignment) (llvm.Value, error) {
|
||||
destination, err := this.generateExpressionLoc(assignment.Location)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return this.generateAssignmentToDestination (
|
||||
assignment.Value,
|
||||
assignment.Location.Type(),
|
||||
destination)
|
||||
}
|
||||
|
||||
// TODO possibly break generateAssignmentToDestination into several routines,
|
||||
// each handling one destination type
|
||||
|
||||
// generateAssignmentToDestination performs type coercions if necessary, mainly
|
||||
// interface assignment. This should be called when the user is assigning a
|
||||
// value to something via an assignment statement, a function/method call, or a
|
||||
// field in a composite literal.
|
||||
// irDestLoc specifies the location to assign the source to. If it is nil, this
|
||||
// function will allocate a destination (if necessary) and return its value as a
|
||||
// register.
|
||||
func (this *generator) generateAssignmentToDestination ( // TODO: add -Val suffix
|
||||
source entity.Expression,
|
||||
destType entity.Type,
|
||||
irDestLoc llvm.Value,
|
||||
) (
|
||||
llvm.Value,
|
||||
error,
|
||||
) {
|
||||
destinationSpecified := irDestLoc != nil
|
||||
destTypeBase := analyzer.ReduceToBase(destType)
|
||||
|
||||
switch destTypeBase := destTypeBase.(type) {
|
||||
// conversion from any type to interface
|
||||
case *entity.TypeInterface:
|
||||
// methods defined on interface or pointer types cannot pass
|
||||
// through an interface!
|
||||
|
||||
// create destination interface, if necessary
|
||||
irDestType, err := this.generateType(destType)
|
||||
if err != nil { return nil, err }
|
||||
if !destinationSpecified {
|
||||
irDestLoc = this.blockManager.newAllocaFront(irDestType)
|
||||
}
|
||||
destDataFieldLoc := this.getInterfaceDataFieldLoc(irDestLoc, irDestType)
|
||||
|
||||
sourceType := source.Type()
|
||||
switch sourceTypeBase := analyzer.ReduceToBase(sourceType).(type) {
|
||||
// conversion from interface to interface
|
||||
case *entity.TypeInterface:
|
||||
// re-assign data
|
||||
source, err := this.generateExpressionLoc(source)
|
||||
if err != nil { return nil, err }
|
||||
irFromType, err := this.generateType(sourceType)
|
||||
if err != nil { return nil, err }
|
||||
sourceDataFieldLoc := this.getInterfaceDataFieldLoc(source, irFromType)
|
||||
sourceDataLoc := this.blockManager.NewLoad(new(llvm.TypePointer), sourceDataFieldLoc)
|
||||
this.blockManager.NewStore(sourceDataLoc, destDataFieldLoc)
|
||||
|
||||
// re-assign behaviors
|
||||
for _, name := range destTypeBase.BehaviorOrder {
|
||||
fromBehaviorField := this.getInterfaceBehaviorFieldLoc (
|
||||
sourceTypeBase, source,
|
||||
irFromType, name)
|
||||
toBehaviorField := this.getInterfaceBehaviorFieldLoc (
|
||||
destTypeBase, irDestLoc,
|
||||
irDestType, name)
|
||||
fromBehavior := this.blockManager.NewLoad(new(llvm.TypePointer), fromBehaviorField)
|
||||
this.blockManager.NewStore(fromBehavior, toBehaviorField)
|
||||
}
|
||||
|
||||
// conversion from pointer to interface
|
||||
case *entity.TypePointer:
|
||||
// assign data
|
||||
source, err := this.generateExpressionVal(source)
|
||||
if err != nil { return nil, err }
|
||||
sourceDataLoc := this.blockManager.NewLoad(new(llvm.TypePointer), source)
|
||||
this.blockManager.NewStore(sourceDataLoc, destDataFieldLoc)
|
||||
|
||||
sourceType, ok := sourceTypeBase.Referenced.(*entity.TypeNamed)
|
||||
if !ok {
|
||||
return nil, errors.New(fmt.Sprint(
|
||||
"can't assign", sourceTypeBase, "to interface"))
|
||||
}
|
||||
|
||||
// assign behaviors
|
||||
for _, name := range destTypeBase.BehaviorOrder {
|
||||
toBehaviorFieldAddress := this.getInterfaceBehaviorFieldLoc (
|
||||
destTypeBase, irDestLoc,
|
||||
irDestType, name)
|
||||
key := entity.Key {
|
||||
Unit: sourceType.Unit(),
|
||||
Name: sourceType.Name,
|
||||
Method: name,
|
||||
}
|
||||
fromBehavior, err := this.method(key)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewStore(fromBehavior, toBehaviorFieldAddress)
|
||||
}
|
||||
|
||||
// conversion from other type to interface
|
||||
default:
|
||||
// assign data
|
||||
sourceLoc, err := this.generateExpressionLoc(source)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewStore(sourceLoc, destDataFieldLoc)
|
||||
|
||||
sourceType, ok := sourceType.(*entity.TypeNamed)
|
||||
if !ok {
|
||||
return nil, errors.New(fmt.Sprint(
|
||||
"can't assign", sourceType, "to interface"))
|
||||
}
|
||||
|
||||
// assign behaviors
|
||||
for _, name := range destTypeBase.BehaviorOrder {
|
||||
toBehaviorFieldAddress := this.getInterfaceBehaviorFieldLoc (
|
||||
destTypeBase, irDestLoc,
|
||||
irDestType, name)
|
||||
key := entity.Key {
|
||||
Unit: sourceType.Type.Unit(),
|
||||
Name: sourceType.Name,
|
||||
Method: name,
|
||||
}
|
||||
fromBehavior, err := this.method(key)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewStore(fromBehavior, toBehaviorFieldAddress)
|
||||
}
|
||||
}
|
||||
if destinationSpecified {
|
||||
return nil, nil
|
||||
} else {
|
||||
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
|
||||
}
|
||||
|
||||
// conversion from any type to pointer
|
||||
case *entity.TypePointer:
|
||||
// assignments
|
||||
switch source := source.(type) {
|
||||
// assignment from array literal to pointer
|
||||
case *entity.LiteralArray:
|
||||
if destinationSpecified {
|
||||
_, err := this.generateLiteralArrayLoc(source, irDestLoc)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// assignment from string literal to pointer
|
||||
case *entity.LiteralString:
|
||||
if destinationSpecified {
|
||||
_, err := this.generateLiteralStringLoc(source, irDestLoc)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// conversion from any type to slice
|
||||
case *entity.TypeSlice:
|
||||
// conversions
|
||||
switch sourceTypeBase := analyzer.ReduceToBase(source.Type()).(type) {
|
||||
// conversion from array to slice
|
||||
case *entity.TypeArray:
|
||||
array, err := this.generateExpressionLoc(source)
|
||||
if err != nil { return nil, err }
|
||||
irDestType, err := this.generateType(destType)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if !destinationSpecified {
|
||||
irDestLoc = this.blockManager.newAllocaFront(irDestType)
|
||||
}
|
||||
destDataField := this.getSliceDataFieldLoc(irDestLoc, irDestType)
|
||||
destLengthField := this.getSliceLengthFieldLoc(irDestLoc, irDestType)
|
||||
this.blockManager.NewStore(array, destDataField)
|
||||
this.blockManager.NewStore (
|
||||
llvm.NewConstInt(llvm.I32, int64(sourceTypeBase.Length)),
|
||||
destLengthField)
|
||||
|
||||
if !destinationSpecified {
|
||||
return this.blockManager.NewLoad(irDestType, irDestLoc), nil
|
||||
}
|
||||
}
|
||||
|
||||
// assignments
|
||||
switch source := source.(type) {
|
||||
// assignment from array literal to slice
|
||||
case *entity.LiteralArray:
|
||||
if destinationSpecified {
|
||||
_, err := this.generateLiteralArrayLoc(source, irDestLoc)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// assignment from string literal to slice
|
||||
case *entity.LiteralString:
|
||||
if destinationSpecified {
|
||||
_, err := this.generateLiteralStringLoc(source, irDestLoc)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// conversion from any type to array
|
||||
case *entity.TypeArray:
|
||||
// assignments
|
||||
switch source := source.(type) {
|
||||
// assignment from array literal to array
|
||||
case *entity.LiteralArray:
|
||||
if destinationSpecified {
|
||||
_, err := this.generateLiteralArrayLoc(source, irDestLoc)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// assignment from string literal to array
|
||||
case *entity.LiteralString:
|
||||
if destinationSpecified {
|
||||
_, err := this.generateLiteralStringLoc(source, irDestLoc)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// conversion from any type to struct
|
||||
case *entity.TypeStruct:
|
||||
// assignments
|
||||
switch source := source.(type) {
|
||||
// assignment from struct literal to struct
|
||||
case *entity.LiteralStruct:
|
||||
if destinationSpecified {
|
||||
_, err := this.generateLiteralStructLoc(source, irDestLoc)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
irSource, err := this.generateExpressionVal(source)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if destinationSpecified {
|
||||
this.blockManager.NewStore(irSource, irDestLoc)
|
||||
return nil, nil
|
||||
} else {
|
||||
return irSource, nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package generator
|
||||
|
||||
import "fmt"
|
||||
import "errors"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
type loopEntry struct {
|
||||
value llvm.Value
|
||||
stub *llvm.Block
|
||||
mode resultMode
|
||||
loc bool
|
||||
}
|
||||
|
||||
type blockManager struct {
|
||||
*llvm.Block
|
||||
generator *generator
|
||||
function *llvm.Function
|
||||
declarations map[*entity.Declaration] llvm.Value
|
||||
|
||||
loops []*loopEntry
|
||||
}
|
||||
|
||||
func (this *blockManager) String () string {
|
||||
return fmt.Sprint(this.function.Name())
|
||||
}
|
||||
|
||||
func (this *generator) pushBlockManager (function *llvm.Function) *blockManager {
|
||||
manager := new(blockManager)
|
||||
manager.generator = this
|
||||
manager.function = function
|
||||
manager.newBlock()
|
||||
manager.declarations = make(map[*entity.Declaration] llvm.Value)
|
||||
|
||||
this.managerStack = append(this.managerStack, manager)
|
||||
this.blockManager = manager
|
||||
return manager
|
||||
}
|
||||
|
||||
func (this *generator) popBlockManager () {
|
||||
this.managerStack = this.managerStack[:len(this.managerStack) - 1]
|
||||
if len(this.managerStack) > 0 {
|
||||
this.blockManager = this.managerStack[len(this.managerStack) - 1]
|
||||
} else {
|
||||
this.blockManager = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *blockManager) pushLoop (mode resultMode) *loopEntry {
|
||||
entry := &loopEntry { mode: mode }
|
||||
this.loops = append(this.loops, entry)
|
||||
return entry
|
||||
}
|
||||
|
||||
func (this *blockManager) popLoop () {
|
||||
this.loops = this.loops[:len(this.loops) - 1]
|
||||
}
|
||||
|
||||
func (this *blockManager) topLoop () *loopEntry {
|
||||
return this.loops[len(this.loops) - 1]
|
||||
}
|
||||
|
||||
func (this *blockManager) newAllocaFront (ty llvm.Type) llvm.Value {
|
||||
alloca := llvm.NewAlloca(ty)
|
||||
|
||||
firstBlock := this.function.Blocks[0]
|
||||
if firstBlock.Terminated() {
|
||||
lastIndex := len(firstBlock.Instructions) - 1
|
||||
firstBlock.Instructions = append (
|
||||
firstBlock.Instructions,
|
||||
firstBlock.Instructions[lastIndex])
|
||||
firstBlock.Instructions[lastIndex] = alloca
|
||||
} else {
|
||||
firstBlock.AddInstruction(alloca)
|
||||
}
|
||||
return alloca
|
||||
}
|
||||
|
||||
func (this *blockManager) newBlock () *llvm.Block {
|
||||
this.Block = this.function.NewBlock()
|
||||
return this.Block
|
||||
}
|
||||
|
||||
func (this *blockManager) variable (declaration *entity.Declaration) (llvm.Value, error) {
|
||||
value, ok := this.declarations[declaration]
|
||||
if !ok {
|
||||
return nil, errors.New(fmt.Sprintf("declaration of %v does not exist", declaration.Name))
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (this *blockManager) addDeclaration (declaration *entity.Declaration, initial llvm.Value) (llvm.Value, error) {
|
||||
ty, err := this.generator.generateType(declaration.Type())
|
||||
if err != nil { return nil, err }
|
||||
location := this.newAllocaFront(ty)
|
||||
this.declarations[declaration] = location
|
||||
if initial != nil {
|
||||
this.NewStore(initial, location)
|
||||
}
|
||||
return location, nil
|
||||
}
|
||||
|
||||
func (this *blockManager) addFunctionArgumentDeclarations (scope entity.Scoped) error {
|
||||
var err error
|
||||
|
||||
switch scope := scope.(type) {
|
||||
case *entity.Function:
|
||||
for index, argument := range scope.Signature.Arguments {
|
||||
if argument.Name == argument.Name {
|
||||
this.addDeclaration (
|
||||
argument,
|
||||
this.function.Parameters[index])
|
||||
}
|
||||
}
|
||||
|
||||
case *entity.Method:
|
||||
this.addDeclaration (
|
||||
scope.This,
|
||||
this.function.Parameters[0])
|
||||
|
||||
for index, argument := range scope.Signature.Arguments {
|
||||
if argument.Name == argument.Name {
|
||||
this.addDeclaration (
|
||||
argument,
|
||||
this.function.Parameters[index + 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValueCastSlicePointer (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%2 = alloca ptr
|
||||
%3 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 0
|
||||
%4 = load ptr, ptr %3
|
||||
store ptr %4, ptr %2
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
slice: *:Byte
|
||||
pointer: *Byte = [~*Byte slice]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestValueCastIntegerIneffectual (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type i64
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 5, ptr %1
|
||||
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::A"
|
||||
%3 = load i64, ptr %1
|
||||
store i64 %3, ptr %2
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: Int
|
||||
[main] = {
|
||||
x:Int = 5
|
||||
y:A = [~A x]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestValueCastStringIneffectual (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
|
||||
%3 = load { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1
|
||||
store { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %3, ptr %2
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
y:*:Byte
|
||||
x:String = [~String y]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBitCastStringIneffectual (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
|
||||
%3 = load { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1
|
||||
store { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %3, ptr %2
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
y:*:Byte
|
||||
x:String = [~~String y]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
// TODO: Perhaps run all generator output in these tests through llc to check the
|
||||
// integrity of the IR.
|
||||
|
||||
// TODO: uncomment and complete once bitcasting is fully supported
|
||||
// func TestBitCastPointer (test *testing.T) {
|
||||
// testString (test,
|
||||
// `
|
||||
// `,
|
||||
// `
|
||||
// Struct: (. x:Int y:Int)
|
||||
// Array: 4:Int
|
||||
// [main] = {
|
||||
// ptr:*Byte
|
||||
// stc:Struct
|
||||
// arr:Array
|
||||
// str:String
|
||||
// flt:F64
|
||||
// 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]]
|
||||
// ; float
|
||||
// ptr = [~~*Byte [~~F64 ptr]]
|
||||
// flt = [~~F64 [~~*Byte flt]]
|
||||
//
|
||||
// ; arithmetic
|
||||
// ptr = [~~*Byte [+ 1 [~~Index ptr]]]
|
||||
// }
|
||||
// `)
|
||||
// }
|
|
@ -0,0 +1,158 @@
|
|||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBranchAssign (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
br i1 true, label %2, label %4
|
||||
2:
|
||||
store i64 5, ptr %1
|
||||
br label %3
|
||||
3:
|
||||
ret void
|
||||
4:
|
||||
store i64 6, ptr %1
|
||||
br label %3
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
x:Int
|
||||
if true then {
|
||||
x = 5
|
||||
} else {
|
||||
x = 6
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBranchAssignNoFalse (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
br i1 true, label %2, label %3
|
||||
2:
|
||||
store i64 5, ptr %1
|
||||
br label %3
|
||||
3:
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
x:Int
|
||||
if true then {
|
||||
x = 5
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBranchResult (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
br i1 true, label %2, label %5
|
||||
2:
|
||||
br label %3
|
||||
3:
|
||||
%4 = phi i64 [ 5, %2 ], [ 6, %5 ]
|
||||
store i64 %4, ptr %1
|
||||
ret void
|
||||
5:
|
||||
br label %3
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = if true then 5 else 6
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLoop (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
br label %1
|
||||
1:
|
||||
br label %1
|
||||
2:
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
loop {
|
||||
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLoopResult (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
br label %2
|
||||
2:
|
||||
br label %3
|
||||
3:
|
||||
store i64 5, ptr %1
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = loop {
|
||||
[break 5]
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLoopBranchResult (test *testing.T) {
|
||||
testString (test,
|
||||
`define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 6, ptr %1
|
||||
br label %2
|
||||
2:
|
||||
%3 = load i64, ptr %1
|
||||
%4 = icmp slt i64 %3, 3
|
||||
br i1 %4, label %5, label %8
|
||||
5:
|
||||
%6 = load i64, ptr %1
|
||||
br label %11
|
||||
7:
|
||||
br label %2
|
||||
8:
|
||||
%9 = load i64, ptr %1
|
||||
%10 = sub i64 %9, 1
|
||||
store i64 %10, ptr %1
|
||||
br label %7
|
||||
11:
|
||||
ret i64 %6
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main]:Int = {
|
||||
y:Int = 6
|
||||
loop {
|
||||
if [< y 3]
|
||||
then [break y]
|
||||
else {
|
||||
y = [-- y]
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package generator
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *generator) generateSlice (ty entity.Type, data llvm.Value, length llvm.Value) (llvm.Value, error) {
|
||||
irType, err := this.generateType(ty)
|
||||
if err != nil { return nil, err }
|
||||
slice := this.blockManager.newAllocaFront(irType)
|
||||
this.sliceSetData(ty, slice, data)
|
||||
this.sliceSetLength(ty, slice, length)
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func (this *generator) generateSliceDefinedLength (ty entity.Type, data llvm.Value, length int64) (llvm.Value, error) {
|
||||
indexType, err := this.generateTypeIndex()
|
||||
if err != nil { return nil, err }
|
||||
return this.generateSlice(ty, data, llvm.NewConstInt(indexType, length))
|
||||
}
|
||||
|
||||
func (this *generator) sliceSetData (ty entity.Type, slice llvm.Value, data llvm.Value) error {
|
||||
irType, err := this.generateType(ty)
|
||||
if err != nil { return err }
|
||||
dataField := this.blockManager.NewGetElementPtr (
|
||||
irType, slice,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 0))
|
||||
this.blockManager.NewStore(data, dataField)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *generator) sliceSetLength (ty entity.Type, slice llvm.Value, length llvm.Value) error {
|
||||
irType, err := this.generateType(ty)
|
||||
if err != nil { return err }
|
||||
lengthField := this.blockManager.NewGetElementPtr (
|
||||
irType, slice,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 1))
|
||||
this.blockManager.NewStore(length, lengthField)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *generator) sliceSetDefinedLength (ty entity.Type, slice llvm.Value, length int64) error {
|
||||
indexType, err := this.generateTypeIndex()
|
||||
if err != nil { return err }
|
||||
return this.sliceSetLength(ty, slice, llvm.NewConstInt(indexType, length))
|
||||
}
|
||||
|
||||
func (this *generator) getSliceDataAddress (slice llvm.Value, irType llvm.Type) llvm.Value {
|
||||
return this.blockManager.NewLoad (
|
||||
new(llvm.TypePointer),
|
||||
this.getSliceDataFieldLoc(slice, irType))
|
||||
}
|
||||
|
||||
func (this *generator) getSliceDataFieldLoc (slice llvm.Value, irType llvm.Type) llvm.Value {
|
||||
return this.blockManager.NewGetElementPtr (
|
||||
irType, slice,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 0))
|
||||
}
|
||||
|
||||
func (this *generator) getSliceLengthFieldLoc (slice llvm.Value, irType llvm.Type) llvm.Value {
|
||||
return this.blockManager.NewGetElementPtr (
|
||||
irType, slice,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 1))
|
||||
}
|
||||
|
||||
func (this *generator) getInterfaceDataFieldLoc (iface llvm.Value, irType llvm.Type) llvm.Value {
|
||||
return this.blockManager.NewGetElementPtr (
|
||||
irType, iface,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 0))
|
||||
}
|
||||
|
||||
func (this *generator) getInterfaceBehaviorFieldLoc (
|
||||
ty *entity.TypeInterface,
|
||||
iface llvm.Value,
|
||||
irType llvm.Type,
|
||||
name string,
|
||||
) llvm.Value {
|
||||
offset := this.getInterfaceBehaviorIndex(ty, name)
|
||||
if offset > -1 {
|
||||
return this.blockManager.NewGetElementPtr (
|
||||
irType, iface,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, int64(offset + 1)))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) getInterfaceBehaviorSignature (ty *entity.TypeInterface, name string) (llvm.Type, error) {
|
||||
return this.generateTypeFunction(ty.BehaviorMap[name])
|
||||
}
|
||||
|
||||
func (this *generator) getInterfaceBehaviorIndex (ty *entity.TypeInterface, name string) int {
|
||||
offset := -1
|
||||
for index, nm := range ty.BehaviorOrder {
|
||||
if nm == name {
|
||||
offset = index
|
||||
break
|
||||
}
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
func (this *generator) getStructMemberIndex (ty *entity.TypeStruct, name string) int {
|
||||
offset := -1
|
||||
for index, nm := range ty.MemberOrder {
|
||||
if nm == name {
|
||||
offset = index
|
||||
break
|
||||
}
|
||||
}
|
||||
return offset
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// Package generator implements the code generation stage of the FSPL compiler.
|
||||
// It converts a well formed semantic tree into LLVM IR code, and outputs it to
|
||||
// an io.Writer.
|
||||
package generator
|
|
@ -0,0 +1,162 @@
|
|||
package generator
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
|
||||
func (this *generator) generateVariableLoc (variable *entity.Variable) (llvm.Value, error) {
|
||||
return this.blockManager.variable(variable.Declaration)
|
||||
}
|
||||
|
||||
func (this *generator) generateDeclarationLoc (declaration *entity.Declaration) (llvm.Value, error) {
|
||||
return this.blockManager.addDeclaration(declaration, nil)
|
||||
}
|
||||
|
||||
func (this *generator) generateSliceLoc (slice *entity.Slice) (llvm.Value, error) {
|
||||
var start, end, dataAddress llvm.Value
|
||||
var elementType llvm.Type
|
||||
|
||||
sizeType, err := this.generateTypeIndex()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if slice.Start != nil {
|
||||
start, err = this.generateExpressionVal(slice.Start)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
if slice.End != nil {
|
||||
end, err = this.generateExpressionVal(slice.End)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
sourceType := analyzer.ReduceToBase(slice.Slice.Type())
|
||||
switch sourceType := sourceType.(type) {
|
||||
case *entity.TypeSlice:
|
||||
source, err := this.generateExpressionLoc(slice.Slice)
|
||||
if err != nil { return nil, err }
|
||||
irSourceType, err := this.generateType(sourceType)
|
||||
if err != nil { return nil, err }
|
||||
elementType, err = this.generateType(sourceType.Element)
|
||||
if err != nil { return nil, err }
|
||||
dataAddressFieldAddress := this.blockManager.NewGetElementPtr (
|
||||
irSourceType,
|
||||
source,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 0))
|
||||
dataAddress = this.blockManager.NewLoad (
|
||||
new(llvm.TypePointer),
|
||||
dataAddressFieldAddress)
|
||||
lengthAddress := this.blockManager.NewGetElementPtr (
|
||||
irSourceType,
|
||||
source,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 1))
|
||||
|
||||
if end == nil {
|
||||
end = this.blockManager.NewLoad(sizeType, lengthAddress)
|
||||
}
|
||||
|
||||
case *entity.TypeArray:
|
||||
elementType, err = this.generateType(sourceType.Element)
|
||||
if err != nil { return nil, err }
|
||||
dataAddress, err = this.generateExpressionLoc(slice.Slice)
|
||||
if err != nil { return nil, err }
|
||||
if end == nil {
|
||||
end = llvm.NewConstInt(sizeType, int64(sourceType.Length))
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: generator can't slice expression ",
|
||||
slice.Slice))
|
||||
}
|
||||
|
||||
// figure out starting position and length
|
||||
var length llvm.Value
|
||||
if start == nil {
|
||||
length = end
|
||||
} else {
|
||||
length = this.blockManager.NewSub(end, start)
|
||||
dataAddress = this.blockManager.NewGetElementPtr (
|
||||
elementType,
|
||||
dataAddress,
|
||||
start)
|
||||
}
|
||||
|
||||
// create new slice descriptor
|
||||
return this.generateSlice(slice.Type(), dataAddress, length)
|
||||
}
|
||||
|
||||
func (this *generator) generateSubscriptLoc (subscript *entity.Subscript) (llvm.Value, error) {
|
||||
source, err := this.generateExpressionLoc(subscript.Slice)
|
||||
if err != nil { return nil, err }
|
||||
offset, err := this.generateExpressionVal(subscript.Offset)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
var elementType entity.Type
|
||||
var dataAddress llvm.Value
|
||||
|
||||
sourceType := analyzer.ReduceToBase(subscript.Slice.Type())
|
||||
switch sourceType := sourceType.(type) {
|
||||
case *entity.TypeSlice:
|
||||
irSourceType, err := this.generateType(subscript.Slice.Type())
|
||||
if err != nil { return nil, err }
|
||||
elementType = sourceType.Element
|
||||
dataAddress = this.getSliceDataAddress(source, irSourceType)
|
||||
|
||||
case *entity.TypeArray:
|
||||
elementType = sourceType.Element
|
||||
dataAddress = source
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: generator can't subscript expression ",
|
||||
subscript.Slice))
|
||||
}
|
||||
|
||||
// get element
|
||||
irElementType, err := this.generateType(elementType)
|
||||
if err != nil { return nil, err }
|
||||
return this.blockManager.NewGetElementPtr (
|
||||
irElementType,
|
||||
dataAddress,
|
||||
offset), nil
|
||||
}
|
||||
|
||||
func (this *generator) generateDereferenceLoc (dereference *entity.Dereference) (llvm.Value, error) {
|
||||
return this.generateExpressionVal(dereference.Pointer)
|
||||
}
|
||||
|
||||
func (this *generator) generateMemberAccessLoc (access *entity.MemberAccess) (llvm.Value, error) {
|
||||
source, err := this.generateExpressionLoc(access.Source)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch sourceType := analyzer.ReduceToBase(access.Source.Type()).(type) {
|
||||
case *entity.TypeStruct:
|
||||
irSourceType, err := this.generateType(access.Source.Type())
|
||||
if err != nil { return nil, err }
|
||||
offset := this.getStructMemberIndex(sourceType, access.Member)
|
||||
return this.blockManager.NewGetElementPtr (
|
||||
irSourceType,
|
||||
source,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, int64(offset))), nil
|
||||
|
||||
case *entity.TypePointer:
|
||||
irSourceType, err := this.generateType(sourceType.Referenced)
|
||||
if err != nil { return nil, err }
|
||||
referencedSourceType := analyzer.ReduceToBase(sourceType.Referenced).(*entity.TypeStruct)
|
||||
offset := this.getStructMemberIndex(referencedSourceType, access.Member)
|
||||
source = this.blockManager.NewLoad(new(llvm.TypePointer), source)
|
||||
return this.blockManager.NewGetElementPtr (
|
||||
irSourceType,
|
||||
source,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, int64(offset))), nil
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: generator can't access members in expression ",
|
||||
access.Source))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
package generator
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
type resultMode int; const (
|
||||
resultModeAny resultMode = iota
|
||||
resultModeVal
|
||||
resultModeLoc
|
||||
)
|
||||
|
||||
func (mode resultMode) String () string {
|
||||
switch(mode) {
|
||||
case resultModeAny: return "resultModeAny"
|
||||
case resultModeVal: return "resultModeVal"
|
||||
case resultModeLoc: return "resultModeLoc"
|
||||
default: return fmt.Sprintf("resultMode(%d)", mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) valueToLocation (value llvm.Value) llvm.Value {
|
||||
pointer := this.blockManager.newAllocaFront(value.Type())
|
||||
this.blockManager.NewStore(value, pointer)
|
||||
return pointer
|
||||
}
|
||||
|
||||
func (this *generator) locationToValue (pointer llvm.Value, ty entity.Type) (llvm.Value, error) {
|
||||
irType, err := this.generateType(ty)
|
||||
if err != nil { return nil, err }
|
||||
return this.blockManager.NewLoad(irType, pointer), nil
|
||||
}
|
||||
|
||||
func (this *generator) generateExpression (
|
||||
expression entity.Expression,
|
||||
mode resultMode,
|
||||
) (
|
||||
register llvm.Value,
|
||||
location bool,
|
||||
err error,
|
||||
) {
|
||||
switch mode {
|
||||
case resultModeAny:
|
||||
return this.generateExpressionAny(expression)
|
||||
case resultModeVal:
|
||||
val, err := this.generateExpressionVal(expression)
|
||||
return val, false, err
|
||||
case resultModeLoc:
|
||||
loc, err := this.generateExpressionLoc(expression)
|
||||
return loc, true, err
|
||||
default: panic("unknown resultMode")
|
||||
}
|
||||
}
|
||||
|
||||
// generateExpression generates an expression and either returns a register
|
||||
// representing the result, or a register containing the location of the result,
|
||||
// whichever will generate the least IR. In the latter case, the boolean
|
||||
// "location" will be true.
|
||||
func (this *generator) generateExpressionAny (expression entity.Expression) (register llvm.Value, location bool, err error) {
|
||||
switch expression := expression.(type) {
|
||||
// these give us an address
|
||||
case *entity.Dereference,
|
||||
*entity.MemberAccess,
|
||||
*entity.Slice,
|
||||
*entity.Subscript,
|
||||
*entity.LiteralArray,
|
||||
*entity.LiteralString,
|
||||
*entity.LiteralStruct,
|
||||
*entity.Variable,
|
||||
*entity.Declaration:
|
||||
|
||||
pointer, err := this.generateExpressionLoc(expression)
|
||||
return pointer, true, err
|
||||
|
||||
// these give us a value
|
||||
case *entity.Call,
|
||||
*entity.MethodCall,
|
||||
*entity.Reference,
|
||||
*entity.Length,
|
||||
*entity.ValueCast,
|
||||
*entity.BitCast,
|
||||
*entity.Operation,
|
||||
*entity.LiteralInt,
|
||||
*entity.LiteralFloat,
|
||||
*entity.LiteralBoolean,
|
||||
*entity.LiteralNil:
|
||||
|
||||
value, err := this.generateExpressionVal(expression)
|
||||
return value, true, err
|
||||
|
||||
// these are capable of giving us both
|
||||
case *entity.Block:
|
||||
return this.generateBlock(expression, resultModeAny)
|
||||
case *entity.IfElse:
|
||||
return this.generateIfElse(expression, resultModeAny)
|
||||
case *entity.Loop:
|
||||
return this.generateLoop(expression, resultModeAny)
|
||||
|
||||
// we get nothing from these
|
||||
case *entity.Assignment:
|
||||
_, err := this.generateAssignment(expression)
|
||||
return nil, false, err
|
||||
case *entity.Break:
|
||||
_, err := this.generateBreak(expression)
|
||||
return nil, false, err
|
||||
case *entity.Return:
|
||||
_, err := this.generateReturn(expression)
|
||||
return nil, false, err
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: generator doesnt know about expression %v, ty: %T",
|
||||
expression, expression))
|
||||
}
|
||||
}
|
||||
|
||||
// generateExpressionVal generates an expression and returns a register
|
||||
// representing the result.
|
||||
func (this *generator) generateExpressionVal (expression entity.Expression) (llvm.Value, error) {
|
||||
// we get an address from these, so we need to load the value
|
||||
switch expression := expression.(type) {
|
||||
case *entity.Dereference,
|
||||
*entity.MemberAccess,
|
||||
*entity.Slice,
|
||||
*entity.Subscript,
|
||||
*entity.LiteralArray,
|
||||
*entity.LiteralString,
|
||||
*entity.LiteralStruct,
|
||||
*entity.Variable,
|
||||
*entity.Declaration:
|
||||
|
||||
pointer, err := this.generateExpressionLoc(expression)
|
||||
if err != nil { return nil, err }
|
||||
return this.locationToValue(pointer, expression.Type())
|
||||
|
||||
// we get a value from these, so we can return them as-is
|
||||
case *entity.Call:
|
||||
return this.generateCallVal(expression)
|
||||
case *entity.MethodCall:
|
||||
return this.generateMethodCallVal(expression)
|
||||
case *entity.Reference:
|
||||
return this.generateReferenceVal(expression)
|
||||
case *entity.Length:
|
||||
return this.generateLengthVal(expression)
|
||||
case *entity.ValueCast:
|
||||
return this.generateValueCastVal(expression)
|
||||
case *entity.BitCast:
|
||||
return this.generateBitCastVal(expression)
|
||||
case *entity.Operation:
|
||||
return this.generateOperationVal(expression)
|
||||
case *entity.Block:
|
||||
loc, _, err := this.generateBlock(expression, resultModeVal)
|
||||
return loc, err
|
||||
case *entity.IfElse:
|
||||
loc, _, err := this.generateIfElse(expression, resultModeVal)
|
||||
return loc, err
|
||||
case *entity.Loop:
|
||||
loc, _, err := this.generateLoop(expression, resultModeVal)
|
||||
return loc, err
|
||||
case *entity.LiteralInt:
|
||||
return this.generateLiteralInt(expression)
|
||||
case *entity.LiteralFloat:
|
||||
return this.generateLiteralFloat(expression)
|
||||
case *entity.LiteralBoolean:
|
||||
return this.generateLiteralBoolean(expression)
|
||||
case *entity.LiteralNil:
|
||||
return this.generateLiteralNil(expression)
|
||||
|
||||
// we get nothing from these
|
||||
case *entity.Assignment:
|
||||
return this.generateAssignment(expression)
|
||||
case *entity.Break:
|
||||
return this.generateBreak(expression)
|
||||
case *entity.Return:
|
||||
return this.generateReturn(expression)
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: generator doesnt know about value expression %v, ty: %T",
|
||||
expression, expression))
|
||||
}
|
||||
}
|
||||
|
||||
// generateExpressionLoc generates an expression and returns a register
|
||||
// representing the address of the result. This function avoids adding alloca
|
||||
// instructions whenever possible, trying to refer to the original data.
|
||||
func (this *generator) generateExpressionLoc (expression entity.Expression) (llvm.Value, error) {
|
||||
switch expression := expression.(type) {
|
||||
|
||||
// we get a value from these, so we need to store the value and then
|
||||
// return an address to that
|
||||
case *entity.Call,
|
||||
*entity.MethodCall,
|
||||
*entity.Reference,
|
||||
*entity.Length,
|
||||
*entity.ValueCast,
|
||||
*entity.BitCast,
|
||||
*entity.Operation,
|
||||
*entity.LiteralInt,
|
||||
*entity.LiteralFloat,
|
||||
*entity.LiteralBoolean,
|
||||
*entity.LiteralNil:
|
||||
|
||||
value, err := this.generateExpressionVal(expression)
|
||||
if err != nil { return nil, err }
|
||||
return this.valueToLocation(value), nil
|
||||
|
||||
// we get an address from these, so we can return them as-is
|
||||
case *entity.Variable:
|
||||
return this.generateVariableLoc(expression)
|
||||
case *entity.Declaration:
|
||||
return this.generateDeclarationLoc(expression)
|
||||
case *entity.Slice:
|
||||
return this.generateSliceLoc(expression)
|
||||
case *entity.Subscript:
|
||||
return this.generateSubscriptLoc(expression)
|
||||
case *entity.Dereference:
|
||||
return this.generateDereferenceLoc(expression)
|
||||
case *entity.MemberAccess:
|
||||
return this.generateMemberAccessLoc(expression)
|
||||
case *entity.Block:
|
||||
loc, _, err := this.generateBlock(expression, resultModeLoc)
|
||||
return loc, err
|
||||
case *entity.IfElse:
|
||||
loc, _, err := this.generateIfElse(expression, resultModeLoc)
|
||||
return loc, err
|
||||
case *entity.Loop:
|
||||
loc, _, err := this.generateLoop(expression, resultModeLoc)
|
||||
return loc, err
|
||||
case *entity.LiteralArray:
|
||||
return this.generateLiteralArrayLoc(expression, nil)
|
||||
case *entity.LiteralString:
|
||||
return this.generateLiteralStringLoc(expression, nil)
|
||||
case *entity.LiteralStruct:
|
||||
return this.generateLiteralStructLoc(expression, nil)
|
||||
|
||||
// we get nothing from these
|
||||
case *entity.Assignment:
|
||||
return this.generateAssignment(expression)
|
||||
case *entity.Break:
|
||||
return this.generateBreak(expression)
|
||||
case *entity.Return:
|
||||
return this.generateReturn(expression)
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: generator doesnt know about location expression %v, ty: %T",
|
||||
expression, expression))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,649 @@
|
|||
package generator
|
||||
|
||||
import "fmt"
|
||||
import "errors"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
|
||||
func (this *generator) generateCallVal (call *entity.Call) (llvm.Value, error) {
|
||||
function, err := this.function(entity.Key {
|
||||
Unit: call.Unit,
|
||||
Name: call.Name,
|
||||
})
|
||||
if err != nil { return nil, err }
|
||||
|
||||
args := make([]llvm.Value, len(call.Arguments))
|
||||
for index, argument := range call.Arguments {
|
||||
irArgument, err := this.generateAssignmentToDestination (
|
||||
argument,
|
||||
call.Function.Signature.Arguments[index].Type(),
|
||||
nil)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
args[index] = irArgument
|
||||
}
|
||||
return this.blockManager.NewCall(function, function.Signature, args...), nil
|
||||
}
|
||||
|
||||
func (this *generator) generateMethodCallVal (call *entity.MethodCall) (llvm.Value, error) {
|
||||
var signature *entity.Signature
|
||||
if call.Method != nil { signature = call.Method.Signature }
|
||||
if call.Behavior != nil { signature = call.Behavior }
|
||||
|
||||
// build list of args
|
||||
args := make([]llvm.Value, len(call.Arguments) + 1)
|
||||
for index, argument := range call.Arguments {
|
||||
irArgument, err := this.generateAssignmentToDestination (
|
||||
argument,
|
||||
signature.Arguments[index].Type(),
|
||||
nil)
|
||||
if err != nil { return nil, err }
|
||||
args[index + 1] = irArgument
|
||||
}
|
||||
|
||||
// check for methods on named type
|
||||
if sourceType, ok := call.Source.Type().(*entity.TypeNamed); ok {
|
||||
methodKey := entity.Key {
|
||||
Unit: sourceType.Type.Unit(),
|
||||
Name: sourceType.Name,
|
||||
Method: call.Name,
|
||||
}
|
||||
method, err := this.method(methodKey)
|
||||
if _, notFound := err.(errNotFound); !notFound {
|
||||
source, err := this.generateExpressionLoc(call.Source)
|
||||
if err != nil { return nil, err }
|
||||
args[0] = source
|
||||
return this.blockManager.NewCall(method, method.Signature, args...), nil
|
||||
}
|
||||
}
|
||||
|
||||
// check for methods on pointer to named type
|
||||
if pointerType, ok := call.Source.Type().(*entity.TypePointer); ok {
|
||||
if sourceType, ok := pointerType.Referenced.(*entity.TypeNamed); ok {
|
||||
method, err := this.method(entity.Key {
|
||||
Unit: sourceType.Unit(),
|
||||
Name: sourceType.Name,
|
||||
Method: call.Name,
|
||||
})
|
||||
if _, notFound := err.(errNotFound); !notFound {
|
||||
source, err := this.generateExpressionVal(call.Source)
|
||||
if err != nil { return nil, err }
|
||||
args[0] = source
|
||||
return this.blockManager.NewCall(method, method.Signature, args...), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check for interface behaviors
|
||||
if sourceType := getInterface(call.Source.Type()); sourceType != nil {
|
||||
irSourceType, err := this.generateType(call.Source.Type())
|
||||
if err != nil { return nil, err }
|
||||
source, err := this.generateExpressionLoc(call.Source)
|
||||
if err != nil { return nil, err }
|
||||
irBehavior := this.getInterfaceBehaviorFieldLoc(sourceType, source, irSourceType, call.Name)
|
||||
|
||||
if irBehavior != nil {
|
||||
irValue := this.getInterfaceDataFieldLoc(source, irSourceType)
|
||||
signature, err := this.getInterfaceBehaviorSignature(sourceType, call.Name)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
args[0] = this.blockManager.NewLoad(irValue.Type(), irValue)
|
||||
irBehavior := this.blockManager.NewLoad(llvm.Pointer, irBehavior)
|
||||
return this.blockManager.NewCall (
|
||||
irBehavior,
|
||||
signature.(*llvm.TypeFunction),
|
||||
args...), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New(fmt.Sprintln("no method", call.Name, "for", call.Source))
|
||||
}
|
||||
|
||||
func (this *generator) generateReferenceVal (reference *entity.Reference) (llvm.Value, error) {
|
||||
return this.generateExpressionLoc(reference.Value)
|
||||
}
|
||||
|
||||
func (this *generator) generateLengthVal (length *entity.Length) (llvm.Value, error) {
|
||||
sizeType, err := this.generateTypeIndex()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
source, err := this.generateExpressionLoc(length.Slice)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
sourceType := analyzer.ReduceToBase(length.Slice.Type())
|
||||
switch sourceType := sourceType.(type) {
|
||||
case *entity.TypeSlice:
|
||||
irSourceType, err := this.generateType(sourceType)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
lengthFieldAddress := this.blockManager.NewGetElementPtr (
|
||||
irSourceType,
|
||||
source,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, 1))
|
||||
return this.blockManager.NewLoad(sizeType, lengthFieldAddress), nil
|
||||
|
||||
case *entity.TypeArray:
|
||||
return llvm.NewConstInt(sizeType, int64(sourceType.Length)), nil
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: generator can't get length of expression ",
|
||||
length.Slice))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateValueCastVal (cast *entity.ValueCast) (llvm.Value, error) {
|
||||
generateFrom := func () (llvm.Value, error) {
|
||||
return this.generateExpressionVal(cast.Value)
|
||||
}
|
||||
generateFromLoc := func () (llvm.Value, error) {
|
||||
return this.generateExpressionLoc(cast.Value)
|
||||
}
|
||||
|
||||
irFromType, err := this.generateType(cast.Value.Type())
|
||||
if err != nil { return nil, err }
|
||||
irToType, err := this.generateType(cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// Attempt to short circuit if types are equal to avoid LLVM bitching at
|
||||
// us
|
||||
if llvm.TypesEqual(irFromType, irToType) {
|
||||
return this.generateExpressionVal(cast.Value)
|
||||
}
|
||||
|
||||
fail := func () (llvm.Value, error) {
|
||||
return nil, errors.New(fmt.Sprintf (
|
||||
"cant convert %v to %v",
|
||||
cast.Value.Type(), cast.Type()))
|
||||
}
|
||||
|
||||
fromType := analyzer.ReduceToBase(cast.Value.Type())
|
||||
toType := analyzer.ReduceToBase(cast.Type())
|
||||
|
||||
switch toType.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
from, err := generateFrom()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch fromType := fromType.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
// cast from integer to integer:
|
||||
// basic truncated value assignment
|
||||
return this.blockManager.NewTrunc(from, irToType), nil
|
||||
case *entity.TypeFloat:
|
||||
if analyzer.IsUnsigned(fromType) {
|
||||
// cast from float to unsigned integer:
|
||||
// convert float to unsigned integer
|
||||
return this.blockManager.NewFPToUI(from, irToType), nil
|
||||
} else {
|
||||
// cast from float to signed integer:
|
||||
// convert float to signed integer
|
||||
return this.blockManager.NewFPToSI(from, irToType), nil
|
||||
}
|
||||
default: return fail()
|
||||
}
|
||||
|
||||
case *entity.TypeFloat:
|
||||
from, err := generateFrom()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch fromType := fromType.(type) {
|
||||
case *entity.TypeFloat:
|
||||
// cast from float to float:
|
||||
// basic truncated value assignment
|
||||
return this.blockManager.NewFPTrunc(from, irToType), nil
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
if analyzer.IsUnsigned(fromType) {
|
||||
// cast from unsigned integer to float:
|
||||
// convert unsigned integer to float
|
||||
return this.blockManager.NewUIToFP(from, irToType), nil
|
||||
} else {
|
||||
// cast from signed integer to float:
|
||||
// convert signed integer to float
|
||||
return this.blockManager.NewSIToFP(from, irToType), nil
|
||||
}
|
||||
default: return fail()
|
||||
}
|
||||
|
||||
case *entity.TypePointer:
|
||||
switch fromType := fromType.(type) {
|
||||
case *entity.TypeSlice:
|
||||
// cast from slice to pointer:
|
||||
// pointer will point to the first element of the slice
|
||||
from, err := generateFromLoc()
|
||||
if err != nil { return nil, err }
|
||||
irFromType, err := this.generateType(fromType)
|
||||
return this.getSliceDataAddress(from, irFromType), nil
|
||||
case *entity.TypePointer:
|
||||
// cast from pointer to pointer:
|
||||
// basic forceful value assignment
|
||||
// (all IR pointer types are equal)
|
||||
return this.generateExpressionVal(cast.Value)
|
||||
default: return fail()
|
||||
}
|
||||
|
||||
case *entity.TypeSlice:
|
||||
switch fromType.(type) {
|
||||
case *entity.TypeSlice:
|
||||
// cast from slice to slice:
|
||||
// basic forceful value assignment
|
||||
// (assume structural equivalence)
|
||||
return this.generateExpressionVal(cast.Value)
|
||||
default: return fail()
|
||||
}
|
||||
|
||||
case *entity.TypeArray:
|
||||
switch fromType.(type) {
|
||||
case *entity.TypeArray:
|
||||
// cast from array to array:
|
||||
// basic forceful value assignment
|
||||
// (assume structural equivalence)
|
||||
return this.generateExpressionVal(cast.Value)
|
||||
default: return fail()
|
||||
}
|
||||
|
||||
case *entity.TypeStruct:
|
||||
switch fromType.(type) {
|
||||
case *entity.TypeArray:
|
||||
// cast from struct to struct:
|
||||
// basic forceful value assignment
|
||||
// (assume structural equivalence)
|
||||
return this.generateExpressionVal(cast.Value)
|
||||
default: return fail()
|
||||
}
|
||||
|
||||
default: return fail()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateBitCastVal (cast *entity.BitCast) (llvm.Value, error) {
|
||||
irFromType, err := this.generateType(cast.Value.Type())
|
||||
if err != nil { return nil, err }
|
||||
irToType, err := this.generateType(cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// attempt to short circuit if types are equal, or else LLVM complains
|
||||
if llvm.TypesEqual(irFromType, irToType) {
|
||||
return this.generateExpressionVal(cast.Value)
|
||||
}
|
||||
|
||||
from, err := this.generateExpressionVal(cast.Value)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// determine how to *bit cast*. because of course its not that simple.
|
||||
// thanks LLVM!
|
||||
fromType := analyzer.ReduceToBase(cast.Value.Type())
|
||||
toType := analyzer.ReduceToBase(cast.Type())
|
||||
switch toType.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
switch fromType.(type) {
|
||||
case *entity.TypePointer:
|
||||
// cast from pointer to int
|
||||
return this.blockManager.NewPtrToInt(from, irToType), nil
|
||||
}
|
||||
case *entity.TypePointer:
|
||||
switch fromType.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
// cast from int to pointer
|
||||
return this.blockManager.NewIntToPtr(from, irToType), nil
|
||||
default:
|
||||
// cast from something else to pointer
|
||||
irIndexType, err := this.generateTypeIndex()
|
||||
if err != nil { return nil, err }
|
||||
temporaryInteger := this.blockManager.NewBitCast(from, irIndexType)
|
||||
return this.blockManager.NewIntToPtr(temporaryInteger, irToType), nil
|
||||
}
|
||||
default:
|
||||
switch fromType.(type) {
|
||||
case *entity.TypePointer:
|
||||
// cast from pointer to something else
|
||||
irIndexType, err := this.generateTypeIndex()
|
||||
if err != nil { return nil, err }
|
||||
temporaryInteger := this.blockManager.NewPtrToInt(from, irIndexType)
|
||||
return this.blockManager.NewBitCast(temporaryInteger, irToType), nil
|
||||
}
|
||||
}
|
||||
|
||||
// default to a normal bit cast
|
||||
return this.blockManager.NewBitCast(from, irToType), nil
|
||||
}
|
||||
|
||||
func (this *generator) generateOperationVal (operation *entity.Operation) (llvm.Value, error) {
|
||||
nSameType := func (binary func (x, y llvm.Value) llvm.Value) (llvm.Value, error) {
|
||||
irX, err := this.generateExpressionVal(operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
for _, argument := range operation.Arguments[1:] {
|
||||
irY, err := this.generateExpressionVal(argument)
|
||||
if err != nil { return nil, err }
|
||||
irX = binary(irX, irY)
|
||||
}
|
||||
return irX, nil
|
||||
}
|
||||
|
||||
ty := analyzer.ReduceToBase(operation.Arguments[0].Type())
|
||||
irType, err := this.generateType(ty)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch operation.Operator {
|
||||
// math
|
||||
case entity.OperatorAdd:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewAdd(x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFAdd(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorSubtract:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewSub(x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFSub(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorMultiply:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewMul(x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFMul(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorDivide:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
if analyzer.IsUnsigned(ty) {
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewUDiv(x, y)
|
||||
})
|
||||
} else {
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewSDiv(x, y)
|
||||
})
|
||||
}
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFDiv(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorIncrement:
|
||||
irX, err := this.generateExpressionVal(operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
irType := irType.(*llvm.TypeInt)
|
||||
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
return this.blockManager.NewAdd (
|
||||
irX,
|
||||
llvm.NewConstInt(irType, 1)), nil
|
||||
case *entity.TypeFloat:
|
||||
return this.blockManager.NewFAdd (
|
||||
irX,
|
||||
llvm.NewConstInt(irType, 1)), nil
|
||||
}
|
||||
|
||||
case entity.OperatorDecrement:
|
||||
irX, err := this.generateExpressionVal(operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
irType := irType.(*llvm.TypeInt)
|
||||
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
return this.blockManager.NewSub (
|
||||
irX,
|
||||
llvm.NewConstInt(irType, 1)), nil
|
||||
case *entity.TypeFloat:
|
||||
return this.blockManager.NewFSub (
|
||||
irX,
|
||||
llvm.NewConstInt(irType, 1)), nil
|
||||
}
|
||||
|
||||
case entity.OperatorModulo:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
if analyzer.IsUnsigned(ty) {
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewURem(x, y)
|
||||
})
|
||||
} else {
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewSRem(x, y)
|
||||
})
|
||||
}
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFDiv(x, y)
|
||||
})
|
||||
}
|
||||
|
||||
// logic
|
||||
case entity.OperatorLogicalNot:
|
||||
irX, err := this.generateExpressionVal(operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
return this.blockManager.NewICmp(llvm.IPredicateEQ, irX, llvm.NewConstBool(false)), nil
|
||||
|
||||
case entity.OperatorLogicalOr:
|
||||
incomings := make([]*llvm.Incoming, len(operation.Arguments) + 1)
|
||||
exit := this.blockManager.newBlock()
|
||||
|
||||
for index, argument := range operation.Arguments {
|
||||
irX, err := this.generateExpressionVal(argument)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
incomings[index] = &llvm.Incoming {
|
||||
X: llvm.NewConstBool(true),
|
||||
Predecessor: this.blockManager.Block,
|
||||
}
|
||||
|
||||
previous := this.blockManager.Block
|
||||
block := this.blockManager.newBlock()
|
||||
previous.NewCondBr(irX, exit, block)
|
||||
|
||||
}
|
||||
this.blockManager.NewBr(exit)
|
||||
incomings[len(incomings) - 1] = &llvm.Incoming {
|
||||
X: llvm.NewConstBool(false),
|
||||
Predecessor: this.blockManager.Block,
|
||||
}
|
||||
this.blockManager.Block = exit
|
||||
return this.blockManager.NewPhi(incomings...), nil
|
||||
|
||||
case entity.OperatorLogicalAnd:
|
||||
incomings := make([]*llvm.Incoming, len(operation.Arguments) + 1)
|
||||
exit := this.blockManager.newBlock()
|
||||
|
||||
for index, argument := range operation.Arguments {
|
||||
irX, err := this.generateExpressionVal(argument)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
incomings[index] = &llvm.Incoming {
|
||||
X: llvm.NewConstBool(false),
|
||||
Predecessor: this.blockManager.Block,
|
||||
}
|
||||
|
||||
previous := this.blockManager.Block
|
||||
block := this.blockManager.newBlock()
|
||||
previous.NewCondBr(irX, block, exit)
|
||||
|
||||
}
|
||||
this.blockManager.NewBr(exit)
|
||||
incomings[len(incomings) - 1] = &llvm.Incoming {
|
||||
X: llvm.NewConstBool(true),
|
||||
Predecessor: this.blockManager.Block,
|
||||
}
|
||||
this.blockManager.Block = exit
|
||||
return this.blockManager.NewPhi(incomings...), nil
|
||||
|
||||
case entity.OperatorLogicalXor:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewICmp (
|
||||
llvm.IPredicateNE,
|
||||
this.blockManager.NewICmp(llvm.IPredicateEQ, x, llvm.NewConstBool(true)),
|
||||
this.blockManager.NewICmp(llvm.IPredicateEQ, x, llvm.NewConstBool(true)))
|
||||
})
|
||||
|
||||
// bit manipulation
|
||||
case entity.OperatorNot:
|
||||
irX, err := this.generateExpressionVal(operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
return this.blockManager.NewXor (
|
||||
irX,
|
||||
llvm.NewConstInt(irType.(*llvm.TypeInt), -1)), nil
|
||||
|
||||
case entity.OperatorOr:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewOr(x, y)
|
||||
})
|
||||
|
||||
case entity.OperatorAnd:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewAnd(x, y)
|
||||
})
|
||||
|
||||
case entity.OperatorXor:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewXor(x, y)
|
||||
})
|
||||
|
||||
case entity.OperatorLeftShift:
|
||||
irX, err := this.generateExpressionVal(operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
irY, err := this.generateExpressionVal(operation.Arguments[1])
|
||||
if err != nil { return nil, err }
|
||||
return this.blockManager.NewShl(irX, irY), nil
|
||||
|
||||
case entity.OperatorRightShift:
|
||||
irX, err := this.generateExpressionVal(operation.Arguments[0])
|
||||
if err != nil { return nil, err }
|
||||
irY, err := this.generateExpressionVal(operation.Arguments[1])
|
||||
if err != nil { return nil, err }
|
||||
if analyzer.IsUnsigned(ty) {
|
||||
return this.blockManager.NewLShr(irX, irY), nil
|
||||
} else {
|
||||
return this.blockManager.NewAShr(irX, irY), nil
|
||||
}
|
||||
|
||||
// comparison
|
||||
case entity.OperatorLess:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
var predicate llvm.IPredicate
|
||||
if analyzer.IsUnsigned(ty) {
|
||||
predicate = llvm.IPredicateULT
|
||||
} else {
|
||||
predicate = llvm.IPredicateSLT
|
||||
}
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewICmp(predicate, x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFCmp (
|
||||
llvm.FPredicateOLT,
|
||||
x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorGreater:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
var predicate llvm.IPredicate
|
||||
if analyzer.IsUnsigned(ty) {
|
||||
predicate = llvm.IPredicateUGT
|
||||
} else {
|
||||
predicate = llvm.IPredicateSGT
|
||||
}
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewICmp(predicate, x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFCmp (
|
||||
llvm.FPredicateOGT,
|
||||
x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorLessEqual:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
var predicate llvm.IPredicate
|
||||
if analyzer.IsUnsigned(ty) {
|
||||
predicate = llvm.IPredicateULE
|
||||
} else {
|
||||
predicate = llvm.IPredicateSLE
|
||||
}
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewICmp(predicate, x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFCmp (
|
||||
llvm.FPredicateOLE,
|
||||
x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorGreaterEqual:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
var predicate llvm.IPredicate
|
||||
if analyzer.IsUnsigned(ty) {
|
||||
predicate = llvm.IPredicateUGE
|
||||
} else {
|
||||
predicate = llvm.IPredicateSGE
|
||||
}
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewICmp(predicate, x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFCmp (
|
||||
llvm.FPredicateOGE,
|
||||
x, y)
|
||||
})
|
||||
}
|
||||
|
||||
case entity.OperatorEqual:
|
||||
switch ty.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewICmp (
|
||||
llvm.IPredicateEQ,
|
||||
x, y)
|
||||
})
|
||||
case *entity.TypeFloat:
|
||||
return nSameType(func (x, y llvm.Value) llvm.Value {
|
||||
return this.blockManager.NewFCmp (
|
||||
llvm.FPredicateOEQ,
|
||||
x, y)
|
||||
})
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
"BUG: generator doesnt know about operator",
|
||||
operation.Operator))
|
||||
}
|
||||
|
||||
|
||||
panic(fmt.Sprint (
|
||||
"BUG: generator failed to generate operation",
|
||||
operation))
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package generator
|
||||
|
||||
// import "fmt"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
// import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
|
||||
func (this *generator) generateBlock (block *entity.Block, mode resultMode) (llvm.Value, bool, error) {
|
||||
if len(block.Steps) == 0 { return nil, false, nil }
|
||||
|
||||
lastIndex := len(block.Steps) - 1
|
||||
for _, step := range block.Steps[:lastIndex] {
|
||||
_, _, err := this.generateExpressionAny(step)
|
||||
if err != nil { return nil, false, err }
|
||||
}
|
||||
|
||||
return this.generateExpression(block.Steps[lastIndex], mode)
|
||||
}
|
||||
|
||||
func (this *generator) generateBreak (brk *entity.Break) (llvm.Value, error) {
|
||||
loopEntry := this.blockManager.topLoop()
|
||||
if brk.Value != nil {
|
||||
value, loc, err := this.generateExpression(brk.Value, loopEntry.mode)
|
||||
if err != nil { return nil, err }
|
||||
loopEntry.value = value
|
||||
loopEntry.loc = loc
|
||||
}
|
||||
|
||||
loopEntry.stub = this.blockManager.Block
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (this *generator) generateReturn (ret *entity.Return) (llvm.Value, error) {
|
||||
if ret.Value == nil {
|
||||
this.blockManager.NewRet(nil)
|
||||
} else {
|
||||
value, err := this.generateExpressionVal(ret.Value)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewRet(value)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (this *generator) generateIfElse (ifelse *entity.IfElse, mode resultMode) (llvm.Value, bool, error) {
|
||||
condition, err := this.generateExpressionVal(ifelse.Condition)
|
||||
if err != nil { return nil, false, err }
|
||||
previous := this.blockManager.Block
|
||||
|
||||
var trueBlock, falseBlock *llvm.Block
|
||||
var tru, fals llvm.Value
|
||||
var loc bool
|
||||
|
||||
trueBlock = this.blockManager.newBlock()
|
||||
exitBlock := this.blockManager.newBlock()
|
||||
this.blockManager.Block = trueBlock
|
||||
|
||||
tru, loc, err = this.generateExpression(ifelse.True, mode)
|
||||
if err != nil { return nil, false, err }
|
||||
if !this.blockManager.Terminated() { this.blockManager.NewBr(exitBlock) }
|
||||
|
||||
if ifelse.False == nil {
|
||||
// there is no false case
|
||||
previous.NewCondBr(condition, trueBlock, exitBlock)
|
||||
this.blockManager.Block = exitBlock
|
||||
return nil, loc, nil
|
||||
} else {
|
||||
// there is a false case
|
||||
falseBlock = this.blockManager.newBlock()
|
||||
fals, _, err = this.generateExpression(ifelse.False, mode)
|
||||
if err != nil { return nil, false, err }
|
||||
if !this.blockManager.Terminated() { this.blockManager.NewBr(exitBlock) }
|
||||
|
||||
if mode == resultModeAny {
|
||||
// discard results of statements
|
||||
previous.NewCondBr(condition, trueBlock, falseBlock)
|
||||
this.blockManager.Block = exitBlock
|
||||
return nil, false, nil
|
||||
} else {
|
||||
// obtain results of statements
|
||||
// set up phi to capture results
|
||||
trueIncoming := &llvm.Incoming {
|
||||
X: tru,
|
||||
Predecessor: trueBlock,
|
||||
}
|
||||
falseIncoming := &llvm.Incoming {
|
||||
X: fals,
|
||||
Predecessor: falseBlock,
|
||||
}
|
||||
|
||||
previous.NewCondBr(condition, trueBlock, falseBlock)
|
||||
this.blockManager.Block = exitBlock
|
||||
return this.blockManager.NewPhi(trueIncoming, falseIncoming), loc, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateLoop (loop *entity.Loop, mode resultMode) (llvm.Value, bool, error) {
|
||||
previous := this.blockManager.Block
|
||||
body := this.blockManager.newBlock()
|
||||
previous.NewBr(body)
|
||||
|
||||
loopEntry := this.blockManager.pushLoop(mode)
|
||||
defer this.blockManager.popLoop()
|
||||
|
||||
_, _, err := this.generateExpressionAny(loop.Body)
|
||||
if err != nil { return nil, false, err }
|
||||
|
||||
this.blockManager.NewBr(body)
|
||||
exit := this.blockManager.newBlock()
|
||||
|
||||
if loopEntry.stub != nil {
|
||||
loopEntry.stub.SetTerminator(&llvm.TerminatorBr {
|
||||
Target: exit,
|
||||
})
|
||||
}
|
||||
return loopEntry.value, loopEntry.loc, nil
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package generator
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
|
||||
func (this *generator) generateFunction (
|
||||
function *entity.Function,
|
||||
) (
|
||||
*llvm.Function,
|
||||
error,
|
||||
) {
|
||||
key := entity.Key {
|
||||
Unit: function.Unit,
|
||||
Name: function.Signature.Name,
|
||||
}
|
||||
|
||||
params := make([]*llvm.Parameter, len(function.Signature.Arguments))
|
||||
|
||||
ret, err := this.generateType(function.Signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
for index, argument := range function.Signature.Arguments {
|
||||
paramType, err := this.generateType(argument.Type())
|
||||
if err != nil { return nil, err }
|
||||
params[index] = llvm.NewParameter(argument.Name, paramType)
|
||||
}
|
||||
|
||||
var name string; if function.LinkName == "" {
|
||||
name = key.LinkName()
|
||||
} else {
|
||||
name = function.LinkName
|
||||
}
|
||||
irFunc := this.module.NewFunction(name, ret, params...)
|
||||
|
||||
if function.Body != nil {
|
||||
this.blockManager = this.pushBlockManager(irFunc)
|
||||
defer this.popBlockManager()
|
||||
|
||||
this.blockManager.addFunctionArgumentDeclarations(function)
|
||||
|
||||
if function.Signature.Return == nil {
|
||||
_, _, err := this.generateExpressionAny(function.Body)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewRet(nil)
|
||||
} else {
|
||||
body, err := this.generateExpressionVal(function.Body)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewRet(body)
|
||||
}
|
||||
}
|
||||
|
||||
this.functions[key] = irFunc
|
||||
return irFunc, nil
|
||||
}
|
||||
|
||||
func (this *generator) generateMethod (
|
||||
method *entity.Method,
|
||||
) (
|
||||
*llvm.Function,
|
||||
error,
|
||||
) {
|
||||
key := entity.Key {
|
||||
Unit: method.Unit,
|
||||
Name: method.TypeName,
|
||||
Method: method.Signature.Name,
|
||||
}
|
||||
|
||||
params := make([]*llvm.Parameter, len(method.Signature.Arguments) + 1)
|
||||
|
||||
ret, err := this.generateType(method.Signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
for index, argument := range method.Signature.Arguments {
|
||||
paramType, err := this.generateType(argument.Type())
|
||||
if err != nil { return nil, err }
|
||||
params[index + 1] = llvm.NewParameter(argument.Name, paramType)
|
||||
}
|
||||
params[0] = llvm.NewParameter("this", new(llvm.TypePointer))
|
||||
|
||||
var name string; if method.LinkName == "" {
|
||||
name = key.LinkName()
|
||||
} else {
|
||||
name = method.LinkName
|
||||
}
|
||||
irFunc := this.module.NewFunction(name, ret, params...)
|
||||
|
||||
if method.Body != nil {
|
||||
this.blockManager = this.pushBlockManager(irFunc)
|
||||
defer this.popBlockManager()
|
||||
|
||||
this.blockManager.addFunctionArgumentDeclarations(method)
|
||||
|
||||
if method.Signature.Return == nil {
|
||||
_, _, err := this.generateExpressionAny(method.Body)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewRet(nil)
|
||||
} else {
|
||||
body, err := this.generateExpressionVal(method.Body)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewRet(body)
|
||||
}
|
||||
}
|
||||
|
||||
this.functions[key] = irFunc
|
||||
return irFunc, nil
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFunctionExternal (test *testing.T) {
|
||||
testString (test,
|
||||
`declare void @exit(i64 %status)
|
||||
define void @main() {
|
||||
0:
|
||||
call void @exit(i64 1)
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[exit status:Int] 'exit'
|
||||
[main] 'main' = [exit 1]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionCall (test *testing.T) {
|
||||
testString (test,
|
||||
`define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::f"() {
|
||||
0:
|
||||
%1 = call i64 @"0zNZN147MN2wzMAQ6NS2dQ==::g"(i64 5)
|
||||
ret i64 %1
|
||||
}
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::g"(i64 %x) {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 %x, ptr %1
|
||||
%2 = load i64, ptr %1
|
||||
%3 = call i64 @"0zNZN147MN2wzMAQ6NS2dQ==::h"(i64 %2)
|
||||
ret i64 %3
|
||||
}
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::h"(i64 %x) {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 %x, ptr %1
|
||||
%2 = load i64, ptr %1
|
||||
ret i64 %2
|
||||
}
|
||||
`,
|
||||
`
|
||||
[f ]:Int = [g 5]
|
||||
[g x:Int]:Int = [h x]
|
||||
[h x:Int]:Int = x
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFunctionCallVoid (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::f"() {
|
||||
0:
|
||||
%1 = call i64 @"0zNZN147MN2wzMAQ6NS2dQ==::g"(i64 5)
|
||||
call void @"0zNZN147MN2wzMAQ6NS2dQ==::h"()
|
||||
%2 = call i64 @"0zNZN147MN2wzMAQ6NS2dQ==::g"(i64 6)
|
||||
ret void
|
||||
}
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::g"(i64 %x) {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 %x, ptr %1
|
||||
%2 = load i64, ptr %1
|
||||
ret i64 %2
|
||||
}
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::h"() {
|
||||
0:
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[f] = { [g 5] [h] [g 6] }
|
||||
[g x:Int]:Int = x
|
||||
[h] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethod (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::Number" = type i64
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Number"
|
||||
store i64 5, ptr %1
|
||||
%2 = call i64 @"0zNZN147MN2wzMAQ6NS2dQ==::Number.number"(ptr %1)
|
||||
ret i64 %2
|
||||
}
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::Number.number"(ptr %this) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = load ptr, ptr %1
|
||||
%3 = load i64, ptr %2
|
||||
ret i64 %3
|
||||
}
|
||||
`,
|
||||
`
|
||||
Number: Int
|
||||
Number.[number]: Int = [.this]
|
||||
|
||||
[main]: Int = {
|
||||
num:Number = 5
|
||||
num.[number]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodChained (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::Number" = type i64
|
||||
define %"0zNZN147MN2wzMAQ6NS2dQ==::Number" @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 5, ptr %1
|
||||
%2 = call %"0zNZN147MN2wzMAQ6NS2dQ==::Number" @"0zNZN147MN2wzMAQ6NS2dQ==::Number.add"(ptr %1, i64 8)
|
||||
%3 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Number"
|
||||
store %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %2, ptr %3
|
||||
%4 = call %"0zNZN147MN2wzMAQ6NS2dQ==::Number" @"0zNZN147MN2wzMAQ6NS2dQ==::Number.mul"(ptr %3, i64 3)
|
||||
ret %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %4
|
||||
}
|
||||
define %"0zNZN147MN2wzMAQ6NS2dQ==::Number" @"0zNZN147MN2wzMAQ6NS2dQ==::Number.mul"(ptr %this, %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Number"
|
||||
store %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x, ptr %2
|
||||
%3 = load ptr, ptr %1
|
||||
%4 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %3
|
||||
%5 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %2
|
||||
%6 = mul %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %4, %5
|
||||
ret %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %6
|
||||
}
|
||||
define %"0zNZN147MN2wzMAQ6NS2dQ==::Number" @"0zNZN147MN2wzMAQ6NS2dQ==::Number.add"(ptr %this, %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Number"
|
||||
store %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x, ptr %2
|
||||
%3 = load ptr, ptr %1
|
||||
%4 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %3
|
||||
%5 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %2
|
||||
%6 = add %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %4, %5
|
||||
ret %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %6
|
||||
}
|
||||
define %"0zNZN147MN2wzMAQ6NS2dQ==::Number" @"0zNZN147MN2wzMAQ6NS2dQ==::Number.div"(ptr %this, %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Number"
|
||||
store %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x, ptr %2
|
||||
%3 = load ptr, ptr %1
|
||||
%4 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %3
|
||||
%5 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %2
|
||||
%6 = sdiv %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %4, %5
|
||||
ret %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %6
|
||||
}
|
||||
define %"0zNZN147MN2wzMAQ6NS2dQ==::Number" @"0zNZN147MN2wzMAQ6NS2dQ==::Number.sub"(ptr %this, %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Number"
|
||||
store %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %x, ptr %2
|
||||
%3 = load ptr, ptr %1
|
||||
%4 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %3
|
||||
%5 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Number", ptr %2
|
||||
%6 = sub %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %4, %5
|
||||
ret %"0zNZN147MN2wzMAQ6NS2dQ==::Number" %6
|
||||
}
|
||||
`,
|
||||
`
|
||||
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]
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethodGreeter (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::Greeter" = type { %"AAAAAAAAAAAAAAAAAAAAAA==::String" }
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Greeter"
|
||||
%2 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Greeter", ptr %1, i32 0, i32 0
|
||||
%3 = alloca [6 x i8]
|
||||
%4 = getelementptr [6 x i8], ptr %3, i32 0, i32 0
|
||||
store i8 104, ptr %4
|
||||
%5 = getelementptr [6 x i8], ptr %3, i32 0, i32 1
|
||||
store i8 101, ptr %5
|
||||
%6 = getelementptr [6 x i8], ptr %3, i32 0, i32 2
|
||||
store i8 108, ptr %6
|
||||
%7 = getelementptr [6 x i8], ptr %3, i32 0, i32 3
|
||||
store i8 108, ptr %7
|
||||
%8 = getelementptr [6 x i8], ptr %3, i32 0, i32 4
|
||||
store i8 111, ptr %8
|
||||
%9 = getelementptr [6 x i8], ptr %3, i32 0, i32 5
|
||||
store i8 0, ptr %9
|
||||
%10 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 0
|
||||
store ptr %3, ptr %10
|
||||
%11 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 6, ptr %11
|
||||
call void @"0zNZN147MN2wzMAQ6NS2dQ==::Greeter.greet"(ptr %1)
|
||||
ret void
|
||||
}
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::Greeter.greet"(ptr %this) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = load ptr, ptr %1
|
||||
%3 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Greeter", ptr %2, i32 0, i32 0
|
||||
%4 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %3, i32 0, i32 0
|
||||
%5 = load ptr, ptr %4
|
||||
%6 = getelementptr i8, ptr %5, i64 0
|
||||
%7 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @puts(ptr %6)
|
||||
ret void
|
||||
}
|
||||
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @puts(ptr %string)
|
||||
`,
|
||||
`
|
||||
[puts string: *Byte]: Index 'puts'
|
||||
|
||||
Greeter: (. message: String)
|
||||
Greeter.[greet] = [puts [@[.this.message 0]]]
|
||||
|
||||
[main] 'main' = {
|
||||
greeter: Greeter = (.
|
||||
message: 'hello\0'
|
||||
)
|
||||
greeter.[greet]
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package generator
|
||||
|
||||
import "sort"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
|
||||
type errNotFound string
|
||||
func (err errNotFound) Error () string {
|
||||
return string(err) + " not found"
|
||||
}
|
||||
|
||||
// Target contains information about the machine the code is being written for.
|
||||
type Target struct {
|
||||
// WordSize is the size of the machine word. This determines the width
|
||||
// of the Word type.
|
||||
WordSize uint64
|
||||
|
||||
// Arch specifies the machine architecture
|
||||
Arch string
|
||||
}
|
||||
|
||||
type generator struct {
|
||||
target Target
|
||||
tree analyzer.Tree
|
||||
module *llvm.Module
|
||||
|
||||
types map[entity.Key] llvm.Type
|
||||
functions map[entity.Key] *llvm.Function
|
||||
|
||||
managerStack []*blockManager
|
||||
blockManager *blockManager
|
||||
}
|
||||
|
||||
// Generate takes in a semantic tree and writes corresponding LLVM IR to the
|
||||
// given io.Writer. It returns an error in case there is something wrong with
|
||||
// the semantic tree that prevents the code generation process from occurring.
|
||||
func (this Target) Generate (tree analyzer.Tree) (*llvm.Module, error) {
|
||||
return (&generator {
|
||||
module: new(llvm.Module),
|
||||
target: this,
|
||||
tree: tree,
|
||||
types: make(map[entity.Key] llvm.Type),
|
||||
functions: make(map[entity.Key] *llvm.Function),
|
||||
}).generate()
|
||||
}
|
||||
|
||||
func (this *generator) generate () (*llvm.Module, error) {
|
||||
// generate functions
|
||||
functions := sortKeyedMapKeys(this.tree.Functions)
|
||||
for _, key := range functions {
|
||||
_, err := this.function(key)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
// generate methods
|
||||
types := sortKeyedMapKeys(this.tree.Types)
|
||||
for _, key := range types {
|
||||
ty := this.tree.Types[key]
|
||||
methods := sortMapKeys(ty.Methods)
|
||||
for _, methodName := range methods {
|
||||
methodKey := key
|
||||
methodKey.Method = methodName
|
||||
_, err := this.method(methodKey)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
}
|
||||
|
||||
return this.module, nil
|
||||
}
|
||||
|
||||
func (this *generator) typedef (key entity.Key) (llvm.Type, error) {
|
||||
ty, exists := this.types[key]
|
||||
if exists { return ty, nil }
|
||||
|
||||
def, exists := this.tree.Types[key]
|
||||
if !exists {
|
||||
return nil, errNotFound("typedef " + key.String())
|
||||
}
|
||||
return this.generateTypedef(def)
|
||||
}
|
||||
|
||||
func (this *generator) method (key entity.Key) (*llvm.Function, error) {
|
||||
method, exists := this.functions[key]
|
||||
if exists { return method, nil }
|
||||
|
||||
ty, exists := this.tree.Types[key.StripMethod()]
|
||||
if !exists {
|
||||
return nil, errNotFound("owner of method " + key.String())
|
||||
}
|
||||
|
||||
if method, exists := ty.Methods[key.Method]; exists {
|
||||
return this.generateMethod(method)
|
||||
}
|
||||
return nil, errNotFound("method " + key.String())
|
||||
}
|
||||
|
||||
func (this *generator) function (key entity.Key) (*llvm.Function, error) {
|
||||
function, exists := this.functions[key]
|
||||
if exists { return function, nil }
|
||||
|
||||
if function, exists := this.tree.Functions[key]; exists {
|
||||
return this.generateFunction(function)
|
||||
}
|
||||
return nil, errNotFound("function " + key.String())
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type keySlice []entity.Key
|
||||
|
||||
func (keys keySlice) Len () int {
|
||||
return len(keys)
|
||||
}
|
||||
|
||||
func (keys keySlice) Less (lefti, righti int) bool {
|
||||
left := keys[lefti]
|
||||
right := keys[righti]
|
||||
|
||||
return left.String() < right.String()
|
||||
}
|
||||
|
||||
func (keys keySlice) Swap (lefti, righti int) {
|
||||
keys[lefti], keys[righti] = keys[righti], keys[lefti]
|
||||
}
|
||||
|
||||
func sortKeyedMapKeys[T any] (unsorted map[entity.Key] T) keySlice {
|
||||
keys := make(keySlice, len(unsorted))
|
||||
index := 0
|
||||
for key := range unsorted {
|
||||
keys[index] = key
|
||||
index ++
|
||||
}
|
||||
sort.Sort(keys)
|
||||
return keys
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInterfaceBasic (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::Doer" = type { ptr, ptr }
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::T" = type i64
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Doer"
|
||||
%2 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Doer", ptr %1, i32 0, i32 0
|
||||
%3 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::T"
|
||||
store ptr %3, ptr %2
|
||||
%4 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Doer", ptr %1, i32 0, i32 1
|
||||
store ptr @"0zNZN147MN2wzMAQ6NS2dQ==::T.do", ptr %4
|
||||
%5 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Doer", ptr %1, i32 0, i32 1
|
||||
%6 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Doer", ptr %1, i32 0, i32 0
|
||||
%7 = load ptr, ptr %6
|
||||
%8 = load ptr, ptr %5
|
||||
call void %8(ptr %7)
|
||||
ret void
|
||||
}
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::T.do"(ptr %this) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
Doer: (~ [do])
|
||||
T: Int
|
||||
T.[do] = { }
|
||||
|
||||
[main] = {
|
||||
ifa:Doer = x:T
|
||||
ifa.[do]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceIntegerReturn (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::Number" = type i64
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::Numbered" = type { ptr, ptr }
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Number"
|
||||
store i64 5, ptr %1
|
||||
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Numbered"
|
||||
%3 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Numbered", ptr %2, i32 0, i32 0
|
||||
store ptr %1, ptr %3
|
||||
%4 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Numbered", ptr %2, i32 0, i32 1
|
||||
store ptr @"0zNZN147MN2wzMAQ6NS2dQ==::Number.number", ptr %4
|
||||
%5 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Numbered", ptr %2, i32 0, i32 1
|
||||
%6 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Numbered", ptr %2, i32 0, i32 0
|
||||
%7 = load ptr, ptr %6
|
||||
%8 = load ptr, ptr %5
|
||||
%9 = call i64 %8(ptr %7)
|
||||
ret i64 %9
|
||||
}
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::Number.number"(ptr %this) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = load ptr, ptr %1
|
||||
%3 = load i64, ptr %2
|
||||
ret i64 %3
|
||||
}
|
||||
`,
|
||||
`
|
||||
Numbered: (~ [number]: Int)
|
||||
Number: Int
|
||||
Number.[number]: Int = [.this]
|
||||
|
||||
[main]: Int = {
|
||||
num:Number = 5
|
||||
ifa:Numbered = num
|
||||
ifa.[number]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceWriter (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::File" = type i32
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::Writer" = type { ptr, ptr }
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
define i64 @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::File"
|
||||
store i32 0, ptr %1
|
||||
%2 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Writer"
|
||||
%3 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Writer", ptr %2, i32 0, i32 0
|
||||
store ptr %1, ptr %3
|
||||
%4 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Writer", ptr %2, i32 0, i32 1
|
||||
store ptr @"0zNZN147MN2wzMAQ6NS2dQ==::File.write", ptr %4
|
||||
%5 = load %"0zNZN147MN2wzMAQ6NS2dQ==::Writer", ptr %2
|
||||
call void @"0zNZN147MN2wzMAQ6NS2dQ==::sayHello"(%"0zNZN147MN2wzMAQ6NS2dQ==::Writer" %5)
|
||||
ret i64 0
|
||||
}
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::sayHello"(%"0zNZN147MN2wzMAQ6NS2dQ==::Writer" %writer) {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Writer"
|
||||
store %"0zNZN147MN2wzMAQ6NS2dQ==::Writer" %writer, ptr %1
|
||||
%2 = alloca { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%3 = alloca [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%4 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 0
|
||||
store i8 119, ptr %4
|
||||
%5 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 1
|
||||
store i8 101, ptr %5
|
||||
%6 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 2
|
||||
store i8 108, ptr %6
|
||||
%7 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 3
|
||||
store i8 108, ptr %7
|
||||
%8 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 4
|
||||
store i8 32, ptr %8
|
||||
%9 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 5
|
||||
store i8 104, ptr %9
|
||||
%10 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 6
|
||||
store i8 101, ptr %10
|
||||
%11 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 7
|
||||
store i8 108, ptr %11
|
||||
%12 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 8
|
||||
store i8 108, ptr %12
|
||||
%13 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 9
|
||||
store i8 111, ptr %13
|
||||
%14 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 10
|
||||
store i8 32, ptr %14
|
||||
%15 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 11
|
||||
store i8 116, ptr %15
|
||||
%16 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 12
|
||||
store i8 104, ptr %16
|
||||
%17 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 13
|
||||
store i8 101, ptr %17
|
||||
%18 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 14
|
||||
store i8 105, ptr %18
|
||||
%19 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 15
|
||||
store i8 114, ptr %19
|
||||
%20 = getelementptr [17 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 16
|
||||
store i8 10, ptr %20
|
||||
%21 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2, i32 0, i32 0
|
||||
store ptr %3, ptr %21
|
||||
%22 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 17, ptr %22
|
||||
%23 = load { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2
|
||||
%24 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Writer", ptr %1, i32 0, i32 1
|
||||
%25 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Writer", ptr %1, i32 0, i32 0
|
||||
%26 = load ptr, ptr %25
|
||||
%27 = load ptr, ptr %24
|
||||
%28 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %27(ptr %26, { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %23)
|
||||
ret void
|
||||
}
|
||||
define %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @"0zNZN147MN2wzMAQ6NS2dQ==::File.write"(ptr %this, { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %buffer) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
%2 = alloca { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
store { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %buffer, ptr %2
|
||||
%3 = load ptr, ptr %1
|
||||
%4 = load %"0zNZN147MN2wzMAQ6NS2dQ==::File", ptr %3
|
||||
%5 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2, i32 0, i32 0
|
||||
%6 = load ptr, ptr %5
|
||||
%7 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2, i32 0, i32 1
|
||||
%8 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %7
|
||||
%9 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @"0zNZN147MN2wzMAQ6NS2dQ==::write"(%"0zNZN147MN2wzMAQ6NS2dQ==::File" %4, ptr %6, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %8)
|
||||
ret %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %9
|
||||
}
|
||||
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @"0zNZN147MN2wzMAQ6NS2dQ==::write"(%"0zNZN147MN2wzMAQ6NS2dQ==::File" %fd, ptr %buffer, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %count)
|
||||
`,
|
||||
`
|
||||
[write fd:File buffer:*Byte count:Index]: Index
|
||||
|
||||
Writer: (~ [write buffer:*:Byte]: Index)
|
||||
File: I32
|
||||
File.[write buffer:*:Byte]:Index = [write [.this] [~*Byte buffer] [#buffer]]
|
||||
|
||||
[sayHello writer:Writer] = writer.[write 'well hello their\n']
|
||||
|
||||
[main]:Int 'main' = {
|
||||
stdout:File = 0
|
||||
[sayHello stdout]
|
||||
0
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceInStruct (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::Writer" = type { ptr, ptr }
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type { %"0zNZN147MN2wzMAQ6NS2dQ==::Writer" }
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::File" = type i32
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::A"
|
||||
%2 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::A", ptr %1, i32 0, i32 0
|
||||
%3 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Writer", ptr %2, i32 0, i32 0
|
||||
%4 = alloca i32
|
||||
store i32 0, ptr %4
|
||||
store ptr %4, ptr %3
|
||||
%5 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Writer", ptr %2, i32 0, i32 1
|
||||
store ptr @"0zNZN147MN2wzMAQ6NS2dQ==::File.write", ptr %5
|
||||
ret void
|
||||
}
|
||||
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @"0zNZN147MN2wzMAQ6NS2dQ==::File.write"(ptr %this, { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %buffer)
|
||||
`,
|
||||
`
|
||||
Writer: (~ [write buffer:*:Byte]: Index)
|
||||
A: (. output: Writer)
|
||||
File: I32
|
||||
File.[write buffer:*:Byte]:Index
|
||||
|
||||
[main] 'main' = {
|
||||
a:A = (. output: [~File 0])
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestInterfaceAssignment (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::Face" = type { ptr, ptr }
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::Impl" = type i64
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Face"
|
||||
%2 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Face", ptr %1, i32 0, i32 0
|
||||
%3 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::Impl"
|
||||
store ptr %3, ptr %2
|
||||
%4 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::Face", ptr %1, i32 0, i32 1
|
||||
store ptr @"0zNZN147MN2wzMAQ6NS2dQ==::Impl.x", ptr %4
|
||||
ret void
|
||||
}
|
||||
define i64 @"0zNZN147MN2wzMAQ6NS2dQ==::Impl.x"(ptr %this) {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
store ptr %this, ptr %1
|
||||
ret i64 5
|
||||
}
|
||||
`,
|
||||
`
|
||||
Face: (~ [x]:Int)
|
||||
Impl: Int
|
||||
Impl.[x]:Int = 5
|
||||
[main] 'main' = {
|
||||
i:Face = f:Impl
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
package generator
|
||||
|
||||
import "fmt"
|
||||
import "errors"
|
||||
import "git.tebibyte.media/fspl/fspl/llvm"
|
||||
import "git.tebibyte.media/fspl/fspl/entity"
|
||||
import "git.tebibyte.media/fspl/fspl/analyzer"
|
||||
|
||||
func (this *generator) generateLiteralInt (literal *entity.LiteralInt) (llvm.Value, error) {
|
||||
irType, err := this.generateType(analyzer.ReduceToBase(literal.Type()))
|
||||
if err != nil { return nil, err }
|
||||
|
||||
base := analyzer.ReduceToBase(literal.Type())
|
||||
switch base.(type) {
|
||||
case *entity.TypeInt, *entity.TypeWord:
|
||||
return llvm.NewConstInt(irType.(*llvm.TypeInt), int64(literal.Value)), nil
|
||||
case *entity.TypeFloat:
|
||||
return llvm.NewConstFloat(irType.(*llvm.TypeFloat), float64(literal.Value)), nil
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintln("int can't be used as", base))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateLiteralFloat (literal *entity.LiteralFloat) (llvm.Value, error) {
|
||||
irType, err := this.generateType(analyzer.ReduceToBase(literal.Type()))
|
||||
if err != nil { return nil, err }
|
||||
return llvm.NewConstFloat(irType.(*llvm.TypeFloat), literal.Value), nil
|
||||
}
|
||||
|
||||
// generateLiteralArrayLoc generates an array literal. irDestLoc specifies the
|
||||
// location to assign the data to. If it is nil, this function will allocate a
|
||||
// destination (if necessary) and return its value as a register.
|
||||
func (this *generator) generateLiteralArrayLoc (literal *entity.LiteralArray, irDestLoc llvm.Value) (llvm.Value, error) {
|
||||
destinationSpecified := irDestLoc != nil
|
||||
|
||||
makeDataType := func (elementType entity.Type, nullTerminator bool) (llvm.Type, int, error) {
|
||||
length := len(literal.Elements)
|
||||
if nullTerminator { length += 1 }
|
||||
irDataType, err := this.generateType(&entity.TypeArray {
|
||||
Element: elementType,
|
||||
Length: length,
|
||||
})
|
||||
return irDataType, length, err
|
||||
}
|
||||
populateData := func (elementType entity.Type, irDestLoc llvm.Value, length int, nullTerminator bool) error {
|
||||
irDataType, _, err := makeDataType(elementType, nullTerminator)
|
||||
if err != nil { return err }
|
||||
irElementType, err := this.generateType(elementType)
|
||||
if err != nil { return err }
|
||||
|
||||
for index := 0; index < length; index ++ {
|
||||
elementPointer := this.blockManager.NewGetElementPtr (
|
||||
irDataType, irDestLoc,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, int64(index)))
|
||||
|
||||
if index >= len(literal.Elements) {
|
||||
this.blockManager.NewStore (
|
||||
llvm.NewConstZeroInitializer(irElementType),
|
||||
elementPointer)
|
||||
} else {
|
||||
_, err := this.generateAssignmentToDestination (
|
||||
literal.Elements[index],
|
||||
elementType,
|
||||
elementPointer)
|
||||
if err != nil { return err }
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
irDestType, err := this.generateType(literal.Type())
|
||||
if err != nil { return nil, err }
|
||||
if !destinationSpecified {
|
||||
irDestLoc = this.blockManager.newAllocaFront(irDestType)
|
||||
}
|
||||
|
||||
base := analyzer.ReduceToBase(literal.Type())
|
||||
switch base := base.(type) {
|
||||
case *entity.TypeArray:
|
||||
populateData(base.Element, irDestLoc, base.Length, false)
|
||||
|
||||
case *entity.TypeSlice:
|
||||
irDataType, length, err := makeDataType(base.Element, false)
|
||||
if err != nil { return nil, err }
|
||||
destDataLoc := this.blockManager.newAllocaFront(irDataType)
|
||||
|
||||
err = populateData(base.Element, destDataLoc, length, false)
|
||||
if err != nil { return nil, err }
|
||||
this.sliceSetData(literal.Type(), irDestLoc, destDataLoc)
|
||||
this.sliceSetDefinedLength(literal.Type(), irDestLoc, int64(len(literal.Elements)))
|
||||
|
||||
case *entity.TypePointer:
|
||||
irDataType, length, err := makeDataType(base.Referenced, true)
|
||||
if err != nil { return nil, err }
|
||||
destDataLoc := this.blockManager.newAllocaFront(irDataType)
|
||||
err = populateData(base.Referenced, destDataLoc, length, true)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewStore(destDataLoc, irDestLoc)
|
||||
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintln("array can't be used as ", base))
|
||||
}
|
||||
|
||||
if destinationSpecified {
|
||||
return nil, nil
|
||||
} else {
|
||||
return irDestLoc, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateLiteralStringLoc (literal *entity.LiteralString, irDestLoc llvm.Value) (llvm.Value, error) {
|
||||
destinationSpecified := irDestLoc != nil
|
||||
|
||||
makeDataType := func (anyElementType entity.Type, nullTerminator bool) (llvm.Type, int, error) {
|
||||
elementType, ok := analyzer.ReduceToBase(anyElementType).(*entity.TypeInt)
|
||||
if !ok {
|
||||
return nil, 0, errors.New(fmt.Sprintln (
|
||||
"string can't be used with element type",
|
||||
anyElementType))
|
||||
}
|
||||
|
||||
var length int; switch {
|
||||
case elementType.Width >= 32: length = len(literal.ValueUTF32)
|
||||
case elementType.Width >= 16: length = len(literal.ValueUTF16)
|
||||
default: length = len(literal.ValueUTF8)
|
||||
}
|
||||
|
||||
if nullTerminator { length += 1 }
|
||||
irDataType, err := this.generateType(&entity.TypeArray {
|
||||
Element: anyElementType,
|
||||
Length: length,
|
||||
})
|
||||
return irDataType, length, err
|
||||
}
|
||||
populateData := func (anyElementType entity.Type, irDestLoc llvm.Value, length int, nullTerminator bool) error {
|
||||
elementType, ok := analyzer.ReduceToBase(anyElementType).(*entity.TypeInt)
|
||||
if !ok {
|
||||
return errors.New(fmt.Sprintln (
|
||||
"string can't be used with element type",
|
||||
anyElementType))
|
||||
}
|
||||
|
||||
irArrayType, _, err := makeDataType(anyElementType, nullTerminator)
|
||||
if err != nil { return err }
|
||||
|
||||
for index := 0; index < length; index ++ {
|
||||
var element llvm.Value; switch {
|
||||
case elementType.Width >= 32:
|
||||
if index >= len(literal.ValueUTF32) {
|
||||
element = llvm.NewConstZeroInitializer(llvm.I32)
|
||||
} else {
|
||||
element = llvm.NewConstInt(llvm.I32, int64(literal.ValueUTF32[index]))
|
||||
}
|
||||
|
||||
case elementType.Width >= 16:
|
||||
if index >= len(literal.ValueUTF16) {
|
||||
element = llvm.NewConstZeroInitializer(llvm.I16)
|
||||
} else {
|
||||
element = llvm.NewConstInt(llvm.I16, int64(literal.ValueUTF16[index]))
|
||||
}
|
||||
|
||||
default:
|
||||
if index >= len(literal.ValueUTF8) {
|
||||
element = llvm.NewConstZeroInitializer(llvm.I8)
|
||||
} else {
|
||||
element = llvm.NewConstInt(llvm.I8, int64(literal.ValueUTF8[index]))
|
||||
}
|
||||
}
|
||||
elementPointer := this.blockManager.NewGetElementPtr (
|
||||
irArrayType, irDestLoc,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, int64(index)))
|
||||
this.blockManager.NewStore(element, elementPointer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
irDestType, err := this.generateType(literal.Type())
|
||||
if err != nil { return nil, err }
|
||||
if !destinationSpecified {
|
||||
irDestLoc = this.blockManager.newAllocaFront(irDestType)
|
||||
}
|
||||
|
||||
base := analyzer.ReduceToBase(literal.Type())
|
||||
switch base := base.(type) {
|
||||
case *entity.TypeInt:
|
||||
var value llvm.Value; switch {
|
||||
case base.Width >= 32:
|
||||
value = llvm.NewConstInt(llvm.I32, int64(literal.ValueUTF32[0]))
|
||||
case base.Width >= 16:
|
||||
value = llvm.NewConstInt(llvm.I16, int64(literal.ValueUTF16[0]))
|
||||
default:
|
||||
value = llvm.NewConstInt(llvm.I8, int64(literal.ValueUTF8[0]))
|
||||
}
|
||||
this.blockManager.NewStore(value, irDestLoc)
|
||||
|
||||
case *entity.TypeArray:
|
||||
err := populateData(base.Element, irDestLoc, base.Length, false)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
case *entity.TypeSlice:
|
||||
irDataType, length, err := makeDataType(base.Element, false)
|
||||
if err != nil { return nil, err }
|
||||
destDataLoc := this.blockManager.newAllocaFront(irDataType)
|
||||
|
||||
err = populateData(base.Element, destDataLoc, length, false)
|
||||
if err != nil { return nil, err }
|
||||
this.sliceSetData(literal.Type(), irDestLoc, destDataLoc)
|
||||
this.sliceSetDefinedLength(literal.Type(), irDestLoc, int64(length))
|
||||
|
||||
case *entity.TypePointer:
|
||||
irDataType, length, err := makeDataType(base.Referenced, true)
|
||||
if err != nil { return nil, err }
|
||||
destDataLoc := this.blockManager.newAllocaFront(irDataType)
|
||||
err = populateData(base.Referenced, destDataLoc, length, true)
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewStore(destDataLoc, irDestLoc)
|
||||
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintln("string can't be used as ", base))
|
||||
}
|
||||
|
||||
if destinationSpecified {
|
||||
return nil, nil
|
||||
} else {
|
||||
return irDestLoc, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateLiteralStructLoc (literal *entity.LiteralStruct, irDestLoc llvm.Value) (llvm.Value, error) {
|
||||
destinationSpecified := irDestLoc != nil
|
||||
|
||||
base, ok := analyzer.ReduceToBase(literal.Type()).(*entity.TypeStruct)
|
||||
if !ok { return nil, errors.New(fmt.Sprintln("struct can't be used as ", literal.Type())) }
|
||||
|
||||
irDestType, err := this.generateType(literal.Type())
|
||||
if err != nil { return nil, err }
|
||||
if !destinationSpecified {
|
||||
irDestLoc = this.blockManager.newAllocaFront(irDestType)
|
||||
}
|
||||
|
||||
for index, member := range base.Members {
|
||||
elementPointer := this.blockManager.NewGetElementPtr (
|
||||
irDestType, irDestLoc,
|
||||
llvm.NewConstInt(llvm.I32, 0),
|
||||
llvm.NewConstInt(llvm.I32, int64(index)))
|
||||
|
||||
if pair, ok := literal.MemberMap[member.Name]; ok {
|
||||
_, err = this.generateAssignmentToDestination (
|
||||
pair.Value,
|
||||
member.Type(),
|
||||
elementPointer)
|
||||
if err != nil { return nil, err }
|
||||
} else {
|
||||
irMemberType, err := this.generateType(member.Type())
|
||||
if err != nil { return nil, err }
|
||||
this.blockManager.NewStore (
|
||||
llvm.NewConstZeroInitializer(irMemberType),
|
||||
elementPointer)
|
||||
}
|
||||
}
|
||||
|
||||
if destinationSpecified {
|
||||
return nil, nil
|
||||
} else {
|
||||
return irDestLoc, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateLiteralBoolean (literal *entity.LiteralBoolean) (llvm.Value, error) {
|
||||
return llvm.NewConstBool(bool(literal.Value)), nil
|
||||
}
|
||||
|
||||
func (this *generator) generateLiteralNil (literal *entity.LiteralNil) (llvm.Value, error) {
|
||||
ty, err := this.generateType(literal.Type())
|
||||
if err != nil { return nil, err }
|
||||
return llvm.NewConstZeroInitializer(ty), nil
|
||||
}
|
|
@ -0,0 +1,372 @@
|
|||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLiteralInteger (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 382, ptr %1
|
||||
%2 = alloca i64
|
||||
store i64 -4392, ptr %2
|
||||
%3 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
|
||||
store i8 128, ptr %3
|
||||
%4 = alloca i8
|
||||
store i8 -127, ptr %4
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
ia:Int = 382
|
||||
ib:Int = -4392
|
||||
ic:Byte = 128
|
||||
id:I8 = -127
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralArray (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca [3 x i64]
|
||||
%2 = getelementptr [3 x i64], ptr %1, i32 0, i32 0
|
||||
store i64 1, ptr %2
|
||||
%3 = getelementptr [3 x i64], ptr %1, i32 0, i32 1
|
||||
store i64 3, ptr %3
|
||||
%4 = getelementptr [3 x i64], ptr %1, i32 0, i32 2
|
||||
store i64 5, ptr %4
|
||||
%5 = alloca { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%6 = alloca [3 x i64]
|
||||
%7 = getelementptr [3 x i64], ptr %6, i32 0, i32 0
|
||||
store i64 4, ptr %7
|
||||
%8 = getelementptr [3 x i64], ptr %6, i32 0, i32 1
|
||||
store i64 6, ptr %8
|
||||
%9 = getelementptr [3 x i64], ptr %6, i32 0, i32 2
|
||||
store i64 8, ptr %9
|
||||
%10 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %5, i32 0, i32 0
|
||||
store ptr %6, ptr %10
|
||||
%11 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %5, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 3, ptr %11
|
||||
%12 = alloca ptr
|
||||
%13 = alloca [4 x i64]
|
||||
%14 = getelementptr [4 x i64], ptr %13, i32 0, i32 0
|
||||
store i64 7, ptr %14
|
||||
%15 = getelementptr [4 x i64], ptr %13, i32 0, i32 1
|
||||
store i64 9, ptr %15
|
||||
%16 = getelementptr [4 x i64], ptr %13, i32 0, i32 2
|
||||
store i64 11, ptr %16
|
||||
%17 = getelementptr [4 x i64], ptr %13, i32 0, i32 3
|
||||
store i64 zeroinitializer, ptr %17
|
||||
store ptr %13, ptr %12
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
a:3:Int = (* 1 3 5)
|
||||
b:*:Int = (* 4 6 8)
|
||||
c:*Int = (* 7 9 11)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralArrayNested (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type [2 x [2 x i64]]
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::A"
|
||||
%2 = getelementptr [2 x [2 x i64]], ptr %1, i32 0, i32 0
|
||||
%3 = getelementptr [2 x i64], ptr %2, i32 0, i32 0
|
||||
store i64 70, ptr %3
|
||||
%4 = getelementptr [2 x i64], ptr %2, i32 0, i32 1
|
||||
store i64 90, ptr %4
|
||||
%5 = getelementptr [2 x [2 x i64]], ptr %1, i32 0, i32 1
|
||||
%6 = getelementptr [2 x i64], ptr %5, i32 0, i32 0
|
||||
store i64 2, ptr %6
|
||||
%7 = getelementptr [2 x i64], ptr %5, i32 0, i32 1
|
||||
store i64 4, ptr %7
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: 2:2:Int
|
||||
[main] 'main' = {
|
||||
a:A = (*
|
||||
(* 70 90)
|
||||
(* 2 4))
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralArrayUndersized (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @main() {
|
||||
0:
|
||||
%1 = alloca [4 x i64]
|
||||
%2 = getelementptr [3 x i64], ptr %1, i32 0, i32 0
|
||||
store i64 5, ptr %2
|
||||
%3 = getelementptr [3 x i64], ptr %1, i32 0, i32 1
|
||||
store i64 9, ptr %3
|
||||
%4 = getelementptr [3 x i64], ptr %1, i32 0, i32 2
|
||||
store i64 3, ptr %4
|
||||
%5 = getelementptr [3 x i64], ptr %1, i32 0, i32 3
|
||||
store i64 zeroinitializer, ptr %5
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
a:4:Int = (* 5 9 3)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralString (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Rune" = type i32
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
|
||||
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
|
||||
store i8 97, ptr %2
|
||||
%3 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %2
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %3, ptr %1
|
||||
%4 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"
|
||||
%5 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"
|
||||
store i32 241, ptr %5
|
||||
%6 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Rune", ptr %5
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Rune" %6, ptr %4
|
||||
%7 = alloca [5 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%8 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %7, i32 0, i32 0
|
||||
store i8 115, ptr %8
|
||||
%9 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %7, i32 0, i32 1
|
||||
store i8 99, ptr %9
|
||||
%10 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %7, i32 0, i32 2
|
||||
store i8 zeroinitializer, ptr %10
|
||||
%11 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %7, i32 0, i32 3
|
||||
store i8 zeroinitializer, ptr %11
|
||||
%12 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %7, i32 0, i32 4
|
||||
store i8 zeroinitializer, ptr %12
|
||||
%13 = alloca [5 x %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"]
|
||||
%14 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"], ptr %13, i32 0, i32 0
|
||||
store i32 115, ptr %14
|
||||
%15 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"], ptr %13, i32 0, i32 1
|
||||
store i32 100, ptr %15
|
||||
%16 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"], ptr %13, i32 0, i32 2
|
||||
store i32 zeroinitializer, ptr %16
|
||||
%17 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"], ptr %13, i32 0, i32 3
|
||||
store i32 zeroinitializer, ptr %17
|
||||
%18 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Rune"], ptr %13, i32 0, i32 4
|
||||
store i32 zeroinitializer, ptr %18
|
||||
%19 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
|
||||
%20 = alloca [2 x i8]
|
||||
%21 = getelementptr [2 x i8], ptr %20, i32 0, i32 0
|
||||
store i8 115, ptr %21
|
||||
%22 = getelementptr [2 x i8], ptr %20, i32 0, i32 1
|
||||
store i8 101, ptr %22
|
||||
%23 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %19, i32 0, i32 0
|
||||
store ptr %20, ptr %23
|
||||
%24 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %19, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 2, ptr %24
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
sa:Byte = 'a'
|
||||
sb:Rune = 'ñ'
|
||||
sc:5:Byte = 'sc'
|
||||
sd:5:Rune = 'sd'
|
||||
se:String = 'se'
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralStringPointer (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca ptr
|
||||
%2 = alloca [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%3 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %2, i32 0, i32 0
|
||||
store i8 115, ptr %3
|
||||
%4 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %2, i32 0, i32 1
|
||||
store i8 102, ptr %4
|
||||
%5 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %2, i32 0, i32 2
|
||||
store i8 zeroinitializer, ptr %5
|
||||
store ptr %2, ptr %1
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
sf:*Byte = 'sf'
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralStringSlice (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
|
||||
%2 = alloca [2 x i8]
|
||||
%3 = getelementptr [2 x i8], ptr %2, i32 0, i32 0
|
||||
store i8 115, ptr %3
|
||||
%4 = getelementptr [2 x i8], ptr %2, i32 0, i32 1
|
||||
store i8 102, ptr %4
|
||||
%5 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 0
|
||||
store ptr %2, ptr %5
|
||||
%6 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %1, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 2, ptr %6
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
sf:String = 'sf'
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralStringUndersized (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca [5 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%2 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %1, i32 0, i32 0
|
||||
store i8 120, ptr %2
|
||||
%3 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %1, i32 0, i32 1
|
||||
store i8 121, ptr %3
|
||||
%4 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %1, i32 0, i32 2
|
||||
store i8 122, ptr %4
|
||||
%5 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %1, i32 0, i32 3
|
||||
store i8 zeroinitializer, ptr %5
|
||||
%6 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %1, i32 0, i32 4
|
||||
store i8 zeroinitializer, ptr %6
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
a:5:Byte = 'xyz'
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestLiteralStruct (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @main() {
|
||||
0:
|
||||
%1 = alloca { i64, { i64, i64 } }
|
||||
%2 = getelementptr { i64, { i64, i64 } }, ptr %1, i32 0, i32 0
|
||||
store i64 1, ptr %2
|
||||
%3 = getelementptr { i64, { i64, i64 } }, ptr %1, i32 0, i32 1
|
||||
%4 = getelementptr { i64, i64 }, ptr %3, i32 0, i32 0
|
||||
store i64 2, ptr %4
|
||||
%5 = getelementptr { i64, i64 }, ptr %3, i32 0, i32 1
|
||||
store i64 3, ptr %5
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
ta:(.x:Int y:(.z:Int a:Int)) = (.x: 1 y: (.z: 2 a: 3))
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedStructInstantiation (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type { i64 }
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::A"
|
||||
%2 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::A", ptr %1, i32 0, i32 0
|
||||
store i64 5, ptr %2
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: (.x:Int)
|
||||
[main] 'main' = {
|
||||
a:A = (.x: 5)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedArrayInstantiation (test *testing.T) {
|
||||
testString (test,
|
||||
`%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type [2 x i64]
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::A"
|
||||
%2 = getelementptr [2 x i64], ptr %1, i32 0, i32 0
|
||||
store i64 1, ptr %2
|
||||
%3 = getelementptr [2 x i64], ptr %1, i32 0, i32 1
|
||||
store i64 2, ptr %3
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: 2:Int
|
||||
[main] 'main' = {
|
||||
a:A = (* 1 2)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypedStringInstantiation (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type [1 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::B" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::C" = type ptr
|
||||
define void @main() {
|
||||
0:
|
||||
%1 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::A"
|
||||
%2 = getelementptr [1 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %1, i32 0, i32 0
|
||||
store i8 104, ptr %2
|
||||
%3 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::B"
|
||||
%4 = alloca [1 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%5 = getelementptr [1 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %4, i32 0, i32 0
|
||||
store i8 104, ptr %5
|
||||
%6 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::B", ptr %3, i32 0, i32 0
|
||||
store ptr %4, ptr %6
|
||||
%7 = getelementptr %"0zNZN147MN2wzMAQ6NS2dQ==::B", ptr %3, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 1, ptr %7
|
||||
%8 = alloca %"0zNZN147MN2wzMAQ6NS2dQ==::C"
|
||||
%9 = alloca [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%10 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %9, i32 0, i32 0
|
||||
store i8 104, ptr %10
|
||||
%11 = getelementptr [2 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %9, i32 0, i32 1
|
||||
store i8 zeroinitializer, ptr %11
|
||||
store ptr %9, ptr %8
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: 1:Byte
|
||||
B: *:Byte
|
||||
C: *Byte
|
||||
[main] 'main' = {
|
||||
a:A = 'h'
|
||||
b:B = 'h'
|
||||
c:C = 'h'
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestPrintDigit (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::printDigit"(%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %digit) {
|
||||
0:
|
||||
%1 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %digit, ptr %1
|
||||
%2 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %1
|
||||
%3 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"
|
||||
store i8 48, ptr %3
|
||||
%4 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Byte", ptr %3
|
||||
%5 = add %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %2, %4
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Byte" %5, ptr %1
|
||||
%6 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 1, ptr %1, i64 1)
|
||||
ret void
|
||||
}
|
||||
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 %file, ptr %buffer, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %count)
|
||||
`,
|
||||
`
|
||||
[write file:I32 buffer:*Byte count:Index]: Index 'write'
|
||||
|
||||
[printDigit digit:Byte] = {
|
||||
digit = [+ digit '0']
|
||||
[write 1 [@digit] 1]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSignedUnsignedDivision (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
%2 = sdiv i64 20, -5
|
||||
store i64 %2, ptr %1
|
||||
%3 = alloca i64
|
||||
%4 = sdiv i64 15, 3
|
||||
store i64 %4, ptr %3
|
||||
%5 = alloca i64
|
||||
%6 = srem i64 7, 3
|
||||
store i64 %6, ptr %5
|
||||
%7 = alloca i64
|
||||
%8 = udiv i64 20, 5
|
||||
store i64 %8, ptr %7
|
||||
%9 = alloca i64
|
||||
%10 = udiv i64 15, 3
|
||||
store i64 %10, ptr %9
|
||||
%11 = alloca i64
|
||||
%12 = urem i64 7, 3
|
||||
store i64 %12, ptr %11
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
a:Int = [/ 20 -5]
|
||||
b:Int = [/ 15 3]
|
||||
c:Int = [% 7 3]
|
||||
|
||||
d:UInt = [/ 20 5]
|
||||
e:UInt = [/ 15 3]
|
||||
f:UInt = [% 7 3]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestFloatMath (test *testing.T) {
|
||||
testString (test,
|
||||
`define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca double
|
||||
%2 = fadd double 2.0, 3.0
|
||||
store double %2, ptr %1
|
||||
%3 = alloca double
|
||||
%4 = load double, ptr %1
|
||||
%5 = fmul double %4, 100.0
|
||||
store double %5, ptr %3
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
[main] = {
|
||||
sum:F64 = [+ 2 3]
|
||||
prd:F64 = [* sum 100]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestCompare (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Bool" = type i8
|
||||
%"0zNZN147MN2wzMAQ6NS2dQ==::A" = type i64
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca i64
|
||||
store i64 32, ptr %1
|
||||
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::Bool"
|
||||
%3 = load i64, ptr %1
|
||||
%4 = icmp sgt i64 %3, 1
|
||||
store i1 %4, ptr %2
|
||||
%5 = load i64, ptr %1
|
||||
%6 = icmp slt i64 0, %5
|
||||
%7 = icmp slt i1 %6, 50
|
||||
store i1 %7, ptr %2
|
||||
%8 = load i64, ptr %1
|
||||
%9 = icmp eq i64 32, %8
|
||||
store i1 %9, ptr %2
|
||||
%10 = icmp sgt i64 5, 3
|
||||
store i1 %10, ptr %2
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: Int
|
||||
[main] = {
|
||||
x:Int = 32
|
||||
b:Bool = [> x 1]
|
||||
b = [< 0 x 50]
|
||||
b = [= 32 x]
|
||||
b = [> [~A 5] [~A 3]]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSlice (test *testing.T) {
|
||||
testString (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::String" = type { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::print"(%"AAAAAAAAAAAAAAAAAAAAAA==::String" %string) {
|
||||
0:
|
||||
%1 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::String" %string, ptr %1
|
||||
%2 = alloca %"AAAAAAAAAAAAAAAAAAAAAA==::String"
|
||||
br label %3
|
||||
3:
|
||||
%4 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 1
|
||||
%5 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %4
|
||||
%6 = icmp ult %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %5, 1
|
||||
br i1 %6, label %7, label %8
|
||||
7:
|
||||
br label %21
|
||||
8:
|
||||
%9 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 0
|
||||
%10 = load ptr, ptr %9
|
||||
%11 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 1, ptr %10, i64 1)
|
||||
%12 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 0
|
||||
%13 = load ptr, ptr %12
|
||||
%14 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %1, i32 0, i32 1
|
||||
%15 = load %"AAAAAAAAAAAAAAAAAAAAAA==::Index", ptr %14
|
||||
%16 = sub %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %15, 1
|
||||
%17 = getelementptr i8, ptr %13, i64 1
|
||||
%18 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 0
|
||||
store ptr %17, ptr %18
|
||||
%19 = getelementptr %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %16, ptr %19
|
||||
%20 = load %"AAAAAAAAAAAAAAAAAAAAAA==::String", ptr %2
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::String" %20, ptr %1
|
||||
br label %3
|
||||
21:
|
||||
ret void
|
||||
}
|
||||
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(i32 %file, ptr %buffer, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %count)
|
||||
`,
|
||||
`
|
||||
[write file:I32 buffer:*Byte count:Index]: Index 'write'
|
||||
|
||||
[print string: String] = loop {
|
||||
if [< [#string] 1] then [break]
|
||||
[write 1 [~*Byte string] 1]
|
||||
string = [\string 1/]
|
||||
}
|
||||
`)
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package generator
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUnitTwo (test *testing.T) {
|
||||
testUnits (test,
|
||||
`%"E80Pxr3rNdulEDOmHs9Hsg==::X" = type i64
|
||||
define %"E80Pxr3rNdulEDOmHs9Hsg==::X" @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
ret i64 5
|
||||
}
|
||||
`,
|
||||
`[main]:something::X = 5`,
|
||||
|
||||
"something.fspl",
|
||||
`+ X:Int`,
|
||||
)}
|
||||
|
||||
func TestUnitAssignRestricted (test *testing.T) {
|
||||
testUnits (test,
|
||||
`%"s0sfKEEPN2W0H9cNKD+OOg==::RestrictedInt" = type i64
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
%1 = alloca %"s0sfKEEPN2W0H9cNKD+OOg==::RestrictedInt"
|
||||
%2 = alloca %"s0sfKEEPN2W0H9cNKD+OOg==::RestrictedInt"
|
||||
%3 = load %"s0sfKEEPN2W0H9cNKD+OOg==::RestrictedInt", ptr %2
|
||||
store %"s0sfKEEPN2W0H9cNKD+OOg==::RestrictedInt" %3, ptr %1
|
||||
ret void
|
||||
}
|
||||
`,
|
||||
`[main] = {
|
||||
x:other::RestrictedInt
|
||||
y:other::RestrictedInt
|
||||
x = y
|
||||
}`,
|
||||
|
||||
"other.fspl",
|
||||
`~ RestrictedInt:Int`,
|
||||
)}
|
||||
|
||||
func TestNestedUnitTypedef (test *testing.T) {
|
||||
testUnits (test,
|
||||
`%"kOsrvvRIOh2nYGTmlfGyKQ==::X" = type i64
|
||||
%"qnTuCId6O/ihTdVz5Ln6WQ==::X" = type %"kOsrvvRIOh2nYGTmlfGyKQ==::X"
|
||||
define %"qnTuCId6O/ihTdVz5Ln6WQ==::X" @"0zNZN147MN2wzMAQ6NS2dQ==::main"() {
|
||||
0:
|
||||
ret i64 5
|
||||
}
|
||||
`,
|
||||
`[main]:layer1::X = 5`,
|
||||
|
||||
"layer0.fspl",
|
||||
`+ X:Int`,
|
||||
|
||||
"layer1.fspl",
|
||||
`+ X:layer0::X`,
|
||||
)}
|
||||
|
||||
func TestUnitInterfaceSatisfaction (test *testing.T) {
|
||||
testUnits (test,
|
||||
`%"AAAAAAAAAAAAAAAAAAAAAA==::Index" = type i64
|
||||
%"qsUY87+PPGG/YPYMcnGU/g==::FileDescriptor" = type i64
|
||||
%"1cqXRNyHN06gi9QQb4GCsg==::File" = type %"qsUY87+PPGG/YPYMcnGU/g==::FileDescriptor"
|
||||
%"1cqXRNyHN06gi9QQb4GCsg==::Writer" = type { ptr, ptr }
|
||||
%"AAAAAAAAAAAAAAAAAAAAAA==::Byte" = type i8
|
||||
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @write(%"qsUY87+PPGG/YPYMcnGU/g==::FileDescriptor" %file, ptr %buffer, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %count)
|
||||
define i32 @main() {
|
||||
0:
|
||||
%1 = alloca %"1cqXRNyHN06gi9QQb4GCsg==::File"
|
||||
store i64 1, ptr %1
|
||||
%2 = alloca %"1cqXRNyHN06gi9QQb4GCsg==::Writer"
|
||||
%3 = getelementptr %"1cqXRNyHN06gi9QQb4GCsg==::Writer", ptr %2, i32 0, i32 0
|
||||
store ptr %1, ptr %3
|
||||
%4 = getelementptr %"1cqXRNyHN06gi9QQb4GCsg==::Writer", ptr %2, i32 0, i32 1
|
||||
store ptr @"1cqXRNyHN06gi9QQb4GCsg==::File.write", ptr %4
|
||||
%5 = load %"1cqXRNyHN06gi9QQb4GCsg==::Writer", ptr %2
|
||||
call void @"0zNZN147MN2wzMAQ6NS2dQ==::sayHello"(%"1cqXRNyHN06gi9QQb4GCsg==::Writer" %5)
|
||||
ret i32 0
|
||||
}
|
||||
define void @"0zNZN147MN2wzMAQ6NS2dQ==::sayHello"(%"1cqXRNyHN06gi9QQb4GCsg==::Writer" %writer) {
|
||||
0:
|
||||
%1 = alloca %"1cqXRNyHN06gi9QQb4GCsg==::Writer"
|
||||
store %"1cqXRNyHN06gi9QQb4GCsg==::Writer" %writer, ptr %1
|
||||
%2 = alloca { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }
|
||||
%3 = alloca [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"]
|
||||
%4 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 0
|
||||
store i8 104, ptr %4
|
||||
%5 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 1
|
||||
store i8 105, ptr %5
|
||||
%6 = getelementptr [3 x %"AAAAAAAAAAAAAAAAAAAAAA==::Byte"], ptr %3, i32 0, i32 2
|
||||
store i8 10, ptr %6
|
||||
%7 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2, i32 0, i32 0
|
||||
store ptr %3, ptr %7
|
||||
%8 = getelementptr { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2, i32 0, i32 1
|
||||
store %"AAAAAAAAAAAAAAAAAAAAAA==::Index" 3, ptr %8
|
||||
%9 = load { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" }, ptr %2
|
||||
%10 = getelementptr %"1cqXRNyHN06gi9QQb4GCsg==::Writer", ptr %1, i32 0, i32 1
|
||||
%11 = getelementptr %"1cqXRNyHN06gi9QQb4GCsg==::Writer", ptr %1, i32 0, i32 0
|
||||
%12 = load ptr, ptr %11
|
||||
%13 = load ptr, ptr %10
|
||||
%14 = call %"AAAAAAAAAAAAAAAAAAAAAA==::Index" %13(ptr %12, { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %9)
|
||||
ret void
|
||||
}
|
||||
declare %"AAAAAAAAAAAAAAAAAAAAAA==::Index" @"1cqXRNyHN06gi9QQb4GCsg==::File.write"(ptr %this, { ptr, %"AAAAAAAAAAAAAAAAAAAAAA==::Index" } %buffer)
|
||||
`,
|
||||
`[sayHello writer:io::Writer] = writer.[write 'hi\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]]`,
|
||||
)}
|
|
@ -0,0 +1,10 @@
|
|||
// Package native provides a generator target describing the current system.
|
||||
// This is accomplished using several conditionally compiled source files.
|
||||
package native
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
// NativeTarget returns a target describing the current system.
|
||||
func NativeTarget () generator.Target {
|
||||
return nativeTarget()
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package native
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
func nativeTarget () generator.Target {
|
||||
return generator.Target {
|
||||
WordSize: 32,
|
||||
Arch: "i386",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package native
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
func nativeTarget () generator.Target {
|
||||
return generator.Target {
|
||||
WordSize: 64,
|
||||
Arch: "x86_64",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package native
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
func nativeTarget () generator.Target {
|
||||
// this may not be accurate, can't find info online about amd64p32
|
||||
return generator.Target {
|
||||
WordSize: 32,
|
||||
Arch: "x86",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package native
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
func nativeTarget () generator.Target {
|
||||
return generator.Target {
|
||||
WordSize: 32,
|
||||
Arch: "arm",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package native
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
func nativeTarget () generator.Target {
|
||||
return generator.Target {
|
||||
WordSize: 64,
|
||||
Arch: "aarch64",
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package native
|
||||
|
||||
import "git.tebibyte.media/fspl/fspl/generator"
|
||||
|
||||
func nativeTarget () generator.Target {
|
||||
return generator.Target {
|
||||
WordSize: 64,
|
||||
Arch: "aarch64_be",
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue