mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-29 06:37:41 +00:00 
			
		
		
		
	Compare commits
	
		
			1385 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 73334f3485 | ||
|   | a5b8da8d67 | ||
|   | e8cccfced5 | ||
|   | 88984f7ffb | ||
|   | 182170b3be | ||
|   | f92412841b | ||
|   | 18c00e89da | ||
|   | 7c38a55a9a | ||
|   | a15916ec9c | ||
|   | 3583d4c92f | ||
|   | a456c67a7b | ||
|   | 6e226e4073 | ||
|   | d23e6614b1 | ||
|   | ab6afa72fd | ||
|   | 9538b8a77c | ||
|   | 36d3804dd2 | ||
|   | a34b8ea68f | ||
|   | 55c10f98bb | ||
|   | 4b5a2a14c0 | ||
|   | 665705d06d | ||
|   | cc8cd4bace | ||
|   | 8f8b6ed001 | ||
|   | aa9efee868 | ||
|   | e0a0e2ed42 | ||
|   | 39f5c539d7 | ||
|   | 1b6437a4f8 | ||
|   | c62c1a58f0 | ||
|   | 3441bcbd69 | ||
|   | 2e6001316a | ||
|   | 53bcc15207 | ||
|   | dd609bb1bb | ||
|   | 5c56c7fa91 | ||
|   | 9a892363a3 | ||
|   | 5f550ea5d4 | ||
|   | 1b6cc023a5 | ||
|   | 410f8d69bc | ||
|   | 6da44bdb6a | ||
|   | d30fd27575 | ||
|   | 1b278fc657 | ||
|   | eecffe01a5 | ||
|   | f63a33884f | ||
|   | fa75a395cb | ||
|   | 1f34ec9902 | ||
|   | f75c08a78e | ||
|   | 5e93f0e34b | ||
|   | 49f151e265 | ||
|   | 2b73a15ad8 | ||
|   | 06d581dde3 | ||
|   | 2b49903c82 | ||
|   | a17ae977a5 | ||
|   | 8a6b44cb4e | ||
|   | 60d9f97750 | ||
|   | f252933f62 | ||
|   | 6dbd7b476c | ||
|   | a47eb847fb | ||
|   | ba5990ef21 | ||
|   | 753911fe2d | ||
|   | 746ced5501 | ||
|   | 1b49934e4f | ||
|   | 682f0f584f | ||
|   | 611b2a6c3a | ||
|   | 8043caf581 | ||
|   | b2d2690eb9 | ||
|   | 7f745a34c3 | ||
|   | b16cf17246 | ||
|   | 67e8518ba6 | ||
|   | e94e8dc484 | ||
|   | 1a24d4fc86 | ||
|   | 6ee05785d1 | ||
|   | 268ff666d2 | ||
|   | 91bb34c3bf | ||
|   | 17d5fb3210 | ||
|   | 687b987f7e | ||
|   | 4daecc9a41 | ||
|   | a85eacadda | ||
|   | ed63987fd1 | ||
|   | ff173047f4 | ||
|   | 83e8aab289 | ||
|   | 85cb35e68f | ||
|   | 952906279c | ||
|   | 5b79b48ae0 | ||
|   | 7c44127bcb | ||
|   | 9338312103 | ||
|   | a0eeb630e7 | ||
|   | 6535d72bd4 | ||
|   | a7d424bc81 | ||
|   | 2bceba4a7a | ||
|   | e3159bb0f5 | ||
|   | 5d1bd8a932 | ||
|   | bafa6bfff0 | ||
|   | e2eb7ab4b2 | ||
|   | 9f4497a5ae | ||
|   | 70de8bf092 | ||
|   | e52575e23a | ||
|   | 10994cbc6a | ||
|   | abad9d7db9 | ||
|   | 5e443cd29d | ||
|   | 7bf3a9d24c | ||
|   | d80a7094ae | ||
|   | ad77bc391c | ||
|   | 2b84fb14b4 | ||
|   | 07155ce657 | ||
|   | 046d28662d | ||
|   | 84dd3db620 | ||
|   | 282f2671ea | ||
|   | 3fc2be3e6e | ||
|   | d10c1fe759 | ||
|   | d18472b07d | ||
|   | 43a68dcd2a | ||
|   | 3d93028088 | ||
|   | 99f0af92bd | ||
|   | 71d81b14a2 | ||
|   | 3894f4021a | ||
|   | 72c659d1ee | ||
|   | 8f879b4adc | ||
|   | 18f2847dc1 | ||
|   | 89b7ff9daf | ||
|   | 26c263d6be | ||
|   | 2570e0f7a0 | ||
|   | 8084e4c728 | ||
|   | ee90f9df62 | ||
|   | 906a982ace | ||
|   | 88e60c309c | ||
|   | 9694aee819 | ||
|   | 2697b0e425 | ||
|   | c0d7a49b19 | ||
|   | f9a6f52d9c | ||
|   | c02c2e3f02 | ||
|   | 1fcd47dd7b | ||
|   | 384ee4f6a9 | ||
|   | e9deec8231 | ||
|   | 2fc77a1b63 | ||
|   | 442fe8209d | ||
|   | 968a0dc4ac | ||
|   | 40c93d0786 | ||
|   | 83b0bc688c | ||
|   | 6185b253be | ||
|   | 17da53d0d9 | ||
|   | 9ffec43d2b | ||
|   | e4f4a42751 | ||
|   | 4f65c2707e | ||
|   | 75bdea5155 | ||
|   | f553c5da47 | ||
|   | 5f70a85f7e | ||
|   | c82fd106a7 | ||
|   | 0e9b866b98 | ||
|   | 67a8c6df09 | ||
|   | 86cf8127b6 | ||
|   | 828e0a07cd | ||
|   | 90018b35c0 | ||
|   | 5a199716cb | ||
|   | 43ecd4f2d8 | ||
|   | c5a9602be9 | ||
|   | e88aab6d68 | ||
|   | ce528251d5 | ||
|   | 9e334da2d6 | ||
|   | c0e508e334 | ||
|   | b63b3bef74 | ||
|   | 05d0b5ac05 | ||
|   | c56d6e8fc1 | ||
|   | 33d2f9a522 | ||
|   | e53d22fad2 | ||
|   | 33f55dc32f | ||
|   | 7e6aad2221 | ||
|   | 3c0c22259c | ||
|   | 42f6af4bf1 | ||
|   | f274b02653 | ||
|   | 70c29b4e5d | ||
|   | 84d43d1039 | ||
|   | 5c67c1165d | ||
|   | 85028967d8 | ||
|   | 6ceff6ecc9 | ||
|   | 06eec06ff0 | ||
|   | 2dcc0adc0e | ||
|   | 8ca1e44af1 | ||
|   | 2aedc6beff | ||
|   | af2eb06298 | ||
|   | 7ff545bd2e | ||
|   | a59b5765b6 | ||
|   | 6bd58dd4c0 | ||
|   | e3406cd922 | ||
|   | ab70524d85 | ||
|   | ce36c4c0d6 | ||
|   | 2b01b780da | ||
|   | f3048a3d6b | ||
|   | accac6c662 | ||
|   | 631622aa48 | ||
|   | aaeaa3a944 | ||
|   | d1104b5a65 | ||
|   | 1f074671ce | ||
|   | 872b39cc32 | ||
|   | 9eab57d194 | ||
|   | 8edd873c3e | ||
|   | 771956b5b6 | ||
|   | ecc4da5113 | ||
|   | f5555d21b9 | ||
|   | 342a29c7be | ||
|   | 368b891499 | ||
|   | f62539ad55 | ||
|   | 4835ecb950 | ||
|   | 31f0ff0d84 | ||
|   | b7b594205c | ||
|   | 190056b863 | ||
|   | ae6b359109 | ||
|   | 3078686f8f | ||
|   | 0f4ecd93ab | ||
|   | 4af187d0ca | ||
|   | a5d6b22838 | ||
|   | fda0a081f5 | ||
|   | 94b7a69741 | ||
|   | 6518257129 | ||
|   | dc325188d0 | ||
|   | 0b51ab157d | ||
|   | f95de25b15 | ||
|   | f424f2936b | ||
|   | 2d6c2ee7c0 | ||
|   | 7cd106a10c | ||
|   | 0d9e999113 | ||
|   | 75710ccabd | ||
|   | 0f60115f27 | ||
|   | 16a3c85baa | ||
|   | 92ff1d3be4 | ||
|   | 58441dc49f | ||
|   | dbc5d688e2 | ||
|   | e2a8951f68 | ||
|   | f0f03ad519 | ||
|   | e37575e763 | ||
|   | f4fd481415 | ||
|   | 8fca6b7af4 | ||
|   | 600e822933 | ||
|   | 2028ac8a20 | ||
|   | 7bae7d9efd | ||
|   | cb54fb02c1 | ||
|   | 7529abb542 | ||
|   | 16ac681ed9 | ||
|   | 74560ff805 | ||
|   | fe348187cc | ||
|   | fd5315793c | ||
|   | 87db463f4e | ||
|   | 1225cd31c8 | ||
|   | 6998865d7b | ||
|   | b8aec50763 | ||
|   | 7efb39d608 | ||
|   | f7c90bc1ff | ||
|   | aee077c1bd | ||
|   | 6968275ddf | ||
|   | 074ae4fc0d | ||
|   | 6cd35ed9c8 | ||
|   | 7911e74222 | ||
|   | 2fafe2b5d1 | ||
|   | de977819ce | ||
|   | 1844beecc3 | ||
|   | cb529bbd63 | ||
|   | 25990867e2 | ||
|   | 4fbc71c70d | ||
|   | eb21d4fff4 | ||
|   | 6d5fc1d743 | ||
|   | e88042b2fa | ||
|   | 750b448f75 | ||
|   | 14d1dc8749 | ||
|   | 8e0340252b | ||
|   | 641a16c133 | ||
|   | 533d78bffe | ||
|   | ae2c5820a1 | ||
|   | 8334504f4e | ||
|   | 2260a593bd | ||
|   | 7d8af2f99a | ||
|   | 46bdcece4d | ||
|   | 7387a1d91e | ||
|   | ae4b8078df | ||
|   | 60e0c8ea92 | ||
|   | 7d3acc0ed6 | ||
|   | 2637b33957 | ||
|   | 58ccb66659 | ||
|   | 634429cf61 | ||
|   | 6ac65e603d | ||
|   | 03166a745a | ||
|   | 4d61ba20ce | ||
|   | 751ff677fe | ||
|   | ace60e1898 | ||
|   | 876b7f106f | ||
|   | 809b6589a1 | ||
|   | 02f53ca014 | ||
|   | 0b03ddb21b | ||
|   | ea5d4fd3af | ||
|   | e6b73f8cd1 | ||
|   | af232ef729 | ||
|   | 2e2f8abfc0 | ||
|   | 91a583db27 | ||
|   | dc5cc630ff | ||
|   | c1647a74c5 | ||
|   | 721f280966 | ||
|   | 258ebb9145 | ||
|   | e914eaf055 | ||
|   | fe54013679 | ||
|   | fdaf2e1594 | ||
|   | f0092ef69b | ||
|   | a88ae7e1d9 | ||
|   | 9946f3bdf4 | ||
|   | c747e8d16c | ||
|   | 3e402d397e | ||
|   | 0350834cd3 | ||
|   | 980981c9ee | ||
|   | 3c8346f24e | ||
|   | 42bd27c24b | ||
|   | 4a0f67f3bd | ||
|   | 09b6fc4670 | ||
|   | 4d9bcd6bcc | ||
|   | cd34b89977 | ||
|   | 3151fa3988 | ||
|   | 5e58110e19 | ||
|   | e1cdd0f8cc | ||
|   | 1f39a0f180 | ||
|   | 367c4b14f5 | ||
|   | 9c437796d3 | ||
|   | 60e22d9703 | ||
|   | ee7362e847 | ||
|   | 369f96b80e | ||
|   | 7c5ed04ab1 | ||
|   | 4779a445e0 | ||
|   | f0f1b7ce9e | ||
|   | 7c9157a0ed | ||
|   | 522a6cb435 | ||
|   | d0d551d739 | ||
|   | 71a123fef7 | ||
|   | 3f40c8d7fb | ||
|   | 983c2e5499 | ||
|   | eebb4c3ade | ||
|   | 50425eac72 | ||
|   | 382ff77bbe | ||
|   | bf680fb5d3 | ||
|   | 4ed7db4f91 | ||
|   | bf19920d65 | ||
|   | 174b5f6686 | ||
|   | 4173645b81 | ||
|   | af511f1f55 | ||
|   | 83c6080380 | ||
|   | 2f0c789ea1 | ||
|   | a9b8f8e8a9 | ||
|   | f92f3eb6fa | ||
|   | 89e74dca3e | ||
|   | f2e86d2f8d | ||
|   | 623da131e5 | ||
|   | e89ec31ae5 | ||
|   | 68a6ed208e | ||
|   | c01b32c4f3 | ||
|   | ee11ff9da9 | ||
|   | ed56d5d6ff | ||
|   | b317ab755c | ||
|   | 9819994999 | ||
|   | e9dbaa81d2 | ||
|   | 9f9146ffae | ||
|   | 9d9732af97 | ||
|   | ebb8fa9787 | ||
|   | 9e6abbf4d4 | ||
|   | 6032a6d658 | ||
|   | c29ab22e6d | ||
|   | 592ac4904c | ||
|   | 03ae2ec153 | ||
|   | 3bc42d0d37 | ||
|   | 12630d3e54 | ||
|   | c9897f99c3 | ||
|   | e66dc14b3a | ||
|   | 7a2868c147 | ||
|   | 9e0daaee09 | ||
|   | c293c7de93 | ||
|   | 49eb5f8563 | ||
|   | 674b375b2c | ||
|   | 7e94c091eb | ||
|   | 5885ccba61 | ||
|   | 431ecd3d1a | ||
|   | f6df8ff935 | ||
|   | 3fd70f0951 | ||
|   | bebb635d4f | ||
|   | 354896bc4b | ||
|   | 5ddefff27e | ||
|   | 91827eef4f | ||
|   | 9c14c09962 | ||
|   | e85a84171f | ||
|   | 3a4f86c3d7 | ||
|   | 5e75963312 | ||
|   | 184d9289b5 | ||
|   | b7ff9577c0 | ||
|   | 942a1aaac6 | ||
|   | 69f0fe004d | ||
|   | 2a04347a42 | ||
|   | 1394f1a5c0 | ||
|   | cf4d19a8ea | ||
|   | 23b0fe9f8e | ||
|   | 1ba718b15e | ||
|   | df5f79ff35 | ||
|   | 6d7e8528ea | ||
|   | 197bb73a62 | ||
|   | f91e599451 | ||
|   | 5b9aa9237c | ||
|   | 61f38fab37 | ||
|   | 9142f38cbc | ||
|   | e8ed961572 | ||
|   | be11a2a1ad | ||
|   | ea75086300 | ||
|   | 9eeefbd79a | ||
|   | c573a98363 | ||
|   | 11d7af3f95 | ||
|   | a10b4f61d8 | ||
|   | a0cb7514f1 | ||
|   | b066edc116 | ||
|   | 938f5a689e | ||
|   | 772f4c26e8 | ||
|   | 6b5d151beb | ||
|   | a9176a77e6 | ||
|   | 16f409c6a9 | ||
|   | 9593c930de | ||
|   | 56f33f514b | ||
|   | 1ccd544b94 | ||
|   | 93c83a2ee2 | ||
|   | f459e32ada | ||
|   | 9b640c8e9c | ||
|   | a3228f4997 | ||
|   | 715eb69d92 | ||
|   | df2d5cb3d3 | ||
|   | 3b189eab64 | ||
|   | 609b629c22 | ||
|   | e74365fe38 | ||
|   | 46b34833c2 | ||
|   | 045c80869d | ||
|   | 2ea2e72ddd | ||
|   | 1b17e12fd6 | ||
|   | cc5beda0d2 | ||
|   | a363fd926d | ||
|   | 21ebede529 | ||
|   | 15d67e9191 | ||
|   | b5996f5f02 | ||
|   | 83204dc293 | ||
|   | e3f4142d2a | ||
|   | f18ad36b1b | ||
|   | cb25a2ecd6 | ||
|   | 741a5036e8 | ||
|   | 549ee95f3d | ||
|   | 6ae81058aa | ||
|   | 267c603824 | ||
|   | a8f583a372 | ||
|   | 2b5d90f73a | ||
|   | 4139e426fe | ||
|   | a775a89e01 | ||
|   | 990f6352e0 | ||
|   | b344702304 | ||
|   | d497612bce | ||
|   | 2a3b101bd8 | ||
|   | 63e93af421 | ||
|   | ab055b3ebe | ||
|   | a9a013473f | ||
|   | 87de1e5766 | ||
|   | 894aaef267 | ||
|   | e209e54ffe | ||
|   | 7511eadaa7 | ||
|   | 6c4906605a | ||
|   | 8a9be9d837 | ||
|   | b72098cc71 | ||
|   | defe60e08b | ||
|   | 7f852b8af4 | ||
|   | d71c100ca7 | ||
|   | 5442c8e86d | ||
|   | cf4901e713 | ||
|   | 4b8c1ac2d2 | ||
|   | 555e0c0b85 | ||
|   | dc301305de | ||
|   | f1111c135b | ||
|   | 3905e92965 | ||
|   | 1418ada38f | ||
|   | 9256a66b76 | ||
|   | e8c013a778 | ||
|   | fea8242ea7 | ||
|   | 7bfb17c209 | ||
|   | e7e4341e70 | ||
|   | 6186be4443 | ||
|   | d07f01d7cb | ||
|   | 73291a30a0 | ||
|   | a3b129845b | ||
|   | 0ff8f58be8 | ||
|   | 66292beec9 | ||
|   | bf2af1051f | ||
|   | b6e3020d4c | ||
|   | 8f516a1e28 | ||
|   | 5f2e287efd | ||
|   | 8c0d65cf9f | ||
|   | fa609a5079 | ||
|   | c708ff9708 | ||
|   | 2ea90334a3 | ||
|   | eea8aa555f | ||
|   | 51a75e1872 | ||
|   | af7ed4322e | ||
|   | 7cdd7cf6eb | ||
|   | 26aa622afc | ||
|   | 84ad161f1e | ||
|   | 6efb965dab | ||
|   | 8c90a12e0f | ||
|   | 2d54e88e74 | ||
|   | 16ea5323e0 | ||
|   | 7a23ce2367 | ||
|   | e05bc7eb54 | ||
|   | b3a6e25ce0 | ||
|   | b63d41102e | ||
|   | 964295b59d | ||
|   | d19db30f3d | ||
|   | d12464fc0e | ||
|   | a96971c8a7 | ||
|   | f6f769503a | ||
|   | 82917ac6e3 | ||
|   | a6ffafb1a2 | ||
|   | fb8c529f2e | ||
|   | 1ee98e1e66 | ||
|   | 81f35f5dd1 | ||
|   | 1b402347cd | ||
|   | 7599656784 | ||
|   | dccb60ba35 | ||
|   | ae642ceca0 | ||
|   | 471b6f9966 | ||
|   | 5dd18bac2c | ||
|   | 018f4e0891 | ||
|   | e85809a98a | ||
|   | e6e9bd8147 | ||
|   | 221645d2ce | ||
|   | 2f4a6214a2 | ||
|   | e00a461c26 | ||
|   | c31314be38 | ||
|   | ee142c4be0 | ||
|   | aeacc0b31b | ||
|   | 7b4c3bdbcc | ||
|   | 910b9cf1fd | ||
|   | b10aaceab0 | ||
|   | 169bd812c9 | ||
|   | 34767f1e13 | ||
|   | 4f642c0843 | ||
|   | 4e5889ed59 | ||
|   | a1b848ad76 | ||
|   | dbcc1fad3e | ||
|   | db366558e7 | ||
|   | a23c03fbd0 | ||
|   | ff18b92eb0 | ||
|   | 7f148522ab | ||
|   | 159c612924 | ||
|   | b95dfd4bdf | ||
|   | e69954af2f | ||
|   | a5ff26f602 | ||
|   | a7536268e1 | ||
|   | 541469371a | ||
|   | a13aeaf955 | ||
|   | 9cf674cdcb | ||
|   | 51c0cf97bc | ||
|   | 4cb1f616c5 | ||
|   | 645109048b | ||
|   | f969fb69e1 | ||
|   | bfb60fdb84 | ||
|   | 2f43cb843e | ||
|   | 874fd2aba7 | ||
|   | 33d1371186 | ||
|   | d2dd241e6b | ||
|   | 4ecadfabf4 | ||
|   | ffd79c6097 | ||
|   | 35a8d2a519 | ||
|   | 21eab7e9cc | ||
|   | d9605c2856 | ||
|   | 70a467d469 | ||
|   | 6e8979336d | ||
|   | ee01045db5 | ||
|   | b7f8224588 | ||
|   | ca4c1e4259 | ||
|   | 91712add3d | ||
|   | 7198dcb416 | ||
|   | 08e20e912d | ||
|   | f45571033c | ||
|   | 2ac36a0572 | ||
|   | 3df1d54847 | ||
|   | f3969b6066 | ||
|   | 6222f35bc8 | ||
|   | 2f178963c0 | ||
|   | 15760b0950 | ||
|   | 43a6a70e1e | ||
|   | cd36f1ef5f | ||
|   | cdd7083c86 | ||
|   | 8df7364319 | ||
|   | 63023722d1 | ||
|   | 79c12e5116 | ||
|   | 53e16944a1 | ||
|   | 7475362c85 | ||
|   | 9238b82cde | ||
|   | 7049f658ec | ||
|   | 701913fb19 | ||
|   | 831f41a62b | ||
|   | 0ea1da80e7 | ||
|   | 06eea74b98 | ||
|   | c8c0e112bc | ||
|   | 7417e82c51 | ||
|   | ecc4d80a5a | ||
|   | 3df24c52f4 | ||
|   | 8a70fb95b5 | ||
|   | d8b45ecd61 | ||
|   | 61712bae9c | ||
|   | 4ff81a5a25 | ||
|   | 08f0e55d8f | ||
|   | 080b37cb31 | ||
|   | bbdcd035ba | ||
|   | f9233ef90b | ||
|   | cd3573a4d2 | ||
|   | 738fe24e6d | ||
|   | c2e55b5486 | ||
|   | 989f0726e3 | ||
|   | bdefd3ba1e | ||
|   | 4efcff33bd | ||
|   | 8183cc5a8d | ||
|   | f3bda1536d | ||
|   | 3b6371e03d | ||
|   | b5d3c87253 | ||
|   | f73b8c550a | ||
|   | 5437744126 | ||
|   | 5a5e70b001 | ||
|   | 348a5bc0a9 | ||
|   | 026c64fa01 | ||
|   | e38663c457 | ||
|   | 117c741c29 | ||
|   | 9bc5bec9f1 | ||
|   | a5f4e4d328 | ||
|   | db0abfde72 | ||
|   | edf263bcb5 | ||
|   | 60fba585e3 | ||
|   | ebb6fe5be3 | ||
|   | d91c95bf92 | ||
|   | 2007438424 | ||
|   | 81423635ad | ||
|   | 58d297364a | ||
|   | db902c90c4 | ||
|   | 42ccd0f790 | ||
|   | 20ec6f574e | ||
|   | b3db367ae7 | ||
|   | 8a62c742e6 | ||
|   | b125cbeac9 | ||
|   | 3f7a2c2197 | ||
|   | f6248369fe | ||
|   | c83f3ec097 | ||
|   | 0cd00da354 | ||
|   | 4b7b285aa9 | ||
|   | d63379e777 | ||
|   | b219b146fa | ||
|   | ff90b81ec3 | ||
|   | 9120eaef79 | ||
|   | 1ccd879916 | ||
|   | f977ace7f8 | ||
|   | c3f4dc0c15 | ||
|   | 78eed9b11c | ||
|   | 3a4d56afca | ||
|   | 63bb93fc07 | ||
|   | 5a39a04a79 | ||
|   | 2fde34b519 | ||
|   | 1ef5c038db | ||
|   | e2459cfb47 | ||
|   | cfffc0bcf1 | ||
|   | 7272f43191 | ||
|   | 2a7ea27bb7 | ||
|   | 32c5b816ae | ||
|   | e54ea7a1d8 | ||
|   | 1077efd03a | ||
|   | f9ab91511d | ||
|   | 2c3ca2984e | ||
|   | 94722e566c | ||
|   | 163f7ee85d | ||
|   | 52d3470cbe | ||
|   | 0bd6e85c61 | ||
|   | e35c6b876f | ||
|   | 9a2897e741 | ||
|   | 70b2e8179d | ||
|   | 5317edc65d | ||
|   | 866d83579e | ||
|   | a238391b36 | ||
|   | 5e152d30db | ||
|   | 57c954783d | ||
|   | b5407ac708 | ||
|   | 472ec730b5 | ||
|   | 8c819b1f91 | ||
|   | 528a516390 | ||
|   | 6509e37c84 | ||
|   | 649173f661 | ||
|   | 1efb0adb35 | ||
|   | 88a8e2c1df | ||
|   | bb4ff05d35 | ||
|   | dd3b601c87 | ||
|   | e22d101a62 | ||
|   | 4b3c813f5a | ||
|   | 67f375bea2 | ||
|   | 88ba99b87e | ||
|   | 53447e9d0b | ||
|   | c4c86f8671 | ||
|   | 658941d26d | ||
|   | e4bf27b01c | ||
|   | 7d48b75f81 | ||
|   | 5f56bf836c | ||
|   | c0f5f97ddb | ||
|   | 15177ac2e9 | ||
|   | 8360bc93ac | ||
|   | e0ea844d50 | ||
|   | 9675411f35 | ||
|   | e97299fc65 | ||
|   | 26a113927e | ||
|   | d0aa7ef590 | ||
|   | 5de889419f | ||
|   | 0fcbda2da7 | ||
|   | 14e33c295f | ||
|   | 644ac8caf8 | ||
|   | 77189b6e66 | ||
|   | 4f8f7f66ee | ||
|   | b099bd97f2 | ||
|   | 961c6ea15a | ||
|   | 9c97d8f648 | ||
|   | ad7bf80611 | ||
|   | 40080b23ae | ||
|   | 7acb5c63e0 | ||
|   | fcca9bbab3 | ||
|   | dbb2187425 | ||
|   | 82e51f9e81 | ||
|   | 4782a76bca | ||
|   | d13788a4ed | ||
|   | e64a0175b1 | ||
|   | 4aca94154f | ||
|   | ac5f118dac | ||
|   | a2812ec5eb | ||
|   | 70f13f1b62 | ||
|   | 77e62a25cb | ||
|   | 09345ec786 | ||
|   | bad73baf98 | ||
|   | 3602f5aa5d | ||
|   | 672b705faf | ||
|   | 64e3cdeb2b | ||
|   | 909c906080 | ||
|   | 71bde11e95 | ||
|   | fc20fbed92 | ||
|   | e6b7c85c37 | ||
|   | b3a92363f8 | ||
|   | e9f2d1aca7 | ||
|   | b4e3dbf331 | ||
|   | c3620786cf | ||
|   | 41943746e4 | ||
|   | 176e816b8c | ||
|   | 50a19bd870 | ||
|   | 57b751b994 | ||
|   | 77732a8f44 | ||
|   | c47c2e538d | ||
|   | cc5545277d | ||
|   | 63353b98cd | ||
|   | 4dfc869b8a | ||
|   | b4b1c7d80b | ||
|   | e53c03028f | ||
|   | 8680aef42f | ||
|   | c3fd71d643 | ||
|   | 30c47d685d | ||
|   | 80db682109 | ||
|   | e8e5f66f4c | ||
|   | aaf3d08bcd | ||
|   | 61132d6c40 | ||
|   | 9cc0645a1e | ||
|   | fc8c6a429e | ||
|   | 2f966883d9 | ||
|   | 320ba80ca1 | ||
|   | b621d4dd2e | ||
|   | 56d927c72d | ||
|   | 53afc2e50a | ||
|   | 89debac8f6 | ||
|   | f2197fa2d8 | ||
|   | a6a097c111 | ||
|   | c3e28bc924 | ||
|   | 8d78fb1f6b | ||
|   | 148917d4ca | ||
|   | d8cf9bf942 | ||
|   | d6f5a060ed | ||
|   | 692b6ef8ac | ||
|   | ac5f1fe1be | ||
|   | 0f35acade1 | ||
|   | 56d72ec4c5 | ||
|   | 71d51c160d | ||
|   | 0b58e505ee | ||
|   | 2a6c615bec | ||
|   | ab8c5a0b5f | ||
|   | 68c35feaea | ||
|   | 88d0c2ca0f | ||
|   | 398833ebe3 | ||
|   | 358f5a03bf | ||
|   | fba1fdabe4 | ||
|   | d42afd21e5 | ||
|   | 20ada86761 | ||
|   | 3b353f1855 | ||
|   | 1467ab4f93 | ||
|   | 7e65c2bdad | ||
|   | 84a4e3e98a | ||
|   | bcbeedb001 | ||
|   | e04b103b5d | ||
|   | ac75b94679 | ||
|   | d3bb06cfd6 | ||
|   | 5cd729c4c1 | ||
|   | c9fd2bdf39 | ||
|   | e4be5992b3 | ||
|   | 2ac4988f1b | ||
|   | 19f14adb9e | ||
|   | 86de039492 | ||
|   | 2360164e4f | ||
|   | c93ddceadb | ||
|   | cd19dec44a | ||
|   | 53ba9c800a | ||
|   | cabbaded68 | ||
|   | 9bb589f827 | ||
|   | c3a06686c2 | ||
|   | 7d57f87007 | ||
|   | 4cc4a9d38b | ||
|   | 02c7cd0194 | ||
|   | 696efcb9e2 | ||
|   | 6e9cde8ac1 | ||
|   | a9fae49671 | ||
|   | 440af9fd64 | ||
|   | 347721ae40 | ||
|   | daea91044c | ||
|   | 4ed3f2c662 | ||
|   | 3641c8f60a | ||
|   | e4b68cd940 | ||
|   | b8c936e2fe | ||
|   | 83cd519702 | ||
|   | 54b54f85f3 | ||
|   | ccd874fe4e | ||
|   | 9dc7e8ed3a | ||
|   | 485099fd6e | ||
|   | d359c6b43e | ||
|   | d9ed7a77f8 | ||
|   | 4238a4ca6a | ||
|   | 0902a5a981 | ||
|   | f3192303ab | ||
|   | bef5bd72c2 | ||
|   | b6175e4296 | ||
|   | 3858b2e177 | ||
|   | 9a76e77981 | ||
|   | 8182d640cd | ||
|   | 1c6fda1a5c | ||
|   | c51db1cf2f | ||
|   | 4e7930fc4c | ||
|   | 3563f8ccdb | ||
|   | 575af763f6 | ||
|   | 8b16b9b246 | ||
|   | 01aab66667 | ||
|   | aa5c987a94 | ||
|   | 75229332c8 | ||
|   | 9d5b1ba838 | ||
|   | f27b225b34 | ||
|   | 3c523d66e9 | ||
|   | 1144c27c54 | ||
|   | b442b21d3f | ||
|   | 746ff5307d | ||
|   | ef85b24d8f | ||
|   | c55d93512b | ||
|   | 2e38f9ba61 | ||
|   | 1cadff8e58 | ||
|   | d1eba60ba8 | ||
|   | 057dccad8f | ||
|   | 4285200b4b | ||
|   | 73c2fbbc2a | ||
|   | 37b7e170fa | ||
|   | b032d94877 | ||
|   | 9476016741 | ||
|   | 7a1c9c7798 | ||
|   | c7fb7b4451 | ||
|   | 67c474fc7a | ||
|   | 4e8154cf8a | ||
|   | 9582d3c623 | ||
|   | 0079500713 | ||
|   | 55af6ce834 | ||
|   | 3e82fdc125 | ||
|   | 7344a6cfc0 | ||
|   | 0aded71343 | ||
|   | 7663b1e703 | ||
|   | 282546c03f | ||
|   | f4bc89d1c0 | ||
|   | fa277c3797 | ||
|   | c0c8ab25e6 | ||
|   | b685bf3026 | ||
|   | ce31db09e4 | ||
|   | 624a6cf619 | ||
|   | 587aa87d28 | ||
|   | 88813c4f87 | ||
|   | dacbe29771 | ||
|   | 244833cfa1 | ||
|   | 05e7f974e3 | ||
|   | 0dbef65a73 | ||
|   | 9106228787 | ||
|   | 6ae3bdb25c | ||
|   | 310bcec260 | ||
|   | 8c4cc4e671 | ||
|   | c6eaaa83ed | ||
|   | 8f598d6f96 | ||
|   | 20bc323d17 | ||
|   | 8b0bcf4db9 | ||
|   | 8955e6f536 | ||
|   | f8ddea6452 | ||
|   | 987e04086d | ||
|   | 85f2acbf52 | ||
|   | 1acf4c3ab7 | ||
|   | 07a3158fba | ||
|   | 2f8bed9d82 | ||
|   | a490937cd9 | ||
|   | 8ee5942481 | ||
|   | 93b469885a | ||
|   | d8d1de2dcb | ||
|   | ab224514f0 | ||
|   | 75179de8da | ||
|   | c28df14e6b | ||
|   | b73855b193 | ||
|   | 2093ab2baa | ||
|   | a0f40042cb | ||
|   | 3254c2c477 | ||
|   | 0a8eb9e3ba | ||
|   | 70e0c6f9ef | ||
|   | a8a78d4525 | ||
|   | 57e6ee963d | ||
|   | ce6bfb8420 | ||
|   | f0672bdc59 | ||
|   | 23de953fbd | ||
|   | 03c496bdd8 | ||
|   | d5ee6cf521 | ||
|   | fb7981e053 | ||
|   | 846123ecab | ||
|   | 373cb444fe | ||
|   | 90f212df92 | ||
|   | 12286e4246 | ||
|   | aa60c1f36a | ||
|   | c731f01067 | ||
|   | 6c9c1cdb30 | ||
|   | 9ba2b40e87 | ||
|   | 7a3d055012 | ||
|   | 0824f45e29 | ||
|   | 4debe3446c | ||
|   | 07fe9bcdf6 | ||
|   | 6a557a73f5 | ||
|   | 8d1cfe0c56 | ||
|   | a3a42eebea | ||
|   | 76be8006a4 | ||
|   | bfcfd58259 | ||
|   | 914a4360e7 | ||
|   | 8c31874eeb | ||
|   | ef7afeb2ea | ||
|   | 4067f883a2 | ||
|   | c8974fffbe | ||
|   | b75fb8dc9e | ||
|   | 57356781a9 | ||
|   | e43eab5fd6 | ||
|   | 894cd0e022 | ||
|   | db2c63fffc | ||
|   | 60e0f32f1a | ||
|   | e731996a68 | ||
|   | 2f69cd4209 | ||
|   | fd59de25c5 | ||
|   | af12c3d41a | ||
|   | 54b52bbeb5 | ||
|   | 1174c68d9a | ||
|   | 448ea7167f | ||
|   | 6b27008c99 | ||
|   | 725c785882 | ||
|   | ab068cff67 | ||
|   | 9dc03adfda | ||
|   | 49f9e4eddf | ||
|   | 43c47ac44c | ||
|   | 1cebe64664 | ||
|   | f33c381043 | ||
|   | 3479841c77 | ||
|   | 6a899968a9 | ||
|   | bb8405a36e | ||
|   | c7bc711f63 | ||
|   | e326071c35 | ||
|   | ad6a669381 | ||
|   | e4c9dafc9a | ||
|   | dfc0aefd87 | ||
|   | 356b39c6f5 | ||
|   | 8da7bb6b68 | ||
|   | 9341081a4d | ||
|   | 324a086eb4 | ||
|   | ed595f52c2 | ||
|   | 64ad0023bb | ||
|   | fe5f661d15 | ||
|   | ff26e3a8ba | ||
|   | 14657a762c | ||
|   | 4754fa3902 | ||
|   | f302f87337 | ||
|   | 94dbcde292 | ||
|   | 4336a174b1 | ||
|   | 0adb13ed71 | ||
|   | e78a3d1c19 | ||
|   | c099ec05ee | ||
|   | a20612478e | ||
|   | f778e8bbd1 | ||
|   | 7203c046f9 | ||
|   | 754b61c593 | ||
|   | 927e9e4e4d | ||
|   | 699f9622d7 | ||
|   | 765eb84c33 | ||
|   | 03ba1f7021 | ||
|   | 1f7f20788c | ||
|   | c59dd29190 | ||
|   | 99f63a41a3 | ||
|   | a575f5df36 | ||
|   | 12a1849090 | ||
|   | 0817e627ee | ||
|   | 14d90239a7 | ||
|   | f5d11dc656 | ||
|   | 6dcf5bf077 | ||
|   | ac2082e9b3 | ||
|   | dbac495bee | ||
|   | fe5ccb163e | ||
|   | 1aea5ee007 | ||
|   | 13cd9f8067 | ||
|   | 34496ecaf0 | ||
|   | c043b1d949 | ||
|   | 9a6d2a7b32 | ||
|   | f8a9efa8e4 | ||
|   | 5b2169e0d1 | ||
|   | 2c927ea768 | ||
|   | f4bbcdcbc8 | ||
|   | 79c375b1af | ||
|   | f443a3b3a1 | ||
|   | 684d2d63f4 | ||
|   | 1900d8f843 | ||
|   | 3c2af95d21 | ||
|   | b35414ea0f | ||
|   | fb5b056f7b | ||
|   | 7248c1dfdb | ||
|   | 4c7ea9e893 | ||
|   | c7801ce277 | ||
|   | f741a8e3ff | ||
|   | 6a92e8b609 | ||
|   | 9da91a8217 | ||
|   | 69853c8e5c | ||
|   | 1f41b6c138 | ||
|   | e001efa9fd | ||
|   | 435e64d4cf | ||
|   | f296c8f5fb | ||
|   | 8d0e6ed32f | ||
|   | b6a36afffe | ||
|   | e422abc269 | ||
|   | 221d71d07b | ||
|   | 9f35f0837e | ||
|   | 515891b035 | ||
|   | 94a506876f | ||
|   | 9bde57854a | ||
|   | f456369941 | ||
|   | 8f0a1ffe5d | ||
|   | e4bafc621a | ||
|   | cfa39ab3b0 | ||
|   | 47e91bfd89 | ||
|   | eecc388ebd | ||
|   | 0a15a5ee56 | ||
|   | cfaae47cea | ||
|   | c1a0352592 | ||
|   | 965f45aa3f | ||
|   | 6ea27fe836 | ||
|   | 0dccc22b38 | ||
|   | cbe833962b | ||
|   | b5720f6f10 | ||
|   | 56b4e0b0ec | ||
|   | e316ccb1e0 | ||
|   | a6f93efd39 | ||
|   | 20511cf608 | ||
|   | 1a1dd39367 | ||
|   | 589981bdcb | ||
|   | 89546776b2 | ||
|   | f0d7b3cd12 | ||
|   | e37be627e0 | ||
|   | d803561582 | ||
|   | a1aab4008f | ||
|   | a1172529bf | ||
|   | 1d905bf07f | ||
|   | eed678a14b | ||
|   | b1bdffbc34 | ||
|   | cff718f37d | ||
|   | 40e9430278 | ||
|   | 62fc55fc74 | ||
|   | 80729353c8 | ||
|   | 105ba5e124 | ||
|   | ad1b50d1f5 | ||
|   | 1905437abe | ||
|   | 87fc339c45 | ||
|   | 3af7d61d3e | ||
|   | a45ef7a856 | ||
|   | 299998055d | ||
|   | c9586d39ed | ||
|   | 2e9f67f4e4 | ||
|   | e318170fea | ||
|   | 73c4289792 | ||
|   | ea45d7ee47 | ||
|   | 6d970725e7 | ||
|   | 458c2c6d88 | ||
|   | 0cc53a8964 | ||
|   | 0bc96304a9 | ||
|   | c75b088ff8 | ||
|   | 181f0341f5 | ||
|   | 33bb08d53b | ||
|   | 6d188f6e44 | ||
|   | c3648331f1 | ||
|   | a5b66029d3 | ||
|   | 49bfe80191 | ||
|   | a5def77bfe | ||
|   | 9ecb5b4791 | ||
|   | 1cc48a370a | ||
|   | f1ec8d1e11 | ||
|   | 55c34cd84f | ||
|   | aca52d1e36 | ||
|   | 6f90df26a5 | ||
|   | 9d9cb378ff | ||
|   | f92aac14aa | ||
|   | 3f27d78ab5 | ||
|   | 282d1ba22f | ||
|   | 94c19575b1 | ||
|   | e3e485285b | ||
|   | 986e36720e | ||
|   | 74348ab6c2 | ||
|   | 8d1ad99f42 | ||
|   | e69bbff195 | ||
|   | c9f33bbde0 | ||
|   | 9c9f9d4fa6 | ||
|   | 2f64a6b0cb | ||
|   | dfa78ad3c6 | ||
|   | 677ae46f0c | ||
|   | 6ada2a458f | ||
|   | 8145f3b68d | ||
|   | 48289acee6 | ||
|   | e5a989c6f9 | ||
|   | 4c56704935 | ||
|   | 9cda44f443 | ||
|   | 431451bac2 | ||
|   | 395ca7feea | ||
|   | e0b7533c39 | ||
|   | 5b2a402930 | ||
|   | 85129a1873 | ||
|   | 487d333024 | ||
|   | fe7d35171f | ||
|   | b3aed13567 | ||
|   | a9d4d2bfa3 | ||
|   | 1ff521683f | ||
|   | 0395a03b6b | ||
|   | 7fda7709ff | ||
|   | 65a9200cff | ||
|   | 473eec26c1 | ||
|   | 9fa945ad93 | ||
|   | a895219d2f | ||
|   | 427f7c362e | ||
|   | 73f5c41fae | ||
|   | b4ec168401 | ||
|   | 726d35c766 | ||
|   | 6db796e10c | ||
|   | c38d9134cd | ||
|   | 471204b163 | ||
|   | 7f23bfa66d | ||
|   | 9287b26042 | ||
|   | e22936fbf8 | ||
|   | 04ace9fc16 | ||
|   | 8466b333fb | ||
|   | 96602612ba | ||
|   | 690b98bff9 | ||
|   | 8329131bfe | ||
|   | 9986aab326 | ||
|   | 0b105bc535 | ||
|   | 51ac9c9506 | ||
|   | 0310176696 | ||
|   | 84a7a2bc3e | ||
|   | 1e66a7e555 | ||
|   | 2bffb9d682 | ||
|   | 811125a760 | ||
|   | 0dd91082a1 | ||
|   | c80587868e | ||
|   | 8c52dc86c7 | ||
|   | be24592bc3 | ||
|   | 0d1a5c621d | ||
|   | 8a3eff3b65 | ||
|   | b1050b884d | ||
|   | 181d883a1d | ||
|   | e01b65fd3d | ||
|   | bbd74b5ae2 | ||
|   | d5a5c49357 | ||
|   | a964b164a6 | ||
|   | 1aac0489d7 | ||
|   | e474755887 | ||
|   | bf9a60f70d | ||
|   | a2ba0913d3 | ||
|   | f74df41fff | ||
|   | 2a950e4ce9 | ||
|   | f05e5f908e | ||
|   | 43139b43b1 | ||
|   | 5811b47aad | ||
|   | 54e3db4d8c | ||
|   | 7491421c31 | ||
|   | 9d0da74347 | ||
|   | e9870b293f | ||
|   | ab910d060b | ||
|   | b60ef68ac6 | ||
|   | c9986936ed | ||
|   | d77be46644 | ||
|   | 3715d7a184 | ||
|   | 1c96c7163a | ||
|   | 9f733b25db | ||
|   | 1419a33b64 | ||
|   | f270739f9f | ||
|   | e51a391286 | ||
|   | c815185574 | ||
|   | 8045e29a52 | ||
|   | bbb3e16fd1 | ||
|   | 3cd1657387 | ||
|   | d7ea122cf7 | ||
|   | 6aea7c7f70 | ||
|   | 56ba1d9cd3 | ||
|   | 408b03ae0d | ||
|   | d94fd746af | ||
|   | dbd1316d1e | ||
|   | 75845c0283 | ||
|   | 88db9751d7 | ||
|   | 6f645c4cb7 | ||
|   | 4e31d85349 | ||
|   | de542a81c0 | ||
|   | 461576e7a2 | ||
|   | 21bd62b1ce | ||
|   | 838cd1157c | ||
|   | 2f068b91d8 | ||
|   | aba87bf1bd | ||
|   | e64da8ede4 | ||
|   | a9f38dfce4 | ||
|   | a097537a03 | ||
|   | 66e0b53cf6 | ||
|   | 06f2e81dd5 | ||
|   | 40ae2e812f | ||
|   | 06f613e40b | ||
|   | 61c8c1e8d2 | ||
|   | ee924ee310 | ||
|   | fad0ce3ced | ||
|   | d396180939 | ||
|   | 0d089abe67 | ||
|   | ed5c1dfc3c | ||
|   | 6b949a7375 | ||
|   | 3028e2908f | ||
|   | 578803b01f | ||
|   | 46738825c0 | ||
|   | 56357699cb | ||
|   | fe8e718183 | ||
|   | 1eb34989d4 | ||
|   | 2f3b4c8bfb | ||
|   | 6412768000 | ||
|   | 82688b9a44 | ||
|   | 651e12cfe4 | ||
|   | 4118d581af | ||
|   | 62608bec03 | ||
|   | 71cffc973d | ||
|   | a8e49d084b | ||
|   | db631097b1 | ||
|   | 0d31674166 | ||
|   | cb5af974a4 | ||
|   | f2f421a0a2 | ||
|   | 413c46e2ee | ||
|   | 3b412d51f0 | ||
|   | 4931e2aee2 | ||
|   | ffadf673cf | ||
|   | 5b5a7e5a24 | ||
|   | ab53208f47 | ||
|   | 7c407705e8 | ||
|   | 60378ff941 | ||
|   | 30a0c77d19 | ||
|   | 07ec89276b | ||
|   | a37dc1af9d | ||
|   | 03458df140 | ||
|   | 164eb9659e | ||
|   | 99cfbaa63b | ||
|   | 8d8a6534e3 | ||
|   | 938c5013c9 | ||
|   | ea9d5ec793 | ||
|   | ec65f038a8 | ||
|   | 199ec36d40 | ||
|   | 1326ded048 | ||
|   | 8347439644 | ||
|   | cddc2a8280 | ||
|   | 97a8938407 | ||
|   | 939d1dcae9 | ||
|   | 9d5cc5c11f | ||
|   | d998f24d26 | ||
|   | d543f8857b | ||
|   | c48a942d22 | ||
|   | e1602618c3 | ||
|   | 36be240623 | ||
|   | 04e499c97f | ||
|   | f586a8a9dc | ||
|   | 5112ed77d6 | ||
|   | bf29a54272 | ||
|   | 6d9286a202 | ||
|   | 92fdd07ca3 | ||
|   | 1c937ad960 | ||
|   | f9891a5c04 | ||
|   | e8ad311d84 | ||
|   | 545c09e202 | ||
|   | 4dc281a05f | ||
|   | 3a0af8caad | ||
|   | 8ff2fecb26 | ||
|   | 1855c6aed5 | ||
|   | d4c6643311 | ||
|   | e8c738002b | ||
|   | 309c3aaeb8 | ||
|   | 1f8bcadb3b | ||
|   | 6f4af5fef8 | ||
|   | 868cdb9f8b | ||
|   | 2f76a429ef | ||
|   | a69799aa42 | ||
|   | 139bef2142 | ||
|   | 8ba142bcf4 | ||
|   | c49e4966f6 | ||
|   | 516fa4e49d | ||
|   | 6bf9f89429 | ||
|   | a0ddfcb109 | ||
|   | 3df7921fdc | ||
|   | 6172a9ca2d | ||
|   | 4a40e57cf0 | ||
|   | cdedda4ca1 | ||
|   | e6babd84f7 | ||
|   | 868ec1a7e3 | ||
|   | e08394c870 | ||
|   | a99500aebf | ||
|   | aa5095c23b | ||
|   | 9e0f36e5a7 | ||
|   | d481d079ba | ||
|   | bc9ec7ac4a | ||
|   | 6f7e81067c | ||
|   | af946f398e | ||
|   | c7ca26e9c7 | ||
|   | ef7129f45d | ||
|   | a20bdd334a | ||
|   | 2ef49a92cc | ||
|   | 75f56b68c6 | ||
|   | d34d319d89 | ||
|   | 6660c1da38 | ||
|   | 4e263b8c39 | ||
|   | 3cb604df02 | ||
|   | af9dc7a69e | ||
|   | 1247e69c78 | ||
|   | aab0e4315d | ||
|   | 14f6517733 | ||
|   | 5d75effb37 | ||
|   | ab4f18954b | ||
|   | e1460c65e8 | ||
|   | 425a0fcf07 | ||
|   | 7205ee5e0a | ||
|   | 72c5db8910 | ||
|   | 3067f4be3a | ||
|   | 2aa1ccdd76 | ||
|   | 0284df503f | ||
|   | 2833a983d8 | ||
|   | 39c6be7cb7 | ||
|   | fdc94c1353 | ||
|   | 9cc4e48124 | ||
|   | 34c7f15d6d | ||
|   | 899a9b025e | ||
|   | deb4315383 | ||
|   | 9a06660fdb | ||
|   | 5c35d24e13 | ||
|   | 03f99752a7 | ||
|   | fd37567c18 | ||
|   | 6e38bf1578 | ||
|   | 8b2d278840 | ||
|   | 06aa0a124d | ||
|   | eb4595158d | ||
|   | 32103441f1 | ||
|   | 7ed0aa6630 | ||
|   | f690229f31 | ||
|   | f3bab72a86 | ||
|   | 2bd63c2d27 | ||
|   | 545d9e85e9 | ||
|   | 21a4ab4ec7 | ||
|   | 66fbbeb5ec | ||
|   | 55879c7b6d | ||
|   | 66c4e5a5e2 | ||
|   | 884139e246 | ||
|   | c3d7b1541e | ||
|   | 51ada4d70b | ||
|   | e3a5d52c5e | ||
|   | 559fd70737 | ||
|   | e0dba85cbb | ||
|   | 74c9cf03d0 | ||
|   | 0774e79e4f | ||
|   | a3ec37741a | ||
|   | 9bf5cd83c3 | ||
|   | fab65d6c40 | ||
|   | 4d983e54b5 | 
| @@ -1,4 +1,4 @@ | ||||
| image: freebsd/12.x | ||||
| image: freebsd/14.x | ||||
| sources: | ||||
| - https://git.sr.ht/~bakpakin/janet | ||||
| packages: | ||||
| @@ -9,3 +9,4 @@ tasks: | ||||
|     gmake | ||||
|     gmake test | ||||
|     sudo gmake install | ||||
|     sudo gmake uninstall | ||||
|   | ||||
| @@ -19,3 +19,8 @@ tasks: | ||||
|     ninja | ||||
|     ninja test | ||||
|     sudo ninja install | ||||
| - meson_min: | | ||||
|     cd janet | ||||
|     meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false | ||||
|     cd build_meson_min | ||||
|     ninja | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| image: openbsd/latest | ||||
| image: openbsd/7.4 | ||||
| sources: | ||||
| - https://git.sr.ht/~bakpakin/janet | ||||
| packages: | ||||
| @@ -11,9 +11,10 @@ tasks: | ||||
|     gmake test | ||||
|     doas gmake install | ||||
|     gmake test-install | ||||
|     doas gmake uninstall | ||||
| - meson_min: | | ||||
|     cd janet | ||||
|     meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dtyped_array=false -Dreduced_os=true | ||||
|     meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false | ||||
|     cd build_meson_min | ||||
|     ninja | ||||
| - meson_prf: | | ||||
| @@ -29,4 +30,3 @@ tasks: | ||||
|     ninja | ||||
|     ninja test | ||||
|     doas ninja install | ||||
|  | ||||
|   | ||||
							
								
								
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,4 @@ | ||||
| *.janet linguist-language=Clojure | ||||
|  | ||||
| *.janet linguist-language=Janet | ||||
| *.janet text eol=lf | ||||
| *.c text eol=lf | ||||
| *.h text eol=lf | ||||
|   | ||||
							
								
								
									
										38
									
								
								.github/cosmo/build
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								.github/cosmo/build
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| #!/bin/sh | ||||
| set -eux | ||||
|  | ||||
| COSMO_DIR="/sc/cosmocc" | ||||
|  | ||||
| # build x86_64 | ||||
| X86_64_CC="/sc/cosmocc/bin/x86_64-unknown-cosmo-cc" | ||||
| X86_64_AR="/sc/cosmocc/bin/x86_64-unknown-cosmo-ar" | ||||
| mkdir -p /sc/cosmocc/x86_64 | ||||
| make -j CC="$X86_64_CC" AR="$X86_64_AR" HAS_SHARED=0 JANET_NO_AMALG=1 | ||||
| cp build/janet /sc/cosmocc/x86_64/janet | ||||
| make clean | ||||
|  | ||||
| # build aarch64 | ||||
| AARCH64_CC="/sc/cosmocc/bin/aarch64-unknown-cosmo-cc" | ||||
| AARCH64_AR="/sc/cosmocc/bin/aarch64-unknown-cosmo-ar" | ||||
| mkdir -p /sc/cosmocc/aarch64 | ||||
| make -j CC="$AARCH64_CC" AR="$AARCH64_AR" HAS_SHARED=0 JANET_NO_AMALG=1 | ||||
| cp build/janet /sc/cosmocc/aarch64/janet | ||||
| make clean | ||||
|  | ||||
| # fat binary | ||||
| apefat () { | ||||
|     OUTPUT="$1" | ||||
|     OLDNAME_X86_64="$(basename -- "$2")" | ||||
|     OLDNAME_AARCH64="$(basename -- "$3")" | ||||
|     TARG_FOLD="$(dirname "$OUTPUT")" | ||||
|     "$COSMO_DIR/bin/apelink" -l "$COSMO_DIR/bin/ape-x86_64.elf" \ | ||||
|         -l "$COSMO_DIR/bin/ape-aarch64.elf" \ | ||||
|         -M "$COSMO_DIR/bin/ape-m1.c" \ | ||||
|         -o "$OUTPUT" \ | ||||
|         "$2" \ | ||||
|         "$3" | ||||
|     cp "$2" "$TARG_FOLD/$OLDNAME_X86_64.x86_64" | ||||
|     cp "$3" "$TARG_FOLD/$OLDNAME_AARCH64.aarch64" | ||||
| } | ||||
|  | ||||
| apefat /sc/cosmocc/janet.com /sc/cosmocc/x86_64/janet /sc/cosmocc/aarch64/janet | ||||
							
								
								
									
										21
									
								
								.github/cosmo/setup
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.github/cosmo/setup
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| #!/bin/sh | ||||
| set -e | ||||
|  | ||||
| sudo apt update | ||||
| sudo apt-get install -y ca-certificates libssl-dev\ | ||||
|     qemu qemu-utils qemu-user-static\ | ||||
|     texinfo groff\ | ||||
|     cmake ninja-build bison zip\ | ||||
|     pkg-config build-essential autoconf re2c | ||||
|  | ||||
| # download cosmocc | ||||
| cd /sc | ||||
| wget https://github.com/jart/cosmopolitan/releases/download/3.3.3/cosmocc-3.3.3.zip | ||||
| mkdir -p cosmocc | ||||
| cd cosmocc | ||||
| unzip ../cosmocc-3.3.3.zip | ||||
|  | ||||
| # register | ||||
| cd /sc/cosmocc | ||||
| sudo cp ./bin/ape-x86_64.elf /usr/bin/ape | ||||
| sudo sh -c "echo ':APE:M::MZqFpD::/usr/bin/ape:' >/proc/sys/fs/binfmt_misc/register" | ||||
							
								
								
									
										42
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| name: "CodeQL" | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ "master" ] | ||||
|   pull_request: | ||||
|     branches: [ "master" ] | ||||
|   schedule: | ||||
|     - cron: "2 7 * * 4" | ||||
|  | ||||
| jobs: | ||||
|   analyze: | ||||
|     name: Analyze | ||||
|     runs-on: ubuntu-latest | ||||
|     permissions: | ||||
|       actions: read | ||||
|       contents: read | ||||
|       security-events: write | ||||
|  | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         language: [ cpp ] | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|  | ||||
|       - name: Initialize CodeQL | ||||
|         uses: github/codeql-action/init@v3 | ||||
|         with: | ||||
|           languages: ${{ matrix.language }} | ||||
|           queries: +security-and-quality | ||||
|           tools: linked | ||||
|  | ||||
|       - name: Autobuild | ||||
|         uses: github/codeql-action/autobuild@v3 | ||||
|  | ||||
|       - name: Perform CodeQL Analysis | ||||
|         uses: github/codeql-action/analyze@v3 | ||||
|         with: | ||||
|           category: "/language:${{ matrix.language }}" | ||||
							
								
								
									
										85
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										85
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -5,14 +5,19 @@ on: | ||||
|     tags: | ||||
|       - "v*.*.*" | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|  | ||||
| jobs: | ||||
|  | ||||
|   release: | ||||
|     permissions: | ||||
|       contents: write  # for softprops/action-gh-release to create GitHub release | ||||
|     name: Build release binaries | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ ubuntu-latest, macos-latest ] | ||||
|         os: [ ubuntu-latest, macos-13 ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
| @@ -33,3 +38,81 @@ jobs: | ||||
|             build/janet.h | ||||
|             build/c/janet.c | ||||
|             build/c/shell.c | ||||
|  | ||||
|   release-arm: | ||||
|     permissions: | ||||
|       contents: write  # for softprops/action-gh-release to create GitHub release | ||||
|     name: Build release binaries | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ macos-latest ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Set the version | ||||
|         run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV | ||||
|       - name: Set the platform | ||||
|         run: echo "platform=$(tr '[A-Z]' '[a-z]' <<< $RUNNER_OS)" >> $GITHUB_ENV | ||||
|       - name: Compile the project | ||||
|         run: make clean && make | ||||
|       - name: Build the artifact | ||||
|         run: JANET_DIST_DIR=janet-${{ env.version }}-${{ env.platform }} make build/janet-${{ env.version }}-${{ env.platform }}-aarch64.tar.gz | ||||
|       - name: Draft the release | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           draft: true | ||||
|           files: | | ||||
|             build/*.gz | ||||
|             build/janet.h | ||||
|             build/c/janet.c | ||||
|             build/c/shell.c | ||||
|  | ||||
|   release-windows: | ||||
|     permissions: | ||||
|       contents: write  # for softprops/action-gh-release to create GitHub release | ||||
|     name: Build release binaries for windows | ||||
|     runs-on: windows-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Setup MSVC | ||||
|         uses: ilammy/msvc-dev-cmd@v1 | ||||
|       - name: Build the project | ||||
|         shell: cmd | ||||
|         run: build_win all | ||||
|       - name: Draft the release | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           draft: true | ||||
|           files: | | ||||
|             ./dist/*.zip | ||||
|             ./*.zip | ||||
|             ./*.msi | ||||
|  | ||||
|   release-cosmo: | ||||
|     permissions: | ||||
|       contents: write  # for softprops/action-gh-release to create GitHub release | ||||
|     name: Build release binaries for Cosmo | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: create build folder | ||||
|         run: | | ||||
|           sudo mkdir -p /sc | ||||
|           sudo chmod -R 0777 /sc | ||||
|       - name: setup Cosmopolitan Libc | ||||
|         run: bash ./.github/cosmo/setup | ||||
|       - name: Set the version | ||||
|         run: echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV | ||||
|       - name: Set the platform | ||||
|         run: echo "platform=cosmo" >> $GITHUB_ENV | ||||
|       - name: build Janet APE binary | ||||
|         run: bash ./.github/cosmo/build | ||||
|       - name: push binary to github | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           draft: true | ||||
|           files: | | ||||
|             /sc/cosmocc/janet.com | ||||
|   | ||||
							
								
								
									
										108
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										108
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,6 +2,9 @@ name: Test | ||||
|  | ||||
| on: [push, pull_request] | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|  | ||||
| jobs: | ||||
|  | ||||
|   test-posix: | ||||
| @@ -9,7 +12,7 @@ jobs: | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ ubuntu-latest, macos-latest ] | ||||
|         os: [ ubuntu-latest, macos-latest, macos-13 ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
| @@ -20,7 +23,10 @@ jobs: | ||||
|  | ||||
|   test-windows: | ||||
|     name: Build and test on Windows | ||||
|     runs-on: windows-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ windows-latest, windows-2019 ] | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
| @@ -32,3 +38,101 @@ jobs: | ||||
|       - name: Test the project | ||||
|         shell: cmd | ||||
|         run: build_win test | ||||
|       - name: Test installer build | ||||
|         shell: cmd | ||||
|         run: build_win dist | ||||
|  | ||||
|   test-windows-min: | ||||
|     name: Build and test on Windows Minimal build | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ windows-2019 ] | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Setup MSVC | ||||
|         uses: ilammy/msvc-dev-cmd@v1 | ||||
|       - name: Setup Python | ||||
|         uses: actions/setup-python@v2 | ||||
|         with: | ||||
|           python-version: '3.x' | ||||
|       - name: Install Python Dependencies | ||||
|         run: pip install meson ninja | ||||
|       - name: Build | ||||
|         shell: cmd | ||||
|         run: | | ||||
|           meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false | ||||
|           cd build_meson_min | ||||
|           ninja | ||||
|  | ||||
|   test-mingw: | ||||
|     name: Build on Windows with Mingw | ||||
|     runs-on: windows-latest | ||||
|     defaults: | ||||
|       run: | ||||
|         shell: msys2 {0} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         msystem: [ UCRT64, CLANG64 ] | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Setup Mingw | ||||
|         uses: msys2/setup-msys2@v2 | ||||
|         with: | ||||
|           msystem: ${{ matrix.msystem }} | ||||
|           update: true | ||||
|           install: >- | ||||
|             base-devel | ||||
|             git | ||||
|             gcc | ||||
|       - name: Build | ||||
|         shell: cmd | ||||
|         run: make -j4 CC=gcc | ||||
|       - name: Test | ||||
|         shell: cmd | ||||
|         run: make -j4 CC=gcc test | ||||
|  | ||||
|   test-mingw-linux: | ||||
|     name: Build and test with Mingw on Linux + Wine | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Setup Mingw and wine | ||||
|         run: | | ||||
|           sudo dpkg --add-architecture i386 | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install libstdc++6:i386 libgcc-s1:i386 | ||||
|           sudo apt-get install gcc-mingw-w64-x86-64-win32 wine wine32 wine64 | ||||
|       - name: Compile the project | ||||
|         run: make clean && make CC=x86_64-w64-mingw32-gcc LD=x86_64-w64-mingw32-gcc UNAME=MINGW RUN=wine | ||||
|       - name: Test the project | ||||
|         run: make test UNAME=MINGW RUN=wine VERBOSE=1 | ||||
|  | ||||
|   test-arm-linux: | ||||
|     name: Build and test ARM32 cross compilation | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@master | ||||
|       - name: Setup qemu and cross compiler | ||||
|         run: | | ||||
|           sudo apt-get update | ||||
|           sudo apt-get install gcc-arm-linux-gnueabi qemu-user | ||||
|       - name: Compile the project | ||||
|         run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" CC=arm-linux-gnueabi-gcc LD=arm-linux-gnueabi-gcc | ||||
|       - name: Test the project | ||||
|         run: make RUN="qemu-arm -L /usr/arm-linux-gnueabi/" SUBRUN="qemu-arm -L /usr/arm-linux-gnueabi/" test VERBOSE=1 | ||||
|  | ||||
|   test-s390x-linux: | ||||
|     name: Build and test s390x in qemu | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|     - name: Checkout the repository | ||||
|       uses: actions/checkout@master | ||||
|     - name: Enable qemu | ||||
|       run: docker run --privileged --rm tonistiigi/binfmt --install s390x | ||||
|     - name: Build and run on emulated architecture | ||||
|       run: docker run --rm -v .:/janet --platform linux/s390x alpine sh -c "apk update && apk add --no-interactive git build-base && cd /janet && make -j3 && make test" | ||||
|   | ||||
							
								
								
									
										18
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -34,7 +34,11 @@ local | ||||
|  | ||||
| # Common test files I use. | ||||
| temp.janet | ||||
| temp.c | ||||
| temp*janet | ||||
| temp*.c | ||||
| scratch.janet | ||||
| scratch.c | ||||
|  | ||||
| # Emscripten | ||||
| *.bc | ||||
| @@ -44,6 +48,8 @@ janet.wasm | ||||
| # Generated files | ||||
| *.gen.h | ||||
| *.gen.c | ||||
| *.tmp | ||||
| temp.* | ||||
|  | ||||
| # Generate test files | ||||
| *.out | ||||
| @@ -56,6 +62,7 @@ xxd.exe | ||||
| # VSCode | ||||
| .vs | ||||
| .clangd | ||||
| .cache | ||||
|  | ||||
| # Swap files | ||||
| *.swp | ||||
| @@ -67,10 +74,13 @@ tags | ||||
| vgcore.* | ||||
| *.out.* | ||||
|  | ||||
| # Wix artifacts | ||||
| # WiX artifacts | ||||
| *.msi | ||||
| *.wixpdb | ||||
|  | ||||
| # Makefile config | ||||
| /config.mk | ||||
|  | ||||
| # Created by https://www.gitignore.io/api/c | ||||
|  | ||||
| ### C ### | ||||
| @@ -118,6 +128,9 @@ vgcore.* | ||||
| *.idb | ||||
| *.pdb | ||||
|  | ||||
| # GGov | ||||
| *.gcov | ||||
|  | ||||
| # Kernel Module Compile Results | ||||
| *.mod* | ||||
| *.cmd | ||||
| @@ -126,6 +139,9 @@ Module.symvers | ||||
| Mkfile.old | ||||
| dkms.conf | ||||
|  | ||||
| # Coverage files | ||||
| *.cov | ||||
|  | ||||
| # End of https://www.gitignore.io/api/c | ||||
|  | ||||
| # Created by https://www.gitignore.io/api/cmake | ||||
|   | ||||
							
								
								
									
										310
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										310
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,308 @@ | ||||
| # Changelog | ||||
| All notable changes to this project will be documented in this file. | ||||
|  | ||||
| ## 1.38.0 - 2025-03-18 | ||||
| - Add `bundle/replace` | ||||
| - Add CLI flags for the `bundle/` module to install and manage bundles. | ||||
| - Improve `?` peg special termination behavior | ||||
| - Add IEEE hex floats to grammar. | ||||
| - Add buffer peg literal support | ||||
| - Improve `split` peg special edge case behavior | ||||
| - Add Arm64 .msi support | ||||
| - Add `no-reuse` argument to `net/listen` to disable reusing server sockets | ||||
| - Add `struct/rawget` | ||||
| - Fix `deep=` and `deep-not=` to better handle degenerate cases with mutable table keys | ||||
| - Long strings will now dedent on `\r\n` instead of just `\n`. | ||||
| - Add `ev/to-file` for synchronous resource operations | ||||
| - Improve `file/open` error message by including path | ||||
|  | ||||
| ## 1.37.1 - 2024-12-05 | ||||
| - Fix meson cross compilation | ||||
| - Update timeout documentation for networking APIs: timeouts raise errors and do not return nil. | ||||
| - Add `janet_addtimeout_nil(double sec);` to the C API. | ||||
| - Change string hashing. | ||||
| - Fix string equality bug. | ||||
| - Add `assertf` | ||||
| - Change how JANET_PROFILE is loaded to allow more easily customizing the environment. | ||||
| - Add `*repl-prompt*` dynamic binding to allow customizing the built in repl. | ||||
| - Add multiple path support in the `JANET_PATH` environment variables. This lets | ||||
|   user more easily import modules from many directories. | ||||
| - Add `nth` and `only-tags` PEG specials to select from sub-captures while | ||||
|   dropping the rest. | ||||
|  | ||||
| ## 1.36.0 - 2024-09-07 | ||||
| - Improve error messages in `bundle/add*` functions. | ||||
| - Add CI testing and verify tests pass on the s390x architecture. | ||||
| - Save `:source-form` in environment entries when `*debug*` is set. | ||||
| - Add experimental `filewatch/` module for listening to file system changes on Linux and Windows. | ||||
| - Add `bundle/who-is` to query which bundle a file on disk was installed by. | ||||
| - Add `geomean` function | ||||
| - Add `:R` and `:W` flags to `os/pipe` to create blocking pipes on Posix and Windows systems. | ||||
|   These streams cannot be directly read to and written from, but can be passed to subprocesses. | ||||
| - Add `array/join` | ||||
| - Add `tuple/join` | ||||
| - Add `bundle/add-bin` to make installing scripts easier. This also establishes a packaging convention for it. | ||||
| - Fix marshalling weak tables and weak arrays. | ||||
| - Fix bug in `ev/` module that could accidentally close sockets on accident. | ||||
| - Expose C functions for constructing weak tables in janet.h | ||||
| - Let range take non-integer values. | ||||
|  | ||||
| ## 1.35.2 - 2024-06-16 | ||||
| - Fix some documentation typos. | ||||
| - Allow using `:only` in import without quoting. | ||||
|  | ||||
| ## 1.35.0 - 2024-06-15 | ||||
| - Add `:only` argument to `import` to allow for easier control over imported bindings. | ||||
| - Add extra optional `env` argument to `eval` and `eval-string`. | ||||
| - Allow naming function literals with a keyword. This allows better stacktraces for macros without | ||||
|   accidentally adding new bindings. | ||||
| - Add `bundle/` module for managing packages within Janet. This should replace the jpm packaging | ||||
|   format eventually and is much simpler and amenable to more complicated builds. | ||||
| - Add macros `ev/with-lock`, `ev/with-rlock`, and `ev/with-wlock` for using mutexes and rwlocks. | ||||
| - Add `with-env` | ||||
| - Add *module-make-env* dynamic binding | ||||
| - Add buffer/format-at | ||||
| - Add long form command line options for readable CLI usage | ||||
| - Fix bug with `net/accept-loop` that would sometimes miss connections. | ||||
|  | ||||
| ## 1.34.0 - 2024-03-22 | ||||
| - Add a new (split) PEG special by @ianthehenry | ||||
| - Add buffer/push-* sized int and float by @pnelson | ||||
| - Documentation improvements: @amano-kenji, @llmII, @MaxGyver83, @pepe, @sogaiu. | ||||
| - Expose _exit to skip certain cleanup with os/exit. | ||||
| - Swap set / body order for each by @sogaiu. | ||||
| - Abort on assert failure instead of exit. | ||||
| - Fix: os/proc-wait by @llmII. | ||||
| - Fix macex1 to keep syntax location for all tuples. | ||||
| - Restore if-let tail calls. | ||||
| - Don't try and resume fibers that can't be resumed. | ||||
| - Register stream on unmarshal. | ||||
| - Fix asm roundtrip issue. | ||||
|  | ||||
| ## 1.33.0 - 2024-01-07 | ||||
| - Add more + and * keywords to default-peg-grammar by @sogaiu. | ||||
| - Use libc strlen in janet_buffer_push_cstring by @williewillus. | ||||
| - Be a bit safer with reference counting. | ||||
| - Add support for atomic loads in Janet's atomic abstraction. | ||||
| - Fix poll event loop CPU usage issue. | ||||
| - Add ipv6, shared, and cryptorand options to meson. | ||||
| - Add more ipv6 feature detection. | ||||
| - Fix loop for forever loop. | ||||
| - Cleaned up unused NetStateConnect, fixed janet_async_end() ev refcount by @zevv. | ||||
| - Fix warnings w/ MSVC and format. | ||||
| - Fix marshal_one_env w/ JANET_MARSHAL_UNSAFE. | ||||
| - Fix `(default)`. | ||||
| - Fix cannot marshal fiber with c stackframe, in a dynamic way that is fairly conservative. | ||||
| - Fix typo for SIGALARM in os/proc-kill. | ||||
| - Prevent bytecode optimization from remove mk* instructions. | ||||
| - Fix arity typo in peg.c by @pepe. | ||||
| - Update Makefile for MinGW. | ||||
| - Fix canceling waiting fiber. | ||||
| - Add a new (sub) PEG special by @ianthehenry. | ||||
| - Fix if net/server's handler has incorrect arity. | ||||
| - Fix macex raising on (). | ||||
|  | ||||
| ## 1.32.1 - 2023-10-15 | ||||
| - Fix return value from C function `janet_dobytes` when called on Janet functions that yield to event loop. | ||||
| - Change C API for event loop interaction - get rid of JanetListener and instead use `janet_async_start` and `janet_async_end`. | ||||
| - Rework event loop to make fewer system calls on kqueue and epoll. | ||||
| - Expose atomic refcount abstraction in janet.h | ||||
| - Add `array/weak` for weak references in arrays | ||||
| - Add support for weak tables via `table/weak`, `table/weak-keys`, and `table/weak-values`. | ||||
| - Fix compiler bug with using the result of `(break x)` expression in some contexts. | ||||
| - Rework internal event loop code to be better behaved on Windows | ||||
| - Update meson build to work better on windows | ||||
|  | ||||
| ## 1.31.0 - 2023-09-17 | ||||
| - Report line and column when using `janet_dobytes` | ||||
| - Add `:unless` loop modifier | ||||
| - Allow calling `reverse` on generators. | ||||
| - Improve performance of a number of core functions including `partition`, `mean`, `keys`, `values`, `pairs`, `interleave`. | ||||
| - Add `lengthable?` | ||||
| - Add `os/sigaction` | ||||
| - Change `every?` and `any?` to behave like the functional versions of the `and` and `or` macros. | ||||
| - Fix bug with garbage collecting threaded abstract types. | ||||
| - Add `:signal` to the `sandbox` function to allow intercepting signals. | ||||
|  | ||||
| ## 1.30.0 - 2023-08-05 | ||||
| - Change indexing of `array/remove` to start from -1 at the end instead of -2. | ||||
| - Add new string escape sequences `\\a`, `\\b`, `\\?`, and `\\'`. | ||||
| - Fix bug with marshalling channels | ||||
| - Add `div` for floored division | ||||
| - Make `div` and `mod` variadic | ||||
| - Support `bnot` for integer types. | ||||
| - Define `(mod x 0)` as `x` | ||||
| - Add `ffi/pointer-cfunction` to convert pointers to cfunctions | ||||
|  | ||||
| ## 1.29.1 - 2023-06-19 | ||||
| - Add support for passing booleans to PEGs for "always" and "never" matching. | ||||
| - Allow dictionary types for `take` and `drop` | ||||
| - Fix bug with closing channels while other fibers were waiting on them - `ev/take`, `ev/give`, and `ev/select`  will now return the correct (documented) value when another fiber closes the channel. | ||||
| - Add `ffi/calling-conventions` to show all available calling conventions for FFI. | ||||
| - Add `net/setsockopt` | ||||
| - Add `signal` argument to `os/proc-kill` to send signals besides `SIGKILL` on Posix. | ||||
| - Add `source` argument to `os/clock` to get different time sources. | ||||
| - Various combinator functions now are variadic like `map` | ||||
| - Add `file/lines` to iterate over lines in a file lazily. | ||||
| - Reorganize test suite to be sorted by module rather than pseudo-randomly. | ||||
| - Add `*task-id*` | ||||
| - Add `env` argument to `fiber/new`. | ||||
| - Add `JANET_NO_AMALG` flag to Makefile to properly incremental builds | ||||
| - Optimize bytecode compiler to generate fewer instructions and improve loops. | ||||
| - Fix bug with `ev/gather` and hung fibers. | ||||
| - Add `os/isatty` | ||||
| - Add `has-key?` and `has-value?` | ||||
| - Make imperative arithmetic macros variadic | ||||
| - `ev/connect` now yields to the event loop instead of blocking while waiting for an ACK. | ||||
|  | ||||
| ## 1.28.0 - 2023-05-13 | ||||
| - Various bug fixes | ||||
| - Make nested short-fn's behave a bit more predictably (it is still not recommended to nest short-fns). | ||||
| - Add `os/strftime` for date formatting. | ||||
| - Fix `ev/select` on threaded channels sometimes live-locking. | ||||
| - Support the `NO_COLOR` environment variable to turn off VT100 color codes in repl (and in scripts). | ||||
|   See http://no-color.org/ | ||||
| - Disallow using `(splice x)` in contexts where it doesn't make sense rather than silently coercing to `x`. | ||||
|   Instead, raise a compiler error. | ||||
| - Change the names of `:user8` and `:user9` signals to `:interrupt` and `:await` | ||||
| - Change the names of `:user8` and `:user9` fiber statuses to `:interrupted` and `:suspended`. | ||||
| - Add `ev/all-tasks` to see all currently suspended fibers. | ||||
| - Add `keep-syntax` and `keep-syntax!` functions to make writing macros easier. | ||||
|  | ||||
| ## 1.27.0 - 2023-03-05 | ||||
| - Change semantics around bracket tuples to no longer be equal to regular tuples. | ||||
| - Add `index` argument to `ffi/write` for symmetry with `ffi/read`. | ||||
| - Add `buffer/push-at` | ||||
| - Add `ffi/pointer-buffer` to convert pointers to buffers the cannot be reallocated. This | ||||
|   allows easier manipulation of FFI memory, memory mapped files, and buffer memory shared between threads. | ||||
| - Calling `ev/cancel` on a fiber waiting on `ev/gather` will correctly | ||||
|   cancel the child fibers. | ||||
| - Add `(sandbox ...)` function to core for permission based security. Also add `janet_sandbox` to C API. | ||||
|   The sandbox allows limiting access to the file system, network, ffi, and OS resources at runtime. | ||||
| - Add `(.locals)` function to debugger to see currently bound local symbols. | ||||
| - Track symbol -> slot mapping so debugger can get symbolic information. This exposes local bindings | ||||
|   in `debug/stack` and `disasm`. | ||||
| - Add `os/compiler` to detect what host compiler was used to compile the interpreter | ||||
| - Add support for mingw and cygwin builds (mingw support also added in jpm). | ||||
|  | ||||
| ## 1.26.0 - 2023-01-07 | ||||
| - Add `ffi/malloc` and `ffi/free`. Useful as tools of last resort. | ||||
| - Add `ffi/jitfn` to allow calling function pointers generated at runtime from machine code. | ||||
|   Bring your own assembler, though. | ||||
| - Channels can now be marshalled. Pending state is not saved, only items in the channel. | ||||
| - Use the new `.length` function pointer on abstract types for lengths. Adding | ||||
|   a `length` method will still work as well. | ||||
| - Support byte views on abstract types with the `.bytes` function pointer. | ||||
| - Add the `u` format specifier to printf family functions. | ||||
| - Allow printing 64 integer types in `printf` and `string/format` family functions. | ||||
| - Allow importing modules from custom directories more easily with the `@` prefix | ||||
|   to module paths. For example, if there is a dynamic binding :custom-modules that | ||||
|   is a file system path to a directory of modules, import from that directory with | ||||
|   `(import @custom-modules/mymod)`. | ||||
| - Fix error message bug in FFI library. | ||||
|  | ||||
| ## 1.25.1 - 2022-10-29 | ||||
| - Add `memcmp` function to core library. | ||||
| - Fix bug in `os/open` with `:rw` permissions not correct on Linux. | ||||
| - Support config.mk for more easily configuring the Makefile. | ||||
|  | ||||
| ## 1.25.0 - 2022-10-10 | ||||
| - Windows FFI fixes. | ||||
| - Fix PEG `if-not` combinator with captures in the condition | ||||
| - Fix bug with `os/date` with nil first argument | ||||
| - Fix bug with `net/accept` on Linux that could leak file descriptors to subprocesses | ||||
| - Reduce number of hash collisions from pointer hashing | ||||
| - Add optional parameter to `marshal` to skip cycle checking code | ||||
|  | ||||
| ## 1.24.1 - 2022-08-24 | ||||
| - Fix FFI bug on Linux/Posix | ||||
| - Improve parse error messages for bad delimiters. | ||||
| - Add optional `name` parameter to the `short-fn` macro. | ||||
|  | ||||
| ## 1.24.0 - 2022-08-14 | ||||
| - Add FFI support to 64-bit windows compiled with MSVC | ||||
| - Don't process shared object names passed to dlopen. | ||||
| - Add better support for windows console in the default shell.c for auto-completion and | ||||
|   other shell-like input features. | ||||
| - Improve default error message from `assert`. | ||||
| - Add the `tabseq` macro for simpler table comprehensions. | ||||
| - Allow setting `(dyn :task-id)` in fibers to improve context in supervisor messages. Prior to | ||||
|   this change, supervisor messages over threaded channels would be from ambiguous threads/fibers. | ||||
|  | ||||
| ## 1.23.0 - 2022-06-20 | ||||
| - Add experimental `ffi/` module for interfacing with dynamic libraries and raw function pointers. Only available | ||||
|   on 64 bit linux, mac, and bsd systems. | ||||
| - Allow using `&named` in function prototypes for named arguments. This is a more ergonomic | ||||
|   variant of `&keys` that isn't as redundant, more self documenting, and allows extension to | ||||
|   things like default arguments. | ||||
| - Add `delay` macro for lazy evaluate-and-save thunks. | ||||
| - Remove pthread.h from janet.h for easier includes. | ||||
| - Add `debugger` - an easy to use debugger function that just takes a fiber. | ||||
| - `dofile` will now start a debugger on errors if the environment it is passed has `:debug` set. | ||||
| - Add `debugger-on-status` function, which can be passed to `run-context` to start a debugger on | ||||
|   abnormal fiber signals. | ||||
| - Allow running scripts with the `-d` flag to use the built-in debugger on errors and breakpoints. | ||||
| - Add mutexes (locks) and reader-writer locks to ev module for thread coordination. | ||||
| - Add `parse-all` as a generalization of the `parse` function. | ||||
| - Add `os/cpu-count` to get the number of available processors on a machine | ||||
|  | ||||
| ## 1.22.0 - 2022-05-09 | ||||
| - Prohibit negative size argument to `table/new`. | ||||
| - Add `module/value`. | ||||
| - Remove `file/popen`. Use `os/spawn` with the `:pipe` options instead. | ||||
| - Fix bug in peg `thru` and `to` combinators. | ||||
| - Fix printing issue in `doc` macro. | ||||
| - Numerous updates to function docstrings | ||||
| - Add `defdyn` aliases for various dynamic bindings used in core. | ||||
| - Install `janet.h` symlink to make Janet native libraries and applications | ||||
|   easier to build without `jpm`. | ||||
|  | ||||
| ## 1.21.2 - 2022-04-01 | ||||
| - C functions `janet_dobytes` and `janet_dostring` will now enter the event loop if it is enabled. | ||||
| - Fix hashing regression - hash of negative 0 must be the same as positive 0 since they are equal. | ||||
| - The `flycheck` function no longer pollutes the module/cache | ||||
| - Fix quasiquote bug in compiler | ||||
| - Disallow use of `cancel` and `resume` on fibers scheduled or created with `ev/go`, as well as the root | ||||
|   fiber. | ||||
|  | ||||
| ## 1.20.0 - 2022-1-27 | ||||
| - Add `:missing-symbol` hook to `compile` that will act as a catch-all macro for undefined symbols. | ||||
| - Add `:redef` dynamic binding that will allow users to redefine top-level bindings with late binding. This | ||||
|   is intended for development use. | ||||
| - Fix a bug with reading from a stream returned by `os/open` on Windows and Linux. | ||||
| - Add `:ppc64` as a detectable OS type. | ||||
| - Add `& more` support for destructuring in the match macro. | ||||
| - Add `& more` support for destructuring in all binding forms (`def`). | ||||
|  | ||||
| ## 1.19.2 - 2021-12-06 | ||||
| - Fix bug with missing status lines in some stack traces. | ||||
| - Update hash function to have better statistical properties. | ||||
|  | ||||
| ## 1.19.1 - 2021-12-04 | ||||
| - Add an optional `prefix` parameter to `debug/stacktrace` to allow printing prettier error messages. | ||||
| - Remove appveyor for CI pipeline | ||||
| - Fixed a bug that prevented sending threaded abstracts over threaded channels. | ||||
| - Fix bug in the `map` function with arity at least 3. | ||||
|  | ||||
| ## 1.19.0 - 2021-11-27 | ||||
| - Add `math/log-gamma` to replace `math/gamma`, and change `math/gamma` to be the expected gamma function. | ||||
| - Fix leaking file-descriptors in os/spawn and os/execute. | ||||
| - Ctrl-C will now raise SIGINT. | ||||
| - Allow quoted literals in the `match` macro to behave as expected in patterns. | ||||
| - Fix windows net related bug for TCP servers. | ||||
| - Allow evaluating ev streams with dofile. | ||||
| - Fix `ev` related bug with operations on already closed file descriptors. | ||||
| - Add struct and table agnostic `getproto` function. | ||||
| - Add a number of functions related to structs. | ||||
| - Add prototypes to structs. Structs can now inherit from other structs, just like tables. | ||||
| - Create a struct with a prototype with `struct/with-proto`. | ||||
| - Deadlocked channels will no longer exit early - instead they will hang, which is more intuitive. | ||||
|  | ||||
| ## 1.18.1 - 2021-10-16 | ||||
| - Fix some documentation typos | ||||
| - Fix - Set pipes passed to subprocess to blocking mode. | ||||
| - Fix `-r` switch in repl. | ||||
|  | ||||
| ## 1.18.0 - 2021-10-10 | ||||
| - Allow `ev/cancel` to work on already scheduled fibers. | ||||
| - Fix bugs with ev/ module. | ||||
| @@ -33,7 +335,7 @@ All notable changes to this project will be documented in this file. | ||||
| - Add the ability to close channels with `ev/chan-close` (or `:close`). | ||||
| - Add threaded channels with `ev/thread-chan`. | ||||
| - Add `JANET_FN` and `JANET_REG` macros to more easily define C functions that export their source mapping information. | ||||
| - Add `janet_interpreter_interupt` and `janet_loop1_interrupt` to interrupt the interpreter while running. | ||||
| - Add `janet_interpreter_interrupt` and `janet_loop1_interrupt` to interrupt the interpreter while running. | ||||
| - Add `table/clear` | ||||
| - Add build option to disable the threading library without disabling all threads. | ||||
| - Remove JPM from the main Janet distribution. Instead, JPM must be installed | ||||
| @@ -87,7 +389,7 @@ saving and restoring the entire VM state. | ||||
| - Sort keys in pretty printing output. | ||||
|  | ||||
| ## 1.15.3 - 2021-02-28 | ||||
| - Fix a fiber bug that occured in deeply nested fibers | ||||
| - Fix a fiber bug that occurred in deeply nested fibers | ||||
| - Add `unref` combinator to pegs. | ||||
| - Small docstring changes. | ||||
|  | ||||
| @@ -237,13 +539,13 @@ saving and restoring the entire VM state. | ||||
| - Add `symbol/slice` | ||||
| - Add `keyword/slice` | ||||
| - Allow cross compilation with Makefile. | ||||
| - Change `compare-primitve` to `cmp` and make it more efficient. | ||||
| - Change `compare-primitive` to `cmp` and make it more efficient. | ||||
| - Add `reverse!` for reversing an array or buffer in place. | ||||
| - `janet_dobytes` and `janet_dostring` return parse errors in \*out | ||||
| - Add `repeat` macro for iterating something n times. | ||||
| - Add `eachy` (each yield) macro for iterating a fiber. | ||||
| - Fix `:generate` verb in loop macro to accept non symbols as bindings. | ||||
| - Add `:h`, `:h+`, and `:h*` in `default-peg-grammar` for hexidecimal digits. | ||||
| - Add `:h`, `:h+`, and `:h*` in `default-peg-grammar` for hexadecimal digits. | ||||
| - Fix `%j` formatter to print numbers precisely (using the `%.17g` format string to printf). | ||||
|  | ||||
| ## 1.10.1 - 2020-06-18 | ||||
|   | ||||
| @@ -43,7 +43,7 @@ For changes to the VM and Core code, you will probably need to know C. Janet is | ||||
| a subset of C99 that works with Microsoft Visual C++. This means most of C99 but with the following | ||||
| omissions. | ||||
|  | ||||
| * No `restrict`  | ||||
| * No `restrict` | ||||
| * Certain functions in the standard library are not always available | ||||
|  | ||||
| In practice, this means programming for both MSVC on one hand and everything else on the other. | ||||
| @@ -64,6 +64,23 @@ ensure a consistent code style for C. | ||||
| All janet code in the project should be formatted similar to the code in core.janet. | ||||
| The auto formatting from janet.vim will work well. | ||||
|  | ||||
| ## Typo Fixing and One-Line changes | ||||
|  | ||||
| Typo fixes are welcome, as are simple one line fixes. Do not open many separate pull requests for each | ||||
| individual typo fix. This is incredibly annoying to deal with as someone needs to review each PR, run | ||||
| CI, and merge. Instead, accumulate batches of typo fixes into a single PR. If there are objections to | ||||
| specific changes, these can be addressed in the review process before the final merge, if the changes | ||||
| are accepted. | ||||
|  | ||||
| Similarly, low effort and bad faith changes are annoying to developers and such issues may be closed | ||||
| immediately without response. | ||||
|  | ||||
| ## Contributions from Automated Tools | ||||
|  | ||||
| People making changes found or generated by automated tools MUST note this when opening an issue | ||||
| or creating a pull request. This can help give context to developers if the change/issue is | ||||
| confusing or nonsensical. | ||||
|  | ||||
| ## Suggesting Changes | ||||
|  | ||||
| To suggest changes, open an issue on GitHub. Check GitHub for other issues | ||||
|   | ||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| Copyright (c) 2021 Calvin Rose and contributors | ||||
| Copyright (c) 2025 Calvin Rose and contributors | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy of | ||||
| this software and associated documentation files (the "Software"), to deal in | ||||
|   | ||||
							
								
								
									
										160
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2021 Calvin Rose | ||||
| # Copyright (c) 2025 Calvin Rose | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -21,61 +21,106 @@ | ||||
| ################################ | ||||
| ##### Set global variables ##### | ||||
| ################################ | ||||
|  | ||||
| sinclude config.mk | ||||
| PREFIX?=/usr/local | ||||
|  | ||||
| JANETCONF_HEADER?=src/conf/janetconf.h | ||||
| INCLUDEDIR?=$(PREFIX)/include | ||||
| BINDIR?=$(PREFIX)/bin | ||||
| LIBDIR?=$(PREFIX)/lib | ||||
| JANET_BUILD?="\"$(shell git log --pretty=format:'%h' -n 1 2> /dev/null || echo local)\"" | ||||
| CLIBS=-lm -lpthread | ||||
| JANET_TARGET=build/janet | ||||
| JANET_BOOT=build/janet_boot | ||||
| JANET_IMPORT_LIB=build/janet.lib | ||||
| JANET_LIBRARY_IMPORT_LIB=build/libjanet.lib | ||||
| JANET_LIBRARY=build/libjanet.so | ||||
| JANET_STATIC_LIBRARY=build/libjanet.a | ||||
| JANET_PATH?=$(LIBDIR)/janet | ||||
| JANET_MANPATH?=$(PREFIX)/share/man/man1/ | ||||
| JANET_PKG_CONFIG_PATH?=$(LIBDIR)/pkgconfig | ||||
| JANET_DIST_DIR?=janet-dist | ||||
| JANET_BOOT_FLAGS:=. JANET_PATH '$(JANET_PATH)' | ||||
| JANET_TARGET_OBJECTS=build/janet.o build/shell.o | ||||
| JPM_TAG?=master | ||||
| SPORK_TAG?=master | ||||
| HAS_SHARED?=1 | ||||
| DEBUGGER=gdb | ||||
| SONAME_SETTER=-Wl,-soname, | ||||
|  | ||||
| # For cross compilation | ||||
| HOSTCC?=$(CC) | ||||
| HOSTAR?=$(AR) | ||||
| CFLAGS?=-O2 | ||||
| # Symbols are (optionally) removed later, keep -g as default! | ||||
| CFLAGS?=-O2 -g | ||||
| LDFLAGS?=-rdynamic | ||||
| LIBJANET_LDFLAGS?=$(LD_FLAGS) | ||||
| RUN:=$(RUN) | ||||
|  | ||||
|  | ||||
| COMMON_CFLAGS:=-std=c99 -Wall -Wextra -Isrc/include -Isrc/conf -fvisibility=hidden -fPIC | ||||
| BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 -g $(COMMON_CFLAGS) | ||||
| BOOT_CFLAGS:=-DJANET_BOOTSTRAP -DJANET_BUILD=$(JANET_BUILD) -O0 $(COMMON_CFLAGS) -g | ||||
| BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS) | ||||
|  | ||||
| # Disable amalgamated build | ||||
| ifeq ($(JANET_NO_AMALG), 1) | ||||
| 	JANET_TARGET_OBJECTS+=$(patsubst src/%.c,build/%.bin.o,$(JANET_CORE_SOURCES)) | ||||
| 	JANET_BOOT_FLAGS+=image-only | ||||
| endif | ||||
|  | ||||
| # For installation | ||||
| LDCONFIG:=ldconfig "$(LIBDIR)" | ||||
|  | ||||
| # Check OS | ||||
| UNAME:=$(shell uname -s) | ||||
| UNAME?=$(shell uname -s) | ||||
| ifeq ($(UNAME), Darwin) | ||||
| 	CLIBS:=$(CLIBS) -ldl | ||||
| 	SONAME_SETTER:=-Wl,-install_name, | ||||
| 	JANET_LIBRARY=build/libjanet.dylib | ||||
| 	LDCONFIG:=true | ||||
| else ifeq ($(UNAME), Linux) | ||||
| 	CLIBS:=$(CLIBS) -lrt -ldl | ||||
| endif | ||||
|  | ||||
| # For other unix likes, add flags here! | ||||
| ifeq ($(UNAME), Haiku) | ||||
| 	LDCONFIG:=true | ||||
| 	LDFLAGS=-Wl,--export-dynamic | ||||
| endif | ||||
| # For Android (termux) | ||||
| ifeq ($(UNAME), Linux) # uname on Darwin doesn't recognise -o | ||||
| ifeq ($(shell uname -o), Android) | ||||
| 	CLIBS:=$(CLIBS) -landroid-spawn | ||||
| endif | ||||
| endif | ||||
|  | ||||
| $(shell mkdir -p build/core build/c build/boot) | ||||
| all: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.h | ||||
| # Mingw | ||||
| MINGW_COMPILER= | ||||
| ifeq ($(findstring MINGW,$(UNAME)), MINGW) | ||||
| 	MINGW_COMPILER=gcc | ||||
| 	CLIBS:=-lws2_32 -lpsapi -lwsock32 | ||||
| 	LDFLAGS:=-Wl,--out-implib,$(JANET_IMPORT_LIB) | ||||
| 	LIBJANET_LDFLAGS:=-Wl,--out-implib,$(JANET_LIBRARY_IMPORT_LIB) | ||||
| 	JANET_TARGET:=$(JANET_TARGET).exe | ||||
| 	JANET_BOOT:=$(JANET_BOOT).exe | ||||
| 	COMPILER_VERSION:=$(shell $(CC) --version) | ||||
| 	ifeq ($(findstring clang,$(COMPILER_VERSION)), clang) | ||||
| 		MINGW_COMPILER=clang | ||||
| 	endif | ||||
| endif | ||||
|  | ||||
|  | ||||
| $(shell mkdir -p build/core build/c build/boot build/mainclient) | ||||
| all: $(JANET_TARGET) $(JANET_STATIC_LIBRARY) build/janet.h | ||||
| ifeq ($(HAS_SHARED), 1) | ||||
| all: $(JANET_LIBRARY) | ||||
| endif | ||||
|  | ||||
| ###################### | ||||
| ##### Name Files ##### | ||||
| ###################### | ||||
|  | ||||
| JANET_HEADERS=src/include/janet.h src/conf/janetconf.h | ||||
| JANET_HEADERS=src/include/janet.h $(JANETCONF_HEADER) | ||||
|  | ||||
| JANET_LOCAL_HEADERS=src/core/features.h \ | ||||
| 					src/core/util.h \ | ||||
| @@ -100,7 +145,9 @@ JANET_CORE_SOURCES=src/core/abstract.c \ | ||||
| 				   src/core/debug.c \ | ||||
| 				   src/core/emit.c \ | ||||
| 				   src/core/ev.c \ | ||||
| 				   src/core/ffi.c \ | ||||
| 				   src/core/fiber.c \ | ||||
| 				   src/core/filewatch.c \ | ||||
| 				   src/core/gc.c \ | ||||
| 				   src/core/inttypes.c \ | ||||
| 				   src/core/io.c \ | ||||
| @@ -146,42 +193,58 @@ $(JANET_BOOT_OBJECTS): $(JANET_BOOT_HEADERS) | ||||
| build/%.boot.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile | ||||
| 	$(CC) $(BOOT_CFLAGS) -o $@ -c $< | ||||
|  | ||||
| build/janet_boot: $(JANET_BOOT_OBJECTS) | ||||
| $(JANET_BOOT): $(JANET_BOOT_OBJECTS) | ||||
| 	$(CC) $(BOOT_CFLAGS) -o $@ $(JANET_BOOT_OBJECTS) $(CLIBS) | ||||
|  | ||||
| # Now the reason we bootstrap in the first place | ||||
| build/c/janet.c: build/janet_boot src/boot/boot.janet | ||||
| 	build/janet_boot . JANET_PATH '$(JANET_PATH)' > $@ | ||||
| build/c/janet.c: $(JANET_BOOT) src/boot/boot.janet | ||||
| 	$(RUN) $(JANET_BOOT) $(JANET_BOOT_FLAGS) > $@ | ||||
| 	cksum $@ | ||||
|  | ||||
| ################## | ||||
| ##### Quicky ##### | ||||
| ################## | ||||
|  | ||||
| build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile | ||||
| 	$(HOSTCC) $(BUILD_CFLAGS) -o $@ -c $< | ||||
|  | ||||
| ######################## | ||||
| ##### Amalgamation ##### | ||||
| ######################## | ||||
|  | ||||
| SONAME=libjanet.so.1.18 | ||||
| ifeq ($(UNAME), Darwin) | ||||
| SONAME=libjanet.1.38.dylib | ||||
| else | ||||
| SONAME=libjanet.so.1.38 | ||||
| endif | ||||
|  | ||||
| ifeq ($(MINGW_COMPILER), clang) | ||||
| 	SONAME= | ||||
| 	SONAME_SETTER= | ||||
| endif | ||||
|  | ||||
| build/c/shell.c: src/mainclient/shell.c | ||||
| 	cp $< $@ | ||||
|  | ||||
| build/janet.h: $(JANET_TARGET) src/include/janet.h src/conf/janetconf.h | ||||
| 	./$(JANET_TARGET) tools/patch-header.janet src/include/janet.h src/conf/janetconf.h $@ | ||||
| build/janet.h: $(JANET_TARGET) src/include/janet.h $(JANETCONF_HEADER) | ||||
| 	$(RUN) ./$(JANET_TARGET) tools/patch-header.janet src/include/janet.h $(JANETCONF_HEADER) $@ | ||||
|  | ||||
| build/janetconf.h: src/conf/janetconf.h | ||||
| build/janetconf.h: $(JANETCONF_HEADER) | ||||
| 	cp $< $@ | ||||
|  | ||||
| build/janet.o: build/c/janet.c src/conf/janetconf.h src/include/janet.h | ||||
| build/janet.o: build/c/janet.c $(JANETCONF_HEADER) src/include/janet.h | ||||
| 	$(HOSTCC) $(BUILD_CFLAGS) -c $< -o $@ | ||||
|  | ||||
| build/shell.o: build/c/shell.c src/conf/janetconf.h src/include/janet.h | ||||
| build/shell.o: build/c/shell.c $(JANETCONF_HEADER) src/include/janet.h | ||||
| 	$(HOSTCC) $(BUILD_CFLAGS) -c $< -o $@ | ||||
|  | ||||
| $(JANET_TARGET): build/janet.o build/shell.o | ||||
| $(JANET_TARGET): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTCC) $(LDFLAGS) $(BUILD_CFLAGS) -o $@ $^ $(CLIBS) | ||||
|  | ||||
| $(JANET_LIBRARY): build/janet.o build/shell.o | ||||
| 	$(HOSTCC) $(LDFLAGS) $(BUILD_CFLAGS) $(SONAME_SETTER)$(SONAME) -shared -o $@ $^ $(CLIBS) | ||||
| $(JANET_LIBRARY): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTCC) $(LIBJANET_LDFLAGS) $(BUILD_CFLAGS) $(SONAME_SETTER)$(SONAME) -shared -o $@ $^ $(CLIBS) | ||||
|  | ||||
| $(JANET_STATIC_LIBRARY): build/janet.o build/shell.o | ||||
| $(JANET_STATIC_LIBRARY): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTAR) rcs $@ $^ | ||||
|  | ||||
| ################### | ||||
| @@ -193,19 +256,19 @@ $(JANET_STATIC_LIBRARY): build/janet.o build/shell.o | ||||
| TEST_SCRIPTS=$(wildcard test/suite*.janet) | ||||
|  | ||||
| repl: $(JANET_TARGET) | ||||
| 	./$(JANET_TARGET) | ||||
| 	$(RUN) ./$(JANET_TARGET) | ||||
|  | ||||
| debug: $(JANET_TARGET) | ||||
| 	$(DEBUGGER) ./$(JANET_TARGET) | ||||
|  | ||||
| VALGRIND_COMMAND=valgrind --leak-check=full | ||||
| VALGRIND_COMMAND=valgrind --leak-check=full --quiet | ||||
|  | ||||
| valgrind: $(JANET_TARGET) | ||||
| 	$(VALGRIND_COMMAND) ./$(JANET_TARGET) | ||||
|  | ||||
| test: $(JANET_TARGET) $(TEST_PROGRAMS) | ||||
| 	for f in test/suite*.janet; do ./$(JANET_TARGET) "$$f" || exit; done | ||||
| 	for f in examples/*.janet; do ./$(JANET_TARGET) -k "$$f"; done | ||||
| 	for f in test/suite*.janet; do $(RUN) ./$(JANET_TARGET) "$$f" || exit; done | ||||
| 	for f in examples/*.janet; do $(RUN) ./$(JANET_TARGET) -k "$$f"; done | ||||
|  | ||||
| valtest: $(JANET_TARGET) $(TEST_PROGRAMS) | ||||
| 	for f in test/suite*.janet; do $(VALGRIND_COMMAND) ./$(JANET_TARGET) "$$f" || exit; done | ||||
| @@ -222,19 +285,25 @@ dist: build/janet-dist.tar.gz | ||||
|  | ||||
| build/janet-%.tar.gz: $(JANET_TARGET) \ | ||||
| 	build/janet.h \ | ||||
| 	janet.1 LICENSE CONTRIBUTING.md $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) \ | ||||
| 	janet.1 LICENSE CONTRIBUTING.md $(JANET_STATIC_LIBRARY) \ | ||||
| 	README.md build/c/janet.c build/c/shell.c | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/bin | ||||
| 	cp $(JANET_TARGET) build/$(JANET_DIST_DIR)/bin/ | ||||
| 	strip -x -S 'build/$(JANET_DIST_DIR)/bin/janet' | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/include | ||||
| 	cp build/janet.h build/$(JANET_DIST_DIR)/include/ | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/lib/ | ||||
| 	cp $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/$(JANET_DIST_DIR)/lib/ | ||||
| 	cp $(JANET_STATIC_LIBRARY) build/$(JANET_DIST_DIR)/lib/ | ||||
| 	cp $(JANET_LIBRARY) build/$(JANET_DIST_DIR)/lib/ || true | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/man/man1/ | ||||
| 	cp janet.1 build/$(JANET_DIST_DIR)/man/man1/janet.1 | ||||
| 	mkdir -p build/$(JANET_DIST_DIR)/src/ | ||||
| 	cp build/c/janet.c build/c/shell.c build/$(JANET_DIST_DIR)/src/ | ||||
| 	cp CONTRIBUTING.md LICENSE README.md build/$(JANET_DIST_DIR)/ | ||||
| 	cd build && tar -czvf ../$@ ./$(JANET_DIST_DIR) | ||||
| ifeq ($(HAS_SHARED), 1) | ||||
| build/janet-%.tar.gz: $(JANET_LIBRARY) | ||||
| endif | ||||
|  | ||||
| ######################### | ||||
| ##### Documentation ##### | ||||
| @@ -243,7 +312,7 @@ build/janet-%.tar.gz: $(JANET_TARGET) \ | ||||
| docs: build/doc.html | ||||
|  | ||||
| build/doc.html: $(JANET_TARGET) tools/gendoc.janet | ||||
| 	$(JANET_TARGET) tools/gendoc.janet > build/doc.html | ||||
| 	$(RUN) $(JANET_TARGET) tools/gendoc.janet > build/doc.html | ||||
|  | ||||
| ######################## | ||||
| ##### Installation ##### | ||||
| @@ -259,7 +328,7 @@ build/janet.pc: $(JANET_TARGET) | ||||
| 	echo "Name: janet" >> $@ | ||||
| 	echo "Url: https://janet-lang.org" >> $@ | ||||
| 	echo "Description: Library for the Janet programming language." >> $@ | ||||
| 	$(JANET_TARGET) -e '(print "Version: " janet/version)' >> $@ | ||||
| 	$(RUN) $(JANET_TARGET) -e '(print "Version: " janet/version)' >> $@ | ||||
| 	echo 'Cflags: -I$${includedir}' >> $@ | ||||
| 	echo 'Libs: -L$${libdir} -ljanet' >> $@ | ||||
| 	echo 'Libs.private: $(CLIBS)' >> $@ | ||||
| @@ -267,19 +336,29 @@ build/janet.pc: $(JANET_TARGET) | ||||
| install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc build/janet.h | ||||
| 	mkdir -p '$(DESTDIR)$(BINDIR)' | ||||
| 	cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet' | ||||
| 	strip -x -S '$(DESTDIR)$(BINDIR)/janet' | ||||
| 	mkdir -p '$(DESTDIR)$(INCLUDEDIR)/janet' | ||||
| 	cp -r build/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet' | ||||
| 	ln -sf ./janet/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet.h' | ||||
| 	mkdir -p '$(DESTDIR)$(JANET_PATH)' | ||||
| 	mkdir -p '$(DESTDIR)$(LIBDIR)' | ||||
| 	cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)')' | ||||
| 	if test $(UNAME) = Darwin ; then \ | ||||
| 		cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib' ; \ | ||||
| 		ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.dylib' ; \ | ||||
| 		ln -sf libjanet.$(shell $(JANET_TARGET) -e '(print janet/version)').dylib $(DESTDIR)$(LIBDIR)/$(SONAME) ; \ | ||||
| 	else \ | ||||
| 		cp $(JANET_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)')' ; \ | ||||
| 		ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.so' ; \ | ||||
| 		ln -sf libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)') $(DESTDIR)$(LIBDIR)/$(SONAME) ; \ | ||||
| 	fi | ||||
| 	cp $(JANET_STATIC_LIBRARY) '$(DESTDIR)$(LIBDIR)/libjanet.a' | ||||
| 	ln -sf $(SONAME) '$(DESTDIR)$(LIBDIR)/libjanet.so' | ||||
| 	ln -sf libjanet.so.$(shell $(JANET_TARGET) -e '(print janet/version)') $(DESTDIR)$(LIBDIR)/$(SONAME) | ||||
| 	mkdir -p '$(DESTDIR)$(JANET_MANPATH)' | ||||
| 	cp janet.1 '$(DESTDIR)$(JANET_MANPATH)' | ||||
| 	mkdir -p '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)' | ||||
| 	cp build/janet.pc '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc' | ||||
| 	[ -z '$(DESTDIR)' ] && $(LDCONFIG) || true | ||||
| 	cp '$(JANET_IMPORT_LIB)' '$(DESTDIR)$(LIBDIR)' || echo 'no import lib to install (mingw only)' | ||||
| 	cp '$(JANET_LIBRARY_IMPORT_LIB)' '$(DESTDIR)$(LIBDIR)' || echo 'no import lib to install (mingw only)' | ||||
| 	[ -z '$(DESTDIR)' ] && $(LDCONFIG) || echo "You can ignore this error for non-Linux systems or local installs" | ||||
|  | ||||
| install-jpm-git: $(JANET_TARGET) | ||||
| 	mkdir -p build | ||||
| @@ -291,11 +370,18 @@ install-jpm-git: $(JANET_TARGET) | ||||
| 		JANET_HEADERPATH='$(INCLUDEDIR)/janet' \ | ||||
| 		JANET_BINPATH='$(BINDIR)' \ | ||||
| 		JANET_LIBPATH='$(LIBDIR)' \ | ||||
| 		../../$(JANET_TARGET) ./bootstrap.janet | ||||
| 		$(RUN) ../../$(JANET_TARGET) ./bootstrap.janet | ||||
|  | ||||
| install-spork-git: $(JANET_TARGET) | ||||
| 	mkdir -p build | ||||
| 	rm -rf build/spork | ||||
| 	git clone --depth=1 --branch='$(SPORK_TAG)' https://github.com/janet-lang/spork.git build/spork | ||||
| 	$(JANET_TARGET) -e '(bundle/install "build/spork")' | ||||
|  | ||||
| uninstall: | ||||
| 	-rm '$(DESTDIR)$(BINDIR)/janet' | ||||
| 	-rm -rf '$(DESTDIR)$(INCLUDEDIR)/janet' | ||||
| 	-rm -rf '$(DESTDIR)$(INCLUDEDIR)/janet.h' | ||||
| 	-rm -rf '$(DESTDIR)$(LIBDIR)'/libjanet.* | ||||
| 	-rm '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc' | ||||
| 	-rm '$(DESTDIR)$(JANET_MANPATH)/janet.1' | ||||
| @@ -306,14 +392,14 @@ uninstall: | ||||
| ################# | ||||
|  | ||||
| format: | ||||
| 	tools/format.sh | ||||
| 	sh tools/format.sh | ||||
|  | ||||
| grammar: build/janet.tmLanguage | ||||
| build/janet.tmLanguage: tools/tm_lang_gen.janet $(JANET_TARGET) | ||||
| 	$(JANET_TARGET) $< > $@ | ||||
| 	$(RUN) $(JANET_TARGET) $< > $@ | ||||
|  | ||||
| compile-commands: | ||||
| 	# Requires pip install copmiledb | ||||
| 	# Requires pip install compiledb | ||||
| 	compiledb make | ||||
|  | ||||
| clean: | ||||
|   | ||||
							
								
								
									
										255
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										255
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,64 +1,133 @@ | ||||
| [](https://gitter.im/janet-language/community) | ||||
| [](https://janet.zulipchat.com) | ||||
|   | ||||
| [](https://ci.appveyor.com/project/bakpakin/janet/branch/master) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/freebsd.yml?) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/openbsd.yml?) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/master/freebsd.yml?) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/master/openbsd.yml?) | ||||
| [](https://github.com/janet-lang/janet/actions/workflows/test.yml) | ||||
|  | ||||
| <img src="https://raw.githubusercontent.com/janet-lang/janet/master/assets/janet-w200.png" alt="Janet logo" width=200 align="left"> | ||||
|  | ||||
| **Janet** is a functional and imperative programming language and bytecode interpreter. It is a | ||||
| lisp-like language, but lists are replaced | ||||
| by other data structures (arrays, tables (hash table), struct (immutable hash table), tuples). | ||||
| The language also supports bridging to native code written in C, meta-programming with macros, and bytecode assembly. | ||||
| **Janet** is a programming language for system scripting, expressive automation, and | ||||
| extending programs written in C or C++ with user scripting capabilities. | ||||
|  | ||||
| Janet makes a good system scripting language, or a language to embed in other programs. | ||||
| It's like Lua and GNU Guile in that regard. It has more built-in functionality and a richer core language than | ||||
| Lua, but smaller than GNU Guile or Python. However, it is much easier to embed and port than Python or Guile. | ||||
|  | ||||
| There is a REPL for trying out the language, as well as the ability | ||||
| to run script files. This client program is separate from the core runtime, so | ||||
| Janet can be embedded in other programs. Try Janet in your browser at | ||||
| [https://janet-lang.org](https://janet-lang.org). | ||||
|  | ||||
| If you'd like to financially support the ongoing development of Janet, consider | ||||
| [sponsoring its primary author](https://github.com/sponsors/bakpakin) through GitHub. | ||||
| <https://janet-lang.org>. | ||||
|  | ||||
| <br> | ||||
|  | ||||
| ## Use Cases | ||||
| ## Examples | ||||
|  | ||||
| Janet makes a good system scripting language, or a language to embed in other programs. | ||||
| It's like Lua and Guile in that regard. It has more built-in functionality and a richer core language than | ||||
| Lua, but smaller than GNU Guile or Python. | ||||
| See the examples directory for all provided example programs. | ||||
|  | ||||
| ## Features | ||||
| ### Game of Life | ||||
|  | ||||
| * Configurable at build time - turn features on or off for a smaller or more featureful build | ||||
| * Minimal setup - one binary and you are good to go! | ||||
| ```janet | ||||
| # John Conway's Game of Life | ||||
|  | ||||
| (def- window | ||||
|   (seq [x :range [-1 2] | ||||
|          y :range [-1 2] | ||||
|          :when (not (and (zero? x) (zero? y)))] | ||||
|        [x y])) | ||||
|  | ||||
| (defn- neighbors | ||||
|   [[x y]] | ||||
|   (map (fn [[x1 y1]] [(+ x x1) (+ y y1)]) window)) | ||||
|  | ||||
| (defn tick | ||||
|   "Get the next state in the Game Of Life." | ||||
|   [state] | ||||
|   (def cell-set (frequencies state)) | ||||
|   (def neighbor-set (frequencies (mapcat neighbors state))) | ||||
|   (seq [coord :keys neighbor-set | ||||
|          :let [count (get neighbor-set coord)] | ||||
|          :when (or (= count 3) (and (get cell-set coord) (= count 2)))] | ||||
|       coord)) | ||||
|  | ||||
| (defn draw | ||||
|   "Draw cells in the game of life from (x1, y1) to (x2, y2)" | ||||
|   [state x1 y1 x2 y2] | ||||
|   (def cellset @{}) | ||||
|   (each cell state (put cellset cell true)) | ||||
|   (loop [x :range [x1 (+ 1 x2)] | ||||
|          :after (print) | ||||
|          y :range [y1 (+ 1 y2)]] | ||||
|     (file/write stdout (if (get cellset [x y]) "X " ". "))) | ||||
|   (print)) | ||||
|  | ||||
| # Print the first 20 generations of a glider | ||||
| (var *state* '[(0 0) (-1 0) (1 0) (1 1) (0 2)]) | ||||
| (for i 0 20 | ||||
|   (print "generation " i) | ||||
|   (draw *state* -7 -7 7 7) | ||||
|   (set *state* (tick *state*))) | ||||
| ``` | ||||
|  | ||||
| ### TCP Echo Server | ||||
|  | ||||
| ```janet | ||||
| # A simple TCP echo server using the built-in socket networking and event loop. | ||||
|  | ||||
| (defn handler | ||||
|   "Simple handler for connections." | ||||
|   [stream] | ||||
|   (defer (:close stream) | ||||
|     (def id (gensym)) | ||||
|     (def b @"") | ||||
|     (print "Connection " id "!") | ||||
|     (while (:read stream 1024 b) | ||||
|       (printf " %v -> %v" id b) | ||||
|       (:write stream b) | ||||
|       (buffer/clear b)) | ||||
|     (printf "Done %v!" id) | ||||
|     (ev/sleep 0.5))) | ||||
|  | ||||
| (net/server "127.0.0.1" "8000" handler) | ||||
| ``` | ||||
|  | ||||
| ### Windows FFI Hello, World! | ||||
|  | ||||
| ```janet | ||||
| # Use the FFI to popup a Windows message box - no C required | ||||
|  | ||||
| (ffi/context "user32.dll") | ||||
|  | ||||
| (ffi/defbind MessageBoxA :int | ||||
|   [w :ptr text :string cap :string typ :int]) | ||||
|  | ||||
| (MessageBoxA nil "Hello, World!" "Test" 0) | ||||
| ``` | ||||
|  | ||||
| ## Language Features | ||||
|  | ||||
| * 600+ functions and macros in the core library | ||||
| * Built-in socket networking, threading, subprocesses, and file system functions. | ||||
| * Parsing Expression Grammars (PEG) engine as a more robust Regex alternative | ||||
| * Macros and compile-time computation | ||||
| * Per-thread event loop for efficient IO (epoll/IOCP/kqueue) | ||||
| * First-class green threads (continuations) as well as OS threads | ||||
| * Erlang-style supervision trees that integrate with the event loop | ||||
| * First-class closures | ||||
| * Garbage collection | ||||
| * First-class green threads (continuations) | ||||
| * Distributed as janet.c and janet.h for embedding into a larger program. | ||||
| * Python-style generators (implemented as a plain macro) | ||||
| * Mutable and immutable arrays (array/tuple) | ||||
| * Mutable and immutable hashtables (table/struct) | ||||
| * Mutable and immutable strings (buffer/string) | ||||
| * Macros | ||||
| * Multithreading | ||||
| * Per-thread event loop for efficient evented IO | ||||
| * Byte code interpreter with an assembly interface, as well as bytecode verification | ||||
| * Tail call Optimization | ||||
| * Direct interop with C via abstract types and C functions | ||||
| * Dynamically load C libraries | ||||
| * Functional and imperative standard library | ||||
| * Lexical scoping | ||||
| * Imperative programming as well as functional | ||||
| * REPL | ||||
| * Parsing Expression Grammars built into the core library | ||||
| * 400+ functions and macros in the core library | ||||
| * Embedding Janet in other programs | ||||
| * Interactive environment with detailed stack traces | ||||
| * Tail recursion | ||||
| * Interface with C functions and dynamically load plugins ("natives"). | ||||
| * Built-in C FFI for when the native bindings are too much work | ||||
| * REPL development with debugger and inspectable runtime | ||||
|  | ||||
| ## Documentation | ||||
|  | ||||
| * For a quick tutorial, see [the introduction](https://janet-lang.org/docs/index.html) for more details. | ||||
| * For the full API for all functions in the core library, see [the core API doc](https://janet-lang.org/api/index.html) | ||||
| * For the full API for all functions in the core library, see [the core API doc](https://janet-lang.org/api/index.html). | ||||
|  | ||||
| Documentation is also available locally in the REPL. | ||||
| Use the `(doc symbol-name)` macro to get API | ||||
| @@ -66,7 +135,7 @@ documentation for symbols in the core library. For example, | ||||
| ``` | ||||
| (doc apply) | ||||
| ``` | ||||
| Shows documentation for the `apply` function. | ||||
| shows documentation for the `apply` function. | ||||
|  | ||||
| To get a list of all bindings in the default | ||||
| environment, use the `(all-bindings)` function. You | ||||
| @@ -85,11 +154,13 @@ the SourceHut mirror is actively maintained. | ||||
|  | ||||
| The Makefile is non-portable and requires GNU-flavored make. | ||||
|  | ||||
| ``` | ||||
| ```sh | ||||
| cd somewhere/my/projects/janet | ||||
| make | ||||
| make test | ||||
| make repl | ||||
| make install | ||||
| make install-jpm-git | ||||
| ``` | ||||
|  | ||||
| Find out more about the available make targets by running `make help`. | ||||
| @@ -99,42 +170,45 @@ Find out more about the available make targets by running `make help`. | ||||
| 32-bit Haiku build instructions are the same as the UNIX-like build instructions, | ||||
| but you need to specify an alternative compiler, such as `gcc-x86`. | ||||
|  | ||||
| ``` | ||||
| ```sh | ||||
| cd somewhere/my/projects/janet | ||||
| make CC=gcc-x86 | ||||
| make test | ||||
| make repl | ||||
| make install | ||||
| make install-jpm-git | ||||
| ``` | ||||
|  | ||||
| ### FreeBSD | ||||
|  | ||||
| FreeBSD build instructions are the same as the UNIX-like build instructions, | ||||
| but you need `gmake` to compile. Alternatively, install directly from | ||||
| packages, using `pkg install lang/janet`. | ||||
| but you need `gmake` to compile. Alternatively, install the package directly with `pkg install lang/janet`. | ||||
|  | ||||
| ``` | ||||
| ```sh | ||||
| cd somewhere/my/projects/janet | ||||
| gmake | ||||
| gmake test | ||||
| gmake repl | ||||
| gmake install | ||||
| gmake install-jpm-git | ||||
| ``` | ||||
|  | ||||
| ### NetBSD | ||||
|  | ||||
| NetBSD build instructions are the same as the FreeBSD build instructions. | ||||
| Alternatively, install directly from packages, using `pkgin install janet`. | ||||
| Alternatively, install the package directly with `pkgin install janet`. | ||||
|  | ||||
| ### Windows | ||||
|  | ||||
| 1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15#) or [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=15#) | ||||
| 2. Run a Visual Studio Command Prompt (cl.exe and link.exe need to be on the PATH) and cd to the directory with janet. | ||||
| 3. Run `build_win` to compile janet. | ||||
| 1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15#) or [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=15#). | ||||
| 2. Run a Visual Studio Command Prompt (`cl.exe` and `link.exe` need to be on your PATH) and `cd` to the directory with Janet. | ||||
| 3. Run `build_win` to compile Janet. | ||||
| 4. Run `build_win test` to make sure everything is working. | ||||
|  | ||||
| To build an `.msi` installer executable, in addition to the above steps, you will have to: | ||||
|  | ||||
| 5. Install, or otherwise add to your PATH the [WiX 3.11 Toolset](https://github.com/wixtoolset/wix3/releases) | ||||
| 6. run `build_win dist` | ||||
| 5. Install, or otherwise add to your PATH the [WiX 3.14 Toolset](https://github.com/wixtoolset/wix3/releases). | ||||
| 6. Run `build_win dist`. | ||||
|  | ||||
| Now you should have an `.msi`. You can run `build_win install` to install the `.msi`, or execute the file itself. | ||||
|  | ||||
| @@ -170,19 +244,21 @@ ninja -C build install | ||||
|  | ||||
| Janet can be hacked on with pretty much any environment you like, but for IDE | ||||
| lovers, [Gnome Builder](https://wiki.gnome.org/Apps/Builder) is probably the | ||||
| best option, as it has excellent meson integration. It also offers code completion | ||||
| best option, as it has excellent Meson integration. It also offers code completion | ||||
| for Janet's C API right out of the box, which is very useful for exploring. VSCode, Vim, | ||||
| Emacs, and Atom will have syntax packages for the Janet language, though. | ||||
| Emacs, and Atom each have syntax packages for the Janet language, though. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| See the [Introduction](https://janet-lang.org/docs/index.html) for more details. If you just want | ||||
| to try out the language, you don't need to install anything. You can also move the `janet` executable wherever you want on your system and run it. | ||||
| If you just want to try out the language, you don't need to install anything. | ||||
| In this case you can also move the `janet` executable wherever you want on | ||||
| your system and run it.  However, for a fuller setup, please see the | ||||
| [Introduction](https://janet-lang.org/docs/index.html) for more details. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| A REPL is launched when the binary is invoked with no arguments. Pass the -h flag | ||||
| to display the usage information. Individual scripts can be run with `./janet myscript.janet` | ||||
| A REPL is launched when the binary is invoked with no arguments. Pass the `-h` flag | ||||
| to display the usage information. Individual scripts can be run with `./janet myscript.janet`. | ||||
|  | ||||
| If you are looking to explore, you can print a list of all available macros, functions, and constants | ||||
| by entering the command `(all-bindings)` into the REPL. | ||||
| @@ -197,20 +273,26 @@ Hello, World! | ||||
| nil | ||||
| janet:3:> (os/exit) | ||||
| $ janet -h | ||||
| usage: build/janet [options] script args... | ||||
| usage: janet [options] script args... | ||||
| Options are: | ||||
|   -h : Show this help | ||||
|   -v : Print the version string | ||||
|   -s : Use raw stdin instead of getline like functionality | ||||
|   -e code : Execute a string of janet | ||||
|   -E code arguments... : Evaluate an expression as a short-fn with arguments | ||||
|   -d : Set the debug flag in the REPL | ||||
|   -r : Enter the REPL after running all scripts | ||||
|   -R : Disables loading profile.janet when JANET_PROFILE is present | ||||
|   -p : Keep on executing if there is a top-level error (persistent) | ||||
|   -q : Hide prompt, logo, and REPL output (quiet) | ||||
|   -q : Hide logo (quiet) | ||||
|   -k : Compile scripts but do not execute (flycheck) | ||||
|   -m syspath : Set system path for loading global modules | ||||
|   -c source output : Compile janet source code into an image | ||||
|   -i : Load the script argument as an image file instead of source code | ||||
|   -n : Disable ANSI color output in the REPL | ||||
|   -l path : Execute code in a file before running the main script | ||||
|   -l lib : Use a module before processing more arguments | ||||
|   -w level : Set the lint warning level - default is "normal" | ||||
|   -x level : Set the lint error level - default is "none" | ||||
|   -- : Stop handling options | ||||
| ``` | ||||
|  | ||||
| @@ -221,8 +303,8 @@ If installed, you can also run `man janet` to get usage information. | ||||
| Janet can be embedded in a host program very easily. The normal build | ||||
| will create a file `build/janet.c`, which is a single C file | ||||
| that contains all the source to Janet. This file, along with | ||||
| `src/include/janet.h` and `src/conf/janetconf.h` can be dragged into any C | ||||
| project and compiled into the project. Janet should be compiled with `-std=c99` | ||||
| `src/include/janet.h` and `src/conf/janetconf.h`, can be dragged into any C | ||||
| project and compiled into it. Janet should be compiled with `-std=c99` | ||||
| on most compilers, and will need to be linked to the math library, `-lm`, and | ||||
| the dynamic linker, `-ldl`, if one wants to be able to load dynamic modules. If | ||||
| there is no need for dynamic modules, add the define | ||||
| @@ -230,26 +312,35 @@ there is no need for dynamic modules, add the define | ||||
|  | ||||
| See the [Embedding Section](https://janet-lang.org/capi/embedding.html) on the website for more information. | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| See the examples directory for some example janet code. | ||||
|  | ||||
| ## Discussion | ||||
|  | ||||
| Feel free to ask questions and join the discussion on the [Janet Gitter Channel](https://gitter.im/janet-language/community). | ||||
| Gitter provides Matrix and irc bridges as well. | ||||
| Feel free to ask questions and join the discussion on the [Janet Zulip Instance](https://janet.zulipchat.com/) | ||||
|  | ||||
| ## FAQ | ||||
|  | ||||
| ### How fast is it? | ||||
|  | ||||
| It is about the same speed as most interpreted languages without a JIT compiler. Tight, critical | ||||
| loops should probably be written in C or C++ . Programs tend to be a bit faster than | ||||
| they would be in a language like Python due to the discouragement of slow Object-Oriented abstraction | ||||
| with lots of hash-table lookups, and making late-binding explicit. All values are boxed in an 8-byte | ||||
| representation by default and allocated on the heap, with the exception of numbers, nils and booleans. The | ||||
| PEG engine is a specialized interpreter that can efficiently process string and buffer data. | ||||
|  | ||||
| The GC is simple and stop-the-world, but GC knobs are exposed in the core library and separate threads | ||||
| have isolated heaps and garbage collectors. Data that is shared between threads is reference counted. | ||||
|  | ||||
| YMMV. | ||||
|  | ||||
| ### Where is (favorite feature from other language)? | ||||
|  | ||||
| It may exist, it may not. If you want to propose major language features, go ahead and open an issue, but | ||||
| they will likely by closed as "will not implement". Often, such features make one usecase simpler at the expense | ||||
| It may exist, it may not. If you want to propose a major language feature, go ahead and open an issue, but | ||||
| it will likely be closed as "will not implement". Often, such features make one usecase simpler at the expense | ||||
| of 5 others by making the language more complicated. | ||||
|  | ||||
| ### Is there a language spec? | ||||
|  | ||||
| There is not currently a spec besides the documentation at https://janet-lang.org. | ||||
| There is not currently a spec besides the documentation at <https://janet-lang.org>. | ||||
|  | ||||
| ### Is this Scheme/Common Lisp? Where are the cons cells? | ||||
|  | ||||
| @@ -257,21 +348,21 @@ Nope. There are no cons cells here. | ||||
|  | ||||
| ### Is this a Clojure port? | ||||
|  | ||||
| No. It's similar to Clojure superficially because I like Lisps and I like the asthetics. | ||||
| Internally, Janet is not at all like Clojure. | ||||
| No. It's similar to Clojure superficially because I like Lisps and I like the aesthetics. | ||||
| Internally, Janet is not at all like Clojure, Scheme, or Common Lisp. | ||||
|  | ||||
| ### Are the immutable data structures (tuples and structs) implemented as hash tries? | ||||
|  | ||||
| No. They are immutable arrays and hash tables. Don't try and use them like Clojure's vectors | ||||
| and maps, instead they work well as table keys or other identifiers. | ||||
|  | ||||
| ### Can I do Object Oriented programming with Janet? | ||||
| ### Can I do object-oriented programming with Janet? | ||||
|  | ||||
| To some extent, yes. However, it is not the recommended method of abstraction, and performance may suffer. | ||||
| That said, tables can be used to make mutable objects with inheritance and polymorphism, where object | ||||
| methods are implemeted with keywords. | ||||
| methods are implemented with keywords. | ||||
|  | ||||
| ``` | ||||
| ```clj | ||||
| (def Car @{:honk (fn [self msg] (print "car " self " goes " msg)) }) | ||||
| (def my-car (table/setproto @{} Car)) | ||||
| (:honk my-car "Beep!") | ||||
| @@ -282,17 +373,25 @@ methods are implemeted with keywords. | ||||
| Usually, one of a few reasons: | ||||
| - Often, it already exists in a different form and the Clojure port would be redundant. | ||||
| - Clojure programs often generate a lot of garbage and rely on the JVM to clean it up. | ||||
|   Janet does not run on the JVM, and has a more primitive garbage collector. | ||||
| - We want to keep the Janet core small. With Lisps, usually a feature can be added as a library | ||||
|   without feeling "bolted on", especially when compared to ALGOL like languages. Adding features | ||||
|   to the core also makes it a bit more difficult keep Janet maximally portable. | ||||
|   Janet does not run on the JVM and has a more primitive garbage collector. | ||||
| - We want to keep the Janet core small. With Lisps, a feature can usually be added as a library | ||||
|   without feeling "bolted on", especially when compared to ALGOL-like languages. Adding features | ||||
|   to the core also makes it a bit more difficult to keep Janet maximally portable. | ||||
|  | ||||
| ### Can I bind to Rust/Zig/Go/Java/Nim/C++/D/Pascal/Fortran/Odin/Jai/(Some new "Systems" Programming Language)? | ||||
|  | ||||
| Probably, if that language has a good interface with C. But the programmer may need to do | ||||
| some extra work to map Janet's internal memory model to that of the bound language. Janet | ||||
| also uses `setjmp`/`longjmp` for non-local returns internally. This | ||||
| approach is out of favor with many programmers now and doesn't always play well with other languages | ||||
| that have exceptions or stack-unwinding. | ||||
|  | ||||
| ### Why is my terminal spitting out junk when I run the REPL? | ||||
|  | ||||
| Make sure your terminal supports ANSI escape codes. Most modern terminals will | ||||
| support these, but some older terminals, Windows consoles, or embedded terminals | ||||
| will not. If your terminal does not support ANSI escape codes, run the REPL with | ||||
| the `-n` flag, which disables color output. You can also try the `-s` if further issues | ||||
| the `-n` flag, which disables color output. You can also try the `-s` flag if further issues | ||||
| ensue. | ||||
|  | ||||
| ## Why is it called "Janet"? | ||||
|   | ||||
							
								
								
									
										51
									
								
								appveyor.yml
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								appveyor.yml
									
									
									
									
									
								
							| @@ -1,51 +0,0 @@ | ||||
| version: build-{build} | ||||
| clone_folder: c:\projects\janet | ||||
| image: | ||||
| - Visual Studio 2019 | ||||
| configuration: | ||||
| - Release | ||||
| platform: | ||||
| - x64 | ||||
| - x86 | ||||
| environment: | ||||
|   matrix: | ||||
|   - arch: Win64 | ||||
| matrix: | ||||
|   fast_finish: true | ||||
|  | ||||
| # skip unsupported combinations | ||||
| init: | ||||
|     - call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" %platform% | ||||
|  | ||||
| install: | ||||
|     - set JANET_BUILD=%appveyor_repo_commit:~0,7% | ||||
|     - build_win all | ||||
|     - set janet_outname=%appveyor_repo_tag_name% | ||||
|     - if "%janet_outname%"=="" set /P janet_outname=<build\version.txt | ||||
| build: off | ||||
|  | ||||
| artifacts: | ||||
|     - name: janet.c | ||||
|       path: dist\janet.c | ||||
|       type: File | ||||
|     - name: janet.h | ||||
|       path: dist\janet.h | ||||
|       type: File | ||||
|     - name: shell.c | ||||
|       path: dist\shell.c | ||||
|       type: File | ||||
|     - name: "janet-$(janet_outname)-windows-%platform%" | ||||
|       path: dist | ||||
|       type: Zip | ||||
|     - path: "janet-$(janet_outname)-windows-%platform%-installer.msi" | ||||
|       type: File | ||||
|  | ||||
| deploy: | ||||
|   description: 'The Janet Programming Language.' | ||||
|   provider: GitHub | ||||
|   auth_token: | ||||
|     secure: lwEXy09qhj2jSH9s1C/KvCkAUqJSma8phFR+0kbsfUc3rVxpNK5uD3z9Md0SjYRx | ||||
|   artifact: /(janet|shell).*/ | ||||
|   draft: true | ||||
|   on: | ||||
|       APPVEYOR_REPO_TAG: true | ||||
| @@ -41,32 +41,34 @@ if not exist build\boot mkdir build\boot | ||||
| @rem Build the bootstrap interpreter | ||||
| for %%f in (src\core\*.c) do ( | ||||
|     %JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f | ||||
|     @if errorlevel 1 goto :BUILDFAIL | ||||
|     @if not errorlevel 0 goto :BUILDFAIL | ||||
| ) | ||||
| for %%f in (src\boot\*.c) do ( | ||||
|     %JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f | ||||
|     @if errorlevel 1 goto :BUILDFAIL | ||||
|     @if not errorlevel 0 goto :BUILDFAIL | ||||
| ) | ||||
| %JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj | ||||
| @if errorlevel 1 goto :BUILDFAIL | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
| build\janet_boot . > build\c\janet.c | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
|  | ||||
| @rem Build the sources | ||||
| %JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c | ||||
| @if errorlevel 1 goto :BUILDFAIL | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
| %JANET_COMPILE% /Fobuild\shell.obj src\mainclient\shell.c | ||||
| @if errorlevel 1 goto :BUILDFAIL | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
|  | ||||
| @rem Build the resources | ||||
| rc /nologo /fobuild\janet_win.res janet_win.rc | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
|  | ||||
| @rem Link everything to main client | ||||
| %JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res | ||||
| @if errorlevel 1 goto :BUILDFAIL | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
|  | ||||
| @rem Build static library (libjanet.a) | ||||
| @rem Build static library (libjanet.lib) | ||||
| %JANET_LINK_STATIC% /out:build\libjanet.lib build\janet.obj | ||||
| @if errorlevel 1 goto :BUILDFAIL | ||||
| @if not errorlevel 0 goto :BUILDFAIL | ||||
|  | ||||
| echo === Successfully built janet.exe for Windows === | ||||
| echo === Run 'build_win test' to run tests. == | ||||
| @@ -89,16 +91,18 @@ exit /b 0 | ||||
|  | ||||
| @rem Clean build artifacts | ||||
| :CLEAN | ||||
| del *.exe *.lib *.exp | ||||
| del *.exe *.lib *.exp *.msi *.wixpdb | ||||
| rd /s /q build | ||||
| rd /s /q dist | ||||
| if exist dist ( | ||||
|     rd /s /q dist | ||||
| ) | ||||
| exit /b 0 | ||||
|  | ||||
| @rem Run tests | ||||
| :TEST | ||||
| for %%f in (test/suite*.janet) do ( | ||||
|     janet.exe test\%%f | ||||
|     @if errorlevel 1 goto TESTFAIL | ||||
|     @if not errorlevel 0 goto TESTFAIL | ||||
| ) | ||||
| exit /b 0 | ||||
|  | ||||
| @@ -134,11 +138,18 @@ if defined APPVEYOR_REPO_TAG_NAME ( | ||||
|     set RELEASE_VERSION=%JANET_VERSION% | ||||
| ) | ||||
| if defined CI ( | ||||
|     set WIXBIN="c:\Program Files (x86)\WiX Toolset v3.11\bin\" | ||||
|     set WIXBIN="%WIX%bin\" | ||||
|     echo WIXBIN = %WIXBIN% | ||||
| ) else ( | ||||
|     set WIXBIN= | ||||
| ) | ||||
| %WIXBIN%candle.exe tools\msi\janet.wxs -arch %BUILDARCH% -out build\ | ||||
|  | ||||
| set WIXARCH=%BUILDARCH% | ||||
| if "%WIXARCH%"=="aarch64" ( | ||||
|     set WIXARCH=arm64 | ||||
| ) | ||||
|  | ||||
| %WIXBIN%candle.exe tools\msi\janet.wxs -arch %WIXARCH% -out build\ | ||||
| %WIXBIN%light.exe "-sice:ICE38" -b tools\msi -ext WixUIExtension build\janet.wixobj -out janet-%RELEASE_VERSION%-windows-%BUILDARCH%-installer.msi | ||||
| exit /b 0 | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| (defn dowork [name n] | ||||
|   (print name " starting work...") | ||||
|   (os/execute [(dyn :executable) "-e" (string "(os/sleep " n ")")]) | ||||
|   (os/execute [(dyn :executable) "-e" (string "(os/sleep " n ")")] :p) | ||||
|   (print name " finished work!")) | ||||
|  | ||||
| # Will be done in parallel | ||||
|   | ||||
							
								
								
									
										35
									
								
								examples/chatserver.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								examples/chatserver.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| (def conmap @{}) | ||||
|  | ||||
| (defn broadcast [em msg] | ||||
|   (eachk par conmap | ||||
|          (if (not= par em) | ||||
|            (if-let [tar (get conmap par)] | ||||
|              (net/write tar (string/format "[%s]:%s" em msg)))))) | ||||
|  | ||||
| (defn handler | ||||
|   [connection] | ||||
|   (print "connection: " connection) | ||||
|   (net/write connection "Whats your name?\n") | ||||
|   (def name (string/trim (string (ev/read connection 100)))) | ||||
|   (print name " connected") | ||||
|   (if (get conmap name) | ||||
|     (do | ||||
|       (net/write connection "Name already taken!") | ||||
|       (:close connection)) | ||||
|     (do | ||||
|       (put conmap name connection) | ||||
|       (net/write connection (string/format "Welcome %s\n" name)) | ||||
|       (defer (do | ||||
|                (put conmap name nil) | ||||
|                (:close connection)) | ||||
|         (while (def msg (ev/read connection 100)) | ||||
|           (broadcast name (string msg))) | ||||
|         (print name " disconnected"))))) | ||||
|  | ||||
| (defn main [& args] | ||||
|   (printf "STARTING SERVER...") | ||||
|   (flush) | ||||
|   (def my-server (net/listen "127.0.0.1" "8000")) | ||||
|   (forever | ||||
|    (def connection (net/accept my-server)) | ||||
|    (ev/call handler connection))) | ||||
							
								
								
									
										45
									
								
								examples/evlocks.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								examples/evlocks.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| (defn sleep | ||||
|   "Sleep the entire thread, not just a single fiber." | ||||
|   [n] | ||||
|   (os/sleep (* 0.1 n))) | ||||
|  | ||||
| (defn work [lock n] | ||||
|   (ev/acquire-lock lock) | ||||
|   (print "working " n "...") | ||||
|   (sleep n) | ||||
|   (print "done working...") | ||||
|   (ev/release-lock lock)) | ||||
|  | ||||
| (defn reader | ||||
|   [rwlock n] | ||||
|   (ev/acquire-rlock rwlock) | ||||
|   (print "reading " n "...") | ||||
|   (sleep n) | ||||
|   (print "done reading " n "...") | ||||
|   (ev/release-rlock rwlock)) | ||||
|  | ||||
| (defn writer | ||||
|   [rwlock n] | ||||
|   (ev/acquire-wlock rwlock) | ||||
|   (print "writing " n "...") | ||||
|   (sleep n) | ||||
|   (print "done writing...") | ||||
|   (ev/release-wlock rwlock)) | ||||
|  | ||||
| (defn test-lock | ||||
|   [] | ||||
|   (def lock (ev/lock)) | ||||
|   (for i 3 7 | ||||
|     (ev/spawn-thread | ||||
|       (work lock i)))) | ||||
|  | ||||
| (defn test-rwlock | ||||
|   [] | ||||
|   (def rwlock (ev/rwlock)) | ||||
|   (for i 0 20 | ||||
|     (if (> 0.1 (math/random)) | ||||
|       (ev/spawn-thread (writer rwlock i)) | ||||
|       (ev/spawn-thread (reader rwlock i))))) | ||||
|  | ||||
| (test-rwlock) | ||||
| (test-lock) | ||||
							
								
								
									
										71
									
								
								examples/ffi/gtk.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								examples/ffi/gtk.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| # :lazy true needed for jpm quickbin | ||||
| # lazily loads library on first function use | ||||
| # so the `main` function | ||||
| # can be marshalled. | ||||
| (ffi/context "/usr/lib/libgtk-3.so" :lazy true) | ||||
|  | ||||
| (ffi/defbind | ||||
|   gtk-application-new :ptr | ||||
|   "Add docstrings as needed." | ||||
|   [title :string flags :uint]) | ||||
|  | ||||
| (ffi/defbind | ||||
|   g-signal-connect-data :ulong | ||||
|   [a :ptr b :ptr c :ptr d :ptr e :ptr f :int]) | ||||
|  | ||||
| (ffi/defbind | ||||
|   g-application-run :int | ||||
|   [app :ptr argc :int argv :ptr]) | ||||
|  | ||||
| (ffi/defbind | ||||
|   gtk-application-window-new :ptr | ||||
|   [a :ptr]) | ||||
|  | ||||
| (ffi/defbind | ||||
|   gtk-button-new-with-label :ptr | ||||
|   [a :ptr]) | ||||
|  | ||||
| (ffi/defbind | ||||
|   gtk-container-add :void | ||||
|   [a :ptr b :ptr]) | ||||
|  | ||||
| (ffi/defbind | ||||
|   gtk-widget-show-all :void | ||||
|   [a :ptr]) | ||||
|  | ||||
| (ffi/defbind | ||||
|   gtk-button-set-label :void | ||||
|   [a :ptr b :ptr]) | ||||
|  | ||||
| (def cb (delay (ffi/trampoline :default))) | ||||
|  | ||||
| (defn ffi/array | ||||
|   ``Convert a janet array to a buffer that can be passed to FFI functions. | ||||
|   For example, to create an array of type `char *` (array of c strings), one | ||||
|   could use `(ffi/array ["hello" "world"] :ptr)`. One needs to be careful that | ||||
|   array elements are not garbage collected though - the GC can't follow references | ||||
|   inside an arbitrary byte buffer.`` | ||||
|   [arr ctype &opt buf] | ||||
|   (default buf @"") | ||||
|   (each el arr | ||||
|     (ffi/write ctype el buf)) | ||||
|   buf) | ||||
|  | ||||
| (defn on-active | ||||
|   [app] | ||||
|   (def window (gtk-application-window-new app)) | ||||
|   (def btn (gtk-button-new-with-label "Click Me!")) | ||||
|   (g-signal-connect-data btn "clicked" (cb) | ||||
|                          (fn [btn] (gtk-button-set-label btn "Hello World")) | ||||
|                          nil 1) | ||||
|   (gtk-container-add window btn) | ||||
|   (gtk-widget-show-all window)) | ||||
|  | ||||
| (defn main | ||||
|   [&] | ||||
|   (def app (gtk-application-new "org.janet-lang.example.HelloApp" 0)) | ||||
|   (g-signal-connect-data app "activate" (cb) on-active nil 1) | ||||
|   # manually build an array with ffi/write | ||||
|   # - we are responsible for preventing gc when the arg array is used | ||||
|   (def argv (ffi/array (dyn *args*) :string)) | ||||
|   (g-application-run app (length (dyn *args*)) argv)) | ||||
							
								
								
									
										227
									
								
								examples/ffi/so.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								examples/ffi/so.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdint.h> | ||||
| #include <string.h> | ||||
|  | ||||
| #ifdef _WIN32 | ||||
| #define EXPORTER __declspec(dllexport) | ||||
| #else | ||||
| #define EXPORTER | ||||
| #endif | ||||
|  | ||||
| /* Structs */ | ||||
|  | ||||
| typedef struct { | ||||
|     int a, b; | ||||
|     float c, d; | ||||
| } Split; | ||||
|  | ||||
| typedef struct { | ||||
|     float c, d; | ||||
|     int a, b; | ||||
| } SplitFlip; | ||||
|  | ||||
| typedef struct { | ||||
|     int u, v, w, x, y, z; | ||||
| } SixInts; | ||||
|  | ||||
| typedef struct { | ||||
|     int a; | ||||
|     int b; | ||||
| } intint; | ||||
|  | ||||
| typedef struct { | ||||
|     int a; | ||||
|     int b; | ||||
|     int c; | ||||
| } intintint; | ||||
|  | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
| } uint64pair; | ||||
|  | ||||
| typedef struct { | ||||
|     int64_t a; | ||||
|     int64_t b; | ||||
|     int64_t c; | ||||
| } big; | ||||
|  | ||||
| /* Functions */ | ||||
|  | ||||
| EXPORTER | ||||
| int int_fn(int a, int b) { | ||||
|     return (a << 2) + b; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double my_fn(int64_t a, int64_t b, const char *x) { | ||||
|     return (double)(a + b) + 0.5 + strlen(x); | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double double_fn(double x, double y, double z) { | ||||
|     return (x + y) * z * 3; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double double_many(double x, double y, double z, double w, double a, double b) { | ||||
|     return x + y + z + w + a + b; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double double_lots( | ||||
|     double a, | ||||
|     double b, | ||||
|     double c, | ||||
|     double d, | ||||
|     double e, | ||||
|     double f, | ||||
|     double g, | ||||
|     double h, | ||||
|     double i, | ||||
|     double j) { | ||||
|     return i + j; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double double_lots_2( | ||||
|     double a, | ||||
|     double b, | ||||
|     double c, | ||||
|     double d, | ||||
|     double e, | ||||
|     double f, | ||||
|     double g, | ||||
|     double h, | ||||
|     double i, | ||||
|     double j) { | ||||
|     return a + | ||||
|            10.0 * b + | ||||
|            100.0 * c + | ||||
|            1000.0 * d + | ||||
|            10000.0 * e + | ||||
|            100000.0 * f + | ||||
|            1000000.0 * g + | ||||
|            10000000.0 * h + | ||||
|            100000000.0 * i + | ||||
|            1000000000.0 * j; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double float_fn(float x, float y, float z) { | ||||
|     return (x + y) * z; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| int intint_fn(double x, intint ii) { | ||||
|     printf("double: %g\n", x); | ||||
|     return ii.a + ii.b; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| int intintint_fn(double x, intintint iii) { | ||||
|     printf("double: %g\n", x); | ||||
|     return iii.a + iii.b + iii.c; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| intint return_struct(int i) { | ||||
|     intint ret; | ||||
|     ret.a = i; | ||||
|     ret.b = i * i; | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| big struct_big(int i, double d) { | ||||
|     big ret; | ||||
|     ret.a = i; | ||||
|     ret.b = (int64_t) d; | ||||
|     ret.c = ret.a + ret.b + 1000; | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| void void_fn(void) { | ||||
|     printf("void fn ran\n"); | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| void void_fn_2(double y) { | ||||
|     printf("y = %f\n", y); | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| void void_ret_fn(int x) { | ||||
|     printf("void fn ran: %d\n", x); | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| int intintint_fn_2(intintint iii, int i) { | ||||
|     fprintf(stderr, "iii.a = %d, iii.b = %d, iii.c = %d, i = %d\n", iii.a, iii.b, iii.c, i); | ||||
|     return i * (iii.a + iii.b + iii.c); | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| float split_fn(Split s) { | ||||
|     return s.a * s.c + s.b * s.d; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| float split_flip_fn(SplitFlip s) { | ||||
|     return s.a * s.c + s.b * s.d; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| Split split_ret_fn(int x, float y) { | ||||
|     Split ret; | ||||
|     ret.a = x; | ||||
|     ret.b = x; | ||||
|     ret.c = y; | ||||
|     ret.d = y; | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| SplitFlip split_flip_ret_fn(int x, float y) { | ||||
|     SplitFlip ret; | ||||
|     ret.a = x; | ||||
|     ret.b = x; | ||||
|     ret.c = y; | ||||
|     ret.d = y; | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| SixInts sixints_fn(void) { | ||||
|     return (SixInts) { | ||||
|         6666, 1111, 2222, 3333, 4444, 5555 | ||||
|     }; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| int sixints_fn_2(int x, SixInts s) { | ||||
|     return x + s.u + s.v + s.w + s.x + s.y + s.z; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| int sixints_fn_3(SixInts s, int x) { | ||||
|     return x + s.u + s.v + s.w + s.x + s.y + s.z; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| intint stack_spill_fn(uint8_t a, uint8_t b, uint8_t c, uint8_t d, | ||||
|                       uint8_t e, uint8_t f, uint8_t g, uint8_t h, | ||||
|                       float i, float j, float k, float l, | ||||
|                       float m, float n, float o, float p, | ||||
|                       float s1, int8_t s2, uint8_t s3, double s4, uint8_t s5, intint s6) { | ||||
|     return (intint) { | ||||
|         (a | b | c | d | e | f | g | h) + (i + j + k + l + m + n + o + p), | ||||
|         s1 *s6.a + s2 *s6.b + s3 *s4 *s5 | ||||
|     }; | ||||
| } | ||||
|  | ||||
| EXPORTER | ||||
| double stack_spill_fn_2(uint64pair a, uint64pair b, uint64pair c, int8_t d, uint64pair e, int8_t f) { | ||||
|     return (double)(a.a * c.a + a.b * c.b + b.a * e.a) * f - (double)(b.b * e.b) + d; | ||||
| } | ||||
							
								
								
									
										150
									
								
								examples/ffi/test.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								examples/ffi/test.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| # | ||||
| # Simple FFI test script that tests against a simple shared object | ||||
| # | ||||
|  | ||||
| (def is-windows (= :windows (os/which))) | ||||
| (def ffi/loc (string "examples/ffi/so." (if is-windows "dll" "so"))) | ||||
| (def ffi/source-loc "examples/ffi/so.c") | ||||
|  | ||||
| (if is-windows | ||||
|   (os/execute ["cl.exe" "/nologo" "/LD" ffi/source-loc "/link" "/DLL" (string "/OUT:" ffi/loc)] :px) | ||||
|   (os/execute ["cc" ffi/source-loc "-g" "-shared" "-o" ffi/loc] :px)) | ||||
|  | ||||
| (ffi/context ffi/loc) | ||||
|  | ||||
| (def intint (ffi/struct :int :int)) | ||||
| (def intintint (ffi/struct :int :int :int)) | ||||
| (def uint64pair (ffi/struct :u64 :u64)) | ||||
| (def big (ffi/struct :s64 :s64 :s64)) | ||||
| (def split (ffi/struct :int :int :float :float)) | ||||
| (def split-flip (ffi/struct :float :float :int :int)) | ||||
| (def six-ints (ffi/struct :int :int :int :int :int :int)) | ||||
|  | ||||
| (ffi/defbind int-fn :int [a :int b :int]) | ||||
| (ffi/defbind double-fn :double [a :double b :double c :double]) | ||||
| (ffi/defbind double-many :double | ||||
|   [x :double y :double z :double w :double a :double b :double]) | ||||
| (ffi/defbind double-lots :double | ||||
|   [a :double b :double c :double d :double e :double f :double g :double h :double i :double j :double]) | ||||
| (ffi/defbind float-fn :double | ||||
|   [x :float y :float z :float]) | ||||
| (ffi/defbind intint-fn :int | ||||
|   [x :double ii [:int :int]]) | ||||
| (ffi/defbind return-struct [:int :int] | ||||
|   [i :int]) | ||||
| (ffi/defbind intintint-fn :int | ||||
|   [x :double iii intintint]) | ||||
| (ffi/defbind struct-big big | ||||
|   [i :int d :double]) | ||||
| (ffi/defbind void-fn :void []) | ||||
| (ffi/defbind double-lots-2 :double | ||||
|   [a :double | ||||
|    b :double | ||||
|    c :double | ||||
|    d :double | ||||
|    e :double | ||||
|    f :double | ||||
|    g :double | ||||
|    h :double | ||||
|    i :double | ||||
|    j :double]) | ||||
| (ffi/defbind void-fn-2 :void [y :double]) | ||||
| (ffi/defbind intintint-fn-2 :int [iii intintint i :int]) | ||||
| (ffi/defbind split-fn :float [s split]) | ||||
| (ffi/defbind split-flip-fn :float [s split-flip]) | ||||
| (ffi/defbind split-ret-fn split [x :int y :float]) | ||||
| (ffi/defbind split-flip-ret-fn split-flip [x :int y :float]) | ||||
| (ffi/defbind sixints-fn six-ints []) | ||||
| (ffi/defbind sixints-fn-2 :int [x :int s six-ints]) | ||||
| (ffi/defbind sixints-fn-3 :int [s six-ints x :int]) | ||||
| (ffi/defbind stack-spill-fn intint | ||||
|              [a :u8 b :u8 c :u8 d :u8 | ||||
|               e :u8 f :u8 g :u8 h :u8 | ||||
|               i :float j :float k :float l :float | ||||
|               m :float n :float o :float p :float | ||||
|               s1 :float s2 :s8 s3 :u8 s4 :double s5 :u8 s6 intint]) | ||||
| (ffi/defbind stack-spill-fn-2 :double [a uint64pair b uint64pair c uint64pair d :s8 e uint64pair f :s8]) | ||||
| (ffi/defbind-alias int-fn int-fn-aliased :int [a :int b :int]) | ||||
|  | ||||
| # | ||||
| # Struct reading and writing | ||||
| # | ||||
|  | ||||
| (defn check-round-trip | ||||
|   [t value] | ||||
|   (def buf (ffi/write t value)) | ||||
|   (def same-value (ffi/read t buf)) | ||||
|   (assert (deep= value same-value) | ||||
|           (string/format "round trip %j (got %j)" value same-value))) | ||||
|  | ||||
| (check-round-trip :bool true) | ||||
| (check-round-trip :bool false) | ||||
| (check-round-trip :void nil) | ||||
| (check-round-trip :void nil) | ||||
| (check-round-trip :s8 10) | ||||
| (check-round-trip :s8 0) | ||||
| (check-round-trip :s8 -10) | ||||
| (check-round-trip :u8 10) | ||||
| (check-round-trip :u8 0) | ||||
| (check-round-trip :s16 10) | ||||
| (check-round-trip :s16 0) | ||||
| (check-round-trip :s16 -12312) | ||||
| (check-round-trip :u16 10) | ||||
| (check-round-trip :u16 0) | ||||
| (check-round-trip :u32 0) | ||||
| (check-round-trip :u32 10) | ||||
| (check-round-trip :u32 0xFFFF7777) | ||||
| (check-round-trip :s32 0x7FFF7777) | ||||
| (check-round-trip :s32 0) | ||||
| (check-round-trip :s32 -1234567) | ||||
|  | ||||
| (def s (ffi/struct :s8 :s8 :s8 :float)) | ||||
| (check-round-trip s [1 3 5 123.5]) | ||||
| (check-round-trip s [-1 -3 -5 -123.5]) | ||||
|  | ||||
| # | ||||
| # Call functions | ||||
| # | ||||
|  | ||||
| (tracev (sixints-fn)) | ||||
| (tracev (sixints-fn-2 100 [1 2 3 4 5 6])) | ||||
| (tracev (sixints-fn-3 [1 2 3 4 5 6] 200)) | ||||
| (tracev (split-ret-fn 10 12)) | ||||
| (tracev (split-flip-ret-fn 10 12)) | ||||
| (tracev (split-flip-ret-fn 12 10)) | ||||
| (tracev (intintint-fn-2 [10 20 30] 3)) | ||||
| (tracev (split-fn [5 6 1.2 3.4])) | ||||
| (tracev (void-fn-2 10.3)) | ||||
| (tracev (double-many 1 2 3 4 5 6)) | ||||
| (tracev (string/format "%.17g" (double-many 1 2 3 4 5 6))) | ||||
| (tracev (type (double-many 1 2 3 4 5 6))) | ||||
| (tracev (double-lots-2 0 1 2 3 4 5 6 7 8 9)) | ||||
| (tracev (void-fn)) | ||||
| (tracev (int-fn 10 20)) | ||||
| (tracev (double-fn 1.5 2.5 3.5)) | ||||
| (tracev (double-lots 1 2 3 4 5 6 7 8 9 10)) | ||||
| (tracev (float-fn 8 4 17)) | ||||
| (tracev (intint-fn 123.456 [10 20])) | ||||
| (tracev (intintint-fn 123.456 [10 20 30])) | ||||
| (tracev (return-struct 42)) | ||||
| (tracev (double-lots 1 2 3 4 5 6 700 800 9 10)) | ||||
| (tracev (struct-big 11 99.5)) | ||||
| (tracev (int-fn-aliased 10 20)) | ||||
|  | ||||
| (assert (= [10 10 12 12] (split-ret-fn 10 12))) | ||||
| (assert (= [12 12 10 10] (split-flip-ret-fn 10 12))) | ||||
| (assert (= 183 (intintint-fn-2 [10 20 31] 3))) | ||||
| (assert (= 264 (math/round (* 10 (split-fn [5 6 1.2 3.4]))))) | ||||
| (assert (= 9876543210 (double-lots-2 0 1 2 3 4 5 6 7 8 9))) | ||||
| (assert (= 60 (int-fn 10 20))) | ||||
| (assert (= 42 (double-fn 1.5 2.5 3.5))) | ||||
| (assert (= 21 (math/round (double-many 1 2 3 4 5 6.01)))) | ||||
| (assert (= 19 (double-lots 1 2 3 4 5 6 7 8 9 10))) | ||||
| (assert (= 204 (float-fn 8 4 17))) | ||||
| (assert (= [0 38534415] (stack-spill-fn | ||||
|                           0 0 0 0 0 0 0 0 | ||||
|                           0 0 0 0 0 0 0 0 | ||||
|                           1.5 -32 196 65536.5 3 [-15 32]))) | ||||
| (assert (= -2806 (stack-spill-fn-2 [2 3] [5 7] [9 11] -19 [13 17] -23))) | ||||
|  | ||||
| (print "Done.") | ||||
							
								
								
									
										7
									
								
								examples/ffi/win32.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/ffi/win32.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| (ffi/context "user32.dll") | ||||
|  | ||||
| (ffi/defbind MessageBoxA :int | ||||
|   [w :ptr text :string cap :string typ :int]) | ||||
|  | ||||
| (MessageBoxA nil "Hello, World!" "Test" 0) | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								examples/jitfn/hello.bin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								examples/jitfn/hello.bin
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										17
									
								
								examples/jitfn/hello.nasm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								examples/jitfn/hello.nasm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| BITS 64 | ||||
|  | ||||
| ;;; | ||||
| ;;; Code | ||||
| ;;; | ||||
| mov rax, 1          ; write( | ||||
| mov rdi, 1          ;   STDOUT_FILENO, | ||||
| lea rsi, [rel msg]  ;   msg, | ||||
| mov rdx, msglen     ;   sizeof(msg) | ||||
| syscall             ; ); | ||||
| ret                 ; return; | ||||
|  | ||||
| ;;; | ||||
| ;;; Constants | ||||
| ;;; | ||||
| msg: db "Hello, world!", 10 | ||||
| msglen: equ $ - msg | ||||
							
								
								
									
										13
									
								
								examples/jitfn/jitfn.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								examples/jitfn/jitfn.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| ### | ||||
| ### Relies on NASM being installed to assemble code. | ||||
| ### Only works on x86-64 Linux. | ||||
| ### | ||||
| ### Before running, compile hello.nasm to hello.bin with | ||||
| ### $ nasm hello.nasm -o hello.bin | ||||
|  | ||||
| (def bin (slurp "hello.bin")) | ||||
| (def f (ffi/jitfn bin)) | ||||
| (def signature (ffi/signature :default :void)) | ||||
| (ffi/call f signature) | ||||
| (print "called a jitted function with FFI!") | ||||
| (print "machine code: " (describe (string/slice f))) | ||||
							
								
								
									
										2
									
								
								examples/lineloop.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								examples/lineloop.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| (while (not (empty? (def line (getline)))) | ||||
|   (prin "line: " line)) | ||||
							
								
								
									
										30
									
								
								examples/marshal-stress.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								examples/marshal-stress.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| (defn init-db [c] | ||||
|   (def res @{:clients @{}}) | ||||
|   (var i 0) | ||||
|   (repeat c | ||||
|     (def n (string "client" i)) | ||||
|     (put-in res [:clients n] @{:name n :projects @{}}) | ||||
|     (++ i) | ||||
|     (repeat c | ||||
|       (def pn (string "project" i)) | ||||
|       (put-in res [:clients n :projects pn] @{:name pn}) | ||||
|       (++ i) | ||||
|       (repeat c | ||||
|         (def tn (string "task" i)) | ||||
|         (put-in res [:clients n :projects pn :tasks tn] @{:name pn}) | ||||
|         (++ i)))) | ||||
|   res) | ||||
|  | ||||
| (loop [c :range [30 80 1]] | ||||
|   (var s (os/clock)) | ||||
|   (print "Marshal DB with " c " clients, " | ||||
|          (* c c) " projects and " | ||||
|          (* c c c) " tasks. " | ||||
|          "Total " (+ (* c c c) (* c c) c) " tables") | ||||
|   (def buf (marshal (init-db c) @{} @"")) | ||||
|   (print "Buffer is " (length buf) " bytes") | ||||
|   (print "Duration " (- (os/clock) s)) | ||||
|   (set s (os/clock)) | ||||
|   (gccollect) | ||||
|   (print "Collected garbage in " (- (os/clock) s))) | ||||
|  | ||||
| @@ -76,9 +76,16 @@ void num_array_put(void *p, Janet key, Janet value) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| static Janet num_array_length(int32_t argc, Janet *argv) { | ||||
|     janet_fixarity(argc, 1); | ||||
|     num_array *array = (num_array *)janet_getabstract(argv, 0, &num_array_type); | ||||
|     return janet_wrap_number(array->size); | ||||
| } | ||||
|  | ||||
| static const JanetMethod methods[] = { | ||||
|     {"scale", num_array_scale}, | ||||
|     {"sum", num_array_sum}, | ||||
|     {"length", num_array_length}, | ||||
|     {NULL, NULL} | ||||
| }; | ||||
|  | ||||
| @@ -109,6 +116,11 @@ static const JanetReg cfuns[] = { | ||||
|         "(numarray/scale numarray factor)\n\n" | ||||
|         "scale numarray by factor" | ||||
|     }, | ||||
|     { | ||||
|         "sum", num_array_sum, | ||||
|         "(numarray/sum numarray)\n\n" | ||||
|         "sums numarray" | ||||
|     }, | ||||
|     {NULL, NULL, NULL} | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| (import build/numarray) | ||||
| (import /build/numarray) | ||||
|  | ||||
| (def a (numarray/new 30)) | ||||
| (print (get a 20)) | ||||
|   | ||||
							
								
								
									
										5
									
								
								examples/posix-exec.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								examples/posix-exec.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| # Switch to python | ||||
|  | ||||
| (print "running in Janet") | ||||
| (os/posix-exec ["python"] :p) | ||||
| (print "will not print") | ||||
							
								
								
									
										1
									
								
								examples/sample-bad-bundle/badmod.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/sample-bad-bundle/badmod.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| (def abc 123) | ||||
							
								
								
									
										7
									
								
								examples/sample-bad-bundle/bundle.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/sample-bad-bundle/bundle.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "badmod.janet")) | ||||
|  | ||||
| (defn check | ||||
|   [&] | ||||
|   (error "Check failed!")) | ||||
							
								
								
									
										1
									
								
								examples/sample-bundle-aliases/aliases-mod.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								examples/sample-bundle-aliases/aliases-mod.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| (defn fun [x] (range x)) | ||||
							
								
								
									
										3
									
								
								examples/sample-bundle-aliases/bundle.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-bundle-aliases/bundle.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "aliases-mod.janet")) | ||||
							
								
								
									
										4
									
								
								examples/sample-bundle-aliases/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/sample-bundle-aliases/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| @{ | ||||
|   :name "sample-bundle-aliases" | ||||
|   :dependencies ["sample-dep1" "sample-dep2"] | ||||
| } | ||||
							
								
								
									
										4
									
								
								examples/sample-bundle/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/sample-bundle/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| @{ | ||||
|   :name "sample-bundle" | ||||
|   :dependencies ["sample-dep1" "sample-dep2"] | ||||
| } | ||||
							
								
								
									
										3
									
								
								examples/sample-bundle/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-bundle/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "mymod.janet")) | ||||
							
								
								
									
										7
									
								
								examples/sample-bundle/mymod.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								examples/sample-bundle/mymod.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| (import dep1) | ||||
| (import dep2) | ||||
|  | ||||
| (defn myfn | ||||
|   [x] | ||||
|   (def y (dep2/function x)) | ||||
|   (dep1/function y)) | ||||
							
								
								
									
										4
									
								
								examples/sample-dep1/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/sample-dep1/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| @{ | ||||
|   :name "sample-dep1" | ||||
|   :dependencies ["sample-dep2"] | ||||
| } | ||||
							
								
								
									
										3
									
								
								examples/sample-dep1/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep1/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "dep1.janet")) | ||||
							
								
								
									
										3
									
								
								examples/sample-dep1/dep1.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep1/dep1.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn function | ||||
|   [x] | ||||
|   (+ x x)) | ||||
							
								
								
									
										3
									
								
								examples/sample-dep2/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep2/bundle/info.jdn
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| @{ | ||||
|   :name "sample-dep2" | ||||
| } | ||||
							
								
								
									
										3
									
								
								examples/sample-dep2/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep2/bundle/init.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn install | ||||
|   [manifest &] | ||||
|   (bundle/add-file manifest "dep2.janet")) | ||||
							
								
								
									
										3
									
								
								examples/sample-dep2/dep2.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/sample-dep2/dep2.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| (defn function | ||||
|   [x] | ||||
|   (* x x)) | ||||
							
								
								
									
										41
									
								
								examples/sigaction.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								examples/sigaction.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| ### | ||||
| ### Usage: janet examples/sigaction.janet 1|2|3|4 & | ||||
| ### | ||||
| ### Then at shell: kill -s SIGTERM $! | ||||
| ### | ||||
|  | ||||
| (defn action | ||||
|   [] | ||||
|   (print "Handled SIGTERM!") | ||||
|   (flush) | ||||
|   (os/exit 1)) | ||||
|  | ||||
| (defn main1 | ||||
|   [] | ||||
|   (os/sigaction :term action true) | ||||
|   (forever)) | ||||
|  | ||||
| (defn main2 | ||||
|   [] | ||||
|   (os/sigaction :term action) | ||||
|   (forever)) | ||||
|  | ||||
| (defn main3 | ||||
|   [] | ||||
|   (os/sigaction :term action true) | ||||
|   (forever (ev/sleep math/inf))) | ||||
|  | ||||
| (defn main4 | ||||
|   [] | ||||
|   (os/sigaction :term action) | ||||
|   (forever (ev/sleep math/inf))) | ||||
|  | ||||
| (defn main | ||||
|   [& args] | ||||
|   (def which (scan-number (get args 1 "1"))) | ||||
|   (case which | ||||
|     1 (main1) # should work | ||||
|     2 (main2) # will not work | ||||
|     3 (main3) # should work | ||||
|     4 (main4) # should work | ||||
|     (error "bad main"))) | ||||
| @@ -1,10 +1,10 @@ | ||||
| # An example of using Janet's extensible module system | ||||
| # to import files from URL. To try this, run `janet -l examples/urlloader.janet` | ||||
| # from the repl, and then: | ||||
| # An example of using Janet's extensible module system to import files from | ||||
| # URL. To try this, run `janet -l ./examples/urlloader.janet` from the command | ||||
| # line, and then at the REPL type: | ||||
| # | ||||
| # (import https://raw.githubusercontent.com/janet-lang/janet/master/examples/colors.janet :as c) | ||||
| # | ||||
| # This will import a file using curl. You can then try | ||||
| # This will import a file using curl. You can then try: | ||||
| # | ||||
| # (print (c/color :green "Hello!")) | ||||
| # | ||||
| @@ -13,9 +13,9 @@ | ||||
|  | ||||
| (defn- load-url | ||||
|   [url args] | ||||
|   (def f (file/popen (string "curl " url))) | ||||
|   (def res (dofile f :source url ;args)) | ||||
|   (try (file/close f) ([err] nil)) | ||||
|   (def p (os/spawn ["curl" url "-s"] :p {:out :pipe})) | ||||
|   (def res (dofile (p :out) :source url ;args)) | ||||
|   (:wait p) | ||||
|   res) | ||||
|  | ||||
| (defn- check-http-url | ||||
|   | ||||
							
								
								
									
										20
									
								
								examples/weak-tables.janet
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								examples/weak-tables.janet
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| (def weak-k (table/weak-keys 10)) | ||||
| (def weak-v (table/weak-values 10)) | ||||
| (def weak-kv (table/weak 10)) | ||||
|  | ||||
| (put weak-kv (gensym) 10) | ||||
| (put weak-kv :hello :world) | ||||
| (put weak-k :abc123zz77asda :stuff) | ||||
| (put weak-k true :abc123zz77asda) | ||||
| (put weak-k :zyzzyz false) | ||||
| (put weak-v (gensym) 10) | ||||
| (put weak-v 20 (gensym)) | ||||
| (print "before gc") | ||||
| (tracev weak-k) | ||||
| (tracev weak-v) | ||||
| (tracev weak-kv) | ||||
| (gccollect) | ||||
| (print "after gc") | ||||
| (tracev weak-k) | ||||
| (tracev weak-v) | ||||
| (tracev weak-kv) | ||||
							
								
								
									
										20
									
								
								janet.1
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								janet.1
									
									
									
									
									
								
							| @@ -164,10 +164,15 @@ Execute a string of Janet source. Source code is executed in the order it is enc | ||||
| arguments are executed before later ones. | ||||
|  | ||||
| .TP | ||||
| .BR \-E\ code arguments | ||||
| .BR \-E\ code\ arguments... | ||||
| Execute a single Janet expression as a Janet short-fn, passing the remaining command line arguments to the expression. This allows | ||||
| more concise one-liners with command line arguments. | ||||
|  | ||||
| Example: janet -E '(print $0)' 12 is equivalent to '((short-fn (print $0)) 12)', which is in turn equivalent to | ||||
| `((fn [k] (print k)) 12)` | ||||
|  | ||||
| See docs for the `short-fn` function for more details. | ||||
|  | ||||
| .TP | ||||
| .BR \-d | ||||
| Enable debug mode. On all terminating signals as well the debug signal, this will | ||||
| @@ -178,6 +183,10 @@ default repl. | ||||
| .BR \-n | ||||
| Disable ANSI colors in the repl. Has no effect if no repl is run. | ||||
|  | ||||
| .TP | ||||
| .BR \-N | ||||
| Enable ANSI colors in the repl. Has no effect if no repl is run. | ||||
|  | ||||
| .TP | ||||
| .BR \-r | ||||
| Open a REPL (Read Eval Print Loop) after executing all sources. By default, if Janet is called with no | ||||
| @@ -246,7 +255,8 @@ and then arguments to the script. | ||||
| .RS | ||||
| The location to look for Janet libraries. This is the only environment variable Janet needs to | ||||
| find native and source code modules. If no JANET_PATH is set, Janet will look in | ||||
| the default location set at compile time. | ||||
| the default location set at compile time. This should be a list of as well as a colon | ||||
| separate list of such directories. | ||||
| .RE | ||||
|  | ||||
| .B JANET_PROFILE | ||||
| @@ -263,5 +273,11 @@ This variable does nothing in the default configuration of Janet, as PRF is disa | ||||
| cannot be defined for this variable to have an effect. | ||||
| .RE | ||||
|  | ||||
| .B NO_COLOR | ||||
| .RS | ||||
| Turn off color by default in the repl and in the error handler of scripts. This can be changed at runtime | ||||
| via dynamic bindings *err-color* and *pretty-format*, or via the command line parameters -n and -N. | ||||
| .RE | ||||
|  | ||||
| .SH AUTHOR | ||||
| Written by Calvin Rose <calsrose@gmail.com> | ||||
|   | ||||
							
								
								
									
										160
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								meson.build
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2021 Calvin Rose and contributors | ||||
| # Copyright (c) 2025 Calvin Rose and contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to | ||||
| @@ -20,16 +20,35 @@ | ||||
|  | ||||
| project('janet', 'c', | ||||
|   default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], | ||||
|   version : '1.18.0') | ||||
|   version : '1.38.0') | ||||
|  | ||||
| # Global settings | ||||
| janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet') | ||||
| header_path = join_paths(get_option('prefix'), get_option('includedir'), 'janet') | ||||
|  | ||||
| # Link math library on all systems | ||||
| # Compilers | ||||
| cc = meson.get_compiler('c') | ||||
| native_cc = meson.get_compiler('c', native : true) | ||||
|  | ||||
| # Native deps | ||||
| native_m_dep = native_cc.find_library('m', required : false) | ||||
| native_dl_dep = native_cc.find_library('dl', required : false) | ||||
| native_android_spawn_dep = native_cc.find_library('android-spawn', required : false) | ||||
| native_thread_dep = dependency('threads', native : true) | ||||
|  | ||||
| # Deps | ||||
| m_dep = cc.find_library('m', required : false) | ||||
| dl_dep = cc.find_library('dl', required : false) | ||||
|  | ||||
| # for MINGW/MSYS2 | ||||
| native_ws2_dep = native_cc.find_library('ws2_32', required: false) | ||||
| native_psapi_dep = native_cc.find_library('psapi', required: false) | ||||
| native_wsock_dep = native_cc.find_library('wsock32', required: false) | ||||
| ws2_dep = cc.find_library('ws2_32', required: false) | ||||
| psapi_dep = cc.find_library('psapi', required: false) | ||||
| wsock_dep = cc.find_library('wsock32', required: false) | ||||
|  | ||||
| android_spawn_dep = cc.find_library('android-spawn', required : false) | ||||
| thread_dep = dependency('threads') | ||||
|  | ||||
| # Link options | ||||
| @@ -60,6 +79,7 @@ conf.set('JANET_NO_SOURCEMAPS', not get_option('sourcemaps')) | ||||
| conf.set('JANET_NO_ASSEMBLER', not get_option('assembler')) | ||||
| conf.set('JANET_NO_PEG', not get_option('peg')) | ||||
| conf.set('JANET_NO_NET', not get_option('net')) | ||||
| conf.set('JANET_NO_IPV6', not get_option('ipv6')) | ||||
| conf.set('JANET_NO_EV', not get_option('ev') or get_option('single_threaded')) | ||||
| conf.set('JANET_REDUCED_OS', get_option('reduced_os')) | ||||
| conf.set('JANET_NO_INT_TYPES', not get_option('int_types')) | ||||
| @@ -75,6 +95,10 @@ conf.set('JANET_SIMPLE_GETLINE', get_option('simple_getline')) | ||||
| conf.set('JANET_EV_NO_EPOLL', not get_option('epoll')) | ||||
| conf.set('JANET_EV_NO_KQUEUE', not get_option('kqueue')) | ||||
| conf.set('JANET_NO_INTERPRETER_INTERRUPT', not get_option('interpreter_interrupt')) | ||||
| conf.set('JANET_NO_FFI', not get_option('ffi')) | ||||
| conf.set('JANET_NO_FFI_JIT', not get_option('ffi_jit')) | ||||
| conf.set('JANET_NO_FILEWATCH', not get_option('filewatch')) | ||||
| conf.set('JANET_NO_CRYPTORAND', not get_option('cryptorand')) | ||||
| if get_option('os_name') != '' | ||||
|   conf.set('JANET_OS_NAME', get_option('os_name')) | ||||
| endif | ||||
| @@ -115,7 +139,9 @@ core_src = [ | ||||
|   'src/core/debug.c', | ||||
|   'src/core/emit.c', | ||||
|   'src/core/ev.c', | ||||
|   'src/core/ffi.c', | ||||
|   'src/core/fiber.c', | ||||
|   'src/core/filewatch.c', | ||||
|   'src/core/gc.c', | ||||
|   'src/core/inttypes.c', | ||||
|   'src/core/io.c', | ||||
| @@ -156,16 +182,23 @@ mainclient_src = [ | ||||
|   'src/mainclient/shell.c' | ||||
| ] | ||||
|  | ||||
| janet_dependencies = [m_dep, dl_dep, android_spawn_dep, ws2_dep, psapi_dep, wsock_dep] | ||||
| janet_native_dependencies = [native_m_dep, native_dl_dep, native_android_spawn_dep, native_ws2_dep, native_psapi_dep, native_wsock_dep] | ||||
| if not get_option('single_threaded') | ||||
|   janet_dependencies += thread_dep | ||||
|   janet_native_dependencies += native_thread_dep | ||||
| endif | ||||
|  | ||||
| # Build boot binary | ||||
| janet_boot = executable('janet-boot', core_src, boot_src, | ||||
|   include_directories : incdir, | ||||
|   c_args : '-DJANET_BOOTSTRAP', | ||||
|   dependencies : [m_dep, dl_dep, thread_dep], | ||||
|   dependencies : janet_native_dependencies, | ||||
|   native : true) | ||||
|  | ||||
| # Build janet.c | ||||
| janetc = custom_target('janetc', | ||||
|   input : [janet_boot], | ||||
|   input : [janet_boot, 'src/boot/boot.janet'], | ||||
|   output : 'janet.c', | ||||
|   capture : true, | ||||
|   command : [ | ||||
| @@ -173,30 +206,41 @@ janetc = custom_target('janetc', | ||||
|     'JANET_PATH', janet_path | ||||
|   ]) | ||||
|  | ||||
| janet_dependencies = [m_dep, dl_dep] | ||||
| if not get_option('single_threaded') | ||||
|   janet_dependencies += thread_dep | ||||
| # Allow building with no shared library | ||||
| if cc.has_argument('-fvisibility=hidden') | ||||
|   lib_cflags = ['-fvisibility=hidden'] | ||||
| else | ||||
|   lib_cflags = [] | ||||
| endif | ||||
|  | ||||
| libjanet = library('janet', janetc, | ||||
|   include_directories : incdir, | ||||
|   dependencies : janet_dependencies, | ||||
|   version: meson.project_version(), | ||||
|   soversion: version_parts[0] + '.' + version_parts[1], | ||||
|   install : true) | ||||
|  | ||||
| if get_option('shared') | ||||
|   libjanet = library('janet', janetc, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     version: meson.project_version(), | ||||
|     soversion: version_parts[0] + '.' + version_parts[1], | ||||
|     c_args : lib_cflags, | ||||
|     install : true) | ||||
| # Extra c flags - adding -fvisibility=hidden matches the Makefile and | ||||
| # shaves off about 10k on linux x64, likely similar on other platforms. | ||||
| if cc.has_argument('-fvisibility=hidden') | ||||
|   extra_cflags = ['-fvisibility=hidden'] | ||||
|   if cc.has_argument('-fvisibility=hidden') | ||||
|     extra_cflags = ['-fvisibility=hidden', '-DJANET_DLL_IMPORT'] | ||||
|   else | ||||
|     extra_cflags = ['-DJANET_DLL_IMPORT'] | ||||
|   endif | ||||
|   janet_mainclient = executable('janet', mainclient_src, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     link_with: [libjanet], | ||||
|     c_args : extra_cflags, | ||||
|     install : true) | ||||
| else | ||||
|   extra_cflags = [] | ||||
|   # No shared library | ||||
|   janet_mainclient = executable('janet', mainclient_src, janetc, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     c_args : lib_cflags, | ||||
|     install : true) | ||||
| endif | ||||
| janet_mainclient = executable('janet', janetc, mainclient_src, | ||||
|   include_directories : incdir, | ||||
|   dependencies : janet_dependencies, | ||||
|   c_args : extra_cflags, | ||||
|   install : true) | ||||
|  | ||||
| if meson.is_cross_build() | ||||
|   native_cc = meson.get_compiler('c', native: true) | ||||
| @@ -207,7 +251,7 @@ if meson.is_cross_build() | ||||
|   endif | ||||
|   janet_nativeclient = executable('janet-native', janetc, mainclient_src, | ||||
|     include_directories : incdir, | ||||
|     dependencies : janet_dependencies, | ||||
|     dependencies : janet_native_dependencies, | ||||
|     c_args : extra_native_cflags, | ||||
|     native : true) | ||||
| else | ||||
| @@ -223,17 +267,37 @@ docs = custom_target('docs', | ||||
|  | ||||
| # Tests | ||||
| test_files = [ | ||||
|   'test/suite0000.janet', | ||||
|   'test/suite0001.janet', | ||||
|   'test/suite0002.janet', | ||||
|   'test/suite0003.janet', | ||||
|   'test/suite0004.janet', | ||||
|   'test/suite0005.janet', | ||||
|   'test/suite0006.janet', | ||||
|   'test/suite0007.janet', | ||||
|   'test/suite0008.janet', | ||||
|   'test/suite0009.janet', | ||||
|   'test/suite0010.janet' | ||||
|   'test/suite-array.janet', | ||||
|   'test/suite-asm.janet', | ||||
|   'test/suite-boot.janet', | ||||
|   'test/suite-buffer.janet', | ||||
|   'test/suite-bundle.janet', | ||||
|   'test/suite-capi.janet', | ||||
|   'test/suite-cfuns.janet', | ||||
|   'test/suite-compile.janet', | ||||
|   'test/suite-corelib.janet', | ||||
|   'test/suite-debug.janet', | ||||
|   'test/suite-ev.janet', | ||||
|   'test/suite-ffi.janet', | ||||
|   'test/suite-filewatch.janet', | ||||
|   'test/suite-inttypes.janet', | ||||
|   'test/suite-io.janet', | ||||
|   'test/suite-marsh.janet', | ||||
|   'test/suite-math.janet', | ||||
|   'test/suite-os.janet', | ||||
|   'test/suite-parse.janet', | ||||
|   'test/suite-peg.janet', | ||||
|   'test/suite-pp.janet', | ||||
|   'test/suite-specials.janet', | ||||
|   'test/suite-string.janet', | ||||
|   'test/suite-strtod.janet', | ||||
|   'test/suite-struct.janet', | ||||
|   'test/suite-symcache.janet', | ||||
|   'test/suite-table.janet', | ||||
|   'test/suite-tuple.janet', | ||||
|   'test/suite-unknown.janet', | ||||
|   'test/suite-value.janet', | ||||
|   'test/suite-vm.janet' | ||||
| ] | ||||
| foreach t : test_files | ||||
|   test(t, janet_nativeclient, args : files([t]), workdir : meson.current_source_dir()) | ||||
| @@ -243,14 +307,15 @@ endforeach | ||||
| run_target('repl', command : [janet_nativeclient]) | ||||
|  | ||||
| # For use as meson subproject (wrap) | ||||
| janet_dep = declare_dependency(include_directories : incdir, | ||||
|   link_with : libjanet) | ||||
|  | ||||
| if get_option('shared') | ||||
|   janet_dep = declare_dependency(include_directories : incdir, | ||||
|     link_with : libjanet) | ||||
| # pkgconfig | ||||
| pkg = import('pkgconfig') | ||||
| pkg.generate(libjanet, | ||||
|   subdirs: 'janet', | ||||
|   description: 'Library for the Janet programming language.') | ||||
|   pkg = import('pkgconfig') | ||||
|   pkg.generate(libjanet, | ||||
|     subdirs: 'janet', | ||||
|     description: 'Library for the Janet programming language.') | ||||
| endif | ||||
|  | ||||
| # Installation | ||||
| install_man('janet.1') | ||||
| @@ -260,5 +325,12 @@ patched_janet = custom_target('patched-janeth', | ||||
|   install : true, | ||||
|   install_dir : join_paths(get_option('includedir'), 'janet'), | ||||
|   build_by_default : true, | ||||
|   output : ['janet.h'], | ||||
|   output : ['janet_' + meson.project_version() + '.h'], | ||||
|   command : [janet_nativeclient, '@INPUT@', '@OUTPUT@']) | ||||
|  | ||||
| # Create a version of the janet.h header that matches what jpm often expects | ||||
| if meson.version().version_compare('>=0.61') | ||||
|   install_symlink('janet.h', pointing_to: 'janet/janet_' + meson.project_version() + '.h', install_dir: get_option('includedir')) | ||||
|   install_symlink('janet.h', pointing_to: 'janet_' + meson.project_version() + '.h', install_dir: join_paths(get_option('includedir'), 'janet')) | ||||
| endif | ||||
|  | ||||
|   | ||||
| @@ -11,14 +11,18 @@ option('peg', type : 'boolean', value : true) | ||||
| option('int_types', type : 'boolean', value : true) | ||||
| option('prf', type : 'boolean', value : false) | ||||
| option('net', type : 'boolean', value : true) | ||||
| option('ipv6', type : 'boolean', value : true) | ||||
| option('ev', type : 'boolean', value : true) | ||||
| option('processes', type : 'boolean', value : true) | ||||
| option('umask', type : 'boolean', value : true) | ||||
| option('realpath', type : 'boolean', value : true) | ||||
| option('simple_getline', type : 'boolean', value : false) | ||||
| option('epoll', type : 'boolean', value : false) | ||||
| option('kqueue', type : 'boolean', value : false) | ||||
| option('interpreter_interrupt', type : 'boolean', value : false) | ||||
| option('epoll', type : 'boolean', value : true) | ||||
| option('kqueue', type : 'boolean', value : true) | ||||
| option('interpreter_interrupt', type : 'boolean', value : true) | ||||
| option('ffi', type : 'boolean', value : true) | ||||
| option('ffi_jit', type : 'boolean', value : true) | ||||
| option('filewatch', type : 'boolean', value : true) | ||||
|  | ||||
| option('recursion_guard', type : 'integer', min : 10, max : 8000, value : 1024) | ||||
| option('max_proto_depth', type : 'integer', min : 10, max : 8000, value : 200) | ||||
| @@ -27,3 +31,5 @@ option('stack_max', type : 'integer', min : 8096, max : 0x7fffffff, value : 0x7f | ||||
|  | ||||
| option('arch_name', type : 'string', value: '') | ||||
| option('os_name', type : 'string', value: '') | ||||
| option('shared', type : 'boolean', value: true) | ||||
| option('cryptorand', type : 'boolean', value: true) | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
							
								
								
									
										3061
									
								
								src/boot/boot.janet
									
									
									
									
									
								
							
							
						
						
									
										3061
									
								
								src/boot/boot.janet
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -22,7 +22,7 @@ | ||||
|  | ||||
| #include <janet.h> | ||||
| #include <assert.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <math.h> | ||||
|  | ||||
| #include "tests.h" | ||||
| @@ -35,6 +35,11 @@ int system_test() { | ||||
|     assert(sizeof(void *) == 8); | ||||
| #endif | ||||
|  | ||||
|     /* Check the version defines are self consistent */ | ||||
|     char version_combined[256]; | ||||
|     sprintf(version_combined, "%d.%d.%d%s", JANET_VERSION_MAJOR, JANET_VERSION_MINOR, JANET_VERSION_PATCH, JANET_VERSION_EXTRA); | ||||
|     assert(!strcmp(JANET_VERSION, version_combined)); | ||||
|  | ||||
|     /* Reflexive testing and nanbox testing */ | ||||
|     assert(janet_equals(janet_wrap_nil(), janet_wrap_nil())); | ||||
|     assert(janet_equals(janet_wrap_false(), janet_wrap_false())); | ||||
| @@ -70,6 +75,5 @@ int system_test() { | ||||
|  | ||||
|     assert(janet_equals(tuple1, tuple2)); | ||||
|  | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
| @@ -4,10 +4,10 @@ | ||||
| #define JANETCONF_H | ||||
|  | ||||
| #define JANET_VERSION_MAJOR 1 | ||||
| #define JANET_VERSION_MINOR 18 | ||||
| #define JANET_VERSION_MINOR 38 | ||||
| #define JANET_VERSION_PATCH 0 | ||||
| #define JANET_VERSION_EXTRA "" | ||||
| #define JANET_VERSION "1.18.0" | ||||
| #define JANET_VERSION "1.38.0" | ||||
|  | ||||
| /* #define JANET_BUILD "local" */ | ||||
|  | ||||
| @@ -29,10 +29,13 @@ | ||||
| /* #define JANET_NO_NET */ | ||||
| /* #define JANET_NO_INT_TYPES */ | ||||
| /* #define JANET_NO_EV */ | ||||
| /* #define JANET_NO_FILEWATCH */ | ||||
| /* #define JANET_NO_REALPATH */ | ||||
| /* #define JANET_NO_SYMLINKS */ | ||||
| /* #define JANET_NO_UMASK */ | ||||
| /* #define JANET_NO_THREADS */ | ||||
| /* #define JANET_NO_FFI */ | ||||
| /* #define JANET_NO_FFI_JIT */ | ||||
|  | ||||
| /* Other settings */ | ||||
| /* #define JANET_DEBUG */ | ||||
| @@ -50,6 +53,9 @@ | ||||
| /* #define JANET_EV_NO_EPOLL */ | ||||
| /* #define JANET_EV_NO_KQUEUE */ | ||||
| /* #define JANET_NO_INTERPRETER_INTERRUPT */ | ||||
| /* #define JANET_NO_IPV6 */ | ||||
| /* #define JANET_NO_CRYPTORAND */ | ||||
| /* #define JANET_USE_STDATOMIC */ | ||||
|  | ||||
| /* Custom vm allocator support */ | ||||
| /* #include <mimalloc.h> */ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -23,14 +23,16 @@ | ||||
| #ifndef JANET_AMALG | ||||
| #include "features.h" | ||||
| #include <janet.h> | ||||
| #include "util.h" | ||||
| #include "gc.h" | ||||
| #include "state.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #endif | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| /* Create new userdata */ | ||||
| void *janet_abstract_begin(const JanetAbstractType *atype, size_t size) { | ||||
| @@ -63,8 +65,8 @@ void *janet_abstract_begin_threaded(const JanetAbstractType *atype, size_t size) | ||||
|     } | ||||
|     janet_vm.next_collection += size + sizeof(JanetAbstractHead); | ||||
|     header->gc.flags = JANET_MEMORY_THREADED_ABSTRACT; | ||||
|     header->gc.next = NULL; /* Clear memory for address sanitizers */ | ||||
|     header->gc.refcount = 1; | ||||
|     header->gc.data.next = NULL; /* Clear memory for address sanitizers */ | ||||
|     header->gc.data.refcount = 1; | ||||
|     header->size = size; | ||||
|     header->type = atype; | ||||
|     void *abstract = (void *) & (header->data); | ||||
| @@ -85,12 +87,12 @@ void *janet_abstract_threaded(const JanetAbstractType *atype, size_t size) { | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
|  | ||||
| static int32_t janet_incref(JanetAbstractHead *ab) { | ||||
|     return InterlockedIncrement(&ab->gc.refcount); | ||||
| size_t janet_os_mutex_size(void) { | ||||
|     return sizeof(CRITICAL_SECTION); | ||||
| } | ||||
|  | ||||
| static int32_t janet_decref(JanetAbstractHead *ab) { | ||||
|     return InterlockedDecrement(&ab->gc.refcount); | ||||
| size_t janet_os_rwlock_size(void) { | ||||
|     return sizeof(void *); | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_init(JanetOSMutex *mutex) { | ||||
| @@ -106,43 +108,97 @@ void janet_os_mutex_lock(JanetOSMutex *mutex) { | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_unlock(JanetOSMutex *mutex) { | ||||
|     /* error handling? May want to keep counter */ | ||||
|     LeaveCriticalSection((CRITICAL_SECTION *) mutex); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_init(JanetOSRWLock *rwlock) { | ||||
|     InitializeSRWLock((PSRWLOCK) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_deinit(JanetOSRWLock *rwlock) { | ||||
|     /* no op? */ | ||||
|     (void) rwlock; | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_rlock(JanetOSRWLock *rwlock) { | ||||
|     AcquireSRWLockShared((PSRWLOCK) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_wlock(JanetOSRWLock *rwlock) { | ||||
|     AcquireSRWLockExclusive((PSRWLOCK) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_runlock(JanetOSRWLock *rwlock) { | ||||
|     ReleaseSRWLockShared((PSRWLOCK) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_wunlock(JanetOSRWLock *rwlock) { | ||||
|     ReleaseSRWLockExclusive((PSRWLOCK) rwlock); | ||||
| } | ||||
|  | ||||
| #else | ||||
|  | ||||
| static int32_t janet_incref(JanetAbstractHead *ab) { | ||||
|     return __atomic_add_fetch(&ab->gc.refcount, 1, __ATOMIC_RELAXED); | ||||
| size_t janet_os_mutex_size(void) { | ||||
|     return sizeof(pthread_mutex_t); | ||||
| } | ||||
|  | ||||
| static int32_t janet_decref(JanetAbstractHead *ab) { | ||||
|     return __atomic_add_fetch(&ab->gc.refcount, -1, __ATOMIC_RELAXED); | ||||
| size_t janet_os_rwlock_size(void) { | ||||
|     return sizeof(pthread_rwlock_t); | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_init(JanetOSMutex *mutex) { | ||||
|     pthread_mutex_init(mutex, NULL); | ||||
|     pthread_mutexattr_t attr; | ||||
|     pthread_mutexattr_init(&attr); | ||||
|     pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); | ||||
|     pthread_mutex_init((pthread_mutex_t *) mutex, &attr); | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_deinit(JanetOSMutex *mutex) { | ||||
|     pthread_mutex_destroy(mutex); | ||||
|     pthread_mutex_destroy((pthread_mutex_t *) mutex); | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_lock(JanetOSMutex *mutex) { | ||||
|     pthread_mutex_lock(mutex); | ||||
|     pthread_mutex_lock((pthread_mutex_t *) mutex); | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_unlock(JanetOSMutex *mutex) { | ||||
|     pthread_mutex_unlock(mutex); | ||||
|     int ret = pthread_mutex_unlock((pthread_mutex_t *) mutex); | ||||
|     if (ret) janet_panic("cannot release lock"); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_init(JanetOSRWLock *rwlock) { | ||||
|     pthread_rwlock_init((pthread_rwlock_t *) rwlock, NULL); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_deinit(JanetOSRWLock *rwlock) { | ||||
|     pthread_rwlock_destroy((pthread_rwlock_t *) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_rlock(JanetOSRWLock *rwlock) { | ||||
|     pthread_rwlock_rdlock((pthread_rwlock_t *) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_wlock(JanetOSRWLock *rwlock) { | ||||
|     pthread_rwlock_wrlock((pthread_rwlock_t *) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_runlock(JanetOSRWLock *rwlock) { | ||||
|     pthread_rwlock_unlock((pthread_rwlock_t *) rwlock); | ||||
| } | ||||
|  | ||||
| void janet_os_rwlock_wunlock(JanetOSRWLock *rwlock) { | ||||
|     pthread_rwlock_unlock((pthread_rwlock_t *) rwlock); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| int32_t janet_abstract_incref(void *abst) { | ||||
|     return janet_incref(janet_abstract_head(abst)); | ||||
|     return janet_atomic_inc(&janet_abstract_head(abst)->gc.data.refcount); | ||||
| } | ||||
|  | ||||
| int32_t janet_abstract_decref(void *abst) { | ||||
|     return janet_decref(janet_abstract_head(abst)); | ||||
|     return janet_atomic_dec(&janet_abstract_head(abst)->gc.data.refcount); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -30,9 +30,7 @@ | ||||
|  | ||||
| #include <string.h> | ||||
|  | ||||
| /* Creates a new array */ | ||||
| JanetArray *janet_array(int32_t capacity) { | ||||
|     JanetArray *array = janet_gcalloc(JANET_MEMORY_ARRAY, sizeof(JanetArray)); | ||||
| static void janet_array_impl(JanetArray *array, int32_t capacity) { | ||||
|     Janet *data = NULL; | ||||
|     if (capacity > 0) { | ||||
|         janet_vm.next_collection += capacity * sizeof(Janet); | ||||
| @@ -44,6 +42,19 @@ JanetArray *janet_array(int32_t capacity) { | ||||
|     array->count = 0; | ||||
|     array->capacity = capacity; | ||||
|     array->data = data; | ||||
| } | ||||
|  | ||||
| /* Creates a new array */ | ||||
| JanetArray *janet_array(int32_t capacity) { | ||||
|     JanetArray *array = janet_gcalloc(JANET_MEMORY_ARRAY, sizeof(JanetArray)); | ||||
|     janet_array_impl(array, capacity); | ||||
|     return array; | ||||
| } | ||||
|  | ||||
| /* Creates a new array with weak references */ | ||||
| JanetArray *janet_array_weak(int32_t capacity) { | ||||
|     JanetArray *array = janet_gcalloc(JANET_MEMORY_ARRAY_WEAK, sizeof(JanetArray)); | ||||
|     janet_array_impl(array, capacity); | ||||
|     return array; | ||||
| } | ||||
|  | ||||
| @@ -125,18 +136,27 @@ Janet janet_array_peek(JanetArray *array) { | ||||
| JANET_CORE_FN(cfun_array_new, | ||||
|               "(array/new capacity)", | ||||
|               "Creates a new empty array with a pre-allocated capacity. The same as " | ||||
|               "(array) but can be more efficient if the maximum size of an array is known.") { | ||||
|               "`(array)` but can be more efficient if the maximum size of an array is known.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     int32_t cap = janet_getinteger(argv, 0); | ||||
|     JanetArray *array = janet_array(cap); | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_array_weak, | ||||
|               "(array/weak capacity)", | ||||
|               "Creates a new empty array with a pre-allocated capacity and support for weak references. Similar to `array/new`.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     int32_t cap = janet_getinteger(argv, 0); | ||||
|     JanetArray *array = janet_array_weak(cap); | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_array_new_filled, | ||||
|               "(array/new-filled count &opt value)", | ||||
|               "Creates a new array of `count` elements, all set to `value`, which defaults to nil. Returns the new array.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     int32_t count = janet_getinteger(argv, 0); | ||||
|     int32_t count = janet_getnat(argv, 0); | ||||
|     Janet x = (argc == 2) ? argv[1] : janet_wrap_nil(); | ||||
|     JanetArray *array = janet_array(count); | ||||
|     for (int32_t i = 0; i < count; i++) { | ||||
| @@ -177,8 +197,8 @@ JANET_CORE_FN(cfun_array_peek, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_array_push, | ||||
|               "(array/push arr x)", | ||||
|               "Insert an element in the end of an array. Modifies the input array and returns it.") { | ||||
|               "(array/push arr & xs)", | ||||
|               "Push all the elements of xs to the end of an array. Modifies the input array and returns it.") { | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetArray *array = janet_getarray(argv, 0); | ||||
|     if (INT32_MAX - argc + 1 <= array->count) { | ||||
| @@ -194,7 +214,7 @@ JANET_CORE_FN(cfun_array_push, | ||||
| JANET_CORE_FN(cfun_array_ensure, | ||||
|               "(array/ensure arr capacity growth)", | ||||
|               "Ensures that the memory backing the array is large enough for `capacity` " | ||||
|               "items at the given rate of growth. Capacity and growth must be integers. " | ||||
|               "items at the given rate of growth. `capacity` and `growth` must be integers. " | ||||
|               "If the backing capacity is already enough, then this function does nothing. " | ||||
|               "Otherwise, the backing memory will be reallocated so that there is enough space.") { | ||||
|     janet_fixarity(argc, 3); | ||||
| @@ -211,7 +231,7 @@ JANET_CORE_FN(cfun_array_slice, | ||||
|               "Takes a slice of array or tuple from `start` to `end`. The range is half open, " | ||||
|               "[start, end). Indexes can also be negative, indicating indexing from the " | ||||
|               "end of the array. By default, `start` is 0 and `end` is the length of the array. " | ||||
|               "Note that index -1 is synonymous with index `(length arrtup)` to allow a full " | ||||
|               "Note that if the range is negative, it is taken as (start, end] to allow a full " | ||||
|               "negative slice range. Returns a new array.") { | ||||
|     JanetView view = janet_getindexed(argv, 0); | ||||
|     JanetRange range = janet_getslice(argc, argv); | ||||
| @@ -255,12 +275,37 @@ JANET_CORE_FN(cfun_array_concat, | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_array_join, | ||||
|               "(array/join arr & parts)", | ||||
|               "Join a variable number of arrays and tuples into the first argument, " | ||||
|               "which must be an array. " | ||||
|               "Return the modified array `arr`.") { | ||||
|     int32_t i; | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetArray *array = janet_getarray(argv, 0); | ||||
|     for (i = 1; i < argc; i++) { | ||||
|         int32_t j, len = 0; | ||||
|         const Janet *vals = NULL; | ||||
|         if (!janet_indexed_view(argv[i], &vals, &len)) { | ||||
|             janet_panicf("expected indexed type for argument %d, got %v", i, argv[i]); | ||||
|         } | ||||
|         if (array->data == vals) { | ||||
|             int32_t newcount = array->count + len; | ||||
|             janet_array_ensure(array, newcount, 2); | ||||
|             janet_indexed_view(argv[i], &vals, &len); | ||||
|         } | ||||
|         for (j = 0; j < len; j++) | ||||
|             janet_array_push(array, vals[j]); | ||||
|     } | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_array_insert, | ||||
|               "(array/insert arr at & xs)", | ||||
|               "Insert all `xs` into array `arr` at index `at`. `at` should be an integer between " | ||||
|               "0 and the length of the array. A negative value for `at` will index backwards from " | ||||
|               "the end of the array, such that inserting at -1 appends to the array. " | ||||
|               "Returns the array.") { | ||||
|               "the end of the array, inserting after the index such that inserting at -1 appends to " | ||||
|               "the array. Returns the array.") { | ||||
|     size_t chunksize, restsize; | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetArray *array = janet_getarray(argv, 0); | ||||
| @@ -297,7 +342,7 @@ JANET_CORE_FN(cfun_array_remove, | ||||
|     int32_t at = janet_getinteger(argv, 1); | ||||
|     int32_t n = 1; | ||||
|     if (at < 0) { | ||||
|         at = array->count + at + 1; | ||||
|         at = array->count + at; | ||||
|     } | ||||
|     if (at < 0 || at > array->count) | ||||
|         janet_panicf("removal index %d out of range [0,%d]", at, array->count); | ||||
| @@ -352,6 +397,7 @@ JANET_CORE_FN(cfun_array_clear, | ||||
| void janet_lib_array(JanetTable *env) { | ||||
|     JanetRegExt array_cfuns[] = { | ||||
|         JANET_CORE_REG("array/new", cfun_array_new), | ||||
|         JANET_CORE_REG("array/weak", cfun_array_weak), | ||||
|         JANET_CORE_REG("array/new-filled", cfun_array_new_filled), | ||||
|         JANET_CORE_REG("array/fill", cfun_array_fill), | ||||
|         JANET_CORE_REG("array/pop", cfun_array_pop), | ||||
| @@ -364,6 +410,7 @@ void janet_lib_array(JanetTable *env) { | ||||
|         JANET_CORE_REG("array/remove", cfun_array_remove), | ||||
|         JANET_CORE_REG("array/trim", cfun_array_trim), | ||||
|         JANET_CORE_REG("array/clear", cfun_array_clear), | ||||
|         JANET_CORE_REG("array/join", cfun_array_join), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, array_cfuns); | ||||
|   | ||||
							
								
								
									
										118
									
								
								src/core/asm.c
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								src/core/asm.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -75,6 +75,7 @@ static const JanetInstructionDef janet_ops[] = { | ||||
|     {"cmp", JOP_COMPARE}, | ||||
|     {"cncl", JOP_CANCEL}, | ||||
|     {"div", JOP_DIVIDE}, | ||||
|     {"divf", JOP_DIVIDE_FLOOR}, | ||||
|     {"divim", JOP_DIVIDE_IMMEDIATE}, | ||||
|     {"eq", JOP_EQUALS}, | ||||
|     {"eqim", JOP_EQUALS_IMMEDIATE}, | ||||
| @@ -137,6 +138,7 @@ static const JanetInstructionDef janet_ops[] = { | ||||
|     {"sru", JOP_SHIFT_RIGHT_UNSIGNED}, | ||||
|     {"sruim", JOP_SHIFT_RIGHT_UNSIGNED_IMMEDIATE}, | ||||
|     {"sub", JOP_SUBTRACT}, | ||||
|     {"subim", JOP_SUBTRACT_IMMEDIATE}, | ||||
|     {"tcall", JOP_TAILCALL}, | ||||
|     {"tchck", JOP_TYPECHECK} | ||||
| }; | ||||
| @@ -187,7 +189,11 @@ static void janet_asm_longjmp(JanetAssembler *a) { | ||||
|  | ||||
| /* Throw some kind of assembly error */ | ||||
| static void janet_asm_error(JanetAssembler *a, const char *message) { | ||||
|     a->errmessage = janet_formatc("%s, instruction %d", message, a->errindex); | ||||
|     if (a->errindex < 0) { | ||||
|         a->errmessage = janet_formatc("%s", message); | ||||
|     } else { | ||||
|         a->errmessage = janet_formatc("%s, instruction %d", message, a->errindex); | ||||
|     } | ||||
|     janet_asm_longjmp(a); | ||||
| } | ||||
| #define janet_asm_assert(a, c, m) do { if (!(c)) janet_asm_error((a), (m)); } while (0) | ||||
| @@ -516,6 +522,7 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int | ||||
| #endif | ||||
|         if (NULL != a.parent) { | ||||
|             janet_asm_deinit(&a); | ||||
|             a.parent->errmessage = a.errmessage; | ||||
|             janet_asm_longjmp(a.parent); | ||||
|         } | ||||
|         result.funcdef = NULL; | ||||
| @@ -553,6 +560,13 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int | ||||
|     x = janet_get1(s, janet_ckeywordv("vararg")); | ||||
|     if (janet_truthy(x)) def->flags |= JANET_FUNCDEF_FLAG_VARARG; | ||||
|  | ||||
|     /* Initialize slotcount */ | ||||
|     def->slotcount = !!(def->flags & JANET_FUNCDEF_FLAG_VARARG) + def->arity; | ||||
|  | ||||
|     /* Check structarg */ | ||||
|     x = janet_get1(s, janet_ckeywordv("structarg")); | ||||
|     if (janet_truthy(x)) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG; | ||||
|  | ||||
|     /* Check source */ | ||||
|     x = janet_get1(s, janet_ckeywordv("source")); | ||||
|     if (janet_checktype(x, JANET_STRING)) def->source = janet_unwrap_string(x); | ||||
| @@ -597,6 +611,9 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int | ||||
|  | ||||
|     /* Parse sub funcdefs */ | ||||
|     x = janet_get1(s, janet_ckeywordv("closures")); | ||||
|     if (janet_checktype(x, JANET_NIL)) { | ||||
|         x = janet_get1(s, janet_ckeywordv("defs")); | ||||
|     } | ||||
|     if (janet_indexed_view(x, &arr, &count)) { | ||||
|         int32_t i; | ||||
|         for (i = 0; i < count; i++) { | ||||
| @@ -709,16 +726,70 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Set symbolmap */ | ||||
|     def->symbolmap = NULL; | ||||
|     def->symbolmap_length = 0; | ||||
|     x = janet_get1(s, janet_ckeywordv("symbolmap")); | ||||
|     if (janet_indexed_view(x, &arr, &count)) { | ||||
|         def->symbolmap_length = count; | ||||
|         def->symbolmap = janet_malloc(sizeof(JanetSymbolMap) * (size_t)count); | ||||
|         if (NULL == def->symbolmap) { | ||||
|             JANET_OUT_OF_MEMORY; | ||||
|         } | ||||
|         for (i = 0; i < count; i++) { | ||||
|             const Janet *tup; | ||||
|             Janet entry = arr[i]; | ||||
|             JanetSymbolMap ss; | ||||
|             if (!janet_checktype(entry, JANET_TUPLE)) { | ||||
|                 janet_asm_error(&a, "expected tuple"); | ||||
|             } | ||||
|             tup = janet_unwrap_tuple(entry); | ||||
|             if (janet_keyeq(tup[0], "upvalue")) { | ||||
|                 ss.birth_pc = UINT32_MAX; | ||||
|             } else if (!janet_checkint(tup[0])) { | ||||
|                 janet_asm_error(&a, "expected integer"); | ||||
|             } else { | ||||
|                 ss.birth_pc = janet_unwrap_integer(tup[0]); | ||||
|             } | ||||
|             if (!janet_checkint(tup[1])) { | ||||
|                 janet_asm_error(&a, "expected integer"); | ||||
|             } | ||||
|             if (!janet_checkint(tup[2])) { | ||||
|                 janet_asm_error(&a, "expected integer"); | ||||
|             } | ||||
|             if (!janet_checktype(tup[3], JANET_SYMBOL)) { | ||||
|                 janet_asm_error(&a, "expected symbol"); | ||||
|             } | ||||
|             ss.death_pc = janet_unwrap_integer(tup[1]); | ||||
|             ss.slot_index = janet_unwrap_integer(tup[2]); | ||||
|             ss.symbol = janet_unwrap_symbol(tup[3]); | ||||
|             def->symbolmap[i] = ss; | ||||
|         } | ||||
|     } | ||||
|     if (def->symbolmap_length) def->flags |= JANET_FUNCDEF_FLAG_HASSYMBOLMAP; | ||||
|  | ||||
|     /* Set environments */ | ||||
|     def->environments = | ||||
|         janet_realloc(def->environments, def->environments_length * sizeof(int32_t)); | ||||
|     if (NULL == def->environments) { | ||||
|     x = janet_get1(s, janet_ckeywordv("environments")); | ||||
|     if (janet_indexed_view(x, &arr, &count)) { | ||||
|         def->environments_length = count; | ||||
|         if (def->environments_length) { | ||||
|             def->environments = janet_realloc(def->environments, def->environments_length * sizeof(int32_t)); | ||||
|         } | ||||
|         for (int32_t i = 0; i < count; i++) { | ||||
|             if (!janet_checkint(arr[i])) { | ||||
|                 janet_asm_error(&a, "expected integer"); | ||||
|             } | ||||
|             def->environments[i] = janet_unwrap_integer(arr[i]); | ||||
|         } | ||||
|     } | ||||
|     if (def->environments_length && NULL == def->environments) { | ||||
|         JANET_OUT_OF_MEMORY; | ||||
|     } | ||||
|  | ||||
|     /* Verify the func def */ | ||||
|     if (janet_verify(def)) { | ||||
|         janet_asm_error(&a, "invalid assembly"); | ||||
|     int verify_status = janet_verify(def); | ||||
|     if (verify_status) { | ||||
|         janet_asm_errorv(&a, janet_formatc("invalid assembly (%d)", verify_status)); | ||||
|     } | ||||
|  | ||||
|     /* Add final flags */ | ||||
| @@ -861,6 +932,29 @@ static Janet janet_disasm_slotcount(JanetFuncDef *def) { | ||||
|     return janet_wrap_integer(def->slotcount); | ||||
| } | ||||
|  | ||||
| static Janet janet_disasm_symbolslots(JanetFuncDef *def) { | ||||
|     if (def->symbolmap == NULL) { | ||||
|         return janet_wrap_nil(); | ||||
|     } | ||||
|     JanetArray *symbolslots = janet_array(def->symbolmap_length); | ||||
|     Janet upvaluekw = janet_ckeywordv("upvalue"); | ||||
|     for (int32_t i = 0; i < def->symbolmap_length; i++) { | ||||
|         JanetSymbolMap ss = def->symbolmap[i]; | ||||
|         Janet *t = janet_tuple_begin(4); | ||||
|         if (ss.birth_pc == UINT32_MAX) { | ||||
|             t[0] = upvaluekw; | ||||
|         } else { | ||||
|             t[0] = janet_wrap_integer(ss.birth_pc); | ||||
|         } | ||||
|         t[1] = janet_wrap_integer(ss.death_pc); | ||||
|         t[2] = janet_wrap_integer(ss.slot_index); | ||||
|         t[3] = janet_wrap_symbol(ss.symbol); | ||||
|         symbolslots->data[i] = janet_wrap_tuple(janet_tuple_end(t)); | ||||
|     } | ||||
|     symbolslots->count = def->symbolmap_length; | ||||
|     return janet_wrap_array(symbolslots); | ||||
| } | ||||
|  | ||||
| static Janet janet_disasm_bytecode(JanetFuncDef *def) { | ||||
|     JanetArray *bcode = janet_array(def->bytecode_length); | ||||
|     for (int32_t i = 0; i < def->bytecode_length; i++) { | ||||
| @@ -884,6 +978,10 @@ static Janet janet_disasm_vararg(JanetFuncDef *def) { | ||||
|     return janet_wrap_boolean(def->flags & JANET_FUNCDEF_FLAG_VARARG); | ||||
| } | ||||
|  | ||||
| static Janet janet_disasm_structarg(JanetFuncDef *def) { | ||||
|     return janet_wrap_boolean(def->flags & JANET_FUNCDEF_FLAG_STRUCTARG); | ||||
| } | ||||
|  | ||||
| static Janet janet_disasm_constants(JanetFuncDef *def) { | ||||
|     JanetArray *constants = janet_array(def->constants_length); | ||||
|     for (int32_t i = 0; i < def->constants_length; i++) { | ||||
| @@ -933,8 +1031,10 @@ Janet janet_disasm(JanetFuncDef *def) { | ||||
|     janet_table_put(ret, janet_ckeywordv("bytecode"), janet_disasm_bytecode(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("source"), janet_disasm_source(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("vararg"), janet_disasm_vararg(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("structarg"), janet_disasm_structarg(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("name"), janet_disasm_name(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("slotcount"), janet_disasm_slotcount(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("symbolmap"), janet_disasm_symbolslots(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("constants"), janet_disasm_constants(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("sourcemap"), janet_disasm_sourcemap(def)); | ||||
|     janet_table_put(ret, janet_ckeywordv("environments"), janet_disasm_environments(def)); | ||||
| @@ -952,7 +1052,7 @@ JANET_CORE_FN(cfun_asm, | ||||
|     JanetAssembleResult res; | ||||
|     res = janet_asm(argv[0], 0); | ||||
|     if (res.status != JANET_ASSEMBLE_OK) { | ||||
|         janet_panics(res.error); | ||||
|         janet_panics(res.error ? res.error : janet_cstring("invalid assembly")); | ||||
|     } | ||||
|     return janet_wrap_function(janet_thunk(res.funcdef)); | ||||
| } | ||||
| @@ -971,6 +1071,7 @@ JANET_CORE_FN(cfun_disasm, | ||||
|               "* :source - name of source file that this function was compiled from.\n" | ||||
|               "* :name - name of function.\n" | ||||
|               "* :slotcount - how many virtual registers, or slots, this function uses. Corresponds to stack space used by function.\n" | ||||
|               "* :symbolmap - all symbols and their slots.\n" | ||||
|               "* :constants - an array of constants referenced by this function.\n" | ||||
|               "* :sourcemap - a mapping of each bytecode instruction to a line and column in the source file.\n" | ||||
|               "* :environments - an internal mapping of which enclosing functions are referenced for bindings.\n" | ||||
| @@ -986,6 +1087,7 @@ JANET_CORE_FN(cfun_disasm, | ||||
|         if (!janet_cstrcmp(kw, "source")) return janet_disasm_source(f->def); | ||||
|         if (!janet_cstrcmp(kw, "name")) return janet_disasm_name(f->def); | ||||
|         if (!janet_cstrcmp(kw, "vararg")) return janet_disasm_vararg(f->def); | ||||
|         if (!janet_cstrcmp(kw, "structarg")) return janet_disasm_structarg(f->def); | ||||
|         if (!janet_cstrcmp(kw, "slotcount")) return janet_disasm_slotcount(f->def); | ||||
|         if (!janet_cstrcmp(kw, "constants")) return janet_disasm_constants(f->def); | ||||
|         if (!janet_cstrcmp(kw, "sourcemap")) return janet_disasm_sourcemap(f->def); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -28,8 +28,15 @@ | ||||
| #include "state.h" | ||||
| #endif | ||||
|  | ||||
| /* Allow for managed buffers that cannot realloc/free their backing memory */ | ||||
| static void janet_buffer_can_realloc(JanetBuffer *buffer) { | ||||
|     if (buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC) { | ||||
|         janet_panic("buffer cannot reallocate foreign memory"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Initialize a buffer */ | ||||
| JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity) { | ||||
| static JanetBuffer *janet_buffer_init_impl(JanetBuffer *buffer, int32_t capacity) { | ||||
|     uint8_t *data = NULL; | ||||
|     if (capacity < 4) capacity = 4; | ||||
|     janet_gcpressure(capacity); | ||||
| @@ -43,15 +50,37 @@ JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity) { | ||||
|     return buffer; | ||||
| } | ||||
|  | ||||
| /* Initialize a buffer */ | ||||
| JanetBuffer *janet_buffer_init(JanetBuffer *buffer, int32_t capacity) { | ||||
|     janet_buffer_init_impl(buffer, capacity); | ||||
|     buffer->gc.data.next = NULL; | ||||
|     buffer->gc.flags = JANET_MEM_DISABLED; | ||||
|     return buffer; | ||||
| } | ||||
|  | ||||
| /* Initialize an unmanaged buffer */ | ||||
| JanetBuffer *janet_pointer_buffer_unsafe(void *memory, int32_t capacity, int32_t count) { | ||||
|     if (count < 0) janet_panic("count < 0"); | ||||
|     if (capacity < count) janet_panic("capacity < count"); | ||||
|     JanetBuffer *buffer = janet_gcalloc(JANET_MEMORY_BUFFER, sizeof(JanetBuffer)); | ||||
|     buffer->gc.flags |= JANET_BUFFER_FLAG_NO_REALLOC; | ||||
|     buffer->capacity = capacity; | ||||
|     buffer->count = count; | ||||
|     buffer->data = (uint8_t *) memory; | ||||
|     return buffer; | ||||
| } | ||||
|  | ||||
| /* Deinitialize a buffer (free data memory) */ | ||||
| void janet_buffer_deinit(JanetBuffer *buffer) { | ||||
|     janet_free(buffer->data); | ||||
|     if (!(buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC)) { | ||||
|         janet_free(buffer->data); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Initialize a buffer */ | ||||
| JanetBuffer *janet_buffer(int32_t capacity) { | ||||
|     JanetBuffer *buffer = janet_gcalloc(JANET_MEMORY_BUFFER, sizeof(JanetBuffer)); | ||||
|     return janet_buffer_init(buffer, capacity); | ||||
|     return janet_buffer_init_impl(buffer, capacity); | ||||
| } | ||||
|  | ||||
| /* Ensure that the buffer has enough internal capacity */ | ||||
| @@ -59,6 +88,7 @@ void janet_buffer_ensure(JanetBuffer *buffer, int32_t capacity, int32_t growth) | ||||
|     uint8_t *new_data; | ||||
|     uint8_t *old = buffer->data; | ||||
|     if (capacity <= buffer->capacity) return; | ||||
|     janet_buffer_can_realloc(buffer); | ||||
|     int64_t big_capacity = ((int64_t) capacity) * growth; | ||||
|     capacity = big_capacity > INT32_MAX ? INT32_MAX : (int32_t) big_capacity; | ||||
|     janet_gcpressure(capacity - buffer->capacity); | ||||
| @@ -91,6 +121,7 @@ void janet_buffer_extra(JanetBuffer *buffer, int32_t n) { | ||||
|     } | ||||
|     int32_t new_size = buffer->count + n; | ||||
|     if (new_size > buffer->capacity) { | ||||
|         janet_buffer_can_realloc(buffer); | ||||
|         int32_t new_capacity = (new_size > (INT32_MAX / 2)) ? INT32_MAX : (new_size * 2); | ||||
|         uint8_t *new_data = janet_realloc(buffer->data, new_capacity * sizeof(uint8_t)); | ||||
|         janet_gcpressure(new_capacity - buffer->capacity); | ||||
| @@ -104,8 +135,7 @@ void janet_buffer_extra(JanetBuffer *buffer, int32_t n) { | ||||
|  | ||||
| /* Push a cstring to buffer */ | ||||
| void janet_buffer_push_cstring(JanetBuffer *buffer, const char *cstring) { | ||||
|     int32_t len = 0; | ||||
|     while (cstring[len]) ++len; | ||||
|     int32_t len = (int32_t) strlen(cstring); | ||||
|     janet_buffer_push_bytes(buffer, (const uint8_t *) cstring, len); | ||||
| } | ||||
|  | ||||
| @@ -164,7 +194,7 @@ void janet_buffer_push_u64(JanetBuffer *buffer, uint64_t x) { | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_new, | ||||
|               "(buffer/new capacity)", | ||||
|               "Creates a new, empty buffer with enough backing memory for capacity bytes. " | ||||
|               "Creates a new, empty buffer with enough backing memory for `capacity` bytes. " | ||||
|               "Returns a new buffer of length 0.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     int32_t cap = janet_getinteger(argv, 0); | ||||
| @@ -174,21 +204,36 @@ JANET_CORE_FN(cfun_buffer_new, | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_new_filled, | ||||
|               "(buffer/new-filled count &opt byte)", | ||||
|               "Creates a new buffer of length count filled with byte. By default, byte is 0. " | ||||
|               "Creates a new buffer of length `count` filled with `byte`. By default, `byte` is 0. " | ||||
|               "Returns the new buffer.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     int32_t count = janet_getinteger(argv, 0); | ||||
|     if (count < 0) count = 0; | ||||
|     int32_t byte = 0; | ||||
|     if (argc == 2) { | ||||
|         byte = janet_getinteger(argv, 1) & 0xFF; | ||||
|     } | ||||
|     JanetBuffer *buffer = janet_buffer(count); | ||||
|     if (buffer->data) | ||||
|     if (buffer->data && count > 0) | ||||
|         memset(buffer->data, byte, count); | ||||
|     buffer->count = count; | ||||
|     return janet_wrap_buffer(buffer); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_frombytes, | ||||
|               "(buffer/from-bytes & byte-vals)", | ||||
|               "Creates a buffer from integer parameters with byte values. All integers " | ||||
|               "will be coerced to the range of 1 byte 0-255.") { | ||||
|     int32_t i; | ||||
|     JanetBuffer *buffer = janet_buffer(argc); | ||||
|     for (i = 0; i < argc; i++) { | ||||
|         int32_t c = janet_getinteger(argv, i); | ||||
|         buffer->data[i] = c & 0xFF; | ||||
|     } | ||||
|     buffer->count = argc; | ||||
|     return janet_wrap_buffer(buffer); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_fill, | ||||
|               "(buffer/fill buffer &opt byte)", | ||||
|               "Fill up a buffer with bytes, defaulting to 0s. Does not change the buffer's length. " | ||||
| @@ -211,6 +256,7 @@ JANET_CORE_FN(cfun_buffer_trim, | ||||
|               "modified buffer.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     janet_buffer_can_realloc(buffer); | ||||
|     if (buffer->count < buffer->capacity) { | ||||
|         int32_t newcap = buffer->count > 4 ? buffer->count : 4; | ||||
|         uint8_t *newData = janet_realloc(buffer->data, newcap); | ||||
| @@ -274,17 +320,135 @@ JANET_CORE_FN(cfun_buffer_chars, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push, | ||||
|               "(buffer/push buffer & xs)", | ||||
|               "Push both individual bytes and byte sequences to a buffer. For each x in xs, " | ||||
|               "push the byte if x is an integer, otherwise push the bytesequence to the buffer. " | ||||
|               "Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. " | ||||
|               "Returns the modified buffer. " | ||||
|               "Will throw an error if the buffer overflows.") { | ||||
|     int32_t i; | ||||
|     janet_arity(argc, 1, -1); | ||||
| static int should_reverse_bytes(const Janet *argv, int32_t argc) { | ||||
|     JanetKeyword order_kw = janet_getkeyword(argv, argc); | ||||
|     if (!janet_cstrcmp(order_kw, "le")) { | ||||
| #if JANET_BIG_ENDIAN | ||||
|         return 1; | ||||
| #endif | ||||
|     } else if (!janet_cstrcmp(order_kw, "be")) { | ||||
| #if JANET_LITTLE_ENDIAN | ||||
|         return 1; | ||||
| #endif | ||||
|     } else if (!janet_cstrcmp(order_kw, "native")) { | ||||
|         return 0; | ||||
|     } else { | ||||
|         janet_panicf("expected endianness :le, :be or :native, got %v", argv[1]); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static void reverse_u32(uint8_t bytes[4]) { | ||||
|     uint8_t temp; | ||||
|     temp = bytes[3]; | ||||
|     bytes[3] = bytes[0]; | ||||
|     bytes[0] = temp; | ||||
|     temp = bytes[2]; | ||||
|     bytes[2] = bytes[1]; | ||||
|     bytes[1] = temp; | ||||
| } | ||||
|  | ||||
| static void reverse_u64(uint8_t bytes[8]) { | ||||
|     uint8_t temp; | ||||
|     temp = bytes[7]; | ||||
|     bytes[7] = bytes[0]; | ||||
|     bytes[0] = temp; | ||||
|     temp = bytes[6]; | ||||
|     bytes[6] = bytes[1]; | ||||
|     bytes[1] = temp; | ||||
|     temp = bytes[5]; | ||||
|     bytes[5] = bytes[2]; | ||||
|     bytes[2] = temp; | ||||
|     temp = bytes[4]; | ||||
|     bytes[4] = bytes[3]; | ||||
|     bytes[3] = temp; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_uint16, | ||||
|               "(buffer/push-uint16 buffer order data)", | ||||
|               "Push a 16 bit unsigned integer data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     for (i = 1; i < argc; i++) { | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     uint16_t data = janet_getuinteger16(argv, 2); | ||||
|     uint8_t bytes[sizeof(data)]; | ||||
|     memcpy(bytes, &data, sizeof(bytes)); | ||||
|     if (reverse) { | ||||
|         uint8_t temp = bytes[1]; | ||||
|         bytes[1] = bytes[0]; | ||||
|         bytes[0] = temp; | ||||
|     } | ||||
|     janet_buffer_push_bytes(buffer, bytes, sizeof(bytes)); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_uint32, | ||||
|               "(buffer/push-uint32 buffer order data)", | ||||
|               "Push a 32 bit unsigned integer data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     uint32_t data = janet_getuinteger(argv, 2); | ||||
|     uint8_t bytes[sizeof(data)]; | ||||
|     memcpy(bytes, &data, sizeof(bytes)); | ||||
|     if (reverse) | ||||
|         reverse_u32(bytes); | ||||
|     janet_buffer_push_bytes(buffer, bytes, sizeof(bytes)); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_uint64, | ||||
|               "(buffer/push-uint64 buffer order data)", | ||||
|               "Push a 64 bit unsigned integer data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     uint64_t data = janet_getuinteger64(argv, 2); | ||||
|     uint8_t bytes[sizeof(data)]; | ||||
|     memcpy(bytes, &data, sizeof(bytes)); | ||||
|     if (reverse) | ||||
|         reverse_u64(bytes); | ||||
|     janet_buffer_push_bytes(buffer, bytes, sizeof(bytes)); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_float32, | ||||
|               "(buffer/push-float32 buffer order data)", | ||||
|               "Push the underlying bytes of a 32 bit float data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     float data = (float) janet_getnumber(argv, 2); | ||||
|     uint8_t bytes[sizeof(data)]; | ||||
|     memcpy(bytes, &data, sizeof(bytes)); | ||||
|     if (reverse) | ||||
|         reverse_u32(bytes); | ||||
|     janet_buffer_push_bytes(buffer, bytes, sizeof(bytes)); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_float64, | ||||
|               "(buffer/push-float64 buffer order data)", | ||||
|               "Push the underlying bytes of a 64 bit float data onto the end of the buffer. " | ||||
|               "Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 3); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int reverse = should_reverse_bytes(argv, 1); | ||||
|     double data = janet_getnumber(argv, 2); | ||||
|     uint8_t bytes[sizeof(data)]; | ||||
|     memcpy(bytes, &data, sizeof(bytes)); | ||||
|     if (reverse) | ||||
|         reverse_u64(bytes); | ||||
|     janet_buffer_push_bytes(buffer, bytes, sizeof(bytes)); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| static void buffer_push_impl(JanetBuffer *buffer, Janet *argv, int32_t argc_offset, int32_t argc) { | ||||
|     for (int32_t i = argc_offset; i < argc; i++) { | ||||
|         if (janet_checktype(argv[i], JANET_NUMBER)) { | ||||
|             janet_buffer_push_u8(buffer, (uint8_t)(janet_getinteger(argv, i) & 0xFF)); | ||||
|         } else { | ||||
| @@ -296,9 +460,39 @@ JANET_CORE_FN(cfun_buffer_push, | ||||
|             janet_buffer_push_bytes(buffer, view.bytes, view.len); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push_at, | ||||
|               "(buffer/push-at buffer index & xs)", | ||||
|               "Same as buffer/push, but copies the new data into the buffer " | ||||
|               " at index `index`.") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int32_t index = janet_getinteger(argv, 1); | ||||
|     int32_t old_count = buffer->count; | ||||
|     if (index < 0 || index > old_count) { | ||||
|         janet_panicf("index out of range [0, %d)", old_count); | ||||
|     } | ||||
|     buffer->count = index; | ||||
|     buffer_push_impl(buffer, argv, 2, argc); | ||||
|     if (buffer->count < old_count) { | ||||
|         buffer->count = old_count; | ||||
|     } | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_push, | ||||
|               "(buffer/push buffer & xs)", | ||||
|               "Push both individual bytes and byte sequences to a buffer. For each x in xs, " | ||||
|               "push the byte if x is an integer, otherwise push the bytesequence to the buffer. " | ||||
|               "Thus, this function behaves like both `buffer/push-string` and `buffer/push-byte`. " | ||||
|               "Returns the modified buffer. " | ||||
|               "Will throw an error if the buffer overflows.") { | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     buffer_push_impl(buffer, argv, 1, argc); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_clear, | ||||
|               "(buffer/clear buffer)", | ||||
| @@ -312,7 +506,7 @@ JANET_CORE_FN(cfun_buffer_clear, | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_popn, | ||||
|               "(buffer/popn buffer n)", | ||||
|               "Removes the last n bytes from the buffer. Returns the modified buffer.") { | ||||
|               "Removes the last `n` bytes from the buffer. Returns the modified buffer.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int32_t n = janet_getinteger(argv, 1); | ||||
| @@ -327,9 +521,9 @@ JANET_CORE_FN(cfun_buffer_popn, | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_slice, | ||||
|               "(buffer/slice bytes &opt start end)", | ||||
|               "Takes a slice of a byte sequence from start to end. The range is half open, " | ||||
|               "Takes a slice of a byte sequence from `start` to `end`. The range is half open, " | ||||
|               "[start, end). Indexes can also be negative, indicating indexing from the end of the " | ||||
|               "end of the array. By default, start is 0 and end is the length of the buffer. " | ||||
|               "end of the array. By default, `start` is 0 and `end` is the length of the buffer. " | ||||
|               "Returns a new buffer.") { | ||||
|     JanetByteView view = janet_getbytes(argv, 0); | ||||
|     JanetRange range = janet_getslice(argc, argv); | ||||
| @@ -399,22 +593,24 @@ JANET_CORE_FN(cfun_buffer_bittoggle, | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_blit, | ||||
|               "(buffer/blit dest src &opt dest-start src-start src-end)", | ||||
|               "Insert the contents of src into dest. Can optionally take indices that " | ||||
|               "indicate which part of src to copy into which part of dest. Indices can be " | ||||
|               "negative to index from the end of src or dest. Returns dest.") { | ||||
|               "Insert the contents of `src` into `dest`. Can optionally take indices that " | ||||
|               "indicate which part of `src` to copy into which part of `dest`. Indices can be " | ||||
|               "negative in order to index from the end of `src` or `dest`. Returns `dest`.") { | ||||
|     janet_arity(argc, 2, 5); | ||||
|     JanetBuffer *dest = janet_getbuffer(argv, 0); | ||||
|     JanetByteView src = janet_getbytes(argv, 1); | ||||
|     int same_buf = src.bytes == dest->data; | ||||
|     int32_t offset_dest = 0; | ||||
|     int32_t offset_src = 0; | ||||
|     if (argc > 2) | ||||
|     if (argc > 2 && !janet_checktype(argv[2], JANET_NIL)) | ||||
|         offset_dest = janet_gethalfrange(argv, 2, dest->count, "dest-start"); | ||||
|     if (argc > 3) | ||||
|     if (argc > 3 && !janet_checktype(argv[3], JANET_NIL)) | ||||
|         offset_src = janet_gethalfrange(argv, 3, src.len, "src-start"); | ||||
|     int32_t length_src; | ||||
|     if (argc > 4) { | ||||
|         int32_t src_end = janet_gethalfrange(argv, 4, src.len, "src-end"); | ||||
|         int32_t src_end = src.len; | ||||
|         if (!janet_checktype(argv[4], JANET_NIL)) | ||||
|             src_end = janet_gethalfrange(argv, 4, src.len, "src-end"); | ||||
|         length_src = src_end - offset_src; | ||||
|         if (length_src < 0) length_src = 0; | ||||
|     } else { | ||||
| @@ -441,7 +637,7 @@ JANET_CORE_FN(cfun_buffer_blit, | ||||
| JANET_CORE_FN(cfun_buffer_format, | ||||
|               "(buffer/format buffer format & args)", | ||||
|               "Snprintf like functionality for printing values into a buffer. Returns " | ||||
|               " the modified buffer.") { | ||||
|               "the modified buffer.") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     const char *strfrmt = (const char *) janet_getstring(argv, 1); | ||||
| @@ -449,16 +645,44 @@ JANET_CORE_FN(cfun_buffer_format, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_buffer_format_at, | ||||
|               "(buffer/format-at buffer at format & args)", | ||||
|               "Snprintf like functionality for printing values into a buffer. Returns " | ||||
|               "the modified buffer.") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetBuffer *buffer = janet_getbuffer(argv, 0); | ||||
|     int32_t at = janet_getinteger(argv, 1); | ||||
|     if (at < 0) { | ||||
|         at += buffer->count + 1; | ||||
|     } | ||||
|     if (at > buffer->count || at < 0) janet_panicf("expected index at to be in range [0, %d), got %d", buffer->count, at); | ||||
|     int32_t oldcount = buffer->count; | ||||
|     buffer->count = at; | ||||
|     const char *strfrmt = (const char *) janet_getstring(argv, 2); | ||||
|     janet_buffer_format(buffer, strfrmt, 2, argc, argv); | ||||
|     if (buffer->count < oldcount) { | ||||
|         buffer->count = oldcount; | ||||
|     } | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| void janet_lib_buffer(JanetTable *env) { | ||||
|     JanetRegExt buffer_cfuns[] = { | ||||
|         JANET_CORE_REG("buffer/new", cfun_buffer_new), | ||||
|         JANET_CORE_REG("buffer/new-filled", cfun_buffer_new_filled), | ||||
|         JANET_CORE_REG("buffer/from-bytes", cfun_buffer_frombytes), | ||||
|         JANET_CORE_REG("buffer/fill", cfun_buffer_fill), | ||||
|         JANET_CORE_REG("buffer/trim", cfun_buffer_trim), | ||||
|         JANET_CORE_REG("buffer/push-byte", cfun_buffer_u8), | ||||
|         JANET_CORE_REG("buffer/push-word", cfun_buffer_word), | ||||
|         JANET_CORE_REG("buffer/push-string", cfun_buffer_chars), | ||||
|         JANET_CORE_REG("buffer/push-uint16", cfun_buffer_push_uint16), | ||||
|         JANET_CORE_REG("buffer/push-uint32", cfun_buffer_push_uint32), | ||||
|         JANET_CORE_REG("buffer/push-uint64", cfun_buffer_push_uint64), | ||||
|         JANET_CORE_REG("buffer/push-float32", cfun_buffer_push_float32), | ||||
|         JANET_CORE_REG("buffer/push-float64", cfun_buffer_push_float64), | ||||
|         JANET_CORE_REG("buffer/push", cfun_buffer_push), | ||||
|         JANET_CORE_REG("buffer/push-at", cfun_buffer_push_at), | ||||
|         JANET_CORE_REG("buffer/popn", cfun_buffer_popn), | ||||
|         JANET_CORE_REG("buffer/clear", cfun_buffer_clear), | ||||
|         JANET_CORE_REG("buffer/slice", cfun_buffer_slice), | ||||
| @@ -468,6 +692,7 @@ void janet_lib_buffer(JanetTable *env) { | ||||
|         JANET_CORE_REG("buffer/bit-toggle", cfun_buffer_bittoggle), | ||||
|         JANET_CORE_REG("buffer/blit", cfun_buffer_blit), | ||||
|         JANET_CORE_REG("buffer/format", cfun_buffer_format), | ||||
|         JANET_CORE_REG("buffer/format-at", cfun_buffer_format_at), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, buffer_cfuns); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -25,6 +25,7 @@ | ||||
| #include <janet.h> | ||||
| #include "gc.h" | ||||
| #include "util.h" | ||||
| #include "regalloc.h" | ||||
| #endif | ||||
|  | ||||
| /* Look up table for instructions */ | ||||
| @@ -36,11 +37,13 @@ enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = { | ||||
|     JINT_0, /* JOP_RETURN_NIL, */ | ||||
|     JINT_SSI, /* JOP_ADD_IMMEDIATE, */ | ||||
|     JINT_SSS, /* JOP_ADD, */ | ||||
|     JINT_SSI, /* JOP_SUBTRACT_IMMEDIATE, */ | ||||
|     JINT_SSS, /* JOP_SUBTRACT, */ | ||||
|     JINT_SSI, /* JOP_MULTIPLY_IMMEDIATE, */ | ||||
|     JINT_SSS, /* JOP_MULTIPLY, */ | ||||
|     JINT_SSI, /* JOP_DIVIDE_IMMEDIATE, */ | ||||
|     JINT_SSS, /* JOP_DIVIDE, */ | ||||
|     JINT_SSS, /* JOP_DIVIDE_FLOOR */ | ||||
|     JINT_SSS, /* JOP_MODULO, */ | ||||
|     JINT_SSS, /* JOP_REMAINDER, */ | ||||
|     JINT_SSS, /* JOP_BAND, */ | ||||
| @@ -106,6 +109,294 @@ enum JanetInstructionType janet_instructions[JOP_INSTRUCTION_COUNT] = { | ||||
|     JINT_SSS /* JOP_CANCEL, */ | ||||
| }; | ||||
|  | ||||
| /* Remove all noops while preserving jumps and debugging information. | ||||
|  * Useful as part of a filtering compiler pass. */ | ||||
| void janet_bytecode_remove_noops(JanetFuncDef *def) { | ||||
|  | ||||
|     /* Get an instruction rewrite map so we can rewrite jumps */ | ||||
|     uint32_t *pc_map = janet_smalloc(sizeof(uint32_t) * (1 + def->bytecode_length)); | ||||
|     uint32_t new_bytecode_length = 0; | ||||
|     for (int32_t i = 0; i < def->bytecode_length; i++) { | ||||
|         uint32_t instr = def->bytecode[i]; | ||||
|         uint32_t opcode = instr & 0x7F; | ||||
|         pc_map[i] = new_bytecode_length; | ||||
|         if (opcode != JOP_NOOP) { | ||||
|             new_bytecode_length++; | ||||
|         } | ||||
|     } | ||||
|     pc_map[def->bytecode_length] = new_bytecode_length; | ||||
|  | ||||
|     /* Linear scan rewrite bytecode and sourcemap. Also fix jumps. */ | ||||
|     int32_t j = 0; | ||||
|     for (int32_t i = 0; i < def->bytecode_length; i++) { | ||||
|         uint32_t instr = def->bytecode[i]; | ||||
|         uint32_t opcode = instr & 0x7F; | ||||
|         int32_t old_jump_target = 0; | ||||
|         int32_t new_jump_target = 0; | ||||
|         switch (opcode) { | ||||
|             case JOP_NOOP: | ||||
|                 continue; | ||||
|             case JOP_JUMP: | ||||
|                 /* relative pc is in DS field of instruction */ | ||||
|                 old_jump_target = i + (((int32_t)instr) >> 8); | ||||
|                 new_jump_target = pc_map[old_jump_target]; | ||||
|                 instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 8; | ||||
|                 break; | ||||
|             case JOP_JUMP_IF: | ||||
|             case JOP_JUMP_IF_NIL: | ||||
|             case JOP_JUMP_IF_NOT: | ||||
|             case JOP_JUMP_IF_NOT_NIL: | ||||
|                 /* relative pc is in ES field of instruction */ | ||||
|                 old_jump_target = i + (((int32_t)instr) >> 16); | ||||
|                 new_jump_target = pc_map[old_jump_target]; | ||||
|                 instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 16; | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|         def->bytecode[j] = instr; | ||||
|         if (def->sourcemap != NULL) { | ||||
|             def->sourcemap[j] = def->sourcemap[i]; | ||||
|         } | ||||
|         j++; | ||||
|     } | ||||
|  | ||||
|     /* Rewrite symbolmap */ | ||||
|     for (int32_t i = 0; i < def->symbolmap_length; i++) { | ||||
|         JanetSymbolMap *sm = def->symbolmap + i; | ||||
|         /* Don't rewrite upvalue mappings */ | ||||
|         if (sm->birth_pc < UINT32_MAX) { | ||||
|             sm->birth_pc = pc_map[sm->birth_pc]; | ||||
|             sm->death_pc = pc_map[sm->death_pc]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     def->bytecode_length = new_bytecode_length; | ||||
|     def->bytecode = janet_realloc(def->bytecode, def->bytecode_length * sizeof(uint32_t)); | ||||
|     janet_sfree(pc_map); | ||||
| } | ||||
|  | ||||
| /* Remove redundant loads, moves and other instructions if possible and convert them to | ||||
|  * noops. Input is assumed valid bytecode. */ | ||||
| void janet_bytecode_movopt(JanetFuncDef *def) { | ||||
|     JanetcRegisterAllocator ra; | ||||
|     int recur = 1; | ||||
|  | ||||
|     /* Iterate this until no more instructions can be removed. */ | ||||
|     while (recur) { | ||||
|         janetc_regalloc_init(&ra); | ||||
|  | ||||
|         /* Look for slots that have writes but no reads (and aren't in the closure bitset). */ | ||||
|         if (def->closure_bitset != NULL) { | ||||
|             for (int32_t i = 0; i < def->slotcount; i++) { | ||||
|                 int32_t index = i >> 5; | ||||
|                 uint32_t mask = 1U << (((uint32_t) i) & 31); | ||||
|                 if (def->closure_bitset[index] & mask) { | ||||
|                     janetc_regalloc_touch(&ra, i); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| #define AA ((instr >> 8)  & 0xFF) | ||||
| #define BB ((instr >> 16) & 0xFF) | ||||
| #define CC (instr >> 24) | ||||
| #define DD (instr >> 8) | ||||
| #define EE (instr >> 16) | ||||
|  | ||||
|         /* Check reads and writes */ | ||||
|         for (int32_t i = 0; i < def->bytecode_length; i++) { | ||||
|             uint32_t instr = def->bytecode[i]; | ||||
|             switch (instr & 0x7F) { | ||||
|  | ||||
|                 /* Group instructions my how they read from slots */ | ||||
|  | ||||
|                 /* No reads or writes */ | ||||
|                 default: | ||||
|                     janet_assert(0, "unhandled instruction"); | ||||
|                 case JOP_JUMP: | ||||
|                 case JOP_NOOP: | ||||
|                 case JOP_RETURN_NIL: | ||||
|                 /* Write A */ | ||||
|                 case JOP_LOAD_INTEGER: | ||||
|                 case JOP_LOAD_CONSTANT: | ||||
|                 case JOP_LOAD_UPVALUE: | ||||
|                 case JOP_CLOSURE: | ||||
|                 /* Write D */ | ||||
|                 case JOP_LOAD_NIL: | ||||
|                 case JOP_LOAD_TRUE: | ||||
|                 case JOP_LOAD_FALSE: | ||||
|                 case JOP_LOAD_SELF: | ||||
|                     break; | ||||
|                 case JOP_MAKE_ARRAY: | ||||
|                 case JOP_MAKE_BUFFER: | ||||
|                 case JOP_MAKE_STRING: | ||||
|                 case JOP_MAKE_STRUCT: | ||||
|                 case JOP_MAKE_TABLE: | ||||
|                 case JOP_MAKE_TUPLE: | ||||
|                 case JOP_MAKE_BRACKET_TUPLE: | ||||
|                     /* Reads from the stack, don't remove */ | ||||
|                     janetc_regalloc_touch(&ra, DD); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read A */ | ||||
|                 case JOP_ERROR: | ||||
|                 case JOP_TYPECHECK: | ||||
|                 case JOP_JUMP_IF: | ||||
|                 case JOP_JUMP_IF_NOT: | ||||
|                 case JOP_JUMP_IF_NIL: | ||||
|                 case JOP_JUMP_IF_NOT_NIL: | ||||
|                 case JOP_SET_UPVALUE: | ||||
|                 /* Write E, Read A */ | ||||
|                 case JOP_MOVE_FAR: | ||||
|                     janetc_regalloc_touch(&ra, AA); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read B */ | ||||
|                 case JOP_SIGNAL: | ||||
|                 /* Write A, Read B */ | ||||
|                 case JOP_ADD_IMMEDIATE: | ||||
|                 case JOP_SUBTRACT_IMMEDIATE: | ||||
|                 case JOP_MULTIPLY_IMMEDIATE: | ||||
|                 case JOP_DIVIDE_IMMEDIATE: | ||||
|                 case JOP_SHIFT_LEFT_IMMEDIATE: | ||||
|                 case JOP_SHIFT_RIGHT_IMMEDIATE: | ||||
|                 case JOP_SHIFT_RIGHT_UNSIGNED_IMMEDIATE: | ||||
|                 case JOP_GREATER_THAN_IMMEDIATE: | ||||
|                 case JOP_LESS_THAN_IMMEDIATE: | ||||
|                 case JOP_EQUALS_IMMEDIATE: | ||||
|                 case JOP_NOT_EQUALS_IMMEDIATE: | ||||
|                 case JOP_GET_INDEX: | ||||
|                     janetc_regalloc_touch(&ra, BB); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read D */ | ||||
|                 case JOP_RETURN: | ||||
|                 case JOP_PUSH: | ||||
|                 case JOP_PUSH_ARRAY: | ||||
|                 case JOP_TAILCALL: | ||||
|                     janetc_regalloc_touch(&ra, DD); | ||||
|                     break; | ||||
|  | ||||
|                 /* Write A, Read E */ | ||||
|                 case JOP_MOVE_NEAR: | ||||
|                 case JOP_LENGTH: | ||||
|                 case JOP_BNOT: | ||||
|                 case JOP_CALL: | ||||
|                     janetc_regalloc_touch(&ra, EE); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read A, B */ | ||||
|                 case JOP_PUT_INDEX: | ||||
|                     janetc_regalloc_touch(&ra, AA); | ||||
|                     janetc_regalloc_touch(&ra, BB); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read A, E */ | ||||
|                 case JOP_PUSH_2: | ||||
|                     janetc_regalloc_touch(&ra, AA); | ||||
|                     janetc_regalloc_touch(&ra, EE); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read B, C */ | ||||
|                 case JOP_PROPAGATE: | ||||
|                 /* Write A, Read B and C */ | ||||
|                 case JOP_BAND: | ||||
|                 case JOP_BOR: | ||||
|                 case JOP_BXOR: | ||||
|                 case JOP_ADD: | ||||
|                 case JOP_SUBTRACT: | ||||
|                 case JOP_MULTIPLY: | ||||
|                 case JOP_DIVIDE: | ||||
|                 case JOP_DIVIDE_FLOOR: | ||||
|                 case JOP_MODULO: | ||||
|                 case JOP_REMAINDER: | ||||
|                 case JOP_SHIFT_LEFT: | ||||
|                 case JOP_SHIFT_RIGHT: | ||||
|                 case JOP_SHIFT_RIGHT_UNSIGNED: | ||||
|                 case JOP_GREATER_THAN: | ||||
|                 case JOP_LESS_THAN: | ||||
|                 case JOP_EQUALS: | ||||
|                 case JOP_COMPARE: | ||||
|                 case JOP_IN: | ||||
|                 case JOP_GET: | ||||
|                 case JOP_GREATER_THAN_EQUAL: | ||||
|                 case JOP_LESS_THAN_EQUAL: | ||||
|                 case JOP_NOT_EQUALS: | ||||
|                 case JOP_CANCEL: | ||||
|                 case JOP_RESUME: | ||||
|                 case JOP_NEXT: | ||||
|                     janetc_regalloc_touch(&ra, BB); | ||||
|                     janetc_regalloc_touch(&ra, CC); | ||||
|                     break; | ||||
|  | ||||
|                 /* Read A, B, C */ | ||||
|                 case JOP_PUT: | ||||
|                 case JOP_PUSH_3: | ||||
|                     janetc_regalloc_touch(&ra, AA); | ||||
|                     janetc_regalloc_touch(&ra, BB); | ||||
|                     janetc_regalloc_touch(&ra, CC); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Iterate and set noops on instructions that make writes that no one ever reads. | ||||
|          * Only set noops for instructions with no side effects - moves, loads, etc. that can't | ||||
|          * raise errors (outside of systemic errors like oom or stack overflow). */ | ||||
|         recur = 0; | ||||
|         for (int32_t i = 0; i < def->bytecode_length; i++) { | ||||
|             uint32_t instr = def->bytecode[i]; | ||||
|             switch (instr & 0x7F) { | ||||
|                 default: | ||||
|                     break; | ||||
|                 /* Write D */ | ||||
|                 case JOP_LOAD_NIL: | ||||
|                 case JOP_LOAD_TRUE: | ||||
|                 case JOP_LOAD_FALSE: | ||||
|                 case JOP_LOAD_SELF: | ||||
|                 case JOP_MAKE_ARRAY: | ||||
|                 case JOP_MAKE_TUPLE: | ||||
|                 case JOP_MAKE_BRACKET_TUPLE: { | ||||
|                     if (!janetc_regalloc_check(&ra, DD)) { | ||||
|                         def->bytecode[i] = JOP_NOOP; | ||||
|                         recur = 1; | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|                 /* Write E, Read A */ | ||||
|                 case JOP_MOVE_FAR: { | ||||
|                     if (!janetc_regalloc_check(&ra, EE)) { | ||||
|                         def->bytecode[i] = JOP_NOOP; | ||||
|                         recur = 1; | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|                 /* Write A, Read E */ | ||||
|                 case JOP_MOVE_NEAR: | ||||
|                 /* Write A, Read B */ | ||||
|                 case JOP_GET_INDEX: | ||||
|                 /* Write A */ | ||||
|                 case JOP_LOAD_INTEGER: | ||||
|                 case JOP_LOAD_CONSTANT: | ||||
|                 case JOP_LOAD_UPVALUE: | ||||
|                 case JOP_CLOSURE: { | ||||
|                     if (!janetc_regalloc_check(&ra, AA)) { | ||||
|                         def->bytecode[i] = JOP_NOOP; | ||||
|                         recur = 1; | ||||
|                     } | ||||
|                 } | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         janetc_regalloc_deinit(&ra); | ||||
| #undef AA | ||||
| #undef BB | ||||
| #undef CC | ||||
| #undef DD | ||||
| #undef EE | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Verify some bytecode */ | ||||
| int janet_verify(JanetFuncDef *def) { | ||||
|     int vargs = !!(def->flags & JANET_FUNCDEF_FLAG_VARARG); | ||||
| @@ -218,6 +509,7 @@ JanetFuncDef *janet_funcdef_alloc(void) { | ||||
|     def->closure_bitset = NULL; | ||||
|     def->flags = 0; | ||||
|     def->slotcount = 0; | ||||
|     def->symbolmap = NULL; | ||||
|     def->arity = 0; | ||||
|     def->min_arity = 0; | ||||
|     def->max_arity = INT32_MAX; | ||||
| @@ -229,6 +521,7 @@ JanetFuncDef *janet_funcdef_alloc(void) { | ||||
|     def->constants_length = 0; | ||||
|     def->bytecode_length = 0; | ||||
|     def->environments_length = 0; | ||||
|     def->symbolmap_length = 0; | ||||
|     return def; | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										214
									
								
								src/core/capi.c
									
									
									
									
									
								
							
							
						
						
									
										214
									
								
								src/core/capi.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -25,14 +25,24 @@ | ||||
| #include <janet.h> | ||||
| #include "state.h" | ||||
| #include "fiber.h" | ||||
| #include "util.h" | ||||
| #endif | ||||
|  | ||||
| #ifndef JANET_SINGLE_THREADED | ||||
| #ifndef JANET_WINDOWS | ||||
| #include <pthread.h> | ||||
| #else | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_USE_STDATOMIC | ||||
| #include <stdatomic.h> | ||||
| /* We don't need stdatomic on most compilers since we use compiler builtins for atomic operations. | ||||
|  * Some (TCC), explicitly require using stdatomic.h and don't have any exposed builtins (that I know of). | ||||
|  * For TCC and similar compilers, one would need -std=c11 or similar then to get access. */ | ||||
| #endif | ||||
|  | ||||
| JANET_NO_RETURN static void janet_top_level_signal(const char *msg) { | ||||
| @@ -52,6 +62,18 @@ JANET_NO_RETURN static void janet_top_level_signal(const char *msg) { | ||||
|  | ||||
| void janet_signalv(JanetSignal sig, Janet message) { | ||||
|     if (janet_vm.return_reg != NULL) { | ||||
|         /* Should match logic in janet_call for coercing everything not ok to an error (no awaits, yields, etc.) */ | ||||
|         if (janet_vm.coerce_error && sig != JANET_SIGNAL_OK) { | ||||
| #ifdef JANET_EV | ||||
|             if (NULL != janet_vm.root_fiber && sig == JANET_SIGNAL_EVENT) { | ||||
|                 janet_vm.root_fiber->sched_id++; | ||||
|             } | ||||
| #endif | ||||
|             if (sig != JANET_SIGNAL_ERROR) { | ||||
|                 message = janet_wrap_string(janet_formatc("%v coerced from %s to error", message, janet_signal_names[sig])); | ||||
|             } | ||||
|             sig = JANET_SIGNAL_ERROR; | ||||
|         } | ||||
|         *janet_vm.return_reg = message; | ||||
|         if (NULL != janet_vm.fiber) { | ||||
|             janet_vm.fiber->flags |= JANET_FIBER_DID_LONGJUMP; | ||||
| @@ -209,12 +231,46 @@ const char *janet_optcstring(const Janet *argv, int32_t argc, int32_t n, const c | ||||
| #undef DEFINE_OPTLEN | ||||
|  | ||||
| const char *janet_getcstring(const Janet *argv, int32_t n) { | ||||
|     const uint8_t *jstr = janet_getstring(argv, n); | ||||
|     const char *cstr = (const char *)jstr; | ||||
|     if (strlen(cstr) != (size_t) janet_string_length(jstr)) { | ||||
|         janet_panic("string contains embedded 0s"); | ||||
|     if (!janet_checktype(argv[n], JANET_STRING)) { | ||||
|         janet_panic_type(argv[n], n, JANET_TFLAG_STRING); | ||||
|     } | ||||
|     return janet_getcbytes(argv, n); | ||||
| } | ||||
|  | ||||
| const char *janet_getcbytes(const Janet *argv, int32_t n) { | ||||
|     /* Ensure buffer 0-padded */ | ||||
|     if (janet_checktype(argv[n], JANET_BUFFER)) { | ||||
|         JanetBuffer *b = janet_unwrap_buffer(argv[n]); | ||||
|         if ((b->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC) && b->count == b->capacity) { | ||||
|             /* Make a copy with janet_smalloc in the rare case we have a buffer that | ||||
|              * cannot be realloced and pushing a 0 byte would panic. */ | ||||
|             char *new_string = janet_smalloc(b->count + 1); | ||||
|             memcpy(new_string, b->data, b->count); | ||||
|             new_string[b->count] = 0; | ||||
|             if (strlen(new_string) != (size_t) b->count) goto badzeros; | ||||
|             return new_string; | ||||
|         } else { | ||||
|             /* Ensure trailing 0 */ | ||||
|             janet_buffer_push_u8(b, 0); | ||||
|             b->count--; | ||||
|             if (strlen((char *)b->data) != (size_t) b->count) goto badzeros; | ||||
|             return (const char *) b->data; | ||||
|         } | ||||
|     } | ||||
|     JanetByteView view = janet_getbytes(argv, n); | ||||
|     const char *cstr = (const char *)view.bytes; | ||||
|     if (strlen(cstr) != (size_t) view.len) goto badzeros; | ||||
|     return cstr; | ||||
|  | ||||
| badzeros: | ||||
|     janet_panic("bytes contain embedded 0s"); | ||||
| } | ||||
|  | ||||
| const char *janet_optcbytes(const Janet *argv, int32_t argc, int32_t n, const char *dflt) { | ||||
|     if (n >= argc || janet_checktype(argv[n], JANET_NIL)) { | ||||
|         return dflt; | ||||
|     } | ||||
|     return janet_getcbytes(argv, n); | ||||
| } | ||||
|  | ||||
| int32_t janet_getnat(const Janet *argv, int32_t n) { | ||||
| @@ -259,12 +315,53 @@ int32_t janet_getinteger(const Janet *argv, int32_t n) { | ||||
|     return janet_unwrap_integer(x); | ||||
| } | ||||
|  | ||||
| uint32_t janet_getuinteger(const Janet *argv, int32_t n) { | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkuint(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 32 bit unsigned integer, got %v", n, x); | ||||
|     } | ||||
|     return (uint32_t) janet_unwrap_number(x); | ||||
| } | ||||
|  | ||||
| int16_t janet_getinteger16(const Janet *argv, int32_t n) { | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkint16(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 16 bit signed integer, got %v", n, x); | ||||
|     } | ||||
|     return (int16_t) janet_unwrap_number(x); | ||||
| } | ||||
|  | ||||
| uint16_t janet_getuinteger16(const Janet *argv, int32_t n) { | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkuint16(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 16 bit unsigned integer, got %v", n, x); | ||||
|     } | ||||
|     return (uint16_t) janet_unwrap_number(x); | ||||
| } | ||||
|  | ||||
|  | ||||
| int64_t janet_getinteger64(const Janet *argv, int32_t n) { | ||||
| #ifdef JANET_INT_TYPES | ||||
|     return janet_unwrap_s64(argv[n]); | ||||
| #else | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkint64(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 64 bit signed integer, got %v", n, x); | ||||
|     } | ||||
|     return (int64_t) janet_unwrap_number(x); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| uint64_t janet_getuinteger64(const Janet *argv, int32_t n) { | ||||
| #ifdef JANET_INT_TYPES | ||||
|     return janet_unwrap_u64(argv[n]); | ||||
| #else | ||||
|     Janet x = argv[n]; | ||||
|     if (!janet_checkuint64(x)) { | ||||
|         janet_panicf("bad slot #%d, expected 64 bit unsigned integer, got %v", n, x); | ||||
|     } | ||||
|     return (uint64_t) janet_unwrap_number(x); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| size_t janet_getsize(const Janet *argv, int32_t n) { | ||||
| @@ -280,16 +377,30 @@ int32_t janet_gethalfrange(const Janet *argv, int32_t n, int32_t length, const c | ||||
|     int32_t not_raw = raw; | ||||
|     if (not_raw < 0) not_raw += length + 1; | ||||
|     if (not_raw < 0 || not_raw > length) | ||||
|         janet_panicf("%s index %d out of range [%d,%d]", which, raw, -length - 1, length); | ||||
|         janet_panicf("%s index %d out of range [%d,%d]", which, (int64_t) raw, -(int64_t)length - 1, (int64_t) length); | ||||
|     return not_raw; | ||||
| } | ||||
|  | ||||
| int32_t janet_getstartrange(const Janet *argv, int32_t argc, int32_t n, int32_t length) { | ||||
|     if (n >= argc || janet_checktype(argv[n], JANET_NIL)) { | ||||
|         return 0; | ||||
|     } | ||||
|     return janet_gethalfrange(argv, n, length, "start"); | ||||
| } | ||||
|  | ||||
| int32_t janet_getendrange(const Janet *argv, int32_t argc, int32_t n, int32_t length) { | ||||
|     if (n >= argc || janet_checktype(argv[n], JANET_NIL)) { | ||||
|         return length; | ||||
|     } | ||||
|     return janet_gethalfrange(argv, n, length, "end"); | ||||
| } | ||||
|  | ||||
| int32_t janet_getargindex(const Janet *argv, int32_t n, int32_t length, const char *which) { | ||||
|     int32_t raw = janet_getinteger(argv, n); | ||||
|     int32_t not_raw = raw; | ||||
|     if (not_raw < 0) not_raw += length; | ||||
|     if (not_raw < 0 || not_raw > length) | ||||
|         janet_panicf("%s index %d out of range [%d,%d)", which, raw, -length, length); | ||||
|         janet_panicf("%s index %d out of range [%d,%d)", which, (int64_t)raw, -(int64_t)length, (int64_t)length); | ||||
|     return not_raw; | ||||
| } | ||||
|  | ||||
| @@ -336,24 +447,10 @@ JanetRange janet_getslice(int32_t argc, const Janet *argv) { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     JanetRange range; | ||||
|     int32_t length = janet_length(argv[0]); | ||||
|     if (argc == 1) { | ||||
|         range.start = 0; | ||||
|         range.end = length; | ||||
|     } else if (argc == 2) { | ||||
|         range.start = janet_checktype(argv[1], JANET_NIL) | ||||
|                       ? 0 | ||||
|                       : janet_gethalfrange(argv, 1, length, "start"); | ||||
|         range.end = length; | ||||
|     } else { | ||||
|         range.start = janet_checktype(argv[1], JANET_NIL) | ||||
|                       ? 0 | ||||
|                       : janet_gethalfrange(argv, 1, length, "start"); | ||||
|         range.end = janet_checktype(argv[2], JANET_NIL) | ||||
|                     ? length | ||||
|                     : janet_gethalfrange(argv, 2, length, "end"); | ||||
|         if (range.end < range.start) | ||||
|             range.end = range.start; | ||||
|     } | ||||
|     range.start = janet_getstartrange(argv, argc, 1, length); | ||||
|     range.end = janet_getendrange(argv, argc, 2, length); | ||||
|     if (range.end < range.start) | ||||
|         range.end = range.start; | ||||
|     return range; | ||||
| } | ||||
|  | ||||
| @@ -381,6 +478,33 @@ void janet_setdyn(const char *name, Janet value) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Create a function that when called, returns X. Trivial in Janet, a pain in C. */ | ||||
| JanetFunction *janet_thunk_delay(Janet x) { | ||||
|     static const uint32_t bytecode[] = { | ||||
|         JOP_LOAD_CONSTANT, | ||||
|         JOP_RETURN | ||||
|     }; | ||||
|     JanetFuncDef *def = janet_funcdef_alloc(); | ||||
|     def->arity = 0; | ||||
|     def->min_arity = 0; | ||||
|     def->max_arity = INT32_MAX; | ||||
|     def->flags = JANET_FUNCDEF_FLAG_VARARG; | ||||
|     def->slotcount = 1; | ||||
|     def->bytecode = janet_malloc(sizeof(bytecode)); | ||||
|     def->bytecode_length = (int32_t)(sizeof(bytecode) / sizeof(uint32_t)); | ||||
|     def->constants = janet_malloc(sizeof(Janet)); | ||||
|     def->constants_length = 1; | ||||
|     def->name = NULL; | ||||
|     if (!def->bytecode || !def->constants) { | ||||
|         JANET_OUT_OF_MEMORY; | ||||
|     } | ||||
|     def->constants[0] = x; | ||||
|     memcpy(def->bytecode, bytecode, sizeof(bytecode)); | ||||
|     janet_def_addflags(def); | ||||
|     /* janet_verify(def); */ | ||||
|     return janet_thunk(def); | ||||
| } | ||||
|  | ||||
| uint64_t janet_getflags(const Janet *argv, int32_t n, const char *flags) { | ||||
|     uint64_t ret = 0; | ||||
|     const uint8_t *keyw = janet_getkeyword(argv, n); | ||||
| @@ -433,9 +557,41 @@ void *janet_optabstract(const Janet *argv, int32_t argc, int32_t n, const JanetA | ||||
|     return janet_getabstract(argv, n, at); | ||||
| } | ||||
|  | ||||
| /* Atomic refcounts */ | ||||
|  | ||||
| JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x) { | ||||
| #ifdef _MSC_VER | ||||
|     return _InterlockedIncrement(x); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_fetch_add_explicit(x, 1, memory_order_relaxed) + 1; | ||||
| #else | ||||
|     return __atomic_add_fetch(x, 1, __ATOMIC_RELAXED); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x) { | ||||
| #ifdef _MSC_VER | ||||
|     return _InterlockedDecrement(x); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_fetch_add_explicit(x, -1, memory_order_acq_rel) - 1; | ||||
| #else | ||||
|     return __atomic_add_fetch(x, -1, __ATOMIC_ACQ_REL); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x) { | ||||
| #ifdef _MSC_VER | ||||
|     return _InterlockedOr(x, 0); | ||||
| #elif defined(JANET_USE_STDATOMIC) | ||||
|     return atomic_load_explicit(x, memory_order_acquire); | ||||
| #else | ||||
|     return __atomic_load_n(x, __ATOMIC_ACQUIRE); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| /* Some definitions for function-like macros */ | ||||
|  | ||||
| JANET_API JanetStructHead *(janet_struct_head)(const JanetKV *st) { | ||||
| JANET_API JanetStructHead *(janet_struct_head)(JanetStruct st) { | ||||
|     return janet_struct_head(st); | ||||
| } | ||||
|  | ||||
| @@ -443,10 +599,10 @@ JANET_API JanetAbstractHead *(janet_abstract_head)(const void *abstract) { | ||||
|     return janet_abstract_head(abstract); | ||||
| } | ||||
|  | ||||
| JANET_API JanetStringHead *(janet_string_head)(const uint8_t *s) { | ||||
| JANET_API JanetStringHead *(janet_string_head)(JanetString s) { | ||||
|     return janet_string_head(s); | ||||
| } | ||||
|  | ||||
| JANET_API JanetTupleHead *(janet_tuple_head)(const Janet *tuple) { | ||||
| JANET_API JanetTupleHead *(janet_tuple_head)(JanetTuple tuple) { | ||||
|     return janet_tuple_head(tuple); | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -99,7 +99,7 @@ static JanetSlot opfunction( | ||||
| static int can_be_imm(Janet x, int8_t *out) { | ||||
|     if (!janet_checkint(x)) return 0; | ||||
|     int32_t integer = janet_unwrap_integer(x); | ||||
|     if (integer > 127 || integer < -127) return 0; | ||||
|     if (integer > INT8_MAX || integer < INT8_MIN) return 0; | ||||
|     *out = (int8_t) integer; | ||||
|     return 1; | ||||
| } | ||||
| @@ -116,12 +116,11 @@ static JanetSlot opreduce( | ||||
|     JanetSlot *args, | ||||
|     int op, | ||||
|     int opim, | ||||
|     Janet nullary) { | ||||
|     Janet nullary, | ||||
|     Janet unary) { | ||||
|     JanetCompiler *c = opts.compiler; | ||||
|     int32_t i, len; | ||||
|     int8_t imm = 0; | ||||
|     int neg = opim < 0; | ||||
|     if (opim < 0) opim = -opim; | ||||
|     len = janet_v_count(args); | ||||
|     JanetSlot t; | ||||
|     if (len == 0) { | ||||
| @@ -132,19 +131,19 @@ static JanetSlot opreduce( | ||||
|         if (op == JOP_SUBTRACT) { | ||||
|             janetc_emit_ssi(c, JOP_MULTIPLY_IMMEDIATE, t, args[0], -1, 1); | ||||
|         } else { | ||||
|             janetc_emit_sss(c, op, t, janetc_cslot(nullary), args[0], 1); | ||||
|             janetc_emit_sss(c, op, t, janetc_cslot(unary), args[0], 1); | ||||
|         } | ||||
|         return t; | ||||
|     } | ||||
|     t = janetc_gettarget(opts); | ||||
|     if (opim && can_slot_be_imm(args[1], &imm)) { | ||||
|         janetc_emit_ssi(c, opim, t, args[0], neg ? -imm : imm, 1); | ||||
|         janetc_emit_ssi(c, opim, t, args[0], imm, 1); | ||||
|     } else { | ||||
|         janetc_emit_sss(c, op, t, args[0], args[1], 1); | ||||
|     } | ||||
|     for (i = 2; i < len; i++) { | ||||
|         if (opim && can_slot_be_imm(args[i], &imm)) { | ||||
|             janetc_emit_ssi(c, opim, t, t, neg ? -imm : imm, 1); | ||||
|             janetc_emit_ssi(c, opim, t, t, imm, 1); | ||||
|         } else { | ||||
|             janetc_emit_sss(c, op, t, t, args[i], 1); | ||||
|         } | ||||
| @@ -155,7 +154,7 @@ static JanetSlot opreduce( | ||||
| /* Function optimizers */ | ||||
|  | ||||
| static JanetSlot do_propagate(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_PROPAGATE, 0, janet_wrap_nil()); | ||||
|     return opreduce(opts, args, JOP_PROPAGATE, 0, janet_wrap_nil(), janet_wrap_nil()); | ||||
| } | ||||
| static JanetSlot do_error(JanetFopts opts, JanetSlot *args) { | ||||
|     janetc_emit_s(opts.compiler, JOP_ERROR, args[0], 0); | ||||
| @@ -172,7 +171,7 @@ static JanetSlot do_debug(JanetFopts opts, JanetSlot *args) { | ||||
|     return t; | ||||
| } | ||||
| static JanetSlot do_in(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_IN, 0, janet_wrap_nil()); | ||||
|     return opreduce(opts, args, JOP_IN, 0, janet_wrap_nil(), janet_wrap_nil()); | ||||
| } | ||||
| static JanetSlot do_get(JanetFopts opts, JanetSlot *args) { | ||||
|     if (janet_v_count(args) == 3) { | ||||
| @@ -192,20 +191,14 @@ static JanetSlot do_get(JanetFopts opts, JanetSlot *args) { | ||||
|         c->buffer[label] |= (current - label) << 16; | ||||
|         return t; | ||||
|     } else { | ||||
|         return opreduce(opts, args, JOP_GET, 0, janet_wrap_nil()); | ||||
|         return opreduce(opts, args, JOP_GET, 0, janet_wrap_nil(), janet_wrap_nil()); | ||||
|     } | ||||
| } | ||||
| static JanetSlot do_next(JanetFopts opts, JanetSlot *args) { | ||||
|     return opfunction(opts, args, JOP_NEXT, janet_wrap_nil()); | ||||
| } | ||||
| static JanetSlot do_modulo(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_MODULO, 0, janet_wrap_nil()); | ||||
| } | ||||
| static JanetSlot do_remainder(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_REMAINDER, 0, janet_wrap_nil()); | ||||
| } | ||||
| static JanetSlot do_cmp(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_COMPARE, 0, janet_wrap_nil()); | ||||
|     return opreduce(opts, args, JOP_COMPARE, 0, janet_wrap_nil(), janet_wrap_nil()); | ||||
| } | ||||
| static JanetSlot do_put(JanetFopts opts, JanetSlot *args) { | ||||
|     if (opts.flags & JANET_FOPTS_DROP) { | ||||
| @@ -262,34 +255,43 @@ static JanetSlot do_apply(JanetFopts opts, JanetSlot *args) { | ||||
| /* Variadic operators specialization */ | ||||
|  | ||||
| static JanetSlot do_add(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_ADD, JOP_ADD_IMMEDIATE, janet_wrap_integer(0)); | ||||
|     return opreduce(opts, args, JOP_ADD, JOP_ADD_IMMEDIATE, janet_wrap_integer(0), janet_wrap_integer(0)); | ||||
| } | ||||
| static JanetSlot do_sub(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_SUBTRACT, -JOP_ADD_IMMEDIATE, janet_wrap_integer(0)); | ||||
|     return opreduce(opts, args, JOP_SUBTRACT, JOP_SUBTRACT_IMMEDIATE, janet_wrap_integer(0), janet_wrap_integer(0)); | ||||
| } | ||||
| static JanetSlot do_mul(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_MULTIPLY, JOP_MULTIPLY_IMMEDIATE, janet_wrap_integer(1)); | ||||
|     return opreduce(opts, args, JOP_MULTIPLY, JOP_MULTIPLY_IMMEDIATE, janet_wrap_integer(1), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_div(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_DIVIDE, JOP_DIVIDE_IMMEDIATE, janet_wrap_integer(1)); | ||||
|     return opreduce(opts, args, JOP_DIVIDE, JOP_DIVIDE_IMMEDIATE, janet_wrap_integer(1), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_divf(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_DIVIDE_FLOOR, 0, janet_wrap_integer(1), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_modulo(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_MODULO, 0, janet_wrap_integer(0), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_remainder(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_REMAINDER, 0, janet_wrap_integer(0), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_band(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_BAND, 0, janet_wrap_integer(-1)); | ||||
|     return opreduce(opts, args, JOP_BAND, 0, janet_wrap_integer(-1), janet_wrap_integer(-1)); | ||||
| } | ||||
| static JanetSlot do_bor(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_BOR, 0, janet_wrap_integer(0)); | ||||
|     return opreduce(opts, args, JOP_BOR, 0, janet_wrap_integer(0), janet_wrap_integer(0)); | ||||
| } | ||||
| static JanetSlot do_bxor(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_BXOR, 0, janet_wrap_integer(0)); | ||||
|     return opreduce(opts, args, JOP_BXOR, 0, janet_wrap_integer(0), janet_wrap_integer(0)); | ||||
| } | ||||
| static JanetSlot do_lshift(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_SHIFT_LEFT, JOP_SHIFT_LEFT_IMMEDIATE, janet_wrap_integer(1)); | ||||
|     return opreduce(opts, args, JOP_SHIFT_LEFT, JOP_SHIFT_LEFT_IMMEDIATE, janet_wrap_integer(1), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_rshift(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_SHIFT_RIGHT, JOP_SHIFT_RIGHT_IMMEDIATE, janet_wrap_integer(1)); | ||||
|     return opreduce(opts, args, JOP_SHIFT_RIGHT, JOP_SHIFT_RIGHT_IMMEDIATE, janet_wrap_integer(1), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_rshiftu(JanetFopts opts, JanetSlot *args) { | ||||
|     return opreduce(opts, args, JOP_SHIFT_RIGHT_UNSIGNED, JOP_SHIFT_RIGHT_UNSIGNED_IMMEDIATE, janet_wrap_integer(1)); | ||||
|     return opreduce(opts, args, JOP_SHIFT_RIGHT_UNSIGNED, JOP_SHIFT_RIGHT_UNSIGNED_IMMEDIATE, janet_wrap_integer(1), janet_wrap_integer(1)); | ||||
| } | ||||
| static JanetSlot do_bnot(JanetFopts opts, JanetSlot *args) { | ||||
|     return genericSS(opts, JOP_BNOT, args[0]); | ||||
| @@ -383,10 +385,11 @@ static const JanetFunOptimizer optimizers[] = { | ||||
|     {fixarity2, do_propagate}, | ||||
|     {arity2or3, do_get}, | ||||
|     {arity1or2, do_next}, | ||||
|     {fixarity2, do_modulo}, | ||||
|     {fixarity2, do_remainder}, | ||||
|     {NULL, do_modulo}, | ||||
|     {NULL, do_remainder}, | ||||
|     {fixarity2, do_cmp}, | ||||
|     {fixarity2, do_cancel}, | ||||
|     {NULL, do_divf} | ||||
| }; | ||||
|  | ||||
| const JanetFunOptimizer *janetc_funopt(uint32_t flags) { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -93,10 +93,14 @@ void janetc_freeslot(JanetCompiler *c, JanetSlot s) { | ||||
| /* Add a slot to a scope with a symbol associated with it (def or var). */ | ||||
| void janetc_nameslot(JanetCompiler *c, const uint8_t *sym, JanetSlot s) { | ||||
|     SymPair sp; | ||||
|     int32_t cnt = janet_v_count(c->buffer); | ||||
|     sp.sym = sym; | ||||
|     sp.sym2 = sym; | ||||
|     sp.slot = s; | ||||
|     sp.keep = 0; | ||||
|     sp.slot.flags |= JANET_SLOT_NAMED; | ||||
|     sp.birth_pc = cnt ? cnt - 1 : 0; | ||||
|     sp.death_pc = UINT32_MAX; | ||||
|     janet_v_push(c->scope->syms, sp); | ||||
| } | ||||
|  | ||||
| @@ -159,21 +163,27 @@ void janetc_popscope(JanetCompiler *c) { | ||||
|         if (oldscope->flags & JANET_SCOPE_CLOSURE) { | ||||
|             newscope->flags |= JANET_SCOPE_CLOSURE; | ||||
|         } | ||||
|         if (newscope->ra.max < oldscope->ra.max) | ||||
|         if (newscope->ra.max < oldscope->ra.max) { | ||||
|             newscope->ra.max = oldscope->ra.max; | ||||
|  | ||||
|         /* Keep upvalue slots */ | ||||
|         for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) { | ||||
|             SymPair pair = oldscope->syms[i]; | ||||
|             if (pair.keep) { | ||||
|                 /* The variable should not be lexically accessible */ | ||||
|                 pair.sym = NULL; | ||||
|                 janet_v_push(newscope->syms, pair); | ||||
|                 janetc_regalloc_touch(&newscope->ra, pair.slot.index); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /* Keep upvalue slots and symbols for debugging. */ | ||||
|         for (int32_t i = 0; i < janet_v_count(oldscope->syms); i++) { | ||||
|             SymPair pair = oldscope->syms[i]; | ||||
|             /* The variable should not be lexically accessible */ | ||||
|             pair.sym = NULL; | ||||
|             if (pair.death_pc == UINT32_MAX) { | ||||
|                 pair.death_pc = (uint32_t) janet_v_count(c->buffer); | ||||
|             } | ||||
|             if (pair.keep) { | ||||
|                 /* The variable should also not be included in the locals */ | ||||
|                 pair.sym2 = NULL; | ||||
|                 janetc_regalloc_touch(&newscope->ra, pair.slot.index); | ||||
|             } | ||||
|             janet_v_push(newscope->syms, pair); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Free the old scope */ | ||||
|     janet_v_free(oldscope->consts); | ||||
|     janet_v_free(oldscope->syms); | ||||
| @@ -197,6 +207,39 @@ void janetc_popscope_keepslot(JanetCompiler *c, JanetSlot retslot) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| static int lookup_missing( | ||||
|     JanetCompiler *c, | ||||
|     const uint8_t *sym, | ||||
|     JanetFunction *handler, | ||||
|     JanetBinding *out) { | ||||
|     int32_t minar = handler->def->min_arity; | ||||
|     int32_t maxar = handler->def->max_arity; | ||||
|     if (minar > 1 || maxar < 1) { | ||||
|         janetc_error(c, janet_cstring("missing symbol lookup handler must take 1 argument")); | ||||
|         return 0; | ||||
|     } | ||||
|     Janet args[1] = { janet_wrap_symbol(sym) }; | ||||
|     JanetFiber *fiberp = janet_fiber(handler, 64, 1, args); | ||||
|     if (NULL == fiberp) { | ||||
|         janetc_error(c, janet_cstring("failed to call missing symbol lookup handler")); | ||||
|         return 0; | ||||
|     } | ||||
|     fiberp->env = c->env; | ||||
|     int lock = janet_gclock(); | ||||
|     Janet tempOut; | ||||
|     JanetSignal status = janet_continue(fiberp, janet_wrap_nil(), &tempOut); | ||||
|     janet_gcunlock(lock); | ||||
|     if (status != JANET_SIGNAL_OK) { | ||||
|         janetc_error(c, janet_formatc("(lookup) %V", tempOut)); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     /* Convert return value as entry. */ | ||||
|     /* Alternative could use janet_resolve_ext(c->env, sym) to read result from environment. */ | ||||
|     *out = janet_binding_from_entry(tempOut); | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| /* Allow searching for symbols. Return information about the symbol */ | ||||
| JanetSlot janetc_resolve( | ||||
|     JanetCompiler *c, | ||||
| @@ -230,6 +273,21 @@ JanetSlot janetc_resolve( | ||||
|     /* Symbol not found - check for global */ | ||||
|     { | ||||
|         JanetBinding binding = janet_resolve_ext(c->env, sym); | ||||
|         if (binding.type == JANET_BINDING_NONE) { | ||||
|             Janet handler = janet_table_get(c->env, janet_ckeywordv("missing-symbol")); | ||||
|             switch (janet_type(handler)) { | ||||
|                 case JANET_NIL: | ||||
|                     break; | ||||
|                 case JANET_FUNCTION: | ||||
|                     if (!lookup_missing(c, sym, janet_unwrap_function(handler), &binding)) | ||||
|                         return janetc_cslot(janet_wrap_nil()); | ||||
|                     break; | ||||
|                 default: | ||||
|                     janetc_error(c, janet_formatc("invalid lookup handler %V", handler)); | ||||
|                     return janetc_cslot(janet_wrap_nil()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         switch (binding.type) { | ||||
|             default: | ||||
|             case JANET_BINDING_NONE: | ||||
| @@ -239,6 +297,12 @@ JanetSlot janetc_resolve( | ||||
|             case JANET_BINDING_MACRO: /* Macro should function like defs when not in calling pos */ | ||||
|                 ret = janetc_cslot(binding.value); | ||||
|                 break; | ||||
|             case JANET_BINDING_DYNAMIC_DEF: | ||||
|             case JANET_BINDING_DYNAMIC_MACRO: | ||||
|                 ret = janetc_cslot(binding.value); | ||||
|                 ret.flags |= JANET_SLOT_REF | JANET_SLOT_NAMED | JANET_SLOTTYPE_ANY; | ||||
|                 ret.flags &= ~JANET_SLOT_CONSTANT; | ||||
|                 break; | ||||
|             case JANET_BINDING_VAR: { | ||||
|                 ret = janetc_cslot(binding.value); | ||||
|                 ret.flags |= JANET_SLOT_REF | JANET_SLOT_NAMED | JANET_SLOT_MUTABLE | JANET_SLOTTYPE_ANY; | ||||
| @@ -280,6 +344,7 @@ found: | ||||
|     } | ||||
|  | ||||
|     /* non-local scope needs to expose its environment */ | ||||
|     JanetScope *original_scope = scope; | ||||
|     pair->keep = 1; | ||||
|     while (scope && !(scope->flags & JANET_SCOPE_FUNCTION)) | ||||
|         scope = scope->parent; | ||||
| @@ -301,7 +366,7 @@ found: | ||||
|             /* Check if scope already has env. If so, break */ | ||||
|             len = janet_v_count(scope->envs); | ||||
|             for (j = 0; j < len; j++) { | ||||
|                 if (scope->envs[j] == envindex) { | ||||
|                 if (scope->envs[j].envindex == envindex) { | ||||
|                     scopefound = 1; | ||||
|                     envindex = j; | ||||
|                     break; | ||||
| @@ -310,7 +375,10 @@ found: | ||||
|             /* Add the environment if it is not already referenced */ | ||||
|             if (!scopefound) { | ||||
|                 len = janet_v_count(scope->envs); | ||||
|                 janet_v_push(scope->envs, envindex); | ||||
|                 JanetEnvRef ref; | ||||
|                 ref.envindex = envindex; | ||||
|                 ref.scope = original_scope; | ||||
|                 janet_v_push(scope->envs, ref); | ||||
|                 envindex = len; | ||||
|             } | ||||
|         } | ||||
| @@ -354,6 +422,7 @@ JanetSlot *janetc_toslots(JanetCompiler *c, const Janet *vals, int32_t len) { | ||||
|     int32_t i; | ||||
|     JanetSlot *ret = NULL; | ||||
|     JanetFopts subopts = janetc_fopts_default(c); | ||||
|     subopts.flags |= JANET_FOPTS_ACCEPT_SPLICE; | ||||
|     for (i = 0; i < len; i++) { | ||||
|         janet_v_push(ret, janetc_value(subopts, vals[i])); | ||||
|     } | ||||
| @@ -364,6 +433,7 @@ JanetSlot *janetc_toslots(JanetCompiler *c, const Janet *vals, int32_t len) { | ||||
| JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds) { | ||||
|     JanetSlot *ret = NULL; | ||||
|     JanetFopts subopts = janetc_fopts_default(c); | ||||
|     subopts.flags |= JANET_FOPTS_ACCEPT_SPLICE; | ||||
|     const JanetKV *kvs = NULL; | ||||
|     int32_t cap = 0, len = 0; | ||||
|     janet_dictionary_view(ds, &kvs, &len, &cap); | ||||
| @@ -651,7 +721,7 @@ static int macroexpand1( | ||||
|     } | ||||
|     Janet macroval; | ||||
|     JanetBindingType btype = janet_resolve(c->env, name, ¯oval); | ||||
|     if (btype != JANET_BINDING_MACRO || | ||||
|     if (!(btype == JANET_BINDING_MACRO || btype == JANET_BINDING_DYNAMIC_MACRO) || | ||||
|             !janet_checktype(macroval, JANET_FUNCTION)) | ||||
|         return 0; | ||||
|  | ||||
| @@ -676,12 +746,14 @@ static int macroexpand1( | ||||
|     int lock = janet_gclock(); | ||||
|     Janet mf_kw = janet_ckeywordv("macro-form"); | ||||
|     janet_table_put(c->env, mf_kw, x); | ||||
|     Janet ml_kw = janet_ckeywordv("macro-lints"); | ||||
|     if (c->lints) { | ||||
|         janet_table_put(c->env, ml_kw, janet_wrap_array(c->lints)); | ||||
|     } | ||||
|     Janet tempOut; | ||||
|     JanetSignal status = janet_continue(fiberp, janet_wrap_nil(), &tempOut); | ||||
|     janet_table_put(c->env, mf_kw, janet_wrap_nil()); | ||||
|     if (c->lints) { | ||||
|         janet_table_put(c->env, janet_ckeywordv("macro-lints"), janet_wrap_array(c->lints)); | ||||
|     } | ||||
|     janet_table_put(c->env, ml_kw, janet_wrap_nil()); | ||||
|     janet_gcunlock(lock); | ||||
|     if (status != JANET_SIGNAL_OK) { | ||||
|         const uint8_t *es = janet_formatc("(macro) %V", tempOut); | ||||
| @@ -814,7 +886,10 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { | ||||
|  | ||||
|     /* Copy envs */ | ||||
|     def->environments_length = janet_v_count(scope->envs); | ||||
|     def->environments = janet_v_flatten(scope->envs); | ||||
|     def->environments = janet_malloc(sizeof(int32_t) * def->environments_length); | ||||
|     for (int32_t i = 0; i < def->environments_length; i++) { | ||||
|         def->environments[i] = scope->envs[i].envindex; | ||||
|     } | ||||
|  | ||||
|     def->constants_length = janet_v_count(scope->consts); | ||||
|     def->constants = janet_v_flatten(scope->consts); | ||||
| @@ -859,7 +934,7 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { | ||||
|         int32_t slotchunks = (def->slotcount + 31) >> 5; | ||||
|         /* numchunks is min of slotchunks and scope->ua.count */ | ||||
|         int32_t numchunks = slotchunks > scope->ua.count ? scope->ua.count : slotchunks; | ||||
|         uint32_t *chunks = janet_calloc(sizeof(uint32_t), slotchunks); | ||||
|         uint32_t *chunks = janet_calloc(slotchunks, sizeof(uint32_t)); | ||||
|         if (NULL == chunks) { | ||||
|             JANET_OUT_OF_MEMORY; | ||||
|         } | ||||
| @@ -869,9 +944,66 @@ JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c) { | ||||
|         def->closure_bitset = chunks; | ||||
|     } | ||||
|  | ||||
|     /* Capture symbol to local mapping */ | ||||
|     JanetSymbolMap *locals = NULL; | ||||
|  | ||||
|     /* Symbol -> upvalue mapping */ | ||||
|     JanetScope *top = c->scope; | ||||
|     while (top->parent) top = top->parent; | ||||
|     for (JanetScope *s = top; s != NULL; s = s->child) { | ||||
|         for (int32_t j = 0; j < janet_v_count(scope->envs); j++) { | ||||
|             JanetEnvRef ref = scope->envs[j]; | ||||
|             JanetScope *upscope = ref.scope; | ||||
|             if (upscope != s) continue; | ||||
|             for (int32_t i = 0; i < janet_v_count(upscope->syms); i++) { | ||||
|                 SymPair pair = upscope->syms[i]; | ||||
|                 if (pair.sym2) { | ||||
|                     JanetSymbolMap jsm; | ||||
|                     jsm.birth_pc = UINT32_MAX; | ||||
|                     jsm.death_pc = j; | ||||
|                     jsm.slot_index = pair.slot.index; | ||||
|                     jsm.symbol = pair.sym2; | ||||
|                     janet_v_push(locals, jsm); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Symbol -> slot mapping */ | ||||
|     for (int32_t i = 0; i < janet_v_count(scope->syms); i++) { | ||||
|         SymPair pair = scope->syms[i]; | ||||
|         if (pair.sym2) { | ||||
|             JanetSymbolMap jsm; | ||||
|             if (pair.death_pc == UINT32_MAX) { | ||||
|                 jsm.death_pc = def->bytecode_length; | ||||
|             } else { | ||||
|                 jsm.death_pc = pair.death_pc - scope->bytecode_start; | ||||
|             } | ||||
|             /* Handle birth_pc == 0 correctly */ | ||||
|             if ((uint32_t) scope->bytecode_start > pair.birth_pc) { | ||||
|                 jsm.birth_pc = 0; | ||||
|             } else { | ||||
|                 jsm.birth_pc = pair.birth_pc - scope->bytecode_start; | ||||
|             } | ||||
|             janet_assert(jsm.birth_pc <= jsm.death_pc, "birth pc after death pc"); | ||||
|             janet_assert(jsm.birth_pc < (uint32_t) def->bytecode_length, "bad birth pc"); | ||||
|             janet_assert(jsm.death_pc <= (uint32_t) def->bytecode_length, "bad death pc"); | ||||
|             jsm.slot_index = pair.slot.index; | ||||
|             jsm.symbol = pair.sym2; | ||||
|             janet_v_push(locals, jsm); | ||||
|         } | ||||
|     } | ||||
|     def->symbolmap_length = janet_v_count(locals); | ||||
|     def->symbolmap = janet_v_flatten(locals); | ||||
|     if (def->symbolmap_length) def->flags |= JANET_FUNCDEF_FLAG_HASSYMBOLMAP; | ||||
|  | ||||
|     /* Pop the scope */ | ||||
|     janetc_popscope(c); | ||||
|  | ||||
|     /* Do basic optimization */ | ||||
|     janet_bytecode_movopt(def); | ||||
|     janet_bytecode_remove_noops(def); | ||||
|  | ||||
|     return def; | ||||
| } | ||||
|  | ||||
| @@ -924,7 +1056,7 @@ JanetCompileResult janet_compile_lint(Janet source, | ||||
|  | ||||
|     if (c.result.status == JANET_COMPILE_OK) { | ||||
|         JanetFuncDef *def = janetc_pop_funcdef(&c); | ||||
|         def->name = janet_cstring("_thunk"); | ||||
|         def->name = janet_cstring("thunk"); | ||||
|         janet_def_addflags(def); | ||||
|         c.result.funcdef = def; | ||||
|     } else { | ||||
| @@ -942,7 +1074,7 @@ JanetCompileResult janet_compile(Janet source, JanetTable *env, const uint8_t *w | ||||
| } | ||||
|  | ||||
| /* C Function for compiling */ | ||||
| JANET_CORE_FN(cfun, | ||||
| JANET_CORE_FN(cfun_compile, | ||||
|               "(compile ast &opt env source lints)", | ||||
|               "Compiles an Abstract Syntax Tree (ast) into a function. " | ||||
|               "Pair the compile function with parsing functionality to implement " | ||||
| @@ -951,16 +1083,25 @@ JANET_CORE_FN(cfun, | ||||
|               "If a `lints` array is given, linting messages will be appended to the array. " | ||||
|               "Each message will be a tuple of the form `(level line col message)`.") { | ||||
|     janet_arity(argc, 1, 4); | ||||
|     JanetTable *env = argc > 1 ? janet_gettable(argv, 1) : janet_vm.fiber->env; | ||||
|     JanetTable *env = (argc > 1 && !janet_checktype(argv[1], JANET_NIL)) | ||||
|                       ? janet_gettable(argv, 1) : janet_vm.fiber->env; | ||||
|     if (NULL == env) { | ||||
|         env = janet_table(0); | ||||
|         janet_vm.fiber->env = env; | ||||
|     } | ||||
|     const uint8_t *source = NULL; | ||||
|     if (argc >= 3) { | ||||
|         source = janet_getstring(argv, 2); | ||||
|         Janet x = argv[2]; | ||||
|         if (janet_checktype(x, JANET_STRING)) { | ||||
|             source = janet_unwrap_string(x); | ||||
|         } else if (janet_checktype(x, JANET_KEYWORD)) { | ||||
|             source = janet_unwrap_keyword(x); | ||||
|         } else if (!janet_checktype(x, JANET_NIL)) { | ||||
|             janet_panic_type(x, 2, JANET_TFLAG_STRING | JANET_TFLAG_KEYWORD); | ||||
|         } | ||||
|     } | ||||
|     JanetArray *lints = (argc >= 4) ? janet_getarray(argv, 3) : NULL; | ||||
|     JanetArray *lints = (argc >= 4 && !janet_checktype(argv[3], JANET_NIL)) | ||||
|                         ? janet_getarray(argv, 3) : NULL; | ||||
|     JanetCompileResult res = janet_compile_lint(argv[0], env, source, lints); | ||||
|     if (res.status == JANET_COMPILE_OK) { | ||||
|         return janet_wrap_function(janet_thunk(res.funcdef)); | ||||
| @@ -982,7 +1123,7 @@ JANET_CORE_FN(cfun, | ||||
|  | ||||
| void janet_lib_compile(JanetTable *env) { | ||||
|     JanetRegExt cfuns[] = { | ||||
|         JANET_CORE_REG("compile", cfun), | ||||
|         JANET_CORE_REG("compile", cfun_compile), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, cfuns); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -69,6 +69,7 @@ typedef enum { | ||||
| #define JANET_FUN_REMAINDER 30 | ||||
| #define JANET_FUN_CMP 31 | ||||
| #define JANET_FUN_CANCEL 32 | ||||
| #define JANET_FUN_DIVIDE_FLOOR 33 | ||||
|  | ||||
| /* Compiler typedefs */ | ||||
| typedef struct JanetCompiler JanetCompiler; | ||||
| @@ -111,13 +112,21 @@ struct JanetSlot { | ||||
| typedef struct SymPair { | ||||
|     JanetSlot slot; | ||||
|     const uint8_t *sym; | ||||
|     const uint8_t *sym2; | ||||
|     int keep; | ||||
|     uint32_t birth_pc; | ||||
|     uint32_t death_pc; | ||||
| } SymPair; | ||||
|  | ||||
| typedef struct JanetEnvRef { | ||||
|     int32_t envindex; | ||||
|     JanetScope *scope; | ||||
| } JanetEnvRef; | ||||
|  | ||||
| /* A lexical scope during compilation */ | ||||
| struct JanetScope { | ||||
|  | ||||
|     /* For debugging */ | ||||
|     /* For debugging the compiler */ | ||||
|     const char *name; | ||||
|  | ||||
|     /* Scopes are doubly linked list */ | ||||
| @@ -133,7 +142,7 @@ struct JanetScope { | ||||
|     /* FuncDefs */ | ||||
|     JanetFuncDef **defs; | ||||
|  | ||||
|     /* Regsiter allocator */ | ||||
|     /* Register allocator */ | ||||
|     JanetcRegisterAllocator ra; | ||||
|  | ||||
|     /* Upvalue allocator */ | ||||
| @@ -142,7 +151,7 @@ struct JanetScope { | ||||
|     /* Referenced closure environments. The values at each index correspond | ||||
|      * to which index to get the environment from in the parent. The environment | ||||
|      * that corresponds to the direct parent's stack will always have value 0. */ | ||||
|     int32_t *envs; | ||||
|     JanetEnvRef *envs; | ||||
|  | ||||
|     int32_t bytecode_start; | ||||
|     int flags; | ||||
| @@ -179,6 +188,7 @@ struct JanetCompiler { | ||||
| #define JANET_FOPTS_TAIL 0x10000 | ||||
| #define JANET_FOPTS_HINT 0x20000 | ||||
| #define JANET_FOPTS_DROP 0x40000 | ||||
| #define JANET_FOPTS_ACCEPT_SPLICE 0x80000 | ||||
|  | ||||
| /* Options for compiling a single form */ | ||||
| struct JanetFopts { | ||||
| @@ -227,7 +237,7 @@ JanetSlot *janetc_toslots(JanetCompiler *c, const Janet *vals, int32_t len); | ||||
| /* Get a bunch of slots for function arguments */ | ||||
| JanetSlot *janetc_toslotskv(JanetCompiler *c, Janet ds); | ||||
|  | ||||
| /* Push slots load via janetc_toslots. */ | ||||
| /* Push slots loaded via janetc_toslots. */ | ||||
| int32_t janetc_pushslots(JanetCompiler *c, JanetSlot *slots); | ||||
|  | ||||
| /* Free slots loaded via janetc_toslots */ | ||||
| @@ -252,10 +262,14 @@ void janetc_popscope(JanetCompiler *c); | ||||
| void janetc_popscope_keepslot(JanetCompiler *c, JanetSlot retslot); | ||||
| JanetFuncDef *janetc_pop_funcdef(JanetCompiler *c); | ||||
|  | ||||
| /* Create a destory slots */ | ||||
| /* Create a destroy slot */ | ||||
| JanetSlot janetc_cslot(Janet x); | ||||
|  | ||||
| /* Search for a symbol */ | ||||
| JanetSlot janetc_resolve(JanetCompiler *c, const uint8_t *sym); | ||||
|  | ||||
| /* Bytecode optimization */ | ||||
| void janet_bytecode_movopt(JanetFuncDef *def); | ||||
| void janet_bytecode_remove_noops(JanetFuncDef *def); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -42,52 +42,8 @@ extern size_t janet_core_image_size; | ||||
| #define JDOC(x) NULL | ||||
| #endif | ||||
|  | ||||
| /* Use LoadLibrary on windows or dlopen on posix to load dynamic libaries | ||||
|  * with native code. */ | ||||
| #if defined(JANET_NO_DYNAMIC_MODULES) | ||||
| typedef int Clib; | ||||
| #define load_clib(name) ((void) name, 0) | ||||
| #define symbol_clib(lib, sym) ((void) lib, (void) sym, NULL) | ||||
| #define error_clib() "dynamic libraries not supported" | ||||
| #elif defined(JANET_WINDOWS) | ||||
| #include <windows.h> | ||||
| typedef HINSTANCE Clib; | ||||
| #define load_clib(name) LoadLibrary((name)) | ||||
| #define symbol_clib(lib, sym) GetProcAddress((lib), (sym)) | ||||
| static char error_clib_buf[256]; | ||||
| static char *error_clib(void) { | ||||
|     FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||||
|                    NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | ||||
|                    error_clib_buf, sizeof(error_clib_buf), NULL); | ||||
|     error_clib_buf[strlen(error_clib_buf) - 1] = '\0'; | ||||
|     return error_clib_buf; | ||||
| } | ||||
| #else | ||||
| #include <dlfcn.h> | ||||
| typedef void *Clib; | ||||
| #define load_clib(name) dlopen((name), RTLD_NOW) | ||||
| #define symbol_clib(lib, sym) dlsym((lib), (sym)) | ||||
| #define error_clib() dlerror() | ||||
| #endif | ||||
|  | ||||
| static char *get_processed_name(const char *name) { | ||||
|     if (name[0] == '.') return (char *) name; | ||||
|     const char *c; | ||||
|     for (c = name; *c; c++) { | ||||
|         if (*c == '/') return (char *) name; | ||||
|     } | ||||
|     size_t l = (size_t)(c - name); | ||||
|     char *ret = janet_malloc(l + 3); | ||||
|     if (NULL == ret) { | ||||
|         JANET_OUT_OF_MEMORY; | ||||
|     } | ||||
|     ret[0] = '.'; | ||||
|     ret[1] = '/'; | ||||
|     memcpy(ret + 2, name, l + 1); | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| JanetModule janet_native(const char *name, const uint8_t **error) { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_DYNAMIC_MODULES); | ||||
|     char *processed_name = get_processed_name(name); | ||||
|     Clib lib = load_clib(processed_name); | ||||
|     JanetModule init; | ||||
| @@ -113,15 +69,15 @@ JanetModule janet_native(const char *name, const uint8_t **error) { | ||||
|             host.minor < modconf.minor || | ||||
|             host.bits != modconf.bits) { | ||||
|         char errbuf[128]; | ||||
|         sprintf(errbuf, "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)", | ||||
|                 host.major, | ||||
|                 host.minor, | ||||
|                 host.patch, | ||||
|                 host.bits, | ||||
|                 modconf.major, | ||||
|                 modconf.minor, | ||||
|                 modconf.patch, | ||||
|                 modconf.bits); | ||||
|         snprintf(errbuf, sizeof(errbuf), "config mismatch - host %d.%.d.%d(%.4x) vs. module %d.%d.%d(%.4x)", | ||||
|                  host.major, | ||||
|                  host.minor, | ||||
|                  host.patch, | ||||
|                  host.bits, | ||||
|                  modconf.major, | ||||
|                  modconf.minor, | ||||
|                  modconf.patch, | ||||
|                  modconf.bits); | ||||
|         *error = janet_cstring(errbuf); | ||||
|         return NULL; | ||||
|     } | ||||
| @@ -154,11 +110,14 @@ JANET_CORE_FN(janet_core_expand_path, | ||||
|               "(module/expand-path path template)", | ||||
|               "Expands a path template as found in `module/paths` for `module/find`. " | ||||
|               "This takes in a path (the argument to require) and a template string, " | ||||
|               "to expand the path to a path that can be " | ||||
|               "used for importing files. The replacements are as follows:\n\n" | ||||
|               "* :all: -- the value of path verbatim\n\n" | ||||
|               "* :cur: -- the current file, or (dyn :current-file)\n\n" | ||||
|               "* :dir: -- the directory containing the current file\n\n" | ||||
|               "to expand the path to a path that can be used for importing files. " | ||||
|               "The replacements are as follows:\n\n" | ||||
|               "* :all: -- the value of path verbatim.\n\n" | ||||
|               "* :@all: -- Same as :all:, but if `path` starts with the @ character, " | ||||
|               "the first path segment is replaced with a dynamic binding " | ||||
|               "`(dyn <first path segment as keyword>)`.\n\n" | ||||
|               "* :cur: -- the directory portion, if any, of (dyn :current-file)\n\n" | ||||
|               "* :dir: -- the directory portion, if any, of the path argument\n\n" | ||||
|               "* :name: -- the name component of path, with extension if given\n\n" | ||||
|               "* :native: -- the extension used to load natives, .so or .dll\n\n" | ||||
|               "* :sys: -- the system path, or (dyn :syspath)") { | ||||
| @@ -202,6 +161,21 @@ JANET_CORE_FN(janet_core_expand_path, | ||||
|             if (strncmp(template + i, ":all:", 5) == 0) { | ||||
|                 janet_buffer_push_cstring(out, input); | ||||
|                 i += 4; | ||||
|             } else if (strncmp(template + i, ":@all:", 6) == 0) { | ||||
|                 if (input[0] == '@') { | ||||
|                     const char *p = input; | ||||
|                     while (*p && !is_path_sep(*p)) p++; | ||||
|                     size_t len = p - input - 1; | ||||
|                     char *str = janet_smalloc(len + 1); | ||||
|                     memcpy(str, input + 1, len); | ||||
|                     str[len] = '\0'; | ||||
|                     janet_formatb(out, "%V", janet_dyn(str)); | ||||
|                     janet_sfree(str); | ||||
|                     janet_buffer_push_cstring(out, p); | ||||
|                 } else { | ||||
|                     janet_buffer_push_cstring(out, input); | ||||
|                 } | ||||
|                 i += 5; | ||||
|             } else if (strncmp(template + i, ":cur:", 5) == 0) { | ||||
|                 janet_buffer_push_bytes(out, (const uint8_t *)curdir, curlen); | ||||
|                 i += 4; | ||||
| @@ -339,7 +313,10 @@ JANET_CORE_FN(janet_core_native, | ||||
|  | ||||
| JANET_CORE_FN(janet_core_describe, | ||||
|               "(describe x)", | ||||
|               "Returns a string that is a human-readable description of a value x.") { | ||||
|               "Returns a string that is a human-readable description of `x`. " | ||||
|               "For recursive data structures, the string returned contains a " | ||||
|               "pointer value from which the identity of `x` " | ||||
|               "can be determined.") { | ||||
|     JanetBuffer *b = janet_buffer(0); | ||||
|     for (int32_t i = 0; i < argc; ++i) | ||||
|         janet_description_b(b, argv[i]); | ||||
| @@ -399,7 +376,7 @@ JANET_CORE_FN(janet_core_is_abstract, | ||||
|  | ||||
| JANET_CORE_FN(janet_core_scannumber, | ||||
|               "(scan-number str &opt base)", | ||||
|               "Parse a number from a byte sequence an return that number, either and integer " | ||||
|               "Parse a number from a byte sequence and return that number, either an integer " | ||||
|               "or a real. The number " | ||||
|               "must be in the same format as numbers in janet source code. Will return nil " | ||||
|               "on an invalid number. Optionally provide a base - if a base is provided, no " | ||||
| @@ -449,6 +426,48 @@ JANET_CORE_FN(janet_core_slice, | ||||
|     } | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_range, | ||||
|               "(range & args)", | ||||
|               "Create an array of values [start, end) with a given step. " | ||||
|               "With one argument, returns a range [0, end). With two arguments, returns " | ||||
|               "a range [start, end). With three, returns a range with optional step size.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     double start = 0, stop = 0, step = 1, count = 0; | ||||
|     if (argc == 3) { | ||||
|         start = janet_getnumber(argv, 0); | ||||
|         stop = janet_getnumber(argv, 1); | ||||
|         step = janet_getnumber(argv, 2); | ||||
|         count = (step > 0) ? (stop - start) / step : | ||||
|                 ((step < 0) ? (stop - start) / step : 0); | ||||
|     } else if (argc == 2) { | ||||
|         start = janet_getnumber(argv, 0); | ||||
|         stop = janet_getnumber(argv, 1); | ||||
|         count = stop - start; | ||||
|     } else { | ||||
|         stop = janet_getnumber(argv, 0); | ||||
|         count = stop; | ||||
|     } | ||||
|     count = (count > 0) ? count : 0; | ||||
|     int32_t int_count; | ||||
|     janet_assert(count >= 0, "bad range code"); | ||||
|     if (count > (double) INT32_MAX) { | ||||
|         janet_panicf("range is too large, %f elements", count); | ||||
|     } else { | ||||
|         int_count = (int32_t) ceil(count); | ||||
|     } | ||||
|     if (step > 0.0) { | ||||
|         janet_assert(start + int_count * step >= stop, "bad range code"); | ||||
|     } else { | ||||
|         janet_assert(start + int_count * step <= stop, "bad range code"); | ||||
|     } | ||||
|     JanetArray *array = janet_array(int_count); | ||||
|     for (int32_t i = 0; i < int_count; i++) { | ||||
|         array->data[i] = janet_wrap_number((double) start + (double) i * step); | ||||
|     } | ||||
|     array->count = int_count; | ||||
|     return janet_wrap_array(array); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_table, | ||||
|               "(table & kvs)", | ||||
|               "Creates a new table from a variadic number of keys and values. " | ||||
| @@ -465,6 +484,25 @@ JANET_CORE_FN(janet_core_table, | ||||
|     return janet_wrap_table(table); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_getproto, | ||||
|               "(getproto x)", | ||||
|               "Get the prototype of a table or struct. Will return nil if `x` has no prototype.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     if (janet_checktype(argv[0], JANET_TABLE)) { | ||||
|         JanetTable *t = janet_unwrap_table(argv[0]); | ||||
|         return t->proto | ||||
|                ? janet_wrap_table(t->proto) | ||||
|                : janet_wrap_nil(); | ||||
|     } | ||||
|     if (janet_checktype(argv[0], JANET_STRUCT)) { | ||||
|         JanetStruct st = janet_unwrap_struct(argv[0]); | ||||
|         return janet_struct_proto(st) | ||||
|                ? janet_wrap_struct(janet_struct_proto(st)) | ||||
|                : janet_wrap_nil(); | ||||
|     } | ||||
|     janet_panicf("expected struct or table, got %v", argv[0]); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_struct, | ||||
|               "(struct & kvs)", | ||||
|               "Create a new struct from a sequence of key value pairs. " | ||||
| @@ -472,8 +510,9 @@ JANET_CORE_FN(janet_core_struct, | ||||
|               "an odd number of elements, an error will be thrown. Returns the " | ||||
|               "new struct.") { | ||||
|     int32_t i; | ||||
|     if (argc & 1) | ||||
|     if (argc & 1) { | ||||
|         janet_panic("expected even number of arguments"); | ||||
|     } | ||||
|     JanetKV *st = janet_struct_begin(argc >> 1); | ||||
|     for (i = 0; i < argc; i += 2) { | ||||
|         janet_struct_put(st, argv[i], argv[i + 1]); | ||||
| @@ -632,31 +671,145 @@ ret_false: | ||||
|     return janet_wrap_false(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_is_bytes, | ||||
|               "(bytes? x)", | ||||
|               "Check if x is a string, symbol, keyword, or buffer.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_boolean(janet_checktypes(argv[0], JANET_TFLAG_BYTES)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_is_indexed, | ||||
|               "(indexed? x)", | ||||
|               "Check if x is an array or tuple.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_boolean(janet_checktypes(argv[0], JANET_TFLAG_INDEXED)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_is_dictionary, | ||||
|               "(dictionary? x)", | ||||
|               "Check if x is a table or struct.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_boolean(janet_checktypes(argv[0], JANET_TFLAG_DICTIONARY)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_is_lengthable, | ||||
|               "(lengthable? x)", | ||||
|               "Check if x is a bytes, indexed, or dictionary.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_boolean(janet_checktypes(argv[0], JANET_TFLAG_LENGTHABLE)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_signal, | ||||
|               "(signal what x)", | ||||
|               "Raise a signal with payload x. ") { | ||||
|               "Raise a signal with payload x. `what` can be an integer\n" | ||||
|               "from 0 through 7 indicating user(0-7), or one of:\n\n" | ||||
|               "* :ok\n" | ||||
|               "* :error\n" | ||||
|               "* :debug\n" | ||||
|               "* :yield\n" | ||||
|               "* :user(0-7)\n" | ||||
|               "* :interrupt\n" | ||||
|               "* :await") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     int sig; | ||||
|     Janet payload = argc == 2 ? argv[1] : janet_wrap_nil(); | ||||
|     if (janet_checkint(argv[0])) { | ||||
|         int32_t s = janet_unwrap_integer(argv[0]); | ||||
|         if (s < 0 || s > 9) { | ||||
|             janet_panicf("expected user signal between 0 and 9, got %d", s); | ||||
|         } | ||||
|         sig = JANET_SIGNAL_USER0 + s; | ||||
|         janet_signalv(JANET_SIGNAL_USER0 + s, payload); | ||||
|     } else { | ||||
|         JanetKeyword kw = janet_getkeyword(argv, 0); | ||||
|         if (!janet_cstrcmp(kw, "yield")) { | ||||
|             sig = JANET_SIGNAL_YIELD; | ||||
|         } else if (!janet_cstrcmp(kw, "error")) { | ||||
|             sig = JANET_SIGNAL_ERROR; | ||||
|         } else if (!janet_cstrcmp(kw, "debug")) { | ||||
|             sig = JANET_SIGNAL_DEBUG; | ||||
|         } else { | ||||
|             janet_panicf("unknown signal, expected :yield, :error, or :debug, got %v", argv[0]); | ||||
|         for (unsigned i = 0; i < sizeof(janet_signal_names) / sizeof(char *); i++) { | ||||
|             if (!janet_cstrcmp(kw, janet_signal_names[i])) { | ||||
|                 janet_signalv((JanetSignal) i, payload); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     Janet payload = argc == 2 ? argv[1] : janet_wrap_nil(); | ||||
|     janet_signalv(sig, payload); | ||||
|     janet_panicf("unknown signal %v", argv[0]); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_core_memcmp, | ||||
|               "(memcmp a b &opt len offset-a offset-b)", | ||||
|               "Compare memory. Takes two byte sequences `a` and `b`, and " | ||||
|               "return 0 if they have identical contents, a negative integer if a is less than b, " | ||||
|               "and a positive integer if a is greater than b. Optionally take a length and offsets " | ||||
|               "to compare slices of the bytes sequences.") { | ||||
|     janet_arity(argc, 2, 5); | ||||
|     JanetByteView a = janet_getbytes(argv, 0); | ||||
|     JanetByteView b = janet_getbytes(argv, 1); | ||||
|     int32_t len = janet_optnat(argv, argc, 2, a.len < b.len ? a.len : b.len); | ||||
|     int32_t offset_a = janet_optnat(argv, argc, 3, 0); | ||||
|     int32_t offset_b = janet_optnat(argv, argc, 4, 0); | ||||
|     if (offset_a + len > a.len) janet_panicf("invalid offset-a: %d", offset_a); | ||||
|     if (offset_b + len > b.len) janet_panicf("invalid offset-b: %d", offset_b); | ||||
|     return janet_wrap_integer(memcmp(a.bytes + offset_a, b.bytes + offset_b, (size_t) len)); | ||||
| } | ||||
|  | ||||
| typedef struct SandboxOption { | ||||
|     const char *name; | ||||
|     uint32_t flag; | ||||
| } SandboxOption; | ||||
|  | ||||
| static const SandboxOption sandbox_options[] = { | ||||
|     {"all", JANET_SANDBOX_ALL}, | ||||
|     {"env", JANET_SANDBOX_ENV}, | ||||
|     {"ffi", JANET_SANDBOX_FFI}, | ||||
|     {"ffi-define", JANET_SANDBOX_FFI_DEFINE}, | ||||
|     {"ffi-jit", JANET_SANDBOX_FFI_JIT}, | ||||
|     {"ffi-use", JANET_SANDBOX_FFI_USE}, | ||||
|     {"fs", JANET_SANDBOX_FS}, | ||||
|     {"fs-read", JANET_SANDBOX_FS_READ}, | ||||
|     {"fs-temp", JANET_SANDBOX_FS_TEMP}, | ||||
|     {"fs-write", JANET_SANDBOX_FS_WRITE}, | ||||
|     {"hrtime", JANET_SANDBOX_HRTIME}, | ||||
|     {"modules", JANET_SANDBOX_DYNAMIC_MODULES}, | ||||
|     {"net", JANET_SANDBOX_NET}, | ||||
|     {"net-connect", JANET_SANDBOX_NET_CONNECT}, | ||||
|     {"net-listen", JANET_SANDBOX_NET_LISTEN}, | ||||
|     {"sandbox", JANET_SANDBOX_SANDBOX}, | ||||
|     {"signal", JANET_SANDBOX_SIGNAL}, | ||||
|     {"subprocess", JANET_SANDBOX_SUBPROCESS}, | ||||
|     {NULL, 0} | ||||
| }; | ||||
|  | ||||
| JANET_CORE_FN(janet_core_sandbox, | ||||
|               "(sandbox & forbidden-capabilities)", | ||||
|               "Disable feature sets to prevent the interpreter from using certain system resources. " | ||||
|               "Once a feature is disabled, there is no way to re-enable it. Capabilities can be:\n\n" | ||||
|               "* :all - disallow all (except IO to stdout, stderr, and stdin)\n" | ||||
|               "* :env - disallow reading and write env variables\n" | ||||
|               "* :ffi - disallow FFI (recommended if disabling anything else)\n" | ||||
|               "* :ffi-define - disallow loading new FFI modules and binding new functions\n" | ||||
|               "* :ffi-jit - disallow calling `ffi/jitfn`\n" | ||||
|               "* :ffi-use - disallow using any previously bound FFI functions and memory-unsafe functions.\n" | ||||
|               "* :fs - disallow access to the file system\n" | ||||
|               "* :fs-read - disallow read access to the file system\n" | ||||
|               "* :fs-temp - disallow creating temporary files\n" | ||||
|               "* :fs-write - disallow write access to the file system\n" | ||||
|               "* :hrtime - disallow high-resolution timers\n" | ||||
|               "* :modules - disallow load dynamic modules (natives)\n" | ||||
|               "* :net - disallow network access\n" | ||||
|               "* :net-connect - disallow making outbound network connections\n" | ||||
|               "* :net-listen - disallow accepting inbound network connections\n" | ||||
|               "* :sandbox - disallow calling this function\n" | ||||
|               "* :signal - disallow adding or removing signal handlers\n" | ||||
|               "* :subprocess - disallow running subprocesses") { | ||||
|     uint32_t flags = 0; | ||||
|     for (int32_t i = 0; i < argc; i++) { | ||||
|         JanetKeyword kw = janet_getkeyword(argv, i); | ||||
|         const SandboxOption *opt = sandbox_options; | ||||
|         while (opt->name != NULL) { | ||||
|             if (janet_cstrcmp(kw, opt->name) == 0) { | ||||
|                 flags |= opt->flag; | ||||
|                 break; | ||||
|             } | ||||
|             opt++; | ||||
|         } | ||||
|         if (opt->name == NULL) janet_panicf("unknown capability %v", argv[i]); | ||||
|     } | ||||
|     janet_sandbox(flags); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| #ifdef JANET_BOOTSTRAP | ||||
| @@ -843,18 +996,17 @@ static void make_apply(JanetTable *env) { | ||||
|         /* Push the array */ | ||||
|         S(JOP_PUSH_ARRAY, 5), | ||||
|  | ||||
|         /* Call the funciton */ | ||||
|         /* Call the function */ | ||||
|         S(JOP_TAILCALL, 0) | ||||
|     }; | ||||
|     janet_quick_asm(env, JANET_FUN_APPLY | JANET_FUNCDEF_FLAG_VARARG, | ||||
|                     "apply", 1, 1, INT32_MAX, 6, apply_asm, sizeof(apply_asm), | ||||
|                     JDOC("(apply f & args)\n\n" | ||||
|                          "Applies a function to a variable number of arguments. Each element in args " | ||||
|                          "is used as an argument to f, except the last element in args, which is expected to " | ||||
|                          "be an array-like. Each element in this last argument is then also pushed as an argument to " | ||||
|                          "f. For example:\n\n" | ||||
|                          "\t(apply + 1000 (range 10))\n\n" | ||||
|                          "sums the first 10 integers and 1000.")); | ||||
|          "Applies a function f to a variable number of arguments. Each " | ||||
|          "element in args is used as an argument to f, except the last " | ||||
|          "element in args, which is expected to be an array or a tuple. " | ||||
|          "Each element in this last argument is then also pushed as an " | ||||
|          "argument to f.")); | ||||
| } | ||||
|  | ||||
| static const uint32_t error_asm[] = { | ||||
| @@ -912,14 +1064,6 @@ static const uint32_t next_asm[] = { | ||||
|     JOP_NEXT | (1 << 24), | ||||
|     JOP_RETURN | ||||
| }; | ||||
| static const uint32_t modulo_asm[] = { | ||||
|     JOP_MODULO | (1 << 24), | ||||
|     JOP_RETURN | ||||
| }; | ||||
| static const uint32_t remainder_asm[] = { | ||||
|     JOP_REMAINDER | (1 << 24), | ||||
|     JOP_RETURN | ||||
| }; | ||||
| static const uint32_t cmp_asm[] = { | ||||
|     JOP_COMPARE | (1 << 24), | ||||
|     JOP_RETURN | ||||
| @@ -958,8 +1102,16 @@ static void janet_load_libs(JanetTable *env) { | ||||
|         JANET_CORE_REG("module/expand-path", janet_core_expand_path), | ||||
|         JANET_CORE_REG("int?", janet_core_check_int), | ||||
|         JANET_CORE_REG("nat?", janet_core_check_nat), | ||||
|         JANET_CORE_REG("bytes?", janet_core_is_bytes), | ||||
|         JANET_CORE_REG("indexed?", janet_core_is_indexed), | ||||
|         JANET_CORE_REG("dictionary?", janet_core_is_dictionary), | ||||
|         JANET_CORE_REG("lengthable?", janet_core_is_lengthable), | ||||
|         JANET_CORE_REG("slice", janet_core_slice), | ||||
|         JANET_CORE_REG("range", janet_core_range), | ||||
|         JANET_CORE_REG("signal", janet_core_signal), | ||||
|         JANET_CORE_REG("memcmp", janet_core_memcmp), | ||||
|         JANET_CORE_REG("getproto", janet_core_getproto), | ||||
|         JANET_CORE_REG("sandbox", janet_core_sandbox), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, corelib_cfuns); | ||||
| @@ -969,6 +1121,7 @@ static void janet_load_libs(JanetTable *env) { | ||||
|     janet_lib_tuple(env); | ||||
|     janet_lib_buffer(env); | ||||
|     janet_lib_table(env); | ||||
|     janet_lib_struct(env); | ||||
|     janet_lib_fiber(env); | ||||
|     janet_lib_os(env); | ||||
|     janet_lib_parse(env); | ||||
| @@ -987,100 +1140,101 @@ static void janet_load_libs(JanetTable *env) { | ||||
| #endif | ||||
| #ifdef JANET_EV | ||||
|     janet_lib_ev(env); | ||||
| #ifdef JANET_FILEWATCH | ||||
|     janet_lib_filewatch(env); | ||||
| #endif | ||||
| #endif | ||||
| #ifdef JANET_NET | ||||
|     janet_lib_net(env); | ||||
| #endif | ||||
| #ifdef JANET_FFI | ||||
|     janet_lib_ffi(env); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #ifdef JANET_BOOTSTRAP | ||||
|  | ||||
| JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|     JanetTable *env = (NULL != replacements) ? replacements : janet_table(0); | ||||
|     janet_quick_asm(env, JANET_FUN_MODULO, | ||||
|                     "mod", 2, 2, 2, 2, modulo_asm, sizeof(modulo_asm), | ||||
|                     JDOC("(mod dividend divisor)\n\n" | ||||
|                          "Returns the modulo of dividend / divisor.")); | ||||
|     janet_quick_asm(env, JANET_FUN_REMAINDER, | ||||
|                     "%", 2, 2, 2, 2, remainder_asm, sizeof(remainder_asm), | ||||
|                     JDOC("(% dividend divisor)\n\n" | ||||
|                          "Returns the remainder of dividend / divisor.")); | ||||
|     janet_quick_asm(env, JANET_FUN_CMP, | ||||
|                     "cmp", 2, 2, 2, 2, cmp_asm, sizeof(cmp_asm), | ||||
|                     JDOC("(cmp x y)\n\n" | ||||
|                          "Returns -1 if x is strictly less than y, 1 if y is strictly greater " | ||||
|                          "than x, and 0 otherwise. To return 0, x and y must be the exact same type.")); | ||||
|          "Returns -1 if x is strictly less than y, 1 if y is strictly greater " | ||||
|          "than x, and 0 otherwise. To return 0, x and y must be the exact same type.")); | ||||
|     janet_quick_asm(env, JANET_FUN_NEXT, | ||||
|                     "next", 2, 1, 2, 2, next_asm, sizeof(next_asm), | ||||
|                     JDOC("(next ds &opt key)\n\n" | ||||
|                          "Gets the next key in a data structure. Can be used to iterate through " | ||||
|                          "the keys of a data structure in an unspecified order. Keys are guaranteed " | ||||
|                          "to be seen only once per iteration if they data structure is not mutated " | ||||
|                          "during iteration. If key is nil, next returns the first key. If next " | ||||
|                          "returns nil, there are no more keys to iterate through.")); | ||||
|          "Gets the next key in a data structure. Can be used to iterate through " | ||||
|          "the keys of a data structure in an unspecified order. Keys are guaranteed " | ||||
|          "to be seen only once per iteration if the data structure is not mutated " | ||||
|          "during iteration. If key is nil, next returns the first key. If next " | ||||
|          "returns nil, there are no more keys to iterate through.")); | ||||
|     janet_quick_asm(env, JANET_FUN_PROP, | ||||
|                     "propagate", 2, 2, 2, 2, propagate_asm, sizeof(propagate_asm), | ||||
|                     JDOC("(propagate x fiber)\n\n" | ||||
|                          "Propagate a signal from a fiber to the current fiber. The resulting " | ||||
|                          "stack trace from the current fiber will include frames from fiber. If " | ||||
|                          "fiber is in a state that can be resumed, resuming the current fiber will " | ||||
|                          "first resume fiber. This function can be used to re-raise an error without " | ||||
|                          "losing the original stack trace.")); | ||||
|          "Propagate a signal from a fiber to the current fiber and " | ||||
|          "set the last value of the current fiber to `x`.  The signal " | ||||
|          "value is then available as the status of the current fiber. " | ||||
|          "The resulting stack trace from the current fiber will include " | ||||
|          "frames from fiber. If fiber is in a state that can be resumed, " | ||||
|          "resuming the current fiber will first resume `fiber`. " | ||||
|          "This function can be used to re-raise an error without losing " | ||||
|          "the original stack trace.")); | ||||
|     janet_quick_asm(env, JANET_FUN_DEBUG, | ||||
|                     "debug", 1, 0, 1, 1, debug_asm, sizeof(debug_asm), | ||||
|                     JDOC("(debug &opt x)\n\n" | ||||
|                          "Throws a debug signal that can be caught by a parent fiber and used to inspect " | ||||
|                          "the running state of the current fiber. Returns the value passed in by resume.")); | ||||
|          "Throws a debug signal that can be caught by a parent fiber and used to inspect " | ||||
|          "the running state of the current fiber. Returns the value passed in by resume.")); | ||||
|     janet_quick_asm(env, JANET_FUN_ERROR, | ||||
|                     "error", 1, 1, 1, 1, error_asm, sizeof(error_asm), | ||||
|                     JDOC("(error e)\n\n" | ||||
|                          "Throws an error e that can be caught and handled by a parent fiber.")); | ||||
|          "Throws an error e that can be caught and handled by a parent fiber.")); | ||||
|     janet_quick_asm(env, JANET_FUN_YIELD, | ||||
|                     "yield", 1, 0, 1, 2, yield_asm, sizeof(yield_asm), | ||||
|                     JDOC("(yield &opt x)\n\n" | ||||
|                          "Yield a value to a parent fiber. When a fiber yields, its execution is paused until " | ||||
|                          "another thread resumes it. The fiber will then resume, and the last yield call will " | ||||
|                          "return the value that was passed to resume.")); | ||||
|          "Yield a value to a parent fiber. When a fiber yields, its execution is paused until " | ||||
|          "another thread resumes it. The fiber will then resume, and the last yield call will " | ||||
|          "return the value that was passed to resume.")); | ||||
|     janet_quick_asm(env, JANET_FUN_CANCEL, | ||||
|                     "cancel", 2, 2, 2, 2, cancel_asm, sizeof(cancel_asm), | ||||
|                     JDOC("(cancel fiber err)\n\n" | ||||
|                          "Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. " | ||||
|                          "Returns the same result as resume.")); | ||||
|          "Resume a fiber but have it immediately raise an error. This lets a programmer unwind a pending fiber. " | ||||
|          "Returns the same result as resume.")); | ||||
|     janet_quick_asm(env, JANET_FUN_RESUME, | ||||
|                     "resume", 2, 1, 2, 2, resume_asm, sizeof(resume_asm), | ||||
|                     JDOC("(resume fiber &opt x)\n\n" | ||||
|                          "Resume a new or suspended fiber and optionally pass in a value to the fiber that " | ||||
|                          "will be returned to the last yield in the case of a pending fiber, or the argument to " | ||||
|                          "the dispatch function in the case of a new fiber. Returns either the return result of " | ||||
|                          "the fiber's dispatch function, or the value from the next yield call in fiber.")); | ||||
|          "Resume a new or suspended fiber and optionally pass in a value to the fiber that " | ||||
|          "will be returned to the last yield in the case of a pending fiber, or the argument to " | ||||
|          "the dispatch function in the case of a new fiber. Returns either the return result of " | ||||
|          "the fiber's dispatch function, or the value from the next yield call in fiber.")); | ||||
|     janet_quick_asm(env, JANET_FUN_IN, | ||||
|                     "in", 3, 2, 3, 4, in_asm, sizeof(in_asm), | ||||
|                     JDOC("(in ds key &opt dflt)\n\n" | ||||
|                          "Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, " | ||||
|                          "strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, " | ||||
|                          "and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can " | ||||
|                          "take any value as a key except nil and will return nil or dflt if not found.")); | ||||
|          "Get value in ds at key, works on associative data structures. Arrays, tuples, tables, structs, " | ||||
|          "strings, symbols, and buffers are all associative and can be used. Arrays, tuples, strings, buffers, " | ||||
|          "and symbols must use integer keys that are in bounds or an error is raised. Structs and tables can " | ||||
|          "take any value as a key except nil and will return nil or dflt if not found.")); | ||||
|     janet_quick_asm(env, JANET_FUN_GET, | ||||
|                     "get", 3, 2, 3, 4, get_asm, sizeof(in_asm), | ||||
|                     JDOC("(get ds key &opt dflt)\n\n" | ||||
|                          "Get the value mapped to key in data structure ds, and return dflt or nil if not found. " | ||||
|                          "Similar to in, but will not throw an error if the key is invalid for the data structure " | ||||
|                          "unless the data structure is an abstract type. In that case, the abstract type getter may throw " | ||||
|                          "an error.")); | ||||
|          "Get the value mapped to key in data structure ds, and return dflt or nil if not found. " | ||||
|          "Similar to in, but will not throw an error if the key is invalid for the data structure " | ||||
|          "unless the data structure is an abstract type. In that case, the abstract type getter may throw " | ||||
|          "an error.")); | ||||
|     janet_quick_asm(env, JANET_FUN_PUT, | ||||
|                     "put", 3, 3, 3, 3, put_asm, sizeof(put_asm), | ||||
|                     JDOC("(put ds key value)\n\n" | ||||
|                          "Associate a key with a value in any mutable associative data structure. Indexed data structures " | ||||
|                          "(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds " | ||||
|                          "value is provided. In an array, extra space will be filled with nils, and in a buffer, extra " | ||||
|                          "space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype " | ||||
|                          "will hide the association defined by the prototype, but will not mutate the prototype table. Putting " | ||||
|                          "a value nil into a table will remove the key from the table. Returns the data structure ds.")); | ||||
|          "Associate a key with a value in any mutable associative data structure. Indexed data structures " | ||||
|          "(arrays and buffers) only accept non-negative integer keys, and will expand if an out of bounds " | ||||
|          "value is provided. In an array, extra space will be filled with nils, and in a buffer, extra " | ||||
|          "space will be filled with 0 bytes. In a table, putting a key that is contained in the table prototype " | ||||
|          "will hide the association defined by the prototype, but will not mutate the prototype table. Putting " | ||||
|          "a value nil into a table will remove the key from the table. Returns the data structure ds.")); | ||||
|     janet_quick_asm(env, JANET_FUN_LENGTH, | ||||
|                     "length", 1, 1, 1, 1, length_asm, sizeof(length_asm), | ||||
|                     JDOC("(length ds)\n\n" | ||||
|                          "Returns the length or count of a data structure in constant time as an integer. For " | ||||
|                          "structs and tables, returns the number of key-value pairs in the data structure.")); | ||||
|          "Returns the length or count of a data structure in constant time as an integer. For " | ||||
|          "structs and tables, returns the number of key-value pairs in the data structure.")); | ||||
|     janet_quick_asm(env, JANET_FUN_BNOT, | ||||
|                     "bnot", 1, 1, 1, 1, bnot_asm, sizeof(bnot_asm), | ||||
|                     JDOC("(bnot x)\n\nReturns the bit-wise inverse of integer x.")); | ||||
| @@ -1089,62 +1243,74 @@ JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|     /* Variadic ops */ | ||||
|     templatize_varop(env, JANET_FUN_ADD, "+", 0, 0, JOP_ADD, | ||||
|                      JDOC("(+ & xs)\n\n" | ||||
|                           "Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0.")); | ||||
|          "Returns the sum of all xs. xs must be integers or real numbers only. If xs is empty, return 0.")); | ||||
|     templatize_varop(env, JANET_FUN_SUBTRACT, "-", 0, 0, JOP_SUBTRACT, | ||||
|                      JDOC("(- & xs)\n\n" | ||||
|                           "Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the " | ||||
|                           "negative value of that element. Otherwise, returns the first element in xs minus the sum of " | ||||
|                           "the rest of the elements.")); | ||||
|          "Returns the difference of xs. If xs is empty, returns 0. If xs has one element, returns the " | ||||
|          "negative value of that element. Otherwise, returns the first element in xs minus the sum of " | ||||
|          "the rest of the elements.")); | ||||
|     templatize_varop(env, JANET_FUN_MULTIPLY, "*", 1, 1, JOP_MULTIPLY, | ||||
|                      JDOC("(* & xs)\n\n" | ||||
|                           "Returns the product of all elements in xs. If xs is empty, returns 1.")); | ||||
|          "Returns the product of all elements in xs. If xs is empty, returns 1.")); | ||||
|     templatize_varop(env, JANET_FUN_DIVIDE, "/", 1, 1, JOP_DIVIDE, | ||||
|                      JDOC("(/ & xs)\n\n" | ||||
|                           "Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns " | ||||
|                           "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " | ||||
|                           "values.")); | ||||
|          "Returns the quotient of xs. If xs is empty, returns 1. If xs has one value x, returns " | ||||
|          "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " | ||||
|          "values.")); | ||||
|     templatize_varop(env, JANET_FUN_DIVIDE_FLOOR, "div", 1, 1, JOP_DIVIDE_FLOOR, | ||||
|                      JDOC("(div & xs)\n\n" | ||||
|          "Returns the floored division of xs. If xs is empty, returns 1. If xs has one value x, returns " | ||||
|          "the reciprocal of x. Otherwise return the first value of xs repeatedly divided by the remaining " | ||||
|          "values.")); | ||||
|     templatize_varop(env, JANET_FUN_MODULO, "mod", 0, 1, JOP_MODULO, | ||||
|                      JDOC("(mod & xs)\n\n" | ||||
|          "Returns the result of applying the modulo operator on the first value of xs with each remaining value. " | ||||
|          "`(mod x 0)` is defined to be `x`.")); | ||||
|     templatize_varop(env, JANET_FUN_REMAINDER, "%", 0, 1, JOP_REMAINDER, | ||||
|                      JDOC("(% & xs)\n\n" | ||||
|          "Returns the remainder of dividing the first value of xs by each remaining value.")); | ||||
|     templatize_varop(env, JANET_FUN_BAND, "band", -1, -1, JOP_BAND, | ||||
|                      JDOC("(band & xs)\n\n" | ||||
|                           "Returns the bit-wise and of all values in xs. Each x in xs must be an integer.")); | ||||
|          "Returns the bit-wise and of all values in xs. Each x in xs must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_BOR, "bor", 0, 0, JOP_BOR, | ||||
|                      JDOC("(bor & xs)\n\n" | ||||
|                           "Returns the bit-wise or of all values in xs. Each x in xs must be an integer.")); | ||||
|          "Returns the bit-wise or of all values in xs. Each x in xs must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_BXOR, "bxor", 0, 0, JOP_BXOR, | ||||
|                      JDOC("(bxor & xs)\n\n" | ||||
|                           "Returns the bit-wise xor of all values in xs. Each in xs must be an integer.")); | ||||
|          "Returns the bit-wise xor of all values in xs. Each in xs must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_LSHIFT, "blshift", 1, 1, JOP_SHIFT_LEFT, | ||||
|                      JDOC("(blshift x & shifts)\n\n" | ||||
|                           "Returns the value of x bit shifted left by the sum of all values in shifts. x " | ||||
|                           "and each element in shift must be an integer.")); | ||||
|          "Returns the value of x bit shifted left by the sum of all values in shifts. x " | ||||
|          "and each element in shift must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_RSHIFT, "brshift", 1, 1, JOP_SHIFT_RIGHT, | ||||
|                      JDOC("(brshift x & shifts)\n\n" | ||||
|                           "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|                           "and each element in shift must be an integer.")); | ||||
|          "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|          "and each element in shift must be an integer.")); | ||||
|     templatize_varop(env, JANET_FUN_RSHIFTU, "brushift", 1, 1, JOP_SHIFT_RIGHT_UNSIGNED, | ||||
|                      JDOC("(brushift x & shifts)\n\n" | ||||
|                           "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|                           "and each element in shift must be an integer. The sign of x is not preserved, so " | ||||
|                           "for positive shifts the return value will always be positive.")); | ||||
|          "Returns the value of x bit shifted right by the sum of all values in shifts. x " | ||||
|          "and each element in shift must be an integer. The sign of x is not preserved, so " | ||||
|          "for positive shifts the return value will always be positive.")); | ||||
|  | ||||
|     /* Variadic comparators */ | ||||
|     templatize_comparator(env, JANET_FUN_GT, ">", 0, JOP_GREATER_THAN, | ||||
|                           JDOC("(> & xs)\n\n" | ||||
|                                "Check if xs is in descending order. Returns a boolean.")); | ||||
|          "Check if xs is in descending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_LT, "<", 0, JOP_LESS_THAN, | ||||
|                           JDOC("(< & xs)\n\n" | ||||
|                                "Check if xs is in ascending order. Returns a boolean.")); | ||||
|          "Check if xs is in ascending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_GTE, ">=", 0, JOP_GREATER_THAN_EQUAL, | ||||
|                           JDOC("(>= & xs)\n\n" | ||||
|                                "Check if xs is in non-ascending order. Returns a boolean.")); | ||||
|          "Check if xs is in non-ascending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_LTE, "<=", 0, JOP_LESS_THAN_EQUAL, | ||||
|                           JDOC("(<= & xs)\n\n" | ||||
|                                "Check if xs is in non-descending order. Returns a boolean.")); | ||||
|          "Check if xs is in non-descending order. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_EQ, "=", 0, JOP_EQUALS, | ||||
|                           JDOC("(= & xs)\n\n" | ||||
|                                "Check if all values in xs are equal. Returns a boolean.")); | ||||
|          "Check if all values in xs are equal. Returns a boolean.")); | ||||
|     templatize_comparator(env, JANET_FUN_NEQ, "not=", 1, JOP_EQUALS, | ||||
|                           JDOC("(not= & xs)\n\n" | ||||
|                                "Check if any values in xs are not equal. Returns a boolean.")); | ||||
|          "Check if any values in xs are not equal. Returns a boolean.")); | ||||
|  | ||||
|     /* Platform detection */ | ||||
|     janet_def(env, "janet/version", janet_cstringv(JANET_VERSION), | ||||
| @@ -1153,7 +1319,7 @@ JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|               JDOC("The build identifier of the running janet program.")); | ||||
|     janet_def(env, "janet/config-bits", janet_wrap_integer(JANET_CURRENT_CONFIG_BITS), | ||||
|               JDOC("The flag set of config options from janetconf.h which is used to check " | ||||
|                    "if native modules are compatible with the host program.")); | ||||
|          "if native modules are compatible with the host program.")); | ||||
|  | ||||
|     /* Allow references to the environment */ | ||||
|     janet_def(env, "root-env", janet_wrap_table(env), | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -86,7 +86,7 @@ void janet_debug_find( | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         current = current->next; | ||||
|         current = current->data.next; | ||||
|     } | ||||
|     if (best_def) { | ||||
|         *def_out = best_def; | ||||
| @@ -96,15 +96,19 @@ void janet_debug_find( | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Error reporting. This can be emulated from within Janet, but for | ||||
|  * consitency with the top level code it is defined once. */ | ||||
| void janet_stacktrace(JanetFiber *fiber, Janet err) { | ||||
|     const char *prefix = janet_checktype(err, JANET_NIL) ? NULL : ""; | ||||
|     janet_stacktrace_ext(fiber, err, prefix); | ||||
| } | ||||
|  | ||||
| /* Error reporting. This can be emulated from within Janet, but for | ||||
|  * consistency with the top level code it is defined once. */ | ||||
| void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) { | ||||
|  | ||||
|     int32_t fi; | ||||
|     const char *errstr = (const char *)janet_to_string(err); | ||||
|     JanetFiber **fibers = NULL; | ||||
|  | ||||
|     /* Don't print error line if it is nil. */ | ||||
|     int wrote_error = janet_checktype(err, JANET_NIL); | ||||
|     int wrote_error = !prefix; | ||||
|  | ||||
|     int print_color = janet_truthy(janet_dyn("err-color")); | ||||
|     if (print_color) janet_eprintf("\x1b[31m"); | ||||
| @@ -126,11 +130,10 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) { | ||||
|             /* Print prelude to stack frame */ | ||||
|             if (!wrote_error) { | ||||
|                 JanetFiberStatus status = janet_fiber_status(fiber); | ||||
|                 const char *prefix = status == JANET_STATUS_ERROR ? "" : "status "; | ||||
|                 janet_eprintf("%s%s: %s\n", | ||||
|                               prefix, | ||||
|                               prefix ? prefix : "", | ||||
|                               janet_status_names[status], | ||||
|                               errstr); | ||||
|                               errstr ? errstr : janet_status_names[status]); | ||||
|                 wrote_error = 1; | ||||
|             } | ||||
|  | ||||
| @@ -161,7 +164,7 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) { | ||||
|                 } | ||||
|             } | ||||
|             if (frame->flags & JANET_STACKFRAME_TAILCALL) | ||||
|                 janet_eprintf(" (tailcall)"); | ||||
|                 janet_eprintf(" (tail call)"); | ||||
|             if (frame->func && frame->pc) { | ||||
|                 int32_t off = (int32_t)(frame->pc - def->bytecode); | ||||
|                 if (def->sourcemap) { | ||||
| @@ -177,6 +180,11 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) { | ||||
|                 } | ||||
|             } | ||||
|             janet_eprintf("\n"); | ||||
|             /* Print fiber points optionally. Clutters traces but provides info | ||||
|             if (i <= 0 && fi > 0) { | ||||
|                 janet_eprintf("  in parent fiber\n"); | ||||
|             } | ||||
|             */ | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -311,6 +319,7 @@ static Janet doframe(JanetStackFrame *frame) { | ||||
|     if (frame->func && frame->pc) { | ||||
|         Janet *stack = (Janet *)frame + JANET_FRAME_SIZE; | ||||
|         JanetArray *slots; | ||||
|         janet_assert(def != NULL, "def != NULL"); | ||||
|         off = (int32_t)(frame->pc - def->bytecode); | ||||
|         janet_table_put(t, janet_ckeywordv("pc"), janet_wrap_integer(off)); | ||||
|         if (def->sourcemap) { | ||||
| @@ -326,6 +335,27 @@ static Janet doframe(JanetStackFrame *frame) { | ||||
|         safe_memcpy(slots->data, stack, sizeof(Janet) * def->slotcount); | ||||
|         slots->count = def->slotcount; | ||||
|         janet_table_put(t, janet_ckeywordv("slots"), janet_wrap_array(slots)); | ||||
|         /* Add local bindings */ | ||||
|         if (def->symbolmap) { | ||||
|             JanetTable *local_bindings = janet_table(0); | ||||
|             for (int32_t i = def->symbolmap_length - 1; i >= 0; i--) { | ||||
|                 JanetSymbolMap jsm = def->symbolmap[i]; | ||||
|                 Janet value = janet_wrap_nil(); | ||||
|                 uint32_t pc = (uint32_t)(frame->pc - def->bytecode); | ||||
|                 if (jsm.birth_pc == UINT32_MAX) { | ||||
|                     JanetFuncEnv *env = frame->func->envs[jsm.death_pc]; | ||||
|                     if (env->offset > 0) { | ||||
|                         value = env->as.fiber->data[env->offset + jsm.slot_index]; | ||||
|                     } else { | ||||
|                         value = env->as.values[jsm.slot_index]; | ||||
|                     } | ||||
|                 } else if (pc >= jsm.birth_pc && pc < jsm.death_pc) { | ||||
|                     value = stack[jsm.slot_index]; | ||||
|                 } | ||||
|                 janet_table_put(local_bindings, janet_wrap_symbol(jsm.symbol), value); | ||||
|             } | ||||
|             janet_table_put(t, janet_ckeywordv("locals"), janet_wrap_table(local_bindings)); | ||||
|         } | ||||
|     } | ||||
|     return janet_wrap_table(t); | ||||
| } | ||||
| @@ -337,9 +367,9 @@ JANET_CORE_FN(cfun_debug_stack, | ||||
|               "stack frame is the first table in the array, and the bottom-most stack frame " | ||||
|               "is the last value. Each stack frame contains some of the following attributes:\n\n" | ||||
|               "* :c - true if the stack frame is a c function invocation\n\n" | ||||
|               "* :column - the current source column of the stack frame\n\n" | ||||
|               "* :source-column - the current source column of the stack frame\n\n" | ||||
|               "* :function - the function that the stack frame represents\n\n" | ||||
|               "* :line - the current source line of the stack frame\n\n" | ||||
|               "* :source-line - the current source line of the stack frame\n\n" | ||||
|               "* :name - the human-friendly name of the function\n\n" | ||||
|               "* :pc - integer indicating the location of the program counter\n\n" | ||||
|               "* :source - string with the file path or other identifier for the source code\n\n" | ||||
| @@ -361,14 +391,15 @@ JANET_CORE_FN(cfun_debug_stack, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_debug_stacktrace, | ||||
|               "(debug/stacktrace fiber &opt err)", | ||||
|               "(debug/stacktrace fiber &opt err prefix)", | ||||
|               "Prints a nice looking stacktrace for a fiber. Can optionally provide " | ||||
|               "an error value to print the stack trace with. If `err` is nil or not " | ||||
|               "an error value to print the stack trace with. If `prefix` is nil or not " | ||||
|               "provided, will skip the error line. Returns the fiber.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     janet_arity(argc, 1, 3); | ||||
|     JanetFiber *fiber = janet_getfiber(argv, 0); | ||||
|     Janet x = argc == 1 ? janet_wrap_nil() : argv[1]; | ||||
|     janet_stacktrace(fiber, x); | ||||
|     const char *prefix = janet_optcstring(argv, argc, 2, NULL); | ||||
|     janet_stacktrace_ext(fiber, x, prefix); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -26,6 +26,7 @@ | ||||
| #include "emit.h" | ||||
| #include "vector.h" | ||||
| #include "regalloc.h" | ||||
| #include "util.h" | ||||
| #endif | ||||
|  | ||||
| /* Get a register */ | ||||
| @@ -128,7 +129,8 @@ static void janetc_movenear(JanetCompiler *c, | ||||
|                     ((uint32_t)(src.envindex) << 16) | | ||||
|                     ((uint32_t)(dest) << 8) | | ||||
|                     JOP_LOAD_UPVALUE); | ||||
|     } else if (src.index > 0xFF || src.index != dest) { | ||||
|     } else if (src.index != dest) { | ||||
|         janet_assert(src.index >= 0, "bad slot"); | ||||
|         janetc_emit(c, | ||||
|                     ((uint32_t)(src.index) << 16) | | ||||
|                     ((uint32_t)(dest) << 8) | | ||||
| @@ -155,6 +157,7 @@ static void janetc_moveback(JanetCompiler *c, | ||||
|                     ((uint32_t)(src) << 8) | | ||||
|                     JOP_SET_UPVALUE); | ||||
|     } else if (dest.index != src) { | ||||
|         janet_assert(dest.index >= 0, "bad slot"); | ||||
|         janetc_emit(c, | ||||
|                     ((uint32_t)(dest.index) << 16) | | ||||
|                     ((uint32_t)(src) << 8) | | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
|   | ||||
							
								
								
									
										1708
									
								
								src/core/ev.c
									
									
									
									
									
								
							
							
						
						
									
										1708
									
								
								src/core/ev.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -26,9 +26,10 @@ | ||||
| #define JANET_FEATURES_H_defined | ||||
|  | ||||
| #if defined(__NetBSD__) || defined(__APPLE__) || defined(__OpenBSD__) \ | ||||
|     || defined(__bsdi__) || defined(__DragonFly__) | ||||
|     || defined(__bsdi__) || defined(__DragonFly__) || defined(__FreeBSD__) | ||||
| /* Use BSD source on any BSD systems, include OSX */ | ||||
| # define _BSD_SOURCE | ||||
| # define _POSIX_C_SOURCE 200809L | ||||
| #else | ||||
| /* Use POSIX feature flags */ | ||||
| # ifndef _POSIX_C_SOURCE | ||||
| @@ -36,13 +37,31 @@ | ||||
| # endif | ||||
| #endif | ||||
|  | ||||
| #if defined(__APPLE__) | ||||
| #define _DARWIN_C_SOURCE | ||||
| #endif | ||||
|  | ||||
| /* Needed for sched.h for cpu count */ | ||||
| #ifdef __linux__ | ||||
| #define _GNU_SOURCE | ||||
| #endif | ||||
|  | ||||
| #if defined(WIN32) || defined(_WIN32) | ||||
| #define WIN32_LEAN_AND_MEAN | ||||
| #endif | ||||
|  | ||||
| /* Needed for realpath on linux */ | ||||
| #if !defined(_XOPEN_SOURCE) && (defined(__linux__) || defined(__EMSCRIPTEN__)) | ||||
| #define _XOPEN_SOURCE 500 | ||||
| /* needed for inet_pton and InitializeSRWLock */ | ||||
| #ifdef __MINGW32__ | ||||
| #define _WIN32_WINNT _WIN32_WINNT_VISTA | ||||
| #endif | ||||
|  | ||||
| /* Needed for realpath on linux, as well as pthread rwlocks. */ | ||||
| #ifndef _XOPEN_SOURCE | ||||
| #define _XOPEN_SOURCE 600 | ||||
| #endif | ||||
| #if _XOPEN_SOURCE < 600 | ||||
| #undef _XOPEN_SOURCE | ||||
| #define _XOPEN_SOURCE 600 | ||||
| #endif | ||||
|  | ||||
| /* Needed for timegm and other extensions when building with -std=c99. | ||||
| @@ -52,4 +71,11 @@ | ||||
| #define _NETBSD_SOURCE | ||||
| #endif | ||||
|  | ||||
| /* Needed for several things when building with -std=c99. */ | ||||
| #if !__BSD_VISIBLE && (defined(__DragonFly__) || defined(__FreeBSD__)) | ||||
| #define __BSD_VISIBLE 1 | ||||
| #endif | ||||
|  | ||||
| #define _FILE_OFFSET_BITS 64 | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										1899
									
								
								src/core/ffi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1899
									
								
								src/core/ffi.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -39,8 +39,10 @@ static void fiber_reset(JanetFiber *fiber) { | ||||
|     fiber->env = NULL; | ||||
|     fiber->last_value = janet_wrap_nil(); | ||||
| #ifdef JANET_EV | ||||
|     fiber->waiting = NULL; | ||||
|     fiber->sched_id = 0; | ||||
|     fiber->ev_callback = NULL; | ||||
|     fiber->ev_state = NULL; | ||||
|     fiber->ev_stream = NULL; | ||||
|     fiber->supervisor_channel = NULL; | ||||
| #endif | ||||
|     janet_fiber_set_status(fiber, JANET_STATUS_NEW); | ||||
| @@ -81,10 +83,10 @@ JanetFiber *janet_fiber_reset(JanetFiber *fiber, JanetFunction *callee, int32_t | ||||
|         } | ||||
|         fiber->stacktop = newstacktop; | ||||
|     } | ||||
|     /* Don't panic on failure since we use this to implement janet_pcall */ | ||||
|     if (janet_fiber_funcframe(fiber, callee)) return NULL; | ||||
|     janet_fiber_frame(fiber)->flags |= JANET_STACKFRAME_ENTRANCE; | ||||
| #ifdef JANET_EV | ||||
|     fiber->waiting = NULL; | ||||
|     fiber->supervisor_channel = NULL; | ||||
| #endif | ||||
|     return fiber; | ||||
| @@ -237,8 +239,8 @@ int janet_fiber_funcframe(JanetFiber *fiber, JanetFunction *func) { | ||||
|                                          fiber->data + tuplehead, | ||||
|                                          oldtop - tuplehead) | ||||
|                                      : janet_wrap_tuple(janet_tuple_n( | ||||
|                                                  fiber->data + tuplehead, | ||||
|                                                  oldtop - tuplehead)); | ||||
|                                              fiber->data + tuplehead, | ||||
|                                              oldtop - tuplehead)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -368,8 +370,8 @@ int janet_fiber_funcframe_tail(JanetFiber *fiber, JanetFunction *func) { | ||||
|                                          fiber->data + tuplehead, | ||||
|                                          fiber->stacktop - tuplehead) | ||||
|                                      : janet_wrap_tuple(janet_tuple_n( | ||||
|                                                  fiber->data + tuplehead, | ||||
|                                                  fiber->stacktop - tuplehead)); | ||||
|                                              fiber->data + tuplehead, | ||||
|                                              fiber->stacktop - tuplehead)); | ||||
|         } | ||||
|         stacksize = tuplehead - fiber->stackstart + 1; | ||||
|     } else { | ||||
| @@ -477,10 +479,10 @@ JANET_CORE_FN(cfun_fiber_setenv, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_fiber_new, | ||||
|               "(fiber/new func &opt sigmask)", | ||||
|               "(fiber/new func &opt sigmask env)", | ||||
|               "Create a new fiber with function body func. Can optionally " | ||||
|               "take a set of signals to block from the current parent fiber " | ||||
|               "when called. The mask is specified as a keyword where each character " | ||||
|               "take a set of signals `sigmask` to capture from child fibers, " | ||||
|               "and an environment table `env`. The mask is specified as a keyword where each character " | ||||
|               "is used to indicate a signal to block. If the ev module is enabled, and " | ||||
|               "this fiber is used as an argument to `ev/go`, these \"blocked\" signals " | ||||
|               "will result in messages being sent to the supervisor channel. " | ||||
| @@ -495,19 +497,25 @@ JANET_CORE_FN(cfun_fiber_new, | ||||
|               "* :t - block termination signals: error + user[0-4]\n" | ||||
|               "* :u - block user signals\n" | ||||
|               "* :y - block yield signals\n" | ||||
|               "* :w - block await signals (user9)\n" | ||||
|               "* :r - block interrupt signals (user8)\n" | ||||
|               "* :0-9 - block a specific user signal\n\n" | ||||
|               "The sigmask argument also can take environment flags. If any mutually " | ||||
|               "exclusive flags are present, the last flag takes precedence.\n\n" | ||||
|               "* :i - inherit the environment from the current fiber\n" | ||||
|               "* :p - the environment table's prototype is the current environment table") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     janet_arity(argc, 1, 3); | ||||
|     JanetFunction *func = janet_getfunction(argv, 0); | ||||
|     JanetFiber *fiber; | ||||
|     if (func->def->min_arity > 1) { | ||||
|         janet_panicf("fiber function must accept 0 or 1 arguments"); | ||||
|     } | ||||
|     fiber = janet_fiber(func, 64, func->def->min_arity, NULL); | ||||
|     if (argc == 2) { | ||||
|     janet_assert(fiber != NULL, "bad fiber arity check"); | ||||
|     if (argc == 3 && !janet_checktype(argv[2], JANET_NIL)) { | ||||
|         fiber->env = janet_gettable(argv, 2); | ||||
|     } | ||||
|     if (argc >= 2) { | ||||
|         int32_t i; | ||||
|         JanetByteView view = janet_getbytes(argv, 1); | ||||
|         fiber->flags = JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP; | ||||
| @@ -518,7 +526,7 @@ JANET_CORE_FN(cfun_fiber_new, | ||||
|             } else { | ||||
|                 switch (view.bytes[i]) { | ||||
|                     default: | ||||
|                         janet_panicf("invalid flag %c, expected a, t, d, e, u, y, i, or p", view.bytes[i]); | ||||
|                         janet_panicf("invalid flag %c, expected a, t, d, e, u, y, w, r, i, or p", view.bytes[i]); | ||||
|                         break; | ||||
|                     case 'a': | ||||
|                         fiber->flags |= | ||||
| @@ -548,6 +556,12 @@ JANET_CORE_FN(cfun_fiber_new, | ||||
|                     case 'y': | ||||
|                         fiber->flags |= JANET_FIBER_MASK_YIELD; | ||||
|                         break; | ||||
|                     case 'w': | ||||
|                         fiber->flags |= JANET_FIBER_MASK_USER9; | ||||
|                         break; | ||||
|                     case 'r': | ||||
|                         fiber->flags |= JANET_FIBER_MASK_USER8; | ||||
|                         break; | ||||
|                     case 'i': | ||||
|                         if (!janet_vm.fiber->env) { | ||||
|                             janet_vm.fiber->env = janet_table(0); | ||||
| @@ -575,7 +589,9 @@ JANET_CORE_FN(cfun_fiber_status, | ||||
|               "* :error - the fiber has errored out\n" | ||||
|               "* :debug - the fiber is suspended in debug mode\n" | ||||
|               "* :pending - the fiber has been yielded\n" | ||||
|               "* :user(0-9) - the fiber is suspended by a user signal\n" | ||||
|               "* :user(0-7) - the fiber is suspended by a user signal\n" | ||||
|               "* :interrupted - the fiber was interrupted\n" | ||||
|               "* :suspended - the fiber is waiting to be resumed by the scheduler\n" | ||||
|               "* :alive - the fiber is currently running and cannot be resumed\n" | ||||
|               "* :new - the fiber has just been created and not yet run") { | ||||
|     janet_fixarity(argc, 1); | ||||
| @@ -625,11 +641,7 @@ JANET_CORE_FN(cfun_fiber_setmaxstack, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_fiber_can_resume, | ||||
|               "(fiber/can-resume? fiber)", | ||||
|               "Check if a fiber is finished and cannot be resumed.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetFiber *fiber = janet_getfiber(argv, 0); | ||||
| int janet_fiber_can_resume(JanetFiber *fiber) { | ||||
|     JanetFiberStatus s = janet_fiber_status(fiber); | ||||
|     int isFinished = s == JANET_STATUS_DEAD || | ||||
|                      s == JANET_STATUS_ERROR || | ||||
| @@ -638,11 +650,19 @@ JANET_CORE_FN(cfun_fiber_can_resume, | ||||
|                      s == JANET_STATUS_USER2 || | ||||
|                      s == JANET_STATUS_USER3 || | ||||
|                      s == JANET_STATUS_USER4; | ||||
|     return janet_wrap_boolean(!isFinished); | ||||
|     return !isFinished; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_fiber_can_resume, | ||||
|               "(fiber/can-resume? fiber)", | ||||
|               "Check if a fiber is finished and cannot be resumed.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetFiber *fiber = janet_getfiber(argv, 0); | ||||
|     return janet_wrap_boolean(janet_fiber_can_resume(fiber)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_fiber_last_value, | ||||
|               "(fiber/last-value)", | ||||
|               "(fiber/last-value fiber)", | ||||
|               "Get the last value returned or signaled from the fiber.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetFiber *fiber = janet_getfiber(argv, 0); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -56,6 +56,13 @@ | ||||
| #define JANET_FIBER_DID_LONGJUMP     0x8000000 | ||||
| #define JANET_FIBER_FLAG_MASK        0xF000000 | ||||
|  | ||||
| #define JANET_FIBER_EV_FLAG_CANCELED 0x10000 | ||||
| #define JANET_FIBER_EV_FLAG_SUSPENDED 0x20000 | ||||
| #define JANET_FIBER_FLAG_ROOT 0x40000 | ||||
| #define JANET_FIBER_EV_FLAG_IN_FLIGHT 0x1 | ||||
|  | ||||
| /* used only on windows, should otherwise be unset */ | ||||
|  | ||||
| #define janet_fiber_set_status(f, s) do {\ | ||||
|     (f)->flags &= ~JANET_FIBER_STATUS_MASK;\ | ||||
|     (f)->flags |= (s) << JANET_FIBER_STATUS_OFFSET;\ | ||||
|   | ||||
							
								
								
									
										688
									
								
								src/core/filewatch.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										688
									
								
								src/core/filewatch.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,688 @@ | ||||
| /* | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| * deal in the Software without restriction, including without limitation the | ||||
| * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||||
| * sell copies of the Software, and to permit persons to whom the Software is | ||||
| * furnished to do so, subject to the following conditions: | ||||
| * | ||||
| * The above copyright notice and this permission notice shall be included in | ||||
| * all copies or substantial portions of the Software. | ||||
| * | ||||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||||
| * IN THE SOFTWARE. | ||||
| */ | ||||
|  | ||||
| #ifndef JANET_AMALG | ||||
| #include "features.h" | ||||
| #include <janet.h> | ||||
| #include "util.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| #ifdef JANET_FILEWATCH | ||||
|  | ||||
| #ifdef JANET_LINUX | ||||
| #include <sys/inotify.h> | ||||
| #include <unistd.h> | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| typedef struct { | ||||
|     const char *name; | ||||
|     uint32_t flag; | ||||
| } JanetWatchFlagName; | ||||
|  | ||||
| typedef struct { | ||||
| #ifndef JANET_WINDOWS | ||||
|     JanetStream *stream; | ||||
| #endif | ||||
|     JanetTable *watch_descriptors; | ||||
|     JanetChannel *channel; | ||||
|     uint32_t default_flags; | ||||
|     int is_watching; | ||||
| } JanetWatcher; | ||||
|  | ||||
| #ifdef JANET_LINUX | ||||
|  | ||||
| #include <sys/inotify.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| static const JanetWatchFlagName watcher_flags_linux[] = { | ||||
|     {"access", IN_ACCESS}, | ||||
|     {"all", IN_ALL_EVENTS}, | ||||
|     {"attrib", IN_ATTRIB}, | ||||
|     {"close-nowrite", IN_CLOSE_NOWRITE}, | ||||
|     {"close-write", IN_CLOSE_WRITE}, | ||||
|     {"create", IN_CREATE}, | ||||
|     {"delete", IN_DELETE}, | ||||
|     {"delete-self", IN_DELETE_SELF}, | ||||
|     {"ignored", IN_IGNORED}, | ||||
|     {"modify", IN_MODIFY}, | ||||
|     {"move-self", IN_MOVE_SELF}, | ||||
|     {"moved-from", IN_MOVED_FROM}, | ||||
|     {"moved-to", IN_MOVED_TO}, | ||||
|     {"open", IN_OPEN}, | ||||
|     {"q-overflow", IN_Q_OVERFLOW}, | ||||
|     {"unmount", IN_UNMOUNT}, | ||||
| }; | ||||
|  | ||||
| static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|     uint32_t flags = 0; | ||||
|     for (int32_t i = 0; i < n; i++) { | ||||
|         if (!(janet_checktype(options[i], JANET_KEYWORD))) { | ||||
|             janet_panicf("expected keyword, got %v", options[i]); | ||||
|         } | ||||
|         JanetKeyword keyw = janet_unwrap_keyword(options[i]); | ||||
|         const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_linux, | ||||
|                                            sizeof(watcher_flags_linux) / sizeof(JanetWatchFlagName), | ||||
|                                            sizeof(JanetWatchFlagName), | ||||
|                                            keyw); | ||||
|         if (!result) { | ||||
|             janet_panicf("unknown inotify flag %v", options[i]); | ||||
|         } | ||||
|         flags |= result->flag; | ||||
|     } | ||||
|     return flags; | ||||
| } | ||||
|  | ||||
| static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { | ||||
|     int fd; | ||||
|     do { | ||||
|         fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); | ||||
|     } while (fd == -1 && errno == EINTR); | ||||
|     if (fd == -1) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
|     watcher->watch_descriptors = janet_table(0); | ||||
|     watcher->channel = channel; | ||||
|     watcher->default_flags = default_flags; | ||||
|     watcher->is_watching = 0; | ||||
|     watcher->stream = janet_stream(fd, JANET_STREAM_READABLE, NULL); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { | ||||
|     if (watcher->stream == NULL) janet_panic("watcher closed"); | ||||
|     int result; | ||||
|     do { | ||||
|         result = inotify_add_watch(watcher->stream->handle, path, flags); | ||||
|     } while (result == -1 && errno == EINTR); | ||||
|     if (result == -1) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
|     Janet name = janet_cstringv(path); | ||||
|     Janet wd = janet_wrap_integer(result); | ||||
|     janet_table_put(watcher->watch_descriptors, name, wd); | ||||
|     janet_table_put(watcher->watch_descriptors, wd, name); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { | ||||
|     if (watcher->stream == NULL) janet_panic("watcher closed"); | ||||
|     Janet check = janet_table_get(watcher->watch_descriptors, janet_cstringv(path)); | ||||
|     janet_assert(janet_checktype(check, JANET_NUMBER), "bad watch descriptor"); | ||||
|     int watch_handle = janet_unwrap_integer(check); | ||||
|     int result; | ||||
|     do { | ||||
|         result = inotify_rm_watch(watcher->stream->handle, watch_handle); | ||||
|     } while (result != -1 && errno == EINTR); | ||||
|     if (result == -1) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     JanetStream *stream = fiber->ev_stream; | ||||
|     JanetWatcher *watcher = *((JanetWatcher **) fiber->ev_state); | ||||
|     char buf[1024]; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_MARK: | ||||
|             janet_mark(janet_wrap_abstract(watcher)); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_ERR: { | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             break; | ||||
|         } | ||||
|     read_more: | ||||
|         case JANET_ASYNC_EVENT_HUP: | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|         case JANET_ASYNC_EVENT_READ: { | ||||
|             Janet name = janet_wrap_nil(); | ||||
|  | ||||
|             /* Assumption - read will never return partial events * | ||||
|              * From documentation: | ||||
|              * | ||||
|              * The behavior when the buffer given to read(2) is too small to | ||||
|              * return information about the next event depends on the kernel | ||||
|              * version: before Linux 2.6.21, read(2) returns 0; since Linux | ||||
|              * 2.6.21, read(2) fails with the error EINVAL.  Specifying a buffer | ||||
|              * of size | ||||
|              * | ||||
|              *     sizeof(struct inotify_event) + NAME_MAX + 1 | ||||
|              * | ||||
|              * will be sufficient to read at least one event. */ | ||||
|             ssize_t nread; | ||||
|             do { | ||||
|                 nread = read(stream->handle, buf, sizeof(buf)); | ||||
|             } while (nread == -1 && errno == EINTR); | ||||
|  | ||||
|             /* Check for errors - special case errors that can just be waited on to fix */ | ||||
|             if (nread == -1) { | ||||
|                 if (errno == EAGAIN || errno == EWOULDBLOCK) { | ||||
|                     break; | ||||
|                 } | ||||
|                 janet_cancel(fiber, janet_ev_lasterr()); | ||||
|                 fiber->ev_state = NULL; | ||||
|                 janet_async_end(fiber); | ||||
|                 break; | ||||
|             } | ||||
|             if (nread < (ssize_t) sizeof(struct inotify_event)) break; | ||||
|  | ||||
|             /* Iterate through all events read from the buffer */ | ||||
|             char *cursor = buf; | ||||
|             while (cursor < buf + nread) { | ||||
|                 struct inotify_event inevent; | ||||
|                 memcpy(&inevent, cursor, sizeof(inevent)); | ||||
|                 cursor += sizeof(inevent); | ||||
|                 /* Read path of inevent */ | ||||
|                 if (inevent.len) { | ||||
|                     name = janet_cstringv(cursor); | ||||
|                     cursor += inevent.len; | ||||
|                 } | ||||
|  | ||||
|                 /* Got an event */ | ||||
|                 Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); | ||||
|                 JanetKV *event = janet_struct_begin(6); | ||||
|                 janet_struct_put(event, janet_ckeywordv("wd"), janet_wrap_integer(inevent.wd)); | ||||
|                 janet_struct_put(event, janet_ckeywordv("wd-path"), path); | ||||
|                 if (janet_checktype(name, JANET_NIL)) { | ||||
|                     /* We were watching a file directly, so path is the full path. Split into dirname / basename */ | ||||
|                     JanetString spath = janet_unwrap_string(path); | ||||
|                     const uint8_t *cursor = spath + janet_string_length(spath); | ||||
|                     const uint8_t *cursor_end = cursor; | ||||
|                     while (cursor > spath && cursor[0] != '/') { | ||||
|                         cursor--; | ||||
|                     } | ||||
|                     if (cursor == spath) { | ||||
|                         janet_struct_put(event, janet_ckeywordv("dir-name"), path); | ||||
|                         janet_struct_put(event, janet_ckeywordv("file-name"), name); | ||||
|                     } else { | ||||
|                         janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(janet_string(spath, (cursor - spath)))); | ||||
|                         janet_struct_put(event, janet_ckeywordv("file-name"), janet_wrap_string(janet_string(cursor + 1, (cursor_end - cursor - 1)))); | ||||
|                     } | ||||
|                 } else { | ||||
|                     janet_struct_put(event, janet_ckeywordv("dir-name"), path); | ||||
|                     janet_struct_put(event, janet_ckeywordv("file-name"), name); | ||||
|                 } | ||||
|                 janet_struct_put(event, janet_ckeywordv("cookie"), janet_wrap_integer(inevent.cookie)); | ||||
|                 Janet etype = janet_ckeywordv("type"); | ||||
|                 const JanetWatchFlagName *wfn_end = watcher_flags_linux + sizeof(watcher_flags_linux) / sizeof(watcher_flags_linux[0]); | ||||
|                 for (const JanetWatchFlagName *wfn = watcher_flags_linux; wfn < wfn_end; wfn++) { | ||||
|                     if ((inevent.mask & wfn->flag) == wfn->flag) janet_struct_put(event, etype, janet_ckeywordv(wfn->name)); | ||||
|                 } | ||||
|                 Janet eventv = janet_wrap_struct(janet_struct_end(event)); | ||||
|  | ||||
|                 janet_channel_give(watcher->channel, eventv); | ||||
|             } | ||||
|  | ||||
|             /* Read some more if possible */ | ||||
|             goto read_more; | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_watcher_listen(JanetWatcher *watcher) { | ||||
|     if (watcher->is_watching) janet_panic("already watching"); | ||||
|     watcher->is_watching = 1; | ||||
|     JanetFunction *thunk = janet_thunk_delay(janet_wrap_nil()); | ||||
|     JanetFiber *fiber = janet_fiber(thunk, 64, 0, NULL); | ||||
|     JanetWatcher **state = janet_malloc(sizeof(JanetWatcher *)); /* Gross */ | ||||
|     *state = watcher; | ||||
|     janet_async_start_fiber(fiber, watcher->stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, state); | ||||
|     janet_gcroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_unlisten(JanetWatcher *watcher) { | ||||
|     if (!watcher->is_watching) return; | ||||
|     watcher->is_watching = 0; | ||||
|     janet_stream_close(watcher->stream); | ||||
|     janet_gcunroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| #elif JANET_WINDOWS | ||||
|  | ||||
| #define WATCHFLAG_RECURSIVE 0x100000u | ||||
|  | ||||
| static const JanetWatchFlagName watcher_flags_windows[] = { | ||||
|     { | ||||
|         "all", | ||||
|         FILE_NOTIFY_CHANGE_ATTRIBUTES | | ||||
|         FILE_NOTIFY_CHANGE_CREATION | | ||||
|         FILE_NOTIFY_CHANGE_DIR_NAME | | ||||
|         FILE_NOTIFY_CHANGE_FILE_NAME | | ||||
|         FILE_NOTIFY_CHANGE_LAST_ACCESS | | ||||
|         FILE_NOTIFY_CHANGE_LAST_WRITE | | ||||
|         FILE_NOTIFY_CHANGE_SECURITY | | ||||
|         FILE_NOTIFY_CHANGE_SIZE | | ||||
|         WATCHFLAG_RECURSIVE | ||||
|     }, | ||||
|     {"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES}, | ||||
|     {"creation", FILE_NOTIFY_CHANGE_CREATION}, | ||||
|     {"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME}, | ||||
|     {"file-name", FILE_NOTIFY_CHANGE_FILE_NAME}, | ||||
|     {"last-access", FILE_NOTIFY_CHANGE_LAST_ACCESS}, | ||||
|     {"last-write", FILE_NOTIFY_CHANGE_LAST_WRITE}, | ||||
|     {"recursive", WATCHFLAG_RECURSIVE}, | ||||
|     {"security", FILE_NOTIFY_CHANGE_SECURITY}, | ||||
|     {"size", FILE_NOTIFY_CHANGE_SIZE}, | ||||
| }; | ||||
|  | ||||
| static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|     uint32_t flags = 0; | ||||
|     for (int32_t i = 0; i < n; i++) { | ||||
|         if (!(janet_checktype(options[i], JANET_KEYWORD))) { | ||||
|             janet_panicf("expected keyword, got %v", options[i]); | ||||
|         } | ||||
|         JanetKeyword keyw = janet_unwrap_keyword(options[i]); | ||||
|         const JanetWatchFlagName *result = janet_strbinsearch(watcher_flags_windows, | ||||
|                                            sizeof(watcher_flags_windows) / sizeof(JanetWatchFlagName), | ||||
|                                            sizeof(JanetWatchFlagName), | ||||
|                                            keyw); | ||||
|         if (!result) { | ||||
|             janet_panicf("unknown windows filewatch flag %v", options[i]); | ||||
|         } | ||||
|         flags |= result->flag; | ||||
|     } | ||||
|     return flags; | ||||
| } | ||||
|  | ||||
| static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { | ||||
|     watcher->watch_descriptors = janet_table(0); | ||||
|     watcher->channel = channel; | ||||
|     watcher->default_flags = default_flags; | ||||
|     watcher->is_watching = 0; | ||||
| } | ||||
|  | ||||
| /* Since the file info padding includes embedded file names, we want to include more space for data. | ||||
|  * We also need to handle manually calculating changes if path names are too long, but ideally just avoid | ||||
|  * that scenario as much as possible */ | ||||
| #define FILE_INFO_PADDING (4096 * 4) | ||||
|  | ||||
| typedef struct { | ||||
|     OVERLAPPED overlapped; | ||||
|     JanetStream *stream; | ||||
|     JanetWatcher *watcher; | ||||
|     JanetFiber *fiber; | ||||
|     JanetString dir_path; | ||||
|     uint32_t flags; | ||||
|     uint64_t buf[FILE_INFO_PADDING / sizeof(uint64_t)]; /* Ensure alignment */ | ||||
| } OverlappedWatch; | ||||
|  | ||||
| #define NotifyChange FILE_NOTIFY_INFORMATION | ||||
|  | ||||
| static void read_dir_changes(OverlappedWatch *ow) { | ||||
|     BOOL result = ReadDirectoryChangesW(ow->stream->handle, | ||||
|                                         (NotifyChange *) ow->buf, | ||||
|                                         FILE_INFO_PADDING, | ||||
|                                         (ow->flags & WATCHFLAG_RECURSIVE) ? TRUE : FALSE, | ||||
|                                         ow->flags & ~WATCHFLAG_RECURSIVE, | ||||
|                                         NULL, | ||||
|                                         (OVERLAPPED *) ow, | ||||
|                                         NULL); | ||||
|     if (!result) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static const char *watcher_actions_windows[] = { | ||||
|     "unknown", | ||||
|     "added", | ||||
|     "removed", | ||||
|     "modified", | ||||
|     "renamed-old", | ||||
|     "renamed-new", | ||||
| }; | ||||
|  | ||||
| static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     OverlappedWatch *ow = (OverlappedWatch *) fiber->ev_state; | ||||
|     JanetWatcher *watcher = ow->watcher; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|             janet_async_in_flight(fiber); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_MARK: | ||||
|             janet_mark(janet_wrap_abstract(ow->stream)); | ||||
|             janet_mark(janet_wrap_fiber(ow->fiber)); | ||||
|             janet_mark(janet_wrap_abstract(watcher)); | ||||
|             janet_mark(janet_wrap_string(ow->dir_path)); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_table_remove(ow->watcher->watch_descriptors, janet_wrap_string(ow->dir_path)); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_ERR: | ||||
|         case JANET_ASYNC_EVENT_FAILED: | ||||
|             janet_stream_close(ow->stream); | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_COMPLETE: { | ||||
|             if (!watcher->is_watching) { | ||||
|                 janet_stream_close(ow->stream); | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             NotifyChange *fni = (NotifyChange *) ow->buf; | ||||
|  | ||||
|             while (1) { | ||||
|                 /* Got an event */ | ||||
|  | ||||
|                 /* Extract name */ | ||||
|                 Janet filename; | ||||
|                 if (fni->FileNameLength) { | ||||
|                     int32_t nbytes = (int32_t) WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), NULL, 0, NULL, NULL); | ||||
|                     janet_assert(nbytes, "bad utf8 path"); | ||||
|                     uint8_t *into = janet_string_begin(nbytes); | ||||
|                     WideCharToMultiByte(CP_UTF8, 0, fni->FileName, fni->FileNameLength / sizeof(wchar_t), (char *) into, nbytes, NULL, NULL); | ||||
|                     filename = janet_wrap_string(janet_string_end(into)); | ||||
|                 } else { | ||||
|                     filename = janet_cstringv(""); | ||||
|                 } | ||||
|  | ||||
|                 JanetKV *event = janet_struct_begin(3); | ||||
|                 janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action])); | ||||
|                 janet_struct_put(event, janet_ckeywordv("file-name"), filename); | ||||
|                 janet_struct_put(event, janet_ckeywordv("dir-name"), janet_wrap_string(ow->dir_path)); | ||||
|                 Janet eventv = janet_wrap_struct(janet_struct_end(event)); | ||||
|  | ||||
|                 janet_channel_give(watcher->channel, eventv); | ||||
|  | ||||
|                 /* Next event */ | ||||
|                 if (!fni->NextEntryOffset) break; | ||||
|                 fni = (NotifyChange *)((char *)fni + fni->NextEntryOffset); | ||||
|             } | ||||
|  | ||||
|             /* Make another call to read directory changes */ | ||||
|             read_dir_changes(ow); | ||||
|             janet_async_in_flight(fiber); | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void start_listening_ow(OverlappedWatch *ow) { | ||||
|     read_dir_changes(ow); | ||||
|     JanetStream *stream = ow->stream; | ||||
|     JanetFunction *thunk = janet_thunk_delay(janet_wrap_nil()); | ||||
|     JanetFiber *fiber = janet_fiber(thunk, 64, 0, NULL); | ||||
|     fiber->supervisor_channel = janet_root_fiber()->supervisor_channel; | ||||
|     ow->fiber = fiber; | ||||
|     janet_async_start_fiber(fiber, stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, ow); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { | ||||
|     HANDLE handle = CreateFileA(path, | ||||
|                                 FILE_LIST_DIRECTORY | GENERIC_READ, | ||||
|                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | ||||
|                                 NULL, | ||||
|                                 OPEN_EXISTING, | ||||
|                                 FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, | ||||
|                                 NULL); | ||||
|     if (handle == INVALID_HANDLE_VALUE) { | ||||
|         janet_panicv(janet_ev_lasterr()); | ||||
|     } | ||||
|     JanetStream *stream = janet_stream(handle, JANET_STREAM_READABLE, NULL); | ||||
|     OverlappedWatch *ow = janet_malloc(sizeof(OverlappedWatch)); | ||||
|     memset(ow, 0, sizeof(OverlappedWatch)); | ||||
|     ow->stream = stream; | ||||
|     ow->dir_path = janet_cstring(path); | ||||
|     ow->fiber = NULL; | ||||
|     Janet pathv = janet_wrap_string(ow->dir_path); | ||||
|     ow->flags = flags | watcher->default_flags; | ||||
|     ow->watcher = watcher; | ||||
|     ow->overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); /* Do we need this */ | ||||
|     Janet streamv = janet_wrap_pointer(ow); | ||||
|     janet_table_put(watcher->watch_descriptors, pathv, streamv); | ||||
|     if (watcher->is_watching) { | ||||
|         start_listening_ow(ow); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { | ||||
|     Janet pathv = janet_cstringv(path); | ||||
|     Janet streamv = janet_table_get(watcher->watch_descriptors, pathv); | ||||
|     if (janet_checktype(streamv, JANET_NIL)) { | ||||
|         janet_panicf("path %v is not being watched", pathv); | ||||
|     } | ||||
|     janet_table_remove(watcher->watch_descriptors, pathv); | ||||
|     OverlappedWatch *ow = janet_unwrap_pointer(streamv); | ||||
|     janet_stream_close(ow->stream); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_listen(JanetWatcher *watcher) { | ||||
|     if (watcher->is_watching) janet_panic("already watching"); | ||||
|     watcher->is_watching = 1; | ||||
|     for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { | ||||
|         const JanetKV *kv = watcher->watch_descriptors->data + i; | ||||
|         if (!janet_checktype(kv->value, JANET_POINTER)) continue; | ||||
|         OverlappedWatch *ow = janet_unwrap_pointer(kv->value); | ||||
|         start_listening_ow(ow); | ||||
|     } | ||||
|     janet_gcroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_unlisten(JanetWatcher *watcher) { | ||||
|     if (!watcher->is_watching) return; | ||||
|     watcher->is_watching = 0; | ||||
|     for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { | ||||
|         const JanetKV *kv = watcher->watch_descriptors->data + i; | ||||
|         if (!janet_checktype(kv->value, JANET_POINTER)) continue; | ||||
|         OverlappedWatch *ow = janet_unwrap_pointer(kv->value); | ||||
|         janet_stream_close(ow->stream); | ||||
|     } | ||||
|     janet_table_clear(watcher->watch_descriptors); | ||||
|     janet_gcunroot(janet_wrap_abstract(watcher)); | ||||
| } | ||||
|  | ||||
| #else | ||||
|  | ||||
| /* Default implementation */ | ||||
|  | ||||
| static uint32_t decode_watch_flags(const Janet *options, int32_t n) { | ||||
|     (void) options; | ||||
|     (void) n; | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static void janet_watcher_init(JanetWatcher *watcher, JanetChannel *channel, uint32_t default_flags) { | ||||
|     (void) watcher; | ||||
|     (void) channel; | ||||
|     (void) default_flags; | ||||
|     janet_panic("filewatch not supported on this platform"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_add(JanetWatcher *watcher, const char *path, uint32_t flags) { | ||||
|     (void) watcher; | ||||
|     (void) flags; | ||||
|     (void) path; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { | ||||
|     (void) watcher; | ||||
|     (void) path; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_listen(JanetWatcher *watcher) { | ||||
|     (void) watcher; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| static void janet_watcher_unlisten(JanetWatcher *watcher) { | ||||
|     (void) watcher; | ||||
|     janet_panic("nyi"); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* C Functions */ | ||||
|  | ||||
| static int janet_filewatch_mark(void *p, size_t s) { | ||||
|     JanetWatcher *watcher = (JanetWatcher *) p; | ||||
|     (void) s; | ||||
|     if (watcher->channel == NULL) return 0; /* Incomplete initialization */ | ||||
| #ifdef JANET_WINDOWS | ||||
|     for (int32_t i = 0; i < watcher->watch_descriptors->capacity; i++) { | ||||
|         const JanetKV *kv = watcher->watch_descriptors->data + i; | ||||
|         if (!janet_checktype(kv->value, JANET_POINTER)) continue; | ||||
|         OverlappedWatch *ow = janet_unwrap_pointer(kv->value); | ||||
|         janet_mark(janet_wrap_fiber(ow->fiber)); | ||||
|         janet_mark(janet_wrap_abstract(ow->stream)); | ||||
|         janet_mark(janet_wrap_string(ow->dir_path)); | ||||
|     } | ||||
| #else | ||||
|     janet_mark(janet_wrap_abstract(watcher->stream)); | ||||
| #endif | ||||
|     janet_mark(janet_wrap_abstract(watcher->channel)); | ||||
|     janet_mark(janet_wrap_table(watcher->watch_descriptors)); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static const JanetAbstractType janet_filewatch_at = { | ||||
|     "filewatch/watcher", | ||||
|     NULL, | ||||
|     janet_filewatch_mark, | ||||
|     JANET_ATEND_GCMARK | ||||
| }; | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_make, | ||||
|               "(filewatch/new channel &opt default-flags)", | ||||
|               "Create a new filewatcher that will give events to a channel channel. See `filewatch/add` for available flags.\n\n" | ||||
|               "When an event is triggered by the filewatcher, a struct containing information will be given to channel as with `ev/give`. " | ||||
|               "The contents of the channel depend on the OS, but will contain some common keys:\n\n" | ||||
|               "* `:type` -- the type of the event that was raised.\n\n" | ||||
|               "* `:file-name` -- the base file name of the file that triggered the event.\n\n" | ||||
|               "* `:dir-name` -- the directory name of the file that triggered the event.\n\n" | ||||
|               "Events also will contain keys specific to the host OS.\n\n" | ||||
|               "Windows has no extra properties on events.\n\n" | ||||
|               "Linux has the following extra properties on events:\n\n" | ||||
|               "* `:wd` -- the integer key returned by `filewatch/add` for the path that triggered this.\n\n" | ||||
|               "* `:wd-path` -- the string path for watched directory of file. For files, will be the same as `:file-name`, and for directories, will be the same as `:dir-name`.\n\n" | ||||
|               "* `:cookie` -- a randomized integer used to associate related events, such as :moved-from and :moved-to events.\n\n" | ||||
|               "") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_FS_READ); | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetChannel *channel = janet_getchannel(argv, 0); | ||||
|     JanetWatcher *watcher = janet_abstract(&janet_filewatch_at, sizeof(JanetWatcher)); | ||||
|     uint32_t default_flags = decode_watch_flags(argv + 1, argc - 1); | ||||
|     janet_watcher_init(watcher, channel, default_flags); | ||||
|     return janet_wrap_abstract(watcher); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_add, | ||||
|               "(filewatch/add watcher path &opt flags)", | ||||
|               "Add a path to the watcher. Available flags depend on the current OS, and are as follows:\n\n" | ||||
|               "Windows/MINGW (flags correspond to FILE_NOTIFY_CHANGE_* flags in win32 documentation):\n\n" | ||||
|               "* `:all` - trigger an event for all of the below triggers.\n\n" | ||||
|               "* `:attributes` - FILE_NOTIFY_CHANGE_ATTRIBUTES\n\n" | ||||
|               "* `:creation` - FILE_NOTIFY_CHANGE_CREATION\n\n" | ||||
|               "* `:dir-name` - FILE_NOTIFY_CHANGE_DIR_NAME\n\n" | ||||
|               "* `:last-access` - FILE_NOTIFY_CHANGE_LAST_ACCESS\n\n" | ||||
|               "* `:last-write` - FILE_NOTIFY_CHANGE_LAST_WRITE\n\n" | ||||
|               "* `:security` - FILE_NOTIFY_CHANGE_SECURITY\n\n" | ||||
|               "* `:size` - FILE_NOTIFY_CHANGE_SIZE\n\n" | ||||
|               "* `:recursive` - watch subdirectories recursively\n\n" | ||||
|               "Linux (flags correspond to IN_* flags from <sys/inotify.h>):\n\n" | ||||
|               "* `:access` - IN_ACCESS\n\n" | ||||
|               "* `:all` - IN_ALL_EVENTS\n\n" | ||||
|               "* `:attrib` - IN_ATTRIB\n\n" | ||||
|               "* `:close-nowrite` - IN_CLOSE_NOWRITE\n\n" | ||||
|               "* `:close-write` - IN_CLOSE_WRITE\n\n" | ||||
|               "* `:create` - IN_CREATE\n\n" | ||||
|               "* `:delete` - IN_DELETE\n\n" | ||||
|               "* `:delete-self` - IN_DELETE_SELF\n\n" | ||||
|               "* `:ignored` - IN_IGNORED\n\n" | ||||
|               "* `:modify` - IN_MODIFY\n\n" | ||||
|               "* `:move-self` - IN_MOVE_SELF\n\n" | ||||
|               "* `:moved-from` - IN_MOVED_FROM\n\n" | ||||
|               "* `:moved-to` - IN_MOVED_TO\n\n" | ||||
|               "* `:open` - IN_OPEN\n\n" | ||||
|               "* `:q-overflow` - IN_Q_OVERFLOW\n\n" | ||||
|               "* `:unmount` - IN_UNMOUNT\n\n\n" | ||||
|               "On Windows, events will have the following possible types:\n\n" | ||||
|               "* `:unknown`\n\n" | ||||
|               "* `:added`\n\n" | ||||
|               "* `:removed`\n\n" | ||||
|               "* `:modified`\n\n" | ||||
|               "* `:renamed-old`\n\n" | ||||
|               "* `:renamed-new`\n\n" | ||||
|               "On Linux, events will a `:type` corresponding to the possible flags, excluding `:all`.\n" | ||||
|               "") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     const char *path = janet_getcstring(argv, 1); | ||||
|     uint32_t flags = watcher->default_flags | decode_watch_flags(argv + 2, argc - 2); | ||||
|     janet_watcher_add(watcher, path, flags); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_remove, | ||||
|               "(filewatch/remove watcher path)", | ||||
|               "Remove a path from the watcher.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     const char *path = janet_getcstring(argv, 1); | ||||
|     janet_watcher_remove(watcher, path); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_listen, | ||||
|               "(filewatch/listen watcher)", | ||||
|               "Listen for changes in the watcher.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     janet_watcher_listen(watcher); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_filewatch_unlisten, | ||||
|               "(filewatch/unlisten watcher)", | ||||
|               "Stop listening for changes on a given watcher.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetWatcher *watcher = janet_getabstract(argv, 0, &janet_filewatch_at); | ||||
|     janet_watcher_unlisten(watcher); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| /* Module entry point */ | ||||
| void janet_lib_filewatch(JanetTable *env) { | ||||
|     JanetRegExt cfuns[] = { | ||||
|         JANET_CORE_REG("filewatch/new", cfun_filewatch_make), | ||||
|         JANET_CORE_REG("filewatch/add", cfun_filewatch_add), | ||||
|         JANET_CORE_REG("filewatch/remove", cfun_filewatch_remove), | ||||
|         JANET_CORE_REG("filewatch/listen", cfun_filewatch_listen), | ||||
|         JANET_CORE_REG("filewatch/unlisten", cfun_filewatch_unlisten), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, cfuns); | ||||
| } | ||||
|  | ||||
| #endif | ||||
| #endif | ||||
							
								
								
									
										193
									
								
								src/core/gc.c
									
									
									
									
									
								
							
							
						
						
									
										193
									
								
								src/core/gc.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -132,6 +132,24 @@ static void janet_mark_many(const Janet *values, int32_t n) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Mark a bunch of key values items in memory */ | ||||
| static void janet_mark_keys(const JanetKV *kvs, int32_t n) { | ||||
|     const JanetKV *end = kvs + n; | ||||
|     while (kvs < end) { | ||||
|         janet_mark(kvs->key); | ||||
|         kvs++; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Mark a bunch of key values items in memory */ | ||||
| static void janet_mark_values(const JanetKV *kvs, int32_t n) { | ||||
|     const JanetKV *end = kvs + n; | ||||
|     while (kvs < end) { | ||||
|         janet_mark(kvs->value); | ||||
|         kvs++; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Mark a bunch of key values items in memory */ | ||||
| static void janet_mark_kvs(const JanetKV *kvs, int32_t n) { | ||||
|     const JanetKV *end = kvs + n; | ||||
| @@ -146,7 +164,9 @@ static void janet_mark_array(JanetArray *array) { | ||||
|     if (janet_gc_reachable(array)) | ||||
|         return; | ||||
|     janet_gc_mark(array); | ||||
|     janet_mark_many(array->data, array->count); | ||||
|     if (janet_gc_type((JanetGCObject *) array) == JANET_MEMORY_ARRAY) { | ||||
|         janet_mark_many(array->data, array->count); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void janet_mark_table(JanetTable *table) { | ||||
| @@ -154,7 +174,15 @@ recur: /* Manual tail recursion */ | ||||
|     if (janet_gc_reachable(table)) | ||||
|         return; | ||||
|     janet_gc_mark(table); | ||||
|     janet_mark_kvs(table->data, table->capacity); | ||||
|     enum JanetMemoryType memtype = janet_gc_type(table); | ||||
|     if (memtype == JANET_MEMORY_TABLE_WEAKK) { | ||||
|         janet_mark_values(table->data, table->capacity); | ||||
|     } else if (memtype == JANET_MEMORY_TABLE_WEAKV) { | ||||
|         janet_mark_keys(table->data, table->capacity); | ||||
|     } else if (memtype == JANET_MEMORY_TABLE) { | ||||
|         janet_mark_kvs(table->data, table->capacity); | ||||
|     } | ||||
|     /* do nothing for JANET_MEMORY_TABLE_WEAKKV */ | ||||
|     if (table->proto) { | ||||
|         table = table->proto; | ||||
|         goto recur; | ||||
| @@ -162,10 +190,13 @@ recur: /* Manual tail recursion */ | ||||
| } | ||||
|  | ||||
| static void janet_mark_struct(const JanetKV *st) { | ||||
| recur: | ||||
|     if (janet_gc_reachable(janet_struct_head(st))) | ||||
|         return; | ||||
|     janet_gc_mark(janet_struct_head(st)); | ||||
|     janet_mark_kvs(st, janet_struct_capacity(st)); | ||||
|     st = janet_struct_proto(st); | ||||
|     if (st) goto recur; | ||||
| } | ||||
|  | ||||
| static void janet_mark_tuple(const Janet *tuple) { | ||||
| @@ -206,6 +237,12 @@ static void janet_mark_funcdef(JanetFuncDef *def) { | ||||
|         janet_mark_string(def->source); | ||||
|     if (def->name) | ||||
|         janet_mark_string(def->name); | ||||
|     if (def->symbolmap) { | ||||
|         for (int i = 0; i < def->symbolmap_length; i++) { | ||||
|             janet_mark_string(def->symbolmap[i].symbol); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| static void janet_mark_function(JanetFunction *func) { | ||||
| @@ -259,6 +296,12 @@ recur: | ||||
|     if (fiber->supervisor_channel) { | ||||
|         janet_mark_abstract(fiber->supervisor_channel); | ||||
|     } | ||||
|     if (fiber->ev_stream) { | ||||
|         janet_mark_abstract(fiber->ev_stream); | ||||
|     } | ||||
|     if (fiber->ev_callback) { | ||||
|         fiber->ev_callback(fiber, JANET_ASYNC_EVENT_MARK); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     /* Explicit tail recursion */ | ||||
| @@ -278,14 +321,26 @@ static void janet_deinit_block(JanetGCObject *mem) { | ||||
|             janet_symbol_deinit(((JanetStringHead *) mem)->data); | ||||
|             break; | ||||
|         case JANET_MEMORY_ARRAY: | ||||
|         case JANET_MEMORY_ARRAY_WEAK: | ||||
|             janet_free(((JanetArray *) mem)->data); | ||||
|             break; | ||||
|         case JANET_MEMORY_TABLE: | ||||
|         case JANET_MEMORY_TABLE_WEAKK: | ||||
|         case JANET_MEMORY_TABLE_WEAKV: | ||||
|         case JANET_MEMORY_TABLE_WEAKKV: | ||||
|             janet_free(((JanetTable *) mem)->data); | ||||
|             break; | ||||
|         case JANET_MEMORY_FIBER: | ||||
|             janet_free(((JanetFiber *)mem)->data); | ||||
|             break; | ||||
|         case JANET_MEMORY_FIBER: { | ||||
|             JanetFiber *f = (JanetFiber *)mem; | ||||
| #ifdef JANET_EV | ||||
|             if (f->ev_state && !(f->flags & JANET_FIBER_EV_FLAG_IN_FLIGHT)) { | ||||
|                 janet_ev_dec_refcount(); | ||||
|                 janet_free(f->ev_state); | ||||
|             } | ||||
| #endif | ||||
|             janet_free(f->data); | ||||
|         } | ||||
|         break; | ||||
|         case JANET_MEMORY_BUFFER: | ||||
|             janet_buffer_deinit((JanetBuffer *) mem); | ||||
|             break; | ||||
| @@ -311,19 +366,85 @@ static void janet_deinit_block(JanetGCObject *mem) { | ||||
|             janet_free(def->bytecode); | ||||
|             janet_free(def->sourcemap); | ||||
|             janet_free(def->closure_bitset); | ||||
|             janet_free(def->symbolmap); | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Check that a value x has been visited in the mark phase */ | ||||
| static int janet_check_liveref(Janet x) { | ||||
|     switch (janet_type(x)) { | ||||
|         default: | ||||
|             return 1; | ||||
|         case JANET_ARRAY: | ||||
|         case JANET_TABLE: | ||||
|         case JANET_FUNCTION: | ||||
|         case JANET_BUFFER: | ||||
|         case JANET_FIBER: | ||||
|             return janet_gc_reachable(janet_unwrap_pointer(x)); | ||||
|         case JANET_STRING: | ||||
|         case JANET_SYMBOL: | ||||
|         case JANET_KEYWORD: | ||||
|             return janet_gc_reachable(janet_string_head(janet_unwrap_string(x))); | ||||
|         case JANET_ABSTRACT: | ||||
|             return janet_gc_reachable(janet_abstract_head(janet_unwrap_abstract(x))); | ||||
|         case JANET_TUPLE: | ||||
|             return janet_gc_reachable(janet_tuple_head(janet_unwrap_tuple(x))); | ||||
|         case JANET_STRUCT: | ||||
|             return janet_gc_reachable(janet_struct_head(janet_unwrap_struct(x))); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Iterate over all allocated memory, and free memory that is not | ||||
|  * marked as reachable. Flip the gc color flag for next sweep. */ | ||||
| void janet_sweep() { | ||||
|     JanetGCObject *previous = NULL; | ||||
|     JanetGCObject *current = janet_vm.blocks; | ||||
|     JanetGCObject *current = janet_vm.weak_blocks; | ||||
|     JanetGCObject *next; | ||||
|  | ||||
|     /* Sweep weak heap to drop weak refs */ | ||||
|     while (NULL != current) { | ||||
|         next = current->next; | ||||
|         next = current->data.next; | ||||
|         if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) { | ||||
|             /* Check for dead references */ | ||||
|             enum JanetMemoryType type = janet_gc_type(current); | ||||
|             if (type == JANET_MEMORY_ARRAY_WEAK) { | ||||
|                 JanetArray *array = (JanetArray *) current; | ||||
|                 for (uint32_t i = 0; i < (uint32_t) array->count; i++) { | ||||
|                     if (!janet_check_liveref(array->data[i])) { | ||||
|                         array->data[i] = janet_wrap_nil(); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 JanetTable *table = (JanetTable *) current; | ||||
|                 int check_values = (type == JANET_MEMORY_TABLE_WEAKV) || (type == JANET_MEMORY_TABLE_WEAKKV); | ||||
|                 int check_keys = (type == JANET_MEMORY_TABLE_WEAKK) || (type == JANET_MEMORY_TABLE_WEAKKV); | ||||
|                 JanetKV *end = table->data + table->capacity; | ||||
|                 JanetKV *kvs = table->data; | ||||
|                 while (kvs < end) { | ||||
|                     int drop = 0; | ||||
|                     if (check_keys && !janet_check_liveref(kvs->key)) drop = 1; | ||||
|                     if (check_values && !janet_check_liveref(kvs->value)) drop = 1; | ||||
|                     if (drop) { | ||||
|                         /* Inlined from janet_table_remove without search */ | ||||
|                         table->count--; | ||||
|                         table->deleted++; | ||||
|                         kvs->key = janet_wrap_nil(); | ||||
|                         kvs->value = janet_wrap_false(); | ||||
|                     } | ||||
|                     kvs++; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         current = next; | ||||
|     } | ||||
|  | ||||
|     /* Sweep weak heap to free blocks */ | ||||
|     previous = NULL; | ||||
|     current = janet_vm.weak_blocks; | ||||
|     while (NULL != current) { | ||||
|         next = current->data.next; | ||||
|         if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) { | ||||
|             previous = current; | ||||
|             current->flags &= ~JANET_MEM_REACHABLE; | ||||
| @@ -331,7 +452,28 @@ void janet_sweep() { | ||||
|             janet_vm.block_count--; | ||||
|             janet_deinit_block(current); | ||||
|             if (NULL != previous) { | ||||
|                 previous->next = next; | ||||
|                 previous->data.next = next; | ||||
|             } else { | ||||
|                 janet_vm.weak_blocks = next; | ||||
|             } | ||||
|             janet_free(current); | ||||
|         } | ||||
|         current = next; | ||||
|     } | ||||
|  | ||||
|     /* Sweep main heap to free blocks */ | ||||
|     previous = NULL; | ||||
|     current = janet_vm.blocks; | ||||
|     while (NULL != current) { | ||||
|         next = current->data.next; | ||||
|         if (current->flags & (JANET_MEM_REACHABLE | JANET_MEM_DISABLED)) { | ||||
|             previous = current; | ||||
|             current->flags &= ~JANET_MEM_REACHABLE; | ||||
|         } else { | ||||
|             janet_vm.block_count--; | ||||
|             janet_deinit_block(current); | ||||
|             if (NULL != previous) { | ||||
|                 previous->data.next = next; | ||||
|             } else { | ||||
|                 janet_vm.blocks = next; | ||||
|             } | ||||
| @@ -339,6 +481,7 @@ void janet_sweep() { | ||||
|         } | ||||
|         current = next; | ||||
|     } | ||||
|  | ||||
| #ifdef JANET_EV | ||||
|     /* Sweep threaded abstract types for references to decrement */ | ||||
|     JanetKV *items = janet_vm.threaded_abstracts.data; | ||||
| @@ -360,14 +503,15 @@ void janet_sweep() { | ||||
|                     if (head->type->gc) { | ||||
|                         janet_assert(!head->type->gc(head->data, head->size), "finalizer failed"); | ||||
|                     } | ||||
|                     /* Mark as tombstone in place */ | ||||
|                     items[i].key = janet_wrap_nil(); | ||||
|                     items[i].value = janet_wrap_false(); | ||||
|                     janet_vm.threaded_abstracts.deleted++; | ||||
|                     janet_vm.threaded_abstracts.count--; | ||||
|                     /* Free memory */ | ||||
|                     janet_free(janet_abstract_head(abst)); | ||||
|                 } | ||||
|  | ||||
|                 /* Mark as tombstone in place */ | ||||
|                 items[i].key = janet_wrap_nil(); | ||||
|                 items[i].value = janet_wrap_false(); | ||||
|                 janet_vm.threaded_abstracts.deleted++; | ||||
|                 janet_vm.threaded_abstracts.count--; | ||||
|             } | ||||
|  | ||||
|             /* Reset for next sweep */ | ||||
| @@ -395,8 +539,15 @@ void *janet_gcalloc(enum JanetMemoryType type, size_t size) { | ||||
|  | ||||
|     /* Prepend block to heap list */ | ||||
|     janet_vm.next_collection += size; | ||||
|     mem->next = janet_vm.blocks; | ||||
|     janet_vm.blocks = mem; | ||||
|     if (type < JANET_MEMORY_TABLE_WEAKK) { | ||||
|         /* normal heap */ | ||||
|         mem->data.next = janet_vm.blocks; | ||||
|         janet_vm.blocks = mem; | ||||
|     } else { | ||||
|         /* weak heap */ | ||||
|         mem->data.next = janet_vm.weak_blocks; | ||||
|         janet_vm.weak_blocks = mem; | ||||
|     } | ||||
|     janet_vm.block_count++; | ||||
|  | ||||
|     return (void *)mem; | ||||
| @@ -427,7 +578,8 @@ void janet_collect(void) { | ||||
|     uint32_t i; | ||||
|     if (janet_vm.gc_suspend) return; | ||||
|     depth = JANET_RECURSION_GUARD; | ||||
|     /* Try and prevent many major collections back to back. | ||||
|     janet_vm.gc_mark_phase = 1; | ||||
|     /* Try to prevent many major collections back to back. | ||||
|      * A full collection will take O(janet_vm.block_count) time. | ||||
|      * If we have a large heap, make sure our interval is not too | ||||
|      * small so we won't make many collections over it. This is just a | ||||
| @@ -446,6 +598,7 @@ void janet_collect(void) { | ||||
|         Janet x = janet_vm.roots[--janet_vm.root_count]; | ||||
|         janet_mark(x); | ||||
|     } | ||||
|     janet_vm.gc_mark_phase = 0; | ||||
|     janet_sweep(); | ||||
|     janet_vm.next_collection = 0; | ||||
|     janet_free_all_scratch(); | ||||
| @@ -532,7 +685,7 @@ void janet_clear_memory(void) { | ||||
|     JanetGCObject *current = janet_vm.blocks; | ||||
|     while (NULL != current) { | ||||
|         janet_deinit_block(current); | ||||
|         JanetGCObject *next = current->next; | ||||
|         JanetGCObject *next = current->data.next; | ||||
|         janet_free(current); | ||||
|         current = next; | ||||
|     } | ||||
| @@ -549,7 +702,9 @@ void janet_gcunlock(int handle) { | ||||
|     janet_vm.gc_suspend = handle; | ||||
| } | ||||
|  | ||||
| /* Scratch memory API */ | ||||
| /* Scratch memory API | ||||
|  * Scratch memory allocations do not need to be free (but optionally can be), and will be automatically cleaned | ||||
|  * up in the next call to janet_collect. */ | ||||
|  | ||||
| void *janet_smalloc(size_t size) { | ||||
|     JanetScratch *s = janet_malloc(sizeof(JanetScratch) + size); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -57,10 +57,14 @@ enum JanetMemoryType { | ||||
|     JANET_MEMORY_FUNCENV, | ||||
|     JANET_MEMORY_FUNCDEF, | ||||
|     JANET_MEMORY_THREADED_ABSTRACT, | ||||
|     JANET_MEMORY_TABLE_WEAKK, | ||||
|     JANET_MEMORY_TABLE_WEAKV, | ||||
|     JANET_MEMORY_TABLE_WEAKKV, | ||||
|     JANET_MEMORY_ARRAY_WEAK | ||||
| }; | ||||
|  | ||||
| /* To allocate collectable memory, one must call janet_alloc, initialize the memory, | ||||
|  * and then call when janet_enablegc when it is initailize and reachable by the gc (on the JANET stack) */ | ||||
|  * and then call when janet_enablegc when it is initialized and reachable by the gc (on the JANET stack) */ | ||||
| void *janet_gcalloc(enum JanetMemoryType type, size_t size); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose & contributors | ||||
| * Copyright (c) 2025 Calvin Rose & contributors | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -73,13 +73,13 @@ static void *int64_unmarshal(JanetMarshalContext *ctx) { | ||||
|  | ||||
| static void it_s64_tostring(void *p, JanetBuffer *buffer) { | ||||
|     char str[32]; | ||||
|     sprintf(str, "%" PRId64, *((int64_t *)p)); | ||||
|     snprintf(str, sizeof(str), "%" PRId64, *((int64_t *)p)); | ||||
|     janet_buffer_push_cstring(buffer, str); | ||||
| } | ||||
|  | ||||
| static void it_u64_tostring(void *p, JanetBuffer *buffer) { | ||||
|     char str[32]; | ||||
|     sprintf(str, "%" PRIu64, *((uint64_t *)p)); | ||||
|     snprintf(str, sizeof(str), "%" PRIu64, *((uint64_t *)p)); | ||||
|     janet_buffer_push_cstring(buffer, str); | ||||
| } | ||||
|  | ||||
| @@ -118,10 +118,9 @@ int64_t janet_unwrap_s64(Janet x) { | ||||
|         default: | ||||
|             break; | ||||
|         case JANET_NUMBER : { | ||||
|             double dbl = janet_unwrap_number(x); | ||||
|             if (fabs(dbl) <=  MAX_INT_IN_DBL) | ||||
|                 return (int64_t)dbl; | ||||
|             break; | ||||
|             double d = janet_unwrap_number(x); | ||||
|             if (!janet_checkint64range(d)) break; | ||||
|             return (int64_t) d; | ||||
|         } | ||||
|         case JANET_STRING: { | ||||
|             int64_t value; | ||||
| @@ -138,7 +137,7 @@ int64_t janet_unwrap_s64(Janet x) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     janet_panicf("bad s64 initializer: %t", x); | ||||
|     janet_panicf("can not convert %t %q to 64 bit signed integer", x, x); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @@ -147,12 +146,9 @@ uint64_t janet_unwrap_u64(Janet x) { | ||||
|         default: | ||||
|             break; | ||||
|         case JANET_NUMBER : { | ||||
|             double dbl = janet_unwrap_number(x); | ||||
|             /* Allow negative values to be cast to "wrap around". | ||||
|              * This let's addition and subtraction work as expected. */ | ||||
|             if (fabs(dbl) <=  MAX_INT_IN_DBL) | ||||
|                 return (uint64_t)dbl; | ||||
|             break; | ||||
|             double d = janet_unwrap_number(x); | ||||
|             if (!janet_checkuint64range(d)) break; | ||||
|             return (uint64_t) d; | ||||
|         } | ||||
|         case JANET_STRING: { | ||||
|             uint64_t value; | ||||
| @@ -169,7 +165,7 @@ uint64_t janet_unwrap_u64(Janet x) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     janet_panicf("bad u64 initializer: %t", x); | ||||
|     janet_panicf("can not convert %t %q to a 64 bit unsigned integer", x, x); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| @@ -195,18 +191,104 @@ Janet janet_wrap_u64(uint64_t x) { | ||||
|  | ||||
| JANET_CORE_FN(cfun_it_s64_new, | ||||
|               "(int/s64 value)", | ||||
|               "Create a boxed signed 64 bit integer from a string value.") { | ||||
|               "Create a boxed signed 64 bit integer from a string value or a number.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_s64(janet_unwrap_s64(argv[0])); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_it_u64_new, | ||||
|               "(int/u64 value)", | ||||
|               "Create a boxed unsigned 64 bit integer from a string value.") { | ||||
|               "Create a boxed unsigned 64 bit integer from a string value or a number.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_u64(janet_unwrap_u64(argv[0])); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_to_number, | ||||
|               "(int/to-number value)", | ||||
|               "Convert an int/u64 or int/s64 to a number. Fails if the number is out of range for an int64.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     if (janet_type(argv[0]) == JANET_ABSTRACT) { | ||||
|         void *abst = janet_unwrap_abstract(argv[0]); | ||||
|  | ||||
|         if (janet_abstract_type(abst) == &janet_s64_type) { | ||||
|             int64_t value = *((int64_t *)abst); | ||||
|             if (value > JANET_INTMAX_INT64) { | ||||
|                 janet_panicf("cannot convert %q to a number, must be in the range [%q, %q]", argv[0], janet_wrap_number(JANET_INTMIN_DOUBLE), janet_wrap_number(JANET_INTMAX_DOUBLE)); | ||||
|             } | ||||
|             if (value < -JANET_INTMAX_INT64) { | ||||
|                 janet_panicf("cannot convert %q to a number, must be in the range [%q, %q]", argv[0], janet_wrap_number(JANET_INTMIN_DOUBLE), janet_wrap_number(JANET_INTMAX_DOUBLE)); | ||||
|             } | ||||
|             return janet_wrap_number((double)value); | ||||
|         } | ||||
|  | ||||
|         if (janet_abstract_type(abst) == &janet_u64_type) { | ||||
|             uint64_t value = *((uint64_t *)abst); | ||||
|             if (value > JANET_INTMAX_INT64) { | ||||
|                 janet_panicf("cannot convert %q to a number, must be in the range [%q, %q]", argv[0], janet_wrap_number(JANET_INTMIN_DOUBLE), janet_wrap_number(JANET_INTMAX_DOUBLE)); | ||||
|             } | ||||
|  | ||||
|             return janet_wrap_number((double)value); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     janet_panicf("expected int/u64 or int/s64, got %q", argv[0]); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_to_bytes, | ||||
|               "(int/to-bytes value &opt endianness buffer)", | ||||
|               "Write the bytes of an `int/s64` or `int/u64` into a buffer.\n" | ||||
|               "The `buffer` parameter specifies an existing buffer to write to, if unset a new buffer will be created.\n" | ||||
|               "Returns the modified buffer.\n" | ||||
|               "The `endianness` parameter indicates the byte order:\n" | ||||
|               "- `nil` (unset): system byte order\n" | ||||
|               "- `:le`: little-endian, least significant byte first\n" | ||||
|               "- `:be`: big-endian, most significant byte first\n") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     if (janet_is_int(argv[0]) == JANET_INT_NONE) { | ||||
|         janet_panicf("int/to-bytes: expected an int/s64 or int/u64, got %q", argv[0]); | ||||
|     } | ||||
|  | ||||
|     int reverse = 0; | ||||
|     if (argc > 1 && !janet_checktype(argv[1], JANET_NIL)) { | ||||
|         JanetKeyword endianness_kw = janet_getkeyword(argv, 1); | ||||
|         if (!janet_cstrcmp(endianness_kw, "le")) { | ||||
| #if JANET_BIG_ENDIAN | ||||
|             reverse = 1; | ||||
| #endif | ||||
|         } else if (!janet_cstrcmp(endianness_kw, "be")) { | ||||
| #if JANET_LITTLE_ENDIAN | ||||
|             reverse = 1; | ||||
| #endif | ||||
|         } else { | ||||
|             janet_panicf("int/to-bytes: expected endianness :le, :be or nil, got %v", argv[1]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     JanetBuffer *buffer = NULL; | ||||
|     if (argc > 2 && !janet_checktype(argv[2], JANET_NIL)) { | ||||
|         if (!janet_checktype(argv[2], JANET_BUFFER)) { | ||||
|             janet_panicf("int/to-bytes: expected buffer or nil, got %q", argv[2]); | ||||
|         } | ||||
|  | ||||
|         buffer = janet_unwrap_buffer(argv[2]); | ||||
|         janet_buffer_extra(buffer, 8); | ||||
|     } else { | ||||
|         buffer = janet_buffer(8); | ||||
|     } | ||||
|  | ||||
|     uint8_t *bytes = janet_unwrap_abstract(argv[0]); | ||||
|     if (reverse) { | ||||
|         for (int i = 0; i < 8; ++i) { | ||||
|             buffer->data[buffer->count + 7 - i] = bytes[i]; | ||||
|         } | ||||
|     } else { | ||||
|         memcpy(buffer->data + buffer->count, bytes, 8); | ||||
|     } | ||||
|     buffer->count += 8; | ||||
|  | ||||
|     return janet_wrap_buffer(buffer); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Code to support polymorphic comparison. | ||||
|  * int/u64 and int/s64 support a "compare" method that allows | ||||
| @@ -221,8 +303,8 @@ static int compare_double_double(double x, double y) { | ||||
|  | ||||
| static int compare_int64_double(int64_t x, double y) { | ||||
|     if (isnan(y)) { | ||||
|         return 0; // clojure and python do this | ||||
|     } else if ((y > (- ((double) MAX_INT_IN_DBL))) && (y < ((double) MAX_INT_IN_DBL))) { | ||||
|         return 0; | ||||
|     } else if ((y > JANET_INTMIN_DOUBLE) && (y < JANET_INTMAX_DOUBLE)) { | ||||
|         double dx = (double) x; | ||||
|         return compare_double_double(dx, y); | ||||
|     } else if (y > ((double) INT64_MAX)) { | ||||
| @@ -237,10 +319,10 @@ static int compare_int64_double(int64_t x, double y) { | ||||
|  | ||||
| static int compare_uint64_double(uint64_t x, double y) { | ||||
|     if (isnan(y)) { | ||||
|         return 0; // clojure and python do this | ||||
|         return 0; | ||||
|     } else if (y < 0) { | ||||
|         return 1; | ||||
|     } else if ((y >= 0) && (y < ((double) MAX_INT_IN_DBL))) { | ||||
|     } else if ((y >= 0) && (y < JANET_INTMAX_DOUBLE)) { | ||||
|         double dx = (double) x; | ||||
|         return compare_double_double(dx, y); | ||||
|     } else if (y > ((double) UINT64_MAX)) { | ||||
| @@ -253,8 +335,9 @@ static int compare_uint64_double(uint64_t x, double y) { | ||||
|  | ||||
| static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { | ||||
|     janet_fixarity(argc, 2); | ||||
|     if (janet_is_int(argv[0]) != JANET_INT_S64) | ||||
|     if (janet_is_int(argv[0]) != JANET_INT_S64) { | ||||
|         janet_panic("compare method requires int/s64 as first argument"); | ||||
|     } | ||||
|     int64_t x = janet_unwrap_s64(argv[0]); | ||||
|     switch (janet_type(argv[1])) { | ||||
|         default: | ||||
| @@ -269,7 +352,6 @@ static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { | ||||
|                 int64_t y = *(int64_t *)abst; | ||||
|                 return janet_wrap_number((x < y) ? -1 : (x > y ? 1 : 0)); | ||||
|             } else if (janet_abstract_type(abst) == &janet_u64_type) { | ||||
|                 // comparing signed to unsigned -- be careful! | ||||
|                 uint64_t y = *(uint64_t *)abst; | ||||
|                 if (x < 0) { | ||||
|                     return janet_wrap_number(-1); | ||||
| @@ -288,8 +370,9 @@ static Janet cfun_it_s64_compare(int32_t argc, Janet *argv) { | ||||
|  | ||||
| static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { | ||||
|     janet_fixarity(argc, 2); | ||||
|     if (janet_is_int(argv[0]) != JANET_INT_U64)  // is this needed? | ||||
|     if (janet_is_int(argv[0]) != JANET_INT_U64) { | ||||
|         janet_panic("compare method requires int/u64 as first argument"); | ||||
|     } | ||||
|     uint64_t x = janet_unwrap_u64(argv[0]); | ||||
|     switch (janet_type(argv[1])) { | ||||
|         default: | ||||
| @@ -304,7 +387,6 @@ static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { | ||||
|                 uint64_t y = *(uint64_t *)abst; | ||||
|                 return janet_wrap_number((x < y) ? -1 : (x > y ? 1 : 0)); | ||||
|             } else if (janet_abstract_type(abst) == &janet_s64_type) { | ||||
|                 // comparing unsigned to signed -- be careful! | ||||
|                 int64_t y = *(int64_t *)abst; | ||||
|                 if (y < 0) { | ||||
|                     return janet_wrap_number(1); | ||||
| @@ -321,25 +403,52 @@ static Janet cfun_it_u64_compare(int32_t argc, Janet *argv) { | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * In C, signed arithmetic overflow is undefined behvior | ||||
|  * but unsigned arithmetic overflow is twos complement | ||||
|  * | ||||
|  * Reference: | ||||
|  * https://en.cppreference.com/w/cpp/language/ub | ||||
|  * http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html | ||||
|  * | ||||
|  * This means OPMETHOD & OPMETHODINVERT must always use | ||||
|  * unsigned arithmetic internally, regardless of the true type. | ||||
|  * This will not affect the end result (property of twos complement). | ||||
|  */ | ||||
| #define OPMETHOD(T, type, name, oper) \ | ||||
| static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
|     janet_arity(argc, 2, -1); \ | ||||
|     T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ | ||||
|     *box = janet_unwrap_##type(argv[0]); \ | ||||
|     for (int32_t i = 1; i < argc; i++) \ | ||||
|         *box oper##= janet_unwrap_##type(argv[i]); \ | ||||
|         /* This avoids undefined behavior. See above for why. */ \ | ||||
|         *box = (T) ((uint64_t) (*box)) oper ((uint64_t) janet_unwrap_##type(argv[i])); \ | ||||
|     return janet_wrap_abstract(box); \ | ||||
| } \ | ||||
|  | ||||
| #define OPMETHODINVERT(T, type, name, oper) \ | ||||
| static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
| static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ | ||||
|     janet_fixarity(argc, 2); \ | ||||
|     T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ | ||||
|     *box = janet_unwrap_##type(argv[1]); \ | ||||
|     *box oper##= janet_unwrap_##type(argv[0]); \ | ||||
|     /* This avoids undefined behavior. See above for why. */ \ | ||||
|     *box = (T) ((uint64_t) *box) oper ((uint64_t) janet_unwrap_##type(argv[0])); \ | ||||
|     return janet_wrap_abstract(box); \ | ||||
| } \ | ||||
|  | ||||
| #define UNARYMETHOD(T, type, name, oper) \ | ||||
| static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
|     janet_fixarity(argc, 1); \ | ||||
|     T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ | ||||
|     *box = oper(janet_unwrap_##type(argv[0])); \ | ||||
|     return janet_wrap_abstract(box); \ | ||||
| } \ | ||||
|  | ||||
| #define DIVZERO(name) DIVZERO_##name | ||||
| #define DIVZERO_div janet_panic("division by zero") | ||||
| #define DIVZERO_rem janet_panic("division by zero") | ||||
| #define DIVZERO_mod return janet_wrap_abstract(box) | ||||
|  | ||||
| #define DIVMETHOD(T, type, name, oper) \ | ||||
| static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
|     janet_arity(argc, 2, -1);                       \ | ||||
| @@ -347,19 +456,19 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
|     *box = janet_unwrap_##type(argv[0]); \ | ||||
|     for (int32_t i = 1; i < argc; i++) { \ | ||||
|       T value = janet_unwrap_##type(argv[i]); \ | ||||
|       if (value == 0) janet_panic("division by zero"); \ | ||||
|       if (value == 0) DIVZERO(name); \ | ||||
|       *box oper##= value; \ | ||||
|     } \ | ||||
|     return janet_wrap_abstract(box); \ | ||||
| } \ | ||||
|  | ||||
| #define DIVMETHODINVERT(T, type, name, oper) \ | ||||
| static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
| static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ | ||||
|     janet_fixarity(argc, 2);                       \ | ||||
|     T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ | ||||
|     *box = janet_unwrap_##type(argv[1]); \ | ||||
|     T value = janet_unwrap_##type(argv[0]); \ | ||||
|     if (value == 0) janet_panic("division by zero"); \ | ||||
|     if (value == 0) DIVZERO(name); \ | ||||
|     *box oper##= value; \ | ||||
|     return janet_wrap_abstract(box); \ | ||||
| } \ | ||||
| @@ -371,7 +480,7 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
|     *box = janet_unwrap_##type(argv[0]); \ | ||||
|     for (int32_t i = 1; i < argc; i++) { \ | ||||
|       T value = janet_unwrap_##type(argv[i]); \ | ||||
|       if (value == 0) janet_panic("division by zero"); \ | ||||
|       if (value == 0) DIVZERO(name); \ | ||||
|       if ((value == -1) && (*box == INT64_MIN)) janet_panic("INT64_MIN divided by -1"); \ | ||||
|       *box oper##= value; \ | ||||
|     } \ | ||||
| @@ -379,51 +488,95 @@ static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
| } \ | ||||
|  | ||||
| #define DIVMETHODINVERT_SIGNED(T, type, name, oper) \ | ||||
| static Janet cfun_it_##type##_##name(int32_t argc, Janet *argv) { \ | ||||
| static Janet cfun_it_##type##_##name##i(int32_t argc, Janet *argv) { \ | ||||
|     janet_fixarity(argc, 2);                       \ | ||||
|     T *box = janet_abstract(&janet_##type##_type, sizeof(T)); \ | ||||
|     *box = janet_unwrap_##type(argv[1]); \ | ||||
|     T value = janet_unwrap_##type(argv[0]); \ | ||||
|     if (value == 0) janet_panic("division by zero"); \ | ||||
|     if (value == 0) DIVZERO(name); \ | ||||
|     if ((value == -1) && (*box == INT64_MIN)) janet_panic("INT64_MIN divided by -1"); \ | ||||
|     *box oper##= value; \ | ||||
|     return janet_wrap_abstract(box); \ | ||||
| } \ | ||||
|  | ||||
| static Janet cfun_it_s64_divf(int32_t argc, Janet *argv) { | ||||
|     janet_fixarity(argc, 2); | ||||
|     int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); | ||||
|     int64_t op1 = janet_unwrap_s64(argv[0]); | ||||
|     int64_t op2 = janet_unwrap_s64(argv[1]); | ||||
|     if (op2 == 0) janet_panic("division by zero"); | ||||
|     int64_t x = op1 / op2; | ||||
|     *box = x - (((op1 ^ op2) < 0) && (x * op2 != op1)); | ||||
|     return janet_wrap_abstract(box); | ||||
| } | ||||
|  | ||||
| static Janet cfun_it_s64_divfi(int32_t argc, Janet *argv) { | ||||
|     janet_fixarity(argc, 2); | ||||
|     int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); | ||||
|     int64_t op2 = janet_unwrap_s64(argv[0]); | ||||
|     int64_t op1 = janet_unwrap_s64(argv[1]); | ||||
|     if (op2 == 0) janet_panic("division by zero"); | ||||
|     int64_t x = op1 / op2; | ||||
|     *box = x - (((op1 ^ op2) < 0) && (x * op2 != op1)); | ||||
|     return janet_wrap_abstract(box); | ||||
| } | ||||
|  | ||||
| static Janet cfun_it_s64_mod(int32_t argc, Janet *argv) { | ||||
|     janet_fixarity(argc, 2); | ||||
|     int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); | ||||
|     int64_t op1 = janet_unwrap_s64(argv[0]); | ||||
|     int64_t op2 = janet_unwrap_s64(argv[1]); | ||||
|     int64_t x = op1 % op2; | ||||
|     *box = (op1 > 0) | ||||
|            ? ((op2 > 0) ? x : (0 == x ? x : x + op2)) | ||||
|            : ((op2 > 0) ? (0 == x ? x : x + op2) : x); | ||||
|     if (op2 == 0) { | ||||
|         *box = op1; | ||||
|     } else { | ||||
|         int64_t x = op1 % op2; | ||||
|         *box = (((op1 ^ op2) < 0) && (x != 0)) ? x + op2 : x; | ||||
|     } | ||||
|     return janet_wrap_abstract(box); | ||||
| } | ||||
|  | ||||
| static Janet cfun_it_s64_modi(int32_t argc, Janet *argv) { | ||||
|     janet_fixarity(argc, 2); | ||||
|     int64_t *box = janet_abstract(&janet_s64_type, sizeof(int64_t)); | ||||
|     int64_t op2 = janet_unwrap_s64(argv[0]); | ||||
|     int64_t op1 = janet_unwrap_s64(argv[1]); | ||||
|     if (op2 == 0) { | ||||
|         *box = op1; | ||||
|     } else { | ||||
|         int64_t x = op1 % op2; | ||||
|         *box = (((op1 ^ op2) < 0) && (x != 0)) ? x + op2 : x; | ||||
|     } | ||||
|     return janet_wrap_abstract(box); | ||||
| } | ||||
|  | ||||
| OPMETHOD(int64_t, s64, add, +) | ||||
| OPMETHOD(int64_t, s64, sub, -) | ||||
| OPMETHODINVERT(int64_t, s64, subi, -) | ||||
| OPMETHODINVERT(int64_t, s64, sub, -) | ||||
| OPMETHOD(int64_t, s64, mul, *) | ||||
| DIVMETHOD_SIGNED(int64_t, s64, div, /) | ||||
| DIVMETHOD_SIGNED(int64_t, s64, rem, %) | ||||
| DIVMETHODINVERT_SIGNED(int64_t, s64, divi, /) | ||||
| DIVMETHODINVERT_SIGNED(int64_t, s64, div, /) | ||||
| DIVMETHODINVERT_SIGNED(int64_t, s64, rem, %) | ||||
| OPMETHOD(int64_t, s64, and, &) | ||||
| OPMETHOD(int64_t, s64, or, |) | ||||
| OPMETHOD(int64_t, s64, xor, ^) | ||||
| UNARYMETHOD(int64_t, s64, not, ~) | ||||
| OPMETHOD(int64_t, s64, lshift, <<) | ||||
| OPMETHOD(int64_t, s64, rshift, >>) | ||||
| OPMETHOD(uint64_t, u64, add, +) | ||||
| OPMETHOD(uint64_t, u64, sub, -) | ||||
| OPMETHODINVERT(uint64_t, u64, subi, -) | ||||
| OPMETHODINVERT(uint64_t, u64, sub, -) | ||||
| OPMETHOD(uint64_t, u64, mul, *) | ||||
| DIVMETHOD(uint64_t, u64, div, /) | ||||
| DIVMETHOD(uint64_t, u64, rem, %) | ||||
| DIVMETHOD(uint64_t, u64, mod, %) | ||||
| DIVMETHODINVERT(uint64_t, u64, divi, /) | ||||
| DIVMETHODINVERT(uint64_t, u64, div, /) | ||||
| DIVMETHODINVERT(uint64_t, u64, rem, %) | ||||
| DIVMETHODINVERT(uint64_t, u64, mod, %) | ||||
| OPMETHOD(uint64_t, u64, and, &) | ||||
| OPMETHOD(uint64_t, u64, or, |) | ||||
| OPMETHOD(uint64_t, u64, xor, ^) | ||||
| UNARYMETHOD(uint64_t, u64, not, ~) | ||||
| OPMETHOD(uint64_t, u64, lshift, <<) | ||||
| OPMETHOD(uint64_t, u64, rshift, >>) | ||||
|  | ||||
| @@ -432,7 +585,6 @@ OPMETHOD(uint64_t, u64, rshift, >>) | ||||
| #undef DIVMETHOD_SIGNED | ||||
| #undef COMPMETHOD | ||||
|  | ||||
|  | ||||
| static JanetMethod it_s64_methods[] = { | ||||
|     {"+", cfun_it_s64_add}, | ||||
|     {"r+", cfun_it_s64_add}, | ||||
| @@ -442,20 +594,22 @@ static JanetMethod it_s64_methods[] = { | ||||
|     {"r*", cfun_it_s64_mul}, | ||||
|     {"/", cfun_it_s64_div}, | ||||
|     {"r/", cfun_it_s64_divi}, | ||||
|     {"div", cfun_it_s64_divf}, | ||||
|     {"rdiv", cfun_it_s64_divfi}, | ||||
|     {"mod", cfun_it_s64_mod}, | ||||
|     {"rmod", cfun_it_s64_mod}, | ||||
|     {"rmod", cfun_it_s64_modi}, | ||||
|     {"%", cfun_it_s64_rem}, | ||||
|     {"r%", cfun_it_s64_rem}, | ||||
|     {"r%", cfun_it_s64_remi}, | ||||
|     {"&", cfun_it_s64_and}, | ||||
|     {"r&", cfun_it_s64_and}, | ||||
|     {"|", cfun_it_s64_or}, | ||||
|     {"r|", cfun_it_s64_or}, | ||||
|     {"^", cfun_it_s64_xor}, | ||||
|     {"r^", cfun_it_s64_xor}, | ||||
|     {"~", cfun_it_s64_not}, | ||||
|     {"<<", cfun_it_s64_lshift}, | ||||
|     {">>", cfun_it_s64_rshift}, | ||||
|     {"compare", cfun_it_s64_compare}, | ||||
|  | ||||
|     {NULL, NULL} | ||||
| }; | ||||
|  | ||||
| @@ -468,20 +622,22 @@ static JanetMethod it_u64_methods[] = { | ||||
|     {"r*", cfun_it_u64_mul}, | ||||
|     {"/", cfun_it_u64_div}, | ||||
|     {"r/", cfun_it_u64_divi}, | ||||
|     {"div", cfun_it_u64_div}, | ||||
|     {"rdiv", cfun_it_u64_divi}, | ||||
|     {"mod", cfun_it_u64_mod}, | ||||
|     {"rmod", cfun_it_u64_mod}, | ||||
|     {"%", cfun_it_u64_mod}, | ||||
|     {"r%", cfun_it_u64_mod}, | ||||
|     {"rmod", cfun_it_u64_modi}, | ||||
|     {"%", cfun_it_u64_rem}, | ||||
|     {"r%", cfun_it_u64_remi}, | ||||
|     {"&", cfun_it_u64_and}, | ||||
|     {"r&", cfun_it_u64_and}, | ||||
|     {"|", cfun_it_u64_or}, | ||||
|     {"r|", cfun_it_u64_or}, | ||||
|     {"^", cfun_it_u64_xor}, | ||||
|     {"r^", cfun_it_u64_xor}, | ||||
|     {"~", cfun_it_u64_not}, | ||||
|     {"<<", cfun_it_u64_lshift}, | ||||
|     {">>", cfun_it_u64_rshift}, | ||||
|     {"compare", cfun_it_u64_compare}, | ||||
|  | ||||
|     {NULL, NULL} | ||||
| }; | ||||
|  | ||||
| @@ -514,6 +670,8 @@ void janet_lib_inttypes(JanetTable *env) { | ||||
|     JanetRegExt it_cfuns[] = { | ||||
|         JANET_CORE_REG("int/s64", cfun_it_s64_new), | ||||
|         JANET_CORE_REG("int/u64", cfun_it_u64_new), | ||||
|         JANET_CORE_REG("int/to-number", cfun_to_number), | ||||
|         JANET_CORE_REG("int/to-bytes", cfun_to_bytes), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, it_cfuns); | ||||
|   | ||||
							
								
								
									
										222
									
								
								src/core/io.c
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								src/core/io.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -31,6 +31,7 @@ | ||||
|  | ||||
| #ifndef JANET_WINDOWS | ||||
| #include <fcntl.h> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/wait.h> | ||||
| #include <unistd.h> | ||||
| #endif | ||||
| @@ -41,6 +42,11 @@ static void io_file_marshal(void *p, JanetMarshalContext *ctx); | ||||
| static void *io_file_unmarshal(JanetMarshalContext *ctx); | ||||
| static Janet io_file_next(void *p, Janet key); | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #define ftell _ftelli64 | ||||
| #define fseek _fseeki64 | ||||
| #endif | ||||
|  | ||||
| const JanetAbstractType janet_file_type = { | ||||
|     "core/file", | ||||
|     cfun_io_gc, | ||||
| @@ -69,12 +75,15 @@ static int32_t checkflags(const uint8_t *str) { | ||||
|             break; | ||||
|         case 'w': | ||||
|             flags |= JANET_FILE_WRITE; | ||||
|             janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); | ||||
|             break; | ||||
|         case 'a': | ||||
|             flags |= JANET_FILE_APPEND; | ||||
|             janet_sandbox_assert(JANET_SANDBOX_FS); | ||||
|             break; | ||||
|         case 'r': | ||||
|             flags |= JANET_FILE_READ; | ||||
|             janet_sandbox_assert(JANET_SANDBOX_FS_READ); | ||||
|             break; | ||||
|     } | ||||
|     for (i = 1; i < len; i++) { | ||||
| @@ -84,6 +93,7 @@ static int32_t checkflags(const uint8_t *str) { | ||||
|                 break; | ||||
|             case '+': | ||||
|                 if (flags & JANET_FILE_UPDATE) return -1; | ||||
|                 janet_sandbox_assert(JANET_SANDBOX_FS_WRITE); | ||||
|                 flags |= JANET_FILE_UPDATE; | ||||
|                 break; | ||||
|             case 'b': | ||||
| @@ -112,57 +122,22 @@ static void *makef(FILE *f, int32_t flags) { | ||||
|     return iof; | ||||
| } | ||||
|  | ||||
| /* Open a process */ | ||||
| #ifndef JANET_NO_PROCESSES | ||||
| JANET_CORE_FN(cfun_io_popen, | ||||
|               "(file/popen command &opt mode) (DEPRECATED for os/spawn)", | ||||
|               "Open a file that is backed by a process. The file must be opened in either " | ||||
|               "the :r (read) or the :w (write) mode. In :r mode, the stdout of the " | ||||
|               "process can be read from the file. In :w mode, the stdin of the process " | ||||
|               "can be written to. Returns the new file.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     const uint8_t *fname = janet_getstring(argv, 0); | ||||
|     const uint8_t *fmode = NULL; | ||||
|     int32_t flags; | ||||
|     if (argc == 2) { | ||||
|         fmode = janet_getkeyword(argv, 1); | ||||
|         flags = JANET_FILE_PIPED | checkflags(fmode); | ||||
|         if (flags & (JANET_FILE_UPDATE | JANET_FILE_BINARY | JANET_FILE_APPEND)) { | ||||
|             janet_panicf("invalid popen file mode :%S, expected :r or :w", fmode); | ||||
|         } | ||||
|         fmode = (const uint8_t *)((fmode[0] == 'r') ? "r" : "w"); | ||||
|     } else { | ||||
|         fmode = (const uint8_t *)"r"; | ||||
|         flags = JANET_FILE_PIPED | JANET_FILE_READ; | ||||
|     } | ||||
| #ifdef JANET_WINDOWS | ||||
| #define popen _popen | ||||
| #endif | ||||
|     FILE *f = popen((const char *)fname, (const char *)fmode); | ||||
|     if (!f) { | ||||
|         if (flags & JANET_FILE_NONIL) | ||||
|             janet_panicf("failed to popen %s: %s", fname, strerror(errno)); | ||||
|         return janet_wrap_nil(); | ||||
|     } | ||||
|     return janet_makefile(f, flags); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_temp, | ||||
|               "(file/temp)", | ||||
|               "Open an anonymous temporary file that is removed on close. " | ||||
|               "Raises an error on failure.") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_FS_TEMP); | ||||
|     (void)argv; | ||||
|     janet_fixarity(argc, 0); | ||||
|     // XXX use mkostemp when we can to avoid CLOEXEC race. | ||||
|     FILE *tmp = tmpfile(); | ||||
|     if (!tmp) | ||||
|         janet_panicf("unable to create temporary file - %s", strerror(errno)); | ||||
|         janet_panicf("unable to create temporary file - %s", janet_strerror(errno)); | ||||
|     return janet_makefile(tmp, JANET_FILE_WRITE | JANET_FILE_READ | JANET_FILE_BINARY); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_fopen, | ||||
|               "(file/open path &opt mode)", | ||||
|               "(file/open path &opt mode buffer-size)", | ||||
|               "Open a file. `path` is an absolute or relative path, and " | ||||
|               "`mode` is a set of flags indicating the mode to open the file in. " | ||||
|               "`mode` is a keyword where each character represents a flag. If the file " | ||||
| @@ -174,8 +149,9 @@ JANET_CORE_FN(cfun_io_fopen, | ||||
|               "Following one of the initial flags, 0 or more of the following flags can be appended:\n\n" | ||||
|               "* b - open the file in binary mode (rather than text mode)\n\n" | ||||
|               "* + - append to the file instead of overwriting it\n\n" | ||||
|               "* n - error if the file cannot be opened instead of returning nil") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|               "* n - error if the file cannot be opened instead of returning nil\n\n" | ||||
|               "See fopen (<stdio.h>, C99) for further details.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     const uint8_t *fname = janet_getstring(argv, 0); | ||||
|     const uint8_t *fmode; | ||||
|     int32_t flags; | ||||
| @@ -184,11 +160,29 @@ JANET_CORE_FN(cfun_io_fopen, | ||||
|         flags = checkflags(fmode); | ||||
|     } else { | ||||
|         fmode = (const uint8_t *)"r"; | ||||
|         janet_sandbox_assert(JANET_SANDBOX_FS_READ); | ||||
|         flags = JANET_FILE_READ; | ||||
|     } | ||||
|     FILE *f = fopen((const char *)fname, (const char *)fmode); | ||||
|     if (f != NULL) { | ||||
| #ifndef JANET_WINDOWS | ||||
|         struct stat st; | ||||
|         fstat(fileno(f), &st); | ||||
|         if (S_ISDIR(st.st_mode)) { | ||||
|             fclose(f); | ||||
|             janet_panicf("cannot open directory: %s", fname); | ||||
|         } | ||||
| #endif | ||||
|         size_t bufsize = janet_optsize(argv, argc, 2, BUFSIZ); | ||||
|         if (bufsize != BUFSIZ) { | ||||
|             int result = setvbuf(f, NULL, bufsize ? _IOFBF : _IONBF, bufsize); | ||||
|             if (result) { | ||||
|                 janet_panic("failed to set buffer size for file"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return f ? janet_makefile(f, flags) | ||||
|            : (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, strerror(errno)), janet_wrap_nil()) | ||||
|            : (flags & JANET_FILE_NONIL) ? (janet_panicf("failed to open file %s: %s", fname, janet_strerror(errno)), janet_wrap_nil()) | ||||
|            : janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -279,6 +273,13 @@ JANET_CORE_FN(cfun_io_fwrite, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| static void io_assert_writeable(JanetFile *iof) { | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         janet_panic("file is closed"); | ||||
|     if (!(iof->flags & (JANET_FILE_WRITE | JANET_FILE_APPEND | JANET_FILE_UPDATE))) | ||||
|         janet_panic("file is not writeable"); | ||||
| } | ||||
|  | ||||
| /* Flush the bytes in the file */ | ||||
| JANET_CORE_FN(cfun_io_fflush, | ||||
|               "(file/flush f)", | ||||
| @@ -286,17 +287,13 @@ JANET_CORE_FN(cfun_io_fflush, | ||||
|               "buffered for efficiency reasons. Returns the file handle.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         janet_panic("file is closed"); | ||||
|     if (!(iof->flags & (JANET_FILE_WRITE | JANET_FILE_APPEND | JANET_FILE_UPDATE))) | ||||
|         janet_panic("file is not writeable"); | ||||
|     io_assert_writeable(iof); | ||||
|     if (fflush(iof->file)) | ||||
|         janet_panic("could not flush file"); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #define pclose _pclose | ||||
| #define WEXITSTATUS(x) x | ||||
| #endif | ||||
|  | ||||
| @@ -304,15 +301,9 @@ JANET_CORE_FN(cfun_io_fflush, | ||||
| int janet_file_close(JanetFile *file) { | ||||
|     int ret = 0; | ||||
|     if (!(file->flags & (JANET_FILE_NOT_CLOSEABLE | JANET_FILE_CLOSED))) { | ||||
| #ifndef JANET_NO_PROCESSES | ||||
|         if (file->flags & JANET_FILE_PIPED) { | ||||
|             ret = pclose(file->file); | ||||
|         } else | ||||
| #endif | ||||
|         { | ||||
|             ret = fclose(file->file); | ||||
|         } | ||||
|         ret = fclose(file->file); | ||||
|         file->flags |= JANET_FILE_CLOSED; | ||||
|         file->file = NULL; /* NULL dereference is easier to debug then other problems */ | ||||
|         return ret; | ||||
|     } | ||||
|     return 0; | ||||
| @@ -331,30 +322,18 @@ JANET_CORE_FN(cfun_io_fclose, | ||||
|               "(file/close f)", | ||||
|               "Close a file and release all related resources. When you are " | ||||
|               "done reading a file, close it to prevent a resource leak and let " | ||||
|               "other processes read the file. If the file is the result of a file/popen " | ||||
|               "call, close waits for and returns the process exit status.") { | ||||
|               "other processes read the file.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         return janet_wrap_nil(); | ||||
|     if (iof->flags & (JANET_FILE_NOT_CLOSEABLE)) | ||||
|         janet_panic("file not closable"); | ||||
|     if (iof->flags & JANET_FILE_PIPED) { | ||||
| #ifndef JANET_NO_PROCESSES | ||||
|         int status = pclose(iof->file); | ||||
|         iof->flags |= JANET_FILE_CLOSED; | ||||
|         if (status == -1) janet_panic("could not close file"); | ||||
|         return janet_wrap_integer(WEXITSTATUS(status)); | ||||
| #else | ||||
|         return janet_wrap_nil(); | ||||
| #endif | ||||
|     } else { | ||||
|         if (fclose(iof->file)) { | ||||
|             iof->flags |= JANET_FILE_NOT_CLOSEABLE; | ||||
|             janet_panic("could not close file"); | ||||
|         } | ||||
|         iof->flags |= JANET_FILE_CLOSED; | ||||
|     if (fclose(iof->file)) { | ||||
|         iof->flags |= JANET_FILE_NOT_CLOSEABLE; | ||||
|         janet_panic("could not close file"); | ||||
|     } | ||||
|     iof->flags |= JANET_FILE_CLOSED; | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -372,7 +351,7 @@ JANET_CORE_FN(cfun_io_fseek, | ||||
|     JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         janet_panic("file is closed"); | ||||
|     long int offset = 0; | ||||
|     int64_t offset = 0; | ||||
|     int whence = SEEK_CUR; | ||||
|     if (argc >= 2) { | ||||
|         const uint8_t *whence_sym = janet_getkeyword(argv, 1); | ||||
| @@ -386,18 +365,31 @@ JANET_CORE_FN(cfun_io_fseek, | ||||
|             janet_panicf("expected one of :cur, :set, :end, got %v", argv[1]); | ||||
|         } | ||||
|         if (argc == 3) { | ||||
|             offset = (long) janet_getinteger64(argv, 2); | ||||
|             offset = (int64_t) janet_getinteger64(argv, 2); | ||||
|         } | ||||
|     } | ||||
|     if (fseek(iof->file, offset, whence)) janet_panic("error seeking file"); | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_ftell, | ||||
|               "(file/tell f)", | ||||
|               "Get the current value of the file position for file `f`.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         janet_panic("file is closed"); | ||||
|     int64_t pos = ftell(iof->file); | ||||
|     if (pos == -1) janet_panic("error getting position in file"); | ||||
|     return janet_wrap_number((double)pos); | ||||
| } | ||||
|  | ||||
| static JanetMethod io_file_methods[] = { | ||||
|     {"close", cfun_io_fclose}, | ||||
|     {"flush", cfun_io_fflush}, | ||||
|     {"read", cfun_io_fread}, | ||||
|     {"seek", cfun_io_fseek}, | ||||
|     {"tell", cfun_io_ftell}, | ||||
|     {"write", cfun_io_fwrite}, | ||||
|     {NULL, NULL} | ||||
| }; | ||||
| @@ -483,6 +475,19 @@ static Janet cfun_io_print_impl_x(int32_t argc, Janet *argv, int newline, | ||||
|                 janet_buffer_push_u8(buf, '\n'); | ||||
|             return janet_wrap_nil(); | ||||
|         } | ||||
|         case JANET_FUNCTION: { | ||||
|             /* Special case function */ | ||||
|             JanetFunction *fun = janet_unwrap_function(x); | ||||
|             JanetBuffer *buf = janet_buffer(0); | ||||
|             for (int32_t i = offset; i < argc; ++i) { | ||||
|                 janet_to_string_b(buf, argv[i]); | ||||
|             } | ||||
|             if (newline) | ||||
|                 janet_buffer_push_u8(buf, '\n'); | ||||
|             Janet args[1] = { janet_wrap_buffer(buf) }; | ||||
|             janet_call(fun, 1, args); | ||||
|             return janet_wrap_nil(); | ||||
|         } | ||||
|         case JANET_NIL: | ||||
|             f = dflt_file; | ||||
|             if (f == NULL) janet_panic("cannot print to nil"); | ||||
| @@ -492,6 +497,7 @@ static Janet cfun_io_print_impl_x(int32_t argc, Janet *argv, int newline, | ||||
|             if (janet_abstract_type(abstract) != &janet_file_type) | ||||
|                 return janet_wrap_nil(); | ||||
|             JanetFile *iofile = abstract; | ||||
|             io_assert_writeable(iofile); | ||||
|             f = iofile->file; | ||||
|             break; | ||||
|         } | ||||
| @@ -522,7 +528,6 @@ static Janet cfun_io_print_impl_x(int32_t argc, Janet *argv, int newline, | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
|  | ||||
| static Janet cfun_io_print_impl(int32_t argc, Janet *argv, | ||||
|                                 int newline, const char *name, FILE *dflt_file) { | ||||
|     Janet x = janet_dyn(name); | ||||
| @@ -533,27 +538,27 @@ JANET_CORE_FN(cfun_io_print, | ||||
|               "(print & xs)", | ||||
|               "Print values to the console (standard out). Value are converted " | ||||
|               "to strings if they are not already. After printing all values, a " | ||||
|               "newline character is printed. Use the value of (dyn :out stdout) to determine " | ||||
|               "what to push characters to. Expects (dyn :out stdout) to be either a core/file or " | ||||
|               "newline character is printed. Use the value of `(dyn :out stdout)` to determine " | ||||
|               "what to push characters to. Expects `(dyn :out stdout)` to be either a core/file or " | ||||
|               "a buffer. Returns nil.") { | ||||
|     return cfun_io_print_impl(argc, argv, 1, "out", stdout); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_prin, | ||||
|               "(prin & xs)", | ||||
|               "Same as print, but does not add trailing newline.") { | ||||
|               "Same as `print`, but does not add trailing newline.") { | ||||
|     return cfun_io_print_impl(argc, argv, 0, "out", stdout); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_eprint, | ||||
|               "(eprint & xs)", | ||||
|               "Same as print, but uses (dyn :err stderr) instead of (dyn :out stdout).") { | ||||
|               "Same as `print`, but uses `(dyn :err stderr)` instead of `(dyn :out stdout)`.") { | ||||
|     return cfun_io_print_impl(argc, argv, 1, "err", stderr); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_eprin, | ||||
|               "(eprin & xs)", | ||||
|               "Same as prin, but uses (dyn :err stderr) instead of (dyn :out stdout).") { | ||||
|               "Same as `prin`, but uses `(dyn :err stderr)` instead of `(dyn :out stdout)`.") { | ||||
|     return cfun_io_print_impl(argc, argv, 0, "err", stderr); | ||||
| } | ||||
|  | ||||
| @@ -561,7 +566,7 @@ JANET_CORE_FN(cfun_io_xprint, | ||||
|               "(xprint to & xs)", | ||||
|               "Print to a file or other value explicitly (no dynamic bindings) with a trailing " | ||||
|               "newline character. The value to print " | ||||
|               "to is the first argument, and is otherwise the same as print. Returns nil.") { | ||||
|               "to is the first argument, and is otherwise the same as `print`. Returns nil.") { | ||||
|     janet_arity(argc, 1, -1); | ||||
|     return cfun_io_print_impl_x(argc, argv, 1, NULL, 1, argv[0]); | ||||
| } | ||||
| @@ -569,7 +574,7 @@ JANET_CORE_FN(cfun_io_xprint, | ||||
| JANET_CORE_FN(cfun_io_xprin, | ||||
|               "(xprin to & xs)", | ||||
|               "Print to a file or other value explicitly (no dynamic bindings). The value to print " | ||||
|               "to is the first argument, and is otherwise the same as prin. Returns nil.") { | ||||
|               "to is the first argument, and is otherwise the same as `prin`. Returns nil.") { | ||||
|     janet_arity(argc, 1, -1); | ||||
|     return cfun_io_print_impl_x(argc, argv, 0, NULL, 1, argv[0]); | ||||
| } | ||||
| @@ -588,6 +593,16 @@ static Janet cfun_io_printf_impl_x(int32_t argc, Janet *argv, int newline, | ||||
|             if (newline) janet_buffer_push_u8(buf, '\n'); | ||||
|             return janet_wrap_nil(); | ||||
|         } | ||||
|         case JANET_FUNCTION: { | ||||
|             /* Special case function */ | ||||
|             JanetFunction *fun = janet_unwrap_function(x); | ||||
|             JanetBuffer *buf = janet_buffer(0); | ||||
|             janet_buffer_format(buf, fmt, offset, argc, argv); | ||||
|             if (newline) janet_buffer_push_u8(buf, '\n'); | ||||
|             Janet args[1] = { janet_wrap_buffer(buf) }; | ||||
|             janet_call(fun, 1, args); | ||||
|             return janet_wrap_nil(); | ||||
|         } | ||||
|         case JANET_NIL: | ||||
|             f = dflt_file; | ||||
|             if (f == NULL) janet_panic("cannot print to nil"); | ||||
| @@ -597,6 +612,10 @@ static Janet cfun_io_printf_impl_x(int32_t argc, Janet *argv, int newline, | ||||
|             if (janet_abstract_type(abstract) != &janet_file_type) | ||||
|                 return janet_wrap_nil(); | ||||
|             JanetFile *iofile = abstract; | ||||
|             if (iofile->flags & JANET_FILE_CLOSED) { | ||||
|                 janet_panic("cannot print to closed file"); | ||||
|             } | ||||
|             io_assert_writeable(iofile); | ||||
|             f = iofile->file; | ||||
|             break; | ||||
|         } | ||||
| @@ -627,38 +646,38 @@ static Janet cfun_io_printf_impl(int32_t argc, Janet *argv, int newline, | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_printf, | ||||
|               "(printf fmt & xs)", | ||||
|               "Prints output formatted as if with (string/format fmt ;xs) to (dyn :out stdout) with a trailing newline.") { | ||||
|               "Prints output formatted as if with `(string/format fmt ;xs)` to `(dyn :out stdout)` with a trailing newline.") { | ||||
|     return cfun_io_printf_impl(argc, argv, 1, "out", stdout); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_prinf, | ||||
|               "(prinf fmt & xs)", | ||||
|               "Like printf but with no trailing newline.") { | ||||
|               "Like `printf` but with no trailing newline.") { | ||||
|     return cfun_io_printf_impl(argc, argv, 0, "out", stdout); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_eprintf, | ||||
|               "(eprintf fmt & xs)", | ||||
|               "Prints output formatted as if with (string/format fmt ;xs) to (dyn :err stderr) with a trailing newline.") { | ||||
|               "Prints output formatted as if with `(string/format fmt ;xs)` to `(dyn :err stderr)` with a trailing newline.") { | ||||
|     return cfun_io_printf_impl(argc, argv, 1, "err", stderr); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_eprinf, | ||||
|               "(eprinf fmt & xs)", | ||||
|               "Like eprintf but with no trailing newline.") { | ||||
|               "Like `eprintf` but with no trailing newline.") { | ||||
|     return cfun_io_printf_impl(argc, argv, 0, "err", stderr); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_xprintf, | ||||
|               "(xprintf to fmt & xs)", | ||||
|               "Like printf but prints to an explicit file or value to. Returns nil.") { | ||||
|               "Like `printf` but prints to an explicit file or value `to`. Returns nil.") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     return cfun_io_printf_impl_x(argc, argv, 1, NULL, 1, argv[0]); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_xprinf, | ||||
|               "(xprinf to fmt & xs)", | ||||
|               "Like prinf but prints to an explicit file or value to. Returns nil.") { | ||||
|               "Like `prinf` but prints to an explicit file or value `to`. Returns nil.") { | ||||
|     janet_arity(argc, 2, -1); | ||||
|     return cfun_io_printf_impl_x(argc, argv, 0, NULL, 1, argv[0]); | ||||
| } | ||||
| @@ -683,7 +702,7 @@ static void janet_flusher(const char *name, FILE *dflt_file) { | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_flush, | ||||
|               "(flush)", | ||||
|               "Flush (dyn :out stdout) if it is a file, otherwise do nothing.") { | ||||
|               "Flush `(dyn :out stdout)` if it is a file, otherwise do nothing.") { | ||||
|     janet_fixarity(argc, 0); | ||||
|     (void) argv; | ||||
|     janet_flusher("out", stdout); | ||||
| @@ -692,7 +711,7 @@ JANET_CORE_FN(cfun_io_flush, | ||||
|  | ||||
| JANET_CORE_FN(cfun_io_eflush, | ||||
|               "(eflush)", | ||||
|               "Flush (dyn :err stderr) if it is a file, otherwise do nothing.") { | ||||
|               "Flush `(dyn :err stderr)` if it is a file, otherwise do nothing.") { | ||||
|     janet_fixarity(argc, 0); | ||||
|     (void) argv; | ||||
|     janet_flusher("err", stderr); | ||||
| @@ -721,12 +740,23 @@ void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...) | ||||
|                 if (janet_abstract_type(abstract) != &janet_file_type) | ||||
|                     break; | ||||
|                 JanetFile *iofile = abstract; | ||||
|                 io_assert_writeable(iofile); | ||||
|                 f = iofile->file; | ||||
|             } | ||||
|             fwrite(buffer.data, buffer.count, 1, f); | ||||
|             janet_buffer_deinit(&buffer); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_FUNCTION: { | ||||
|             JanetFunction *fun = janet_unwrap_function(x); | ||||
|             int32_t len = 0; | ||||
|             while (format[len]) len++; | ||||
|             JanetBuffer *buf = janet_buffer(len); | ||||
|             janet_formatbv(buf, format, args); | ||||
|             Janet args[1] = { janet_wrap_buffer(buf) }; | ||||
|             janet_call(fun, 1, args); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_BUFFER: | ||||
|             janet_formatbv(janet_unwrap_buffer(x), format, args); | ||||
|             break; | ||||
| @@ -741,17 +771,17 @@ JanetFile *janet_getjfile(const Janet *argv, int32_t n) { | ||||
|     return janet_getabstract(argv, n, &janet_file_type); | ||||
| } | ||||
|  | ||||
| FILE *janet_getfile(const Janet *argv, int32_t n, int *flags) { | ||||
| FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags) { | ||||
|     JanetFile *iof = janet_getabstract(argv, n, &janet_file_type); | ||||
|     if (NULL != flags) *flags = iof->flags; | ||||
|     return iof->file; | ||||
| } | ||||
|  | ||||
| JanetFile *janet_makejfile(FILE *f, int flags) { | ||||
| JanetFile *janet_makejfile(FILE *f, int32_t flags) { | ||||
|     return makef(f, flags); | ||||
| } | ||||
|  | ||||
| Janet janet_makefile(FILE *f, int flags) { | ||||
| Janet janet_makefile(FILE *f, int32_t flags) { | ||||
|     return janet_wrap_abstract(makef(f, flags)); | ||||
| } | ||||
|  | ||||
| @@ -759,7 +789,7 @@ JanetAbstract janet_checkfile(Janet j) { | ||||
|     return janet_checkabstract(j, &janet_file_type); | ||||
| } | ||||
|  | ||||
| FILE *janet_unwrapfile(Janet j, int *flags) { | ||||
| FILE *janet_unwrapfile(Janet j, int32_t *flags) { | ||||
|     JanetFile *iof = janet_unwrap_abstract(j); | ||||
|     if (NULL != flags) *flags = iof->flags; | ||||
|     return iof->file; | ||||
| @@ -789,9 +819,7 @@ void janet_lib_io(JanetTable *env) { | ||||
|         JANET_CORE_REG("file/write", cfun_io_fwrite), | ||||
|         JANET_CORE_REG("file/flush", cfun_io_fflush), | ||||
|         JANET_CORE_REG("file/seek", cfun_io_fseek), | ||||
| #ifndef JANET_NO_PROCESSES | ||||
|         JANET_CORE_REG("file/popen", cfun_io_popen), | ||||
| #endif | ||||
|         JANET_CORE_REG("file/tell", cfun_io_ftell), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, io_cfuns); | ||||
|   | ||||
							
								
								
									
										289
									
								
								src/core/marsh.c
									
									
									
									
									
								
							
							
						
						
									
										289
									
								
								src/core/marsh.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -37,6 +37,7 @@ typedef struct { | ||||
|     JanetFuncEnv **seen_envs; | ||||
|     JanetFuncDef **seen_defs; | ||||
|     int32_t nextid; | ||||
|     int maybe_cycles; | ||||
| } MarshalState; | ||||
|  | ||||
| /* Lead bytes in marshaling protocol */ | ||||
| @@ -64,9 +65,18 @@ enum { | ||||
|     LB_FUNCDEF_REF, /* 220 */ | ||||
|     LB_UNSAFE_CFUNCTION, /* 221 */ | ||||
|     LB_UNSAFE_POINTER, /* 222 */ | ||||
|     LB_STRUCT_PROTO, /* 223 */ | ||||
| #ifdef JANET_EV | ||||
|     LB_THREADED_ABSTRACT/* 223 */ | ||||
|     LB_THREADED_ABSTRACT, /* 224 */ | ||||
|     LB_POINTER_BUFFER, /* 225 */ | ||||
| #endif | ||||
|     LB_TABLE_WEAKK, /* 226 */ | ||||
|     LB_TABLE_WEAKV, /* 227 */ | ||||
|     LB_TABLE_WEAKKV, /* 228 */ | ||||
|     LB_TABLE_WEAKK_PROTO, /* 229 */ | ||||
|     LB_TABLE_WEAKV_PROTO, /* 230 */ | ||||
|     LB_TABLE_WEAKKV_PROTO, /* 231 */ | ||||
|     LB_ARRAY_WEAK, /* 232 */ | ||||
| } LeadBytes; | ||||
|  | ||||
| /* Helper to look inside an entry in an environment */ | ||||
| @@ -151,6 +161,10 @@ static void pushbytes(MarshalState *st, const uint8_t *bytes, int32_t len) { | ||||
|     janet_buffer_push_bytes(st->buf, bytes, len); | ||||
| } | ||||
|  | ||||
| static void pushpointer(MarshalState *st, const void *ptr) { | ||||
|     janet_buffer_push_bytes(st->buf, (const uint8_t *) &ptr, sizeof(ptr)); | ||||
| } | ||||
|  | ||||
| /* Marshal a size_t onto the buffer */ | ||||
| static void push64(MarshalState *st, uint64_t x) { | ||||
|     if (x <= 0xF0) { | ||||
| @@ -178,6 +192,19 @@ static void marshal_one_env(MarshalState *st, JanetFuncEnv *env, int flags); | ||||
| /* Prevent stack overflows */ | ||||
| #define MARSH_STACKCHECK if ((flags & 0xFFFF) > JANET_RECURSION_GUARD) janet_panic("stack overflow") | ||||
|  | ||||
| /* Quick check if a fiber cannot be marshalled. This is will | ||||
|  * have no false positives, but may have false negatives. */ | ||||
| static int fiber_cannot_be_marshalled(JanetFiber *fiber) { | ||||
|     if (janet_fiber_status(fiber) == JANET_STATUS_ALIVE) return 1; | ||||
|     int32_t i = fiber->frame; | ||||
|     while (i > 0) { | ||||
|         JanetStackFrame *frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE); | ||||
|         if (!frame->func) return 1; /* has cfunction on stack */ | ||||
|         i = frame->prevframe; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* Marshal a function env */ | ||||
| static void marshal_one_env(MarshalState *st, JanetFuncEnv *env, int flags) { | ||||
|     MARSH_STACKCHECK; | ||||
| @@ -190,7 +217,9 @@ static void marshal_one_env(MarshalState *st, JanetFuncEnv *env, int flags) { | ||||
|     } | ||||
|     janet_env_valid(env); | ||||
|     janet_v_push(st->seen_envs, env); | ||||
|     if (env->offset > 0 && (JANET_STATUS_ALIVE == janet_fiber_status(env->as.fiber))) { | ||||
|  | ||||
|     /* Special case for early detachment */ | ||||
|     if (env->offset > 0 && fiber_cannot_be_marshalled(env->as.fiber)) { | ||||
|         pushint(st, 0); | ||||
|         pushint(st, env->length); | ||||
|         Janet *values = env->as.fiber->data + env->offset; | ||||
| @@ -239,6 +268,7 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) { | ||||
|     } | ||||
|     /* Add to lookup */ | ||||
|     janet_v_push(st->seen_defs, def); | ||||
|  | ||||
|     pushint(st, def->flags); | ||||
|     pushint(st, def->slotcount); | ||||
|     pushint(st, def->arity); | ||||
| @@ -250,6 +280,8 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) { | ||||
|         pushint(st, def->environments_length); | ||||
|     if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS) | ||||
|         pushint(st, def->defs_length); | ||||
|     if (def->flags & JANET_FUNCDEF_FLAG_HASSYMBOLMAP) | ||||
|         pushint(st, def->symbolmap_length); | ||||
|     if (def->flags & JANET_FUNCDEF_FLAG_HASNAME) | ||||
|         marshal_one(st, janet_wrap_string(def->name), flags); | ||||
|     if (def->flags & JANET_FUNCDEF_FLAG_HASSOURCE) | ||||
| @@ -257,7 +289,15 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) { | ||||
|  | ||||
|     /* marshal constants */ | ||||
|     for (int32_t i = 0; i < def->constants_length; i++) | ||||
|         marshal_one(st, def->constants[i], flags); | ||||
|         marshal_one(st, def->constants[i], flags + 1); | ||||
|  | ||||
|     /* Marshal symbol map, if needed */ | ||||
|     for (int32_t i = 0; i < def->symbolmap_length; i++) { | ||||
|         pushint(st, (int32_t) def->symbolmap[i].birth_pc); | ||||
|         pushint(st, (int32_t) def->symbolmap[i].death_pc); | ||||
|         pushint(st, (int32_t) def->symbolmap[i].slot_index); | ||||
|         marshal_one(st, janet_wrap_symbol(def->symbolmap[i].symbol), flags + 1); | ||||
|     } | ||||
|  | ||||
|     /* marshal the bytecode */ | ||||
|     janet_marshal_u32s(st, def->bytecode, def->bytecode_length); | ||||
| @@ -268,7 +308,7 @@ static void marshal_one_def(MarshalState *st, JanetFuncDef *def, int flags) { | ||||
|  | ||||
|     /* marshal the sub funcdefs if needed */ | ||||
|     for (int32_t i = 0; i < def->defs_length; i++) | ||||
|         marshal_one_def(st, def->defs[i], flags); | ||||
|         marshal_one_def(st, def->defs[i], flags + 1); | ||||
|  | ||||
|     /* marshal source maps if needed */ | ||||
|     if (def->flags & JANET_FUNCDEF_FLAG_HASSOURCEMAP) { | ||||
| @@ -310,7 +350,7 @@ static void marshal_one_fiber(MarshalState *st, JanetFiber *fiber, int flags) { | ||||
|     while (i > 0) { | ||||
|         JanetStackFrame *frame = (JanetStackFrame *)(fiber->data + i - JANET_FRAME_SIZE); | ||||
|         if (frame->env) frame->flags |= JANET_STACKFRAME_HASENV; | ||||
|         if (!frame->func) janet_panic("cannot marshal fiber with c stackframe"); | ||||
|         if (!frame->func) janet_panicf("cannot marshal fiber with c stackframe (%v)", janet_wrap_cfunction((JanetCFunction) frame->pc)); | ||||
|         pushint(st, frame->flags); | ||||
|         pushint(st, frame->prevframe); | ||||
|         int32_t pcdiff = (int32_t)(frame->pc - frame->func->def->bytecode); | ||||
| @@ -345,6 +385,15 @@ void janet_marshal_int(JanetMarshalContext *ctx, int32_t value) { | ||||
|     pushint(st, value); | ||||
| } | ||||
|  | ||||
| /* Only use in unsafe - don't marshal pointers otherwise */ | ||||
| void janet_marshal_ptr(JanetMarshalContext *ctx, const void *ptr) { | ||||
|     if (!(ctx->flags & JANET_MARSHAL_UNSAFE)) { | ||||
|         janet_panic("can only marshal pointers in unsafe mode"); | ||||
|     } | ||||
|     MarshalState *st = (MarshalState *)(ctx->m_state); | ||||
|     pushpointer(st, ptr); | ||||
| } | ||||
|  | ||||
| void janet_marshal_byte(JanetMarshalContext *ctx, uint8_t value) { | ||||
|     MarshalState *st = (MarshalState *)(ctx->m_state); | ||||
|     pushbyte(st, value); | ||||
| @@ -361,16 +410,27 @@ void janet_marshal_janet(JanetMarshalContext *ctx, Janet x) { | ||||
|     marshal_one(st, x, ctx->flags + 1); | ||||
| } | ||||
|  | ||||
| #ifdef JANET_MARSHAL_DEBUG | ||||
| #define MARK_SEEN() \ | ||||
|     do { if (st->maybe_cycles) { \ | ||||
|         Janet _check = janet_table_get(&st->seen, x); \ | ||||
|         if (!janet_checktype(_check, JANET_NIL)) janet_eprintf("double MARK_SEEN on %v\n", x); \ | ||||
|         janet_eprintf("made reference %d (%t) to %v\n", st->nextid, x, x); \ | ||||
|         janet_table_put(&st->seen, x, janet_wrap_integer(st->nextid++)); \ | ||||
|     } } while (0) | ||||
| #else | ||||
| #define MARK_SEEN() \ | ||||
|     do { if (st->maybe_cycles) { \ | ||||
|         janet_table_put(&st->seen, x, janet_wrap_integer(st->nextid++)); \ | ||||
|     } } while (0) | ||||
| #endif | ||||
|  | ||||
| void janet_marshal_abstract(JanetMarshalContext *ctx, void *abstract) { | ||||
|     MarshalState *st = (MarshalState *)(ctx->m_state); | ||||
|     janet_table_put(&st->seen, | ||||
|                     janet_wrap_abstract(abstract), | ||||
|                     janet_wrap_integer(st->nextid++)); | ||||
|     Janet x = janet_wrap_abstract(abstract); | ||||
|     MARK_SEEN(); | ||||
| } | ||||
|  | ||||
| #define MARK_SEEN() \ | ||||
|     janet_table_put(&st->seen, x, janet_wrap_integer(st->nextid++)) | ||||
|  | ||||
| static void marshal_one_abstract(MarshalState *st, Janet x, int flags) { | ||||
|     void *abstract = janet_unwrap_abstract(x); | ||||
| #ifdef JANET_EV | ||||
| @@ -392,7 +452,7 @@ static void marshal_one_abstract(MarshalState *st, Janet x, int flags) { | ||||
|     if (at->marshal) { | ||||
|         pushbyte(st, LB_ABSTRACT); | ||||
|         marshal_one(st, janet_csymbolv(at->name), flags + 1); | ||||
|         JanetMarshalContext context = {st, NULL, flags, NULL, at}; | ||||
|         JanetMarshalContext context = {st, NULL, flags + 1, NULL, at}; | ||||
|         at->marshal(abstract, &context); | ||||
|     } else { | ||||
|         janet_panicf("cannot marshal %p", x); | ||||
| @@ -427,11 +487,14 @@ static void marshal_one(MarshalState *st, Janet x, int flags) { | ||||
|  | ||||
|     /* Check reference and registry value */ | ||||
|     { | ||||
|         Janet check = janet_table_get(&st->seen, x); | ||||
|         if (janet_checkint(check)) { | ||||
|             pushbyte(st, LB_REFERENCE); | ||||
|             pushint(st, janet_unwrap_integer(check)); | ||||
|             return; | ||||
|         Janet check; | ||||
|         if (st->maybe_cycles) { | ||||
|             check = janet_table_get(&st->seen, x); | ||||
|             if (janet_checkint(check)) { | ||||
|                 pushbyte(st, LB_REFERENCE); | ||||
|                 pushint(st, janet_unwrap_integer(check)); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|         if (st->rreg) { | ||||
|             check = janet_table_get(st->rreg, x); | ||||
| @@ -494,6 +557,16 @@ static void marshal_one(MarshalState *st, Janet x, int flags) { | ||||
|             JanetBuffer *buffer = janet_unwrap_buffer(x); | ||||
|             /* Record reference */ | ||||
|             MARK_SEEN(); | ||||
| #ifdef JANET_EV | ||||
|             if ((flags & JANET_MARSHAL_UNSAFE) && | ||||
|                     (buffer->gc.flags & JANET_BUFFER_FLAG_NO_REALLOC)) { | ||||
|                 pushbyte(st, LB_POINTER_BUFFER); | ||||
|                 pushint(st, buffer->count); | ||||
|                 pushint(st, buffer->capacity); | ||||
|                 pushpointer(st, buffer->data); | ||||
|                 return; | ||||
|             } | ||||
| #endif | ||||
|             pushbyte(st, LB_BUFFER); | ||||
|             pushint(st, buffer->count); | ||||
|             pushbytes(st, buffer->data, buffer->count); | ||||
| @@ -503,7 +576,8 @@ static void marshal_one(MarshalState *st, Janet x, int flags) { | ||||
|             int32_t i; | ||||
|             JanetArray *a = janet_unwrap_array(x); | ||||
|             MARK_SEEN(); | ||||
|             pushbyte(st, LB_ARRAY); | ||||
|             enum JanetMemoryType memtype = janet_gc_type(a); | ||||
|             pushbyte(st, memtype == JANET_MEMORY_ARRAY_WEAK ? LB_ARRAY_WEAK : LB_ARRAY); | ||||
|             pushint(st, a->count); | ||||
|             for (i = 0; i < a->count; i++) | ||||
|                 marshal_one(st, a->data[i], flags + 1); | ||||
| @@ -526,7 +600,16 @@ static void marshal_one(MarshalState *st, Janet x, int flags) { | ||||
|         case JANET_TABLE: { | ||||
|             JanetTable *t = janet_unwrap_table(x); | ||||
|             MARK_SEEN(); | ||||
|             pushbyte(st, t->proto ? LB_TABLE_PROTO : LB_TABLE); | ||||
|             enum JanetMemoryType memtype = janet_gc_type(t); | ||||
|             if (memtype == JANET_MEMORY_TABLE_WEAKK) { | ||||
|                 pushbyte(st, t->proto ? LB_TABLE_WEAKK_PROTO : LB_TABLE_WEAKK); | ||||
|             } else if (memtype == JANET_MEMORY_TABLE_WEAKV) { | ||||
|                 pushbyte(st, t->proto ? LB_TABLE_WEAKV_PROTO : LB_TABLE_WEAKV); | ||||
|             } else if (memtype == JANET_MEMORY_TABLE_WEAKKV) { | ||||
|                 pushbyte(st, t->proto ? LB_TABLE_WEAKKV_PROTO : LB_TABLE_WEAKKV); | ||||
|             } else { | ||||
|                 pushbyte(st, t->proto ? LB_TABLE_PROTO : LB_TABLE); | ||||
|             } | ||||
|             pushint(st, t->count); | ||||
|             if (t->proto) | ||||
|                 marshal_one(st, janet_wrap_table(t->proto), flags + 1); | ||||
| @@ -542,8 +625,10 @@ static void marshal_one(MarshalState *st, Janet x, int flags) { | ||||
|             int32_t count; | ||||
|             const JanetKV *struct_ = janet_unwrap_struct(x); | ||||
|             count = janet_struct_length(struct_); | ||||
|             pushbyte(st, LB_STRUCT); | ||||
|             pushbyte(st, janet_struct_proto(struct_) ? LB_STRUCT_PROTO : LB_STRUCT); | ||||
|             pushint(st, count); | ||||
|             if (janet_struct_proto(struct_)) | ||||
|                 marshal_one(st, janet_wrap_struct(janet_struct_proto(struct_)), flags + 1); | ||||
|             for (int32_t i = 0; i < janet_struct_capacity(struct_); i++) { | ||||
|                 if (janet_checktype(struct_[i].key, JANET_NIL)) | ||||
|                     continue; | ||||
| @@ -587,8 +672,7 @@ static void marshal_one(MarshalState *st, Janet x, int flags) { | ||||
|             if (!(flags & JANET_MARSHAL_UNSAFE)) goto no_registry; | ||||
|             MARK_SEEN(); | ||||
|             pushbyte(st, LB_UNSAFE_POINTER); | ||||
|             void *ptr = janet_unwrap_pointer(x); | ||||
|             pushbytes(st, (uint8_t *) &ptr, sizeof(void *)); | ||||
|             pushpointer(st, janet_unwrap_pointer(x)); | ||||
|             return; | ||||
|         } | ||||
|     no_registry: | ||||
| @@ -610,6 +694,7 @@ void janet_marshal( | ||||
|     st.seen_defs = NULL; | ||||
|     st.seen_envs = NULL; | ||||
|     st.rreg = rreg; | ||||
|     st.maybe_cycles = !(flags & JANET_MARSHAL_NO_CYCLES); | ||||
|     janet_table_init(&st.seen, 0); | ||||
|     marshal_one(&st, x, flags); | ||||
|     janet_table_deinit(&st.seen); | ||||
| @@ -694,9 +779,22 @@ static uint64_t read64(UnmarshalState *st, const uint8_t **atdata) { | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| #ifdef JANET_MARSHAL_DEBUG | ||||
| static void dump_reference_table(UnmarshalState *st) { | ||||
|     for (int32_t i = 0; i < janet_v_count(st->lookup); i++) { | ||||
|         janet_eprintf("  reference %d (%t) = %v\n", i, st->lookup[i], st->lookup[i]); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| /* Assert a janet type */ | ||||
| static void janet_asserttype(Janet x, JanetType t) { | ||||
| static void janet_asserttype(Janet x, JanetType t, UnmarshalState *st) { | ||||
|     if (!janet_checktype(x, t)) { | ||||
| #ifdef JANET_MARSHAL_DEBUG | ||||
|         dump_reference_table(st); | ||||
| #else | ||||
|         (void) st; | ||||
| #endif | ||||
|         janet_panicf("expected type %T, got %v", 1 << t, x); | ||||
|     } | ||||
| } | ||||
| @@ -748,7 +846,7 @@ static const uint8_t *unmarshal_one_env( | ||||
|             Janet fiberv; | ||||
|             /* On stack variant */ | ||||
|             data = unmarshal_one(st, data, &fiberv, flags); | ||||
|             janet_asserttype(fiberv, JANET_FIBER); | ||||
|             janet_asserttype(fiberv, JANET_FIBER, st); | ||||
|             env->as.fiber = janet_unwrap_fiber(fiberv); | ||||
|             /* Negative offset indicates untrusted input */ | ||||
|             env->offset = -offset; | ||||
| @@ -814,6 +912,8 @@ static const uint8_t *unmarshal_one_def( | ||||
|         def->constants = NULL; | ||||
|         def->bytecode = NULL; | ||||
|         def->sourcemap = NULL; | ||||
|         def->symbolmap = NULL; | ||||
|         def->symbolmap_length = 0; | ||||
|         janet_v_push(st->lookup_defs, def); | ||||
|  | ||||
|         /* Set default lengths to zero */ | ||||
| @@ -821,6 +921,7 @@ static const uint8_t *unmarshal_one_def( | ||||
|         int32_t constants_length = 0; | ||||
|         int32_t environments_length = 0; | ||||
|         int32_t defs_length = 0; | ||||
|         int32_t symbolmap_length = 0; | ||||
|  | ||||
|         /* Read flags and other fixed values */ | ||||
|         def->flags = readint(st, &data); | ||||
| @@ -836,18 +937,20 @@ static const uint8_t *unmarshal_one_def( | ||||
|             environments_length = readnat(st, &data); | ||||
|         if (def->flags & JANET_FUNCDEF_FLAG_HASDEFS) | ||||
|             defs_length = readnat(st, &data); | ||||
|         if (def->flags & JANET_FUNCDEF_FLAG_HASSYMBOLMAP) | ||||
|             symbolmap_length = readnat(st, &data); | ||||
|  | ||||
|         /* Check name and source (optional) */ | ||||
|         if (def->flags & JANET_FUNCDEF_FLAG_HASNAME) { | ||||
|             Janet x; | ||||
|             data = unmarshal_one(st, data, &x, flags + 1); | ||||
|             janet_asserttype(x, JANET_STRING); | ||||
|             janet_asserttype(x, JANET_STRING, st); | ||||
|             def->name = janet_unwrap_string(x); | ||||
|         } | ||||
|         if (def->flags & JANET_FUNCDEF_FLAG_HASSOURCE) { | ||||
|             Janet x; | ||||
|             data = unmarshal_one(st, data, &x, flags + 1); | ||||
|             janet_asserttype(x, JANET_STRING); | ||||
|             janet_asserttype(x, JANET_STRING, st); | ||||
|             def->source = janet_unwrap_string(x); | ||||
|         } | ||||
|  | ||||
| @@ -864,6 +967,27 @@ static const uint8_t *unmarshal_one_def( | ||||
|         } | ||||
|         def->constants_length = constants_length; | ||||
|  | ||||
|         /* Unmarshal symbol map, if needed */ | ||||
|         if (def->flags & JANET_FUNCDEF_FLAG_HASSYMBOLMAP) { | ||||
|             size_t size = sizeof(JanetSymbolMap) * symbolmap_length; | ||||
|             def->symbolmap = janet_malloc(size); | ||||
|             if (def->symbolmap == NULL) { | ||||
|                 JANET_OUT_OF_MEMORY; | ||||
|             } | ||||
|             for (int32_t i = 0; i < symbolmap_length; i++) { | ||||
|                 def->symbolmap[i].birth_pc = (uint32_t) readint(st, &data); | ||||
|                 def->symbolmap[i].death_pc = (uint32_t) readint(st, &data); | ||||
|                 def->symbolmap[i].slot_index = (uint32_t) readint(st, &data); | ||||
|                 Janet value; | ||||
|                 data = unmarshal_one(st, data, &value, flags + 1); | ||||
|                 if (!janet_checktype(value, JANET_SYMBOL)) { | ||||
|                     janet_panicf("corrupted symbolmap when unmarshalling debug info, got %v", value); | ||||
|                 } | ||||
|                 def->symbolmap[i].symbol = janet_unwrap_symbol(value); | ||||
|             } | ||||
|             def->symbolmap_length = (uint32_t) symbolmap_length; | ||||
|         } | ||||
|  | ||||
|         /* Unmarshal bytecode */ | ||||
|         def->bytecode = janet_malloc(sizeof(uint32_t) * bytecode_length); | ||||
|         if (!def->bytecode) { | ||||
| @@ -956,9 +1080,11 @@ static const uint8_t *unmarshal_one_fiber( | ||||
|     fiber->env = NULL; | ||||
|     fiber->last_value = janet_wrap_nil(); | ||||
| #ifdef JANET_EV | ||||
|     fiber->waiting = NULL; | ||||
|     fiber->sched_id = 0; | ||||
|     fiber->supervisor_channel = NULL; | ||||
|     fiber->ev_state = NULL; | ||||
|     fiber->ev_callback = NULL; | ||||
|     fiber->ev_stream = NULL; | ||||
| #endif | ||||
|  | ||||
|     /* Push fiber to seen stack */ | ||||
| @@ -1007,7 +1133,7 @@ static const uint8_t *unmarshal_one_fiber( | ||||
|         /* Get function */ | ||||
|         Janet funcv; | ||||
|         data = unmarshal_one(st, data, &funcv, flags + 1); | ||||
|         janet_asserttype(funcv, JANET_FUNCTION); | ||||
|         janet_asserttype(funcv, JANET_FUNCTION, st); | ||||
|         func = janet_unwrap_function(funcv); | ||||
|         def = func->def; | ||||
|  | ||||
| @@ -1053,7 +1179,7 @@ static const uint8_t *unmarshal_one_fiber( | ||||
|         Janet envv; | ||||
|         fiber_flags &= ~JANET_FIBER_FLAG_HASENV; | ||||
|         data = unmarshal_one(st, data, &envv, flags + 1); | ||||
|         janet_asserttype(envv, JANET_TABLE); | ||||
|         janet_asserttype(envv, JANET_TABLE, st); | ||||
|         fiber_env = janet_unwrap_table(envv); | ||||
|     } | ||||
|  | ||||
| @@ -1062,7 +1188,7 @@ static const uint8_t *unmarshal_one_fiber( | ||||
|         Janet fiberv; | ||||
|         fiber_flags &= ~JANET_FIBER_FLAG_HASCHILD; | ||||
|         data = unmarshal_one(st, data, &fiberv, flags + 1); | ||||
|         janet_asserttype(fiberv, JANET_FIBER); | ||||
|         janet_asserttype(fiberv, JANET_FIBER, st); | ||||
|         fiber->child = janet_unwrap_fiber(fiberv); | ||||
|     } | ||||
|  | ||||
| @@ -1106,6 +1232,18 @@ int64_t janet_unmarshal_int64(JanetMarshalContext *ctx) { | ||||
|     return read64(st, &(ctx->data)); | ||||
| } | ||||
|  | ||||
| void *janet_unmarshal_ptr(JanetMarshalContext *ctx) { | ||||
|     if (!(ctx->flags & JANET_MARSHAL_UNSAFE)) { | ||||
|         janet_panic("can only unmarshal pointers in unsafe mode"); | ||||
|     } | ||||
|     UnmarshalState *st = (UnmarshalState *)(ctx->u_state); | ||||
|     void *ptr; | ||||
|     MARSH_EOS(st, ctx->data + sizeof(void *) - 1); | ||||
|     memcpy((char *) &ptr, ctx->data, sizeof(void *)); | ||||
|     ctx->data += sizeof(void *); | ||||
|     return ptr; | ||||
| } | ||||
|  | ||||
| uint8_t janet_unmarshal_byte(JanetMarshalContext *ctx) { | ||||
|     UnmarshalState *st = (UnmarshalState *)(ctx->u_state); | ||||
|     MARSH_EOS(st, ctx->data); | ||||
| @@ -1141,6 +1279,18 @@ void *janet_unmarshal_abstract(JanetMarshalContext *ctx, size_t size) { | ||||
|     return p; | ||||
| } | ||||
|  | ||||
| void *janet_unmarshal_abstract_threaded(JanetMarshalContext *ctx, size_t size) { | ||||
| #ifdef JANET_THREADS | ||||
|     void *p = janet_abstract_threaded(ctx->at, size); | ||||
|     janet_unmarshal_abstract_reuse(ctx, p); | ||||
|     return p; | ||||
| #else | ||||
|     (void) ctx; | ||||
|     (void) size; | ||||
|     janet_panic("threaded abstracts not supported"); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| static const uint8_t *unmarshal_one_abstract(UnmarshalState *st, const uint8_t *data, Janet *out, int flags) { | ||||
|     Janet key; | ||||
|     data = unmarshal_one(st, data, &key, flags + 1); | ||||
| @@ -1148,7 +1298,9 @@ static const uint8_t *unmarshal_one_abstract(UnmarshalState *st, const uint8_t * | ||||
|     if (at == NULL) janet_panic("unknown abstract type"); | ||||
|     if (at->unmarshal) { | ||||
|         JanetMarshalContext context = {NULL, st, flags, data, at}; | ||||
|         *out = janet_wrap_abstract(at->unmarshal(&context)); | ||||
|         void *abst = at->unmarshal(&context); | ||||
|         janet_assert(abst != NULL, "null pointer abstract"); | ||||
|         *out = janet_wrap_abstract(abst); | ||||
|         if (context.at != NULL) { | ||||
|             janet_panic("janet_unmarshal_abstract not called"); | ||||
|         } | ||||
| @@ -1249,7 +1401,7 @@ static const uint8_t *unmarshal_one( | ||||
|         } | ||||
|         case LB_FIBER: { | ||||
|             JanetFiber *fiber; | ||||
|             data = unmarshal_one_fiber(st, data + 1, &fiber, flags); | ||||
|             data = unmarshal_one_fiber(st, data + 1, &fiber, flags + 1); | ||||
|             *out = janet_wrap_fiber(fiber); | ||||
|             return data; | ||||
|         } | ||||
| @@ -1264,6 +1416,9 @@ static const uint8_t *unmarshal_one( | ||||
|             func = janet_gcalloc(JANET_MEMORY_FUNCTION, sizeof(JanetFunction) + | ||||
|                                  len * sizeof(JanetFuncEnv)); | ||||
|             func->def = NULL; | ||||
|             for (int32_t i = 0; i < len; i++) { | ||||
|                 func->envs[i] = NULL; | ||||
|             } | ||||
|             *out = janet_wrap_function(func); | ||||
|             janet_v_push(st->lookup, *out); | ||||
|             data = unmarshal_one_def(st, data, &def, flags + 1); | ||||
| @@ -1279,10 +1434,18 @@ static const uint8_t *unmarshal_one( | ||||
|         } | ||||
|         case LB_REFERENCE: | ||||
|         case LB_ARRAY: | ||||
|         case LB_ARRAY_WEAK: | ||||
|         case LB_TUPLE: | ||||
|         case LB_STRUCT: | ||||
|         case LB_STRUCT_PROTO: | ||||
|         case LB_TABLE: | ||||
|         case LB_TABLE_PROTO: | ||||
|         case LB_TABLE_WEAKK: | ||||
|         case LB_TABLE_WEAKV: | ||||
|         case LB_TABLE_WEAKKV: | ||||
|         case LB_TABLE_WEAKK_PROTO: | ||||
|         case LB_TABLE_WEAKV_PROTO: | ||||
|         case LB_TABLE_WEAKKV_PROTO: | ||||
|             /* Things that open with integers */ | ||||
|         { | ||||
|             data++; | ||||
| @@ -1291,9 +1454,9 @@ static const uint8_t *unmarshal_one( | ||||
|             if (lead != LB_REFERENCE) { | ||||
|                 MARSH_EOS(st, data - 1 + len); | ||||
|             } | ||||
|             if (lead == LB_ARRAY) { | ||||
|             if (lead == LB_ARRAY || lead == LB_ARRAY_WEAK) { | ||||
|                 /* Array */ | ||||
|                 JanetArray *array = janet_array(len); | ||||
|                 JanetArray *array = (lead == LB_ARRAY_WEAK) ? janet_array_weak(len) : janet_array(len); | ||||
|                 array->count = len; | ||||
|                 *out = janet_wrap_array(array); | ||||
|                 janet_v_push(st->lookup, *out); | ||||
| @@ -1310,9 +1473,15 @@ static const uint8_t *unmarshal_one( | ||||
|                 } | ||||
|                 *out = janet_wrap_tuple(janet_tuple_end(tup)); | ||||
|                 janet_v_push(st->lookup, *out); | ||||
|             } else if (lead == LB_STRUCT) { | ||||
|             } else if (lead == LB_STRUCT || lead == LB_STRUCT_PROTO) { | ||||
|                 /* Struct */ | ||||
|                 JanetKV *struct_ = janet_struct_begin(len); | ||||
|                 if (lead == LB_STRUCT_PROTO) { | ||||
|                     Janet proto; | ||||
|                     data = unmarshal_one(st, data, &proto, flags + 1); | ||||
|                     janet_asserttype(proto, JANET_STRUCT, st); | ||||
|                     janet_struct_proto(struct_) = janet_unwrap_struct(proto); | ||||
|                 } | ||||
|                 for (int32_t i = 0; i < len; i++) { | ||||
|                     Janet key, value; | ||||
|                     data = unmarshal_one(st, data, &key, flags + 1); | ||||
| @@ -1327,13 +1496,22 @@ static const uint8_t *unmarshal_one( | ||||
|                 *out = st->lookup[len]; | ||||
|             } else { | ||||
|                 /* Table */ | ||||
|                 JanetTable *t = janet_table(len); | ||||
|                 JanetTable *t; | ||||
|                 if (lead == LB_TABLE_WEAKK_PROTO || lead == LB_TABLE_WEAKK) { | ||||
|                     t = janet_table_weakk(len); | ||||
|                 } else if (lead == LB_TABLE_WEAKV_PROTO || lead == LB_TABLE_WEAKV) { | ||||
|                     t = janet_table_weakv(len); | ||||
|                 } else if (lead == LB_TABLE_WEAKKV_PROTO || lead == LB_TABLE_WEAKKV) { | ||||
|                     t = janet_table_weakkv(len); | ||||
|                 } else { | ||||
|                     t = janet_table(len); | ||||
|                 } | ||||
|                 *out = janet_wrap_table(t); | ||||
|                 janet_v_push(st->lookup, *out); | ||||
|                 if (lead == LB_TABLE_PROTO) { | ||||
|                 if (lead == LB_TABLE_PROTO || lead == LB_TABLE_WEAKK_PROTO || lead == LB_TABLE_WEAKV_PROTO || lead == LB_TABLE_WEAKKV_PROTO) { | ||||
|                     Janet proto; | ||||
|                     data = unmarshal_one(st, data, &proto, flags + 1); | ||||
|                     janet_asserttype(proto, JANET_TABLE); | ||||
|                     janet_asserttype(proto, JANET_TABLE, st); | ||||
|                     t->proto = janet_unwrap_table(proto); | ||||
|                 } | ||||
|                 for (int32_t i = 0; i < len; i++) { | ||||
| @@ -1363,6 +1541,29 @@ static const uint8_t *unmarshal_one( | ||||
|             janet_v_push(st->lookup, *out); | ||||
|             return data; | ||||
|         } | ||||
| #ifdef JANET_EV | ||||
|         case LB_POINTER_BUFFER: { | ||||
|             data++; | ||||
|             int32_t count = readnat(st, &data); | ||||
|             int32_t capacity = readnat(st, &data); | ||||
|             MARSH_EOS(st, data + sizeof(void *)); | ||||
|             union { | ||||
|                 void *ptr; | ||||
|                 uint8_t bytes[sizeof(void *)]; | ||||
|             } u; | ||||
|             if (!(flags & JANET_MARSHAL_UNSAFE)) { | ||||
|                 janet_panicf("unsafe flag not given, " | ||||
|                              "will not unmarshal raw pointer at index %d", | ||||
|                              (int)(data - st->start)); | ||||
|             } | ||||
|             memcpy(u.bytes, data, sizeof(void *)); | ||||
|             data += sizeof(void *); | ||||
|             JanetBuffer *buffer = janet_pointer_buffer_unsafe(u.ptr, capacity, count); | ||||
|             *out = janet_wrap_buffer(buffer); | ||||
|             janet_v_push(st->lookup, *out); | ||||
|             return data; | ||||
|         } | ||||
| #endif | ||||
|         case LB_UNSAFE_CFUNCTION: { | ||||
|             MARSH_EOS(st, data + sizeof(JanetCFunction)); | ||||
|             data++; | ||||
| @@ -1461,16 +1662,17 @@ JANET_CORE_FN(cfun_env_lookup, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_marshal, | ||||
|               "(marshal x &opt reverse-lookup buffer)", | ||||
|               "(marshal x &opt reverse-lookup buffer no-cycles)", | ||||
|               "Marshal a value into a buffer and return the buffer. The buffer " | ||||
|               "can then later be unmarshalled to reconstruct the initial value. " | ||||
|               "Optionally, one can pass in a reverse lookup table to not marshal " | ||||
|               "aliased values that are found in the table. Then a forward " | ||||
|               "lookup table can be used to recover the original value when " | ||||
|               "unmarshalling.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     janet_arity(argc, 1, 4); | ||||
|     JanetBuffer *buffer; | ||||
|     JanetTable *rreg = NULL; | ||||
|     uint32_t flags = 0; | ||||
|     if (argc > 1) { | ||||
|         rreg = janet_gettable(argv, 1); | ||||
|     } | ||||
| @@ -1479,7 +1681,10 @@ JANET_CORE_FN(cfun_marshal, | ||||
|     } else { | ||||
|         buffer = janet_buffer(10); | ||||
|     } | ||||
|     janet_marshal(buffer, argv[0], rreg, 0); | ||||
|     if (argc > 3 && janet_truthy(argv[3])) { | ||||
|         flags |= JANET_MARSHAL_NO_CYCLES; | ||||
|     } | ||||
|     janet_marshal(buffer, argv[0], rreg, flags); | ||||
|     return janet_wrap_buffer(buffer); | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										154
									
								
								src/core/math.c
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								src/core/math.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -85,10 +85,10 @@ void janet_rng_longseed(JanetRNG *rng, const uint8_t *bytes, int32_t len) { | ||||
|     uint8_t state[16] = {0}; | ||||
|     for (int32_t i = 0; i < len; i++) | ||||
|         state[i & 0xF] ^= bytes[i]; | ||||
|     rng->a = state[0] + (state[1] << 8) + (state[2] << 16) + (state[3] << 24); | ||||
|     rng->b = state[4] + (state[5] << 8) + (state[6] << 16) + (state[7] << 24); | ||||
|     rng->c = state[8] + (state[9] << 8) + (state[10] << 16) + (state[11] << 24); | ||||
|     rng->d = state[12] + (state[13] << 8) + (state[14] << 16) + (state[15] << 24); | ||||
|     rng->a = state[0] + ((uint32_t) state[1] << 8) + ((uint32_t) state[2] << 16) + ((uint32_t) state[3] << 24); | ||||
|     rng->b = state[4] + ((uint32_t) state[5] << 8) + ((uint32_t) state[6] << 16) + ((uint32_t) state[7] << 24); | ||||
|     rng->c = state[8] + ((uint32_t) state[9] << 8) + ((uint32_t) state[10] << 16) + ((uint32_t) state[11] << 24); | ||||
|     rng->d = state[12] + ((uint32_t) state[13] << 8) + ((uint32_t) state[14] << 16) + ((uint32_t) state[15] << 24); | ||||
|     rng->counter = 0u; | ||||
|     /* a, b, c, d can't all be 0 */ | ||||
|     if (rng->a == 0) rng->a = 1u; | ||||
| @@ -119,7 +119,7 @@ double janet_rng_double(JanetRNG *rng) { | ||||
|  | ||||
| JANET_CORE_FN(cfun_rng_make, | ||||
|               "(math/rng &opt seed)", | ||||
|               "Creates a Psuedo-Random number generator, with an optional seed. " | ||||
|               "Creates a Pseudo-Random number generator, with an optional seed. " | ||||
|               "The seed should be an unsigned 32 bit integer or a buffer. " | ||||
|               "Do not use this for cryptography. Returns a core/rng abstract type." | ||||
|              ) { | ||||
| @@ -150,8 +150,8 @@ JANET_CORE_FN(cfun_rng_uniform, | ||||
|  | ||||
| JANET_CORE_FN(cfun_rng_int, | ||||
|               "(math/rng-int rng &opt max)", | ||||
|               "Extract a random random integer in the range [0, max] from the RNG. If " | ||||
|               "no max is given, the default is 2^31 - 1." | ||||
|               "Extract a random integer in the range [0, max) for max > 0 from the RNG.  " | ||||
|               "If max is 0, return 0.  If no max is given, the default is 2^31 - 1." | ||||
|              ) { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     JanetRNG *rng = janet_getabstract(argv, 0, &janet_rng_type); | ||||
| @@ -231,7 +231,7 @@ static Janet janet_rng_next(void *p, Janet key) { | ||||
| /* Get a random number */ | ||||
| JANET_CORE_FN(janet_rand, | ||||
|               "(math/random)", | ||||
|               "Returns a uniformly distributed random number between 0 and 1") { | ||||
|               "Returns a uniformly distributed random number between 0 and 1.") { | ||||
|     (void) argv; | ||||
|     janet_fixarity(argc, 0); | ||||
|     return janet_wrap_number(janet_rng_double(&janet_vm.rng)); | ||||
| @@ -240,7 +240,7 @@ JANET_CORE_FN(janet_rand, | ||||
| /* Seed the random number generator */ | ||||
| JANET_CORE_FN(janet_srand, | ||||
|               "(math/seedrandom seed)", | ||||
|               "Set the seed for the random number generator. seed should be " | ||||
|               "Set the seed for the random number generator. `seed` should be " | ||||
|               "an integer or a buffer." | ||||
|              ) { | ||||
|     janet_fixarity(argc, 1); | ||||
| @@ -254,42 +254,45 @@ JANET_CORE_FN(janet_srand, | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| #define JANET_DEFINE_MATHOP(name, fop, doc)\ | ||||
| JANET_CORE_FN(janet_##name, "(math/" #name " x)", doc) {\ | ||||
| #define JANET_DEFINE_NAMED_MATHOP(janet_name, fop, doc)\ | ||||
| JANET_CORE_FN(janet_##fop, "(math/" janet_name " x)", doc) {\ | ||||
|     janet_fixarity(argc, 1); \ | ||||
|     double x = janet_getnumber(argv, 0); \ | ||||
|     return janet_wrap_number(fop(x)); \ | ||||
| } | ||||
|  | ||||
| JANET_DEFINE_MATHOP(acos, acos, "Returns the arccosize of x.") | ||||
| JANET_DEFINE_MATHOP(asin, asin, "Returns the arcsin of x.") | ||||
| JANET_DEFINE_MATHOP(atan, atan, "Returns the arctangent of x.") | ||||
| JANET_DEFINE_MATHOP(cos, cos, "Returns the cosine of x.") | ||||
| JANET_DEFINE_MATHOP(cosh, cosh, "Returns the hyperbolic cosine of x.") | ||||
| JANET_DEFINE_MATHOP(acosh, acosh, "Returns the hyperbolic arccosine of x.") | ||||
| JANET_DEFINE_MATHOP(sin, sin, "Returns the sine of x.") | ||||
| JANET_DEFINE_MATHOP(sinh, sinh, "Returns the hyperbolic sine of x.") | ||||
| JANET_DEFINE_MATHOP(asinh, asinh, "Returns the hypberbolic arcsine of x.") | ||||
| JANET_DEFINE_MATHOP(tan, tan, "Returns the tangent of x.") | ||||
| JANET_DEFINE_MATHOP(tanh, tanh, "Returns the hyperbolic tangent of x.") | ||||
| JANET_DEFINE_MATHOP(atanh, atanh, "Returns the hyperbolic arctangent of x.") | ||||
| JANET_DEFINE_MATHOP(exp, exp, "Returns e to the power of x.") | ||||
| JANET_DEFINE_MATHOP(exp2, exp2, "Returns 2 to the power of x.") | ||||
| JANET_DEFINE_MATHOP(expm1, expm1, "Returns e to the power of x minus 1.") | ||||
| JANET_DEFINE_MATHOP(log, log, "Returns the natural logarithm of x.") | ||||
| JANET_DEFINE_MATHOP(log10, log10, "Returns the log base 10 of x.") | ||||
| JANET_DEFINE_MATHOP(log2, log2, "Returns the log base 2 of x.") | ||||
| JANET_DEFINE_MATHOP(sqrt, sqrt, "Returns the square root of x.") | ||||
| JANET_DEFINE_MATHOP(cbrt, cbrt, "Returns the cube root of x.") | ||||
| JANET_DEFINE_MATHOP(ceil, ceil, "Returns the smallest integer value number that is not less than x.") | ||||
| JANET_DEFINE_MATHOP(fabs, fabs, "Return the absolute value of x.") | ||||
| JANET_DEFINE_MATHOP(floor, floor, "Returns the largest integer value number that is not greater than x.") | ||||
| JANET_DEFINE_MATHOP(trunc, trunc, "Returns the integer between x and 0 nearest to x.") | ||||
| JANET_DEFINE_MATHOP(round, round, "Returns the integer nearest to x.") | ||||
| JANET_DEFINE_MATHOP(gamma, lgamma, "Returns gamma(x).") | ||||
| JANET_DEFINE_MATHOP(log1p, log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)") | ||||
| JANET_DEFINE_MATHOP(erf, erf, "Returns the error function of x.") | ||||
| JANET_DEFINE_MATHOP(erfc, erfc, "Returns the complementary error function of x.") | ||||
| #define JANET_DEFINE_MATHOP(fop, doc) JANET_DEFINE_NAMED_MATHOP(#fop, fop, doc) | ||||
|  | ||||
| JANET_DEFINE_MATHOP(acos, "Returns the arccosine of x.") | ||||
| JANET_DEFINE_MATHOP(asin, "Returns the arcsin of x.") | ||||
| JANET_DEFINE_MATHOP(atan, "Returns the arctangent of x.") | ||||
| JANET_DEFINE_MATHOP(cos, "Returns the cosine of x.") | ||||
| JANET_DEFINE_MATHOP(cosh, "Returns the hyperbolic cosine of x.") | ||||
| JANET_DEFINE_MATHOP(acosh, "Returns the hyperbolic arccosine of x.") | ||||
| JANET_DEFINE_MATHOP(sin, "Returns the sine of x.") | ||||
| JANET_DEFINE_MATHOP(sinh, "Returns the hyperbolic sine of x.") | ||||
| JANET_DEFINE_MATHOP(asinh, "Returns the hyperbolic arcsine of x.") | ||||
| JANET_DEFINE_MATHOP(tan, "Returns the tangent of x.") | ||||
| JANET_DEFINE_MATHOP(tanh, "Returns the hyperbolic tangent of x.") | ||||
| JANET_DEFINE_MATHOP(atanh, "Returns the hyperbolic arctangent of x.") | ||||
| JANET_DEFINE_MATHOP(exp, "Returns e to the power of x.") | ||||
| JANET_DEFINE_MATHOP(exp2, "Returns 2 to the power of x.") | ||||
| JANET_DEFINE_MATHOP(expm1, "Returns e to the power of x minus 1.") | ||||
| JANET_DEFINE_MATHOP(log, "Returns the natural logarithm of x.") | ||||
| JANET_DEFINE_MATHOP(log10, "Returns the log base 10 of x.") | ||||
| JANET_DEFINE_MATHOP(log2, "Returns the log base 2 of x.") | ||||
| JANET_DEFINE_MATHOP(sqrt, "Returns the square root of x.") | ||||
| JANET_DEFINE_MATHOP(cbrt, "Returns the cube root of x.") | ||||
| JANET_DEFINE_MATHOP(ceil, "Returns the smallest integer value number that is not less than x.") | ||||
| JANET_DEFINE_MATHOP(floor, "Returns the largest integer value number that is not greater than x.") | ||||
| JANET_DEFINE_MATHOP(trunc, "Returns the integer between x and 0 nearest to x.") | ||||
| JANET_DEFINE_MATHOP(round, "Returns the integer nearest to x.") | ||||
| JANET_DEFINE_MATHOP(log1p, "Returns (log base e of x) + 1 more accurately than (+ (math/log x) 1)") | ||||
| JANET_DEFINE_MATHOP(erf, "Returns the error function of x.") | ||||
| JANET_DEFINE_MATHOP(erfc, "Returns the complementary error function of x.") | ||||
| JANET_DEFINE_NAMED_MATHOP("log-gamma", lgamma, "Returns log-gamma(x).") | ||||
| JANET_DEFINE_NAMED_MATHOP("abs", fabs, "Return the absolute value of x.") | ||||
| JANET_DEFINE_NAMED_MATHOP("gamma", tgamma, "Returns gamma(x).") | ||||
|  | ||||
| #define JANET_DEFINE_MATH2OP(name, fop, signature, doc)\ | ||||
| JANET_CORE_FN(janet_##name, signature, doc) {\ | ||||
| @@ -302,13 +305,69 @@ JANET_CORE_FN(janet_##name, signature, doc) {\ | ||||
| JANET_DEFINE_MATH2OP(atan2, atan2, "(math/atan2 y x)", "Returns the arctangent of y/x. Works even when x is 0.") | ||||
| JANET_DEFINE_MATH2OP(pow, pow, "(math/pow a x)", "Returns a to the power of x.") | ||||
| JANET_DEFINE_MATH2OP(hypot, hypot, "(math/hypot a b)", "Returns c from the equation c^2 = a^2 + b^2.") | ||||
| JANET_DEFINE_MATH2OP(nextafter, nextafter,  "(math/next x y)", "Returns the next representable floating point vaue after x in the direction of y.") | ||||
| JANET_DEFINE_MATH2OP(nextafter, nextafter,  "(math/next x y)", "Returns the next representable floating point value after x in the direction of y.") | ||||
|  | ||||
| JANET_CORE_FN(janet_not, "(not x)", "Returns the boolean inverse of x.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     return janet_wrap_boolean(!janet_truthy(argv[0])); | ||||
| } | ||||
|  | ||||
| static double janet_gcd(double x, double y) { | ||||
|     if (isnan(x) || isnan(y)) { | ||||
| #ifdef NAN | ||||
|         return NAN; | ||||
| #else | ||||
|         return 0.0 / 0.0; | ||||
| #endif | ||||
|     } | ||||
|     if (isinf(x) || isinf(y)) return INFINITY; | ||||
|     while (y != 0) { | ||||
|         double temp = y; | ||||
|         y = fmod(x, y); | ||||
|         x = temp; | ||||
|     } | ||||
|     return x; | ||||
| } | ||||
|  | ||||
| static double janet_lcm(double x, double y) { | ||||
|     return (x / janet_gcd(x, y)) * y; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_gcd, "(math/gcd x y)", | ||||
|               "Returns the greatest common divisor between x and y.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     double x = janet_getnumber(argv, 0); | ||||
|     double y = janet_getnumber(argv, 1); | ||||
|     return janet_wrap_number(janet_gcd(x, y)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_lcm, "(math/lcm x y)", | ||||
|               "Returns the least common multiple of x and y.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     double x = janet_getnumber(argv, 0); | ||||
|     double y = janet_getnumber(argv, 1); | ||||
|     return janet_wrap_number(janet_lcm(x, y)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_frexp, "(math/frexp x)", | ||||
|               "Returns a tuple of (mantissa, exponent) from number.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     double x = janet_getnumber(argv, 0); | ||||
|     int exp; | ||||
|     x = frexp(x, &exp); | ||||
|     Janet *result = janet_tuple_begin(2); | ||||
|     result[0] = janet_wrap_number(x); | ||||
|     result[1] = janet_wrap_number((double) exp); | ||||
|     return janet_wrap_tuple(janet_tuple_end(result)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(janet_cfun_ldexp, "(math/ldexp m e)", | ||||
|               "Creates a new number from a mantissa and an exponent.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     double x = janet_getnumber(argv, 0); | ||||
|     int32_t y = janet_getinteger(argv, 1); | ||||
|     return janet_wrap_number(ldexp(x, y)); | ||||
| } | ||||
|  | ||||
| /* Module entry point */ | ||||
| void janet_lib_math(JanetTable *env) { | ||||
| @@ -346,13 +405,18 @@ void janet_lib_math(JanetTable *env) { | ||||
|         JANET_CORE_REG("math/hypot", janet_hypot), | ||||
|         JANET_CORE_REG("math/exp2", janet_exp2), | ||||
|         JANET_CORE_REG("math/log1p", janet_log1p), | ||||
|         JANET_CORE_REG("math/gamma", janet_gamma), | ||||
|         JANET_CORE_REG("math/gamma", janet_tgamma), | ||||
|         JANET_CORE_REG("math/log-gamma", janet_lgamma), | ||||
|         JANET_CORE_REG("math/erfc", janet_erfc), | ||||
|         JANET_CORE_REG("math/erf", janet_erf), | ||||
|         JANET_CORE_REG("math/expm1", janet_expm1), | ||||
|         JANET_CORE_REG("math/trunc", janet_trunc), | ||||
|         JANET_CORE_REG("math/round", janet_round), | ||||
|         JANET_CORE_REG("math/next", janet_nextafter), | ||||
|         JANET_CORE_REG("math/gcd", janet_cfun_gcd), | ||||
|         JANET_CORE_REG("math/lcm", janet_cfun_lcm), | ||||
|         JANET_CORE_REG("math/frexp", janet_cfun_frexp), | ||||
|         JANET_CORE_REG("math/ldexp", janet_cfun_ldexp), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, math_cfuns); | ||||
| @@ -369,11 +433,11 @@ void janet_lib_math(JanetTable *env) { | ||||
|     JANET_CORE_DEF(env, "math/int32-min", janet_wrap_number(INT32_MIN), | ||||
|                    "The minimum contiguous integer representable by a 32 bit signed integer"); | ||||
|     JANET_CORE_DEF(env, "math/int32-max", janet_wrap_number(INT32_MAX), | ||||
|                    "The maximum contiguous integer represtenable by a 32 bit signed integer"); | ||||
|                    "The maximum contiguous integer representable by a 32 bit signed integer"); | ||||
|     JANET_CORE_DEF(env, "math/int-min", janet_wrap_number(JANET_INTMIN_DOUBLE), | ||||
|                    "The minimum contiguous integer representable by a double (2^53)"); | ||||
|     JANET_CORE_DEF(env, "math/int-max", janet_wrap_number(JANET_INTMAX_DOUBLE), | ||||
|                    "The maximum contiguous integer represtenable by a double (-(2^53))"); | ||||
|                    "The maximum contiguous integer representable by a double (-(2^53))"); | ||||
| #ifdef NAN | ||||
|     JANET_CORE_DEF(env, "math/nan", janet_wrap_number(NAN), "Not a number (IEEE-754 NaN)"); | ||||
| #else | ||||
|   | ||||
							
								
								
									
										381
									
								
								src/core/net.c
									
									
									
									
									
								
							
							
						
						
									
										381
									
								
								src/core/net.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose and contributors. | ||||
| * Copyright (c) 2025 Calvin Rose and contributors. | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -24,6 +24,7 @@ | ||||
| #include "features.h" | ||||
| #include <janet.h> | ||||
| #include "util.h" | ||||
| #include "fiber.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_NET | ||||
| @@ -34,9 +35,11 @@ | ||||
| #include <windows.h> | ||||
| #include <ws2tcpip.h> | ||||
| #include <mswsock.h> | ||||
| #ifdef JANET_MSVC | ||||
| #pragma comment (lib, "Ws2_32.lib") | ||||
| #pragma comment (lib, "Mswsock.lib") | ||||
| #pragma comment (lib, "Advapi32.lib") | ||||
| #endif | ||||
| #else | ||||
| #include <arpa/inet.h> | ||||
| #include <unistd.h> | ||||
| @@ -76,12 +79,20 @@ const JanetAbstractType janet_address_type = { | ||||
|  | ||||
| /* maximum number of bytes in a socket address host (post name resolution) */ | ||||
| #ifdef JANET_WINDOWS | ||||
| #ifdef JANET_NO_IPV6 | ||||
| #define SA_ADDRSTRLEN (INET_ADDRSTRLEN + 1) | ||||
| #else | ||||
| #define SA_ADDRSTRLEN (INET6_ADDRSTRLEN + 1) | ||||
| #endif | ||||
| typedef unsigned short in_port_t; | ||||
| #else | ||||
| #define JANET_SA_MAX(a, b) (((a) > (b))? (a) : (b)) | ||||
| #ifdef JANET_NO_IPV6 | ||||
| #define SA_ADDRSTRLEN JANET_SA_MAX(INET_ADDRSTRLEN + 1, (sizeof ((struct sockaddr_un *)0)->sun_path) + 1) | ||||
| #else | ||||
| #define SA_ADDRSTRLEN JANET_SA_MAX(INET6_ADDRSTRLEN + 1, (sizeof ((struct sockaddr_un *)0)->sun_path) + 1) | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| static JanetStream *make_stream(JSock handle, uint32_t flags); | ||||
|  | ||||
| @@ -109,12 +120,57 @@ static void janet_net_socknoblock(JSock s) { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| /* State machine for async connect */ | ||||
|  | ||||
| void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     JanetStream *stream = fiber->ev_stream; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
| #ifndef JANET_WINDOWS | ||||
|         /* Wait until we have an actual event before checking. | ||||
|          * Windows doesn't support async connect with this, just try immediately.*/ | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
| #endif | ||||
|         case JANET_ASYNC_EVENT_DEINIT: | ||||
|             return; | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_cancel(fiber, janet_cstringv("stream closed")); | ||||
|             janet_async_end(fiber); | ||||
|             return; | ||||
|     } | ||||
| #ifdef JANET_WINDOWS | ||||
|     int res = 0; | ||||
|     int size = sizeof(res); | ||||
|     int r = getsockopt((SOCKET)stream->handle, SOL_SOCKET, SO_ERROR, (char *)&res, &size); | ||||
| #else | ||||
|     int res = 0; | ||||
|     socklen_t size = sizeof res; | ||||
|     int r = getsockopt(stream->handle, SOL_SOCKET, SO_ERROR, &res, &size); | ||||
| #endif | ||||
|     if (r == 0) { | ||||
|         if (res == 0) { | ||||
|             janet_schedule(fiber, janet_wrap_abstract(stream)); | ||||
|         } else { | ||||
|             janet_cancel(fiber, janet_cstringv(janet_strerror(res))); | ||||
|             stream->flags |= JANET_STREAM_TOCLOSE; | ||||
|         } | ||||
|     } else { | ||||
|         janet_cancel(fiber, janet_ev_lasterr()); | ||||
|         stream->flags |= JANET_STREAM_TOCLOSE; | ||||
|     } | ||||
|     janet_async_end(fiber); | ||||
| } | ||||
|  | ||||
| static JANET_NO_RETURN void net_sched_connect(JanetStream *stream) { | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_WRITE, net_callback_connect, NULL); | ||||
| } | ||||
|  | ||||
| /* State machine for accepting connections. */ | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
|  | ||||
| typedef struct { | ||||
|     JanetListenerState head; | ||||
|     WSAOVERLAPPED overlapped; | ||||
|     JanetFunction *function; | ||||
|     JanetStream *lstream; | ||||
| @@ -122,72 +178,74 @@ typedef struct { | ||||
|     char buf[1024]; | ||||
| } NetStateAccept; | ||||
|  | ||||
| static int net_sched_accept_impl(NetStateAccept *state, Janet *err); | ||||
| static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet *err); | ||||
|  | ||||
| JanetAsyncStatus net_machine_accept(JanetListenerState *s, JanetAsyncEvent event) { | ||||
|     NetStateAccept *state = (NetStateAccept *)s; | ||||
| void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     NetStateAccept *state = (NetStateAccept *)fiber->ev_state; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
|         case JANET_ASYNC_EVENT_MARK: { | ||||
|             if (state->lstream) janet_mark(janet_wrap_abstract(state->lstream)); | ||||
|             if (state->astream) janet_mark(janet_wrap_abstract(state->astream)); | ||||
|             if (state->function) janet_mark(janet_wrap_abstract(state->function)); | ||||
|             if (state->function) janet_mark(janet_wrap_function(state->function)); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_schedule(s->fiber, janet_wrap_nil()); | ||||
|             return JANET_ASYNC_STATUS_DONE; | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             return; | ||||
|         case JANET_ASYNC_EVENT_COMPLETE: { | ||||
|             int seconds; | ||||
|             int bytes = sizeof(seconds); | ||||
|             if (NO_ERROR != getsockopt((SOCKET) state->astream->handle, SOL_SOCKET, SO_CONNECT_TIME, | ||||
|                                        (char *)&seconds, &bytes)) { | ||||
|                 janet_cancel(s->fiber, janet_cstringv("failed to accept connection")); | ||||
|                 return JANET_ASYNC_STATUS_DONE; | ||||
|             if (state->astream->flags & JANET_STREAM_CLOSED) { | ||||
|                 janet_cancel(fiber, janet_cstringv("failed to accept connection")); | ||||
|                 janet_async_end(fiber); | ||||
|                 return; | ||||
|             } | ||||
|             SOCKET lsock = (SOCKET) state->lstream->handle; | ||||
|             if (NO_ERROR != setsockopt((SOCKET) state->astream->handle, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, | ||||
|                                        (char *) & (state->lstream->handle), sizeof(SOCKET))) { | ||||
|                 janet_cancel(s->fiber, janet_cstringv("failed to accept connection")); | ||||
|                 return JANET_ASYNC_STATUS_DONE; | ||||
|                                        (char *) &lsock, sizeof(lsock))) { | ||||
|                 janet_cancel(fiber, janet_cstringv("failed to accept connection")); | ||||
|                 janet_async_end(fiber); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             Janet streamv = janet_wrap_abstract(state->astream); | ||||
|             if (state->function) { | ||||
|                 /* Schedule worker */ | ||||
|                 JanetFiber *fiber = janet_fiber(state->function, 64, 1, &streamv); | ||||
|                 fiber->supervisor_channel = s->fiber->supervisor_channel; | ||||
|                 janet_schedule(fiber, janet_wrap_nil()); | ||||
|                 JanetFiber *sub_fiber = janet_fiber(state->function, 64, 1, &streamv); | ||||
|                 sub_fiber->supervisor_channel = fiber->supervisor_channel; | ||||
|                 janet_schedule(sub_fiber, janet_wrap_nil()); | ||||
|                 /* Now listen again for next connection */ | ||||
|                 Janet err; | ||||
|                 if (net_sched_accept_impl(state, &err)) { | ||||
|                     janet_cancel(s->fiber, err); | ||||
|                     return JANET_ASYNC_STATUS_DONE; | ||||
|                 if (net_sched_accept_impl(state, fiber, &err)) { | ||||
|                     janet_cancel(fiber, err); | ||||
|                     janet_async_end(fiber); | ||||
|                     return; | ||||
|                 } | ||||
|             } else { | ||||
|                 janet_schedule(s->fiber, streamv); | ||||
|                 return JANET_ASYNC_STATUS_DONE; | ||||
|                 janet_schedule(fiber, streamv); | ||||
|                 janet_async_end(fiber); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return JANET_ASYNC_STATUS_NOT_DONE; | ||||
| } | ||||
|  | ||||
| JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) { | ||||
|     Janet err; | ||||
|     SOCKET lsock = (SOCKET) stream->handle; | ||||
|     JanetListenerState *s = janet_listen(stream, net_machine_accept, JANET_ASYNC_LISTEN_READ, sizeof(NetStateAccept), NULL); | ||||
|     NetStateAccept *state = (NetStateAccept *)s; | ||||
|     NetStateAccept *state = janet_malloc(sizeof(NetStateAccept)); | ||||
|     memset(&state->overlapped, 0, sizeof(WSAOVERLAPPED)); | ||||
|     memset(&state->buf, 0, 1024); | ||||
|     state->function = fun; | ||||
|     state->lstream = stream; | ||||
|     s->tag = &state->overlapped; | ||||
|     if (net_sched_accept_impl(state, &err)) janet_panicv(err); | ||||
|     janet_await(); | ||||
|     if (net_sched_accept_impl(state, janet_root_fiber(), &err)) { | ||||
|         janet_free(state); | ||||
|         janet_panicv(err); | ||||
|     } | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_READ, net_callback_accept, state); | ||||
| } | ||||
|  | ||||
| static int net_sched_accept_impl(NetStateAccept *state, Janet *err) { | ||||
| static int net_sched_accept_impl(NetStateAccept *state, JanetFiber *fiber, Janet *err) { | ||||
|     SOCKET lsock = (SOCKET) state->lstream->handle; | ||||
|     SOCKET asock = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); | ||||
|     if (asock == INVALID_SOCKET) { | ||||
| @@ -199,7 +257,11 @@ static int net_sched_accept_impl(NetStateAccept *state, Janet *err) { | ||||
|     int socksize = sizeof(SOCKADDR_STORAGE) + 16; | ||||
|     if (FALSE == AcceptEx(lsock, asock, state->buf, 0, socksize, socksize, NULL, &state->overlapped)) { | ||||
|         int code = WSAGetLastError(); | ||||
|         if (code == WSA_IO_PENDING) return 0; /* indicates io is happening async */ | ||||
|         if (code == WSA_IO_PENDING) { | ||||
|             /* indicates io is happening async */ | ||||
|             janet_async_in_flight(fiber); | ||||
|             return 0; | ||||
|         } | ||||
|         *err = janet_ev_lasterr(); | ||||
|         return 1; | ||||
|     } | ||||
| @@ -209,12 +271,12 @@ static int net_sched_accept_impl(NetStateAccept *state, Janet *err) { | ||||
| #else | ||||
|  | ||||
| typedef struct { | ||||
|     JanetListenerState head; | ||||
|     JanetFunction *function; | ||||
| } NetStateAccept; | ||||
|  | ||||
| JanetAsyncStatus net_machine_accept(JanetListenerState *s, JanetAsyncEvent event) { | ||||
|     NetStateAccept *state = (NetStateAccept *)s; | ||||
| void net_callback_accept(JanetFiber *fiber, JanetAsyncEvent event) { | ||||
|     JanetStream *stream = fiber->ev_stream; | ||||
|     NetStateAccept *state = (NetStateAccept *)fiber->ev_state; | ||||
|     switch (event) { | ||||
|         default: | ||||
|             break; | ||||
| @@ -223,39 +285,47 @@ JanetAsyncStatus net_machine_accept(JanetListenerState *s, JanetAsyncEvent event | ||||
|             break; | ||||
|         } | ||||
|         case JANET_ASYNC_EVENT_CLOSE: | ||||
|             janet_schedule(s->fiber, janet_wrap_nil()); | ||||
|             return JANET_ASYNC_STATUS_DONE; | ||||
|             janet_schedule(fiber, janet_wrap_nil()); | ||||
|             janet_async_end(fiber); | ||||
|             return; | ||||
|         case JANET_ASYNC_EVENT_INIT: | ||||
|         case JANET_ASYNC_EVENT_READ: { | ||||
|             JSock connfd = accept(s->stream->handle, NULL, NULL); | ||||
| #if defined(JANET_LINUX) | ||||
|             JSock connfd = accept4(stream->handle, NULL, NULL, SOCK_CLOEXEC); | ||||
| #else | ||||
|             /* On BSDs, CLOEXEC should be inherited from server socket */ | ||||
|             JSock connfd = accept(stream->handle, NULL, NULL); | ||||
| #endif | ||||
|             if (JSOCKVALID(connfd)) { | ||||
|                 janet_net_socknoblock(connfd); | ||||
|                 JanetStream *stream = make_stream(connfd, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE); | ||||
|                 Janet streamv = janet_wrap_abstract(stream); | ||||
|                 if (state->function) { | ||||
|                     JanetFiber *fiber = janet_fiber(state->function, 64, 1, &streamv); | ||||
|                     fiber->supervisor_channel = s->fiber->supervisor_channel; | ||||
|                     janet_schedule(fiber, janet_wrap_nil()); | ||||
|                     JanetFiber *sub_fiber = janet_fiber(state->function, 64, 1, &streamv); | ||||
|                     sub_fiber->supervisor_channel = fiber->supervisor_channel; | ||||
|                     janet_schedule(sub_fiber, janet_wrap_nil()); | ||||
|                 } else { | ||||
|                     janet_schedule(s->fiber, streamv); | ||||
|                     return JANET_ASYNC_STATUS_DONE; | ||||
|                     janet_schedule(fiber, streamv); | ||||
|                     janet_async_end(fiber); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return JANET_ASYNC_STATUS_NOT_DONE; | ||||
| } | ||||
|  | ||||
| JANET_NO_RETURN static void janet_sched_accept(JanetStream *stream, JanetFunction *fun) { | ||||
|     NetStateAccept *state = (NetStateAccept *) janet_listen(stream, net_machine_accept, JANET_ASYNC_LISTEN_READ, sizeof(NetStateAccept), NULL); | ||||
|     NetStateAccept *state = janet_malloc(sizeof(NetStateAccept)); | ||||
|     memset(state, 0, sizeof(NetStateAccept)); | ||||
|     state->function = fun; | ||||
|     janet_await(); | ||||
|     if (fun) janet_stream_level_triggered(stream); | ||||
|     janet_async_start(stream, JANET_ASYNC_LISTEN_READ, net_callback_accept, state); | ||||
| } | ||||
|  | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* Adress info */ | ||||
| /* Address info */ | ||||
|  | ||||
| static int janet_get_sockettype(Janet *argv, int32_t argc, int32_t n) { | ||||
|     JanetKeyword stype = janet_optkeyword(argv, argc, n, NULL); | ||||
| @@ -330,6 +400,7 @@ JANET_CORE_FN(cfun_net_sockaddr, | ||||
|               "given in the port argument. On Linux, abstract " | ||||
|               "unix domain sockets are specified with a leading '@' character in port. If `multi` is truthy, will " | ||||
|               "return all address that match in an array instead of just the first.") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_NET_CONNECT); /* connect OR listen */ | ||||
|     janet_arity(argc, 2, 4); | ||||
|     int socktype = janet_get_sockettype(argv, argc, 2); | ||||
|     int is_unix = 0; | ||||
| @@ -375,6 +446,7 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|               "to specify a connection type, either :stream or :datagram. The default is :stream. " | ||||
|               "Bindhost is an optional string to select from what address to make the outgoing " | ||||
|               "connection, with the default being the same as using the OS's preferred address. ") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_NET_CONNECT); | ||||
|     janet_arity(argc, 2, 5); | ||||
|  | ||||
|     /* Check arguments */ | ||||
| @@ -382,7 +454,7 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     int is_unix = 0; | ||||
|     char *bindhost = (char *) janet_optcstring(argv, argc, 3, NULL); | ||||
|     char *bindport = NULL; | ||||
|     if (janet_checkint(argv[4])) { | ||||
|     if (argc >= 5 && janet_checkint(argv[4])) { | ||||
|         bindport = (char *)janet_to_string(argv[4]); | ||||
|     } else { | ||||
|         bindport = (char *)janet_optcstring(argv, argc, 4, NULL); | ||||
| @@ -431,7 +503,7 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|         struct addrinfo *rp = NULL; | ||||
|         for (rp = ai; rp != NULL; rp = rp->ai_next) { | ||||
| #ifdef JANET_WINDOWS | ||||
|             sock = WSASocketW(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED); | ||||
|             sock = WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, NULL, 0, WSA_FLAG_OVERLAPPED); | ||||
| #else | ||||
|             sock = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol); | ||||
| #endif | ||||
| @@ -453,7 +525,7 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     if (binding) { | ||||
|         struct addrinfo *rp = NULL; | ||||
|         int did_bind = 0; | ||||
|         for (rp = ai; rp != NULL; rp = rp->ai_next) { | ||||
|         for (rp = binding; rp != NULL; rp = rp->ai_next) { | ||||
|             if (bind(sock, rp->ai_addr, (int) rp->ai_addrlen) == 0) { | ||||
|                 did_bind = 1; | ||||
|                 break; | ||||
| @@ -470,14 +542,23 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Wrap socket in abstract type JanetStream */ | ||||
|     JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE); | ||||
|  | ||||
|     /* Set up the socket for non-blocking IO before connecting */ | ||||
|     janet_net_socknoblock(sock); | ||||
|  | ||||
|     /* Connect to socket */ | ||||
| #ifdef JANET_WINDOWS | ||||
|     int status = WSAConnect(sock, addr, addrlen, NULL, NULL, NULL, NULL); | ||||
|     Janet lasterr = janet_ev_lasterr(); | ||||
|     int err = WSAGetLastError(); | ||||
|     freeaddrinfo(ai); | ||||
| #else | ||||
|     int status = connect(sock, addr, addrlen); | ||||
|     Janet lasterr = janet_ev_lasterr(); | ||||
|     int status; | ||||
|     do { | ||||
|         status = connect(sock, addr, addrlen); | ||||
|     } while (status == -1 && errno == EINTR); | ||||
|     int err = errno; | ||||
|     if (is_unix) { | ||||
|         janet_free(ai); | ||||
|     } else { | ||||
| @@ -485,30 +566,38 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     if (status == -1) { | ||||
|         JSOCKCLOSE(sock); | ||||
|         janet_panicf("could not connect socket: %V", lasterr); | ||||
|     if (status) { | ||||
| #ifdef JANET_WINDOWS | ||||
|         if (err != WSAEWOULDBLOCK) { | ||||
| #else | ||||
|         if (err != EINPROGRESS) { | ||||
| #endif | ||||
|             JSOCKCLOSE(sock); | ||||
|             Janet lasterr = janet_ev_lasterr(); | ||||
|             janet_panicf("could not connect socket: %V", lasterr); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Set up the socket for non-blocking IO after connect - TODO - non-blocking connect? */ | ||||
|     janet_net_socknoblock(sock); | ||||
|  | ||||
|     /* Wrap socket in abstract type JanetStream */ | ||||
|     JanetStream *stream = make_stream(sock, JANET_STREAM_READABLE | JANET_STREAM_WRITABLE); | ||||
|     return janet_wrap_abstract(stream); | ||||
|     net_sched_connect(stream); | ||||
| } | ||||
|  | ||||
| static const char *serverify_socket(JSock sfd) { | ||||
| static const char *serverify_socket(JSock sfd, int reuse_addr, int reuse_port) { | ||||
|     /* Set various socket options */ | ||||
|     int enable = 1; | ||||
|     if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) { | ||||
|         return "setsockopt(SO_REUSEADDR) failed"; | ||||
|     if (reuse_addr) { | ||||
|         if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *) &enable, sizeof(int)) < 0) { | ||||
|             return "setsockopt(SO_REUSEADDR) failed"; | ||||
|         } | ||||
|     } | ||||
|     if (reuse_port) { | ||||
| #ifdef SO_REUSEPORT | ||||
|     if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { | ||||
|         return "setsockopt(SO_REUSEPORT) failed"; | ||||
|     } | ||||
|         if (setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)) < 0) { | ||||
|             return "setsockopt(SO_REUSEPORT) failed"; | ||||
|         } | ||||
| #else | ||||
|         (void) reuse_port; | ||||
| #endif | ||||
|     } | ||||
|     janet_net_socknoblock(sfd); | ||||
|     return NULL; | ||||
| } | ||||
| @@ -562,18 +651,21 @@ JANET_CORE_FN(cfun_net_shutdown, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_net_listen, | ||||
|               "(net/listen host port &opt type)", | ||||
|               "(net/listen host port &opt type no-reuse)", | ||||
|               "Creates a server. Returns a new stream that is neither readable nor " | ||||
|               "writeable. Use net/accept or net/accept-loop be to handle connections and start the server. " | ||||
|               "The type parameter specifies the type of network connection, either " | ||||
|               "a :stream (usually tcp), or :datagram (usually udp). If not specified, the default is " | ||||
|               ":stream. The host and port arguments are the same as in net/address.") { | ||||
|     janet_arity(argc, 2, 3); | ||||
|               ":stream. The host and port arguments are the same as in net/address. The last boolean parameter `no-reuse` will " | ||||
|               "disable the use of SO_REUSEADDR and SO_REUSEPORT when creating a server on some operating systems.") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_NET_LISTEN); | ||||
|     janet_arity(argc, 2, 4); | ||||
|  | ||||
|     /* Get host, port, and handler*/ | ||||
|     int socktype = janet_get_sockettype(argv, argc, 2); | ||||
|     int is_unix = 0; | ||||
|     struct addrinfo *ai = janet_get_addrinfo(argv, 0, socktype, 1, &is_unix); | ||||
|     int reuse = !(argc >= 4 && janet_truthy(argv[3])); | ||||
|  | ||||
|     JSock sfd = JSOCKDEFAULT; | ||||
| #ifndef JANET_WINDOWS | ||||
| @@ -583,7 +675,7 @@ JANET_CORE_FN(cfun_net_listen, | ||||
|             janet_free(ai); | ||||
|             janet_panicf("could not create socket: %V", janet_ev_lasterr()); | ||||
|         } | ||||
|         const char *err = serverify_socket(sfd); | ||||
|         const char *err = serverify_socket(sfd, reuse, 0); | ||||
|         if (NULL != err || bind(sfd, (struct sockaddr *)ai, sizeof(struct sockaddr_un))) { | ||||
|             JSOCKCLOSE(sfd); | ||||
|             janet_free(ai); | ||||
| @@ -606,7 +698,7 @@ JANET_CORE_FN(cfun_net_listen, | ||||
|             sfd = socket(rp->ai_family, rp->ai_socktype | JSOCKFLAGS, rp->ai_protocol); | ||||
| #endif | ||||
|             if (!JSOCKVALID(sfd)) continue; | ||||
|             const char *err = serverify_socket(sfd); | ||||
|             const char *err = serverify_socket(sfd, reuse, reuse); | ||||
|             if (NULL != err) { | ||||
|                 JSOCKCLOSE(sfd); | ||||
|                 continue; | ||||
| @@ -649,7 +741,7 @@ struct sockaddr_in6 *sin6;     // IPv6 address + port | ||||
| struct sockaddr_un *sun;       // Unix Domain Socket Address | ||||
| */ | ||||
|  | ||||
| /* Turn a socket address into a host, port pair (port is optional). | ||||
| /* Turn a socket address into a host, port pair. | ||||
|  * For unix domain sockets, returned tuple will have only a single element, the path string. */ | ||||
| static Janet janet_so_getname(const void *sa_any) { | ||||
|     const struct sockaddr *sa = sa_any; | ||||
| @@ -663,16 +755,18 @@ static Janet janet_so_getname(const void *sa_any) { | ||||
|                 janet_panic("unable to decode ipv4 host address"); | ||||
|             } | ||||
|             Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai->sin_port))}; | ||||
|             return janet_wrap_tuple(janet_tuple_n(pair, sai->sin_port ? 2 : 1)); | ||||
|             return janet_wrap_tuple(janet_tuple_n(pair, 2)); | ||||
|         } | ||||
| #ifndef JANET_NO_IPV6 | ||||
|         case AF_INET6: { | ||||
|             const struct sockaddr_in6 *sai6 = sa_any; | ||||
|             if (!inet_ntop(AF_INET6, &(sai6->sin6_addr), buffer, sizeof(buffer))) { | ||||
|                 janet_panic("unable to decode ipv4 host address"); | ||||
|             } | ||||
|             Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai6->sin6_port))}; | ||||
|             return janet_wrap_tuple(janet_tuple_n(pair, sai6->sin6_port ? 2 : 1)); | ||||
|             return janet_wrap_tuple(janet_tuple_n(pair, 2)); | ||||
|         } | ||||
| #endif | ||||
| #ifndef JANET_WINDOWS | ||||
|         case AF_UNIX: { | ||||
|             const struct sockaddr_un *sun = sa_any; | ||||
| @@ -695,13 +789,14 @@ JANET_CORE_FN(cfun_net_getsockname, | ||||
|               "Gets the local address and port in a tuple in that order.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetStream *js = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     if (js->flags & JANET_STREAM_CLOSED) janet_panic("stream closed"); | ||||
|     struct sockaddr_storage ss; | ||||
|     socklen_t slen = sizeof(ss); | ||||
|     memset(&ss, 0, slen); | ||||
|     if (getsockname((JSock)js->handle, (struct sockaddr *) &ss, &slen)) { | ||||
|         janet_panicf("Failed to get localname on %v: %V", argv[0], janet_ev_lasterr()); | ||||
|     } | ||||
|     janet_assert(slen <= sizeof(ss), "socket address truncated"); | ||||
|     janet_assert(slen <= (socklen_t) sizeof(ss), "socket address truncated"); | ||||
|     return janet_so_getname(&ss); | ||||
| } | ||||
|  | ||||
| @@ -710,19 +805,20 @@ JANET_CORE_FN(cfun_net_getpeername, | ||||
|               "Gets the remote peer's address and port in a tuple in that order.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetStream *js = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     if (js->flags & JANET_STREAM_CLOSED) janet_panic("stream closed"); | ||||
|     struct sockaddr_storage ss; | ||||
|     socklen_t slen = sizeof(ss); | ||||
|     memset(&ss, 0, slen); | ||||
|     if (getpeername((JSock)js->handle, (struct sockaddr *)&ss, &slen)) { | ||||
|         janet_panicf("Failed to get peername on %v: %V", argv[0], janet_ev_lasterr()); | ||||
|     } | ||||
|     janet_assert(slen <= sizeof(ss), "socket address truncated"); | ||||
|     janet_assert(slen <= (socklen_t) sizeof(ss), "socket address truncated"); | ||||
|     return janet_so_getname(&ss); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_net_address_unpack, | ||||
|               "(net/address-unpack address)", | ||||
|               "Given an address returned by net/adress, return a host, port pair. Unix domain sockets " | ||||
|               "Given an address returned by net/address, return a host, port pair. Unix domain sockets " | ||||
|               "will have only the path in the returned tuple.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     struct sockaddr *sa = janet_getabstract(argv, 0, &janet_address_type); | ||||
| @@ -737,13 +833,14 @@ JANET_CORE_FN(cfun_stream_accept_loop, | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     janet_stream_flags(stream, JANET_STREAM_ACCEPTABLE | JANET_STREAM_SOCKET); | ||||
|     JanetFunction *fun = janet_getfunction(argv, 1); | ||||
|     if (fun->def->min_arity < 1) janet_panic("handler function must take at least 1 argument"); | ||||
|     janet_sched_accept(stream, fun); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_accept, | ||||
|               "(net/accept stream &opt timeout)", | ||||
|               "Get the next connection on a server stream. This would usually be called in a loop in a dedicated fiber. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns a new duplex stream which represents a connection to the client.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
| @@ -758,7 +855,7 @@ JANET_CORE_FN(cfun_stream_read, | ||||
|               "Read up to n bytes from a stream, suspending the current fiber until the bytes are available. " | ||||
|               "`n` can also be the keyword `:all` to read into the buffer until end of stream. " | ||||
|               "If less than n bytes are available (and more than 0), will push those bytes and return early. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns a buffer with up to n more bytes in it, or raises an error if the read failed.") { | ||||
|     janet_arity(argc, 2, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
| @@ -773,13 +870,12 @@ JANET_CORE_FN(cfun_stream_read, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_recv(stream, buffer, n, MSG_NOSIGNAL); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_chunk, | ||||
|               "(net/chunk stream nbytes &opt buf timeout)", | ||||
|               "Same a net/read, but will wait for all n bytes to arrive rather than return early. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil.") { | ||||
|               "Takes an optional timeout in seconds, after which will raise an error.") { | ||||
|     janet_arity(argc, 2, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     janet_stream_flags(stream, JANET_STREAM_READABLE | JANET_STREAM_SOCKET); | ||||
| @@ -788,13 +884,12 @@ JANET_CORE_FN(cfun_stream_chunk, | ||||
|     double to = janet_optnumber(argv, argc, 3, INFINITY); | ||||
|     if (to != INFINITY) janet_addtimeout(to); | ||||
|     janet_ev_recvchunk(stream, buffer, n, MSG_NOSIGNAL); | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_recv_from, | ||||
|               "(net/recv-from stream nbytes buf &opt timoeut)", | ||||
|               "(net/recv-from stream nbytes buf &opt timeout)", | ||||
|               "Receives data from a server stream and puts it into a buffer. Returns the socket-address the " | ||||
|               "packet came from. Takes an optional timeout in seconds, after which will return nil.") { | ||||
|               "packet came from. Takes an optional timeout in seconds, after which will raise an error.") { | ||||
|     janet_arity(argc, 3, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     janet_stream_flags(stream, JANET_STREAM_UDPSERVER | JANET_STREAM_SOCKET); | ||||
| @@ -803,13 +898,12 @@ JANET_CORE_FN(cfun_stream_recv_from, | ||||
|     double to = janet_optnumber(argv, argc, 3, INFINITY); | ||||
|     if (to != INFINITY) janet_addtimeout(to); | ||||
|     janet_ev_recvfrom(stream, buffer, n, MSG_NOSIGNAL); | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_write, | ||||
|               "(net/write stream data &opt timeout)", | ||||
|               "Write data to a stream, suspending the current fiber until the write " | ||||
|               "completes. Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "completes. Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns nil, or raises an error if the write failed.") { | ||||
|     janet_arity(argc, 2, 3); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
| @@ -823,13 +917,12 @@ JANET_CORE_FN(cfun_stream_write, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_send_string(stream, bytes.bytes, MSG_NOSIGNAL); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_send_to, | ||||
|               "(net/send-to stream dest data &opt timeout)", | ||||
|               "Writes a datagram to a server stream. dest is a the destination address of the packet. " | ||||
|               "Takes an optional timeout in seconds, after which will return nil. " | ||||
|               "Takes an optional timeout in seconds, after which will raise an error. " | ||||
|               "Returns stream.") { | ||||
|     janet_arity(argc, 3, 4); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
| @@ -844,7 +937,6 @@ JANET_CORE_FN(cfun_stream_send_to, | ||||
|         if (to != INFINITY) janet_addtimeout(to); | ||||
|         janet_ev_sendto_string(stream, bytes.bytes, dest, MSG_NOSIGNAL); | ||||
|     } | ||||
|     janet_await(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_stream_flush, | ||||
| @@ -862,6 +954,104 @@ JANET_CORE_FN(cfun_stream_flush, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| struct sockopt_type { | ||||
|     const char *name; | ||||
|     int level; | ||||
|     int optname; | ||||
|     enum JanetType type; | ||||
| }; | ||||
|  | ||||
| /* List of supported socket options; The type JANET_POINTER is used | ||||
|  * for options that require special handling depending on the type. */ | ||||
| static const struct sockopt_type sockopt_type_list[] = { | ||||
|     { "so-broadcast", SOL_SOCKET, SO_BROADCAST, JANET_BOOLEAN }, | ||||
|     { "so-reuseaddr", SOL_SOCKET, SO_REUSEADDR, JANET_BOOLEAN }, | ||||
|     { "so-keepalive", SOL_SOCKET, SO_KEEPALIVE, JANET_BOOLEAN }, | ||||
|     { "ip-multicast-ttl", IPPROTO_IP, IP_MULTICAST_TTL, JANET_NUMBER }, | ||||
|     { "ip-add-membership", IPPROTO_IP, IP_ADD_MEMBERSHIP, JANET_POINTER }, | ||||
|     { "ip-drop-membership", IPPROTO_IP, IP_DROP_MEMBERSHIP, JANET_POINTER }, | ||||
| #ifndef JANET_NO_IPV6 | ||||
|     { "ipv6-join-group", IPPROTO_IPV6, IPV6_JOIN_GROUP, JANET_POINTER }, | ||||
|     { "ipv6-leave-group", IPPROTO_IPV6, IPV6_LEAVE_GROUP, JANET_POINTER }, | ||||
| #endif | ||||
|     { NULL, 0, 0, JANET_POINTER } | ||||
| }; | ||||
|  | ||||
| JANET_CORE_FN(cfun_net_setsockopt, | ||||
|               "(net/setsockopt stream option value)", | ||||
|               "set socket options.\n" | ||||
|               "\n" | ||||
|               "supported options and associated value types:\n" | ||||
|               "- :so-broadcast boolean\n" | ||||
|               "- :so-reuseaddr boolean\n" | ||||
|               "- :so-keepalive boolean\n" | ||||
|               "- :ip-multicast-ttl number\n" | ||||
|               "- :ip-add-membership string\n" | ||||
|               "- :ip-drop-membership string\n" | ||||
|               "- :ipv6-join-group string\n" | ||||
|               "- :ipv6-leave-group string\n") { | ||||
|     janet_arity(argc, 3, 3); | ||||
|     JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); | ||||
|     janet_stream_flags(stream, JANET_STREAM_SOCKET); | ||||
|     JanetKeyword optstr = janet_getkeyword(argv, 1); | ||||
|  | ||||
|     const struct sockopt_type *st = sockopt_type_list; | ||||
|     while (st->name) { | ||||
|         if (janet_cstrcmp(optstr, st->name) == 0) { | ||||
|             break; | ||||
|         } | ||||
|         st++; | ||||
|     } | ||||
|  | ||||
|     if (st->name == NULL) { | ||||
|         janet_panicf("unknown socket option %q", argv[1]); | ||||
|     } | ||||
|  | ||||
|     union { | ||||
|         int v_int; | ||||
|         struct ip_mreq v_mreq; | ||||
| #ifndef JANET_NO_IPV6 | ||||
|         struct ipv6_mreq v_mreq6; | ||||
| #endif | ||||
|     } val; | ||||
|  | ||||
|     void *optval = (void *)&val; | ||||
|     socklen_t optlen = 0; | ||||
|  | ||||
|     if (st->type == JANET_BOOLEAN) { | ||||
|         val.v_int = janet_getboolean(argv, 2); | ||||
|         optlen = sizeof(val.v_int); | ||||
|     } else if (st->type == JANET_NUMBER) { | ||||
|         val.v_int = janet_getinteger(argv, 2); | ||||
|         optlen = sizeof(val.v_int); | ||||
|     } else if (st->optname == IP_ADD_MEMBERSHIP || st->optname == IP_DROP_MEMBERSHIP) { | ||||
|         const char *addr = janet_getcstring(argv, 2); | ||||
|         memset(&val.v_mreq, 0, sizeof val.v_mreq); | ||||
|         val.v_mreq.imr_interface.s_addr = htonl(INADDR_ANY); | ||||
|         inet_pton(AF_INET, addr, &val.v_mreq.imr_multiaddr.s_addr); | ||||
|         optlen = sizeof(val.v_mreq); | ||||
| #ifndef JANET_NO_IPV6 | ||||
|     } else if (st->optname == IPV6_JOIN_GROUP || st->optname == IPV6_LEAVE_GROUP) { | ||||
|         const char *addr = janet_getcstring(argv, 2); | ||||
|         memset(&val.v_mreq6, 0, sizeof val.v_mreq6); | ||||
|         val.v_mreq6.ipv6mr_interface = 0; | ||||
|         inet_pton(AF_INET6, addr, &val.v_mreq6.ipv6mr_multiaddr); | ||||
|         optlen = sizeof(val.v_mreq6); | ||||
| #endif | ||||
|     } else { | ||||
|         janet_panicf("invalid socket option type"); | ||||
|     } | ||||
|  | ||||
|     janet_assert(optlen != 0, "invalid socket option value"); | ||||
|  | ||||
|     int r = setsockopt((JSock) stream->handle, st->level, st->optname, optval, optlen); | ||||
|     if (r == -1) { | ||||
|         janet_panicf("setsockopt(%q): %s", argv[1], janet_strerror(errno)); | ||||
|     } | ||||
|  | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| static const JanetMethod net_stream_methods[] = { | ||||
|     {"chunk", cfun_stream_chunk}, | ||||
|     {"close", janet_cfun_stream_close}, | ||||
| @@ -876,6 +1066,7 @@ static const JanetMethod net_stream_methods[] = { | ||||
|     {"evchunk", janet_cfun_stream_chunk}, | ||||
|     {"evwrite", janet_cfun_stream_write}, | ||||
|     {"shutdown", cfun_net_shutdown}, | ||||
|     {"setsockopt", cfun_net_setsockopt}, | ||||
|     {NULL, NULL} | ||||
| }; | ||||
|  | ||||
| @@ -883,7 +1074,6 @@ static JanetStream *make_stream(JSock handle, uint32_t flags) { | ||||
|     return janet_stream((JanetHandle) handle, flags | JANET_STREAM_SOCKET, net_stream_methods); | ||||
| } | ||||
|  | ||||
|  | ||||
| void janet_lib_net(JanetTable *env) { | ||||
|     JanetRegExt net_cfuns[] = { | ||||
|         JANET_CORE_REG("net/address", cfun_net_sockaddr), | ||||
| @@ -901,6 +1091,7 @@ void janet_lib_net(JanetTable *env) { | ||||
|         JANET_CORE_REG("net/peername", cfun_net_getpeername), | ||||
|         JANET_CORE_REG("net/localname", cfun_net_getsockname), | ||||
|         JANET_CORE_REG("net/address-unpack", cfun_net_address_unpack), | ||||
|         JANET_CORE_REG("net/setsockopt", cfun_net_setsockopt), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, net_cfuns); | ||||
|   | ||||
							
								
								
									
										1002
									
								
								src/core/os.c
									
									
									
									
									
								
							
							
						
						
									
										1002
									
								
								src/core/os.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										148
									
								
								src/core/parse.c
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								src/core/parse.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -51,15 +51,15 @@ static const uint32_t symchars[8] = { | ||||
| }; | ||||
|  | ||||
| /* Check if a character is a valid symbol character | ||||
|  * symbol chars are A-Z, a-z, 0-9, or one of !$&*+-./:<=>@\^_~| */ | ||||
| static int is_symbol_char(uint8_t c) { | ||||
|  * symbol chars are A-Z, a-z, 0-9, or one of !$&*+-./:<=>@\^_| */ | ||||
| int janet_is_symbol_char(uint8_t c) { | ||||
|     return symchars[c >> 5] & ((uint32_t)1 << (c & 0x1F)); | ||||
| } | ||||
|  | ||||
| /* Validate some utf8. Useful for identifiers. Only validates | ||||
|  * the encoding, does not check for valid code points (they | ||||
|  * are less well defined than the encoding). */ | ||||
| static int valid_utf8(const uint8_t *str, int32_t len) { | ||||
| int janet_valid_utf8(const uint8_t *str, int32_t len) { | ||||
|     int32_t i = 0; | ||||
|     int32_t j; | ||||
|     while (i < len) { | ||||
| @@ -206,6 +206,37 @@ static void popstate(JanetParser *p, Janet val) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void delim_error(JanetParser *parser, size_t stack_index, char c, const char *msg) { | ||||
|     JanetParseState *s = parser->states + stack_index; | ||||
|     JanetBuffer *buffer = janet_buffer(40); | ||||
|     if (msg) { | ||||
|         janet_buffer_push_cstring(buffer, msg); | ||||
|     } | ||||
|     if (c) { | ||||
|         janet_buffer_push_u8(buffer, c); | ||||
|     } | ||||
|     if (stack_index > 0) { | ||||
|         janet_buffer_push_cstring(buffer, ", "); | ||||
|         if (s->flags & PFLAG_PARENS) { | ||||
|             janet_buffer_push_u8(buffer, '('); | ||||
|         } else if (s->flags & PFLAG_SQRBRACKETS) { | ||||
|             janet_buffer_push_u8(buffer, '['); | ||||
|         } else if (s->flags & PFLAG_CURLYBRACKETS) { | ||||
|             janet_buffer_push_u8(buffer, '{'); | ||||
|         } else if (s->flags & PFLAG_STRING) { | ||||
|             janet_buffer_push_u8(buffer, '"'); | ||||
|         } else if (s->flags & PFLAG_LONGSTRING) { | ||||
|             int32_t i; | ||||
|             for (i = 0; i < s->argn; i++) { | ||||
|                 janet_buffer_push_u8(buffer, '`'); | ||||
|             } | ||||
|         } | ||||
|         janet_formatb(buffer, " opened at line %d, column %d", (int32_t) s->line, (int32_t) s->column); | ||||
|     } | ||||
|     parser->error = (const char *) janet_string(buffer->data, buffer->count); | ||||
|     parser->flag |= JANET_PARSER_GENERATED_ERROR; | ||||
| } | ||||
|  | ||||
| static int checkescape(uint8_t c) { | ||||
|     switch (c) { | ||||
|         default: | ||||
| @@ -228,6 +259,14 @@ static int checkescape(uint8_t c) { | ||||
|             return '\f'; | ||||
|         case 'v': | ||||
|             return '\v'; | ||||
|         case 'a': | ||||
|             return '\a'; | ||||
|         case 'b': | ||||
|             return '\b'; | ||||
|         case '\'': | ||||
|             return '\''; | ||||
|         case '?': | ||||
|             return '?'; | ||||
|         case 'e': | ||||
|             return 27; | ||||
|         case '"': | ||||
| @@ -324,8 +363,7 @@ static int stringend(JanetParser *p, JanetParseState *state) { | ||||
|         JanetParseState top = p->states[p->statecount - 1]; | ||||
|         int32_t indent_col = (int32_t) top.column - 1; | ||||
|         uint8_t *r = bufstart, *end = r + buflen; | ||||
|         /* Check if there are any characters before the start column - | ||||
|          * if so, do not reindent. */ | ||||
|         /* Unless there are only spaces before EOLs, disable reindenting */ | ||||
|         int reindent = 1; | ||||
|         while (reindent && (r < end)) { | ||||
|             if (*r++ == '\n') { | ||||
| @@ -335,34 +373,36 @@ static int stringend(JanetParser *p, JanetParseState *state) { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') reindent = 1; | ||||
|             } | ||||
|         } | ||||
|         /* Now reindent if able to, otherwise just drop leading newline. */ | ||||
|         if (!reindent) { | ||||
|             if (buflen > 0 && bufstart[0] == '\n') { | ||||
|                 buflen--; | ||||
|                 bufstart++; | ||||
|             } | ||||
|         } else { | ||||
|         /* Now reindent if able */ | ||||
|         if (reindent) { | ||||
|             uint8_t *w = bufstart; | ||||
|             r = bufstart; | ||||
|             while (r < end) { | ||||
|                 if (*r == '\n') { | ||||
|                     if (r == bufstart) { | ||||
|                         /* Skip leading newline */ | ||||
|                         r++; | ||||
|                     } else { | ||||
|                         *w++ = *r++; | ||||
|                     } | ||||
|                     *w++ = *r++; | ||||
|                     for (int32_t j = 0; (r < end) && (*r != '\n') && (j < indent_col); j++, r++); | ||||
|                     if ((r + 1) < end && *r == '\r' && *(r + 1) == '\n') *w++ = *r++; | ||||
|                 } else { | ||||
|                     *w++ = *r++; | ||||
|                 } | ||||
|             } | ||||
|             buflen = (int32_t)(w - bufstart); | ||||
|         } | ||||
|         /* Check for trailing newline character so we can remove it */ | ||||
|         if (buflen > 0 && bufstart[buflen - 1] == '\n') { | ||||
|         /* Check for leading EOL so we can remove it */ | ||||
|         if (buflen > 1 && bufstart[0] == '\r' && bufstart[1] == '\n') { /* Windows EOL */ | ||||
|             buflen = buflen - 2; | ||||
|             bufstart = bufstart + 2; | ||||
|         } else if (buflen > 0 && bufstart[0] == '\n') { /* Unix EOL */ | ||||
|             buflen--; | ||||
|             bufstart++; | ||||
|         } | ||||
|         /* Check for trailing EOL so we can remove it */ | ||||
|         if (buflen > 1 && bufstart[buflen - 2] == '\r' && bufstart[buflen - 1] == '\n') { /* Windows EOL */ | ||||
|             buflen = buflen - 2; | ||||
|         } else if (buflen > 0 && bufstart[buflen - 1] == '\n') { /* Unix EOL */ | ||||
|             buflen--; | ||||
|         } | ||||
|     } | ||||
| @@ -411,7 +451,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|     Janet ret; | ||||
|     double numval; | ||||
|     int32_t blen; | ||||
|     if (is_symbol_char(c)) { | ||||
|     if (janet_is_symbol_char(c)) { | ||||
|         push_buf(p, (uint8_t) c); | ||||
|         if (c > 127) state->argn = 1; /* Use to indicate non ascii */ | ||||
|         return 1; | ||||
| @@ -422,14 +462,19 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|     int start_num = start_dig || p->buf[0] == '-' || p->buf[0] == '+' || p->buf[0] == '.'; | ||||
|     if (p->buf[0] == ':') { | ||||
|         /* Don't do full utf-8 check unless we have seen non ascii characters. */ | ||||
|         int valid = (!state->argn) || valid_utf8(p->buf + 1, blen - 1); | ||||
|         int valid = (!state->argn) || janet_valid_utf8(p->buf + 1, blen - 1); | ||||
|         if (!valid) { | ||||
|             p->error = "invalid utf-8 in keyword"; | ||||
|             return 0; | ||||
|         } | ||||
|         ret = janet_keywordv(p->buf + 1, blen - 1); | ||||
| #ifdef JANET_INT_TYPES | ||||
|     } else if (start_num && !janet_scan_numeric(p->buf, blen, &ret)) { | ||||
|         (void) numval; | ||||
| #else | ||||
|     } else if (start_num && !janet_scan_number(p->buf, blen, &numval)) { | ||||
|         ret = janet_wrap_number(numval); | ||||
| #endif | ||||
|     } else if (!check_str_const("nil", p->buf, blen)) { | ||||
|         ret = janet_wrap_nil(); | ||||
|     } else if (!check_str_const("false", p->buf, blen)) { | ||||
| @@ -442,7 +487,7 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|             return 0; | ||||
|         } else { | ||||
|             /* Don't do full utf-8 check unless we have seen non ascii characters. */ | ||||
|             int valid = (!state->argn) || valid_utf8(p->buf, blen); | ||||
|             int valid = (!state->argn) || janet_valid_utf8(p->buf, blen); | ||||
|             if (!valid) { | ||||
|                 p->error = "invalid utf-8 in symbol"; | ||||
|                 return 0; | ||||
| @@ -582,7 +627,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|     switch (c) { | ||||
|         default: | ||||
|             if (is_whitespace(c)) return 1; | ||||
|             if (!is_symbol_char(c)) { | ||||
|             if (!janet_is_symbol_char(c)) { | ||||
|                 p->error = "unexpected character"; | ||||
|                 return 1; | ||||
|             } | ||||
| @@ -612,7 +657,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|         case '}': { | ||||
|             Janet ds; | ||||
|             if (p->statecount == 1) { | ||||
|                 p->error = "unexpected delimiter"; | ||||
|                 delim_error(p, 0, c, "unexpected closing delimiter "); | ||||
|                 return 1; | ||||
|             } | ||||
|             if ((c == ')' && (state->flags & PFLAG_PARENS)) || | ||||
| @@ -633,7 +678,7 @@ static int root(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|                     ds = close_struct(p, state); | ||||
|                 } | ||||
|             } else { | ||||
|                 p->error = "mismatched delimiter"; | ||||
|                 delim_error(p, p->statecount - 1, c, "mismatched delimiter "); | ||||
|                 return 1; | ||||
|             } | ||||
|             popstate(p, ds); | ||||
| @@ -684,26 +729,7 @@ void janet_parser_eof(JanetParser *parser) { | ||||
|     size_t oldline = parser->line; | ||||
|     janet_parser_consume(parser, '\n'); | ||||
|     if (parser->statecount > 1) { | ||||
|         JanetParseState *s = parser->states + (parser->statecount - 1); | ||||
|         JanetBuffer *buffer = janet_buffer(40); | ||||
|         janet_buffer_push_cstring(buffer, "unexpected end of source, "); | ||||
|         if (s->flags & PFLAG_PARENS) { | ||||
|             janet_buffer_push_u8(buffer, '('); | ||||
|         } else if (s->flags & PFLAG_SQRBRACKETS) { | ||||
|             janet_buffer_push_u8(buffer, '['); | ||||
|         } else if (s->flags & PFLAG_CURLYBRACKETS) { | ||||
|             janet_buffer_push_u8(buffer, '{'); | ||||
|         } else if (s->flags & PFLAG_STRING) { | ||||
|             janet_buffer_push_u8(buffer, '"'); | ||||
|         } else if (s->flags & PFLAG_LONGSTRING) { | ||||
|             int32_t i; | ||||
|             for (i = 0; i < s->argn; i++) { | ||||
|                 janet_buffer_push_u8(buffer, '`'); | ||||
|             } | ||||
|         } | ||||
|         janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column); | ||||
|         parser->error = (const char *) janet_string(buffer->data, buffer->count); | ||||
|         parser->flag |= JANET_PARSER_GENERATED_ERROR; | ||||
|         delim_error(parser, parser->statecount - 1, 0, "unexpected end of source"); | ||||
|     } | ||||
|     parser->line = oldline; | ||||
|     parser->column = oldcolumn; | ||||
| @@ -746,6 +772,7 @@ Janet janet_parser_produce(JanetParser *parser) { | ||||
|     } | ||||
|     parser->pending--; | ||||
|     parser->argcount--; | ||||
|     parser->states[0].argn--; | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| @@ -759,6 +786,7 @@ Janet janet_parser_produce_wrapped(JanetParser *parser) { | ||||
|     } | ||||
|     parser->pending--; | ||||
|     parser->argcount--; | ||||
|     parser->states[0].argn--; | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| @@ -881,7 +909,7 @@ const JanetAbstractType janet_parser_type = { | ||||
| JANET_CORE_FN(cfun_parse_parser, | ||||
|               "(parser/new)", | ||||
|               "Creates and returns a new parser object. Parsers are state machines " | ||||
|               "that can receive bytes, and generate a stream of values.") { | ||||
|               "that can receive bytes and generate a stream of values.") { | ||||
|     (void) argv; | ||||
|     janet_fixarity(argc, 0); | ||||
|     JanetParser *p = janet_abstract(&janet_parser_type, sizeof(JanetParser)); | ||||
| @@ -892,7 +920,7 @@ JANET_CORE_FN(cfun_parse_parser, | ||||
| JANET_CORE_FN(cfun_parse_consume, | ||||
|               "(parser/consume parser bytes &opt index)", | ||||
|               "Input bytes into the parser and parse them. Will not throw errors " | ||||
|               "if there is a parse error. Starts at the byte index given by index. Returns " | ||||
|               "if there is a parse error. Starts at the byte index given by `index`. Returns " | ||||
|               "the number of bytes read.") { | ||||
|     janet_arity(argc, 2, 3); | ||||
|     JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type); | ||||
| @@ -920,7 +948,7 @@ JANET_CORE_FN(cfun_parse_consume, | ||||
|  | ||||
| JANET_CORE_FN(cfun_parse_eof, | ||||
|               "(parser/eof parser)", | ||||
|               "Indicate that the end of file was reached to the parser. This puts the parser in the :dead state.") { | ||||
|               "Indicate to the parser that the end of file was reached. This puts the parser in the :dead state.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type); | ||||
|     janet_parser_eof(p); | ||||
| @@ -980,7 +1008,7 @@ JANET_CORE_FN(cfun_parse_has_more, | ||||
|  | ||||
| JANET_CORE_FN(cfun_parse_byte, | ||||
|               "(parser/byte parser b)", | ||||
|               "Input a single byte into the parser byte stream. Returns the parser.") { | ||||
|               "Input a single byte `b` into the parser byte stream. Returns the parser.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type); | ||||
|     int32_t i = janet_getinteger(argv, 1); | ||||
| @@ -1020,7 +1048,7 @@ JANET_CORE_FN(cfun_parse_error, | ||||
|               "If the parser is in the error state, returns the message associated with " | ||||
|               "that error. Otherwise, returns nil. Also flushes the parser state and parser " | ||||
|               "queue, so be sure to handle everything in the queue before calling " | ||||
|               "parser/error.") { | ||||
|               "`parser/error`.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetParser *p = janet_getabstract(argv, 0, &janet_parser_type); | ||||
|     const char *err = janet_parser_error(p); | ||||
| @@ -1093,8 +1121,9 @@ static Janet janet_wrap_parse_state(JanetParseState *s, Janet *args, | ||||
|  | ||||
|     if (s->flags & PFLAG_CONTAINER) { | ||||
|         JanetArray *container_args = janet_array(s->argn); | ||||
|         container_args->count = s->argn; | ||||
|         safe_memcpy(container_args->data, args, sizeof(args[0])*s->argn); | ||||
|         for (int32_t i = 0; i < s->argn; i++) { | ||||
|             janet_array_push(container_args, args[i]); | ||||
|         } | ||||
|         janet_table_put(state, janet_ckeywordv("args"), | ||||
|                         janet_wrap_array(container_args)); | ||||
|     } | ||||
| @@ -1179,7 +1208,8 @@ static Janet parser_state_delimiters(const JanetParser *_p) { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     str = janet_string(p->buf + oldcount, (int32_t)(p->bufcount - oldcount)); | ||||
|     /* avoid ptr arithmetic on NULL */ | ||||
|     str = janet_string(oldcount ? p->buf + oldcount : p->buf, (int32_t)(p->bufcount - oldcount)); | ||||
|     p->bufcount = oldcount; | ||||
|     return janet_wrap_string(str); | ||||
| } | ||||
| @@ -1189,11 +1219,15 @@ static Janet parser_state_frames(const JanetParser *p) { | ||||
|     JanetArray *states = janet_array(count); | ||||
|     states->count = count; | ||||
|     uint8_t *buf = p->buf; | ||||
|     Janet *args = p->args; | ||||
|     /* Iterate arg stack backwards */ | ||||
|     Janet *args = p->argcount ? p->args + p->argcount : p->args; /* avoid ptr arithmetic on NULL */ | ||||
|     for (int32_t i = count - 1; i >= 0; --i) { | ||||
|         JanetParseState *s = p->states + i; | ||||
|         /* avoid ptr arithmetic on args if NULL */ | ||||
|         if ((s->flags & PFLAG_CONTAINER) && s->argn) { | ||||
|             args -= s->argn; | ||||
|         } | ||||
|         states->data[i] = janet_wrap_parse_state(s, args, buf, (uint32_t) p->bufcount); | ||||
|         args -= s->argn; | ||||
|     } | ||||
|     return janet_wrap_array(states); | ||||
| } | ||||
|   | ||||
							
								
								
									
										311
									
								
								src/core/peg.c
									
									
									
									
									
								
							
							
						
						
									
										311
									
								
								src/core/peg.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -39,6 +39,10 @@ | ||||
| typedef struct { | ||||
|     const uint8_t *text_start; | ||||
|     const uint8_t *text_end; | ||||
|     /* text_end can be restricted by some rules, but | ||||
|        outer_text_end will always contain the real end of | ||||
|        input, which we need to generate a line mapping */ | ||||
|     const uint8_t *outer_text_end; | ||||
|     const uint32_t *bytecode; | ||||
|     const Janet *constants; | ||||
|     JanetArray *captures; | ||||
| @@ -114,12 +118,12 @@ static LineCol get_linecol_from_position(PegState *s, int32_t position) { | ||||
|     /* Generate if not made yet */ | ||||
|     if (s->linemaplen < 0) { | ||||
|         int32_t newline_count = 0; | ||||
|         for (const uint8_t *c = s->text_start; c < s->text_end; c++) { | ||||
|         for (const uint8_t *c = s->text_start; c < s->outer_text_end; c++) { | ||||
|             if (*c == '\n') newline_count++; | ||||
|         } | ||||
|         int32_t *mem = janet_smalloc(sizeof(int32_t) * newline_count); | ||||
|         size_t index = 0; | ||||
|         for (const uint8_t *c = s->text_start; c < s->text_end; c++) { | ||||
|         for (const uint8_t *c = s->text_start; c < s->outer_text_end; c++) { | ||||
|             if (*c == '\n') mem[index++] = (int32_t)(c - s->text_start); | ||||
|         } | ||||
|         s->linemaplen = newline_count; | ||||
| @@ -130,7 +134,7 @@ static LineCol get_linecol_from_position(PegState *s, int32_t position) { | ||||
|      *   a newline character is consider to be on the same line as the character before | ||||
|      *   (\n is line terminator, not line separator). | ||||
|      * - in the not-found case, we still want to find the greatest-indexed newline that | ||||
|      *   is before position. we use that to calcuate the line and column. | ||||
|      *   is before position. we use that to calculate the line and column. | ||||
|      * - in the case that lo = 0 and s->linemap[0] is still greater than position, we | ||||
|      *   are on the first line and our column is position + 1. */ | ||||
|     int32_t hi = s->linemaplen; /* hi is greater than the actual line */ | ||||
| @@ -179,7 +183,7 @@ static const uint8_t *peg_rule( | ||||
|     const uint32_t *rule, | ||||
|     const uint8_t *text) { | ||||
| tail: | ||||
|     switch (*rule & 0x1F) { | ||||
|     switch (*rule) { | ||||
|         default: | ||||
|             janet_panic("unexpected opcode"); | ||||
|             return NULL; | ||||
| @@ -211,9 +215,10 @@ tail: | ||||
|         } | ||||
|  | ||||
|         case RULE_SET: { | ||||
|             if (text >= s->text_end) return NULL; | ||||
|             uint32_t word = rule[1 + (text[0] >> 5)]; | ||||
|             uint32_t mask = (uint32_t)1 << (text[0] & 0x1F); | ||||
|             return (text < s->text_end && (word & mask)) | ||||
|             return (word & mask) | ||||
|                    ? text + 1 | ||||
|                    : NULL; | ||||
|         } | ||||
| @@ -260,30 +265,52 @@ tail: | ||||
|             goto tail; | ||||
|         } | ||||
|  | ||||
|         case RULE_IF: | ||||
|         case RULE_IFNOT: { | ||||
|         case RULE_IF: { | ||||
|             const uint32_t *rule_a = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_b = s->bytecode + rule[2]; | ||||
|             down1(s); | ||||
|             const uint8_t *result = peg_rule(s, rule_a, text); | ||||
|             up1(s); | ||||
|             if (rule[0] == RULE_IF ? !result : !!result) return NULL; | ||||
|             if (!result) return NULL; | ||||
|             rule = rule_b; | ||||
|             goto tail; | ||||
|         } | ||||
|         case RULE_IFNOT: { | ||||
|             const uint32_t *rule_a = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_b = s->bytecode + rule[2]; | ||||
|             down1(s); | ||||
|             CapState cs = cap_save(s); | ||||
|             const uint8_t *result = peg_rule(s, rule_a, text); | ||||
|             if (!!result) { | ||||
|                 up1(s); | ||||
|                 return NULL; | ||||
|             } else { | ||||
|                 cap_load(s, cs); | ||||
|                 up1(s); | ||||
|                 rule = rule_b; | ||||
|                 goto tail; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         case RULE_NOT: { | ||||
|             const uint32_t *rule_a = s->bytecode + rule[1]; | ||||
|             down1(s); | ||||
|             CapState cs = cap_save(s); | ||||
|             const uint8_t *result = peg_rule(s, rule_a, text); | ||||
|             up1(s); | ||||
|             return (result) ? NULL : text; | ||||
|             if (result) { | ||||
|                 up1(s); | ||||
|                 return NULL; | ||||
|             } else { | ||||
|                 cap_load(s, cs); | ||||
|                 up1(s); | ||||
|                 return text; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         case RULE_THRU: | ||||
|         case RULE_TO: { | ||||
|             const uint32_t *rule_a = s->bytecode + rule[1]; | ||||
|             const uint8_t *next_text; | ||||
|             const uint8_t *next_text = NULL; | ||||
|             CapState cs = cap_save(s); | ||||
|             down1(s); | ||||
|             while (text <= s->text_end) { | ||||
| @@ -293,6 +320,7 @@ tail: | ||||
|                     if (rule[0] == RULE_TO) cap_load(s, cs2); | ||||
|                     break; | ||||
|                 } | ||||
|                 cap_load(s, cs2); | ||||
|                 text++; | ||||
|             } | ||||
|             up1(s); | ||||
| @@ -314,7 +342,7 @@ tail: | ||||
|             while (captured < hi) { | ||||
|                 CapState cs2 = cap_save(s); | ||||
|                 next_text = peg_rule(s, rule_a, text); | ||||
|                 if (!next_text || next_text == text) { | ||||
|                 if (!next_text || ((next_text == text) && (hi == UINT32_MAX))) { | ||||
|                     cap_load(s, cs2); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -437,6 +465,16 @@ tail: | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_ONLY_TAGS: { | ||||
|             CapState cs = cap_save(s); | ||||
|             down1(s); | ||||
|             const uint8_t *result = peg_rule(s, s->bytecode + rule[1], text); | ||||
|             up1(s); | ||||
|             if (!result) return NULL; | ||||
|             cap_load_keept(s, cs); | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_GROUP: { | ||||
|             uint32_t tag = rule[2]; | ||||
|             int oldmode = s->mode; | ||||
| @@ -458,6 +496,131 @@ tail: | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_NTH: { | ||||
|             uint32_t nth = rule[1]; | ||||
|             if (nth > INT32_MAX) nth = INT32_MAX; | ||||
|             uint32_t tag = rule[3]; | ||||
|             int oldmode = s->mode; | ||||
|             CapState cs = cap_save(s); | ||||
|             s->mode = PEG_MODE_NORMAL; | ||||
|             down1(s); | ||||
|             const uint8_t *result = peg_rule(s, s->bytecode + rule[2], text); | ||||
|             up1(s); | ||||
|             s->mode = oldmode; | ||||
|             if (!result) return NULL; | ||||
|             int32_t num_sub_captures = s->captures->count - cs.cap; | ||||
|             Janet cap; | ||||
|             if (num_sub_captures > (int32_t) nth) { | ||||
|                 cap = s->captures->data[cs.cap + nth]; | ||||
|             } else { | ||||
|                 return NULL; | ||||
|             } | ||||
|             cap_load_keept(s, cs); | ||||
|             pushcap(s, cap, tag); | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         case RULE_SUB: { | ||||
|             const uint8_t *text_start = text; | ||||
|             const uint32_t *rule_window = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_subpattern = s->bytecode + rule[2]; | ||||
|             down1(s); | ||||
|             const uint8_t *window_end = peg_rule(s, rule_window, text); | ||||
|             up1(s); | ||||
|             if (!window_end) { | ||||
|                 return NULL; | ||||
|             } | ||||
|             const uint8_t *saved_end = s->text_end; | ||||
|             s->text_end = window_end; | ||||
|             down1(s); | ||||
|             const uint8_t *next_text = peg_rule(s, rule_subpattern, text_start); | ||||
|             up1(s); | ||||
|             s->text_end = saved_end; | ||||
|  | ||||
|             if (!next_text) { | ||||
|                 return NULL; | ||||
|             } | ||||
|  | ||||
|             return window_end; | ||||
|         } | ||||
|  | ||||
|         case RULE_TIL: { | ||||
|             const uint32_t *rule_terminus = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_subpattern = s->bytecode + rule[2]; | ||||
|  | ||||
|             const uint8_t *terminus_start = text; | ||||
|             const uint8_t *terminus_end = NULL; | ||||
|             down1(s); | ||||
|             while (terminus_start <= s->text_end) { | ||||
|                 CapState cs2 = cap_save(s); | ||||
|                 terminus_end = peg_rule(s, rule_terminus, terminus_start); | ||||
|                 cap_load(s, cs2); | ||||
|                 if (terminus_end) { | ||||
|                     break; | ||||
|                 } | ||||
|                 terminus_start++; | ||||
|             } | ||||
|             up1(s); | ||||
|  | ||||
|             if (!terminus_end) { | ||||
|                 return NULL; | ||||
|             } | ||||
|  | ||||
|             const uint8_t *saved_end = s->text_end; | ||||
|             s->text_end = terminus_start; | ||||
|             down1(s); | ||||
|             const uint8_t *matched = peg_rule(s, rule_subpattern, text); | ||||
|             up1(s); | ||||
|             s->text_end = saved_end; | ||||
|  | ||||
|             if (!matched) { | ||||
|                 return NULL; | ||||
|             } | ||||
|  | ||||
|             return terminus_end; | ||||
|         } | ||||
|  | ||||
|         case RULE_SPLIT: { | ||||
|             const uint8_t *saved_end = s->text_end; | ||||
|             const uint32_t *rule_separator = s->bytecode + rule[1]; | ||||
|             const uint32_t *rule_subpattern = s->bytecode + rule[2]; | ||||
|  | ||||
|             const uint8_t *chunk_start = text; | ||||
|             const uint8_t *chunk_end = NULL; | ||||
|  | ||||
|             while (text <= saved_end) { | ||||
|                 /* Find next split (or end of text) */ | ||||
|                 CapState cs = cap_save(s); | ||||
|                 down1(s); | ||||
|                 while (text <= saved_end) { | ||||
|                     chunk_end = text; | ||||
|                     const uint8_t *check = peg_rule(s, rule_separator, text); | ||||
|                     cap_load(s, cs); | ||||
|                     if (check) { | ||||
|                         text = check; | ||||
|                         break; | ||||
|                     } | ||||
|                     text++; | ||||
|                 } | ||||
|                 up1(s); | ||||
|  | ||||
|                 /* Match between splits */ | ||||
|                 s->text_end = chunk_end; | ||||
|                 down1(s); | ||||
|                 const uint8_t *subpattern_end = peg_rule(s, rule_subpattern, chunk_start); | ||||
|                 up1(s); | ||||
|                 s->text_end = saved_end; | ||||
|                 if (!subpattern_end) return NULL; /* Don't match anything */ | ||||
|  | ||||
|                 /* Ensure forward progress */ | ||||
|                 if (text == chunk_start) return NULL; | ||||
|                 chunk_start = text; | ||||
|             } | ||||
|  | ||||
|             s->text_end = saved_end; | ||||
|             return s->text_end; | ||||
|         } | ||||
|  | ||||
|         case RULE_REPLACE: | ||||
|         case RULE_MATCHTIME: { | ||||
|             uint32_t tag = rule[3]; | ||||
| @@ -577,11 +740,11 @@ tail: | ||||
|         case RULE_READINT: { | ||||
|             uint32_t tag = rule[2]; | ||||
|             uint32_t signedness = rule[1] & 0x10; | ||||
|             uint32_t endianess = rule[1] & 0x20; | ||||
|             uint32_t endianness = rule[1] & 0x20; | ||||
|             int width = (int)(rule[1] & 0xF); | ||||
|             if (text + width > s->text_end) return NULL; | ||||
|             uint64_t accum = 0; | ||||
|             if (endianess) { | ||||
|             if (endianness) { | ||||
|                 /* BE */ | ||||
|                 for (int i = 0; i < width; i++) accum = (accum << 8) | text[i]; | ||||
|             } else { | ||||
| @@ -971,6 +1134,9 @@ static void spec_thru(Builder *b, int32_t argc, const Janet *argv) { | ||||
| static void spec_drop(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     spec_onerule(b, argc, argv, RULE_DROP); | ||||
| } | ||||
| static void spec_only_tags(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     spec_onerule(b, argc, argv, RULE_ONLY_TAGS); | ||||
| } | ||||
|  | ||||
| /* Rule of the form [rule, tag] */ | ||||
| static void spec_cap1(Builder *b, int32_t argc, const Janet *argv, uint32_t op) { | ||||
| @@ -994,6 +1160,15 @@ static void spec_unref(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     spec_cap1(b, argc, argv, RULE_UNREF); | ||||
| } | ||||
|  | ||||
| static void spec_nth(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_arity(b, argc, 2, 3); | ||||
|     Reserve r = reserve(b, 4); | ||||
|     uint32_t nth = peg_getnat(b, argv[0]); | ||||
|     uint32_t rule = peg_compile1(b, argv[1]); | ||||
|     uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0; | ||||
|     emit_3(r, RULE_NTH, nth, rule, tag); | ||||
| } | ||||
|  | ||||
| static void spec_capture_number(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_arity(b, argc, 1, 3); | ||||
|     Reserve r = reserve(b, 4); | ||||
| @@ -1010,7 +1185,7 @@ static void spec_capture_number(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     emit_3(r, RULE_CAPTURE_NUM, rule, base, tag); | ||||
|     return; | ||||
| error: | ||||
|     peg_panicf(b, "expected integer between 2 and 36, got %v", argv[2]); | ||||
|     peg_panicf(b, "expected integer between 2 and 36, got %v", argv[1]); | ||||
| } | ||||
|  | ||||
| static void spec_reference(Builder *b, int32_t argc, const Janet *argv) { | ||||
| @@ -1076,13 +1251,37 @@ static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     Janet fun = argv[1]; | ||||
|     if (!janet_checktype(fun, JANET_FUNCTION) && | ||||
|             !janet_checktype(fun, JANET_CFUNCTION)) { | ||||
|         peg_panicf(b, "expected function|cfunction, got %v", fun); | ||||
|         peg_panicf(b, "expected function or cfunction, got %v", fun); | ||||
|     } | ||||
|     uint32_t tag = (argc == 3) ? emit_tag(b, argv[2]) : 0; | ||||
|     uint32_t cindex = emit_constant(b, fun); | ||||
|     emit_3(r, RULE_MATCHTIME, subrule, cindex, tag); | ||||
| } | ||||
|  | ||||
| static void spec_sub(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_fixarity(b, argc, 2); | ||||
|     Reserve r = reserve(b, 3); | ||||
|     uint32_t subrule1 = peg_compile1(b, argv[0]); | ||||
|     uint32_t subrule2 = peg_compile1(b, argv[1]); | ||||
|     emit_2(r, RULE_SUB, subrule1, subrule2); | ||||
| } | ||||
|  | ||||
| static void spec_til(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_fixarity(b, argc, 2); | ||||
|     Reserve r = reserve(b, 3); | ||||
|     uint32_t subrule1 = peg_compile1(b, argv[0]); | ||||
|     uint32_t subrule2 = peg_compile1(b, argv[1]); | ||||
|     emit_2(r, RULE_TIL, subrule1, subrule2); | ||||
| } | ||||
|  | ||||
| static void spec_split(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     peg_fixarity(b, argc, 2); | ||||
|     Reserve r = reserve(b, 3); | ||||
|     uint32_t subrule1 = peg_compile1(b, argv[0]); | ||||
|     uint32_t subrule2 = peg_compile1(b, argv[1]); | ||||
|     emit_2(r, RULE_SPLIT, subrule1, subrule2); | ||||
| } | ||||
|  | ||||
| #ifdef JANET_INT_TYPES | ||||
| #define JANET_MAX_READINT_WIDTH 8 | ||||
| #else | ||||
| @@ -1156,7 +1355,9 @@ static const SpecialPair peg_specials[] = { | ||||
|     {"line", spec_line}, | ||||
|     {"look", spec_look}, | ||||
|     {"not", spec_not}, | ||||
|     {"nth", spec_nth}, | ||||
|     {"number", spec_capture_number}, | ||||
|     {"only-tags", spec_only_tags}, | ||||
|     {"opt", spec_opt}, | ||||
|     {"position", spec_position}, | ||||
|     {"quote", spec_capture}, | ||||
| @@ -1166,7 +1367,10 @@ static const SpecialPair peg_specials[] = { | ||||
|     {"sequence", spec_sequence}, | ||||
|     {"set", spec_set}, | ||||
|     {"some", spec_some}, | ||||
|     {"split", spec_split}, | ||||
|     {"sub", spec_sub}, | ||||
|     {"thru", spec_thru}, | ||||
|     {"til", spec_til}, | ||||
|     {"to", spec_to}, | ||||
|     {"uint", spec_uint_le}, | ||||
|     {"uint-be", spec_uint_be}, | ||||
| @@ -1237,6 +1441,13 @@ static uint32_t peg_compile1(Builder *b, Janet peg) { | ||||
|         default: | ||||
|             peg_panic(b, "unexpected peg source"); | ||||
|             return 0; | ||||
|  | ||||
|         case JANET_BOOLEAN: { | ||||
|             int n = janet_unwrap_boolean(peg); | ||||
|             Reserve r = reserve(b, 2); | ||||
|             emit_1(r, n ? RULE_NCHAR : RULE_NOTNCHAR, 0); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_NUMBER: { | ||||
|             int32_t n = peg_getinteger(b, peg); | ||||
|             Reserve r = reserve(b, 2); | ||||
| @@ -1253,6 +1464,23 @@ static uint32_t peg_compile1(Builder *b, Janet peg) { | ||||
|             emit_bytes(b, RULE_LITERAL, len, str); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_BUFFER: { | ||||
|             const JanetBuffer *buf = janet_unwrap_buffer(peg); | ||||
|             emit_bytes(b, RULE_LITERAL, buf->count, buf->data); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_TABLE: { | ||||
|             /* Build grammar table */ | ||||
|             JanetTable *new_grammar = janet_table_clone(janet_unwrap_table(peg)); | ||||
|             new_grammar->proto = grammar; | ||||
|             b->grammar = grammar = new_grammar; | ||||
|             /* Run the main rule */ | ||||
|             Janet main_rule = janet_table_rawget(grammar, janet_ckeywordv("main")); | ||||
|             if (janet_checktype(main_rule, JANET_NIL)) | ||||
|                 peg_panic(b, "grammar requires :main rule"); | ||||
|             rule = peg_compile1(b, main_rule); | ||||
|             break; | ||||
|         } | ||||
|         case JANET_STRUCT: { | ||||
|             /* Build grammar table */ | ||||
|             const JanetKV *st = janet_unwrap_struct(peg); | ||||
| @@ -1388,7 +1616,7 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|         uint32_t instr = bytecode[i]; | ||||
|         uint32_t *rule = bytecode + i; | ||||
|         op_flags[i] |= 0x02; | ||||
|         switch (instr & 0x1F) { | ||||
|         switch (instr) { | ||||
|             case RULE_LITERAL: | ||||
|                 i += 2 + ((rule[1] + 3) >> 2); | ||||
|                 break; | ||||
| @@ -1481,8 +1709,19 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|                 op_flags[rule[1]] |= 0x01; | ||||
|                 i += 4; | ||||
|                 break; | ||||
|             case RULE_SUB: | ||||
|             case RULE_TIL: | ||||
|             case RULE_SPLIT: | ||||
|                 /* [rule, rule] */ | ||||
|                 if (rule[1] >= blen) goto bad; | ||||
|                 if (rule[2] >= blen) goto bad; | ||||
|                 op_flags[rule[1]] |= 0x01; | ||||
|                 op_flags[rule[2]] |= 0x01; | ||||
|                 i += 3; | ||||
|                 break; | ||||
|             case RULE_ERROR: | ||||
|             case RULE_DROP: | ||||
|             case RULE_ONLY_TAGS: | ||||
|             case RULE_NOT: | ||||
|             case RULE_TO: | ||||
|             case RULE_THRU: | ||||
| @@ -1492,10 +1731,16 @@ static void *peg_unmarshal(JanetMarshalContext *ctx) { | ||||
|                 i += 2; | ||||
|                 break; | ||||
|             case RULE_READINT: | ||||
|                 /* [ width | (endianess << 5) | (signedness << 6), tag ] */ | ||||
|                 /* [ width | (endianness << 5) | (signedness << 6), tag ] */ | ||||
|                 if (rule[1] > JANET_MAX_READINT_WIDTH) goto bad; | ||||
|                 i += 3; | ||||
|                 break; | ||||
|             case RULE_NTH: | ||||
|                 /* [nth, rule, tag] */ | ||||
|                 if (rule[2] >= blen) goto bad; | ||||
|                 op_flags[rule[2]] |= 0x01; | ||||
|                 i += 4; | ||||
|                 break; | ||||
|             default: | ||||
|                 goto bad; | ||||
|         } | ||||
| @@ -1589,7 +1834,7 @@ static JanetPeg *compile_peg(Janet x) { | ||||
| JANET_CORE_FN(cfun_peg_compile, | ||||
|               "(peg/compile peg)", | ||||
|               "Compiles a peg source data structure into a <core/peg>. This will speed up matching " | ||||
|               "if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to suppliment " | ||||
|               "if the same peg will be used multiple times. Will also use `(dyn :peg-grammar)` to supplement " | ||||
|               "the grammar of the peg for otherwise undefined peg keywords.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetPeg *peg = compile_peg(argv[0]); | ||||
| @@ -1601,7 +1846,7 @@ typedef struct { | ||||
|     JanetPeg *peg; | ||||
|     PegState s; | ||||
|     JanetByteView bytes; | ||||
|     JanetByteView repl; | ||||
|     Janet subst; | ||||
|     int32_t start; | ||||
| } PegCall; | ||||
|  | ||||
| @@ -1609,7 +1854,7 @@ typedef struct { | ||||
| static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) { | ||||
|     PegCall ret; | ||||
|     int32_t min = get_replace ? 3 : 2; | ||||
|     janet_arity(argc, get_replace, -1); | ||||
|     janet_arity(argc, min, -1); | ||||
|     if (janet_checktype(argv[0], JANET_ABSTRACT) && | ||||
|             janet_abstract_type(janet_unwrap_abstract(argv[0])) == &janet_peg_type) { | ||||
|         ret.peg = janet_unwrap_abstract(argv[0]); | ||||
| @@ -1617,7 +1862,7 @@ static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) { | ||||
|         ret.peg = compile_peg(argv[0]); | ||||
|     } | ||||
|     if (get_replace) { | ||||
|         ret.repl = janet_getbytes(argv, 1); | ||||
|         ret.subst = argv[1]; | ||||
|         ret.bytes = janet_getbytes(argv, 2); | ||||
|     } else { | ||||
|         ret.bytes = janet_getbytes(argv, 1); | ||||
| @@ -1634,6 +1879,7 @@ static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) { | ||||
|     ret.s.mode = PEG_MODE_NORMAL; | ||||
|     ret.s.text_start = ret.bytes.bytes; | ||||
|     ret.s.text_end = ret.bytes.bytes + ret.bytes.len; | ||||
|     ret.s.outer_text_end = ret.s.text_end; | ||||
|     ret.s.depth = JANET_RECURSION_GUARD; | ||||
|     ret.s.captures = janet_array(0); | ||||
|     ret.s.tagged_captures = janet_array(0); | ||||
| @@ -1648,7 +1894,9 @@ static PegCall peg_cfun_init(int32_t argc, Janet *argv, int get_replace) { | ||||
| } | ||||
|  | ||||
| static void peg_call_reset(PegCall *c) { | ||||
|     c->s.depth = JANET_RECURSION_GUARD; | ||||
|     c->s.captures->count = 0; | ||||
|     c->s.tagged_captures->count = 0; | ||||
|     c->s.scratch->count = 0; | ||||
|     c->s.tags->count = 0; | ||||
| } | ||||
| @@ -1700,7 +1948,8 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) { | ||||
|                 trail = i; | ||||
|             } | ||||
|             int32_t nexti = (int32_t)(result - c.bytes.bytes); | ||||
|             janet_buffer_push_bytes(ret, c.repl.bytes, c.repl.len); | ||||
|             JanetByteView subst = janet_text_substitution(&c.subst, c.bytes.bytes + i, nexti - i, c.s.captures); | ||||
|             janet_buffer_push_bytes(ret, subst.bytes, subst.len); | ||||
|             trail = nexti; | ||||
|             if (nexti == i) nexti++; | ||||
|             i = nexti; | ||||
| @@ -1716,14 +1965,20 @@ static Janet cfun_peg_replace_generic(int32_t argc, Janet *argv, int only_one) { | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_peg_replace_all, | ||||
|               "(peg/replace-all peg repl text &opt start & args)", | ||||
|               "Replace all matches of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement.") { | ||||
|               "(peg/replace-all peg subst text &opt start & args)", | ||||
|               "Replace all matches of `peg` in `text` with `subst`, returning a new buffer. " | ||||
|               "The peg does not need to make captures to do replacement. " | ||||
|               "If `subst` is a function, it will be called with the " | ||||
|               "matching text followed by any captures.") { | ||||
|     return cfun_peg_replace_generic(argc, argv, 0); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_peg_replace, | ||||
|               "(peg/replace peg repl text &opt start & args)", | ||||
|               "Replace first match of peg in text with repl, returning a new buffer. The peg does not need to make captures to do replacement. " | ||||
|               "(peg/replace peg subst text &opt start & args)", | ||||
|               "Replace first match of `peg` in `text` with `subst`, returning a new buffer. " | ||||
|               "The peg does not need to make captures to do replacement. " | ||||
|               "If `subst` is a function, it will be called with the " | ||||
|               "matching text followed by any captures. " | ||||
|               "If no matches are found, returns the input string in a new buffer.") { | ||||
|     return cfun_peg_replace_generic(argc, argv, 1); | ||||
| } | ||||
|   | ||||
							
								
								
									
										157
									
								
								src/core/pp.c
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								src/core/pp.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -30,6 +30,8 @@ | ||||
|  | ||||
| #include <string.h> | ||||
| #include <ctype.h> | ||||
| #include <inttypes.h> | ||||
| #include <float.h> | ||||
|  | ||||
| /* Implements a pretty printer for Janet. The pretty printer | ||||
|  * is simple and not that flexible, but fast. */ | ||||
| @@ -37,11 +39,15 @@ | ||||
| /* Temporary buffer size */ | ||||
| #define BUFSIZE 64 | ||||
|  | ||||
| /* Preprocessor hacks */ | ||||
| #define STR_HELPER(x) #x | ||||
| #define STR(x) STR_HELPER(x) | ||||
|  | ||||
| static void number_to_string_b(JanetBuffer *buffer, double x) { | ||||
|     janet_buffer_ensure(buffer, buffer->count + BUFSIZE, 2); | ||||
|     const char *fmt = (x == floor(x) && | ||||
|                        x <= JANET_INTMAX_DOUBLE && | ||||
|                        x >= JANET_INTMIN_DOUBLE) ? "%.0f" : "%g"; | ||||
|                        x >= JANET_INTMIN_DOUBLE) ? "%.0f" : ("%." STR(DBL_DIG) "g"); | ||||
|     int count; | ||||
|     if (x == 0.0) { | ||||
|         /* Prevent printing of '-0' */ | ||||
| @@ -108,7 +114,7 @@ static void string_description_b(JanetBuffer *buffer, const char *title, void *p | ||||
|     pbuf.p = pointer; | ||||
|     *c++ = '<'; | ||||
|     /* Maximum of 32 bytes for abstract type name */ | ||||
|     for (i = 0; title[i] && i < 32; ++i) | ||||
|     for (i = 0; i < 32 && title[i]; ++i) | ||||
|         *c++ = ((uint8_t *)title) [i]; | ||||
|     *c++ = ' '; | ||||
|     *c++ = '0'; | ||||
| @@ -151,6 +157,12 @@ static void janet_escape_string_impl(JanetBuffer *buffer, const uint8_t *str, in | ||||
|             case '\v': | ||||
|                 janet_buffer_push_bytes(buffer, (const uint8_t *)"\\v", 2); | ||||
|                 break; | ||||
|             case '\a': | ||||
|                 janet_buffer_push_bytes(buffer, (const uint8_t *)"\\a", 2); | ||||
|                 break; | ||||
|             case '\b': | ||||
|                 janet_buffer_push_bytes(buffer, (const uint8_t *)"\\b", 2); | ||||
|                 break; | ||||
|             case 27: | ||||
|                 janet_buffer_push_bytes(buffer, (const uint8_t *)"\\e", 2); | ||||
|                 break; | ||||
| @@ -243,6 +255,10 @@ void janet_to_string_b(JanetBuffer *buffer, Janet x) { | ||||
|         case JANET_FUNCTION: { | ||||
|             JanetFunction *fun = janet_unwrap_function(x); | ||||
|             JanetFuncDef *def = fun->def; | ||||
|             if (def == NULL) { | ||||
|                 janet_buffer_push_cstring(buffer, "<incomplete function>"); | ||||
|                 break; | ||||
|             } | ||||
|             if (def->name) { | ||||
|                 const uint8_t *n = def->name; | ||||
|                 janet_buffer_push_cstring(buffer, "<function "); | ||||
| @@ -261,21 +277,13 @@ void janet_to_string_b(JanetBuffer *buffer, Janet x) { | ||||
|  | ||||
| /* See parse.c for full table */ | ||||
|  | ||||
| static const uint32_t pp_symchars[8] = { | ||||
|     0x00000000, 0xf7ffec72, 0xc7ffffff, 0x07fffffe, | ||||
|     0x00000000, 0x00000000, 0x00000000, 0x00000000 | ||||
| }; | ||||
|  | ||||
| static int pp_is_symbol_char(uint8_t c) { | ||||
|     return pp_symchars[c >> 5] & ((uint32_t)1 << (c & 0x1F)); | ||||
| } | ||||
|  | ||||
| /* Check if a symbol or keyword contains no symbol characters */ | ||||
| static int contains_bad_chars(const uint8_t *sym, int issym) { | ||||
|     int32_t len = janet_string_length(sym); | ||||
|     if (len && issym && sym[0] >= '0' && sym[0] <= '9') return 1; | ||||
|     if (!janet_valid_utf8(sym, len)) return 1; | ||||
|     for (int32_t i = 0; i < len; i++) { | ||||
|         if (!pp_is_symbol_char(sym[i])) return 1; | ||||
|         if (!janet_is_symbol_char(sym[i])) return 1; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| @@ -371,8 +379,10 @@ static int print_jdn_one(struct pretty *S, Janet x, int depth) { | ||||
|             break; | ||||
|         case JANET_NUMBER: | ||||
|             janet_buffer_ensure(S->buffer, S->buffer->count + BUFSIZE, 2); | ||||
|             int count = snprintf((char *) S->buffer->data + S->buffer->count, BUFSIZE, "%.17g", janet_unwrap_number(x)); | ||||
|             S->buffer->count += count; | ||||
|             double num = janet_unwrap_number(x); | ||||
|             if (isnan(num)) return 1; | ||||
|             if (isinf(num)) return 1; | ||||
|             janet_buffer_dtostr(S->buffer, num); | ||||
|             break; | ||||
|         case JANET_SYMBOL: | ||||
|         case JANET_KEYWORD: | ||||
| @@ -570,12 +580,12 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { | ||||
|         case JANET_STRUCT: | ||||
|         case JANET_TABLE: { | ||||
|             int istable = janet_checktype(x, JANET_TABLE); | ||||
|             janet_buffer_push_cstring(S->buffer, istable ? "@" : "{"); | ||||
|  | ||||
|             /* For object-like tables, print class name */ | ||||
|             if (istable) { | ||||
|                 JanetTable *t = janet_unwrap_table(x); | ||||
|                 JanetTable *proto = t->proto; | ||||
|                 janet_buffer_push_cstring(S->buffer, "@"); | ||||
|                 if (NULL != proto) { | ||||
|                     Janet name = janet_table_get(proto, janet_ckeywordv("_name")); | ||||
|                     const uint8_t *n; | ||||
| @@ -590,8 +600,25 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 janet_buffer_push_cstring(S->buffer, "{"); | ||||
|             } else { | ||||
|                 JanetStruct st = janet_unwrap_struct(x); | ||||
|                 JanetStruct proto = janet_struct_proto(st); | ||||
|                 if (NULL != proto) { | ||||
|                     Janet name = janet_struct_get(proto, janet_ckeywordv("_name")); | ||||
|                     const uint8_t *n; | ||||
|                     int32_t len; | ||||
|                     if (janet_bytes_view(name, &n, &len)) { | ||||
|                         if (S->flags & JANET_PRETTY_COLOR) { | ||||
|                             janet_buffer_push_cstring(S->buffer, janet_class_color); | ||||
|                         } | ||||
|                         janet_buffer_push_bytes(S->buffer, n, len); | ||||
|                         if (S->flags & JANET_PRETTY_COLOR) { | ||||
|                             janet_buffer_push_cstring(S->buffer, "\x1B[0m"); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             janet_buffer_push_cstring(S->buffer, "{"); | ||||
|  | ||||
|             S->depth--; | ||||
|             S->indent += 2; | ||||
| @@ -627,7 +654,7 @@ static void janet_pretty_one(struct pretty *S, Janet x, int is_dict_value) { | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 janet_sorted_keys(kvs, cap, S->keysort_buffer + ks_start); | ||||
|                 janet_sorted_keys(kvs, cap, S->keysort_buffer == NULL ? NULL : S->keysort_buffer + ks_start); | ||||
|                 S->keysort_start += len; | ||||
|                 if (!(S->flags & JANET_PRETTY_NOTRUNC) && (len > JANET_PRETTY_DICT_LIMIT)) { | ||||
|                     len = JANET_PRETTY_DICT_LIMIT; | ||||
| @@ -726,7 +753,7 @@ static void pushtypes(JanetBuffer *buffer, int types) { | ||||
|             if (first) { | ||||
|                 first = 0; | ||||
|             } else { | ||||
|                 janet_buffer_push_u8(buffer, '|'); | ||||
|                 janet_buffer_push_cstring(buffer, (types == 1) ? " or " : ", "); | ||||
|             } | ||||
|             janet_buffer_push_cstring(buffer, janet_type_names[i]); | ||||
|         } | ||||
| @@ -741,20 +768,48 @@ static void pushtypes(JanetBuffer *buffer, int types) { | ||||
|  | ||||
| #define MAX_ITEM  256 | ||||
| #define FMT_FLAGS "-+ #0" | ||||
| #define FMT_REPLACE_INTTYPES "diouxX" | ||||
| #define MAX_FORMAT 32 | ||||
|  | ||||
| struct FmtMapping { | ||||
|     char c; | ||||
|     const char *mapping; | ||||
| }; | ||||
|  | ||||
| /* Janet uses fixed width integer types for most things, so map | ||||
|  * format specifiers to these fixed sizes */ | ||||
| static const struct FmtMapping format_mappings[] = { | ||||
|     {'D', PRId64}, | ||||
|     {'I', PRIi64}, | ||||
|     {'d', PRId64}, | ||||
|     {'i', PRIi64}, | ||||
|     {'o', PRIo64}, | ||||
|     {'u', PRIu64}, | ||||
|     {'x', PRIx64}, | ||||
|     {'X', PRIX64}, | ||||
| }; | ||||
|  | ||||
| static const char *get_fmt_mapping(char c) { | ||||
|     for (size_t i = 0; i < (sizeof(format_mappings) / sizeof(struct FmtMapping)); i++) { | ||||
|         if (format_mappings[i].c == c) | ||||
|             return format_mappings[i].mapping; | ||||
|     } | ||||
|     janet_assert(0, "bad format mapping"); | ||||
| } | ||||
|  | ||||
| static const char *scanformat( | ||||
|     const char *strfrmt, | ||||
|     char *form, | ||||
|     char width[3], | ||||
|     char precision[3]) { | ||||
|     const char *p = strfrmt; | ||||
|  | ||||
|     /* Parse strfrmt */ | ||||
|     memset(width, '\0', 3); | ||||
|     memset(precision, '\0', 3); | ||||
|     while (*p != '\0' && strchr(FMT_FLAGS, *p) != NULL) | ||||
|         p++; /* skip flags */ | ||||
|     if ((size_t)(p - strfrmt) >= sizeof(FMT_FLAGS) / sizeof(char)) | ||||
|         janet_panic("invalid format (repeated flags)"); | ||||
|     if ((size_t)(p - strfrmt) >= sizeof(FMT_FLAGS)) janet_panic("invalid format (repeated flags)"); | ||||
|     if (isdigit((int)(*p))) | ||||
|         width[0] = *p++; /* skip width */ | ||||
|     if (isdigit((int)(*p))) | ||||
| @@ -768,10 +823,23 @@ static const char *scanformat( | ||||
|     } | ||||
|     if (isdigit((int)(*p))) | ||||
|         janet_panic("invalid format (width or precision too long)"); | ||||
|  | ||||
|     /* Write to form - replace characters with fixed size stuff */ | ||||
|     *(form++) = '%'; | ||||
|     memcpy(form, strfrmt, ((p - strfrmt) + 1) * sizeof(char)); | ||||
|     form += (p - strfrmt) + 1; | ||||
|     const char *p2 = strfrmt; | ||||
|     while (p2 <= p) { | ||||
|         char *loc = strchr(FMT_REPLACE_INTTYPES, *p2); | ||||
|         if (loc != NULL && *loc != '\0') { | ||||
|             const char *mapping = get_fmt_mapping(*p2++); | ||||
|             size_t len = strlen(mapping); | ||||
|             memcpy(form, mapping, len); | ||||
|             form += len; | ||||
|         } else { | ||||
|             *(form++) = *(p2++); | ||||
|         } | ||||
|     } | ||||
|     *form = '\0'; | ||||
|  | ||||
|     return p; | ||||
| } | ||||
|  | ||||
| @@ -791,16 +859,27 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { | ||||
|             c = scanformat(c, form, width, precision); | ||||
|             switch (*c++) { | ||||
|                 case 'c': { | ||||
|                     int n = va_arg(args, long); | ||||
|                     int n = va_arg(args, int); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'd': | ||||
|                 case 'i': | ||||
|                 case 'o': | ||||
|                 case 'i': { | ||||
|                     int64_t n = (int64_t) va_arg(args, int32_t); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'D': | ||||
|                 case 'I': { | ||||
|                     int64_t n = va_arg(args, int64_t); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'x': | ||||
|                 case 'X': { | ||||
|                     int32_t n = va_arg(args, long); | ||||
|                 case 'X': | ||||
|                 case 'o': | ||||
|                 case 'u': { | ||||
|                     uint64_t n = va_arg(args, uint64_t); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -844,7 +923,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { | ||||
|                     janet_buffer_push_cstring(b, typestr(va_arg(args, Janet))); | ||||
|                     break; | ||||
|                 case 'T': { | ||||
|                     int types = va_arg(args, long); | ||||
|                     int types = va_arg(args, int); | ||||
|                     pushtypes(b, types); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -953,12 +1032,19 @@ void janet_buffer_format( | ||||
|                                   janet_getinteger(argv, arg)); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'D': | ||||
|                 case 'I': | ||||
|                 case 'd': | ||||
|                 case 'i': | ||||
|                 case 'o': | ||||
|                 case 'i': { | ||||
|                     int64_t n = janet_getinteger64(argv, arg); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'x': | ||||
|                 case 'X': { | ||||
|                     int32_t n = janet_getinteger(argv, arg); | ||||
|                 case 'X': | ||||
|                 case 'o': | ||||
|                 case 'u': { | ||||
|                     uint64_t n = janet_getuinteger64(argv, arg); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -974,8 +1060,9 @@ void janet_buffer_format( | ||||
|                     break; | ||||
|                 } | ||||
|                 case 's': { | ||||
|                     const uint8_t *s = janet_getstring(argv, arg); | ||||
|                     int32_t l = janet_string_length(s); | ||||
|                     JanetByteView bytes = janet_getbytes(argv, arg); | ||||
|                     const uint8_t *s = bytes.bytes; | ||||
|                     int32_t l = bytes.len; | ||||
|                     if (form[2] == '\0') | ||||
|                         janet_buffer_push_bytes(b, s, l); | ||||
|                     else { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -27,6 +27,8 @@ | ||||
| #include "util.h" | ||||
| #endif | ||||
|  | ||||
| /* The JanetRegisterAllocator is really just a bitset. */ | ||||
|  | ||||
| void janetc_regalloc_init(JanetcRegisterAllocator *ra) { | ||||
|     ra->chunks = NULL; | ||||
|     ra->count = 0; | ||||
| @@ -139,6 +141,14 @@ void janetc_regalloc_free(JanetcRegisterAllocator *ra, int32_t reg) { | ||||
|     ra->chunks[chunk] &= ~ithbit(bit); | ||||
| } | ||||
|  | ||||
| /* Check if a register is set. */ | ||||
| int janetc_regalloc_check(JanetcRegisterAllocator *ra, int32_t reg) { | ||||
|     int32_t chunk = reg >> 5; | ||||
|     int32_t bit = reg & 0x1F; | ||||
|     while (chunk >= ra->count) pushchunk(ra); | ||||
|     return !!(ra->chunks[chunk] & ithbit(bit)); | ||||
| } | ||||
|  | ||||
| /* Get a register that will fit in 8 bits (< 256). Do not call this | ||||
|  * twice with the same value of nth without calling janetc_regalloc_free | ||||
|  * on the returned register before. */ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -56,5 +56,6 @@ int32_t janetc_regalloc_temp(JanetcRegisterAllocator *ra, JanetcRegisterTemp nth | ||||
| void janetc_regalloc_freetemp(JanetcRegisterAllocator *ra, int32_t reg, JanetcRegisterTemp nth); | ||||
| void janetc_regalloc_clone(JanetcRegisterAllocator *dest, JanetcRegisterAllocator *src); | ||||
| void janetc_regalloc_touch(JanetcRegisterAllocator *ra, int32_t reg); | ||||
| int janetc_regalloc_check(JanetcRegisterAllocator *ra, int32_t reg); | ||||
|  | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -23,45 +23,57 @@ | ||||
| #ifndef JANET_AMALG | ||||
| #include "features.h" | ||||
| #include <janet.h> | ||||
| #include "state.h" | ||||
| #endif | ||||
|  | ||||
| /* Run a string */ | ||||
| int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char *sourcePath, Janet *out) { | ||||
|     JanetParser parser; | ||||
|     JanetParser *parser; | ||||
|     int errflags = 0, done = 0; | ||||
|     int32_t index = 0; | ||||
|     Janet ret = janet_wrap_nil(); | ||||
|     JanetFiber *fiber = NULL; | ||||
|     const uint8_t *where = sourcePath ? janet_cstring(sourcePath) : NULL; | ||||
|  | ||||
|     if (where) janet_gcroot(janet_wrap_string(where)); | ||||
|     if (NULL == sourcePath) sourcePath = "<unknown>"; | ||||
|     janet_parser_init(&parser); | ||||
|     parser = janet_abstract(&janet_parser_type, sizeof(JanetParser)); | ||||
|     janet_parser_init(parser); | ||||
|     janet_gcroot(janet_wrap_abstract(parser)); | ||||
|  | ||||
|     /* While we haven't seen an error */ | ||||
|     while (!done) { | ||||
|  | ||||
|         /* Evaluate parsed values */ | ||||
|         while (janet_parser_has_more(&parser)) { | ||||
|             Janet form = janet_parser_produce(&parser); | ||||
|         while (janet_parser_has_more(parser)) { | ||||
|             Janet form = janet_parser_produce(parser); | ||||
|             JanetCompileResult cres = janet_compile(form, env, where); | ||||
|             if (cres.status == JANET_COMPILE_OK) { | ||||
|                 JanetFunction *f = janet_thunk(cres.funcdef); | ||||
|                 JanetFiber *fiber = janet_fiber(f, 64, 0, NULL); | ||||
|                 fiber = janet_fiber(f, 64, 0, NULL); | ||||
|                 fiber->env = env; | ||||
|                 JanetSignal status = janet_continue(fiber, janet_wrap_nil(), &ret); | ||||
|                 if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) { | ||||
|                     janet_stacktrace(fiber, ret); | ||||
|                     janet_stacktrace_ext(fiber, ret, ""); | ||||
|                     errflags |= 0x01; | ||||
|                     done = 1; | ||||
|                 } | ||||
|             } else { | ||||
|                 ret = janet_wrap_string(cres.error); | ||||
|                 int32_t line = (int32_t) parser->line; | ||||
|                 int32_t col = (int32_t) parser->column; | ||||
|                 if ((cres.error_mapping.line > 0) && | ||||
|                         (cres.error_mapping.column > 0)) { | ||||
|                     line = cres.error_mapping.line; | ||||
|                     col = cres.error_mapping.column; | ||||
|                 } | ||||
|                 if (cres.macrofiber) { | ||||
|                     janet_eprintf("compile error in %s: ", sourcePath); | ||||
|                     janet_stacktrace(cres.macrofiber, ret); | ||||
|                     janet_eprintf("%s:%d:%d: compile error", sourcePath, | ||||
|                                   line, col); | ||||
|                     janet_stacktrace_ext(cres.macrofiber, ret, ""); | ||||
|                 } else { | ||||
|                     janet_eprintf("compile error in %s: %s\n", sourcePath, | ||||
|                                   (const char *)cres.error); | ||||
|                     janet_eprintf("%s:%d:%d: compile error: %s\n", sourcePath, | ||||
|                                   line, col, (const char *)cres.error); | ||||
|                 } | ||||
|                 errflags |= 0x02; | ||||
|                 done = 1; | ||||
| @@ -71,16 +83,16 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|         if (done) break; | ||||
|  | ||||
|         /* Dispatch based on parse state */ | ||||
|         switch (janet_parser_status(&parser)) { | ||||
|         switch (janet_parser_status(parser)) { | ||||
|             case JANET_PARSE_DEAD: | ||||
|                 done = 1; | ||||
|                 break; | ||||
|             case JANET_PARSE_ERROR: { | ||||
|                 const char *e = janet_parser_error(&parser); | ||||
|                 const char *e = janet_parser_error(parser); | ||||
|                 errflags |= 0x04; | ||||
|                 ret = janet_cstringv(e); | ||||
|                 int32_t line = parser.line; | ||||
|                 int32_t col = parser.column; | ||||
|                 int32_t line = (int32_t) parser->line; | ||||
|                 int32_t col = (int32_t) parser->column; | ||||
|                 janet_eprintf("%s:%d:%d: parse error: %s\n", sourcePath, line, col, e); | ||||
|                 done = 1; | ||||
|                 break; | ||||
| @@ -88,9 +100,9 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|             case JANET_PARSE_ROOT: | ||||
|             case JANET_PARSE_PENDING: | ||||
|                 if (index >= len) { | ||||
|                     janet_parser_eof(&parser); | ||||
|                     janet_parser_eof(parser); | ||||
|                 } else { | ||||
|                     janet_parser_consume(&parser, bytes[index++]); | ||||
|                     janet_parser_consume(parser, bytes[index++]); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
| @@ -98,8 +110,21 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|     } | ||||
|  | ||||
|     /* Clean up and return errors */ | ||||
|     janet_parser_deinit(&parser); | ||||
|     janet_gcunroot(janet_wrap_abstract(parser)); | ||||
|     if (where) janet_gcunroot(janet_wrap_string(where)); | ||||
| #ifdef JANET_EV | ||||
|     /* Enter the event loop if we are not already in it */ | ||||
|     if (janet_vm.stackn == 0) { | ||||
|         if (fiber) { | ||||
|             janet_gcroot(janet_wrap_fiber(fiber)); | ||||
|         } | ||||
|         janet_loop(); | ||||
|         if (fiber) { | ||||
|             janet_gcunroot(janet_wrap_fiber(fiber)); | ||||
|             ret = fiber->last_value; | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
|     if (out) *out = ret; | ||||
|     return errflags; | ||||
| } | ||||
| @@ -121,7 +146,7 @@ int janet_loop_fiber(JanetFiber *fiber) { | ||||
|     Janet out; | ||||
|     status = janet_continue(fiber, janet_wrap_nil(), &out); | ||||
|     if (status != JANET_SIGNAL_OK && status != JANET_SIGNAL_EVENT) { | ||||
|         janet_stacktrace(fiber, out); | ||||
|         janet_stacktrace_ext(fiber, out, ""); | ||||
|     } | ||||
| #endif | ||||
|     return status; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -31,7 +31,7 @@ | ||||
|  | ||||
| static JanetSlot janetc_quote(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     if (argn != 1) { | ||||
|         janetc_cerror(opts.compiler, "expected 1 argument"); | ||||
|         janetc_cerror(opts.compiler, "expected 1 argument to quote"); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     return janetc_cslot(argv[0]); | ||||
| @@ -39,8 +39,12 @@ static JanetSlot janetc_quote(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|  | ||||
| static JanetSlot janetc_splice(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     JanetSlot ret; | ||||
|     if (!(opts.flags & JANET_FOPTS_ACCEPT_SPLICE)) { | ||||
|         janetc_cerror(opts.compiler, "splice can only be used in function parameters and data constructors, it has no effect here"); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     if (argn != 1) { | ||||
|         janetc_cerror(opts.compiler, "expected 1 argument"); | ||||
|         janetc_cerror(opts.compiler, "expected 1 argument to splice"); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     ret = janetc_value(opts, argv[0]); | ||||
| @@ -62,6 +66,8 @@ static JanetSlot quasiquote(JanetFopts opts, Janet x, int depth, int level) { | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     JanetSlot *slots = NULL; | ||||
|     JanetFopts subopts = opts; | ||||
|     subopts.flags &= ~JANET_FOPTS_HINT; | ||||
|     switch (janet_type(x)) { | ||||
|         default: | ||||
|             return janetc_cslot(x); | ||||
| @@ -73,7 +79,9 @@ static JanetSlot quasiquote(JanetFopts opts, Janet x, int depth, int level) { | ||||
|                 const uint8_t *head = janet_unwrap_symbol(tup[0]); | ||||
|                 if (!janet_cstrcmp(head, "unquote")) { | ||||
|                     if (level == 0) { | ||||
|                         return janetc_value(janetc_fopts_default(opts.compiler), tup[1]); | ||||
|                         JanetFopts subopts = janetc_fopts_default(opts.compiler); | ||||
|                         subopts.flags |= JANET_FOPTS_ACCEPT_SPLICE; | ||||
|                         return janetc_value(subopts, tup[1]); | ||||
|                     } else { | ||||
|                         level--; | ||||
|                     } | ||||
| @@ -82,7 +90,7 @@ static JanetSlot quasiquote(JanetFopts opts, Janet x, int depth, int level) { | ||||
|                 } | ||||
|             } | ||||
|             for (i = 0; i < len; i++) | ||||
|                 janet_v_push(slots, quasiquote(opts, tup[i], depth - 1, level)); | ||||
|                 janet_v_push(slots, quasiquote(subopts, tup[i], depth - 1, level)); | ||||
|             return qq_slots(opts, slots, (janet_tuple_flag(tup) & JANET_TUPLE_FLAG_BRACKETCTOR) | ||||
|                             ? JOP_MAKE_BRACKET_TUPLE | ||||
|                             : JOP_MAKE_TUPLE); | ||||
| @@ -91,7 +99,7 @@ static JanetSlot quasiquote(JanetFopts opts, Janet x, int depth, int level) { | ||||
|             int32_t i; | ||||
|             JanetArray *array = janet_unwrap_array(x); | ||||
|             for (i = 0; i < array->count; i++) | ||||
|                 janet_v_push(slots, quasiquote(opts, array->data[i], depth - 1, level)); | ||||
|                 janet_v_push(slots, quasiquote(subopts, array->data[i], depth - 1, level)); | ||||
|             return qq_slots(opts, slots, JOP_MAKE_ARRAY); | ||||
|         } | ||||
|         case JANET_TABLE: | ||||
| @@ -100,8 +108,8 @@ static JanetSlot quasiquote(JanetFopts opts, Janet x, int depth, int level) { | ||||
|             int32_t len, cap = 0; | ||||
|             janet_dictionary_view(x, &kvs, &len, &cap); | ||||
|             while ((kv = janet_dictionary_next(kvs, cap, kv))) { | ||||
|                 JanetSlot key = quasiquote(opts, kv->key, depth - 1, level); | ||||
|                 JanetSlot value =  quasiquote(opts, kv->value, depth - 1, level); | ||||
|                 JanetSlot key = quasiquote(subopts, kv->key, depth - 1, level); | ||||
|                 JanetSlot value =  quasiquote(subopts, kv->value, depth - 1, level); | ||||
|                 key.flags &= ~JANET_SLOT_SPLICED; | ||||
|                 value.flags &= ~JANET_SLOT_SPLICED; | ||||
|                 janet_v_push(slots, key); | ||||
| @@ -115,7 +123,7 @@ static JanetSlot quasiquote(JanetFopts opts, Janet x, int depth, int level) { | ||||
|  | ||||
| static JanetSlot janetc_quasiquote(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     if (argn != 1) { | ||||
|         janetc_cerror(opts.compiler, "expected 1 argument"); | ||||
|         janetc_cerror(opts.compiler, "expected 1 argument to quasiquote"); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     return quasiquote(opts, argv[0], JANET_RECURSION_GUARD, 0); | ||||
| @@ -141,7 +149,7 @@ static int destructure(JanetCompiler *c, | ||||
|                        JanetTable *attr) { | ||||
|     switch (janet_type(left)) { | ||||
|         default: | ||||
|             janetc_cerror(c, "unexpected type in destructuring"); | ||||
|             janetc_error(c, janet_formatc("unexpected type in destructuring, got %v", left)); | ||||
|             return 1; | ||||
|         case JANET_SYMBOL: | ||||
|             /* Leaf, assign right to left */ | ||||
| @@ -154,6 +162,67 @@ static int destructure(JanetCompiler *c, | ||||
|             for (int32_t i = 0; i < len; i++) { | ||||
|                 JanetSlot nextright = janetc_farslot(c); | ||||
|                 Janet subval = values[i]; | ||||
|  | ||||
|                 if (janet_checktype(subval, JANET_SYMBOL) && !janet_cstrcmp(janet_unwrap_symbol(subval), "&")) { | ||||
|                     if (i + 1 >= len) { | ||||
|                         janetc_cerror(c, "expected symbol following '& in destructuring pattern"); | ||||
|                         return 1; | ||||
|                     } | ||||
|  | ||||
|                     if (i + 2 < len) { | ||||
|                         int32_t num_extra = len - i - 1; | ||||
|                         Janet *extra = janet_tuple_begin(num_extra); | ||||
|                         janet_tuple_flag(extra) |= JANET_TUPLE_FLAG_BRACKETCTOR; | ||||
|  | ||||
|                         for (int32_t j = 0; j < num_extra; ++j) { | ||||
|                             extra[j] = values[j + i + 1]; | ||||
|                         } | ||||
|  | ||||
|                         janetc_error(c, janet_formatc("expected a single symbol follow '& in destructuring pattern, found %q", janet_wrap_tuple(janet_tuple_end(extra)))); | ||||
|                         return 1; | ||||
|                     } | ||||
|  | ||||
|                     if (!janet_checktype(values[i + 1], JANET_SYMBOL)) { | ||||
|                         janetc_error(c, janet_formatc("expected symbol following '& in destructuring pattern, found %q", values[i + 1])); | ||||
|                         return 1; | ||||
|                     } | ||||
|  | ||||
|                     JanetSlot argi = janetc_farslot(c); | ||||
|                     JanetSlot arg  = janetc_farslot(c); | ||||
|                     JanetSlot len  = janetc_farslot(c); | ||||
|  | ||||
|                     janetc_emit_si(c, JOP_LOAD_INTEGER, argi, i, 0); | ||||
|                     janetc_emit_ss(c, JOP_LENGTH, len, right, 0); | ||||
|  | ||||
|                     /* loop condition - reuse arg slot for the condition result */ | ||||
|                     int32_t label_loop_start = janetc_emit_sss(c, JOP_LESS_THAN, arg, argi, len, 0); | ||||
|                     int32_t label_loop_cond_jump = janetc_emit_si(c, JOP_JUMP_IF_NOT, arg, 0, 0); | ||||
|  | ||||
|                     /* loop body */ | ||||
|                     janetc_emit_sss(c, JOP_GET, arg, right, argi, 0); | ||||
|                     janetc_emit_s(c, JOP_PUSH, arg, 0); | ||||
|                     janetc_emit_ssi(c, JOP_ADD_IMMEDIATE, argi, argi, 1, 0); | ||||
|  | ||||
|                     /* loop - jump back to the start of the loop */ | ||||
|                     int32_t label_loop_loop = janet_v_count(c->buffer); | ||||
|                     janetc_emit(c, JOP_JUMP); | ||||
|                     int32_t label_loop_exit = janet_v_count(c->buffer); | ||||
|  | ||||
|                     /* avoid shifting negative numbers */ | ||||
|                     c->buffer[label_loop_cond_jump] |= (uint32_t)(label_loop_exit - label_loop_cond_jump) << 16; | ||||
|                     c->buffer[label_loop_loop] |= (uint32_t)(label_loop_start - label_loop_loop) << 8; | ||||
|  | ||||
|                     janetc_freeslot(c, argi); | ||||
|                     janetc_freeslot(c, arg); | ||||
|                     janetc_freeslot(c, len); | ||||
|  | ||||
|                     janetc_emit_s(c, JOP_MAKE_TUPLE, nextright, 1); | ||||
|  | ||||
|                     leaf(c, janet_unwrap_symbol(values[i + 1]), nextright, attr); | ||||
|                     janetc_freeslot(c, nextright); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 if (i < 0x100) { | ||||
|                     janetc_emit_ssu(c, JOP_GET_INDEX, nextright, right, (uint8_t) i, 1); | ||||
|                 } else { | ||||
| @@ -194,7 +263,7 @@ static const Janet *janetc_make_sourcemap(JanetCompiler *c) { | ||||
|  | ||||
| static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     if (argn != 2) { | ||||
|         janetc_cerror(opts.compiler, "expected 2 arguments"); | ||||
|         janetc_cerror(opts.compiler, "expected 2 arguments to set"); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     JanetFopts subopts = janetc_fopts_default(opts.compiler); | ||||
| @@ -236,14 +305,24 @@ static JanetSlot janetc_varset(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
| } | ||||
|  | ||||
| /* Add attributes to a global def or var table */ | ||||
| static JanetTable *handleattr(JanetCompiler *c, int32_t argn, const Janet *argv) { | ||||
| static JanetTable *handleattr(JanetCompiler *c, const char *kind, int32_t argn, const Janet *argv) { | ||||
|     int32_t i; | ||||
|     JanetTable *tab = janet_table(2); | ||||
|     const char *binding_name = janet_type(argv[0]) == JANET_SYMBOL | ||||
|                                ? ((const char *)janet_unwrap_symbol(argv[0])) | ||||
|                                : "<multiple bindings>"; | ||||
|     if (argn < 2) { | ||||
|         janetc_error(c, janet_formatc("expected at least 2 arguments to %s", kind)); | ||||
|         return NULL; | ||||
|     } | ||||
|     for (i = 1; i < argn - 1; i++) { | ||||
|         Janet attr = argv[i]; | ||||
|         switch (janet_type(attr)) { | ||||
|             case JANET_TUPLE: | ||||
|                 janetc_cerror(c, "unexpected form - did you intend to use defn?"); | ||||
|                 break; | ||||
|             default: | ||||
|                 janetc_cerror(c, "could not add metadata to binding"); | ||||
|                 janetc_error(c, janet_formatc("cannot add metadata %v to binding %s", attr, binding_name)); | ||||
|                 break; | ||||
|             case JANET_KEYWORD: | ||||
|                 janet_table_put(tab, attr, janet_wrap_true()); | ||||
| @@ -259,18 +338,52 @@ static JanetTable *handleattr(JanetCompiler *c, int32_t argn, const Janet *argv) | ||||
|     return tab; | ||||
| } | ||||
|  | ||||
| static JanetSlot dohead(JanetCompiler *c, JanetFopts opts, Janet *head, int32_t argn, const Janet *argv) { | ||||
| typedef struct SlotHeadPair { | ||||
|     Janet lhs; | ||||
|     JanetSlot rhs; | ||||
| } SlotHeadPair; | ||||
|  | ||||
| SlotHeadPair *dohead_destructure(JanetCompiler *c, SlotHeadPair *into, JanetFopts opts, Janet lhs, Janet rhs) { | ||||
|  | ||||
|     /* Detect if we can do an optimization to avoid some allocations */ | ||||
|     int can_destructure_lhs = janet_checktype(lhs, JANET_TUPLE) | ||||
|                               || janet_checktype(lhs, JANET_ARRAY); | ||||
|     int rhs_is_indexed = janet_checktype(rhs, JANET_ARRAY) | ||||
|                          || (janet_checktype(rhs, JANET_TUPLE) && (janet_tuple_flag(janet_unwrap_tuple(rhs)) & JANET_TUPLE_FLAG_BRACKETCTOR)); | ||||
|     uint32_t has_drop = opts.flags & JANET_FOPTS_DROP; | ||||
|  | ||||
|     JanetFopts subopts = janetc_fopts_default(c); | ||||
|     JanetSlot ret; | ||||
|     if (argn < 2) { | ||||
|         janetc_cerror(c, "expected at least 2 arguments"); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     *head = argv[0]; | ||||
|     subopts.flags = opts.flags & ~(JANET_FOPTS_TAIL | JANET_FOPTS_DROP); | ||||
|  | ||||
|     if (has_drop && can_destructure_lhs && rhs_is_indexed) { | ||||
|         /* Code is of the form (def [a b] [1 2]), avoid the allocation of two tuples */ | ||||
|         JanetView view_lhs = {0}; | ||||
|         JanetView view_rhs = {0}; | ||||
|         janet_indexed_view(lhs, &view_lhs.items, &view_lhs.len); | ||||
|         janet_indexed_view(rhs, &view_rhs.items, &view_rhs.len); | ||||
|         int found_amp = 0; | ||||
|         for (int32_t i = 0; i < view_lhs.len; i++) { | ||||
|             if (janet_symeq(view_lhs.items[i], "&")) { | ||||
|                 found_amp = 1; | ||||
|                 /* Good error will be generated later. */ | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         if (!found_amp) { | ||||
|             for (int32_t i = 0; i < view_lhs.len; i++) { | ||||
|                 Janet sub_rhs = view_rhs.len <= i ? janet_wrap_nil() : view_rhs.items[i]; | ||||
|                 into = dohead_destructure(c, into, subopts, view_lhs.items[i], sub_rhs); | ||||
|             } | ||||
|             return into; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* No optimization, do the simple way */ | ||||
|     subopts.hint = opts.hint; | ||||
|     ret = janetc_value(subopts, argv[argn - 1]); | ||||
|     return ret; | ||||
|     JanetSlot ret = janetc_value(subopts, rhs); | ||||
|     SlotHeadPair shp = {lhs, ret}; | ||||
|     janet_v_push(into, shp); | ||||
|     return into; | ||||
| } | ||||
|  | ||||
| /* Def or var a symbol in a local scope */ | ||||
| @@ -278,7 +391,17 @@ static int namelocal(JanetCompiler *c, const uint8_t *head, int32_t flags, Janet | ||||
|     int isUnnamedRegister = !(ret.flags & JANET_SLOT_NAMED) && | ||||
|                             ret.index > 0 && | ||||
|                             ret.envindex >= 0; | ||||
|     if (!isUnnamedRegister) { | ||||
|     /* optimization for `(def x my-def)` - don't emit a movn/movf instruction, we can just alias my-def */ | ||||
|     /* TODO - implement optimization for `(def x my-var)` correctly as well w/ de-aliasing */ | ||||
|     int canAlias = !(flags & JANET_SLOT_MUTABLE) && | ||||
|                    !(ret.flags & JANET_SLOT_MUTABLE) && | ||||
|                    (ret.flags & JANET_SLOT_NAMED) && | ||||
|                    (ret.index >= 0) && | ||||
|                    (ret.envindex == -1); | ||||
|     if (canAlias) { | ||||
|         ret.flags &= ~JANET_SLOT_MUTABLE; | ||||
|         isUnnamedRegister = 1; /* don't free slot after use - is an alias for another slot */ | ||||
|     } else if (!isUnnamedRegister) { | ||||
|         /* Slot is not able to be named */ | ||||
|         JanetSlot localslot = janetc_farslot(c); | ||||
|         janetc_copy(c, localslot, ret); | ||||
| @@ -298,8 +421,20 @@ static int varleaf( | ||||
|         /* Global var, generate var */ | ||||
|         JanetSlot refslot; | ||||
|         JanetTable *entry = janet_table_clone(reftab); | ||||
|         JanetArray *ref = janet_array(1); | ||||
|         janet_array_push(ref, janet_wrap_nil()); | ||||
|  | ||||
|         Janet redef_kw = janet_ckeywordv("redef"); | ||||
|         int is_redef = janet_truthy(janet_table_get(c->env, redef_kw)); | ||||
|  | ||||
|         JanetArray *ref; | ||||
|         JanetBinding old_binding; | ||||
|         if (is_redef && (old_binding = janet_resolve_ext(c->env, sym), | ||||
|                          old_binding.type == JANET_BINDING_VAR)) { | ||||
|             ref = janet_unwrap_array(old_binding.value); | ||||
|         } else { | ||||
|             ref = janet_array(1); | ||||
|             janet_array_push(ref, janet_wrap_nil()); | ||||
|         } | ||||
|  | ||||
|         janet_table_put(entry, janet_ckeywordv("ref"), janet_wrap_array(ref)); | ||||
|         janet_table_put(entry, janet_ckeywordv("source-map"), | ||||
|                         janet_wrap_tuple(janetc_make_sourcemap(c))); | ||||
| @@ -314,11 +449,23 @@ static int varleaf( | ||||
|  | ||||
| static JanetSlot janetc_var(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     JanetCompiler *c = opts.compiler; | ||||
|     Janet head; | ||||
|     JanetSlot ret = dohead(c, opts, &head, argn, argv); | ||||
|     if (c->result.status == JANET_COMPILE_ERROR) | ||||
|     JanetTable *attr_table = handleattr(c, "var", argn, argv); | ||||
|     if (c->result.status == JANET_COMPILE_ERROR) { | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     destructure(c, argv[0], ret, varleaf, handleattr(c, argn, argv)); | ||||
|     } | ||||
|     SlotHeadPair *into = NULL; | ||||
|     into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]); | ||||
|     if (c->result.status == JANET_COMPILE_ERROR) { | ||||
|         janet_v_free(into); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     JanetSlot ret; | ||||
|     janet_assert(janet_v_count(into) > 0, "bad destructure"); | ||||
|     for (int32_t i = 0; i < janet_v_count(into); i++) { | ||||
|         destructure(c, into[i].lhs, into[i].rhs, varleaf, attr_table); | ||||
|         ret = into[i].rhs; | ||||
|     } | ||||
|     janet_v_free(into); | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| @@ -331,29 +478,78 @@ static int defleaf( | ||||
|         JanetTable *entry = janet_table_clone(tab); | ||||
|         janet_table_put(entry, janet_ckeywordv("source-map"), | ||||
|                         janet_wrap_tuple(janetc_make_sourcemap(c))); | ||||
|         JanetSlot valsym = janetc_cslot(janet_ckeywordv("value")); | ||||
|         JanetSlot tabslot = janetc_cslot(janet_wrap_table(entry)); | ||||
|  | ||||
|         Janet redef_kw = janet_ckeywordv("redef"); | ||||
|         int is_redef = janet_truthy(janet_table_get(c->env, redef_kw)); | ||||
|         if (is_redef) janet_table_put(entry, redef_kw, janet_wrap_true()); | ||||
|  | ||||
|         if (is_redef) { | ||||
|             JanetBinding binding = janet_resolve_ext(c->env, sym); | ||||
|             JanetArray *ref; | ||||
|             if (binding.type == JANET_BINDING_DYNAMIC_DEF || binding.type == JANET_BINDING_DYNAMIC_MACRO) { | ||||
|                 ref = janet_unwrap_array(binding.value); | ||||
|             } else { | ||||
|                 ref = janet_array(1); | ||||
|                 janet_array_push(ref, janet_wrap_nil()); | ||||
|             } | ||||
|             janet_table_put(entry, janet_ckeywordv("ref"), janet_wrap_array(ref)); | ||||
|             JanetSlot refslot = janetc_cslot(janet_wrap_array(ref)); | ||||
|             janetc_emit_ssu(c, JOP_PUT_INDEX, refslot, s, 0, 0); | ||||
|         } else { | ||||
|             JanetSlot valsym = janetc_cslot(janet_ckeywordv("value")); | ||||
|             JanetSlot tabslot = janetc_cslot(janet_wrap_table(entry)); | ||||
|             janetc_emit_sss(c, JOP_PUT, tabslot, valsym, s, 0); | ||||
|         } | ||||
|  | ||||
|         /* Add env entry to env */ | ||||
|         janet_table_put(c->env, janet_wrap_symbol(sym), janet_wrap_table(entry)); | ||||
|  | ||||
|         /* Put value in table when evaulated */ | ||||
|         janetc_emit_sss(c, JOP_PUT, tabslot, valsym, s, 0); | ||||
|     } | ||||
|     return namelocal(c, sym, 0, s); | ||||
| } | ||||
|  | ||||
| static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     JanetCompiler *c = opts.compiler; | ||||
|     Janet head; | ||||
|     opts.flags &= ~JANET_FOPTS_HINT; | ||||
|     JanetSlot ret = dohead(c, opts, &head, argn, argv); | ||||
|     if (c->result.status == JANET_COMPILE_ERROR) | ||||
|     JanetTable *attr_table = handleattr(c, "def", argn, argv); | ||||
|     if (c->result.status == JANET_COMPILE_ERROR) { | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     destructure(c, argv[0], ret, defleaf, handleattr(c, argn, argv)); | ||||
|     } | ||||
|     opts.flags &= ~JANET_FOPTS_HINT; | ||||
|     SlotHeadPair *into = NULL; | ||||
|     into = dohead_destructure(c, into, opts, argv[0], argv[argn - 1]); | ||||
|     if (c->result.status == JANET_COMPILE_ERROR) { | ||||
|         janet_v_free(into); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|     JanetSlot ret; | ||||
|     janet_assert(janet_v_count(into) > 0, "bad destructure"); | ||||
|     for (int32_t i = 0; i < janet_v_count(into); i++) { | ||||
|         destructure(c, into[i].lhs, into[i].rhs, defleaf, attr_table); | ||||
|         ret = into[i].rhs; | ||||
|     } | ||||
|     janet_v_free(into); | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
| /* Check if a form matches the pattern (= nil _) or (not= nil _) */ | ||||
| static int janetc_check_nil_form(Janet x, Janet *capture, uint32_t fun_tag) { | ||||
|     if (!janet_checktype(x, JANET_TUPLE)) return 0; | ||||
|     JanetTuple tup = janet_unwrap_tuple(x); | ||||
|     if (3 != janet_tuple_length(tup)) return 0; | ||||
|     Janet op1 = tup[0]; | ||||
|     if (!janet_checktype(op1, JANET_FUNCTION)) return 0; | ||||
|     JanetFunction *fun = janet_unwrap_function(op1); | ||||
|     uint32_t tag = fun->def->flags & JANET_FUNCDEF_FLAG_TAG; | ||||
|     if (tag != fun_tag) return 0; | ||||
|     if (janet_checktype(tup[1], JANET_NIL)) { | ||||
|         *capture = tup[2]; | ||||
|         return 1; | ||||
|     } else if (janet_checktype(tup[2], JANET_NIL)) { | ||||
|         *capture = tup[1]; | ||||
|         return 1; | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * :condition | ||||
|  * ... | ||||
| @@ -374,6 +570,7 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     JanetScope condscope, tempscope; | ||||
|     const int tail = opts.flags & JANET_FOPTS_TAIL; | ||||
|     const int drop = opts.flags & JANET_FOPTS_DROP; | ||||
|     uint8_t ifnjmp = JOP_JUMP_IF_NOT; | ||||
|  | ||||
|     if (argn < 2 || argn > 3) { | ||||
|         janetc_cerror(c, "expected 2 or 3 arguments to if"); | ||||
| @@ -387,6 +584,7 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     /* Get options */ | ||||
|     condopts = janetc_fopts_default(c); | ||||
|     bodyopts = opts; | ||||
|     bodyopts.flags &= ~JANET_FOPTS_ACCEPT_SPLICE; | ||||
|  | ||||
|     /* Set target for compilation */ | ||||
|     target = (drop || tail) | ||||
| @@ -395,12 +593,24 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|  | ||||
|     /* Compile condition */ | ||||
|     janetc_scope(&condscope, c, 0, "if"); | ||||
|     cond = janetc_value(condopts, argv[0]); | ||||
|  | ||||
|     Janet condform = argv[0]; | ||||
|     if (janetc_check_nil_form(condform, &condform, JANET_FUN_EQ)) { | ||||
|         ifnjmp = JOP_JUMP_IF_NOT_NIL; | ||||
|     } else if (janetc_check_nil_form(condform, &condform, JANET_FUN_NEQ)) { | ||||
|         ifnjmp = JOP_JUMP_IF_NIL; | ||||
|     } | ||||
|  | ||||
|     cond = janetc_value(condopts, condform); | ||||
|  | ||||
|     /* Check constant condition. */ | ||||
|     /* TODO: Use type info for more short circuits */ | ||||
|     if (cond.flags & JANET_SLOT_CONSTANT) { | ||||
|         if (!janet_truthy(cond.constant)) { | ||||
|         int swap_condition = 0; | ||||
|         if (ifnjmp == JOP_JUMP_IF_NOT && !janet_truthy(cond.constant)) swap_condition = 1; | ||||
|         if (ifnjmp == JOP_JUMP_IF_NIL && janet_checktype(cond.constant, JANET_NIL)) swap_condition = 1; | ||||
|         if (ifnjmp == JOP_JUMP_IF_NOT_NIL && !janet_checktype(cond.constant, JANET_NIL)) swap_condition = 1; | ||||
|         if (swap_condition) { | ||||
|             /* Swap the true and false bodies */ | ||||
|             Janet temp = falsebody; | ||||
|             falsebody = truebody; | ||||
| @@ -418,7 +628,7 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     } | ||||
|  | ||||
|     /* Compile jump to right */ | ||||
|     labeljr = janetc_emit_si(c, JOP_JUMP_IF_NOT, cond, 0, 0); | ||||
|     labeljr = janetc_emit_si(c, ifnjmp, cond, 0, 0); | ||||
|  | ||||
|     /* Condition left body */ | ||||
|     janetc_scope(&tempscope, c, 0, "if-true"); | ||||
| @@ -428,7 +638,7 @@ static JanetSlot janetc_if(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|  | ||||
|     /* Compile jump to done */ | ||||
|     labeljd = janet_v_count(c->buffer); | ||||
|     if (!tail) janetc_emit(c, JOP_JUMP); | ||||
|     if (!tail && !(drop && janet_checktype(falsebody, JANET_NIL))) janetc_emit(c, JOP_JUMP); | ||||
|  | ||||
|     /* Compile right body */ | ||||
|     labelr = janet_v_count(c->buffer); | ||||
| @@ -463,6 +673,7 @@ static JanetSlot janetc_do(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|             subopts.flags = JANET_FOPTS_DROP; | ||||
|         } else { | ||||
|             subopts = opts; | ||||
|             subopts.flags &= ~JANET_FOPTS_ACCEPT_SPLICE; | ||||
|         } | ||||
|         ret = janetc_value(subopts, argv[i]); | ||||
|         if (i != argn - 1) { | ||||
| @@ -473,7 +684,6 @@ static JanetSlot janetc_do(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
|  | ||||
| /* Compile an upscope form. Upscope forms execute their body sequentially and | ||||
|  * evaluate to the last expression in the body, but without lexical scope. */ | ||||
| static JanetSlot janetc_upscope(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
| @@ -486,6 +696,7 @@ static JanetSlot janetc_upscope(JanetFopts opts, int32_t argn, const Janet *argv | ||||
|             subopts.flags = JANET_FOPTS_DROP; | ||||
|         } else { | ||||
|             subopts = opts; | ||||
|             subopts.flags &= ~JANET_FOPTS_ACCEPT_SPLICE; | ||||
|         } | ||||
|         ret = janetc_value(subopts, argv[i]); | ||||
|         if (i != argn - 1) { | ||||
| @@ -538,9 +749,8 @@ static JanetSlot janetc_break(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|         if (!(scope->flags & JANET_SCOPE_WHILE) && argn) { | ||||
|             /* Closure body with return argument */ | ||||
|             subopts.flags |= JANET_FOPTS_TAIL; | ||||
|             JanetSlot ret = janetc_value(subopts, argv[0]); | ||||
|             ret.flags |= JANET_SLOT_RETURNED; | ||||
|             return ret; | ||||
|             janetc_value(subopts, argv[0]); | ||||
|             return janetc_cslot(janet_wrap_nil()); | ||||
|         } else { | ||||
|             /* while loop IIFE or no argument */ | ||||
|             if (argn) { | ||||
| @@ -548,9 +758,7 @@ static JanetSlot janetc_break(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|                 janetc_value(subopts, argv[0]); | ||||
|             } | ||||
|             janetc_emit(c, JOP_RETURN_NIL); | ||||
|             JanetSlot s = janetc_cslot(janet_wrap_nil()); | ||||
|             s.flags |= JANET_SLOT_RETURNED; | ||||
|             return s; | ||||
|             return janetc_cslot(janet_wrap_nil()); | ||||
|         } | ||||
|     } else { | ||||
|         if (argn) { | ||||
| @@ -563,20 +771,6 @@ static JanetSlot janetc_break(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* Check if a form matches the pattern (not= nil _) */ | ||||
| static int janetc_check_notnil_form(Janet x, Janet *capture) { | ||||
|     if (!janet_checktype(x, JANET_TUPLE)) return 0; | ||||
|     JanetTuple tup = janet_unwrap_tuple(x); | ||||
|     if (!janet_checktype(tup[0], JANET_FUNCTION)) return 0; | ||||
|     if (3 != janet_tuple_length(tup)) return 0; | ||||
|     JanetFunction *fun = janet_unwrap_function(tup[0]); | ||||
|     uint32_t tag = fun->def->flags & JANET_FUNCDEF_FLAG_TAG; | ||||
|     if (tag != JANET_FUN_NEQ) return 0; | ||||
|     if (!janet_checktype(tup[1], JANET_NIL)) return 0; | ||||
|     *capture = tup[2]; | ||||
|     return 1; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * :whiletop | ||||
|  * ... | ||||
| @@ -593,12 +787,13 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|     JanetScope tempscope; | ||||
|     int32_t labelwt, labeld, labeljt, labelc, i; | ||||
|     int infinite = 0; | ||||
|     int is_nil_form = 0; | ||||
|     int is_notnil_form = 0; | ||||
|     uint8_t ifjmp = JOP_JUMP_IF; | ||||
|     uint8_t ifnjmp = JOP_JUMP_IF_NOT; | ||||
|  | ||||
|     if (argn < 2) { | ||||
|         janetc_cerror(c, "expected at least 2 arguments"); | ||||
|     if (argn < 1) { | ||||
|         janetc_cerror(c, "expected at least 1 argument to while"); | ||||
|         return janetc_cslot(janet_wrap_nil()); | ||||
|     } | ||||
|  | ||||
| @@ -606,11 +801,16 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|  | ||||
|     janetc_scope(&tempscope, c, JANET_SCOPE_WHILE, "while"); | ||||
|  | ||||
|     /* Check for `(not= nil _)` in condition, and if so, use the | ||||
|     /* Check for `(= nil _)` or `(not= nil _)` in condition, and if so, use the | ||||
|      * jmpnl or jmpnn instructions. This let's us implement `(each ...)` | ||||
|      * more efficiently. */ | ||||
|     Janet condform = argv[0]; | ||||
|     if (janetc_check_notnil_form(condform, &condform)) { | ||||
|     if (janetc_check_nil_form(condform, &condform, JANET_FUN_EQ)) { | ||||
|         is_nil_form = 1; | ||||
|         ifjmp = JOP_JUMP_IF_NIL; | ||||
|         ifnjmp = JOP_JUMP_IF_NOT_NIL; | ||||
|     } | ||||
|     if (janetc_check_nil_form(condform, &condform, JANET_FUN_NEQ)) { | ||||
|         is_notnil_form = 1; | ||||
|         ifjmp = JOP_JUMP_IF_NOT_NIL; | ||||
|         ifnjmp = JOP_JUMP_IF_NIL; | ||||
| @@ -622,7 +822,9 @@ static JanetSlot janetc_while(JanetFopts opts, int32_t argn, const Janet *argv) | ||||
|     /* Check for constant condition */ | ||||
|     if (cond.flags & JANET_SLOT_CONSTANT) { | ||||
|         /* Loop never executes */ | ||||
|         int never_executes = is_notnil_form | ||||
|         int never_executes = is_nil_form | ||||
|                              ? !janet_checktype(cond.constant, JANET_NIL) | ||||
|                              : is_notnil_form | ||||
|                              ? janet_checktype(cond.constant, JANET_NIL) | ||||
|                              : !janet_truthy(cond.constant); | ||||
|         if (never_executes) { | ||||
| @@ -713,7 +915,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     JanetSlot ret; | ||||
|     Janet head; | ||||
|     JanetScope fnscope; | ||||
|     int32_t paramcount, argi, parami, arity, min_arity, max_arity, defindex, i; | ||||
|     int32_t paramcount, argi, parami, arity, min_arity = 0, max_arity, defindex, i; | ||||
|     JanetFopts subopts = janetc_fopts_default(c); | ||||
|     const Janet *params; | ||||
|     const char *errmsg = NULL; | ||||
| @@ -723,8 +925,10 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     int structarg = 0; | ||||
|     int allow_extra = 0; | ||||
|     int selfref = 0; | ||||
|     int hasname = 0; | ||||
|     int seenamp = 0; | ||||
|     int seenopt = 0; | ||||
|     int namedargs = 0; | ||||
|  | ||||
|     /* Begin function */ | ||||
|     c->scope->flags |= JANET_SCOPE_CLOSURE; | ||||
| @@ -740,6 +944,10 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     head = argv[0]; | ||||
|     if (janet_checktype(head, JANET_SYMBOL)) { | ||||
|         selfref = 1; | ||||
|         hasname = 1; | ||||
|         parami = 1; | ||||
|     } else if (janet_checktype(head, JANET_KEYWORD)) { | ||||
|         hasname = 1; | ||||
|         parami = 1; | ||||
|     } | ||||
|     if (parami >= argn || !janet_checktype(argv[parami], JANET_TUPLE)) { | ||||
| @@ -749,6 +957,9 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|  | ||||
|     /* Keep track of destructured parameters */ | ||||
|     JanetSlot *destructed_params = NULL; | ||||
|     JanetSlot *named_params = NULL; | ||||
|     JanetTable *named_table = NULL; | ||||
|     JanetSlot named_slot; | ||||
|  | ||||
|     /* Compile function parameters */ | ||||
|     params = janet_unwrap_tuple(argv[parami]); | ||||
| @@ -756,49 +967,75 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     arity = paramcount; | ||||
|     for (i = 0; i < paramcount; i++) { | ||||
|         Janet param = params[i]; | ||||
|         if (janet_checktype(param, JANET_SYMBOL)) { | ||||
|         if (namedargs) { | ||||
|             arity--; | ||||
|             if (!janet_checktype(param, JANET_SYMBOL)) { | ||||
|                 errmsg = "only named arguments can follow &named"; | ||||
|                 goto error; | ||||
|             } | ||||
|             Janet key = janet_wrap_keyword(janet_unwrap_symbol(param)); | ||||
|             janet_table_put(named_table, key, param); | ||||
|             janet_v_push(named_params, janetc_farslot(c)); | ||||
|         } else if (janet_checktype(param, JANET_SYMBOL)) { | ||||
|             /* Check for varargs and unfixed arity */ | ||||
|             if (!janet_cstrcmp(janet_unwrap_symbol(param), "&")) { | ||||
|                 if (seenamp) { | ||||
|                     errmsg = "& in unexpected location"; | ||||
|                     goto error; | ||||
|                 } else if (i == paramcount - 1) { | ||||
|                     allow_extra = 1; | ||||
|             const uint8_t *sym = janet_unwrap_symbol(param); | ||||
|             if (sym[0] == '&') { | ||||
|                 if (!janet_cstrcmp(sym, "&")) { | ||||
|                     if (seenamp) { | ||||
|                         errmsg = "& in unexpected location"; | ||||
|                         goto error; | ||||
|                     } else if (i == paramcount - 1) { | ||||
|                         allow_extra = 1; | ||||
|                         arity--; | ||||
|                     } else if (i == paramcount - 2) { | ||||
|                         vararg = 1; | ||||
|                         arity -= 2; | ||||
|                     } else { | ||||
|                         errmsg = "& in unexpected location"; | ||||
|                         goto error; | ||||
|                     } | ||||
|                     seenamp = 1; | ||||
|                 } else if (!janet_cstrcmp(sym, "&opt")) { | ||||
|                     if (seenopt) { | ||||
|                         errmsg = "only one &opt allowed"; | ||||
|                         goto error; | ||||
|                     } else if (i == paramcount - 1) { | ||||
|                         errmsg = "&opt cannot be last item in parameter list"; | ||||
|                         goto error; | ||||
|                     } | ||||
|                     min_arity = i; | ||||
|                     arity--; | ||||
|                 } else if (i == paramcount - 2) { | ||||
|                     vararg = 1; | ||||
|                     arity -= 2; | ||||
|                 } else { | ||||
|                     errmsg = "& in unexpected location"; | ||||
|                     goto error; | ||||
|                 } | ||||
|                 seenamp = 1; | ||||
|             } else if (!janet_cstrcmp(janet_unwrap_symbol(param), "&opt")) { | ||||
|                 if (seenopt) { | ||||
|                     errmsg = "only one &opt allowed"; | ||||
|                     goto error; | ||||
|                 } else if (i == paramcount - 1) { | ||||
|                     errmsg = "&opt cannot be last item in parameter list"; | ||||
|                     goto error; | ||||
|                 } | ||||
|                 min_arity = i; | ||||
|                 arity--; | ||||
|                 seenopt = 1; | ||||
|             } else if (!janet_cstrcmp(janet_unwrap_symbol(param), "&keys")) { | ||||
|                 if (seenamp) { | ||||
|                     errmsg = "&keys in unexpected location"; | ||||
|                     goto error; | ||||
|                 } else if (i == paramcount - 2) { | ||||
|                     seenopt = 1; | ||||
|                 } else if (!janet_cstrcmp(sym, "&keys")) { | ||||
|                     if (seenamp) { | ||||
|                         errmsg = "&keys in unexpected location"; | ||||
|                         goto error; | ||||
|                     } else if (i == paramcount - 2) { | ||||
|                         vararg = 1; | ||||
|                         structarg = 1; | ||||
|                         arity -= 2; | ||||
|                     } else { | ||||
|                         errmsg = "&keys in unexpected location"; | ||||
|                         goto error; | ||||
|                     } | ||||
|                     seenamp = 1; | ||||
|                 } else if (!janet_cstrcmp(sym, "&named")) { | ||||
|                     if (seenamp) { | ||||
|                         errmsg = "&named in unexpected location"; | ||||
|                         goto error; | ||||
|                     } | ||||
|                     vararg = 1; | ||||
|                     structarg = 1; | ||||
|                     arity -= 2; | ||||
|                     arity--; | ||||
|                     seenamp = 1; | ||||
|                     namedargs = 1; | ||||
|                     named_table = janet_table(10); | ||||
|                     named_slot = janetc_farslot(c); | ||||
|                 } else { | ||||
|                     errmsg = "&keys in unexpected location"; | ||||
|                     goto error; | ||||
|                     janetc_nameslot(c, sym, janetc_farslot(c)); | ||||
|                 } | ||||
|                 seenamp = 1; | ||||
|             } else { | ||||
|                 janetc_nameslot(c, janet_unwrap_symbol(param), janetc_farslot(c)); | ||||
|                 janetc_nameslot(c, sym, janetc_farslot(c)); | ||||
|             } | ||||
|         } else { | ||||
|             janet_v_push(destructed_params, janetc_farslot(c)); | ||||
| @@ -810,6 +1047,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     for (i = 0; i < paramcount; i++) { | ||||
|         Janet param = params[i]; | ||||
|         if (!janet_checktype(param, JANET_SYMBOL)) { | ||||
|             janet_assert(janet_v_count(destructed_params) > j, "out of bounds"); | ||||
|             JanetSlot reg = destructed_params[j++]; | ||||
|             destructure(c, param, reg, defleaf, NULL); | ||||
|             janetc_freeslot(c, reg); | ||||
| @@ -817,15 +1055,37 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     } | ||||
|     janet_v_free(destructed_params); | ||||
|  | ||||
|     /* Compile named arguments */ | ||||
|     if (namedargs) { | ||||
|         Janet param = janet_wrap_table(named_table); | ||||
|         destructure(c, param, named_slot, defleaf, NULL); | ||||
|         janetc_freeslot(c, named_slot); | ||||
|         janet_v_free(named_params); | ||||
|     } | ||||
|  | ||||
|     max_arity = (vararg || allow_extra) ? INT32_MAX : arity; | ||||
|     if (!seenopt) min_arity = arity; | ||||
|  | ||||
|     /* Check for self ref */ | ||||
|     /* Check for self ref (also avoid if arguments shadow own name) */ | ||||
|     if (selfref) { | ||||
|         JanetSlot slot = janetc_farslot(c); | ||||
|         slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION; | ||||
|         janetc_emit_s(c, JOP_LOAD_SELF, slot, 1); | ||||
|         janetc_nameslot(c, janet_unwrap_symbol(head), slot); | ||||
|         /* Check if the parameters shadow the function name. If so, don't | ||||
|          * emit JOP_LOAD_SELF and add a binding since that most users | ||||
|          * seem to expect that function parameters take precedence over the | ||||
|          * function name */ | ||||
|         const uint8_t *sym = janet_unwrap_symbol(head); | ||||
|         int32_t len = janet_v_count(c->scope->syms); | ||||
|         int found = 0; | ||||
|         for (int32_t i = 0; i < len; i++) { | ||||
|             if (c->scope->syms[i].sym == sym) { | ||||
|                 found = 1; | ||||
|             } | ||||
|         } | ||||
|         if (!found) { | ||||
|             JanetSlot slot = janetc_farslot(c); | ||||
|             slot.flags = JANET_SLOT_NAMED | JANET_FUNCTION; | ||||
|             janetc_emit_s(c, JOP_LOAD_SELF, slot, 1); | ||||
|             janetc_nameslot(c, sym, slot); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Compile function body */ | ||||
| @@ -848,7 +1108,7 @@ static JanetSlot janetc_fn(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     if (vararg) def->flags |= JANET_FUNCDEF_FLAG_VARARG; | ||||
|     if (structarg) def->flags |= JANET_FUNCDEF_FLAG_STRUCTARG; | ||||
|  | ||||
|     if (selfref) def->name = janet_unwrap_symbol(head); | ||||
|     if (hasname) def->name = janet_unwrap_symbol(head); /* Also correctly unwraps keyword */ | ||||
|     janet_def_addflags(def); | ||||
|     defindex = janetc_addfuncdef(c, def); | ||||
|  | ||||
| @@ -892,4 +1152,3 @@ const JanetSpecial *janetc_special(const uint8_t *name) { | ||||
|                sizeof(JanetSpecial), | ||||
|                name); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -24,6 +24,11 @@ | ||||
| #include "features.h" | ||||
| #include <janet.h> | ||||
| #include "state.h" | ||||
| #include "util.h" | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #endif | ||||
|  | ||||
| JANET_THREAD_LOCAL JanetVM janet_vm; | ||||
| @@ -53,9 +58,14 @@ void janet_vm_load(JanetVM *from) { | ||||
| } | ||||
|  | ||||
| /* Trigger suspension of the Janet vm by trying to | ||||
|  * exit the interpeter loop when convenient. You can optionally | ||||
|  * exit the interpreter loop when convenient. You can optionally | ||||
|  * use NULL to interrupt the current VM when convenient */ | ||||
| void janet_interpreter_interrupt(JanetVM *vm) { | ||||
|     vm = vm ? vm : &janet_vm; | ||||
|     vm->auto_suspend = 1; | ||||
|     janet_atomic_inc(&vm->auto_suspend); | ||||
| } | ||||
|  | ||||
| void janet_interpreter_interrupt_handled(JanetVM *vm) { | ||||
|     vm = vm ? vm : &janet_vm; | ||||
|     janet_atomic_dec(&vm->auto_suspend); | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -23,8 +23,15 @@ | ||||
| #ifndef JANET_STATE_H_defined | ||||
| #define JANET_STATE_H_defined | ||||
|  | ||||
| #include <janet.h> | ||||
| #include <stdint.h> | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| #ifndef JANET_WINDOWS | ||||
| #include <pthread.h> | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| typedef int64_t JanetTimestamp; | ||||
|  | ||||
| typedef struct JanetScratch { | ||||
| @@ -54,7 +61,7 @@ typedef struct { | ||||
|     int is_error; | ||||
| } JanetTimeout; | ||||
|  | ||||
| /* Registry table for C functions - containts metadata that can | ||||
| /* Registry table for C functions - contains metadata that can | ||||
|  * be looked up by cfunction pointer. All strings here are pointing to | ||||
|  * static memory not managed by Janet. */ | ||||
| typedef struct { | ||||
| @@ -82,10 +89,10 @@ struct JanetVM { | ||||
|  | ||||
|     /* If this flag is true, suspend on function calls and backwards jumps. | ||||
|      * When this occurs, this flag will be reset to 0. */ | ||||
|     int auto_suspend; | ||||
|     volatile JanetAtomicInt auto_suspend; | ||||
|  | ||||
|     /* The current running fiber on the current thread. | ||||
|      * Set and unset by janet_run. */ | ||||
|      * Set and unset by functions in vm.c */ | ||||
|     JanetFiber *fiber; | ||||
|     JanetFiber *root_fiber; | ||||
|  | ||||
| @@ -93,6 +100,7 @@ struct JanetVM { | ||||
|      * return point for panics. */ | ||||
|     jmp_buf *signal_buf; | ||||
|     Janet *return_reg; | ||||
|     int coerce_error; | ||||
|  | ||||
|     /* The global registry for c functions. Used to store meta-data | ||||
|      * along with otherwise bare c function pointers. */ | ||||
| @@ -101,7 +109,7 @@ struct JanetVM { | ||||
|     size_t registry_count; | ||||
|     int registry_dirty; | ||||
|  | ||||
|     /* Registry for abstract abstract types that can be marshalled. | ||||
|     /* Registry for abstract types that can be marshalled. | ||||
|      * We need this to look up the constructors when unmarshalling. */ | ||||
|     JanetTable *abstract_registry; | ||||
|  | ||||
| @@ -114,10 +122,12 @@ struct JanetVM { | ||||
|  | ||||
|     /* Garbage collection */ | ||||
|     void *blocks; | ||||
|     void *weak_blocks; | ||||
|     size_t gc_interval; | ||||
|     size_t next_collection; | ||||
|     size_t block_count; | ||||
|     int gc_suspend; | ||||
|     int gc_mark_phase; | ||||
|  | ||||
|     /* GC roots */ | ||||
|     Janet *roots; | ||||
| @@ -129,6 +139,9 @@ struct JanetVM { | ||||
|     size_t scratch_cap; | ||||
|     size_t scratch_len; | ||||
|  | ||||
|     /* Sandbox flags */ | ||||
|     uint32_t sandbox_flags; | ||||
|  | ||||
|     /* Random number generator */ | ||||
|     JanetRNG rng; | ||||
|  | ||||
| @@ -137,6 +150,11 @@ struct JanetVM { | ||||
|     JanetTraversalNode *traversal_top; | ||||
|     JanetTraversalNode *traversal_base; | ||||
|  | ||||
|     /* Thread safe strerror error buffer - for janet_strerror */ | ||||
| #ifndef JANET_WINDOWS | ||||
|     char strerror_buf[256]; | ||||
| #endif | ||||
|  | ||||
|     /* Event loop and scheduler globals */ | ||||
| #ifdef JANET_EV | ||||
|     size_t tq_count; | ||||
| @@ -144,24 +162,29 @@ struct JanetVM { | ||||
|     JanetQueue spawn; | ||||
|     JanetTimeout *tq; | ||||
|     JanetRNG ev_rng; | ||||
|     JanetListenerState **listeners; | ||||
|     size_t listener_count; | ||||
|     size_t listener_cap; | ||||
|     size_t extra_listeners; | ||||
|     volatile JanetAtomicInt listener_count; /* used in signal handler, must be volatile */ | ||||
|     JanetTable threaded_abstracts; /* All abstract types that can be shared between threads (used in this thread) */ | ||||
|     JanetTable active_tasks; /* All possibly live task fibers - used just for tracking */ | ||||
|     JanetTable signal_handlers; | ||||
| #ifdef JANET_WINDOWS | ||||
|     void **iocp; | ||||
| #elif defined(JANET_EV_EPOLL) | ||||
|     pthread_attr_t new_thread_attr; | ||||
|     JanetHandle selfpipe[2]; | ||||
|     int epoll; | ||||
|     int timerfd; | ||||
|     int timer_enabled; | ||||
| #elif defined(JANET_EV_KQUEUE) | ||||
|     pthread_attr_t new_thread_attr; | ||||
|     JanetHandle selfpipe[2]; | ||||
|     int kq; | ||||
|     int timer; | ||||
|     int timer_enabled; | ||||
| #else | ||||
|     JanetStream **streams; | ||||
|     size_t stream_count; | ||||
|     size_t stream_capacity; | ||||
|     pthread_attr_t new_thread_attr; | ||||
|     JanetHandle selfpipe[2]; | ||||
|     struct pollfd *fds; | ||||
| #endif | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -71,10 +71,10 @@ int janet_string_compare(const uint8_t *lhs, const uint8_t *rhs) { | ||||
| int janet_string_equalconst(const uint8_t *lhs, const uint8_t *rhs, int32_t rlen, int32_t rhash) { | ||||
|     int32_t lhash = janet_string_hash(lhs); | ||||
|     int32_t llen = janet_string_length(lhs); | ||||
|     if (lhs == rhs) | ||||
|         return 1; | ||||
|     if (lhash != rhash || llen != rlen) | ||||
|         return 0; | ||||
|     if (lhs == rhs) | ||||
|         return 1; | ||||
|     return !memcmp(lhs, rhs, rlen); | ||||
| } | ||||
|  | ||||
| @@ -173,10 +173,11 @@ static int32_t kmp_next(struct kmp_state *state) { | ||||
| JANET_CORE_FN(cfun_string_slice, | ||||
|               "(string/slice bytes &opt start end)", | ||||
|               "Returns a substring from a byte sequence. The substring is from " | ||||
|               "index start inclusive to index end exclusive. All indexing " | ||||
|               "is from 0. 'start' and 'end' can also be negative to indicate indexing " | ||||
|               "from the end of the string. Note that index -1 is synonymous with " | ||||
|               "index (length bytes) to allow a full negative slice range. ") { | ||||
|               "index `start` inclusive to index `end`, exclusive. All indexing " | ||||
|               "is from 0. `start` and `end` can also be negative to indicate indexing " | ||||
|               "from the end of the string. Note that if `start` is negative it is " | ||||
|               "exclusive, and if `end` is negative it is inclusive, to allow a full " | ||||
|               "negative slice range.") { | ||||
|     JanetByteView view = janet_getbytes(argv, 0); | ||||
|     JanetRange range = janet_getslice(argc, argv); | ||||
|     return janet_stringv(view.bytes + range.start, range.end - range.start); | ||||
| @@ -184,7 +185,7 @@ JANET_CORE_FN(cfun_string_slice, | ||||
|  | ||||
| JANET_CORE_FN(cfun_symbol_slice, | ||||
|               "(symbol/slice bytes &opt start end)", | ||||
|               "Same a string/slice, but returns a symbol.") { | ||||
|               "Same as string/slice, but returns a symbol.") { | ||||
|     JanetByteView view = janet_getbytes(argv, 0); | ||||
|     JanetRange range = janet_getslice(argc, argv); | ||||
|     return janet_symbolv(view.bytes + range.start, range.end - range.start); | ||||
| @@ -192,7 +193,7 @@ JANET_CORE_FN(cfun_symbol_slice, | ||||
|  | ||||
| JANET_CORE_FN(cfun_keyword_slice, | ||||
|               "(keyword/slice bytes &opt start end)", | ||||
|               "Same a string/slice, but returns a keyword.") { | ||||
|               "Same as string/slice, but returns a keyword.") { | ||||
|     JanetByteView view = janet_getbytes(argv, 0); | ||||
|     JanetRange range = janet_getslice(argc, argv); | ||||
|     return janet_keywordv(view.bytes + range.start, range.end - range.start); | ||||
| @@ -200,7 +201,7 @@ JANET_CORE_FN(cfun_keyword_slice, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_repeat, | ||||
|               "(string/repeat bytes n)", | ||||
|               "Returns a string that is n copies of bytes concatenated.") { | ||||
|               "Returns a string that is `n` copies of `bytes` concatenated.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetByteView view = janet_getbytes(argv, 0); | ||||
|     int32_t rep = janet_getinteger(argv, 1); | ||||
| @@ -218,7 +219,7 @@ JANET_CORE_FN(cfun_string_repeat, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_bytes, | ||||
|               "(string/bytes str)", | ||||
|               "Returns an array of integers that are the byte values of the string.") { | ||||
|               "Returns a tuple of integers that are the byte values of the string.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetByteView view = janet_getbytes(argv, 0); | ||||
|     Janet *tup = janet_tuple_begin(view.len); | ||||
| @@ -282,7 +283,7 @@ JANET_CORE_FN(cfun_string_asciiupper, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_reverse, | ||||
|               "(string/reverse str)", | ||||
|               "Returns a string that is the reversed version of str.") { | ||||
|               "Returns a string that is the reversed version of `str`.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetByteView view = janet_getbytes(argv, 0); | ||||
|     uint8_t *buf = janet_string_begin(view.len); | ||||
| @@ -308,8 +309,8 @@ static void findsetup(int32_t argc, Janet *argv, struct kmp_state *s, int32_t ex | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_find, | ||||
|               "(string/find patt str &opt start-index)", | ||||
|               "Searches for the first instance of pattern patt in string " | ||||
|               "str. Returns the index of the first character in patt if found, " | ||||
|               "Searches for the first instance of pattern `patt` in string " | ||||
|               "`str`. Returns the index of the first character in `patt` if found, " | ||||
|               "otherwise returns nil.") { | ||||
|     int32_t result; | ||||
|     struct kmp_state state; | ||||
| @@ -323,7 +324,7 @@ JANET_CORE_FN(cfun_string_find, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_hasprefix, | ||||
|               "(string/has-prefix? pfx str)", | ||||
|               "Tests whether str starts with pfx.") { | ||||
|               "Tests whether `str` starts with `pfx`.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetByteView prefix = janet_getbytes(argv, 0); | ||||
|     JanetByteView str = janet_getbytes(argv, 1); | ||||
| @@ -334,7 +335,7 @@ JANET_CORE_FN(cfun_string_hasprefix, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_hassuffix, | ||||
|               "(string/has-suffix? sfx str)", | ||||
|               "Tests whether str ends with sfx.") { | ||||
|               "Tests whether `str` ends with `sfx`.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetByteView suffix = janet_getbytes(argv, 0); | ||||
|     JanetByteView str = janet_getbytes(argv, 1); | ||||
| @@ -347,9 +348,9 @@ JANET_CORE_FN(cfun_string_hassuffix, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_findall, | ||||
|               "(string/find-all patt str &opt start-index)", | ||||
|               "Searches for all instances of pattern patt in string " | ||||
|               "str. Returns an array of all indices of found patterns. Overlapping " | ||||
|               "instances of the pattern are counted individually, meaning a byte in str " | ||||
|               "Searches for all instances of pattern `patt` in string " | ||||
|               "`str`. Returns an array of all indices of found patterns. Overlapping " | ||||
|               "instances of the pattern are counted individually, meaning a byte in `str` " | ||||
|               "may contribute to multiple found patterns.") { | ||||
|     int32_t result; | ||||
|     struct kmp_state state; | ||||
| @@ -364,14 +365,13 @@ JANET_CORE_FN(cfun_string_findall, | ||||
|  | ||||
| struct replace_state { | ||||
|     struct kmp_state kmp; | ||||
|     const uint8_t *subst; | ||||
|     int32_t substlen; | ||||
|     Janet subst; | ||||
| }; | ||||
|  | ||||
| static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) { | ||||
|     janet_arity(argc, 3, 4); | ||||
|     JanetByteView pat = janet_getbytes(argv, 0); | ||||
|     JanetByteView subst = janet_getbytes(argv, 1); | ||||
|     Janet subst = argv[1]; | ||||
|     JanetByteView text = janet_getbytes(argv, 2); | ||||
|     int32_t start = 0; | ||||
|     if (argc == 4) { | ||||
| @@ -380,14 +380,15 @@ static void replacesetup(int32_t argc, Janet *argv, struct replace_state *s) { | ||||
|     } | ||||
|     kmp_init(&s->kmp, text.bytes, text.len, pat.bytes, pat.len); | ||||
|     s->kmp.i = start; | ||||
|     s->subst = subst.bytes; | ||||
|     s->substlen = subst.len; | ||||
|     s->subst = subst; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_replace, | ||||
|               "(string/replace patt subst str)", | ||||
|               "Replace the first occurrence of patt with subst in the string str. " | ||||
|               "Will return the new string if patt is found, otherwise returns str.") { | ||||
|               "Replace the first occurrence of `patt` with `subst` in the string `str`. " | ||||
|               "If `subst` is a function, it will be called with `patt` only if a match is found, " | ||||
|               "and should return the actual replacement text to use. " | ||||
|               "Will return the new string if `patt` is found, otherwise returns `str`.") { | ||||
|     int32_t result; | ||||
|     struct replace_state s; | ||||
|     uint8_t *buf; | ||||
| @@ -397,10 +398,11 @@ JANET_CORE_FN(cfun_string_replace, | ||||
|         kmp_deinit(&s.kmp); | ||||
|         return janet_stringv(s.kmp.text, s.kmp.textlen); | ||||
|     } | ||||
|     buf = janet_string_begin(s.kmp.textlen - s.kmp.patlen + s.substlen); | ||||
|     JanetByteView subst = janet_text_substitution(&s.subst, s.kmp.text + result, s.kmp.patlen, NULL); | ||||
|     buf = janet_string_begin(s.kmp.textlen - s.kmp.patlen + subst.len); | ||||
|     safe_memcpy(buf, s.kmp.text, result); | ||||
|     safe_memcpy(buf + result, s.subst, s.substlen); | ||||
|     safe_memcpy(buf + result + s.substlen, | ||||
|     safe_memcpy(buf + result, subst.bytes, subst.len); | ||||
|     safe_memcpy(buf + result + subst.len, | ||||
|                 s.kmp.text + result + s.kmp.patlen, | ||||
|                 s.kmp.textlen - result - s.kmp.patlen); | ||||
|     kmp_deinit(&s.kmp); | ||||
| @@ -409,9 +411,11 @@ JANET_CORE_FN(cfun_string_replace, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_replaceall, | ||||
|               "(string/replace-all patt subst str)", | ||||
|               "Replace all instances of patt with subst in the string str. Overlapping " | ||||
|               "Replace all instances of `patt` with `subst` in the string `str`. Overlapping " | ||||
|               "matches will not be counted, only the first match in such a span will be replaced. " | ||||
|               "Will return the new string if patt is found, otherwise returns str.") { | ||||
|               "If `subst` is a function, it will be called with `patt` once for each match, " | ||||
|               "and should return the actual replacement text to use. " | ||||
|               "Will return the new string if `patt` is found, otherwise returns `str`.") { | ||||
|     int32_t result; | ||||
|     struct replace_state s; | ||||
|     JanetBuffer b; | ||||
| @@ -419,8 +423,9 @@ JANET_CORE_FN(cfun_string_replaceall, | ||||
|     replacesetup(argc, argv, &s); | ||||
|     janet_buffer_init(&b, s.kmp.textlen); | ||||
|     while ((result = kmp_next(&s.kmp)) >= 0) { | ||||
|         JanetByteView subst = janet_text_substitution(&s.subst, s.kmp.text + result, s.kmp.patlen, NULL); | ||||
|         janet_buffer_push_bytes(&b, s.kmp.text + lastindex, result - lastindex); | ||||
|         janet_buffer_push_bytes(&b, s.subst, s.substlen); | ||||
|         janet_buffer_push_bytes(&b, subst.bytes, subst.len); | ||||
|         lastindex = result + s.kmp.patlen; | ||||
|         kmp_seti(&s.kmp, lastindex); | ||||
|     } | ||||
| @@ -433,11 +438,11 @@ JANET_CORE_FN(cfun_string_replaceall, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_split, | ||||
|               "(string/split delim str &opt start limit)", | ||||
|               "Splits a string str with delimiter delim and returns an array of " | ||||
|               "substrings. The substrings will not contain the delimiter delim. If delim " | ||||
|               "Splits a string `str` with delimiter `delim` and returns an array of " | ||||
|               "substrings. The substrings will not contain the delimiter `delim`. If `delim` " | ||||
|               "is not found, the returned array will have one element. Will start searching " | ||||
|               "for delim at the index start (if provided), and return up to a maximum " | ||||
|               "of limit results (if provided).") { | ||||
|               "for `delim` at the index `start` (if provided), and return up to a maximum " | ||||
|               "of `limit` results (if provided).") { | ||||
|     int32_t result; | ||||
|     JanetArray *array; | ||||
|     struct kmp_state state; | ||||
| @@ -461,9 +466,9 @@ JANET_CORE_FN(cfun_string_split, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_checkset, | ||||
|               "(string/check-set set str)", | ||||
|               "Checks that the string str only contains bytes that appear in the string set. " | ||||
|               "Returns true if all bytes in str appear in set, false if some bytes in str do " | ||||
|               "not appear in set.") { | ||||
|               "Checks that the string `str` only contains bytes that appear in the string `set`. " | ||||
|               "Returns true if all bytes in `str` appear in `set`, false if some bytes in `str` do " | ||||
|               "not appear in `set`.") { | ||||
|     uint32_t bitset[8] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetByteView set = janet_getbytes(argv, 0); | ||||
| @@ -488,7 +493,7 @@ JANET_CORE_FN(cfun_string_checkset, | ||||
| JANET_CORE_FN(cfun_string_join, | ||||
|               "(string/join parts &opt sep)", | ||||
|               "Joins an array of strings into one string, optionally separated by " | ||||
|               "a separator string sep.") { | ||||
|               "a separator string `sep`.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     JanetView parts = janet_getindexed(argv, 0); | ||||
|     JanetByteView joiner; | ||||
| @@ -530,8 +535,31 @@ JANET_CORE_FN(cfun_string_join, | ||||
|  | ||||
| JANET_CORE_FN(cfun_string_format, | ||||
|               "(string/format format & values)", | ||||
|               "Similar to snprintf, but specialized for operating with Janet values. Returns " | ||||
|               "a new string.") { | ||||
|               "Similar to C's `snprintf`, but specialized for operating with Janet values. Returns " | ||||
|               "a new string.\n\n" | ||||
|               "The following conversion specifiers are supported, where the upper case specifiers generate " | ||||
|               "upper case output:\n" | ||||
|               "- `c`: ASCII character.\n" | ||||
|               "- `d`, `i`: integer, formatted as a decimal number.\n" | ||||
|               "- `x`, `X`: integer, formatted as a hexadecimal number.\n" | ||||
|               "- `o`: integer, formatted as an octal number.\n" | ||||
|               "- `f`, `F`: floating point number, formatted as a decimal number.\n" | ||||
|               "- `e`, `E`: floating point number, formatted in scientific notation.\n" | ||||
|               "- `g`, `G`: floating point number, formatted in its shortest form.\n" | ||||
|               "- `a`, `A`: floating point number, formatted as a hexadecimal number.\n" | ||||
|               "- `s`: formatted as a string, precision indicates padding and maximum length.\n" | ||||
|               "- `t`: emit the type of the given value.\n" | ||||
|               "- `v`: format with (describe x)\n" | ||||
|               "- `V`: format with (string x)\n" | ||||
|               "- `j`: format to jdn (Janet data notation).\n" | ||||
|               "\n" | ||||
|               "The following conversion specifiers are used for \"pretty-printing\", where the upper-case " | ||||
|               "variants generate colored output. These specifiers can take a precision " | ||||
|               "argument to specify the maximum nesting depth to print.\n" | ||||
|               "- `p`, `P`: pretty format, truncating if necessary\n" | ||||
|               "- `m`, `M`: pretty format without truncating.\n" | ||||
|               "- `q`, `Q`: pretty format on one line, truncating if necessary.\n" | ||||
|               "- `n`, `N`: pretty format on one line without truncation.\n") { | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetBuffer *buffer = janet_buffer(0); | ||||
|     const char *strfrmt = (const char *) janet_getstring(argv, 0); | ||||
| @@ -574,7 +602,7 @@ static void trim_help_args(int32_t argc, Janet *argv, JanetByteView *str, JanetB | ||||
| JANET_CORE_FN(cfun_string_trim, | ||||
|               "(string/trim str &opt set)", | ||||
|               "Trim leading and trailing whitespace from a byte sequence. If the argument " | ||||
|               "set is provided, consider only characters in set to be whitespace.") { | ||||
|               "`set` is provided, consider only characters in `set` to be whitespace.") { | ||||
|     JanetByteView str, set; | ||||
|     trim_help_args(argc, argv, &str, &set); | ||||
|     int32_t left_edge = trim_help_leftedge(str, set); | ||||
| @@ -587,7 +615,7 @@ JANET_CORE_FN(cfun_string_trim, | ||||
| JANET_CORE_FN(cfun_string_triml, | ||||
|               "(string/triml str &opt set)", | ||||
|               "Trim leading whitespace from a byte sequence. If the argument " | ||||
|               "set is provided, consider only characters in set to be whitespace.") { | ||||
|               "`set` is provided, consider only characters in `set` to be whitespace.") { | ||||
|     JanetByteView str, set; | ||||
|     trim_help_args(argc, argv, &str, &set); | ||||
|     int32_t left_edge = trim_help_leftedge(str, set); | ||||
| @@ -597,7 +625,7 @@ JANET_CORE_FN(cfun_string_triml, | ||||
| JANET_CORE_FN(cfun_string_trimr, | ||||
|               "(string/trimr str &opt set)", | ||||
|               "Trim trailing whitespace from a byte sequence. If the argument " | ||||
|               "set is provided, consider only characters in set to be whitespace.") { | ||||
|               "`set` is provided, consider only characters in `set` to be whitespace.") { | ||||
|     JanetByteView str, set; | ||||
|     trim_help_args(argc, argv, &str, &set); | ||||
|     int32_t right_edge = trim_help_rightedge(str, set); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -34,9 +34,9 @@ | ||||
|  * because E is a valid digit in bases 15 or greater. For bases greater than | ||||
|  * 10, the letters are used as digits. A through Z correspond to the digits 10 | ||||
|  * through 35, and the lowercase letters have the same values. The radix number | ||||
|  * is always in base 10. For example, a hexidecimal number could be written | ||||
|  * is always in base 10. For example, a hexadecimal number could be written | ||||
|  * '16rdeadbeef'. janet_scan_number also supports some c style syntax for | ||||
|  * hexidecimal literals. The previous number could also be written | ||||
|  * hexadecimal literals. The previous number could also be written | ||||
|  * '0xdeadbeef'. | ||||
|  */ | ||||
|  | ||||
| @@ -301,6 +301,7 @@ int janet_scan_number_base( | ||||
|     if (base == 0) { | ||||
|         base = 10; | ||||
|     } | ||||
|     int exp_base = base; | ||||
|  | ||||
|     /* Skip leading zeros */ | ||||
|     while (str < end && (*str == '0' || *str == '.')) { | ||||
| @@ -322,6 +323,12 @@ int janet_scan_number_base( | ||||
|         } else if (*str == '&') { | ||||
|             foundexp = 1; | ||||
|             break; | ||||
|         } else if (base == 16 && (*str == 'P' || *str == 'p')) { /* IEEE hex float */ | ||||
|             foundexp = 1; | ||||
|             exp_base = 10; | ||||
|             base = 2; | ||||
|             ex *= 4; /* We need to correct the current exponent after we change the base */ | ||||
|             break; | ||||
|         } else if (base == 10 && (*str == 'E' || *str == 'e')) { | ||||
|             foundexp = 1; | ||||
|             break; | ||||
| @@ -360,9 +367,9 @@ int janet_scan_number_base( | ||||
|         } | ||||
|         while (str < end) { | ||||
|             int digit = digit_lookup[*str & 0x7F]; | ||||
|             if (*str > 127 || digit >= base) goto error; | ||||
|             if (*str > 127 || digit >= exp_base) goto error; | ||||
|             if (ee < (INT32_MAX / 40)) { | ||||
|                 ee = base * ee + digit; | ||||
|                 ee = exp_base * ee + digit; | ||||
|             } | ||||
|             str++; | ||||
|             seenadigit = 1; | ||||
| @@ -489,4 +496,53 @@ int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out) { | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* Similar to janet_scan_number but allows for | ||||
|  * more numeric types with a given suffix. */ | ||||
| int janet_scan_numeric( | ||||
|     const uint8_t *str, | ||||
|     int32_t len, | ||||
|     Janet *out) { | ||||
|     int result; | ||||
|     double num; | ||||
|     int64_t i64 = 0; | ||||
|     uint64_t u64 = 0; | ||||
|     if (len < 2 || str[len - 2] != ':') { | ||||
|         result = janet_scan_number_base(str, len, 0, &num); | ||||
|         *out = janet_wrap_number(num); | ||||
|         return result; | ||||
|     } | ||||
|     switch (str[len - 1]) { | ||||
|         default: | ||||
|             return 1; | ||||
|         case 'n': | ||||
|             result = janet_scan_number_base(str, len - 2, 0, &num); | ||||
|             *out = janet_wrap_number(num); | ||||
|             return result; | ||||
|         /* Condition is inverted janet_scan_int64 and janet_scan_uint64 */ | ||||
|         case 's': | ||||
|             result = !janet_scan_int64(str, len - 2, &i64); | ||||
|             *out = janet_wrap_s64(i64); | ||||
|             return result; | ||||
|         case 'u': | ||||
|             result = !janet_scan_uint64(str, len - 2, &u64); | ||||
|             *out = janet_wrap_u64(u64); | ||||
|             return result; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| void janet_buffer_dtostr(JanetBuffer *buffer, double x) { | ||||
| #define BUFSIZE 32 | ||||
|     janet_buffer_extra(buffer, BUFSIZE); | ||||
|     int count = snprintf((char *) buffer->data + buffer->count, BUFSIZE, "%.17g", x); | ||||
| #undef BUFSIZE | ||||
|     /* fix locale issues with commas */ | ||||
|     for (int i = 0; i < count; i++) { | ||||
|         char c = buffer->data[buffer->count + i]; | ||||
|         if (c == ',') { | ||||
|             buffer->data[buffer->count + i] = '.'; | ||||
|         } | ||||
|     } | ||||
|     buffer->count += count; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -39,13 +39,14 @@ JanetKV *janet_struct_begin(int32_t count) { | ||||
|     head->length = count; | ||||
|     head->capacity = capacity; | ||||
|     head->hash = 0; | ||||
|     head->proto = NULL; | ||||
|  | ||||
|     JanetKV *st = (JanetKV *)(head->data); | ||||
|     janet_memempty(st, capacity); | ||||
|     return st; | ||||
| } | ||||
|  | ||||
| /* Find an item in a struct. Should be similar to janet_dict_find, but | ||||
| /* Find an item in a struct without looking for prototypes. Should be similar to janet_dict_find, but | ||||
|  * specialized to structs (slightly more compact). */ | ||||
| const JanetKV *janet_struct_find(const JanetKV *st, Janet key) { | ||||
|     int32_t cap = janet_struct_capacity(st); | ||||
| @@ -68,7 +69,7 @@ const JanetKV *janet_struct_find(const JanetKV *st, Janet key) { | ||||
|  * preforms an in-place insertion sort. This ensures the internal structure of the | ||||
|  * hash map is independent of insertion order. | ||||
|  */ | ||||
| void janet_struct_put(JanetKV *st, Janet key, Janet value) { | ||||
| void janet_struct_put_ext(JanetKV *st, Janet key, Janet value, int replace) { | ||||
|     int32_t cap = janet_struct_capacity(st); | ||||
|     int32_t hash = janet_hash(key); | ||||
|     int32_t index = janet_maphash(cap, hash); | ||||
| @@ -123,13 +124,19 @@ void janet_struct_put(JanetKV *st, Janet key, Janet value) { | ||||
|                 dist = otherdist; | ||||
|                 hash = otherhash; | ||||
|             } else if (status == 0) { | ||||
|                 /* A key was added to the struct more than once - replace old value */ | ||||
|                 kv->value = value; | ||||
|                 if (replace) { | ||||
|                     /* A key was added to the struct more than once - replace old value */ | ||||
|                     kv->value = value; | ||||
|                 } | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
| } | ||||
|  | ||||
| void janet_struct_put(JanetKV *st, Janet key, Janet value) { | ||||
|     janet_struct_put_ext(st, key, value, 1); | ||||
| } | ||||
|  | ||||
| /* Finish building a struct */ | ||||
| const JanetKV *janet_struct_end(JanetKV *st) { | ||||
|     if (janet_struct_hash(st) != janet_struct_length(st)) { | ||||
| @@ -143,16 +150,43 @@ const JanetKV *janet_struct_end(JanetKV *st) { | ||||
|                 janet_struct_put(newst, kv->key, kv->value); | ||||
|             } | ||||
|         } | ||||
|         janet_struct_proto(newst) = janet_struct_proto(st); | ||||
|         st = newst; | ||||
|     } | ||||
|     janet_struct_hash(st) = janet_kv_calchash(st, janet_struct_capacity(st)); | ||||
|     if (janet_struct_proto(st)) { | ||||
|         janet_struct_hash(st) += 2654435761u * janet_struct_hash(janet_struct_proto(st)); | ||||
|     } | ||||
|     return (const JanetKV *)st; | ||||
| } | ||||
|  | ||||
| /* Get an item from a struct without looking into prototypes. */ | ||||
| Janet janet_struct_rawget(const JanetKV *st, Janet key) { | ||||
|     const JanetKV *kv = janet_struct_find(st, key); | ||||
|     return kv ? kv->value : janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| /* Get an item from a struct */ | ||||
| Janet janet_struct_get(const JanetKV *st, Janet key) { | ||||
|     const JanetKV *kv = janet_struct_find(st, key); | ||||
|     return kv ? kv->value : janet_wrap_nil(); | ||||
|     for (int i = JANET_MAX_PROTO_DEPTH; st && i; --i, st = janet_struct_proto(st)) { | ||||
|         const JanetKV *kv = janet_struct_find(st, key); | ||||
|         if (NULL != kv && !janet_checktype(kv->key, JANET_NIL)) { | ||||
|             return kv->value; | ||||
|         } | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| /* Get an item from a struct, and record which prototype the item came from. */ | ||||
| Janet janet_struct_get_ex(const JanetKV *st, Janet key, JanetStruct *which) { | ||||
|     for (int i = JANET_MAX_PROTO_DEPTH; st && i; --i, st = janet_struct_proto(st)) { | ||||
|         const JanetKV *kv = janet_struct_find(st, key); | ||||
|         if (NULL != kv && !janet_checktype(kv->key, JANET_NIL)) { | ||||
|             *which = st; | ||||
|             return kv->value; | ||||
|         } | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| /* Convert struct to table */ | ||||
| @@ -167,3 +201,118 @@ JanetTable *janet_struct_to_table(const JanetKV *st) { | ||||
|     } | ||||
|     return table; | ||||
| } | ||||
|  | ||||
| /* C Functions */ | ||||
|  | ||||
| JANET_CORE_FN(cfun_struct_with_proto, | ||||
|               "(struct/with-proto proto & kvs)", | ||||
|               "Create a structure, as with the usual struct constructor but set the " | ||||
|               "struct prototype as well.") { | ||||
|     janet_arity(argc, 1, -1); | ||||
|     JanetStruct proto = janet_optstruct(argv, argc, 0, NULL); | ||||
|     if (!(argc & 1)) | ||||
|         janet_panic("expected odd number of arguments"); | ||||
|     JanetKV *st = janet_struct_begin(argc / 2); | ||||
|     for (int32_t i = 1; i < argc; i += 2) { | ||||
|         janet_struct_put(st, argv[i], argv[i + 1]); | ||||
|     } | ||||
|     janet_struct_proto(st) = proto; | ||||
|     return janet_wrap_struct(janet_struct_end(st)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_struct_getproto, | ||||
|               "(struct/getproto st)", | ||||
|               "Return the prototype of a struct, or nil if it doesn't have one.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetStruct st = janet_getstruct(argv, 0); | ||||
|     return janet_struct_proto(st) | ||||
|            ? janet_wrap_struct(janet_struct_proto(st)) | ||||
|            : janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_struct_flatten, | ||||
|               "(struct/proto-flatten st)", | ||||
|               "Convert a struct with prototypes to a struct with no prototypes by merging " | ||||
|               "all key value pairs from recursive prototypes into one new struct.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetStruct st = janet_getstruct(argv, 0); | ||||
|  | ||||
|     /* get an upper bounds on the number of items in the final struct */ | ||||
|     int64_t pair_count = 0; | ||||
|     JanetStruct cursor = st; | ||||
|     while (cursor) { | ||||
|         pair_count += janet_struct_length(cursor); | ||||
|         cursor = janet_struct_proto(cursor); | ||||
|     } | ||||
|  | ||||
|     if (pair_count > INT32_MAX) { | ||||
|         janet_panic("struct too large"); | ||||
|     } | ||||
|  | ||||
|     JanetKV *accum = janet_struct_begin((int32_t) pair_count); | ||||
|     cursor = st; | ||||
|     while (cursor) { | ||||
|         for (int32_t i = 0; i < janet_struct_capacity(cursor); i++) { | ||||
|             const JanetKV *kv = cursor + i; | ||||
|             if (!janet_checktype(kv->key, JANET_NIL)) { | ||||
|                 janet_struct_put_ext(accum, kv->key, kv->value, 0); | ||||
|             } | ||||
|         } | ||||
|         cursor = janet_struct_proto(cursor); | ||||
|     } | ||||
|     return janet_wrap_struct(janet_struct_end(accum)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_struct_to_table, | ||||
|               "(struct/to-table st &opt recursive)", | ||||
|               "Convert a struct to a table. If recursive is true, also convert the " | ||||
|               "table's prototypes into the new struct's prototypes as well.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     JanetStruct st = janet_getstruct(argv, 0); | ||||
|     int recursive = argc > 1 && janet_truthy(argv[1]); | ||||
|     JanetTable *tab = NULL; | ||||
|     JanetStruct cursor = st; | ||||
|     JanetTable *tab_cursor = tab; | ||||
|     do { | ||||
|         if (tab) { | ||||
|             tab_cursor->proto = janet_table(janet_struct_length(cursor)); | ||||
|             tab_cursor = tab_cursor->proto; | ||||
|         } else { | ||||
|             tab = janet_table(janet_struct_length(cursor)); | ||||
|             tab_cursor = tab; | ||||
|         } | ||||
|         /* TODO - implement as memcpy since struct memory should be compatible | ||||
|          * with table memory */ | ||||
|         for (int32_t i = 0; i < janet_struct_capacity(cursor); i++) { | ||||
|             const JanetKV *kv = cursor + i; | ||||
|             if (!janet_checktype(kv->key, JANET_NIL)) { | ||||
|                 janet_table_put(tab_cursor, kv->key, kv->value); | ||||
|             } | ||||
|         } | ||||
|         cursor = janet_struct_proto(cursor); | ||||
|     } while (recursive && cursor); | ||||
|     return janet_wrap_table(tab); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_struct_rawget, | ||||
|               "(struct/rawget st key)", | ||||
|               "Gets a value from a struct `st` without looking at the prototype struct. " | ||||
|               "If `st` does not contain the key directly, the function will return " | ||||
|               "nil without checking the prototype. Returns the value in the struct.") { | ||||
|     janet_fixarity(argc, 2); | ||||
|     JanetStruct st = janet_getstruct(argv, 0); | ||||
|     return janet_struct_rawget(st, argv[1]); | ||||
| } | ||||
|  | ||||
| /* Load the struct module */ | ||||
| void janet_lib_struct(JanetTable *env) { | ||||
|     JanetRegExt struct_cfuns[] = { | ||||
|         JANET_CORE_REG("struct/with-proto", cfun_struct_with_proto), | ||||
|         JANET_CORE_REG("struct/getproto", cfun_struct_getproto), | ||||
|         JANET_CORE_REG("struct/proto-flatten", cfun_struct_flatten), | ||||
|         JANET_CORE_REG("struct/to-table", cfun_struct_to_table), | ||||
|         JANET_CORE_REG("struct/rawget", cfun_struct_rawget), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, struct_cfuns); | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2021 Calvin Rose | ||||
| * Copyright (c) 2025 Calvin Rose | ||||
| * | ||||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| * of this software and associated documentation files (the "Software"), to | ||||
| @@ -108,6 +108,7 @@ static const uint8_t **janet_symcache_findmem( | ||||
|         } | ||||
| notfound: | ||||
|     *success = 0; | ||||
|     janet_assert(firstEmpty != NULL, "symcache failed to get memory"); | ||||
|     return firstEmpty; | ||||
| } | ||||
|  | ||||
| @@ -233,6 +234,7 @@ const uint8_t *janet_symbol_gen(void) { | ||||
|     head->hash = hash; | ||||
|     sym = (uint8_t *)(head->data); | ||||
|     memcpy(sym, janet_vm.gensym_counter, sizeof(janet_vm.gensym_counter)); | ||||
|     sym[head->length] = 0; | ||||
|     janet_symcache_put((const uint8_t *)sym, bucket); | ||||
|     return (const uint8_t *)sym; | ||||
| } | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user