mirror of
				https://github.com/janet-lang/janet
				synced 2025-10-29 06:37:41 +00:00 
			
		
		
		
	Compare commits
	
		
			593 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 | 
| @@ -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,6 +11,7 @@ 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 -Dreduced_os=true -Dffi=false | ||||
| @@ -29,4 +30,3 @@ tasks: | ||||
|     ninja | ||||
|     ninja test | ||||
|     doas ninja install | ||||
|  | ||||
|   | ||||
							
								
								
									
										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" | ||||
							
								
								
									
										7
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/codeql.yml
									
									
									
									
										vendored
									
									
								
							| @@ -27,15 +27,16 @@ jobs: | ||||
|         uses: actions/checkout@v3 | ||||
|  | ||||
|       - name: Initialize CodeQL | ||||
|         uses: github/codeql-action/init@v2 | ||||
|         uses: github/codeql-action/init@v3 | ||||
|         with: | ||||
|           languages: ${{ matrix.language }} | ||||
|           queries: +security-and-quality | ||||
|           tools: linked | ||||
|  | ||||
|       - name: Autobuild | ||||
|         uses: github/codeql-action/autobuild@v2 | ||||
|         uses: github/codeql-action/autobuild@v3 | ||||
|  | ||||
|       - name: Perform CodeQL Analysis | ||||
|         uses: github/codeql-action/analyze@v2 | ||||
|         uses: github/codeql-action/analyze@v3 | ||||
|         with: | ||||
|           category: "/language:${{ matrix.language }}" | ||||
|   | ||||
							
								
								
									
										58
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										58
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ jobs: | ||||
|     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 | ||||
| @@ -39,6 +39,35 @@ jobs: | ||||
|             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 | ||||
| @@ -60,3 +89,30 @@ jobs: | ||||
|             ./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 | ||||
|   | ||||
							
								
								
									
										65
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										65
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -12,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 | ||||
| @@ -23,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 | ||||
| @@ -35,28 +38,61 @@ 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 (no test yet) | ||||
|     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: UCRT64 | ||||
|           msystem: ${{ matrix.msystem }} | ||||
|           update: true | ||||
|           install: >- | ||||
|             base-devel | ||||
|             git | ||||
|             gcc | ||||
|       - name: Build the project | ||||
|       - name: Build | ||||
|         shell: cmd | ||||
|         run: make -j CC=gcc | ||||
|         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 | ||||
| @@ -73,7 +109,7 @@ jobs: | ||||
|       - 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 | ||||
|         run: make test UNAME=MINGW RUN=wine VERBOSE=1 | ||||
|  | ||||
|   test-arm-linux: | ||||
|     name: Build and test ARM32 cross compilation | ||||
| @@ -86,6 +122,17 @@ jobs: | ||||
|           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  | ||||
|         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 | ||||
|         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" | ||||
|   | ||||
							
								
								
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -34,8 +34,11 @@ local | ||||
|  | ||||
| # Common test files I use. | ||||
| temp.janet | ||||
| temp*.janet | ||||
| temp.c | ||||
| temp*janet | ||||
| temp*.c | ||||
| scratch.janet | ||||
| scratch.c | ||||
|  | ||||
| # Emscripten | ||||
| *.bc | ||||
| @@ -45,6 +48,8 @@ janet.wasm | ||||
| # Generated files | ||||
| *.gen.h | ||||
| *.gen.c | ||||
| *.tmp | ||||
| temp.* | ||||
|  | ||||
| # Generate test files | ||||
| *.out | ||||
| @@ -57,6 +62,7 @@ xxd.exe | ||||
| # VSCode | ||||
| .vs | ||||
| .clangd | ||||
| .cache | ||||
|  | ||||
| # Swap files | ||||
| *.swp | ||||
| @@ -122,6 +128,9 @@ vgcore.* | ||||
| *.idb | ||||
| *.pdb | ||||
|  | ||||
| # GGov | ||||
| *.gcov | ||||
|  | ||||
| # Kernel Module Compile Results | ||||
| *.mod* | ||||
| *.cmd | ||||
| @@ -130,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 | ||||
|   | ||||
							
								
								
									
										133
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,129 @@ | ||||
| # 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 `\\'`. | ||||
| @@ -41,7 +164,7 @@ All notable changes to this project will be documented in this file. | ||||
|   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` sigals to `:interrupt` and `:await` | ||||
| - 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. | ||||
| @@ -212,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 | ||||
| @@ -266,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. | ||||
|  | ||||
| @@ -416,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 | ||||
|   | ||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| Copyright (c) 2023 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 | ||||
|   | ||||
							
								
								
									
										47
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 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 | ||||
| @@ -33,6 +33,7 @@ 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 | ||||
| @@ -42,6 +43,8 @@ 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, | ||||
|  | ||||
| @@ -51,8 +54,10 @@ HOSTAR?=$(AR) | ||||
| # 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 $(COMMON_CFLAGS) -g | ||||
| BUILD_CFLAGS:=$(CFLAGS) $(COMMON_CFLAGS) | ||||
| @@ -90,15 +95,26 @@ endif | ||||
| endif | ||||
|  | ||||
| # 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_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.h | ||||
| all: $(JANET_TARGET) $(JANET_STATIC_LIBRARY) build/janet.h | ||||
| ifeq ($(HAS_SHARED), 1) | ||||
| all: $(JANET_LIBRARY) | ||||
| endif | ||||
|  | ||||
| ###################### | ||||
| ##### Name Files ##### | ||||
| @@ -131,6 +147,7 @@ JANET_CORE_SOURCES=src/core/abstract.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 \ | ||||
| @@ -196,9 +213,14 @@ build/%.bin.o: src/%.c $(JANET_HEADERS) $(JANET_LOCAL_HEADERS) Makefile | ||||
| ######################## | ||||
|  | ||||
| ifeq ($(UNAME), Darwin) | ||||
| SONAME=libjanet.1.30.dylib | ||||
| SONAME=libjanet.1.38.dylib | ||||
| else | ||||
| SONAME=libjanet.so.1.30 | ||||
| SONAME=libjanet.so.1.38 | ||||
| endif | ||||
|  | ||||
| ifeq ($(MINGW_COMPILER), clang) | ||||
| 	SONAME= | ||||
| 	SONAME_SETTER= | ||||
| endif | ||||
|  | ||||
| build/c/shell.c: src/mainclient/shell.c | ||||
| @@ -220,7 +242,7 @@ $(JANET_TARGET): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTCC) $(LDFLAGS) $(BUILD_CFLAGS) -o $@ $^ $(CLIBS) | ||||
|  | ||||
| $(JANET_LIBRARY): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTCC) $(LDFLAGS) $(BUILD_CFLAGS) $(SONAME_SETTER)$(SONAME) -shared -o $@ $^ $(CLIBS) | ||||
| 	$(HOSTCC) $(LIBJANET_LDFLAGS) $(BUILD_CFLAGS) $(SONAME_SETTER)$(SONAME) -shared -o $@ $^ $(CLIBS) | ||||
|  | ||||
| $(JANET_STATIC_LIBRARY): $(JANET_TARGET_OBJECTS) | ||||
| 	$(HOSTAR) rcs $@ $^ | ||||
| @@ -263,7 +285,7 @@ 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/ | ||||
| @@ -271,13 +293,17 @@ build/janet-%.tar.gz: $(JANET_TARGET) \ | ||||
| 	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 ##### | ||||
| @@ -331,6 +357,7 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc | ||||
| 	mkdir -p '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)' | ||||
| 	cp build/janet.pc '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc' | ||||
| 	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) | ||||
| @@ -345,6 +372,12 @@ install-jpm-git: $(JANET_TARGET) | ||||
| 		JANET_LIBPATH='$(LIBDIR)' \ | ||||
| 		$(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' | ||||
|   | ||||
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| [](https://gitter.im/janet-language/community) | ||||
| [](https://janet.zulipchat.com) | ||||
|   | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/master/freebsd.yml?) | ||||
| [](https://builds.sr.ht/~bakpakin/janet/commits/master/openbsd.yml?) | ||||
| @@ -18,9 +18,6 @@ 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>. | ||||
|  | ||||
| If you'd like to financially support the ongoing development of Janet, consider | ||||
| [sponsoring its primary author](https://github.com/sponsors/bakpakin) through GitHub. | ||||
|  | ||||
| <br> | ||||
|  | ||||
| ## Examples | ||||
| @@ -210,7 +207,7 @@ Alternatively, install the package directly with `pkgin install janet`. | ||||
|  | ||||
| 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). | ||||
| 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. | ||||
| @@ -253,8 +250,10 @@ 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 | ||||
|  | ||||
| @@ -315,8 +314,7 @@ See the [Embedding Section](https://janet-lang.org/capi/embedding.html) on the w | ||||
|  | ||||
| ## 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 | ||||
|  | ||||
| @@ -383,7 +381,7 @@ Usually, one of a few reasons: | ||||
| ### 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 may need some to that of the bound language. Janet | ||||
| 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. | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										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))) | ||||
| @@ -35,6 +35,11 @@ typedef struct { | ||||
|     int c; | ||||
| } intintint; | ||||
|  | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
| } uint64pair; | ||||
|  | ||||
| typedef struct { | ||||
|     int64_t a; | ||||
|     int64_t b; | ||||
| @@ -203,3 +208,20 @@ 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; | ||||
| } | ||||
|   | ||||
| @@ -8,11 +8,13 @@ | ||||
|  | ||||
| (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 "-shared" "-o" 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)) | ||||
| @@ -55,6 +57,14 @@ | ||||
| (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 | ||||
| @@ -119,6 +129,7 @@ | ||||
| (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))) | ||||
| @@ -130,5 +141,10 @@ | ||||
| (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.") | ||||
|   | ||||
							
								
								
									
										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"))) | ||||
							
								
								
									
										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) | ||||
							
								
								
									
										3
									
								
								janet.1
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								janet.1
									
									
									
									
									
								
							| @@ -255,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 | ||||
|   | ||||
							
								
								
									
										113
									
								
								meson.build
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								meson.build
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 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,34 @@ | ||||
|  | ||||
| project('janet', 'c', | ||||
|   default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'], | ||||
|   version : '1.30.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') | ||||
|  | ||||
| @@ -61,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')) | ||||
| @@ -78,6 +97,8 @@ 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 | ||||
| @@ -120,6 +141,7 @@ core_src = [ | ||||
|   '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', | ||||
| @@ -160,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, android_spawn_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 : [ | ||||
| @@ -177,30 +206,41 @@ janetc = custom_target('janetc', | ||||
|     'JANET_PATH', janet_path | ||||
|   ]) | ||||
|  | ||||
| janet_dependencies = [m_dep, dl_dep, android_spawn_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) | ||||
| @@ -211,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 | ||||
| @@ -231,6 +271,7 @@ test_files = [ | ||||
|   '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', | ||||
| @@ -238,6 +279,7 @@ test_files = [ | ||||
|   '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', | ||||
| @@ -252,6 +294,7 @@ test_files = [ | ||||
|   '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' | ||||
| @@ -264,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') | ||||
| @@ -281,11 +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.h', install_dir: get_option('includedir')) | ||||
|   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,16 +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) | ||||
| @@ -29,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) 2023 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) 2023 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 | ||||
|   | ||||
							
								
								
									
										1444
									
								
								src/boot/boot.janet
									
									
									
									
									
								
							
							
						
						
									
										1444
									
								
								src/boot/boot.janet
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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) 2023 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) 2023 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())); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 30 | ||||
| #define JANET_VERSION_MINOR 38 | ||||
| #define JANET_VERSION_PATCH 0 | ||||
| #define JANET_VERSION_EXTRA "" | ||||
| #define JANET_VERSION "1.30.0" | ||||
| #define JANET_VERSION "1.38.0" | ||||
|  | ||||
| /* #define JANET_BUILD "local" */ | ||||
|  | ||||
| @@ -29,6 +29,7 @@ | ||||
| /* #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 */ | ||||
| @@ -52,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) 2023 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,8 +31,6 @@ | ||||
| #ifdef JANET_EV | ||||
| #ifdef JANET_WINDOWS | ||||
| #include <windows.h> | ||||
| #else | ||||
| #include <stdatomic.h> | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| @@ -97,14 +95,6 @@ size_t janet_os_rwlock_size(void) { | ||||
|     return sizeof(void *); | ||||
| } | ||||
|  | ||||
| static int32_t janet_incref(JanetAbstractHead *ab) { | ||||
|     return InterlockedIncrement((LONG volatile *) &ab->gc.data.refcount); | ||||
| } | ||||
|  | ||||
| static int32_t janet_decref(JanetAbstractHead *ab) { | ||||
|     return InterlockedDecrement((LONG volatile *) &ab->gc.data.refcount); | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_init(JanetOSMutex *mutex) { | ||||
|     InitializeCriticalSection((CRITICAL_SECTION *) mutex); | ||||
| } | ||||
| @@ -157,14 +147,6 @@ size_t janet_os_rwlock_size(void) { | ||||
|     return sizeof(pthread_rwlock_t); | ||||
| } | ||||
|  | ||||
| static int32_t janet_incref(JanetAbstractHead *ab) { | ||||
|     return __atomic_add_fetch(&ab->gc.data.refcount, 1, __ATOMIC_RELAXED); | ||||
| } | ||||
|  | ||||
| static int32_t janet_decref(JanetAbstractHead *ab) { | ||||
|     return __atomic_add_fetch(&ab->gc.data.refcount, -1, __ATOMIC_RELAXED); | ||||
| } | ||||
|  | ||||
| void janet_os_mutex_init(JanetOSMutex *mutex) { | ||||
|     pthread_mutexattr_t attr; | ||||
|     pthread_mutexattr_init(&attr); | ||||
| @@ -212,11 +194,11 @@ void janet_os_rwlock_wunlock(JanetOSRWLock *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) 2023 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; | ||||
| } | ||||
|  | ||||
| @@ -132,6 +143,15 @@ JANET_CORE_FN(cfun_array_new, | ||||
|     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.") { | ||||
| @@ -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) { | ||||
| @@ -255,6 +275,31 @@ 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 " | ||||
| @@ -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); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -560,6 +560,9 @@ 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; | ||||
| @@ -784,8 +787,9 @@ static JanetAssembleResult janet_asm1(JanetAssembler *parent, Janet source, int | ||||
|     } | ||||
|  | ||||
|     /* 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 */ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -135,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); | ||||
| } | ||||
|  | ||||
| @@ -221,6 +220,20 @@ JANET_CORE_FN(cfun_buffer_new_filled, | ||||
|     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. " | ||||
| @@ -307,6 +320,133 @@ JANET_CORE_FN(cfun_buffer_chars, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| 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); | ||||
|     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)) { | ||||
| @@ -505,15 +645,42 @@ 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), | ||||
| @@ -525,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) 2023 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 | ||||
| @@ -140,7 +140,7 @@ void janet_bytecode_remove_noops(JanetFuncDef *def) { | ||||
|                 /* 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 += (new_jump_target - old_jump_target + (i - j)) << 8; | ||||
|                 instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 8; | ||||
|                 break; | ||||
|             case JOP_JUMP_IF: | ||||
|             case JOP_JUMP_IF_NIL: | ||||
| @@ -149,7 +149,7 @@ void janet_bytecode_remove_noops(JanetFuncDef *def) { | ||||
|                 /* 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 += (new_jump_target - old_jump_target + (i - j)) << 16; | ||||
|                 instr += (uint32_t)(new_jump_target - old_jump_target + (i - j)) << 16; | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
| @@ -226,6 +226,7 @@ void janet_bytecode_movopt(JanetFuncDef *def) { | ||||
|                 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: | ||||
| @@ -233,6 +234,8 @@ void janet_bytecode_movopt(JanetFuncDef *def) { | ||||
|                 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 */ | ||||
|   | ||||
							
								
								
									
										116
									
								
								src/core/capi.c
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								src/core/capi.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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; | ||||
| @@ -296,11 +318,28 @@ int32_t janet_getinteger(const Janet *argv, int32_t n) { | ||||
| 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 signed integer, got %v", n, x); | ||||
|         janet_panicf("bad slot #%d, expected 32 bit unsigned integer, got %v", n, x); | ||||
|     } | ||||
|     return janet_unwrap_integer(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]); | ||||
| @@ -338,7 +377,7 @@ 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; | ||||
| } | ||||
|  | ||||
| @@ -361,7 +400,7 @@ int32_t janet_getargindex(const Janet *argv, int32_t n, int32_t length, const ch | ||||
|     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; | ||||
| } | ||||
|  | ||||
| @@ -439,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); | ||||
| @@ -491,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); | ||||
| } | ||||
|  | ||||
| @@ -501,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) 2023 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) 2023 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 | ||||
| @@ -934,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; | ||||
|         } | ||||
| @@ -1056,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 { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -262,7 +262,7 @@ 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 */ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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,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; | ||||
|     } | ||||
| @@ -110,14 +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" | ||||
|               "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,\n" | ||||
|               "           the first path segment is replaced with a dynamic binding\n" | ||||
|               "           `(dyn <first path segment as keyword>)`.\n\n" | ||||
|               "* :cur: -- the current file, or (dyn :current-file)\n\n" | ||||
|               "* :dir: -- the directory containing the current file\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)") { | ||||
| @@ -432,27 +432,39 @@ JANET_CORE_FN(janet_core_range, | ||||
|               "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); | ||||
|     int32_t start = 0, stop = 0, step = 1, count = 0; | ||||
|     double start = 0, stop = 0, step = 1, count = 0; | ||||
|     if (argc == 3) { | ||||
|         start = janet_getinteger(argv, 0); | ||||
|         stop = janet_getinteger(argv, 1); | ||||
|         step = janet_getinteger(argv, 2); | ||||
|         count = (step > 0) ? (stop - start - 1) / step + 1 : | ||||
|                 ((step < 0) ? (stop - start + 1) / step + 1 : 0); | ||||
|         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_getinteger(argv, 0); | ||||
|         stop = janet_getinteger(argv, 1); | ||||
|         start = janet_getnumber(argv, 0); | ||||
|         stop = janet_getnumber(argv, 1); | ||||
|         count = stop - start; | ||||
|     } else { | ||||
|         stop = janet_getinteger(argv, 0); | ||||
|         stop = janet_getnumber(argv, 0); | ||||
|         count = stop; | ||||
|     } | ||||
|     count = (count > 0) ? count : 0; | ||||
|     JanetArray *array = janet_array(count); | ||||
|     for (int32_t i = 0; i < count; i++) { | ||||
|         array->data[i] = janet_wrap_number(start + i * step); | ||||
|     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); | ||||
|     } | ||||
|     array->count = 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); | ||||
| } | ||||
|  | ||||
| @@ -659,9 +671,45 @@ 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); | ||||
|     Janet payload = argc == 2 ? argv[1] : janet_wrap_nil(); | ||||
|     if (janet_checkint(argv[0])) { | ||||
| @@ -720,6 +768,7 @@ static const SandboxOption sandbox_options[] = { | ||||
|     {"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} | ||||
| }; | ||||
| @@ -744,6 +793,7 @@ JANET_CORE_FN(janet_core_sandbox, | ||||
|               "* :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++) { | ||||
| @@ -946,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[] = { | ||||
| @@ -1053,6 +1102,10 @@ 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), | ||||
| @@ -1087,6 +1140,9 @@ 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); | ||||
| @@ -1103,79 +1159,82 @@ JanetTable *janet_core_env(JanetTable *replacements) { | ||||
|     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.")); | ||||
| @@ -1184,74 +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.")); | ||||
|          "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`.")); | ||||
|          "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.")); | ||||
|          "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), | ||||
| @@ -1260,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) 2023 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 | ||||
| @@ -102,7 +102,7 @@ void janet_stacktrace(JanetFiber *fiber, Janet err) { | ||||
| } | ||||
|  | ||||
| /* Error reporting. This can be emulated from within Janet, but for | ||||
|  * consitency with the top level code it is defined once. */ | ||||
|  * consistency with the top level code it is defined once. */ | ||||
| void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) { | ||||
|  | ||||
|     int32_t fi; | ||||
| @@ -164,7 +164,7 @@ void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) { | ||||
|                 } | ||||
|             } | ||||
|             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) { | ||||
| @@ -180,6 +180,11 @@ void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *prefix) { | ||||
|                 } | ||||
|             } | ||||
|             janet_eprintf("\n"); | ||||
|             /* Print fiber points optionally. Clutters traces but provides info | ||||
|             if (i <= 0 && fi > 0) { | ||||
|                 janet_eprintf("  in parent fiber\n"); | ||||
|             } | ||||
|             */ | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -388,8 +393,8 @@ JANET_CORE_FN(cfun_debug_stack, | ||||
| JANET_CORE_FN(cfun_debug_stacktrace, | ||||
|               "(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 " | ||||
|               "provided, and no prefix is given, will skip the error line. Returns the fiber.") { | ||||
|               "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, 3); | ||||
|     JanetFiber *fiber = janet_getfiber(argv, 0); | ||||
|     Janet x = argc == 1 ? janet_wrap_nil() : argv[1]; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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) 2023 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 | ||||
|   | ||||
							
								
								
									
										1322
									
								
								src/core/ev.c
									
									
									
									
									
								
							
							
						
						
									
										1322
									
								
								src/core/ev.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -76,4 +76,6 @@ | ||||
| #define __BSD_VISIBLE 1 | ||||
| #endif | ||||
|  | ||||
| #define _FILE_OFFSET_BITS 64 | ||||
|  | ||||
| #endif | ||||
|   | ||||
							
								
								
									
										320
									
								
								src/core/ffi.c
									
									
									
									
									
								
							
							
						
						
									
										320
									
								
								src/core/ffi.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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,9 @@ | ||||
| #if (defined(__x86_64__) || defined(_M_X64)) && !defined(JANET_WINDOWS) | ||||
| #define JANET_FFI_SYSV64_ENABLED | ||||
| #endif | ||||
| #if (defined(__aarch64__) || defined(_M_ARM64)) && !defined(JANET_WINDOWS) | ||||
| #define JANET_FFI_AAPCS64_ENABLED | ||||
| #endif | ||||
|  | ||||
| typedef struct JanetFFIType JanetFFIType; | ||||
| typedef struct JanetFFIStruct JanetFFIStruct; | ||||
| @@ -140,7 +143,13 @@ typedef enum { | ||||
|     JANET_WIN64_REGISTER, | ||||
|     JANET_WIN64_STACK, | ||||
|     JANET_WIN64_REGISTER_REF, | ||||
|     JANET_WIN64_STACK_REF | ||||
|     JANET_WIN64_STACK_REF, | ||||
|     JANET_AAPCS64_GENERAL, | ||||
|     JANET_AAPCS64_SSE, | ||||
|     JANET_AAPCS64_GENERAL_REF, | ||||
|     JANET_AAPCS64_STACK, | ||||
|     JANET_AAPCS64_STACK_REF, | ||||
|     JANET_AAPCS64_NONE | ||||
| } JanetFFIWordSpec; | ||||
|  | ||||
| /* Describe how each Janet argument is interpreted in terms of machine words | ||||
| @@ -155,13 +164,16 @@ typedef struct { | ||||
| typedef enum { | ||||
|     JANET_FFI_CC_NONE, | ||||
|     JANET_FFI_CC_SYSV_64, | ||||
|     JANET_FFI_CC_WIN_64 | ||||
|     JANET_FFI_CC_WIN_64, | ||||
|     JANET_FFI_CC_AAPCS64 | ||||
| } JanetFFICallingConvention; | ||||
|  | ||||
| #ifdef JANET_FFI_WIN64_ENABLED | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_WIN_64 | ||||
| #elif defined(JANET_FFI_SYSV64_ENABLED) | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_SYSV_64 | ||||
| #elif defined(JANET_FFI_AAPCS64_ENABLED) | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_AAPCS64 | ||||
| #else | ||||
| #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_NONE | ||||
| #endif | ||||
| @@ -301,6 +313,9 @@ static JanetFFICallingConvention decode_ffi_cc(const uint8_t *name) { | ||||
| #endif | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|     if (!janet_cstrcmp(name, "sysv64")) return JANET_FFI_CC_SYSV_64; | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|     if (!janet_cstrcmp(name, "aapcs64")) return JANET_FFI_CC_AAPCS64; | ||||
| #endif | ||||
|     if (!janet_cstrcmp(name, "default")) return JANET_FFI_CC_DEFAULT; | ||||
|     janet_panicf("unknown calling convention %s", name); | ||||
| @@ -385,7 +400,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) { | ||||
|  | ||||
|     JanetFFIStruct *st = janet_abstract(&janet_struct_type, | ||||
|                                         sizeof(JanetFFIStruct) + argc * sizeof(JanetFFIStructMember)); | ||||
|     st->field_count = member_count; | ||||
|     st->field_count = 0; | ||||
|     st->size = 0; | ||||
|     st->align = 1; | ||||
|     if (argc == 0) { | ||||
| @@ -403,6 +418,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) { | ||||
|         st->fields[i].type = decode_ffi_type(argv[j]); | ||||
|         size_t el_size = type_size(st->fields[i].type); | ||||
|         size_t el_align = type_align(st->fields[i].type); | ||||
|         if (el_align <= 0) janet_panicf("bad field type %V", argv[j]); | ||||
|         if (all_packed || pack_one) { | ||||
|             if (st->size % el_align != 0) is_aligned = 0; | ||||
|             st->fields[i].offset = st->size; | ||||
| @@ -418,6 +434,7 @@ static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) { | ||||
|     st->size += (st->align - 1); | ||||
|     st->size /= st->align; | ||||
|     st->size *= st->align; | ||||
|     st->field_count = member_count; | ||||
|     return st; | ||||
| } | ||||
|  | ||||
| @@ -475,7 +492,7 @@ JANET_CORE_FN(cfun_ffi_align, | ||||
| static void *janet_ffi_getpointer(const Janet *argv, int32_t n) { | ||||
|     switch (janet_type(argv[n])) { | ||||
|         default: | ||||
|             janet_panicf("bad slot #%d, expected ffi pointer convertable type, got %v", n, argv[n]); | ||||
|             janet_panicf("bad slot #%d, expected ffi pointer convertible type, got %v", n, argv[n]); | ||||
|         case JANET_POINTER: | ||||
|         case JANET_STRING: | ||||
|         case JANET_KEYWORD: | ||||
| @@ -763,6 +780,101 @@ static JanetFFIWordSpec sysv64_classify(JanetFFIType type) { | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
| /* Procedure Call Standard for the Arm® 64-bit Architecture (AArch64) 2023Q3 – October 6, 2023 | ||||
|  * See section 6.8.2 Parameter passing rules. | ||||
|  * https://github.com/ARM-software/abi-aa/releases/download/2023Q3/aapcs64.pdf | ||||
|  * | ||||
|  * Additional documentation needed for Apple platforms. | ||||
|  * https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms */ | ||||
|  | ||||
| #define JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ptr, alignment) (ptr = ((ptr) + ((alignment) - 1)) & ~((alignment) - 1)) | ||||
| #if !defined(JANET_APPLE) | ||||
| #define JANET_FFI_AAPCS64_STACK_ALIGN(ptr, alignment) ((void) alignment, JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ptr, 8)) | ||||
| #else | ||||
| #define JANET_FFI_AAPCS64_STACK_ALIGN(ptr, alignment) JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ptr, alignment) | ||||
| #endif | ||||
|  | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
| } Aapcs64Variant1ReturnGeneral; | ||||
|  | ||||
| typedef struct { | ||||
|     double a; | ||||
|     double b; | ||||
|     double c; | ||||
|     double d; | ||||
| } Aapcs64Variant2ReturnSse; | ||||
|  | ||||
| /* Workaround for passing a return value pointer through x8. | ||||
|  * Limits struct returns to 128 bytes. */ | ||||
| typedef struct { | ||||
|     uint64_t a; | ||||
|     uint64_t b; | ||||
|     uint64_t c; | ||||
|     uint64_t d; | ||||
|     uint64_t e; | ||||
|     uint64_t f; | ||||
|     uint64_t g; | ||||
|     uint64_t h; | ||||
|     uint64_t i; | ||||
|     uint64_t j; | ||||
|     uint64_t k; | ||||
|     uint64_t l; | ||||
|     uint64_t m; | ||||
|     uint64_t n; | ||||
|     uint64_t o; | ||||
|     uint64_t p; | ||||
| } Aapcs64Variant3ReturnPointer; | ||||
|  | ||||
| static JanetFFIWordSpec aapcs64_classify(JanetFFIType type) { | ||||
|     switch (type.prim) { | ||||
|         case JANET_FFI_TYPE_PTR: | ||||
|         case JANET_FFI_TYPE_STRING: | ||||
|         case JANET_FFI_TYPE_BOOL: | ||||
|         case JANET_FFI_TYPE_INT8: | ||||
|         case JANET_FFI_TYPE_INT16: | ||||
|         case JANET_FFI_TYPE_INT32: | ||||
|         case JANET_FFI_TYPE_INT64: | ||||
|         case JANET_FFI_TYPE_UINT8: | ||||
|         case JANET_FFI_TYPE_UINT16: | ||||
|         case JANET_FFI_TYPE_UINT32: | ||||
|         case JANET_FFI_TYPE_UINT64: | ||||
|             return JANET_AAPCS64_GENERAL; | ||||
|         case JANET_FFI_TYPE_DOUBLE: | ||||
|         case JANET_FFI_TYPE_FLOAT: | ||||
|             return JANET_AAPCS64_SSE; | ||||
|         case JANET_FFI_TYPE_STRUCT: { | ||||
|             JanetFFIStruct *st = type.st; | ||||
|             if (st->field_count <= 4 && aapcs64_classify(st->fields[0].type) == JANET_AAPCS64_SSE) { | ||||
|                 bool is_hfa = true; | ||||
|                 for (uint32_t i = 1; i < st->field_count; i++) { | ||||
|                     if (st->fields[0].type.prim != st->fields[i].type.prim) { | ||||
|                         is_hfa = false; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 if (is_hfa) { | ||||
|                     return JANET_AAPCS64_SSE; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (type_size(type) > 16) { | ||||
|                 return JANET_AAPCS64_GENERAL_REF; | ||||
|             } | ||||
|  | ||||
|             return JANET_AAPCS64_GENERAL; | ||||
|         } | ||||
|         case JANET_FFI_TYPE_VOID: | ||||
|             return JANET_AAPCS64_NONE; | ||||
|         default: | ||||
|             janet_panic("nyi"); | ||||
|             return JANET_AAPCS64_NONE; | ||||
|     } | ||||
| } | ||||
| #endif | ||||
|  | ||||
| JANET_CORE_FN(cfun_ffi_signature, | ||||
|               "(ffi/signature calling-convention ret-type & arg-types)", | ||||
|               "Create a function signature object that can be used to make calls " | ||||
| @@ -960,6 +1072,96 @@ JANET_CORE_FN(cfun_ffi_signature, | ||||
|         } | ||||
|         break; | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|         case JANET_FFI_CC_AAPCS64: { | ||||
|             uint32_t next_general_reg = 0; | ||||
|             uint32_t next_fp_reg = 0; | ||||
|             uint32_t stack_offset = 0; | ||||
|             uint32_t ref_stack_offset = 0; | ||||
|  | ||||
|             JanetFFIWordSpec ret_spec = aapcs64_classify(ret_type); | ||||
|             ret.spec = ret_spec; | ||||
|             if (ret_spec == JANET_AAPCS64_SSE) { | ||||
|                 variant = 1; | ||||
|             } else if (ret_spec == JANET_AAPCS64_GENERAL_REF) { | ||||
|                 if (type_size(ret_type) > sizeof(Aapcs64Variant3ReturnPointer)) { | ||||
|                     janet_panic("return value bigger than supported"); | ||||
|                 } | ||||
|                 variant = 2; | ||||
|             } else { | ||||
|                 variant = 0; | ||||
|             } | ||||
|  | ||||
|             for (uint32_t i = 0; i < arg_count; i++) { | ||||
|                 mappings[i].type = decode_ffi_type(argv[i + 2]); | ||||
|                 mappings[i].spec = aapcs64_classify(mappings[i].type); | ||||
|                 size_t arg_size = type_size(mappings[i].type); | ||||
|  | ||||
|                 switch (mappings[i].spec) { | ||||
|                     case JANET_AAPCS64_GENERAL: { | ||||
|                         bool arg_is_struct = mappings[i].type.prim == JANET_FFI_TYPE_STRUCT; | ||||
|                         uint32_t needed_registers = (arg_size + 7) / 8; | ||||
|                         if (next_general_reg + needed_registers <= 8) { | ||||
|                             mappings[i].offset = next_general_reg; | ||||
|                             next_general_reg += needed_registers; | ||||
|                         } else { | ||||
|                             size_t arg_align = arg_is_struct ? 8 : type_align(mappings[i].type); | ||||
|                             mappings[i].spec = JANET_AAPCS64_STACK; | ||||
|                             mappings[i].offset = JANET_FFI_AAPCS64_STACK_ALIGN(stack_offset, arg_align); | ||||
| #if !defined(JANET_APPLE) | ||||
|                             stack_offset += arg_size > 8 ? arg_size : 8; | ||||
| #else | ||||
|                             stack_offset += arg_size; | ||||
| #endif | ||||
|                             next_general_reg = 8; | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                     case JANET_AAPCS64_GENERAL_REF: | ||||
|                         if (next_general_reg < 8) { | ||||
|                             mappings[i].offset = next_general_reg++; | ||||
|                         } else { | ||||
|                             mappings[i].spec = JANET_AAPCS64_STACK_REF; | ||||
|                             mappings[i].offset = JANET_FFI_AAPCS64_STACK_ALIGN(stack_offset, 8); | ||||
|                             stack_offset += 8; | ||||
|                         } | ||||
|                         mappings[i].offset2 = JANET_FFI_AAPCS64_FORCE_STACK_ALIGN(ref_stack_offset, 8); | ||||
|                         ref_stack_offset += arg_size; | ||||
|                         break; | ||||
|                     case JANET_AAPCS64_SSE: { | ||||
|                         uint32_t needed_registers = (arg_size + 7) / 8; | ||||
|                         if (next_fp_reg + needed_registers <= 8) { | ||||
|                             mappings[i].offset = next_fp_reg; | ||||
|                             next_fp_reg += needed_registers; | ||||
|                         } else { | ||||
|                             mappings[i].spec = JANET_AAPCS64_STACK; | ||||
|                             mappings[i].offset = JANET_FFI_AAPCS64_STACK_ALIGN(stack_offset, 8); | ||||
| #if !defined(JANET_APPLE) | ||||
|                             stack_offset += 8; | ||||
| #else | ||||
|                             stack_offset += arg_size; | ||||
| #endif | ||||
|                         } | ||||
|                         break; | ||||
|                     } | ||||
|                     default: | ||||
|                         janet_panic("nyi"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             stack_offset = (stack_offset + 15) & ~0xFUL; | ||||
|             ref_stack_offset = (ref_stack_offset + 15) & ~0xFUL; | ||||
|             stack_count = stack_offset + ref_stack_offset; | ||||
|  | ||||
|             for (uint32_t i = 0; i < arg_count; i++) { | ||||
|                 if (mappings[i].spec == JANET_AAPCS64_GENERAL_REF || mappings[i].spec == JANET_AAPCS64_STACK_REF) { | ||||
|                     mappings[i].offset2 = stack_offset + mappings[i].offset2; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         break; | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     /* Create signature abstract value */ | ||||
| @@ -1294,6 +1496,99 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|  | ||||
| static void janet_ffi_aapcs64_standard_callback(void *ctx, void *userdata) { | ||||
|     janet_ffi_trampoline(ctx, userdata); | ||||
| } | ||||
|  | ||||
| typedef Aapcs64Variant1ReturnGeneral janet_aapcs64_variant_1(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7, | ||||
|         double v0, double v1, double v2, double v3, double v4, double v5, double v6, double v7); | ||||
| typedef Aapcs64Variant2ReturnSse janet_aapcs64_variant_2(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7, | ||||
|         double v0, double v1, double v2, double v3, double v4, double v5, double v6, double v7); | ||||
| typedef Aapcs64Variant3ReturnPointer janet_aapcs64_variant_3(uint64_t x0, uint64_t x1, uint64_t x2, uint64_t x3, uint64_t x4, uint64_t x5, uint64_t x6, uint64_t x7, | ||||
|         double v0, double v1, double v2, double v3, double v4, double v5, double v6, double v7); | ||||
|  | ||||
|  | ||||
| static Janet janet_ffi_aapcs64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) { | ||||
|     union { | ||||
|         Aapcs64Variant1ReturnGeneral general_return; | ||||
|         Aapcs64Variant2ReturnSse sse_return; | ||||
|         Aapcs64Variant3ReturnPointer pointer_return; | ||||
|     } retu; | ||||
|     uint64_t regs[8]; | ||||
|     double fp_regs[8]; | ||||
|     void *ret_mem = &retu.general_return; | ||||
|  | ||||
|     /* Apple's stack values do not need to be 8-byte aligned, | ||||
|      * thus all stack offsets refer to actual byte positions. */ | ||||
|     uint8_t *stack = alloca(signature->stack_count); | ||||
| #if defined(JANET_APPLE) | ||||
|     /* Values must be zero-extended by the caller instead of the callee. */ | ||||
|     memset(stack, 0, signature->stack_count); | ||||
| #endif | ||||
|     for (uint32_t i = 0; i < signature->arg_count; i++) { | ||||
|         int32_t n = i + 2; | ||||
|         JanetFFIMapping arg = signature->args[i]; | ||||
|         void *to = NULL; | ||||
|  | ||||
|         switch (arg.spec) { | ||||
|             case JANET_AAPCS64_GENERAL: | ||||
|                 to = regs + arg.offset; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_GENERAL_REF: | ||||
|                 to = stack + arg.offset2; | ||||
|                 regs[arg.offset] = (uint64_t) to; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_SSE: | ||||
|                 to = fp_regs + arg.offset; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_STACK: | ||||
|                 to = stack + arg.offset; | ||||
|                 break; | ||||
|             case JANET_AAPCS64_STACK_REF: | ||||
|                 to = stack + arg.offset2; | ||||
|                 uint64_t *ptr = (uint64_t *) stack + arg.offset; | ||||
|                 *ptr = (uint64_t) to; | ||||
|                 break; | ||||
|             default: | ||||
|                 janet_panic("nyi"); | ||||
|         } | ||||
|  | ||||
|         if (to) { | ||||
|             janet_ffi_write_one(to, argv, n, arg.type, JANET_FFI_MAX_RECUR); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     switch (signature->variant) { | ||||
|         case 0: | ||||
|             retu.general_return = ((janet_aapcs64_variant_1 *)(function_pointer))( | ||||
|                                       regs[0], regs[1], regs[2], regs[3], | ||||
|                                       regs[4], regs[5], regs[6], regs[7], | ||||
|                                       fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], | ||||
|                                       fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); | ||||
|             break; | ||||
|         case 1: | ||||
|             retu.sse_return = ((janet_aapcs64_variant_2 *)(function_pointer))( | ||||
|                                   regs[0], regs[1], regs[2], regs[3], | ||||
|                                   regs[4], regs[5], regs[6], regs[7], | ||||
|                                   fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], | ||||
|                                   fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); | ||||
|             break; | ||||
|         case 2: { | ||||
|             retu.pointer_return = ((janet_aapcs64_variant_3 *)(function_pointer))( | ||||
|                                       regs[0], regs[1], regs[2], regs[3], | ||||
|                                       regs[4], regs[5], regs[6], regs[7], | ||||
|                                       fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], | ||||
|                                       fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR); | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* Allocate executable memory chunks in sizes of a page. Ideally we would keep | ||||
|  * an allocator around so that multiple JIT allocations would point to the same | ||||
|  * region but it isn't really worth it. */ | ||||
| @@ -1373,6 +1668,10 @@ JANET_CORE_FN(cfun_ffi_call, | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|         case JANET_FFI_CC_SYSV_64: | ||||
|             return janet_ffi_sysv64(signature, function_pointer, argv); | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|         case JANET_FFI_CC_AAPCS64: | ||||
|             return janet_ffi_aapcs64(signature, function_pointer, argv); | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| @@ -1381,7 +1680,7 @@ JANET_CORE_FN(cfun_ffi_buffer_write, | ||||
|               "(ffi/write ffi-type data &opt buffer index)", | ||||
|               "Append a native type to a buffer such as it would appear in memory. This can be used " | ||||
|               "to pass pointers to structs in the ffi, or send C/C++/native structs over the network " | ||||
|               "or to files. Returns a modifed buffer or a new buffer if one is not supplied.") { | ||||
|               "or to files. Returns a modified buffer or a new buffer if one is not supplied.") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_FFI_USE); | ||||
|     janet_arity(argc, 2, 4); | ||||
|     JanetFFIType type = decode_ffi_type(argv[0]); | ||||
| @@ -1442,6 +1741,10 @@ JANET_CORE_FN(cfun_ffi_get_callback_trampoline, | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|         case JANET_FFI_CC_SYSV_64: | ||||
|             return janet_wrap_pointer(janet_ffi_sysv64_standard_callback); | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|         case JANET_FFI_CC_AAPCS64: | ||||
|             return janet_wrap_pointer(janet_ffi_aapcs64_standard_callback); | ||||
| #endif | ||||
|     } | ||||
| } | ||||
| @@ -1548,7 +1851,7 @@ JANET_CORE_FN(cfun_ffi_pointer_cfunction, | ||||
|  | ||||
| JANET_CORE_FN(cfun_ffi_supported_calling_conventions, | ||||
|               "(ffi/calling-conventions)", | ||||
|               "Get an array of all supported calling conventions on the current arhcitecture. Some architectures may have some FFI " | ||||
|               "Get an array of all supported calling conventions on the current architecture. Some architectures may have some FFI " | ||||
|               "functionality (ffi/malloc, ffi/free, ffi/read, ffi/write, etc.) but not support " | ||||
|               "any calling conventions. This function can be used to get all supported calling conventions " | ||||
|               "that can be used on this architecture. All architectures support the :none calling " | ||||
| @@ -1561,6 +1864,9 @@ JANET_CORE_FN(cfun_ffi_supported_calling_conventions, | ||||
| #endif | ||||
| #ifdef JANET_FFI_SYSV64_ENABLED | ||||
|     janet_array_push(array, janet_ckeywordv("sysv64")); | ||||
| #endif | ||||
| #ifdef JANET_FFI_AAPCS64_ENABLED | ||||
|     janet_array_push(array, janet_ckeywordv("aapcs64")); | ||||
| #endif | ||||
|     janet_array_push(array, janet_ckeywordv("none")); | ||||
|     return janet_wrap_array(array); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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); | ||||
| @@ -85,7 +87,6 @@ JanetFiber *janet_fiber_reset(JanetFiber *fiber, JanetFunction *callee, int32_t | ||||
|     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; | ||||
| @@ -238,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)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -369,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 { | ||||
| @@ -661,7 +662,7 @@ JANET_CORE_FN(cfun_fiber_can_resume, | ||||
| } | ||||
|  | ||||
| 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) 2023 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 | ||||
| @@ -59,6 +59,9 @@ | ||||
| #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;\ | ||||
|   | ||||
							
								
								
									
										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 | ||||
							
								
								
									
										177
									
								
								src/core/gc.c
									
									
									
									
									
								
							
							
						
						
									
										177
									
								
								src/core/gc.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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; | ||||
| @@ -268,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 */ | ||||
| @@ -287,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; | ||||
| @@ -326,12 +372,98 @@ static void janet_deinit_block(JanetGCObject *mem) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* 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->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; | ||||
|         } else { | ||||
|             janet_vm.block_count--; | ||||
|             janet_deinit_block(current); | ||||
|             if (NULL != previous) { | ||||
|                 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)) { | ||||
| @@ -349,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; | ||||
| @@ -370,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 */ | ||||
| @@ -405,8 +539,15 @@ void *janet_gcalloc(enum JanetMemoryType type, size_t size) { | ||||
|  | ||||
|     /* Prepend block to heap list */ | ||||
|     janet_vm.next_collection += size; | ||||
|     mem->data.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; | ||||
| @@ -437,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 | ||||
| @@ -456,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(); | ||||
| @@ -559,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) 2023 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) 2023 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); | ||||
| } | ||||
|  | ||||
| @@ -191,21 +191,21 @@ 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 int32.") { | ||||
|               "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]); | ||||
| @@ -239,7 +239,7 @@ JANET_CORE_FN(cfun_to_bytes, | ||||
|               "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` paramater indicates the byte order:\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") { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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, | ||||
| @@ -126,12 +132,12 @@ JANET_CORE_FN(cfun_io_temp, | ||||
|     // 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 " | ||||
| @@ -145,7 +151,7 @@ JANET_CORE_FN(cfun_io_fopen, | ||||
|               "* + - append to the file instead of overwriting it\n\n" | ||||
|               "* 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, 2); | ||||
|     janet_arity(argc, 1, 3); | ||||
|     const uint8_t *fname = janet_getstring(argv, 0); | ||||
|     const uint8_t *fmode; | ||||
|     int32_t flags; | ||||
| @@ -158,8 +164,25 @@ JANET_CORE_FN(cfun_io_fopen, | ||||
|         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(); | ||||
| } | ||||
|  | ||||
| @@ -280,7 +303,7 @@ int janet_file_close(JanetFile *file) { | ||||
|     if (!(file->flags & (JANET_FILE_NOT_CLOSEABLE | JANET_FILE_CLOSED))) { | ||||
|         ret = fclose(file->file); | ||||
|         file->flags |= JANET_FILE_CLOSED; | ||||
|         file->file = NULL; /* NULL derefence is easier to debug then other problems */ | ||||
|         file->file = NULL; /* NULL dereference is easier to debug then other problems */ | ||||
|         return ret; | ||||
|     } | ||||
|     return 0; | ||||
| @@ -328,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); | ||||
| @@ -342,7 +365,7 @@ 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"); | ||||
| @@ -356,7 +379,7 @@ JANET_CORE_FN(cfun_io_ftell, | ||||
|     JanetFile *iof = janet_getabstract(argv, 0, &janet_file_type); | ||||
|     if (iof->flags & JANET_FILE_CLOSED) | ||||
|         janet_panic("file is closed"); | ||||
|     long pos = ftell(iof->file); | ||||
|     int64_t pos = ftell(iof->file); | ||||
|     if (pos == -1) janet_panic("error getting position in file"); | ||||
|     return janet_wrap_number((double)pos); | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -68,8 +68,15 @@ enum { | ||||
|     LB_STRUCT_PROTO, /* 223 */ | ||||
| #ifdef JANET_EV | ||||
|     LB_THREADED_ABSTRACT, /* 224 */ | ||||
|     LB_POINTER_BUFFER, /* 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 */ | ||||
| @@ -185,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; | ||||
| @@ -197,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; | ||||
| @@ -328,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); | ||||
| @@ -554,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); | ||||
| @@ -577,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); | ||||
| @@ -1048,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 */ | ||||
| @@ -1245,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); | ||||
| @@ -1388,11 +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++; | ||||
| @@ -1401,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); | ||||
| @@ -1443,10 +1496,19 @@ 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, st); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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." | ||||
|              ) { | ||||
| @@ -349,6 +349,26 @@ JANET_CORE_FN(janet_cfun_lcm, "(math/lcm x y)", | ||||
|     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) { | ||||
|     JanetRegExt math_cfuns[] = { | ||||
| @@ -395,6 +415,8 @@ void janet_lib_math(JanetTable *env) { | ||||
|         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); | ||||
| @@ -411,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 | ||||
|   | ||||
							
								
								
									
										223
									
								
								src/core/net.c
									
									
									
									
									
								
							
							
						
						
									
										223
									
								
								src/core/net.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -78,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); | ||||
|  | ||||
| @@ -111,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; | ||||
| @@ -124,10 +178,10 @@ 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; | ||||
| @@ -138,55 +192,60 @@ 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_COMPLETE: { | ||||
|             if (state->astream->flags & JANET_STREAM_CLOSED) { | ||||
|                 janet_cancel(s->fiber, janet_cstringv("failed to accept connection")); | ||||
|                 return JANET_ASYNC_STATUS_DONE; | ||||
|                 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 *) &lsock, sizeof(lsock))) { | ||||
|                 janet_cancel(s->fiber, janet_cstringv("failed to accept connection")); | ||||
|                 return JANET_ASYNC_STATUS_DONE; | ||||
|                 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; | ||||
|     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) { | ||||
| @@ -198,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; | ||||
|     } | ||||
| @@ -208,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; | ||||
| @@ -222,43 +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: { | ||||
| #if defined(JANET_LINUX) | ||||
|             JSock connfd = accept4(s->stream->handle, NULL, NULL, SOCK_CLOEXEC); | ||||
|             JSock connfd = accept4(stream->handle, NULL, NULL, SOCK_CLOEXEC); | ||||
| #else | ||||
|             /* On BSDs, CLOEXEC should be inherited from server socket */ | ||||
|             JSock connfd = accept(s->stream->handle, NULL, NULL); | ||||
|             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); | ||||
| @@ -487,7 +554,10 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     int err = WSAGetLastError(); | ||||
|     freeaddrinfo(ai); | ||||
| #else | ||||
|     int status = connect(sock, addr, addrlen); | ||||
|     int status; | ||||
|     do { | ||||
|         status = connect(sock, addr, addrlen); | ||||
|     } while (status == -1 && errno == EINTR); | ||||
|     int err = errno; | ||||
|     if (is_unix) { | ||||
|         janet_free(ai); | ||||
| @@ -496,7 +566,7 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     if (status != 0) { | ||||
|     if (status) { | ||||
| #ifdef JANET_WINDOWS | ||||
|         if (err != WSAEWOULDBLOCK) { | ||||
| #else | ||||
| @@ -508,23 +578,26 @@ JANET_CORE_FN(cfun_net_connect, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* Handle the connect() result in the event loop*/ | ||||
|     janet_ev_connect(stream, MSG_NOSIGNAL); | ||||
|  | ||||
|     janet_await(); | ||||
|     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; | ||||
| } | ||||
| @@ -578,19 +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.") { | ||||
|               ":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, 3); | ||||
|     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 | ||||
| @@ -600,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); | ||||
| @@ -623,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; | ||||
| @@ -682,6 +757,7 @@ static Janet janet_so_getname(const void *sa_any) { | ||||
|             Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai->sin_port))}; | ||||
|             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))) { | ||||
| @@ -690,6 +766,7 @@ static Janet janet_so_getname(const void *sa_any) { | ||||
|             Janet pair[2] = {janet_cstringv(buffer), janet_wrap_integer(ntohs(sai6->sin6_port))}; | ||||
|             return janet_wrap_tuple(janet_tuple_n(pair, 2)); | ||||
|         } | ||||
| #endif | ||||
| #ifndef JANET_WINDOWS | ||||
|         case AF_UNIX: { | ||||
|             const struct sockaddr_un *sun = sa_any; | ||||
| @@ -756,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); | ||||
| @@ -777,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); | ||||
| @@ -792,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); | ||||
| @@ -807,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 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); | ||||
| @@ -822,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); | ||||
| @@ -842,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); | ||||
| @@ -863,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, | ||||
| @@ -897,8 +970,10 @@ static const struct sockopt_type sockopt_type_list[] = { | ||||
|     { "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 } | ||||
| }; | ||||
|  | ||||
| @@ -935,7 +1010,9 @@ JANET_CORE_FN(cfun_net_setsockopt, | ||||
|     union { | ||||
|         int v_int; | ||||
|         struct ip_mreq v_mreq; | ||||
| #ifndef JANET_NO_IPV6 | ||||
|         struct ipv6_mreq v_mreq6; | ||||
| #endif | ||||
|     } val; | ||||
|  | ||||
|     void *optval = (void *)&val; | ||||
| @@ -953,12 +1030,14 @@ JANET_CORE_FN(cfun_net_setsockopt, | ||||
|         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"); | ||||
|     } | ||||
| @@ -967,7 +1046,7 @@ JANET_CORE_FN(cfun_net_setsockopt, | ||||
|  | ||||
|     int r = setsockopt((JSock) stream->handle, st->level, st->optname, optval, optlen); | ||||
|     if (r == -1) { | ||||
|         janet_panicf("setsockopt(%q): %s", argv[1], strerror(errno)); | ||||
|         janet_panicf("setsockopt(%q): %s", argv[1], janet_strerror(errno)); | ||||
|     } | ||||
|  | ||||
|     return janet_wrap_nil(); | ||||
|   | ||||
							
								
								
									
										498
									
								
								src/core/os.c
									
									
									
									
									
								
							
							
						
						
									
										498
									
								
								src/core/os.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -27,9 +27,10 @@ | ||||
| #include "gc.h" | ||||
| #endif | ||||
|  | ||||
| #include <stdlib.h> | ||||
|  | ||||
| #ifndef JANET_REDUCED_OS | ||||
|  | ||||
| #include <stdlib.h> | ||||
| #include <time.h> | ||||
| #include <fcntl.h> | ||||
| #include <errno.h> | ||||
| @@ -38,6 +39,7 @@ | ||||
| #include <string.h> | ||||
| #include <sys/stat.h> | ||||
| #include <signal.h> | ||||
| #include <locale.h> | ||||
|  | ||||
| #ifdef JANET_BSD | ||||
| #include <sys/sysctl.h> | ||||
| @@ -173,6 +175,8 @@ JANET_CORE_FN(os_arch, | ||||
|               "* :riscv64\n\n" | ||||
|               "* :sparc\n\n" | ||||
|               "* :wasm\n\n" | ||||
|               "* :s390\n\n" | ||||
|               "* :s390x\n\n" | ||||
|               "* :unknown\n") { | ||||
|     janet_fixarity(argc, 0); | ||||
|     (void) argv; | ||||
| @@ -199,6 +203,10 @@ JANET_CORE_FN(os_arch, | ||||
|     return janet_ckeywordv("ppc"); | ||||
| #elif (defined(__ppc64__) || defined(_ARCH_PPC64) || defined(_M_PPC)) | ||||
|     return janet_ckeywordv("ppc64"); | ||||
| #elif (defined(__s390x__)) | ||||
|     return janet_ckeywordv("s390x"); | ||||
| #elif (defined(__s390__)) | ||||
|     return janet_ckeywordv("s390"); | ||||
| #else | ||||
|     return janet_ckeywordv("unknown"); | ||||
| #endif | ||||
| @@ -229,10 +237,11 @@ JANET_CORE_FN(os_compiler, | ||||
| #undef janet_stringify | ||||
|  | ||||
| JANET_CORE_FN(os_exit, | ||||
|               "(os/exit &opt x)", | ||||
|               "(os/exit &opt x force)", | ||||
|               "Exit from janet with an exit code equal to x. If x is not an integer, " | ||||
|               "the exit with status equal the hash of x.") { | ||||
|     janet_arity(argc, 0, 1); | ||||
|               "the exit with status equal the hash of x. If `force` is truthy will exit immediately and " | ||||
|               "skip cleanup code.") { | ||||
|     janet_arity(argc, 0, 2); | ||||
|     int status; | ||||
|     if (argc == 0) { | ||||
|         status = EXIT_SUCCESS; | ||||
| @@ -242,7 +251,11 @@ JANET_CORE_FN(os_exit, | ||||
|         status = EXIT_FAILURE; | ||||
|     } | ||||
|     janet_deinit(); | ||||
|     exit(status); | ||||
|     if (argc >= 2 && janet_truthy(argv[1])) { | ||||
|         _Exit(status); | ||||
|     } else { | ||||
|         exit(status); | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -500,8 +513,11 @@ static int proc_get_status(JanetProc *proc) { | ||||
|         status = WEXITSTATUS(status); | ||||
|     } else if (WIFSTOPPED(status)) { | ||||
|         status = WSTOPSIG(status) + 128; | ||||
|     } else { | ||||
|     } else if (WIFSIGNALED(status)) { | ||||
|         status = WTERMSIG(status) + 128; | ||||
|     } else { | ||||
|         /* Could possibly return -1 but for now, just panic */ | ||||
|         janet_panicf("Undefined status code for process termination, %d.", status); | ||||
|     } | ||||
|     return status; | ||||
| } | ||||
| @@ -517,7 +533,6 @@ static JanetEVGenericMessage janet_proc_wait_subr(JanetEVGenericMessage args) { | ||||
|  | ||||
| /* Callback that is called in main thread when subroutine completes. */ | ||||
| static void janet_proc_wait_cb(JanetEVGenericMessage args) { | ||||
|     janet_ev_dec_refcount(); | ||||
|     JanetProc *proc = (JanetProc *) args.argp; | ||||
|     if (NULL != proc) { | ||||
|         int status = args.tag; | ||||
| @@ -526,11 +541,14 @@ static void janet_proc_wait_cb(JanetEVGenericMessage args) { | ||||
|         proc->flags &= ~JANET_PROC_WAITING; | ||||
|         janet_gcunroot(janet_wrap_abstract(proc)); | ||||
|         janet_gcunroot(janet_wrap_fiber(args.fiber)); | ||||
|         if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) { | ||||
|             JanetString s = janet_formatc("command failed with non-zero exit code %d", status); | ||||
|             janet_cancel(args.fiber, janet_wrap_string(s)); | ||||
|         } else { | ||||
|             janet_schedule(args.fiber, janet_wrap_integer(status)); | ||||
|         uint32_t sched_id = (uint32_t) args.argi; | ||||
|         if (janet_fiber_can_resume(args.fiber) && args.fiber->sched_id == sched_id) { | ||||
|             if ((status != 0) && (proc->flags & JANET_PROC_ERROR_NONZERO)) { | ||||
|                 JanetString s = janet_formatc("command failed with non-zero exit code %d", status); | ||||
|                 janet_cancel(args.fiber, janet_wrap_string(s)); | ||||
|             } else { | ||||
|                 janet_schedule(args.fiber, janet_wrap_integer(status)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -586,6 +604,7 @@ os_proc_wait_impl(JanetProc *proc) { | ||||
|     memset(&targs, 0, sizeof(targs)); | ||||
|     targs.argp = proc; | ||||
|     targs.fiber = janet_root_fiber(); | ||||
|     targs.argi = (uint32_t) targs.fiber->sched_id; | ||||
|     janet_gcroot(janet_wrap_abstract(proc)); | ||||
|     janet_gcroot(janet_wrap_fiber(targs.fiber)); | ||||
|     janet_ev_threaded_call(janet_proc_wait_subr, targs, janet_proc_wait_cb); | ||||
| @@ -612,12 +631,15 @@ os_proc_wait_impl(JanetProc *proc) { | ||||
|  | ||||
| JANET_CORE_FN(os_proc_wait, | ||||
|               "(os/proc-wait proc)", | ||||
|               "Block until the subprocess completes. Returns the subprocess return code.") { | ||||
|               "Suspend the current fiber until the subprocess `proc` completes. Once `proc` " | ||||
|               "completes, return the exit code of `proc`. If called more than once on the same " | ||||
|               "core/process value, will raise an error. When creating subprocesses using " | ||||
|               "`os/spawn`, this function should be called on the returned value to avoid zombie " | ||||
|               "processes.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
| #ifdef JANET_EV | ||||
|     os_proc_wait_impl(proc); | ||||
|     return janet_wrap_nil(); | ||||
| #else | ||||
|     return os_proc_wait_impl(proc); | ||||
| #endif | ||||
| @@ -641,7 +663,7 @@ static const struct keyword_signal signal_keywords[] = { | ||||
| #ifdef SIGTERM | ||||
|     {"term", SIGTERM}, | ||||
| #endif | ||||
| #ifdef SIGARLM | ||||
| #ifdef SIGALRM | ||||
|     {"alrm", SIGALRM}, | ||||
| #endif | ||||
| #ifdef SIGHUP | ||||
| @@ -706,15 +728,29 @@ static const struct keyword_signal signal_keywords[] = { | ||||
| #endif | ||||
|     {NULL, 0}, | ||||
| }; | ||||
|  | ||||
| static int get_signal_kw(const Janet *argv, int32_t n) { | ||||
|     JanetKeyword signal_kw = janet_getkeyword(argv, n); | ||||
|     const struct keyword_signal *ptr = signal_keywords; | ||||
|     while (ptr->keyword) { | ||||
|         if (!janet_cstrcmp(signal_kw, ptr->keyword)) { | ||||
|             return ptr->signal; | ||||
|         } | ||||
|         ptr++; | ||||
|     } | ||||
|     janet_panicf("undefined signal %v", argv[n]); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| JANET_CORE_FN(os_proc_kill, | ||||
|               "(os/proc-kill proc &opt wait signal)", | ||||
|               "Kill a subprocess by sending SIGKILL to it on posix systems, or by closing the process " | ||||
|               "handle on windows. If `wait` is truthy, will wait for the process to finish and " | ||||
|               "returns the exit code. Otherwise, returns `proc`. If signal is specified send it instead." | ||||
|               "Signal keywords are named after their C counterparts but in lowercase with the leading " | ||||
|               "`SIG` stripped. Signals are ignored on windows.") { | ||||
|               "Kill the subprocess `proc` by sending SIGKILL to it on POSIX systems, or by closing " | ||||
|               "the process handle on Windows. If `proc` has already completed, raise an error. If " | ||||
|               "`wait` is truthy, will wait for `proc` to complete and return the exit code (this " | ||||
|               "will raise an error if `proc` is being waited for). Otherwise, return `proc`. If " | ||||
|               "`signal` is provided, send it instead of SIGKILL. Signal keywords are named after " | ||||
|               "their C counterparts but in lowercase with the leading SIG stripped. `signal` is " | ||||
|               "ignored on Windows.") { | ||||
|     janet_arity(argc, 1, 3); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
|     if (proc->flags & JANET_PROC_WAITED) { | ||||
| @@ -731,29 +767,17 @@ JANET_CORE_FN(os_proc_kill, | ||||
| #else | ||||
|     int signal = -1; | ||||
|     if (argc == 3) { | ||||
|         JanetKeyword signal_kw = janet_getkeyword(argv, 2); | ||||
|         const struct keyword_signal *ptr = signal_keywords; | ||||
|         while (ptr->keyword) { | ||||
|             if (!janet_cstrcmp(signal_kw, ptr->keyword)) { | ||||
|                 signal = ptr->signal; | ||||
|                 break; | ||||
|             } | ||||
|             ptr++; | ||||
|         } | ||||
|         if (signal == -1) { | ||||
|             janet_panic("undefined signal"); | ||||
|         } | ||||
|         signal = get_signal_kw(argv, 2); | ||||
|     } | ||||
|     int status = kill(proc->pid, signal == -1 ? SIGKILL : signal); | ||||
|     if (status) { | ||||
|         janet_panic(strerror(errno)); | ||||
|         janet_panic(janet_strerror(errno)); | ||||
|     } | ||||
| #endif | ||||
|     /* After killing process we wait on it. */ | ||||
|     if (argc > 1 && janet_truthy(argv[1])) { | ||||
| #ifdef JANET_EV | ||||
|         os_proc_wait_impl(proc); | ||||
|         return janet_wrap_nil(); | ||||
| #else | ||||
|         return os_proc_wait_impl(proc); | ||||
| #endif | ||||
| @@ -764,8 +788,9 @@ JANET_CORE_FN(os_proc_kill, | ||||
|  | ||||
| JANET_CORE_FN(os_proc_close, | ||||
|               "(os/proc-close proc)", | ||||
|               "Wait on a process if it has not been waited on, and close pipes created by `os/spawn` " | ||||
|               "if they have not been closed. Returns nil.") { | ||||
|               "Close pipes created for subprocess `proc` by `os/spawn` if they have not been " | ||||
|               "closed. Then, if `proc` is not being waited for, wait. If this function waits, when " | ||||
|               "`proc` completes, return the exit code of `proc`. Otherwise, return nil.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     JanetProc *proc = janet_getabstract(argv, 0, &ProcAT); | ||||
| #ifdef JANET_EV | ||||
| @@ -783,7 +808,6 @@ JANET_CORE_FN(os_proc_close, | ||||
|     } | ||||
| #ifdef JANET_EV | ||||
|     os_proc_wait_impl(proc); | ||||
|     return janet_wrap_nil(); | ||||
| #else | ||||
|     return os_proc_wait_impl(proc); | ||||
| #endif | ||||
| @@ -803,6 +827,106 @@ static void close_handle(JanetHandle handle) { | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #ifdef JANET_EV | ||||
|  | ||||
| #ifndef JANET_WINDOWS | ||||
| static void janet_signal_callback(JanetEVGenericMessage msg) { | ||||
|     int sig = msg.tag; | ||||
|     if (msg.argi) janet_interpreter_interrupt_handled(NULL); | ||||
|     Janet handlerv = janet_table_get(&janet_vm.signal_handlers, janet_wrap_integer(sig)); | ||||
|     if (!janet_checktype(handlerv, JANET_FUNCTION)) { | ||||
|         /* Let another thread/process try to handle this */ | ||||
|         sigset_t set; | ||||
|         sigemptyset(&set); | ||||
|         sigaddset(&set, sig); | ||||
| #ifdef JANET_THREADS | ||||
|         pthread_sigmask(SIG_BLOCK, &set, NULL); | ||||
| #else | ||||
|         sigprocmask(SIG_BLOCK, &set, NULL); | ||||
| #endif | ||||
|         raise(sig); | ||||
|         return; | ||||
|     } | ||||
|     JanetFunction *handler = janet_unwrap_function(handlerv); | ||||
|     JanetFiber *fiber = janet_fiber(handler, 64, 0, NULL); | ||||
|     janet_schedule_soon(fiber, janet_wrap_nil(), JANET_SIGNAL_OK); | ||||
| } | ||||
|  | ||||
| static void janet_signal_trampoline_no_interrupt(int sig) { | ||||
|     /* Do not interact with global janet state here except for janet_ev_post_event, unsafe! */ | ||||
|     JanetEVGenericMessage msg; | ||||
|     memset(&msg, 0, sizeof(msg)); | ||||
|     msg.tag = sig; | ||||
|     janet_ev_post_event(&janet_vm, janet_signal_callback, msg); | ||||
| } | ||||
|  | ||||
| static void janet_signal_trampoline(int sig) { | ||||
|     /* Do not interact with global janet state here except for janet_ev_post_event, unsafe! */ | ||||
|     JanetEVGenericMessage msg; | ||||
|     memset(&msg, 0, sizeof(msg)); | ||||
|     msg.tag = sig; | ||||
|     msg.argi = 1; | ||||
|     janet_interpreter_interrupt(NULL); | ||||
|     janet_ev_post_event(&janet_vm, janet_signal_callback, msg); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| JANET_CORE_FN(os_sigaction, | ||||
|               "(os/sigaction which &opt handler interrupt-interpreter)", | ||||
|               "Add a signal handler for a given action. Use nil for the `handler` argument to remove a signal handler. " | ||||
|               "All signal handlers are the same as supported by `os/proc-kill`.") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_SIGNAL); | ||||
|     janet_arity(argc, 1, 3); | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argv; | ||||
|     janet_panic("unsupported on this platform"); | ||||
| #else | ||||
|     /* TODO - per thread signal masks */ | ||||
|     int rc; | ||||
|     int sig = get_signal_kw(argv, 0); | ||||
|     JanetFunction *handler = janet_optfunction(argv, argc, 1, NULL); | ||||
|     int can_interrupt = janet_optboolean(argv, argc, 2, 0); | ||||
|     Janet oldhandler = janet_table_get(&janet_vm.signal_handlers, janet_wrap_integer(sig)); | ||||
|     if (!janet_checktype(oldhandler, JANET_NIL)) { | ||||
|         janet_gcunroot(oldhandler); | ||||
|     } | ||||
|     if (NULL != handler) { | ||||
|         Janet handlerv = janet_wrap_function(handler); | ||||
|         janet_gcroot(handlerv); | ||||
|         janet_table_put(&janet_vm.signal_handlers, janet_wrap_integer(sig), handlerv); | ||||
|     } else { | ||||
|         janet_table_put(&janet_vm.signal_handlers, janet_wrap_integer(sig), janet_wrap_nil()); | ||||
|     } | ||||
|     struct sigaction action; | ||||
|     sigset_t mask; | ||||
|     sigaddset(&mask, sig); | ||||
|     memset(&action, 0, sizeof(action)); | ||||
|     action.sa_flags |= SA_RESTART; | ||||
|     if (can_interrupt) { | ||||
| #ifdef JANET_NO_INTERPRETER_INTERRUPT | ||||
|         janet_panic("interpreter interrupt not enabled"); | ||||
| #else | ||||
|         action.sa_handler = janet_signal_trampoline; | ||||
| #endif | ||||
|     } else { | ||||
|         action.sa_handler = janet_signal_trampoline_no_interrupt; | ||||
|     } | ||||
|     action.sa_mask = mask; | ||||
|     RETRY_EINTR(rc, sigaction(sig, &action, NULL)); | ||||
|     sigset_t set; | ||||
|     sigemptyset(&set); | ||||
|     sigaddset(&set, sig); | ||||
| #ifdef JANET_THREADS | ||||
|     pthread_sigmask(SIG_UNBLOCK, &set, NULL); | ||||
| #else | ||||
|     sigprocmask(SIG_UNBLOCK, &set, NULL); | ||||
| #endif | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* Create piped file for os/execute and os/spawn. Need to be careful that we mark | ||||
|    the error flag if we can't create pipe and don't leak handles. *handle will be cleaned | ||||
|    up by the calling function. If everything goes well, *handle is owned by the calling function, | ||||
| @@ -982,11 +1106,18 @@ static JanetFile *get_stdio_for_handle(JanetHandle handle, void *orig, int iswri | ||||
| } | ||||
| #endif | ||||
|  | ||||
| static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
| typedef enum { | ||||
|     JANET_EXECUTE_EXECUTE, | ||||
|     JANET_EXECUTE_SPAWN, | ||||
|     JANET_EXECUTE_EXEC | ||||
| } JanetExecuteMode; | ||||
|  | ||||
| static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); | ||||
|     janet_arity(argc, 1, 3); | ||||
|  | ||||
|     /* Get flags */ | ||||
|     int is_spawn = mode == JANET_EXECUTE_SPAWN; | ||||
|     uint64_t flags = 0; | ||||
|     if (argc > 1) { | ||||
|         flags = janet_getflags(argv, 1, "epxd"); | ||||
| @@ -1010,7 +1141,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
|     int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0; | ||||
|  | ||||
|     /* Get optional redirections */ | ||||
|     if (argc > 2) { | ||||
|     if (argc > 2 && (mode != JANET_EXECUTE_EXEC)) { | ||||
|         JanetDictView tab = janet_getdictionary(argv, 2); | ||||
|         Janet maybe_stdin = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("in")); | ||||
|         Janet maybe_stdout = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("out")); | ||||
| @@ -1131,12 +1262,28 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
|      * of posix_spawn would modify the argv array passed in. */ | ||||
|     char *const *cargv = (char *const *)child_argv; | ||||
|  | ||||
|     /* Use posix_spawn to spawn new process */ | ||||
|  | ||||
|     if (use_environ) { | ||||
|         janet_lock_environ(); | ||||
|     } | ||||
|  | ||||
|     /* exec mode */ | ||||
|     if (mode == JANET_EXECUTE_EXEC) { | ||||
|         int status; | ||||
|         if (!use_environ) { | ||||
|             environ = envp; | ||||
|         } | ||||
|         do { | ||||
|             if (janet_flag_at(flags, 1)) { | ||||
|                 status = execvp(cargv[0], cargv); | ||||
|             } else { | ||||
|                 status = execv(cargv[0], cargv); | ||||
|             } | ||||
|         } while (status == -1 && errno == EINTR); | ||||
|         janet_panicf("%p: %s", cargv[0], janet_strerror(errno ? errno : ENOENT)); | ||||
|     } | ||||
|  | ||||
|     /* Use posix_spawn to spawn new process */ | ||||
|  | ||||
|     /* Posix spawn setup */ | ||||
|     posix_spawn_file_actions_t actions; | ||||
|     posix_spawn_file_actions_init(&actions); | ||||
| @@ -1188,7 +1335,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
|     os_execute_cleanup(envp, child_argv); | ||||
|     if (status) { | ||||
|         /* correct for macos bug where errno is not set */ | ||||
|         janet_panicf("%p: %s", argv[0], strerror(errno ? errno : ENOENT)); | ||||
|         janet_panicf("%p: %s", argv[0], janet_strerror(errno ? errno : ENOENT)); | ||||
|     } | ||||
|  | ||||
| #endif | ||||
| @@ -1233,32 +1380,84 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_spawn) { | ||||
|  | ||||
| JANET_CORE_FN(os_execute, | ||||
|               "(os/execute args &opt flags env)", | ||||
|               "Execute a program on the system and pass it string arguments. `flags` " | ||||
|               "is a keyword that modifies how the program will execute.\n" | ||||
|               "* :e - enables passing an environment to the program. Without :e, the " | ||||
|               "Execute a program on the system and return the exit code. `args` is an array/tuple " | ||||
|               "of strings. The first string is the name of the program and the remainder are " | ||||
|               "arguments passed to the program. `flags` is a keyword made from the following " | ||||
|               "characters that modifies how the program executes:\n" | ||||
|               "* :e - enables passing an environment to the program. Without 'e', the " | ||||
|               "current environment is inherited.\n" | ||||
|               "* :p - allows searching the current PATH for the binary to execute. " | ||||
|               "Without this flag, binaries must use absolute paths.\n" | ||||
|               "* :x - raise error if exit code is non-zero.\n" | ||||
|               "* :d - Don't try and terminate the process on garbage collection (allow spawning zombies).\n" | ||||
|               "`env` is a table or struct mapping environment variables to values. It can also " | ||||
|               "contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. " | ||||
|               "These arguments should be core/file values. " | ||||
|               "Returns the exit status of the program.") { | ||||
|     return os_execute_impl(argc, argv, 0); | ||||
|               "* :p - allows searching the current PATH for the program to execute. " | ||||
|               "Without this flag, the first element of `args` must be an absolute path.\n" | ||||
|               "* :x - raises error if exit code is non-zero.\n" | ||||
|               "* :d - prevents the garbage collector terminating the program (if still running) " | ||||
|               "and calling the equivalent of `os/proc-wait` (allows zombie processes).\n" | ||||
|               "`env` is a table/struct mapping environment variables to values. It can also " | ||||
|               "contain the keys :in, :out, and :err, which allow redirecting stdio in the " | ||||
|               "subprocess. :in, :out, and :err should be core/file or core/stream values. " | ||||
|               "If core/stream values are used, the caller is responsible for ensuring pipes do not " | ||||
|               "cause the program to block and deadlock.") { | ||||
|     return os_execute_impl(argc, argv, JANET_EXECUTE_EXECUTE); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_spawn, | ||||
|               "(os/spawn args &opt flags env)", | ||||
|               "Execute a program on the system and return a handle to the process. Otherwise, takes the " | ||||
|               "same arguments as `os/execute`. Does not wait for the process. " | ||||
|               "For each of the :in, :out, and :err keys to the `env` argument, one " | ||||
|               "can also pass in the keyword `:pipe` " | ||||
|               "to get streams for standard IO of the subprocess that can be read from and written to. " | ||||
|               "The returned value `proc` has the fields :in, :out, :err, :return-code, and " | ||||
|               "the additional field :pid on unix-like platforms. Use `(os/proc-wait proc)` to rejoin the " | ||||
|               "subprocess or `(os/proc-kill proc)`.") { | ||||
|     return os_execute_impl(argc, argv, 1); | ||||
|               "Execute a program on the system and return a core/process value representing the " | ||||
|               "spawned subprocess. Takes the same arguments as `os/execute` but does not wait for " | ||||
|               "the subprocess to complete. Unlike `os/execute`, the value `:pipe` can be used for " | ||||
|               ":in, :out and :err keys in `env`. If used, the returned core/process will have a " | ||||
|               "writable stream in the :in field and readable streams in the :out and :err fields. " | ||||
|               "On non-Windows systems, the subprocess PID will be in the :pid field. The caller is " | ||||
|               "responsible for waiting on the process (e.g. by calling `os/proc-wait` on the " | ||||
|               "returned core/process value) to avoid creating zombie process. After the subprocess " | ||||
|               "completes, the exit value is in the :return-code field. If `flags` includes 'x', a " | ||||
|               "non-zero exit code will cause a waiting fiber to raise an error. The use of " | ||||
|               "`:pipe` may fail if there are too many active file descriptors. The caller is " | ||||
|               "responsible for closing pipes created by `:pipe` (either individually or using " | ||||
|               "`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring " | ||||
|               "pipes do not cause the program to block and deadlock.") { | ||||
|     return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_posix_exec, | ||||
|               "(os/posix-exec args &opt flags env)", | ||||
|               "Use the execvpe or execve system calls to replace the current process with an interface similar to os/execute. " | ||||
|               "However, instead of creating a subprocess, the current process is replaced. Is not supported on Windows, and " | ||||
|               "does not allow redirection of stdio.") { | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     return os_execute_impl(argc, argv, JANET_EXECUTE_EXEC); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_posix_fork, | ||||
|               "(os/posix-fork)", | ||||
|               "Make a `fork` system call and create a new process. Return nil if in the new process, otherwise a core/process object (as returned by os/spawn). " | ||||
|               "Not supported on all systems (POSIX only).") { | ||||
|     janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); | ||||
|     janet_fixarity(argc, 0); | ||||
|     (void) argv; | ||||
| #ifdef JANET_WINDOWS | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     pid_t result; | ||||
|     do { | ||||
|         result = fork(); | ||||
|     } while (result == -1 && errno == EINTR); | ||||
|     if (result == -1) { | ||||
|         janet_panic(janet_strerror(errno)); | ||||
|     } | ||||
|     if (result) { | ||||
|         JanetProc *proc = janet_abstract(&ProcAT, sizeof(JanetProc)); | ||||
|         memset(proc, 0, sizeof(JanetProc)); | ||||
|         proc->pid = result; | ||||
|         proc->flags = JANET_PROC_ALLOW_ZOMBIE; | ||||
|         return janet_wrap_abstract(proc); | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| @@ -1334,8 +1533,8 @@ JANET_CORE_FN(os_getenv, | ||||
|     janet_sandbox_assert(JANET_SANDBOX_ENV); | ||||
|     janet_arity(argc, 1, 2); | ||||
|     const char *cstr = janet_getcstring(argv, 0); | ||||
|     const char *res = getenv(cstr); | ||||
|     janet_lock_environ(); | ||||
|     const char *res = getenv(cstr); | ||||
|     Janet ret = res | ||||
|                 ? janet_cstringv(res) | ||||
|                 : argc == 2 | ||||
| @@ -1380,34 +1579,51 @@ JANET_CORE_FN(os_time, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_clock, | ||||
|               "(os/clock &opt source)", | ||||
|               "Return the number of whole + fractional seconds of the requested clock source.\n\n" | ||||
|               "(os/clock &opt source format)", | ||||
|               "Return the current time of the requested clock source.\n\n" | ||||
|               "The `source` argument selects the clock source to use, when not specified the default " | ||||
|               "is `:realtime`:\n" | ||||
|               "- :realtime: Return the real (i.e., wall-clock) time. This clock is affected by discontinuous " | ||||
|               "  jumps in the system time\n" | ||||
|               "- :monotonic: Return the number of whole + fractional seconds since some fixed point in " | ||||
|               "  time. The clock is guaranteed to be non-decreasing in real time.\n" | ||||
|               "- :cputime: Return the CPU time consumed by this process  (i.e. all threads in the process)\n") { | ||||
|               "- :cputime: Return the CPU time consumed by this process  (i.e. all threads in the process)\n" | ||||
|               "The `format` argument selects the type of output, when not specified the default is `:double`:\n" | ||||
|               "- :double: Return the number of seconds + fractional seconds as a double\n" | ||||
|               "- :int: Return the number of seconds as an integer\n" | ||||
|               "- :tuple: Return a 2 integer tuple [seconds, nanoseconds]\n") { | ||||
|     enum JanetTimeSource source; | ||||
|     janet_sandbox_assert(JANET_SANDBOX_HRTIME); | ||||
|     janet_arity(argc, 0, 1); | ||||
|     enum JanetTimeSource source = JANET_TIME_REALTIME; | ||||
|     if (argc == 1) { | ||||
|         JanetKeyword sourcestr = janet_getkeyword(argv, 0); | ||||
|         if (janet_cstrcmp(sourcestr, "realtime") == 0) { | ||||
|             source = JANET_TIME_REALTIME; | ||||
|         } else if (janet_cstrcmp(sourcestr, "monotonic") == 0) { | ||||
|             source = JANET_TIME_MONOTONIC; | ||||
|         } else if (janet_cstrcmp(sourcestr, "cputime") == 0) { | ||||
|             source = JANET_TIME_CPUTIME; | ||||
|         } else { | ||||
|             janet_panicf("expected :realtime, :monotonic, or :cputime, got %v", argv[0]); | ||||
|         } | ||||
|     janet_arity(argc, 0, 2); | ||||
|  | ||||
|     JanetKeyword sourcestr = janet_optkeyword(argv, argc, 0, NULL); | ||||
|     if (sourcestr == NULL || janet_cstrcmp(sourcestr, "realtime") == 0) { | ||||
|         source = JANET_TIME_REALTIME; | ||||
|     } else if (janet_cstrcmp(sourcestr, "monotonic") == 0) { | ||||
|         source = JANET_TIME_MONOTONIC; | ||||
|     } else if (janet_cstrcmp(sourcestr, "cputime") == 0) { | ||||
|         source = JANET_TIME_CPUTIME; | ||||
|     } else { | ||||
|         janet_panicf("expected :realtime, :monotonic, or :cputime, got %v", argv[0]); | ||||
|     } | ||||
|  | ||||
|     struct timespec tv; | ||||
|     if (janet_gettime(&tv, source)) janet_panic("could not get time"); | ||||
|     double dtime = tv.tv_sec + (tv.tv_nsec / 1E9); | ||||
|     return janet_wrap_number(dtime); | ||||
|  | ||||
|     JanetKeyword formatstr = janet_optkeyword(argv, argc, 1, NULL); | ||||
|     if (formatstr == NULL || janet_cstrcmp(formatstr, "double") == 0) { | ||||
|         double dtime = (double)(tv.tv_sec + (tv.tv_nsec / 1E9)); | ||||
|         return janet_wrap_number(dtime); | ||||
|     } else if (janet_cstrcmp(formatstr, "int") == 0) { | ||||
|         return janet_wrap_number((double)(tv.tv_sec)); | ||||
|     } else if (janet_cstrcmp(formatstr, "tuple") == 0) { | ||||
|         Janet tup[2] = {janet_wrap_number((double)tv.tv_sec), | ||||
|                         janet_wrap_number((double)tv.tv_nsec) | ||||
|                        }; | ||||
|         return janet_wrap_tuple(janet_tuple_n(tup, 2)); | ||||
|     } else { | ||||
|         janet_panicf("expected :double, :int, or :tuple, got %v", argv[1]); | ||||
|     } | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_sleep, | ||||
| @@ -1443,7 +1659,7 @@ JANET_CORE_FN(os_isatty, | ||||
|     return janet_wrap_boolean(_isatty(fd)); | ||||
| #else | ||||
|     int fd = fileno(f); | ||||
|     if (fd == -1) janet_panic(strerror(errno)); | ||||
|     if (fd == -1) janet_panic(janet_strerror(errno)); | ||||
|     return janet_wrap_boolean(isatty(fd)); | ||||
| #endif | ||||
| } | ||||
| @@ -1671,14 +1887,13 @@ JANET_CORE_FN(os_mktime, | ||||
|         /* utc time */ | ||||
| #ifdef JANET_NO_UTC_MKTIME | ||||
|         janet_panic("os/mktime UTC not supported on this platform"); | ||||
|         return janet_wrap_nil(); | ||||
| #else | ||||
|         t = timegm(&t_info); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     if (t == (time_t) -1) { | ||||
|         janet_panicf("%s", strerror(errno)); | ||||
|         janet_panicf("%s", janet_strerror(errno)); | ||||
|     } | ||||
|  | ||||
|     return janet_wrap_number((double)t); | ||||
| @@ -1690,6 +1905,43 @@ JANET_CORE_FN(os_mktime, | ||||
| #define j_symlink symlink | ||||
| #endif | ||||
|  | ||||
| JANET_CORE_FN(os_setlocale, | ||||
|               "(os/setlocale &opt locale category)", | ||||
|               "Set the system locale, which affects how dates and numbers are formatted. " | ||||
|               "Passing nil to locale will return the current locale. Category can be one of:\n\n" | ||||
|               " * :all (default)\n" | ||||
|               " * :collate\n" | ||||
|               " * :ctype\n" | ||||
|               " * :monetary\n" | ||||
|               " * :numeric\n" | ||||
|               " * :time\n\n" | ||||
|               "Returns the new locale if set successfully, otherwise nil. Note that this will affect " | ||||
|               "other functions such as `os/strftime` and even `printf`.") { | ||||
|     janet_arity(argc, 0, 2); | ||||
|     const char *locale_name = janet_optcstring(argv, argc, 0, NULL); | ||||
|     int category_int = LC_ALL; | ||||
|     if (argc > 1 && !janet_checktype(argv[1], JANET_NIL)) { | ||||
|         if (janet_keyeq(argv[1], "all")) { | ||||
|             category_int = LC_ALL; | ||||
|         } else if (janet_keyeq(argv[1], "collate")) { | ||||
|             category_int = LC_COLLATE; | ||||
|         } else if (janet_keyeq(argv[1], "ctype")) { | ||||
|             category_int = LC_CTYPE; | ||||
|         } else if (janet_keyeq(argv[1], "monetary")) { | ||||
|             category_int = LC_MONETARY; | ||||
|         } else if (janet_keyeq(argv[1], "numeric")) { | ||||
|             category_int = LC_NUMERIC; | ||||
|         } else if (janet_keyeq(argv[1], "time")) { | ||||
|             category_int = LC_TIME; | ||||
|         } else { | ||||
|             janet_panicf("expected one of :all, :collate, :ctype, :monetary, :numeric, or :time, got %v", argv[1]); | ||||
|         } | ||||
|     } | ||||
|     const char *old = setlocale(category_int, locale_name); | ||||
|     if (old == NULL) return janet_wrap_nil(); | ||||
|     return janet_cstringv(old); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_link, | ||||
|               "(os/link oldpath newpath &opt symlink)", | ||||
|               "Create a link at newpath that points to oldpath and returns nil. " | ||||
| @@ -1701,13 +1953,12 @@ JANET_CORE_FN(os_link, | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("os/link not supported on Windows"); | ||||
|     return janet_wrap_nil(); | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     const char *oldpath = janet_getcstring(argv, 0); | ||||
|     const char *newpath = janet_getcstring(argv, 1); | ||||
|     int res = ((argc == 3 && janet_truthy(argv[2])) ? j_symlink : link)(oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", strerror(errno), oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", janet_strerror(errno), oldpath, newpath); | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
| @@ -1720,13 +1971,12 @@ JANET_CORE_FN(os_symlink, | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("os/symlink not supported on Windows"); | ||||
|     return janet_wrap_nil(); | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     const char *oldpath = janet_getcstring(argv, 0); | ||||
|     const char *newpath = janet_getcstring(argv, 1); | ||||
|     int res = j_symlink(oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", strerror(errno), oldpath, newpath); | ||||
|     if (-1 == res) janet_panicf("%s: %s -> %s", janet_strerror(errno), oldpath, newpath); | ||||
|     return janet_wrap_nil(); | ||||
| #endif | ||||
| } | ||||
| @@ -1748,7 +1998,7 @@ JANET_CORE_FN(os_mkdir, | ||||
| #endif | ||||
|     if (res == 0) return janet_wrap_true(); | ||||
|     if (errno == EEXIST) return janet_wrap_false(); | ||||
|     janet_panicf("%s: %s", strerror(errno), path); | ||||
|     janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_rmdir, | ||||
| @@ -1762,7 +2012,7 @@ JANET_CORE_FN(os_rmdir, | ||||
| #else | ||||
|     int res = rmdir(path); | ||||
| #endif | ||||
|     if (-1 == res) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1777,7 +2027,7 @@ JANET_CORE_FN(os_cd, | ||||
| #else | ||||
|     int res = chdir(path); | ||||
| #endif | ||||
|     if (-1 == res) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1801,7 +2051,7 @@ JANET_CORE_FN(os_touch, | ||||
|         bufp = NULL; | ||||
|     } | ||||
|     int res = utime(path, bufp); | ||||
|     if (-1 == res) janet_panic(strerror(errno)); | ||||
|     if (-1 == res) janet_panic(janet_strerror(errno)); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1811,7 +2061,7 @@ JANET_CORE_FN(os_remove, | ||||
|     janet_fixarity(argc, 1); | ||||
|     const char *path = janet_getcstring(argv, 0); | ||||
|     int status = remove(path); | ||||
|     if (-1 == status) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == status) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -1823,14 +2073,13 @@ JANET_CORE_FN(os_readlink, | ||||
| #ifdef JANET_WINDOWS | ||||
|     (void) argc; | ||||
|     (void) argv; | ||||
|     janet_panic("os/readlink not supported on Windows"); | ||||
|     return janet_wrap_nil(); | ||||
|     janet_panic("not supported on Windows"); | ||||
| #else | ||||
|     static char buffer[PATH_MAX]; | ||||
|     const char *path = janet_getcstring(argv, 0); | ||||
|     ssize_t len = readlink(path, buffer, sizeof buffer); | ||||
|     if (len < 0 || (size_t)len >= sizeof buffer) | ||||
|         janet_panicf("%s: %s", strerror(errno), path); | ||||
|         janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_stringv((const uint8_t *)buffer, len); | ||||
| #endif | ||||
| } | ||||
| @@ -2080,7 +2329,6 @@ static Janet os_stat_or_lstat(int do_lstat, int32_t argc, Janet *argv) { | ||||
|             return sg->fn(&st); | ||||
|         } | ||||
|         janet_panicf("unexpected keyword %v", janet_wrap_keyword(key)); | ||||
|         return janet_wrap_nil(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -2125,7 +2373,7 @@ JANET_CORE_FN(os_chmod, | ||||
| #else | ||||
|     int res = chmod(path, os_getmode(argv, 1)); | ||||
| #endif | ||||
|     if (-1 == res) janet_panicf("%s: %s", strerror(errno), path); | ||||
|     if (-1 == res) janet_panicf("%s: %s", janet_strerror(errno), path); | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
|  | ||||
| @@ -2161,7 +2409,7 @@ JANET_CORE_FN(os_dir, | ||||
|         janet_panicf("path too long: %s", dir); | ||||
|     sprintf(pattern, "%s/*", dir); | ||||
|     intptr_t res = _findfirst(pattern, &afile); | ||||
|     if (-1 == res) janet_panicv(janet_cstringv(strerror(errno))); | ||||
|     if (-1 == res) janet_panicv(janet_cstringv(janet_strerror(errno))); | ||||
|     do { | ||||
|         if (strcmp(".", afile.name) && strcmp("..", afile.name)) { | ||||
|             janet_array_push(paths, janet_cstringv(afile.name)); | ||||
| @@ -2172,8 +2420,18 @@ JANET_CORE_FN(os_dir, | ||||
|     /* Read directory items with opendir / readdir / closedir */ | ||||
|     struct dirent *dp; | ||||
|     DIR *dfd = opendir(dir); | ||||
|     if (dfd == NULL) janet_panicf("cannot open directory %s", dir); | ||||
|     while ((dp = readdir(dfd)) != NULL) { | ||||
|     if (dfd == NULL) janet_panicf("cannot open directory %s: %s", dir, janet_strerror(errno)); | ||||
|     for (;;) { | ||||
|         errno = 0; | ||||
|         dp = readdir(dfd); | ||||
|         if (dp == NULL) { | ||||
|             if (errno) { | ||||
|                 int olderr = errno; | ||||
|                 closedir(dfd); | ||||
|                 janet_panicf("failed to read directory %s: %s", dir, janet_strerror(olderr)); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) { | ||||
|             continue; | ||||
|         } | ||||
| @@ -2193,7 +2451,7 @@ JANET_CORE_FN(os_rename, | ||||
|     const char *dest = janet_getcstring(argv, 1); | ||||
|     int status = rename(src, dest); | ||||
|     if (status) { | ||||
|         janet_panic(strerror(errno)); | ||||
|         janet_panic(janet_strerror(errno)); | ||||
|     } | ||||
|     return janet_wrap_nil(); | ||||
| } | ||||
| @@ -2213,7 +2471,7 @@ JANET_CORE_FN(os_realpath, | ||||
| #else | ||||
|     char *dest = realpath(src, NULL); | ||||
| #endif | ||||
|     if (NULL == dest) janet_panicf("%s: %s", strerror(errno), src); | ||||
|     if (NULL == dest) janet_panicf("%s: %s", janet_strerror(errno), src); | ||||
|     Janet ret = janet_cstringv(dest); | ||||
|     janet_free(dest); | ||||
|     return ret; | ||||
| @@ -2419,7 +2677,7 @@ JANET_CORE_FN(os_open, | ||||
|     } else if (write_flag && !read_flag) { | ||||
|         open_flags |= O_WRONLY; | ||||
|     } else { | ||||
|         open_flags = O_RDWR; | ||||
|         open_flags |= O_RDWR; | ||||
|     } | ||||
|  | ||||
|     do { | ||||
| @@ -2431,16 +2689,24 @@ JANET_CORE_FN(os_open, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(os_pipe, | ||||
|               "(os/pipe)", | ||||
|               "(os/pipe &opt flags)", | ||||
|               "Create a readable stream and a writable stream that are connected. Returns a two-element " | ||||
|               "tuple where the first element is a readable stream and the second element is the writable " | ||||
|               "stream.") { | ||||
|               "stream. `flags` is a keyword set of flags to disable non-blocking settings on the ends of the pipe. " | ||||
|               "This may be desired if passing the pipe to a subprocess with `os/spawn`.\n\n" | ||||
|               "* :W - sets the writable end of the pipe to a blocking stream.\n" | ||||
|               "* :R - sets the readable end of the pipe to a blocking stream.\n\n" | ||||
|               "By default, both ends of the pipe are non-blocking for use with the `ev` module.") { | ||||
|     (void) argv; | ||||
|     janet_fixarity(argc, 0); | ||||
|     janet_arity(argc, 0, 1); | ||||
|     JanetHandle fds[2]; | ||||
|     if (janet_make_pipe(fds, 0)) janet_panicv(janet_ev_lasterr()); | ||||
|     JanetStream *reader = janet_stream(fds[0], JANET_STREAM_READABLE, NULL); | ||||
|     JanetStream *writer = janet_stream(fds[1], JANET_STREAM_WRITABLE, NULL); | ||||
|     int flags = 0; | ||||
|     if (argc > 0 && !janet_checktype(argv[0], JANET_NIL)) { | ||||
|         flags = (int) janet_getflags(argv, 0, "WR"); | ||||
|     } | ||||
|     if (janet_make_pipe(fds, flags)) janet_panicv(janet_ev_lasterr()); | ||||
|     JanetStream *reader = janet_stream(fds[0], (flags & 2) ? 0 : JANET_STREAM_READABLE, NULL); | ||||
|     JanetStream *writer = janet_stream(fds[1], (flags & 1) ? 0 : JANET_STREAM_WRITABLE, NULL); | ||||
|     Janet tup[2] = {janet_wrap_abstract(reader), janet_wrap_abstract(writer)}; | ||||
|     return janet_wrap_tuple(janet_tuple_n(tup, 2)); | ||||
| } | ||||
| @@ -2487,6 +2753,7 @@ void janet_lib_os(JanetTable *env) { | ||||
|         JANET_CORE_REG("os/strftime", os_strftime), | ||||
|         JANET_CORE_REG("os/sleep", os_sleep), | ||||
|         JANET_CORE_REG("os/isatty", os_isatty), | ||||
|         JANET_CORE_REG("os/setlocale", os_setlocale), | ||||
|  | ||||
|         /* env functions */ | ||||
|         JANET_CORE_REG("os/environ", os_environ), | ||||
| @@ -2523,6 +2790,8 @@ void janet_lib_os(JanetTable *env) { | ||||
|         JANET_CORE_REG("os/execute", os_execute), | ||||
|         JANET_CORE_REG("os/spawn", os_spawn), | ||||
|         JANET_CORE_REG("os/shell", os_shell), | ||||
|         JANET_CORE_REG("os/posix-fork", os_posix_fork), | ||||
|         JANET_CORE_REG("os/posix-exec", os_posix_exec), | ||||
|         /* no need to sandbox process management if you can't create processes | ||||
|          * (allows for limited functionality if use exposes C-functions to create specific processes) */ | ||||
|         JANET_CORE_REG("os/proc-wait", os_proc_wait), | ||||
| @@ -2536,6 +2805,7 @@ void janet_lib_os(JanetTable *env) { | ||||
| #ifdef JANET_EV | ||||
|         JANET_CORE_REG("os/open", os_open), /* fs read and write */ | ||||
|         JANET_CORE_REG("os/pipe", os_pipe), | ||||
|         JANET_CORE_REG("os/sigaction", os_sigaction), | ||||
| #endif | ||||
| #endif | ||||
|         JANET_REG_END | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -231,7 +231,7 @@ static void delim_error(JanetParser *parser, size_t stack_index, char c, const c | ||||
|                 janet_buffer_push_u8(buffer, '`'); | ||||
|             } | ||||
|         } | ||||
|         janet_formatb(buffer, " opened at line %d, column %d", s->line, s->column); | ||||
|         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; | ||||
| @@ -363,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') { | ||||
| @@ -374,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--; | ||||
|         } | ||||
|     } | ||||
| @@ -467,8 +468,13 @@ static int tokenchar(JanetParser *p, JanetParseState *state, uint8_t c) { | ||||
|             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)) { | ||||
|   | ||||
							
								
								
									
										229
									
								
								src/core/peg.c
									
									
									
									
									
								
							
							
						
						
									
										229
									
								
								src/core/peg.c
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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; | ||||
| @@ -338,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; | ||||
|                 } | ||||
| @@ -461,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; | ||||
| @@ -482,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]; | ||||
| @@ -601,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 { | ||||
| @@ -995,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) { | ||||
| @@ -1018,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); | ||||
| @@ -1107,6 +1258,30 @@ static void spec_matchtime(Builder *b, int32_t argc, const Janet *argv) { | ||||
|     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 | ||||
| @@ -1180,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}, | ||||
| @@ -1190,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}, | ||||
| @@ -1284,6 +1464,11 @@ 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)); | ||||
| @@ -1431,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; | ||||
| @@ -1524,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: | ||||
| @@ -1535,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; | ||||
|         } | ||||
| @@ -1632,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]); | ||||
| @@ -1652,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]); | ||||
| @@ -1677,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); | ||||
| @@ -1771,7 +1974,7 @@ JANET_CORE_FN(cfun_peg_replace_all, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_peg_replace, | ||||
|               "(peg/replace peg repl text &opt start & args)", | ||||
|               "(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 " | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 @@ | ||||
| #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. */ | ||||
| @@ -38,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' */ | ||||
| @@ -374,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: | ||||
| @@ -772,6 +779,8 @@ struct FmtMapping { | ||||
| /* 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}, | ||||
| @@ -823,7 +832,7 @@ static const char *scanformat( | ||||
|         if (loc != NULL && *loc != '\0') { | ||||
|             const char *mapping = get_fmt_mapping(*p2++); | ||||
|             size_t len = strlen(mapping); | ||||
|             strcpy(form, mapping); | ||||
|             memcpy(form, mapping, len); | ||||
|             form += len; | ||||
|         } else { | ||||
|             *(form++) = *(p2++); | ||||
| @@ -850,13 +859,19 @@ 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': { | ||||
|                     int64_t n = va_arg(args, int); | ||||
|                     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; | ||||
|                 } | ||||
| @@ -864,7 +879,7 @@ void janet_formatbv(JanetBuffer *b, const char *format, va_list args) { | ||||
|                 case 'X': | ||||
|                 case 'o': | ||||
|                 case 'u': { | ||||
|                     uint64_t n = va_arg(args, unsigned int); | ||||
|                     uint64_t n = va_arg(args, uint64_t); | ||||
|                     nb = snprintf(item, MAX_ITEM, form, n); | ||||
|                     break; | ||||
|                 } | ||||
| @@ -908,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; | ||||
|                 } | ||||
| @@ -1017,6 +1032,8 @@ void janet_buffer_format( | ||||
|                                   janet_getinteger(argv, arg)); | ||||
|                     break; | ||||
|                 } | ||||
|                 case 'D': | ||||
|                 case 'I': | ||||
|                 case 'd': | ||||
|                 case 'i': { | ||||
|                     int64_t n = janet_getinteger64(argv, arg); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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) 2023 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) 2023 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,26 +28,29 @@ | ||||
|  | ||||
| /* 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) { | ||||
| @@ -57,12 +60,20 @@ int janet_dobytes(JanetTable *env, const uint8_t *bytes, int32_t len, const char | ||||
|                 } | ||||
|             } 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_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; | ||||
| @@ -72,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 = (int32_t) parser.line; | ||||
|                 int32_t col = (int32_t) 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; | ||||
| @@ -89,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; | ||||
|         } | ||||
| @@ -99,14 +110,19 @@ 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) { | ||||
|         janet_gcroot(ret); | ||||
|         if (fiber) { | ||||
|             janet_gcroot(janet_wrap_fiber(fiber)); | ||||
|         } | ||||
|         janet_loop(); | ||||
|         janet_gcunroot(ret); | ||||
|         if (fiber) { | ||||
|             janet_gcunroot(janet_wrap_fiber(fiber)); | ||||
|             ret = fiber->last_value; | ||||
|         } | ||||
|     } | ||||
| #endif | ||||
|     if (out) *out = ret; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -149,7 +149,7 @@ static int destructure(JanetCompiler *c, | ||||
|                        JanetTable *attr) { | ||||
|     switch (janet_type(left)) { | ||||
|         default: | ||||
|             janetc_error(c, janet_formatc("unexpected type in destruction, got %v", left)); | ||||
|             janetc_error(c, janet_formatc("unexpected type in destructuring, got %v", left)); | ||||
|             return 1; | ||||
|         case JANET_SYMBOL: | ||||
|             /* Leaf, assign right to left */ | ||||
| @@ -530,6 +530,26 @@ static JanetSlot janetc_def(JanetFopts opts, int32_t argn, const Janet *argv) { | ||||
|     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 | ||||
|  * ... | ||||
| @@ -550,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"); | ||||
| @@ -572,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; | ||||
| @@ -595,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"); | ||||
| @@ -605,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); | ||||
| @@ -716,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) { | ||||
| @@ -726,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) { | ||||
| @@ -741,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 | ||||
|  * ... | ||||
| @@ -771,6 +787,7 @@ 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; | ||||
| @@ -784,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; | ||||
| @@ -800,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) { | ||||
| @@ -901,6 +925,7 @@ 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; | ||||
| @@ -919,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)) { | ||||
| @@ -1079,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); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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) 2023 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 | ||||
| @@ -89,7 +89,7 @@ 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 functions in vm.c */ | ||||
| @@ -100,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. */ | ||||
| @@ -121,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; | ||||
| @@ -147,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; | ||||
| @@ -154,12 +162,10 @@ 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) | ||||
| @@ -175,6 +181,9 @@ struct JanetVM { | ||||
|     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; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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); | ||||
| } | ||||
|  | ||||
| @@ -549,12 +549,12 @@ JANET_CORE_FN(cfun_string_format, | ||||
|               "- `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)" | ||||
|               "- `V`: format with (string x)" | ||||
|               "- `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 speficiers can take a precision " | ||||
|               "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" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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) 2023 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 | ||||
| @@ -294,6 +294,16 @@ JANET_CORE_FN(cfun_struct_to_table, | ||||
|     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[] = { | ||||
| @@ -301,6 +311,7 @@ void janet_lib_struct(JanetTable *env) { | ||||
|         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) 2023 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 | ||||
| @@ -234,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; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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) 2023 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 | ||||
| @@ -67,7 +67,7 @@ static JanetTable *janet_table_init_impl(JanetTable *table, int32_t capacity, in | ||||
|     return table; | ||||
| } | ||||
|  | ||||
| /* Initialize a table (for use withs scratch memory) */ | ||||
| /* Initialize a table (for use with scratch memory) */ | ||||
| JanetTable *janet_table_init(JanetTable *table, int32_t capacity) { | ||||
|     return janet_table_init_impl(table, capacity, 1); | ||||
| } | ||||
| @@ -87,11 +87,27 @@ void janet_table_deinit(JanetTable *table) { | ||||
| } | ||||
|  | ||||
| /* Create a new table */ | ||||
|  | ||||
| JanetTable *janet_table(int32_t capacity) { | ||||
|     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE, sizeof(JanetTable)); | ||||
|     return janet_table_init_impl(table, capacity, 0); | ||||
| } | ||||
|  | ||||
| JanetTable *janet_table_weakk(int32_t capacity) { | ||||
|     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE_WEAKK, sizeof(JanetTable)); | ||||
|     return janet_table_init_impl(table, capacity, 0); | ||||
| } | ||||
|  | ||||
| JanetTable *janet_table_weakv(int32_t capacity) { | ||||
|     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE_WEAKV, sizeof(JanetTable)); | ||||
|     return janet_table_init_impl(table, capacity, 0); | ||||
| } | ||||
|  | ||||
| JanetTable *janet_table_weakkv(int32_t capacity) { | ||||
|     JanetTable *table = janet_gcalloc(JANET_MEMORY_TABLE_WEAKKV, sizeof(JanetTable)); | ||||
|     return janet_table_init_impl(table, capacity, 0); | ||||
| } | ||||
|  | ||||
| /* Find the bucket that contains the given key. Will also return | ||||
|  * bucket where key should go if not in the table. */ | ||||
| JanetKV *janet_table_find(JanetTable *t, Janet key) { | ||||
| @@ -111,12 +127,11 @@ static void janet_table_rehash(JanetTable *t, int32_t size) { | ||||
|             JANET_OUT_OF_MEMORY; | ||||
|         } | ||||
|     } | ||||
|     int32_t i, oldcapacity; | ||||
|     oldcapacity = t->capacity; | ||||
|     int32_t oldcapacity = t->capacity; | ||||
|     t->data = newdata; | ||||
|     t->capacity = size; | ||||
|     t->deleted = 0; | ||||
|     for (i = 0; i < oldcapacity; i++) { | ||||
|     for (int32_t i = 0; i < oldcapacity; i++) { | ||||
|         JanetKV *kv = olddata + i; | ||||
|         if (!janet_checktype(kv->key, JANET_NIL)) { | ||||
|             JanetKV *newkv = janet_table_find(t, kv->key); | ||||
| @@ -298,12 +313,40 @@ JANET_CORE_FN(cfun_table_new, | ||||
|               "Creates a new empty table with pre-allocated memory " | ||||
|               "for `capacity` entries. This means that if one knows the number of " | ||||
|               "entries going into a table on creation, extra memory allocation " | ||||
|               "can be avoided. Returns the new table.") { | ||||
|               "can be avoided. " | ||||
|               "Returns the new table.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     int32_t cap = janet_getnat(argv, 0); | ||||
|     return janet_wrap_table(janet_table(cap)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_weak, | ||||
|               "(table/weak capacity)", | ||||
|               "Creates a new empty table with weak references to keys and values. Similar to `table/new`. " | ||||
|               "Returns the new table.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     int32_t cap = janet_getnat(argv, 0); | ||||
|     return janet_wrap_table(janet_table_weakkv(cap)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_weak_keys, | ||||
|               "(table/weak-keys capacity)", | ||||
|               "Creates a new empty table with weak references to keys and normal references to values. Similar to `table/new`. " | ||||
|               "Returns the new table.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     int32_t cap = janet_getnat(argv, 0); | ||||
|     return janet_wrap_table(janet_table_weakk(cap)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_weak_values, | ||||
|               "(table/weak-values capacity)", | ||||
|               "Creates a new empty table with normal references to keys and weak references to values. Similar to `table/new`. " | ||||
|               "Returns the new table.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|     int32_t cap = janet_getnat(argv, 0); | ||||
|     return janet_wrap_table(janet_table_weakv(cap)); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_getproto, | ||||
|               "(table/getproto tab)", | ||||
|               "Get the prototype table of a table. Returns nil if the table " | ||||
| @@ -329,12 +372,14 @@ JANET_CORE_FN(cfun_table_setproto, | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_tostruct, | ||||
|               "(table/to-struct tab)", | ||||
|               "Convert a table to a struct. Returns a new struct. This function " | ||||
|               "does not take into account prototype tables.") { | ||||
|     janet_fixarity(argc, 1); | ||||
|               "(table/to-struct tab &opt proto)", | ||||
|               "Convert a table to a struct. Returns a new struct.") { | ||||
|     janet_arity(argc, 1, 2); | ||||
|     JanetTable *t = janet_gettable(argv, 0); | ||||
|     return janet_wrap_struct(janet_table_to_struct(t)); | ||||
|     JanetStruct proto = janet_optstruct(argv, argc, 1, NULL); | ||||
|     JanetStruct st = janet_table_to_struct(t); | ||||
|     janet_struct_proto(st) = proto; | ||||
|     return janet_wrap_struct(st); | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_table_rawget, | ||||
| @@ -377,6 +422,9 @@ JANET_CORE_FN(cfun_table_proto_flatten, | ||||
| void janet_lib_table(JanetTable *env) { | ||||
|     JanetRegExt table_cfuns[] = { | ||||
|         JANET_CORE_REG("table/new", cfun_table_new), | ||||
|         JANET_CORE_REG("table/weak", cfun_table_weak), | ||||
|         JANET_CORE_REG("table/weak-keys", cfun_table_weak_keys), | ||||
|         JANET_CORE_REG("table/weak-values", cfun_table_weak_values), | ||||
|         JANET_CORE_REG("table/to-struct", cfun_table_tostruct), | ||||
|         JANET_CORE_REG("table/getproto", cfun_table_getproto), | ||||
|         JANET_CORE_REG("table/setproto", cfun_table_setproto), | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -116,6 +116,34 @@ JANET_CORE_FN(cfun_tuple_setmap, | ||||
|     return argv[0]; | ||||
| } | ||||
|  | ||||
| JANET_CORE_FN(cfun_tuple_join, | ||||
|               "(tuple/join & parts)", | ||||
|               "Create a tuple by joining together other tuples and arrays.") { | ||||
|     janet_arity(argc, 0, -1); | ||||
|     int32_t total_len = 0; | ||||
|     for (int32_t i = 0; i < argc; i++) { | ||||
|         int32_t 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 (INT32_MAX - total_len < len) { | ||||
|             janet_panic("tuple too large"); | ||||
|         } | ||||
|         total_len += len; | ||||
|     } | ||||
|     Janet *tup = janet_tuple_begin(total_len); | ||||
|     Janet *tup_cursor = tup; | ||||
|     for (int32_t i = 0; i < argc; i++) { | ||||
|         int32_t len = 0; | ||||
|         const Janet *vals = NULL; | ||||
|         janet_indexed_view(argv[i], &vals, &len); | ||||
|         memcpy(tup_cursor, vals, len * sizeof(Janet)); | ||||
|         tup_cursor += len; | ||||
|     } | ||||
|     return janet_wrap_tuple(janet_tuple_end(tup)); | ||||
| } | ||||
|  | ||||
| /* Load the tuple module */ | ||||
| void janet_lib_tuple(JanetTable *env) { | ||||
|     JanetRegExt tuple_cfuns[] = { | ||||
| @@ -124,6 +152,7 @@ void janet_lib_tuple(JanetTable *env) { | ||||
|         JANET_CORE_REG("tuple/type", cfun_tuple_type), | ||||
|         JANET_CORE_REG("tuple/sourcemap", cfun_tuple_sourcemap), | ||||
|         JANET_CORE_REG("tuple/setmap", cfun_tuple_setmap), | ||||
|         JANET_CORE_REG("tuple/join", cfun_tuple_join), | ||||
|         JANET_REG_END | ||||
|     }; | ||||
|     janet_core_cfuns_ext(env, NULL, tuple_cfuns); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -79,6 +79,7 @@ const char *const janet_type_names[16] = { | ||||
|     "pointer" | ||||
| }; | ||||
|  | ||||
| /* Docstring for signal lists these */ | ||||
| const char *const janet_signal_names[14] = { | ||||
|     "ok", | ||||
|     "error", | ||||
| @@ -96,6 +97,7 @@ const char *const janet_signal_names[14] = { | ||||
|     "await" | ||||
| }; | ||||
|  | ||||
| /* Docstring for fiber/status lists these */ | ||||
| const char *const janet_status_names[16] = { | ||||
|     "dead", | ||||
|     "error", | ||||
| @@ -115,14 +117,20 @@ const char *const janet_status_names[16] = { | ||||
|     "alive" | ||||
| }; | ||||
|  | ||||
| uint32_t janet_hash_mix(uint32_t input, uint32_t more) { | ||||
|     uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2)); | ||||
|     return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2)); | ||||
| } | ||||
|  | ||||
| #ifndef JANET_PRF | ||||
|  | ||||
| int32_t janet_string_calchash(const uint8_t *str, int32_t len) { | ||||
|     if (NULL == str) return 5381; | ||||
|     if (NULL == str || len == 0) return 5381; | ||||
|     const uint8_t *end = str + len; | ||||
|     uint32_t hash = 5381; | ||||
|     while (str < end) | ||||
|         hash = (hash << 5) + hash + *str++; | ||||
|     hash = janet_hash_mix(hash, (uint32_t) len); | ||||
|     return (int32_t) hash; | ||||
| } | ||||
|  | ||||
| @@ -238,11 +246,6 @@ int32_t janet_string_calchash(const uint8_t *str, int32_t len) { | ||||
|  | ||||
| #endif | ||||
|  | ||||
| uint32_t janet_hash_mix(uint32_t input, uint32_t more) { | ||||
|     uint32_t mix1 = (more + 0x9e3779b9 + (input << 6) + (input >> 2)); | ||||
|     return input ^ (0x9e3779b9 + (mix1 << 6) + (mix1 >> 2)); | ||||
| } | ||||
|  | ||||
| /* Computes hash of an array of values */ | ||||
| int32_t janet_array_calchash(const Janet *array, int32_t len) { | ||||
|     const Janet *end = array + len; | ||||
| @@ -826,6 +829,20 @@ int janet_checkuint64(Janet x) { | ||||
|     return janet_checkuint64range(dval); | ||||
| } | ||||
|  | ||||
| int janet_checkint16(Janet x) { | ||||
|     if (!janet_checktype(x, JANET_NUMBER)) | ||||
|         return 0; | ||||
|     double dval = janet_unwrap_number(x); | ||||
|     return janet_checkint16range(dval); | ||||
| } | ||||
|  | ||||
| int janet_checkuint16(Janet x) { | ||||
|     if (!janet_checktype(x, JANET_NUMBER)) | ||||
|         return 0; | ||||
|     double dval = janet_unwrap_number(x); | ||||
|     return janet_checkuint16range(dval); | ||||
| } | ||||
|  | ||||
| int janet_checksize(Janet x) { | ||||
|     if (!janet_checktype(x, JANET_NUMBER)) | ||||
|         return 0; | ||||
| @@ -953,6 +970,20 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source) { | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| /* Better strerror (thread-safe if available) */ | ||||
| const char *janet_strerror(int e) { | ||||
| #ifdef JANET_WINDOWS | ||||
|     /* Microsoft strerror seems sane here and is thread safe by default */ | ||||
|     return strerror(e); | ||||
| #elif defined(__GLIBC__) | ||||
|     /* See https://linux.die.net/man/3/strerror_r */ | ||||
|     return strerror_r(e, janet_vm.strerror_buf, sizeof(janet_vm.strerror_buf)); | ||||
| #else | ||||
|     strerror_r(e, janet_vm.strerror_buf, sizeof(janet_vm.strerror_buf)); | ||||
|     return janet_vm.strerror_buf; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| /* Setting C99 standard makes this not available, but it should | ||||
|  * work/link properly if we detect a BSD */ | ||||
| #if defined(JANET_BSD) || defined(MAC_OS_X_VERSION_10_7) | ||||
| @@ -960,6 +991,7 @@ void arc4random_buf(void *buf, size_t nbytes); | ||||
| #endif | ||||
|  | ||||
| int janet_cryptorand(uint8_t *out, size_t n) { | ||||
| #ifndef JANET_NO_CRYPTORAND | ||||
| #ifdef JANET_WINDOWS | ||||
|     for (size_t i = 0; i < n; i += sizeof(unsigned int)) { | ||||
|         unsigned int v; | ||||
| @@ -971,7 +1003,10 @@ int janet_cryptorand(uint8_t *out, size_t n) { | ||||
|         } | ||||
|     } | ||||
|     return 0; | ||||
| #elif defined(JANET_LINUX) || defined(JANET_CYGWIN) || ( defined(JANET_APPLE) && !defined(MAC_OS_X_VERSION_10_7) ) | ||||
| #elif defined(JANET_BSD) || defined(MAC_OS_X_VERSION_10_7) | ||||
|     arc4random_buf(out, n); | ||||
|     return 0; | ||||
| #else | ||||
|     /* We should be able to call getrandom on linux, but it doesn't seem | ||||
|        to be uniformly supported on linux distros. | ||||
|        On Mac, arc4random_buf wasn't available on until 10.7. | ||||
| @@ -993,12 +1028,10 @@ int janet_cryptorand(uint8_t *out, size_t n) { | ||||
|     } | ||||
|     RETRY_EINTR(rc, close(randfd)); | ||||
|     return 0; | ||||
| #elif defined(JANET_BSD) || defined(MAC_OS_X_VERSION_10_7) | ||||
|     arc4random_buf(out, n); | ||||
|     return 0; | ||||
| #endif | ||||
| #else | ||||
|     (void) n; | ||||
|     (void) out; | ||||
|     (void) n; | ||||
|     return -1; | ||||
| #endif | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -33,6 +33,7 @@ | ||||
| #include <errno.h> | ||||
| #include <stddef.h> | ||||
| #include <stdbool.h> | ||||
| #include <math.h> | ||||
|  | ||||
| #ifdef JANET_EV | ||||
| #ifndef JANET_WINDOWS | ||||
| @@ -49,11 +50,11 @@ | ||||
| #ifndef JANET_EXIT | ||||
| #include <stdio.h> | ||||
| #define JANET_EXIT(m) do { \ | ||||
|     fprintf(stderr, "C runtime error at line %d in file %s: %s\n",\ | ||||
|     fprintf(stderr, "janet internal error at line %d in file %s: %s\n",\ | ||||
|         __LINE__,\ | ||||
|         __FILE__,\ | ||||
|         (m));\ | ||||
|     exit(1);\ | ||||
|     abort();\ | ||||
| } while (0) | ||||
| #endif | ||||
|  | ||||
| @@ -80,6 +81,8 @@ void janet_memempty(JanetKV *mem, int32_t count); | ||||
| void *janet_memalloc_empty(int32_t count); | ||||
| JanetTable *janet_get_core_table(const char *name); | ||||
| void janet_def_addflags(JanetFuncDef *def); | ||||
| void janet_buffer_dtostr(JanetBuffer *buffer, double x); | ||||
| const char *janet_strerror(int e); | ||||
| const void *janet_strbinsearch( | ||||
|     const void *tab, | ||||
|     size_t tabcount, | ||||
| @@ -139,7 +142,7 @@ int janet_gettime(struct timespec *spec, enum JanetTimeSource source); | ||||
| #define strdup(x) _strdup(x) | ||||
| #endif | ||||
|  | ||||
| /* Use LoadLibrary on windows or dlopen on posix to load dynamic libaries | ||||
| /* Use LoadLibrary on windows or dlopen on posix to load dynamic libraries | ||||
|  * with native code. */ | ||||
| #if defined(JANET_NO_DYNAMIC_MODULES) | ||||
| typedef int Clib; | ||||
| @@ -187,9 +190,6 @@ void janet_lib_debug(JanetTable *env); | ||||
| #ifdef JANET_PEG | ||||
| void janet_lib_peg(JanetTable *env); | ||||
| #endif | ||||
| #ifdef JANET_TYPED_ARRAY | ||||
| void janet_lib_typed_array(JanetTable *env); | ||||
| #endif | ||||
| #ifdef JANET_INT_TYPES | ||||
| void janet_lib_inttypes(JanetTable *env); | ||||
| #endif | ||||
| @@ -200,7 +200,11 @@ extern const JanetAbstractType janet_address_type; | ||||
| #ifdef JANET_EV | ||||
| void janet_lib_ev(JanetTable *env); | ||||
| void janet_ev_mark(void); | ||||
| void janet_async_start_fiber(JanetFiber *fiber, JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state); | ||||
| int janet_make_pipe(JanetHandle handles[2], int mode); | ||||
| #ifdef JANET_FILEWATCH | ||||
| void janet_lib_filewatch(JanetTable *env); | ||||
| #endif | ||||
| #endif | ||||
| #ifdef JANET_FFI | ||||
| void janet_lib_ffi(JanetTable *env); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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) 2023 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) 2023 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) 2023 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 | ||||
| @@ -116,7 +116,6 @@ | ||||
| #else | ||||
| #define vm_maybe_auto_suspend(COND) do { \ | ||||
|     if ((COND) && janet_vm.auto_suspend) { \ | ||||
|         janet_vm.auto_suspend = 0; \ | ||||
|         fiber->flags |= (JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP); \ | ||||
|         vm_return(JANET_SIGNAL_INTERRUPT, janet_wrap_nil()); \ | ||||
|     } \ | ||||
| @@ -319,7 +318,7 @@ static Janet janet_binop_call(const char *lmethod, const char *rmethod, Janet lh | ||||
|         Janet lr = janet_method_lookup(rhs, rmethod); | ||||
|         Janet argv[2] = { rhs, lhs }; | ||||
|         if (janet_checktype(lr, JANET_NIL)) { | ||||
|             janet_panicf("could not find method :%s for %v, or :%s for %v", | ||||
|             janet_panicf("could not find method :%s for %v or :%s for %v", | ||||
|                          lmethod, lhs, | ||||
|                          rmethod, rhs); | ||||
|         } | ||||
| @@ -799,14 +798,14 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     vm_pcnext(); | ||||
|  | ||||
|     VM_OP(JOP_JUMP) | ||||
|     vm_maybe_auto_suspend(DS <= 0); | ||||
|     pc += DS; | ||||
|     vm_maybe_auto_suspend(DS < 0); | ||||
|     vm_next(); | ||||
|  | ||||
|     VM_OP(JOP_JUMP_IF) | ||||
|     if (janet_truthy(stack[A])) { | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES < 0); | ||||
|     } else { | ||||
|         pc++; | ||||
|     } | ||||
| @@ -816,15 +815,15 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     if (janet_truthy(stack[A])) { | ||||
|         pc++; | ||||
|     } else { | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES < 0); | ||||
|     } | ||||
|     vm_next(); | ||||
|  | ||||
|     VM_OP(JOP_JUMP_IF_NIL) | ||||
|     if (janet_checktype(stack[A], JANET_NIL)) { | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES < 0); | ||||
|     } else { | ||||
|         pc++; | ||||
|     } | ||||
| @@ -834,8 +833,8 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     if (janet_checktype(stack[A], JANET_NIL)) { | ||||
|         pc++; | ||||
|     } else { | ||||
|         vm_maybe_auto_suspend(ES <= 0); | ||||
|         pc += ES; | ||||
|         vm_maybe_auto_suspend(ES < 0); | ||||
|     } | ||||
|     vm_next(); | ||||
|  | ||||
| @@ -862,7 +861,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     vm_pcnext(); | ||||
|  | ||||
|     VM_OP(JOP_EQUALS_IMMEDIATE) | ||||
|     stack[A] = janet_wrap_boolean(janet_unwrap_number(stack[B]) == (double) CS); | ||||
|     stack[A] = janet_wrap_boolean(janet_checktype(stack[B], JANET_NUMBER) && (janet_unwrap_number(stack[B]) == (double) CS)); | ||||
|     vm_pcnext(); | ||||
|  | ||||
|     VM_OP(JOP_NOT_EQUALS) | ||||
| @@ -870,7 +869,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
|     vm_pcnext(); | ||||
|  | ||||
|     VM_OP(JOP_NOT_EQUALS_IMMEDIATE) | ||||
|     stack[A] = janet_wrap_boolean(janet_unwrap_number(stack[B]) != (double) CS); | ||||
|     stack[A] = janet_wrap_boolean(!janet_checktype(stack[B], JANET_NUMBER) || (janet_unwrap_number(stack[B]) != (double) CS)); | ||||
|     vm_pcnext(); | ||||
|  | ||||
|     VM_OP(JOP_COMPARE) | ||||
| @@ -1269,7 +1268,7 @@ static JanetSignal run_vm(JanetFiber *fiber, Janet in) { | ||||
| /* | ||||
|  * Execute a single instruction in the fiber. Does this by inspecting | ||||
|  * the fiber, setting a breakpoint at the next instruction, executing, and | ||||
|  * reseting breakpoints to how they were prior. Yes, it's a bit hacky. | ||||
|  * resetting breakpoints to how they were prior. Yes, it's a bit hacky. | ||||
|  */ | ||||
| JanetSignal janet_step(JanetFiber *fiber, Janet in, Janet *out) { | ||||
|     /* No finished or currently alive fibers. */ | ||||
| @@ -1374,7 +1373,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) { | ||||
|  | ||||
|     /* Run vm */ | ||||
|     janet_vm.fiber->flags |= JANET_FIBER_RESUME_NO_USEVAL | JANET_FIBER_RESUME_NO_SKIP; | ||||
|     int old_coerce_error = janet_vm.coerce_error; | ||||
|     janet_vm.coerce_error = 1; | ||||
|     JanetSignal signal = run_vm(janet_vm.fiber, janet_wrap_nil()); | ||||
|     janet_vm.coerce_error = old_coerce_error; | ||||
|  | ||||
|     /* Teardown */ | ||||
|     janet_vm.stackn = oldn; | ||||
| @@ -1385,6 +1387,15 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) { | ||||
|     } | ||||
|  | ||||
|     if (signal != JANET_SIGNAL_OK) { | ||||
|         /* Should match logic in janet_signalv */ | ||||
| #ifdef JANET_EV | ||||
|         if (janet_vm.root_fiber != NULL && signal == JANET_SIGNAL_EVENT) { | ||||
|             janet_vm.root_fiber->sched_id++; | ||||
|         } | ||||
| #endif | ||||
|         if (signal != JANET_SIGNAL_ERROR) { | ||||
|             *janet_vm.return_reg = janet_wrap_string(janet_formatc("%v coerced from %s to error", *janet_vm.return_reg, janet_signal_names[signal])); | ||||
|         } | ||||
|         janet_panicv(*janet_vm.return_reg); | ||||
|     } | ||||
|  | ||||
| @@ -1431,8 +1442,10 @@ void janet_try_init(JanetTryState *state) { | ||||
|     state->vm_fiber = janet_vm.fiber; | ||||
|     state->vm_jmp_buf = janet_vm.signal_buf; | ||||
|     state->vm_return_reg = janet_vm.return_reg; | ||||
|     state->coerce_error = janet_vm.coerce_error; | ||||
|     janet_vm.return_reg = &(state->payload); | ||||
|     janet_vm.signal_buf = &(state->buf); | ||||
|     janet_vm.coerce_error = 0; | ||||
| } | ||||
|  | ||||
| void janet_restore(JanetTryState *state) { | ||||
| @@ -1441,6 +1454,7 @@ void janet_restore(JanetTryState *state) { | ||||
|     janet_vm.fiber = state->vm_fiber; | ||||
|     janet_vm.signal_buf = state->vm_jmp_buf; | ||||
|     janet_vm.return_reg = state->vm_return_reg; | ||||
|     janet_vm.coerce_error = state->coerce_error; | ||||
| } | ||||
|  | ||||
| static JanetSignal janet_continue_no_check(JanetFiber *fiber, Janet in, Janet *out) { | ||||
| @@ -1586,9 +1600,11 @@ int janet_init(void) { | ||||
|  | ||||
|     /* Garbage collection */ | ||||
|     janet_vm.blocks = NULL; | ||||
|     janet_vm.weak_blocks = NULL; | ||||
|     janet_vm.next_collection = 0; | ||||
|     janet_vm.gc_interval = 0x400000; | ||||
|     janet_vm.block_count = 0; | ||||
|     janet_vm.gc_mark_phase = 0; | ||||
|  | ||||
|     janet_symcache_init(); | ||||
|  | ||||
| @@ -1612,7 +1628,7 @@ int janet_init(void) { | ||||
|     janet_vm.registry_count = 0; | ||||
|     janet_vm.registry_dirty = 0; | ||||
|  | ||||
|     /* Intialize abstract registry */ | ||||
|     /* Initialize abstract registry */ | ||||
|     janet_vm.abstract_registry = janet_table(0); | ||||
|     janet_gcroot(janet_wrap_table(janet_vm.abstract_registry)); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -43,10 +43,10 @@ int (janet_truthy)(Janet x) { | ||||
|     return janet_truthy(x); | ||||
| } | ||||
|  | ||||
| const JanetKV *(janet_unwrap_struct)(Janet x) { | ||||
| JanetStruct(janet_unwrap_struct)(Janet x) { | ||||
|     return janet_unwrap_struct(x); | ||||
| } | ||||
| const Janet *(janet_unwrap_tuple)(Janet x) { | ||||
| JanetTuple(janet_unwrap_tuple)(Janet x) { | ||||
|     return janet_unwrap_tuple(x); | ||||
| } | ||||
| JanetFiber *(janet_unwrap_fiber)(Janet x) { | ||||
| @@ -61,16 +61,16 @@ JanetTable *(janet_unwrap_table)(Janet x) { | ||||
| JanetBuffer *(janet_unwrap_buffer)(Janet x) { | ||||
|     return janet_unwrap_buffer(x); | ||||
| } | ||||
| const uint8_t *(janet_unwrap_string)(Janet x) { | ||||
| JanetString(janet_unwrap_string)(Janet x) { | ||||
|     return janet_unwrap_string(x); | ||||
| } | ||||
| const uint8_t *(janet_unwrap_symbol)(Janet x) { | ||||
| JanetSymbol(janet_unwrap_symbol)(Janet x) { | ||||
|     return janet_unwrap_symbol(x); | ||||
| } | ||||
| const uint8_t *(janet_unwrap_keyword)(Janet x) { | ||||
| JanetKeyword(janet_unwrap_keyword)(Janet x) { | ||||
|     return janet_unwrap_keyword(x); | ||||
| } | ||||
| void *(janet_unwrap_abstract)(Janet x) { | ||||
| JanetAbstract(janet_unwrap_abstract)(Janet x) { | ||||
|     return janet_unwrap_abstract(x); | ||||
| } | ||||
| void *(janet_unwrap_pointer)(Janet x) { | ||||
| @@ -102,22 +102,22 @@ Janet(janet_wrap_false)(void) { | ||||
| Janet(janet_wrap_boolean)(int x) { | ||||
|     return janet_wrap_boolean(x); | ||||
| } | ||||
| Janet(janet_wrap_string)(const uint8_t *x) { | ||||
| Janet(janet_wrap_string)(JanetString x) { | ||||
|     return janet_wrap_string(x); | ||||
| } | ||||
| Janet(janet_wrap_symbol)(const uint8_t *x) { | ||||
| Janet(janet_wrap_symbol)(JanetSymbol x) { | ||||
|     return janet_wrap_symbol(x); | ||||
| } | ||||
| Janet(janet_wrap_keyword)(const uint8_t *x) { | ||||
| Janet(janet_wrap_keyword)(JanetKeyword x) { | ||||
|     return janet_wrap_keyword(x); | ||||
| } | ||||
| Janet(janet_wrap_array)(JanetArray *x) { | ||||
|     return janet_wrap_array(x); | ||||
| } | ||||
| Janet(janet_wrap_tuple)(const Janet *x) { | ||||
| Janet(janet_wrap_tuple)(JanetTuple x) { | ||||
|     return janet_wrap_tuple(x); | ||||
| } | ||||
| Janet(janet_wrap_struct)(const JanetKV *x) { | ||||
| Janet(janet_wrap_struct)(JanetStruct x) { | ||||
|     return janet_wrap_struct(x); | ||||
| } | ||||
| Janet(janet_wrap_fiber)(JanetFiber *x) { | ||||
| @@ -135,7 +135,7 @@ Janet(janet_wrap_cfunction)(JanetCFunction x) { | ||||
| Janet(janet_wrap_table)(JanetTable *x) { | ||||
|     return janet_wrap_table(x); | ||||
| } | ||||
| Janet(janet_wrap_abstract)(void *x) { | ||||
| Janet(janet_wrap_abstract)(JanetAbstract x) { | ||||
|     return janet_wrap_abstract(x); | ||||
| } | ||||
| Janet(janet_wrap_pointer)(void *x) { | ||||
| @@ -317,4 +317,3 @@ JANET_WRAP_DEFINE(pointer, void *, JANET_POINTER, pointer) | ||||
| #undef JANET_WRAP_DEFINE | ||||
|  | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -46,7 +46,7 @@ extern "C" { | ||||
| #endif | ||||
|  | ||||
| /* | ||||
|  * Detect OS and endianess. | ||||
|  * Detect OS and endianness. | ||||
|  * From webkit source. There is likely some extreneous | ||||
|  * detection for unsupported platforms | ||||
|  */ | ||||
| @@ -112,7 +112,8 @@ extern "C" { | ||||
|     || defined(__s390x__) /* S390 64-bit (BE) */ \ | ||||
|     || (defined(__ppc64__) || defined(__PPC64__)) \ | ||||
|     || defined(__aarch64__) /* ARM 64-bit */ \ | ||||
|     || (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */ | ||||
|     || (defined(__riscv) && (__riscv_xlen == 64)) /* RISC-V 64-bit */ \ | ||||
|     || defined(__loongarch64) /* LoongArch64 64-bit */ | ||||
| #define JANET_64 1 | ||||
| #else | ||||
| #define JANET_32 1 | ||||
| @@ -209,6 +210,11 @@ extern "C" { | ||||
| #define JANET_EV | ||||
| #endif | ||||
|  | ||||
| /* Enable or disable the filewatch/ module */ | ||||
| #if !defined(JANET_NO_FILEWATCH) | ||||
| #define JANET_FILEWATCH | ||||
| #endif | ||||
|  | ||||
| /* Enable or disable networking */ | ||||
| #if defined(JANET_EV) && !defined(JANET_NO_NET) && !defined(__EMSCRIPTEN__) | ||||
| #define JANET_NET | ||||
| @@ -234,16 +240,34 @@ extern "C" { | ||||
| #define JANET_EV_KQUEUE | ||||
| #endif | ||||
|  | ||||
| /* Use poll as last resort */ | ||||
| #if !defined(JANET_WINDOWS) && !defined(JANET_EV_EPOLL) && !defined(JANET_EV_KQUEUE) | ||||
| #define JANET_EV_POLL | ||||
| #endif | ||||
|  | ||||
| /* How to export symbols */ | ||||
| #ifndef JANET_EXPORT | ||||
| #ifdef JANET_WINDOWS | ||||
| #define JANET_EXPORT __declspec(dllexport) | ||||
| #else | ||||
| #define JANET_EXPORT __attribute__((visibility ("default"))) | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| /* How declare API functions */ | ||||
| #ifndef JANET_API | ||||
| #ifdef JANET_WINDOWS | ||||
| #ifdef JANET_DLL_IMPORT | ||||
| #define JANET_API __declspec(dllimport) | ||||
| #else | ||||
| #define JANET_API __declspec(dllexport) | ||||
| #endif | ||||
| #else | ||||
| #define JANET_API __attribute__((visibility ("default"))) | ||||
| #endif | ||||
| #endif | ||||
|  | ||||
| /* Tell complier some functions don't return */ | ||||
| /* Tell compiler some functions don't return */ | ||||
| #ifndef JANET_NO_RETURN | ||||
| #ifdef JANET_WINDOWS | ||||
| #define JANET_NO_RETURN __declspec(noreturn) | ||||
| @@ -253,7 +277,7 @@ extern "C" { | ||||
| #endif | ||||
|  | ||||
| /* Prevent some recursive functions from recursing too deeply | ||||
|  * ands crashing (the parser). Instead, error out. */ | ||||
|  * and crashing (the parser). Instead, error out. */ | ||||
| #define JANET_RECURSION_GUARD 1024 | ||||
|  | ||||
| /* Maximum depth to follow table prototypes before giving up and returning nil. */ | ||||
| @@ -335,6 +359,7 @@ typedef struct { | ||||
| #ifdef JANET_EV | ||||
| typedef struct JanetOSMutex JanetOSMutex; | ||||
| typedef struct JanetOSRWLock JanetOSRWLock; | ||||
| typedef struct JanetChannel JanetChannel; | ||||
| #endif | ||||
|  | ||||
| /***** END SECTION CONFIG *****/ | ||||
| @@ -393,12 +418,11 @@ typedef enum { | ||||
|     JANET_SIGNAL_USER6, | ||||
|     JANET_SIGNAL_USER7, | ||||
|     JANET_SIGNAL_USER8, | ||||
|     JANET_SIGNAL_USER9 | ||||
|     JANET_SIGNAL_USER9, | ||||
|     JANET_SIGNAL_INTERRUPT = JANET_SIGNAL_USER8, | ||||
|     JANET_SIGNAL_EVENT = JANET_SIGNAL_USER9, | ||||
| } JanetSignal; | ||||
|  | ||||
| #define JANET_SIGNAL_EVENT JANET_SIGNAL_USER9 | ||||
| #define JANET_SIGNAL_INTERRUPT JANET_SIGNAL_USER8 | ||||
|  | ||||
| /* Fiber statuses - mostly corresponds to signals. */ | ||||
| typedef enum { | ||||
|     JANET_STATUS_DEAD, | ||||
| @@ -562,7 +586,7 @@ typedef void *JanetAbstract; | ||||
|  | ||||
| #define JANET_STREAM_CLOSED 0x1 | ||||
| #define JANET_STREAM_SOCKET 0x2 | ||||
| #define JANET_STREAM_IOCP 0x4 | ||||
| #define JANET_STREAM_UNREGISTERED 0x4 | ||||
| #define JANET_STREAM_READABLE 0x200 | ||||
| #define JANET_STREAM_WRITABLE 0x400 | ||||
| #define JANET_STREAM_ACCEPTABLE 0x800 | ||||
| @@ -570,62 +594,75 @@ typedef void *JanetAbstract; | ||||
| #define JANET_STREAM_TOCLOSE 0x10000 | ||||
|  | ||||
| typedef enum { | ||||
|     JANET_ASYNC_EVENT_INIT, | ||||
|     JANET_ASYNC_EVENT_MARK, | ||||
|     JANET_ASYNC_EVENT_DEINIT, | ||||
|     JANET_ASYNC_EVENT_CLOSE, | ||||
|     JANET_ASYNC_EVENT_ERR, | ||||
|     JANET_ASYNC_EVENT_HUP, | ||||
|     JANET_ASYNC_EVENT_READ, | ||||
|     JANET_ASYNC_EVENT_WRITE, | ||||
|     JANET_ASYNC_EVENT_CANCEL, | ||||
|     JANET_ASYNC_EVENT_COMPLETE, /* Used on windows for IOCP */ | ||||
|     JANET_ASYNC_EVENT_USER | ||||
|     JANET_ASYNC_EVENT_INIT = 0, | ||||
|     JANET_ASYNC_EVENT_MARK = 1, | ||||
|     JANET_ASYNC_EVENT_DEINIT = 2, | ||||
|     JANET_ASYNC_EVENT_CLOSE = 3, | ||||
|     JANET_ASYNC_EVENT_ERR = 4, | ||||
|     JANET_ASYNC_EVENT_HUP = 5, | ||||
|     JANET_ASYNC_EVENT_READ = 6, | ||||
|     JANET_ASYNC_EVENT_WRITE = 7, | ||||
|     JANET_ASYNC_EVENT_COMPLETE = 8, /* Used on windows for IOCP */ | ||||
|     JANET_ASYNC_EVENT_FAILED = 9 /* Used on windows for IOCP */ | ||||
| } JanetAsyncEvent; | ||||
|  | ||||
| #define JANET_ASYNC_LISTEN_READ (1 << JANET_ASYNC_EVENT_READ) | ||||
| #define JANET_ASYNC_LISTEN_WRITE (1 << JANET_ASYNC_EVENT_WRITE) | ||||
|  | ||||
| typedef enum { | ||||
|     JANET_ASYNC_STATUS_NOT_DONE, | ||||
|     JANET_ASYNC_STATUS_DONE | ||||
| } JanetAsyncStatus; | ||||
|     JANET_ASYNC_LISTEN_READ = 1, | ||||
|     JANET_ASYNC_LISTEN_WRITE, | ||||
|     JANET_ASYNC_LISTEN_BOTH | ||||
| } JanetAsyncMode; | ||||
|  | ||||
| /* Typedefs */ | ||||
| typedef struct JanetListenerState JanetListenerState; | ||||
| typedef struct JanetStream JanetStream; | ||||
| typedef JanetAsyncStatus(*JanetListener)(JanetListenerState *state, JanetAsyncEvent event); | ||||
|  | ||||
| /* Wrapper around file descriptors and HANDLEs that can be polled. */ | ||||
| struct JanetStream { | ||||
|     JanetHandle handle; | ||||
|     uint32_t flags; | ||||
|     /* Linked list of all in-flight IO routines for this stream */ | ||||
|     JanetListenerState *state; | ||||
|     uint32_t index; | ||||
|     JanetFiber *read_fiber; | ||||
|     JanetFiber *write_fiber; | ||||
|     const void *methods; /* Methods for this stream */ | ||||
|     /* internal - used to disallow multiple concurrent reads / writes on the same stream. | ||||
|      * this constraint may be lifted later but allowing such would require more internal book keeping | ||||
|      * for some implementations. You can read and write at the same time on the same stream, though. */ | ||||
|     int _mask; | ||||
| }; | ||||
|  | ||||
| /* Interface for state machine based event loop */ | ||||
| struct JanetListenerState { | ||||
|     JanetListener machine; | ||||
|     JanetFiber *fiber; | ||||
|     JanetStream *stream; | ||||
|     void *event; /* Used to pass data from asynchronous IO event. Contents depend on both | ||||
|                     implementation of the event loop and the particular event. */ | ||||
| typedef void (*JanetEVCallback)(JanetFiber *fiber, JanetAsyncEvent event); | ||||
|  | ||||
| /* Start listening for events from a stream on the current root fiber. After | ||||
|  * calling this, users should call janet_await() before returning from the | ||||
|  * current C Function. This also will call janet_await. | ||||
|  * mode is which events to listen for, and callback is the function pointer to | ||||
|  * call when ever an event is sent from the event loop. state is an optional (can be NULL) | ||||
|  * pointer to data allocated with janet_malloc. This pointer will be passed to callback as | ||||
|  * fiber->ev_state. It will also be freed for you by the runtime when the event loop determines | ||||
|  * it can no longer be referenced. On windows, the contents of state MUST contained an OVERLAPPED struct at the 0 offset. */ | ||||
|  | ||||
| JANET_API void janet_async_start_fiber(JanetFiber *fiber, JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state); | ||||
| JANET_API JANET_NO_RETURN void janet_async_start(JanetStream *stream, JanetAsyncMode mode, JanetEVCallback callback, void *state); | ||||
|  | ||||
| /* Do not send any more events to the given callback. Call this after scheduling fiber to be resume | ||||
|  * or canceled. */ | ||||
| JANET_API void janet_async_end(JanetFiber *fiber); | ||||
|  | ||||
| /* Needed for windows to mark a fiber as waiting for an IOCP completion event. Noop on other platforms. */ | ||||
| JANET_API void janet_async_in_flight(JanetFiber *fiber); | ||||
|  | ||||
| /* On some platforms, it is important to be able to control if a stream is edge-trigger or level triggered. | ||||
|  * For example, a server that is accepting connections might want to be level triggered or edge-triggered | ||||
|  * depending on expected service. */ | ||||
| JANET_API void janet_stream_edge_triggered(JanetStream *stream); | ||||
| JANET_API void janet_stream_level_triggered(JanetStream *stream); | ||||
|  | ||||
| #endif | ||||
|  | ||||
| /* Janet uses atomic integers in several places for synchronization between threads and | ||||
|  * signals. Define them here */ | ||||
| #ifdef JANET_WINDOWS | ||||
|     void *tag; /* Used to associate listeners with an overlapped structure */ | ||||
|     int bytes; /* Used to track how many bytes were transfered. */ | ||||
| #endif | ||||
|     /* internal */ | ||||
|     size_t _index; | ||||
|     int _mask; | ||||
|     JanetListenerState *_next; | ||||
| }; | ||||
| typedef long JanetAtomicInt; | ||||
| #else | ||||
| typedef int32_t JanetAtomicInt; | ||||
| #endif | ||||
| JANET_API JanetAtomicInt janet_atomic_inc(JanetAtomicInt volatile *x); | ||||
| JANET_API JanetAtomicInt janet_atomic_dec(JanetAtomicInt volatile *x); | ||||
| JANET_API JanetAtomicInt janet_atomic_load(JanetAtomicInt volatile *x); | ||||
|  | ||||
| /* We provide three possible implementations of Janets. The preferred | ||||
|  * nanboxing approach, for 32 or 64 bits, and the standard C version. Code in the rest of the | ||||
| @@ -653,10 +690,10 @@ struct JanetListenerState { | ||||
|  * external bindings, we should prefer using the Head structs directly, and | ||||
|  * use the host language to add sugar around the manipulation of the Janet types. */ | ||||
|  | ||||
| JANET_API JanetStructHead *janet_struct_head(const JanetKV *st); | ||||
| JANET_API JanetStructHead *janet_struct_head(JanetStruct st); | ||||
| JANET_API JanetAbstractHead *janet_abstract_head(const void *abstract); | ||||
| JANET_API JanetStringHead *janet_string_head(const uint8_t *s); | ||||
| JANET_API JanetTupleHead *janet_tuple_head(const Janet *tuple); | ||||
| JANET_API JanetStringHead *janet_string_head(JanetString s); | ||||
| JANET_API JanetTupleHead *janet_tuple_head(JanetTuple tuple); | ||||
|  | ||||
| /* Some language bindings won't have access to the macro versions. */ | ||||
|  | ||||
| @@ -665,16 +702,16 @@ JANET_API int janet_checktype(Janet x, JanetType type); | ||||
| JANET_API int janet_checktypes(Janet x, int typeflags); | ||||
| JANET_API int janet_truthy(Janet x); | ||||
|  | ||||
| JANET_API const JanetKV *janet_unwrap_struct(Janet x); | ||||
| JANET_API const Janet *janet_unwrap_tuple(Janet x); | ||||
| JANET_API JanetStruct janet_unwrap_struct(Janet x); | ||||
| JANET_API JanetTuple janet_unwrap_tuple(Janet x); | ||||
| JANET_API JanetFiber *janet_unwrap_fiber(Janet x); | ||||
| JANET_API JanetArray *janet_unwrap_array(Janet x); | ||||
| JANET_API JanetTable *janet_unwrap_table(Janet x); | ||||
| JANET_API JanetBuffer *janet_unwrap_buffer(Janet x); | ||||
| JANET_API const uint8_t *janet_unwrap_string(Janet x); | ||||
| JANET_API const uint8_t *janet_unwrap_symbol(Janet x); | ||||
| JANET_API const uint8_t *janet_unwrap_keyword(Janet x); | ||||
| JANET_API void *janet_unwrap_abstract(Janet x); | ||||
| JANET_API JanetString janet_unwrap_string(Janet x); | ||||
| JANET_API JanetSymbol janet_unwrap_symbol(Janet x); | ||||
| JANET_API JanetKeyword janet_unwrap_keyword(Janet x); | ||||
| JANET_API JanetAbstract janet_unwrap_abstract(Janet x); | ||||
| JANET_API void *janet_unwrap_pointer(Janet x); | ||||
| JANET_API JanetFunction *janet_unwrap_function(Janet x); | ||||
| JANET_API JanetCFunction janet_unwrap_cfunction(Janet x); | ||||
| @@ -687,18 +724,18 @@ JANET_API Janet janet_wrap_number(double x); | ||||
| JANET_API Janet janet_wrap_true(void); | ||||
| JANET_API Janet janet_wrap_false(void); | ||||
| JANET_API Janet janet_wrap_boolean(int x); | ||||
| JANET_API Janet janet_wrap_string(const uint8_t *x); | ||||
| JANET_API Janet janet_wrap_symbol(const uint8_t *x); | ||||
| JANET_API Janet janet_wrap_keyword(const uint8_t *x); | ||||
| JANET_API Janet janet_wrap_string(JanetString x); | ||||
| JANET_API Janet janet_wrap_symbol(JanetSymbol x); | ||||
| JANET_API Janet janet_wrap_keyword(JanetKeyword x); | ||||
| JANET_API Janet janet_wrap_array(JanetArray *x); | ||||
| JANET_API Janet janet_wrap_tuple(const Janet *x); | ||||
| JANET_API Janet janet_wrap_struct(const JanetKV *x); | ||||
| JANET_API Janet janet_wrap_tuple(JanetTuple x); | ||||
| JANET_API Janet janet_wrap_struct(JanetStruct x); | ||||
| JANET_API Janet janet_wrap_fiber(JanetFiber *x); | ||||
| JANET_API Janet janet_wrap_buffer(JanetBuffer *x); | ||||
| JANET_API Janet janet_wrap_function(JanetFunction *x); | ||||
| JANET_API Janet janet_wrap_cfunction(JanetCFunction x); | ||||
| JANET_API Janet janet_wrap_table(JanetTable *x); | ||||
| JANET_API Janet janet_wrap_abstract(void *x); | ||||
| JANET_API Janet janet_wrap_abstract(JanetAbstract x); | ||||
| JANET_API Janet janet_wrap_pointer(void *x); | ||||
| JANET_API Janet janet_wrap_integer(int32_t x); | ||||
|  | ||||
| @@ -730,6 +767,7 @@ JANET_API Janet janet_wrap_integer(int32_t x); | ||||
|         ? janet_nanbox_isnumber(x) \ | ||||
|         : janet_nanbox_checkauxtype((x), (t))) | ||||
|  | ||||
| /* Use JANET_API so that modules will use a local version of these functions if possible */ | ||||
| JANET_API void *janet_nanbox_to_pointer(Janet x); | ||||
| JANET_API Janet janet_nanbox_from_pointer(void *p, uint64_t tagmask); | ||||
| JANET_API Janet janet_nanbox_from_cpointer(const void *p, uint64_t tagmask); | ||||
| @@ -776,14 +814,14 @@ JANET_API Janet janet_nanbox_from_bits(uint64_t bits); | ||||
| #define janet_wrap_pointer(s) janet_nanbox_wrap_((s), JANET_POINTER) | ||||
|  | ||||
| /* Unwrap the pointer types */ | ||||
| #define janet_unwrap_struct(x) ((const JanetKV *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_tuple(x) ((const Janet *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_struct(x) ((JanetStruct)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_tuple(x) ((JanetTuple)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_fiber(x) ((JanetFiber *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_array(x) ((JanetArray *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_table(x) ((JanetTable *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_buffer(x) ((JanetBuffer *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_string(x) ((const uint8_t *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_symbol(x) ((const uint8_t *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_string(x) ((JanetString)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_symbol(x) ((JanetSymbol)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_keyword(x) ((const uint8_t *)janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_abstract(x) (janet_nanbox_to_pointer(x)) | ||||
| #define janet_unwrap_pointer(x) (janet_nanbox_to_pointer(x)) | ||||
| @@ -825,15 +863,15 @@ JANET_API Janet janet_nanbox32_from_tagp(uint32_t tag, void *pointer); | ||||
| #define janet_wrap_cfunction(s) janet_nanbox32_from_tagp(JANET_CFUNCTION, (void *)(s)) | ||||
| #define janet_wrap_pointer(s) janet_nanbox32_from_tagp(JANET_POINTER, (void *)(s)) | ||||
|  | ||||
| #define janet_unwrap_struct(x) ((const JanetKV *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_tuple(x) ((const Janet *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_struct(x) ((JanetStruct)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_tuple(x) ((JanetTuple)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_fiber(x) ((JanetFiber *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_array(x) ((JanetArray *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_table(x) ((JanetTable *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_buffer(x) ((JanetBuffer *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_string(x) ((const uint8_t *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_symbol(x) ((const uint8_t *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_keyword(x) ((const uint8_t *)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_string(x) ((JanetString)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_symbol(x) ((JanetSymbol)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_keyword(x) ((JanetKeyword)(x).tagged.payload.pointer) | ||||
| #define janet_unwrap_abstract(x) ((x).tagged.payload.pointer) | ||||
| #define janet_unwrap_pointer(x) ((x).tagged.payload.pointer) | ||||
| #define janet_unwrap_function(x) ((JanetFunction *)(x).tagged.payload.pointer) | ||||
| @@ -848,15 +886,15 @@ JANET_API Janet janet_nanbox32_from_tagp(uint32_t tag, void *pointer); | ||||
| #define janet_truthy(x) \ | ||||
|     ((x).type != JANET_NIL && ((x).type != JANET_BOOLEAN || ((x).as.u64 & 0x1))) | ||||
|  | ||||
| #define janet_unwrap_struct(x) ((const JanetKV *)(x).as.pointer) | ||||
| #define janet_unwrap_tuple(x) ((const Janet *)(x).as.pointer) | ||||
| #define janet_unwrap_struct(x) ((JanetStruct)(x).as.pointer) | ||||
| #define janet_unwrap_tuple(x) ((JanetTuple)(x).as.pointer) | ||||
| #define janet_unwrap_fiber(x) ((JanetFiber *)(x).as.pointer) | ||||
| #define janet_unwrap_array(x) ((JanetArray *)(x).as.pointer) | ||||
| #define janet_unwrap_table(x) ((JanetTable *)(x).as.pointer) | ||||
| #define janet_unwrap_buffer(x) ((JanetBuffer *)(x).as.pointer) | ||||
| #define janet_unwrap_string(x) ((const uint8_t *)(x).as.pointer) | ||||
| #define janet_unwrap_symbol(x) ((const uint8_t *)(x).as.pointer) | ||||
| #define janet_unwrap_keyword(x) ((const uint8_t *)(x).as.pointer) | ||||
| #define janet_unwrap_string(x) ((JanetString)(x).as.pointer) | ||||
| #define janet_unwrap_symbol(x) ((JanetSymbol)(x).as.pointer) | ||||
| #define janet_unwrap_keyword(x) ((JanetKeyword)(x).as.pointer) | ||||
| #define janet_unwrap_abstract(x) ((x).as.pointer) | ||||
| #define janet_unwrap_pointer(x) ((x).as.pointer) | ||||
| #define janet_unwrap_function(x) ((JanetFunction *)(x).as.pointer) | ||||
| @@ -867,12 +905,16 @@ JANET_API Janet janet_nanbox32_from_tagp(uint32_t tag, void *pointer); | ||||
| /* End of tagged union implementation */ | ||||
| #endif | ||||
|  | ||||
| JANET_API int janet_checkint16(Janet x); | ||||
| JANET_API int janet_checkuint16(Janet x); | ||||
| JANET_API int janet_checkint(Janet x); | ||||
| JANET_API int janet_checkuint(Janet x); | ||||
| JANET_API int janet_checkint64(Janet x); | ||||
| JANET_API int janet_checkuint64(Janet x); | ||||
| JANET_API int janet_checksize(Janet x); | ||||
| JANET_API JanetAbstract janet_checkabstract(Janet x, const JanetAbstractType *at); | ||||
| #define janet_checkint16range(x) ((x) >= INT16_MIN && (x) <= INT16_MAX && (x) == (int16_t)(x)) | ||||
| #define janet_checkuint16range(x) ((x) >= 0 && (x) <= UINT16_MAX && (x) == (uint16_t)(x)) | ||||
| #define janet_checkintrange(x) ((x) >= INT32_MIN && (x) <= INT32_MAX && (x) == (int32_t)(x)) | ||||
| #define janet_checkuintrange(x) ((x) >= 0 && (x) <= UINT32_MAX && (x) == (uint32_t)(x)) | ||||
| #define janet_checkint64range(x) ((x) >= JANET_INTMIN_DOUBLE && (x) <= JANET_INTMAX_DOUBLE && (x) == (int64_t)(x)) | ||||
| @@ -889,7 +931,7 @@ struct JanetGCObject { | ||||
|     int32_t flags; | ||||
|     union { | ||||
|         JanetGCObject *next; | ||||
|         int32_t refcount; /* For threaded abstract types */ | ||||
|         volatile JanetAtomicInt refcount; /* For threaded abstract types */ | ||||
|     } data; | ||||
| }; | ||||
|  | ||||
| @@ -912,8 +954,10 @@ struct JanetFiber { | ||||
|      * that is, fibers that are scheduled on the event loop and behave much like threads | ||||
|      * in a multi-tasking system. It would be possible to move these fields to a new | ||||
|      * type, say "JanetTask", that as separate from fibers to save a bit of space. */ | ||||
|     JanetListenerState *waiting; | ||||
|     uint32_t sched_id; /* Increment everytime fiber is scheduled by event loop */ | ||||
|     JanetEVCallback ev_callback; /* Call this before starting scheduled fibers */ | ||||
|     JanetStream *ev_stream; /* which stream we are waiting on */ | ||||
|     void *ev_state; /* Extra data for ev callback state. On windows, first element must be OVERLAPPED. */ | ||||
|     void *supervisor_channel; /* Channel to push self to when complete */ | ||||
| #endif | ||||
| }; | ||||
| @@ -1217,6 +1261,7 @@ typedef struct { | ||||
|     /* new state */ | ||||
|     jmp_buf buf; | ||||
|     Janet payload; | ||||
|     int coerce_error; | ||||
| } JanetTryState; | ||||
|  | ||||
| /***** END SECTION TYPES *****/ | ||||
| @@ -1377,6 +1422,7 @@ JANET_API void janet_loop1_interrupt(JanetVM *vm); | ||||
|  | ||||
| /* Wrapper around streams */ | ||||
| JANET_API JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod *methods); | ||||
| JANET_API JanetStream *janet_stream_ext(JanetHandle handle, uint32_t flags, const JanetMethod *methods, size_t size); /* Allow for type punning streams */ | ||||
| JANET_API void janet_stream_close(JanetStream *stream); | ||||
| JANET_API Janet janet_cfun_stream_close(int32_t argc, Janet *argv); | ||||
| JANET_API Janet janet_cfun_stream_read(int32_t argc, Janet *argv); | ||||
| @@ -1388,9 +1434,7 @@ JANET_API void janet_stream_flags(JanetStream *stream, uint32_t flags); | ||||
| JANET_API void janet_schedule(JanetFiber *fiber, Janet value); | ||||
| JANET_API void janet_cancel(JanetFiber *fiber, Janet value); | ||||
| JANET_API void janet_schedule_signal(JanetFiber *fiber, Janet value, JanetSignal sig); | ||||
|  | ||||
| /* Start a state machine listening for events from a stream */ | ||||
| JANET_API JanetListenerState *janet_listen(JanetStream *stream, JanetListener behavior, int mask, size_t size, void *user); | ||||
| JANET_API void janet_schedule_soon(JanetFiber *fiber, Janet value, JanetSignal sig); | ||||
|  | ||||
| /* Shorthand for yielding to event loop in C */ | ||||
| JANET_NO_RETURN JANET_API void janet_await(void); | ||||
| @@ -1399,6 +1443,7 @@ JANET_NO_RETURN JANET_API void janet_sleep_await(double sec); | ||||
| /* For use inside listeners - adds a timeout to the current fiber, such that | ||||
|  * it will be resumed after sec seconds if no other event schedules the current fiber. */ | ||||
| JANET_API void janet_addtimeout(double sec); | ||||
| JANET_API void janet_addtimeout_nil(double sec); | ||||
| JANET_API void janet_ev_inc_refcount(void); | ||||
| JANET_API void janet_ev_dec_refcount(void); | ||||
|  | ||||
| @@ -1409,6 +1454,14 @@ JANET_API void *janet_abstract_threaded(const JanetAbstractType *atype, size_t s | ||||
| JANET_API int32_t janet_abstract_incref(void *abst); | ||||
| JANET_API int32_t janet_abstract_decref(void *abst); | ||||
|  | ||||
| /* Expose channel utilities */ | ||||
| JanetChannel *janet_channel_make(uint32_t limit); | ||||
| JanetChannel *janet_channel_make_threaded(uint32_t limit); | ||||
| JanetChannel *janet_getchannel(const Janet *argv, int32_t n); | ||||
| JanetChannel *janet_optchannel(const Janet *argv, int32_t argc, int32_t n, JanetChannel *dflt); | ||||
| JANET_API int janet_channel_give(JanetChannel *channel, Janet x); | ||||
| JANET_API int janet_channel_take(JanetChannel *channel, Janet *out); | ||||
|  | ||||
| /* Expose some OS sync primitives */ | ||||
| JANET_API size_t janet_os_mutex_size(void); | ||||
| JANET_API size_t janet_os_rwlock_size(void); | ||||
| @@ -1478,23 +1531,22 @@ JANET_API void janet_ev_post_event(JanetVM *vm, JanetCallback cb, JanetEVGeneric | ||||
| JANET_API void janet_ev_default_threaded_callback(JanetEVGenericMessage return_value); | ||||
|  | ||||
| /* Read async from a stream */ | ||||
| JANET_API void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_read(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_readchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes); | ||||
| #ifdef JANET_NET | ||||
| JANET_API void janet_ev_recv(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_API void janet_ev_connect(JanetStream *stream, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_recv(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_recvchunk(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_recvfrom(JanetStream *stream, JanetBuffer *buf, int32_t nbytes, int flags); | ||||
| #endif | ||||
|  | ||||
| /* Write async to a stream */ | ||||
| JANET_API void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf); | ||||
| JANET_API void janet_ev_write_string(JanetStream *stream, JanetString str); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_write_buffer(JanetStream *stream, JanetBuffer *buf); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_write_string(JanetStream *stream, JanetString str); | ||||
| #ifdef JANET_NET | ||||
| JANET_API void janet_ev_send_buffer(JanetStream *stream, JanetBuffer *buf, int flags); | ||||
| JANET_API void janet_ev_send_string(JanetStream *stream, JanetString str, int flags); | ||||
| JANET_API void janet_ev_sendto_buffer(JanetStream *stream, JanetBuffer *buf, void *dest, int flags); | ||||
| JANET_API void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_send_buffer(JanetStream *stream, JanetBuffer *buf, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_send_string(JanetStream *stream, JanetString str, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_sendto_buffer(JanetStream *stream, JanetBuffer *buf, void *dest, int flags); | ||||
| JANET_NO_RETURN JANET_API void janet_ev_sendto_string(JanetStream *stream, JanetString str, void *dest, int flags); | ||||
| #endif | ||||
|  | ||||
| #endif | ||||
| @@ -1565,6 +1617,9 @@ JANET_API int janet_scan_number(const uint8_t *str, int32_t len, double *out); | ||||
| JANET_API int janet_scan_number_base(const uint8_t *str, int32_t len, int32_t base, double *out); | ||||
| JANET_API int janet_scan_int64(const uint8_t *str, int32_t len, int64_t *out); | ||||
| JANET_API int janet_scan_uint64(const uint8_t *str, int32_t len, uint64_t *out); | ||||
| #ifdef JANET_INT_TYPES | ||||
| JANET_API int janet_scan_numeric(const uint8_t *str, int32_t len, Janet *out); | ||||
| #endif | ||||
|  | ||||
| /* Debugging */ | ||||
| JANET_API void janet_debug_break(JanetFuncDef *def, int32_t pc); | ||||
| @@ -1583,6 +1638,7 @@ JANET_API double janet_rng_double(JanetRNG *rng); | ||||
|  | ||||
| /* Array functions */ | ||||
| JANET_API JanetArray *janet_array(int32_t capacity); | ||||
| JANET_API JanetArray *janet_array_weak(int32_t capacity); | ||||
| JANET_API JanetArray *janet_array_n(const Janet *elements, int32_t n); | ||||
| JANET_API void janet_array_ensure(JanetArray *array, int32_t capacity, int32_t growth); | ||||
| JANET_API void janet_array_setcount(JanetArray *array, int32_t count); | ||||
| @@ -1612,7 +1668,7 @@ JANET_API void janet_buffer_push_u64(JanetBuffer *buffer, uint64_t x); | ||||
| #define JANET_TUPLE_FLAG_BRACKETCTOR 0x10000 | ||||
|  | ||||
| #define janet_tuple_head(t) ((JanetTupleHead *)((char *)t - offsetof(JanetTupleHead, data))) | ||||
| #define janet_tuple_from_head(gcobject) ((const Janet *)((char *)gcobject + offsetof(JanetTupleHead, data))) | ||||
| #define janet_tuple_from_head(gcobject) ((JanetTuple)((char *)gcobject + offsetof(JanetTupleHead, data))) | ||||
| #define janet_tuple_length(t) (janet_tuple_head(t)->length) | ||||
| #define janet_tuple_hash(t) (janet_tuple_head(t)->hash) | ||||
| #define janet_tuple_sm_line(t) (janet_tuple_head(t)->sm_line) | ||||
| @@ -1658,7 +1714,7 @@ JANET_API JanetSymbol janet_symbol_gen(void); | ||||
|  | ||||
| /* Structs */ | ||||
| #define janet_struct_head(t) ((JanetStructHead *)((char *)t - offsetof(JanetStructHead, data))) | ||||
| #define janet_struct_from_head(t) ((const JanetKV *)((char *)gcobject + offsetof(JanetStructHead, data))) | ||||
| #define janet_struct_from_head(t) ((JanetStruct)((char *)gcobject + offsetof(JanetStructHead, data))) | ||||
| #define janet_struct_length(t) (janet_struct_head(t)->length) | ||||
| #define janet_struct_capacity(t) (janet_struct_head(t)->capacity) | ||||
| #define janet_struct_hash(t) (janet_struct_head(t)->hash) | ||||
| @@ -1688,6 +1744,9 @@ JANET_API void janet_table_merge_struct(JanetTable *table, JanetStruct other); | ||||
| JANET_API JanetKV *janet_table_find(JanetTable *t, Janet key); | ||||
| JANET_API JanetTable *janet_table_clone(JanetTable *table); | ||||
| JANET_API void janet_table_clear(JanetTable *table); | ||||
| JANET_API JanetTable *janet_table_weakk(int32_t capacity); | ||||
| JANET_API JanetTable *janet_table_weakv(int32_t capacity); | ||||
| JANET_API JanetTable *janet_table_weakkv(int32_t capacity); | ||||
|  | ||||
| /* Fiber */ | ||||
| JANET_API JanetFiber *janet_fiber(JanetFunction *callee, int32_t capacity, int32_t argc, const Janet *argv); | ||||
| @@ -1751,6 +1810,7 @@ JANET_API void janet_gcpressure(size_t s); | ||||
| /* Functions */ | ||||
| JANET_API JanetFuncDef *janet_funcdef_alloc(void); | ||||
| JANET_API JanetFunction *janet_thunk(JanetFuncDef *def); | ||||
| JANET_API JanetFunction *janet_thunk_delay(Janet x); | ||||
| JANET_API int janet_verify(JanetFuncDef *def); | ||||
|  | ||||
| /* Pretty printing */ | ||||
| @@ -1799,6 +1859,7 @@ JANET_API void janet_vm_free(JanetVM *vm); | ||||
| JANET_API void janet_vm_save(JanetVM *into); | ||||
| JANET_API void janet_vm_load(JanetVM *from); | ||||
| JANET_API void janet_interpreter_interrupt(JanetVM *vm); | ||||
| JANET_API void janet_interpreter_interrupt_handled(JanetVM *vm); | ||||
| JANET_API JanetSignal janet_continue(JanetFiber *fiber, Janet in, Janet *out); | ||||
| JANET_API JanetSignal janet_continue_signal(JanetFiber *fiber, Janet in, Janet *out, JanetSignal sig); | ||||
| JANET_API JanetSignal janet_pcall(JanetFunction *fun, int32_t argn, const Janet *argv, Janet *out, JanetFiber **f); | ||||
| @@ -1822,6 +1883,7 @@ JANET_API void janet_stacktrace_ext(JanetFiber *fiber, Janet err, const char *pr | ||||
| #define JANET_SANDBOX_FS_TEMP 1024 | ||||
| #define JANET_SANDBOX_FFI_USE 2048 | ||||
| #define JANET_SANDBOX_FFI_JIT 4096 | ||||
| #define JANET_SANDBOX_SIGNAL 8192 | ||||
| #define JANET_SANDBOX_FFI (JANET_SANDBOX_FFI_DEFINE | JANET_SANDBOX_FFI_USE | JANET_SANDBOX_FFI_JIT) | ||||
| #define JANET_SANDBOX_FS (JANET_SANDBOX_FS_WRITE | JANET_SANDBOX_FS_READ | JANET_SANDBOX_FS_TEMP) | ||||
| #define JANET_SANDBOX_NET (JANET_SANDBOX_NET_CONNECT | JANET_SANDBOX_NET_LISTEN) | ||||
| @@ -1945,10 +2007,10 @@ JANET_API void janet_register(const char *name, JanetCFunction cfun); | ||||
| #endif | ||||
| #ifndef JANET_ENTRY_NAME | ||||
| #define JANET_MODULE_ENTRY \ | ||||
|     JANET_MODULE_PREFIX JANET_API JanetBuildConfig _janet_mod_config(void) { \ | ||||
|     JANET_MODULE_PREFIX JANET_EXPORT JanetBuildConfig _janet_mod_config(void) { \ | ||||
|         return janet_config_current(); \ | ||||
|     } \ | ||||
|     JANET_MODULE_PREFIX JANET_API void _janet_init | ||||
|     JANET_MODULE_PREFIX JANET_EXPORT void _janet_init | ||||
| #else | ||||
| #define JANET_MODULE_ENTRY JANET_MODULE_PREFIX JANET_API void JANET_ENTRY_NAME | ||||
| #endif | ||||
| @@ -1988,7 +2050,10 @@ JANET_API void *janet_getpointer(const Janet *argv, int32_t n); | ||||
|  | ||||
| JANET_API int32_t janet_getnat(const Janet *argv, int32_t n); | ||||
| JANET_API int32_t janet_getinteger(const Janet *argv, int32_t n); | ||||
| JANET_API int16_t janet_getinteger16(const Janet *argv, int32_t n); | ||||
| JANET_API int64_t janet_getinteger64(const Janet *argv, int32_t n); | ||||
| JANET_API uint32_t janet_getuinteger(const Janet *argv, int32_t n); | ||||
| JANET_API uint16_t janet_getuinteger16(const Janet *argv, int32_t n); | ||||
| JANET_API uint64_t janet_getuinteger64(const Janet *argv, int32_t n); | ||||
| JANET_API size_t janet_getsize(const Janet *argv, int32_t n); | ||||
| JANET_API JanetView janet_getindexed(const Janet *argv, int32_t n); | ||||
| @@ -2072,6 +2137,7 @@ JANET_API uint8_t janet_unmarshal_byte(JanetMarshalContext *ctx); | ||||
| JANET_API void janet_unmarshal_bytes(JanetMarshalContext *ctx, uint8_t *dest, size_t len); | ||||
| JANET_API Janet janet_unmarshal_janet(JanetMarshalContext *ctx); | ||||
| JANET_API JanetAbstract janet_unmarshal_abstract(JanetMarshalContext *ctx, size_t size); | ||||
| JANET_API JanetAbstract janet_unmarshal_abstract_threaded(JanetMarshalContext *ctx, size_t size); | ||||
| JANET_API void janet_unmarshal_abstract_reuse(JanetMarshalContext *ctx, void *p); | ||||
|  | ||||
| JANET_API void janet_register_abstract_type(const JanetAbstractType *at); | ||||
| @@ -2110,11 +2176,16 @@ typedef enum { | ||||
|     RULE_TO,           /* [rule] */ | ||||
|     RULE_THRU,         /* [rule] */ | ||||
|     RULE_LENPREFIX,    /* [rule_a, rule_b (repeat rule_b rule_a times)] */ | ||||
|     RULE_READINT,      /* [(signedness << 4) | (endianess << 5) | bytewidth, tag] */ | ||||
|     RULE_READINT,      /* [(signedness << 4) | (endianness << 5) | bytewidth, tag] */ | ||||
|     RULE_LINE,         /* [tag] */ | ||||
|     RULE_COLUMN,       /* [tag] */ | ||||
|     RULE_UNREF,        /* [rule, tag] */ | ||||
|     RULE_CAPTURE_NUM   /* [rule, tag] */ | ||||
|     RULE_CAPTURE_NUM,  /* [rule, tag] */ | ||||
|     RULE_SUB,          /* [rule, rule] */ | ||||
|     RULE_TIL,          /* [rule, rule] */ | ||||
|     RULE_SPLIT,        /* [rule, rule] */ | ||||
|     RULE_NTH,          /* [nth, rule, tag] */ | ||||
|     RULE_ONLY_TAGS,    /* [rule] */ | ||||
| } JanetPegOpcod; | ||||
|  | ||||
| typedef struct { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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 | ||||
| @@ -502,10 +502,10 @@ static void kright(void) { | ||||
| } | ||||
|  | ||||
| static void krightw(void) { | ||||
|     while (gbl_pos != gbl_len && !isspace(gbl_buf[gbl_pos])) { | ||||
|     while (gbl_pos != gbl_len && isspace(gbl_buf[gbl_pos])) { | ||||
|         gbl_pos++; | ||||
|     } | ||||
|     while (gbl_pos != gbl_len && isspace(gbl_buf[gbl_pos])) { | ||||
|     while (gbl_pos != gbl_len && !isspace(gbl_buf[gbl_pos])) { | ||||
|         gbl_pos++; | ||||
|     } | ||||
|     refresh(); | ||||
| @@ -867,7 +867,7 @@ static int line() { | ||||
|     if (write_console((char *) gbl_prompt, gbl_plen) == -1) return -1; | ||||
|     for (;;) { | ||||
|         char c; | ||||
|         char seq[3]; | ||||
|         char seq[5]; | ||||
|  | ||||
|         int rc; | ||||
|         do { | ||||
| @@ -991,6 +991,20 @@ static int line() { | ||||
|                                 default: | ||||
|                                     break; | ||||
|                             } | ||||
|                         } else if (seq[2] == ';') { | ||||
|                             if (read_console(seq + 3, 2) == -1) break; | ||||
|                             if (seq[3] == '5') { | ||||
|                                 switch (seq[4]) { | ||||
|                                     case 'C': /* ctrl-right */ | ||||
|                                         krightw(); | ||||
|                                         break; | ||||
|                                     case 'D': /* ctrl-left */ | ||||
|                                         kleftw(); | ||||
|                                         break; | ||||
|                                     default: | ||||
|                                         break; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } else if (seq[0] == 'O') { | ||||
|                         if (read_console(seq + 1, 1) == -1) break; | ||||
| @@ -1163,6 +1177,7 @@ int main(int argc, char **argv) { | ||||
|     janet_resolve(env, janet_csymbol("cli-main"), &mainfun); | ||||
|     Janet mainargs[1] = { janet_wrap_array(args) }; | ||||
|     JanetFiber *fiber = janet_fiber(janet_unwrap_function(mainfun), 64, 1, mainargs); | ||||
|     janet_gcroot(janet_wrap_fiber(fiber)); | ||||
|     fiber->env = env; | ||||
|  | ||||
|     /* Run the fiber in an event loop */ | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
| * Copyright (c) 2023 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,24 +4,47 @@ | ||||
| (var num-tests-run 0) | ||||
| (var suite-name 0) | ||||
| (var start-time 0) | ||||
| (var skip-count 0) | ||||
| (var skip-n 0) | ||||
|  | ||||
| (def is-verbose (os/getenv "VERBOSE")) | ||||
|  | ||||
| (defn assert | ||||
| (defn- assert-no-tail | ||||
|   "Override's the default assert with some nice error handling." | ||||
|   [x &opt e] | ||||
|   (default e "assert error") | ||||
|   (++ num-tests-run) | ||||
|   (when (pos? skip-n) | ||||
|     (-- skip-n) | ||||
|     (++ skip-count) | ||||
|     (break x)) | ||||
|   (default e "assert error") | ||||
|   (when x (++ num-tests-passed)) | ||||
|   (def str (string e)) | ||||
|   (def frame (last (debug/stack (fiber/current)))) | ||||
|   (def stack (debug/stack (fiber/current))) | ||||
|   (def frame (last stack)) | ||||
|   (def line-info (string/format "%s:%d" | ||||
|                               (frame :source) (frame :source-line))) | ||||
|   (if x | ||||
|     (when is-verbose (eprintf "\e[32m✔\e[0m %s: %s: %v" line-info (describe e) x)) | ||||
|     (eprintf "\e[31m✘\e[0m %s: %s: %v" line-info (describe e) x)) | ||||
|     (do | ||||
|       (eprintf "\e[31m✘\e[0m %s: %s: %v" line-info (describe e) x) (eflush))) | ||||
|   x) | ||||
|  | ||||
| (defn skip-asserts | ||||
|   "Skip some asserts" | ||||
|   [n] | ||||
|   (+= skip-n n) | ||||
|   nil) | ||||
|  | ||||
| (defmacro assert | ||||
|   [x &opt e] | ||||
|   (def xx (gensym)) | ||||
|   (default e (string/format "%j" x)) | ||||
|   ~(do | ||||
|      (def ,xx ,x) | ||||
|      (,assert-no-tail ,xx ,e) | ||||
|      ,xx)) | ||||
|  | ||||
| (defmacro assert-error | ||||
|   [msg & forms] | ||||
|   (def errsym (keyword (gensym))) | ||||
| @@ -34,17 +57,17 @@ | ||||
|  | ||||
| (defmacro assert-no-error | ||||
|   [msg & forms] | ||||
|   (def errsym (keyword (gensym))) | ||||
|   ~(assert (not= ,errsym (try (do ,;forms) ([_] ,errsym))) ,msg)) | ||||
|   (def e (gensym)) | ||||
|   (def f (gensym)) | ||||
|   (if is-verbose | ||||
|   ~(try (do ,;forms (,assert true ,msg)) ([,e ,f] (,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m "))) | ||||
|   ~(try (do ,;forms (,assert true ,msg)) ([_] (,assert false ,msg))))) | ||||
|  | ||||
| (defn start-suite [&opt x] | ||||
|   (default x (dyn :current-file)) | ||||
|   (set suite-name | ||||
|        (cond | ||||
|          (number? x) (string x) | ||||
|          (string? x) (string/slice x | ||||
|                                    (length "test/suite-") | ||||
|                                    (- (inc (length ".janet")))) | ||||
|          (string x))) | ||||
|   (set start-time (os/clock)) | ||||
|   (eprint "Starting suite " suite-name "...")) | ||||
| @@ -52,5 +75,22 @@ | ||||
| (defn end-suite [] | ||||
|   (def delta (- (os/clock) start-time)) | ||||
|   (eprinf "Finished suite %s in %.3f seconds - " suite-name delta) | ||||
|   (eprint num-tests-passed " of " num-tests-run " tests passed.") | ||||
|   (if (not= num-tests-passed num-tests-run) (os/exit 1))) | ||||
|   (eprint num-tests-passed " of " num-tests-run " tests passed (" skip-count " skipped).") | ||||
|   (if (not= (+ skip-count num-tests-passed) num-tests-run) (os/exit 1))) | ||||
|  | ||||
| (defn rmrf | ||||
|   "rm -rf in janet" | ||||
|   [x] | ||||
|   (case (os/lstat x :mode) | ||||
|     nil nil | ||||
|     :directory (do | ||||
|                  (each y (os/dir x) | ||||
|                    (rmrf (string x "/" y))) | ||||
|                  (os/rmdir x)) | ||||
|     (os/rm x)) | ||||
|   nil) | ||||
|  | ||||
| (defn randdir | ||||
|   "Get a random directory name" | ||||
|   [] | ||||
|   (string "tmp_dir_" (slice (string (math/random) ".tmp") 2))) | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| # Copyright (c) 2023 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 | ||||
| @@ -46,7 +46,6 @@ | ||||
| (assert (deep= (array/remove @[1 2 3 4 5] 2 200) @[1 2]) "array/remove 3") | ||||
| (assert (deep= (array/remove @[1 2 3 4 5] -2 200) @[1 2 3]) "array/remove 4") | ||||
|  | ||||
|  | ||||
| # array/peek | ||||
| (assert (nil? (array/peek @[])) "array/peek empty") | ||||
|  | ||||
| @@ -76,6 +75,16 @@ | ||||
| (array/trim a) | ||||
| (array/ensure @[1 1] 6 2) | ||||
|  | ||||
| # array/join | ||||
| (assert (deep= @[1 2 3] (array/join @[] [1] [2] [3])) "array/join 1") | ||||
| (assert (deep= @[] (array/join @[])) "array/join 2") | ||||
| (assert (deep= @[1 :a :b :c] (array/join @[1] @[:a :b] [] [:c])) "array/join 3") | ||||
| (assert (deep= @[:x :y :z "abc123" "def456"] (array/join @[:x :y :z] ["abc123" "def456"])) "array/join 4") | ||||
| (assert-error "array/join error 1" (array/join)) | ||||
| (assert-error "array/join error 2" (array/join [])) | ||||
| (assert-error "array/join error 3" (array/join [] "abc123")) | ||||
| (assert-error "array/join error 4" (array/join @[] "abc123")) | ||||
| (assert-error "array/join error 5" (array/join @[] "abc123")) | ||||
|  | ||||
| (end-suite) | ||||
|  | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user