Compare commits
1528 Commits
Author | SHA1 | Date |
---|---|---|
greg | 71b3365de2 | |
greg | cf9ce74394 | |
greg | f5d1c89574 | |
greg | 8d1e0ebdea | |
greg | 69c215eac9 | |
greg | 8a34034819 | |
greg | 403b171c72 | |
greg | e5a09a6ee8 | |
greg | e1a83b5de3 | |
greg | 8b1dd561f2 | |
greg | 6ebe893acb | |
greg | c9052e0a3b | |
greg | 56e6eb44f9 | |
greg | 642f21d298 | |
greg | c12cb99b24 | |
greg | 8dc8833eb3 | |
greg | b517bc2366 | |
greg | 73519d5be5 | |
greg | 8b6de6961f | |
greg | 3eaeeb5509 | |
greg | b91c3c9da5 | |
greg | 08da787aae | |
greg | d6f2fe6e02 | |
greg | a85d3c46bd | |
greg | 25f51a314d | |
greg | 6c3a4f907b | |
greg | 22887678bd | |
greg | 1ecf1e506c | |
greg | 72944ded1b | |
greg | b65779fb93 | |
greg | 418d77770f | |
greg | 5572e0eebb | |
greg | 65bc32b033 | |
greg | 29f4060a71 | |
greg | 09dbe5b736 | |
greg | cfa65e5339 | |
greg | 9a28ccfd85 | |
greg | ea542192be | |
greg | 79635f2f86 | |
greg | 2b5b1589b0 | |
greg | 44c073320b | |
greg | c04e4356a1 | |
greg | 24e0ecbe73 | |
greg | fd66a9711d | |
greg | a5c9aca4d7 | |
greg | cefaeb1180 | |
greg | 724237545f | |
greg | 0f7f5cb416 | |
greg | b4da57f5c5 | |
greg | 8b87945bee | |
greg | f96469178d | |
greg | 34abb9b081 | |
greg | 89d967aee4 | |
greg | 0540df4024 | |
greg | 61182a847f | |
greg | f6dcd7f0b8 | |
greg | 16dc973aa6 | |
greg | 611e46938d | |
greg | 3d6447abb4 | |
greg | a74027bb1f | |
greg | 583e87c19a | |
greg | 12ed2f5c8e | |
greg | 3caf9c763c | |
greg | cd20afc3c7 | |
greg | 063a13f7ff | |
greg | b0a1f3337c | |
greg | 2e147e141e | |
greg | 44938aa4e6 | |
greg | 44ae10b7ae | |
greg | fa1544c71f | |
greg | fde169b623 | |
greg | 6e92b03f81 | |
greg | 0dd6b26e5a | |
greg | a3bb3ee514 | |
greg | 7ae41e717d | |
greg | 24089da788 | |
greg | bfb36b90e4 | |
greg | e750247134 | |
greg | a8efe40b57 | |
greg | dae619c6fa | |
greg | c9bfa2b540 | |
greg | e708c728d2 | |
greg | b65d6e4c8e | |
greg | d9eca8ffb3 | |
greg | a600d34712 | |
greg | aae2ee53cd | |
greg | bf3dcc18d0 | |
greg | baf499ee5a | |
greg | 3b19fc5aa9 | |
greg | 16bf166fa9 | |
greg | d832583ed9 | |
greg | 87ecc6f0cb | |
greg | ee87695626 | |
greg | 37c77d93d7 | |
greg | b62968379a | |
greg | aa705b4eee | |
greg | d67ccf5c7a | |
greg | d9330bed26 | |
greg | efe65edfe6 | |
greg | 7c9154de53 | |
greg | 10e40669b5 | |
greg | ca37e006b9 | |
greg | 6d3f5f4b81 | |
greg | e3bd108e6c | |
greg | 2ec3b21ebf | |
greg | b6e3469573 | |
greg | 32fe7430a4 | |
greg | c332747c3e | |
greg | 33c2786ea1 | |
greg | 30498d5c98 | |
greg | bc01a5ded8 | |
greg | 71386be80e | |
greg | ccdc02bbd0 | |
greg | 3a207cf7a7 | |
greg | 66f71606ef | |
greg | 53ce31ea8c | |
greg | 4c688ce8b2 | |
greg | 40579d80ce | |
greg | fa1257e2cd | |
greg | e9fd20bfe5 | |
greg | dfbd951aaf | |
greg | 6b47ecf2d7 | |
greg | a8b9f5046e | |
greg | 83e05fe382 | |
greg | 5271429715 | |
greg | f88f2e8550 | |
greg | 7097775a4a | |
greg | 32d082e119 | |
greg | 376fa1d1d1 | |
greg | 6fb9b4c2d3 | |
greg | f1d1042916 | |
greg | 207f73d607 | |
greg | 8dc0ad2348 | |
greg | bb39c59db2 | |
greg | 10bfeab7e4 | |
greg | fe08e64860 | |
greg | fd517351de | |
greg | e12ff6f30b | |
greg | 176b286332 | |
greg | 3987360f8e | |
greg | 78d1e93e4b | |
greg | 856c0f95ce | |
greg | 3fa624bef4 | |
greg | f27a65018d | |
greg | 548a7b5f36 | |
greg | d80d0d0904 | |
greg | 6162bae1ac | |
greg | fe7ba339b5 | |
greg | 6a232907c5 | |
greg | a8583f6bc4 | |
greg | bdee4fe7c6 | |
greg | 5cdc2f3d07 | |
greg | eb2adb5b79 | |
greg | 2b407a4a83 | |
greg | 6da6f6312d | |
greg | ce2a65b044 | |
greg | ffdae14a88 | |
greg | 94ea7bcd09 | |
greg | 4ebf7fe879 | |
greg | efbeff916a | |
greg | e9ea7811df | |
greg | 198f93c533 | |
greg | 694c152fcd | |
greg | f8f3095f89 | |
greg | c68c23ed68 | |
greg | 4f972f20a7 | |
greg | 9d2e5918af | |
greg | 14fc2a5d10 | |
greg | 2b8e2749a4 | |
greg | 6c369b072f | |
greg | 938c0401d1 | |
greg | a829fb6cd8 | |
greg | 004b056232 | |
greg | 8e9b410e02 | |
greg | b82eebdeec | |
greg | 153e7977d3 | |
greg | 5b5368ce6f | |
greg | 7a67890227 | |
greg | 04253543e9 | |
greg | 3a98096b61 | |
greg | 9476e7039b | |
greg | c767402865 | |
greg | 61972410ea | |
greg | d3f9430a18 | |
greg | 81323cafd4 | |
greg | 14c08bbcdb | |
greg | 4319c802f5 | |
greg | 9e58e3d7de | |
greg | ac0050e5d1 | |
greg | d06cf90fce | |
greg | 712da62d35 | |
greg | 57f3d39ea1 | |
greg | 6d88447458 | |
greg | 0451676ba7 | |
greg | 2929362046 | |
greg | 375db28ebb | |
greg | 1622a6ce44 | |
greg | 7e899246e9 | |
greg | 8610bd7a87 | |
greg | 70f715fbb2 | |
greg | 7360e698dd | |
greg | 5b35c2a036 | |
greg | 8d8d7d8bf8 | |
greg | 981d4f88bf | |
greg | 42aa316a23 | |
greg | 58b37e56ae | |
greg | 2bf777f37b | |
greg | bdcae36b60 | |
greg | dbcd2278a6 | |
greg | 2490aaf3f4 | |
greg | d4ad97b39a | |
greg | 24213070a3 | |
greg | 051669b4cc | |
greg | c64f53a050 | |
greg | 8f176543c7 | |
greg | 9716b5e55b | |
greg | 956353cd80 | |
greg | 98db60498a | |
greg | 7694afc9e2 | |
greg | 0bcd7e6f41 | |
greg | d515b1658a | |
greg | e501f4bd10 | |
greg | 5bac01cf20 | |
greg | 0e9b3229e9 | |
greg | b709cfd51a | |
greg | e34295a6f7 | |
greg | 8dc34e4b49 | |
greg | 2cc3367666 | |
greg | 452f2ab188 | |
greg | be175a2b75 | |
greg | 00a0de4431 | |
greg | f041cc17d2 | |
greg | 95fe1941a1 | |
greg | b35262c444 | |
greg | 9bb3a2be88 | |
greg | 9fa0576547 | |
greg | 6fba0cc5b4 | |
greg | a6eb2b4020 | |
greg | 03793e08d3 | |
greg | 2be55958f4 | |
greg | bcf48d0ecb | |
greg | f0ed63ccf3 | |
greg | 6012bd1087 | |
greg | 866c9211f9 | |
greg | df7e74c79d | |
greg | abbd02eaef | |
greg | 993741e67f | |
greg | fbb7b995b8 | |
greg | 9d4f086a04 | |
greg | e38ae1c3f1 | |
greg | d969d573fa | |
greg | 35da1748f0 | |
greg | 5e1799268d | |
greg | 42a801d346 | |
greg | a80e1bd706 | |
greg | afd9aa52c5 | |
greg | 5a70784346 | |
greg | 0dff177e8f | |
greg | cf91f74912 | |
greg | 06e9452718 | |
greg | 7d3ae36058 | |
greg | e8f1f51639 | |
greg | 170cf349d7 | |
greg | f3f1dcc0a4 | |
greg | c0111e30bc | |
greg | c225e469ee | |
greg | 1ce06bc0ef | |
greg | 10c3a60515 | |
greg | ff73ce7b36 | |
greg | ede8a9076a | |
greg | a63dcf91b0 | |
greg | 479a098e0f | |
greg | 1085b528fe | |
greg | 9b3b5c5541 | |
greg | ab8e24a276 | |
greg | 09e2d8579d | |
greg | ee7861cbd0 | |
greg | b88def8a2e | |
greg | 30676722a3 | |
greg | 801c90aaa7 | |
greg | 02667b018c | |
greg | 1032c7c7a2 | |
greg | fa295aab28 | |
greg | a0f4abb9a3 | |
greg | 78b454fb32 | |
greg | 5491169d55 | |
greg | 2b338fd3c9 | |
greg | 821f321261 | |
greg | 846eeae04c | |
greg | 22f2750853 | |
greg | f2f8ac7ee8 | |
greg | d0c5dce92b | |
greg | 8eda74c9a5 | |
greg | 2efac109ef | |
greg | 215e2bbb0d | |
greg | 2590def3be | |
greg | 879a7de83d | |
greg | 4c2e0b8a21 | |
greg | 282c42da3c | |
greg | 87e68988c8 | |
greg | 7ac97ca6e8 | |
greg | 26a8ff307f | |
greg | 6be208b51d | |
greg | e00948cad9 | |
greg | 0af6fed505 | |
greg | 1f527f7949 | |
greg | 8680c4faf6 | |
greg | b198984fc5 | |
greg | 58779f8470 | |
greg | a0fa50392c | |
greg | d357876b16 | |
greg | e42f0c644c | |
greg | 2ec7bf3b9a | |
greg | 5147e1a3eb | |
greg | 955c073174 | |
greg | 7c46a29141 | |
greg | 0adc761e72 | |
greg | b2039a7b67 | |
greg | b4c4531e4d | |
greg | 2d36ad44d6 | |
greg | 21132a369c | |
greg | ff0294c56e | |
greg | bc80c8f9ad | |
greg | e39356c0e5 | |
greg | d44bb02d61 | |
greg | 9056e9b0e1 | |
greg | e9b90412ce | |
greg | 65c47c20fc | |
greg | fab3fb8ec2 | |
greg | 0d5ccd21fe | |
greg | 69b7b9f528 | |
greg | 9a09f40222 | |
greg | 020819550b | |
greg | 15f9dbe7a6 | |
greg | 836bed1207 | |
greg | cee5b085d5 | |
greg | 837a55c718 | |
greg | f4f89b39b6 | |
greg | c6b4ed7ee4 | |
greg | be425860af | |
greg | 17e88b33f2 | |
greg | 47f7eb1ef6 | |
greg | 72d0cfe466 | |
greg | cea2f63b44 | |
greg | eec315dd58 | |
greg | 1e9aa91c5d | |
greg | 9813609ad7 | |
greg | 5953d9d815 | |
greg | a74e09c761 | |
greg | ad53d4394b | |
greg | 151246e1c5 | |
greg | 77d2826918 | |
greg | 1bd48ed5db | |
greg | c394b81746 | |
greg | ec29077247 | |
greg | 62043ac2d1 | |
greg | bada386979 | |
greg | e71d404071 | |
greg | cab4702bd6 | |
greg | ec5a9d457e | |
greg | bfbc1580aa | |
greg | 2d6c9010b9 | |
greg | f4ff92302f | |
greg | e88ed97b06 | |
greg | b8df09e956 | |
greg | d7f0147a4f | |
greg | f883512882 | |
greg | 37070a6b3e | |
greg | ffe7deb00a | |
greg | d7baf065fb | |
greg | 6b42f8b8de | |
greg | d9e67a6341 | |
greg | 7de536ade0 | |
greg | f62b4c6906 | |
greg | 4679a9fc7f | |
greg | c25354b2c7 | |
greg | 5f8b842bf2 | |
greg | fef66e345b | |
greg | e57d33eae7 | |
greg | dca9ad06c3 | |
greg | 354148c5ba | |
greg | 6219a06d6f | |
greg | 3b20b40eb7 | |
greg | 4ecf63c54d | |
greg | 3d00667caf | |
greg | 4b9c7e38dd | |
greg | 03317233c6 | |
greg | dff204069f | |
greg | f2282f0101 | |
greg | 40ccea8c05 | |
greg | cae6f2f768 | |
greg | 1be6991f55 | |
greg | 1b60bd38ff | |
greg | 3b20b9e209 | |
greg | de0e150536 | |
greg | baf51fb147 | |
greg | dc9e493fa1 | |
greg | d57a8045a9 | |
greg | 50d5176b45 | |
greg | 501eaeee87 | |
greg | 8619c94217 | |
greg | fc7c86be1a | |
greg | 77e0d639c2 | |
greg | 9927a6b1fd | |
greg | e8dfc2be34 | |
greg | abe2db25b2 | |
greg | a99a5f10a4 | |
greg | c52bc65bb9 | |
greg | 819c422cee | |
greg | 1c11fec803 | |
greg | a5c3c383dc | |
greg | 76046b134a | |
greg | c24223f28e | |
greg | 79e02b0999 | |
greg | 80eb703f5e | |
greg | 4fccff5e27 | |
greg | e934d7bdc5 | |
greg | de199e785a | |
greg | 81ac918a59 | |
greg | 5d4505241a | |
greg | f67793308e | |
greg | 693766fa59 | |
greg | f9f29dd0dd | |
greg | 3c1823510f | |
greg | 92078ef7d8 | |
greg | 1abbe2e448 | |
greg | 065bdd6bda | |
greg | e125e8b440 | |
greg | 8565c7dfb3 | |
greg | f885d5dfb6 | |
greg | b85725125c | |
greg | 2d961d6402 | |
greg | fa7b6ce96b | |
greg | 5c9180efc2 | |
greg | 1d5e5aa735 | |
greg | 2c298c7247 | |
greg | f00fee0e37 | |
greg | 0d13b5e3bc | |
greg | 98f597f00a | |
greg | fb71881409 | |
greg | d1c3b4a81b | |
greg | f9181b5786 | |
greg | 0e914cf057 | |
greg | 04ea8c5ebc | |
greg | 492ef4ae19 | |
greg | 75a7a4499d | |
greg | 99e6668c9a | |
greg | 1d38a07cf8 | |
greg | 0fa844bcf9 | |
greg | 97bee58fbe | |
greg | 34c2b43371 | |
greg | 88b617de52 | |
greg | 482674b19a | |
greg | a72b387ceb | |
greg | 864e932e9f | |
greg | d7e73be44c | |
greg | 6a548c9086 | |
greg | 0c0690e86e | |
greg | 6d18f80185 | |
greg | 6825de3916 | |
greg | 1b78fbff82 | |
greg | 897c1181a9 | |
greg | 6833bc4f00 | |
greg | f2ded78776 | |
greg | 9debdd8d66 | |
greg | 8067c862f3 | |
greg | f9c2fc3f9d | |
greg | 5ead1e5d44 | |
greg | 348a6f7c76 | |
greg | 5f336ec1a9 | |
greg | da59fae0d3 | |
greg | 5b5689accf | |
greg | 32acf89814 | |
greg | c637a922a9 | |
greg | 42d0aba21c | |
greg | 7548bdbb78 | |
greg | bc6d4d19b5 | |
greg | a2b1b0f953 | |
greg | 75bf4b5697 | |
greg | 35f5a9623a | |
greg | 98e812968b | |
greg | 250c486143 | |
greg | 38eb065511 | |
greg | 9e24c3b336 | |
greg | 1d2f1624a1 | |
greg | 4ca57e4aea | |
greg | 82502ad0ad | |
greg | 7d68b2a05a | |
greg | c2db212c78 | |
greg | 837a180b09 | |
greg | 5ecd298e6a | |
greg | 8aa33d0872 | |
greg | 21a8868bcf | |
greg | 5a91957fa1 | |
greg | 176d43e56f | |
greg | 90ecde89c9 | |
greg | 65c2cd521b | |
greg | f7dbbddad1 | |
greg | 43ff08b04c | |
greg | 00692aa89e | |
greg | 0ec29f6dd0 | |
greg | 5e48eb2dee | |
greg | 3597ad4eef | |
greg | 072eab1a80 | |
greg | 1761d11d36 | |
greg | d075f613f9 | |
greg | ee55729d5f | |
greg | 17a4028185 | |
greg | 947f4f2ea6 | |
greg | 41c9dfae06 | |
greg | fe1a508e25 | |
greg | 55a8cabd7c | |
greg | 46b6aeb4db | |
greg | 3c022fc4ef | |
greg | 0a02c21e70 | |
greg | 927f427a86 | |
greg | 005aba7a10 | |
greg | 7882e92ab5 | |
greg | f582ab4eaa | |
greg | f2dce38647 | |
greg | ba4cd9da39 | |
greg | ba319a7bc3 | |
greg | 2d052d34f7 | |
greg | 654eeef428 | |
greg | a96fbc9592 | |
greg | 196954326e | |
greg | 3f2fff276c | |
greg | e6679ff523 | |
greg | ebcea685f3 | |
greg | 3b9084810e | |
greg | 7809cda240 | |
greg | f1679e83b7 | |
greg | f98d8e2bb0 | |
greg | d0a0cc8209 | |
greg | 5aa0e10e7a | |
greg | 27729cefdf | |
greg | df76e7c120 | |
greg | 889610f0b0 | |
greg | 3beabf4678 | |
greg | 25790f8643 | |
greg | ff5446af3f | |
greg | 5d84153c9e | |
greg | 0b0f6b6b50 | |
greg | 856a360aba | |
greg | 81ca9ee20f | |
greg | 4caf8096b3 | |
greg | c65907388d | |
greg | 33c22c8bbc | |
greg | 4a27af2136 | |
greg | 07af54b78a | |
greg | c4666b82ec | |
greg | 274dd1ccb0 | |
greg | 70ec79c4b3 | |
greg | f88d2331e3 | |
greg | c8f961abbf | |
greg | d040d76bfa | |
greg | 887ba46b0b | |
greg | a80db9e4c2 | |
greg | c986233a95 | |
greg | bb29df4a73 | |
greg | 4db3595d7c | |
greg | 217ee73fc9 | |
greg | 93309c025e | |
greg | b67512a9e1 | |
greg | 8e6f605fab | |
greg | ba4185b0fb | |
greg | 7a2a4df297 | |
greg | 642e9da8ee | |
greg | cea7427847 | |
greg | 3156c31dfc | |
greg | 2e457cd5e8 | |
greg | 843d895f2b | |
greg | 734c53ce0d | |
greg | 3a3b8dd440 | |
greg | c96a56a7ac | |
greg | 4017857a3a | |
greg | 131c83b64d | |
greg | 9e0f8b8a14 | |
greg | 7121624f77 | |
greg | 48e795decc | |
greg | a26da934f4 | |
greg | 1de1cd9cfd | |
greg | 6f639b9030 | |
greg | 8f0104ebc7 | |
greg | 36cd7e080d | |
greg | f48a25779c | |
greg | 808a1bfc98 | |
greg | c7e46c1cfa | |
greg | 98cfcfc18d | |
greg | b4c7ea3d02 | |
greg | e7c89ed840 | |
greg | b0e38f7f5b | |
greg | 276662d98a | |
greg | e8e9265b26 | |
greg | cb316a973e | |
greg | e64861b602 | |
greg | 1673fd1cf9 | |
greg | c00effcbdd | |
greg | 8378170fbd | |
greg | 7ab385d398 | |
greg | 9fb148bb02 | |
greg | 97df2fa344 | |
greg | a08134a747 | |
greg | 6707b2bb9c | |
greg | 3ac50f974d | |
greg | 160ce95e5f | |
greg | afc4281e7f | |
greg | 8d6fea942f | |
greg | 6d93c758a2 | |
greg | aff421cd99 | |
greg | 493d76da0b | |
greg | eb681fbff9 | |
greg | f3e3843528 | |
greg | 6b90e19eb1 | |
greg | 210ae47c8b | |
greg | 70794d8ff1 | |
greg | 532c8c45b4 | |
greg | 24b532df06 | |
greg | ac576be604 | |
greg | 6bf106a1a3 | |
greg | 161e47fe91 | |
greg | 1a58f3b7af | |
greg | 44e585fca2 | |
greg | 3f836eb74f | |
greg | abf25d648d | |
greg | 1f6e6d9b31 | |
greg | e2703121d8 | |
greg | e5b6b41422 | |
greg | 6c5e3dea5d | |
greg | bd8bf1945c | |
greg | 9e393d2753 | |
greg | 822420a9d5 | |
greg | 6f8dc9bedd | |
greg | 3b134d7fb6 | |
greg | e0cec8b8a6 | |
greg | 1a84f62818 | |
greg | b1966d7199 | |
greg | fdbb21990d | |
greg | 1011ff08f3 | |
greg | 6d8d2aecbd | |
greg | 848306ad1a | |
greg | e6f0710e41 | |
greg | d7e3f695b7 | |
greg | 78ba4e1ed3 | |
greg | 481afb0f87 | |
greg | 01986e7474 | |
greg | 9cf5260d4b | |
greg | b50d87b85b | |
greg | 18c8176134 | |
greg | 2cb7d35008 | |
greg | bd1eed884f | |
greg | 67917612e6 | |
greg | b4a16cdc55 | |
greg | 4d5ab95946 | |
greg | ce71254b69 | |
greg | 065e58f87e | |
greg | 29cabb119f | |
greg | 6768cebc48 | |
greg | ec5580d20b | |
greg | 9de66a9af3 | |
greg | 633b4fe7a4 | |
greg | 87c3b8e234 | |
greg | 16a463b1a0 | |
greg | c3be644133 | |
greg | e7615fda8b | |
greg | 111657b567 | |
greg | c5e8d3e080 | |
greg | 4f49c183b0 | |
greg | 81368179bb | |
greg | 30128d7d34 | |
greg | 6c718e5d4f | |
greg | 774ddd665b | |
greg | 0bb0ecea76 | |
greg | 59a7c11031 | |
greg | b54a9774ed | |
greg | a9c0341d38 | |
greg | 2d260c14d7 | |
greg | 7686707602 | |
greg | 670833185b | |
greg | 012c50b7c3 | |
greg | f1a64adfd9 | |
greg | e46eeb91f3 | |
greg | d524389f1d | |
greg | 890e6bd4c5 | |
greg | 8826d5b0d4 | |
greg | 8ad5dd9056 | |
greg | fb168da8bd | |
greg | 78fdea180e | |
greg | 00e68d09c7 | |
greg | 73c3eeb69d | |
greg | 86e88ee1bf | |
greg | d1a2473bb2 | |
greg | 57ccdd5ead | |
greg | c0746028f4 | |
greg | c6f038a307 | |
greg | e498e19ffc | |
greg | 51cdedb9cc | |
greg | 50236ac942 | |
greg | 1e4554258f | |
greg | 9ade0dd1e2 | |
greg | 7ba8c9dab9 | |
greg | 774ab5f72e | |
greg | 50499c8a33 | |
greg | a10df92ab8 | |
greg | fe64cbcd3a | |
greg | 061d54702f | |
greg | 27885500fd | |
greg | aaf98db2b7 | |
greg | fff587cd6a | |
greg | 83fe71f721 | |
greg | 491face68b | |
greg | 258e813a39 | |
greg | 5d69b530c5 | |
greg | 8a5b8619fa | |
greg | 832d0d4ee3 | |
greg | 57a18a0768 | |
greg | 2c5ebd636f | |
greg | 06638dc030 | |
greg | 3a181dd0ac | |
greg | 1d1a5fb6fc | |
greg | fb4de6f2d6 | |
greg | 18c86c26f0 | |
greg | ac44df8d1e | |
greg | 12c7cebb38 | |
greg | f22f089b9b | |
greg | 3d960d5697 | |
greg | 1f4228b887 | |
greg | 5abaadc0ca | |
greg | fd89de77cc | |
greg | a305610a39 | |
greg | 14f31a5186 | |
greg | b936132ca6 | |
greg | a1016293ac | |
greg | 8e42f7e0bc | |
greg | b8a25dbaac | |
greg | 66b6ddcf93 | |
greg | 1c0365529d | |
greg | f795612884 | |
greg | c9ea48e9d1 | |
greg | 65f42981ff | |
greg | e2970dbc42 | |
greg | 7d2bc4188d | |
greg | eb987bb5b0 | |
greg | 0de504eb9e | |
greg | 635887f7a5 | |
greg | ecebbb2eae | |
greg | 78f12c8f1d | |
greg | ebda79e5fd | |
greg | 819a06503f | |
greg | 664003a9d7 | |
greg | e1398bd063 | |
greg | 898b185509 | |
greg | 7592209cdb | |
greg | 6f43c3b81d | |
greg | 072eeaa127 | |
greg | 6bd3ed7b65 | |
greg | 8f19f2e414 | |
greg | 5f279cb400 | |
greg | 795b4adc6b | |
greg | 9d4082463a | |
greg | 43ade31f3e | |
greg | 9f2fbda31f | |
greg | b31325c315 | |
greg | 95a2620754 | |
greg | e67b22d109 | |
greg | 61eccba173 | |
greg | f56d7120c4 | |
greg | 6140de9f9c | |
greg | b54c71633c | |
greg | 1eeafb80dc | |
greg | 59d621ed75 | |
greg | 76fadf0701 | |
greg | 6e6d494d50 | |
greg | a0bb2837c1 | |
greg | a4dd492c26 | |
greg | d0b6840670 | |
greg | b65eb0e459 | |
greg | 3f1e83dfda | |
greg | 5ddfc132e7 | |
greg | f1f7f43e20 | |
greg | 86d9e90e7c | |
greg | a7672171a6 | |
greg | 08e10739e5 | |
greg | a300f78e19 | |
greg | 0423017125 | |
greg | 8ef5a28aff | |
greg | a92a2e4454 | |
greg | 8d79074ea9 | |
greg | 4e7806d053 | |
greg | 507e0b7255 | |
greg | 9b760244d5 | |
greg | 88e027f536 | |
greg | 2e41f8ffe3 | |
greg | b18c2eee96 | |
greg | 0c78f50568 | |
greg | 2dc9b4c09f | |
greg | 73206d345e | |
greg | 1a74e16af5 | |
greg | ae2182db5d | |
greg | ad450469a5 | |
greg | df88e33579 | |
greg | 9d72a92f0b | |
greg | fa6c2a6f45 | |
greg | 92e6830979 | |
greg | ef9cd04605 | |
greg | 1eaf201145 | |
greg | 876373c9fd | |
greg | 63f5f155ae | |
greg | 51cf8a4824 | |
greg | e0cc12276c | |
greg | d69970a806 | |
greg | 522d9fc951 | |
greg | 63c3e0a4db | |
greg | 547def990e | |
greg | 6e105bac55 | |
greg | a396c448ec | |
greg | d3ef980dc5 | |
greg | df86e0c16e | |
greg | 274bf80b5d | |
greg | f0a39ac88a | |
greg | 85e65273fe | |
greg | 413c5afe67 | |
greg | 36174140bc | |
greg | 75ecfb4e86 | |
greg | e86d401c90 | |
greg | b2319f0971 | |
greg | d423e88845 | |
greg | 5cb0e6715d | |
greg | 5bb2c319e8 | |
greg | 440783bb64 | |
greg | 9834ee295e | |
greg | 9346bb9581 | |
greg | f46f593c44 | |
greg | ec7d185ed5 | |
greg | 3f1cf1d975 | |
greg | 39ee550b54 | |
greg | d5df868f10 | |
greg | 55629e6d9d | |
greg | 9d99971f49 | |
greg | 76575e9ba3 | |
greg | a50d8d9e3f | |
greg | c2cd419e5a | |
greg | bcec8e27f8 | |
greg | e6a015090c | |
greg | c18bf9c29f | |
greg | cfc507a2df | |
greg | f7e88c7cab | |
greg | 4d0bfa2a52 | |
greg | 99e5d86764 | |
greg | 17e8ebe789 | |
greg | 253a85005c | |
greg | 967e5cc436 | |
greg | 7a6ace5db1 | |
greg | 129af43e69 | |
greg | 17dccf65c8 | |
greg | 95c6a23bf1 | |
greg | 2bff53846c | |
greg | 514d117c7e | |
greg | ae65687a93 | |
greg | 9ec983dc20 | |
greg | cab0ca6f47 | |
greg | 8f6c80ac8c | |
greg | 7f546fa879 | |
greg | 48a35aa382 | |
greg | 0c64b14be0 | |
greg | 5d9fa6679b | |
greg | ea24ae1bb5 | |
greg | 0d2a0e3536 | |
greg | 339e3464e3 | |
greg | c35b684bdd | |
greg | d11c518721 | |
greg | 8dde8c7381 | |
greg | 47cad3712c | |
greg | ffcc0ef379 | |
greg | 6766791627 | |
greg | 05de5ebe61 | |
greg | 98fa8403b3 | |
greg | ce83306581 | |
greg | 29ebd35165 | |
greg | 622b50a40c | |
greg | 9f916c7c02 | |
greg | 85375bb9df | |
greg | d11500c643 | |
greg | 8493233b69 | |
greg | 60644ba3d7 | |
greg | 254f2ae4b8 | |
greg | e243b99d3b | |
greg | 3d023a6704 | |
greg | 857b77f2e3 | |
greg | 4d89dcc85e | |
greg | fe0e58efe7 | |
greg | 73612d1465 | |
greg | afd2b018f4 | |
greg | d1a15b64ff | |
greg | 66e8643382 | |
greg | ad58fc1ad1 | |
greg | adc7be30a9 | |
greg | 72097fa125 | |
greg | ae9d93f6dc | |
greg | 3d421c7039 | |
greg | 166bc3b3cb | |
greg | 2f263de8ba | |
greg | 46ae176498 | |
greg | d84def35e7 | |
greg | 07e55ca04e | |
greg | 6dcf5c7945 | |
greg | 568ee88f3a | |
greg | 8749ed984d | |
greg | 559eaf54de | |
greg | bf42b58ca5 | |
greg | ecdcb7ff3d | |
greg | 766209e5b2 | |
greg | e9429ed62a | |
greg | 6e188976f9 | |
greg | d235b47bc5 | |
greg | 3fcb840ce5 | |
greg | 523bd179a4 | |
greg | 35e715dfd6 | |
greg | 6eb0fc8834 | |
greg | c0a5418c27 | |
greg | 42749c1ff6 | |
greg | 42b9507af0 | |
greg | 38e85e2c78 | |
greg | 7c5fef49f8 | |
greg | c1e214c701 | |
greg | 66e3de41dd | |
greg | 9545130fd3 | |
greg | ef7412dcd5 | |
greg | dee470cb8b | |
greg | c057f068ef | |
greg | c4dbdf1fe7 | |
greg | 4c7174e4c4 | |
greg | d0538faef3 | |
greg | b97da01370 | |
greg | b09efd3660 | |
greg | a42a58b155 | |
greg | 708c0ab103 | |
greg | 1d9d0c4395 | |
greg | ffb87ebb82 | |
greg | 30c741f459 | |
greg | d19541b3e1 | |
greg | 3651461bbc | |
greg | 7730457878 | |
greg | 46dbac7f69 | |
greg | f68167f3a2 | |
greg | c9625ffa77 | |
greg | cc3833754d | |
greg | 9afbd2305f | |
greg | d7564f81c9 | |
greg | 2fbb8f2b2f | |
greg | 1884eae191 | |
greg | bb880d44fa | |
greg | 22b4738726 | |
greg | 0202aab181 | |
greg | f9c9ed6b29 | |
greg | 04cb1616f7 | |
greg | 5f1c46cb87 | |
greg | 0ea9bd3d95 | |
greg | 0cf56eea4f | |
greg | ab53c5394e | |
greg | f6c85951fe | |
greg | c530715671 | |
greg | 617a30b967 | |
greg | cd11d18385 | |
greg | f82c6199c0 | |
greg | f75cd763f8 | |
greg | 54c16f0190 | |
greg | 8d8e3cd565 | |
greg | 47975cf8f6 | |
greg | ddd861fbea | |
greg | 200d0f9867 | |
greg | 3e44bd3a18 | |
greg | e2a94280c2 | |
greg | c5b3bafe43 | |
greg | b417451536 | |
greg | a0faed3603 | |
greg | 83752a1c74 | |
greg | 66c7bbeb07 | |
greg | ed8359bcd7 | |
greg | 996f75e15c | |
greg | 30a54d997c | |
greg | 4bcbf1854a | |
greg | f2c6556c2a | |
greg | 9161e2751f | |
greg | 60fc9fd7e1 | |
greg | 3b249045aa | |
greg | ff0e14d9a9 | |
greg | 8fe535597e | |
greg | 4bb8f82579 | |
greg | 5cb8423ecc | |
greg | 4032707dc9 | |
greg | 1a8423535a | |
greg | 338981febe | |
greg | 6dff8b029e | |
greg | df877830d3 | |
greg | 40696b3cbd | |
greg | 40a82d7e25 | |
greg | c605f76059 | |
greg | a6d71821b9 | |
greg | c4f0331d1a | |
greg | b4054d7581 | |
greg | 74d3828c71 | |
greg | bb57da564d | |
greg | 3f9ae5fac3 | |
greg | 62edc7c996 | |
greg | e412fb9a89 | |
greg | 3a97401f61 | |
greg | 87cfe854ac | |
greg | 184a2ae03a | |
greg | 50ceb92d6b | |
greg | dd7736e32d | |
greg | 3025af3ded | |
greg | 24608362a5 | |
greg | c83df6fd84 | |
greg | 65f64ebcc2 | |
greg | 00ee802fbd | |
greg | c88d59401c | |
greg | 1aa4e3b942 | |
greg | abbbb34901 | |
greg | 3ff4a34aeb | |
greg | 7430aebe63 | |
greg | 9071846df3 | |
greg | 29d307ff53 | |
greg | 89482e5b5a | |
greg | 6435d5e958 | |
greg | f6536e7ebd | |
greg | c5feea4597 | |
greg | c5cb223168 | |
greg | d16a0c9380 | |
greg | daf9878020 | |
greg | f825c87397 | |
greg | 8d2a65b44e | |
greg | 6b9fee1aed | |
greg | d05f173dd3 | |
greg | e88a0f59b5 | |
greg | 90cf7db609 | |
greg | 1ae9dbcba7 | |
greg | 9214f36c04 | |
greg | 98169bd352 | |
greg | d60cf99ab5 | |
greg | bb93d29beb | |
greg | c20f93e18c | |
greg | f48adbd9bf | |
greg | 9ad506fc78 | |
greg | 4c81c36d67 | |
greg | 230f2dd7ff | |
greg | 1615269a7b | |
greg | 1a8ba8d8a2 | |
greg | d4aec19c71 | |
greg | 10b1864680 | |
greg | 4831a24853 | |
greg | 67ff21d408 | |
greg | 8b83d982c0 | |
greg | 6bff7aac0d | |
greg | 18fa160fed | |
greg | 7ac5846282 | |
greg | 88209d370d | |
greg | 0f9d2d76c4 | |
greg | 006fd7d411 | |
greg | e3b236a15d | |
greg | 68bbd62ab6 | |
greg | e47a2c7241 | |
greg | a8b77848b4 | |
greg | 839731f2d1 | |
greg | f51e1a3c47 | |
greg | fc350cd03e | |
greg | 8fe7c85b00 | |
greg | b920fae93b | |
greg | 1981b74d89 | |
greg | 077ab8ddb8 | |
greg | 9775bfc342 | |
greg | 505d23a327 | |
greg | 81c4566c2b | |
greg | 8be757beca | |
greg | 20c74953b5 | |
greg | d5c3227966 | |
greg | fbeb101e7f | |
greg | 18c761a5b5 | |
greg | 89cf101362 | |
greg | 66d10604ba | |
greg | 565461e1db | |
greg | 5ecd28d057 | |
greg | 6c5dbac406 | |
greg | 5dd1cd79ff | |
greg | dfc89e5060 | |
greg | 5871bf68de | |
greg | 34b569eb5f | |
greg | 3220f0aec7 | |
greg | 5810fb7961 | |
greg | 1a3076a949 | |
greg | 1d9e5edfba | |
greg | 555d2a7ba5 | |
greg | 291fb61c8d | |
greg | 685b579fdd | |
greg | f72e77cbb6 | |
greg | 4b5afef17e | |
greg | 5889998126 | |
greg | c19946bb6d | |
greg | d1301b30e6 | |
greg | f8287e42ce | |
greg | bd6bf2f4bb | |
greg | a6b336d84c | |
greg | deab74b992 | |
greg | 0755d42112 | |
greg | 117e0e38a8 | |
greg | 3f1de5f60d | |
greg | c52fd4c73d | |
greg | cac3ea86cf | |
greg | 92ece39d5e | |
greg | 14c09bb40c | |
greg | 0dabbc700b | |
greg | 741e5f7f9b | |
greg | cfefceabf9 | |
greg | ea08f8cab8 | |
greg | 7d1c07c481 | |
greg | 7831cb8d8a | |
greg | 16d9e3eb60 | |
greg | 737dad6438 | |
greg | 74f8c16599 | |
greg | a82f24a158 | |
greg | 6459ad28e8 | |
greg | 5e0c7e5a95 | |
greg | 57d4222746 | |
greg | 88d1896281 | |
greg | 7fe0a6589e | |
greg | ac5bdd7bcb | |
greg | 8bf5f40a2a | |
greg | 7e505dd88e | |
greg | f15427e5d9 | |
greg | 8230b115de | |
greg | a53135a897 | |
greg | f3c8474c93 | |
greg | 8dc8d15437 | |
greg | b5a6c5903e | |
greg | c97e58c2aa | |
greg | cb9b56f000 | |
greg | 55e1600b97 | |
greg | fb009497a4 | |
greg | 4b13fef734 | |
greg | 14ccf9f1be | |
greg | 7a6dfbbd0e | |
greg | bb3f85dd16 | |
greg | 3e66568ddd | |
greg | 3abe299361 | |
greg | 626b17cbd2 | |
greg | 192a7e611f | |
greg | d3febb201b | |
greg | f9fe81993f | |
greg | ff01d4b798 | |
greg | dd22ca0291 | |
greg | 801896bcc6 | |
greg | 6f8c89af37 | |
greg | f82e1e699c | |
greg | a0f3583ab1 | |
greg | e81d5e108b | |
greg | 5d15d60ab6 | |
greg | f0de3c3d12 | |
greg | 9dd8f90e3c | |
greg | e0f5f01e69 | |
greg | 424998c128 | |
greg | b93625819c | |
greg | f90bfb88ca | |
greg | 850b77541b | |
greg | dbf5886aad | |
greg | dd93adf5b7 | |
greg | d8df98ba01 | |
greg | 4da771036a | |
greg | 3911c45dde | |
greg | f3c3d4595e | |
greg | e4a42e7691 | |
greg | cc537f292d | |
greg | 840e093bc4 | |
greg | 815f2b8242 | |
greg | 34dba9cc4d | |
greg | 6e28ae68a0 | |
greg | 48b0b8d053 | |
greg | e0c49abe56 | |
greg | 65dc362a1d | |
greg | 8ff1c632c2 | |
greg | 039022bfc5 | |
greg | 387ec25cda | |
greg | ecf60198fa | |
greg | f83cece2b4 | |
greg | 8fd5fb5a0b | |
greg | 455fe2abe2 | |
greg | 902c85ccd7 | |
greg | 4ea600d55c | |
greg | 6dec35d460 | |
greg | cc855affbf | |
greg | a303aa2a5b | |
greg | 421a9a7e9b | |
greg | f37ab80163 | |
greg | 178434171e | |
greg | fd4610e175 | |
greg | 5103f03fa5 | |
greg | 1a4bf24ab1 | |
greg | 9d6bdf22da | |
greg | 8326a12c9c | |
greg | 5e474231da | |
greg | 1ac440c8df | |
greg | f5022a771c | |
greg | eaf86ea908 | |
greg | eb6354e55a | |
greg | 751c6f65bd | |
greg | 3e231b4715 | |
greg | e103ba221c | |
greg | d5f01a7b1f | |
greg | 1702163478 | |
greg | bdd6f75cf6 | |
greg | 2681dbc4f2 | |
greg | 9454fc6194 | |
greg | c8feaa9b57 | |
greg | 518414ffd5 | |
greg | 06a5de6e32 | |
greg | dd4816624c | |
greg | 748a85db02 | |
greg | 8f2d9b900b | |
greg | b9d1140264 | |
greg | 7188a7d33e | |
greg | 0c7099771f | |
greg | afec7e829c | |
greg | a6773d59bd | |
greg | d804efdc5e | |
greg | 0ace370fc2 | |
greg | 1f50fcc620 | |
greg | d7181afa91 | |
greg | 4eb7683f47 | |
greg | b04a8f0092 | |
greg | c50be58cd2 | |
greg | 5911a07f4f | |
greg | 26bc6e90f3 | |
greg | b0655d7cab | |
greg | a46ede9395 | |
greg | d9ab5a58cf | |
greg | 77297c7e06 | |
greg | d93b5c0a2e | |
greg | 0b9dc113d1 | |
greg | d6fc13f08d | |
greg | 825c271b17 | |
greg | 8c4f7e141a | |
greg | 12fbc51da1 | |
greg | db108ee434 | |
greg | 7ddb421ced | |
greg | 1631bb0a04 | |
greg | 5923cc2317 | |
greg | 1fa56800c5 | |
greg | 2b4d3e8516 | |
greg | 9b74527618 | |
greg | d23e5bff35 | |
greg | 3a4f5ae840 | |
greg | 298194c42d | |
greg | 23d2209d8b | |
greg | 4cf165b408 | |
greg | 154839979b | |
greg | 6741787852 | |
greg | 538f0b18f4 | |
greg | dc81d237c5 | |
greg | 8651839a66 | |
greg | f6e5ea250d | |
greg | 9801f53a17 | |
greg | db92292569 | |
greg | e1ce54aece | |
greg | c227ad656f | |
greg | b45d09e81a | |
greg | 761500b9d6 | |
greg | e888e82404 | |
greg | 328ec4ba87 | |
greg | 4a7b570603 | |
greg | 7eb48fb4ef | |
greg | 8ebf1b3056 | |
greg | 905431b33c | |
greg | 2996198eff | |
greg | 06771979df | |
greg | f158b6c712 | |
greg | ba8f67441f | |
greg | 872e9ce7ee | |
greg | 2722533efd | |
greg | edf342e65a | |
greg | 27d4c2ccbd | |
greg | 6794d22f1d | |
greg | 1858d26638 | |
greg | 84fbe73cf6 | |
greg | ad994c38ae | |
greg | 48343d3fad | |
greg | 4f8ff35d0f | |
greg | b210ad5e19 | |
greg | 7311d0311f | |
greg | 1b59c264b4 | |
greg | b2e453a9de | |
greg | 59226eb731 | |
greg | 297003c0b0 | |
greg | e5ee072b00 | |
greg | 9b62efc830 | |
greg | f626ca1427 | |
greg | 82c52ede48 | |
greg | 7f52b20d97 | |
greg | 12fee6158c | |
greg | 2d21de7cc3 | |
greg | 9cc9c5977d | |
greg | ed9d1312d1 | |
greg | 29d9e50311 | |
greg | e84550f3ec | |
greg | 3063de1242 | |
greg | e1d07b4e66 | |
greg | af45004afa | |
greg | d4d61ce5ad | |
greg | 743311d18a | |
greg | 17f9846bb9 | |
greg | fe8418edbe | |
greg | 1d8102b9fa | |
greg | aac3ca40fe | |
greg | c389b44bf8 | |
greg | db52f9b716 | |
greg | d3a743442b | |
greg | e46d840d96 | |
greg | 3d406f1dd2 | |
greg | fca307d3ab | |
greg | a65544356c | |
greg | 10d51cc29c | |
greg | 95b773de7f | |
greg | c032da712f | |
greg | cfb93d9dea | |
greg | 08798fb690 | |
greg | e0eef5e58f | |
greg | 2107e6344e | |
greg | 2ad65a1c5d | |
greg | a01b6c874e | |
greg | 907af38f44 | |
greg | 923566c4e9 | |
greg | 96c51a9b88 | |
greg | 8528c912bd | |
greg | 47f4c25020 | |
greg | 6ed67cd325 | |
greg | c18c1a639a | |
greg | d3f6bbabaa | |
greg | 5d0648b9c0 | |
greg | ba7a2e658f | |
greg | ee12e10ac6 | |
greg | 6a0b50f278 | |
greg | 6c44d295db | |
greg | cd69ebaa9d | |
greg | 3fe9ec95d5 | |
greg | 8b5d1ecd15 | |
greg | 272fa92052 | |
greg | 0c717c721e | |
greg | 64d560a1fc | |
greg | 8a92d5ffa8 | |
greg | 5ed0ed727b | |
greg | 5428810d2c | |
greg | b48c007bca | |
greg | 5aa4c404a5 | |
greg | 0e3aaa8b08 | |
greg | f33cfdadfe | |
greg | 231de69084 | |
greg | f014c1a9d9 | |
greg | c36bc3377e | |
greg | 5eba222679 | |
greg | dcf89aa429 | |
greg | 1ffbeb6472 | |
greg | f8a521fc9b | |
greg | 77f72806be | |
greg | dcde5d6018 | |
greg | 6bb227d052 | |
greg | 29d4cb53a4 | |
greg | 3915c1f035 | |
greg | b400796e4d | |
greg | 96e6a87f64 | |
greg | b1f9e5cefc | |
greg | be36d4697d | |
greg | ce8c511929 | |
greg | a8cafa8c64 | |
greg | 229e6ae733 | |
greg | e9dd0d9ae8 | |
greg | 15d4317191 | |
greg | 7114e446a4 | |
greg | 044f534ac5 | |
greg | d3207ad890 | |
greg | 19fffd5063 | |
greg | 785c916ece | |
greg | 5a9ebb188d | |
greg | 16e8d969be | |
greg | 70bf68d9bd | |
greg | f53c14535b | |
greg | 4f96abd7d9 | |
greg | fdaf4c302c | |
greg | 8ce53d7c72 | |
greg | 428d560e2a | |
greg | 80bc7ec089 | |
greg | e6591b80d9 | |
greg | e099f713ad | |
greg | 9b54256521 | |
greg | 087402ece6 | |
greg | 252b6e8bd9 | |
greg | b1163e2ae4 | |
greg | 032d01c9f5 | |
greg | c4ab1ed105 | |
greg | 9a257f08d7 | |
greg | 47d56a7b44 | |
greg | 1f7ae2e30f | |
greg | e3c8753a4d | |
greg | e1aa7ecb17 | |
greg | f09a6e14ba | |
greg | 31da25a66e | |
greg | fff9cb7d25 | |
greg | db1e188fdb | |
greg | 935185ed92 | |
greg | 0999cbe28e | |
greg | 674f70a428 | |
greg | b1b6672399 | |
greg | bd1c455dc8 | |
greg | b62ef43f07 | |
greg | 09b67dc3f7 | |
greg | a613fa73e5 | |
greg | 8c473c554e | |
greg | 3e04cbfa29 | |
greg | 570650cbfa | |
greg | 49be163181 | |
greg | b4f93acbd8 | |
greg | 8c65ae3214 | |
greg | e436533638 | |
greg | 4601a56867 | |
greg | 2f7a1850db | |
greg | 71aef379d3 | |
greg | 8662a3ba0e | |
greg | 5ca98c7d77 | |
greg | 13cde3106c | |
greg | 09d524c74a | |
greg | 61c36c4def | |
greg | bc4fbe4276 | |
greg | fc11ee753d | |
greg | dd2b4893a4 | |
greg | 51745fd800 | |
greg | 8c0ac19fa8 | |
greg | 971ab9ba21 | |
greg | 819fb3f58f | |
greg | 1c23329656 | |
greg | 6da20cbfaf | |
greg | f48451125e | |
greg | 06c3999430 | |
greg | 1af1589550 | |
greg | 32a90b8103 | |
greg | 186c900920 | |
greg | 509ab80b9c | |
greg | 247638c4db | |
greg | be98f8387e | |
greg | 841b38d5b1 | |
greg | 16dfbb27d5 | |
greg | 3af7e6a409 | |
greg | 123f388711 | |
greg | bb349cda5f | |
greg | caa331ecdc | |
greg | ae3a030ad8 | |
greg | ddb09b453d | |
greg | 9a4760d44f | |
greg | eb5ce2ef9e | |
greg | b080ea7c81 | |
greg | dffab8ae94 | |
greg | 48ee6c9a75 | |
greg | 58d399dace | |
greg | 57ea1bae30 | |
greg | e870d8172a | |
greg | 72b26755a7 | |
greg | 1416c9d444 | |
greg | 5c79563e53 | |
greg | c27c900e7f | |
greg | 50c5dbe96d | |
greg | be8c8b3343 | |
greg | b856023072 | |
greg | 868373f409 | |
greg | 582a7fd6dc | |
greg | a947ec3cb2 | |
greg | 79619025ea | |
greg | 56b338a6a8 | |
greg | 7d6f946e22 | |
greg | 0da7f7e3a1 | |
greg | 19a344fa77 | |
greg | 626a7f3861 | |
greg | 8e3a571d67 | |
greg | f88f115567 | |
greg | 08f1092b69 | |
greg | 6ddea790c0 | |
greg | 6897eb1283 | |
greg | 34fdf2be00 | |
greg | 4ef93fafb5 | |
greg | d2108f0f97 | |
greg | 2989ac338c | |
greg | 8b6d54aec2 | |
greg | cdb47bb3b9 | |
greg | 9d6dc5a5f2 | |
greg | 71d2428e57 | |
greg | 8f9bfbc5bd | |
greg | c9fdd5e83c | |
greg | 30eddf7737 | |
greg | 42719dc2f2 | |
greg | 8fcc850d77 | |
greg | f421918945 | |
greg | 0e4469fa58 | |
greg | edf100b583 | |
greg | 169e662049 | |
greg | 46999beabf | |
greg | 1342a76786 | |
greg | 05238bced3 | |
greg | a97cce184c | |
greg | 1ae61287c1 | |
greg | 329c521964 | |
greg | bfa16fd6fb | |
greg | 25f5188d8c | |
greg | 5213dd327f | |
greg | cea29094cd | |
greg | 67eafba97a | |
greg | 1059a88ee6 | |
greg | 429ace73bd | |
greg | 044e7a6a26 | |
greg | dbdae42c1b | |
greg | fc3dcf792d | |
greg | 02b34ca105 | |
greg | 1e9cd551a6 | |
greg | 9f4330889a | |
greg | 3058af4f05 | |
greg | 4f17d5a0dc | |
greg | 8e3774ffca | |
greg | c6059ada7d | |
greg | b5ee45f639 | |
greg | 04f53b6beb | |
greg | 2aaa600d53 | |
greg | c6a92728ee | |
greg | b2e23bed86 | |
greg | 3fdacf018e |
|
@ -1,3 +1,5 @@
|
|||
Cargo.lock
|
||||
target
|
||||
node_modules/
|
||||
experiments/tree-sitter-test/src
|
||||
.schala_repl
|
||||
.schala_history
|
||||
rusty-tags.vi
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "experiments"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "schala-main"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "schala-parser"
|
||||
version = "0.1.0"
|
22
Cargo.toml
22
Cargo.toml
|
@ -1,7 +1,17 @@
|
|||
[package]
|
||||
name = "schala"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
|
||||
schala-repl = { path = "schala-repl" }
|
||||
schala-lang = { path = "schala-lang/language" }
|
||||
# maaru-lang = { path = "maaru" }
|
||||
# rukka-lang = { path = "rukka" }
|
||||
# robo-lang = { path = "robo" }
|
||||
|
||||
[build-dependencies]
|
||||
includedir_codegen = "0.2.0"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"schala-main",
|
||||
"schala-parser",
|
||||
"experiments",
|
||||
]
|
||||
resolver = "2"
|
||||
|
|
|
@ -0,0 +1,920 @@
|
|||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedLists #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
|
||||
|
||||
-- | This module is an extensively documented walkthrough for typechecking a
|
||||
-- basic functional language using the Hindley-Damas-Milner algorithm.
|
||||
--
|
||||
-- In the end, we'll be able to infer the type of expressions like
|
||||
--
|
||||
-- @
|
||||
-- find (λx. (>) x 0)
|
||||
-- :: [Integer] -> Either () Integer
|
||||
-- @
|
||||
--
|
||||
-- It can be used in multiple different forms:
|
||||
--
|
||||
-- * The source is written in literate programming style, so you can almost
|
||||
-- read it from top to bottom, minus some few references to later topics.
|
||||
-- * /Loads/ of doctests (runnable and verified code examples) are included
|
||||
-- * The code is runnable in GHCi, all definitions are exposed.
|
||||
-- * A small main module that gives many examples of what you might try out in
|
||||
-- GHCi is also included.
|
||||
-- * The Haddock output yields a nice overview over the definitions given, with
|
||||
-- a nice rendering of a truckload of Haddock comments.
|
||||
|
||||
module HindleyMilner where
|
||||
|
||||
import Control.Monad.Trans
|
||||
import Control.Monad.Trans.Except
|
||||
import Control.Monad.Trans.State
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as M
|
||||
import Data.Monoid
|
||||
import Data.Set (Set)
|
||||
import qualified Data.Set as S
|
||||
import Data.String
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
|
||||
|
||||
|
||||
-- $setup
|
||||
--
|
||||
-- For running doctests:
|
||||
--
|
||||
-- >>> :set -XOverloadedStrings
|
||||
-- >>> :set -XOverloadedLists
|
||||
-- >>> :set -XLambdaCase
|
||||
-- >>> import qualified Data.Text.IO as T
|
||||
-- >>> let putPprLn = T.putStrLn . ppr
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
-- * Preliminaries
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Prettyprinting
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A prettyprinter class. Similar to 'Show', but with a focus on having
|
||||
-- human-readable output as opposed to being valid Haskell.
|
||||
class Pretty a where
|
||||
ppr :: a -> Text
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Names
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A 'name' is an identifier in the language we're going to typecheck.
|
||||
-- Variables on both the term and type level have 'Name's, for example.
|
||||
newtype Name = Name Text
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
-- | >>> "lorem" :: Name
|
||||
-- Name "lorem"
|
||||
instance IsString Name where
|
||||
fromString = Name . T.pack
|
||||
|
||||
-- | >>> putPprLn (Name "var")
|
||||
-- var
|
||||
instance Pretty Name where
|
||||
ppr (Name n) = n
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Monotypes
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A monotype is an unquantified/unparametric type, in other words it contains
|
||||
-- no @forall@s. Monotypes are the inner building blocks of all types. Examples
|
||||
-- of monotypes are @Int@, @a@, @a -> b@.
|
||||
--
|
||||
-- In formal notation, 'MType's are often called τ (tau) types.
|
||||
data MType = TVar Name -- ^ @a@
|
||||
| TFun MType MType -- ^ @a -> b@
|
||||
| TConst Name -- ^ @Int@, @()@, …
|
||||
|
||||
-- Since we can't declare our own types in our simple type system
|
||||
-- here, we'll hard-code certain basic ones so we can typecheck some
|
||||
-- familar functions that use them later.
|
||||
| TList MType -- ^ @[a]@
|
||||
| TEither MType MType -- ^ @Either a b@
|
||||
| TTuple MType MType -- ^ @(a,b)@
|
||||
deriving Show
|
||||
|
||||
-- | >>> putPprLn (TFun (TEither (TVar "a") (TVar "b")) (TFun (TVar "c") (TVar "d")))
|
||||
-- Either a b → c → d
|
||||
--
|
||||
-- Using the 'IsString' instance:
|
||||
--
|
||||
-- >>> putPprLn (TFun (TEither "a" "b") (TFun "c" "d"))
|
||||
-- Either a b → c → d
|
||||
instance Pretty MType where
|
||||
ppr = go False
|
||||
where
|
||||
go _ (TVar name) = ppr name
|
||||
go _ (TList a) = "[" <> ppr a <> "]"
|
||||
go _ (TEither l r) = "Either " <> ppr l <> " " <> ppr r
|
||||
go _ (TTuple a b) = "(" <> ppr a <> ", " <> ppr b <> ")"
|
||||
go _ (TConst name) = ppr name
|
||||
go parenthesize (TFun a b)
|
||||
| parenthesize = "(" <> lhs <> " → " <> rhs <> ")"
|
||||
| otherwise = lhs <> " → " <> rhs
|
||||
where lhs = go True a
|
||||
rhs = go False b
|
||||
|
||||
-- | >>> "var" :: MType
|
||||
-- TVar (Name "var")
|
||||
instance IsString MType where
|
||||
fromString = TVar . fromString
|
||||
|
||||
|
||||
|
||||
-- | The free variables of an 'MType'. This is simply the collection of all the
|
||||
-- individual type variables occurring inside of it.
|
||||
--
|
||||
-- __Example:__ The free variables of @a -> b@ are @a@ and @b@.
|
||||
freeMType :: MType -> Set Name
|
||||
freeMType = \case
|
||||
TVar a -> [a]
|
||||
TFun a b -> freeMType a <> freeMType b
|
||||
TList a -> freeMType a
|
||||
TEither l r -> freeMType l <> freeMType r
|
||||
TTuple a b -> freeMType a <> freeMType b
|
||||
TConst _ -> []
|
||||
|
||||
|
||||
|
||||
-- | Substitute all the contained type variables mentioned in the substitution,
|
||||
-- and leave everything else alone.
|
||||
instance Substitutable MType where
|
||||
applySubst s = \case
|
||||
TVar a -> let Subst s' = s
|
||||
in M.findWithDefault (TVar a) a s'
|
||||
TFun f x -> TFun (applySubst s f) (applySubst s x)
|
||||
TList a -> TList (applySubst s a)
|
||||
TEither l r -> TEither (applySubst s l) (applySubst s r)
|
||||
TTuple a b -> TTuple (applySubst s a) (applySubst s b)
|
||||
c@TConst {} -> c
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Polytypes
|
||||
-- #############################################################################
|
||||
|
||||
-- | A polytype is a monotype universally quantified over a number of type
|
||||
-- variables. In Haskell, all definitions have polytypes, but since the @forall@
|
||||
-- is implicit they look a bit like monotypes, maybe confusingly so. For
|
||||
-- example, the type of @1 :: Int@ is actually @forall <nothing>. Int@, and the
|
||||
-- type of @id@ is @forall a. a -> a@, although GHC displays it as @a -> a@.
|
||||
--
|
||||
-- A polytype claims to work "for all imaginable type parameters", very similar
|
||||
-- to how a lambda claims to work "for all imaginable value parameters". We can
|
||||
-- insert a value into a lambda's parameter to evaluate it to a new value, and
|
||||
-- similarly we'll later insert types into a polytype's quantified variables to
|
||||
-- gain new types.
|
||||
--
|
||||
-- __Example:__ in a definition @id :: forall a. a -> a@, the @a@ after the
|
||||
-- ∀ ("forall") is the collection of type variables, and @a -> a@ is the 'MType'
|
||||
-- quantified over. When we have such an @id@, we also have its specialized
|
||||
-- version @Int -> Int@ available. This process will be the topic of the type
|
||||
-- inference/unification algorithms.
|
||||
--
|
||||
-- In formal notation, 'PType's are often called σ (sigma) types.
|
||||
--
|
||||
-- The purpose of having monotypes and polytypes is that we'd like to only have
|
||||
-- universal quantification at the top level, restricting our language to rank-1
|
||||
-- polymorphism, where type inferece is total (all types can be inferred) and
|
||||
-- simple (only a handful of typing rules). Weakening this constraint would be
|
||||
-- easy: if we allowed universal quantification within function types we would
|
||||
-- get rank-N polymorphism. Taking it even further to allow it anywhere,
|
||||
-- effectively replacing all occurrences of 'MType' with 'PType', yields
|
||||
-- impredicative types. Both these extensions make the type system
|
||||
-- *significantly* more complex though.
|
||||
data PType = Forall (Set Name) MType -- ^ ∀{α}. τ
|
||||
|
||||
-- | >>> putPprLn (Forall ["a"] (TFun "a" "a"))
|
||||
-- ∀a. a → a
|
||||
instance Pretty PType where
|
||||
ppr (Forall qs mType) = "∀" <> pprUniversals <> ". " <> ppr mType
|
||||
where
|
||||
pprUniversals
|
||||
| S.null qs = "∅"
|
||||
| otherwise = (T.intercalate " " . map ppr . S.toList) qs
|
||||
|
||||
|
||||
|
||||
-- | The free variables of a 'PType' are the free variables of the contained
|
||||
-- 'MType', except those universally quantified.
|
||||
--
|
||||
-- >>> let sigma = Forall ["a"] (TFun "a" (TFun (TTuple "b" "a") "c"))
|
||||
-- >>> putPprLn sigma
|
||||
-- ∀a. a → (b, a) → c
|
||||
-- >>> let display = T.putStrLn . T.intercalate ", " . foldMap (\x -> [ppr x])
|
||||
-- >>> display (freePType sigma)
|
||||
-- b, c
|
||||
freePType :: PType -> Set Name
|
||||
freePType (Forall qs mType) = freeMType mType `S.difference` qs
|
||||
|
||||
|
||||
|
||||
-- | Substitute all the free type variables.
|
||||
instance Substitutable PType where
|
||||
applySubst (Subst subst) (Forall qs mType) =
|
||||
let qs' = M.fromSet (const ()) qs
|
||||
subst' = Subst (subst `M.difference` qs')
|
||||
in Forall qs (applySubst subst' mType)
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** The environment
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | The environment consists of all the values available in scope, and their
|
||||
-- associated polytypes. Other common names for it include "(typing) context",
|
||||
-- and because of the commonly used symbol for it sometimes directly
|
||||
-- \"Gamma"/@"Γ"@.
|
||||
--
|
||||
-- There are two kinds of membership in an environment,
|
||||
--
|
||||
-- - @∈@: an environment @Γ@ can be viewed as a set of @(value, type)@ pairs,
|
||||
-- and we can test whether something is /literally contained/ by it via
|
||||
-- x:σ ∈ Γ
|
||||
-- - @⊢@, pronounced /entails/, describes all the things that are well-typed,
|
||||
-- given an environment @Γ@. @Γ ⊢ x:τ@ can thus be seen as a judgement that
|
||||
-- @x:τ@ is /figuratively contained/ in @Γ@.
|
||||
--
|
||||
-- For example, the environment @{x:Int}@ literally contains @x@, but given
|
||||
-- this, it also entails @λy. x@, @λy z. x@, @let id = λy. y in id x@ and so on.
|
||||
--
|
||||
-- In Haskell terms, the environment consists of all the things you currently
|
||||
-- have available, or that can be built by comining them. If you import the
|
||||
-- Prelude, your environment entails
|
||||
--
|
||||
-- @
|
||||
-- id → ∀a. a→a
|
||||
-- map → ∀a b. (a→b) → [a] → [b]
|
||||
-- putStrLn → ∀∅. String → IO ()
|
||||
-- …
|
||||
-- id map → ∀a b. (a→b) → [a] → [b]
|
||||
-- map putStrLn → ∀∅. [String] -> [IO ()]
|
||||
-- …
|
||||
-- @
|
||||
newtype Env = Env (Map Name PType)
|
||||
|
||||
-- | >>> :{
|
||||
-- putPprLn (Env
|
||||
-- [ ("id", Forall ["a"] (TFun "a" "a"))
|
||||
-- , ("const", Forall ["a", "b"] (TFun "a" (TFun "b" "a"))) ])
|
||||
-- :}
|
||||
-- Γ = { const : ∀a b. a → b → a
|
||||
-- , id : ∀a. a → a }
|
||||
instance Pretty Env where
|
||||
ppr (Env env) = "Γ = { " <> T.intercalate "\n , " pprBindings <> " }"
|
||||
where
|
||||
bindings = M.assocs env
|
||||
pprBinding (name, pType) = ppr name <> " : " <> ppr pType
|
||||
pprBindings = map pprBinding bindings
|
||||
|
||||
|
||||
|
||||
-- | The free variables of an 'Env'ironment are all the free variables of the
|
||||
-- 'PType's it contains.
|
||||
freeEnv :: Env -> Set Name
|
||||
freeEnv (Env env) = let allPTypes = M.elems env
|
||||
in S.unions (map freePType allPTypes)
|
||||
|
||||
|
||||
|
||||
-- | Performing a 'Subst'itution in an 'Env'ironment means performing that
|
||||
-- substituion on all the contained 'PType's.
|
||||
instance Substitutable Env where
|
||||
applySubst s (Env env) = Env (M.map (applySubst s) env)
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Substitutions
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | A substitution is a mapping from type variables to 'MType's. Applying a
|
||||
-- substitution means applying those replacements. For example, the substitution
|
||||
-- @a -> Int@ applied to @a -> a@ yields the result @Int -> Int@.
|
||||
--
|
||||
-- A key concept behind Hindley-Milner is that once we dive deeper into an
|
||||
-- expression, we learn more about our type variables. We might learn that @a@
|
||||
-- has to be specialized to @b -> b@, and then later on that @b@ is actually
|
||||
-- @Int@. Substitutions are an organized way of carrying this information along.
|
||||
newtype Subst = Subst (Map Name MType)
|
||||
|
||||
|
||||
|
||||
-- | We're going to apply substitutions to a variety of other values that
|
||||
-- somehow contain type variables, so we overload this application operation in
|
||||
-- a class here.
|
||||
--
|
||||
-- Laws:
|
||||
--
|
||||
-- @
|
||||
-- 'applySubst' 'mempty' ≡ 'id'
|
||||
-- 'applySubst' (s1 '<>' s2) ≡ 'applySubst' s1 . 'applySubst' s2
|
||||
-- @
|
||||
class Substitutable a where
|
||||
applySubst :: Subst -> a -> a
|
||||
|
||||
instance (Substitutable a, Substitutable b) => Substitutable (a,b) where
|
||||
applySubst s (x,y) = (applySubst s x, applySubst s y)
|
||||
|
||||
-- | @'applySubst' s1 s2@ applies one substitution to another, replacing all the
|
||||
-- bindings in the second argument @s2@ with their values mentioned in the first
|
||||
-- one (@s1@).
|
||||
instance Substitutable Subst where
|
||||
applySubst s (Subst target) = Subst (fmap (applySubst s) target)
|
||||
|
||||
-- | >>> :{
|
||||
-- putPprLn (Subst
|
||||
-- [ ("a", TFun "b" "b")
|
||||
-- , ("b", TEither "c" "d") ])
|
||||
-- :}
|
||||
-- { a ––> b → b
|
||||
-- , b ––> Either c d }
|
||||
instance Pretty Subst where
|
||||
ppr (Subst s) = "{ " <> T.intercalate "\n, " [ ppr k <> " ––> " <> ppr v | (k,v) <- M.toList s ] <> " }"
|
||||
|
||||
-- | Combine two substitutions by applying all substitutions mentioned in the
|
||||
-- first argument to the type variables contained in the second.
|
||||
instance Monoid Subst where
|
||||
-- Considering that all we can really do with a substitution is apply it, we
|
||||
-- can use the one of 'Substitutable's laws to show that substitutions
|
||||
-- combine associatively,
|
||||
--
|
||||
-- @
|
||||
-- applySubst (compose s1 (compose s2 s3))
|
||||
-- = applySubst s1 . applySubst (compose s2 s3)
|
||||
-- = applySubst s1 . applySubst s2 . applySubst s3
|
||||
-- = applySubst (compose s1 s2) . applySubst s3
|
||||
-- = applySubst (compose (compose s1 s2) s3)
|
||||
-- @
|
||||
mappend subst1 subst2 = Subst (s1 `M.union` s2)
|
||||
where
|
||||
Subst s1 = subst1
|
||||
Subst s2 = applySubst subst1 subst2
|
||||
|
||||
mempty = Subst M.empty
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
-- * Typechecking
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
|
||||
-- $ Typechecking does two things:
|
||||
--
|
||||
-- 1. If two types are not immediately identical, attempt to 'unify' them
|
||||
-- to get a type compatible with both of them
|
||||
-- 2. 'infer' the most general type of a value by comparing the values in its
|
||||
-- definition with the 'Env'ironment
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Inference context
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | The inference type holds a supply of unique names, and can fail with a
|
||||
-- descriptive error if something goes wrong.
|
||||
--
|
||||
-- /Invariant:/ the supply must be infinite, or we might run out of names to
|
||||
-- give to things.
|
||||
newtype Infer a = Infer (ExceptT InferError (State [Name]) a)
|
||||
deriving (Functor, Applicative, Monad)
|
||||
|
||||
-- | Errors that can happen during the type inference process.
|
||||
data InferError =
|
||||
-- | Two types that don't match were attempted to be unified.
|
||||
--
|
||||
-- For example, @a -> a@ and @Int@ do not unify.
|
||||
--
|
||||
-- >>> putPprLn (CannotUnify (TFun "a" "a") (TConst "Int"))
|
||||
-- Cannot unify a → a with Int
|
||||
CannotUnify MType MType
|
||||
|
||||
-- | A 'TVar' is bound to an 'MType' that already contains it.
|
||||
--
|
||||
-- The canonical example of this is @λx. x x@, where the first @x@
|
||||
-- in the body has to have type @a -> b@, and the second one @a@. Since
|
||||
-- they're both the same @x@, this requires unification of @a@ with
|
||||
-- @a -> b@, which only works if @a = a -> b = (a -> b) -> b = …@, yielding
|
||||
-- an infinite type.
|
||||
--
|
||||
-- >>> putPprLn (OccursCheckFailed "a" (TFun "a" "a"))
|
||||
-- Occurs check failed: a already appears in a → a
|
||||
| OccursCheckFailed Name MType
|
||||
|
||||
-- | The value of an unknown identifier was read.
|
||||
--
|
||||
-- >>> putPprLn (UnknownIdentifier "a")
|
||||
-- Unknown identifier: a
|
||||
| UnknownIdentifier Name
|
||||
deriving Show
|
||||
|
||||
-- | >>> putPprLn (CannotUnify (TEither "a" "b") (TTuple "a" "b"))
|
||||
-- Cannot unify Either a b with (a, b)
|
||||
instance Pretty InferError where
|
||||
ppr = \case
|
||||
CannotUnify t1 t2 ->
|
||||
"Cannot unify " <> ppr t1 <> " with " <> ppr t2
|
||||
OccursCheckFailed name ty ->
|
||||
"Occurs check failed: " <> ppr name <> " already appears in " <> ppr ty
|
||||
UnknownIdentifier name ->
|
||||
"Unknown identifier: " <> ppr name
|
||||
|
||||
|
||||
|
||||
-- | Evaluate a value in an 'Infer'ence context.
|
||||
--
|
||||
-- >>> let expr = EAbs "f" (EAbs "g" (EAbs "x" (EApp (EApp "f" "x") (EApp "g" "x"))))
|
||||
-- >>> putPprLn expr
|
||||
-- λf g x. f x (g x)
|
||||
-- >>> let inferred = runInfer (infer (Env []) expr)
|
||||
-- >>> let demonstrate = \case Right (_, ty) -> T.putStrLn (":: " <> ppr ty)
|
||||
-- >>> demonstrate inferred
|
||||
-- :: (c → e → f) → (c → e) → c → f
|
||||
runInfer :: Infer a -- ^ Inference data
|
||||
-> Either InferError a
|
||||
runInfer (Infer inf) =
|
||||
evalState (runExceptT inf) (map Name (infiniteSupply alphabet))
|
||||
where
|
||||
|
||||
alphabet = map T.singleton ['a'..'z']
|
||||
|
||||
-- [a, b, c] ==> [a,b,c, a1,b1,c1, a2,b2,c2, …]
|
||||
infiniteSupply supply = supply <> addSuffixes supply (1 :: Integer)
|
||||
where
|
||||
addSuffixes xs n = map (\x -> addSuffix x n) xs <> addSuffixes xs (n+1)
|
||||
addSuffix x n = x <> T.pack (show n)
|
||||
|
||||
|
||||
|
||||
-- | Throw an 'InferError' in an 'Infer'ence context.
|
||||
--
|
||||
-- >>> case runInfer (throw (UnknownIdentifier "var")) of Left err -> putPprLn err
|
||||
-- Unknown identifier: var
|
||||
throw :: InferError -> Infer a
|
||||
throw = Infer . throwE
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Unification
|
||||
-- #############################################################################
|
||||
|
||||
-- $ Unification describes the process of making two different types compatible
|
||||
-- by specializing them where needed. A desirable property to have here is being
|
||||
-- able to find the most general unifier. Luckily, we'll be able to do that in
|
||||
-- our type system.
|
||||
|
||||
|
||||
|
||||
-- | The unification of two 'MType's is the most general substituion that can be
|
||||
-- applied to both of them in order to yield the same result.
|
||||
--
|
||||
-- >>> let m1 = TFun "a" "b"
|
||||
-- >>> putPprLn m1
|
||||
-- a → b
|
||||
-- >>> let m2 = TFun "c" (TEither "d" "e")
|
||||
-- >>> putPprLn m2
|
||||
-- c → Either d e
|
||||
-- >>> let inferSubst = unify (m1, m2)
|
||||
-- >>> case runInfer inferSubst of Right subst -> putPprLn subst
|
||||
-- { a ––> c
|
||||
-- , b ––> Either d e }
|
||||
unify :: (MType, MType) -> Infer Subst
|
||||
unify = \case
|
||||
(TFun a b, TFun x y) -> unifyBinary (a,b) (x,y)
|
||||
(TVar v, x) -> v `bindVariableTo` x
|
||||
(x, TVar v) -> v `bindVariableTo` x
|
||||
(TConst a, TConst b) | a == b -> pure mempty
|
||||
(TList a, TList b) -> unify (a,b)
|
||||
(TEither a b, TEither x y) -> unifyBinary (a,b) (x,y)
|
||||
(TTuple a b, TTuple x y) -> unifyBinary (a,b) (x,y)
|
||||
(a, b) -> throw (CannotUnify a b)
|
||||
|
||||
where
|
||||
|
||||
-- Unification of binary type constructors, such as functions and Either.
|
||||
-- Unification is first done for the first operand, and assuming the
|
||||
-- required substitution, for the second one.
|
||||
unifyBinary :: (MType, MType) -> (MType, MType) -> Infer Subst
|
||||
unifyBinary (a,b) (x,y) = do
|
||||
s1 <- unify (a, x)
|
||||
s2 <- unify (applySubst s1 (b, y))
|
||||
pure (s1 <> s2)
|
||||
|
||||
|
||||
|
||||
-- | Build a 'Subst'itution that binds a 'Name' of a 'TVar' to an 'MType'. The
|
||||
-- resulting substitution should be idempotent, i.e. applying it more than once
|
||||
-- to something should not be any different from applying it only once.
|
||||
--
|
||||
-- - In the simplest case, this just means building a substitution that just
|
||||
-- does that.
|
||||
-- - Substituting a 'Name' with a 'TVar' with the same name unifies a type
|
||||
-- variable with itself, and the resulting substitution does nothing new.
|
||||
-- - If the 'Name' we're trying to bind to an 'MType' already occurs in that
|
||||
-- 'MType', the resulting substitution would not be idempotent: the 'MType'
|
||||
-- would be replaced again, yielding a different result. This is known as the
|
||||
-- Occurs Check.
|
||||
bindVariableTo :: Name -> MType -> Infer Subst
|
||||
|
||||
bindVariableTo name (TVar v) | boundToSelf = pure mempty
|
||||
where
|
||||
boundToSelf = name == v
|
||||
|
||||
bindVariableTo name mType | name `occursIn` mType = throw (OccursCheckFailed name mType)
|
||||
where
|
||||
n `occursIn` ty = n `S.member` freeMType ty
|
||||
|
||||
bindVariableTo name mType = pure (Subst (M.singleton name mType))
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Type inference
|
||||
-- #############################################################################
|
||||
|
||||
-- $ Type inference is the act of finding out a value's type by looking at the
|
||||
-- environment it is in, in order to make it compatible with it.
|
||||
--
|
||||
-- In literature, the Hindley-Damas-Milner inference algorithm ("Algorithm W")
|
||||
-- is often presented in the style of logical formulas, and below you'll find
|
||||
-- that version along with code that actually does what they say.
|
||||
--
|
||||
-- These formulas look a bit like fractions, where the "numerator" is a
|
||||
-- collection of premises, and the denominator is the consequence if all of them
|
||||
-- hold.
|
||||
--
|
||||
-- __Example:__
|
||||
--
|
||||
-- @
|
||||
-- Γ ⊢ even : Int → Bool Γ ⊢ 1 : Int
|
||||
-- –––––––––––––––––––––––––––––––––––
|
||||
-- Γ ⊢ even 1 : Bool
|
||||
-- @
|
||||
--
|
||||
-- means that if we have a value of type @Int -> Bool@ called "even" and a value
|
||||
-- of type @Int@ called @1@, then we also have a value of type @Bool@ via
|
||||
-- @even 1@ available to us.
|
||||
--
|
||||
-- The actual inference rules are polymorphic versions of this example, and
|
||||
-- the code comments will explain each step in detail.
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- *** The language: typed lambda calculus
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-- | The syntax tree of the language we'd like to typecheck. You can view it as
|
||||
-- a close relative to simply typed lambda calculus, having only the most
|
||||
-- necessary syntax elements.
|
||||
--
|
||||
-- Since 'ELet' is non-recursive, the usual fixed-point function
|
||||
-- @fix : (a → a) → a@ can be introduced to allow recursive definitions.
|
||||
data Exp = ELit Lit -- ^ True, 1
|
||||
| EVar Name -- ^ @x@
|
||||
| EApp Exp Exp -- ^ @f x@
|
||||
| EAbs Name Exp -- ^ @λx. e@
|
||||
| ELet Name Exp Exp -- ^ @let x = e in e'@ (non-recursive)
|
||||
deriving Show
|
||||
|
||||
|
||||
|
||||
-- | Literals we'd like to support. Since we can't define new data types in our
|
||||
-- simple type system, we'll have to hard-code the possible ones here.
|
||||
data Lit = LBool Bool
|
||||
| LInteger Integer
|
||||
deriving Show
|
||||
|
||||
|
||||
|
||||
-- | >>> putPprLn (EAbs "f" (EAbs "g" (EAbs "x" (EApp (EApp "f" "x") (EApp "g" "x")))))
|
||||
-- λf g x. f x (g x)
|
||||
instance Pretty Exp where
|
||||
ppr (ELit lit) = ppr lit
|
||||
|
||||
ppr (EVar name) = ppr name
|
||||
|
||||
ppr (EApp f x) = pprApp1 f <> " " <> pprApp2 x
|
||||
where
|
||||
pprApp1 = \case
|
||||
eLet@ELet{} -> "(" <> ppr eLet <> ")"
|
||||
eLet@EAbs{} -> "(" <> ppr eLet <> ")"
|
||||
e -> ppr e
|
||||
pprApp2 = \case
|
||||
eApp@EApp{} -> "(" <> ppr eApp <> ")"
|
||||
e -> pprApp1 e
|
||||
|
||||
ppr x@EAbs{} = pprAbs True x
|
||||
where
|
||||
pprAbs True (EAbs name expr) = "λ" <> ppr name <> pprAbs False expr
|
||||
pprAbs False (EAbs name expr) = " " <> ppr name <> pprAbs False expr
|
||||
pprAbs _ expr = ". " <> ppr expr
|
||||
|
||||
ppr (ELet name value body) =
|
||||
"let " <> ppr name <> " = " <> ppr value <> " in " <> ppr body
|
||||
|
||||
-- | >>> putPprLn (LBool True)
|
||||
-- True
|
||||
--
|
||||
-- >>> putPprLn (LInteger 127)
|
||||
-- 127
|
||||
instance Pretty Lit where
|
||||
ppr = \case
|
||||
LBool b -> showT b
|
||||
LInteger i -> showT i
|
||||
where
|
||||
showT :: Show a => a -> Text
|
||||
showT = T.pack . show
|
||||
|
||||
-- | >>> "var" :: Exp
|
||||
-- EVar (Name "var")
|
||||
instance IsString Exp where
|
||||
fromString = EVar . fromString
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- *** Some useful definitions
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-- | Generate a fresh 'Name' in a type 'Infer'ence context. An example use case
|
||||
-- of this is η expansion, which transforms @f@ into @λx. f x@, where "x" is a
|
||||
-- new name, i.e. unbound in the current context.
|
||||
fresh :: Infer MType
|
||||
fresh = drawFromSupply >>= \case
|
||||
Right name -> pure (TVar name)
|
||||
Left err -> throw err
|
||||
|
||||
where
|
||||
|
||||
drawFromSupply :: Infer (Either InferError Name)
|
||||
drawFromSupply = Infer (do
|
||||
s:upply <- lift get
|
||||
lift (put upply)
|
||||
pure (Right s) )
|
||||
|
||||
|
||||
|
||||
-- | Add a new binding to the environment.
|
||||
--
|
||||
-- The Haskell equivalent would be defining a new value, for example in module
|
||||
-- scope or in a @let@ block. This corresponds to the "comma" operation used in
|
||||
-- formal notation,
|
||||
--
|
||||
-- @
|
||||
-- Γ, x:σ ≡ extendEnv Γ (x,σ)
|
||||
-- @
|
||||
extendEnv :: Env -> (Name, PType) -> Env
|
||||
extendEnv (Env env) (name, pType) = Env (M.insert name pType env)
|
||||
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- *** Inferring the types of all language constructs
|
||||
-- -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
-- | Infer the type of an 'Exp'ression in an 'Env'ironment, resulting in the
|
||||
-- 'Exp's 'MType' along with a substitution that has to be done in order to reach
|
||||
-- this goal.
|
||||
--
|
||||
-- This is widely known as /Algorithm W/.
|
||||
infer :: Env -> Exp -> Infer (Subst, MType)
|
||||
infer env = \case
|
||||
ELit lit -> inferLit lit
|
||||
EVar name -> inferVar env name
|
||||
EApp f x -> inferApp env f x
|
||||
EAbs x e -> inferAbs env x e
|
||||
ELet x e e' -> inferLet env x e e'
|
||||
|
||||
|
||||
|
||||
-- | Literals such as 'True' and '1' have their types hard-coded.
|
||||
inferLit :: Lit -> Infer (Subst, MType)
|
||||
inferLit lit = pure (mempty, TConst litTy)
|
||||
where
|
||||
litTy = case lit of
|
||||
LBool {} -> "Bool"
|
||||
LInteger {} -> "Integer"
|
||||
|
||||
|
||||
|
||||
-- | Inferring the type of a variable is done via
|
||||
--
|
||||
-- @
|
||||
-- x:σ ∈ Γ τ = instantiate(σ)
|
||||
-- –––––––––––––––––––––––––––– [Var]
|
||||
-- Γ ⊢ x:τ
|
||||
-- @
|
||||
--
|
||||
-- This means that if @Γ@ /literally contains/ (@∈@) a value, then it also
|
||||
-- /entails it/ (@⊢@) in all its instantiations.
|
||||
inferVar :: Env -> Name -> Infer (Subst, MType)
|
||||
inferVar env name = do
|
||||
sigma <- lookupEnv env name -- x:σ ∈ Γ
|
||||
tau <- instantiate sigma -- τ = instantiate(σ)
|
||||
-- ------------------
|
||||
pure (mempty, tau) -- Γ ⊢ x:τ
|
||||
|
||||
|
||||
|
||||
-- | Look up the 'PType' of a 'Name' in the 'Env'ironment.
|
||||
--
|
||||
-- This checks whether @x:σ@ is /literally contained/ in @Γ@. For more details
|
||||
-- about this, see the documentation of 'Env'.
|
||||
--
|
||||
-- To give a Haskell analogon, looking up @id@ when @Prelude@ is loaded, the
|
||||
-- resulting 'PType' would be @id@'s type, namely @forall a. a -> a@.
|
||||
lookupEnv :: Env -> Name -> Infer PType
|
||||
lookupEnv (Env env) name = case M.lookup name env of
|
||||
Just x -> pure x
|
||||
Nothing -> throw (UnknownIdentifier name)
|
||||
|
||||
|
||||
|
||||
-- | Bind all quantified variables of a 'PType' to 'fresh' type variables.
|
||||
--
|
||||
-- __Example:__ instantiating @forall a. a -> b -> a@ results in the 'MType'
|
||||
-- @c -> b -> c@, where @c@ is a fresh name (to avoid shadowing issues).
|
||||
--
|
||||
-- You can picture the 'PType' to be the prototype converted to an instantiated
|
||||
-- 'MType', which can now be used in the unification process.
|
||||
--
|
||||
-- Another way of looking at it is by simply forgetting which variables were
|
||||
-- quantified, carefully avoiding name clashes when doing so.
|
||||
--
|
||||
-- 'instantiate' can also be seen as the opposite of 'generalize', which we'll
|
||||
-- need later to convert an 'MType' to a 'PType'.
|
||||
instantiate :: PType -> Infer MType
|
||||
instantiate (Forall qs t) = do
|
||||
subst <- substituteAllWithFresh qs
|
||||
pure (applySubst subst t)
|
||||
|
||||
where
|
||||
-- For each given name, add a substitution from that name to a fresh type
|
||||
-- variable to the result.
|
||||
substituteAllWithFresh :: Set Name -> Infer Subst
|
||||
substituteAllWithFresh xs = do
|
||||
let freshSubstActions = M.fromSet (const fresh) xs
|
||||
freshSubsts <- sequenceA freshSubstActions
|
||||
pure (Subst freshSubsts)
|
||||
|
||||
|
||||
|
||||
-- | Function application captures the fact that if we have a function and an
|
||||
-- argument we can give to that function, we also have the result value of the
|
||||
-- result type available to us.
|
||||
--
|
||||
-- @
|
||||
-- Γ ⊢ f : fτ Γ ⊢ x : xτ fxτ = fresh unify(fτ, xτ → fxτ)
|
||||
-- ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– [App]
|
||||
-- Γ ⊢ f x : fxτ
|
||||
-- @
|
||||
--
|
||||
-- This rule says that given a function and a value with a type, the function
|
||||
-- type has to unify with a function type that allows the value type to be its
|
||||
-- argument.
|
||||
inferApp
|
||||
:: Env
|
||||
-> Exp -- ^ __f__ x
|
||||
-> Exp -- ^ f __x__
|
||||
-> Infer (Subst, MType)
|
||||
inferApp env f x = do
|
||||
(s1, fTau) <- infer env f -- f : fτ
|
||||
(s2, xTau) <- infer (applySubst s1 env) x -- x : xτ
|
||||
fxTau <- fresh -- fxτ = fresh
|
||||
s3 <- unify (applySubst s2 fTau, TFun xTau fxTau) -- unify (fτ, xτ → fxτ)
|
||||
let s = s3 <> s2 <> s1 -- --------------------
|
||||
pure (s, applySubst s3 fxTau) -- f x : fxτ
|
||||
|
||||
|
||||
|
||||
-- | Lambda abstraction is based on the fact that when we introduce a new
|
||||
-- variable, the resulting lambda maps from that variable's type to the type of
|
||||
-- the body.
|
||||
--
|
||||
-- @
|
||||
-- τ = fresh σ = ∀∅. τ Γ, x:σ ⊢ e:τ'
|
||||
-- ––––––––––––––––––––––––––––––––––––– [Abs]
|
||||
-- Γ ⊢ λx.e : τ→τ'
|
||||
-- @
|
||||
--
|
||||
-- Here, @Γ, x:τ@ is @Γ@ extended by one additional mapping, namely @x:τ@.
|
||||
--
|
||||
-- Abstraction is typed by extending the environment by a new 'MType', and if
|
||||
-- under this assumption we can construct a function mapping to a value of that
|
||||
-- type, we can say that the lambda takes a value and maps to it.
|
||||
inferAbs
|
||||
:: Env
|
||||
-> Name -- ^ λ__x__. e
|
||||
-> Exp -- ^ λx. __e__
|
||||
-> Infer (Subst, MType)
|
||||
inferAbs env x e = do
|
||||
tau <- fresh -- τ = fresh
|
||||
let sigma = Forall [] tau -- σ = ∀∅. τ
|
||||
env' = extendEnv env (x, sigma) -- Γ, x:σ …
|
||||
(s, tau') <- infer env' e -- … ⊢ e:τ'
|
||||
-- ---------------
|
||||
pure (s, TFun (applySubst s tau) tau') -- λx.e : τ→τ'
|
||||
|
||||
|
||||
|
||||
-- | A let binding allows extending the environment with new bindings in a
|
||||
-- principled manner. To do this, we first have to typecheck the expression to
|
||||
-- be introduced. The result of this is then generalized to a 'PType', since let
|
||||
-- bindings introduce new polymorphic values, which are then added to the
|
||||
-- environment. Now we can finally typecheck the body of the "in" part of the
|
||||
-- let binding.
|
||||
--
|
||||
-- Note that in our simple language, let is non-recursive, but recursion can be
|
||||
-- introduced as usual by adding a primitive @fix : (a → a) → a@ if desired.
|
||||
--
|
||||
-- @
|
||||
-- Γ ⊢ e:τ σ = gen(Γ,τ) Γ, x:σ ⊢ e':τ'
|
||||
-- ––––––––––––––––––––––––––––––––––––––– [Let]
|
||||
-- Γ ⊢ let x = e in e' : τ'
|
||||
-- @
|
||||
inferLet
|
||||
:: Env
|
||||
-> Name -- ^ let __x__ = e in e'
|
||||
-> Exp -- ^ let x = __e__ in e'
|
||||
-> Exp -- ^ let x = e in __e'__
|
||||
-> Infer (Subst, MType)
|
||||
inferLet env x e e' = do
|
||||
(s1, tau) <- infer env e -- Γ ⊢ e:τ
|
||||
let env' = applySubst s1 env
|
||||
let sigma = generalize env' tau -- σ = gen(Γ,τ)
|
||||
let env'' = extendEnv env' (x, sigma) -- Γ, x:σ
|
||||
(s2, tau') <- infer env'' e' -- Γ ⊢ …
|
||||
-- --------------------------
|
||||
pure (s2 <> s1, tau') -- … let x = e in e' : τ'
|
||||
|
||||
|
||||
|
||||
-- | Generalize an 'MType' to a 'PType' by universally quantifying over all the
|
||||
-- type variables contained in it, except those already free in the environment.
|
||||
--
|
||||
-- >>> let tau = TFun "a" (TFun "b" "a")
|
||||
-- >>> putPprLn tau
|
||||
-- a → b → a
|
||||
-- >>> putPprLn (generalize (Env [("x", Forall [] "b")]) tau)
|
||||
-- ∀a. a → b → a
|
||||
--
|
||||
-- In more formal notation,
|
||||
--
|
||||
-- @
|
||||
-- gen(Γ,τ) = ∀{α}. τ
|
||||
-- where {α} = free(τ) – free(Γ)
|
||||
-- @
|
||||
--
|
||||
-- 'generalize' can also be seen as the opposite of 'instantiate', which
|
||||
-- converts a 'PType' to an 'MType'.
|
||||
generalize :: Env -> MType -> PType
|
||||
generalize env mType = Forall qs mType
|
||||
where
|
||||
qs = freeMType mType `S.difference` freeEnv env
|
|
@ -0,0 +1,185 @@
|
|||
{-# LANGUAGE OverloadedLists #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Main where
|
||||
|
||||
|
||||
|
||||
import qualified Data.Map as M
|
||||
import Data.Monoid
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text.IO as T
|
||||
|
||||
import HindleyMilner
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
-- * Testing
|
||||
-- #############################################################################
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** A small custom Prelude
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
prelude :: Env
|
||||
prelude = Env (M.fromList
|
||||
[ ("(*)", Forall [] (tInteger ~> tInteger ~> tInteger))
|
||||
, ("(+)", Forall [] (tInteger ~> tInteger ~> tInteger))
|
||||
, ("(,)", Forall ["a","b"] ("a" ~> "b" ~> TTuple "a" "b"))
|
||||
, ("(-)", Forall [] (tInteger ~> tInteger ~> tInteger))
|
||||
, ("(.)", Forall ["a", "b", "c"] (("b" ~> "c") ~> ("a" ~> "b") ~> "a" ~> "c"))
|
||||
, ("(<)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("(<=)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("(>)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("(>=)", Forall [] (tInteger ~> tInteger ~> tBool))
|
||||
, ("const", Forall ["a","b"] ("a" ~> "b" ~> "a"))
|
||||
, ("Cont/>>=", Forall ["a"] ((("a" ~> "r") ~> "r") ~> ("a" ~> (("b" ~> "r") ~> "r")) ~> (("b" ~> "r") ~> "r")))
|
||||
, ("find", Forall ["a","b"] (("a" ~> tBool) ~> TList "a" ~> tMaybe "a"))
|
||||
, ("fix", Forall ["a"] (("a" ~> "a") ~> "a"))
|
||||
, ("foldr", Forall ["a","b"] (("a" ~> "b" ~> "b") ~> "b" ~> TList "a" ~> "b"))
|
||||
, ("id", Forall ["a"] ("a" ~> "a"))
|
||||
, ("ifThenElse", Forall ["a"] (tBool ~> "a" ~> "a" ~> "a"))
|
||||
, ("Left", Forall ["a","b"] ("a" ~> TEither "a" "b"))
|
||||
, ("length", Forall ["a"] (TList "a" ~> tInteger))
|
||||
, ("map", Forall ["a","b"] (("a" ~> "b") ~> TList "a" ~> TList "b"))
|
||||
, ("reverse", Forall ["a"] (TList "a" ~> TList "a"))
|
||||
, ("Right", Forall ["a","b"] ("b" ~> TEither "a" "b"))
|
||||
, ("[]", Forall ["a"] (TList "a"))
|
||||
, ("(:)", Forall ["a"] ("a" ~> TList "a" ~> TList "a"))
|
||||
])
|
||||
where
|
||||
tBool = TConst "Bool"
|
||||
tInteger = TConst "Integer"
|
||||
tMaybe = TEither (TConst "()")
|
||||
|
||||
|
||||
|
||||
-- | Synonym for 'TFun' to make writing type signatures easier.
|
||||
--
|
||||
-- Instead of
|
||||
--
|
||||
-- @
|
||||
-- Forall ["a","b"] (TFun "a" (TFun "b" "a"))
|
||||
-- @
|
||||
--
|
||||
-- we can write
|
||||
--
|
||||
-- @
|
||||
-- Forall ["a","b"] ("a" ~> "b" ~> "a")
|
||||
-- @
|
||||
(~>) :: MType -> MType -> MType
|
||||
(~>) = TFun
|
||||
infixr 9 ~>
|
||||
|
||||
|
||||
|
||||
-- #############################################################################
|
||||
-- ** Run it!
|
||||
-- #############################################################################
|
||||
|
||||
|
||||
|
||||
-- | Run type inference on a cuple of values
|
||||
main :: IO ()
|
||||
main = do
|
||||
let inferAndPrint = T.putStrLn . (" " <>) . showType prelude
|
||||
T.putStrLn "Well-typed:"
|
||||
do
|
||||
inferAndPrint (lambda ["x"] "x")
|
||||
inferAndPrint (lambda ["f","g","x"] (apply "f" ["x", apply "g" ["x"]]))
|
||||
inferAndPrint (lambda ["f","g","x"] (apply "f" [apply "g" ["x"]]))
|
||||
inferAndPrint (lambda ["m", "k", "c"] (apply "m" [lambda ["x"] (apply "k" ["x", "c"])])) -- >>= for Cont
|
||||
inferAndPrint (lambda ["f"] (apply "(.)" ["reverse", apply "map" ["f"]]))
|
||||
inferAndPrint (apply "find" [lambda ["x"] (apply "(>)" ["x", int 0])])
|
||||
inferAndPrint (apply "map" [apply "map" ["map"]])
|
||||
inferAndPrint (apply "(*)" [int 1, int 2])
|
||||
inferAndPrint (apply "foldr" ["(+)", int 0])
|
||||
inferAndPrint (apply "map" ["length"])
|
||||
inferAndPrint (apply "map" ["map"])
|
||||
inferAndPrint (lambda ["x"] (apply "ifThenElse" [apply "(<)" ["x", int 0], int 0, "x"]))
|
||||
inferAndPrint (lambda ["x"] (apply "fix" [lambda ["xs"] (apply "(:)" ["x", "xs"])]))
|
||||
T.putStrLn "Ill-typed:"
|
||||
do
|
||||
inferAndPrint (apply "(*)" [int 1, bool True])
|
||||
inferAndPrint (apply "foldr" [int 1])
|
||||
inferAndPrint (lambda ["x"] (apply "x" ["x"]))
|
||||
inferAndPrint (lambda ["x"] (ELet "xs" (apply "(:)" ["x", "xs"]) "xs"))
|
||||
|
||||
|
||||
|
||||
-- | Build multiple lambda bindings.
|
||||
--
|
||||
-- Instead of
|
||||
--
|
||||
-- @
|
||||
-- EAbs "f" (EAbs "x" (EApp "f" "x"))
|
||||
-- @
|
||||
--
|
||||
-- we can write
|
||||
--
|
||||
-- @
|
||||
-- lambda ["f", "x"] (EApp "f" "x")
|
||||
-- @
|
||||
--
|
||||
-- for
|
||||
--
|
||||
-- @
|
||||
-- λf x. f x
|
||||
-- @
|
||||
lambda :: [Name] -> Exp -> Exp
|
||||
lambda names expr = foldr EAbs expr names
|
||||
|
||||
|
||||
|
||||
-- | Apply a function to multiple arguments.
|
||||
--
|
||||
-- Instead of
|
||||
--
|
||||
-- @
|
||||
-- EApp (EApp (EApp "f" "x") "y") "z")
|
||||
-- @
|
||||
--
|
||||
-- we can write
|
||||
--
|
||||
-- @
|
||||
-- apply "f" ["x", "y", "z"]
|
||||
-- @
|
||||
--
|
||||
-- for
|
||||
--
|
||||
-- @
|
||||
-- f x y z
|
||||
-- @
|
||||
apply :: Exp -> [Exp] -> Exp
|
||||
apply = foldl EApp
|
||||
|
||||
|
||||
|
||||
-- | Construct an integer literal.
|
||||
int :: Integer -> Exp
|
||||
int = ELit . LInteger
|
||||
|
||||
|
||||
|
||||
-- | Construct a boolean literal.
|
||||
bool :: Bool -> Exp
|
||||
bool = ELit . LBool
|
||||
|
||||
|
||||
|
||||
-- | Convenience function to run type inference algorithm
|
||||
showType :: Env -- ^ Starting environment, e.g. 'prelude'.
|
||||
-> Exp -- ^ Expression to typecheck
|
||||
-> Text -- ^ Text representation of the result. Contains an error
|
||||
-- message on failure.
|
||||
showType env expr =
|
||||
case (runInfer . fmap (generalize (Env mempty) . uncurry applySubst) . infer env) expr of
|
||||
Left err -> "Error inferring type of " <> ppr expr <>": " <> ppr err
|
||||
Right ty -> ppr expr <> " :: " <> ppr ty
|
97
README.md
97
README.md
|
@ -1,4 +1,95 @@
|
|||
# Schala - A Programming Language Implementation
|
||||
# Schala - a programming language meta-interpreter
|
||||
|
||||
`schala` is an implementation of a yet-unnamed quasi-functional programming
|
||||
language.
|
||||
Schala is a Rust framework written to make it easy to create and experiment
|
||||
with multipl toy programming languages. It provides a cross-language REPL and
|
||||
provisions for tokenizing text, parsing tokens, evaluating an abstract syntax
|
||||
tree, and other tasks that are common to all programming languages, as well as sharing state
|
||||
between multiple programming languages.
|
||||
|
||||
Schala is implemented as a Rust library `schala-repl`, which provides a
|
||||
function `start_repl`, meant to be used as entry point into a common REPL or
|
||||
non-interactive environment. Clients are expected to invoke `start_repl` with a
|
||||
vector of programming languages. Individual programming language
|
||||
implementations are Rust types that implement the
|
||||
`ProgrammingLanguageInterface` trait and store whatever persistent state is
|
||||
relevant to that language.
|
||||
|
||||
Run schala with: `cargo run`. This will drop you into a REPL environment. Type
|
||||
`:help` for more information, or type in text in any supported programming
|
||||
language (currently only schala-lang) to evaluate it in the REPL.
|
||||
|
||||
## History
|
||||
|
||||
Schala started out life as an experiment in writing a Javascript-like
|
||||
programming language that would never encounter any kind of runtime value
|
||||
error, but rather always return `null` under any kind of error condition. I had
|
||||
seen one too many Javascript `Uncaught TypeError: Cannot read property ___ of
|
||||
undefined` messages, and I was a bit frustrated. Plus I had always wanted to
|
||||
write a programming langauge from scratch, and Rust is a fun language to
|
||||
program in. Over time I became interested in playing around with other sorts
|
||||
of programming languages as well, and wanted to make the process as general as
|
||||
possible.
|
||||
|
||||
The name of the project comes from Schala the Princess of Zeal from the 1995
|
||||
SNES RPG *Chrono Trigger*. I like classic JRPGs and enjoyed the thought of
|
||||
creating a language name confusingly close to Scala. The naming scheme for
|
||||
languages implemented with the Schala meta-interpreter is Chrono Trigger
|
||||
characters.
|
||||
|
||||
Schala and languages implemented with it are incomplete alpha software and are
|
||||
not ready for public release.
|
||||
|
||||
## Languages implemented using the meta-interpreter
|
||||
|
||||
* The eponymous *Schala* language is a work-in-progress general purpose
|
||||
programming language with static typing and algebraic data types. Its design
|
||||
goals include having a very straightforward implemenation and being syntactically
|
||||
minimal.
|
||||
|
||||
* *Maaru* is a very simple dynamically-typed scripting language, with the semantics
|
||||
that all runtime errors return a `null` value rather than fail.
|
||||
|
||||
* *Robo* is an experiment in creating a lazy, functional, strongly-typed language
|
||||
much like Haskell
|
||||
|
||||
* *Rukka* is a straightforward LISP implementation
|
||||
|
||||
## Reference works
|
||||
|
||||
Here's a partial list of resources I've made use of in the process
|
||||
of learning how to write a programming language.
|
||||
|
||||
### General
|
||||
|
||||
http://thume.ca/2019/04/18/writing-a-compiler-in-rust/
|
||||
|
||||
### Type-checking
|
||||
https://skillsmatter.com/skillscasts/10868-inside-the-rust-compiler
|
||||
https://www.youtube.com/watch?v=il3gD7XMdmA
|
||||
http://dev.stephendiehl.com/fun/006_hindley_milner.html
|
||||
https://rust-lang-nursery.github.io/rustc-guide/type-inference.html
|
||||
|
||||
https://eli.thegreenplace.net/2018/unification/
|
||||
https://eli.thegreenplace.net/2018/type-inference/
|
||||
http://smallcultfollowing.com/babysteps/blog/2017/03/25/unification-in-chalk-part-1/
|
||||
http://reasonableapproximation.net/2019/05/05/hindley-milner.html
|
||||
https://rickyhan.com/jekyll/update/2018/05/26/hindley-milner-tutorial-rust.html
|
||||
|
||||
### Evaluation
|
||||
*Understanding Computation*, Tom Stuart, O'Reilly 2013
|
||||
|
||||
*Basics of Compiler Design*, Torben Mogensen
|
||||
|
||||
### Parsing
|
||||
http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
|
||||
https://soc.github.io/languages/unified-condition-syntax
|
||||
|
||||
[Crafting Interpreters](http://www.craftinginterpreters.com/)
|
||||
|
||||
### LLVM
|
||||
http://blog.ulysse.io/2016/07/03/llvm-getting-started.html
|
||||
|
||||
###Rust resources
|
||||
https://thefullsnack.com/en/rust-for-the-web.html
|
||||
|
||||
https://rocket.rs/guide/getting-started/
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
# TODO items
|
||||
|
||||
## General code cleanup
|
||||
-experiment with storing metadata via ItemIds on AST nodes (cf. https://rust-lang.github.io/rustc-guide/hir.html, https://github.com/rust-lang/rust/blob/master/src/librustc/hir/mod.rs )
|
||||
-implement and test open/use statements
|
||||
-implement field access
|
||||
- standardize on an error type that isn't String
|
||||
-implement a visitor pattern for the use of scope_resolver
|
||||
|
||||
## Reduction
|
||||
- make a good type for actual language builtins to avoid string comparisons
|
||||
|
||||
## Typechecking
|
||||
|
||||
- make a type to represent types rather than relying on string comparisons
|
||||
|
||||
- look at https://rickyhan.com/jekyll/update/2018/05/26/hindley-milner-tutorial-rust.html
|
||||
|
||||
- cf. the notation mentioned in the cardelli paper, the debug information for the `typechecking` pass should
|
||||
print the generated type variable for every subexpression in an expression
|
||||
|
||||
- think about idris-related ideas of multiple implementations of a type for an interface (+ vs * impl for monoids, for preorder/inorder/postorder for Foldable)
|
||||
|
||||
-should have an Idris-like `cast To From` function
|
||||
|
||||
## Schala-lang syntax
|
||||
|
||||
-idea: the `type` declaration should have some kind of GADT-like syntax
|
||||
|
||||
- Idea: if you have a pattern-match where one variant has a variable and the other lacks it
|
||||
instead of treating this as a type error, promote the bound variable to an option type
|
||||
|
||||
- Include extensible scala-style html"string ${var}" string interpolations
|
||||
|
||||
- A neat idea for pattern matching optimization would be if you could match on one of several things in a list
|
||||
ex:
|
||||
```if x {
|
||||
is (comp, LHSPat, RHSPat) if comp in ["==, "<"] -> ...
|
||||
}```
|
||||
|
||||
- Schala should have both currying *and* default arguments!
|
||||
```fn a(b: Int, c:Int, d:Int = 1) -> Int
|
||||
a(1,2) : Int
|
||||
a(1,2,d=2): Int
|
||||
a(_,1,3) : Int -> Int
|
||||
a(1,2, c=_): Int -> Int
|
||||
a(_,_,_) : Int -> Int -> Int -> Int
|
||||
```
|
||||
|
||||
- scoped types - be able to define a quick enum type scoped to a function or other type for
|
||||
something, that only is meant to be used as a quick bespoke interface between
|
||||
two other things
|
||||
|
||||
ex.
|
||||
```type enum {
|
||||
type enum MySubVariant {
|
||||
SubVariant1, SubVariant2, etc.
|
||||
}
|
||||
Variant1(MySubVariant),
|
||||
Variant2(...),
|
||||
}```
|
||||
|
||||
- inclusive/exclusive range syntax like .. vs ..=
|
||||
|
||||
## Compilation
|
||||
-look into Inkwell for rust LLVM bindings
|
||||
|
||||
-https://cranelift.readthedocs.io/en/latest/?badge=latest<Paste>
|
||||
|
||||
|
||||
## Other links of note
|
||||
|
||||
- https://nshipster.com/never/
|
||||
-consult http://gluon-lang.org/book/embedding-api.html
|
||||
|
||||
|
||||
|
||||
## Playing around with conditional syntax ideas
|
||||
|
||||
-
|
||||
|
||||
- if/match playground
|
||||
|
||||
simple if
|
||||
`if x == 1.0 { "a" } else { "b" }`
|
||||
|
||||
one comparison multiple targets:
|
||||
`if x == { 1.0 -> "a", 2.0 -> "b", else -> "c" }`
|
||||
|
||||
different comparison operators/ method calls:
|
||||
`if x { == 1.0 -> "a", eq NaN -> "n", .hella() -> "h", else -> "z" }`
|
||||
|
||||
pattern matching/introducing bindings:
|
||||
`if alice { .age < 18 -> "18", is Person("Alice", age) -> "${age}", else -> "none" }`
|
||||
|
||||
pattern matching w/ if-let:
|
||||
`if person is Person("Alice", age) { "${age}" } else { "nope" }`
|
||||
|
||||
-https://soc.github.io/languages/unified-condition-syntax syntax:
|
||||
|
||||
`if <cond-expr>" then <then-expr> else <else-expr>`
|
||||
`if <half-expr> \n <rest-expr1> then <result1-expr> \n <rest-expr2> then <result-expr2> else <result3-expr>`
|
||||
-and rest-exprs (or "targets") can have 'is' for pattern-matching, actually so can a full cond-expr
|
||||
|
||||
UNIFIED IF EXPRESSIONS FINAL WORK:
|
||||
|
||||
basic syntax:
|
||||
|
||||
`if_expr := if discriminator '{' (guard_expr)* '}'`
|
||||
`guard_expr := pattern 'then' block_or_expr'`
|
||||
`pattern := rhs | is_pattern`
|
||||
`is_pattern := 'is' ???`
|
||||
`rhs := expression | ???`
|
||||
|
||||
|
||||
if the only two guard patterns are true and false, then the abbreviated syntax:
|
||||
`'if' discriminator 'then' block_or_expr 'else' block_or_expr`
|
||||
can replace `'if' discriminator '{' 'true' 'then' block_or_expr; 'false' 'then' block_or_expr '}'`
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[package]
|
||||
name = "experiments"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
module.exports = grammar({
|
||||
name: "TestLang",
|
||||
rules: {
|
||||
source_file: $ => repeat($._definition),
|
||||
|
||||
_definition: $ => choice(
|
||||
$.function_definition
|
||||
//TODO others
|
||||
),
|
||||
|
||||
function_definition: $ => seq(
|
||||
'fn',
|
||||
$.identifier,
|
||||
$.parameter_list,
|
||||
field("return_type", optional($._type)),
|
||||
$.block,
|
||||
),
|
||||
parameter_list: $ => seq("(", /* TODO */ ")"),
|
||||
|
||||
block: $ => seq(
|
||||
"{",
|
||||
choice(
|
||||
repeat($._statement),
|
||||
"",
|
||||
),
|
||||
"}"
|
||||
),
|
||||
|
||||
_statement: $ => choice(
|
||||
$._return_statement
|
||||
),
|
||||
|
||||
_return_statement: $ => seq("return", $._expression, ";"),
|
||||
|
||||
_expression: $ => choice($.identifier, $.unary, $.binary),
|
||||
|
||||
unary: $ => prec(4, choice(seq("-", $._expression), seq("!", $._expression))),
|
||||
binary: $ => choice(prec.left(2, seq($._expression, "*", $._expression)), prec.left(1, seq($._expression, "+", $._expression))),
|
||||
|
||||
_type: $ => "bool",
|
||||
_type: $ => choice(
|
||||
$.primitive_type,
|
||||
),
|
||||
primitive_type: $ => choice("bool", "int"),
|
||||
|
||||
identifier: $ => /[a-z]+/,
|
||||
}
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
_default:
|
||||
just --list
|
||||
|
||||
# Test out the grammar
|
||||
test-grammar:
|
||||
#!/usr/bin/env bash
|
||||
tree-sitter generate
|
||||
tree-sitter test
|
|
@ -1,380 +0,0 @@
|
|||
{
|
||||
"name": "tree-sitter-test",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tree-sitter-test",
|
||||
"version": "1.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0",
|
||||
"node-gyp-build": "^4.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prebuildify": "^6.0.0",
|
||||
"tree-sitter-cli": "^0.22.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.21.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree_sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.60.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.60.0.tgz",
|
||||
"integrity": "sha512-zcGgwoXbzw9NczqbGzAWL/ToDYAxv1V8gL1D67ClbdkIfeeDBbY0GelZtC25ayLvVjr2q2cloHeQV1R0QAWqRQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
|
||||
"integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==",
|
||||
"engines": {
|
||||
"node": "^16 || ^18 || >= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
|
||||
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
|
||||
"bin": {
|
||||
"node-gyp-build": "bin.js",
|
||||
"node-gyp-build-optional": "optional.js",
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
|
||||
"integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/prebuildify": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
|
||||
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"node-abi": "^3.3.0",
|
||||
"npm-run-path": "^3.1.0",
|
||||
"pump": "^3.0.0",
|
||||
"tar-fs": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuildify": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz",
|
||||
"integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==",
|
||||
"hasInstallScript": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.0.0",
|
||||
"node-gyp-build": "^4.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-cli": {
|
||||
"version": "0.22.5",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.22.5.tgz",
|
||||
"integrity": "sha512-c3VT46Bc3a6pEd0JAwufbqEw9Q2FRLDp5E230hGvnr+Hivw+Y6jyeP+3T89KDptvn48MOPVmbgaLm69xYgLVTw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"tree-sitter": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter/node_modules/node-addon-api": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.0.0.tgz",
|
||||
"integrity": "sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"name": "tree-sitter-test",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"types": "bindings/node",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"install": "node-gyp-build",
|
||||
"prebuildify": "prebuildify --napi --strip"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0",
|
||||
"node-gyp-build": "^4.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.21.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree_sitter": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"prebuildify": "^6.0.0",
|
||||
"tree-sitter-cli": "^0.22.5"
|
||||
},
|
||||
"files": [
|
||||
"grammar.js",
|
||||
"binding.gyp",
|
||||
"prebuilds/**",
|
||||
"bindings/node/*",
|
||||
"queries/*",
|
||||
"src/**"
|
||||
]
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
=============
|
||||
Initial test
|
||||
=============
|
||||
fn main() {
|
||||
|
||||
}
|
||||
----
|
||||
(source_file
|
||||
(function_definition
|
||||
(identifier)
|
||||
(parameter_list)
|
||||
(block)
|
||||
)
|
||||
)
|
||||
|
||||
====
|
||||
Another test
|
||||
====
|
||||
|
||||
fn yolo() bool { }
|
||||
|
||||
----
|
||||
|
||||
(source_file
|
||||
(function_definition
|
||||
(identifier) (parameter_list) (primitive_type) (block)))
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "maaru-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.5.8"
|
||||
take_mut = "0.1.3"
|
||||
llvm-sys = "*"
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
|
@ -0,0 +1,481 @@
|
|||
extern crate take_mut;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use parser::{AST, Statement, Expression, Function, Callable, BinOp};
|
||||
use std::rc::Rc;
|
||||
use std::io::{Write, Stdout, BufWriter};
|
||||
use std::convert::From;
|
||||
|
||||
use parser::Expression::*;
|
||||
use parser::Statement::*;
|
||||
|
||||
type Reduction<T> = (T, Option<SideEffect>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ReducedValue {
|
||||
StringLiteral(Rc<String>),
|
||||
ListLiteral(VecDeque<Expression>),
|
||||
StructLiteral(VecDeque<(Rc<String>, Expression)>),
|
||||
Number(f64),
|
||||
Lambda(Function),
|
||||
}
|
||||
|
||||
impl From<ReducedValue> for Expression {
|
||||
fn from(rv: ReducedValue) -> Expression {
|
||||
match rv {
|
||||
ReducedValue::Number(n) => Expression::Number(n),
|
||||
ReducedValue::StringLiteral(n) => Expression::StringLiteral(n),
|
||||
ReducedValue::Lambda(f) => Expression::Lambda(f),
|
||||
ReducedValue::ListLiteral(items) => Expression::ListLiteral(items),
|
||||
ReducedValue::StructLiteral(items) => Expression::StructLiteral(items),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Expression> for ReducedValue {
|
||||
fn from(rv: Expression) -> ReducedValue {
|
||||
match rv {
|
||||
Expression::Number(n) => ReducedValue::Number(n),
|
||||
Expression::StringLiteral(n) => ReducedValue::StringLiteral(n),
|
||||
Expression::Lambda(f) => ReducedValue::Lambda(f),
|
||||
Expression::ListLiteral(items) => ReducedValue::ListLiteral(items),
|
||||
Expression::StructLiteral(items) => ReducedValue::StructLiteral(items),
|
||||
_ => panic!("trying to store a non-fully-reduced variable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_indexer(f: f64) -> Option<usize> {
|
||||
if f.fract() == 0.0 {
|
||||
if f.trunc() >= 0.0 {
|
||||
return Some(f.trunc() as usize);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SideEffect {
|
||||
Print(String),
|
||||
AddBinding(Rc<String>, ReducedValue),
|
||||
}
|
||||
|
||||
pub struct Evaluator<'a> {
|
||||
parent: Option<&'a Evaluator<'a>>,
|
||||
variables: HashMap<String, ReducedValue>,
|
||||
stdout: BufWriter<Stdout>,
|
||||
pub trace_evaluation: bool,
|
||||
}
|
||||
|
||||
impl<'a> Evaluator<'a> {
|
||||
pub fn new(parent: Option<&'a Evaluator>) -> Evaluator<'a> {
|
||||
Evaluator {
|
||||
variables: HashMap::new(),
|
||||
parent: parent,
|
||||
stdout: BufWriter::new(::std::io::stdout()),
|
||||
trace_evaluation: parent.map_or(false, |e| e.trace_evaluation),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, ast: AST) -> Vec<String> {
|
||||
ast.into_iter()
|
||||
.map(|astnode| format!("{}", self.reduction_loop(astnode)))
|
||||
.collect()
|
||||
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, var: String, value: ReducedValue) {
|
||||
self.variables.insert(var, value);
|
||||
}
|
||||
|
||||
fn lookup_binding(&self, var: &str) -> Option<ReducedValue> {
|
||||
match self.variables.get(var) {
|
||||
Some(expr) => Some(expr.clone()),
|
||||
None => match self.parent {
|
||||
Some(env) => env.lookup_binding(var),
|
||||
None => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait Evaluable {
|
||||
fn is_reducible(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Evaluable for Statement {
|
||||
fn is_reducible(&self) -> bool {
|
||||
match self {
|
||||
&ExprNode(ref expr) => expr.is_reducible(),
|
||||
&FuncDefNode(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Evaluable for Expression {
|
||||
fn is_reducible(&self) -> bool {
|
||||
match *self {
|
||||
Null => false,
|
||||
StringLiteral(_) => false,
|
||||
Lambda(_) => false,
|
||||
Number(_) => false,
|
||||
ListLiteral(ref items) => {
|
||||
items.iter().any(|x| x.is_reducible())
|
||||
}
|
||||
StructLiteral(ref items) => {
|
||||
items.iter().any(|pair| pair.1.is_reducible())
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
fn is_truthy(&self) -> bool {
|
||||
match *self {
|
||||
Null => false,
|
||||
StringLiteral(ref s) if **s == "" => false,
|
||||
Number(n) if n == 0.0 => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_assignment(op: &BinOp) -> bool {
|
||||
use self::BinOp::*;
|
||||
match *op {
|
||||
Assign | AddAssign | SubAssign |
|
||||
MulAssign | DivAssign => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Evaluator<'a> {
|
||||
fn reduction_loop(&mut self, mut node: Statement) -> Statement {
|
||||
loop {
|
||||
node = self.step(node);
|
||||
if !node.is_reducible() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
fn step(&mut self, node: Statement) -> Statement {
|
||||
let mut trace = String::new();
|
||||
if self.trace_evaluation {
|
||||
trace.push_str(&format!("Step: {:?}", node));
|
||||
}
|
||||
|
||||
let (new_node, side_effect) = self.reduce_astnode(node);
|
||||
|
||||
if self.trace_evaluation {
|
||||
trace.push_str(&format!(" ➜ {:?}", new_node));
|
||||
}
|
||||
if let Some(s) = side_effect {
|
||||
if self.trace_evaluation {
|
||||
trace.push_str(&format!(" | side-effect: {:?}", s));
|
||||
}
|
||||
self.perform_side_effect(s);
|
||||
}
|
||||
if self.trace_evaluation {
|
||||
println!("{}", trace);
|
||||
}
|
||||
new_node
|
||||
}
|
||||
|
||||
fn perform_side_effect(&mut self, side_effect: SideEffect) {
|
||||
use self::SideEffect::*;
|
||||
match side_effect {
|
||||
Print(s) => {
|
||||
write!(self.stdout, "{}\n", s).unwrap();
|
||||
match self.stdout.flush() {
|
||||
Ok(_) => (),
|
||||
Err(_) => println!("Could not flush stdout"),
|
||||
};
|
||||
}
|
||||
AddBinding(var, value) => {
|
||||
self.add_binding((*var).clone(), value);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_astnode(&mut self, node: Statement) -> Reduction<Statement> {
|
||||
match node {
|
||||
ExprNode(expr) => {
|
||||
if expr.is_reducible() {
|
||||
let (new_expr, side_effect) = self.reduce_expr(expr);
|
||||
(ExprNode(new_expr), side_effect)
|
||||
} else {
|
||||
(ExprNode(expr), None)
|
||||
}
|
||||
}
|
||||
FuncDefNode(func) => {
|
||||
let name = func.prototype.name.clone();
|
||||
let reduced_value = ReducedValue::Lambda(func.clone());
|
||||
let binding = Some(SideEffect::AddBinding(name, reduced_value));
|
||||
(ExprNode(Expression::Lambda(func)), binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO I probably want another Expression variant that holds a ReducedValue
|
||||
fn reduce_expr(&mut self, expression: Expression) -> Reduction<Expression> {
|
||||
match expression {
|
||||
Null => (Null, None),
|
||||
e @ StringLiteral(_) => (e, None),
|
||||
e @ Number(_) => (e, None),
|
||||
e @ Lambda(_) => (e, None),
|
||||
Variable(ref var) => {
|
||||
match self.lookup_binding(var).map(|x| x.into()) {
|
||||
None => (Null, None),
|
||||
Some(expr) => (expr, None),
|
||||
}
|
||||
}
|
||||
BinExp(op, mut left, mut right) => {
|
||||
if right.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(right.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
return (BinExp(op, left, right), side_effect);
|
||||
}
|
||||
|
||||
if let BinOp::Assign = op {
|
||||
return match *left {
|
||||
Variable(var) => {
|
||||
let reduced_value: ReducedValue = ReducedValue::from(*right);
|
||||
let binding = SideEffect::AddBinding(var, reduced_value);
|
||||
(Null, Some(binding))
|
||||
},
|
||||
_ => (Null, None)
|
||||
};
|
||||
}
|
||||
|
||||
if is_assignment(&op) {
|
||||
use self::BinOp::*;
|
||||
let new_op = match op {
|
||||
AddAssign => Add,
|
||||
SubAssign => Sub,
|
||||
MulAssign => Mul,
|
||||
DivAssign => Div,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let reduction =
|
||||
BinExp(BinOp::Assign,
|
||||
Box::new(*left.clone()),
|
||||
Box::new(BinExp(new_op, left, right))
|
||||
);
|
||||
|
||||
return (reduction, None);
|
||||
}
|
||||
|
||||
if left.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(left.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
(BinExp(op, left, right), side_effect)
|
||||
} else {
|
||||
(self.reduce_binop(op, *left, *right), None) //can assume both arguments are maximally reduced
|
||||
}
|
||||
}
|
||||
Call(callable, mut args) => {
|
||||
let mut f = true;
|
||||
for arg in args.iter_mut() {
|
||||
if arg.is_reducible() {
|
||||
take_mut::take(arg, |arg| self.reduce_expr(arg).0);
|
||||
f = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if f {
|
||||
self.reduce_call(callable, args)
|
||||
} else {
|
||||
(Call(callable, args), None)
|
||||
}
|
||||
}
|
||||
While(test, body) => {
|
||||
let mut block = VecDeque::from(body.clone());
|
||||
block.push_back(While(test.clone(), body.clone()));
|
||||
let reduction = Conditional(test, Box::new(Block(block)), None);
|
||||
(reduction, None)
|
||||
}
|
||||
Conditional(box test, then_block, else_block) => {
|
||||
if test.is_reducible() {
|
||||
let (new_test, new_effect) = self.reduce_expr(test);
|
||||
(Conditional(Box::new(new_test), then_block, else_block), new_effect)
|
||||
} else {
|
||||
if test.is_truthy() {
|
||||
(*then_block, None)
|
||||
} else {
|
||||
match else_block {
|
||||
Some(box expr) => (expr, None),
|
||||
None => (Null, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Block(mut exprs) => {
|
||||
let first = exprs.pop_front();
|
||||
match first {
|
||||
None => (Null, None),
|
||||
Some(expr) => {
|
||||
if exprs.len() == 0 {
|
||||
(expr, None)
|
||||
} else {
|
||||
if expr.is_reducible() {
|
||||
let (new, side_effect) = self.reduce_expr(expr);
|
||||
exprs.push_front(new);
|
||||
(Block(exprs), side_effect)
|
||||
} else {
|
||||
(Block(exprs), None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Index(mut expr, mut index_expr) => {
|
||||
if index_expr.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(index_expr.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
return (Index(expr, index_expr), side_effect)
|
||||
}
|
||||
|
||||
if expr.is_reducible() {
|
||||
let mut side_effect = None;
|
||||
take_mut::take(expr.as_mut(), |expr| { let (a, b) = self.reduce_expr(expr); side_effect = b; a});
|
||||
return (Index(expr, index_expr), side_effect);
|
||||
}
|
||||
|
||||
match (*expr, *index_expr) {
|
||||
(ListLiteral(list_items), Number(n)) => {
|
||||
let indexed_expr = get_indexer(n).and_then(|i| list_items.get(i));
|
||||
if let Some(e) = indexed_expr {
|
||||
(e.clone(), None)
|
||||
} else {
|
||||
(Null, None)
|
||||
}
|
||||
}
|
||||
(StructLiteral(items), StringLiteral(s)) => {
|
||||
for item in items {
|
||||
if s == item.0 {
|
||||
return (item.1.clone(), None); //TODO this is hella inefficient
|
||||
}
|
||||
}
|
||||
(Null, None)
|
||||
},
|
||||
_ => (Null, None)
|
||||
}
|
||||
}
|
||||
ListLiteral(mut exprs) => {
|
||||
let mut side_effect = None;
|
||||
for expr in exprs.iter_mut() {
|
||||
if expr.is_reducible() {
|
||||
take_mut::take(expr, |expr| {
|
||||
let (a, b) = self.reduce_expr(expr);
|
||||
side_effect = b;
|
||||
a
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
(ListLiteral(exprs), side_effect)
|
||||
},
|
||||
|
||||
StructLiteral(mut items) => {
|
||||
let mut side_effect = None;
|
||||
for pair in items.iter_mut() {
|
||||
if pair.1.is_reducible() {
|
||||
take_mut::take(pair, |pair| {
|
||||
let (name, expr) = pair;
|
||||
let (a, b) = self.reduce_expr(expr);
|
||||
side_effect = b;
|
||||
(name, a)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(StructLiteral(items), side_effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_binop(&mut self, op: BinOp, left: Expression, right: Expression) -> Expression {
|
||||
use self::BinOp::*;
|
||||
let truthy = Number(1.0);
|
||||
let falsy = Null;
|
||||
match (op, left, right) {
|
||||
(Add, Number(l), Number(r)) => Number(l + r),
|
||||
(Add, StringLiteral(s1), StringLiteral(s2)) => StringLiteral(Rc::new(format!("{}{}", *s1, *s2))),
|
||||
(Add, StringLiteral(s1), Number(r)) => StringLiteral(Rc::new(format!("{}{}", *s1, r))),
|
||||
(Add, Number(l), StringLiteral(s1)) => StringLiteral(Rc::new(format!("{}{}", l, *s1))),
|
||||
(Sub, Number(l), Number(r)) => Number(l - r),
|
||||
(Mul, Number(l), Number(r)) => Number(l * r),
|
||||
(Div, Number(l), Number(r)) if r != 0.0 => Number(l / r),
|
||||
(Mod, Number(l), Number(r)) => Number(l % r),
|
||||
(Less, Number(l), Number(r)) => if l < r { truthy } else { falsy },
|
||||
(LessEq, Number(l), Number(r)) => if l <= r { truthy } else { falsy },
|
||||
(Greater, Number(l), Number(r)) => if l > r { truthy } else { falsy },
|
||||
(GreaterEq, Number(l), Number(r)) => if l >= r { truthy } else { falsy },
|
||||
(Equal, Number(l), Number(r)) => if l == r { truthy } else { falsy },
|
||||
(Equal, Null, Null) => truthy,
|
||||
(Equal, StringLiteral(s1), StringLiteral(s2)) => if s1 == s2 { truthy } else { falsy },
|
||||
(Equal, _, _) => falsy,
|
||||
_ => falsy,
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_call(&mut self, callable: Callable, arguments: Vec<Expression>) -> Reduction<Expression> {
|
||||
if let Some(res) = handle_builtin(&callable, &arguments) {
|
||||
return res;
|
||||
}
|
||||
|
||||
let function = match callable {
|
||||
Callable::Lambda(func) => func.clone(),
|
||||
Callable::NamedFunction(name) => {
|
||||
match self.lookup_binding(&*name) {
|
||||
Some(ReducedValue::Lambda(func)) => func,
|
||||
_ => return (Null, None),
|
||||
}
|
||||
}
|
||||
};
|
||||
if function.prototype.parameters.len() != arguments.len() {
|
||||
return (Null, None);
|
||||
}
|
||||
|
||||
let mut evaluator = Evaluator::new(Some(self));
|
||||
for (binding, expr) in function.prototype.parameters.iter().zip(arguments.iter()) {
|
||||
evaluator.add_binding((**binding).clone(), expr.clone().into());
|
||||
}
|
||||
|
||||
let nodes = function.body.iter().map(|node| node.clone());
|
||||
let mut retval = ExprNode(Null);
|
||||
for n in nodes {
|
||||
retval = evaluator.reduction_loop(n);
|
||||
}
|
||||
|
||||
match retval {
|
||||
ExprNode(expr) => (expr, None),
|
||||
FuncDefNode(_) => panic!("This should never happen! A maximally-reduced node\
|
||||
should never be a function definition!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_builtin(callable: &Callable, arguments: &Vec<Expression>) -> Option<Reduction<Expression>> {
|
||||
let name: &str = match *callable {
|
||||
Callable::NamedFunction(ref name) => *&name,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
match name {
|
||||
"print" => {
|
||||
let mut s = String::new();
|
||||
for arg in arguments {
|
||||
s.push_str(&format!("{} ", arg));
|
||||
}
|
||||
return Some((Null, Some(SideEffect::Print(s))));
|
||||
},
|
||||
_ => None
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
#![feature(box_patterns)]
|
||||
|
||||
extern crate schala_repl;
|
||||
|
||||
mod tokenizer;
|
||||
mod parser;
|
||||
mod eval;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TokenError {
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl TokenError {
|
||||
pub fn new(msg: &str) -> TokenError {
|
||||
TokenError { msg: msg.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
pub use self::eval::Evaluator as MaaruEvaluator;
|
||||
|
||||
pub struct Maaru<'a> {
|
||||
evaluator: MaaruEvaluator<'a>
|
||||
}
|
||||
|
||||
impl<'a> Maaru<'a> {
|
||||
pub fn new() -> Maaru<'a> {
|
||||
Maaru {
|
||||
evaluator: MaaruEvaluator::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn execute_pipeline(&mut self, input: &str, options: &EvalOptions) -> Result<String, String> {
|
||||
let mut output = UnfinishedComputation::default();
|
||||
|
||||
let tokens = match tokenizer::tokenize(input) {
|
||||
Ok(tokens) => {
|
||||
if let Some(_) = options.debug_passes.get("tokens") {
|
||||
output.add_artifact(TraceArtifact::new("tokens", format!("{:?}", tokens)));
|
||||
}
|
||||
tokens
|
||||
},
|
||||
Err(err) => {
|
||||
return output.finish(Err(format!("Tokenization error: {:?}\n", err.msg)))
|
||||
}
|
||||
};
|
||||
|
||||
let ast = match parser::parse(&tokens, &[]) {
|
||||
Ok(ast) => {
|
||||
if let Some(_) = options.debug_passes.get("ast") {
|
||||
output.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast)));
|
||||
}
|
||||
ast
|
||||
},
|
||||
Err(err) => {
|
||||
return output.finish(Err(format!("Parse error: {:?}\n", err.msg)))
|
||||
}
|
||||
};
|
||||
let mut evaluation_output = String::new();
|
||||
for s in self.evaluator.run(ast).iter() {
|
||||
evaluation_output.push_str(s);
|
||||
}
|
||||
Ok(evaluation_output)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
impl<'a> ProgrammingLanguageInterface for Maaru<'a> {
|
||||
fn get_language_name(&self) -> String {
|
||||
"Maaru".to_string()
|
||||
}
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
format!("maaru")
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,755 @@
|
|||
use tokenizer::{Token, Kw, OpTok};
|
||||
use tokenizer::Token::*;
|
||||
|
||||
use std::fmt;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
use std::convert::From;
|
||||
|
||||
// Grammar
|
||||
// program := (statement delimiter ?)*
|
||||
// delimiter := Newline | Semicolon
|
||||
// statement := declaration | expression
|
||||
// declaration := FN prototype LCurlyBrace (statement)* RCurlyBrace
|
||||
// prototype := identifier LParen identlist RParen
|
||||
// identlist := Ident (Comma Ident)* | ε
|
||||
// exprlist := Expression (Comma Expression)* | ε
|
||||
// itemlist := Ident COLON Expression (Comma Ident COLON Expression)* | ε
|
||||
//
|
||||
// expression := postop_expression (op postop_expression)*
|
||||
// postop_expression := primary_expression postop
|
||||
// primary_expression := number_expr | String | identifier_expr | paren_expr | conditional_expr | while_expr | lambda_expr | list_expr | struct_expr
|
||||
// number_expr := (PLUS | MINUS ) number_expr | Number
|
||||
// identifier_expr := call_expression | Variable
|
||||
// list_expr := LSquareBracket exprlist RSquareBracket
|
||||
// struct_expr := LCurlyBrace itemlist RCurlyBrace
|
||||
// call_expression := Identifier LParen exprlist RParen
|
||||
// while_expr := WHILE primary_expression LCurlyBrace (expression delimiter)* RCurlyBrace
|
||||
// paren_expr := LParen expression RParen
|
||||
// conditional_expr := IF expression LCurlyBrace (expression delimiter)* RCurlyBrace (LCurlyBrace (expresion delimiter)* RCurlyBrace)?
|
||||
// lambda_expr := FN LParen identlist RParen LCurlyBrace (expression delimiter)* RCurlyBrace
|
||||
// lambda_call := | LParen exprlist RParen
|
||||
// postop := ε | LParen exprlist RParen | LBracket expression RBracket
|
||||
// op := '+', '-', etc.
|
||||
//
|
||||
|
||||
pub type AST = Vec<Statement>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Statement {
|
||||
ExprNode(Expression),
|
||||
FuncDefNode(Function),
|
||||
}
|
||||
|
||||
impl fmt::Display for Statement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Statement::*;
|
||||
match *self {
|
||||
ExprNode(ref expr) => write!(f, "{}", expr),
|
||||
FuncDefNode(_) => write!(f, "UNIMPLEMENTED"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
pub prototype: Prototype,
|
||||
pub body: Vec<Statement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Prototype {
|
||||
pub name: Rc<String>,
|
||||
pub parameters: Vec<Rc<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expression {
|
||||
Null,
|
||||
StringLiteral(Rc<String>),
|
||||
Number(f64),
|
||||
Variable(Rc<String>),
|
||||
BinExp(BinOp, Box<Expression>, Box<Expression>),
|
||||
Call(Callable, Vec<Expression>),
|
||||
Conditional(Box<Expression>, Box<Expression>, Option<Box<Expression>>),
|
||||
Lambda(Function),
|
||||
Block(VecDeque<Expression>),
|
||||
While(Box<Expression>, Vec<Expression>),
|
||||
Index(Box<Expression>, Box<Expression>),
|
||||
ListLiteral(VecDeque<Expression>),
|
||||
StructLiteral(VecDeque<(Rc<String>, Expression)>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Callable {
|
||||
NamedFunction(Rc<String>),
|
||||
Lambda(Function),
|
||||
}
|
||||
|
||||
//TODO this ought to be ReducedExpression
|
||||
impl fmt::Display for Expression {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::Expression::*;
|
||||
match *self {
|
||||
Null => write!(f, "null"),
|
||||
StringLiteral(ref s) => write!(f, "\"{}\"", s),
|
||||
Number(n) => write!(f, "{}", n),
|
||||
Lambda(Function { prototype: Prototype { ref name, ref parameters, .. }, .. }) => {
|
||||
write!(f, "«function: {}, {} arg(s)»", name, parameters.len())
|
||||
}
|
||||
ListLiteral(ref items) => {
|
||||
write!(f, "[ ")?;
|
||||
let mut iter = items.iter().peekable();
|
||||
while let Some(item) = iter.next() {
|
||||
write!(f, "{}", item)?;
|
||||
if let Some(_) = iter.peek() {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
write!(f, " ]")
|
||||
}
|
||||
StructLiteral(ref items) => {
|
||||
write!(f, "{} ", "{")?;
|
||||
let mut iter = items.iter().peekable();
|
||||
while let Some(pair) = iter.next() {
|
||||
write!(f, "{}: {}", pair.0, pair.1)?;
|
||||
if let Some(_) = iter.peek() {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
write!(f, "{} ", "}")
|
||||
}
|
||||
_ => write!(f, "UNIMPLEMENTED"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BinOp {
|
||||
Add,
|
||||
AddAssign,
|
||||
Sub,
|
||||
SubAssign,
|
||||
Mul,
|
||||
MulAssign,
|
||||
Div,
|
||||
DivAssign,
|
||||
Mod,
|
||||
Less,
|
||||
LessEq,
|
||||
Greater,
|
||||
GreaterEq,
|
||||
Equal,
|
||||
Assign,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl From<OpTok> for BinOp {
|
||||
fn from(token: OpTok) -> BinOp {
|
||||
use self::BinOp::*;
|
||||
match &token.0[..] {
|
||||
"+" => Add,
|
||||
"+=" => AddAssign,
|
||||
"-" => Sub,
|
||||
"-=" => SubAssign,
|
||||
"*" => Mul,
|
||||
"*=" => MulAssign,
|
||||
"/" => Div,
|
||||
"/=" => DivAssign,
|
||||
"%" => Mod,
|
||||
"<" => Less,
|
||||
"<=" => LessEq,
|
||||
">" => Greater,
|
||||
">=" => GreaterEq,
|
||||
"==" => Equal,
|
||||
"=" => Assign,
|
||||
op => Custom(op.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Precedence = u8;
|
||||
|
||||
// TODO make this support incomplete parses
|
||||
pub type ParseResult<T> = Result<T, ParseError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseError {
|
||||
pub msg: String,
|
||||
pub remaining_tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
fn result_from_str<T>(msg: &str) -> ParseResult<T> {
|
||||
Err(ParseError {
|
||||
msg: msg.to_string(),
|
||||
remaining_tokens: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn initialize(tokens: &[Token]) -> Parser {
|
||||
let mut tokens = tokens.to_vec();
|
||||
tokens.reverse();
|
||||
Parser { tokens: tokens }
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<Token> {
|
||||
self.tokens.last().map(|x| x.clone())
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Option<Token> {
|
||||
self.tokens.pop()
|
||||
}
|
||||
|
||||
fn get_precedence(&self, op: &OpTok) -> Precedence {
|
||||
match &op.0[..] {
|
||||
"+" => 10,
|
||||
"-" => 10,
|
||||
"*" => 20,
|
||||
"/" => 20,
|
||||
"%" => 20,
|
||||
"==" => 40,
|
||||
"=" | "+=" | "-=" | "*=" | "/=" => 1,
|
||||
">" | ">=" | "<" | "<=" => 30,
|
||||
_ => 255,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! expect {
|
||||
($self_:expr, $token:pat) => {
|
||||
match $self_.peek() {
|
||||
Some($token) => {$self_.next();},
|
||||
Some(x) => {
|
||||
let err = format!("Expected `{:?}` but got `{:?}`", stringify!($token), x);
|
||||
return ParseError::result_from_str(&err)
|
||||
},
|
||||
None => {
|
||||
let err = format!("Expected `{:?}` but got end of input", stringify!($token));
|
||||
return ParseError::result_from_str(&err) //TODO make this not require 2 stringifications
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! expect_identifier {
|
||||
($self_:expr) => {
|
||||
match $self_.peek() {
|
||||
Some(Identifier(s)) => {$self_.next(); s},
|
||||
Some(x) => return ParseError::result_from_str(&format!("Expected identifier, but got {:?}", x)),
|
||||
None => return ParseError::result_from_str("Expected identifier, but got end of input"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! skip_whitespace {
|
||||
($_self: expr) => {
|
||||
loop {
|
||||
match $_self.peek() {
|
||||
Some(ref t) if is_delimiter(t) => {
|
||||
$_self.next();
|
||||
continue;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! delimiter_block {
|
||||
($_self: expr, $try_parse: ident, $($break_pattern: pat)|+) => {
|
||||
{
|
||||
let mut acc = Vec::new();
|
||||
loop {
|
||||
match $_self.peek() {
|
||||
None => break,
|
||||
Some(ref t) if is_delimiter(t) => { $_self.next(); continue; },
|
||||
$($break_pattern)|+ => break,
|
||||
_ => {
|
||||
let a = try!($_self.$try_parse());
|
||||
acc.push(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_delimiter(token: &Token) -> bool {
|
||||
match *token {
|
||||
Newline | Semicolon => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn program(&mut self) -> ParseResult<AST> {
|
||||
let mut ast = Vec::new(); //TODO have this come from previously-parsed tree
|
||||
loop {
|
||||
let result: ParseResult<Statement> = match self.peek() {
|
||||
Some(ref t) if is_delimiter(t) => {
|
||||
self.next();
|
||||
continue;
|
||||
}
|
||||
Some(_) => self.statement(),
|
||||
None => break,
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(node) => ast.push(node),
|
||||
Err(mut err) => {
|
||||
err.remaining_tokens = self.tokens.clone();
|
||||
err.remaining_tokens.reverse();
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ast)
|
||||
}
|
||||
|
||||
fn statement(&mut self) -> ParseResult<Statement> {
|
||||
let node: Statement = match self.peek() {
|
||||
Some(Keyword(Kw::Fn)) => self.declaration()?,
|
||||
Some(_) => Statement::ExprNode(self.expression()?),
|
||||
None => panic!("Unexpected end of tokens"),
|
||||
};
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
fn declaration(&mut self) -> ParseResult<Statement> {
|
||||
expect!(self, Keyword(Kw::Fn));
|
||||
let prototype = self.prototype()?;
|
||||
expect!(self, LCurlyBrace);
|
||||
let body = self.body()?;
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(Statement::FuncDefNode(Function {
|
||||
prototype: prototype,
|
||||
body: body,
|
||||
}))
|
||||
}
|
||||
|
||||
fn prototype(&mut self) -> ParseResult<Prototype> {
|
||||
let name = expect_identifier!(self);
|
||||
expect!(self, LParen);
|
||||
let parameters = self.identlist()?;
|
||||
expect!(self, RParen);
|
||||
Ok(Prototype {
|
||||
name: name,
|
||||
parameters: parameters,
|
||||
})
|
||||
}
|
||||
|
||||
fn identlist(&mut self) -> ParseResult<Vec<Rc<String>>> {
|
||||
let mut args = Vec::new();
|
||||
while let Some(Identifier(name)) = self.peek() {
|
||||
args.push(name.clone());
|
||||
self.next();
|
||||
match self.peek() {
|
||||
Some(Comma) => {self.next();},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn exprlist(&mut self) -> ParseResult<Vec<Expression>> {
|
||||
let mut exprs = Vec::new();
|
||||
loop {
|
||||
if let Some(RParen) = self.peek() {
|
||||
break;
|
||||
}
|
||||
let exp = self.expression()?;
|
||||
exprs.push(exp);
|
||||
match self.peek() {
|
||||
Some(Comma) => {self.next();},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
fn itemlist(&mut self) -> ParseResult<VecDeque<(Rc<String>, Expression)>> {
|
||||
let mut items = VecDeque::new();
|
||||
loop {
|
||||
if let Some(RCurlyBrace) = self.peek() {
|
||||
break;
|
||||
}
|
||||
let name = expect_identifier!(self);
|
||||
expect!(self, Colon);
|
||||
let expr = self.expression()?;
|
||||
items.push_back((name, expr));
|
||||
match self.peek() {
|
||||
Some(Comma) => {self.next();},
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
fn body(&mut self) -> ParseResult<Vec<Statement>> {
|
||||
let statements = delimiter_block!(
|
||||
self,
|
||||
statement,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
fn expression(&mut self) -> ParseResult<Expression> {
|
||||
let lhs: Expression = self.postop_expression()?;
|
||||
self.precedence_expr(lhs, 0)
|
||||
}
|
||||
|
||||
fn precedence_expr(&mut self,
|
||||
mut lhs: Expression,
|
||||
min_precedence: u8)
|
||||
-> ParseResult<Expression> {
|
||||
while let Some(Operator(op)) = self.peek() {
|
||||
let precedence = self.get_precedence(&op);
|
||||
if precedence < min_precedence {
|
||||
break;
|
||||
}
|
||||
self.next();
|
||||
let mut rhs = self.postop_expression()?;
|
||||
while let Some(Operator(ref op)) = self.peek() {
|
||||
if self.get_precedence(op) > precedence {
|
||||
let new_prec = self.get_precedence(op);
|
||||
rhs = self.precedence_expr(rhs, new_prec)?;
|
||||
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lhs = Expression::BinExp(op.into(), Box::new(lhs), Box::new(rhs));
|
||||
}
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn postop_expression(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
let expr = self.primary_expression()?;
|
||||
let ret = match self.peek() {
|
||||
Some(LParen) => {
|
||||
let args = self.call_expression()?;
|
||||
match expr {
|
||||
Lambda(f) => Call(Callable::Lambda(f), args),
|
||||
e => {
|
||||
let err = format!("Expected lambda expression before a call, got {:?}", e);
|
||||
return ParseError::result_from_str(&err);
|
||||
},
|
||||
}
|
||||
},
|
||||
Some(LSquareBracket) => {
|
||||
expect!(self, LSquareBracket);
|
||||
let index_expr = self.expression()?;
|
||||
expect!(self, RSquareBracket);
|
||||
Index(Box::new(expr), Box::new(index_expr))
|
||||
},
|
||||
_ => {
|
||||
expr
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn primary_expression(&mut self) -> ParseResult<Expression> {
|
||||
Ok(match self.peek() {
|
||||
Some(Keyword(Kw::Null)) => {
|
||||
self.next();
|
||||
Expression::Null
|
||||
}
|
||||
Some(NumLiteral(_)) => self.number_expression()?,
|
||||
Some(Operator(OpTok(ref a))) if **a == "+" || **a == "-" => self.number_expression()?,
|
||||
Some(StrLiteral(s)) => {
|
||||
self.next();
|
||||
Expression::StringLiteral(s)
|
||||
}
|
||||
Some(Keyword(Kw::If)) => self.conditional_expr()?,
|
||||
Some(Keyword(Kw::While)) => self.while_expr()?,
|
||||
Some(Identifier(_)) => self.identifier_expr()?,
|
||||
Some(Token::LParen) => self.paren_expr()?,
|
||||
Some(Keyword(Kw::Fn)) => self.lambda_expr()?,
|
||||
Some(Token::LSquareBracket) => self.list_expr()?,
|
||||
Some(Token::LCurlyBrace) => self.struct_expr()?,
|
||||
Some(e) => {
|
||||
return ParseError::result_from_str(&format!("Expected primary expression, got \
|
||||
{:?}",
|
||||
e));
|
||||
}
|
||||
None => return ParseError::result_from_str("Expected primary expression received EoI"),
|
||||
})
|
||||
}
|
||||
|
||||
fn list_expr(&mut self) -> ParseResult<Expression> {
|
||||
expect!(self, LSquareBracket);
|
||||
let exprlist: Vec<Expression> = self.exprlist()?;
|
||||
expect!(self, RSquareBracket);
|
||||
|
||||
Ok(Expression::ListLiteral(VecDeque::from(exprlist)))
|
||||
}
|
||||
|
||||
fn struct_expr(&mut self) -> ParseResult<Expression> {
|
||||
expect!(self, LCurlyBrace);
|
||||
let struct_items = self.itemlist()?;
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(Expression::StructLiteral(struct_items))
|
||||
}
|
||||
|
||||
fn number_expression(&mut self) -> ParseResult<Expression> {
|
||||
let mut multiplier = 1;
|
||||
loop {
|
||||
match self.peek() {
|
||||
Some(NumLiteral(n)) => {
|
||||
self.next();
|
||||
return Ok(Expression::Number(n * multiplier as f64));
|
||||
}
|
||||
Some(Operator(OpTok(ref a))) if **a == "+" => {
|
||||
self.next();
|
||||
}
|
||||
Some(Operator(OpTok(ref a))) if **a == "-" => {
|
||||
multiplier *= -1;
|
||||
self.next();
|
||||
}
|
||||
Some(e) => {
|
||||
return ParseError::result_from_str(
|
||||
&format!("Expected +, - or number, got {:?}", e));
|
||||
}
|
||||
None => {
|
||||
return ParseError::result_from_str(
|
||||
&format!("Expected +, - or number, got EoI"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lambda_expr(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
expect!(self, Keyword(Kw::Fn));
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LParen);
|
||||
let parameters = self.identlist()?;
|
||||
expect!(self, RParen);
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LCurlyBrace);
|
||||
let body = self.body()?;
|
||||
expect!(self, RCurlyBrace);
|
||||
|
||||
let prototype = Prototype {
|
||||
name: Rc::new("a lambda yo!".to_string()),
|
||||
parameters: parameters,
|
||||
};
|
||||
|
||||
let function = Function {
|
||||
prototype: prototype,
|
||||
body: body,
|
||||
};
|
||||
|
||||
Ok(Lambda(function))
|
||||
}
|
||||
|
||||
fn while_expr(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
expect!(self, Keyword(Kw::While));
|
||||
let test = self.expression()?;
|
||||
expect!(self, LCurlyBrace);
|
||||
let body = delimiter_block!(
|
||||
self,
|
||||
expression,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(While(Box::new(test), body))
|
||||
}
|
||||
|
||||
fn conditional_expr(&mut self) -> ParseResult<Expression> {
|
||||
use self::Expression::*;
|
||||
expect!(self, Keyword(Kw::If));
|
||||
let test = self.expression()?;
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LCurlyBrace);
|
||||
skip_whitespace!(self);
|
||||
let then_block = delimiter_block!(
|
||||
self,
|
||||
expression,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
expect!(self, RCurlyBrace);
|
||||
skip_whitespace!(self);
|
||||
let else_block = if let Some(Keyword(Kw::Else)) = self.peek() {
|
||||
self.next();
|
||||
skip_whitespace!(self);
|
||||
expect!(self, LCurlyBrace);
|
||||
let else_exprs = delimiter_block!(
|
||||
self,
|
||||
expression,
|
||||
Some(RCurlyBrace)
|
||||
);
|
||||
Some(else_exprs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
expect!(self, RCurlyBrace);
|
||||
Ok(Conditional(Box::new(test),
|
||||
Box::new(Block(VecDeque::from(then_block))),
|
||||
else_block.map(|list| Box::new(Block(VecDeque::from(list))))))
|
||||
}
|
||||
|
||||
fn identifier_expr(&mut self) -> ParseResult<Expression> {
|
||||
let name = expect_identifier!(self);
|
||||
let expr = match self.peek() {
|
||||
Some(LParen) => {
|
||||
let args = self.call_expression()?;
|
||||
Expression::Call(Callable::NamedFunction(name), args)
|
||||
}
|
||||
__ => Expression::Variable(name),
|
||||
};
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn call_expression(&mut self) -> ParseResult<Vec<Expression>> {
|
||||
expect!(self, LParen);
|
||||
let args: Vec<Expression> = self.exprlist()?;
|
||||
expect!(self, RParen);
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn paren_expr(&mut self) -> ParseResult<Expression> {
|
||||
expect!(self, Token::LParen);
|
||||
let expr = self.expression()?;
|
||||
expect!(self, Token::RParen);
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(tokens: &[Token], _parsed_tree: &[Statement]) -> ParseResult<AST> {
|
||||
let mut parser = Parser::initialize(tokens);
|
||||
parser.program()
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use schala_lang::tokenizer;
|
||||
use super::*;
|
||||
use super::Statement::*;
|
||||
use super::Expression::*;
|
||||
|
||||
macro_rules! parsetest {
|
||||
($input:expr, $output:pat, $ifexpr:expr) => {
|
||||
{
|
||||
let tokens = tokenizer::tokenize($input).unwrap();
|
||||
let ast = parse(&tokens, &[]).unwrap();
|
||||
match &ast[..] {
|
||||
$output if $ifexpr => (),
|
||||
x => panic!("Error in parse test, got {:?} instead", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_parse_test() {
|
||||
use super::Function;
|
||||
parsetest!(
|
||||
"fn a() { 1 + 2 }",
|
||||
&[FuncDefNode(Function {prototype: Prototype { ref name, ref parameters }, ref body})],
|
||||
match &body[..] { &[ExprNode(BinExp(_, box Number(1.0), box Number(2.0)))] => true, _ => false }
|
||||
&& **name == "a" && match ¶meters[..] { &[] => true, _ => false }
|
||||
);
|
||||
|
||||
parsetest!(
|
||||
"fn a(x,y){ 1 + 2 }",
|
||||
&[FuncDefNode(Function {prototype: Prototype { ref name, ref parameters }, ref body})],
|
||||
match &body[..] { &[ExprNode(BinExp(_, box Number(1.0), box Number(2.0)))] => true, _ => false }
|
||||
&& **name == "a" && *parameters[0] == "x" && *parameters[1] == "y" && parameters.len() == 2
|
||||
);
|
||||
|
||||
let t3 = "fn (x) { x + 2 }";
|
||||
let tokens3 = tokenizer::tokenize(t3).unwrap();
|
||||
assert!(parse(&tokens3, &[]).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expression_parse_test() {
|
||||
parsetest!("a", &[ExprNode(Variable(ref s))], **s == "a");
|
||||
parsetest!("a + b",
|
||||
&[ExprNode(BinExp(BinOp::Add, box Variable(ref a), box Variable(ref b)))],
|
||||
**a == "a" && **b == "b");
|
||||
parsetest!("a + b * c",
|
||||
&[ExprNode(BinExp(BinOp::Add, box Variable(ref a), box BinExp(BinOp::Mul, box Variable(ref b), box Variable(ref c))))],
|
||||
**a == "a" && **b == "b" && **c == "c");
|
||||
parsetest!("a * b + c",
|
||||
&[ExprNode(BinExp(BinOp::Add, box BinExp(BinOp::Mul, box Variable(ref a), box Variable(ref b)), box Variable(ref c)))],
|
||||
**a == "a" && **b == "b" && **c == "c");
|
||||
parsetest!("(a + b) * c",
|
||||
&[ExprNode(BinExp(BinOp::Mul, box BinExp(BinOp::Add, box Variable(ref a), box Variable(ref b)), box Variable(ref c)))],
|
||||
**a == "a" && **b == "b" && **c == "c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda_parse_test() {
|
||||
use schala_lang::tokenizer;
|
||||
let t1 = "(fn(x) { x + 2 })";
|
||||
let tokens1 = tokenizer::tokenize(t1).unwrap();
|
||||
match parse(&tokens1, &[]).unwrap()[..] {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let t2 = "fn(x) { x + 2 }";
|
||||
let tokens2 = tokenizer::tokenize(t2).unwrap();
|
||||
assert!(parse(&tokens2, &[]).is_err());
|
||||
|
||||
let t3 = "(fn(x) { x + 10 })(20)";
|
||||
let tokens3 = tokenizer::tokenize(t3).unwrap();
|
||||
match parse(&tokens3, &[]).unwrap() {
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_parse_test() {
|
||||
use schala_lang::tokenizer;
|
||||
let t1 = "if null { 20 } else { 40 }";
|
||||
let tokens = tokenizer::tokenize(t1).unwrap();
|
||||
match parse(&tokens, &[]).unwrap()[..] {
|
||||
[ExprNode(Conditional(box Null, box Block(_), Some(box Block(_))))] => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
let t2 = r"
|
||||
if null {
|
||||
20
|
||||
} else {
|
||||
40
|
||||
}
|
||||
";
|
||||
let tokens2 = tokenizer::tokenize(t2).unwrap();
|
||||
match parse(&tokens2, &[]).unwrap()[..] {
|
||||
[ExprNode(Conditional(box Null, box Block(_), Some(box Block(_))))] => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
let t2 = r"
|
||||
if null {
|
||||
20 } else
|
||||
{
|
||||
40
|
||||
}
|
||||
";
|
||||
let tokens3 = tokenizer::tokenize(t2).unwrap();
|
||||
match parse(&tokens3, &[]).unwrap()[..] {
|
||||
[ExprNode(Conditional(box Null, box Block(_), Some(box Block(_))))] => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,208 @@
|
|||
extern crate itertools;
|
||||
|
||||
use std::iter::Peekable;
|
||||
use std::str::Chars;
|
||||
use self::itertools::Itertools;
|
||||
use std::rc::Rc;
|
||||
|
||||
use TokenError;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Token {
|
||||
Newline,
|
||||
Semicolon,
|
||||
LParen,
|
||||
RParen,
|
||||
LSquareBracket,
|
||||
RSquareBracket,
|
||||
LCurlyBrace,
|
||||
RCurlyBrace,
|
||||
Comma,
|
||||
Period,
|
||||
Colon,
|
||||
NumLiteral(f64),
|
||||
StrLiteral(Rc<String>),
|
||||
Identifier(Rc<String>),
|
||||
Operator(OpTok),
|
||||
Keyword(Kw),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct OpTok(pub Rc<String>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Kw {
|
||||
If,
|
||||
Else,
|
||||
While,
|
||||
Let,
|
||||
Fn,
|
||||
Null,
|
||||
}
|
||||
|
||||
pub type TokenizeResult = Result<Vec<Token>, TokenError>;
|
||||
|
||||
fn is_digit(c: &char) -> bool {
|
||||
c.is_digit(10)
|
||||
}
|
||||
|
||||
pub fn tokenize(input: &str) -> TokenizeResult {
|
||||
use self::Token::*;
|
||||
let mut tokens = Vec::new();
|
||||
let mut iter: Peekable<Chars> = input.chars().peekable();
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '#' {
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let cur_tok = match c {
|
||||
c if char::is_whitespace(c) && c != '\n' => continue,
|
||||
'\n' => Newline,
|
||||
';' => Semicolon,
|
||||
'(' => LParen,
|
||||
')' => RParen,
|
||||
':' => Colon,
|
||||
',' => Comma,
|
||||
'{' => LCurlyBrace,
|
||||
'}' => RCurlyBrace,
|
||||
'[' => LSquareBracket,
|
||||
']' => RSquareBracket,
|
||||
'"' => tokenize_str(&mut iter)?,
|
||||
c if !char::is_alphanumeric(c) => tokenize_operator(c, &mut iter)?,
|
||||
c @ '.' | c if is_digit(&c) => tokenize_number_or_period(c, &mut iter)?,
|
||||
c => tokenize_identifier(c, &mut iter)?,
|
||||
};
|
||||
tokens.push(cur_tok);
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
fn tokenize_str(iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
// TODO handle string escapes, interpolation
|
||||
match iter.next() {
|
||||
Some(x) if x == '"' => break,
|
||||
Some(x) => buffer.push(x),
|
||||
None => return Err(TokenError::new("Unclosed quote")),
|
||||
}
|
||||
}
|
||||
Ok(Token::StrLiteral(Rc::new(buffer)))
|
||||
}
|
||||
|
||||
fn tokenize_operator(c: char, iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| !char::is_alphanumeric(*x) && !char::is_whitespace(*x)));
|
||||
Ok(Token::Operator(OpTok(Rc::new(buffer))))
|
||||
}
|
||||
|
||||
fn tokenize_number_or_period(c: char, iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
if c == '.' && !iter.peek().map_or(false, is_digit) {
|
||||
return Ok(Token::Period);
|
||||
}
|
||||
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| is_digit(x) || *x == '.'));
|
||||
|
||||
match buffer.parse::<f64>() {
|
||||
Ok(f) => Ok(Token::NumLiteral(f)),
|
||||
Err(_) => Err(TokenError::new("Failed to parse digit")),
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize_identifier(c: char, iter: &mut Peekable<Chars>) -> Result<Token, TokenError> {
|
||||
fn ends_identifier(c: &char) -> bool {
|
||||
let c = *c;
|
||||
char::is_whitespace(c) || is_digit(&c) || c == ';' || c == '(' || c == ')' ||
|
||||
c == ',' || c == '.' || c == ',' || c == ':' || c == '[' || c == ']'
|
||||
}
|
||||
|
||||
use self::Token::*;
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| !ends_identifier(x)));
|
||||
|
||||
Ok(match &buffer[..] {
|
||||
"if" => Keyword(Kw::If),
|
||||
"else" => Keyword(Kw::Else),
|
||||
"while" => Keyword(Kw::While),
|
||||
"let" => Keyword(Kw::Let),
|
||||
"fn" => Keyword(Kw::Fn),
|
||||
"null" => Keyword(Kw::Null),
|
||||
b => Identifier(Rc::new(b.to_string())),
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::Token::*;
|
||||
|
||||
macro_rules! token_test {
|
||||
($input: expr, $output: pat, $ifexpr: expr) => {
|
||||
let tokens = tokenize($input).unwrap();
|
||||
match tokens[..] {
|
||||
$output if $ifexpr => (),
|
||||
_ => panic!("Actual output: {:?}", tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_tokeniziation_tests() {
|
||||
token_test!("let a = 3\n",
|
||||
[Keyword(Kw::Let), Identifier(ref a), Operator(OpTok(ref b)), NumLiteral(3.0), Newline],
|
||||
**a == "a" && **b == "=");
|
||||
|
||||
token_test!("2+1",
|
||||
[NumLiteral(2.0), Operator(OpTok(ref a)), NumLiteral(1.0)],
|
||||
**a == "+");
|
||||
|
||||
token_test!("2 + 1",
|
||||
[NumLiteral(2.0), Operator(OpTok(ref a)), NumLiteral(1.0)],
|
||||
**a == "+");
|
||||
|
||||
token_test!("2.3*49.2",
|
||||
[NumLiteral(2.3), Operator(OpTok(ref a)), NumLiteral(49.2)],
|
||||
**a == "*");
|
||||
|
||||
token_test!("a+3",
|
||||
[Identifier(ref a), NumLiteral(3.0)],
|
||||
**a == "a+");
|
||||
|
||||
assert!(tokenize("2.4.5").is_err());
|
||||
|
||||
token_test!("fn my_func(a) { a ? 3[1] }",
|
||||
[Keyword(Kw::Fn), Identifier(ref a), LParen, Identifier(ref b), RParen, LCurlyBrace, Identifier(ref c),
|
||||
Operator(OpTok(ref d)), NumLiteral(3.0), LSquareBracket, NumLiteral(1.0), RSquareBracket, RCurlyBrace],
|
||||
**a == "my_func" && **b == "a" && **c == "a" && **d == "?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_test() {
|
||||
token_test!("null + \"a string\"",
|
||||
[Keyword(Kw::Null), Operator(OpTok(ref a)), StrLiteral(ref b)],
|
||||
**a == "+" && **b == "a string");
|
||||
|
||||
token_test!("\"{?'q@?\"",
|
||||
[StrLiteral(ref a)],
|
||||
**a == "{?'q@?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operator_test() {
|
||||
token_test!("a *> b",
|
||||
[Identifier(ref a), Operator(OpTok(ref b)), Identifier(ref c)],
|
||||
**a == "a" && **b == "*>" && **c == "b");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "robo-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.5.8"
|
||||
take_mut = "0.1.3"
|
||||
llvm-sys = "*"
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
|
@ -0,0 +1,158 @@
|
|||
#![feature(box_patterns)]
|
||||
|
||||
extern crate itertools;
|
||||
extern crate schala_repl;
|
||||
|
||||
use itertools::Itertools;
|
||||
use schala_repl::{ProgrammingLanguageInterface, EvalOptions};
|
||||
|
||||
pub struct Robo {
|
||||
}
|
||||
|
||||
impl Robo {
|
||||
pub fn new() -> Robo {
|
||||
Robo { }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TokenError {
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
impl TokenError {
|
||||
pub fn new(msg: &str) -> TokenError {
|
||||
TokenError { msg: msg.to_string() }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Token {
|
||||
StrLiteral(String),
|
||||
Backtick,
|
||||
Newline,
|
||||
LParen,
|
||||
RParen,
|
||||
LBracket,
|
||||
RBracket,
|
||||
LBrace,
|
||||
RBrace,
|
||||
Period,
|
||||
Comma,
|
||||
Colon,
|
||||
Semicolon,
|
||||
SingleQuote,
|
||||
Identifier(String),
|
||||
Operator(String),
|
||||
NumLiteral(Number),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Number {
|
||||
IntegerRep(String),
|
||||
FloatRep(String)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub type AST = Vec<ASTNode>;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum ASTNode {
|
||||
FunctionDefinition(String, Expression),
|
||||
ImportStatement(String),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub enum Expression {
|
||||
|
||||
}
|
||||
|
||||
fn tokenize(input: &str) -> Result<Vec<Token>, TokenError> {
|
||||
use self::Token::*;
|
||||
let mut tokens = Vec::new();
|
||||
let mut iter = input.chars().peekable();
|
||||
while let Some(c) = iter.next() {
|
||||
if c == ';' {
|
||||
while let Some(c) = iter.next() {
|
||||
if c == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let cur_tok = match c {
|
||||
c if char::is_whitespace(c) && c != '\n' => continue,
|
||||
'\n' => Newline,
|
||||
'(' => LParen,
|
||||
')' => RParen,
|
||||
'[' => LBracket,
|
||||
']' => RBracket,
|
||||
'{' => LBrace,
|
||||
'}' => RBrace,
|
||||
',' => Comma,
|
||||
':' => Colon,
|
||||
';' => Semicolon,
|
||||
'.' => Period,
|
||||
'`' => Backtick,
|
||||
'\'' => SingleQuote,
|
||||
'"' => {
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
match iter.next() {
|
||||
Some(x) if x == '"' => break,
|
||||
Some(x) => buffer.push(x),
|
||||
None => return Err(TokenError::new("Unclosed quote")),
|
||||
}
|
||||
}
|
||||
StrLiteral(buffer)
|
||||
}
|
||||
c if c.is_digit(10) => {
|
||||
let mut integer = true;
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| x.is_digit(10)));
|
||||
if let Some(&'.') = iter.peek() {
|
||||
buffer.push(iter.next().unwrap());
|
||||
integer = false;
|
||||
}
|
||||
buffer.extend(iter.peeking_take_while(|x| x.is_digit(10)));
|
||||
let inner = if integer {
|
||||
Number::IntegerRep(buffer)
|
||||
} else {
|
||||
Number::FloatRep(buffer)
|
||||
};
|
||||
NumLiteral(inner)
|
||||
},
|
||||
c if char::is_alphanumeric(c) => {
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| char::is_alphanumeric(*x)));
|
||||
Identifier(buffer)
|
||||
},
|
||||
c => {
|
||||
let mut buffer = String::new();
|
||||
buffer.push(c);
|
||||
buffer.extend(iter.peeking_take_while(|x| !char::is_whitespace(*x)));
|
||||
Operator(buffer)
|
||||
}
|
||||
};
|
||||
tokens.push(cur_tok);
|
||||
}
|
||||
|
||||
Ok(tokens)
|
||||
}
|
||||
|
||||
impl ProgrammingLanguageInterface for Robo {
|
||||
fn get_language_name(&self) -> String {
|
||||
"Robo".to_string()
|
||||
}
|
||||
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
format!("robo")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "rukka-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.5.8"
|
||||
take_mut = "0.1.3"
|
||||
llvm-sys = "*"
|
||||
|
||||
schala-repl = { path = "../schala-repl" }
|
|
@ -0,0 +1,417 @@
|
|||
#![feature(box_patterns)]
|
||||
|
||||
extern crate itertools;
|
||||
extern crate schala_repl;
|
||||
|
||||
use itertools::Itertools;
|
||||
use schala_repl::{ProgrammingLanguageInterface, EvalOptions};
|
||||
use std::iter::Peekable;
|
||||
use std::vec::IntoIter;
|
||||
use std::str::Chars;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct EvaluatorState {
|
||||
binding_stack: Vec<HashMap<String, Sexp>>
|
||||
}
|
||||
|
||||
impl EvaluatorState {
|
||||
fn new() -> EvaluatorState {
|
||||
use self::Sexp::Primitive;
|
||||
use self::PrimitiveFn::*;
|
||||
let mut default_map = HashMap::new();
|
||||
default_map.insert(format!("+"), Primitive(Plus));
|
||||
default_map.insert(format!("-"), Primitive(Minus));
|
||||
default_map.insert(format!("*"), Primitive(Mult));
|
||||
default_map.insert(format!("/"), Primitive(Div));
|
||||
default_map.insert(format!("%"), Primitive(Mod));
|
||||
default_map.insert(format!(">"), Primitive(Greater));
|
||||
default_map.insert(format!("<"), Primitive(Less));
|
||||
default_map.insert(format!("<="), Primitive(LessThanOrEqual));
|
||||
default_map.insert(format!(">="), Primitive(GreaterThanOrEqual));
|
||||
default_map.insert(format!("display"), Primitive(Display));
|
||||
|
||||
EvaluatorState {
|
||||
binding_stack: vec![default_map],
|
||||
}
|
||||
}
|
||||
fn set_var(&mut self, var: String, value: Sexp) {
|
||||
let binding = self.binding_stack.last_mut().unwrap();
|
||||
binding.insert(var, value);
|
||||
}
|
||||
fn get_var(&self, var: &str) -> Option<&Sexp> {
|
||||
for bindings in self.binding_stack.iter().rev() {
|
||||
match bindings.get(var) {
|
||||
Some(x) => return Some(x),
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn push_env(&mut self) {
|
||||
self.binding_stack.push(HashMap::new());
|
||||
}
|
||||
fn pop_env(&mut self) {
|
||||
self.binding_stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Rukka {
|
||||
state: EvaluatorState
|
||||
}
|
||||
|
||||
impl Rukka {
|
||||
pub fn new() -> Rukka { Rukka { state: EvaluatorState::new() } }
|
||||
}
|
||||
|
||||
impl ProgrammingLanguageInterface for Rukka {
|
||||
fn get_language_name(&self) -> String {
|
||||
"Rukka".to_string()
|
||||
}
|
||||
|
||||
fn get_source_file_suffix(&self) -> String {
|
||||
format!("rukka")
|
||||
}
|
||||
}
|
||||
|
||||
impl EvaluatorState {
|
||||
fn eval(&mut self, expr: Sexp) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
Ok(match expr {
|
||||
SymbolAtom(ref sym) => match self.get_var(sym) {
|
||||
Some(ref sexp) => {
|
||||
let q: &Sexp = sexp; //WTF? if I delete this line, the copy doesn't work??
|
||||
q.clone() //TODO make this not involve a clone
|
||||
},
|
||||
None => return Err(format!("Variable {} not bound", sym)),
|
||||
},
|
||||
expr @ Primitive(_) => expr,
|
||||
expr @ FnLiteral { .. } => expr,
|
||||
expr @ StringAtom(_) => expr,
|
||||
expr @ NumberAtom(_) => expr,
|
||||
expr @ BoolAtom(_) => expr,
|
||||
Cons(box operator, box operands) => match operator {
|
||||
SymbolAtom(ref sym) if match &sym[..] {
|
||||
"quote" | "eq?" | "cons" | "car" | "cdr" | "atom?" | "define" | "lambda" | "if" | "cond" => true, _ => false
|
||||
} => self.eval_special_form(sym, operands)?,
|
||||
_ => {
|
||||
let evaled = self.eval(operator)?;
|
||||
self.apply(evaled, operands)?
|
||||
}
|
||||
},
|
||||
Nil => Nil,
|
||||
})
|
||||
}
|
||||
fn eval_special_form(&mut self, form: &str, operands: Sexp) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
Ok(match form {
|
||||
"quote" => match operands {
|
||||
Cons(box quoted, box Nil) => quoted,
|
||||
_ => return Err(format!("Bad syntax in quote")),
|
||||
},
|
||||
"eq?" => match operands {//TODO make correct
|
||||
Cons(box lhs, box Cons(box rhs, _)) => BoolAtom(lhs == rhs),
|
||||
_ => BoolAtom(true),
|
||||
},
|
||||
"cons" => match operands {
|
||||
Cons(box cadr, box Cons(box caddr, box Nil)) => {
|
||||
let newl = self.eval(cadr)?;
|
||||
let newr = self.eval(caddr)?;
|
||||
Cons(Box::new(newl), Box::new(newr))
|
||||
},
|
||||
_ => return Err(format!("Bad arguments for cons")),
|
||||
},
|
||||
"car" => match operands {
|
||||
Cons(box car, _) => car,
|
||||
_ => return Err(format!("called car with a non-pair argument")),
|
||||
},
|
||||
"cdr" => match operands {
|
||||
Cons(_, box cdr) => cdr,
|
||||
_ => return Err(format!("called cdr with a non-pair argument")),
|
||||
},
|
||||
"atom?" => match operands {
|
||||
Cons(_, _) => BoolAtom(false),
|
||||
_ => BoolAtom(true),
|
||||
},
|
||||
"define" => match operands {
|
||||
Cons(box SymbolAtom(sym), box Cons(box expr, box Nil)) => {
|
||||
let evaluated = self.eval(expr)?;
|
||||
self.set_var(sym, evaluated);
|
||||
Nil
|
||||
},
|
||||
_ => return Err(format!("Bad assignment")),
|
||||
}
|
||||
"lambda" => match operands {
|
||||
Cons(box mut paramlist, box Cons(box formalexp, box Nil)) => {
|
||||
let mut formal_params = vec![];
|
||||
{
|
||||
let mut ptr = ¶mlist;
|
||||
loop {
|
||||
match ptr {
|
||||
&Cons(ref arg, ref rest) => {
|
||||
if let SymbolAtom(ref sym) = **arg {
|
||||
formal_params.push(sym.clone());
|
||||
ptr = rest;
|
||||
} else {
|
||||
return Err(format!("Bad lambda format"));
|
||||
}
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
FnLiteral {
|
||||
formal_params,
|
||||
body: Box::new(formalexp)
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Bad lambda expression")),
|
||||
},
|
||||
"if" => match operands {
|
||||
Cons(box test, box body) => {
|
||||
let truth_value = test.truthy();
|
||||
match (truth_value, body) {
|
||||
(true, Cons(box consequent, _)) => consequent,
|
||||
(false, Cons(_, box Cons(box alternative, _))) => alternative,
|
||||
_ => return Err(format!("Bad if expression"))
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Bad if expression"))
|
||||
},
|
||||
s => return Err(format!("Non-existent special form {}; this should never happen", s)),
|
||||
})
|
||||
}
|
||||
|
||||
fn apply(&mut self, function: Sexp, operands: Sexp) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
match function {
|
||||
FnLiteral { formal_params, body } => {
|
||||
self.push_env();
|
||||
|
||||
let mut cur = operands;
|
||||
for param in formal_params {
|
||||
match cur {
|
||||
Cons(box arg, box rest) => {
|
||||
cur = rest;
|
||||
self.set_var(param, arg);
|
||||
},
|
||||
_ => return Err(format!("Bad argument for function application")),
|
||||
}
|
||||
}
|
||||
let result = self.eval(*body);
|
||||
self.pop_env();
|
||||
result
|
||||
},
|
||||
Primitive(prim) => {
|
||||
let mut evaled_operands = Vec::new();
|
||||
let mut cur_operand = operands;
|
||||
loop {
|
||||
match cur_operand {
|
||||
Nil => break,
|
||||
Cons(box l, box rest) => {
|
||||
evaled_operands.push(self.eval(l)?);
|
||||
cur_operand = rest;
|
||||
},
|
||||
_ => return Err(format!("Bad operands list"))
|
||||
}
|
||||
}
|
||||
|
||||
prim.apply(evaled_operands)
|
||||
}
|
||||
_ => return Err(format!("Bad type to apply")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read(input: &str) -> Result<Vec<Sexp>, String> {
|
||||
let mut chars: Peekable<Chars> = input.chars().peekable();
|
||||
let mut tokens = tokenize(&mut chars).into_iter().peekable();
|
||||
let mut sexps = Vec::new();
|
||||
while let Some(_) = tokens.peek() {
|
||||
sexps.push(parse(&mut tokens)?);
|
||||
}
|
||||
Ok(sexps)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Token {
|
||||
LParen,
|
||||
RParen,
|
||||
Quote,
|
||||
Word(String),
|
||||
StringLiteral(String),
|
||||
NumLiteral(u64),
|
||||
}
|
||||
|
||||
//TODO make this notion of Eq more sophisticated
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum Sexp {
|
||||
SymbolAtom(String),
|
||||
StringAtom(String),
|
||||
NumberAtom(u64),
|
||||
BoolAtom(bool),
|
||||
Cons(Box<Sexp>, Box<Sexp>),
|
||||
Nil,
|
||||
FnLiteral {
|
||||
formal_params: Vec<String>,
|
||||
body: Box<Sexp>
|
||||
},
|
||||
Primitive(PrimitiveFn)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum PrimitiveFn {
|
||||
Plus, Minus, Mult, Div, Mod, Greater, Less, GreaterThanOrEqual, LessThanOrEqual, Display
|
||||
}
|
||||
|
||||
impl PrimitiveFn {
|
||||
fn apply(&self, evaled_operands: Vec<Sexp>) -> Result<Sexp, String> {
|
||||
use self::Sexp::*;
|
||||
use self::PrimitiveFn::*;
|
||||
let op = self.clone();
|
||||
Ok(match op {
|
||||
Display => {
|
||||
for arg in evaled_operands {
|
||||
print!("{}\n", arg.print());
|
||||
}
|
||||
Nil
|
||||
},
|
||||
Plus | Mult => {
|
||||
let mut result = match op { Plus => 0, Mult => 1, _ => unreachable!() };
|
||||
for arg in evaled_operands {
|
||||
if let NumberAtom(n) = arg {
|
||||
if let Plus = op {
|
||||
result += n;
|
||||
} else if let Mult = op {
|
||||
result *= n;
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Bad operand: {:?}", arg));
|
||||
}
|
||||
}
|
||||
NumberAtom(result)
|
||||
},
|
||||
op => return Err(format!("Primitive op {:?} not implemented", op)),
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl Sexp {
|
||||
fn print(&self) -> String {
|
||||
use self::Sexp::*;
|
||||
match self {
|
||||
&BoolAtom(true) => format!("#t"),
|
||||
&BoolAtom(false) => format!("#f"),
|
||||
&SymbolAtom(ref sym) => format!("{}", sym),
|
||||
&StringAtom(ref s) => format!("\"{}\"", s),
|
||||
&NumberAtom(ref n) => format!("{}", n),
|
||||
&Cons(ref car, ref cdr) => format!("({} . {})", car.print(), cdr.print()),
|
||||
&Nil => format!("()"),
|
||||
&FnLiteral { ref formal_params, .. } => format!("<lambda {:?}>", formal_params),
|
||||
&Primitive(ref sym) => format!("<primitive \"{:?}\">", sym),
|
||||
}
|
||||
}
|
||||
|
||||
fn truthy(&self) -> bool {
|
||||
use self::Sexp::*;
|
||||
match self {
|
||||
&BoolAtom(false) => false,
|
||||
_ => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenize(input: &mut Peekable<Chars>) -> Vec<Token> {
|
||||
use self::Token::*;
|
||||
let mut tokens = Vec::new();
|
||||
loop {
|
||||
match input.next() {
|
||||
None => break,
|
||||
Some('(') => tokens.push(LParen),
|
||||
Some(')') => tokens.push(RParen),
|
||||
Some('\'') => tokens.push(Quote),
|
||||
Some(c) if c.is_whitespace() => continue,
|
||||
Some(c) if c.is_numeric() => {
|
||||
let tok: String = input.peeking_take_while(|next| next.is_numeric()).collect();
|
||||
let n: u64 = format!("{}{}", c, tok).parse().unwrap();
|
||||
tokens.push(NumLiteral(n));
|
||||
},
|
||||
Some('"') => {
|
||||
let string: String = input.scan(false, |escape, cur_char| {
|
||||
let seen_escape = *escape;
|
||||
*escape = cur_char == '\\' && !seen_escape;
|
||||
match (cur_char, seen_escape) {
|
||||
('"', false) => None,
|
||||
('\\', false) => Some(None),
|
||||
(c, _) => Some(Some(c))
|
||||
}
|
||||
}).filter_map(|x| x).collect();
|
||||
tokens.push(StringLiteral(string));
|
||||
}
|
||||
Some(c) => {
|
||||
let sym: String = input.peeking_take_while(|next| {
|
||||
match *next {
|
||||
'(' | ')' => false,
|
||||
c if c.is_whitespace() => false,
|
||||
_ => true
|
||||
}
|
||||
}).collect();
|
||||
tokens.push(Word(format!("{}{}", c, sym)));
|
||||
}
|
||||
}
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
fn parse(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Sexp, String> {
|
||||
use self::Token::*;
|
||||
use self::Sexp::*;
|
||||
match tokens.next() {
|
||||
Some(Word(ref s)) if s == "#f" => Ok(BoolAtom(false)),
|
||||
Some(Word(ref s)) if s == "#t" => Ok(BoolAtom(true)),
|
||||
Some(Word(s)) => Ok(SymbolAtom(s)),
|
||||
Some(StringLiteral(s)) => Ok(StringAtom(s)),
|
||||
Some(LParen) => parse_sexp(tokens),
|
||||
Some(RParen) => Err(format!("Unexpected ')'")),
|
||||
Some(Quote) => {
|
||||
let quoted = parse(tokens)?;
|
||||
Ok(Cons(Box::new(SymbolAtom(format!("quote"))), Box::new(Cons(Box::new(quoted), Box::new(Nil)))))
|
||||
},
|
||||
Some(NumLiteral(n)) => Ok(NumberAtom(n)),
|
||||
None => Err(format!("Unexpected end of input")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sexp(tokens: &mut Peekable<IntoIter<Token>>) -> Result<Sexp, String> {
|
||||
use self::Token::*;
|
||||
use self::Sexp::*;
|
||||
let mut cell = Nil;
|
||||
{
|
||||
let mut cell_ptr = &mut cell;
|
||||
loop {
|
||||
match tokens.peek() {
|
||||
None => return Err(format!("Unexpected end of input")),
|
||||
Some(&RParen) => {
|
||||
tokens.next();
|
||||
break;
|
||||
},
|
||||
_ => {
|
||||
let current = parse(tokens)?;
|
||||
let new_cdr = Cons(Box::new(current), Box::new(Nil));
|
||||
match cell_ptr {
|
||||
&mut Cons(_, ref mut cdr) => **cdr = new_cdr,
|
||||
&mut Nil => *cell_ptr = new_cdr,
|
||||
_ => unreachable!()
|
||||
};
|
||||
|
||||
let old_ptr = cell_ptr;
|
||||
let new_ptr: &mut Sexp = match old_ptr { &mut Cons(_, ref mut cdr) => cdr, _ => unreachable!() } as &mut Sexp;
|
||||
cell_ptr = new_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(cell)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "schala-lang-codegen"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "0.15.12", features = ["full", "extra-traits", "fold"] }
|
||||
quote = "0.6.8"
|
|
@ -0,0 +1,50 @@
|
|||
#![feature(box_patterns)]
|
||||
#![recursion_limit="128"]
|
||||
extern crate proc_macro;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
#[macro_use]
|
||||
extern crate syn;
|
||||
|
||||
use self::proc_macro::TokenStream;
|
||||
use self::syn::fold::Fold;
|
||||
|
||||
struct RecursiveDescentFn {
|
||||
}
|
||||
|
||||
impl Fold for RecursiveDescentFn {
|
||||
fn fold_item_fn(&mut self, mut i: syn::ItemFn) -> syn::ItemFn {
|
||||
let box block = i.block;
|
||||
let ref ident = i.ident;
|
||||
|
||||
let new_block: syn::Block = parse_quote! {
|
||||
{
|
||||
let next_token_before_parse = self.token_handler.peek();
|
||||
let record = ParseRecord {
|
||||
production_name: stringify!(#ident).to_string(),
|
||||
next_token: format!("{}", next_token_before_parse.to_string_with_metadata()),
|
||||
level: self.parse_level,
|
||||
};
|
||||
self.parse_level += 1;
|
||||
self.parse_record.push(record);
|
||||
let result = { #block };
|
||||
|
||||
if self.parse_level != 0 {
|
||||
self.parse_level -= 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
};
|
||||
i.block = Box::new(new_block);
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn recursive_descent_method(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||
|
||||
let input: syn::ItemFn = parse_macro_input!(item as syn::ItemFn);
|
||||
let mut folder = RecursiveDescentFn {};
|
||||
let output = folder.fold_item_fn(input);
|
||||
TokenStream::from(quote!(#output))
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "schala-lang"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.8.0"
|
||||
take_mut = "0.2.2"
|
||||
maplit = "1.0.1"
|
||||
lazy_static = "1.3.0"
|
||||
failure = "0.1.5"
|
||||
ena = "0.11.0"
|
||||
stopwatch = "0.0.7"
|
||||
derivative = "1.0.3"
|
||||
|
||||
schala-lang-codegen = { path = "../codegen" }
|
||||
schala-repl = { path = "../../schala-repl" }
|
|
@ -0,0 +1,310 @@
|
|||
use std::rc::Rc;
|
||||
use std::convert::From;
|
||||
|
||||
use crate::derivative::Derivative;
|
||||
use crate::symbol_table::FullyQualifiedSymbolName;
|
||||
|
||||
mod operators;
|
||||
pub use operators::*;
|
||||
|
||||
/// An abstract identifier for an AST node
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||
pub struct ItemId {
|
||||
idx: u32,
|
||||
}
|
||||
|
||||
impl ItemId {
|
||||
fn new(n: u32) -> ItemId {
|
||||
ItemId { idx: n }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ItemIdStore {
|
||||
last_idx: u32
|
||||
}
|
||||
|
||||
impl ItemIdStore {
|
||||
pub fn new() -> ItemIdStore {
|
||||
ItemIdStore { last_idx: 0 }
|
||||
}
|
||||
/// Always returns an ItemId with internal value zero
|
||||
#[cfg(test)]
|
||||
pub fn new_id() -> ItemId {
|
||||
ItemId { idx: 0 }
|
||||
}
|
||||
|
||||
/// This limits the size of the AST to 2^32 tree elements
|
||||
pub fn fresh(&mut self) -> ItemId {
|
||||
let idx = self.last_idx;
|
||||
self.last_idx += 1;
|
||||
ItemId::new(idx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Meta<T> {
|
||||
pub n: T,
|
||||
pub fqsn: Option<FullyQualifiedSymbolName>
|
||||
}
|
||||
|
||||
impl<T> Meta<T> {
|
||||
pub fn new(n: T) -> Meta<T> {
|
||||
Meta { n,
|
||||
fqsn: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node(&self) -> &T {
|
||||
&self.n
|
||||
}
|
||||
|
||||
pub fn mut_node(&mut self) -> &mut T {
|
||||
&mut self.n
|
||||
}
|
||||
}
|
||||
|
||||
//TODO this PartialEq is here to make tests work - find a way to make it not necessary
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
struct SourceMap {
|
||||
}
|
||||
|
||||
impl From<Expression> for Meta<Expression> {
|
||||
fn from(expr: Expression) -> Meta<Expression> {
|
||||
Meta::new(expr)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Derivative, Debug)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct AST {
|
||||
#[derivative(PartialEq="ignore")]
|
||||
pub id: ItemId,
|
||||
pub statements: Vec<Statement>
|
||||
}
|
||||
|
||||
#[derive(Derivative, Debug, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct Statement {
|
||||
#[derivative(PartialEq="ignore")]
|
||||
pub id: ItemId,
|
||||
pub kind: StatementKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum StatementKind {
|
||||
Expression(Expression),
|
||||
Declaration(Declaration), //TODO Declaration should also be Meta-wrapped; only Expression and Declaration are Meta-wrapped maybe?
|
||||
}
|
||||
|
||||
pub type Block = Vec<Statement>;
|
||||
pub type ParamName = Rc<String>;
|
||||
|
||||
#[derive(Debug, Derivative, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct QualifiedName {
|
||||
#[derivative(PartialEq="ignore")]
|
||||
pub id: ItemId,
|
||||
pub components: Vec<Rc<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct FormalParam {
|
||||
pub name: ParamName,
|
||||
pub default: Option<Expression>,
|
||||
pub anno: Option<TypeIdentifier>
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Declaration {
|
||||
FuncSig(Signature),
|
||||
FuncDecl(Signature, Block),
|
||||
TypeDecl {
|
||||
name: TypeSingletonName,
|
||||
body: TypeBody,
|
||||
mutable: bool
|
||||
},
|
||||
TypeAlias(Rc<String>, Rc<String>), //should have TypeSingletonName in it, or maybe just String, not sure
|
||||
Binding {
|
||||
name: Rc<String>,
|
||||
constant: bool,
|
||||
type_anno: Option<TypeIdentifier>,
|
||||
expr: Expression,
|
||||
},
|
||||
Impl {
|
||||
type_name: TypeIdentifier,
|
||||
interface_name: Option<TypeSingletonName>,
|
||||
block: Vec<Declaration>,
|
||||
},
|
||||
Interface {
|
||||
name: Rc<String>,
|
||||
signatures: Vec<Signature>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Signature {
|
||||
pub name: Rc<String>,
|
||||
pub operator: bool,
|
||||
pub params: Vec<FormalParam>,
|
||||
pub type_anno: Option<TypeIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TypeBody(pub Vec<Variant>);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Variant {
|
||||
UnitStruct(Rc<String>),
|
||||
TupleStruct(Rc<String>, Vec<TypeIdentifier>),
|
||||
Record {
|
||||
name: Rc<String>,
|
||||
members: Vec<(Rc<String>, TypeIdentifier)>,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Derivative, Clone)]
|
||||
#[derivative(PartialEq)]
|
||||
pub struct Expression {
|
||||
#[derivative(PartialEq="ignore")]
|
||||
pub id: ItemId,
|
||||
pub kind: ExpressionKind,
|
||||
pub type_anno: Option<TypeIdentifier>
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
pub fn new(id: ItemId, kind: ExpressionKind) -> Expression {
|
||||
Expression { id, kind, type_anno: None }
|
||||
}
|
||||
|
||||
pub fn with_anno(id: ItemId, kind: ExpressionKind, type_anno: TypeIdentifier) -> Expression {
|
||||
Expression { id, kind, type_anno: Some(type_anno) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TypeIdentifier {
|
||||
Tuple(Vec<TypeIdentifier>),
|
||||
Singleton(TypeSingletonName)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct TypeSingletonName {
|
||||
pub name: Rc<String>,
|
||||
pub params: Vec<TypeIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ExpressionKind {
|
||||
NatLiteral(u64),
|
||||
FloatLiteral(f64),
|
||||
StringLiteral(Rc<String>),
|
||||
BoolLiteral(bool),
|
||||
BinExp(BinOp, Box<Expression>, Box<Expression>),
|
||||
PrefixExp(PrefixOp, Box<Expression>),
|
||||
TupleLiteral(Vec<Expression>),
|
||||
Value(QualifiedName),
|
||||
NamedStruct {
|
||||
name: QualifiedName,
|
||||
fields: Vec<(Rc<String>, Expression)>,
|
||||
},
|
||||
Call {
|
||||
f: Box<Expression>,
|
||||
arguments: Vec<InvocationArgument>,
|
||||
},
|
||||
Index {
|
||||
indexee: Box<Expression>,
|
||||
indexers: Vec<Expression>,
|
||||
},
|
||||
IfExpression {
|
||||
discriminator: Box<Discriminator>,
|
||||
body: Box<IfExpressionBody>,
|
||||
},
|
||||
WhileExpression {
|
||||
condition: Option<Box<Expression>>,
|
||||
body: Block,
|
||||
},
|
||||
ForExpression {
|
||||
enumerators: Vec<Enumerator>,
|
||||
body: Box<ForBody>,
|
||||
},
|
||||
Lambda {
|
||||
params: Vec<FormalParam>,
|
||||
type_anno: Option<TypeIdentifier>,
|
||||
body: Block,
|
||||
},
|
||||
ListLiteral(Vec<Expression>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum InvocationArgument {
|
||||
Positional(Expression),
|
||||
Keyword {
|
||||
name: Rc<String>,
|
||||
expr: Expression,
|
||||
},
|
||||
Ignored
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Discriminator {
|
||||
Simple(Expression),
|
||||
BinOp(Expression, BinOp)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum IfExpressionBody {
|
||||
SimpleConditional(Block, Option<Block>),
|
||||
SimplePatternMatch(Pattern, Block, Option<Block>),
|
||||
GuardList(Vec<GuardArm>)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct GuardArm {
|
||||
pub guard: Guard,
|
||||
pub body: Block,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Guard {
|
||||
Pat(Pattern),
|
||||
HalfExpr(HalfExpr)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct HalfExpr {
|
||||
pub op: Option<BinOp>,
|
||||
pub expr: ExpressionKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Pattern {
|
||||
Ignored,
|
||||
TuplePattern(Vec<Pattern>),
|
||||
Literal(PatternLiteral),
|
||||
TupleStruct(QualifiedName, Vec<Pattern>),
|
||||
Record(QualifiedName, Vec<(Rc<String>, Pattern)>),
|
||||
VarOrName(QualifiedName),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PatternLiteral {
|
||||
NumPattern {
|
||||
neg: bool,
|
||||
num: ExpressionKind,
|
||||
},
|
||||
StringPattern(Rc<String>),
|
||||
BoolPattern(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Enumerator {
|
||||
pub id: Rc<String>,
|
||||
pub generator: Expression,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum ForBody {
|
||||
MonadicReturn(Expression),
|
||||
StatementBlock(Block),
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::tokenizing::TokenKind;
|
||||
use crate::builtin::Builtin;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct PrefixOp {
|
||||
sigil: Rc<String>,
|
||||
pub builtin: Option<Builtin>,
|
||||
}
|
||||
|
||||
impl PrefixOp {
|
||||
#[allow(dead_code)]
|
||||
pub fn sigil(&self) -> &Rc<String> {
|
||||
&self.sigil
|
||||
}
|
||||
pub fn is_prefix(op: &str) -> bool {
|
||||
match op {
|
||||
"+" => true,
|
||||
"-" => true,
|
||||
"!" => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PrefixOp {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use Builtin::*;
|
||||
|
||||
let builtin = match s {
|
||||
"+" => Ok(Increment),
|
||||
"-" => Ok(Negate),
|
||||
"!" => Ok(BooleanNot),
|
||||
_ => Err(())
|
||||
};
|
||||
|
||||
builtin.map(|builtin| PrefixOp { sigil: Rc::new(s.to_string()), builtin: Some(builtin) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct BinOp {
|
||||
sigil: Rc<String>,
|
||||
pub builtin: Option<Builtin>,
|
||||
}
|
||||
|
||||
impl BinOp {
|
||||
pub fn from_sigil(sigil: &str) -> BinOp {
|
||||
let builtin = Builtin::from_str(sigil).ok();
|
||||
BinOp { sigil: Rc::new(sigil.to_string()), builtin }
|
||||
}
|
||||
pub fn sigil(&self) -> &Rc<String> {
|
||||
&self.sigil
|
||||
}
|
||||
pub fn from_sigil_token(tok: &TokenKind) -> Option<BinOp> {
|
||||
let s = token_kind_to_sigil(tok)?;
|
||||
Some(BinOp::from_sigil(s))
|
||||
}
|
||||
|
||||
pub fn min_precedence() -> i32 {
|
||||
i32::min_value()
|
||||
}
|
||||
pub fn get_precedence_from_token(op_tok: &TokenKind) -> Option<i32> {
|
||||
let s = token_kind_to_sigil(op_tok)?;
|
||||
Some(binop_precedences(s))
|
||||
}
|
||||
|
||||
pub fn get_precedence(&self) -> i32 {
|
||||
binop_precedences(&self.sigil)
|
||||
}
|
||||
}
|
||||
|
||||
fn token_kind_to_sigil<'a>(tok: &'a TokenKind) -> Option<&'a str> {
|
||||
use self::TokenKind::*;
|
||||
Some(match tok {
|
||||
Operator(op) => op.as_str(),
|
||||
Period => ".",
|
||||
Pipe => "|",
|
||||
Slash => "/",
|
||||
LAngleBracket => "<",
|
||||
RAngleBracket => ">",
|
||||
Equals => "=",
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
|
||||
fn binop_precedences(s: &str) -> i32 {
|
||||
let default = 10_000_000;
|
||||
match s {
|
||||
"+" => 10,
|
||||
"-" => 10,
|
||||
"*" => 20,
|
||||
"/" => 20,
|
||||
"%" => 20,
|
||||
"++" => 30,
|
||||
"^" => 30,
|
||||
"&" => 20,
|
||||
"|" => 20,
|
||||
">" => 20,
|
||||
">=" => 20,
|
||||
"<" => 20,
|
||||
"<=" => 20,
|
||||
"==" => 40,
|
||||
"=" => 10,
|
||||
"<=>" => 30,
|
||||
_ => default,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::typechecking::{TypeConst, Type};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Builtin {
|
||||
Add,
|
||||
Increment,
|
||||
Subtract,
|
||||
Negate,
|
||||
Multiply,
|
||||
Divide,
|
||||
Quotient,
|
||||
Modulo,
|
||||
Exponentiation,
|
||||
BitwiseAnd,
|
||||
BitwiseOr,
|
||||
BooleanAnd,
|
||||
BooleanOr,
|
||||
BooleanNot,
|
||||
Equality,
|
||||
LessThan,
|
||||
LessThanOrEqual,
|
||||
GreaterThan,
|
||||
GreaterThanOrEqual,
|
||||
Comparison,
|
||||
FieldAccess,
|
||||
IOPrint,
|
||||
IOPrintLn,
|
||||
IOGetLine,
|
||||
Assignment,
|
||||
Concatenate,
|
||||
}
|
||||
|
||||
impl Builtin {
|
||||
pub fn get_type(&self) -> Type {
|
||||
use Builtin::*;
|
||||
match self {
|
||||
Add => ty!(Nat -> Nat -> Nat),
|
||||
Subtract => ty!(Nat -> Nat -> Nat),
|
||||
Multiply => ty!(Nat -> Nat -> Nat),
|
||||
Divide => ty!(Nat -> Nat -> Float),
|
||||
Quotient => ty!(Nat -> Nat -> Nat),
|
||||
Modulo => ty!(Nat -> Nat -> Nat),
|
||||
Exponentiation => ty!(Nat -> Nat -> Nat),
|
||||
BitwiseAnd => ty!(Nat -> Nat -> Nat),
|
||||
BitwiseOr => ty!(Nat -> Nat -> Nat),
|
||||
BooleanAnd => ty!(Bool -> Bool -> Bool),
|
||||
BooleanOr => ty!(Bool -> Bool -> Bool),
|
||||
BooleanNot => ty!(Bool -> Bool),
|
||||
Equality => ty!(Nat -> Nat -> Bool),
|
||||
LessThan => ty!(Nat -> Nat -> Bool),
|
||||
LessThanOrEqual => ty!(Nat -> Nat -> Bool),
|
||||
GreaterThan => ty!(Nat -> Nat -> Bool),
|
||||
GreaterThanOrEqual => ty!(Nat -> Nat -> Bool),
|
||||
Comparison => ty!(Nat -> Nat -> Ordering),
|
||||
FieldAccess => ty!(Unit),
|
||||
IOPrint => ty!(Unit),
|
||||
IOPrintLn => ty!(Unit) ,
|
||||
IOGetLine => ty!(StringT),
|
||||
Assignment => ty!(Unit),
|
||||
Concatenate => ty!(StringT -> StringT -> StringT),
|
||||
Increment => ty!(Nat -> Int),
|
||||
Negate => ty!(Nat -> Int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Builtin {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use Builtin::*;
|
||||
Ok(match s {
|
||||
"+" => Add,
|
||||
"-" => Subtract,
|
||||
"*" => Multiply,
|
||||
"/" => Divide,
|
||||
"quot" => Quotient,
|
||||
"%" => Modulo,
|
||||
"++" => Concatenate,
|
||||
"^" => Exponentiation,
|
||||
"&" => BitwiseAnd,
|
||||
"&&" => BooleanAnd,
|
||||
"|" => BitwiseOr,
|
||||
"||" => BooleanOr,
|
||||
"!" => BooleanNot,
|
||||
">" => GreaterThan,
|
||||
">=" => GreaterThanOrEqual,
|
||||
"<" => LessThan,
|
||||
"<=" => LessThanOrEqual,
|
||||
"==" => Equality,
|
||||
"=" => Assignment,
|
||||
"<=>" => Comparison,
|
||||
"." => FieldAccess,
|
||||
"print" => IOPrint,
|
||||
"println" => IOPrintLn,
|
||||
"getline" => IOGetLine,
|
||||
_ => return Err(())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
use crate::ast::*;
|
||||
|
||||
impl AST {
|
||||
pub fn compact_debug(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
pub fn expanded_debug(&self) -> String {
|
||||
format!("{:#?}", self)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,502 @@
|
|||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::fmt::Write;
|
||||
use std::io;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::util::ScopeStack;
|
||||
use crate::reduced_ast::{BoundVars, ReducedAST, Stmt, Expr, Lit, Func, Alternative, Subpattern};
|
||||
use crate::symbol_table::{SymbolSpec, Symbol, SymbolTable, ScopeSegment, ScopeSegmentKind, FullyQualifiedSymbolName};
|
||||
use crate::builtin::Builtin;
|
||||
|
||||
mod test;
|
||||
|
||||
pub struct State<'a> {
|
||||
values: ScopeStack<'a, Rc<String>, ValueEntry>,
|
||||
symbol_table_handle: Rc<RefCell<SymbolTable>>,
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn new(symbol_table_handle: Rc<RefCell<SymbolTable>>) -> State<'a> {
|
||||
let values = ScopeStack::new(Some(format!("global")));
|
||||
State { values, symbol_table_handle }
|
||||
}
|
||||
|
||||
pub fn debug_print(&self) -> String {
|
||||
format!("Values: {:?}", self.values)
|
||||
}
|
||||
|
||||
fn new_frame(&'a self, items: &'a Vec<Node>, bound_vars: &BoundVars) -> State<'a> {
|
||||
let mut inner_state = State {
|
||||
values: self.values.new_scope(None),
|
||||
symbol_table_handle: self.symbol_table_handle.clone(),
|
||||
};
|
||||
for (bound_var, val) in bound_vars.iter().zip(items.iter()) {
|
||||
if let Some(bv) = bound_var.as_ref() {
|
||||
inner_state.values.insert(bv.clone(), ValueEntry::Binding { constant: true, val: val.clone() });
|
||||
}
|
||||
}
|
||||
inner_state
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Node {
|
||||
Expr(Expr),
|
||||
PrimObject {
|
||||
name: Rc<String>,
|
||||
tag: usize,
|
||||
items: Vec<Node>,
|
||||
},
|
||||
PrimTuple {
|
||||
items: Vec<Node>
|
||||
}
|
||||
}
|
||||
|
||||
fn paren_wrapped_vec(terms: impl Iterator<Item=String>) -> String {
|
||||
let mut buf = String::new();
|
||||
write!(buf, "(").unwrap();
|
||||
for term in terms.map(|e| Some(e)).intersperse(None) {
|
||||
match term {
|
||||
Some(e) => write!(buf, "{}", e).unwrap(),
|
||||
None => write!(buf, ", ").unwrap(),
|
||||
};
|
||||
}
|
||||
write!(buf, ")").unwrap();
|
||||
buf
|
||||
}
|
||||
|
||||
|
||||
impl Node {
|
||||
fn to_repl(&self, symbol_table: &SymbolTable) -> String {
|
||||
match self {
|
||||
Node::Expr(e) => e.to_repl(symbol_table),
|
||||
Node::PrimObject { name, items, .. } if items.len() == 0 => format!("{}", name),
|
||||
Node::PrimObject { name, items, .. } => format!("{}{}", name, paren_wrapped_vec(items.iter().map(|x| x.to_repl(symbol_table)))),
|
||||
Node::PrimTuple { items } => format!("{}", paren_wrapped_vec(items.iter().map(|x| x.to_repl(symbol_table)))),
|
||||
}
|
||||
}
|
||||
fn is_true(&self) -> bool {
|
||||
match self {
|
||||
Node::Expr(Expr::Lit(crate::reduced_ast::Lit::Bool(true))) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ValueEntry {
|
||||
Binding {
|
||||
constant: bool,
|
||||
val: /*FullyEvaluatedExpr*/ Node, //TODO make this use a subtype to represent fully evaluatedness
|
||||
}
|
||||
}
|
||||
|
||||
type EvalResult<T> = Result<T, String>;
|
||||
|
||||
impl Expr {
|
||||
fn to_node(self) -> Node {
|
||||
Node::Expr(self)
|
||||
}
|
||||
fn to_repl(&self, symbol_table: &SymbolTable) -> String {
|
||||
use self::Lit::*;
|
||||
use self::Func::*;
|
||||
|
||||
let _ = symbol_table;
|
||||
|
||||
match self {
|
||||
Expr::Lit(ref l) => match l {
|
||||
Nat(n) => format!("{}", n),
|
||||
Int(i) => format!("{}", i),
|
||||
Float(f) => format!("{}", f),
|
||||
Bool(b) => format!("{}", b),
|
||||
StringLit(s) => format!("\"{}\"", s),
|
||||
},
|
||||
Expr::Func(f) => match f {
|
||||
BuiltIn(builtin) => format!("<built-in function '{:?}'>", builtin),
|
||||
UserDefined { name: None, .. } => format!("<function>"),
|
||||
UserDefined { name: Some(name), .. } => format!("<function '{}'>", name),
|
||||
},
|
||||
Expr::Constructor { type_name, arity, .. } => {
|
||||
format!("<constructor for `{}` arity {}>", type_name, arity)
|
||||
},
|
||||
Expr::Tuple(exprs) => paren_wrapped_vec(exprs.iter().map(|x| x.to_repl(symbol_table))),
|
||||
_ => format!("{:?}", self),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_conditional_target_sigil(self, replacement: &Expr) -> Expr {
|
||||
use self::Expr::*;
|
||||
|
||||
match self {
|
||||
ConditionalTargetSigilValue => replacement.clone(),
|
||||
Unit | Lit(_) | Func(_) | Sym(_) | Constructor { .. } |
|
||||
CaseMatch { .. } | UnimplementedSigilValue | ReductionError(_) => self,
|
||||
Tuple(exprs) => Tuple(exprs.into_iter().map(|e| e.replace_conditional_target_sigil(replacement)).collect()),
|
||||
Call { f, args } => {
|
||||
let new_args = args.into_iter().map(|e| e.replace_conditional_target_sigil(replacement)).collect();
|
||||
Call { f, args: new_args }
|
||||
},
|
||||
Conditional { .. } => panic!("Dunno if I need this, but if so implement"),
|
||||
Assign { .. } => panic!("I'm pretty sure I don't need this"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> State<'a> {
|
||||
pub fn evaluate(&mut self, ast: ReducedAST, repl: bool) -> Vec<Result<String, String>> {
|
||||
let mut acc = vec![];
|
||||
|
||||
// handle prebindings
|
||||
for statement in ast.0.iter() {
|
||||
self.prebinding(statement);
|
||||
}
|
||||
|
||||
for statement in ast.0 {
|
||||
match self.statement(statement) {
|
||||
Ok(Some(ref output)) if repl => {
|
||||
let ref symbol_table = self.symbol_table_handle.borrow();
|
||||
acc.push(Ok(output.to_repl(symbol_table)))
|
||||
},
|
||||
Ok(_) => (),
|
||||
Err(error) => {
|
||||
acc.push(Err(format!("Runtime error: {}", error)));
|
||||
return acc;
|
||||
},
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
fn prebinding(&mut self, stmt: &Stmt) {
|
||||
match stmt {
|
||||
Stmt::PreBinding { name, func } => {
|
||||
let v_entry = ValueEntry::Binding { constant: true, val: Node::Expr(Expr::Func(func.clone())) };
|
||||
self.values.insert(name.clone(), v_entry);
|
||||
},
|
||||
Stmt::Expr(_expr) => {
|
||||
//TODO have this support things like nested function defs
|
||||
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn statement(&mut self, stmt: Stmt) -> EvalResult<Option<Node>> {
|
||||
match stmt {
|
||||
Stmt::Binding { name, constant, expr } => {
|
||||
let val = self.expression(Node::Expr(expr))?;
|
||||
self.values.insert(name.clone(), ValueEntry::Binding { constant, val });
|
||||
Ok(None)
|
||||
},
|
||||
Stmt::Expr(expr) => Ok(Some(self.expression(expr.to_node())?)),
|
||||
Stmt::PreBinding {..} | Stmt::Noop => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn block(&mut self, stmts: Vec<Stmt>) -> EvalResult<Node> {
|
||||
let mut ret = None;
|
||||
for stmt in stmts {
|
||||
ret = self.statement(stmt)?;
|
||||
}
|
||||
Ok(ret.unwrap_or(Node::Expr(Expr::Unit)))
|
||||
}
|
||||
|
||||
fn expression(&mut self, node: Node) -> EvalResult<Node> {
|
||||
use self::Expr::*;
|
||||
match node {
|
||||
t @ Node::PrimTuple { .. } => Ok(t),
|
||||
obj @ Node::PrimObject { .. } => Ok(obj),
|
||||
Node::Expr(expr) => match expr {
|
||||
literal @ Lit(_) => Ok(Node::Expr(literal)),
|
||||
Call { box f, args } => self.call_expression(f, args),
|
||||
Sym(v) => self.handle_sym(v),
|
||||
Constructor { arity, ref name, tag, .. } if arity == 0 => Ok(Node::PrimObject { name: name.clone(), tag, items: vec![] }),
|
||||
constructor @ Constructor { .. } => Ok(Node::Expr(constructor)),
|
||||
func @ Func(_) => Ok(Node::Expr(func)),
|
||||
Tuple(exprs) => {
|
||||
let nodes = exprs.into_iter().map(|expr| self.expression(Node::Expr(expr))).collect::<Result<Vec<Node>,_>>()?;
|
||||
Ok(Node::PrimTuple { items: nodes })
|
||||
},
|
||||
Conditional { box cond, then_clause, else_clause } => self.conditional(cond, then_clause, else_clause),
|
||||
Assign { box val, box expr } => self.assign_expression(val, expr),
|
||||
Unit => Ok(Node::Expr(Unit)),
|
||||
CaseMatch { box cond, alternatives } => self.case_match_expression(cond, alternatives),
|
||||
ConditionalTargetSigilValue => Ok(Node::Expr(ConditionalTargetSigilValue)),
|
||||
UnimplementedSigilValue => Err(format!("Sigil value eval not implemented")),
|
||||
ReductionError(err) => Err(format!("Reduction error: {}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call_expression(&mut self, f: Expr, args: Vec<Expr>) -> EvalResult<Node> {
|
||||
use self::Expr::*;
|
||||
match self.expression(Node::Expr(f))? {
|
||||
Node::Expr(Constructor { type_name, name, tag, arity }) => self.apply_data_constructor(type_name, name, tag, arity, args),
|
||||
Node::Expr(Func(f)) => self.apply_function(f, args),
|
||||
other => return Err(format!("Tried to call {:?} which is not a function or data constructor", other)),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_data_constructor(&mut self, _type_name: Rc<String>, name: Rc<String>, tag: usize, arity: usize, args: Vec<Expr>) -> EvalResult<Node> {
|
||||
if arity != args.len() {
|
||||
return Err(format!("Data constructor {} requires {} arg(s)", name, arity));
|
||||
}
|
||||
|
||||
let evaled_args = args.into_iter().map(|expr| self.expression(Node::Expr(expr))).collect::<Result<Vec<Node>,_>>()?;
|
||||
//let evaled_args = vec![];
|
||||
Ok(Node::PrimObject {
|
||||
name: name.clone(),
|
||||
items: evaled_args,
|
||||
tag
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_function(&mut self, f: Func, args: Vec<Expr>) -> EvalResult<Node> {
|
||||
match f {
|
||||
Func::BuiltIn(builtin) => Ok(self.apply_builtin(builtin, args)?),
|
||||
Func::UserDefined { params, body, name } => {
|
||||
|
||||
if params.len() != args.len() {
|
||||
return Err(format!("calling a {}-argument function with {} args", params.len(), args.len()))
|
||||
}
|
||||
let mut func_state = State {
|
||||
values: self.values.new_scope(name.map(|n| format!("{}", n))),
|
||||
symbol_table_handle: self.symbol_table_handle.clone(),
|
||||
};
|
||||
for (param, val) in params.into_iter().zip(args.into_iter()) {
|
||||
let val = func_state.expression(Node::Expr(val))?;
|
||||
func_state.values.insert(param, ValueEntry::Binding { constant: true, val });
|
||||
}
|
||||
// TODO figure out function return semantics
|
||||
func_state.block(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_builtin(&mut self, builtin: Builtin, args: Vec<Expr>) -> EvalResult<Node> {
|
||||
use self::Expr::*;
|
||||
use self::Lit::*;
|
||||
use Builtin::*;
|
||||
|
||||
let evaled_args: Result<Vec<Node>, String> = args.into_iter().map(|arg| self.expression(arg.to_node()))
|
||||
.collect();
|
||||
let evaled_args = evaled_args?;
|
||||
|
||||
Ok(match (builtin, evaled_args.as_slice()) {
|
||||
(FieldAccess, &[Node::PrimObject { .. }]) => {
|
||||
//TODO implement field access
|
||||
unimplemented!()
|
||||
},
|
||||
(binop, &[Node::Expr(ref lhs), Node::Expr(ref rhs)]) => match (binop, lhs, rhs) {
|
||||
/* binops */
|
||||
(Add, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l + r)),
|
||||
(Concatenate, Lit(StringLit(ref s1)), Lit(StringLit(ref s2))) => Lit(StringLit(Rc::new(format!("{}{}", s1, s2)))),
|
||||
(Subtract, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l - r)),
|
||||
(Multiply, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l * r)),
|
||||
(Divide, Lit(Nat(l)), Lit(Nat(r))) => Lit(Float((*l as f64)/ (*r as f64))),
|
||||
(Quotient, Lit(Nat(l)), Lit(Nat(r))) => if *r == 0 {
|
||||
return Err(format!("divide by zero"));
|
||||
} else {
|
||||
Lit(Nat(l / r))
|
||||
},
|
||||
(Modulo, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l % r)),
|
||||
(Exponentiation, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l ^ r)),
|
||||
(BitwiseAnd, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l & r)),
|
||||
(BitwiseOr, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l | r)),
|
||||
|
||||
/* comparisons */
|
||||
(Equality, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l == r)),
|
||||
(Equality, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l == r)),
|
||||
(Equality, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l == r)),
|
||||
(Equality, Lit(Bool(l)), Lit(Bool(r))) => Lit(Bool(l == r)),
|
||||
(Equality, Lit(StringLit(ref l)), Lit(StringLit(ref r))) => Lit(Bool(l == r)),
|
||||
|
||||
(LessThan, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l < r)),
|
||||
(LessThan, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l < r)),
|
||||
(LessThan, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l < r)),
|
||||
|
||||
(LessThanOrEqual, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l <= r)),
|
||||
(LessThanOrEqual, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l <= r)),
|
||||
(LessThanOrEqual, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l <= r)),
|
||||
|
||||
(GreaterThan, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l > r)),
|
||||
(GreaterThan, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l > r)),
|
||||
(GreaterThan, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l > r)),
|
||||
|
||||
(GreaterThanOrEqual, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l >= r)),
|
||||
(GreaterThanOrEqual, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l >= r)),
|
||||
(GreaterThanOrEqual, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l >= r)),
|
||||
_ => return Err("No valid binop".to_string())
|
||||
}.to_node(),
|
||||
(prefix, &[Node::Expr(ref arg)]) => match (prefix, arg) {
|
||||
(BooleanNot, Lit(Bool(true))) => Lit(Bool(false)),
|
||||
(BooleanNot, Lit(Bool(false))) => Lit(Bool(true)),
|
||||
(Negate, Lit(Nat(n))) => Lit(Int(-1*(*n as i64))),
|
||||
(Negate, Lit(Int(n))) => Lit(Int(-1*(*n as i64))),
|
||||
(Increment, Lit(Int(n))) => Lit(Int(*n)),
|
||||
(Increment, Lit(Nat(n))) => Lit(Nat(*n)),
|
||||
_ => return Err("No valid prefix op".to_string())
|
||||
}.to_node(),
|
||||
|
||||
/* builtin functions */
|
||||
(IOPrint, &[ref anything]) => {
|
||||
let ref symbol_table = self.symbol_table_handle.borrow();
|
||||
print!("{}", anything.to_repl(symbol_table));
|
||||
Expr::Unit.to_node()
|
||||
},
|
||||
(IOPrintLn, &[ref anything]) => {
|
||||
let ref symbol_table = self.symbol_table_handle.borrow();
|
||||
println!("{}", anything.to_repl(symbol_table));
|
||||
Expr::Unit.to_node()
|
||||
},
|
||||
(IOGetLine, &[]) => {
|
||||
let mut buf = String::new();
|
||||
io::stdin().read_line(&mut buf).expect("Error readling line in 'getline'");
|
||||
Lit(StringLit(Rc::new(buf.trim().to_string()))).to_node()
|
||||
},
|
||||
(x, args) => return Err(format!("bad or unimplemented builtin {:?} | {:?}", x, args)),
|
||||
})
|
||||
}
|
||||
|
||||
fn conditional(&mut self, cond: Expr, then_clause: Vec<Stmt>, else_clause: Vec<Stmt>) -> EvalResult<Node> {
|
||||
let cond = self.expression(Node::Expr(cond))?;
|
||||
Ok(match cond {
|
||||
Node::Expr(Expr::Lit(Lit::Bool(true))) => self.block(then_clause)?,
|
||||
Node::Expr(Expr::Lit(Lit::Bool(false))) => self.block(else_clause)?,
|
||||
_ => return Err(format!("Conditional with non-boolean condition"))
|
||||
})
|
||||
}
|
||||
|
||||
fn assign_expression(&mut self, val: Expr, expr: Expr) -> EvalResult<Node> {
|
||||
let name = match val {
|
||||
Expr::Sym(name) => name,
|
||||
_ => return Err(format!("Trying to assign to a non-value")),
|
||||
};
|
||||
|
||||
let constant = match self.values.lookup(&name) {
|
||||
None => return Err(format!("Constant {} is undefined", name)),
|
||||
Some(ValueEntry::Binding { constant, .. }) => constant.clone(),
|
||||
};
|
||||
if constant {
|
||||
return Err(format!("trying to update {}, a non-mutable binding", name));
|
||||
}
|
||||
let val = self.expression(Node::Expr(expr))?;
|
||||
self.values.insert(name.clone(), ValueEntry::Binding { constant: false, val });
|
||||
Ok(Node::Expr(Expr::Unit))
|
||||
}
|
||||
|
||||
fn guard_passes(&mut self, guard: &Option<Expr>, cond: &Node) -> EvalResult<bool> {
|
||||
if let Some(ref guard_expr) = guard {
|
||||
let guard_expr = match cond {
|
||||
Node::Expr(ref e) => guard_expr.clone().replace_conditional_target_sigil(e),
|
||||
_ => guard_expr.clone()
|
||||
};
|
||||
Ok(self.expression(guard_expr.to_node())?.is_true())
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn case_match_expression(&mut self, cond: Expr, alternatives: Vec<Alternative>) -> EvalResult<Node> {
|
||||
|
||||
//TODO need to handle recursive subpatterns
|
||||
let all_subpatterns_pass = |state: &mut State, subpatterns: &Vec<Option<Subpattern>>, items: &Vec<Node>| -> EvalResult<bool> {
|
||||
|
||||
if subpatterns.len() == 0 {
|
||||
return Ok(true)
|
||||
}
|
||||
|
||||
if items.len() != subpatterns.len() {
|
||||
return Err(format!("Subpattern length isn't correct items {} subpatterns {}", items.len(), subpatterns.len()));
|
||||
}
|
||||
|
||||
for (maybe_subp, cond) in subpatterns.iter().zip(items.iter()) {
|
||||
if let Some(subp) = maybe_subp {
|
||||
if !state.guard_passes(&subp.guard, &cond)? {
|
||||
return Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
};
|
||||
|
||||
let cond = self.expression(Node::Expr(cond))?;
|
||||
for alt in alternatives {
|
||||
// no matter what type of condition we have, ignore alternative if the guard evaluates false
|
||||
if !self.guard_passes(&alt.matchable.guard, &cond)? {
|
||||
continue;
|
||||
}
|
||||
|
||||
match cond {
|
||||
Node::PrimObject { ref tag, ref items, .. } => {
|
||||
if alt.matchable.tag.map(|t| t == *tag).unwrap_or(true) {
|
||||
let mut inner_state = self.new_frame(items, &alt.matchable.bound_vars);
|
||||
if all_subpatterns_pass(&mut inner_state, &alt.matchable.subpatterns, items)? {
|
||||
return inner_state.block(alt.item);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
Node::PrimTuple { ref items } => {
|
||||
let mut inner_state = self.new_frame(items, &alt.matchable.bound_vars);
|
||||
if all_subpatterns_pass(&mut inner_state, &alt.matchable.subpatterns, items)? {
|
||||
return inner_state.block(alt.item);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Node::Expr(ref _e) => {
|
||||
if let None = alt.matchable.tag {
|
||||
return self.block(alt.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(format!("{:?} failed pattern match", cond))
|
||||
}
|
||||
|
||||
//TODO if I don't need to lookup by name here...
|
||||
fn handle_sym(&mut self, name: Rc<String>) -> EvalResult<Node> {
|
||||
use self::ValueEntry::*;
|
||||
use self::Func::*;
|
||||
//TODO add a layer of indirection here to talk to the symbol table first, and only then look up
|
||||
//in the values table
|
||||
|
||||
let symbol_table = self.symbol_table_handle.borrow();
|
||||
let value = symbol_table.lookup_by_fqsn(&fqsn!(name ; tr));
|
||||
Ok(match value {
|
||||
Some(Symbol { name, spec, .. }) => match spec {
|
||||
//TODO I'll need this type_name later to do a table lookup
|
||||
SymbolSpec::DataConstructor { type_name: _type_name, type_args, .. } => {
|
||||
if type_args.len() == 0 {
|
||||
Node::PrimObject { name: name.clone(), tag: 0, items: vec![] }
|
||||
} else {
|
||||
return Err(format!("This data constructor thing not done"))
|
||||
}
|
||||
},
|
||||
SymbolSpec::Func(_) => match self.values.lookup(&name) {
|
||||
Some(Binding { val: Node::Expr(Expr::Func(UserDefined { name, params, body })), .. }) => {
|
||||
Node::Expr(Expr::Func(UserDefined { name: name.clone(), params: params.clone(), body: body.clone() }))
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
SymbolSpec::RecordConstructor { .. } => return Err(format!("This shouldn't be a record!")),
|
||||
SymbolSpec::Binding => match self.values.lookup(&name) {
|
||||
Some(Binding { val, .. }) => val.clone(),
|
||||
None => return Err(format!("Symbol {} exists in symbol table but not in evaluator table", name))
|
||||
}
|
||||
},
|
||||
//TODO ideally this should be returning a runtime error if this is ever None, but it's not
|
||||
//handling all bindings correctly yet
|
||||
//None => return Err(format!("Couldn't find value {}", name)),
|
||||
None => match self.values.lookup(&name) {
|
||||
Some(Binding { val, .. }) => val.clone(),
|
||||
None => return Err(format!("Couldn't find value {}", name)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
#![cfg(test)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::symbol_table::SymbolTable;
|
||||
use crate::scope_resolution::ScopeResolver;
|
||||
use crate::reduced_ast::reduce;
|
||||
use crate::eval::State;
|
||||
|
||||
fn evaluate_all_outputs(input: &str) -> Vec<Result<String, String>> {
|
||||
let symbol_table = Rc::new(RefCell::new(SymbolTable::new()));
|
||||
let mut state = State::new(symbol_table);
|
||||
let mut ast = crate::util::quick_ast(input);
|
||||
state.symbol_table_handle.borrow_mut().add_top_level_symbols(&ast).unwrap();
|
||||
{
|
||||
let mut t = &mut state.symbol_table_handle.borrow_mut();
|
||||
let mut scope_resolver = crate::scope_resolution::ScopeResolver::new(&mut t);
|
||||
let _ = scope_resolver.resolve(&mut ast);
|
||||
}
|
||||
|
||||
let reduced = reduce(&ast, &state.symbol_table_handle.borrow());
|
||||
let all_output = state.evaluate(reduced, true);
|
||||
all_output
|
||||
}
|
||||
|
||||
macro_rules! test_in_fresh_env {
|
||||
($string:expr, $correct:expr) => {
|
||||
{
|
||||
let all_output = evaluate_all_outputs($string);
|
||||
let ref output = all_output.last().unwrap();
|
||||
assert_eq!(**output, Ok($correct.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_eval() {
|
||||
test_in_fresh_env!("1 + 2", "3");
|
||||
test_in_fresh_env!("let mut a = 1; a = 2", "Unit");
|
||||
/*
|
||||
test_in_fresh_env!("let mut a = 1; a = 2; a", "2");
|
||||
test_in_fresh_env!(r#"("a", 1 + 2)"#, r#"("a", 3)"#);
|
||||
*/
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn op_eval() {
|
||||
test_in_fresh_env!("- 13", "-13");
|
||||
test_in_fresh_env!("10 - 2", "8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_eval() {
|
||||
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(4)", "5");
|
||||
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(1+2)", "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scopes() {
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn haha() {
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
haha()
|
||||
"#;
|
||||
test_in_fresh_env!(scope_ok, "10");
|
||||
let scope_ok = r#"
|
||||
let a = 20
|
||||
fn haha() {
|
||||
let a = 10
|
||||
a
|
||||
}
|
||||
a
|
||||
"#;
|
||||
test_in_fresh_env!(scope_ok, "20");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_is_patterns() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let x = Option::Some(9); if x is Option::Some(q) then { q } else { 0 }"#;
|
||||
test_in_fresh_env!(source, "9");
|
||||
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let x = Option::None; if x is Option::Some(q) then { q } else { 0 }"#;
|
||||
test_in_fresh_env!(source, "0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_if_matching() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let a = Option::None
|
||||
if a { is Option::None -> 4, is Option::Some(x) -> x }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "4");
|
||||
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
let a = Option::Some(99)
|
||||
if a { is Option::None -> 4, is Option::Some(x) -> x }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "99");
|
||||
|
||||
let source = r#"
|
||||
let a = 10
|
||||
if a { is 10 -> "x", is 4 -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"x\"");
|
||||
|
||||
let source = r#"
|
||||
let a = 10
|
||||
if a { is 15 -> "x", is 10 -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"y\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_pattern() {
|
||||
let source = r#"
|
||||
let a = "foo"
|
||||
if a { is "foo" -> "x", is _ -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"x\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_pattern() {
|
||||
let source = r#"
|
||||
let a = true
|
||||
if a {
|
||||
is true -> "x",
|
||||
is false -> "y"
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"x\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_pattern_2() {
|
||||
let source = r#"
|
||||
let a = false
|
||||
if a { is true -> "x", is false -> "y" }
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"y\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_pattern() {
|
||||
let source = r#"
|
||||
type Option<T> = Some(T) | None
|
||||
if Option::Some(10) {
|
||||
is _ -> "hella"
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, "\"hella\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern() {
|
||||
let source = r#"
|
||||
if (1, 2) {
|
||||
is (1, x) -> x,
|
||||
is _ -> 99
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 2);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_2() {
|
||||
let source = r#"
|
||||
if (1, 2) {
|
||||
is (10, x) -> x,
|
||||
is (y, x) -> x + y
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_3() {
|
||||
let source = r#"
|
||||
if (1, 5) {
|
||||
is (10, x) -> x,
|
||||
is (1, x) -> x
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pattern_4() {
|
||||
let source = r#"
|
||||
if (1, 5) {
|
||||
is (10, x) -> x,
|
||||
is (1, x) -> x,
|
||||
}
|
||||
"#;
|
||||
test_in_fresh_env!(source, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prim_obj_pattern() {
|
||||
let source = r#"
|
||||
type Stuff = Mulch(Nat) | Jugs(Nat, String) | Mardok
|
||||
let a = Stuff::Mulch(20)
|
||||
let b = Stuff::Jugs(1, "haha")
|
||||
let c = Stuff::Mardok
|
||||
|
||||
let x = if a {
|
||||
is Stuff::Mulch(20) -> "x",
|
||||
is _ -> "ERR"
|
||||
}
|
||||
|
||||
let y = if b {
|
||||
is Stuff::Mulch(n) -> "ERR",
|
||||
is Stuff::Jugs(2, _) -> "ERR",
|
||||
is Stuff::Jugs(1, s) -> s,
|
||||
is _ -> "ERR",
|
||||
}
|
||||
|
||||
let z = if c {
|
||||
is Stuff::Jugs(_, _) -> "ERR",
|
||||
is Stuff::Mardok -> "NIGH",
|
||||
is _ -> "ERR",
|
||||
}
|
||||
|
||||
(x, y, z)
|
||||
"#;
|
||||
test_in_fresh_env!(source, r#"("x", "haha", "NIGH")"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_lambda_syntax() {
|
||||
let source = r#"
|
||||
let q = \(x, y) { x * y }
|
||||
let x = q(5,2)
|
||||
let y = \(m, n, o) { m + n + o }(1,2,3)
|
||||
(x, y)
|
||||
"#;
|
||||
test_in_fresh_env!(source, r"(10, 6)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda_syntax_2() {
|
||||
let source = r#"
|
||||
fn milta() {
|
||||
\(x) { x + 33 }
|
||||
}
|
||||
milta()(10)
|
||||
"#;
|
||||
test_in_fresh_env!(source, "43");
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
#![feature(trace_macros)]
|
||||
#![feature(custom_attribute)]
|
||||
//#![feature(unrestricted_attribute_tokens)]
|
||||
#![feature(slice_patterns, box_patterns, box_syntax)]
|
||||
|
||||
//! `schala-lang` is where the Schala programming language is actually implemented.
|
||||
//! It defines the `Schala` type, which contains the state for a Schala REPL, and implements
|
||||
//! `ProgrammingLanguageInterface` and the chain of compiler passes for it.
|
||||
|
||||
extern crate itertools;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate maplit;
|
||||
extern crate schala_repl;
|
||||
#[macro_use]
|
||||
extern crate schala_lang_codegen;
|
||||
extern crate ena;
|
||||
extern crate derivative;
|
||||
|
||||
|
||||
macro_rules! bx {
|
||||
($e:expr) => { Box::new($e) }
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
#[macro_use]
|
||||
mod typechecking;
|
||||
mod debugging;
|
||||
|
||||
mod tokenizing;
|
||||
mod ast;
|
||||
mod parsing;
|
||||
#[macro_use]
|
||||
mod symbol_table;
|
||||
mod scope_resolution;
|
||||
mod builtin;
|
||||
mod reduced_ast;
|
||||
mod eval;
|
||||
|
||||
mod schala;
|
||||
|
||||
pub use schala::Schala;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,709 @@
|
|||
#![cfg(test)]
|
||||
use ::std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::tokenize;
|
||||
use super::ParseResult;
|
||||
use crate::ast::{ItemIdStore, AST, Meta, Expression, Statement, StatementKind, IfExpressionBody, Discriminator, Pattern, PatternLiteral, TypeBody, Enumerator, ForBody, InvocationArgument, FormalParam, PrefixOp, BinOp, QualifiedName};
|
||||
use super::Declaration::*;
|
||||
use super::Signature;
|
||||
use super::TypeIdentifier::*;
|
||||
use super::TypeSingletonName;
|
||||
use super::ExpressionKind::*;
|
||||
use super::Variant::*;
|
||||
use super::ForBody::*;
|
||||
|
||||
fn parse(input: &str) -> ParseResult<AST> {
|
||||
let tokens: Vec<crate::tokenizing::Token> = tokenize(input);
|
||||
let mut parser = super::Parser::new(tokens);
|
||||
parser.parse()
|
||||
}
|
||||
|
||||
macro_rules! parse_test {
|
||||
($string:expr, $correct:expr) => {
|
||||
assert_eq!(parse($string).unwrap(), $correct)
|
||||
};
|
||||
}
|
||||
macro_rules! parse_test_wrap_ast {
|
||||
($string:expr, $correct:expr) => { parse_test!($string, AST { id: ItemIdStore::new_id(), statements: vec![$correct] }) }
|
||||
}
|
||||
macro_rules! parse_error {
|
||||
($string:expr) => { assert!(parse($string).is_err()) }
|
||||
}
|
||||
macro_rules! qname {
|
||||
( $( $component:expr),* ) => {
|
||||
{
|
||||
let mut components = vec![];
|
||||
$(
|
||||
components.push(rc!($component));
|
||||
)*
|
||||
QualifiedName { components, id: ItemIdStore::new_id() }
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! val {
|
||||
($var:expr) => { Value(QualifiedName { components: vec![Rc::new($var.to_string())], id: ItemIdStore::new_id() }) };
|
||||
}
|
||||
macro_rules! ty {
|
||||
($name:expr) => { Singleton(tys!($name)) }
|
||||
}
|
||||
macro_rules! tys {
|
||||
($name:expr) => { TypeSingletonName { name: Rc::new($name.to_string()), params: vec![] } };
|
||||
}
|
||||
|
||||
macro_rules! decl {
|
||||
($expr_type:expr) => {
|
||||
Statement { id: ItemIdStore::new_id(), kind: StatementKind::Declaration($expr_type) }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! ex {
|
||||
($expr_type:expr) => { Expression::new(ItemIdStore::new_id(), $expr_type) };
|
||||
(m $expr_type:expr) => { Meta::new(Expression::new(ItemIdStore::new_id(), $expr_type)) };
|
||||
(m $expr_type:expr, $type_anno:expr) => { Meta::new(Expression::with_anno(ItemIdStore::new_id(), $expr_type, $type_anno)) };
|
||||
(s $expr_text:expr) => {
|
||||
{
|
||||
let tokens: Vec<crate::tokenizing::Token> = tokenize($expr_text);
|
||||
let mut parser = super::Parser::new(tokens);
|
||||
parser.expression().unwrap()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! inv {
|
||||
($expr_type:expr) => { InvocationArgument::Positional($expr_type) }
|
||||
}
|
||||
|
||||
macro_rules! binexp {
|
||||
($op:expr, $lhs:expr, $rhs:expr) => { BinExp(BinOp::from_sigil($op), bx!(Expression::new(ItemIdStore::new_id(), $lhs).into()), bx!(Expression::new(ItemIdStore::new_id(), $rhs).into())) }
|
||||
}
|
||||
macro_rules! prefexp {
|
||||
($op:expr, $lhs:expr) => { PrefixExp(PrefixOp::from_str($op).unwrap(), bx!(Expression::new(ItemIdStore::new_id(), $lhs).into())) }
|
||||
}
|
||||
macro_rules! exst {
|
||||
($expr_type:expr) => { Meta::new(Statement { id: ItemIdStore::new_id(), kind: StatementKind::Expression(Expression::new(ItemIdStore::new_id(), $expr_type).into())}) };
|
||||
($expr_type:expr, $type_anno:expr) => { Meta::new(Statement { id: ItemIdStore::new_id(), kind: StatementKind::Expression(Expression::with_anno(ItemIdStore::new_id(), $expr_type, $type_anno).into())}) };
|
||||
($op:expr, $lhs:expr, $rhs:expr) => { Meta::new(
|
||||
Statement { id: ItemIdStore::new_id(), ,kind: StatementKind::Expression(ex!(binexp!($op, $lhs, $rhs)))}
|
||||
)};
|
||||
(s $statement_text:expr) => {
|
||||
{
|
||||
let tokens: Vec<crate::tokenizing::Token> = tokenize($statement_text);
|
||||
let mut parser = super::Parser::new(tokens);
|
||||
Meta::new(parser.statement().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_number_literals_and_binexps() {
|
||||
parse_test_wrap_ast! { ".2", exst!(FloatLiteral(0.2)) };
|
||||
parse_test_wrap_ast! { "8.1", exst!(FloatLiteral(8.1)) };
|
||||
|
||||
parse_test_wrap_ast! { "0b010", exst!(NatLiteral(2)) };
|
||||
parse_test_wrap_ast! { "0b0_1_0_", exst!(NatLiteral(2)) }
|
||||
|
||||
parse_test_wrap_ast! {"0xff", exst!(NatLiteral(255)) };
|
||||
parse_test_wrap_ast! {"0xf_f_", exst!(NatLiteral(255)) };
|
||||
|
||||
parse_test_wrap_ast! {"0xf_f_+1", exst!(binexp!("+", NatLiteral(255), NatLiteral(1))) };
|
||||
|
||||
parse_test! {"3; 4; 4.3",
|
||||
AST {
|
||||
id: ItemIdStore::new_id(),
|
||||
statements: vec![exst!(NatLiteral(3)), exst!(NatLiteral(4)),
|
||||
exst!(FloatLiteral(4.3))]
|
||||
}
|
||||
};
|
||||
|
||||
parse_test_wrap_ast!("1 + 2 * 3",
|
||||
exst!(binexp!("+", NatLiteral(1), binexp!("*", NatLiteral(2), NatLiteral(3))))
|
||||
);
|
||||
|
||||
parse_test_wrap_ast!("1 * 2 + 3",
|
||||
exst!(binexp!("+", binexp!("*", NatLiteral(1), NatLiteral(2)), NatLiteral(3)))
|
||||
) ;
|
||||
|
||||
parse_test_wrap_ast!("1 && 2", exst!(binexp!("&&", NatLiteral(1), NatLiteral(2))));
|
||||
|
||||
parse_test_wrap_ast!("1 + 2 * 3 + 4", exst!(
|
||||
binexp!("+",
|
||||
binexp!("+", NatLiteral(1), binexp!("*", NatLiteral(2), NatLiteral(3))),
|
||||
NatLiteral(4))));
|
||||
|
||||
parse_test_wrap_ast!("(1 + 2) * 3",
|
||||
exst!(binexp!("*", binexp!("+", NatLiteral(1), NatLiteral(2)), NatLiteral(3))));
|
||||
|
||||
parse_test_wrap_ast!(".1 + .2", exst!(binexp!("+", FloatLiteral(0.1), FloatLiteral(0.2))));
|
||||
parse_test_wrap_ast!("1 / 2", exst!(binexp!("/", NatLiteral(1), NatLiteral(2))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_tuples() {
|
||||
parse_test_wrap_ast!("()", exst!(TupleLiteral(vec![])));
|
||||
parse_test_wrap_ast!("(\"hella\", 34)", exst!(
|
||||
TupleLiteral(
|
||||
vec![ex!(s r#""hella""#).into(), ex!(s "34").into()]
|
||||
)
|
||||
));
|
||||
parse_test_wrap_ast!("((1+2), \"slough\")", exst!(TupleLiteral(vec![
|
||||
ex!(binexp!("+", NatLiteral(1), NatLiteral(2))).into(),
|
||||
ex!(StringLiteral(rc!(slough))).into(),
|
||||
])))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_identifiers() {
|
||||
parse_test_wrap_ast!("a", exst!(val!("a")));
|
||||
parse_test_wrap_ast!("some_value", exst!(val!("some_value")));
|
||||
parse_test_wrap_ast!("a + b", exst!(binexp!("+", val!("a"), val!("b"))));
|
||||
//parse_test!("a[b]", AST(vec![Expression(
|
||||
//parse_test!("a[]", <- TODO THIS NEEDS TO FAIL
|
||||
//parse_test("a()[b]()[d]")
|
||||
//TODO fix this parsing stuff
|
||||
/*
|
||||
parse_test! { "perspicacity()[a]", AST(vec![
|
||||
exst!(Index {
|
||||
indexee: bx!(ex!(Call { f: bx!(ex!(val!("perspicacity"))), arguments: vec![] })),
|
||||
indexers: vec![ex!(val!("a"))]
|
||||
})
|
||||
])
|
||||
}
|
||||
*/
|
||||
parse_test_wrap_ast!("a[b,c]", exst!(Index { indexee: bx!(ex!(m val!("a"))), indexers: vec![ex!(m val!("b")), ex!(m val!("c"))]} ));
|
||||
|
||||
parse_test_wrap_ast!("None", exst!(val!("None")));
|
||||
parse_test_wrap_ast!("Pandas { a: x + y }",
|
||||
exst!(NamedStruct { name: Meta::new(qname!(Pandas)), fields: vec![(rc!(a), ex!(m binexp!("+", val!("x"), val!("y"))))]})
|
||||
);
|
||||
parse_test_wrap_ast! { "Pandas { a: n, b: q, }",
|
||||
exst!(NamedStruct { name: Meta::new(qname!(Pandas)), fields:
|
||||
vec![(rc!(a), ex!(m val!("n"))), (rc!(b), ex!(m val!("q")))]
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn qualified_identifiers() {
|
||||
parse_test_wrap_ast! {
|
||||
"let q_q = Yolo::Swaggins",
|
||||
Meta::new(decl!(Binding { name: rc!(q_q), constant: true, type_anno: None,
|
||||
expr: Meta::new(Expression::new(ItemIdStore::new_id(), Value(qname!(Yolo, Swaggins)))),
|
||||
}))
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"thing::item::call()",
|
||||
exst!(Call { f: bx![ex!(m Value(qname!(thing, item, call)))], arguments: vec![] })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_words() {
|
||||
parse_error!("module::item::call()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_complicated_operators() {
|
||||
parse_test_wrap_ast!("a <- b", exst!(binexp!("<-", val!("a"), val!("b"))));
|
||||
parse_test_wrap_ast!("a || b", exst!(binexp!("||", val!("a"), val!("b"))));
|
||||
parse_test_wrap_ast!("a<>b", exst!(binexp!("<>", val!("a"), val!("b"))));
|
||||
parse_test_wrap_ast!("a.b.c.d", exst!(binexp!(".",
|
||||
binexp!(".",
|
||||
binexp!(".", val!("a"), val!("b")),
|
||||
val!("c")),
|
||||
val!("d"))));
|
||||
parse_test_wrap_ast!("-3", exst!(prefexp!("-", NatLiteral(3))));
|
||||
parse_test_wrap_ast!("-0.2", exst!(prefexp!("-", FloatLiteral(0.2))));
|
||||
parse_test_wrap_ast!("!3", exst!(prefexp!("!", NatLiteral(3))));
|
||||
parse_test_wrap_ast!("a <- -b", exst!(binexp!("<-", val!("a"), prefexp!("-", val!("b")))));
|
||||
parse_test_wrap_ast!("a <--b", exst!(binexp!("<--", val!("a"), val!("b"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_functions() {
|
||||
parse_test_wrap_ast!("fn oi()", Meta::new(decl!(FuncSig(Signature { name: rc!(oi), operator: false, params: vec![], type_anno: None }))));
|
||||
parse_test_wrap_ast!("oi()", exst!(Call { f: bx!(ex!(m val!("oi"))), arguments: vec![] }));
|
||||
parse_test_wrap_ast!("oi(a, 2 + 2)", exst!(Call
|
||||
{ f: bx!(ex!(m val!("oi"))),
|
||||
arguments: vec![inv!(ex!(m val!("a"))), inv!(ex!(m binexp!("+", NatLiteral(2), NatLiteral(2)))).into()]
|
||||
}));
|
||||
parse_error!("a(b,,c)");
|
||||
|
||||
parse_test_wrap_ast!("fn a(b, c: Int): Int", Meta::new(decl!(
|
||||
FuncSig(Signature { name: rc!(a), operator: false, params: vec![
|
||||
FormalParam { name: rc!(b), anno: None, default: None },
|
||||
FormalParam { name: rc!(c), anno: Some(ty!("Int")), default: None }
|
||||
], type_anno: Some(ty!("Int")) }))));
|
||||
|
||||
|
||||
parse_test_wrap_ast!("fn a(x) { x() }", Meta::new(decl!(
|
||||
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), anno: None, default: None }], type_anno: None },
|
||||
vec![exst!(Call { f: bx!(ex!(m val!("x"))), arguments: vec![] })]))));
|
||||
parse_test_wrap_ast!("fn a(x) {\n x() }", Meta::new(decl!(
|
||||
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), anno: None, default: None }], type_anno: None },
|
||||
vec![exst!(Call { f: bx!(ex!(m val!("x"))), arguments: vec![] })]))));
|
||||
|
||||
let multiline = r#"
|
||||
fn a(x) {
|
||||
x()
|
||||
}
|
||||
"#;
|
||||
parse_test_wrap_ast!(multiline, Meta::new(decl!(
|
||||
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), default: None, anno: None }], type_anno: None },
|
||||
vec![exst!(Call { f: bx!(ex!(m val!("x"))), arguments: vec![] })]))));
|
||||
let multiline2 = r#"
|
||||
fn a(x) {
|
||||
|
||||
x()
|
||||
|
||||
}
|
||||
"#;
|
||||
parse_test_wrap_ast!(multiline2, Meta::new(decl!(
|
||||
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), default: None, anno: None }], type_anno: None },
|
||||
vec![exst!(s "x()")]))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functions_with_default_args() {
|
||||
parse_test_wrap_ast! {
|
||||
"fn func(x: Int, y: Int = 4) { }",
|
||||
Meta::new(decl!(
|
||||
FuncDecl(Signature { name: rc!(func), operator: false, type_anno: None, params: vec![
|
||||
FormalParam { name: rc!(x), default: None, anno: Some(ty!("Int")) },
|
||||
FormalParam { name: rc!(y), default: Some(Meta::new(ex!(s "4"))), anno: Some(ty!("Int")) }
|
||||
]}, vec![])
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_bools() {
|
||||
parse_test_wrap_ast!("false", exst!(BoolLiteral(false)));
|
||||
parse_test_wrap_ast!("true", exst!(BoolLiteral(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_strings() {
|
||||
parse_test_wrap_ast!(r#""hello""#, exst!(StringLiteral(rc!(hello))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_types() {
|
||||
parse_test_wrap_ast!("type Yolo = Yolo", Meta::new(decl!(TypeDecl { name: tys!("Yolo"), body: TypeBody(vec![UnitStruct(rc!(Yolo))]), mutable: false} )));
|
||||
parse_test_wrap_ast!("type mut Yolo = Yolo", Meta::new(decl!(TypeDecl { name: tys!("Yolo"), body: TypeBody(vec![UnitStruct(rc!(Yolo))]), mutable: true} )));
|
||||
parse_test_wrap_ast!("type alias Sex = Drugs", Meta::new(decl!(TypeAlias(rc!(Sex), rc!(Drugs)))));
|
||||
parse_test_wrap_ast!("type Sanchez = Miguel | Alejandro(Int, Option<a>) | Esperanza { a: Int, b: String }",
|
||||
Meta::new(decl!(TypeDecl {
|
||||
name: tys!("Sanchez"),
|
||||
body: TypeBody(vec![
|
||||
UnitStruct(rc!(Miguel)),
|
||||
TupleStruct(rc!(Alejandro), vec![
|
||||
Singleton(TypeSingletonName { name: rc!(Int), params: vec![] }),
|
||||
Singleton(TypeSingletonName { name: rc!(Option), params: vec![Singleton(TypeSingletonName { name: rc!(a), params: vec![] })] }),
|
||||
]),
|
||||
Record{
|
||||
name: rc!(Esperanza),
|
||||
members: vec![
|
||||
(rc!(a), Singleton(TypeSingletonName { name: rc!(Int), params: vec![] })),
|
||||
(rc!(b), Singleton(TypeSingletonName { name: rc!(String), params: vec![] })),
|
||||
]
|
||||
}
|
||||
]),
|
||||
mutable: false
|
||||
})));
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"type Jorge<a> = Diego | Kike(a)",
|
||||
Meta::new(decl!(TypeDecl{
|
||||
name: TypeSingletonName { name: rc!(Jorge), params: vec![Singleton(TypeSingletonName { name: rc!(a), params: vec![] })] },
|
||||
body: TypeBody(vec![UnitStruct(rc!(Diego)), TupleStruct(rc!(Kike), vec![Singleton(TypeSingletonName { name: rc!(a), params: vec![] })])]),
|
||||
mutable: false
|
||||
}
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_bindings() {
|
||||
parse_test_wrap_ast!("let mut a = 10", Meta::new(decl!(Binding { name: rc!(a), constant: false, type_anno: None, expr: ex!(m NatLiteral(10)) } )));
|
||||
parse_test_wrap_ast!("let a = 2 + 2", Meta::new(decl!(Binding { name: rc!(a), constant: true, type_anno: None, expr: ex!(m binexp!("+", NatLiteral(2), NatLiteral(2))) }) ));
|
||||
parse_test_wrap_ast!("let a: Nat = 2 + 2", Meta::new(decl!(
|
||||
Binding { name: rc!(a), constant: true, type_anno: Some(Singleton(TypeSingletonName { name: rc!(Nat), params: vec![] })),
|
||||
expr: Meta::new(ex!(binexp!("+", NatLiteral(2), NatLiteral(2)))) }
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_block_expressions() {
|
||||
parse_test_wrap_ast! {
|
||||
"if a() then { b(); c() }", exst!(
|
||||
IfExpression {
|
||||
discriminator: bx! {
|
||||
Discriminator::Simple(ex!(m Call { f: bx!(ex!(m val!("a"))), arguments: vec![]}))
|
||||
},
|
||||
body: bx! {
|
||||
IfExpressionBody::SimpleConditional(
|
||||
vec![exst!(Call { f: bx!(ex!(m val!("b"))), arguments: vec![]}), exst!(Call { f: bx!(ex!(m val!("c"))), arguments: vec![] })],
|
||||
None
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"if a() then { b(); c() } else { q }", exst!(
|
||||
IfExpression {
|
||||
discriminator: bx! {
|
||||
Discriminator::Simple(ex!(m Call { f: bx!(ex!(m val!("a"))), arguments: vec![]}))
|
||||
},
|
||||
body: bx! {
|
||||
IfExpressionBody::SimpleConditional(
|
||||
vec![exst!(Call { f: bx!(ex!(m val!("b"))), arguments: vec![]}), exst!(Call { f: bx!(ex!(m val!("c"))), arguments: vec![] })],
|
||||
Some(
|
||||
vec![exst!(val!("q"))],
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
/*
|
||||
parse_test!("if a() then { b(); c() }", AST(vec![exst!(
|
||||
IfExpression(bx!(ex!(Call { f: bx!(ex!(val!("a"))), arguments: vec![]})),
|
||||
vec![exst!(Call { f: bx!(ex!(val!("b"))), arguments: vec![]}), exst!(Call { f: bx!(ex!(val!("c"))), arguments: vec![] })],
|
||||
None)
|
||||
)]));
|
||||
parse_test!(r#"
|
||||
if true then {
|
||||
const a = 10
|
||||
b
|
||||
} else {
|
||||
c
|
||||
}"#,
|
||||
AST(vec![exst!(IfExpression(bx!(ex!(BoolLiteral(true))),
|
||||
vec![decl!(Binding { name: rc!(a), constant: true, expr: ex!(NatLiteral(10)) }),
|
||||
exst!(val!(rc!(b)))],
|
||||
Some(vec![exst!(val!(rc!(c)))])))])
|
||||
);
|
||||
|
||||
parse_test!("if a { b } else { c }", AST(vec![exst!(
|
||||
IfExpression(bx!(ex!(val!("a"))),
|
||||
vec![exst!(val!("b"))],
|
||||
Some(vec![exst!(val!("c"))])))]));
|
||||
|
||||
parse_test!("if (A {a: 1}) { b } else { c }", AST(vec![exst!(
|
||||
IfExpression(bx!(ex!(NamedStruct { name: rc!(A), fields: vec![(rc!(a), ex!(NatLiteral(1)))]})),
|
||||
vec![exst!(val!("b"))],
|
||||
Some(vec![exst!(val!("c"))])))]));
|
||||
|
||||
parse_error!("if A {a: 1} { b } else { c }");
|
||||
*/
|
||||
}
|
||||
#[test]
|
||||
fn parsing_interfaces() {
|
||||
parse_test_wrap_ast!("interface Unglueable { fn unglue(a: Glue); fn mar(): Glue }",
|
||||
Meta::new(decl!(Interface {
|
||||
name: rc!(Unglueable),
|
||||
signatures: vec![
|
||||
Signature {
|
||||
name: rc!(unglue),
|
||||
operator: false,
|
||||
params: vec![
|
||||
FormalParam { name: rc!(a), anno: Some(Singleton(TypeSingletonName { name: rc!(Glue), params: vec![] })), default: None }
|
||||
],
|
||||
type_anno: None
|
||||
},
|
||||
Signature { name: rc!(mar), operator: false, params: vec![], type_anno: Some(Singleton(TypeSingletonName { name: rc!(Glue), params: vec![] })) },
|
||||
]
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_impls() {
|
||||
parse_test_wrap_ast!("impl Heh { fn yolo(); fn swagg(); }",
|
||||
Meta::new(
|
||||
decl!(Impl {
|
||||
type_name: ty!("Heh"),
|
||||
interface_name: None,
|
||||
block: vec![
|
||||
FuncSig(Signature { name: rc!(yolo), operator: false, params: vec![], type_anno: None }),
|
||||
FuncSig(Signature { name: rc!(swagg), operator: false, params: vec![], type_anno: None })
|
||||
] })));
|
||||
|
||||
parse_test_wrap_ast!("impl Mondai for Lollerino { fn yolo(); fn swagg(); }",
|
||||
Meta::new(decl!(Impl {
|
||||
type_name: ty!("Lollerino"),
|
||||
interface_name: Some(TypeSingletonName { name: rc!(Mondai), params: vec![] }),
|
||||
block: vec![
|
||||
FuncSig(Signature { name: rc!(yolo), operator: false, params: vec![], type_anno: None}),
|
||||
FuncSig(Signature { name: rc!(swagg), operator: false, params: vec![], type_anno: None })
|
||||
] })));
|
||||
|
||||
parse_test_wrap_ast!("impl Hella<T> for (Alpha, Omega) { }",
|
||||
Meta::new(decl!(Impl {
|
||||
type_name: Tuple(vec![ty!("Alpha"), ty!("Omega")]),
|
||||
interface_name: Some(TypeSingletonName { name: rc!(Hella), params: vec![ty!("T")] }),
|
||||
block: vec![]
|
||||
}))
|
||||
);
|
||||
|
||||
parse_test_wrap_ast!("impl Option<WTFMate> { fn oi() }",
|
||||
Meta::new(
|
||||
decl!(Impl {
|
||||
type_name: Singleton(TypeSingletonName { name: rc!(Option), params: vec![ty!("WTFMate")]}),
|
||||
interface_name: None,
|
||||
block: vec![
|
||||
FuncSig(Signature { name: rc!(oi), operator: false, params: vec![], type_anno: None }),
|
||||
]
|
||||
})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_type_annotations() {
|
||||
parse_test_wrap_ast!("let a = b : Int",
|
||||
Meta::new(
|
||||
decl!(Binding { name: rc!(a), constant: true, type_anno: None, expr:
|
||||
ex!(m val!("b"), ty!("Int")) })));
|
||||
|
||||
parse_test_wrap_ast!("a : Int",
|
||||
exst!(val!("a"), ty!("Int"))
|
||||
);
|
||||
|
||||
parse_test_wrap_ast!("a : Option<Int>",
|
||||
exst!(val!("a"), Singleton(TypeSingletonName { name: rc!(Option), params: vec![ty!("Int")] }))
|
||||
);
|
||||
|
||||
parse_test_wrap_ast!("a : KoreanBBQSpecifier<Kimchi, Option<Bulgogi> >",
|
||||
exst!(val!("a"), Singleton(TypeSingletonName { name: rc!(KoreanBBQSpecifier), params: vec![
|
||||
ty!("Kimchi"), Singleton(TypeSingletonName { name: rc!(Option), params: vec![ty!("Bulgogi")] })
|
||||
] }))
|
||||
);
|
||||
|
||||
parse_test_wrap_ast!("a : (Int, Yolo<a>)",
|
||||
exst!(val!("a"), Tuple(
|
||||
vec![ty!("Int"), Singleton(TypeSingletonName {
|
||||
name: rc!(Yolo), params: vec![ty!("a")]
|
||||
})])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_lambdas() {
|
||||
parse_test_wrap_ast! { r#"\(x) { x + 1}"#, exst!(
|
||||
Lambda { params: vec![FormalParam { name: rc!(x), anno: None, default: None } ], type_anno: None, body: vec![exst!(s "x + 1")] }
|
||||
)
|
||||
}
|
||||
|
||||
parse_test_wrap_ast!(r#"\ (x: Int, y) { a;b;c;}"#,
|
||||
exst!(Lambda {
|
||||
params: vec![
|
||||
FormalParam { name: rc!(x), anno: Some(ty!("Int")), default: None },
|
||||
FormalParam { name: rc!(y), anno: None, default: None }
|
||||
],
|
||||
type_anno: None,
|
||||
body: vec![exst!(s "a"), exst!(s "b"), exst!(s "c")]
|
||||
})
|
||||
);
|
||||
|
||||
parse_test_wrap_ast! { r#"\(x){y}(1)"#,
|
||||
exst!(Call { f: bx!(ex!(m
|
||||
Lambda {
|
||||
params: vec![
|
||||
FormalParam { name: rc!(x), anno: None, default: None }
|
||||
],
|
||||
type_anno: None,
|
||||
body: vec![exst!(s "y")] }
|
||||
)),
|
||||
arguments: vec![inv!(ex!(m NatLiteral(1))).into()] })
|
||||
};
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
r#"\(x: Int): String { "q" }"#,
|
||||
exst!(Lambda {
|
||||
params: vec![
|
||||
FormalParam { name: rc!(x), anno: Some(ty!("Int")), default: None },
|
||||
],
|
||||
type_anno: Some(ty!("String")),
|
||||
body: vec![exst!(s r#""q""#)]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_param_lambda() {
|
||||
parse_test_wrap_ast! {
|
||||
r"\x { x + 10 }",
|
||||
exst!(Lambda {
|
||||
params: vec![FormalParam { name: rc!(x), anno: None, default: None }],
|
||||
type_anno: None,
|
||||
body: vec![exst!(s r"x + 10")]
|
||||
})
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
r"\x: Nat { x + 10 }",
|
||||
exst!(Lambda {
|
||||
params: vec![FormalParam { name: rc!(x), anno: Some(ty!("Nat")), default: None }],
|
||||
type_anno: None,
|
||||
body: vec![exst!(s r"x + 10")]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn more_advanced_lambdas() {
|
||||
parse_test! {
|
||||
r#"fn wahoo() { let a = 10; \(x) { x + a } };
|
||||
wahoo()(3) "#,
|
||||
AST {
|
||||
id: ItemIdStore::new_id(),
|
||||
statements: vec![
|
||||
exst!(s r"fn wahoo() { let a = 10; \(x) { x + a } }"),
|
||||
exst! {
|
||||
Call {
|
||||
f: bx!(ex!(m Call { f: bx!(ex!(m val!("wahoo"))), arguments: vec![] })),
|
||||
arguments: vec![inv!(ex!(m NatLiteral(3))).into()],
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_literals() {
|
||||
parse_test_wrap_ast! {
|
||||
"[1,2]",
|
||||
exst!(ListLiteral(vec![ex!(m NatLiteral(1)), ex!(m NatLiteral(2))]))
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn while_expr() {
|
||||
parse_test_wrap_ast! {
|
||||
"while { }",
|
||||
exst!(WhileExpression { condition: None, body: vec![] })
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"while a == b { }",
|
||||
exst!(WhileExpression { condition: Some(bx![ex![m binexp!("==", val!("a"), val!("b"))]]), body: vec![] })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn for_expr() {
|
||||
parse_test_wrap_ast! {
|
||||
"for { a <- maybeValue } return 1",
|
||||
exst!(ForExpression {
|
||||
enumerators: vec![Enumerator { id: rc!(a), generator: ex!(val!("maybeValue")) }],
|
||||
body: bx!(MonadicReturn(ex!(s "1")))
|
||||
})
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"for n <- someRange { f(n); }",
|
||||
exst!(ForExpression { enumerators: vec![Enumerator { id: rc!(n), generator: ex!(val!("someRange"))}],
|
||||
body: bx!(ForBody::StatementBlock(vec![exst!(s "f(n)")]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn patterns() {
|
||||
parse_test_wrap_ast! {
|
||||
"if x is Some(a) then { 4 } else { 9 }", exst!(
|
||||
IfExpression {
|
||||
discriminator: bx!(Discriminator::Simple(Meta::new(ex!(s "x")))),
|
||||
body: bx!(IfExpressionBody::SimplePatternMatch(Pattern::TupleStruct(qname!(Some),
|
||||
vec![Pattern::VarOrName(qname!(a))]), vec![exst!(s "4")], Some(vec![exst!(s "9")]))) }
|
||||
)
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"if x is Some(a) then 4 else 9", exst!(
|
||||
IfExpression {
|
||||
discriminator: bx!(Discriminator::Simple(Meta::new(ex!(s "x")))),
|
||||
body: bx!(IfExpressionBody::SimplePatternMatch(Pattern::TupleStruct(qname!(Some),
|
||||
vec![Pattern::VarOrName(qname!(a))]), vec![exst!(s "4")], Some(vec![exst!(s "9")]))) }
|
||||
)
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"if x is Something { a, b: x } then { 4 } else { 9 }", exst!(
|
||||
IfExpression {
|
||||
discriminator: bx!(Discriminator::Simple(Meta::new(ex!(s "x")))),
|
||||
body: bx!(IfExpressionBody::SimplePatternMatch(
|
||||
Pattern::Record(qname!(Something), vec![
|
||||
(rc!(a),Pattern::Literal(PatternLiteral::StringPattern(rc!(a)))),
|
||||
(rc!(b),Pattern::VarOrName(qname!(x)))
|
||||
]),
|
||||
vec![exst!(s "4")], Some(vec![exst!(s "9")])))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_literals() {
|
||||
parse_test_wrap_ast! {
|
||||
"if x is -1 then 1 else 2",
|
||||
exst!(
|
||||
IfExpression {
|
||||
discriminator: bx!(Discriminator::Simple(Meta::new(ex!(s "x")))),
|
||||
body: bx!(IfExpressionBody::SimplePatternMatch(
|
||||
Pattern::Literal(PatternLiteral::NumPattern { neg: true, num: NatLiteral(1) }),
|
||||
vec![exst!(NatLiteral(1))],
|
||||
Some(vec![exst!(NatLiteral(2))]),
|
||||
))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"if x is 1 then 1 else 2",
|
||||
exst!(
|
||||
IfExpression {
|
||||
discriminator: bx!(Discriminator::Simple(Meta::new(ex!(s "x")))),
|
||||
body: bx!(IfExpressionBody::SimplePatternMatch(
|
||||
Pattern::Literal(PatternLiteral::NumPattern { neg: false, num: NatLiteral(1) }),
|
||||
vec![exst!(s "1")],
|
||||
Some(vec![exst!(s "2")]),
|
||||
))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"if x is true then 1 else 2",
|
||||
exst!(
|
||||
IfExpression {
|
||||
discriminator: bx!(Discriminator::Simple(Meta::new(ex!(s "x")))),
|
||||
body: bx!(IfExpressionBody::SimplePatternMatch(
|
||||
Pattern::Literal(PatternLiteral::BoolPattern(true)),
|
||||
vec![exst!(NatLiteral(1))],
|
||||
Some(vec![exst!(NatLiteral(2))]),
|
||||
))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
parse_test_wrap_ast! {
|
||||
"if x is \"gnosticism\" then 1 else 2",
|
||||
exst!(
|
||||
IfExpression {
|
||||
discriminator: bx!(Discriminator::Simple(Meta::new(ex!(s "x")))),
|
||||
body: bx!(IfExpressionBody::SimplePatternMatch(
|
||||
Pattern::Literal(PatternLiteral::StringPattern(rc!(gnosticism))),
|
||||
vec![exst!(s "1")],
|
||||
Some(vec![exst!(s "2")]),
|
||||
))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
type Option<T> = Some(T) | None
|
||||
type Ord = LT | EQ | GT
|
||||
|
||||
|
||||
fn map(input: Option<T>, func: Func): Option<T> {
|
||||
if input {
|
||||
is Option::Some(x) -> Option::Some(func(x)),
|
||||
is Option::None -> Option::None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type Complicated = Sunrise | Metal { black: bool, norwegian: bool } | Fella(String, Int)
|
|
@ -0,0 +1,516 @@
|
|||
//! # Reduced AST
|
||||
//! The reduced AST is a minimal AST designed to be built from the full AST after all possible
|
||||
//! static checks have been done. Consequently, the AST reduction phase does very little error
|
||||
//! checking itself - any errors should ideally be caught either by an earlier phase, or are
|
||||
//! runtime errors that the evaluator should handle. That said, becuase it does do table lookups
|
||||
//! that can in principle fail [especially at the moment with most static analysis not yet complete],
|
||||
//! there is an Expr variant `ReductionError` to handle these cases.
|
||||
//!
|
||||
//! A design decision to make - should the ReducedAST types contain all information about
|
||||
//! type/layout necessary for the evaluator to work? If so, then the evaluator should not
|
||||
//! have access to the symbol table at all and ReducedAST should carry that information. If not,
|
||||
//! then ReducedAST shouldn't be duplicating information that can be queried at runtime from the
|
||||
//! symbol table. But I think the former might make sense since ultimately the bytecode will be
|
||||
//! built from the ReducedAST.
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::symbol_table::{Symbol, SymbolSpec, SymbolTable, FullyQualifiedSymbolName};
|
||||
use crate::builtin::Builtin;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReducedAST(pub Vec<Stmt>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Stmt {
|
||||
PreBinding {
|
||||
name: Rc<String>,
|
||||
func: Func,
|
||||
},
|
||||
Binding {
|
||||
name: Rc<String>,
|
||||
constant: bool,
|
||||
expr: Expr,
|
||||
},
|
||||
Expr(Expr),
|
||||
Noop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Expr {
|
||||
Unit,
|
||||
Lit(Lit),
|
||||
Tuple(Vec<Expr>),
|
||||
Func(Func),
|
||||
Sym(Rc<String>),
|
||||
Constructor {
|
||||
type_name: Rc<String>,
|
||||
name: Rc<String>,
|
||||
tag: usize,
|
||||
arity: usize, // n.b. arity here is always the value from the symbol table - if it doesn't match what it's being called with, that's an eval error, eval will handle it
|
||||
},
|
||||
Call {
|
||||
f: Box<Expr>,
|
||||
args: Vec<Expr>,
|
||||
},
|
||||
Assign {
|
||||
val: Box<Expr>,
|
||||
expr: Box<Expr>,
|
||||
},
|
||||
Conditional {
|
||||
cond: Box<Expr>,
|
||||
then_clause: Vec<Stmt>,
|
||||
else_clause: Vec<Stmt>,
|
||||
},
|
||||
ConditionalTargetSigilValue,
|
||||
CaseMatch {
|
||||
cond: Box<Expr>,
|
||||
alternatives: Vec<Alternative>
|
||||
},
|
||||
UnimplementedSigilValue,
|
||||
ReductionError(String),
|
||||
}
|
||||
|
||||
pub type BoundVars = Vec<Option<Rc<String>>>; //remember that order matters here
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Alternative {
|
||||
pub matchable: Subpattern,
|
||||
pub item: Vec<Stmt>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subpattern {
|
||||
pub tag: Option<usize>,
|
||||
pub subpatterns: Vec<Option<Subpattern>>,
|
||||
pub bound_vars: BoundVars,
|
||||
pub guard: Option<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Lit {
|
||||
Nat(u64),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
StringLit(Rc<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Func {
|
||||
BuiltIn(Builtin),
|
||||
UserDefined {
|
||||
name: Option<Rc<String>>,
|
||||
params: Vec<Rc<String>>,
|
||||
body: Vec<Stmt>,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reduce(ast: &AST, symbol_table: &SymbolTable) -> ReducedAST {
|
||||
let mut reducer = Reducer { symbol_table };
|
||||
reducer.ast(ast)
|
||||
}
|
||||
|
||||
struct Reducer<'a> {
|
||||
symbol_table: &'a SymbolTable
|
||||
}
|
||||
|
||||
impl<'a> Reducer<'a> {
|
||||
fn ast(&mut self, ast: &AST) -> ReducedAST {
|
||||
let mut output = vec![];
|
||||
for statement in ast.statements.iter() {
|
||||
output.push(self.statement(statement));
|
||||
}
|
||||
ReducedAST(output)
|
||||
}
|
||||
|
||||
fn statement(&mut self, stmt: &Statement) -> Stmt {
|
||||
match &stmt.kind {
|
||||
StatementKind::Expression(expr) => Stmt::Expr(self.expression(&expr)),
|
||||
StatementKind::Declaration(decl) => self.declaration(&decl),
|
||||
}
|
||||
}
|
||||
|
||||
fn block(&mut self, block: &Block) -> Vec<Stmt> {
|
||||
block.iter().map(|stmt| self.statement(stmt)).collect()
|
||||
}
|
||||
|
||||
fn invocation_argument(&mut self, invoc: &InvocationArgument) -> Expr {
|
||||
use crate::ast::InvocationArgument::*;
|
||||
match invoc {
|
||||
Positional(ex) => self.expression(ex),
|
||||
Keyword { .. } => Expr::UnimplementedSigilValue,
|
||||
Ignored => Expr::UnimplementedSigilValue,
|
||||
}
|
||||
}
|
||||
|
||||
fn expression(&mut self, expr: &Expression) -> Expr {
|
||||
use crate::ast::ExpressionKind::*;
|
||||
let symbol_table = self.symbol_table;
|
||||
let ref input = expr.kind;
|
||||
match input {
|
||||
NatLiteral(n) => Expr::Lit(Lit::Nat(*n)),
|
||||
FloatLiteral(f) => Expr::Lit(Lit::Float(*f)),
|
||||
StringLiteral(s) => Expr::Lit(Lit::StringLit(s.clone())),
|
||||
BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)),
|
||||
BinExp(binop, lhs, rhs) => self.binop(binop, lhs, rhs),
|
||||
PrefixExp(op, arg) => self.prefix(op, arg),
|
||||
Value(qualified_name) => {
|
||||
let ref id = qualified_name.id;
|
||||
let ref sym_name = match symbol_table.get_fqsn_from_id(id) {
|
||||
Some(fqsn) => fqsn,
|
||||
None => return Expr::ReductionError(format!("FQSN lookup for Value {:?} failed", qualified_name)),
|
||||
};
|
||||
//TODO this probably needs to change
|
||||
let FullyQualifiedSymbolName(ref v) = sym_name;
|
||||
let name = v.last().unwrap().name.clone();
|
||||
match symbol_table.lookup_by_fqsn(&sym_name) {
|
||||
Some(Symbol { spec: SymbolSpec::DataConstructor { index, type_args, type_name}, .. }) => Expr::Constructor {
|
||||
type_name: type_name.clone(),
|
||||
name: name.clone(),
|
||||
tag: index.clone(),
|
||||
arity: type_args.len(),
|
||||
},
|
||||
_ => Expr::Sym(name.clone()),
|
||||
}
|
||||
},
|
||||
Call { f, arguments } => self.reduce_call_expression(f, arguments),
|
||||
TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| self.expression(e)).collect()),
|
||||
IfExpression { discriminator, body } => self.reduce_if_expression(discriminator, body),
|
||||
Lambda { params, body, .. } => self.reduce_lambda(params, body),
|
||||
NamedStruct { name, fields } => self.reduce_named_struct(name, fields),
|
||||
Index { .. } => Expr::UnimplementedSigilValue,
|
||||
WhileExpression { .. } => Expr::UnimplementedSigilValue,
|
||||
ForExpression { .. } => Expr::UnimplementedSigilValue,
|
||||
ListLiteral { .. } => Expr::UnimplementedSigilValue,
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_lambda(&mut self, params: &Vec<FormalParam>, body: &Block) -> Expr {
|
||||
Expr::Func(Func::UserDefined {
|
||||
name: None,
|
||||
params: params.iter().map(|param| param.name.clone()).collect(),
|
||||
body: self.block(body),
|
||||
})
|
||||
}
|
||||
|
||||
fn reduce_named_struct(&mut self, name: &QualifiedName, fields: &Vec<(Rc<String>, Expression)>) -> Expr {
|
||||
let symbol_table = self.symbol_table;
|
||||
let ref sym_name = match symbol_table.get_fqsn_from_id(&name.id) {
|
||||
Some(fqsn) => fqsn,
|
||||
None => return Expr::ReductionError(format!("FQSN lookup for name {:?} failed", name)),
|
||||
};
|
||||
|
||||
let FullyQualifiedSymbolName(ref v) = sym_name;
|
||||
let ref name = v.last().unwrap().name;
|
||||
let (type_name, index, members_from_table) = match symbol_table.lookup_by_fqsn(&sym_name) {
|
||||
Some(Symbol { spec: SymbolSpec::RecordConstructor { members, type_name, index }, .. }) => (type_name.clone(), index, members),
|
||||
_ => return Expr::ReductionError("Not a record constructor".to_string()),
|
||||
};
|
||||
let arity = members_from_table.len();
|
||||
|
||||
let mut args: Vec<(Rc<String>, Expr)> = fields.iter()
|
||||
.map(|(name, expr)| (name.clone(), self.expression(expr)))
|
||||
.collect();
|
||||
|
||||
args.as_mut_slice()
|
||||
.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); //arbitrary - sorting by alphabetical order
|
||||
|
||||
let args = args.into_iter().map(|(_, expr)| expr).collect();
|
||||
|
||||
//TODO make sure this sorting actually works
|
||||
let f = box Expr::Constructor { type_name, name: name.clone(), tag: *index, arity, };
|
||||
Expr::Call { f, args }
|
||||
}
|
||||
|
||||
fn reduce_call_expression(&mut self, func: &Expression, arguments: &Vec<InvocationArgument>) -> Expr {
|
||||
Expr::Call {
|
||||
f: Box::new(self.expression(func)),
|
||||
args: arguments.iter().map(|arg| self.invocation_argument(arg)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce_if_expression(&mut self, discriminator: &Discriminator, body: &IfExpressionBody) -> Expr {
|
||||
let symbol_table = self.symbol_table;
|
||||
let cond = Box::new(match *discriminator {
|
||||
Discriminator::Simple(ref expr) => self.expression(expr),
|
||||
Discriminator::BinOp(ref _expr, ref _binop) => panic!("Can't yet handle binop discriminators")
|
||||
});
|
||||
match *body {
|
||||
IfExpressionBody::SimpleConditional(ref then_clause, ref else_clause) => {
|
||||
let then_clause = self.block(then_clause);
|
||||
let else_clause = match else_clause {
|
||||
None => vec![],
|
||||
Some(stmts) => self.block(stmts),
|
||||
};
|
||||
Expr::Conditional { cond, then_clause, else_clause }
|
||||
},
|
||||
IfExpressionBody::SimplePatternMatch(ref pat, ref then_clause, ref else_clause) => {
|
||||
let then_clause = self.block(then_clause);
|
||||
let else_clause = match else_clause {
|
||||
None => vec![],
|
||||
Some(stmts) => self.block(stmts),
|
||||
};
|
||||
|
||||
let alternatives = vec![
|
||||
pat.to_alternative(then_clause, symbol_table),
|
||||
Alternative {
|
||||
matchable: Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
bound_vars: vec![],
|
||||
guard: None,
|
||||
},
|
||||
item: else_clause
|
||||
},
|
||||
];
|
||||
|
||||
Expr::CaseMatch {
|
||||
cond,
|
||||
alternatives,
|
||||
}
|
||||
},
|
||||
IfExpressionBody::GuardList(ref guard_arms) => {
|
||||
let mut alternatives = vec![];
|
||||
for arm in guard_arms {
|
||||
match arm.guard {
|
||||
Guard::Pat(ref p) => {
|
||||
let item = self.block(&arm.body);
|
||||
let alt = p.to_alternative(item, symbol_table);
|
||||
alternatives.push(alt);
|
||||
},
|
||||
Guard::HalfExpr(HalfExpr { op: _, expr: _ }) => {
|
||||
return Expr::UnimplementedSigilValue
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::CaseMatch { cond, alternatives }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn binop(&mut self, binop: &BinOp, lhs: &Box<Expression>, rhs: &Box<Expression>) -> Expr {
|
||||
let operation = Builtin::from_str(binop.sigil()).ok();
|
||||
match operation {
|
||||
Some(Builtin::Assignment) => Expr::Assign {
|
||||
val: Box::new(self.expression(&*lhs)),
|
||||
expr: Box::new(self.expression(&*rhs)),
|
||||
},
|
||||
Some(op) => {
|
||||
let f = Box::new(Expr::Func(Func::BuiltIn(op)));
|
||||
Expr::Call { f, args: vec![self.expression(&*lhs), self.expression(&*rhs)] }
|
||||
},
|
||||
None => {
|
||||
//TODO handle a user-defined operation
|
||||
Expr::UnimplementedSigilValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prefix(&mut self, prefix: &PrefixOp, arg: &Box<Expression>) -> Expr {
|
||||
match prefix.builtin {
|
||||
Some(op) => {
|
||||
let f = Box::new(Expr::Func(Func::BuiltIn(op)));
|
||||
Expr::Call { f, args: vec![self.expression(arg)] }
|
||||
},
|
||||
None => { //TODO need this for custom prefix ops
|
||||
Expr::UnimplementedSigilValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn declaration(&mut self, declaration: &Declaration) -> Stmt {
|
||||
use self::Declaration::*;
|
||||
match declaration {
|
||||
Binding {name, constant, expr, .. } => Stmt::Binding { name: name.clone(), constant: *constant, expr: self.expression(expr) },
|
||||
FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding {
|
||||
name: name.clone(),
|
||||
func: Func::UserDefined {
|
||||
name: Some(name.clone()),
|
||||
params: params.iter().map(|param| param.name.clone()).collect(),
|
||||
body: self.block(&statements),
|
||||
}
|
||||
},
|
||||
TypeDecl { .. } => Stmt::Noop,
|
||||
TypeAlias(_, _) => Stmt::Noop,
|
||||
Interface { .. } => Stmt::Noop,
|
||||
Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue),
|
||||
_ => Stmt::Expr(Expr::UnimplementedSigilValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ig var pat
|
||||
* x is SomeBigOldEnum(_, x, Some(t))
|
||||
*/
|
||||
|
||||
fn handle_symbol(symbol: Option<&Symbol>, inner_patterns: &Vec<Pattern>, symbol_table: &SymbolTable) -> Subpattern {
|
||||
use self::Pattern::*;
|
||||
let tag = symbol.map(|symbol| match symbol.spec {
|
||||
SymbolSpec::DataConstructor { index, .. } => index.clone(),
|
||||
_ => panic!("Symbol is not a data constructor - this should've been caught in type-checking"),
|
||||
});
|
||||
let bound_vars = inner_patterns.iter().map(|p| match p {
|
||||
VarOrName(qualified_name) => {
|
||||
let fqsn = symbol_table.get_fqsn_from_id(&qualified_name.id);
|
||||
let symbol_exists = fqsn.and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)).is_some();
|
||||
if symbol_exists {
|
||||
None
|
||||
} else {
|
||||
let QualifiedName { components, .. } = qualified_name;
|
||||
if components.len() == 1 {
|
||||
Some(components[0].clone())
|
||||
} else {
|
||||
panic!("Bad variable name in pattern");
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => None,
|
||||
}).collect();
|
||||
|
||||
let subpatterns = inner_patterns.iter().map(|p| match p {
|
||||
Ignored => None,
|
||||
VarOrName(_) => None,
|
||||
Literal(other) => Some(other.to_subpattern(symbol_table)),
|
||||
tp @ TuplePattern(_) => Some(tp.to_subpattern(symbol_table)),
|
||||
ts @ TupleStruct(_, _) => Some(ts.to_subpattern(symbol_table)),
|
||||
Record(..) => unimplemented!(),
|
||||
}).collect();
|
||||
|
||||
let guard = None;
|
||||
/*
|
||||
let guard_equality_exprs: Vec<Expr> = subpatterns.iter().map(|p| match p {
|
||||
Literal(lit) => match lit {
|
||||
_ => unimplemented!()
|
||||
},
|
||||
_ => unimplemented!()
|
||||
}).collect();
|
||||
*/
|
||||
|
||||
Subpattern {
|
||||
tag,
|
||||
subpatterns,
|
||||
guard,
|
||||
bound_vars,
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
fn to_alternative(&self, item: Vec<Stmt>, symbol_table: &SymbolTable) -> Alternative {
|
||||
let s = self.to_subpattern(symbol_table);
|
||||
Alternative {
|
||||
matchable: Subpattern {
|
||||
tag: s.tag,
|
||||
subpatterns: s.subpatterns,
|
||||
bound_vars: s.bound_vars,
|
||||
guard: s.guard,
|
||||
},
|
||||
item
|
||||
}
|
||||
}
|
||||
|
||||
fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern {
|
||||
use self::Pattern::*;
|
||||
match self {
|
||||
TupleStruct(QualifiedName{ components, id }, inner_patterns) => {
|
||||
let fqsn = symbol_table.get_fqsn_from_id(&id);
|
||||
match fqsn.and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)) {
|
||||
Some(symbol) => handle_symbol(Some(symbol), inner_patterns, symbol_table),
|
||||
None => {
|
||||
panic!("Symbol {:?} not found", components);
|
||||
}
|
||||
}
|
||||
},
|
||||
TuplePattern(inner_patterns) => handle_symbol(None, inner_patterns, symbol_table),
|
||||
Record(_name, _pairs) => {
|
||||
unimplemented!()
|
||||
},
|
||||
Ignored => Subpattern { tag: None, subpatterns: vec![], guard: None, bound_vars: vec![] },
|
||||
Literal(lit) => lit.to_subpattern(symbol_table),
|
||||
VarOrName(QualifiedName { components, id }) => {
|
||||
// if fqsn is Some, treat this as a symbol pattern. If it's None, treat it
|
||||
// as a variable.
|
||||
println!("Calling VarOrName reduction with : {:?}", components);
|
||||
let fqsn = symbol_table.get_fqsn_from_id(&id);
|
||||
match fqsn.and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)) {
|
||||
Some(symbol) => handle_symbol(Some(symbol), &vec![], symbol_table),
|
||||
None => {
|
||||
let name = if components.len() == 1 {
|
||||
components[0].clone()
|
||||
} else {
|
||||
panic!("check this line of code yo");
|
||||
};
|
||||
Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard: None,
|
||||
bound_vars: vec![Some(name.clone())],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PatternLiteral {
|
||||
fn to_subpattern(&self, _symbol_table: &SymbolTable) -> Subpattern {
|
||||
use self::PatternLiteral::*;
|
||||
match self {
|
||||
NumPattern { neg, num } => {
|
||||
let comparison = Expr::Lit(match (neg, num) {
|
||||
(false, ExpressionKind::NatLiteral(n)) => Lit::Nat(*n),
|
||||
(false, ExpressionKind::FloatLiteral(f)) => Lit::Float(*f),
|
||||
(true, ExpressionKind::NatLiteral(n)) => Lit::Int(-1*(*n as i64)),
|
||||
(true, ExpressionKind::FloatLiteral(f)) => Lit::Float(-1.0*f),
|
||||
_ => panic!("This should never happen")
|
||||
});
|
||||
let guard = Some(Expr::Call {
|
||||
f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))),
|
||||
args: vec![comparison, Expr::ConditionalTargetSigilValue],
|
||||
});
|
||||
Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard,
|
||||
bound_vars: vec![],
|
||||
}
|
||||
},
|
||||
StringPattern(s) => {
|
||||
let guard = Some(Expr::Call {
|
||||
f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))),
|
||||
args: vec![Expr::Lit(Lit::StringLit(s.clone())), Expr::ConditionalTargetSigilValue]
|
||||
});
|
||||
|
||||
Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard,
|
||||
bound_vars: vec![],
|
||||
}
|
||||
},
|
||||
BoolPattern(b) => {
|
||||
let guard = Some(if *b {
|
||||
Expr::ConditionalTargetSigilValue
|
||||
} else {
|
||||
Expr::Call {
|
||||
f: Box::new(Expr::Func(Func::BuiltIn(Builtin::BooleanNot))),
|
||||
args: vec![Expr::ConditionalTargetSigilValue]
|
||||
}
|
||||
});
|
||||
Subpattern {
|
||||
tag: None,
|
||||
subpatterns: vec![],
|
||||
guard,
|
||||
bound_vars: vec![],
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,322 @@
|
|||
use stopwatch::Stopwatch;
|
||||
|
||||
use std::time::Duration;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use schala_repl::{ProgrammingLanguageInterface,
|
||||
ComputationRequest, ComputationResponse,
|
||||
LangMetaRequest, LangMetaResponse, GlobalOutputStats,
|
||||
DebugResponse, DebugAsk};
|
||||
use crate::{ast, reduced_ast, tokenizing, parsing, eval, typechecking, symbol_table};
|
||||
|
||||
/// All the state necessary to parse and execute a Schala program are stored in this struct.
|
||||
/// `state` represents the execution state for the AST-walking interpreter, the other fields
|
||||
/// should be self-explanatory.
|
||||
pub struct Schala {
|
||||
source_reference: SourceReference,
|
||||
state: eval::State<'static>,
|
||||
symbol_table: Rc<RefCell<symbol_table::SymbolTable>>,
|
||||
type_context: typechecking::TypeContext<'static>,
|
||||
active_parser: Option<parsing::Parser>,
|
||||
}
|
||||
|
||||
impl Schala {
|
||||
fn handle_docs(&self, source: String) -> LangMetaResponse {
|
||||
LangMetaResponse::Docs {
|
||||
doc_string: format!("Schala item `{}` : <<Schala-lang documentation not yet implemented>>", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Schala {
|
||||
/// Creates a new Schala environment *without* any prelude.
|
||||
fn new_blank_env() -> Schala {
|
||||
let symbols = Rc::new(RefCell::new(symbol_table::SymbolTable::new()));
|
||||
Schala {
|
||||
source_reference: SourceReference::new(),
|
||||
symbol_table: symbols.clone(),
|
||||
state: eval::State::new(symbols),
|
||||
type_context: typechecking::TypeContext::new(),
|
||||
active_parser: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Schala environment with the standard prelude, which is defined as ordinary
|
||||
/// Schala code in the file `prelude.schala`
|
||||
pub fn new() -> Schala {
|
||||
let prelude = include_str!("prelude.schala");
|
||||
let mut s = Schala::new_blank_env();
|
||||
|
||||
let request = ComputationRequest { source: prelude, debug_requests: HashSet::default() };
|
||||
s.run_computation(request);
|
||||
s
|
||||
}
|
||||
|
||||
fn handle_debug_immediate(&self, request: DebugAsk) -> DebugResponse {
|
||||
use DebugAsk::*;
|
||||
match request {
|
||||
Timing => DebugResponse { ask: Timing, value: format!("Invalid") },
|
||||
ByStage { stage_name, token } => match &stage_name[..] {
|
||||
"symbol-table" => {
|
||||
let value = self.symbol_table.borrow().debug_symbol_table();
|
||||
DebugResponse {
|
||||
ask: ByStage { stage_name: format!("symbol-table"), token },
|
||||
value
|
||||
}
|
||||
},
|
||||
s => {
|
||||
DebugResponse {
|
||||
ask: ByStage { stage_name: s.to_string(), token: None },
|
||||
value: format!("Not-implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tokenizing(input: &str, _handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<Vec<tokenizing::Token>, String> {
|
||||
let tokens = tokenizing::tokenize(input);
|
||||
comp.map(|comp| {
|
||||
let token_string = tokens.iter().map(|t| t.to_string_with_metadata()).join(", ");
|
||||
comp.add_artifact(token_string);
|
||||
});
|
||||
|
||||
let errors: Vec<String> = tokens.iter().filter_map(|t| t.get_error()).collect();
|
||||
if errors.len() == 0 {
|
||||
Ok(tokens)
|
||||
} else {
|
||||
Err(format!("{:?}", errors))
|
||||
}
|
||||
}
|
||||
|
||||
fn parsing(input: Vec<tokenizing::Token>, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
|
||||
use crate::parsing::Parser;
|
||||
use ParsingDebugType::*;
|
||||
|
||||
let mut parser = handle.active_parser.take().unwrap_or_else(|| Parser::new(input));
|
||||
let ast = parser.parse();
|
||||
|
||||
comp.map(|comp| {
|
||||
let debug_format = comp.parsing.as_ref().unwrap_or(&CompactAST);
|
||||
let debug_info = match debug_format {
|
||||
CompactAST => match ast{
|
||||
Ok(ref ast) => ast.compact_debug(),
|
||||
Err(_) => "Error - see output".to_string(),
|
||||
},
|
||||
ExpandedAST => match ast{
|
||||
Ok(ref ast) => ast.expanded_debug(),
|
||||
Err(_) => "Error - see output".to_string(),
|
||||
},
|
||||
Trace => parser.format_parse_trace(),
|
||||
};
|
||||
comp.add_artifact(debug_info);
|
||||
});
|
||||
ast.map_err(|err| format_parse_error(err, handle))
|
||||
}
|
||||
|
||||
fn format_parse_error(error: parsing::ParseError, handle: &mut Schala) -> String {
|
||||
let line_num = error.token.line_num;
|
||||
let ch = error.token.char_num;
|
||||
let line_from_program = handle.source_reference.get_line(line_num);
|
||||
let location_pointer = format!("{}^", " ".repeat(ch));
|
||||
|
||||
let line_num_digits = format!("{}", line_num).chars().count();
|
||||
let space_padding = " ".repeat(line_num_digits);
|
||||
|
||||
format!(r#"
|
||||
{error_msg}
|
||||
{space_padding} |
|
||||
{line_num} | {}
|
||||
{space_padding} | {}
|
||||
"#, line_from_program, location_pointer, error_msg=error.msg, space_padding=space_padding, line_num=line_num)
|
||||
}
|
||||
|
||||
fn symbol_table(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
|
||||
let () = handle.symbol_table.borrow_mut().add_top_level_symbols(&input)?;
|
||||
comp.map(|comp| {
|
||||
let debug = handle.symbol_table.borrow().debug_symbol_table();
|
||||
comp.add_artifact(debug);
|
||||
});
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn scope_resolution(mut input: ast::AST, handle: &mut Schala, _com: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
|
||||
let mut symbol_table = handle.symbol_table.borrow_mut();
|
||||
let mut resolver = crate::scope_resolution::ScopeResolver::new(&mut symbol_table);
|
||||
let () = resolver.resolve(&mut input)?;
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn typechecking(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
|
||||
let result = handle.type_context.typecheck(&input);
|
||||
|
||||
comp.map(|comp| {
|
||||
comp.add_artifact(match result {
|
||||
Ok(ty) => ty.to_string(),
|
||||
Err(err) => format!("Type error: {}", err.msg)
|
||||
});
|
||||
});
|
||||
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
fn ast_reducing(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<reduced_ast::ReducedAST, String> {
|
||||
let ref symbol_table = handle.symbol_table.borrow();
|
||||
let output = reduced_ast::reduce(&input, symbol_table);
|
||||
comp.map(|comp| comp.add_artifact(format!("{:?}", output)));
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn eval(input: reduced_ast::ReducedAST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<String, String> {
|
||||
comp.map(|comp| comp.add_artifact(handle.state.debug_print()));
|
||||
let evaluation_outputs = handle.state.evaluate(input, true);
|
||||
let text_output: Result<Vec<String>, String> = evaluation_outputs
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let eval_output: Result<String, String> = text_output
|
||||
.map(|v| { v.into_iter().intersperse(format!("\n")).collect() });
|
||||
eval_output
|
||||
}
|
||||
|
||||
/// Represents lines of source code
|
||||
struct SourceReference {
|
||||
lines: Option<Vec<String>>
|
||||
}
|
||||
|
||||
impl SourceReference {
|
||||
fn new() -> SourceReference {
|
||||
SourceReference { lines: None }
|
||||
}
|
||||
|
||||
fn load_new_source(&mut self, source: &str) {
|
||||
//TODO this is a lot of heap allocations - maybe there's a way to make it more efficient?
|
||||
self.lines = Some(source.lines().map(|s| s.to_string()).collect()); }
|
||||
|
||||
fn get_line(&self, line: usize) -> String {
|
||||
self.lines.as_ref().and_then(|x| x.get(line).map(|s| s.to_string())).unwrap_or(format!("NO LINE FOUND"))
|
||||
}
|
||||
}
|
||||
|
||||
enum ParsingDebugType {
|
||||
CompactAST,
|
||||
ExpandedAST,
|
||||
Trace
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PassDebugArtifact {
|
||||
parsing: Option<ParsingDebugType>,
|
||||
artifacts: Vec<String>
|
||||
|
||||
}
|
||||
impl PassDebugArtifact {
|
||||
fn add_artifact(&mut self, artifact: String) {
|
||||
self.artifacts.push(artifact)
|
||||
}
|
||||
}
|
||||
|
||||
fn stage_names() -> Vec<&'static str> {
|
||||
vec![
|
||||
"tokenizing",
|
||||
"parsing",
|
||||
"symbol-table",
|
||||
"scope-resolution",
|
||||
"typechecking",
|
||||
"ast-reduction",
|
||||
"ast-walking-evaluation"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
impl ProgrammingLanguageInterface for Schala {
|
||||
fn get_language_name(&self) -> String { format!("Schala") }
|
||||
fn get_source_file_suffix(&self) -> String { format!("schala") }
|
||||
|
||||
fn run_computation(&mut self, request: ComputationRequest) -> ComputationResponse {
|
||||
struct PassToken<'a> {
|
||||
schala: &'a mut Schala,
|
||||
stage_durations: &'a mut Vec<(String, Duration)>,
|
||||
sw: &'a Stopwatch,
|
||||
debug_requests: &'a HashSet<DebugAsk>,
|
||||
debug_responses: &'a mut Vec<DebugResponse>,
|
||||
}
|
||||
|
||||
fn output_wrapper<Input, Output, F>(n: usize, func: F, input: Input, token: &mut PassToken) -> Result<Output, String>
|
||||
where F: Fn(Input, &mut Schala, Option<&mut PassDebugArtifact>) -> Result<Output, String>
|
||||
{
|
||||
let stage_names = stage_names();
|
||||
let cur_stage_name = stage_names[n];
|
||||
let ask = token.debug_requests.iter().find(|ask| ask.is_for_stage(cur_stage_name));
|
||||
|
||||
let parsing = match ask {
|
||||
Some(DebugAsk::ByStage { token, .. }) if cur_stage_name == "parsing" => Some(
|
||||
token.as_ref().map(|token| match &token[..] {
|
||||
"compact" => ParsingDebugType::CompactAST,
|
||||
"expanded" => ParsingDebugType::ExpandedAST,
|
||||
"trace" => ParsingDebugType::Trace,
|
||||
_ => ParsingDebugType::CompactAST,
|
||||
}).unwrap_or(ParsingDebugType::CompactAST)
|
||||
),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut debug_artifact = ask.map(|_| PassDebugArtifact {
|
||||
parsing, ..Default::default()
|
||||
});
|
||||
|
||||
let output = func(input, token.schala, debug_artifact.as_mut());
|
||||
|
||||
//TODO I think this is not counting the time since the *previous* stage
|
||||
token.stage_durations.push((cur_stage_name.to_string(), token.sw.elapsed()));
|
||||
if let Some(artifact) = debug_artifact {
|
||||
for value in artifact.artifacts.into_iter() {
|
||||
let resp = DebugResponse { ask: ask.unwrap().clone(), value };
|
||||
token.debug_responses.push(resp);
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
let ComputationRequest { source, debug_requests } = request;
|
||||
self.source_reference.load_new_source(source);
|
||||
let sw = Stopwatch::start_new();
|
||||
let mut stage_durations = Vec::new();
|
||||
let mut debug_responses = Vec::new();
|
||||
let mut tok = PassToken { schala: self, stage_durations: &mut stage_durations, sw: &sw, debug_requests: &debug_requests, debug_responses: &mut debug_responses };
|
||||
|
||||
let main_output: Result<String, String> = Ok(source)
|
||||
.and_then(|source| output_wrapper(0, tokenizing, source, &mut tok))
|
||||
.and_then(|tokens| output_wrapper(1, parsing, tokens, &mut tok))
|
||||
.and_then(|ast| output_wrapper(2, symbol_table, ast, &mut tok))
|
||||
.and_then(|ast| output_wrapper(3, scope_resolution, ast, &mut tok))
|
||||
.and_then(|ast| output_wrapper(4, typechecking, ast, &mut tok))
|
||||
.and_then(|ast| output_wrapper(5, ast_reducing, ast, &mut tok))
|
||||
.and_then(|reduced_ast| output_wrapper(6, eval, reduced_ast, &mut tok));
|
||||
|
||||
let total_duration = sw.elapsed();
|
||||
let global_output_stats = GlobalOutputStats {
|
||||
total_duration, stage_durations
|
||||
};
|
||||
|
||||
ComputationResponse {
|
||||
main_output,
|
||||
global_output_stats,
|
||||
debug_responses,
|
||||
}
|
||||
}
|
||||
|
||||
fn request_meta(&mut self, request: LangMetaRequest) -> LangMetaResponse {
|
||||
match request {
|
||||
LangMetaRequest::StageNames => LangMetaResponse::StageNames(stage_names().iter().map(|s| s.to_string()).collect()),
|
||||
LangMetaRequest::Docs { source } => self.handle_docs(source),
|
||||
LangMetaRequest::ImmediateDebug(debug_request) =>
|
||||
LangMetaResponse::ImmediateDebug(self.handle_debug_immediate(debug_request)),
|
||||
LangMetaRequest::Custom { .. } => LangMetaResponse::Custom { kind: format!("not-implemented"), value: format!("") }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
use crate::symbol_table::{SymbolTable, ScopeSegment, ScopeSegmentKind, FullyQualifiedSymbolName};
|
||||
use crate::ast::*;
|
||||
|
||||
pub struct ScopeResolver<'a> {
|
||||
symbol_table: &'a mut SymbolTable
|
||||
}
|
||||
|
||||
impl<'a> ScopeResolver<'a> {
|
||||
pub fn new(symbol_table: &'a mut SymbolTable) -> ScopeResolver {
|
||||
ScopeResolver { symbol_table }
|
||||
}
|
||||
pub fn resolve(&mut self, ast: &mut AST) -> Result<(), String> {
|
||||
for statement in ast.statements.iter() {
|
||||
match statement.kind {
|
||||
StatementKind::Declaration(ref decl) => self.decl(decl),
|
||||
StatementKind::Expression(ref expr) => self.expr(expr),
|
||||
}?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decl(&mut self, decl: &Declaration) -> Result<(), String> {
|
||||
use Declaration::*;
|
||||
match decl {
|
||||
Binding { expr, .. } => self.expr(expr),
|
||||
FuncDecl(_, block) => self.block(block),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
fn block(&mut self, block: &Block) -> Result<(), String> {
|
||||
for statement in block.iter() {
|
||||
match statement.kind {
|
||||
StatementKind::Declaration(ref decl) => self.decl(decl),
|
||||
StatementKind::Expression(ref expr) => self.expr(expr),
|
||||
}?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr(&mut self, expr: &Expression) -> Result<(), String> {
|
||||
use ExpressionKind::*;
|
||||
match &expr.kind {
|
||||
ExpressionKind::Value(qualified_name) => {
|
||||
let fqsn = lookup_name_in_scope(&qualified_name);
|
||||
let ref id = qualified_name.id;
|
||||
self.symbol_table.map_id_to_fqsn(id, fqsn);
|
||||
},
|
||||
NamedStruct { name, .. } => {
|
||||
let ref id = name.id;
|
||||
let fqsn = lookup_name_in_scope(&name);
|
||||
self.symbol_table.map_id_to_fqsn(id, fqsn);
|
||||
},
|
||||
BinExp(_, ref lhs, ref rhs) => {
|
||||
self.expr(lhs)?;
|
||||
self.expr(rhs)?;
|
||||
},
|
||||
PrefixExp(_, ref arg) => {
|
||||
self.expr(arg)?;
|
||||
},
|
||||
TupleLiteral(exprs) => {
|
||||
for expr in exprs.iter() {
|
||||
self.expr(expr)?;
|
||||
}
|
||||
},
|
||||
Call { f, arguments } => {
|
||||
self.expr(&f)?;
|
||||
for arg in arguments.iter() {
|
||||
self.invoc(arg)?;
|
||||
}
|
||||
},
|
||||
Lambda { params, body, .. } => {
|
||||
self.block(&body)?;
|
||||
for param in params.iter() {
|
||||
if let Some(ref expr) = param.default {
|
||||
self.expr(expr)?;
|
||||
}
|
||||
}
|
||||
},
|
||||
IfExpression { ref body, ref discriminator } => {
|
||||
match &**discriminator {
|
||||
Discriminator::Simple(expr) | Discriminator::BinOp(expr, _) => self.expr(expr)?
|
||||
};
|
||||
|
||||
match &**body {
|
||||
IfExpressionBody::SimplePatternMatch(ref pat, ref alt1, ref alt2) => {
|
||||
self.pattern(pat)?;
|
||||
self.block(alt1)?;
|
||||
if let Some(alt) = alt2 {
|
||||
self.block(alt)?;
|
||||
}
|
||||
},
|
||||
IfExpressionBody::GuardList(guardarms) => {
|
||||
for arm in guardarms.iter() {
|
||||
if let Guard::Pat(ref pat) = arm.guard {
|
||||
self.pattern(pat)?;
|
||||
}
|
||||
self.block(&arm.body)?;
|
||||
}
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
},
|
||||
_ => ()
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn invoc(&mut self, invoc: &InvocationArgument) -> Result<(), String> {
|
||||
use InvocationArgument::*;
|
||||
match invoc {
|
||||
Positional(expr) => self.expr(expr),
|
||||
Keyword { expr, .. } => self.expr(expr),
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn pattern(&mut self, pat: &Pattern) -> Result<(), String> {
|
||||
use Pattern::*;
|
||||
match pat {
|
||||
Ignored => (),
|
||||
TuplePattern(patterns) => {
|
||||
for pat in patterns {
|
||||
self.pattern(pat)?;
|
||||
}
|
||||
},
|
||||
Literal(_) => (),
|
||||
TupleStruct(name, patterns) => {
|
||||
self.qualified_name_in_pattern(name);
|
||||
for pat in patterns {
|
||||
self.pattern(pat)?;
|
||||
}
|
||||
},
|
||||
Record(name, key_patterns) => {
|
||||
self.qualified_name_in_pattern(name);
|
||||
for (_, pat) in key_patterns {
|
||||
self.pattern(pat)?;
|
||||
}
|
||||
},
|
||||
VarOrName(name) => {
|
||||
self.qualified_name_in_pattern(name);
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// this might be a variable or a pattern. if a variable, set to none
|
||||
fn qualified_name_in_pattern(&mut self, qualified_name: &QualifiedName) {
|
||||
let ref id = qualified_name.id;
|
||||
let fqsn = lookup_name_in_scope(qualified_name);
|
||||
if self.symbol_table.lookup_by_fqsn(&fqsn).is_some() {
|
||||
self.symbol_table.map_id_to_fqsn(&id, fqsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO this is incomplete
|
||||
fn lookup_name_in_scope(sym_name: &QualifiedName) -> FullyQualifiedSymbolName {
|
||||
let QualifiedName { components: vec, .. } = sym_name;
|
||||
let len = vec.len();
|
||||
let new_vec: Vec<ScopeSegment> = vec.iter().enumerate().map(|(i, name)| {
|
||||
let kind = if i == (len - 1) {
|
||||
ScopeSegmentKind::Terminal
|
||||
} else {
|
||||
ScopeSegmentKind::Type
|
||||
};
|
||||
ScopeSegment { name: name.clone(), kind }
|
||||
}).collect();
|
||||
FullyQualifiedSymbolName(new_vec)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn basic_scope() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,464 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::rc::Rc;
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
|
||||
use crate::ast;
|
||||
use crate::ast::{ItemId, TypeBody, TypeSingletonName, Signature, Statement, StatementKind};
|
||||
use crate::typechecking::TypeName;
|
||||
|
||||
type LineNumber = u32;
|
||||
type SymbolTrackTable = HashMap<Rc<String>, LineNumber>;
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug, Clone)]
|
||||
pub struct FullyQualifiedSymbolName(pub Vec<ScopeSegment>);
|
||||
|
||||
impl fmt::Display for FullyQualifiedSymbolName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let FullyQualifiedSymbolName(v) = self;
|
||||
for segment in v {
|
||||
write!(f, "::{}", segment)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct ScopeSegment {
|
||||
pub name: Rc<String>, //TODO maybe this could be a &str, for efficiency?
|
||||
pub kind: ScopeSegmentKind,
|
||||
}
|
||||
|
||||
impl fmt::Display for ScopeSegment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use ScopeSegmentKind::*;
|
||||
let kind = match self.kind {
|
||||
Function => "fn",
|
||||
Type => "ty",
|
||||
Terminal => "tr",
|
||||
};
|
||||
write!(f, "{}({})", self.name, kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScopeSegment {
|
||||
pub fn new(name: Rc<String>, kind: ScopeSegmentKind) -> ScopeSegment {
|
||||
ScopeSegment { name, kind }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ScopeSegmentKind {
|
||||
Function,
|
||||
Type,
|
||||
Terminal,
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! fqsn {
|
||||
( $( $name:expr ; $kind:tt),* ) => {
|
||||
{
|
||||
let mut vec = vec![];
|
||||
$(
|
||||
vec.push(ScopeSegment::new(
|
||||
Rc::new($name.to_string()),
|
||||
sym_path_kind!($kind),
|
||||
));
|
||||
)*
|
||||
FullyQualifiedSymbolName(vec)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! sym_path_kind {
|
||||
(fn) => { ScopeSegmentKind::Function };
|
||||
(ty) => { ScopeSegmentKind::Type };
|
||||
(tr) => { ScopeSegmentKind::Terminal };
|
||||
}
|
||||
|
||||
//cf. p. 150 or so of Language Implementation Patterns
|
||||
pub struct SymbolTable {
|
||||
symbol_path_to_symbol: HashMap<FullyQualifiedSymbolName, Symbol>,
|
||||
id_to_fqsn: HashMap<ItemId, FullyQualifiedSymbolName>,
|
||||
}
|
||||
|
||||
//TODO add various types of lookups here, maybe multiple hash tables internally?
|
||||
impl SymbolTable {
|
||||
pub fn new() -> SymbolTable {
|
||||
SymbolTable {
|
||||
symbol_path_to_symbol: HashMap::new(),
|
||||
id_to_fqsn: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_id_to_fqsn(&mut self, id: &ItemId, fqsn: FullyQualifiedSymbolName) {
|
||||
self.id_to_fqsn.insert(id.clone(), fqsn);
|
||||
}
|
||||
|
||||
pub fn get_fqsn_from_id(&self, id: &ItemId) -> Option<FullyQualifiedSymbolName> {
|
||||
self.id_to_fqsn.get(&id).cloned()
|
||||
}
|
||||
|
||||
fn add_new_symbol(&mut self, name: &Rc<String>, scope_path: &Vec<ScopeSegment>, spec: SymbolSpec) {
|
||||
let mut vec: Vec<ScopeSegment> = scope_path.clone();
|
||||
vec.push(ScopeSegment { name: name.clone(), kind: ScopeSegmentKind::Terminal });
|
||||
let fully_qualified_name = FullyQualifiedSymbolName(vec);
|
||||
let symbol = Symbol { name: name.clone(), fully_qualified_name: fully_qualified_name.clone(), spec };
|
||||
self.symbol_path_to_symbol.insert(fully_qualified_name, symbol);
|
||||
}
|
||||
|
||||
pub fn lookup_by_fqsn(&self, fully_qualified_path: &FullyQualifiedSymbolName) -> Option<&Symbol> {
|
||||
self.symbol_path_to_symbol.get(fully_qualified_path)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Symbol {
|
||||
pub name: Rc<String>, //TODO does this need to be pub?
|
||||
fully_qualified_name: FullyQualifiedSymbolName,
|
||||
pub spec: SymbolSpec,
|
||||
}
|
||||
|
||||
impl fmt::Display for Symbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "<Name: {}, Spec: {}>", self.name, self.spec)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SymbolSpec {
|
||||
Func(Vec<TypeName>),
|
||||
DataConstructor {
|
||||
index: usize,
|
||||
type_name: TypeName,
|
||||
type_args: Vec<Rc<String>>,
|
||||
},
|
||||
RecordConstructor {
|
||||
index: usize,
|
||||
members: HashMap<Rc<String>, TypeName>,
|
||||
type_name: TypeName,
|
||||
},
|
||||
Binding
|
||||
}
|
||||
|
||||
impl fmt::Display for SymbolSpec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::SymbolSpec::*;
|
||||
match self {
|
||||
Func(type_names) => write!(f, "Func({:?})", type_names),
|
||||
DataConstructor { index, type_name, type_args } => write!(f, "DataConstructor(idx: {})({:?} -> {})", index, type_args, type_name),
|
||||
RecordConstructor { type_name, index, ..} => write!(f, "RecordConstructor(idx: {})(<members> -> {})", index, type_name),
|
||||
Binding => write!(f, "Binding"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolTable {
|
||||
/* note: this adds names for *forward reference* but doesn't actually create any types. solve that problem
|
||||
* later */
|
||||
|
||||
pub fn add_top_level_symbols(&mut self, ast: &ast::AST) -> Result<(), String> {
|
||||
let mut scope_name_stack = Vec::new();
|
||||
self.add_symbols_from_scope(&ast.statements, &mut scope_name_stack)
|
||||
}
|
||||
|
||||
fn add_symbols_from_scope<'a>(&'a mut self, statements: &Vec<Statement>, scope_name_stack: &mut Vec<ScopeSegment>) -> Result<(), String> {
|
||||
use self::ast::Declaration::*;
|
||||
|
||||
fn insert_and_check_duplicate_symbol(table: &mut SymbolTrackTable, name: &Rc<String>) -> Result<(), String> {
|
||||
match table.entry(name.clone()) {
|
||||
Entry::Occupied(o) => {
|
||||
let line_number = o.get(); //TODO make this actually work
|
||||
Err(format!("Duplicate definition: {}. It's already defined at {}", name, line_number))
|
||||
},
|
||||
Entry::Vacant(v) => {
|
||||
let line_number = 0; //TODO should work
|
||||
v.insert(line_number);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen_identifiers: SymbolTrackTable = HashMap::new();
|
||||
|
||||
for statement in statements.iter() {
|
||||
if let Statement { kind: StatementKind::Declaration(decl), .. } = statement {
|
||||
match decl {
|
||||
FuncSig(ref signature) => {
|
||||
insert_and_check_duplicate_symbol(&mut seen_identifiers, &signature.name)?;
|
||||
self.add_function_signature(signature, scope_name_stack)?
|
||||
}
|
||||
FuncDecl(ref signature, ref body) => {
|
||||
insert_and_check_duplicate_symbol(&mut seen_identifiers, &signature.name)?;
|
||||
self.add_function_signature(signature, scope_name_stack)?;
|
||||
scope_name_stack.push(ScopeSegment{
|
||||
name: signature.name.clone(),
|
||||
kind: ScopeSegmentKind::Function,
|
||||
});
|
||||
let output = self.add_symbols_from_scope(body, scope_name_stack);
|
||||
let _ = scope_name_stack.pop();
|
||||
output?
|
||||
},
|
||||
TypeDecl { name, body, mutable } => {
|
||||
insert_and_check_duplicate_symbol(&mut seen_identifiers, &name.name)?;
|
||||
//TODO add ScopeSegmentKind::Type here
|
||||
self.add_type_decl(name, body, mutable, scope_name_stack)?
|
||||
},
|
||||
Binding { name, .. } => {
|
||||
insert_and_check_duplicate_symbol(&mut seen_identifiers, name)?;
|
||||
self.add_new_symbol(name, scope_name_stack, SymbolSpec::Binding);
|
||||
}
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn debug_symbol_table(&self) -> String {
|
||||
let mut output = format!("Symbol table\n");
|
||||
for (name, sym) in &self.symbol_path_to_symbol {
|
||||
write!(output, "{} -> {}\n", name, sym).unwrap();
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn add_function_signature(&mut self, signature: &Signature, scope_name_stack: &mut Vec<ScopeSegment>) -> Result<(), String> {
|
||||
let mut local_type_context = LocalTypeContext::new();
|
||||
let types = signature.params.iter().map(|param| match param.anno {
|
||||
Some(ref type_identifier) => Rc::new(format!("{:?}", type_identifier)),
|
||||
None => local_type_context.new_universal_type()
|
||||
}).collect();
|
||||
self.add_new_symbol(&signature.name, scope_name_stack, SymbolSpec::Func(types));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//TODO handle type mutability
|
||||
fn add_type_decl(&mut self, type_name: &TypeSingletonName, body: &TypeBody, _mutable: &bool, scope_name_stack: &mut Vec<ScopeSegment>) -> Result<(), String> {
|
||||
use crate::ast::{TypeIdentifier, Variant};
|
||||
let TypeBody(variants) = body;
|
||||
let ref type_name = type_name.name;
|
||||
scope_name_stack.push(ScopeSegment{
|
||||
name: type_name.clone(),
|
||||
kind: ScopeSegmentKind::Type,
|
||||
});
|
||||
//TODO figure out why _params isn't being used here
|
||||
for (index, var) in variants.iter().enumerate() {
|
||||
match var {
|
||||
Variant::UnitStruct(variant_name) => {
|
||||
let spec = SymbolSpec::DataConstructor {
|
||||
index,
|
||||
type_name: type_name.clone(),
|
||||
type_args: vec![],
|
||||
};
|
||||
self.add_new_symbol(variant_name, scope_name_stack, spec);
|
||||
},
|
||||
Variant::TupleStruct(variant_name, tuple_members) => {
|
||||
//TODO fix the notion of a tuple type
|
||||
let type_args = tuple_members.iter().map(|type_name| match type_name {
|
||||
TypeIdentifier::Singleton(TypeSingletonName { name, ..}) => name.clone(),
|
||||
TypeIdentifier::Tuple(_) => unimplemented!(),
|
||||
}).collect();
|
||||
let spec = SymbolSpec::DataConstructor {
|
||||
index,
|
||||
type_name: type_name.clone(),
|
||||
type_args
|
||||
};
|
||||
self.add_new_symbol(variant_name, scope_name_stack, spec);
|
||||
},
|
||||
Variant::Record { name, members: defined_members } => {
|
||||
let mut members = HashMap::new();
|
||||
let mut duplicate_member_definitions = Vec::new();
|
||||
for (member_name, member_type) in defined_members {
|
||||
match members.entry(member_name.clone()) {
|
||||
Entry::Occupied(_) => duplicate_member_definitions.push(member_name.clone()),
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(match member_type {
|
||||
TypeIdentifier::Singleton(TypeSingletonName { name, ..}) => name.clone(),
|
||||
TypeIdentifier::Tuple(_) => unimplemented!(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if duplicate_member_definitions.len() != 0 {
|
||||
return Err(format!("Duplicate member(s) in definition of type {}: {:?}", type_name, duplicate_member_definitions));
|
||||
}
|
||||
let spec = SymbolSpec::RecordConstructor { index, type_name: type_name.clone(), members };
|
||||
self.add_new_symbol(name, scope_name_stack, spec);
|
||||
},
|
||||
}
|
||||
}
|
||||
scope_name_stack.pop();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalTypeContext {
|
||||
state: u8
|
||||
}
|
||||
impl LocalTypeContext {
|
||||
fn new() -> LocalTypeContext {
|
||||
LocalTypeContext { state: 0 }
|
||||
}
|
||||
|
||||
fn new_universal_type(&mut self) -> TypeName {
|
||||
let n = self.state;
|
||||
self.state += 1;
|
||||
Rc::new(format!("{}", (('a' as u8) + n) as char))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod symbol_table_tests {
|
||||
use super::*;
|
||||
use crate::util::quick_ast;
|
||||
|
||||
macro_rules! values_in_table {
|
||||
//TODO multiple values
|
||||
($source:expr, $single_value:expr) => {
|
||||
{
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast($source);
|
||||
symbol_table.add_top_level_symbols(&ast).unwrap();
|
||||
match symbol_table.lookup_by_fqsn($single_value) {
|
||||
Some(_spec) => (),
|
||||
None => panic!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_symbol_table() {
|
||||
values_in_table! { "let a = 10; fn b() { 20 }", &fqsn!("b"; tr) }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_duplicates() {
|
||||
let source = r#"
|
||||
fn a() { 1 }
|
||||
fn b() { 2 }
|
||||
fn a() { 3 }
|
||||
"#;
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast(source);
|
||||
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
|
||||
assert!(output.contains("Duplicate"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_duplicates_2() {
|
||||
let source = r#"
|
||||
let a = 20;
|
||||
let q = 39;
|
||||
let a = 30;
|
||||
"#;
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast(source);
|
||||
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
|
||||
assert!(output.contains("Duplicate"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_duplicates_3() {
|
||||
let source = r#"
|
||||
fn a() {
|
||||
let a = 20
|
||||
let b = 40
|
||||
a + b
|
||||
}
|
||||
|
||||
fn q() {
|
||||
let x = 30
|
||||
let x = 33
|
||||
}
|
||||
"#;
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast(source);
|
||||
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
|
||||
assert!(output.contains("Duplicate"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_falsely_detect_duplicates() {
|
||||
let source = r#"
|
||||
let a = 20;
|
||||
fn some_func() {
|
||||
let a = 40;
|
||||
77
|
||||
}
|
||||
let q = 39;
|
||||
"#;
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast(source);
|
||||
symbol_table.add_top_level_symbols(&ast).unwrap();
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!["a"; tr]).is_some());
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!["some_func"; fn, "a";tr]).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosing_scopes() {
|
||||
let source = r#"
|
||||
fn outer_func(x) {
|
||||
fn inner_func(arg) {
|
||||
arg
|
||||
}
|
||||
x + inner_func(x)
|
||||
}"#;
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast(source);
|
||||
symbol_table.add_top_level_symbols(&ast).unwrap();
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; tr)).is_some());
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "inner_func"; tr)).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosing_scopes_2() {
|
||||
let source = r#"
|
||||
fn outer_func(x) {
|
||||
fn inner_func(arg) {
|
||||
arg
|
||||
}
|
||||
|
||||
fn second_inner_func() {
|
||||
fn another_inner_func() {
|
||||
}
|
||||
}
|
||||
|
||||
inner_func(x)
|
||||
}"#;
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast(source);
|
||||
symbol_table.add_top_level_symbols(&ast).unwrap();
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; tr)).is_some());
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "inner_func"; tr)).is_some());
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "second_inner_func"; tr)).is_some());
|
||||
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "second_inner_func"; fn, "another_inner_func"; tr)).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosing_scopes_3() {
|
||||
let source = r#"
|
||||
fn outer_func(x) {
|
||||
fn inner_func(arg) {
|
||||
arg
|
||||
}
|
||||
|
||||
fn second_inner_func() {
|
||||
fn another_inner_func() {
|
||||
}
|
||||
fn another_inner_func() {
|
||||
}
|
||||
}
|
||||
|
||||
inner_func(x)
|
||||
}"#;
|
||||
let mut symbol_table = SymbolTable::new();
|
||||
let ast = quick_ast(source);
|
||||
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
|
||||
assert!(output.contains("Duplicate"))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::iter::{Iterator, Peekable};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TokenKind {
|
||||
Newline, Semicolon,
|
||||
|
||||
LParen, RParen,
|
||||
LSquareBracket, RSquareBracket,
|
||||
LAngleBracket, RAngleBracket,
|
||||
LCurlyBrace, RCurlyBrace,
|
||||
Pipe, Backslash,
|
||||
|
||||
Comma, Period, Colon, Underscore,
|
||||
Slash, Equals,
|
||||
|
||||
Operator(Rc<String>),
|
||||
DigitGroup(Rc<String>), HexLiteral(Rc<String>), BinNumberSigil,
|
||||
StrLiteral(Rc<String>),
|
||||
Identifier(Rc<String>),
|
||||
Keyword(Kw),
|
||||
|
||||
EOF,
|
||||
|
||||
Error(String),
|
||||
}
|
||||
use self::TokenKind::*;
|
||||
|
||||
impl fmt::Display for TokenKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&Operator(ref s) => write!(f, "Operator({})", **s),
|
||||
&DigitGroup(ref s) => write!(f, "DigitGroup({})", s),
|
||||
&HexLiteral(ref s) => write!(f, "HexLiteral({})", s),
|
||||
&StrLiteral(ref s) => write!(f, "StrLiteral({})", s),
|
||||
&Identifier(ref s) => write!(f, "Identifier({})", s),
|
||||
&Error(ref s) => write!(f, "Error({})", s),
|
||||
other => write!(f, "{:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Kw {
|
||||
If, Then, Else,
|
||||
Is,
|
||||
Func,
|
||||
For, While,
|
||||
Const, Let, In,
|
||||
Mut,
|
||||
Return,
|
||||
Alias, Type, SelfType, SelfIdent,
|
||||
Interface, Impl,
|
||||
True, False,
|
||||
Module
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref KEYWORDS: HashMap<&'static str, Kw> =
|
||||
hashmap! {
|
||||
"if" => Kw::If,
|
||||
"then" => Kw::Then,
|
||||
"else" => Kw::Else,
|
||||
"is" => Kw::Is,
|
||||
"fn" => Kw::Func,
|
||||
"for" => Kw::For,
|
||||
"while" => Kw::While,
|
||||
"const" => Kw::Const,
|
||||
"let" => Kw::Let,
|
||||
"in" => Kw::In,
|
||||
"mut" => Kw::Mut,
|
||||
"return" => Kw::Return,
|
||||
"alias" => Kw::Alias,
|
||||
"type" => Kw::Type,
|
||||
"Self" => Kw::SelfType,
|
||||
"self" => Kw::SelfIdent,
|
||||
"interface" => Kw::Interface,
|
||||
"impl" => Kw::Impl,
|
||||
"true" => Kw::True,
|
||||
"false" => Kw::False,
|
||||
"module" => Kw::Module,
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Token {
|
||||
pub kind: TokenKind,
|
||||
pub line_num: usize,
|
||||
pub char_num: usize
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn get_error(&self) -> Option<String> {
|
||||
match self.kind {
|
||||
TokenKind::Error(ref s) => Some(s.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn to_string_with_metadata(&self) -> String {
|
||||
format!("{}(L:{},c:{})", self.kind, self.line_num, self.char_num)
|
||||
}
|
||||
|
||||
pub fn get_kind(&self) -> TokenKind {
|
||||
self.kind.clone()
|
||||
}
|
||||
}
|
||||
|
||||
const OPERATOR_CHARS: [char; 18] = ['!', '$', '%', '&', '*', '+', '-', '.', ':', '<', '>', '=', '?', '@', '^', '|', '~', '`'];
|
||||
fn is_operator(c: &char) -> bool {
|
||||
OPERATOR_CHARS.iter().any(|x| x == c)
|
||||
}
|
||||
|
||||
type CharData = (usize, usize, char);
|
||||
|
||||
pub fn tokenize(input: &str) -> Vec<Token> {
|
||||
let mut tokens: Vec<Token> = Vec::new();
|
||||
|
||||
let mut input = input.lines().enumerate()
|
||||
.intersperse((0, "\n"))
|
||||
.flat_map(|(line_idx, ref line)| {
|
||||
line.chars().enumerate().map(move |(ch_idx, ch)| (line_idx, ch_idx, ch))
|
||||
})
|
||||
.peekable();
|
||||
|
||||
while let Some((line_num, char_num, c)) = input.next() {
|
||||
let cur_tok_kind = match c {
|
||||
'/' => match input.peek().map(|t| t.2) {
|
||||
Some('/') => {
|
||||
while let Some((_, _, c)) = input.next() {
|
||||
if c == '\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
},
|
||||
Some('*') => {
|
||||
input.next();
|
||||
let mut comment_level = 1;
|
||||
while let Some((_, _, c)) = input.next() {
|
||||
if c == '*' && input.peek().map(|t| t.2) == Some('/') {
|
||||
input.next();
|
||||
comment_level -= 1;
|
||||
} else if c == '/' && input.peek().map(|t| t.2) == Some('*') {
|
||||
input.next();
|
||||
comment_level += 1;
|
||||
}
|
||||
if comment_level == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
},
|
||||
_ => Slash
|
||||
},
|
||||
c if c.is_whitespace() && c != '\n' => continue,
|
||||
'\n' => Newline, ';' => Semicolon,
|
||||
':' => Colon, ',' => Comma,
|
||||
'(' => LParen, ')' => RParen,
|
||||
'{' => LCurlyBrace, '}' => RCurlyBrace,
|
||||
'[' => LSquareBracket, ']' => RSquareBracket,
|
||||
'"' => handle_quote(&mut input),
|
||||
'\\' => Backslash,
|
||||
c if c.is_digit(10) => handle_digit(c, &mut input),
|
||||
c if c.is_alphabetic() || c == '_' => handle_alphabetic(c, &mut input),
|
||||
c if is_operator(&c) => handle_operator(c, &mut input),
|
||||
unknown => Error(format!("Unexpected character: {}", unknown)),
|
||||
};
|
||||
tokens.push(Token { kind: cur_tok_kind, line_num, char_num });
|
||||
}
|
||||
tokens
|
||||
}
|
||||
|
||||
fn handle_digit(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
if c == '0' && input.peek().map_or(false, |&(_, _, c)| { c == 'x' }) {
|
||||
input.next();
|
||||
let rest: String = input.peeking_take_while(|&(_, _, ref c)| c.is_digit(16) || *c == '_').map(|(_, _, c)| { c }).collect();
|
||||
HexLiteral(Rc::new(rest))
|
||||
} else if c == '0' && input.peek().map_or(false, |&(_, _, c)| { c == 'b' }) {
|
||||
input.next();
|
||||
BinNumberSigil
|
||||
} else {
|
||||
let mut buf = c.to_string();
|
||||
buf.extend(input.peeking_take_while(|&(_, _, ref c)| c.is_digit(10)).map(|(_, _, c)| { c }));
|
||||
DigitGroup(Rc::new(buf))
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_quote(input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match input.next().map(|(_, _, c)| { c }) {
|
||||
Some('"') => break,
|
||||
Some('\\') => {
|
||||
let next = input.peek().map(|&(_, _, c)| { c });
|
||||
if next == Some('n') {
|
||||
input.next();
|
||||
buf.push('\n')
|
||||
} else if next == Some('"') {
|
||||
input.next();
|
||||
buf.push('"');
|
||||
} else if next == Some('t') {
|
||||
input.next();
|
||||
buf.push('\t');
|
||||
}
|
||||
},
|
||||
Some(c) => buf.push(c),
|
||||
None => return TokenKind::Error(format!("Unclosed string")),
|
||||
}
|
||||
}
|
||||
TokenKind::StrLiteral(Rc::new(buf))
|
||||
}
|
||||
|
||||
fn handle_alphabetic(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
let mut buf = String::new();
|
||||
buf.push(c);
|
||||
if c == '_' && input.peek().map(|&(_, _, c)| { !c.is_alphabetic() }).unwrap_or(true) {
|
||||
return TokenKind::Underscore
|
||||
}
|
||||
|
||||
loop {
|
||||
match input.peek().map(|&(_, _, c)| { c }) {
|
||||
Some(c) if c.is_alphanumeric() || c == '_' => {
|
||||
input.next();
|
||||
buf.push(c);
|
||||
},
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
match KEYWORDS.get(buf.as_str()) {
|
||||
Some(kw) => TokenKind::Keyword(*kw),
|
||||
None => TokenKind::Identifier(Rc::new(buf)),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_operator(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
|
||||
match c {
|
||||
'<' | '>' | '|' | '.' | '=' => {
|
||||
let ref next = input.peek().map(|&(_, _, c)| { c });
|
||||
if !next.map(|n| { is_operator(&n) }).unwrap_or(false) {
|
||||
return match c {
|
||||
'<' => LAngleBracket,
|
||||
'>' => RAngleBracket,
|
||||
'|' => Pipe,
|
||||
'.' => Period,
|
||||
'=' => Equals,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
if c == '`' {
|
||||
loop {
|
||||
match input.peek().map(|&(_, _, c)| { c }) {
|
||||
Some(c) if c.is_alphabetic() || c == '_' => {
|
||||
input.next();
|
||||
buf.push(c);
|
||||
},
|
||||
Some('`') => {
|
||||
input.next();
|
||||
break;
|
||||
},
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf.push(c);
|
||||
loop {
|
||||
match input.peek().map(|&(_, _, c)| { c }) {
|
||||
Some(c) if is_operator(&c) => {
|
||||
input.next();
|
||||
buf.push(c);
|
||||
},
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
}
|
||||
TokenKind::Operator(Rc::new(buf))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod schala_tokenizer_tests {
|
||||
use super::*;
|
||||
use super::Kw::*;
|
||||
|
||||
macro_rules! digit { ($ident:expr) => { DigitGroup(Rc::new($ident.to_string())) } }
|
||||
macro_rules! ident { ($ident:expr) => { Identifier(Rc::new($ident.to_string())) } }
|
||||
macro_rules! op { ($ident:expr) => { Operator(Rc::new($ident.to_string())) } }
|
||||
|
||||
#[test]
|
||||
fn tokens() {
|
||||
let a = tokenize("let a: A<B> = c ++ d");
|
||||
let token_kinds: Vec<TokenKind> = a.into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![Keyword(Let), ident!("a"), Colon, ident!("A"),
|
||||
LAngleBracket, ident!("B"), RAngleBracket, Equals, ident!("c"), op!("++"), ident!("d")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn underscores() {
|
||||
let token_kinds: Vec<TokenKind> = tokenize("4_8").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![digit!("4"), Underscore, digit!("8")]);
|
||||
|
||||
let token_kinds2: Vec<TokenKind> = tokenize("aba_yo").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds2, vec![ident!("aba_yo")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comments() {
|
||||
let token_kinds: Vec<TokenKind> = tokenize("1 + /* hella /* bro */ */ 2").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![digit!("1"), op!("+"), digit!("2")]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backtick_operators() {
|
||||
let token_kinds: Vec<TokenKind> = tokenize("1 `plus` 2").into_iter().map(move |t| t.kind).collect();
|
||||
assert_eq!(token_kinds, vec![digit!("1"), op!("plus"), digit!("2")]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,445 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
||||
use parsing::{AST, Statement, Declaration, Signature, Expression, ExpressionType, Operation, Variant, TypeName, TypeSingletonName};
|
||||
|
||||
// from Niko's talk
|
||||
/* fn type_check(expression, expected_ty) -> Ty {
|
||||
let ty = bare_type_check(expression, expected_type);
|
||||
if ty icompatible with expected_ty {
|
||||
try_coerce(expression, ty, expected_ty)
|
||||
} else {
|
||||
ty
|
||||
}
|
||||
}
|
||||
|
||||
fn bare_type_check(exprssion, expected_type) -> Ty { ... }
|
||||
*/
|
||||
|
||||
/* H-M ALGO NOTES
|
||||
from https://www.youtube.com/watch?v=il3gD7XMdmA
|
||||
(also check out http://dev.stephendiehl.com/fun/006_hindley_milner.html)
|
||||
|
||||
typeInfer :: Expr a -> Matching (Type a)
|
||||
unify :: Type a -> Type b -> Matching (Type c)
|
||||
|
||||
(Matching a) is a monad in which unification is done
|
||||
|
||||
ex:
|
||||
|
||||
typeInfer (If e1 e2 e3) = do
|
||||
t1 <- typeInfer e1
|
||||
t2 <- typeInfer e2
|
||||
t3 <- typeInfer e3
|
||||
_ <- unify t1 BoolType
|
||||
unify t2 t3 -- b/c t2 and t3 have to be the same type
|
||||
|
||||
typeInfer (Const (ConstInt _)) = IntType -- same for other literals
|
||||
|
||||
--function application
|
||||
typeInfer (Apply f x) = do
|
||||
tf <- typeInfer f
|
||||
tx <- typeInfer x
|
||||
case tf of
|
||||
FunctionType t1 t2 -> do
|
||||
_ <- unify t1 tx
|
||||
return t2
|
||||
_ -> fail "Not a function"
|
||||
|
||||
--type annotation
|
||||
typeInfer (Typed x t) = do
|
||||
tx <- typeInfer x
|
||||
unify tx t
|
||||
|
||||
--variable and let expressions - need to pass around a map of variable names to types here
|
||||
typeInfer :: [ (Var, Type Var) ] -> Expr Var -> Matching (Type Var)
|
||||
|
||||
typeInfer ctx (Var x) = case (lookup x ctx) of
|
||||
Just t -> return t
|
||||
Nothing -> fail "Unknown variable"
|
||||
|
||||
--let x = e1 in e2
|
||||
typeInfer ctx (Let x e1 e2) = do
|
||||
t1 <- typeInfer ctx e1
|
||||
typeInfer ((x, t1) :: ctx) e2
|
||||
|
||||
--lambdas are complicated (this represents ʎx.e)
|
||||
typeInfer ctx (Lambda x e) = do
|
||||
t1 <- allocExistentialVariable
|
||||
t2 <- typeInfer ((x, t1) :: ctx) e
|
||||
return $ FunctionType t1 t2 -- ie. t1 -> t2
|
||||
|
||||
|
||||
--to solve the problem of map :: (a -> b) -> [a] -> [b]
|
||||
when we use a variable whose type has universal tvars, convert those universal
|
||||
tvars to existential ones
|
||||
-and each distinct universal tvar needs to map to the same existential type
|
||||
|
||||
-so we change typeinfer:
|
||||
typeInfer ctx (Var x) = do
|
||||
case (lookup x ctx) of
|
||||
Nothing -> ...
|
||||
Just t -> do
|
||||
let uvars = nub (toList t) -- nub removes duplicates, so this gets unique universally quantified variables
|
||||
evars <- mapM (const allocExistentialVariable) uvars
|
||||
let varMap = zip uvars evars
|
||||
let vixVar varMap v = fromJust $ lookup v varMap
|
||||
return (fmap (fixVar varMap) t)
|
||||
|
||||
--how do we define unify??
|
||||
|
||||
-recall, type signature is:
|
||||
unify :: Type a -> Type b -> Matching (Type c)
|
||||
unify BoolType BoolType = BoolType --easy, same for all constants
|
||||
unify (FunctionType t1 t2) (FunctionType t3 t4) = do
|
||||
t5 <- unify t1 t3
|
||||
t6 <- unify t2 t4
|
||||
return $ FunctionType t5 t6
|
||||
unify (TVar a) (TVar b) = if a == b then TVar a else fail
|
||||
--existential types can be assigned another type at most once
|
||||
--some complicated stuff about hanlding existential types
|
||||
--everything else is a type error
|
||||
unify a b = fail
|
||||
|
||||
|
||||
SKOLEMIZATION - how you prevent an unassigned existential type variable from leaking!
|
||||
-before a type gets to global scope, replace all unassigned existential vars w/ new unique universal
|
||||
type variables
|
||||
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Type {
|
||||
TVar(TypeVar),
|
||||
TConst(TypeConst),
|
||||
TFunc(Box<Type>, Box<Type>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TypeVar {
|
||||
Univ(Rc<String>),
|
||||
Exist(u64),
|
||||
}
|
||||
impl TypeVar {
|
||||
fn univ(label: &str) -> TypeVar {
|
||||
TypeVar::Univ(Rc::new(label.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TypeConst {
|
||||
UserT(Rc<String>),
|
||||
Integer,
|
||||
Float,
|
||||
StringT,
|
||||
Boolean,
|
||||
Unit,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
type TypeCheckResult = Result<Type, String>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
struct PathSpecifier(Rc<String>);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct TypeContextEntry {
|
||||
ty: Type,
|
||||
constant: bool
|
||||
}
|
||||
|
||||
pub struct TypeContext {
|
||||
symbol_table: HashMap<PathSpecifier, TypeContextEntry>,
|
||||
evar_table: HashMap<u64, Type>,
|
||||
existential_type_label_count: u64
|
||||
}
|
||||
|
||||
impl TypeContext {
|
||||
pub fn new() -> TypeContext {
|
||||
TypeContext {
|
||||
symbol_table: HashMap::new(),
|
||||
evar_table: HashMap::new(),
|
||||
existential_type_label_count: 0,
|
||||
}
|
||||
}
|
||||
pub fn add_symbols(&mut self, ast: &AST) {
|
||||
use self::Declaration::*;
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
for statement in ast.0.iter() {
|
||||
match *statement {
|
||||
Statement::ExpressionStatement(_) => (),
|
||||
Statement::Declaration(ref decl) => match *decl {
|
||||
FuncSig(_) => (),
|
||||
Impl { .. } => (),
|
||||
TypeDecl(ref type_constructor, ref body) => {
|
||||
for variant in body.0.iter() {
|
||||
let (spec, ty) = match variant {
|
||||
&Variant::UnitStruct(ref data_constructor) => {
|
||||
let spec = PathSpecifier(data_constructor.clone());
|
||||
let ty = TConst(UserT(type_constructor.name.clone()));
|
||||
(spec, ty)
|
||||
},
|
||||
&Variant::TupleStruct(ref data_construcor, ref args) => {
|
||||
//TODO fix
|
||||
let arg = args.get(0).unwrap();
|
||||
let type_arg = self.from_anno(arg);
|
||||
let spec = PathSpecifier(data_construcor.clone());
|
||||
let ty = TFunc(Box::new(type_arg), Box::new(TConst(UserT(type_constructor.name.clone()))));
|
||||
(spec, ty)
|
||||
},
|
||||
&Variant::Record(_, _) => unimplemented!(),
|
||||
};
|
||||
let entry = TypeContextEntry { ty, constant: true };
|
||||
self.symbol_table.insert(spec, entry);
|
||||
}
|
||||
},
|
||||
TypeAlias { .. } => (),
|
||||
Binding {ref name, ref constant, ref expr} => {
|
||||
let spec = PathSpecifier(name.clone());
|
||||
let ty = expr.1.as_ref()
|
||||
.map(|ty| self.from_anno(ty))
|
||||
.unwrap_or_else(|| { self.alloc_existential_type() }); // this call to alloc_existential is OK b/c a binding only ever has one type, so if the annotation is absent, it's fine to just make one de novo
|
||||
let entry = TypeContextEntry { ty, constant: *constant };
|
||||
self.symbol_table.insert(spec, entry);
|
||||
},
|
||||
FuncDecl(ref signature, _) => {
|
||||
let spec = PathSpecifier(signature.name.clone());
|
||||
let ty = self.from_signature(signature);
|
||||
let entry = TypeContextEntry { ty, constant: true };
|
||||
self.symbol_table.insert(spec, entry);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn lookup(&mut self, binding: &Rc<String>) -> Option<TypeContextEntry> {
|
||||
let key = PathSpecifier(binding.clone());
|
||||
self.symbol_table.get(&key).map(|entry| entry.clone())
|
||||
}
|
||||
pub fn debug_symbol_table(&self) -> String {
|
||||
format!("Symbol table:\n {:?}\nEvar table:\n{:?}", self.symbol_table, self.evar_table)
|
||||
}
|
||||
fn alloc_existential_type(&mut self) -> Type {
|
||||
let ret = Type::TVar(TypeVar::Exist(self.existential_type_label_count));
|
||||
self.existential_type_label_count += 1;
|
||||
ret
|
||||
}
|
||||
|
||||
fn from_anno(&mut self, anno: &TypeName) -> Type {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
match anno {
|
||||
&TypeName::Singleton(TypeSingletonName { ref name, .. }) => {
|
||||
match name.as_ref().as_ref() {
|
||||
"Int" => TConst(Integer),
|
||||
"Float" => TConst(Float),
|
||||
"Bool" => TConst(Boolean),
|
||||
"String" => TConst(StringT),
|
||||
s => TVar(TypeVar::Univ(Rc::new(format!("{}",s)))),
|
||||
}
|
||||
},
|
||||
&TypeName::Tuple(ref items) => {
|
||||
if items.len() == 1 {
|
||||
TConst(Unit)
|
||||
} else {
|
||||
TConst(Bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_signature(&mut self, sig: &Signature) -> Type {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
//TODO this won't work properly until you make sure that all (universal) type vars in the function have the same existential type var
|
||||
// actually this should never even put existential types into the symbol table at all
|
||||
|
||||
//this will crash if more than 5 arg function is used
|
||||
let names = vec!["a", "b", "c", "d", "e", "f"];
|
||||
let mut idx = 0;
|
||||
|
||||
let mut get_type = || { let q = TVar(TypeVar::Univ(Rc::new(format!("{}", names.get(idx).unwrap())))); idx += 1; q };
|
||||
|
||||
let return_type = sig.type_anno.as_ref().map(|anno| self.from_anno(&anno)).unwrap_or_else(|| { get_type() });
|
||||
if sig.params.len() == 0 {
|
||||
TFunc(Box::new(TConst(Unit)), Box::new(return_type))
|
||||
} else {
|
||||
let mut output_type = return_type;
|
||||
for p in sig.params.iter() {
|
||||
let p_type = p.1.as_ref().map(|anno| self.from_anno(anno)).unwrap_or_else(|| { get_type() });
|
||||
output_type = TFunc(Box::new(p_type), Box::new(output_type));
|
||||
}
|
||||
output_type
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_check(&mut self, ast: &AST) -> TypeCheckResult {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
let mut last = TConst(Unit);
|
||||
|
||||
for statement in ast.0.iter() {
|
||||
match statement {
|
||||
&Statement::Declaration(ref _decl) => {
|
||||
//return Err(format!("Declarations not supported"));
|
||||
},
|
||||
&Statement::ExpressionStatement(ref expr) => {
|
||||
last = self.infer(expr)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(last)
|
||||
}
|
||||
fn infer(&mut self, expr: &Expression) -> TypeCheckResult {
|
||||
match (&expr.0, &expr.1) {
|
||||
(exprtype, &Some(ref anno)) => {
|
||||
let tx = self.infer_no_anno(exprtype)?;
|
||||
let ty = self.from_anno(anno);
|
||||
self.unify(tx, ty)
|
||||
},
|
||||
(exprtype, &None) => self.infer_no_anno(exprtype),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_no_anno(&mut self, ex: &ExpressionType) -> TypeCheckResult {
|
||||
use self::ExpressionType::*;
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
|
||||
Ok(match ex {
|
||||
&IntLiteral(_) => TConst(Integer),
|
||||
&FloatLiteral(_) => TConst(Float),
|
||||
&StringLiteral(_) => TConst(StringT),
|
||||
&BoolLiteral(_) => TConst(Boolean),
|
||||
&Value(ref name, _) => {
|
||||
self.lookup(name)
|
||||
.map(|entry| entry.ty)
|
||||
.ok_or(format!("Couldn't find {}", name))?
|
||||
},
|
||||
&BinExp(ref op, ref lhs, ref rhs) => {
|
||||
let t_lhs = self.infer(lhs)?;
|
||||
match self.infer_op(op)? {
|
||||
TFunc(t1, t2) => {
|
||||
let _ = self.unify(t_lhs, *t1)?;
|
||||
let t_rhs = self.infer(rhs)?;
|
||||
let x = *t2;
|
||||
match x {
|
||||
TFunc(t3, t4) => {
|
||||
let _ = self.unify(t_rhs, *t3)?;
|
||||
*t4
|
||||
},
|
||||
_ => return Err(format!("Not a function type either")),
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Op {:?} is not a function type", op)),
|
||||
}
|
||||
},
|
||||
&Call { ref f, ref arguments } => {
|
||||
let tf = self.infer(f)?;
|
||||
let targ = self.infer(arguments.get(0).unwrap())?;
|
||||
match tf {
|
||||
TFunc(box t1, box t2) => {
|
||||
let _ = self.unify(t1, targ)?;
|
||||
t2
|
||||
},
|
||||
_ => return Err(format!("Not a function!")),
|
||||
}
|
||||
},
|
||||
_ => TConst(Bottom),
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_op(&mut self, op: &Operation) -> TypeCheckResult {
|
||||
use self::Type::*;
|
||||
use self::TypeConst::*;
|
||||
macro_rules! binoptype {
|
||||
($lhs:expr, $rhs:expr, $out:expr) => { TFunc(Box::new($lhs), Box::new(TFunc(Box::new($rhs), Box::new($out)))) };
|
||||
}
|
||||
|
||||
Ok(match (*op.0).as_ref() {
|
||||
"+" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"++" => binoptype!(TConst(StringT), TConst(StringT), TConst(StringT)),
|
||||
"-" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"*" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"/" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
"%" => binoptype!(TConst(Integer), TConst(Integer), TConst(Integer)),
|
||||
_ => TConst(Bottom)
|
||||
})
|
||||
}
|
||||
|
||||
fn unify(&mut self, t1: Type, t2: Type) -> TypeCheckResult {
|
||||
use self::Type::*;
|
||||
use self::TypeVar::*;
|
||||
|
||||
println!("Calling unify with `{:?}` and `{:?}`", t1, t2);
|
||||
|
||||
match (&t1, &t2) {
|
||||
(&TConst(ref c1), &TConst(ref c2)) if c1 == c2 => Ok(TConst(c1.clone())),
|
||||
(&TFunc(ref t1, ref t2), &TFunc(ref t3, ref t4)) => {
|
||||
let t5 = self.unify(*t1.clone().clone(), *t3.clone().clone())?;
|
||||
let t6 = self.unify(*t2.clone().clone(), *t4.clone().clone())?;
|
||||
Ok(TFunc(Box::new(t5), Box::new(t6)))
|
||||
},
|
||||
(&TVar(Univ(ref a)), &TVar(Univ(ref b))) => {
|
||||
if a == b {
|
||||
Ok(TVar(Univ(a.clone())))
|
||||
} else {
|
||||
Err(format!("Couldn't unify universal types {} and {}", a, b))
|
||||
}
|
||||
},
|
||||
//the interesting case!!
|
||||
(&TVar(Exist(ref a)), ref t2) => {
|
||||
let x = self.evar_table.get(a).map(|x| x.clone());
|
||||
match x {
|
||||
Some(ref t1) => self.unify(t1.clone().clone(), t2.clone().clone()),
|
||||
None => {
|
||||
self.evar_table.insert(*a, t2.clone().clone());
|
||||
Ok(t2.clone().clone())
|
||||
}
|
||||
}
|
||||
},
|
||||
(ref t1, &TVar(Exist(ref a))) => {
|
||||
let x = self.evar_table.get(a).map(|x| x.clone());
|
||||
match x {
|
||||
Some(ref t2) => self.unify(t2.clone().clone(), t1.clone().clone()),
|
||||
None => {
|
||||
self.evar_table.insert(*a, t1.clone().clone());
|
||||
Ok(t1.clone().clone())
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => Err(format!("Types {:?} and {:?} don't unify", t1, t2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Type, TypeVar, TypeConst, TypeContext};
|
||||
use super::Type::*;
|
||||
use super::TypeConst::*;
|
||||
use schala_lang::parsing::{parse, tokenize};
|
||||
|
||||
macro_rules! type_test {
|
||||
($input:expr, $correct:expr) => {
|
||||
{
|
||||
let mut tc = TypeContext::new();
|
||||
let ast = parse(tokenize($input)).0.unwrap() ;
|
||||
tc.add_symbols(&ast);
|
||||
assert_eq!($correct, tc.type_check(&ast).unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_inference() {
|
||||
type_test!("30", TConst(Integer));
|
||||
type_test!("fn x(a: Int): Bool {}; x(1)", TConst(Boolean));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,484 @@
|
|||
use std::rc::Rc;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ena::unify::{UnifyKey, InPlaceUnificationTable, UnificationTable, EqUnifyValue};
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::util::ScopeStack;
|
||||
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TypeData {
|
||||
ty: Option<Type>
|
||||
}
|
||||
|
||||
impl TypeData {
|
||||
pub fn new() -> TypeData {
|
||||
TypeData { ty: None }
|
||||
}
|
||||
}
|
||||
|
||||
pub type TypeName = Rc<String>;
|
||||
|
||||
pub struct TypeContext<'a> {
|
||||
variable_map: ScopeStack<'a, Rc<String>, Type>,
|
||||
unification_table: InPlaceUnificationTable<TypeVar>,
|
||||
}
|
||||
|
||||
/// `InferResult` is the monad in which type inference takes place.
|
||||
type InferResult<T> = Result<T, TypeError>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TypeError { pub msg: String }
|
||||
|
||||
impl TypeError {
|
||||
fn new<A, T>(msg: T) -> InferResult<A> where T: Into<String> {
|
||||
Err(TypeError { msg: msg.into() })
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // avoids warning from Compound
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Type {
|
||||
Const(TypeConst),
|
||||
Var(TypeVar),
|
||||
Arrow {
|
||||
params: Vec<Type>,
|
||||
ret: Box<Type>
|
||||
},
|
||||
Compound {
|
||||
ty_name: String,
|
||||
args:Vec<Type>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct TypeVar(usize);
|
||||
|
||||
impl UnifyKey for TypeVar {
|
||||
type Value = Option<TypeConst>;
|
||||
fn index(&self) -> u32 { self.0 as u32 }
|
||||
fn from_index(u: u32) -> TypeVar { TypeVar(u as usize) }
|
||||
fn tag() -> &'static str { "TypeVar" }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TypeConst {
|
||||
Unit,
|
||||
Nat,
|
||||
Int,
|
||||
Float,
|
||||
StringT,
|
||||
Bool,
|
||||
Ordering,
|
||||
//UserDefined
|
||||
}
|
||||
|
||||
impl TypeConst {
|
||||
pub fn to_string(&self) -> String {
|
||||
use self::TypeConst::*;
|
||||
match self {
|
||||
Unit => format!("()"),
|
||||
Nat => format!("Nat"),
|
||||
Int => format!("Int"),
|
||||
Float => format!("Float"),
|
||||
StringT => format!("String"),
|
||||
Bool => format!("Bool"),
|
||||
Ordering => format!("Ordering"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EqUnifyValue for TypeConst { }
|
||||
|
||||
macro_rules! ty {
|
||||
($type_name:ident) => { Type::Const(TypeConst::$type_name) };
|
||||
($t1:ident -> $t2:ident) => { Type::Arrow { params: vec![ty!($t1)], ret: box ty!($t2) } };
|
||||
($t1:ident -> $t2:ident -> $t3:ident) => { Type::Arrow { params: vec![ty!($t1), ty!($t2)], ret: box ty!($t3) } };
|
||||
($type_list:ident, $ret_type:ident) => {
|
||||
Type::Arrow {
|
||||
params: $type_list,
|
||||
ret: box $ret_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO find a better way to capture the to/from string logic
|
||||
impl Type {
|
||||
pub fn to_string(&self) -> String {
|
||||
use self::Type::*;
|
||||
match self {
|
||||
Const(c) => c.to_string(),
|
||||
Var(v) => format!("t_{}", v.0),
|
||||
Arrow { params, box ref ret } => {
|
||||
if params.len() == 0 {
|
||||
format!("-> {}", ret.to_string())
|
||||
} else {
|
||||
let mut buf = String::new();
|
||||
for p in params.iter() {
|
||||
write!(buf, "{} -> ", p.to_string()).unwrap();
|
||||
}
|
||||
write!(buf, "{}", ret.to_string()).unwrap();
|
||||
buf
|
||||
}
|
||||
},
|
||||
Compound { .. } => format!("<some compound type>")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_string(string: &str) -> Option<Type> {
|
||||
Some(match string {
|
||||
"()" | "Unit" => ty!(Unit),
|
||||
"Nat" => ty!(Nat),
|
||||
"Int" => ty!(Int),
|
||||
"Float" => ty!(Float),
|
||||
"String" => ty!(StringT),
|
||||
"Bool" => ty!(Bool),
|
||||
"Ordering" => ty!(Ordering),
|
||||
_ => return None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// `Type` is parameterized by whether the type variables can be just universal, or universal or
|
||||
/// existential.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Type<A> {
|
||||
Var(A),
|
||||
Const(TConst),
|
||||
Arrow(Box<Type<A>>, Box<Type<A>>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum TVar {
|
||||
Univ(UVar),
|
||||
Exist(ExistentialVar)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct UVar(Rc<String>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExistentialVar(u32);
|
||||
|
||||
impl Type<UVar> {
|
||||
fn to_tvar(&self) -> Type<TVar> {
|
||||
match self {
|
||||
Type::Var(UVar(name)) => Type::Var(TVar::Univ(UVar(name.clone()))),
|
||||
Type::Const(ref c) => Type::Const(c.clone()),
|
||||
Type::Arrow(a, b) => Type::Arrow(
|
||||
Box::new(a.to_tvar()),
|
||||
Box::new(b.to_tvar())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type<TVar> {
|
||||
fn skolemize(&self) -> Type<UVar> {
|
||||
match self {
|
||||
Type::Var(TVar::Univ(uvar)) => Type::Var(uvar.clone()),
|
||||
Type::Var(TVar::Exist(_)) => Type::Var(UVar(Rc::new(format!("sk")))),
|
||||
Type::Const(ref c) => Type::Const(c.clone()),
|
||||
Type::Arrow(a, b) => Type::Arrow(
|
||||
Box::new(a.skolemize()),
|
||||
Box::new(b.skolemize())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeIdentifier {
|
||||
fn to_monotype(&self) -> Type<UVar> {
|
||||
match self {
|
||||
TypeIdentifier::Tuple(_) => Type::Const(TConst::Nat),
|
||||
TypeIdentifier::Singleton(TypeSingletonName { name, .. }) => {
|
||||
match &name[..] {
|
||||
"Nat" => Type::Const(TConst::Nat),
|
||||
"Int" => Type::Const(TConst::Int),
|
||||
"Float" => Type::Const(TConst::Float),
|
||||
"Bool" => Type::Const(TConst::Bool),
|
||||
"String" => Type::Const(TConst::StringT),
|
||||
_ => Type::Const(TConst::Nat),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum TConst {
|
||||
User(Rc<String>),
|
||||
Unit,
|
||||
Nat,
|
||||
Int,
|
||||
Float,
|
||||
StringT,
|
||||
Bool,
|
||||
}
|
||||
|
||||
impl TConst {
|
||||
fn user(name: &str) -> TConst {
|
||||
TConst::User(Rc::new(name.to_string()))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
impl<'a> TypeContext<'a> {
|
||||
pub fn new() -> TypeContext<'a> {
|
||||
TypeContext {
|
||||
variable_map: ScopeStack::new(None),
|
||||
unification_table: UnificationTable::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fn new_env(&'a self, new_var: Rc<String>, ty: Type) -> TypeContext<'a> {
|
||||
let mut new_context = TypeContext {
|
||||
variable_map: self.variable_map.new_scope(None),
|
||||
unification_table: UnificationTable::new(), //???? not sure if i want this
|
||||
};
|
||||
|
||||
new_context.variable_map.insert(new_var, ty);
|
||||
new_context
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
fn get_type_from_name(&self, name: &TypeIdentifier) -> InferResult<Type> {
|
||||
use self::TypeIdentifier::*;
|
||||
Ok(match name {
|
||||
Singleton(TypeSingletonName { name,.. }) => {
|
||||
match Type::from_string(&name) {
|
||||
Some(ty) => ty,
|
||||
None => return TypeError::new(format!("Unknown type name: {}", name))
|
||||
}
|
||||
},
|
||||
Tuple(_) => return TypeError::new("tuples aren't ready yet"),
|
||||
})
|
||||
}
|
||||
|
||||
/// `typecheck` is the entry into the type-inference system, accepting an AST as an argument
|
||||
/// Following the example of GHC, the compiler deliberately does typechecking before de-sugaring
|
||||
/// the AST to ReducedAST
|
||||
pub fn typecheck(&mut self, ast: &AST) -> Result<Type, TypeError> {
|
||||
let mut returned_type = Type::Const(TypeConst::Unit);
|
||||
for statement in ast.statements.iter() {
|
||||
returned_type = self.statement(statement)?;
|
||||
}
|
||||
Ok(returned_type)
|
||||
}
|
||||
|
||||
fn statement(&mut self, statement: &Statement) -> InferResult<Type> {
|
||||
match &statement.kind {
|
||||
StatementKind::Expression(e) => self.expr(e),
|
||||
StatementKind::Declaration(decl) => self.decl(&decl),
|
||||
}
|
||||
}
|
||||
|
||||
fn decl(&mut self, decl: &Declaration) -> InferResult<Type> {
|
||||
use self::Declaration::*;
|
||||
match decl {
|
||||
Binding { name, expr, .. } => {
|
||||
let ty = self.expr(expr)?;
|
||||
self.variable_map.insert(name.clone(), ty);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
Ok(ty!(Unit))
|
||||
}
|
||||
|
||||
fn invoc(&mut self, invoc: &InvocationArgument) -> InferResult<Type> {
|
||||
use InvocationArgument::*;
|
||||
match invoc {
|
||||
Positional(expr) => self.expr(expr),
|
||||
_ => Ok(ty!(Nat)) //TODO this is wrong
|
||||
}
|
||||
}
|
||||
|
||||
fn expr(&mut self, expr: &Expression) -> InferResult<Type> {
|
||||
match expr {
|
||||
Expression { kind, type_anno: Some(anno), .. } => {
|
||||
let t1 = self.expr_type(kind)?;
|
||||
let t2 = self.get_type_from_name(anno)?;
|
||||
self.unify(t2, t1)
|
||||
},
|
||||
Expression { kind, type_anno: None, .. } => self.expr_type(kind)
|
||||
}
|
||||
}
|
||||
|
||||
fn expr_type(&mut self, expr: &ExpressionKind) -> InferResult<Type> {
|
||||
use self::ExpressionKind::*;
|
||||
Ok(match expr {
|
||||
NatLiteral(_) => ty!(Nat),
|
||||
BoolLiteral(_) => ty!(Bool),
|
||||
FloatLiteral(_) => ty!(Float),
|
||||
StringLiteral(_) => ty!(StringT),
|
||||
PrefixExp(op, expr) => self.prefix(op, expr)?,
|
||||
BinExp(op, lhs, rhs) => self.binexp(op, lhs, rhs)?,
|
||||
IfExpression { discriminator, body } => self.if_expr(discriminator, body)?,
|
||||
Value(val) => self.handle_value(val)?,
|
||||
Call { box ref f, arguments } => self.call(f, arguments)?,
|
||||
Lambda { params, type_anno, body } => self.lambda(params, type_anno, body)?,
|
||||
_ => ty!(Unit),
|
||||
})
|
||||
}
|
||||
|
||||
fn prefix(&mut self, op: &PrefixOp, expr: &Expression) -> InferResult<Type> {
|
||||
let tf = match op.builtin.map(|b| b.get_type()) {
|
||||
Some(ty) => ty,
|
||||
None => return TypeError::new("no type found")
|
||||
};
|
||||
|
||||
let tx = self.expr(expr)?;
|
||||
self.handle_apply(tf, vec![tx])
|
||||
}
|
||||
|
||||
fn binexp(&mut self, op: &BinOp, lhs: &Expression, rhs: &Expression) -> InferResult<Type> {
|
||||
let tf = match op.builtin.map(|b| b.get_type()) {
|
||||
Some(ty) => ty,
|
||||
None => return TypeError::new("no type found"),
|
||||
};
|
||||
|
||||
let t_lhs = self.expr(lhs)?;
|
||||
let t_rhs = self.expr(rhs)?; //TODO is this order a problem? not sure
|
||||
|
||||
self.handle_apply(tf, vec![t_lhs, t_rhs])
|
||||
}
|
||||
|
||||
fn if_expr(&mut self, discriminator: &Discriminator, body: &IfExpressionBody) -> InferResult<Type> {
|
||||
use self::Discriminator::*; use self::IfExpressionBody::*;
|
||||
match (discriminator, body) {
|
||||
(Simple(expr), SimpleConditional(then_clause, else_clause)) => self.handle_simple_if(expr, then_clause, else_clause),
|
||||
_ => TypeError::new(format!("Complex conditionals not supported"))
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_simple_if(&mut self, expr: &Expression, then_clause: &Block, else_clause: &Option<Block>) -> InferResult<Type> {
|
||||
let t1 = self.expr(expr)?;
|
||||
let t2 = self.block(then_clause)?;
|
||||
let t3 = match else_clause {
|
||||
Some(block) => self.block(block)?,
|
||||
None => ty!(Unit)
|
||||
};
|
||||
|
||||
let _ = self.unify(ty!(Bool), t1)?;
|
||||
self.unify(t2, t3)
|
||||
}
|
||||
|
||||
fn lambda(&mut self, params: &Vec<FormalParam>, type_anno: &Option<TypeIdentifier>, _body: &Block) -> InferResult<Type> {
|
||||
let argument_types: InferResult<Vec<Type>> = params.iter().map(|param: &FormalParam| {
|
||||
if let FormalParam { anno: Some(type_identifier), .. } = param {
|
||||
self.get_type_from_name(type_identifier)
|
||||
} else {
|
||||
Ok(Type::Var(self.fresh_type_variable()))
|
||||
}
|
||||
}).collect();
|
||||
let argument_types = argument_types?;
|
||||
let ret_type = match type_anno.as_ref() {
|
||||
Some(anno) => self.get_type_from_name(anno)?,
|
||||
None => Type::Var(self.fresh_type_variable())
|
||||
};
|
||||
|
||||
Ok(ty!(argument_types, ret_type))
|
||||
}
|
||||
|
||||
fn call(&mut self, f: &Expression, args: &Vec<InvocationArgument>) -> InferResult<Type> {
|
||||
let tf = self.expr(f)?;
|
||||
let arg_types: InferResult<Vec<Type>> = args.iter().map(|ex| self.invoc(ex)).collect();
|
||||
let arg_types = arg_types?;
|
||||
self.handle_apply(tf, arg_types)
|
||||
}
|
||||
|
||||
fn handle_apply(&mut self, tf: Type, args: Vec<Type>) -> InferResult<Type> {
|
||||
Ok(match tf {
|
||||
Type::Arrow { ref params, ret: box ref t_ret } if params.len() == args.len() => {
|
||||
for (t_param, t_arg) in params.iter().zip(args.iter()) {
|
||||
let _ = self.unify(t_param.clone(), t_arg.clone())?; //TODO I think this needs to reference a sub-scope
|
||||
}
|
||||
t_ret.clone()
|
||||
},
|
||||
Type::Arrow { .. } => return TypeError::new("Wrong length"),
|
||||
_ => return TypeError::new(format!("Not a function"))
|
||||
})
|
||||
}
|
||||
|
||||
fn block(&mut self, block: &Block) -> InferResult<Type> {
|
||||
let mut output = ty!(Unit);
|
||||
for statement in block.iter() {
|
||||
output = self.statement(statement)?;
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn handle_value(&mut self, val: &QualifiedName) -> InferResult<Type> {
|
||||
let QualifiedName { components: vec, .. } = val;
|
||||
let var = &vec[0];
|
||||
match self.variable_map.lookup(var) {
|
||||
Some(ty) => Ok(ty.clone()),
|
||||
None => TypeError::new(format!("Couldn't find variable: {}", &var)),
|
||||
}
|
||||
}
|
||||
|
||||
fn unify(&mut self, t1: Type, t2: Type) -> InferResult<Type> {
|
||||
use self::Type::*;
|
||||
|
||||
match (t1, t2) {
|
||||
(Const(ref c1), Const(ref c2)) if c1 == c2 => Ok(Const(c1.clone())), //choice of c1 is arbitrary I *think*
|
||||
(a @ Var(_), b @ Const(_)) => self.unify(b, a),
|
||||
(Const(ref c1), Var(ref v2)) => {
|
||||
self.unification_table.unify_var_value(v2.clone(), Some(c1.clone()))
|
||||
.or_else(|_| TypeError::new(format!("Couldn't unify {:?} and {:?}", Const(c1.clone()), Var(*v2))))?;
|
||||
Ok(Const(c1.clone()))
|
||||
},
|
||||
(Var(v1), Var(v2)) => {
|
||||
//TODO add occurs check
|
||||
self.unification_table.unify_var_var(v1.clone(), v2.clone())
|
||||
.or_else(|e| {
|
||||
println!("Unify error: {:?}", e);
|
||||
TypeError::new(format!("Two type variables {:?} and {:?} couldn't unify", v1, v2))
|
||||
})?;
|
||||
Ok(Var(v1.clone())) //arbitrary decision I think
|
||||
},
|
||||
(a, b) => TypeError::new(format!("{:?} and {:?} do not unify", a, b)),
|
||||
}
|
||||
}
|
||||
|
||||
fn fresh_type_variable(&mut self) -> TypeVar {
|
||||
let new_type_var = self.unification_table.new_key(None);
|
||||
new_type_var
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod typechecking_tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! assert_type_in_fresh_context {
|
||||
($string:expr, $type:expr) => {
|
||||
let mut tc = TypeContext::new();
|
||||
let ref ast = crate::util::quick_ast($string);
|
||||
let ty = tc.typecheck(ast).unwrap();
|
||||
assert_eq!(ty, $type)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_test() {
|
||||
assert_type_in_fresh_context!("1", ty!(Nat));
|
||||
assert_type_in_fresh_context!(r#""drugs""#, ty!(StringT));
|
||||
assert_type_in_fresh_context!("true", ty!(Bool));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn operators() {
|
||||
//TODO fix these with new operator regime
|
||||
/*
|
||||
assert_type_in_fresh_context!("-1", ty!(Int));
|
||||
assert_type_in_fresh_context!("1 + 2", ty!(Nat));
|
||||
assert_type_in_fresh_context!("-2", ty!(Int));
|
||||
assert_type_in_fresh_context!("!true", ty!(Bool));
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::cmp::Eq;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ScopeStack<'a, T: 'a, V: 'a> where T: Hash + Eq {
|
||||
parent: Option<&'a ScopeStack<'a, T, V>>,
|
||||
values: HashMap<T, V>,
|
||||
scope_name: Option<String>
|
||||
}
|
||||
|
||||
impl<'a, T, V> ScopeStack<'a, T, V> where T: Hash + Eq {
|
||||
pub fn new(name: Option<String>) -> ScopeStack<'a, T, V> where T: Hash + Eq {
|
||||
ScopeStack {
|
||||
parent: None,
|
||||
values: HashMap::new(),
|
||||
scope_name: name
|
||||
}
|
||||
}
|
||||
pub fn insert(&mut self, key: T, value: V) where T: Hash + Eq {
|
||||
self.values.insert(key, value);
|
||||
}
|
||||
pub fn lookup(&self, key: &T) -> Option<&V> where T: Hash + Eq {
|
||||
match (self.values.get(key), self.parent) {
|
||||
(None, None) => None,
|
||||
(None, Some(parent)) => parent.lookup(key),
|
||||
(Some(value), _) => Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_scope(&'a self, name: Option<String>) -> ScopeStack<'a, T, V> where T: Hash + Eq {
|
||||
ScopeStack {
|
||||
parent: Some(self),
|
||||
values: HashMap::default(),
|
||||
scope_name: name,
|
||||
}
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn get_name(&self) -> Option<&String> {
|
||||
self.scope_name.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// this is intended for use in tests, and does no error-handling whatsoever
|
||||
#[allow(dead_code)]
|
||||
pub fn quick_ast(input: &str) -> crate::ast::AST {
|
||||
let tokens = crate::tokenizing::tokenize(input);
|
||||
let mut parser = crate::parsing::Parser::new(tokens);
|
||||
parser.parse().unwrap()
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! rc {
|
||||
($string:tt) => { Rc::new(stringify!($string).to_string()) }
|
||||
}
|
BIN
schala-logo.ico
BIN
schala-logo.ico
Binary file not shown.
Before Width: | Height: | Size: 5.8 KiB |
|
@ -1,8 +0,0 @@
|
|||
[package]
|
||||
name = "schala-main"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
println!("Schala");
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
[package]
|
||||
name = "schala-parser"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "schala-repl"
|
||||
version = "0.1.0"
|
||||
authors = ["greg <greg.shuflin@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
llvm-sys = "70.0.2"
|
||||
take_mut = "0.2.2"
|
||||
itertools = "0.5.8"
|
||||
getopts = "0.2.18"
|
||||
lazy_static = "0.2.8"
|
||||
maplit = "*"
|
||||
colored = "1.8"
|
||||
serde = "1.0.91"
|
||||
serde_derive = "1.0.91"
|
||||
serde_json = "1.0.15"
|
||||
phf = "0.7.12"
|
||||
includedir = "0.2.0"
|
||||
linefeed = "0.6.0"
|
||||
regex = "0.2"
|
||||
|
||||
[build-dependencies]
|
||||
includedir_codegen = "0.2.0"
|
|
@ -0,0 +1,10 @@
|
|||
extern crate includedir_codegen;
|
||||
|
||||
use includedir_codegen::Compression;
|
||||
|
||||
fn main() {
|
||||
includedir_codegen::start("WEBFILES")
|
||||
.dir("../static", Compression::Gzip)
|
||||
.build("static.rs")
|
||||
.unwrap();
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
use std::time;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub trait ProgrammingLanguageInterface {
|
||||
fn get_language_name(&self) -> String;
|
||||
fn get_source_file_suffix(&self) -> String;
|
||||
|
||||
fn run_computation(&mut self, _request: ComputationRequest) -> ComputationResponse {
|
||||
ComputationResponse {
|
||||
main_output: Err(format!("Computation pipeline not implemented")),
|
||||
global_output_stats: GlobalOutputStats::default(),
|
||||
debug_responses: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn request_meta(&mut self, _request: LangMetaRequest) -> LangMetaResponse {
|
||||
LangMetaResponse::Custom { kind: format!("not-implemented"), value: format!("") }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ComputationRequest<'a> {
|
||||
pub source: &'a str,
|
||||
pub debug_requests: HashSet<DebugAsk>,
|
||||
}
|
||||
|
||||
pub struct ComputationResponse {
|
||||
pub main_output: Result<String, String>,
|
||||
pub global_output_stats: GlobalOutputStats,
|
||||
pub debug_responses: Vec<DebugResponse>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct GlobalOutputStats {
|
||||
pub total_duration: time::Duration,
|
||||
pub stage_durations: Vec<(String, time::Duration)>
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub enum DebugAsk {
|
||||
Timing,
|
||||
ByStage { stage_name: String, token: Option<String> },
|
||||
}
|
||||
|
||||
impl DebugAsk {
|
||||
pub fn is_for_stage(&self, name: &str) -> bool {
|
||||
match self {
|
||||
DebugAsk::ByStage { stage_name, .. } if stage_name == name => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugResponse {
|
||||
pub ask: DebugAsk,
|
||||
pub value: String
|
||||
}
|
||||
|
||||
pub enum LangMetaRequest {
|
||||
StageNames,
|
||||
Docs {
|
||||
source: String,
|
||||
},
|
||||
Custom {
|
||||
kind: String,
|
||||
value: String
|
||||
},
|
||||
ImmediateDebug(DebugAsk),
|
||||
}
|
||||
|
||||
pub enum LangMetaResponse {
|
||||
StageNames(Vec<String>),
|
||||
Docs {
|
||||
doc_string: String,
|
||||
},
|
||||
Custom {
|
||||
kind: String,
|
||||
value: String
|
||||
},
|
||||
ImmediateDebug(DebugResponse),
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
#![feature(link_args)]
|
||||
#![feature(slice_patterns, box_patterns, box_syntax, proc_macro_hygiene, decl_macro)]
|
||||
#![feature(plugin)]
|
||||
extern crate getopts;
|
||||
extern crate linefeed;
|
||||
extern crate itertools;
|
||||
extern crate colored;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_json;
|
||||
extern crate includedir;
|
||||
extern crate phf;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::process::exit;
|
||||
|
||||
mod repl;
|
||||
mod language;
|
||||
|
||||
pub use language::{ProgrammingLanguageInterface,
|
||||
ComputationRequest, ComputationResponse,
|
||||
LangMetaRequest, LangMetaResponse,
|
||||
DebugResponse, DebugAsk, GlobalOutputStats};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/static.rs"));
|
||||
const VERSION_STRING: &'static str = "0.1.0";
|
||||
|
||||
pub fn start_repl(langs: Vec<Box<dyn ProgrammingLanguageInterface>>) {
|
||||
let options = command_line_options().parse(std::env::args()).unwrap_or_else(|e| {
|
||||
println!("{:?}", e);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
if options.opt_present("help") {
|
||||
println!("{}", command_line_options().usage("Schala metainterpreter"));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
match options.free[..] {
|
||||
[] | [_] => {
|
||||
let mut repl = repl::Repl::new(langs);
|
||||
repl.run_repl();
|
||||
}
|
||||
[_, ref filename, ..] => {
|
||||
run_noninteractive(filename, langs);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn run_noninteractive(filename: &str, languages: Vec<Box<dyn ProgrammingLanguageInterface>>) {
|
||||
let path = Path::new(filename);
|
||||
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or_else(|| {
|
||||
println!("Source file lacks extension");
|
||||
exit(1);
|
||||
});
|
||||
let mut language = Box::new(languages.into_iter().find(|lang| lang.get_source_file_suffix() == ext)
|
||||
.unwrap_or_else(|| {
|
||||
println!("Extension .{} not recognized", ext);
|
||||
exit(1);
|
||||
}));
|
||||
|
||||
let mut source_file = File::open(path).unwrap();
|
||||
let mut buffer = String::new();
|
||||
source_file.read_to_string(&mut buffer).unwrap();
|
||||
|
||||
let request = ComputationRequest {
|
||||
source: &buffer,
|
||||
debug_requests: HashSet::new(),
|
||||
};
|
||||
|
||||
let response = language.run_computation(request);
|
||||
match response.main_output {
|
||||
Ok(s) => println!("{}", s),
|
||||
Err(s) => println!("{}", s)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fn command_line_options() -> getopts::Options {
|
||||
let mut options = getopts::Options::new();
|
||||
options.optflag("h",
|
||||
"help",
|
||||
"Show help text");
|
||||
options.optflag("w",
|
||||
"webapp",
|
||||
"Start up web interpreter");
|
||||
options
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
use super::{Repl, InterpreterDirectiveOutput};
|
||||
use crate::repl::directive_actions::DirectiveAction;
|
||||
use colored::*;
|
||||
|
||||
/// A CommandTree is either a `Terminal` or a `NonTerminal`. When command parsing reaches the first
|
||||
/// Terminal, it will use the `DirectiveAction` found there to find an appropriate function to execute,
|
||||
/// and then execute it with any remaining arguments
|
||||
#[derive(Clone)]
|
||||
pub enum CommandTree {
|
||||
Terminal {
|
||||
name: String,
|
||||
children: Vec<CommandTree>,
|
||||
help_msg: Option<String>,
|
||||
action: DirectiveAction,
|
||||
},
|
||||
NonTerminal {
|
||||
name: String,
|
||||
children: Vec<CommandTree>,
|
||||
help_msg: Option<String>,
|
||||
action: DirectiveAction,
|
||||
},
|
||||
Top(Vec<CommandTree>),
|
||||
}
|
||||
|
||||
impl CommandTree {
|
||||
pub fn nonterm_no_further_tab_completions(s: &str, help: Option<&str>) -> CommandTree {
|
||||
CommandTree::NonTerminal {name: s.to_string(), help_msg: help.map(|x| x.to_string()), children: vec![], action: DirectiveAction::Null }
|
||||
}
|
||||
|
||||
pub fn terminal(s: &str, help: Option<&str>, children: Vec<CommandTree>, action: DirectiveAction) -> CommandTree {
|
||||
CommandTree::Terminal {name: s.to_string(), help_msg: help.map(|x| x.to_string()), children, action}
|
||||
}
|
||||
|
||||
pub fn nonterm(s: &str, help: Option<&str>, children: Vec<CommandTree>) -> CommandTree {
|
||||
CommandTree::NonTerminal {
|
||||
name: s.to_string(),
|
||||
help_msg: help.map(|x| x.to_string()),
|
||||
children,
|
||||
action: DirectiveAction::Null
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cmd(&self) -> &str {
|
||||
match self {
|
||||
CommandTree::Terminal { name, .. } => name.as_str(),
|
||||
CommandTree::NonTerminal {name, ..} => name.as_str(),
|
||||
CommandTree::Top(_) => "",
|
||||
}
|
||||
}
|
||||
pub fn get_help(&self) -> &str {
|
||||
match self {
|
||||
CommandTree::Terminal { help_msg, ..} => help_msg.as_ref().map(|s| s.as_str()).unwrap_or(""),
|
||||
CommandTree::NonTerminal { help_msg, .. } => help_msg.as_ref().map(|s| s.as_str()).unwrap_or(""),
|
||||
CommandTree::Top(_) => ""
|
||||
}
|
||||
}
|
||||
pub fn get_children(&self) -> &Vec<CommandTree> {
|
||||
use CommandTree::*;
|
||||
match self {
|
||||
Terminal { children, .. } |
|
||||
NonTerminal { children, .. } |
|
||||
Top(children) => children
|
||||
}
|
||||
}
|
||||
pub fn get_subcommands(&self) -> Vec<&str> {
|
||||
self.get_children().iter().map(|x| x.get_cmd()).collect()
|
||||
}
|
||||
|
||||
pub fn perform(&self, repl: &mut Repl, arguments: &Vec<&str>) -> InterpreterDirectiveOutput {
|
||||
let mut dir_pointer: &CommandTree = self;
|
||||
let mut idx = 0;
|
||||
|
||||
let res: Result<(DirectiveAction, usize), String> = loop {
|
||||
match dir_pointer {
|
||||
CommandTree::Top(subcommands) | CommandTree::NonTerminal { children: subcommands, .. } => {
|
||||
let next_command = match arguments.get(idx) {
|
||||
Some(cmd) => cmd,
|
||||
None => break Err(format!("Command requires arguments"))
|
||||
};
|
||||
idx += 1;
|
||||
match subcommands.iter().find(|sc| sc.get_cmd() == *next_command) {
|
||||
Some(command_tree) => {
|
||||
dir_pointer = command_tree;
|
||||
},
|
||||
None => break Err(format!("Command {} not found", next_command))
|
||||
};
|
||||
},
|
||||
CommandTree::Terminal { action, .. } => {
|
||||
break Ok((action.clone(), idx));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok((action, idx)) => action.perform(repl, &arguments[idx..]),
|
||||
Err(err) => Some(err.red().to_string())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
use super::{Repl, InterpreterDirectiveOutput};
|
||||
use crate::repl::help::help;
|
||||
use crate::language::{LangMetaRequest, LangMetaResponse, DebugAsk, DebugResponse};
|
||||
use itertools::Itertools;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DirectiveAction {
|
||||
Null,
|
||||
Help,
|
||||
QuitProgram,
|
||||
ListPasses,
|
||||
ShowImmediate,
|
||||
Show,
|
||||
Hide,
|
||||
TotalTimeOff,
|
||||
TotalTimeOn,
|
||||
StageTimeOff,
|
||||
StageTimeOn,
|
||||
Doc,
|
||||
}
|
||||
|
||||
impl DirectiveAction {
|
||||
pub fn perform(&self, repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
|
||||
use DirectiveAction::*;
|
||||
match self {
|
||||
Null => None,
|
||||
Help => help(repl, arguments),
|
||||
QuitProgram => {
|
||||
repl.save_before_exit();
|
||||
::std::process::exit(0)
|
||||
},
|
||||
ListPasses => {
|
||||
let language_state = repl.get_cur_language_state();
|
||||
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) {
|
||||
LangMetaResponse::StageNames(names) => names,
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
for pass in pass_names.iter().map(|name| Some(name)).intersperse(None) {
|
||||
match pass {
|
||||
Some(pass) => write!(buf, "{}", pass).unwrap(),
|
||||
None => write!(buf, " -> ").unwrap(),
|
||||
}
|
||||
}
|
||||
Some(buf)
|
||||
},
|
||||
ShowImmediate => {
|
||||
let cur_state = repl.get_cur_language_state();
|
||||
let stage_name = match arguments.get(0) {
|
||||
Some(s) => s.to_string(),
|
||||
None => return Some(format!("Must specify a thing to debug")),
|
||||
};
|
||||
let meta = LangMetaRequest::ImmediateDebug(DebugAsk::ByStage { stage_name: stage_name.clone(), token: None });
|
||||
let meta_response = cur_state.request_meta(meta);
|
||||
|
||||
let response = match meta_response {
|
||||
LangMetaResponse::ImmediateDebug(DebugResponse { ask, value }) => match ask {
|
||||
DebugAsk::ByStage { stage_name: ref this_stage_name, ..} if *this_stage_name == stage_name => value,
|
||||
_ => return Some(format!("Wrong debug stage"))
|
||||
},
|
||||
_ => return Some(format!("Invalid language meta response")),
|
||||
};
|
||||
Some(response)
|
||||
},
|
||||
Show => {
|
||||
let this_stage_name = match arguments.get(0) {
|
||||
Some(s) => s.to_string(),
|
||||
None => return Some(format!("Must specify a stage to show")),
|
||||
};
|
||||
let token = arguments.get(1).map(|s| s.to_string());
|
||||
repl.options.debug_asks.retain(|ask| match ask {
|
||||
DebugAsk::ByStage { stage_name, .. } if *stage_name == this_stage_name => false,
|
||||
_ => true
|
||||
});
|
||||
|
||||
let ask = DebugAsk::ByStage { stage_name: this_stage_name, token };
|
||||
repl.options.debug_asks.insert(ask);
|
||||
None
|
||||
},
|
||||
Hide => {
|
||||
let stage_name_to_remove = match arguments.get(0) {
|
||||
Some(s) => s.to_string(),
|
||||
None => return Some(format!("Must specify a stage to hide")),
|
||||
};
|
||||
repl.options.debug_asks.retain(|ask| match ask {
|
||||
DebugAsk::ByStage { stage_name, .. } if *stage_name == stage_name_to_remove => false,
|
||||
_ => true
|
||||
});
|
||||
None
|
||||
},
|
||||
TotalTimeOff => total_time_off(repl, arguments),
|
||||
TotalTimeOn => total_time_on(repl, arguments),
|
||||
StageTimeOff => stage_time_off(repl, arguments),
|
||||
StageTimeOn => stage_time_on(repl, arguments),
|
||||
Doc => doc(repl, arguments),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn total_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
|
||||
repl.options.show_total_time = true;
|
||||
None
|
||||
}
|
||||
|
||||
fn total_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
|
||||
repl.options.show_total_time = false;
|
||||
None
|
||||
}
|
||||
|
||||
fn stage_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
|
||||
repl.options.show_stage_times = true;
|
||||
None
|
||||
}
|
||||
|
||||
fn stage_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
|
||||
repl.options.show_stage_times = false;
|
||||
None
|
||||
}
|
||||
|
||||
fn doc(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
|
||||
arguments.get(0).map(|cmd| {
|
||||
let source = cmd.to_string();
|
||||
let meta = LangMetaRequest::Docs { source };
|
||||
let cur_state = repl.get_cur_language_state();
|
||||
match cur_state.request_meta(meta) {
|
||||
LangMetaResponse::Docs { doc_string } => Some(doc_string),
|
||||
_ => Some(format!("Invalid doc response"))
|
||||
}
|
||||
}).unwrap_or(Some(format!(":docs needs an argument")))
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
use crate::repl::command_tree::CommandTree;
|
||||
use crate::repl::directive_actions::DirectiveAction;
|
||||
|
||||
pub fn directives_from_pass_names(pass_names: &Vec<String>) -> CommandTree {
|
||||
let passes_directives: Vec<CommandTree> = pass_names.iter()
|
||||
.map(|pass_name| {
|
||||
if pass_name == "parsing" {
|
||||
CommandTree::nonterm(pass_name, None, vec![
|
||||
CommandTree::nonterm_no_further_tab_completions("compact", None),
|
||||
CommandTree::nonterm_no_further_tab_completions("expanded", None),
|
||||
CommandTree::nonterm_no_further_tab_completions("trace", None),
|
||||
])
|
||||
} else {
|
||||
CommandTree::nonterm_no_further_tab_completions(pass_name, None)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
CommandTree::Top(get_list(&passes_directives, true))
|
||||
}
|
||||
|
||||
fn get_list(passes_directives: &Vec<CommandTree>, include_help: bool) -> Vec<CommandTree> {
|
||||
use DirectiveAction::*;
|
||||
|
||||
vec![
|
||||
CommandTree::terminal("exit", Some("exit the REPL"), vec![], QuitProgram),
|
||||
CommandTree::terminal("quit", Some("exit the REPL"), vec![], QuitProgram),
|
||||
CommandTree::terminal("help", Some("Print this help message"), if include_help { get_list(passes_directives, false) } else { vec![] }, Help),
|
||||
CommandTree::nonterm("debug",
|
||||
Some("Configure debug information"),
|
||||
vec![
|
||||
CommandTree::terminal("list-passes", Some("List all registered compiler passes"), vec![], ListPasses),
|
||||
CommandTree::terminal("show-immediate", None, passes_directives.clone(), ShowImmediate),
|
||||
CommandTree::terminal("show", Some("Show debug output for a specific pass"), passes_directives.clone(), Show),
|
||||
CommandTree::terminal("hide", Some("Hide debug output for a specific pass"), passes_directives.clone(), Hide),
|
||||
CommandTree::nonterm("total-time", None, vec![
|
||||
CommandTree::terminal("on", None, vec![], TotalTimeOn),
|
||||
CommandTree::terminal("off", None, vec![], TotalTimeOff),
|
||||
]),
|
||||
CommandTree::nonterm("stage-times", Some("Computation time per-stage"), vec![
|
||||
CommandTree::terminal("on", None, vec![], StageTimeOn),
|
||||
CommandTree::terminal("off", None, vec![], StageTimeOff),
|
||||
])
|
||||
]
|
||||
),
|
||||
CommandTree::nonterm("lang",
|
||||
Some("switch between languages, or go directly to a langauge by name"),
|
||||
vec![
|
||||
CommandTree::nonterm_no_further_tab_completions("next", None),
|
||||
CommandTree::nonterm_no_further_tab_completions("prev", None),
|
||||
CommandTree::nonterm("go", None, vec![]),
|
||||
]
|
||||
),
|
||||
CommandTree::terminal("doc", Some("Get language-specific help for an item"), vec![], Doc),
|
||||
]
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
use std::fmt::Write as FmtWrite;
|
||||
|
||||
use colored::*;
|
||||
use super::command_tree::CommandTree;
|
||||
use super::{Repl, InterpreterDirectiveOutput};
|
||||
|
||||
pub fn help(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
|
||||
match arguments {
|
||||
[] => return global_help(repl),
|
||||
commands => {
|
||||
let dirs = repl.get_directives();
|
||||
Some(match get_directive_from_commands(commands, &dirs) {
|
||||
None => format!("Directive `{}` not found", commands.last().unwrap()),
|
||||
Some(dir) => {
|
||||
let mut buf = String::new();
|
||||
writeln!(buf, "`{}` - {}", dir.get_cmd(), dir.get_help()).unwrap();
|
||||
buf
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_directive_from_commands<'a>(commands: &[&str], dirs: &'a CommandTree) -> Option<&'a CommandTree> {
|
||||
let mut directive_list = dirs.get_children();
|
||||
let mut matched_directive = None;
|
||||
for cmd in commands {
|
||||
let found = directive_list.iter().find(|directive| directive.get_cmd() == *cmd);
|
||||
if let Some(dir) = found {
|
||||
directive_list = dir.get_children();
|
||||
}
|
||||
|
||||
matched_directive = found;
|
||||
}
|
||||
matched_directive
|
||||
}
|
||||
|
||||
fn global_help(repl: &mut Repl) -> InterpreterDirectiveOutput {
|
||||
let mut buf = String::new();
|
||||
let sigil = repl.interpreter_directive_sigil;
|
||||
|
||||
writeln!(buf, "{} version {}", "Schala REPL".bright_red().bold(), crate::VERSION_STRING).unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
|
||||
for directive in repl.get_directives().get_children() {
|
||||
writeln!(buf, "{}{} - {}", sigil, directive.get_cmd(), directive.get_help()).unwrap();
|
||||
}
|
||||
|
||||
let ref lang = repl.get_cur_language_state();
|
||||
writeln!(buf, "").unwrap();
|
||||
writeln!(buf, "Language-specific help for {}", lang.get_language_name()).unwrap();
|
||||
writeln!(buf, "-----------------------").unwrap();
|
||||
Some(buf)
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
use std::sync::Arc;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::language::{ProgrammingLanguageInterface,
|
||||
ComputationRequest, LangMetaResponse, LangMetaRequest};
|
||||
|
||||
mod command_tree;
|
||||
use self::command_tree::CommandTree;
|
||||
mod repl_options;
|
||||
use repl_options::ReplOptions;
|
||||
mod directive_actions;
|
||||
mod directives;
|
||||
use directives::directives_from_pass_names;
|
||||
mod help;
|
||||
mod response;
|
||||
use response::ReplResponse;
|
||||
|
||||
const HISTORY_SAVE_FILE: &'static str = ".schala_history";
|
||||
const OPTIONS_SAVE_FILE: &'static str = ".schala_repl";
|
||||
|
||||
type InterpreterDirectiveOutput = Option<String>;
|
||||
|
||||
pub struct Repl {
|
||||
pub interpreter_directive_sigil: char,
|
||||
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
|
||||
language_states: Vec<Box<dyn ProgrammingLanguageInterface>>,
|
||||
options: ReplOptions,
|
||||
}
|
||||
|
||||
impl Repl {
|
||||
pub fn new(initial_states: Vec<Box<dyn ProgrammingLanguageInterface>>) -> Repl {
|
||||
use linefeed::Interface;
|
||||
let line_reader = Interface::new("schala-repl").unwrap();
|
||||
let interpreter_directive_sigil = ':';
|
||||
|
||||
Repl {
|
||||
interpreter_directive_sigil,
|
||||
line_reader,
|
||||
language_states: initial_states,
|
||||
options: ReplOptions::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_repl(&mut self) {
|
||||
println!("Schala MetaInterpreter version {}", crate::VERSION_STRING);
|
||||
println!("Type {}help for help with the REPL", self.interpreter_directive_sigil);
|
||||
self.load_options();
|
||||
self.handle_repl_loop();
|
||||
self.save_before_exit();
|
||||
println!("Exiting...");
|
||||
}
|
||||
|
||||
fn load_options(&mut self) {
|
||||
self.line_reader.load_history(HISTORY_SAVE_FILE).unwrap_or(());
|
||||
match ReplOptions::load_from_file(OPTIONS_SAVE_FILE) {
|
||||
Ok(options) => {
|
||||
self.options = options;
|
||||
},
|
||||
Err(()) => ()
|
||||
};
|
||||
}
|
||||
|
||||
fn handle_repl_loop(&mut self) {
|
||||
use linefeed::ReadResult::*;
|
||||
|
||||
loop {
|
||||
self.update_line_reader();
|
||||
match self.line_reader.read_line() {
|
||||
Err(e) => {
|
||||
println!("readline IO Error: {}", e);
|
||||
break;
|
||||
},
|
||||
Ok(Eof) | Ok(Signal(_)) => break,
|
||||
Ok(Input(ref input)) => {
|
||||
self.line_reader.add_history_unique(input.to_string());
|
||||
match input.chars().nth(0) {
|
||||
Some(ch) if ch == self.interpreter_directive_sigil => match self.handle_interpreter_directive(input) {
|
||||
Some(directive_output) => println!("<> {}", directive_output),
|
||||
None => (),
|
||||
},
|
||||
_ => {
|
||||
for repl_response in self.handle_input(input) {
|
||||
println!("{}", repl_response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_line_reader(&mut self) {
|
||||
let tab_complete_handler = TabCompleteHandler::new(self.interpreter_directive_sigil, self.get_directives());
|
||||
self.line_reader.set_completer(Arc::new(tab_complete_handler)); //TODO fix this here
|
||||
let prompt_str = format!(">> ");
|
||||
self.line_reader.set_prompt(&prompt_str).unwrap();
|
||||
}
|
||||
|
||||
fn save_before_exit(&self) {
|
||||
self.line_reader.save_history(HISTORY_SAVE_FILE).unwrap_or(());
|
||||
self.options.save_to_file(OPTIONS_SAVE_FILE);
|
||||
}
|
||||
|
||||
fn handle_interpreter_directive(&mut self, input: &str) -> InterpreterDirectiveOutput {
|
||||
let mut iter = input.chars();
|
||||
iter.next();
|
||||
let arguments: Vec<&str> = iter
|
||||
.as_str()
|
||||
.split_whitespace()
|
||||
.collect();
|
||||
|
||||
if arguments.len() < 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let directives = self.get_directives();
|
||||
directives.perform(self, &arguments)
|
||||
}
|
||||
|
||||
fn get_cur_language_state(&mut self) -> &mut Box<dyn ProgrammingLanguageInterface> {
|
||||
//TODO this is obviously not complete
|
||||
&mut self.language_states[0]
|
||||
}
|
||||
|
||||
fn handle_input(&mut self, input: &str) -> Vec<ReplResponse> {
|
||||
let mut debug_requests = HashSet::new();
|
||||
for ask in self.options.debug_asks.iter() {
|
||||
debug_requests.insert(ask.clone());
|
||||
}
|
||||
|
||||
let request = ComputationRequest { source: input, debug_requests };
|
||||
let ref mut language_state = self.get_cur_language_state();
|
||||
let response = language_state.run_computation(request);
|
||||
response::handle_computation_response(response, &self.options)
|
||||
}
|
||||
|
||||
fn get_directives(&mut self) -> CommandTree {
|
||||
let language_state = self.get_cur_language_state();
|
||||
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) {
|
||||
LangMetaResponse::StageNames(names) => names,
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
directives_from_pass_names(&pass_names)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct TabCompleteHandler {
|
||||
sigil: char,
|
||||
top_level_commands: CommandTree,
|
||||
}
|
||||
|
||||
use linefeed::complete::{Completion, Completer};
|
||||
use linefeed::terminal::Terminal;
|
||||
|
||||
impl TabCompleteHandler {
|
||||
fn new(sigil: char, top_level_commands: CommandTree) -> TabCompleteHandler {
|
||||
TabCompleteHandler {
|
||||
top_level_commands,
|
||||
sigil,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Terminal> Completer<T> for TabCompleteHandler {
|
||||
fn complete(&self, word: &str, prompter: &::linefeed::prompter::Prompter<T>, start: usize, _end: usize) -> Option<Vec<Completion>> {
|
||||
let line = prompter.buffer();
|
||||
|
||||
if !line.starts_with(self.sigil) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut words = line[1..(if start == 0 { 1 } else { start })].split_whitespace();
|
||||
let mut completions = Vec::new();
|
||||
let mut command_tree: Option<&CommandTree> = Some(&self.top_level_commands);
|
||||
|
||||
loop {
|
||||
match words.next() {
|
||||
None => {
|
||||
let top = match command_tree {
|
||||
Some(CommandTree::Top(_)) => true,
|
||||
_ => false
|
||||
};
|
||||
let word = if top { word.get(1..).unwrap() } else { word };
|
||||
for cmd in command_tree.map(|x| x.get_subcommands()).unwrap_or(vec![]).into_iter() {
|
||||
if cmd.starts_with(word) {
|
||||
completions.push(Completion {
|
||||
completion: format!("{}{}", if top { ":" } else { "" }, cmd),
|
||||
display: Some(cmd.to_string()),
|
||||
suffix: ::linefeed::complete::Suffix::Some(' ')
|
||||
})
|
||||
}
|
||||
}
|
||||
break;
|
||||
},
|
||||
Some(s) => {
|
||||
let new_ptr: Option<&CommandTree> = command_tree.and_then(|cm| match cm {
|
||||
CommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::NonTerminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
|
||||
CommandTree::Terminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
|
||||
});
|
||||
command_tree = new_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(completions)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
use crate::language::DebugAsk;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::collections::HashSet;
|
||||
use std::fs::File;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ReplOptions {
|
||||
pub debug_asks: HashSet<DebugAsk>,
|
||||
pub show_total_time: bool,
|
||||
pub show_stage_times: bool,
|
||||
}
|
||||
|
||||
impl ReplOptions {
|
||||
pub fn new() -> ReplOptions {
|
||||
ReplOptions {
|
||||
debug_asks: HashSet::new(),
|
||||
show_total_time: true,
|
||||
show_stage_times: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save_to_file(&self, filename: &str) {
|
||||
let res = File::create(filename)
|
||||
.and_then(|mut file| {
|
||||
let buf = crate::serde_json::to_string(self).unwrap();
|
||||
file.write_all(buf.as_bytes())
|
||||
});
|
||||
if let Err(err) = res {
|
||||
println!("Error saving {} file {}", filename, err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from_file(filename: &str) -> Result<ReplOptions, ()> {
|
||||
File::open(filename)
|
||||
.and_then(|mut file| {
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
})
|
||||
.and_then(|contents| {
|
||||
let output: ReplOptions = crate::serde_json::from_str(&contents)?;
|
||||
Ok(output)
|
||||
})
|
||||
.map_err(|_| ())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
use colored::*;
|
||||
use std::fmt;
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::ReplOptions;
|
||||
use crate::language::{ DebugAsk, ComputationResponse};
|
||||
|
||||
pub struct ReplResponse {
|
||||
label: Option<String>,
|
||||
text: String,
|
||||
color: Option<Color>
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplResponse {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut buf = String::new();
|
||||
if let Some(ref label) = self.label {
|
||||
write!(buf, "({})", label).unwrap();
|
||||
}
|
||||
write!(buf, "=> {}", self.text).unwrap();
|
||||
write!(f, "{}", match self.color {
|
||||
Some(c) => buf.color(c),
|
||||
None => buf.normal()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn handle_computation_response(response: ComputationResponse, options: &ReplOptions) -> Vec<ReplResponse> {
|
||||
let mut responses = vec![];
|
||||
|
||||
if options.show_total_time {
|
||||
responses.push(ReplResponse {
|
||||
label: Some("Total time".to_string()),
|
||||
text: format!("{:?}", response.global_output_stats.total_duration),
|
||||
color: None,
|
||||
});
|
||||
}
|
||||
|
||||
if options.show_stage_times {
|
||||
responses.push(ReplResponse {
|
||||
label: Some("Stage times".to_string()),
|
||||
text: format!("{:?}", response.global_output_stats.stage_durations),
|
||||
color: None,
|
||||
});
|
||||
}
|
||||
|
||||
for debug_resp in response.debug_responses {
|
||||
let stage_name = match debug_resp.ask {
|
||||
DebugAsk::ByStage { stage_name, .. } => stage_name,
|
||||
_ => continue,
|
||||
};
|
||||
responses.push(ReplResponse {
|
||||
label: Some(stage_name.to_string()),
|
||||
text: debug_resp.value,
|
||||
color: Some(Color::Red),
|
||||
});
|
||||
}
|
||||
|
||||
responses.push(match response.main_output {
|
||||
Ok(s) => ReplResponse { label: None, text: s, color: None },
|
||||
Err(e) => ReplResponse { label: Some("Error".to_string()), text: e, color: Some(Color::Red) },
|
||||
});
|
||||
|
||||
responses
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
|
||||
fn outer() {
|
||||
fn inner(a) {
|
||||
a + 10
|
||||
}
|
||||
|
||||
inner(20) + 8.3
|
||||
}
|
||||
|
||||
outer()
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
fn hella(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
fn paha(x, y, z) {
|
||||
x * y * z
|
||||
}
|
||||
|
||||
a = 1
|
||||
|
||||
c = if a {
|
||||
10
|
||||
} else {
|
||||
20
|
||||
}
|
||||
|
||||
q = 4
|
||||
q = q + 2
|
||||
q + 1 + c
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
if 20 {
|
||||
a = 20
|
||||
b = 30
|
||||
c = 40
|
||||
a + b + c
|
||||
} else {
|
||||
Null
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
(fn(q) { q * 2 }(25))
|
||||
|
||||
a = fn(x) { x + 5 }
|
||||
a(2)
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
fn subtract(a, b) {
|
||||
a - b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
first_value = add(20, 20)
|
||||
second_value = subtract(700, 650)
|
||||
first_value + second_value
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
|
||||
fn hella(x) {
|
||||
print("hey")
|
||||
if x == 3 {
|
||||
Null
|
||||
} else {
|
||||
hella(x + 1)
|
||||
}
|
||||
}
|
||||
|
||||
hella(0)
|
||||
|
||||
|
||||
|
||||
fn fib(x) {
|
||||
if x < 3 {
|
||||
1
|
||||
} else {
|
||||
fib(x - 1) + fib(x - 2)
|
||||
}
|
||||
}
|
||||
|
||||
fib(10)
|
|
@ -0,0 +1,11 @@
|
|||
let c = 10
|
||||
|
||||
fn add(a, b) {
|
||||
let c = a + b
|
||||
c
|
||||
}
|
||||
|
||||
let mut b = 20
|
||||
|
||||
println(add(1,2))
|
||||
println(c + b)
|
|
@ -0,0 +1,17 @@
|
|||
fn main() {
|
||||
let a = 10
|
||||
let b = 20
|
||||
a + b
|
||||
}
|
||||
|
||||
//this is a one-line comment
|
||||
|
||||
/* this is
|
||||
a multiline
|
||||
comment
|
||||
*/
|
||||
|
||||
print(main())
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
for n <- 1..=100 {
|
||||
if n % 15 == 0 {
|
||||
print("FizzBuzz")
|
||||
} else if n % 5 == 0 {
|
||||
print("Buzz")
|
||||
} else if n % 3 == 0 {
|
||||
print("Fizz")
|
||||
} else {
|
||||
print(n.to_string())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
|
||||
fn main() {
|
||||
|
||||
//comments are C-style
|
||||
/* nested comments /* are cool */ */
|
||||
|
||||
}
|
||||
|
||||
@annotations are with @-
|
||||
|
||||
// variable expressions
|
||||
var a: I32 = 20
|
||||
const b: String = 20
|
||||
|
||||
there(); can(); be(); multiple(); statements(); per_line();
|
||||
|
||||
//string interpolation
|
||||
const yolo = "I have ${a + b} people in my house"
|
||||
|
||||
// let expressions ??? not sure if I want this
|
||||
let a = 10, b = 20, c = 30 in a + b + c
|
||||
|
||||
//list literal
|
||||
const q = [1,2,3,4]
|
||||
|
||||
//lambda literal
|
||||
q.map({|item| item * 100 })
|
||||
|
||||
fn yolo(a: MyType, b: YourType): ReturnType<Param1, Param2> {
|
||||
if a == 20 {
|
||||
return "early"
|
||||
}
|
||||
var sex = 20
|
||||
sex
|
||||
}
|
||||
|
||||
|
||||
/* for/while loop topics */
|
||||
|
||||
//infinite loop
|
||||
while {
|
||||
if x() { break }
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
//conditional loop
|
||||
while conditionHolds() {
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
//iteration over a variable
|
||||
for i <- [1..1000] {
|
||||
|
||||
} //return type is return type of block
|
||||
|
||||
|
||||
//monadic decomposition
|
||||
for {
|
||||
a <- maybeInt();
|
||||
s <- foo()
|
||||
} return {
|
||||
a + s
|
||||
} //return type is Monad<return type of block>
|
||||
|
||||
/* end of for loops */
|
||||
|
||||
|
||||
|
||||
/* conditionals/pattern matching */
|
||||
|
||||
// "is" operator for "does this pattern match"
|
||||
|
||||
x is Some(t) // type bool
|
||||
|
||||
if x {
|
||||
is Some(t) => {
|
||||
},
|
||||
is None => {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//syntax is, I guess, for <expr> <brace-block>, where <expr> is a bool, or a <arrow-expr>
|
||||
|
||||
// type level alises
|
||||
typealias <name> = <other type> #maybe thsi should be 'alias'?
|
||||
|
||||
/*
|
||||
what if type A = B meant that you could had to create A's with A(B), but when you used A's the interface was exactly like B's?
|
||||
maybe introduce a 'newtype' keyword for this
|
||||
*/
|
||||
|
||||
//declaring types of all stripes
|
||||
type MyData = { a: i32, b: String }
|
||||
type MyType = MyType
|
||||
type Option<a> = None | Some(a)
|
||||
type Signal = Absence | SimplePresence(i32) | ComplexPresence {a: i32, b: MyCustomData}
|
||||
|
||||
//traits
|
||||
|
||||
trait Bashable { }
|
||||
trait Luggable {
|
||||
fn lug(self, a: Option<Self>)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// lambdas
|
||||
// ruby-style not rust-style
|
||||
const a: X -> Y -> Z = {|x,y| }
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
println(sua(4))
|
||||
|
||||
fn sua(x): Int {
|
||||
x + 10
|
||||
}
|
||||
|
||||
|
||||
//let a = getline()
|
||||
|
||||
/*
|
||||
if a == "true" {
|
||||
println("You typed true")
|
||||
} else {
|
||||
println("You typed something else")
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
fn a(x) {
|
||||
x + 20
|
||||
}
|
||||
|
||||
fn x(x) {
|
||||
x + a(9384)
|
||||
}
|
||||
|
||||
a(0)
|
||||
x(1)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
|
||||
(display (+ 1 2))
|
||||
(display "Hello")
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
fn めんどくさい(a) {
|
||||
a + 20
|
||||
}
|
||||
|
||||
print(めんどくさい(394))
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
|
||||
a = 0
|
||||
while a < 100000
|
||||
print("hello", a)
|
||||
a = a + 1
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
extern crate schala_repl;
|
||||
|
||||
//extern crate maaru_lang;
|
||||
//extern crate rukka_lang;
|
||||
//extern crate robo_lang;
|
||||
extern crate schala_lang;
|
||||
use schala_repl::{ProgrammingLanguageInterface, start_repl};
|
||||
|
||||
extern { }
|
||||
|
||||
fn main() {
|
||||
let langs: Vec<Box<dyn ProgrammingLanguageInterface>> = vec![Box::new(schala_lang::Schala::new())];
|
||||
start_repl(langs);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Schala Metainterpreter Web Evaluator</title>
|
||||
<style>
|
||||
.CodeArea {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
</div>
|
||||
<script src="bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,64 @@
|
|||
const React = require("react");
|
||||
const ReactDOM = require("react-dom");
|
||||
const superagent = require("superagent");
|
||||
|
||||
const serverAddress = "http://localhost:8000";
|
||||
|
||||
class CodeArea extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {value: "", lastOutput: null};
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.submit = this.submit.bind(this);
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({value: event.target.value});
|
||||
}
|
||||
|
||||
submit(event) {
|
||||
console.log("Event", this.state.value);
|
||||
const source = this.state.value;
|
||||
|
||||
superagent.post(`${serverAddress}/input`)
|
||||
.send({ source })
|
||||
.set("accept", "json")
|
||||
.end((error, response) => {
|
||||
if (response) {
|
||||
console.log("Resp", response);
|
||||
this.setState({lastOutput: response.body.text})
|
||||
} else {
|
||||
console.error("Error: ", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderOutput() {
|
||||
if (!this.state.lastOutput) {
|
||||
return null;
|
||||
}
|
||||
return <textarea readOnly value={ this.state.lastOutput } />;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className="CodeArea">
|
||||
<div className="input">
|
||||
<textarea value={ this.state.value } onChange={this.handleChange}>
|
||||
</textarea>
|
||||
<button onClick={ this.submit }>Run!</button>
|
||||
</div>
|
||||
<div className="output">
|
||||
{ this.renderOutput() }
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
const main = (<div>
|
||||
<h1>Schala web input</h1>
|
||||
<p>Write your source code here</p>
|
||||
<CodeArea/>
|
||||
</div>);
|
||||
|
||||
const rootDom = document.getElementById("main");
|
||||
ReactDOM.render(main, rootDom);
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "static",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"babel": "^6.23.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^14.4.0",
|
||||
"react": "^15.6.1",
|
||||
"react-dom": "^15.6.1",
|
||||
"superagent": "^3.6.3",
|
||||
"uglify-js": "^3.1.1"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"babel-preset-react",
|
||||
"babel-preset-es2015"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"build": "browserify main.jsx -t babelify -o bundle.js",
|
||||
"build-minify": "browserify main.jsx -t babelify | uglifyjs > bundle.js"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +0,0 @@
|
|||
/target
|
||||
/Cargo.lock
|
|
@ -1,13 +0,0 @@
|
|||
[package]
|
||||
name = "parser-combinator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
arbitrary = "1.2.0"
|
||||
proptest = "1.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.16.0"
|
|
@ -1,10 +0,0 @@
|
|||
# Rust Parser Combinator
|
||||
|
||||
This is a super-basic Rust parser combinator library I wrote mostly
|
||||
as an exercise for myself. Inspired by [nom](https://github.com/rust-bakery/nom)
|
||||
and [chumsky](https://github.com/zesterer/chumsky)
|
||||
|
||||
## Ideas for future work
|
||||
|
||||
* See if some of the ideas in [Efficient Parsing with Parser Combinators](https://research.rug.nl/en/publications/efficient-parsing-with-parser-combinators)
|
||||
can be incorporated here.
|
|
@ -1,198 +0,0 @@
|
|||
use crate::parser::{ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub fn choice2<P1, P2, I, O, E>(parser1: P1, parser2: P2) -> impl Parser<I, O, E>
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
choice((parser1, parser2))
|
||||
}
|
||||
|
||||
pub fn choice<C, I, O, E>(choices: C) -> impl Parser<I, O, E>
|
||||
where
|
||||
C: Choice<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
let rep = choices.representation();
|
||||
(move |input| choices.parse(input), rep)
|
||||
}
|
||||
|
||||
pub trait Choice<I: Clone, O, E> {
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E>;
|
||||
fn representation(&self) -> Representation;
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2> Choice<I, O, E> for (P1, P2)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3> Choice<I, O, E> for (P1, P2, P3)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3, P4> Choice<I, O, E> for (P1, P2, P3, P4)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
P4: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2, &self.3];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![&self.0 as &dyn Parser<I, O, E>, &self.1, &self.2, &self.3];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3, P4, P5> Choice<I, O, E> for (P1, P2, P3, P4, P5)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
P4: Parser<I, O, E>,
|
||||
P5: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, P1, P2, P3, P4, P5, P6> Choice<I, O, E> for (P1, P2, P3, P4, P5, P6)
|
||||
where
|
||||
P1: Parser<I, O, E>,
|
||||
P2: Parser<I, O, E>,
|
||||
P3: Parser<I, O, E>,
|
||||
P4: Parser<I, O, E>,
|
||||
P5: Parser<I, O, E>,
|
||||
P6: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
&self.5,
|
||||
];
|
||||
choice_loop(input, parsers)
|
||||
}
|
||||
fn representation(&self) -> Representation {
|
||||
let parsers = vec![
|
||||
&self.0 as &dyn Parser<I, O, E>,
|
||||
&self.1,
|
||||
&self.2,
|
||||
&self.3,
|
||||
&self.4,
|
||||
&self.5,
|
||||
];
|
||||
repr_loop(parsers)
|
||||
}
|
||||
}
|
||||
|
||||
fn choice_loop<I, O, E>(input: I, parsers: Vec<&dyn Parser<I, O, E>>) -> ParseResult<I, O, E>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
//TODO need a more principled way to return an error when no choices work
|
||||
let mut err = None;
|
||||
|
||||
for parser in parsers.iter() {
|
||||
match parser.parse(input.clone()) {
|
||||
Ok(result) => return Ok(result),
|
||||
Err(e) => {
|
||||
err = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err.unwrap())
|
||||
}
|
||||
|
||||
fn repr_loop<I, O, E>(parsers: Vec<&dyn Parser<I, O, E>>) -> Representation
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
let mut iter = parsers.iter().map(|p| p.representation());
|
||||
Representation::from_choice(&mut iter)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::combinators::repeated;
|
||||
use crate::primitives::literal;
|
||||
|
||||
#[test]
|
||||
fn test_choice() {
|
||||
let p = choice2(
|
||||
literal("gnostika").to(1),
|
||||
repeated(literal(" ")).at_least(1).to(2),
|
||||
);
|
||||
assert_eq!(p.parse("gnostika twentynine"), Ok((1, " twentynine")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_several_choices() {
|
||||
let p = choice((
|
||||
literal("a").to(1),
|
||||
literal("q").to(10),
|
||||
repeated(literal("chutney")).to(200),
|
||||
literal("banana").to(10000),
|
||||
));
|
||||
|
||||
assert_eq!(p.parse("q drugs").unwrap(), (10, " drugs"));
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
use crate::parser::{Parser, ParserInput};
|
||||
|
||||
pub fn map<P, F, I, O1, O2, E>(parser: P, map_fn: F) -> impl Parser<I, O2, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
P: Parser<I, O1, E>,
|
||||
F: Fn(O1) -> O2,
|
||||
{
|
||||
let rep = parser.representation();
|
||||
let p = move |input| {
|
||||
parser
|
||||
.parse(input)
|
||||
.map(|(result, rest)| (map_fn(result), rest))
|
||||
};
|
||||
(p, rep)
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
mod map;
|
||||
mod optional;
|
||||
mod repeated;
|
||||
mod separated_by;
|
||||
|
||||
pub use map::map;
|
||||
pub use optional::optional;
|
||||
pub use repeated::repeated;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::Parser;
|
||||
use crate::primitives::literal;
|
||||
|
||||
#[test]
|
||||
fn test_map() {
|
||||
let lit_a = literal("a");
|
||||
let output = lit_a.map(|s| s.to_uppercase()).parse("a yolo");
|
||||
assert_eq!(output.unwrap(), ("A".to_string(), " yolo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_or_more() {
|
||||
let p = repeated(literal("bongo ")).at_least(1);
|
||||
let input = "bongo bongo bongo bongo bongo ";
|
||||
|
||||
let (output, rest) = p.parse(input).unwrap();
|
||||
assert_eq!(rest, "");
|
||||
assert_eq!(output.len(), 5);
|
||||
|
||||
let (output, rest) = p.parse("bongo ecks").unwrap();
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(rest, "ecks");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_separated_by() {
|
||||
let p = repeated(literal("garb").to(20))
|
||||
.separated_by(repeated(literal(" ")).at_least(1), false);
|
||||
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb").unwrap(),
|
||||
(vec![20, 20, 20, 20], "")
|
||||
);
|
||||
|
||||
assert!(p.parse("garb garb garb garb ").is_err());
|
||||
|
||||
let p =
|
||||
repeated(literal("garb").to(20)).separated_by(repeated(literal(" ")).at_least(1), true);
|
||||
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb").unwrap(),
|
||||
(vec![20, 20, 20, 20], "")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb ").unwrap(),
|
||||
(vec![20, 20, 20, 20], "")
|
||||
);
|
||||
assert_eq!(
|
||||
p.parse("garb garb garb garb q").unwrap(),
|
||||
(vec![20, 20, 20, 20], "q")
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
use crate::parser::{Parser, ParserInput, Representation};
|
||||
|
||||
pub fn optional<P, I, O, E>(parser: P) -> impl Parser<I, Option<O>, E>
|
||||
where
|
||||
P: Parser<I, O, E>,
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
let rep = Representation::from_choice(
|
||||
&mut [parser.representation(), Representation::new("ε")].into_iter(),
|
||||
);
|
||||
let p = move |input: I| match parser.parse(input.clone()) {
|
||||
Ok((output, rest)) => Ok((Some(output), rest)),
|
||||
Err(_e) => Ok((None, input)),
|
||||
};
|
||||
|
||||
(p, rep)
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
use crate::combinators::separated_by::SeparatedBy;
|
||||
use crate::parser::{BoxedParser, ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub fn repeated<'a, P, I, O>(parser: P) -> Repeated<'a, I, O>
|
||||
where
|
||||
P: Parser<I, O, I> + 'a,
|
||||
I: ParserInput + Clone + 'a,
|
||||
{
|
||||
Repeated {
|
||||
inner_parser: BoxedParser::new(parser),
|
||||
at_least: None,
|
||||
at_most: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Repeated<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
pub(super) inner_parser: BoxedParser<'a, I, O, I>,
|
||||
pub(super) at_least: Option<u16>,
|
||||
pub(super) at_most: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'a, I, O> Repeated<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
pub fn at_least(self, n: u16) -> Self {
|
||||
Self {
|
||||
at_least: Some(n),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub fn at_most(self, n: u16) -> Self {
|
||||
Self {
|
||||
at_most: Some(n),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separated_by<D, O2>(self, delimiter: D, allow_trailing: bool) -> SeparatedBy<'a, I, O>
|
||||
where
|
||||
D: Parser<I, O2, I> + 'a,
|
||||
O2: 'a,
|
||||
I: 'a,
|
||||
{
|
||||
SeparatedBy {
|
||||
inner_repeated: self,
|
||||
delimiter: BoxedParser::new(delimiter.to(())),
|
||||
allow_trailing,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I, O> Parser<I, Vec<O>, I> for Repeated<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone + 'a,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, Vec<O>, I> {
|
||||
let at_least = self.at_least.unwrap_or(0);
|
||||
let at_most = self.at_most.unwrap_or(u16::MAX);
|
||||
|
||||
if at_most == 0 {
|
||||
return Ok((vec![], input));
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut count: u16 = 0;
|
||||
let mut further_input = input.clone();
|
||||
|
||||
while let Ok((item, rest)) = self.inner_parser.parse(further_input.clone()) {
|
||||
results.push(item);
|
||||
further_input = rest;
|
||||
count += 1;
|
||||
if count >= at_most {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if count < at_least {
|
||||
return Err(input);
|
||||
}
|
||||
|
||||
Ok((results, further_input))
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
Representation::repeated(
|
||||
self.inner_parser.representation(),
|
||||
self.at_least.unwrap_or(0),
|
||||
self.at_most.unwrap_or(u16::MAX),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
use crate::combinators::repeated::Repeated;
|
||||
use crate::parser::{BoxedParser, ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub struct SeparatedBy<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone,
|
||||
{
|
||||
pub(super) inner_repeated: Repeated<'a, I, O>,
|
||||
pub(super) delimiter: BoxedParser<'a, I, (), I>,
|
||||
pub(super) allow_trailing: bool,
|
||||
}
|
||||
|
||||
impl<'a, I, O> Parser<I, Vec<O>, I> for SeparatedBy<'a, I, O>
|
||||
where
|
||||
I: ParserInput + Clone + 'a,
|
||||
{
|
||||
fn representation(&self) -> Representation {
|
||||
Representation::new("sepby")
|
||||
}
|
||||
|
||||
fn parse(&self, input: I) -> ParseResult<I, Vec<O>, I> {
|
||||
let at_least = self.inner_repeated.at_least.unwrap_or(0);
|
||||
let at_most = self.inner_repeated.at_most.unwrap_or(u16::MAX);
|
||||
let parser = &self.inner_repeated.inner_parser;
|
||||
let delimiter = &self.delimiter;
|
||||
|
||||
if at_most == 0 {
|
||||
return Ok((vec![], input));
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut count: u16 = 0;
|
||||
let mut further_input;
|
||||
|
||||
match parser.parse(input.clone()) {
|
||||
Ok((item, rest)) => {
|
||||
results.push(item);
|
||||
further_input = rest;
|
||||
}
|
||||
Err(_e) => {
|
||||
if at_least > 0 {
|
||||
return Err(input);
|
||||
} else {
|
||||
return Ok((vec![], input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match delimiter.parse(further_input.clone()) {
|
||||
Ok(((), rest)) => {
|
||||
further_input = rest;
|
||||
}
|
||||
Err(_e) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match parser.parse(further_input.clone()) {
|
||||
Ok((item, rest)) => {
|
||||
results.push(item);
|
||||
further_input = rest;
|
||||
count += 1;
|
||||
}
|
||||
Err(_e) if self.allow_trailing => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
if count >= at_most {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if count < at_least {
|
||||
return Err(input);
|
||||
}
|
||||
|
||||
Ok((results, further_input))
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
pub mod choice;
|
||||
pub mod combinators;
|
||||
mod parser;
|
||||
pub mod primitives;
|
||||
pub mod sequence;
|
||||
|
||||
pub use parser::{ParseResult, Parser, ParserInput, Representation};
|
|
@ -1,38 +0,0 @@
|
|||
use crate::parser::{ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub struct BoxedParser<'a, I, O, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
{
|
||||
inner: Box<dyn Parser<I, O, E> + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, I, O, E> BoxedParser<'a, I, O, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
{
|
||||
pub(crate) fn new<P>(inner: P) -> Self
|
||||
where
|
||||
P: Parser<I, O, E> + 'a,
|
||||
{
|
||||
BoxedParser {
|
||||
inner: Box::new(inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I: ParserInput, O, E> Parser<I, O, E> for BoxedParser<'a, I, O, E> {
|
||||
fn representation(&self) -> Representation {
|
||||
self.inner.representation()
|
||||
}
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
self.inner.parse(input)
|
||||
}
|
||||
|
||||
fn boxed<'b>(self) -> BoxedParser<'b, I, O, E>
|
||||
where
|
||||
Self: Sized + 'b,
|
||||
{
|
||||
self
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
mod boxed_parser;
|
||||
mod named_parser;
|
||||
mod parser_input;
|
||||
mod representation;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
pub use boxed_parser::BoxedParser;
|
||||
pub use named_parser::NamedParser;
|
||||
pub use parser_input::ParserInput;
|
||||
pub use representation::Representation;
|
||||
|
||||
pub type ParseResult<I, O, E> = Result<(O, I), E>;
|
||||
|
||||
pub trait Parser<I, O, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E>;
|
||||
|
||||
fn representation(&self) -> Representation;
|
||||
|
||||
fn boxed<'a>(self) -> BoxedParser<'a, I, O, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
BoxedParser::new(self)
|
||||
}
|
||||
|
||||
fn map<'a, F, O2>(self, map_fn: F) -> BoxedParser<'a, I, O2, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
E: 'a,
|
||||
O: 'a,
|
||||
O2: 'a,
|
||||
F: Fn(O) -> O2 + 'a,
|
||||
{
|
||||
crate::combinators::map(self, map_fn).boxed()
|
||||
}
|
||||
|
||||
fn to<'a, O2>(self, item: O2) -> BoxedParser<'a, I, O2, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
O: 'a,
|
||||
O2: Clone + 'a,
|
||||
E: 'a,
|
||||
{
|
||||
self.map(move |_| item.clone())
|
||||
}
|
||||
|
||||
fn then<'a, P, O2>(self, next_parser: P) -> BoxedParser<'a, I, (O, O2), E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
O: 'a,
|
||||
O2: 'a,
|
||||
E: 'a,
|
||||
P: Parser<I, O2, E> + 'a,
|
||||
{
|
||||
crate::sequence::tuple2(self, next_parser).boxed()
|
||||
}
|
||||
|
||||
fn ignore_then<'a, P, O2>(self, next_parser: P) -> BoxedParser<'a, I, O2, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
O: 'a,
|
||||
O2: 'a,
|
||||
E: 'a,
|
||||
P: Parser<I, O2, E> + 'a,
|
||||
{
|
||||
crate::sequence::tuple2(self, next_parser).map(|(_, next_output)| next_output)
|
||||
}
|
||||
|
||||
fn then_ignore<'a, P, O2>(self, next_parser: P) -> BoxedParser<'a, I, O, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
O: 'a,
|
||||
O2: 'a,
|
||||
E: 'a,
|
||||
P: Parser<I, O2, E> + 'a,
|
||||
{
|
||||
crate::sequence::tuple2(self, next_parser).map(|(this_output, _)| this_output)
|
||||
}
|
||||
|
||||
fn delimited<'a, P1, O1, P2, O2>(self, left: P1, right: P2) -> BoxedParser<'a, I, O, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
O1: 'a,
|
||||
O2: 'a,
|
||||
O: 'a,
|
||||
E: 'a,
|
||||
P1: Parser<I, O1, E> + 'a,
|
||||
P2: Parser<I, O2, E> + 'a,
|
||||
{
|
||||
crate::sequence::seq((left, self, right)).map(|(_, output, _)| output)
|
||||
}
|
||||
|
||||
fn surrounded_by<'a, P, O1>(self, surrounding: P) -> BoxedParser<'a, I, O, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
O1: 'a,
|
||||
O: 'a,
|
||||
E: 'a,
|
||||
P: Parser<I, O1, E> + 'a,
|
||||
{
|
||||
BoxedParser::new(move |input| {
|
||||
let p1 = |i| surrounding.parse(i);
|
||||
let p2 = |i| surrounding.parse(i);
|
||||
let main = |i| self.parse(i);
|
||||
crate::sequence::seq((p1, main, p2))
|
||||
.map(|(_, output, _)| output)
|
||||
.parse(input)
|
||||
})
|
||||
}
|
||||
|
||||
fn optional<'a>(self) -> BoxedParser<'a, I, Option<O>, E>
|
||||
where
|
||||
I: Clone + 'a,
|
||||
O: 'a,
|
||||
E: 'a,
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
crate::combinators::optional(self).boxed()
|
||||
}
|
||||
|
||||
fn named<'a>(self, parser_name: &str) -> NamedParser<'a, I, O, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
I: 'a,
|
||||
{
|
||||
NamedParser::new(self.boxed(), parser_name.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ParserInput, O, E, F> Parser<I, O, E> for F
|
||||
where
|
||||
F: Fn(I) -> ParseResult<I, O, E>,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
self(input)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
Representation::new("NOT IMPL'D")
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: ParserInput, O, E, F> Parser<I, O, E> for (F, Representation)
|
||||
where
|
||||
F: Fn(I) -> ParseResult<I, O, E>,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
self.0(input)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
self.1.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, E, T> Parser<I, O, E> for Rc<T>
|
||||
where
|
||||
I: ParserInput,
|
||||
T: Parser<I, O, E>,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
self.as_ref().parse(input)
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
self.as_ref().representation()
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
use super::boxed_parser::BoxedParser;
|
||||
use crate::parser::{ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub struct NamedParser<'a, I, O, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
{
|
||||
inner_parser: BoxedParser<'a, I, O, E>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl<'a, I, O, E> NamedParser<'a, I, O, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
{
|
||||
pub(super) fn new(inner_parser: BoxedParser<'a, I, O, E>, name: String) -> Self
|
||||
where
|
||||
I: 'a,
|
||||
{
|
||||
NamedParser { inner_parser, name }
|
||||
}
|
||||
|
||||
pub fn get_name(&'a self) -> &'a str {
|
||||
self.name.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I: ParserInput, O, E> Parser<I, O, E> for NamedParser<'a, I, O, E> {
|
||||
fn representation(&self) -> Representation {
|
||||
self.inner_parser.representation()
|
||||
}
|
||||
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E> {
|
||||
self.inner_parser.parse(input)
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
pub trait ParserInput: std::fmt::Debug {
|
||||
type Output;
|
||||
fn next_token() -> Self::Output;
|
||||
}
|
||||
|
||||
impl ParserInput for &str {
|
||||
type Output = ();
|
||||
fn next_token() -> Self::Output {
|
||||
()
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Representation {
|
||||
val: String,
|
||||
}
|
||||
|
||||
impl Representation {
|
||||
pub fn new(from: &str) -> Self {
|
||||
Self {
|
||||
val: from.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_choice(
|
||||
choice_parser_reps: &mut impl Iterator<Item = Representation>,
|
||||
) -> Self {
|
||||
let mut buf = String::new();
|
||||
let mut iter = choice_parser_reps.peekable();
|
||||
loop {
|
||||
let rep = match iter.next() {
|
||||
Some(r) => r,
|
||||
None => break,
|
||||
};
|
||||
buf.push_str(&rep.val);
|
||||
match iter.peek() {
|
||||
Some(_) => {
|
||||
buf.push_str(" | ");
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Representation::new(&buf)
|
||||
}
|
||||
|
||||
pub(crate) fn from_sequence(
|
||||
sequence_representations: &mut impl Iterator<Item = Representation>,
|
||||
) -> Self {
|
||||
let mut buf = String::new();
|
||||
let mut iter = sequence_representations.peekable();
|
||||
loop {
|
||||
let rep = match iter.next() {
|
||||
Some(r) => r,
|
||||
None => break,
|
||||
};
|
||||
buf.push_str(&rep.val);
|
||||
match iter.peek() {
|
||||
Some(_) => {
|
||||
buf.push_str(" ");
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Representation::new(&buf)
|
||||
}
|
||||
|
||||
// TODO use at_least, at_most
|
||||
pub(crate) fn repeated(underlying: Representation, at_least: u16, _at_most: u16) -> Self {
|
||||
let sigil = if at_least == 0 { "*" } else { "+" };
|
||||
Representation::new(&format!("({}){}", underlying.val, sigil))
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
use crate::parser::{ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub fn literal_char(expected: char) -> impl Fn(&str) -> ParseResult<&str, char, &str> {
|
||||
move |input| match input.chars().next() {
|
||||
Some(ch) if ch == expected => Ok((expected, &input[ch.len_utf8()..])),
|
||||
_ => Err(input),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn literal<'a>(expected: &'static str) -> impl Parser<&'a str, &'a str, &'a str> {
|
||||
println!("literal call expected: {}", expected);
|
||||
let rep = Representation::new(expected);
|
||||
let p = move |input: &'a str| match input.get(0..expected.len()) {
|
||||
Some(next) if next == expected => Ok((expected, &input[expected.len()..])),
|
||||
_ => Err(input),
|
||||
};
|
||||
(p, rep)
|
||||
}
|
||||
|
||||
pub fn any_char(input: &str) -> ParseResult<&str, char, &str> {
|
||||
match input.chars().next() {
|
||||
Some(ch) => Ok((ch, &input[ch.len_utf8()..])),
|
||||
None => Err(input),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn one_of<'a>(items: &'static str) -> impl Parser<&'a str, &'a str, &'a str> {
|
||||
let p = move |input: &'a str| {
|
||||
if let Some(ch) = input.chars().next() {
|
||||
if items.contains(ch) {
|
||||
let (first, rest) = input.split_at(1);
|
||||
return Ok((first, rest));
|
||||
}
|
||||
}
|
||||
Err(input)
|
||||
};
|
||||
|
||||
let mut s = String::new();
|
||||
for ch in items.chars() {
|
||||
s.push(ch);
|
||||
s.push_str(" | ");
|
||||
}
|
||||
let rep = Representation::new(&s);
|
||||
(p, rep)
|
||||
}
|
||||
|
||||
pub fn pred<P, F, I, O>(parser: P, pred_fn: F) -> impl Parser<I, O, I>
|
||||
where
|
||||
I: ParserInput,
|
||||
P: Parser<I, O, I>,
|
||||
F: Fn(&O) -> bool,
|
||||
{
|
||||
let orig_rep = parser.representation();
|
||||
(
|
||||
move |input| {
|
||||
parser.parse(input).and_then(|(result, rest)| {
|
||||
if pred_fn(&result) {
|
||||
Ok((result, rest))
|
||||
} else {
|
||||
Err(rest)
|
||||
}
|
||||
})
|
||||
},
|
||||
Representation::new(&format!("{:?} if <PREDICATE>", orig_rep)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses a standard identifier in a programming language
|
||||
pub fn identifier(input: &str) -> ParseResult<&str, String, &str> {
|
||||
let mut chars = input.chars();
|
||||
let mut buf = String::new();
|
||||
|
||||
match chars.next() {
|
||||
Some(ch) if ch.is_alphabetic() => buf.push(ch),
|
||||
_ => return Err(input),
|
||||
}
|
||||
|
||||
for next in chars {
|
||||
if next.is_alphanumeric() {
|
||||
buf.push(next);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let next_index = buf.len();
|
||||
Ok((buf, &input[next_index..]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_identifier() {
|
||||
assert_eq!(
|
||||
identifier("bongo1beans").unwrap(),
|
||||
(("bongo1beans".to_string(), ""))
|
||||
);
|
||||
assert_eq!(identifier("2bongo1beans"), Err("2bongo1beans"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pred() {
|
||||
let p = pred(any_char, |c| *c == 'f');
|
||||
assert_eq!(p.parse("frog"), Ok(('f', "rog")));
|
||||
}
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
use crate::parser::{ParseResult, Parser, ParserInput, Representation};
|
||||
|
||||
pub fn tuple2<P1, P2, I, O1, O2, E>(parser1: P1, parser2: P2) -> impl Parser<I, (O1, O2), E>
|
||||
where
|
||||
I: ParserInput,
|
||||
P1: Parser<I, O1, E>,
|
||||
P2: Parser<I, O2, E>,
|
||||
{
|
||||
seq((parser1, parser2))
|
||||
}
|
||||
|
||||
pub fn seq<T, I, O, E>(sequence: T) -> impl Parser<I, O, E>
|
||||
where
|
||||
I: ParserInput,
|
||||
T: Sequence<I, O, E>,
|
||||
{
|
||||
let rep = sequence.representation();
|
||||
let p = move |input| sequence.parse(input);
|
||||
(p, rep)
|
||||
}
|
||||
|
||||
/* TODO - eventually rewrite this parser combinator in Schala. Seeing what this
|
||||
* code that makes heavy use of type variables and abstraction over types looks like
|
||||
* in Schala's type system should be educational
|
||||
*/
|
||||
|
||||
pub trait Sequence<I, O, E> {
|
||||
fn parse(&self, input: I) -> ParseResult<I, O, E>;
|
||||
fn representation(&self) -> Representation;
|
||||
}
|
||||
|
||||
impl<I, O1, O2, E, P1, P2> Sequence<I, (O1, O2), E> for (P1, P2)
|
||||
where
|
||||
I: ParserInput,
|
||||
P1: Parser<I, O1, E>,
|
||||
P2: Parser<I, O2, E>,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, (O1, O2), E> {
|
||||
let parser1 = &self.0;
|
||||
let parser2 = &self.1;
|
||||
parser1.parse(input).and_then(|(result1, rest1)| {
|
||||
parser2
|
||||
.parse(rest1)
|
||||
.map(|(result2, rest2)| ((result1, result2), rest2))
|
||||
})
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let mut iter = [self.0.representation(), self.1.representation()].into_iter();
|
||||
Representation::from_sequence(&mut iter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O1, O2, O3, E, P1, P2, P3> Sequence<I, (O1, O2, O3), E> for (P1, P2, P3)
|
||||
where
|
||||
I: ParserInput,
|
||||
P1: Parser<I, O1, E>,
|
||||
P2: Parser<I, O2, E>,
|
||||
P3: Parser<I, O3, E>,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, (O1, O2, O3), E> {
|
||||
let parser1 = &self.0;
|
||||
let parser2 = &self.1;
|
||||
let parser3 = &self.2;
|
||||
|
||||
let (result1, rest1) = parser1.parse(input)?;
|
||||
let (result2, rest2) = parser2.parse(rest1)?;
|
||||
let (result3, rest3) = parser3.parse(rest2)?;
|
||||
|
||||
Ok(((result1, result2, result3), rest3))
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let mut iter = [
|
||||
self.0.representation(),
|
||||
self.1.representation(),
|
||||
self.2.representation(),
|
||||
]
|
||||
.into_iter();
|
||||
Representation::from_sequence(&mut iter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O1, O2, O3, O4, E, P1, P2, P3, P4> Sequence<I, (O1, O2, O3, O4), E> for (P1, P2, P3, P4)
|
||||
where
|
||||
I: ParserInput,
|
||||
P1: Parser<I, O1, E>,
|
||||
P2: Parser<I, O2, E>,
|
||||
P3: Parser<I, O3, E>,
|
||||
P4: Parser<I, O4, E>,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, (O1, O2, O3, O4), E> {
|
||||
let parser1 = &self.0;
|
||||
let parser2 = &self.1;
|
||||
let parser3 = &self.2;
|
||||
let parser4 = &self.3;
|
||||
|
||||
let (result1, rest1) = parser1.parse(input)?;
|
||||
let (result2, rest2) = parser2.parse(rest1)?;
|
||||
let (result3, rest3) = parser3.parse(rest2)?;
|
||||
let (result4, rest4) = parser4.parse(rest3)?;
|
||||
|
||||
Ok(((result1, result2, result3, result4), rest4))
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let mut iter = [
|
||||
self.0.representation(),
|
||||
self.1.representation(),
|
||||
self.2.representation(),
|
||||
self.3.representation(),
|
||||
]
|
||||
.into_iter();
|
||||
Representation::from_sequence(&mut iter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O1, O2, O3, O4, O5, E, P1, P2, P3, P4, P5> Sequence<I, (O1, O2, O3, O4, O5), E>
|
||||
for (P1, P2, P3, P4, P5)
|
||||
where
|
||||
I: ParserInput,
|
||||
P1: Parser<I, O1, E>,
|
||||
P2: Parser<I, O2, E>,
|
||||
P3: Parser<I, O3, E>,
|
||||
P4: Parser<I, O4, E>,
|
||||
P5: Parser<I, O5, E>,
|
||||
{
|
||||
fn parse(&self, input: I) -> ParseResult<I, (O1, O2, O3, O4, O5), E> {
|
||||
let parser1 = &self.0;
|
||||
let parser2 = &self.1;
|
||||
let parser3 = &self.2;
|
||||
let parser4 = &self.3;
|
||||
let parser5 = &self.4;
|
||||
|
||||
let (result1, rest1) = parser1.parse(input)?;
|
||||
let (result2, rest2) = parser2.parse(rest1)?;
|
||||
let (result3, rest3) = parser3.parse(rest2)?;
|
||||
let (result4, rest4) = parser4.parse(rest3)?;
|
||||
let (result5, rest5) = parser5.parse(rest4)?;
|
||||
|
||||
Ok(((result1, result2, result3, result4, result5), rest5))
|
||||
}
|
||||
|
||||
fn representation(&self) -> Representation {
|
||||
let mut iter = [
|
||||
self.0.representation(),
|
||||
self.1.representation(),
|
||||
self.2.representation(),
|
||||
self.3.representation(),
|
||||
self.4.representation(),
|
||||
]
|
||||
.into_iter();
|
||||
Representation::from_sequence(&mut iter)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::combinators::repeated;
|
||||
use crate::primitives::{identifier, literal};
|
||||
|
||||
#[test]
|
||||
fn test_tuple2() {
|
||||
let p = tuple2(identifier, tuple2(literal(" "), literal("ruts")));
|
||||
let (output, _rest) = p.parse("fort1 ruts").unwrap();
|
||||
assert_eq!(output, ("fort1".into(), (" ", "ruts")));
|
||||
|
||||
let p = identifier.then(literal(" ")).then(literal("ruts"));
|
||||
let (output, _rest) = p.parse("fort1 ruts").unwrap();
|
||||
assert_eq!(output, (("fort1".into(), " "), "ruts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_seq() {
|
||||
let p = seq((
|
||||
literal("bong").to(10),
|
||||
repeated(literal(" ")).to(()),
|
||||
literal("hits").to(20),
|
||||
));
|
||||
assert_eq!(p.parse("bong hits").unwrap(), ((10, (), 20), ""));
|
||||
|
||||
let p = seq((
|
||||
literal("alpha").to(10),
|
||||
repeated(literal(" ")).to(()),
|
||||
repeated(literal("-")).to(()),
|
||||
repeated(literal(" ")),
|
||||
literal("beta"),
|
||||
));
|
||||
assert_eq!(
|
||||
p.parse("alpha ------ beta gamma").unwrap(),
|
||||
((10, (), (), vec![" ", " ", " "], "beta"), " gamma")
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"$schema": "https://joplinapp.org/schema/settings.json",
|
||||
"locale": "en_GB",
|
||||
"sync.target": 6,
|
||||
"markdown.plugin.softbreaks": false,
|
||||
"markdown.plugin.typographer": false,
|
||||
"spellChecker.language": "en-US",
|
||||
"ui.layout": {
|
||||
"key": "root",
|
||||
"children": [
|
||||
{
|
||||
"key": "sideBar",
|
||||
"width": 250,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "noteList",
|
||||
"width": 250,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"key": "editor",
|
||||
"visible": true,
|
||||
"width": 1493
|
||||
},
|
||||
{
|
||||
"key": "plugin-view-joplin.plugin.note.tabs-note.tabs.panel",
|
||||
"context": {
|
||||
"pluginId": "joplin.plugin.note.tabs"
|
||||
},
|
||||
"visible": true
|
||||
}
|
||||
],
|
||||
"visible": true
|
||||
},
|
||||
"noteVisiblePanes": [
|
||||
"editor",
|
||||
"viewer"
|
||||
],
|
||||
"theme": 4,
|
||||
"sync.6.username": "webdav",
|
||||
"net.ignoreTlsErrors": true,
|
||||
"style.editor.contentMaxWidth": 600,
|
||||
"editor.codeView": true,
|
||||
"markdown.plugin.sub": true,
|
||||
"markdown.plugin.sup": true,
|
||||
"markdown.plugin.multitable": true
|
||||
}
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
use parser_combinator::choice::choice;
|
||||
use parser_combinator::combinators::repeated;
|
||||
use parser_combinator::primitives::{any_char, literal, literal_char, one_of, pred};
|
||||
use parser_combinator::sequence::seq;
|
||||
use parser_combinator::Parser;
|
||||
use parser_combinator::Representation;
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use rstest::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn doesnt_crash(s in "\\PC*") {
|
||||
let _output = json_object().parse(&s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_string(s in r#"[^"]+"#) {
|
||||
let input = format!("\"{}\"", s);
|
||||
let output = json_string().parse(&input).unwrap();
|
||||
match output {
|
||||
(JsonValue::Str(output_s), "") if output_s == s => (),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsing() {
|
||||
let output = literal("a").parse("a yolo");
|
||||
assert_eq!(output.unwrap(), ("a", " yolo"));
|
||||
}
|
||||
|
||||
/*
|
||||
* JSON BNF
|
||||
* <JSON> ::= <value>
|
||||
<value> ::= <object> | <array> | <boolean> | <string> | <number> | <null>
|
||||
<array> ::= "[" [<value>] {"," <value>}* "]"
|
||||
<object> ::= "{" [<property>] {"," <property>}* "}"
|
||||
<property> ::= <string> ":" <value>
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
enum JsonValue {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Str(String),
|
||||
Num(f64),
|
||||
Array(Vec<JsonValue>),
|
||||
Object(Vec<(String, JsonValue)>),
|
||||
}
|
||||
|
||||
trait JsonParser<'a, T>: Parser<&'a str, T, &'a str> {}
|
||||
impl<'a, T, P> JsonParser<'a, T> for P where P: Parser<&'a str, T, &'a str> {}
|
||||
|
||||
fn json_null<'a>() -> impl JsonParser<'a, JsonValue> {
|
||||
literal("null").to(JsonValue::Null)
|
||||
}
|
||||
|
||||
fn json_bool<'a>() -> impl JsonParser<'a, JsonValue> {
|
||||
choice((
|
||||
literal("true").to(JsonValue::Bool(true)),
|
||||
literal("false").to(JsonValue::Bool(false)),
|
||||
))
|
||||
}
|
||||
|
||||
fn json_number<'a>() -> impl JsonParser<'a, JsonValue> {
|
||||
fn digit<'a>() -> impl JsonParser<'a, &'a str> {
|
||||
one_of("1234567890")
|
||||
}
|
||||
|
||||
fn digits<'a>() -> impl JsonParser<'a, Vec<&'a str>> {
|
||||
repeated(digit()).at_least(1)
|
||||
}
|
||||
|
||||
let json_number_inner = choice((
|
||||
seq((digits(), literal(".").ignore_then(digits()).optional())).map(
|
||||
|(mut digits, maybe_decimal)| {
|
||||
if let Some(decimal_digits) = maybe_decimal {
|
||||
digits.push(".");
|
||||
digits.extend(decimal_digits.into_iter());
|
||||
}
|
||||
digits.into_iter().collect::<String>()
|
||||
},
|
||||
),
|
||||
literal(".").ignore_then(digits()).map(|decimal_digits| {
|
||||
let mut d = vec!["."];
|
||||
d.extend(decimal_digits.into_iter());
|
||||
d.into_iter().collect::<String>()
|
||||
}),
|
||||
))
|
||||
.map(|digits| digits.parse::<f64>().unwrap());
|
||||
|
||||
literal("-")
|
||||
.optional()
|
||||
.then(json_number_inner)
|
||||
.map(|(maybe_sign, mut val)| {
|
||||
if maybe_sign.is_some() {
|
||||
val *= -1.0;
|
||||
}
|
||||
JsonValue::Num(val)
|
||||
})
|
||||
}
|
||||
|
||||
fn json_string_raw<'a>() -> impl JsonParser<'a, String> {
|
||||
seq((
|
||||
literal_char('"'),
|
||||
repeated(pred(any_char, |ch| *ch != '"')),
|
||||
literal_char('"'),
|
||||
))
|
||||
.map(|(_, s, _)| s.iter().cloned().collect::<String>())
|
||||
}
|
||||
|
||||
fn json_string<'a>() -> impl JsonParser<'a, JsonValue> {
|
||||
json_string_raw().map(JsonValue::Str)
|
||||
}
|
||||
|
||||
fn whitespace<'a>() -> impl JsonParser<'a, ()> {
|
||||
repeated(choice((
|
||||
literal_char('\t'),
|
||||
literal_char('\n'),
|
||||
literal_char(' '),
|
||||
)))
|
||||
.to(())
|
||||
}
|
||||
|
||||
fn json_array<'a>() -> impl JsonParser<'a, JsonValue> {
|
||||
move |input| {
|
||||
let val = json_value().surrounded_by(whitespace());
|
||||
|
||||
repeated(val)
|
||||
.separated_by(literal(","), false)
|
||||
.delimited(literal_char('['), literal_char(']'))
|
||||
.map(JsonValue::Array)
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn json_object<'a>() -> impl JsonParser<'a, JsonValue> {
|
||||
move |input| {
|
||||
let kv = json_string_raw()
|
||||
.surrounded_by(whitespace())
|
||||
.then_ignore(literal_char(':'))
|
||||
.then(json_value().surrounded_by(whitespace()));
|
||||
|
||||
repeated(kv)
|
||||
.separated_by(literal_char(','), false)
|
||||
.delimited(literal_char('{'), literal_char('}'))
|
||||
.map(JsonValue::Object)
|
||||
.parse(input)
|
||||
}
|
||||
}
|
||||
|
||||
fn json_value<'a>() -> impl JsonParser<'a, JsonValue> {
|
||||
choice((
|
||||
json_null(),
|
||||
json_bool(),
|
||||
json_number(),
|
||||
json_string(),
|
||||
json_array(),
|
||||
json_object(),
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_json_primitives() {
|
||||
assert_eq!(
|
||||
json_string().parse(r#""yolo swagg""#).unwrap(),
|
||||
(JsonValue::Str("yolo swagg".into()), "")
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
json_number().parse("-383").unwrap().0,
|
||||
JsonValue::Num(-383f64)
|
||||
);
|
||||
assert_eq!(
|
||||
json_number().parse("-.383").unwrap().0,
|
||||
JsonValue::Num(-0.383)
|
||||
);
|
||||
assert_eq!(
|
||||
json_number().parse(".383").unwrap().0,
|
||||
JsonValue::Num(0.383)
|
||||
);
|
||||
assert_eq!(
|
||||
json_number().parse("-1.383").unwrap().0,
|
||||
JsonValue::Num(-1.383)
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(r#"[ 4, 9, "ara",]"#)]
|
||||
fn parse_json_array_err(#[case] input: &str) {
|
||||
assert!(json_array().parse(input).is_err());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("[[],[]]", (JsonValue::Array(vec![JsonValue::Array(vec![]), JsonValue::Array(vec![])]), ""))]
|
||||
#[case(r#"[ 4, 9, "foo" ]"#, (
|
||||
JsonValue::Array(vec![
|
||||
JsonValue::Num(4.),
|
||||
JsonValue::Num(9.0),
|
||||
JsonValue::Str("foo".to_string())
|
||||
]),
|
||||
""
|
||||
))]
|
||||
#[case(r#"[8,null,[],5],{}"#,
|
||||
(
|
||||
JsonValue::Array(vec![
|
||||
JsonValue::Num(8.),
|
||||
JsonValue::Null,
|
||||
JsonValue::Array(vec![]),
|
||||
JsonValue::Num(5.),
|
||||
]),
|
||||
",{}"
|
||||
))]
|
||||
fn parse_json_array(#[case] input: &str, #[case] expected: (JsonValue, &str)) {
|
||||
assert_eq!(json_array().parse(input).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_json_object() {
|
||||
assert_eq!(
|
||||
json_object().parse(r#"{ "a": 23}"#).unwrap().0,
|
||||
JsonValue::Object(vec![("a".into(), JsonValue::Num(23.))])
|
||||
);
|
||||
assert_eq!(
|
||||
json_object().parse(r#"{}"#).unwrap().0,
|
||||
JsonValue::Object(vec![])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_json_document() {
|
||||
let test_json = include_str!("joplin-cfg.json");
|
||||
let parsed_json = json_object().parse(test_json);
|
||||
assert!(parsed_json.is_ok());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(json_null().representation(), Representation::new("null"))]
|
||||
#[case(json_bool().representation(), Representation::new("true | false"))]
|
||||
#[case(json_number().representation(), Representation::new("- | ε (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | )+ . (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | )+ | ε | . (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | )+"))]
|
||||
fn representations_test(
|
||||
#[case] parser_representation: Representation,
|
||||
#[case] expected: Representation,
|
||||
) {
|
||||
assert_eq!(parser_representation, expected);
|
||||
}
|
Loading…
Reference in New Issue