mirror of
				https://github.com/irmen/prog8.git
				synced 2025-10-31 00:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			924 Commits
		
	
	
		
			v10.4
			...
			v12.0-beta
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5d9fbd2ccc | ||
|  | 7efc709538 | ||
|  | 79419a98d0 | ||
|  | 1c77d5d5e7 | ||
|  | c6854e22a3 | ||
|  | 83acc2f285 | ||
|  | 6de95f7a3b | ||
|  | 82839a2d82 | ||
|  | e5fc9b3132 | ||
|  | ced4c5944a | ||
|  | d4c460072b | ||
|  | 0b9384b556 | ||
|  | 6c3277e3e3 | ||
|  | d9ff1eb38a | ||
|  | e178097735 | ||
|  | a1ab8ed208 | ||
|  | 6ababbf8f4 | ||
|  | 79629befc1 | ||
|  | 8022c0772a | ||
|  | 8ad2b4638b | ||
|  | 3a0392df8a | ||
|  | beb28b061d | ||
|  | c39acc5031 | ||
|  | f54a29c415 | ||
|  | 783b111059 | ||
|  | fb286f8b54 | ||
|  | 5999110e3f | ||
|  | 52a757ea78 | ||
|  | 28df08eea8 | ||
|  | 79505308ba | ||
|  | a7e9d8e14b | ||
|  | 08f3abe5bf | ||
|  | 3ef09d7d9a | ||
|  | a9142b9ce5 | ||
|  | 13e6f64d3b | ||
|  | 4a1256c772 | ||
|  | ff9bec90ec | ||
|  | a6fee1e510 | ||
|  | 80538f101e | ||
|  | aee53b14c7 | ||
|  | 5eb2fc8d86 | ||
|  | 98f91bbf88 | ||
|  | b48b36ef18 | ||
|  | 221a093e5f | ||
|  | 2ca1820d4e | ||
|  | d58737d5be | ||
|  | b52cee3154 | ||
|  | 9a76941e10 | ||
|  | 0285a4cce1 | ||
|  | 5a3aa1bd25 | ||
|  | 0f79351de9 | ||
|  | 3cdb25ce8e | ||
|  | b7193bd0c6 | ||
|  | 10ff6a0095 | ||
|  | d30f58004e | ||
|  | 17bdb22e4f | ||
|  | a68209be37 | ||
|  | 7b40eade44 | ||
|  | 8a717c74b9 | ||
|  | aa72ded21e | ||
|  | 8e53c83844 | ||
|  | e2a2db1256 | ||
|  | 2afd2d4dae | ||
|  | fad17cc094 | ||
|  | f93d957999 | ||
|  | 8db0344cee | ||
|  | 32e531f951 | ||
|  | a4769702f9 | ||
|  | cf19fb8df1 | ||
|  | 79b8bb5c9f | ||
|  | fc5889ec0b | ||
|  | 369303c46d | ||
|  | d65670cc7b | ||
|  | f74eeaee0f | ||
|  | 826fb3e9c2 | ||
|  | a3d7b8a899 | ||
|  | 0cc36ed6e4 | ||
|  | 976bd52972 | ||
|  | 4a8d5def84 | ||
|  | 2f60716082 | ||
|  | 729efb04e1 | ||
|  | 4ea8b4d445 | ||
|  | e800c165f9 | ||
|  | fd9bd23449 | ||
|  | 8880ed1393 | ||
|  | f7fde070ca | ||
|  | 5ada80779d | ||
|  | 8972235a0e | ||
|  | e56f533e38 | ||
|  | 324fb7dbf7 | ||
|  | 44285b9b5d | ||
|  | a68f477d61 | ||
|  | ae9f99448e | ||
|  | 7c0fb10197 | ||
|  | 9e85571a7b | ||
|  | 9e10c15e2e | ||
|  | 6bd7752bac | ||
|  | 83ec437e8a | ||
|  | 4a1d05dd46 | ||
|  | aa324e355a | ||
|  | 5cb8bcead7 | ||
|  | bbd06c0c99 | ||
|  | 651830ea82 | ||
|  | c70146f1dc | ||
|  | d4e83b28bb | ||
|  | bc58a25765 | ||
|  | 38645022c9 | ||
|  | 647cd0fbe1 | ||
|  | ea8935a346 | ||
|  | 7ea80babfc | ||
|  | dee761a99e | ||
|  | 88ee7a8187 | ||
|  | eb8b408b82 | ||
|  | 3d10882f57 | ||
|  | 1988496512 | ||
|  | 88b074c208 | ||
|  | c4c5636a81 | ||
|  | c39d570b72 | ||
|  | 4ccd7f9f3a | ||
|  | 1c9c5aeef7 | ||
|  | 23ad540aa5 | ||
|  | 08810c2749 | ||
|  | a52966f327 | ||
|  | 624220e9a3 | ||
|  | 842b11ed9e | ||
|  | 82267b3f56 | ||
|  | 67fb45a55b | ||
|  | 11186f1dbe | ||
|  | 0116fac201 | ||
|  | 817f4f8e7c | ||
|  | 866313209b | ||
|  | 28e351daab | ||
|  | 893e16d814 | ||
|  | 33470c47fc | ||
|  | 63f7b87572 | ||
|  | e2901cca1b | ||
|  | ce8006992a | ||
|  | 0b5413ad83 | ||
|  | dd7adde387 | ||
|  | 23058b51a1 | ||
|  | 2f90c53ad0 | ||
|  | c3be7ab4b3 | ||
|  | a9b8fbc6c6 | ||
|  | 720988ae72 | ||
|  | b0981a5fae | ||
|  | ea5deeefbd | ||
|  | 054c98da7c | ||
|  | dc434d034a | ||
|  | a4a1b563aa | ||
|  | 3f34b83e0d | ||
|  | 9c63ef39c7 | ||
|  | 9f6106452e | ||
|  | f9fbfe30e3 | ||
|  | 9a9bf170c6 | ||
|  | 7dd64b4f13 | ||
|  | b6c0bac96f | ||
|  | 8ede098154 | ||
|  | 2a4a3b786e | ||
|  | b4e0a2019e | ||
|  | e14c3f8b59 | ||
|  | c81f76226d | ||
|  | edc353cc24 | ||
|  | dcce519c69 | ||
|  | 0a16dcafc0 | ||
|  | 54d41b7f6f | ||
|  | 0541b84d09 | ||
|  | 1b420f7fe7 | ||
|  | 6a9a82ff9d | ||
|  | aa36e6b19f | ||
|  | 51cb6aad50 | ||
|  | b5ce409592 | ||
|  | 2119817e4a | ||
|  | 1efdfe8ea1 | ||
|  | 67d4180825 | ||
|  | be31e190d2 | ||
|  | a68cf3c812 | ||
|  | c2bf9024f8 | ||
|  | bd72eaad4c | ||
|  | b5d1575823 | ||
|  | 1d306e5cdc | ||
|  | b137164fe6 | ||
|  | 67d4ad50e1 | ||
|  | c71066af4c | ||
|  | 6f0a0981bd | ||
|  | 49a4d9ba37 | ||
|  | fcdfa741b9 | ||
|  | e3e395836d | ||
|  | 3bab177d50 | ||
|  | 12abafb917 | ||
|  | 8dc2e47507 | ||
|  | 0be90dedf2 | ||
|  | daf7c3357c | ||
|  | e6bab3ceeb | ||
|  | 59387b2ae8 | ||
|  | e8795859c5 | ||
|  | bebe60b687 | ||
|  | ddceec364e | ||
|  | f8f20440d3 | ||
|  | f8722faa4e | ||
|  | d067fa4b73 | ||
|  | 26fbbf48a4 | ||
|  | d5cc414221 | ||
|  | b5e51ab937 | ||
|  | 552e55c29f | ||
|  | a228908c1a | ||
|  | 15fc3b6c04 | ||
|  | 0456badd02 | ||
|  | d28f154f1c | ||
|  | 399cf5118d | ||
|  | a87f2640d3 | ||
|  | a90ef274d7 | ||
|  | 1c02179c5c | ||
|  | 77584493fd | ||
|  | a36709e638 | ||
|  | 341778ba67 | ||
|  | 8d63cce749 | ||
|  | ec50b5a007 | ||
|  | 8e7bbcdbe0 | ||
|  | 37ecdc47b3 | ||
|  | 112ca3cc53 | ||
|  | 33b3a1664c | ||
|  | 8a0c02e264 | ||
|  | 31d84c8921 | ||
|  | 34bedbeef1 | ||
|  | 3b1b0985c1 | ||
|  | e40ace9dea | ||
|  | 4c0e6e2640 | ||
|  | 08b314c37d | ||
|  | 86da9d3c7e | ||
|  | 4e61e25c02 | ||
|  | 5097d52d99 | ||
|  | 368387e1a7 | ||
|  | 09d2185bb1 | ||
|  | 5c02e2bd71 | ||
|  | fb01389b3d | ||
|  | aaa81210ce | ||
|  | 51269257ea | ||
|  | 23a853db1e | ||
|  | 9da430ffeb | ||
|  | cc063124cf | ||
|  | 3b37b89951 | ||
|  | 844b537d1e | ||
|  | caf1d4a22a | ||
|  | d8e244df99 | ||
|  | 548e421e27 | ||
|  | 322fa7ea69 | ||
|  | db6c887795 | ||
|  | cf7bea0985 | ||
|  | 61fe55168a | ||
|  | 25d7f8808f | ||
|  | 1c4999ec87 | ||
|  | c726d3f937 | ||
|  | f70341df1b | ||
|  | f0b791452e | ||
|  | adf5600a9b | ||
|  | 6d4ccc5feb | ||
|  | 5f3829d5cc | ||
|  | 770ebdcd4a | ||
|  | 96f690e749 | ||
|  | eabdd3a8f3 | ||
|  | 50650b966b | ||
|  | 65e34d4989 | ||
|  | 05dad5ab5f | ||
|  | 1a69a2f1bc | ||
|  | 435faafaad | ||
|  | 686b32dc29 | ||
|  | 0e64a22910 | ||
|  | 4f0839f27e | ||
|  | bb1953267d | ||
|  | acc630972a | ||
|  | 6a33be3fd8 | ||
|  | cd8aae4681 | ||
|  | 11456496bd | ||
|  | f5fc4e345c | ||
|  | 86eef7039f | ||
|  | f4b2264fcf | ||
|  | 9b36ae2277 | ||
|  | 913ab03963 | ||
|  | 38448e471c | ||
|  | 67231af623 | ||
|  | e31ef6f06f | ||
|  | 09d188106a | ||
|  | d8e2116481 | ||
|  | 435dfbb932 | ||
|  | ba93966474 | ||
|  | ea8d17cdb2 | ||
|  | 082265fb25 | ||
|  | d138a7a567 | ||
|  | ea27d732ab | ||
|  | 9e557ce8ac | ||
|  | 924e28e9b3 | ||
|  | e5d9af75de | ||
|  | 31c1bf8bc5 | ||
|  | 37d4055036 | ||
|  | 78b1076110 | ||
|  | 0a3c748e41 | ||
|  | ebf79ef9e2 | ||
|  | 60a73248cd | ||
|  | abbb7d7ba3 | ||
|  | 59c378089e | ||
|  | 0b789b5f0b | ||
|  | 4382b96a9a | ||
|  | 246e4f35a6 | ||
|  | 99b9370178 | ||
|  | 506062c6b6 | ||
|  | d634061cd9 | ||
|  | 8353c689ca | ||
|  | d59d8ff1fe | ||
|  | e98e6f70ac | ||
|  | 53e442d509 | ||
|  | 134352ed7c | ||
|  | f7cbfdff06 | ||
|  | b28ee0819f | ||
|  | 5de626aab8 | ||
|  | 7aad5d486e | ||
|  | 701f155951 | ||
|  | 8c324d7514 | ||
|  | 522958e0e9 | ||
|  | 97390db5f5 | ||
|  | af920d1427 | ||
|  | 779ebc0537 | ||
|  | 38949b82c3 | ||
|  | d11386ef26 | ||
|  | 0e0377d1f0 | ||
|  | 55e0dbab27 | ||
|  | 4dc82f2c83 | ||
|  | 1ba5587404 | ||
|  | 835c4b6da3 | ||
|  | dbd955b61e | ||
|  | d20e2fd88c | ||
|  | e0dea89477 | ||
|  | ccc6b56e35 | ||
|  | 6fc2902895 | ||
|  | c96e4b40d4 | ||
|  | 37da3e2170 | ||
|  | 2661d3c489 | ||
|  | b89bbb9281 | ||
|  | 696bf636ed | ||
|  | 40952a788a | ||
|  | 0162e7a0c1 | ||
|  | 6ce099f176 | ||
|  | 476a4bac8e | ||
|  | 63a410a6df | ||
|  | cca27faa3b | ||
|  | 803e6bd81a | ||
|  | 88269628a2 | ||
|  | b920d553a0 | ||
|  | 5e2d0d0dfc | ||
|  | 2ae3bd68eb | ||
|  | 9c183f27eb | ||
|  | 8046023e82 | ||
|  | e328520588 | ||
|  | 7eb079050c | ||
|  | 2fdd5543b2 | ||
|  | d04164c0a6 | ||
|  | b047731f82 | ||
|  | 4d91f92a2e | ||
|  | 98505d27b1 | ||
|  | cd63a58ad9 | ||
|  | 170f8dd092 | ||
|  | 619dcb6a84 | ||
|  | 99ae8ea52e | ||
|  | dc031c30eb | ||
|  | 1e702439b7 | ||
|  | 8debc42381 | ||
|  | 532d719089 | ||
|  | b40860aca4 | ||
|  | 2cbe6b5f7f | ||
|  | d2cc7ccdfa | ||
|  | 2cb183c6d8 | ||
|  | 84026b105f | ||
|  | a4d0589f10 | ||
|  | e375f6afce | ||
|  | 5a7bc04816 | ||
|  | bd1894580e | ||
|  | 9e694c0337 | ||
|  | c82586db28 | ||
|  | dd2d466350 | ||
|  | 830da8de0a | ||
|  | 4e5ee333c8 | ||
|  | 9df899eb63 | ||
|  | ca7491a702 | ||
|  | 1a07129865 | ||
|  | 4fbd67ff99 | ||
|  | 5bc6c50f42 | ||
|  | 063de3801d | ||
|  | ae65266a4a | ||
|  | 8ed2401e0b | ||
|  | d2e8ee8269 | ||
|  | 1f996e3b8b | ||
|  | 7108b74105 | ||
|  | 801fe1b604 | ||
|  | fb44c87597 | ||
|  | 6b9cdbd482 | ||
|  | 0ab98033b5 | ||
|  | 14a2b96609 | ||
|  | f829b689db | ||
|  | dfda8b7ed5 | ||
|  | 4388466451 | ||
|  | 5c2f509a52 | ||
|  | 59582f5210 | ||
|  | e2a8bdbdfb | ||
|  | 0916b943da | ||
|  | 9c7ebc883c | ||
|  | 0ee42b9aa0 | ||
|  | 37b3868ca3 | ||
|  | a6835ce3f0 | ||
|  | 69c96ad99b | ||
|  | b72877d59d | ||
|  | 05eb15d4f7 | ||
|  | f1fec37c79 | ||
|  | 73f6880ff8 | ||
|  | 8a53742f31 | ||
|  | 9be40e85ff | ||
|  | 61079c1eb7 | ||
|  | 1075ee8fc3 | ||
|  | a28b265197 | ||
|  | 20e534c468 | ||
|  | da7aa5dc49 | ||
|  | 8f2a43ca0a | ||
|  | d0909d7810 | ||
|  | 1641999d20 | ||
|  | e16452037c | ||
|  | 344d79684a | ||
|  | 573a1d9b7b | ||
|  | 25ab57580c | ||
|  | a332e0e3d1 | ||
|  | 376f1cb139 | ||
|  | 90f80558d7 | ||
|  | e281994898 | ||
|  | 29fac122e1 | ||
|  | 1dc412eb90 | ||
|  | 3770a4fe0c | ||
|  | 79cda544c8 | ||
|  | f04b97d890 | ||
|  | 3e9b4ccc45 | ||
|  | 2c3d838dd8 | ||
|  | 7668a3c660 | ||
|  | 5dd45b714a | ||
|  | 8b08895d0f | ||
|  | 8f8d99e3ed | ||
|  | 23474360ec | ||
|  | 81c255c450 | ||
|  | ef23d52ed7 | ||
|  | 220ab773aa | ||
|  | e3e5bff7bb | ||
|  | 7b9a841b2a | ||
|  | 40423911ef | ||
|  | 582a70b046 | ||
|  | 5b63590ebf | ||
|  | 2b6510dc19 | ||
|  | 9d49589d73 | ||
|  | 125b66c929 | ||
|  | 5255f1c052 | ||
|  | a6ba05d60c | ||
|  | 41e963b04b | ||
|  | 6ff75bef29 | ||
|  | 72c16d0d32 | ||
|  | 94653e5c8c | ||
|  | 3e2b2a698d | ||
|  | ae04f5aee8 | ||
|  | 5c56267662 | ||
|  | e55ce5504e | ||
|  | fb1e89d9ef | ||
|  | bc550a4549 | ||
|  | ebdea9cf76 | ||
|  | 09ec508f82 | ||
|  | d06e9ea7f6 | ||
|  | a36bdc54fd | ||
|  | 0814ea9711 | ||
|  | daefe839d8 | ||
|  | e6088dd315 | ||
|  | fc03d6f332 | ||
|  | 2aeb7a838e | ||
|  | 99ff5dd078 | ||
|  | 49982b49b6 | ||
|  | fd39c22616 | ||
|  | 9e79722a7f | ||
|  | 17334a1c58 | ||
|  | c7f0ff11ac | ||
|  | cd2cc89e6a | ||
|  | 069143092d | ||
|  | efd41260f2 | ||
|  | 8d2410622c | ||
|  | 60554389b3 | ||
|  | a940dc7d43 | ||
|  | 06ca68a625 | ||
|  | 5b58e5b158 | ||
|  | 74dd8fe80b | ||
|  | 75ddcda5f3 | ||
|  | 216825b98a | ||
|  | a96defab86 | ||
|  | 0864b0a1b7 | ||
|  | 8b158d9240 | ||
|  | f335251c2b | ||
|  | 67bc0b6931 | ||
|  | e646dd1ed1 | ||
|  | 2b7947f9b0 | ||
|  | ec0cfb4b3f | ||
|  | 9cdf53019c | ||
|  | 1a04a3eb3a | ||
|  | 105d3995e0 | ||
|  | 8ce3204f93 | ||
|  | d0f15f1285 | ||
|  | 66d6f67120 | ||
|  | a106c88054 | ||
|  | ee784e1ccc | ||
|  | bb75be0b44 | ||
|  | 2478aea316 | ||
|  | 1e17df5296 | ||
|  | 8583a96519 | ||
|  | d0c184c7de | ||
|  | 0191acb2b3 | ||
|  | 277a1a32b2 | ||
|  | 7a13f57ab0 | ||
|  | 0c882836d9 | ||
|  | 228be5cd04 | ||
|  | 08cd2fd6e8 | ||
|  | bc7b086f0f | ||
|  | e8f3af6981 | ||
|  | f9c7c7dab7 | ||
|  | 09a17743ad | ||
|  | 4f096a7511 | ||
|  | 2ab2130000 | ||
|  | 66558f7638 | ||
|  | a6f9ed07e7 | ||
|  | 7268a8736f | ||
|  | 8f6b5676d7 | ||
|  | ca9422bbe9 | ||
|  | 35d9412559 | ||
|  | f071c07dd9 | ||
|  | e5ff3c1ff3 | ||
|  | f0e8ff0326 | ||
|  | 3b5cda85ff | ||
|  | 420793f9e2 | ||
|  | cf1dbaf0d8 | ||
|  | d187cef6b7 | ||
|  | 3b3616afda | ||
|  | 0ffebc25d0 | ||
|  | 478e2b4ebd | ||
|  | a56ae7539a | ||
|  | 407773bda2 | ||
|  | 823eaa8918 | ||
|  | a2be42c5ca | ||
|  | a76b8d66ff | ||
|  | b7f47d354f | ||
|  | 5d33c93af9 | ||
|  | 4db6859f3f | ||
|  | 45fe1bb16e | ||
|  | b014facbd3 | ||
|  | 3b4b37f16b | ||
|  | 68d5983a14 | ||
|  | f2cfcfdf31 | ||
|  | 10b9162dc5 | ||
|  | c84cc8f8c9 | ||
|  | 78c71bbf0e | ||
|  | 37c2c1bf0b | ||
|  | c8996418da | ||
|  | 76b29aa629 | ||
|  | ee521793f8 | ||
|  | f42e12bc13 | ||
|  | 427451a23f | ||
|  | af7930d494 | ||
|  | e2882d37bf | ||
|  | 942d3ee640 | ||
|  | 7b4a82b91a | ||
|  | 056c0a24d9 | ||
|  | 827df04b32 | ||
|  | e174b31344 | ||
|  | 49959af752 | ||
|  | c86c0912f8 | ||
|  | 268b0c9365 | ||
|  | 099fe280ba | ||
|  | f786f60e9c | ||
|  | f40e1eb1f2 | ||
|  | 8b9da65357 | ||
|  | 2cbbe0d48a | ||
|  | b6e1fb3ba8 | ||
|  | bdccffbb8e | ||
|  | 5a85474712 | ||
|  | f50899c6fa | ||
|  | 4daa909f32 | ||
|  | 4555edf369 | ||
|  | 529ea5bf58 | ||
|  | fe011de934 | ||
|  | 0653d430a7 | ||
|  | a587f6e9a0 | ||
|  | 3850e1dbb5 | ||
|  | 91cde072e0 | ||
|  | 2ca4aed566 | ||
|  | 5071da6784 | ||
|  | 4c1e2f3110 | ||
|  | 2727a4dcb3 | ||
|  | 126d4c69e6 | ||
|  | 7657edcb7d | ||
|  | 580e786952 | ||
|  | c0ae35b3a3 | ||
|  | c3dc74788a | ||
|  | 379d241a0d | ||
|  | 1f49e8fe75 | ||
|  | d70cfbb661 | ||
|  | 5482ac0302 | ||
|  | 131d5ceb4f | ||
|  | 512ddd1694 | ||
|  | 14a213bff9 | ||
|  | d586846bc5 | ||
|  | ef4efcb112 | ||
|  | b01555d75e | ||
|  | 3804fba0f1 | ||
|  | f93b7e3303 | ||
|  | 73baaeff1f | ||
|  | 7c79cdbd2f | ||
|  | 8ea032ed66 | ||
|  | e7a0cb636c | ||
|  | 02f3f5d0f5 | ||
|  | 1e9bbd662b | ||
|  | 8644a4ae91 | ||
|  | 1e85f7812f | ||
|  | 80d88b3c61 | ||
|  | d2827a7431 | ||
|  | 28c721fa7d | ||
|  | 8f799567cf | ||
|  | 9e8cc8b54d | ||
|  | cc59069876 | ||
|  | 697d54e10a | ||
|  | 1679ca79b4 | ||
|  | 124ec77b58 | ||
|  | 3675d7961b | ||
|  | f8aaa2d13c | ||
|  | b7afda781a | ||
|  | 535ec13072 | ||
|  | 26d0a174db | ||
|  | b2e821755c | ||
|  | 2e303041c1 | ||
|  | 96bed8f57f | ||
|  | 86d4a4309f | ||
|  | 1a1ab0dac6 | ||
|  | ba8c3d14f7 | ||
|  | 617ea15c3a | ||
|  | ef192a5778 | ||
|  | 565973c520 | ||
|  | 25b1043572 | ||
|  | 1ebfff7c7b | ||
|  | 8341f9c066 | ||
|  | 28cac291de | ||
|  | 8fa14a10e2 | ||
|  | 55dbd095ed | ||
|  | 31ad8bdd8d | ||
|  | 181f3e9eb1 | ||
|  | 50c3d809dc | ||
|  | 58f696d00a | ||
|  | f603c543d3 | ||
|  | 6aaa0f928e | ||
|  | feb8aa435e | ||
|  | 310e8f15cd | ||
|  | da03941582 | ||
|  | dcbb36a3bd | ||
|  | 53558f5c1d | ||
|  | 189399d5f8 | ||
|  | 5406a992f5 | ||
|  | bc9683cc54 | ||
|  | 2eed75f602 | ||
|  | d58f9f56c4 | ||
|  | 2e35f3c3a3 | ||
|  | 5c6bd9c091 | ||
|  | 857d2eefca | ||
|  | 90f1e7fd6a | ||
|  | 18e37accf9 | ||
|  | cc53d698bf | ||
|  | cb86206698 | ||
|  | d77b1944fb | ||
|  | a58cb43c4a | ||
|  | 88574c87c4 | ||
|  | 3a7a7091c0 | ||
|  | 906b137a7c | ||
|  | 42e2c5f605 | ||
|  | cc13a51493 | ||
|  | f569ce6141 | ||
|  | 4958463e75 | ||
|  | 2360625927 | ||
|  | 8badc40883 | ||
|  | 844c97930f | ||
|  | 5c09dc10ae | ||
|  | 9fd9e9ab5f | ||
|  | 35c477b5a6 | ||
|  | ae0cadb383 | ||
|  | 984230e8fa | ||
|  | a874aec6a1 | ||
|  | ea1daa97d3 | ||
|  | fb0d9b46b0 | ||
|  | 9da70bdf05 | ||
|  | d640cfbe13 | ||
|  | 51a05ec4b7 | ||
|  | 1f5706bbeb | ||
|  | 25c9b2fea4 | ||
|  | 154f9b300f | ||
|  | d78ce77536 | ||
|  | b4fb43bc80 | ||
|  | e0e01f794e | ||
|  | 08865dbb4e | ||
|  | b9ad7e0e55 | ||
|  | 07158a6f1a | ||
|  | 957c42bc1d | ||
|  | f784da2da6 | ||
|  | c080fbe59a | ||
|  | d70b8303b1 | ||
|  | 1d38c3582a | ||
|  | 9438e996d7 | ||
|  | 3b4a5e27f7 | ||
|  | 648d9fc269 | ||
|  | 54fccec7d7 | ||
|  | 4f9693055e | ||
|  | 555c50ee10 | ||
|  | bf98ceca2c | ||
|  | 573cecb087 | ||
|  | 1b528491c2 | ||
|  | 4bdabe1961 | ||
|  | a3fa527378 | ||
|  | 84f5ffa426 | ||
|  | 25d2b42283 | ||
|  | 300d1a871c | ||
|  | 2fcb83a39f | ||
|  | 3ba1d00a7c | ||
|  | 64164c1c72 | ||
|  | 3ee6058524 | ||
|  | 93a0a41e73 | ||
|  | e7ab7b6d7a | ||
|  | 7d4dc3c063 | ||
|  | a50400b7d1 | ||
|  | f89f1a84d0 | ||
|  | 688dce6145 | ||
|  | b88f550c5b | ||
|  | 9864abd393 | ||
|  | c702c4a6df | ||
|  | 77e376f6bf | ||
|  | 491e5dbcfb | ||
|  | a5c7393561 | ||
|  | 7fd3e9bb7d | ||
|  | 459e9f8f3b | ||
|  | 5b1143bcb3 | ||
|  | fddd390d31 | ||
|  | e514eeba17 | ||
|  | c11a52b278 | ||
|  | 85e87dfe2e | ||
|  | cb47e2c149 | ||
|  | 0fc9aa6b2d | ||
|  | 155896c4c7 | ||
|  | 178e60bba0 | ||
|  | 9f84aa5fb2 | ||
|  | 66fc109ce5 | ||
|  | a231872821 | ||
|  | 7cfb33a448 | ||
|  | 3b798097b9 | ||
|  | 6fb05bdefc | ||
|  | 64ea72ed4d | ||
|  | 89425088ce | ||
|  | 925b9d845d | ||
|  | ad074076c2 | ||
|  | a2194c43a6 | ||
|  | 4b23b1dc86 | ||
|  | 9005c7994a | ||
|  | 4a47e15b1c | ||
|  | 09cbdf410a | ||
|  | df6a43c7f0 | ||
|  | 4ce130dc8b | ||
|  | 94d76aa82c | ||
|  | 73609636c5 | ||
|  | 66b06d6c40 | ||
|  | eeeb8d81f4 | ||
|  | 6f727aff88 | ||
|  | 518e5a30c2 | ||
|  | bbba4b3d60 | ||
|  | 967adb9a87 | ||
|  | 040a6c62de | ||
|  | 483d193ced | ||
|  | 62458216c9 | ||
|  | 76b05cb5fd | ||
|  | 570b574b93 | ||
|  | a82f211f9a | ||
|  | 504c80cddf | ||
|  | 4b4af9b527 | ||
|  | 28b383f888 | ||
|  | 40ce7725a1 | ||
|  | 1f2d46628e | ||
|  | c9535049c8 | ||
|  | 9317cf8a35 | ||
|  | 1cd754f05d | ||
|  | 97b8cb748d | ||
|  | 84d9040b57 | ||
|  | fdd18c615c | ||
|  | c14f6cfc2b | ||
|  | 326eab3dd1 | ||
|  | 6da1f7eb4c | ||
|  | 1e82483152 | ||
|  | 6e2fd41a8b | ||
|  | 9927af1095 | ||
|  | 7585b6ef6f | ||
|  | a6159702da | ||
|  | 0247fb0d84 | ||
|  | 6de760885f | ||
|  | 9851d14fb9 | ||
|  | d5fc69d3e4 | ||
|  | a40d120f2a | ||
|  | fcdd9414d9 | ||
|  | 272a1001a8 | ||
|  | 2a52241f1c | ||
|  | d8f1822c12 | ||
|  | ce7d094adb | ||
|  | a0cf1889a3 | ||
|  | 38ef394e15 | ||
|  | abbf7c7cb0 | ||
|  | ca5f7ae32f | ||
|  | cbc4b75e50 | ||
|  | 65ddcf91d0 | ||
|  | 5280e1b449 | ||
|  | b6ffb81909 | ||
|  | e9edffa9f0 | ||
|  | 0dd1c17ff4 | ||
|  | aef211e5f3 | ||
|  | 66829203d8 | ||
|  | 7a0eaf3148 | ||
|  | fa5479ee5f | ||
|  | 03412cacba | ||
|  | 01a38a0b11 | ||
|  | f43c14bd78 | ||
|  | fb23452383 | ||
|  | ab7dde1450 | ||
|  | 8d9bc2f5ff | ||
|  | 7651ccc84e | ||
|  | 1a6b95b388 | ||
|  | 78ec1e7512 | ||
|  | 7e38d26c33 | ||
|  | ed09dd4e9e | ||
|  | 5731b79554 | ||
|  | eaa22a9d13 | ||
|  | b2bdfe8482 | ||
|  | fea531be9a | ||
|  | 7c69d38588 | ||
|  | a088ee56b0 | ||
|  | ae669af904 | ||
|  | d1ddf05e38 | ||
|  | 51279a98b3 | ||
|  | bf33a4f82d | ||
|  | fff0d741c3 | ||
|  | e83d0ee820 | ||
|  | 09f3eecf56 | ||
|  | 2bd4326ff6 | ||
|  | c13168b60c | ||
|  | ea3871d0c4 | ||
|  | 70a2b11271 | ||
|  | 3cf39e072e | ||
|  | 413b86cc4a | ||
|  | a6107fcfdf | ||
|  | a064ade1e0 | ||
|  | df35aa7942 | ||
|  | cd49c5f88d | ||
|  | 1541ad2160 | ||
|  | c78b7b1a24 | ||
|  | 9c7a645e18 | ||
|  | 4acf38031a | ||
|  | 4cd7271e30 | ||
|  | 3f630ab1b0 | ||
|  | 04cb684fd4 | ||
|  | 4c843571ea | ||
|  | 1326498802 | ||
|  | b7ebd8c4a6 | ||
|  | 24e0a69480 | ||
|  | 4bcb2bdede | ||
|  | d27f3eb8a4 | ||
|  | d3e4481112 | ||
|  | 1d1d6b3d98 | ||
|  | 90b8a22a71 | ||
|  | 8dbfb8ab76 | ||
|  | e29ff1c848 | ||
|  | 585f6ffc9b | ||
|  | 46b94c17d6 | ||
|  | 7af8007447 | ||
|  | 16a2b2f566 | ||
|  | ea2a90c3c5 | ||
|  | 5cda750e5e | ||
|  | 4e143d45c8 | ||
|  | 4c50980d81 | ||
|  | 2954f5f04d | ||
|  | cac4c1eb1e | ||
|  | 0b1f30d98c | ||
|  | c7b1e8d772 | ||
|  | a4f7512d44 | ||
|  | 0d3ad80659 | ||
|  | aba1a73e28 | ||
|  | dca31b2ca3 | ||
|  | 0cb378ca31 | ||
|  | cf551d2cc7 | ||
|  | ac0c8a68f6 | ||
|  | 5986dcdd2f | ||
|  | 6be6eb2227 | ||
|  | d34015eec5 | ||
|  | 255c5bfaca | ||
|  | 01c6754928 | ||
|  | 8eaf884f69 | ||
|  | 699a2bb7ab | ||
|  | 4a2dcd20d1 | ||
|  | 4e98fb75d6 | ||
|  | 64e66e732f | ||
|  | 7aec627f6b | ||
|  | 59a2fec176 | ||
|  | edc5a5a94f | ||
|  | c5b7edad82 | ||
|  | 124ffac4e4 | ||
|  | 6d2a36fb2b | ||
|  | 28b43b3e1d | ||
|  | f7feaf158d | ||
|  | 2396f707c6 | ||
|  | d4d8e1b1ba | ||
|  | 44fec2c729 | ||
|  | a80a6913e3 | ||
|  | 0eac04c220 | ||
|  | 29dd758302 | ||
|  | 5c45adc7f0 | ||
|  | ad22cf08cd | ||
|  | 2c2ae64194 | ||
|  | 97c2dadd16 | ||
|  | b36e1e3baf | ||
|  | 2da35fec17 | ||
|  | bdeac74cfc | 
							
								
								
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | ||||||
| #patreon: # Replace with a single Patreon username | #patreon: # Replace with a single Patreon username | ||||||
| open_collective: # Replace with a single Open Collective username | #open_collective: # Replace with a single Open Collective username | ||||||
| ko_fi: irmen | ko_fi: irmen | ||||||
| #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | ||||||
| #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||||||
| @@ -11,3 +11,4 @@ ko_fi: irmen | |||||||
| #otechie: # Replace with a single Otechie username | #otechie: # Replace with a single Otechie username | ||||||
| #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry | ||||||
| #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] | ||||||
|  | custom: ['https://paypal.me/irmendejong'] | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,17 +1,23 @@ | |||||||
| .idea/workspace.xml | .idea/workspace.xml | ||||||
| .idea/discord.xml | .idea/discord.xml | ||||||
|  | .idea/developer-tools.xml | ||||||
|  | .idea/usage.statistics.xml | ||||||
|  | .idea/shelf/ | ||||||
| build/ | build/ | ||||||
| dist/ | dist/ | ||||||
| output/ | output/ | ||||||
|  | out/ | ||||||
|  | out-new/ | ||||||
|  | out-old/ | ||||||
| .*cache/ | .*cache/ | ||||||
| *.directory | *.directory | ||||||
| *.prg | *.prg | ||||||
| *.bin | *.bin | ||||||
|  | *.p8ir | ||||||
| *.labels.txt | *.labels.txt | ||||||
| *.vm.txt | *.vm.txt | ||||||
| *.vice-mon-list | *.vice-mon-list | ||||||
| docs/build | docs/build | ||||||
| out/ |  | ||||||
| parser/**/*.interp | parser/**/*.interp | ||||||
| parser/**/*.tokens | parser/**/*.tokens | ||||||
| parser/**/*.java | parser/**/*.java | ||||||
| @@ -22,6 +28,7 @@ compiler/src/prog8/buildversion/* | |||||||
| .eggs/ | .eggs/ | ||||||
| /MANIFEST | /MANIFEST | ||||||
| .tox/ | .tox/ | ||||||
|  | .kotlin/ | ||||||
| __pycache__/ | __pycache__/ | ||||||
| parser.out | parser.out | ||||||
| parsetab.py | parsetab.py | ||||||
| @@ -30,7 +37,6 @@ parsetab.py | |||||||
| compiler/lib/ | compiler/lib/ | ||||||
|  |  | ||||||
| .gradle | .gradle | ||||||
| /prog8compiler.jar |  | ||||||
| sd*.img | sd*.img | ||||||
| *.d64 | *.d64 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1582
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1582
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										12
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,9 +1,19 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <project version="4"> | <project version="4"> | ||||||
|  |   <component name="Kotlin2JsCompilerArguments"> | ||||||
|  |     <option name="moduleKind" value="plain" /> | ||||||
|  |   </component> | ||||||
|   <component name="Kotlin2JvmCompilerArguments"> |   <component name="Kotlin2JvmCompilerArguments"> | ||||||
|     <option name="jvmTarget" value="11" /> |     <option name="jvmTarget" value="11" /> | ||||||
|   </component> |   </component> | ||||||
|  |   <component name="KotlinCommonCompilerArguments"> | ||||||
|  |     <option name="apiVersion" value="2.2" /> | ||||||
|  |     <option name="languageVersion" value="2.2" /> | ||||||
|  |   </component> | ||||||
|  |   <component name="KotlinCompilerSettings"> | ||||||
|  |     <option name="additionalArguments" value="-Xwhen-guards -jvm-default=no-compatibility" /> | ||||||
|  |   </component> | ||||||
|   <component name="KotlinJpsPluginSettings"> |   <component name="KotlinJpsPluginSettings"> | ||||||
|     <option name="version" value="1.9.24" /> |     <option name="version" value="2.2.0" /> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
							
								
								
									
										20
									
								
								.idea/libraries/KotlinJavaRuntime.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										20
									
								
								.idea/libraries/KotlinJavaRuntime.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,23 +1,23 @@ | |||||||
| <component name="libraryTable"> | <component name="libraryTable"> | ||||||
|   <library name="KotlinJavaRuntime" type="repository"> |   <library name="KotlinJavaRuntime" type="repository"> | ||||||
|     <properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" /> |     <properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.20" /> | ||||||
|     <CLASSES> |     <CLASSES> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20.jar!/" /> | ||||||
|     </CLASSES> |     </CLASSES> | ||||||
|     <JAVADOC> |     <JAVADOC> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-javadoc.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-javadoc.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-javadoc.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20-javadoc.jar!/" /> | ||||||
|     </JAVADOC> |     </JAVADOC> | ||||||
|     <SOURCES> |     <SOURCES> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.2.20/kotlin-stdlib-jdk8-2.2.20-sources.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-sources.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.20/kotlin-stdlib-2.2.20-sources.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.2.20/kotlin-stdlib-jdk7-2.2.20-sources.jar!/" /> | ||||||
|     </SOURCES> |     </SOURCES> | ||||||
|   </library> |   </library> | ||||||
| </component> | </component> | ||||||
							
								
								
									
										13
									
								
								.idea/libraries/eclipse_lsp4j.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.idea/libraries/eclipse_lsp4j.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="eclipse.lsp4j" type="repository"> | ||||||
|  |     <properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" /> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
| @@ -1,18 +1,21 @@ | |||||||
| <component name="libraryTable"> | <component name="libraryTable"> | ||||||
|   <library name="io.kotest.assertions.core.jvm" type="repository"> |   <library name="io.kotest.framework.datatest" type="repository"> | ||||||
|     <properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.1" /> |     <properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" /> | ||||||
|     <CLASSES> |     <CLASSES> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" /> |  | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" /> |  | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" /> |  | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" /> |  | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" /> |  | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" /> |  | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" /> | ||||||
|     </CLASSES> |     </CLASSES> | ||||||
|     <JAVADOC /> |     <JAVADOC /> | ||||||
|     <SOURCES /> |     <SOURCES /> | ||||||
							
								
								
									
										18
									
								
								.idea/libraries/michael_bull_kotlin_result_jvm.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								.idea/libraries/michael_bull_kotlin_result_jvm.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,20 @@ | |||||||
| <component name="libraryTable"> | <component name="libraryTable"> | ||||||
|   <library name="michael.bull.kotlin.result.jvm" type="repository"> |   <library name="michael.bull.kotlin.result.jvm" type="repository"> | ||||||
|     <properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0" /> |     <properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0" /> | ||||||
|     <CLASSES> |     <CLASSES> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0.jar!/" /> | ||||||
|       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" /> |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" /> | ||||||
|     </CLASSES> |     </CLASSES> | ||||||
|     <JAVADOC /> |     <JAVADOC> | ||||||
|     <SOURCES /> |       <root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-javadoc.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-javadoc.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" /> | ||||||
|  |     </JAVADOC> | ||||||
|  |     <SOURCES> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.1.0/kotlin-result-jvm-2.1.0-sources.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.2.0/kotlin-stdlib-2.2.0-sources.jar!/" /> | ||||||
|  |       <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" /> | ||||||
|  |     </SOURCES> | ||||||
|   </library> |   </library> | ||||||
| </component> | </component> | ||||||
							
								
								
									
										10
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @@ -4,14 +4,15 @@ | |||||||
|     <option name="perGrammarGenerationSettings"> |     <option name="perGrammarGenerationSettings"> | ||||||
|       <list> |       <list> | ||||||
|         <PerGrammarGenerationSettings> |         <PerGrammarGenerationSettings> | ||||||
|           <option name="fileName" value="$PROJECT_DIR$/parser/antlr/Prog8ANTLR.g4" /> |           <option name="fileName" value="$PROJECT_DIR$/parser/src/main/antlr/Prog8ANTLR.g4" /> | ||||||
|           <option name="autoGen" value="true" /> |           <option name="autoGen" value="true" /> | ||||||
|           <option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" /> |           <option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" /> | ||||||
|           <option name="libDir" value="" /> |           <option name="libDir" value="" /> | ||||||
|           <option name="encoding" value="" /> |           <option name="encoding" value="" /> | ||||||
|           <option name="pkg" value="" /> |           <option name="pkg" value="" /> | ||||||
|           <option name="language" value="" /> |           <option name="language" value="Java" /> | ||||||
|           <option name="generateListener" value="false" /> |           <option name="generateListener" value="false" /> | ||||||
|  |           <option name="generateVisitor" value="true" /> | ||||||
|         </PerGrammarGenerationSettings> |         </PerGrammarGenerationSettings> | ||||||
|       </list> |       </list> | ||||||
|     </option> |     </option> | ||||||
| @@ -22,7 +23,10 @@ | |||||||
|   <component name="FrameworkDetectionExcludesConfiguration"> |   <component name="FrameworkDetectionExcludesConfiguration"> | ||||||
|     <type id="Python" /> |     <type id="Python" /> | ||||||
|   </component> |   </component> | ||||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK"> |   <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK"> | ||||||
|     <output url="file://$PROJECT_DIR$/out" /> |     <output url="file://$PROJECT_DIR$/out" /> | ||||||
|   </component> |   </component> | ||||||
|  |   <component name="PythonCompatibilityInspectionAdvertiser"> | ||||||
|  |     <option name="version" value="3" /> | ||||||
|  |   </component> | ||||||
| </project> | </project> | ||||||
							
								
								
									
										4
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							| @@ -2,6 +2,8 @@ | |||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="ProjectModuleManager"> |   <component name="ProjectModuleManager"> | ||||||
|     <modules> |     <modules> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/beanshell/beanshell.iml" filepath="$PROJECT_DIR$/beanshell/beanshell.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" /> |       <module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" /> |       <module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" /> |       <module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" /> | ||||||
| @@ -14,6 +16,8 @@ | |||||||
|       <module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" /> |       <module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" /> |       <module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" /> |       <module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" /> |       <module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" /> | ||||||
|     </modules> |     </modules> | ||||||
|   </component> |   </component> | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							| @@ -4,9 +4,9 @@ | |||||||
|  |  | ||||||
| all: | all: | ||||||
| 	gradle installdist installshadowdist | 	gradle installdist installshadowdist | ||||||
| 	@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile" | 	@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c" | ||||||
|  |  | ||||||
| test: | test: | ||||||
| 	gradle build | 	gradle build | ||||||
| 	@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/p8compile" | 	@echo "compiler launch script can be found here: compiler/build/install/compiler-shadow/bin/prog8c" | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,4 +1,7 @@ | |||||||
| [](https://ko-fi.com/H2H6S0FFF) | [](https://ko-fi.com/H2H6S0FFF) | ||||||
|  |  | ||||||
|  | PayPal: [](https://paypal.me/irmendejong) | ||||||
|  |  | ||||||
| [](https://prog8.readthedocs.io/) | [](https://prog8.readthedocs.io/) | ||||||
|  |  | ||||||
| Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors | Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors | ||||||
| @@ -14,7 +17,7 @@ which aims to provide many conveniences over raw assembly code (even when using | |||||||
|  |  | ||||||
| This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can. | This project was created over the last couple of years by dedicating thousands of hours of my free time to it, to make it the best I possibly can. | ||||||
| If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,  | If you like Prog8, and think it's worth a nice cup of hot coffee or a delicious pizza,  | ||||||
| you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen).  | you can help me out a little bit over at [ko-fi.com/irmen](https://ko-fi.com/irmen) or [PayPal](https://paypal.me/irmendejong) | ||||||
|  |  | ||||||
|  |  | ||||||
| Documentation | Documentation | ||||||
| @@ -29,6 +32,8 @@ How to get it/build it | |||||||
| - Or, if you want/need a bleeding edge development version, you can: | - Or, if you want/need a bleeding edge development version, you can: | ||||||
|   - download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions). |   - download a build artifact zipfile from a recent [github action build](https://github.com/irmen/prog8/actions). | ||||||
|   - you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html). |   - you can also compile it yourself from source. [Instructions here](https://prog8.readthedocs.io/en/latest/compiling.html). | ||||||
|  |     Note that if you are not using *gradle* to build it, you might have to perform some manual | ||||||
|  |     tasks once to make it compile fully. These are explained in the linked instructions.  | ||||||
| - Alternatively, you can also install the compiler as a package on some linux distros: | - Alternatively, you can also install the compiler as a package on some linux distros: | ||||||
|   - Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8) |   - Arch (via AUR): [`prog8`](https://aur.archlinux.org/packages/prog8) | ||||||
|  |  | ||||||
| @@ -52,27 +57,32 @@ What does Prog8 provide? | |||||||
| ------------------------ | ------------------------ | ||||||
|  |  | ||||||
| - all advantages of a higher level language over having to write assembly code manually | - all advantages of a higher level language over having to write assembly code manually | ||||||
| - programs run very fast because compilation to native machine code | - programs run very fast because it's compiled to native machine code | ||||||
| - code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS | - code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS | ||||||
| - modularity, symbol scoping, subroutines | - modularity, symbol scoping, subroutines. No need for forward declarations. | ||||||
| - various data types other than just bytes (16-bit words, floats, strings) | - various data types other than just bytes (16-bit words, floats, strings) | ||||||
| - floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do) | - Structs and typed pointers | ||||||
|  | - floating point math is supported on certain targets | ||||||
|  | - access to most Kernal ROM routines as external subroutine definitions you can call normally | ||||||
|  | - tight control over Zeropage usage | ||||||
|  | - programs can be run multiple times without reloading because of automatic variable (re)initializations. | ||||||
|  | - programs can be configured to execute in ROM | ||||||
| - strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents. | - strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents. | ||||||
| - automatic static variable allocations, automatic string and array variables and string sharing | - automatic static variable allocations, automatic string and array variables and string sharing | ||||||
| - subroutines with input parameters and result values |  | ||||||
| - high-level program optimizations | - high-level program optimizations | ||||||
| - no need for forward declarations | - conditional branches that map 1:1 to cpu status flags | ||||||
| - small program boilerplate/compilersupport overhead |  | ||||||
| - programs can be run multiple times without reloading because of automatic variable (re)initializations. |  | ||||||
| - conditional branches |  | ||||||
| - ``when`` statement to provide a concise jump table alternative to if/elseif chains | - ``when`` statement to provide a concise jump table alternative to if/elseif chains | ||||||
|  | - ``on .. goto`` statement for fast jump tables | ||||||
| - ``in`` expression for concise and efficient multi-value/containment check  | - ``in`` expression for concise and efficient multi-value/containment check  | ||||||
|  | - ``defer`` statement to help write concise and robust subroutine cleanup logic | ||||||
| - several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror`` | - several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror`` | ||||||
| - various powerful built-in libraries to do I/O, number conversions, graphics and more   | - various powerful built-in libraries to do I/O, number conversions, graphics and more   | ||||||
| - convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses | - subroutines can return more than one result value | ||||||
| - inline assembly allows you to have full control when every cycle or byte matters | - inline assembly allows you to have full control when every cycle or byte matters | ||||||
| - supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64. | - supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets) | ||||||
| - encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16) | - encode strings and characters into petscii or screencodes or even other encodings | ||||||
|  | - Automatic ROM/RAM bank switching on certain compiler targets when calling routines in other banks | ||||||
|  | - 50 Kb of available program RAM size on the C64 by default; because Basic ROM is banked out altogether  | ||||||
|  |  | ||||||
| *Rapid edit-compile-run-debug cycle:* | *Rapid edit-compile-run-debug cycle:* | ||||||
|  |  | ||||||
| @@ -83,11 +93,11 @@ What does Prog8 provide? | |||||||
|  |  | ||||||
| *Multiple supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): | *Multiple supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): | ||||||
|  |  | ||||||
|  | - "cx16": [CommanderX16](https://www.commanderx16.com)  (65c02 CPU) | ||||||
| - "c64": Commodore-64  (6502 like CPU) | - "c64": Commodore-64  (6502 like CPU) | ||||||
| - "c128": Commodore-128  (6502 like CPU - the Z80 cpu mode is not supported) | - "c128": Commodore-128  (6502 like CPU - the Z80 cpu mode is not supported) | ||||||
| - "cx16": [CommanderX16](https://www.commanderx16.com)  (65c02 CPU) | - "pet32": Commodore PET (limited support) | ||||||
| - "pet32": Commodore PET (experimental) | - via external configurable targets: Atari 800 XL, Neo6502, NES, C64 OS, Foenix F256, ... | ||||||
| - "atari": Atari 8 bit such as 800XL (experimental) |  | ||||||
| - If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag) | - If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								beanshell/beanshell.iml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								beanshell/beanshell.iml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <module type="JAVA_MODULE" version="4"> | ||||||
|  |   <component name="NewModuleRootManager" inherit-compiler-output="true"> | ||||||
|  |     <exclude-output /> | ||||||
|  |     <content url="file://$MODULE_DIR$"> | ||||||
|  |       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/build" /> | ||||||
|  |     </content> | ||||||
|  |     <orderEntry type="inheritedJdk" /> | ||||||
|  |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|  |     <orderEntry type="library" name="KotlinJavaRuntime" level="project" /> | ||||||
|  |     <orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" /> | ||||||
|  |     <orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" /> | ||||||
|  |     <orderEntry type="module-library"> | ||||||
|  |       <library> | ||||||
|  |         <CLASSES> | ||||||
|  |           <root url="jar://$MODULE_DIR$/lib/bsh-3.0.0-SNAPSHOT.jar!/" /> | ||||||
|  |         </CLASSES> | ||||||
|  |         <JAVADOC /> | ||||||
|  |         <SOURCES /> | ||||||
|  |       </library> | ||||||
|  |     </orderEntry> | ||||||
|  |   </component> | ||||||
|  | </module> | ||||||
							
								
								
									
										65
									
								
								beanshell/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								beanshell/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | import org.jetbrains.kotlin.gradle.dsl.JvmTarget | ||||||
|  |  | ||||||
|  | plugins { | ||||||
|  |     id("application") | ||||||
|  |     kotlin("jvm") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | val serverMainClassName = "prog8lsp.MainKt" | ||||||
|  | val applicationName = "prog8-beanshell" | ||||||
|  |  | ||||||
|  | application { | ||||||
|  |     mainClass.set(serverMainClassName) | ||||||
|  |     description = "Code completions, diagnostics and more for Prog8" | ||||||
|  |     // applicationDefaultJvmArgs = listOf("-DkotlinLanguageServer.version=$version") | ||||||
|  |     applicationDistribution.into("bin") { | ||||||
|  |         filePermissions { | ||||||
|  |             user { | ||||||
|  |                 read=true | ||||||
|  |                 execute=true | ||||||
|  |                 write=true | ||||||
|  |             } | ||||||
|  |             other.execute = true | ||||||
|  |             group.execute = true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | repositories { | ||||||
|  |     mavenCentral() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     implementation(files("lib/bsh-3.0.0-SNAPSHOT.jar")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | configurations.forEach { config -> | ||||||
|  |     config.resolutionStrategy { | ||||||
|  |         preferProjectModules() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sourceSets.main { | ||||||
|  |     java.srcDir("src") | ||||||
|  |     resources.srcDir("resources") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.startScripts { | ||||||
|  |     applicationName = "prog8-beanshell" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.register<Exec>("fixFilePermissions") { | ||||||
|  |     // When running on macOS or Linux the start script | ||||||
|  |     // needs executable permissions to run. | ||||||
|  |  | ||||||
|  |     onlyIf { !System.getProperty("os.name").lowercase().contains("windows") } | ||||||
|  |     commandLine("chmod", "+x", "${tasks.installDist.get().destinationDir}/bin/prog8-beanshell") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.installDist { | ||||||
|  |     finalizedBy("fixFilePermissions") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.build { | ||||||
|  |     finalizedBy("installDist") | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								beanshell/lib/bsh-3.0.0-SNAPSHOT.jar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								beanshell/lib/bsh-3.0.0-SNAPSHOT.jar
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										48
									
								
								beanshell/src/prog8beanshell/CommandLineReader.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								beanshell/src/prog8beanshell/CommandLineReader.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | package prog8beanshell | ||||||
|  |  | ||||||
|  | import java.io.FilterReader | ||||||
|  | import java.io.Reader | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class CommandLineReader(val input: Reader): FilterReader(input)  { | ||||||
|  |     private val normal = 0 | ||||||
|  |     private val lastCharNL = 1 | ||||||
|  |     private val sentSemi = 2 | ||||||
|  |     private var state = lastCharNL | ||||||
|  |  | ||||||
|  |     override fun read(): Int { | ||||||
|  |         if (state == sentSemi) { | ||||||
|  |             this.state = lastCharNL | ||||||
|  |             return 10 | ||||||
|  |         } else { | ||||||
|  |             var b = input.read() | ||||||
|  |             while(b==13) b = input.read() | ||||||
|  |  | ||||||
|  |             if (b == 10) { | ||||||
|  |                 if (this.state == lastCharNL) { | ||||||
|  |                     b = 59 | ||||||
|  |                     this.state = sentSemi | ||||||
|  |                 } else { | ||||||
|  |                     this.state = lastCharNL | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 this.state = normal | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return b | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun read(buff: CharArray, off: Int, len: Int): Int { | ||||||
|  |         val b = read() | ||||||
|  |         if (b == -1) { | ||||||
|  |             return -1 | ||||||
|  |         } else { | ||||||
|  |             buff[off] = b.toChar() | ||||||
|  |             return 1 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								beanshell/src/prog8beanshell/Main.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								beanshell/src/prog8beanshell/Main.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | package prog8beanshell | ||||||
|  |  | ||||||
|  | import bsh.FileReader | ||||||
|  | import bsh.Interpreter | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BeanshellInterpreter { | ||||||
|  |  | ||||||
|  |     fun run(symbols: Map<String, Any>) { | ||||||
|  |         val interpreter = Interpreter(CommandLineReader(FileReader(System.`in`)), System.out, System.err, true) | ||||||
|  |         interpreter.setExitOnEOF(false) | ||||||
|  |         symbols.forEach { (name, value) -> interpreter.set(name, value) } | ||||||
|  |         interpreter.run() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun main(args: Array<String>) { | ||||||
|  |     val i = BeanshellInterpreter() | ||||||
|  |     i.run(mapOf( | ||||||
|  |         "env" to System.getenv(), | ||||||
|  |         "args" to args | ||||||
|  |     )) | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								benchmark-program/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								benchmark-program/Makefile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | .PHONY:  clean run | ||||||
|  |  | ||||||
|  | run: | ||||||
|  | 	prog8c -target cx16 benchmark.p8 | ||||||
|  | 	x16emu -run -prg benchmark.prg -warp | ||||||
|  |  | ||||||
|  | clean: | ||||||
|  | 	rm -f *.prg *.PRG *.asm *.vice-* *.BIN *.PAL *.zip *.7z | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										109
									
								
								benchmark-program/b_3d.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								benchmark-program/b_3d.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | |||||||
|  | %import textio | ||||||
|  | %import math | ||||||
|  |  | ||||||
|  | rotate3d { | ||||||
|  |     const ubyte WIDTH = 40 | ||||||
|  |     const ubyte HEIGHT = 30 | ||||||
|  |  | ||||||
|  |     sub benchmark(uword max_time) -> uword { | ||||||
|  |  | ||||||
|  |         uword anglex | ||||||
|  |         uword angley | ||||||
|  |         uword anglez | ||||||
|  |         uword frames | ||||||
|  |  | ||||||
|  |         txt.nl() | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |  | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  |             matrix_math.rotate_vertices(msb(anglex), msb(angley), msb(anglez)) | ||||||
|  |             draw_edges()   ; doesn't really draw anything in the benchmark, but does do the screen calculations | ||||||
|  |             anglex+=500 | ||||||
|  |             angley+=215 | ||||||
|  |             anglez+=453 | ||||||
|  |             frames++ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return frames | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub draw_edges() { | ||||||
|  |  | ||||||
|  |         ; plot the points of the 3d cube | ||||||
|  |         ; first the points on the back, then the points on the front (painter algorithm) | ||||||
|  |  | ||||||
|  |         ubyte @zp i | ||||||
|  |         word @zp rz | ||||||
|  |         word @zp persp | ||||||
|  |         byte @shared sx | ||||||
|  |         byte @shared sy | ||||||
|  |  | ||||||
|  |         for i in 0 to len(matrix_math.xcoor)-1 { | ||||||
|  |             rz = matrix_math.rotatedz[i] | ||||||
|  |             if rz >= 10 { | ||||||
|  |                 persp = 600 + rz/64 | ||||||
|  |                 sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2 | ||||||
|  |                 sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2 | ||||||
|  |                 ;; txt.setcc(sx as ubyte, sy as ubyte, 46, 7) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for i in 0 to len(matrix_math.xcoor)-1 { | ||||||
|  |             rz = matrix_math.rotatedz[i] | ||||||
|  |             if rz < 10 { | ||||||
|  |                 persp = 600 + rz/64 | ||||||
|  |                 sx = matrix_math.rotatedx[i] / persp as byte + WIDTH/2 | ||||||
|  |                 sy = matrix_math.rotatedy[i] / persp as byte + HEIGHT/2 | ||||||
|  |                 ;; txt.setcc(sx as ubyte, sy as ubyte, 81, 7) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         txt.chrout('.') | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | matrix_math { | ||||||
|  |     ; vertices | ||||||
|  |     word[] xcoor = [ -40, -40, -40, -40,  40,  40,  40, 40 ] | ||||||
|  |     word[] ycoor = [ -40, -40,  40,  40, -40, -40,  40, 40 ] | ||||||
|  |     word[] zcoor = [ -40,  40, -40,  40, -40,  40, -40, 40 ] | ||||||
|  |  | ||||||
|  |     ; storage for rotated coordinates | ||||||
|  |     word[len(xcoor)] rotatedx | ||||||
|  |     word[len(ycoor)] rotatedy | ||||||
|  |     word[len(zcoor)] rotatedz | ||||||
|  |  | ||||||
|  |     sub rotate_vertices(ubyte ax, ubyte ay, ubyte az) { | ||||||
|  |         ; rotate around origin (0,0,0) | ||||||
|  |  | ||||||
|  |         ; set up the 3d rotation matrix values | ||||||
|  |         word wcosa = math.cos8(ax) | ||||||
|  |         word wsina = math.sin8(ax) | ||||||
|  |         word wcosb = math.cos8(ay) | ||||||
|  |         word wsinb = math.sin8(ay) | ||||||
|  |         word wcosc = math.cos8(az) | ||||||
|  |         word wsinc = math.sin8(az) | ||||||
|  |  | ||||||
|  |         word wcosa_sinb = wcosa*wsinb / 128 | ||||||
|  |         word wsina_sinb = wsina*wsinb / 128 | ||||||
|  |  | ||||||
|  |         word Axx = wcosa*wcosb / 128 | ||||||
|  |         word Axy = (wcosa_sinb*wsinc - wsina*wcosc) / 128 | ||||||
|  |         word Axz = (wcosa_sinb*wcosc + wsina*wsinc) / 128 | ||||||
|  |         word Ayx = wsina*wcosb / 128 | ||||||
|  |         word Ayy = (wsina_sinb*wsinc + wcosa*wcosc) / 128 | ||||||
|  |         word Ayz = (wsina_sinb*wcosc - wcosa*wsinc) / 128 | ||||||
|  |         word Azx = -wsinb | ||||||
|  |         word Azy = wcosb*wsinc / 128 | ||||||
|  |         word Azz = wcosb*wcosc / 128 | ||||||
|  |  | ||||||
|  |         ubyte @zp i | ||||||
|  |         for i in 0 to len(xcoor)-1 { | ||||||
|  |             ; don't normalize by dividing by 128, instead keep some precision for perspective calc later | ||||||
|  |             rotatedx[i] = Axx*xcoor[i] + Axy*ycoor[i] + Axz*zcoor[i] | ||||||
|  |             rotatedy[i] = Ayx*xcoor[i] + Ayy*ycoor[i] + Ayz*zcoor[i] | ||||||
|  |             rotatedz[i] = Azx*xcoor[i] + Azy*ycoor[i] + Azz*zcoor[i] | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								benchmark-program/b_adpcm.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								benchmark-program/b_adpcm.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | adpcm { | ||||||
|  |  | ||||||
|  |     sub decode_benchmark(uword max_time) -> uword { | ||||||
|  |         uword num_blocks | ||||||
|  |         txt.nl() | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |  | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  |             adpcm.init(0,0) | ||||||
|  |             uword @requirezp nibbles_ptr = $a000 ; for benchmark purposes, the exact nibbles don't really matter, so we just take the basic ROM as input | ||||||
|  |             repeat 252/2 { | ||||||
|  |                 unroll 2 { | ||||||
|  |                     ubyte @zp nibble = @(nibbles_ptr) | ||||||
|  |                     adpcm.decode_nibble(nibble & 15)     ; first word  (note: upper nibble needs to be zero!) | ||||||
|  |                     adpcm.decode_nibble(nibble>>4)       ; second word  (note: upper nibble is zero, after the shifts.) | ||||||
|  |                     nibbles_ptr++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             num_blocks++ | ||||||
|  |             txt.chrout('.') | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return num_blocks | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ; IMA ADPCM decoder.  Supports mono and stereo streams. | ||||||
|  |  | ||||||
|  |     byte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8] | ||||||
|  |     uword[] t_step = [ | ||||||
|  |             7, 8, 9, 10, 11, 12, 13, 14, | ||||||
|  |             16, 17, 19, 21, 23, 25, 28, 31, | ||||||
|  |             34, 37, 41, 45, 50, 55, 60, 66, | ||||||
|  |             73, 80, 88, 97, 107, 118, 130, 143, | ||||||
|  |             157, 173, 190, 209, 230, 253, 279, 307, | ||||||
|  |             337, 371, 408, 449, 494, 544, 598, 658, | ||||||
|  |             724, 796, 876, 963, 1060, 1166, 1282, 1411, | ||||||
|  |             1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, | ||||||
|  |             3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, | ||||||
|  |             7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, | ||||||
|  |             15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, | ||||||
|  |             32767] | ||||||
|  |  | ||||||
|  |     uword @requirezp predict       ; decoded 16 bit pcm sample for first channel. | ||||||
|  |     ubyte @requirezp index | ||||||
|  |     uword @requirezp pstep | ||||||
|  |  | ||||||
|  |     sub init(uword startPredict, ubyte startIndex) { | ||||||
|  |         ; initialize first decoding channel. | ||||||
|  |         predict = startPredict | ||||||
|  |         index = startIndex | ||||||
|  |         pstep = t_step[index] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub decode_nibble(ubyte @zp nibble) { | ||||||
|  |         ; Decoder for a single nibble for the first channel. (value of 'nibble' needs to be strictly 0-15 !) | ||||||
|  |         ; This is the hotspot of the decoder algorithm! | ||||||
|  |         ; Note that the generated assembly from this is pretty efficient, | ||||||
|  |         ; rewriting it by hand in asm seems to improve it only ~10%. | ||||||
|  |         cx16.r0s = 0                ; difference | ||||||
|  |         if nibble & %0100 !=0 | ||||||
|  |             cx16.r0s += pstep | ||||||
|  |         pstep >>= 1 | ||||||
|  |         if nibble & %0010 !=0 | ||||||
|  |             cx16.r0s += pstep | ||||||
|  |         pstep >>= 1 | ||||||
|  |         if nibble & %0001 !=0 | ||||||
|  |             cx16.r0s += pstep | ||||||
|  |         pstep >>= 1 | ||||||
|  |         cx16.r0s += pstep | ||||||
|  |         if nibble & %1000 !=0 | ||||||
|  |             predict -= cx16.r0 | ||||||
|  |         else | ||||||
|  |             predict += cx16.r0 | ||||||
|  |  | ||||||
|  |         ; NOTE: the original C/Python code uses a 32 bits prediction value and clips it to a 16 bit word | ||||||
|  |         ;       but for speed reasons we only work with 16 bit words here all the time (with possible clipping error) | ||||||
|  |         ; if predicted > 32767: | ||||||
|  |         ;    predicted = 32767 | ||||||
|  |         ; elif predicted < -32767: | ||||||
|  |         ;    predicted = - 32767 | ||||||
|  |  | ||||||
|  |         index += t_index[nibble] as ubyte | ||||||
|  |         if_neg | ||||||
|  |             index = 0 | ||||||
|  |         else if index >= len(t_step)-1 | ||||||
|  |             index = len(t_step)-1 | ||||||
|  |         pstep = t_step[index] | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										111
									
								
								benchmark-program/b_circles.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								benchmark-program/b_circles.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | %import gfx_lores | ||||||
|  | %import math | ||||||
|  |  | ||||||
|  | circles { | ||||||
|  |     const ubyte MAX_NUM_CIRCLES = 80 | ||||||
|  |     const ubyte GROWTH_RATE = 4 | ||||||
|  |     uword[MAX_NUM_CIRCLES] circle_x | ||||||
|  |     uword[MAX_NUM_CIRCLES] circle_y | ||||||
|  |     ubyte[MAX_NUM_CIRCLES] circle_radius | ||||||
|  |     ubyte color | ||||||
|  |     uword total_num_circles | ||||||
|  |  | ||||||
|  |     sub draw(bool use_kernal, uword max_time) -> uword { | ||||||
|  |         if use_kernal | ||||||
|  |             cx16.set_screen_mode(128) | ||||||
|  |         else | ||||||
|  |             gfx_lores.graphics_mode() | ||||||
|  |  | ||||||
|  |         math.rndseed(12345,6789) | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |  | ||||||
|  |         total_num_circles = 0 | ||||||
|  |         color = 16 | ||||||
|  |  | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  |             if use_kernal { | ||||||
|  |                 cx16.GRAPH_set_colors(0,0,0) | ||||||
|  |                 cx16.GRAPH_clear() | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |                 gfx_lores.clear_screen(0) | ||||||
|  |             total_num_circles += draw_circles(use_kernal, max_time) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if use_kernal | ||||||
|  |             cx16.set_screen_mode(3) | ||||||
|  |         else { | ||||||
|  |             gfx_lores.text_mode() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return total_num_circles | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub draw_circles(bool use_kernal, uword max_time) -> uword { | ||||||
|  |         uword @zp x | ||||||
|  |         uword @zp y | ||||||
|  |         ubyte @zp radius | ||||||
|  |  | ||||||
|  |         ubyte num_circles | ||||||
|  |  | ||||||
|  |         while num_circles<MAX_NUM_CIRCLES and cbm.RDTIM16()<max_time { | ||||||
|  |             x = math.rndw() % 320 | ||||||
|  |             y = math.rndw() % 240 | ||||||
|  |             radius = GROWTH_RATE | ||||||
|  |             if not_colliding() { | ||||||
|  |                 while not_edge() and not_colliding() { | ||||||
|  |                     radius += GROWTH_RATE | ||||||
|  |                 } | ||||||
|  |                 radius -= GROWTH_RATE | ||||||
|  |                 if radius>0 { | ||||||
|  |                     color++ | ||||||
|  |                     if color==0 | ||||||
|  |                         color=16 | ||||||
|  |                     if use_kernal { | ||||||
|  |                         cx16.GRAPH_set_colors(color, 255-color, 0) | ||||||
|  |                         cx16.GRAPH_draw_oval(x-radius, y-radius, radius*2, radius*2, true) | ||||||
|  |                     } | ||||||
|  |                     else | ||||||
|  |                         gfx_lores.disc(x, y as ubyte, radius, color) | ||||||
|  |                     circle_x[num_circles] = x | ||||||
|  |                     circle_y[num_circles] = y | ||||||
|  |                     circle_radius[num_circles] = radius | ||||||
|  |                     num_circles++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return num_circles | ||||||
|  |  | ||||||
|  |         sub not_colliding() -> bool { | ||||||
|  |             if num_circles==0 | ||||||
|  |                 return true | ||||||
|  |             ubyte @zp c | ||||||
|  |             for c in 0 to num_circles-1 { | ||||||
|  |                 if distance(c) < (radius as uword) + circle_radius[c] | ||||||
|  |                     return false | ||||||
|  |             } | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub distance(ubyte cix) -> uword { | ||||||
|  |             word dx = x as word - circle_x[cix] | ||||||
|  |             word dy = y as word - circle_y[cix] | ||||||
|  |             uword sqx = dx*dx as uword | ||||||
|  |             uword sqy = dy*dy as uword | ||||||
|  |             return sqrt(sqx + sqy) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub not_edge() -> bool { | ||||||
|  |             if x as word - radius < 0 | ||||||
|  |                 return false | ||||||
|  |             if x + radius >= 320 | ||||||
|  |                 return false | ||||||
|  |             if y as word - radius < 0 | ||||||
|  |                 return false | ||||||
|  |             if y + radius >= 240 | ||||||
|  |                 return false | ||||||
|  |             return true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								benchmark-program/b_life.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								benchmark-program/b_life.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | ; conway's game of life. | ||||||
|  |  | ||||||
|  | %import math | ||||||
|  | %import textio | ||||||
|  |  | ||||||
|  | life { | ||||||
|  |     const ubyte WIDTH = 40 | ||||||
|  |     const ubyte HEIGHT = 30 | ||||||
|  |     const uword STRIDE = $0002+WIDTH | ||||||
|  |     uword world1 = memory("world1", (WIDTH+2)*(HEIGHT+2), 0) | ||||||
|  |     uword world2 = memory("world2", (WIDTH+2)*(HEIGHT+2), 0) | ||||||
|  |     uword @requirezp active_world = world1 | ||||||
|  |  | ||||||
|  |    sub benchmark(uword max_time) -> uword { | ||||||
|  |         txt.clear_screen() | ||||||
|  |         sys.memset(world1, (WIDTH+2)*(HEIGHT+2), 0) | ||||||
|  |         sys.memset(world2, (WIDTH+2)*(HEIGHT+2), 0) | ||||||
|  |  | ||||||
|  |         set_start_gen() | ||||||
|  |  | ||||||
|  |         uword gen | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |  | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  |             next_gen() | ||||||
|  |             gen++ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return gen | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_start_gen() { | ||||||
|  |  | ||||||
|  | ; some way to set a custom start generation: | ||||||
|  | ;        str start_gen = "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "          **    " + | ||||||
|  | ;                        "        *    *  " + | ||||||
|  | ;                        "       *        " + | ||||||
|  | ;                        "       *     *  " + | ||||||
|  | ;                        "       ******   " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "                " + | ||||||
|  | ;                        "               " | ||||||
|  | ; | ||||||
|  | ;        for y in 0 to 15 { | ||||||
|  | ;            for x in 0 to 15 { | ||||||
|  | ;                if start_gen[y*16 + x]=='*' | ||||||
|  | ;                    active_world[offset + x] = 1 | ||||||
|  | ;            } | ||||||
|  | ;            offset += STRIDE | ||||||
|  | ;        } | ||||||
|  |  | ||||||
|  |         ; randomize whole world | ||||||
|  |         math.rndseed(12345,9999) | ||||||
|  |         uword offset = STRIDE+1 | ||||||
|  |         ubyte x | ||||||
|  |         ubyte y | ||||||
|  |         for y in 0 to HEIGHT-1 { | ||||||
|  |             for x in 0 to WIDTH-1 { | ||||||
|  |                 active_world[offset+x] = math.rnd() & 1 | ||||||
|  |             } | ||||||
|  |             offset += STRIDE | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub next_gen() { | ||||||
|  |         const ubyte DXOFFSET = 0 | ||||||
|  |         const ubyte DYOFFSET = 0 | ||||||
|  |         ubyte[2] cell_chars = [sc:' ', sc:'●'] | ||||||
|  |  | ||||||
|  |         uword @requirezp new_world = world1 | ||||||
|  |         if active_world == world1 | ||||||
|  |             new_world = world2 | ||||||
|  |  | ||||||
|  |         ; To avoid re-calculating word index lookups into the new- and active world arrays, | ||||||
|  |         ; we calculate the required pointer values upfront. | ||||||
|  |         ; Inside the loop we can use ptr+x just fine (results in efficient LDA (ptr),Y instruction because x is a byte type), | ||||||
|  |         ; and for each row we simply add the stride to the pointer. | ||||||
|  |         ; It's more readable to use active_world[offset] etc, but offset is a word value, and this produces | ||||||
|  |         ; inefficient assembly code because we can't use a register indexed mode in this case. Costly inside a loop. | ||||||
|  |  | ||||||
|  |         uword @requirezp new_world_ptr = new_world + STRIDE+1-DXOFFSET | ||||||
|  |         uword @requirezp active_world_ptr = active_world + STRIDE+1-DXOFFSET | ||||||
|  |  | ||||||
|  |         ubyte x | ||||||
|  |         ubyte y | ||||||
|  |         for y in DYOFFSET to HEIGHT+DYOFFSET-1 { | ||||||
|  |  | ||||||
|  |             cx16.vaddr_autoincr(1, $b000 + 256*y, 0, 2)     ;  allows us to use simple Vera data byte assigns later instead of setchr() calls | ||||||
|  |  | ||||||
|  |             for x in DXOFFSET to WIDTH+DXOFFSET-1 { | ||||||
|  |                 ; count the living neighbors | ||||||
|  |                 ubyte cell = @(active_world_ptr + x) | ||||||
|  |                 uword @requirezp ptr = active_world_ptr + x - STRIDE - 1 | ||||||
|  |                 ubyte neighbors = @(ptr) + @(ptr+1) + @(ptr+2) + | ||||||
|  |                                   @(ptr+STRIDE) + cell + @(ptr+STRIDE+2) + | ||||||
|  |                                   @(ptr+STRIDE*2) + @(ptr+STRIDE*2+1) + @(ptr+STRIDE*2+2) | ||||||
|  |  | ||||||
|  |                 ; apply game of life rules | ||||||
|  |                 if neighbors==3 | ||||||
|  |                     cell=1 | ||||||
|  |                 else if neighbors!=4 | ||||||
|  |                     cell=0 | ||||||
|  |                 @(new_world_ptr + x) = cell | ||||||
|  |  | ||||||
|  |                 ; draw new cell | ||||||
|  |                 ; txt.setchr(x,y,cell_chars[cell]) | ||||||
|  |                 cx16.VERA_DATA0 = cell_chars[cell] | ||||||
|  |             } | ||||||
|  |             active_world_ptr += STRIDE | ||||||
|  |             new_world_ptr += STRIDE | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         active_world = new_world | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								benchmark-program/b_mandelbrot.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								benchmark-program/b_mandelbrot.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | %import textio | ||||||
|  | %import floats | ||||||
|  |  | ||||||
|  | mandelbrot { | ||||||
|  |     const ubyte width = 39 | ||||||
|  |     const ubyte height = 29 | ||||||
|  |     const ubyte max_iter = 15 | ||||||
|  |  | ||||||
|  |     sub calc(uword max_time) -> uword  { | ||||||
|  |         uword num_pixels | ||||||
|  |         ubyte pixelx | ||||||
|  |         ubyte pixely | ||||||
|  |  | ||||||
|  |         txt.home() | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |  | ||||||
|  |         while cbm.RDTIM16() < max_time { | ||||||
|  |             for pixely in 0 to height-1 { | ||||||
|  |                 float yy = (pixely as float)/0.40/height - 1.3 | ||||||
|  |  | ||||||
|  |                 for pixelx in 0 to width-1 { | ||||||
|  |                     float xx = (pixelx as float)/0.32/width - 2.2 | ||||||
|  |  | ||||||
|  |                     float xsquared = 0.0 | ||||||
|  |                     float ysquared = 0.0 | ||||||
|  |                     float x = 0.0 | ||||||
|  |                     float y = 0.0 | ||||||
|  |                     ubyte iter = 0 | ||||||
|  |  | ||||||
|  |                     while iter<max_iter and xsquared+ysquared<4.0 { | ||||||
|  |                         y = x*y*2.0 + yy | ||||||
|  |                         x = xsquared - ysquared + xx | ||||||
|  |                         xsquared = x*x | ||||||
|  |                         ysquared = y*y | ||||||
|  |                         iter++ | ||||||
|  |                     } | ||||||
|  |                     txt.color2(1, max_iter-iter) | ||||||
|  |                     txt.spc() | ||||||
|  |                     num_pixels++ | ||||||
|  |  | ||||||
|  |                     if cbm.RDTIM16()>=max_time | ||||||
|  |                         goto finished | ||||||
|  |                 } | ||||||
|  |                 txt.nl() | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             txt.clear_screen() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | finished: | ||||||
|  |         txt.color2(1, 6) | ||||||
|  |         return num_pixels | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										343
									
								
								benchmark-program/b_maze.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										343
									
								
								benchmark-program/b_maze.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,343 @@ | |||||||
|  | %import textio | ||||||
|  | %import math | ||||||
|  |  | ||||||
|  | ; Even though prog8 only has support for extremely limited recursion, | ||||||
|  | ; you can write recursive algorithms with a bit of extra work by building your own explicit stack structure. | ||||||
|  | ; This program shows a depth-first maze generation algorithm (1 possible path from start to finish), | ||||||
|  | ; and a depth-first maze solver algorithm, both using a stack to store the path taken. | ||||||
|  |  | ||||||
|  | ; Note: this program can be compiled for multiple target systems. | ||||||
|  |  | ||||||
|  | maze { | ||||||
|  |     uword score | ||||||
|  |  | ||||||
|  |     sub bench(uword max_time) -> uword { | ||||||
|  |         txt.nl() | ||||||
|  |         score=0 | ||||||
|  |         math.rndseed(2345,44332) | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  |             maze.initialize() | ||||||
|  |             maze.drawStartFinish() | ||||||
|  |             if maze.generate(max_time) { | ||||||
|  |                 maze.openpassages() | ||||||
|  |                 maze.drawStartFinish() | ||||||
|  |                 if maze.solve(max_time) { | ||||||
|  |                     maze.drawStartFinish() | ||||||
|  |                 } else break | ||||||
|  |             } else break | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return score | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const uword screenwidth = 40 | ||||||
|  |     const uword screenheight = 30 | ||||||
|  |  | ||||||
|  |     const ubyte numCellsHoriz = (screenwidth-1) / 2 | ||||||
|  |     const ubyte numCellsVert = (screenheight-1) / 2 | ||||||
|  |  | ||||||
|  |     ; maze start and finish cells | ||||||
|  |     const ubyte startCx = 0 | ||||||
|  |     const ubyte startCy = 0 | ||||||
|  |     const ubyte finishCx = numCellsHoriz-1 | ||||||
|  |     const ubyte finishCy = numCellsVert-1 | ||||||
|  |  | ||||||
|  |     ; cell properties | ||||||
|  |     const ubyte STONE = 128 | ||||||
|  |     const ubyte WALKED = 64 | ||||||
|  |     const ubyte BACKTRACKED = 32 | ||||||
|  |     const ubyte UP = 1 | ||||||
|  |     const ubyte RIGHT = 2 | ||||||
|  |     const ubyte DOWN = 4 | ||||||
|  |     const ubyte LEFT = 8 | ||||||
|  |     const ubyte WALLCOLOR = 12 | ||||||
|  |     const ubyte EMPTYCOLOR = 0 | ||||||
|  |  | ||||||
|  |     ; unfortunately on larger screens (cx16), the number of cells exceeds 256 and doesn't fit in a regular array anymore. | ||||||
|  |     uword cells = memory("cells", numCellsHoriz*numCellsVert, 0) | ||||||
|  |  | ||||||
|  |     ubyte[256] cx_stack | ||||||
|  |     ubyte[256] cy_stack | ||||||
|  |     ubyte stackptr | ||||||
|  |  | ||||||
|  |     ubyte[4] directionflags = [LEFT,RIGHT,UP,DOWN] | ||||||
|  |  | ||||||
|  |     sub generate(uword max_time) -> bool { | ||||||
|  |         ubyte cx = startCx | ||||||
|  |         ubyte cy = startCy | ||||||
|  |  | ||||||
|  |         stackptr = 0 | ||||||
|  |         @(celladdr(cx,cy)) &= ~STONE | ||||||
|  |         drawCell(cx, cy) | ||||||
|  |         uword cells_to_carve = numCellsHoriz * numCellsVert - 1 | ||||||
|  |  | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  | carve_restart_after_repath: | ||||||
|  |             ubyte direction = choose_uncarved_direction() | ||||||
|  |             if direction==0 { | ||||||
|  |                 ;backtrack | ||||||
|  |                 stackptr-- | ||||||
|  |                 if stackptr==255 { | ||||||
|  |                     ; stack empty. | ||||||
|  |                     ; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit) | ||||||
|  |                     if cells_to_carve!=0 { | ||||||
|  |                         if repath() | ||||||
|  |                             goto carve_restart_after_repath | ||||||
|  |                     } | ||||||
|  |                     return true | ||||||
|  |                 } | ||||||
|  |                 cx = cx_stack[stackptr] | ||||||
|  |                 cy = cy_stack[stackptr] | ||||||
|  |             } else { | ||||||
|  |                 cx_stack[stackptr] = cx | ||||||
|  |                 cy_stack[stackptr] = cy | ||||||
|  |                 stackptr++ | ||||||
|  |                 if stackptr==0 { | ||||||
|  |                     ; stack overflow, we can't track our path any longer. | ||||||
|  |                     ; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit) | ||||||
|  |                     if cells_to_carve!=0 { | ||||||
|  |                         if repath() | ||||||
|  |                             goto carve_restart_after_repath | ||||||
|  |                     } | ||||||
|  |                     return true | ||||||
|  |                 } | ||||||
|  |                 @(celladdr(cx,cy)) |= direction | ||||||
|  |                 when direction { | ||||||
|  |                     UP -> { | ||||||
|  |                         cy-- | ||||||
|  |                         @(celladdr(cx,cy)) |= DOWN | ||||||
|  |                     } | ||||||
|  |                     RIGHT -> { | ||||||
|  |                         cx++ | ||||||
|  |                         @(celladdr(cx,cy)) |= LEFT | ||||||
|  |  | ||||||
|  |                         score++ | ||||||
|  |                     } | ||||||
|  |                     DOWN -> { | ||||||
|  |                         cy++ | ||||||
|  |                         @(celladdr(cx,cy)) |= UP | ||||||
|  |                     } | ||||||
|  |                     LEFT -> { | ||||||
|  |                         cx-- | ||||||
|  |                         @(celladdr(cx,cy)) |= RIGHT | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 @(celladdr(cx,cy)) &= ~STONE | ||||||
|  |                 cells_to_carve-- | ||||||
|  |                 drawCell(cx, cy) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return false | ||||||
|  |  | ||||||
|  |         sub repath() -> bool { | ||||||
|  |             ; repath: try to find a new start cell with possible directions. | ||||||
|  |             ; we limit our number of searches so that the algorith doesn't get stuck | ||||||
|  |             ; for too long on bad rng... just accept a few unused cells in that case. | ||||||
|  |             repeat 255 { | ||||||
|  |                 do { | ||||||
|  |                     cx = math.rnd() % numCellsHoriz | ||||||
|  |                     cy = math.rnd() % numCellsVert | ||||||
|  |                 } until @(celladdr(cx, cy)) & STONE ==0 | ||||||
|  |                 if available_uncarved()!=0 | ||||||
|  |                     return true | ||||||
|  |             } | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub available_uncarved() -> ubyte { | ||||||
|  |             ubyte candidates = 0 | ||||||
|  |             if cx>0 and @(celladdr(cx-1, cy)) & STONE !=0 | ||||||
|  |                 candidates |= LEFT | ||||||
|  |             if cx<numCellsHoriz-1 and @(celladdr(cx+1, cy)) & STONE !=0 | ||||||
|  |                 candidates |= RIGHT | ||||||
|  |             if cy>0 and @(celladdr(cx, cy-1)) & STONE !=0 | ||||||
|  |                 candidates |= UP | ||||||
|  |             if cy<numCellsVert-1 and @(celladdr(cx, cy+1)) & STONE !=0 | ||||||
|  |                 candidates |= DOWN | ||||||
|  |             return candidates | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub choose_uncarved_direction() -> ubyte { | ||||||
|  |             ubyte candidates =  available_uncarved() | ||||||
|  |             if candidates==0 | ||||||
|  |                 return 0 | ||||||
|  |  | ||||||
|  |             repeat { | ||||||
|  |                 ubyte choice = candidates & directionflags[math.rnd() & 3] | ||||||
|  |                 if choice!=0 | ||||||
|  |                     return choice | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub openpassages() { | ||||||
|  |         ; open just a few extra passages, so that multiple routes are possible in theory. | ||||||
|  |         ubyte numpassages | ||||||
|  |         ubyte cx | ||||||
|  |         ubyte cy | ||||||
|  |         do { | ||||||
|  |             do { | ||||||
|  |                 cx = math.rnd() % (numCellsHoriz-2) + 1 | ||||||
|  |                 cy = math.rnd() % (numCellsVert-2) + 1 | ||||||
|  |             } until @(celladdr(cx, cy)) & STONE ==0 | ||||||
|  |             ubyte direction = directionflags[math.rnd() & 3] | ||||||
|  |             if @(celladdr(cx, cy)) & direction == 0 { | ||||||
|  |                 when direction { | ||||||
|  |                     LEFT -> { | ||||||
|  |                         if @(celladdr(cx-1,cy)) & STONE == 0 { | ||||||
|  |                             @(celladdr(cx,cy)) |= LEFT | ||||||
|  |                             drawCell(cx,cy) | ||||||
|  |                             numpassages++ | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     RIGHT -> { | ||||||
|  |                         if @(celladdr(cx+1,cy)) & STONE == 0 { | ||||||
|  |                             @(celladdr(cx,cy)) |= RIGHT | ||||||
|  |                             drawCell(cx,cy) | ||||||
|  |                             numpassages++ | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     UP -> { | ||||||
|  |                         if @(celladdr(cx,cy-1)) & STONE == 0 { | ||||||
|  |                             @(celladdr(cx,cy)) |= UP | ||||||
|  |                             drawCell(cx,cy) | ||||||
|  |                             numpassages++ | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     DOWN -> { | ||||||
|  |                         if @(celladdr(cx,cy+1)) & STONE == 0 { | ||||||
|  |                             @(celladdr(cx,cy)) |= DOWN | ||||||
|  |                             drawCell(cx,cy) | ||||||
|  |                             numpassages++ | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } until numpassages==10 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub solve(uword max_time) -> bool { | ||||||
|  |         ubyte cx = startCx | ||||||
|  |         ubyte cy = startCy | ||||||
|  |         const uword max_path_length = 1024 | ||||||
|  |  | ||||||
|  |         ; the path through the maze can be longer than 256 so doesn't fit in a regular array.... :( | ||||||
|  |         uword pathstack = memory("pathstack", max_path_length, 0) | ||||||
|  |         uword pathstackptr = 0 | ||||||
|  |  | ||||||
|  |         @(celladdr(cx,cy)) |= WALKED | ||||||
|  |         ; txt.setcc(cx*2+1, cy*2+1, 81, 1) | ||||||
|  |  | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  | solve_loop: | ||||||
|  |             if cx==finishCx and cy==finishCy { | ||||||
|  |                 ;txt.home() | ||||||
|  |                 txt.print("found! path length: ") | ||||||
|  |                 txt.print_uw(pathstackptr) | ||||||
|  |                 txt.nl() | ||||||
|  |                 return true | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ubyte cell = @(celladdr(cx,cy)) | ||||||
|  |             if cell & UP!=0 and @(celladdr(cx,cy-1)) & (WALKED|BACKTRACKED) ==0 { | ||||||
|  |                 @(pathstack + pathstackptr) = UP | ||||||
|  |                 ;txt.setcc(cx*2+1, cy*2, 81, 3) | ||||||
|  |                 cy-- | ||||||
|  |             } | ||||||
|  |             else if cell & DOWN !=0 and @(celladdr(cx,cy+1)) & (WALKED|BACKTRACKED) ==0 { | ||||||
|  |                 @(pathstack + pathstackptr) = DOWN | ||||||
|  |                 ;txt.setcc(cx*2+1, cy*2+2, 81, 3) | ||||||
|  |                 cy++ | ||||||
|  |             } | ||||||
|  |             else if cell & LEFT !=0 and @(celladdr(cx-1,cy)) & (WALKED|BACKTRACKED) ==0 { | ||||||
|  |                 @(pathstack + pathstackptr) = LEFT | ||||||
|  |                 ;txt.setcc(cx*2, cy*2+1, 81, 3) | ||||||
|  |                 cx-- | ||||||
|  |             } | ||||||
|  |             else if cell & RIGHT !=0 and @(celladdr(cx+1,cy)) & (WALKED|BACKTRACKED) ==0 { | ||||||
|  |                 @(pathstack + pathstackptr) = RIGHT | ||||||
|  |                 ;txt.setcc(cx*2+2, cy*2+1, 81, 3) | ||||||
|  |                 cx++ | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 ; dead end, pop stack | ||||||
|  |                 pathstackptr-- | ||||||
|  |                 if pathstackptr==65535 { | ||||||
|  |                     txt.print("no solution?!\n") | ||||||
|  |                     return true | ||||||
|  |                 } | ||||||
|  |                 @(celladdr(cx,cy)) |= BACKTRACKED | ||||||
|  |                 ;txt.setcc(cx*2+1, cy*2+1, 81, 2) | ||||||
|  |                 when @(pathstack + pathstackptr) { | ||||||
|  |                     UP -> { | ||||||
|  |                         ;txt.setcc(cx*2+1, cy*2+2, 81, 9) | ||||||
|  |                         cy++ | ||||||
|  |                     } | ||||||
|  |                     DOWN -> { | ||||||
|  |                         ;txt.setcc(cx*2+1, cy*2, 81, 9) | ||||||
|  |                         cy-- | ||||||
|  |                     } | ||||||
|  |                     LEFT -> { | ||||||
|  |                         ;txt.setcc(cx*2+2, cy*2+1, 81, 9) | ||||||
|  |                         cx++ | ||||||
|  |                     } | ||||||
|  |                     RIGHT -> { | ||||||
|  |                         ;txt.setcc(cx*2, cy*2+1, 81, 9) | ||||||
|  |                         cx-- | ||||||
|  |  | ||||||
|  |                         score++ | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 goto solve_loop | ||||||
|  |             } | ||||||
|  |             pathstackptr++ | ||||||
|  |             if pathstackptr==max_path_length { | ||||||
|  |                 txt.print("stack overflow, path too long\n") | ||||||
|  |                 return true | ||||||
|  |             } | ||||||
|  |             @(celladdr(cx,cy)) |= WALKED | ||||||
|  |             ;txt.setcc(cx*2+1, cy*2+1, 81, 1) | ||||||
|  |         } | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub celladdr(ubyte cx, ubyte cy) -> uword { | ||||||
|  |         return cells+(numCellsHoriz as uword)*cy+cx | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub drawCell(ubyte cx, ubyte cy) { | ||||||
|  |         return | ||||||
|  | ;        ubyte x = cx * 2 + 1 | ||||||
|  | ;        ubyte y = cy * 2 + 1 | ||||||
|  | ;        ubyte doors = @(celladdr(cx,cy)) | ||||||
|  | ;        if doors & UP !=0 | ||||||
|  | ;            txt.setcc(x, y-1, ' ', EMPTYCOLOR) | ||||||
|  | ;        if doors & RIGHT !=0 | ||||||
|  | ;            txt.setcc(x+1, y, ' ', EMPTYCOLOR) | ||||||
|  | ;        if doors & DOWN !=0 | ||||||
|  | ;            txt.setcc(x, y+1, ' ', EMPTYCOLOR) | ||||||
|  | ;        if doors & LEFT !=0 | ||||||
|  | ;            txt.setcc(x-1, y, ' ', EMPTYCOLOR) | ||||||
|  | ;        if doors & STONE !=0 | ||||||
|  | ;            txt.setcc(x, y, 160, WALLCOLOR) | ||||||
|  | ;        else | ||||||
|  | ;            txt.setcc(x, y, 32, EMPTYCOLOR) | ||||||
|  | ; | ||||||
|  | ;        if doors & WALKED !=0 | ||||||
|  | ;            txt.setcc(x, y, 81, 1) | ||||||
|  | ;        if doors & BACKTRACKED !=0 | ||||||
|  | ;            txt.setcc(x, y, 81, 2) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub initialize() { | ||||||
|  |         sys.memset(cells, numCellsHoriz*numCellsVert, STONE) | ||||||
|  |         ; txt.fill_screen(160, WALLCOLOR) | ||||||
|  |         drawStartFinish() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub drawStartFinish() { | ||||||
|  |         ; txt.setcc(startCx*2+1,startCy*2+1,sc:'s',5) | ||||||
|  |         ; txt.setcc(finishCx*2+1, finishCy*2+1, sc:'f', 13) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								benchmark-program/b_queens.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								benchmark-program/b_queens.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | %import textio | ||||||
|  |  | ||||||
|  | ; Recursive N-Queens solver. | ||||||
|  | ; The problem is: find all possible ways to place 8 Queen chess pieces on a chess board, so that none of them attacks any other. | ||||||
|  | ; (this program prints all solutions without taking mirroring and flipping the chess board into account) | ||||||
|  | ; Note: this program can be compiled for multiple target systems. | ||||||
|  |  | ||||||
|  | queens { | ||||||
|  |     const ubyte NUMQUEENS=8 | ||||||
|  |     ubyte[NUMQUEENS] board | ||||||
|  |  | ||||||
|  |     sub could_place(ubyte row, ubyte col) -> bool { | ||||||
|  |         if row==0 | ||||||
|  |             return true | ||||||
|  |         ubyte i | ||||||
|  |         for i in 0 to row-1 { | ||||||
|  |             if board[i]==col or board[i]-i==col-row or board[i]+i==col+row | ||||||
|  |                 return false | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     uword solution_count | ||||||
|  |     uword maximum_duration | ||||||
|  |  | ||||||
|  |     sub place_queen(ubyte row) -> bool { | ||||||
|  |         if row == NUMQUEENS { | ||||||
|  |             solution_count++ | ||||||
|  |             txt.chrout('.') | ||||||
|  |             return cbm.RDTIM16()<maximum_duration | ||||||
|  |         } | ||||||
|  |         bool continue_running=true | ||||||
|  |         ubyte col | ||||||
|  |         for col in 0 to NUMQUEENS-1 { | ||||||
|  |             if could_place(row, col) { | ||||||
|  |                 board[row] = col | ||||||
|  |                 ; we need to save the local variables row and col. | ||||||
|  |                 sys.push(row) | ||||||
|  |                 sys.push(col) | ||||||
|  |                 continue_running = place_queen(row + 1) | ||||||
|  |                 ; restore the local variables after the recursive call. | ||||||
|  |                 col = sys.pop() | ||||||
|  |                 row = sys.pop() | ||||||
|  |                 board[row] = 0 | ||||||
|  |  | ||||||
|  |                 if not continue_running | ||||||
|  |                     break | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return continue_running | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub bench(uword max_time) -> uword { | ||||||
|  |         solution_count = 0 | ||||||
|  |         maximum_duration = max_time | ||||||
|  |         txt.nl() | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |         while cbm.RDTIM16() < maximum_duration { | ||||||
|  |             void place_queen(0) | ||||||
|  |         } | ||||||
|  |         return solution_count | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										69
									
								
								benchmark-program/b_sprites.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								benchmark-program/b_sprites.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | %import sprites | ||||||
|  | %import coroutines | ||||||
|  | %import math | ||||||
|  |  | ||||||
|  |  | ||||||
|  | animsprites { | ||||||
|  |     uword num_iterations | ||||||
|  |     ubyte[64] sx | ||||||
|  |     ubyte[64] sy | ||||||
|  |     ubyte[64] sc | ||||||
|  |     ubyte[64] dx | ||||||
|  |     ubyte[64] dy | ||||||
|  |     uword maximum_duration | ||||||
|  |  | ||||||
|  |     sub benchmark(uword max_duration) -> uword { | ||||||
|  |         maximum_duration = max_duration | ||||||
|  |         math.rndseed(1122,9876) | ||||||
|  |         cx16.set_screen_mode(3) | ||||||
|  |         cx16.mouse_config2(1) | ||||||
|  |         sprites.set_mousepointer_hand() | ||||||
|  |         repeat 64 | ||||||
|  |             void coroutines.add(animsprite, 0) | ||||||
|  |         cx16.mouse_config2(0) | ||||||
|  |  | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |         coroutines.run(supervisor) | ||||||
|  |  | ||||||
|  |         sprites.reset(0, 64) | ||||||
|  |         return num_iterations | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub supervisor() -> bool { | ||||||
|  |         if cbm.RDTIM16() >= maximum_duration { | ||||||
|  |             coroutines.killall() | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub animsprite() { | ||||||
|  |         num_iterations++ | ||||||
|  |         ; set up the sprite | ||||||
|  |         ubyte sprnum = coroutines.current() | ||||||
|  |         cx16.r6L, cx16.r7 = sprites.get_data_ptr(0) | ||||||
|  |         sprites.init(sprnum, cx16.r6L, cx16.r7, sprites.SIZE_16, sprites.SIZE_16, sprites.COLORS_256, 0) | ||||||
|  |         sx[sprnum] = math.rnd() | ||||||
|  |         sy[sprnum] = math.rnd() | ||||||
|  |         sc[sprnum] = math.rnd() | ||||||
|  |         dx[sprnum] = if math.rnd()&1 == 1  1 else 255 | ||||||
|  |         dy[sprnum] = if math.rnd()&1 == 1  1 else 255 | ||||||
|  |  | ||||||
|  |         ; move the sprite around | ||||||
|  |         while sc[sprnum]!=0 { | ||||||
|  |             animate(sprnum) | ||||||
|  |             void coroutines.yield() | ||||||
|  |             sprnum = coroutines.current() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub animate(ubyte spr) { | ||||||
|  |             defer sc[spr]-- | ||||||
|  |             sprites.pos(spr, sx[spr], sy[spr]) | ||||||
|  |             sx[spr] += dx[spr] | ||||||
|  |             sy[spr] += dy[spr] | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ; end the task but replace it with a fresh animated sprite task | ||||||
|  |         void coroutines.add(animsprite, 0) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										989
									
								
								benchmark-program/b_textelite.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										989
									
								
								benchmark-program/b_textelite.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,989 @@ | |||||||
|  | %import textio | ||||||
|  | %import conv | ||||||
|  | %import strings | ||||||
|  |  | ||||||
|  |  | ||||||
|  | textelite { | ||||||
|  |  | ||||||
|  |     const ubyte numforLave = 7      ;  Lave is 7th generated planet in galaxy one | ||||||
|  |     const ubyte numforZaonce = 129 | ||||||
|  |     const ubyte numforDiso = 147 | ||||||
|  |     const ubyte numforRiedquat = 46 | ||||||
|  |     uword num_commands | ||||||
|  |  | ||||||
|  |     sub bench(uword max_time) -> uword { | ||||||
|  |         num_commands = 0 | ||||||
|  |         txt.lowercase() | ||||||
|  |         cbm.SETTIM(0,0,0) | ||||||
|  |         while cbm.RDTIM16()<max_time { | ||||||
|  |             reinit() | ||||||
|  |             run_commands(max_time) | ||||||
|  |         } | ||||||
|  |         return num_commands | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub reinit() { | ||||||
|  |         ;txt.clear_screen() | ||||||
|  |         ;txt.print("\n --- TextElite v1.3 ---\n") | ||||||
|  |         txt.print("\nnew game\n") | ||||||
|  |         elite_planet.set_seed(0, 0) | ||||||
|  |         elite_galaxy.travel_to(1, numforLave) | ||||||
|  |         elite_market.init(0)  ;  Lave's market is seeded with 0 | ||||||
|  |         elite_ship.init() | ||||||
|  |         elite_planet.display(false, 0) | ||||||
|  |         input_index = 0 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub run_commands(uword max_time) { | ||||||
|  |         while cbm.RDTIM16() < max_time { | ||||||
|  |             str input = "????????" | ||||||
|  |             ;txt.print("\nCash: ") | ||||||
|  |             ;elite_util.print_10s(elite_ship.cash) | ||||||
|  |             ;txt.print("\nCommand (?=help): ") | ||||||
|  |             ubyte num_chars = next_input(input) | ||||||
|  |             ;txt.nl() | ||||||
|  |             if num_chars!=0 { | ||||||
|  |                 when input[0] { | ||||||
|  |                     'q' -> { | ||||||
|  |                         bool has_error = false | ||||||
|  |                         if elite_galaxy.number != 2 { | ||||||
|  |                             txt.print("\nERROR: galaxy is not 2: ") | ||||||
|  |                             txt.print_ub(elite_galaxy.number) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if elite_planet.number != 164 { | ||||||
|  |                             txt.print("\nERROR: planet is not 164: ") | ||||||
|  |                             txt.print_ub(elite_planet.number) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if elite_planet.x != 116 { | ||||||
|  |                             txt.print("\nERROR: planet.x is not 116: ") | ||||||
|  |                             txt.print_ub(elite_planet.x) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if elite_planet.y != 201 { | ||||||
|  |                             txt.print("\nERROR: planet.y is not 201: ") | ||||||
|  |                             txt.print_ub(elite_planet.y) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if "ribeen" != elite_planet.name { | ||||||
|  |                             txt.print("\nERROR: planet.name is not 'ribeen': ") | ||||||
|  |                             txt.print(elite_planet.name) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if elite_ship.cash != 1212 { | ||||||
|  |                             txt.print("\nERROR: cash is not 1212: ") | ||||||
|  |                             txt.print_uw(elite_ship.cash) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if elite_ship.fuel != 50 { | ||||||
|  |                             txt.print("\nERROR: fuel is not 50:") | ||||||
|  |                             txt.print_ub(elite_ship.fuel) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if elite_ship.cargohold[0] != 3 { | ||||||
|  |                             txt.print("\nERROR: food is not 3:") | ||||||
|  |                             txt.print_ub(elite_ship.cargohold[0]) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if elite_ship.cargohold[1] != 0 { | ||||||
|  |                             txt.print("\nERROR: textiles is not 0:") | ||||||
|  |                             txt.print_ub(elite_ship.cargohold[1]) | ||||||
|  |                             txt.nl() | ||||||
|  |                             has_error=true | ||||||
|  |                         } | ||||||
|  |                         if has_error | ||||||
|  |                             sys.exit(1) | ||||||
|  |                         return | ||||||
|  |                     } | ||||||
|  |                     'b' -> elite_trader.do_buy() | ||||||
|  |                     's' -> elite_trader.do_sell() | ||||||
|  |                     'f' -> elite_trader.do_fuel() | ||||||
|  |                     'j' -> elite_trader.do_jump() | ||||||
|  |                     't' -> elite_trader.do_teleport() | ||||||
|  |                     'g' -> elite_trader.do_next_galaxy() | ||||||
|  |                     'i' -> elite_trader.do_info() | ||||||
|  |                     'm' -> { | ||||||
|  |                         if input[1]=='a' and input[2]=='p' | ||||||
|  |                             elite_trader.do_map() | ||||||
|  |                         else | ||||||
|  |                             elite_trader.do_show_market() | ||||||
|  |                     } | ||||||
|  |                     'l' -> elite_trader.do_local() | ||||||
|  |                     'c' -> elite_trader.do_cash() | ||||||
|  |                     'h' -> elite_trader.do_hold() | ||||||
|  |                 } | ||||||
|  |                 num_commands++ | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     str[] inputs = [ | ||||||
|  |         "i", | ||||||
|  |         "diso", | ||||||
|  |         "i", | ||||||
|  |         "lave", | ||||||
|  |         "m", | ||||||
|  |         "b", | ||||||
|  |         "food", | ||||||
|  |         "15", | ||||||
|  |         "map", | ||||||
|  |         "g", | ||||||
|  |         "map", | ||||||
|  |         "l", | ||||||
|  |         "j", | ||||||
|  |         "zao", | ||||||
|  |         "s", | ||||||
|  |         "food", | ||||||
|  |         "12", | ||||||
|  |         "tele", | ||||||
|  |         "quti", | ||||||
|  |         "tele", | ||||||
|  |         "aro", | ||||||
|  |         "i", | ||||||
|  |         "diso", | ||||||
|  |         "i", | ||||||
|  |         "lave", | ||||||
|  |         "i", | ||||||
|  |         "zao", | ||||||
|  |         "galhyp", | ||||||
|  |         "fuel", | ||||||
|  |         "20", | ||||||
|  |         "j", | ||||||
|  |         "rib", | ||||||
|  |         "i", | ||||||
|  |         "rib", | ||||||
|  |         "i", | ||||||
|  |         "tiri", | ||||||
|  |         "q", | ||||||
|  |         0 | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     ubyte input_index | ||||||
|  |  | ||||||
|  |     sub next_input(str buffer) -> ubyte { | ||||||
|  |         input_index++ | ||||||
|  |         return strings.copy(inputs[input_index], buffer) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | elite_trader { | ||||||
|  |     str input = "??????????" | ||||||
|  |     ubyte num_chars | ||||||
|  |  | ||||||
|  |     sub do_jump() { | ||||||
|  |         ;txt.print("\nJump to what system? ") | ||||||
|  |         jump_to_system() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_teleport() { | ||||||
|  |         ;txt.print("\nCheat! Teleport to what system? ") | ||||||
|  |         ubyte fuel = elite_ship.fuel | ||||||
|  |         elite_ship.fuel = 255 | ||||||
|  |         jump_to_system() | ||||||
|  |         elite_ship.fuel = fuel | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub jump_to_system() { | ||||||
|  |         void textelite.next_input(input) | ||||||
|  |         ubyte current_planet = elite_planet.number | ||||||
|  |         ubyte x = elite_planet.x | ||||||
|  |         ubyte y = elite_planet.y | ||||||
|  |         if elite_galaxy.search_closest_planet(input) { | ||||||
|  |             ubyte distance = elite_planet.distance(x, y) | ||||||
|  |             if distance <= elite_ship.fuel { | ||||||
|  |                 elite_galaxy.init_market_for_planet() | ||||||
|  |                 elite_ship.fuel -= distance | ||||||
|  |                 ;txt.print("\n\nHyperspace jump! Arrived at:\n") | ||||||
|  |                 elite_planet.display(true,0 ) | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  |             ;txt.print("\nInsufficient fuel\n") | ||||||
|  |         } else { | ||||||
|  |             ;txt.print(" Not found!\n") | ||||||
|  |         } | ||||||
|  |         elite_galaxy.travel_to(elite_galaxy.number, current_planet) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_buy() { | ||||||
|  |         ;txt.print("\nBuy what commodity? ") | ||||||
|  |         str commodity = "???????????????" | ||||||
|  |         void textelite.next_input(commodity) | ||||||
|  |         ubyte ci = elite_market.match(commodity) | ||||||
|  |         if ci & 128 !=0 { | ||||||
|  |             txt.print("Unknown\n") | ||||||
|  |         } else { | ||||||
|  |             ;txt.print("\nHow much? ") | ||||||
|  |             void textelite.next_input(input) | ||||||
|  |             ubyte amount = conv.str2ubyte(input) | ||||||
|  |             if elite_market.current_quantity[ci] < amount { | ||||||
|  |                 txt.print(" Insufficient supply!\n") | ||||||
|  |             } else { | ||||||
|  |                 uword price = elite_market.current_price[ci] * amount | ||||||
|  |                 ;txt.print(" Total price: ") | ||||||
|  |                 ;elite_util.print_10s(price) | ||||||
|  |                 if price > elite_ship.cash { | ||||||
|  |                     txt.print(" Not enough cash!\n") | ||||||
|  |                 } else { | ||||||
|  |                     elite_ship.cash -= price | ||||||
|  |                     elite_ship.cargohold[ci] += amount | ||||||
|  |                     elite_market.current_quantity[ci] -= amount | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_sell() { | ||||||
|  |         ;txt.print("\nSell what commodity? ") | ||||||
|  |         str commodity = "???????????????" | ||||||
|  |         void textelite.next_input(commodity) | ||||||
|  |         ubyte ci = elite_market.match(commodity) | ||||||
|  |         if ci & 128 !=0 { | ||||||
|  |             txt.print("Unknown\n") | ||||||
|  |         } else { | ||||||
|  |             ;txt.print("\nHow much? ") | ||||||
|  |             void textelite.next_input(input) | ||||||
|  |             ubyte amount = conv.str2ubyte(input) | ||||||
|  |             if elite_ship.cargohold[ci] < amount { | ||||||
|  |                 txt.print(" Insufficient supply!\n") | ||||||
|  |             } else { | ||||||
|  |                 uword price = elite_market.current_price[ci] * amount | ||||||
|  |                 ;txt.print(" Total price: ") | ||||||
|  |                 ;elite_util.print_10s(price) | ||||||
|  |                 elite_ship.cash += price | ||||||
|  |                 elite_ship.cargohold[ci] -= amount | ||||||
|  |                 elite_market.current_quantity[ci] += amount | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_fuel() { | ||||||
|  |         ;txt.print("\nBuy fuel. Amount? ") | ||||||
|  |         void textelite.next_input(input) | ||||||
|  |         ubyte buy_fuel = 10*conv.str2ubyte(input) | ||||||
|  |         ubyte max_fuel = elite_ship.Max_fuel - elite_ship.fuel | ||||||
|  |         if buy_fuel > max_fuel | ||||||
|  |             buy_fuel = max_fuel | ||||||
|  |         uword price = buy_fuel as uword * elite_ship.Fuel_cost | ||||||
|  |         if price > elite_ship.cash { | ||||||
|  |             txt.print("Not enough cash!\n") | ||||||
|  |         } else { | ||||||
|  |             elite_ship.cash -= price | ||||||
|  |             elite_ship.fuel += buy_fuel | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_cash() { | ||||||
|  |         ;txt.print("\nCheat! Set cash amount: ") | ||||||
|  |         void textelite.next_input(input) | ||||||
|  |         elite_ship.cash = conv.str2uword(input) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_hold() { | ||||||
|  |         ;txt.print("\nCheat! Set cargohold size: ") | ||||||
|  |         void textelite.next_input(input) | ||||||
|  |         elite_ship.Max_cargo = conv.str2ubyte(input) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_next_galaxy() { | ||||||
|  |         txt.print("\n>>>>> Galaxy Hyperjump!\n") | ||||||
|  |         elite_galaxy.travel_to(elite_galaxy.number+1, elite_planet.number) | ||||||
|  |         elite_planet.display(false, 0) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_info() { | ||||||
|  |         ;txt.print("\nSystem name (empty=current): ") | ||||||
|  |         num_chars = textelite.next_input(input) | ||||||
|  |         if num_chars!=0 { | ||||||
|  |             ubyte current_planet = elite_planet.number | ||||||
|  |             ubyte x = elite_planet.x | ||||||
|  |             ubyte y = elite_planet.y | ||||||
|  |             if elite_galaxy.search_closest_planet(input) { | ||||||
|  |                 ubyte distance = elite_planet.distance(x, y) | ||||||
|  |                 elite_planet.display(false, distance) | ||||||
|  |             } else { | ||||||
|  |                 ;txt.print(" Not found!") | ||||||
|  |             } | ||||||
|  |             elite_galaxy.travel_to(elite_galaxy.number, current_planet) | ||||||
|  |         } else { | ||||||
|  |             elite_planet.display(false, 0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_local() { | ||||||
|  |         elite_galaxy.local_area() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_map() { | ||||||
|  |         ;txt.print("\n(l)ocal or (g)alaxy starmap? ") | ||||||
|  |         num_chars = textelite.next_input(input) | ||||||
|  |         if num_chars!=0 { | ||||||
|  |             elite_galaxy.starmap(input[0]=='l') | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub do_show_market() { | ||||||
|  |         elite_market.display() | ||||||
|  |         ;txt.print("\nFuel: ") | ||||||
|  |         ;elite_util.print_10s(elite_ship.fuel) | ||||||
|  |         ;txt.print("   Cargohold space: ") | ||||||
|  |         ;txt.print_ub(elite_ship.cargo_free()) | ||||||
|  |         ;txt.print("t\n") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | elite_ship { | ||||||
|  |     const ubyte Max_fuel = 70 | ||||||
|  |     const ubyte Fuel_cost = 2 | ||||||
|  |     ubyte Max_cargo = 20 | ||||||
|  |  | ||||||
|  |     ubyte fuel | ||||||
|  |     uword cash | ||||||
|  |     ubyte[17] cargohold | ||||||
|  |  | ||||||
|  |     sub init() { | ||||||
|  |         sys.memset(cargohold, len(cargohold), 0) | ||||||
|  |         fuel = Max_fuel | ||||||
|  |         cash = 1000 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | elite_market { | ||||||
|  |     ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35] | ||||||
|  |     byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F] | ||||||
|  |     ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0] | ||||||
|  |     ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07] | ||||||
|  |     str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers", | ||||||
|  |                      "Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"] | ||||||
|  |  | ||||||
|  |     ubyte[17] current_quantity | ||||||
|  |     uword[17] current_price | ||||||
|  |  | ||||||
|  |     sub init(ubyte fluct) { | ||||||
|  |         ; Prices and availabilities are influenced by the planet's economy type | ||||||
|  |         ; (0-7) and a random "fluctuation" byte that was kept within the saved | ||||||
|  |         ; commander position to keep the market prices constant over gamesaves. | ||||||
|  |         ; Availabilities must be saved with the game since the player alters them | ||||||
|  |         ; by buying (and selling(?)) | ||||||
|  |         ; | ||||||
|  |         ; Almost all commands are one byte only and overflow "errors" are | ||||||
|  |         ; extremely frequent and exploited. | ||||||
|  |         ; | ||||||
|  |         ; Trade Item prices are held internally in a single byte=true value/4. | ||||||
|  |         ; The decimal point in prices is introduced only when printing them. | ||||||
|  |         ; Internally, all prices are integers. | ||||||
|  |         ; The player's cash is held in four bytes. | ||||||
|  |         ubyte ci | ||||||
|  |         for ci in 0 to len(names)-1 { | ||||||
|  |             word product | ||||||
|  |             byte changing | ||||||
|  |             product = elite_planet.economy as word * gradients[ci] | ||||||
|  |             changing = fluct & maskbytes[ci]  as byte | ||||||
|  |             ubyte q = (basequants[ci] as word + changing - product) as ubyte | ||||||
|  |             if q & $80 !=0 | ||||||
|  |                 q = 0  ; clip to positive 8-bit | ||||||
|  |             current_quantity[ci] = q & $3f | ||||||
|  |             q = (baseprices[ci] + changing + product) as ubyte | ||||||
|  |             current_price[ci] = q * $0004 | ||||||
|  |         } | ||||||
|  |         current_quantity[16] = 0        ; force nonavailability of Alien Items | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub display() { | ||||||
|  |         return | ||||||
|  | ;        ubyte ci | ||||||
|  | ;        txt.nl() | ||||||
|  | ;        elite_planet.print_name_uppercase() | ||||||
|  | ;        txt.print(" trade market:\n    COMMODITY / PRICE / AVAIL / IN HOLD\n") | ||||||
|  | ;        for ci in 0 to len(names)-1 { | ||||||
|  | ;            elite_util.print_right(13, names[ci]) | ||||||
|  | ;            txt.print("   ") | ||||||
|  | ;            elite_util.print_10s(current_price[ci]) | ||||||
|  | ;            txt.column(24) | ||||||
|  | ;            txt.print_ub(current_quantity[ci]) | ||||||
|  | ;            txt.chrout(' ') | ||||||
|  | ;            when units[ci] { | ||||||
|  | ;                0 -> txt.chrout('t') | ||||||
|  | ;                1 -> txt.print("kg") | ||||||
|  | ;                2 -> txt.chrout('g') | ||||||
|  | ;            } | ||||||
|  | ;            txt.column(32) | ||||||
|  | ;            txt.print_ub(elite_ship.cargohold[ci]) | ||||||
|  | ;            txt.nl() | ||||||
|  | ;        } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub match(uword nameptr) -> ubyte { | ||||||
|  |         ubyte ci | ||||||
|  |         for ci in 0 to len(names)-1 { | ||||||
|  |             if elite_util.prefix_matches(nameptr, names[ci]) | ||||||
|  |                 return ci | ||||||
|  |         } | ||||||
|  |         return 255 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | elite_galaxy { | ||||||
|  |     const uword GALSIZE = 256 | ||||||
|  |     const uword base0 = $5A4A       ; seeds for the first galaxy | ||||||
|  |     const uword base1 = $0248 | ||||||
|  |     const uword base2 = $B753 | ||||||
|  |  | ||||||
|  |     str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion" | ||||||
|  |  | ||||||
|  |     ubyte number | ||||||
|  |  | ||||||
|  |     uword[3] seed | ||||||
|  |  | ||||||
|  |     sub init(ubyte galaxynum) { | ||||||
|  |         number = 1 | ||||||
|  |         elite_planet.number = 255 | ||||||
|  |         seed[0] = base0 | ||||||
|  |         seed[1] = base1 | ||||||
|  |         seed[2] = base2 | ||||||
|  |         repeat galaxynum-1 { | ||||||
|  |             nextgalaxy() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub nextgalaxy() { | ||||||
|  |         textelite.num_commands++ | ||||||
|  |  | ||||||
|  |         seed[0] = twist(seed[0]) | ||||||
|  |         seed[1] = twist(seed[1]) | ||||||
|  |         seed[2] = twist(seed[2]) | ||||||
|  |         number++ | ||||||
|  |         if number==9 | ||||||
|  |             number = 1 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub travel_to(ubyte galaxynum, ubyte system) { | ||||||
|  |         init(galaxynum) | ||||||
|  |         generate_next_planet()   ; always at least planet 0  (separate to avoid repeat ubyte overflow) | ||||||
|  |         repeat system { | ||||||
|  |             generate_next_planet() | ||||||
|  |             textelite.num_commands++ | ||||||
|  |         } | ||||||
|  |         elite_planet.name = make_current_planet_name() | ||||||
|  |         init_market_for_planet() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub init_market_for_planet() { | ||||||
|  |         elite_market.init(lsb(seed[0])+msb(seed[2])) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub search_closest_planet(uword nameptr) -> bool { | ||||||
|  |         textelite.num_commands++ | ||||||
|  |  | ||||||
|  |         ubyte x = elite_planet.x | ||||||
|  |         ubyte y = elite_planet.y | ||||||
|  |         ubyte current_planet_num = elite_planet.number | ||||||
|  |  | ||||||
|  |         init(number) | ||||||
|  |         bool found = false | ||||||
|  |         ubyte current_closest_pi | ||||||
|  |         ubyte current_distance = 127 | ||||||
|  |         ubyte pi | ||||||
|  |         for pi in 0 to 255 { | ||||||
|  |             generate_next_planet() | ||||||
|  |             elite_planet.name = make_current_planet_name() | ||||||
|  |             if elite_util.prefix_matches(nameptr, elite_planet.name) { | ||||||
|  |                 ubyte distance = elite_planet.distance(x, y) | ||||||
|  |                 if distance < current_distance { | ||||||
|  |                     current_distance = distance | ||||||
|  |                     current_closest_pi = pi | ||||||
|  |                     found = true | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if found | ||||||
|  |             travel_to(number, current_closest_pi) | ||||||
|  |         else | ||||||
|  |             travel_to(number, current_planet_num) | ||||||
|  |  | ||||||
|  |         return found | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub local_area() { | ||||||
|  |         ubyte current_planet = elite_planet.number | ||||||
|  |         ubyte px = elite_planet.x | ||||||
|  |         ubyte py = elite_planet.y | ||||||
|  |         ubyte pn = 0 | ||||||
|  |  | ||||||
|  |         init(number) | ||||||
|  | ;        txt.print("\nGalaxy #") | ||||||
|  | ;        txt.print_ub(number) | ||||||
|  | ;        txt.print(" - systems in vicinity:\n") | ||||||
|  |         do { | ||||||
|  |             generate_next_planet() | ||||||
|  |             ubyte distance = elite_planet.distance(px, py) | ||||||
|  |             if distance <= elite_ship.Max_fuel { | ||||||
|  | ;                if distance <= elite_ship.fuel | ||||||
|  | ;                    txt.chrout('*') | ||||||
|  | ;                else | ||||||
|  | ;                    txt.chrout('-') | ||||||
|  | ;                txt.spc() | ||||||
|  |                 elite_planet.name = make_current_planet_name() | ||||||
|  |                 elite_planet.display(true, distance) | ||||||
|  |             } | ||||||
|  |             pn++ | ||||||
|  |         } until pn==0 | ||||||
|  |  | ||||||
|  |         travel_to(number, current_planet) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub starmap(bool local) { | ||||||
|  |         ubyte current_planet = elite_planet.number | ||||||
|  |         ubyte px = elite_planet.x | ||||||
|  |         ubyte py = elite_planet.y | ||||||
|  |         str current_name = "        "       ; 8 max | ||||||
|  |         ubyte pn = 0 | ||||||
|  |  | ||||||
|  |         current_name = elite_planet.name | ||||||
|  |         init(number) | ||||||
|  | ;        txt.clear_screen() | ||||||
|  | ;        txt.print("Galaxy #") | ||||||
|  | ;        txt.print_ub(number) | ||||||
|  | ;        if local | ||||||
|  | ;            txt.print(" - local systems") | ||||||
|  | ;        else | ||||||
|  | ;            txt.print(" - galaxy") | ||||||
|  | ;        txt.print(" starmap:\n") | ||||||
|  |         ubyte max_distance = 255 | ||||||
|  |         if local | ||||||
|  |             max_distance = elite_ship.Max_fuel | ||||||
|  |         ubyte home_sx | ||||||
|  |         ubyte home_sy | ||||||
|  |         ubyte home_distance | ||||||
|  |  | ||||||
|  |         do { | ||||||
|  |             generate_next_planet() | ||||||
|  |             ubyte distance = elite_planet.distance(px, py) | ||||||
|  |             if distance <= max_distance { | ||||||
|  |                 elite_planet.name = make_current_planet_name() | ||||||
|  |                 elite_planet.name[0] = strings.upperchar(elite_planet.name[0]) | ||||||
|  |                 uword tx = elite_planet.x | ||||||
|  |                 uword ty = elite_planet.y | ||||||
|  |                 if local { | ||||||
|  |                     tx = tx + 24 - px | ||||||
|  |                     ty = ty + 24 - py | ||||||
|  |                 } | ||||||
|  |                 ubyte sx = display_scale_x(tx) | ||||||
|  |                 ubyte sy = display_scale_y(ty) | ||||||
|  |                 ubyte char = '*' | ||||||
|  |                 if elite_planet.number==current_planet | ||||||
|  |                     char = '%' | ||||||
|  |                 if local { | ||||||
|  |                     print_planet_details(elite_planet.name, sx, sy, distance) | ||||||
|  |                 } else if elite_planet.number==current_planet { | ||||||
|  |                     home_distance = distance | ||||||
|  |                     home_sx = sx | ||||||
|  |                     home_sy = sy | ||||||
|  |                 } | ||||||
|  |                 ; txt.setchr(2+sx, 2+sy, char) | ||||||
|  |             } | ||||||
|  |             pn++ | ||||||
|  |         } until pn==0 | ||||||
|  |  | ||||||
|  |         if not local | ||||||
|  |             print_planet_details(current_name, home_sx, home_sy, home_distance) | ||||||
|  |  | ||||||
|  | ;        if local | ||||||
|  | ;            txt.plot(0, display_scale_y(64) + 4) | ||||||
|  | ;        else | ||||||
|  | ;            txt.plot(0, display_scale_y(256) + 4 as ubyte) | ||||||
|  |         travel_to(number, current_planet) | ||||||
|  |  | ||||||
|  |         sub print_planet_details(str name, ubyte screenx, ubyte screeny, ubyte d) { | ||||||
|  |             return | ||||||
|  | ;            txt.plot(2+screenx-2, 2+screeny+1) | ||||||
|  | ;            txt.print(name) | ||||||
|  | ;            if d!=0 { | ||||||
|  | ;                txt.plot(2+screenx-2, 2+screeny+2) | ||||||
|  | ;                elite_util.print_10s(d) | ||||||
|  | ;                txt.print(" LY") | ||||||
|  | ;            } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub display_scale_x(uword x) -> ubyte { | ||||||
|  |             if local | ||||||
|  |                 return x/2 as ubyte | ||||||
|  |             return x/8 as ubyte | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub display_scale_y(uword y) -> ubyte { | ||||||
|  |             if local | ||||||
|  |                 return y/4 as ubyte | ||||||
|  |             return y/16 as ubyte | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ubyte pn_pair1 | ||||||
|  |     ubyte pn_pair2 | ||||||
|  |     ubyte pn_pair3 | ||||||
|  |     ubyte pn_pair4 | ||||||
|  |     bool longname | ||||||
|  |  | ||||||
|  |     sub generate_next_planet() { | ||||||
|  |         determine_planet_properties() | ||||||
|  |         longname = lsb(seed[0]) & 64 !=0 | ||||||
|  |  | ||||||
|  |         ; Always four iterations of random number | ||||||
|  |         pn_pair1 = (msb(seed[2]) & 31) * 2 | ||||||
|  |         tweakseed() | ||||||
|  |         pn_pair2 = (msb(seed[2]) & 31) * 2 | ||||||
|  |         tweakseed() | ||||||
|  |         pn_pair3 = (msb(seed[2]) & 31) * 2 | ||||||
|  |         tweakseed() | ||||||
|  |         pn_pair4 = (msb(seed[2]) & 31) * 2 | ||||||
|  |         tweakseed() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub make_current_planet_name() -> str { | ||||||
|  |         ubyte ni = 0 | ||||||
|  |         str name = "         "    ; max 8 | ||||||
|  |  | ||||||
|  |         if pn_pairs[pn_pair1] != '.' { | ||||||
|  |             name[ni] = pn_pairs[pn_pair1] | ||||||
|  |             ni++ | ||||||
|  |         } | ||||||
|  |         if pn_pairs[pn_pair1+1] != '.' { | ||||||
|  |             name[ni] = pn_pairs[pn_pair1+1] | ||||||
|  |             ni++ | ||||||
|  |         } | ||||||
|  |         if pn_pairs[pn_pair2] != '.' { | ||||||
|  |             name[ni] = pn_pairs[pn_pair2] | ||||||
|  |             ni++ | ||||||
|  |         } | ||||||
|  |         if pn_pairs[pn_pair2+1] != '.' { | ||||||
|  |             name[ni] = pn_pairs[pn_pair2+1] | ||||||
|  |             ni++ | ||||||
|  |         } | ||||||
|  |         if pn_pairs[pn_pair3] != '.' { | ||||||
|  |             name[ni] = pn_pairs[pn_pair3] | ||||||
|  |             ni++ | ||||||
|  |         } | ||||||
|  |         if pn_pairs[pn_pair3+1] != '.' { | ||||||
|  |             name[ni] = pn_pairs[pn_pair3+1] | ||||||
|  |             ni++ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if longname { | ||||||
|  |             if pn_pairs[pn_pair4] != '.' { | ||||||
|  |                 name[ni] = pn_pairs[pn_pair4] | ||||||
|  |                 ni++ | ||||||
|  |             } | ||||||
|  |             if pn_pairs[pn_pair4+1] != '.' { | ||||||
|  |                 name[ni] = pn_pairs[pn_pair4+1] | ||||||
|  |                 ni++ | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         name[ni] = 0 | ||||||
|  |         return name | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub determine_planet_properties() { | ||||||
|  |         ; create the planet's characteristics | ||||||
|  |         elite_planet.number++ | ||||||
|  |         elite_planet.x = msb(seed[1]) | ||||||
|  |         elite_planet.y = msb(seed[0]) | ||||||
|  |         elite_planet.govtype = lsb(seed[1]) >> 3 & 7  ; bits 3,4 &5 of w1 | ||||||
|  |         elite_planet.economy = msb(seed[0]) & 7  ; bits 8,9 &A of w0 | ||||||
|  |         if elite_planet.govtype <= 1 | ||||||
|  |             elite_planet.economy = (elite_planet.economy | 2) | ||||||
|  |         elite_planet.techlevel = (msb(seed[1]) & 3) + (elite_planet.economy ^ 7) | ||||||
|  |         elite_planet.techlevel += elite_planet.govtype >> 1 | ||||||
|  |         if elite_planet.govtype & 1 !=0 | ||||||
|  |             elite_planet.techlevel++ | ||||||
|  |         elite_planet.population = 4 * elite_planet.techlevel + elite_planet.economy | ||||||
|  |         elite_planet.population += elite_planet.govtype + 1 | ||||||
|  |         elite_planet.productivity = ((elite_planet.economy ^ 7) + 3) * (elite_planet.govtype + 4) | ||||||
|  |         elite_planet.productivity *= elite_planet.population * 8 | ||||||
|  |         ubyte seed2_msb = msb(seed[2]) | ||||||
|  |         elite_planet.radius = mkword((seed2_msb & 15) + 11, elite_planet.x) | ||||||
|  |         elite_planet.species_is_alien = lsb(seed[2]) & 128 !=0      ; bit 7 of w2_lo | ||||||
|  |         if elite_planet.species_is_alien { | ||||||
|  |             elite_planet.species_size = (seed2_msb >> 2) & 7      ; bits 2-4 of w2_hi | ||||||
|  |             elite_planet.species_color = seed2_msb >> 5           ; bits 5-7 of w2_hi | ||||||
|  |             elite_planet.species_look = (seed2_msb ^ msb(seed[1])) & 7   ;bits 0-2 of (w0_hi EOR w1_hi) | ||||||
|  |             elite_planet.species_kind = (elite_planet.species_look + (seed2_msb & 3)) & 7      ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         elite_planet.goatsoup_seed[0] = lsb(seed[1]) | ||||||
|  |         elite_planet.goatsoup_seed[1] = msb(seed[1]) | ||||||
|  |         elite_planet.goatsoup_seed[2] = lsb(seed[2]) | ||||||
|  |         elite_planet.goatsoup_seed[3] = seed2_msb | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub tweakseed() { | ||||||
|  |         uword temp = seed[0] + seed[1] + seed[2] | ||||||
|  |         seed[0] = seed[1] | ||||||
|  |         seed[1] = seed[2] | ||||||
|  |         seed[2] = temp | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub twist(uword x) -> uword { | ||||||
|  |         ubyte xh = msb(x) | ||||||
|  |         ubyte xl = lsb(x) | ||||||
|  |         xh <<= 1        ; make sure carry flag is not used on first shift! | ||||||
|  |         rol(xl) | ||||||
|  |         return mkword(xh, xl) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | elite_planet { | ||||||
|  |     str[] @nosplit words81 = ["fabled", "notable", "well known", "famous", "noted"] | ||||||
|  |     str[] @nosplit words82 = ["very", "mildly", "most", "reasonably", ""] | ||||||
|  |     str[] @nosplit words83 = ["ancient", "\x95", "great", "vast", "pink"] | ||||||
|  |     str[] @nosplit words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"] | ||||||
|  |     str[] @nosplit words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"] | ||||||
|  |     str[] @nosplit words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"] | ||||||
|  |     str[] @nosplit words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"] | ||||||
|  |     str[] @nosplit words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"] | ||||||
|  |     str[] @nosplit words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"] | ||||||
|  |     str[] @nosplit words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"] | ||||||
|  |     str[] @nosplit words8B = ["juice", "brandy", "water", "brew", "gargle blasters"] | ||||||
|  |     str[] @nosplit words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"] | ||||||
|  |     str[] @nosplit words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"] | ||||||
|  |     str[] @nosplit words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "] | ||||||
|  |     str[] @nosplit words8F = ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"] | ||||||
|  |     str[] @nosplit words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"] | ||||||
|  |     str[] @nosplit words91 = ["planet", "world", "place", "little planet", "dump"] | ||||||
|  |     str[] @nosplit words92 = ["wasp", "moth", "grub", "ant", "\xB2"] | ||||||
|  |     str[] @nosplit words93 = ["poet", "arts graduate", "yak", "snail", "slug"] | ||||||
|  |     str[] @nosplit words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"] | ||||||
|  |     str[] @nosplit words95 = ["funny", "wierd", "unusual", "strange", "peculiar"] | ||||||
|  |     str[] @nosplit words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"] | ||||||
|  |     str[] @nosplit words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"] | ||||||
|  |     str[] @nosplit words98 = ["\x9B", "mountain", "edible", "tree", "spotted"] | ||||||
|  |     str[] @nosplit words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"] | ||||||
|  |     str[] @nosplit words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"] | ||||||
|  |     str[] @nosplit words9B = ["killer", "deadly", "evil", "lethal", "vicious"] | ||||||
|  |     str[] @nosplit words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"] | ||||||
|  |     str[] @nosplit words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"] | ||||||
|  |     str[] @nosplit words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"] | ||||||
|  |     str[] @nosplit words9F = ["shrew", "beast", "bison", "snake", "wolf"] | ||||||
|  |     str[] @nosplit wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"] | ||||||
|  |     str[] @nosplit wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"] | ||||||
|  |     str[] @nosplit wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"] | ||||||
|  |     str[] @nosplit wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"] | ||||||
|  |     str[] @nosplit wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"] | ||||||
|  |  | ||||||
|  |     uword[] @shared wordlists = [ | ||||||
|  |         words81, words82, words83, words84, words85, words86, words87, words88, | ||||||
|  |         words89, words8A, words8B, words8C, words8D, words8E, words8F, words90, | ||||||
|  |         words91, words92, words93, words94, words95, words96, words97, words98, | ||||||
|  |         words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0, | ||||||
|  |         wordsA1, wordsA2, wordsA3, wordsA4] | ||||||
|  |  | ||||||
|  |     str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe" | ||||||
|  |  | ||||||
|  |     ubyte[4] goatsoup_rnd = [0, 0, 0, 0] | ||||||
|  |     ubyte[4] goatsoup_seed = [0, 0, 0, 0] | ||||||
|  |  | ||||||
|  |     str name = "        "       ; 8 max | ||||||
|  |     ubyte number                ; starts at 0 in new galaxy, then increases by 1 for each generated planet | ||||||
|  |     ubyte x | ||||||
|  |     ubyte y | ||||||
|  |     ubyte economy | ||||||
|  |     ubyte govtype | ||||||
|  |     ubyte techlevel | ||||||
|  |     ubyte population | ||||||
|  |     uword productivity | ||||||
|  |     uword radius | ||||||
|  |     bool species_is_alien      ; otherwise "Human Colonials" | ||||||
|  |     ubyte species_size | ||||||
|  |     ubyte species_color | ||||||
|  |     ubyte species_look | ||||||
|  |     ubyte species_kind | ||||||
|  |  | ||||||
|  |     sub set_seed(uword s1, uword s2) { | ||||||
|  |         goatsoup_seed[0] = lsb(s1) | ||||||
|  |         goatsoup_seed[1] = msb(s1) | ||||||
|  |         goatsoup_seed[2] = lsb(s2) | ||||||
|  |         goatsoup_seed[3] = msb(s2) | ||||||
|  |         reset_rnd() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub reset_rnd() { | ||||||
|  |         goatsoup_rnd[0] = goatsoup_seed[0] | ||||||
|  |         goatsoup_rnd[1] = goatsoup_seed[1] | ||||||
|  |         goatsoup_rnd[2] = goatsoup_seed[2] | ||||||
|  |         goatsoup_rnd[3] = goatsoup_seed[3] | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub random_name() -> str { | ||||||
|  |         ubyte ii | ||||||
|  |         str randname = "        "       ; 8 chars max | ||||||
|  |         ubyte nx = 0 | ||||||
|  |         for ii in 0 to goatsoup_rnd_number() & 3 { | ||||||
|  |             ubyte xx = goatsoup_rnd_number() & $3e | ||||||
|  |             if pairs0[xx] != '.' { | ||||||
|  |                 randname[nx] = pairs0[xx] | ||||||
|  |                 nx++ | ||||||
|  |             } | ||||||
|  |             xx++ | ||||||
|  |             if pairs0[xx] != '.' { | ||||||
|  |                 randname[nx] = pairs0[xx] | ||||||
|  |                 nx++ | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         randname[nx] = 0 | ||||||
|  |         randname[0] = strings.upperchar(randname[0]) | ||||||
|  |         return randname | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub goatsoup_rnd_number() -> ubyte { | ||||||
|  |         ubyte xx = goatsoup_rnd[0] * 2 | ||||||
|  |         uword a = xx as uword + goatsoup_rnd[2] | ||||||
|  |         if goatsoup_rnd[0] > 127 | ||||||
|  |             a ++ | ||||||
|  |         goatsoup_rnd[0] = lsb(a) | ||||||
|  |         goatsoup_rnd[2] = xx | ||||||
|  |         xx = goatsoup_rnd[1] | ||||||
|  |         ubyte ac = xx + goatsoup_rnd[3] + msb(a) | ||||||
|  |         goatsoup_rnd[1] = ac | ||||||
|  |         goatsoup_rnd[3] = xx | ||||||
|  |         return ac | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub distance(ubyte px, ubyte py) -> ubyte { | ||||||
|  |         uword ax | ||||||
|  |         uword ay | ||||||
|  |         if px>x | ||||||
|  |             ax=px-x | ||||||
|  |         else | ||||||
|  |             ax=x-px | ||||||
|  |         if py>y | ||||||
|  |             ay=py-y | ||||||
|  |         else | ||||||
|  |             ay=y-py | ||||||
|  |         ay /= 2 | ||||||
|  |         ubyte d = sqrt(ax*ax + ay*ay) | ||||||
|  |         if d>63 | ||||||
|  |             return 255 | ||||||
|  |         return d*4 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub soup() -> str { | ||||||
|  |         str planet_result = " " * 160 | ||||||
|  |         uword[6] source_stack | ||||||
|  |         ubyte stack_ptr = 0 | ||||||
|  |         str start_source = "\x8F is \x97." | ||||||
|  |         uword source_ptr = &start_source | ||||||
|  |         uword result_ptr = &planet_result | ||||||
|  |  | ||||||
|  |         reset_rnd() | ||||||
|  |         recursive_soup() | ||||||
|  |         return planet_result | ||||||
|  |  | ||||||
|  |         sub recursive_soup() { | ||||||
|  |             repeat { | ||||||
|  |                 ubyte c = @(source_ptr) | ||||||
|  |                 source_ptr++ | ||||||
|  |                 if c == $00 { | ||||||
|  |                     @(result_ptr) = 0 | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 else if c <= $80 { | ||||||
|  |                     @(result_ptr) = c | ||||||
|  |                     result_ptr++ | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     if c <= $a4 { | ||||||
|  |                         ubyte rnr = goatsoup_rnd_number() | ||||||
|  |                         ubyte wordNr = ((rnr >= $33) as ubyte) + ((rnr >= $66) as ubyte) + ((rnr >= $99) as ubyte) + ((rnr >= $CC) as ubyte) | ||||||
|  |                         source_stack[stack_ptr] = source_ptr | ||||||
|  |                         stack_ptr++ | ||||||
|  |                         source_ptr = getword(c, wordNr) | ||||||
|  |                         recursive_soup()    ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case | ||||||
|  |                         stack_ptr-- | ||||||
|  |                         source_ptr = source_stack[stack_ptr] | ||||||
|  |                     } else { | ||||||
|  |                         if c == $b0 { | ||||||
|  |                             @(result_ptr) = strings.upperchar(name[0]) | ||||||
|  |                             result_ptr++ | ||||||
|  |                             concat_string(&name + 1) | ||||||
|  |                         } | ||||||
|  |                         else if c == $b1 { | ||||||
|  |                             @(result_ptr) = strings.upperchar(name[0]) | ||||||
|  |                             result_ptr++ | ||||||
|  |                             ubyte ni | ||||||
|  |                             for ni in 1 to len(name) { | ||||||
|  |                                 ubyte cc = name[ni] | ||||||
|  |                                 if cc in ['e', 'o', 0] | ||||||
|  |                                     break | ||||||
|  |                                 else { | ||||||
|  |                                     @(result_ptr) = cc | ||||||
|  |                                     result_ptr++ | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                             @(result_ptr) = 'i' | ||||||
|  |                             result_ptr++ | ||||||
|  |                             @(result_ptr) = 'a' | ||||||
|  |                             result_ptr++ | ||||||
|  |                             @(result_ptr) = 'n' | ||||||
|  |                             result_ptr++ | ||||||
|  |                         } | ||||||
|  |                         else if c == $b2 { | ||||||
|  |                             concat_string(random_name()) | ||||||
|  |                         } | ||||||
|  |                         else { | ||||||
|  |                             @(result_ptr) = c | ||||||
|  |                             result_ptr++ | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub concat_string(uword str_ptr) { | ||||||
|  |             repeat { | ||||||
|  |                 ubyte c = @(str_ptr) | ||||||
|  |                 if c==0 | ||||||
|  |                     break | ||||||
|  |                 else { | ||||||
|  |                     @(result_ptr) = c | ||||||
|  |                     str_ptr++ | ||||||
|  |                     result_ptr++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub display(bool compressed, ubyte distance) { | ||||||
|  |         txt.print(soup()) | ||||||
|  |         txt.nl() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub getword(ubyte listnum, ubyte wordidx) -> uword { | ||||||
|  |         uword list = wordlists[listnum-$81] | ||||||
|  |         return peekw(list + wordidx*2) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | elite_util { | ||||||
|  |     sub prefix_matches(uword prefixptr, uword stringptr) -> bool { | ||||||
|  |         repeat { | ||||||
|  |             ubyte pc = @(prefixptr) | ||||||
|  |             ubyte sc = @(stringptr) | ||||||
|  |             if pc == 0 | ||||||
|  |                 return true | ||||||
|  |             ; to lowercase for case insensitive compare: | ||||||
|  |             if strings.lowerchar(pc)!=strings.lowerchar(sc) | ||||||
|  |                 return false | ||||||
|  |             prefixptr++ | ||||||
|  |             stringptr++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								benchmark-program/benchmark.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								benchmark-program/benchmark.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  |  | ||||||
|  | ;  This benchmark program is meant to check for regressions in the | ||||||
|  | ;  Prog8 compiler's code-generator (performance wise). | ||||||
|  | ; | ||||||
|  | ;  As the X16 computer is a more or less fixed system, it's not very useful | ||||||
|  | ;  to benchmark the computer itself with. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | %import textio | ||||||
|  | %import b_adpcm | ||||||
|  | %import b_circles | ||||||
|  | %import b_3d | ||||||
|  | %import b_life | ||||||
|  | %import b_mandelbrot | ||||||
|  | %import b_queens | ||||||
|  | %import b_textelite | ||||||
|  | %import b_maze | ||||||
|  | %import b_sprites | ||||||
|  |  | ||||||
|  | %zeropage basicsafe | ||||||
|  | %option no_sysinit | ||||||
|  |  | ||||||
|  |  | ||||||
|  | main { | ||||||
|  |  | ||||||
|  |     str[20] benchmark_names | ||||||
|  |     uword[20] benchmark_score | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     sub start() { | ||||||
|  |         ubyte benchmark_number | ||||||
|  |  | ||||||
|  |         cx16.set_screen_mode(3) | ||||||
|  |         txt.color2(1, 6) | ||||||
|  |         txt.clear_screen() | ||||||
|  |  | ||||||
|  |         txt.print("\n\n\n  prog8 compiler benchmark tests.\n") | ||||||
|  |         sys.wait(60) | ||||||
|  |  | ||||||
|  |         benchmark_number = 0 | ||||||
|  |  | ||||||
|  |         announce_benchmark("maze solver") | ||||||
|  |         benchmark_score[benchmark_number]  = maze.bench(300) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("n-queens") | ||||||
|  |         benchmark_score[benchmark_number]  = queens.bench(300) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("mandelbrot (floating point)") | ||||||
|  |         benchmark_score[benchmark_number]  = mandelbrot.calc(400) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("game of life") | ||||||
|  |         benchmark_score[benchmark_number]  = life.benchmark(300) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("3d model rotation") | ||||||
|  |         benchmark_score[benchmark_number]  = rotate3d.benchmark(300) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("adpcm audio decoding") | ||||||
|  |         benchmark_score[benchmark_number]  = adpcm.decode_benchmark(300) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("circles with gfx_lores") | ||||||
|  |         benchmark_score[benchmark_number]  = circles.draw(false, 300) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  | ;        announce_benchmark("circles with kernal") | ||||||
|  | ;        benchmark_score[benchmark_number]  = circles.draw(true, 300) | ||||||
|  | ;        benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("text-elite") | ||||||
|  |         benchmark_score[benchmark_number]  = textelite.bench(120) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         announce_benchmark("sprites-coroutines-defer") | ||||||
|  |         benchmark_score[benchmark_number]  = animsprites.benchmark(300) | ||||||
|  |         benchmark_number++ | ||||||
|  |  | ||||||
|  |         benchmark_names[benchmark_number] = 0 | ||||||
|  |         benchmark_score[benchmark_number] = 0 | ||||||
|  |  | ||||||
|  |         cx16.set_screen_mode(3) | ||||||
|  |         txt.uppercase() | ||||||
|  |         txt.color2(1, 6) | ||||||
|  |         uword final_score | ||||||
|  |         benchmark_number = 0 | ||||||
|  |         txt.print("\nscore benchmark\n\n") | ||||||
|  |         do { | ||||||
|  |             txt.spc() | ||||||
|  |             txt.print_uw(benchmark_score[benchmark_number]) | ||||||
|  |             txt.column(6) | ||||||
|  |             txt.print(benchmark_names[benchmark_number]) | ||||||
|  |             final_score += benchmark_score[benchmark_number] | ||||||
|  |             txt.nl() | ||||||
|  |             benchmark_number++ | ||||||
|  |         } until benchmark_names[benchmark_number]==0 | ||||||
|  |  | ||||||
|  |         txt.print("\n\nfinal score : ") | ||||||
|  |         txt.print_uw(final_score) | ||||||
|  |         txt.nl() | ||||||
|  |  | ||||||
|  |         sub announce_benchmark(str name) { | ||||||
|  |             benchmark_names[benchmark_number] = name | ||||||
|  |             cx16.set_screen_mode(3) | ||||||
|  |             txt.uppercase() | ||||||
|  |             txt.color2(1, 6) | ||||||
|  |             txt.clear_screen() | ||||||
|  |             txt.plot(4, 6) | ||||||
|  |             txt.print(benchmark_names[benchmark_number]) | ||||||
|  |             txt.nl() | ||||||
|  |             sys.wait(60) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -1,10 +0,0 @@ | |||||||
| plugins { |  | ||||||
|     id "org.jetbrains.kotlin.jvm" version "$kotlinVersion" apply false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| allprojects { |  | ||||||
|     repositories { |  | ||||||
|         mavenLocal() |  | ||||||
|         mavenCentral() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										38
									
								
								build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | import org.jetbrains.kotlin.gradle.dsl.JvmTarget | ||||||
|  | import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode | ||||||
|  | import org.jetbrains.kotlin.gradle.internal.config.LanguageFeature | ||||||
|  | import org.jetbrains.kotlin.gradle.dsl.KotlinVersion | ||||||
|  |  | ||||||
|  |  | ||||||
|  | plugins { | ||||||
|  |     kotlin("jvm") version "2.2.20" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | allprojects { | ||||||
|  |     apply(plugin="kotlin") | ||||||
|  |  | ||||||
|  |     repositories { | ||||||
|  |         mavenLocal() | ||||||
|  |         mavenCentral() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     kotlin { | ||||||
|  |         compilerOptions { | ||||||
|  |             freeCompilerArgs = listOf("-Xwhen-guards") | ||||||
|  |             jvmTarget = JvmTarget.JVM_11 | ||||||
|  |             jvmDefault = JvmDefaultMode.NO_COMPATIBILITY | ||||||
|  |             // languageVersion.set(KotlinVersion.KOTLIN_2_3) | ||||||
|  |         } | ||||||
|  |         sourceSets.all { | ||||||
|  |             languageSettings { | ||||||
|  |                 // enable language features like so: | ||||||
|  |                 // enableLanguageFeature(LanguageFeature.WhenGuards.name) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     java { | ||||||
|  |         targetCompatibility = JavaVersion.VERSION_11 | ||||||
|  |         sourceCompatibility = JavaVersion.VERSION_11 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,42 +0,0 @@ | |||||||
|  |  | ||||||
| plugins { |  | ||||||
|     id 'java' |  | ||||||
|     id 'application' |  | ||||||
|     id "org.jetbrains.kotlin.jvm" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| java { |  | ||||||
|     targetCompatibility = JavaLanguageVersion.of(javaVersion) |  | ||||||
|     sourceCompatibility = JavaLanguageVersion.of(javaVersion) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| compileKotlin { |  | ||||||
|     kotlinOptions { |  | ||||||
|         jvmTarget = javaVersion |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| compileTestKotlin { |  | ||||||
|     kotlinOptions { |  | ||||||
|         jvmTarget = javaVersion |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| dependencies { |  | ||||||
|     // should have no dependencies to other modules |  | ||||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" |  | ||||||
|     implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| sourceSets { |  | ||||||
|     main { |  | ||||||
|         java { |  | ||||||
|             srcDir "${project.projectDir}/src" |  | ||||||
|         } |  | ||||||
|         resources { |  | ||||||
|             srcDir "${project.projectDir}/res" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // note: there are no unit tests in this module! |  | ||||||
							
								
								
									
										22
									
								
								codeCore/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								codeCore/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | plugins { | ||||||
|  |     kotlin("jvm") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     // should have no dependencies to other modules | ||||||
|  |     // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") | ||||||
|  |     implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sourceSets { | ||||||
|  |     main { | ||||||
|  |         java { | ||||||
|  |             srcDir("${project.projectDir}/src") | ||||||
|  |         } | ||||||
|  |         resources { | ||||||
|  |             srcDir("${project.projectDir}/res") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // note: there are no unit tests in this module! | ||||||
							
								
								
									
										31
									
								
								codeCore/src/prog8/code/Globals.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								codeCore/src/prog8/code/Globals.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | package prog8.code | ||||||
|  |  | ||||||
|  | import java.io.IOException | ||||||
|  | import java.nio.file.Path | ||||||
|  | import kotlin.io.path.absolute | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // the automatically generated module where all string literals are interned to: | ||||||
|  | const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings" | ||||||
|  |  | ||||||
|  | // all automatically generated labels everywhere need to have the same label name prefix: | ||||||
|  | const val GENERATED_LABEL_PREFIX = "p8_label_gen_" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Returns the absolute path of the given path, | ||||||
|  |  * where links are replaced by the actual directories, | ||||||
|  |  * and containing no redundant path elements. | ||||||
|  |  * If the path doesn't refer to an existing directory or file on the file system, | ||||||
|  |  * it is returned unchanged. | ||||||
|  |  */ | ||||||
|  | fun Path.sanitize(): Path { | ||||||
|  |     return try { | ||||||
|  |         this.toRealPath().normalize() | ||||||
|  |     } catch (_: java.nio.file.NoSuchFileException) { | ||||||
|  |         this.absolute().normalize() | ||||||
|  |         //throw NoSuchFileException(this.toFile(), null, nx.reason).also { it.initCause(nx) } | ||||||
|  |     } catch (iox: IOException) { | ||||||
|  |         throw FileSystemException(this.toFile()).also { it.initCause(iox) } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,256 +0,0 @@ | |||||||
| package prog8.code |  | ||||||
|  |  | ||||||
| import prog8.code.ast.PtNode |  | ||||||
| import prog8.code.ast.PtProgram |  | ||||||
| import prog8.code.core.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Tree structure containing all symbol definitions in the program |  | ||||||
|  * (blocks, subroutines, variables (all types), memoryslabs, and labels). |  | ||||||
|  */ |  | ||||||
| class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GLOBAL, astProgram) { |  | ||||||
|     /** |  | ||||||
|      * The table as a flat mapping of scoped names to the StNode. |  | ||||||
|      * This gives the fastest lookup possible (no need to traverse tree nodes) |  | ||||||
|      */ |  | ||||||
|  |  | ||||||
|     private var cachedFlat: Map<String, StNode>? = null |  | ||||||
|  |  | ||||||
|     val flat: Map<String, StNode> get()  { |  | ||||||
|         if(cachedFlat!=null) |  | ||||||
|             return cachedFlat!! |  | ||||||
|  |  | ||||||
|         val result = mutableMapOf<String, StNode>() |  | ||||||
|         fun collect(node: StNode) { |  | ||||||
|             for(child in node.children) { |  | ||||||
|                 result[child.value.scopedName] = child.value |  | ||||||
|                 collect(child.value) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         collect(this) |  | ||||||
|         cachedFlat = result |  | ||||||
|         return result |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun resetCachedFlat() { |  | ||||||
|         cachedFlat = null |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val allVariables: Collection<StStaticVariable> by lazy { |  | ||||||
|         val vars = mutableListOf<StStaticVariable>() |  | ||||||
|         fun collect(node: StNode) { |  | ||||||
|             for(child in node.children) { |  | ||||||
|                 if(child.value.type== StNodeType.STATICVAR) |  | ||||||
|                     vars.add(child.value as StStaticVariable) |  | ||||||
|                 else |  | ||||||
|                     collect(child.value) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         collect(this) |  | ||||||
|         vars |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val allMemMappedVariables: Collection<StMemVar> by lazy { |  | ||||||
|         val vars = mutableListOf<StMemVar>() |  | ||||||
|         fun collect(node: StNode) { |  | ||||||
|             for(child in node.children) { |  | ||||||
|                 if(child.value.type== StNodeType.MEMVAR) |  | ||||||
|                     vars.add(child.value as StMemVar) |  | ||||||
|                 else |  | ||||||
|                     collect(child.value) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         collect(this) |  | ||||||
|         vars |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val allMemorySlabs: Collection<StMemorySlab> by lazy { |  | ||||||
|         val vars = mutableListOf<StMemorySlab>() |  | ||||||
|         fun collect(node: StNode) { |  | ||||||
|             for(child in node.children) { |  | ||||||
|                 if(child.value.type== StNodeType.MEMORYSLAB) |  | ||||||
|                     vars.add(child.value as StMemorySlab) |  | ||||||
|                 else |  | ||||||
|                     collect(child.value) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         collect(this) |  | ||||||
|         vars |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun lookup(scopedName: String) = flat[scopedName] |  | ||||||
|  |  | ||||||
|     fun getLength(name: String): Int? { |  | ||||||
|         return when(val node = flat[name]) { |  | ||||||
|             is StMemVar -> node.length |  | ||||||
|             is StMemorySlab -> node.size.toInt() |  | ||||||
|             is StStaticVariable -> node.length |  | ||||||
|             else -> null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| enum class StNodeType { |  | ||||||
|     GLOBAL, |  | ||||||
|     // MODULE,     // not used with current scoping rules |  | ||||||
|     BLOCK, |  | ||||||
|     SUBROUTINE, |  | ||||||
|     ROMSUB, |  | ||||||
|     LABEL, |  | ||||||
|     STATICVAR, |  | ||||||
|     MEMVAR, |  | ||||||
|     CONSTANT, |  | ||||||
|     BUILTINFUNC, |  | ||||||
|     MEMORYSLAB |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| open class StNode(val name: String, |  | ||||||
|                   val type: StNodeType, |  | ||||||
|                   val astNode: PtNode, |  | ||||||
|                   val children: MutableMap<String, StNode> = mutableMapOf() |  | ||||||
| ) { |  | ||||||
|  |  | ||||||
|     lateinit var parent: StNode |  | ||||||
|  |  | ||||||
|     val scopedName: String by lazy { scopedNameList.joinToString(".") } |  | ||||||
|  |  | ||||||
|     open fun lookup(scopedName: String) = |  | ||||||
|         lookup(scopedName.split('.')) |  | ||||||
|  |  | ||||||
|     fun lookupUnscopedOrElse(name: String, default: () -> StNode) = |  | ||||||
|         lookupUnscoped(name) ?: default() |  | ||||||
|  |  | ||||||
|     fun lookupOrElse(scopedName: String, default: () -> StNode): StNode = |  | ||||||
|         lookup(scopedName.split('.')) ?: default() |  | ||||||
|  |  | ||||||
|     fun lookupUnscoped(name: String): StNode? { |  | ||||||
|         // first consider the builtin functions |  | ||||||
|         var globalscope = this |  | ||||||
|         while(globalscope.type!= StNodeType.GLOBAL) |  | ||||||
|             globalscope = globalscope.parent |  | ||||||
|         val globalNode = globalscope.children[name] |  | ||||||
|         if(globalNode!=null && globalNode.type== StNodeType.BUILTINFUNC) |  | ||||||
|             return globalNode |  | ||||||
|  |  | ||||||
|         // search for the unqualified name in the current scope or its parent scopes |  | ||||||
|         var scope=this |  | ||||||
|         while(true) { |  | ||||||
|             val node = scope.children[name] |  | ||||||
|             if(node!=null) |  | ||||||
|                 return node |  | ||||||
|             if(scope.type== StNodeType.GLOBAL) |  | ||||||
|                 return null |  | ||||||
|             else |  | ||||||
|                 scope = scope.parent |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun add(child: StNode) { |  | ||||||
|         children[child.name] = child |  | ||||||
|         child.parent = this |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private val scopedNameList: List<String> by lazy { |  | ||||||
|         if(type==StNodeType.GLOBAL) |  | ||||||
|             emptyList() |  | ||||||
|         else |  | ||||||
|             parent.scopedNameList + name |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun lookup(scopedName: List<String>): StNode? { |  | ||||||
|         // a scoped name refers to a name in another namespace, and always stars from the root. |  | ||||||
|         var node = this |  | ||||||
|         while(node.type!=StNodeType.GLOBAL) |  | ||||||
|             node = node.parent |  | ||||||
|  |  | ||||||
|         for(name in scopedName) { |  | ||||||
|             if(name in node.children) |  | ||||||
|                 node = node.children.getValue(name) |  | ||||||
|             else |  | ||||||
|                 return null |  | ||||||
|         } |  | ||||||
|         return node |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class StStaticVariable(name: String, |  | ||||||
|                        val dt: DataType, |  | ||||||
|                        val onetimeInitializationNumericValue: Double?,      // regular (every-run-time) initialization is done via regular assignments |  | ||||||
|                        val onetimeInitializationStringValue: StString?, |  | ||||||
|                        val onetimeInitializationArrayValue: StArray?, |  | ||||||
|                        val length: Int?,            // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte |  | ||||||
|                        val zpwish: ZeropageWish,    // used in the variable allocator |  | ||||||
|                        astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) { |  | ||||||
|  |  | ||||||
|     val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         if(length!=null) { |  | ||||||
|             require(onetimeInitializationNumericValue == null) |  | ||||||
|             if(onetimeInitializationArrayValue!=null) |  | ||||||
|                 require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length) |  | ||||||
|         } |  | ||||||
|         if(onetimeInitializationNumericValue!=null) { |  | ||||||
|             require(dt in NumericDatatypes || dt==DataType.BOOL) |  | ||||||
|         } |  | ||||||
|         if(onetimeInitializationArrayValue!=null) { |  | ||||||
|             require(dt in ArrayDatatypes) |  | ||||||
|             require(length==onetimeInitializationArrayValue.size) |  | ||||||
|         } |  | ||||||
|         if(onetimeInitializationStringValue!=null) { |  | ||||||
|             require(dt == DataType.STR) |  | ||||||
|             require(length == onetimeInitializationStringValue.first.length+1) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) : |  | ||||||
|     StNode(name, StNodeType.CONSTANT, astNode) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StMemVar(name: String, |  | ||||||
|                val dt: DataType, |  | ||||||
|                val address: UInt, |  | ||||||
|                val length: Int?,             // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte |  | ||||||
|                astNode: PtNode) : |  | ||||||
|     StNode(name, StNodeType.MEMVAR, astNode) { |  | ||||||
|  |  | ||||||
|     init{ |  | ||||||
|         require(dt!=DataType.BOOL && dt!=DataType.ARRAY_BOOL) |  | ||||||
|         if(dt in ArrayDatatypes || dt == DataType.STR) |  | ||||||
|             requireNotNull(length) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class StMemorySlab( |  | ||||||
|     name: String, |  | ||||||
|     val size: UInt, |  | ||||||
|     val align: UInt, |  | ||||||
|     astNode: PtNode |  | ||||||
| ): |  | ||||||
|     StNode(name, StNodeType.MEMORYSLAB, astNode) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) : |  | ||||||
|         StNode(name, StNodeType.SUBROUTINE, astNode) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StRomSub(name: String, |  | ||||||
|                val address: UInt?,      // null in case of asmsub, specified in case of romsub |  | ||||||
|                val parameters: List<StRomSubParameter>, |  | ||||||
|                val returns: List<StRomSubParameter>, |  | ||||||
|                astNode: PtNode) : |  | ||||||
|     StNode(name, StNodeType.ROMSUB, astNode) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class StSubroutineParameter(val name: String, val type: DataType) |  | ||||||
| class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType) |  | ||||||
| class StArrayElement(val number: Double?, val addressOfSymbol: String?, val boolean: Boolean?) |  | ||||||
|  |  | ||||||
| typealias StString = Pair<String, Encoding> |  | ||||||
| typealias StArray = List<StArrayElement> |  | ||||||
| @@ -1,180 +0,0 @@ | |||||||
| package prog8.code.ast |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sealed interface IPtSubroutine { |  | ||||||
|     val name: String |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class PtAsmSub( |  | ||||||
|     name: String, |  | ||||||
|     val address: UInt?, |  | ||||||
|     val clobbers: Set<CpuRegister>, |  | ||||||
|     val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>, |  | ||||||
|     val returns: List<Pair<RegisterOrStatusflag, DataType>>, |  | ||||||
|     val inline: Boolean, |  | ||||||
|     position: Position |  | ||||||
| ) : PtNamedNode(name, position), IPtSubroutine |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtSub( |  | ||||||
|     name: String, |  | ||||||
|     val parameters: List<PtSubroutineParameter>, |  | ||||||
|     val returntype: DataType?, |  | ||||||
|     position: Position |  | ||||||
| ) : PtNamedNode(name, position), IPtSubroutine, IPtStatementContainer { |  | ||||||
|     init { |  | ||||||
|         // params and return value should not be str |  | ||||||
|         if(parameters.any{ it.type !in NumericDatatypes && it.type!=DataType.BOOL }) |  | ||||||
|             throw AssemblyError("non-numeric/non-bool parameter") |  | ||||||
|         if(returntype!=null && returntype !in NumericDatatypes && returntype!=DataType.BOOL) |  | ||||||
|             throw AssemblyError("non-numeric/non-bool returntype $returntype") |  | ||||||
|         parameters.forEach { it.parent=this } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sealed interface IPtAssignment { |  | ||||||
|     val children: MutableList<PtNode> |  | ||||||
|     val target: PtAssignTarget |  | ||||||
|         get() { |  | ||||||
|             if(children.size==2) |  | ||||||
|                 return children[0] as PtAssignTarget |  | ||||||
|             else if(children.size<2) |  | ||||||
|                 throw AssemblyError("incomplete node") |  | ||||||
|             else |  | ||||||
|                 throw AssemblyError("no singular target") |  | ||||||
|         } |  | ||||||
|     val value: PtExpression |  | ||||||
|         get() = children.last() as PtExpression |  | ||||||
|     val multiTarget: Boolean |  | ||||||
|         get() = children.size>2 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class PtAssignment(position: Position) : PtNode(position), IPtAssignment |  | ||||||
|  |  | ||||||
| class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtAssignTarget(val void: Boolean, position: Position) : PtNode(position) { |  | ||||||
|     val identifier: PtIdentifier? |  | ||||||
|         get() = children.single() as? PtIdentifier |  | ||||||
|     val array: PtArrayIndexer? |  | ||||||
|         get() = children.single() as? PtArrayIndexer |  | ||||||
|     val memory: PtMemoryByte? |  | ||||||
|         get() = children.single() as? PtMemoryByte |  | ||||||
|  |  | ||||||
|     val type: DataType |  | ||||||
|         get() { |  | ||||||
|             return when(val tgt = children.single()) { |  | ||||||
|                 is PtIdentifier -> tgt.type |  | ||||||
|                 is PtArrayIndexer -> tgt.type |  | ||||||
|                 is PtMemoryByte -> tgt.type |  | ||||||
|                 else -> throw AssemblyError("weird target $tgt") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     infix fun isSameAs(expression: PtExpression): Boolean = !void && expression.isSameAs(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtConditionalBranch(val condition: BranchCondition, position: Position) : PtNode(position) { |  | ||||||
|     val trueScope: PtNodeGroup |  | ||||||
|         get() = children[0] as PtNodeGroup |  | ||||||
|     val falseScope: PtNodeGroup |  | ||||||
|         get() = children[1] as PtNodeGroup |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtForLoop(position: Position) : PtNode(position) { |  | ||||||
|     val variable: PtIdentifier |  | ||||||
|         get() = children[0] as PtIdentifier |  | ||||||
|     val iterable: PtExpression |  | ||||||
|         get() = children[1] as PtExpression |  | ||||||
|     val statements: PtNodeGroup |  | ||||||
|         get() = children[2] as PtNodeGroup |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtIfElse(position: Position) : PtNode(position) { |  | ||||||
|     val condition: PtExpression |  | ||||||
|         get() = children[0] as PtExpression |  | ||||||
|     val ifScope: PtNodeGroup |  | ||||||
|         get() = children[1] as PtNodeGroup |  | ||||||
|     val elseScope: PtNodeGroup |  | ||||||
|         get() = children[2] as PtNodeGroup |  | ||||||
|  |  | ||||||
|     fun hasElse(): Boolean = children.size==3 && elseScope.children.isNotEmpty() |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtJump(val identifier: PtIdentifier?,     // note: even ad-hoc labels are wrapped as an Identifier to simplify code. Just use dummy type and position. |  | ||||||
|              val address: UInt?, |  | ||||||
|              position: Position) : PtNode(position) { |  | ||||||
|     init { |  | ||||||
|         identifier?.let {it.parent = this } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtRepeatLoop(position: Position) : PtNode(position) { |  | ||||||
|     val count: PtExpression |  | ||||||
|         get() = children[0] as PtExpression |  | ||||||
|     val statements: PtNodeGroup |  | ||||||
|         get() = children[1] as PtNodeGroup |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtReturn(position: Position) : PtNode(position) { |  | ||||||
|     val hasValue = children.any() |  | ||||||
|     val value: PtExpression? |  | ||||||
|         get() { |  | ||||||
|             return if(children.any()) |  | ||||||
|                 children.single() as PtExpression |  | ||||||
|             else |  | ||||||
|                 null |  | ||||||
|         } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| sealed interface IPtVariable { |  | ||||||
|     val name: String |  | ||||||
|     val type: DataType |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable { |  | ||||||
|     init { |  | ||||||
|         value?.let {it.parent=this} |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable { |  | ||||||
|     init { |  | ||||||
|         require(type!=DataType.BOOL && type!=DataType.ARRAY_BOOL) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtWhen(position: Position) : PtNode(position) { |  | ||||||
|     val value: PtExpression |  | ||||||
|         get() = children[0] as PtExpression |  | ||||||
|     val choices: PtNodeGroup |  | ||||||
|         get() = children[1] as PtNodeGroup |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) { |  | ||||||
|     val values: PtNodeGroup |  | ||||||
|         get() = children[0] as PtNodeGroup |  | ||||||
|     val statements: PtNodeGroup |  | ||||||
|         get() = children[1] as PtNodeGroup |  | ||||||
| } |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| package prog8.code.core | package prog8.code.core | ||||||
|  |  | ||||||
| class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean) | class ReturnConvention(val dt: BaseDataType?, val reg: RegisterOrPair?) | ||||||
| class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean) | class ParamConvention(val dt: BaseDataType, val reg: RegisterOrPair?, val variable: Boolean) | ||||||
| class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) { | class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) { | ||||||
|     override fun toString(): String { |     override fun toString(): String { | ||||||
|         val paramConvs =  params.mapIndexed { index, it -> |         val paramConvs =  params.mapIndexed { index, it -> | ||||||
| @@ -13,35 +13,41 @@ class CallConvention(val params: List<ParamConvention>, val returns: ReturnConve | |||||||
|         } |         } | ||||||
|         val returnConv = |         val returnConv = | ||||||
|             when { |             when { | ||||||
|  |                 returns.reg == RegisterOrPair.FAC1 -> "floatFAC1" | ||||||
|                 returns.reg != null -> returns.reg.toString() |                 returns.reg != null -> returns.reg.toString() | ||||||
|                 returns.floatFac1 -> "floatFAC1" |  | ||||||
|                 else -> "<no returnvalue>" |                 else -> "<no returnvalue>" | ||||||
|             } |             } | ||||||
|         return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]" |         return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| class FParam(val name: String, val possibleDatatypes: Array<DataType>) | class FParam(val name: String, vararg val possibleDatatypes: BaseDataType) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | private val IterableDatatypes = arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) | ||||||
|  | private val IntegerDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG) | ||||||
|  | private val NumericDatatypes = arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT) | ||||||
|  |  | ||||||
|  |  | ||||||
| class FSignature(val pure: Boolean,      // does it have side effects? | class FSignature(val pure: Boolean,      // does it have side effects? | ||||||
|                       val parameters: List<FParam>, |                  val returnType: BaseDataType?, | ||||||
|                       val returnType: DataType?) { |                  vararg val parameters: FParam) { | ||||||
|  |  | ||||||
|     fun callConvention(actualParamTypes: List<DataType>): CallConvention { |     fun callConvention(actualParamTypes: List<BaseDataType>): CallConvention { | ||||||
|         val returns: ReturnConvention = when (returnType) { |         val returns: ReturnConvention = when (returnType) { | ||||||
|             DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false) |             BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A) | ||||||
|             DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false) |             BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY) | ||||||
|             DataType.FLOAT -> ReturnConvention(returnType, null, true) |             BaseDataType.FLOAT -> ReturnConvention(returnType, RegisterOrPair.FAC1) | ||||||
|             in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false) |             in IterableDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY) | ||||||
|             null -> ReturnConvention(null, null, false) |             null -> ReturnConvention(null, null) | ||||||
|             else -> { |             else -> { | ||||||
|                 // return type depends on arg type |                 // return type depends on arg type | ||||||
|                 when (val paramType = actualParamTypes.first()) { |                 when (val paramType = actualParamTypes.first()) { | ||||||
|                     DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false) |                     BaseDataType.UBYTE, BaseDataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A) | ||||||
|                     DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false) |                     BaseDataType.UWORD, BaseDataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY) | ||||||
|                     DataType.FLOAT -> ReturnConvention(paramType, null, true) |                     BaseDataType.FLOAT -> ReturnConvention(paramType, RegisterOrPair.FAC1) | ||||||
|                     in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false) |                     in IterableDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY) | ||||||
|                     else -> ReturnConvention(paramType, null, false) |                     else -> ReturnConvention(paramType, null) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -49,16 +55,27 @@ class FSignature(val pure: Boolean,      // does it have side effects? | |||||||
|         return when { |         return when { | ||||||
|             actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns) |             actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns) | ||||||
|             actualParamTypes.size==1 -> { |             actualParamTypes.size==1 -> { | ||||||
|                 // one parameter goes via register/registerpair |                 // One parameter goes via register/registerpair. | ||||||
|  |                 // this avoids repeated code for every caller to store the value in the subroutine's argument variable. | ||||||
|  |                 // (that store is still done, but only coded once at the start at the subroutine itself rather than at every call site). | ||||||
|                 val paramConv = when(val paramType = actualParamTypes[0]) { |                 val paramConv = when(val paramType = actualParamTypes[0]) { | ||||||
|                     DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false) |                     BaseDataType.UBYTE, BaseDataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false) | ||||||
|                     DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false) |                     BaseDataType.UWORD, BaseDataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false) | ||||||
|                     DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) |                     BaseDataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)      // NOTE: for builtin functions, floating point arguments are passed by reference (so you get a pointer in AY) | ||||||
|                     in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false) |                     in IterableDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false) | ||||||
|                     else -> ParamConvention(paramType, null, false) |                     else -> ParamConvention(paramType, null, false) | ||||||
|                 } |                 } | ||||||
|                 CallConvention(listOf(paramConv), returns) |                 CallConvention(listOf(paramConv), returns) | ||||||
|             } |             } | ||||||
|  |             actualParamTypes.size==2 && (actualParamTypes[0].isByte && actualParamTypes[1].isWord) -> { | ||||||
|  |                 TODO("opportunity to pass word+byte arguments in A,Y and X registers but not implemented yet") | ||||||
|  |             } | ||||||
|  |             actualParamTypes.size==2 && (actualParamTypes[0].isWord && actualParamTypes[1].isByte) -> { | ||||||
|  |                 TODO("opportunity to pass word+byte arguments in A,Y and X registers but not implemented yet") | ||||||
|  |             } | ||||||
|  |             actualParamTypes.size==3 && actualParamTypes.all { it.isByte } -> { | ||||||
|  |                 TODO("opportunity to pass 3 byte arguments in A,Y and X registers but not implemented yet") | ||||||
|  |             } | ||||||
|             else -> { |             else -> { | ||||||
|                 // multiple parameters go via variables |                 // multiple parameters go via variables | ||||||
|                 val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) } |                 val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) } | ||||||
| @@ -69,66 +86,69 @@ class FSignature(val pure: Boolean,      // does it have side effects? | |||||||
| } | } | ||||||
|  |  | ||||||
| val BuiltinFunctions: Map<String, FSignature> = mapOf( | val BuiltinFunctions: Map<String, FSignature> = mapOf( | ||||||
|     // this set of function have no return value and operate in-place: |     "setlsb"  to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)), | ||||||
|     "setlsb"    to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null), |     "setmsb"  to FSignature(false, null, FParam("variable", BaseDataType.WORD, BaseDataType.UWORD), FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)), | ||||||
|     "setmsb"    to FSignature(false, listOf(FParam("variable", arrayOf(DataType.WORD, DataType.UWORD)), FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), null), |     "rol"     to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)), | ||||||
|     "rol"       to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null), |     "ror"     to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)), | ||||||
|     "ror"       to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null), |     "rol2"    to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)), | ||||||
|     "rol2"      to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null), |     "ror2"    to FSignature(false, null, FParam("item", BaseDataType.UBYTE, BaseDataType.UWORD)), | ||||||
|     "ror2"      to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null), |     "cmp"     to FSignature(false, null, FParam("value1", *IntegerDatatypes), FParam("value2", *NumericDatatypes)),  // cmp returns a status in the carry flag, but not a proper return value | ||||||
|     // cmp returns a status in the carry flag, but not a proper return value |     "prog8_lib_stringcompare"     to FSignature(true, BaseDataType.BYTE, FParam("str1", BaseDataType.STR), FParam("str2", BaseDataType.STR)), | ||||||
|     "cmp"       to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null), |     "prog8_lib_square_byte"       to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE, BaseDataType.UBYTE)), | ||||||
|     "prog8_lib_stringcompare"     to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE), |     "prog8_lib_square_word"       to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD, BaseDataType.UWORD)), | ||||||
|     "prog8_lib_arraycopy"         to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null), |     "prog8_lib_structalloc"       to FSignature(true, BaseDataType.UWORD), | ||||||
|     "prog8_lib_square_byte"       to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE), |     "abs"           to FSignature(true, null, FParam("value", *NumericDatatypes)), | ||||||
|     "prog8_lib_square_word"       to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD), |     "abs__byte"     to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.BYTE)), | ||||||
|     "prog8_ifelse_bittest_set"    to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL), |     "abs__word"     to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.WORD)), | ||||||
|     "prog8_ifelse_bittest_notset" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL), |     "abs__float"    to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)), | ||||||
|     "abs"       to FSignature(true, listOf(FParam("value", NumericDatatypes)), null), |     "len"           to FSignature(true, BaseDataType.UWORD, FParam("values", *IterableDatatypes)), | ||||||
|     "abs__byte"  to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE), |     "sizeof"        to FSignature(true, BaseDataType.UBYTE, FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())), | ||||||
|     "abs__word"  to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD), |     "sgn"           to FSignature(true, BaseDataType.BYTE, FParam("value", *NumericDatatypes)), | ||||||
|     "abs__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT), |     "sqrt"          to FSignature(true, null, FParam("value", *NumericDatatypes)), | ||||||
|     "len"       to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD), |     "sqrt__ubyte"   to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE)), | ||||||
|     // normal functions follow: |     "sqrt__uword"   to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD)), | ||||||
|     "sizeof"    to FSignature(true, listOf(FParam("object", DataType.entries.toTypedArray())), DataType.UBYTE), |     "sqrt__float"   to FSignature(true, BaseDataType.FLOAT, FParam("value", BaseDataType.FLOAT)), | ||||||
|     "sgn"       to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE), |     "divmod"        to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("divisor", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("quotient", BaseDataType.UBYTE, BaseDataType.UWORD), FParam("remainder", BaseDataType.UBYTE, BaseDataType.UWORD)), | ||||||
|     "sqrt"        to FSignature(true, listOf(FParam("value", NumericDatatypes)), null), |     "divmod__ubyte" to FSignature(false, null, FParam("dividend", BaseDataType.UBYTE), FParam("divisor", BaseDataType.UBYTE), FParam("quotient", BaseDataType.UBYTE), FParam("remainder", BaseDataType.UBYTE)), | ||||||
|     "sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE), |     "divmod__uword" to FSignature(false, null, FParam("dividend", BaseDataType.UWORD), FParam("divisor", BaseDataType.UWORD), FParam("quotient", BaseDataType.UWORD), FParam("remainder", BaseDataType.UWORD)), | ||||||
|     "sqrt__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE), |     "lsb"           to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)), | ||||||
|     "sqrt__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT), |     "lsw"           to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)), | ||||||
|     "divmod"           to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divisor", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("quotient", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null), |     "msb"           to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)), | ||||||
|     "divmod__ubyte"    to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE)), FParam("divisor", arrayOf(DataType.UBYTE)), FParam("quotient", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null), |     "msw"           to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG)), | ||||||
|     "divmod__uword"    to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UWORD)), FParam("divisor", arrayOf(DataType.UWORD)), FParam("quotient", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null), |     "mkword"        to FSignature(true, BaseDataType.UWORD, FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)), | ||||||
|     "lsb"       to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE), |     "clamp"         to FSignature(true, null, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)), | ||||||
|     "msb"       to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE), |     "clamp__byte"   to FSignature(true, BaseDataType.BYTE, FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)), | ||||||
|     "mkword"    to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD), |     "clamp__ubyte"  to FSignature(true, BaseDataType.UBYTE, FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)), | ||||||
|     "clamp"     to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE)), FParam("minimum", arrayOf(DataType.BYTE)), FParam("maximum", arrayOf(DataType.BYTE))), null), |     "clamp__word"   to FSignature(true, BaseDataType.WORD, FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)), | ||||||
|     "clamp__byte"  to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE)), FParam("minimum", arrayOf(DataType.BYTE)), FParam("maximum", arrayOf(DataType.BYTE))), DataType.BYTE), |     "clamp__uword"  to FSignature(true, BaseDataType.UWORD, FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)), | ||||||
|     "clamp__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE)), FParam("minimum", arrayOf(DataType.UBYTE)), FParam("maximum", arrayOf(DataType.UBYTE))), DataType.UBYTE), |     "min"           to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)), | ||||||
|     "clamp__word"  to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD)), FParam("minimum", arrayOf(DataType.WORD)), FParam("maximum", arrayOf(DataType.WORD))), DataType.WORD), |     "min__byte"     to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)), | ||||||
|     "clamp__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD)), FParam("minimum", arrayOf(DataType.UWORD)), FParam("maximum", arrayOf(DataType.UWORD))), DataType.UWORD), |     "min__ubyte"    to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)), | ||||||
|     "min"        to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), null), |     "min__word"     to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)), | ||||||
|     "min__byte"  to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), DataType.BYTE), |     "min__uword"    to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)), | ||||||
|     "min__ubyte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UBYTE)), FParam("val2", arrayOf(DataType.UBYTE))), DataType.UBYTE), |     "max"           to FSignature(true, null, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)), | ||||||
|     "min__word"  to FSignature(true, listOf(FParam("val1", arrayOf(DataType.WORD)), FParam("val2", arrayOf(DataType.WORD))), DataType.WORD), |     "max__byte"     to FSignature(true, BaseDataType.BYTE, FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)), | ||||||
|     "min__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD), |     "max__ubyte"    to FSignature(true, BaseDataType.UBYTE, FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)), | ||||||
|     "max"        to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), null), |     "max__word"     to FSignature(true, BaseDataType.WORD, FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)), | ||||||
|     "max__byte"  to FSignature(true, listOf(FParam("val1", arrayOf(DataType.BYTE)), FParam("val2", arrayOf(DataType.BYTE))), DataType.BYTE), |     "max__uword"    to FSignature(true, BaseDataType.UWORD, FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)), | ||||||
|     "max__ubyte" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UBYTE)), FParam("val2", arrayOf(DataType.UBYTE))), DataType.UBYTE), |     "peek"          to FSignature(true, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD)), | ||||||
|     "max__word"  to FSignature(true, listOf(FParam("val1", arrayOf(DataType.WORD)), FParam("val2", arrayOf(DataType.WORD))), DataType.WORD), |     "peekbool"      to FSignature(true, BaseDataType.BOOL, FParam("address", BaseDataType.UWORD)), | ||||||
|     "max__uword" to FSignature(true, listOf(FParam("val1", arrayOf(DataType.UWORD)), FParam("val2", arrayOf(DataType.UWORD))), DataType.UWORD), |     "peekw"         to FSignature(true, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)), | ||||||
|     "peek"      to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE), |     "peekl"         to FSignature(true, BaseDataType.LONG, FParam("address", BaseDataType.UWORD)), | ||||||
|     "peekw"     to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD), |     "peekf"         to FSignature(true, BaseDataType.FLOAT, FParam("address", BaseDataType.UWORD)), | ||||||
|     "peekf"     to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.FLOAT), |     "poke"          to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)), | ||||||
|     "poke"      to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null), |     "pokebool"      to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)), | ||||||
|     "pokew"     to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null), |     "pokebowl"      to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.BOOL)), | ||||||
|     "pokef"     to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.FLOAT))), null), |     "pokew"         to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UWORD)), | ||||||
|     "pokemon"   to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE), |     "pokel"         to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.LONG)), | ||||||
|     "rsave"     to FSignature(false, emptyList(), null), |     "pokef"         to FSignature(false, null, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.FLOAT)), | ||||||
|     "rrestore"  to FSignature(false, emptyList(), null), |     "pokemon"       to FSignature(false, BaseDataType.UBYTE, FParam("address", BaseDataType.UWORD), FParam("value", BaseDataType.UBYTE)), | ||||||
|     "memory"    to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD), |     "rsave"         to FSignature(false, null), | ||||||
|     "callfar"   to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD), |     "rrestore"      to FSignature(false, null), | ||||||
|     "call"      to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD), |     "memory"        to FSignature(true, BaseDataType.UWORD, FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)), | ||||||
|  |     "callfar"       to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)), | ||||||
|  |     "callfar2"      to FSignature(false, BaseDataType.UWORD, FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)), | ||||||
|  |     "call"          to FSignature(false, BaseDataType.UWORD, FParam("address", BaseDataType.UWORD)), | ||||||
| ) | ) | ||||||
|  |  | ||||||
| val InplaceModifyingBuiltinFunctions = setOf( | val InplaceModifyingBuiltinFunctions = setOf( | ||||||
|   | |||||||
| @@ -11,9 +11,11 @@ class CompilationOptions(val output: OutputType, | |||||||
|                          val zpAllowed: List<UIntRange>, |                          val zpAllowed: List<UIntRange>, | ||||||
|                          val floats: Boolean, |                          val floats: Boolean, | ||||||
|                          val noSysInit: Boolean, |                          val noSysInit: Boolean, | ||||||
|  |                          val romable: Boolean, | ||||||
|                          val compTarget: ICompilationTarget, |                          val compTarget: ICompilationTarget, | ||||||
|                          // these are set later, based on command line arguments or options in the source code: |                          // these are set later, based on command line arguments or options in the source code: | ||||||
|                          var loadAddress: UInt, |                          var loadAddress: UInt, | ||||||
|  |                          var memtopAddress: UInt, | ||||||
|                          var warnSymbolShadowing: Boolean = false, |                          var warnSymbolShadowing: Boolean = false, | ||||||
|                          var optimize: Boolean = false, |                          var optimize: Boolean = false, | ||||||
|                          var asmQuiet: Boolean = false, |                          var asmQuiet: Boolean = false, | ||||||
| @@ -26,14 +28,15 @@ class CompilationOptions(val output: OutputType, | |||||||
|                          var varsGolden: Boolean = false, |                          var varsGolden: Boolean = false, | ||||||
|                          var slabsHighBank: Int? = null, |                          var slabsHighBank: Int? = null, | ||||||
|                          var slabsGolden: Boolean = false, |                          var slabsGolden: Boolean = false, | ||||||
|                          var splitWordArrays: Boolean = false, |                          var dontSplitWordArrays: Boolean = false, | ||||||
|                          var strictBool: Boolean = true, |  | ||||||
|                          var breakpointCpuInstruction: String? = null, |                          var breakpointCpuInstruction: String? = null, | ||||||
|  |                          var ignoreFootguns: Boolean = false, | ||||||
|                          var outputDir: Path = Path(""), |                          var outputDir: Path = Path(""), | ||||||
|  |                          var quiet: Boolean = false, | ||||||
|                          var symbolDefs: Map<String, String> = emptyMap() |                          var symbolDefs: Map<String, String> = emptyMap() | ||||||
| ) { | ) { | ||||||
|     init { |     init { | ||||||
|         compTarget.machine.initializeMemoryAreas(this) |         compTarget.initializeMemoryAreas(this) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ fun Number.toHex(): String { | |||||||
|     //  0..15 -> "0".."15" |     //  0..15 -> "0".."15" | ||||||
|     //  16..255 -> "$10".."$ff" |     //  16..255 -> "$10".."$ff" | ||||||
|     //  256..65536 -> "$0100".."$ffff" |     //  256..65536 -> "$0100".."$ffff" | ||||||
|  |     //  larger -> "$12345678" | ||||||
|     // negative values are prefixed with '-'. |     // negative values are prefixed with '-'. | ||||||
|     val integer = this.toInt() |     val integer = this.toInt() | ||||||
|     if(integer<0) |     if(integer<0) | ||||||
| @@ -19,7 +20,7 @@ fun Number.toHex(): String { | |||||||
|         in 0 until 16 -> integer.toString() |         in 0 until 16 -> integer.toString() | ||||||
|         in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0') |         in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0') | ||||||
|         in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0') |         in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0') | ||||||
|         else -> throw IllegalArgumentException("number too large for 16 bits $this") |         else -> "$"+integer.toString(16).padStart(8,'0') | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -27,11 +28,12 @@ fun UInt.toHex(): String { | |||||||
|     //  0..15 -> "0".."15" |     //  0..15 -> "0".."15" | ||||||
|     //  16..255 -> "$10".."$ff" |     //  16..255 -> "$10".."$ff" | ||||||
|     //  256..65536 -> "$0100".."$ffff" |     //  256..65536 -> "$0100".."$ffff" | ||||||
|  |     //  larger -> "$12345678" | ||||||
|     return when (this) { |     return when (this) { | ||||||
|         in 0u until 16u -> this.toString() |         in 0u until 16u -> this.toString() | ||||||
|         in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0') |         in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0') | ||||||
|         in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0') |         in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0') | ||||||
|         else -> throw IllegalArgumentException("number too large for 16 bits $this") |         else -> "$"+this.toString(16).padStart(8,'0') | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,63 +1,382 @@ | |||||||
| package prog8.code.core | package prog8.code.core | ||||||
|  |  | ||||||
| enum class DataType { | import java.util.* | ||||||
|  |  | ||||||
|  | enum class BaseDataType { | ||||||
|     UBYTE,              // pass by value            8 bits unsigned |     UBYTE,              // pass by value            8 bits unsigned | ||||||
|     BYTE,               // pass by value            8 bits signed |     BYTE,               // pass by value            8 bits signed | ||||||
|     UWORD,              // pass by value            16 bits unsigned |     UWORD,              // pass by value            16 bits unsigned | ||||||
|     WORD,               // pass by value            16 bits signed |     WORD,               // pass by value            16 bits signed | ||||||
|     LONG,               // pass by value            32 bits signed |     LONG,               // pass by value            32 bits signed | ||||||
|     FLOAT,              // pass by value            machine dependent |     FLOAT,              // pass by value            machine dependent | ||||||
|     BOOL,               // pass by value            bit 0 of a 8 bit byte |     BOOL,               // pass by value            bit 0 of an 8-bit byte | ||||||
|     STR,                // pass by reference |     STR,                // pass by reference | ||||||
|     ARRAY_UB,           // pass by reference |     ARRAY,              // pass by reference, subtype is the element type | ||||||
|     ARRAY_B,            // pass by reference |     ARRAY_SPLITW,       // pass by reference, split word layout, subtype is the element type (restricted to word types) | ||||||
|     ARRAY_UW,           // pass by reference |     POINTER,            // typed pointer, subtype is whatever type is pointed to | ||||||
|     ARRAY_UW_SPLIT,     // pass by reference, lo/hi byte split |     STRUCT_INSTANCE,    // the actual instance of a struct (not directly supported in the language yet, but we need its type) | ||||||
|     ARRAY_W,            // pass by reference |     ARRAY_POINTER,      // array of pointers (uwords), subtype is whatever type each element points to | ||||||
|     ARRAY_W_SPLIT,      // pass by reference, lo/hi byte split |  | ||||||
|     ARRAY_F,            // pass by reference |  | ||||||
|     ARRAY_BOOL,         // pass by reference |  | ||||||
|     UNDEFINED; |     UNDEFINED; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * is the type assignable to the given other type (perhaps via a typecast) without loss of precision? |  | ||||||
|      */ |  | ||||||
|     infix fun isAssignableTo(targetType: DataType) = |  | ||||||
|         when(this) { |  | ||||||
|             BOOL -> targetType == BOOL |  | ||||||
|             UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, LONG, FLOAT) |  | ||||||
|             BYTE -> targetType.oneOf(BYTE, WORD, LONG, FLOAT) |  | ||||||
|             UWORD -> targetType.oneOf(UWORD, LONG, FLOAT) |  | ||||||
|             WORD -> targetType.oneOf(WORD, LONG, FLOAT) |  | ||||||
|             LONG -> targetType.oneOf(LONG, FLOAT) |  | ||||||
|             FLOAT -> targetType.oneOf(FLOAT) |  | ||||||
|             STR -> targetType.oneOf(STR, UWORD) |  | ||||||
|             in ArrayDatatypes -> targetType == this |  | ||||||
|             else -> false |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     fun oneOf(vararg types: DataType) = this in types |     fun largerSizeThan(other: BaseDataType) = | ||||||
|  |  | ||||||
|     infix fun largerThan(other: DataType) = |  | ||||||
|         when { |         when { | ||||||
|             this == other -> false |             this == other -> false | ||||||
|             this in ByteDatatypesWithBoolean -> false |             this.isByteOrBool -> false | ||||||
|             this in WordDatatypes -> other in ByteDatatypesWithBoolean |             this.isWord -> other.isByteOrBool | ||||||
|             this == LONG -> other in ByteDatatypesWithBoolean+WordDatatypes |             this == LONG -> other.isByteOrBool || other.isWord | ||||||
|             this == STR && other == UWORD || this == UWORD && other == STR -> false |             this == STR && other == UWORD || this == UWORD && other == STR -> false | ||||||
|  |             this.isArray && other.isArray -> false | ||||||
|  |             this.isArray -> other != FLOAT | ||||||
|  |             this == STR -> other != FLOAT | ||||||
|  |             this.isPointer -> other.isByteOrBool | ||||||
|             else -> true |             else -> true | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     infix fun equalsSize(other: DataType) = |     fun equalsSize(other: BaseDataType) = | ||||||
|         when { |         when { | ||||||
|             this == other -> true |             this == other -> true | ||||||
|             this in ByteDatatypesWithBoolean -> other in ByteDatatypesWithBoolean |             this.isArray && other.isArray -> true | ||||||
|             this in WordDatatypes -> other in WordDatatypes |             this.isByteOrBool -> other.isByteOrBool | ||||||
|  |             this.isWord -> other.isWord || other.isPointer | ||||||
|  |             this.isPointer -> other.isWord | ||||||
|             this == STR && other== UWORD || this== UWORD && other== STR -> true |             this == STR && other== UWORD || this== UWORD && other== STR -> true | ||||||
|  |             this == STR && other.isArray -> true | ||||||
|  |             this.isArray && other == STR -> true | ||||||
|             else -> false |             else -> false | ||||||
|         } |         } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | val BaseDataType.isByte get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE) | ||||||
|  | val BaseDataType.isByteOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL) | ||||||
|  | val BaseDataType.isWord get() = this in arrayOf(BaseDataType.UWORD, BaseDataType.WORD) | ||||||
|  | val BaseDataType.isInteger get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG) | ||||||
|  | val BaseDataType.isIntegerOrBool get() = this in arrayOf(BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.BOOL) | ||||||
|  | val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger | ||||||
|  | val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric | ||||||
|  | val BaseDataType.isSigned get() = this in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT) | ||||||
|  | val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER | ||||||
|  | val BaseDataType.isPointer get() = this == BaseDataType.POINTER | ||||||
|  | val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE | ||||||
|  | val BaseDataType.isPointerArray get() = this == BaseDataType.ARRAY_POINTER | ||||||
|  | val BaseDataType.isSplitWordArray get() = this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER       // pointer arrays are also always stored as split uwords | ||||||
|  | val BaseDataType.isIterable get() =  this in arrayOf(BaseDataType.STR, BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW, BaseDataType.ARRAY_POINTER) | ||||||
|  | val BaseDataType.isPassByRef get() = this.isIterable && !this.isPointer | ||||||
|  | val BaseDataType.isPassByValue get() = !this.isIterable || this.isPointer | ||||||
|  |  | ||||||
|  |  | ||||||
|  | interface ISubType { | ||||||
|  |     val scopedNameString: String | ||||||
|  |     fun memsize(sizer: IMemSizer): Int | ||||||
|  |     fun sameas(other: ISubType): Boolean | ||||||
|  |     fun getFieldType(name: String): DataType? | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class DataType private constructor(val base: BaseDataType, val sub: BaseDataType?, var subType: ISubType?, var subTypeFromAntlr: List<String>?=null) { | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         when { | ||||||
|  |             base.isPointerArray -> { | ||||||
|  |                 require(sub!=null || subType!=null || subTypeFromAntlr!=null) | ||||||
|  |             } | ||||||
|  |             base.isArray -> { | ||||||
|  |                 require(sub != null && subType==null && subTypeFromAntlr==null) | ||||||
|  |                 if(base.isSplitWordArray) | ||||||
|  |                     require(sub == BaseDataType.UWORD || sub == BaseDataType.WORD) | ||||||
|  |             } | ||||||
|  |             base==BaseDataType.STR -> require(sub==BaseDataType.UBYTE) { "string subtype should be ubyte" } | ||||||
|  |             base!=BaseDataType.POINTER -> require(sub == null) { "only string, array and pointer base types can have a subtype"} | ||||||
|  |             else -> { | ||||||
|  |                 require(sub == null || (subType == null && subTypeFromAntlr == null)) { | ||||||
|  |                     "sub and subtype can't both be set" | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is DataType) return false | ||||||
|  |         return base == other.base && sub == other.sub && (subType==other.subType || subType!!.sameas(other.subType!!)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun hashCode(): Int = Objects.hash(base, sub, subType) | ||||||
|  |  | ||||||
|  |     fun setActualSubType(actualSubType: ISubType) { | ||||||
|  |         subType = actualSubType | ||||||
|  |         subTypeFromAntlr = null | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |  | ||||||
|  |         val UBYTE = DataType(BaseDataType.UBYTE, null, null) | ||||||
|  |         val BYTE = DataType(BaseDataType.BYTE, null, null) | ||||||
|  |         val UWORD = DataType(BaseDataType.UWORD, null, null) | ||||||
|  |         val WORD = DataType(BaseDataType.WORD, null, null) | ||||||
|  |         val LONG = DataType(BaseDataType.LONG, null, null) | ||||||
|  |         val FLOAT = DataType(BaseDataType.FLOAT, null, null) | ||||||
|  |         val BOOL = DataType(BaseDataType.BOOL, null, null) | ||||||
|  |         val STR = DataType(BaseDataType.STR, BaseDataType.UBYTE, null) | ||||||
|  |         val UNDEFINED = DataType(BaseDataType.UNDEFINED, null, null) | ||||||
|  |  | ||||||
|  |         private val simpletypes = mapOf( | ||||||
|  |             BaseDataType.UBYTE to DataType(BaseDataType.UBYTE, null, null), | ||||||
|  |             BaseDataType.BYTE to DataType(BaseDataType.BYTE, null, null), | ||||||
|  |             BaseDataType.UWORD to DataType(BaseDataType.UWORD, null, null), | ||||||
|  |             BaseDataType.WORD to DataType(BaseDataType.WORD, null, null), | ||||||
|  |             BaseDataType.LONG to DataType(BaseDataType.LONG, null, null), | ||||||
|  |             BaseDataType.FLOAT to DataType(BaseDataType.FLOAT, null, null), | ||||||
|  |             BaseDataType.BOOL to DataType(BaseDataType.BOOL, null, null), | ||||||
|  |             BaseDataType.STR to DataType(BaseDataType.STR, BaseDataType.UBYTE, null), | ||||||
|  |             BaseDataType.UNDEFINED to DataType(BaseDataType.UNDEFINED, null, null) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         fun forDt(dt: BaseDataType): DataType { | ||||||
|  |             if(dt.isStructInstance) | ||||||
|  |                 TODO("cannot use struct instance as a data type (yet) - use a pointer instead") | ||||||
|  |             return simpletypes.getValue(dt) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun arrayFor(elementDt: BaseDataType, splitwordarray: Boolean=true): DataType { | ||||||
|  |             require(!elementDt.isPointer) { "use other array constructor for arrays of pointers" } | ||||||
|  |             val actualElementDt = if(elementDt==BaseDataType.STR) BaseDataType.UWORD else elementDt      // array of strings is actually just an array of UWORD pointers | ||||||
|  |             return if(splitwordarray && actualElementDt.isWord) | ||||||
|  |                 DataType(BaseDataType.ARRAY_SPLITW, actualElementDt, null) | ||||||
|  |             else { | ||||||
|  |                 if(actualElementDt.isNumericOrBool && actualElementDt != BaseDataType.LONG) | ||||||
|  |                     DataType(BaseDataType.ARRAY, actualElementDt, null) | ||||||
|  |                 else | ||||||
|  |                     throw NoSuchElementException("invalid basic element dt $elementDt") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun arrayOfPointersTo(sub: BaseDataType): DataType = DataType(BaseDataType.ARRAY_POINTER, sub, null) | ||||||
|  |         fun arrayOfPointersTo(structType: ISubType?): DataType = DataType(BaseDataType.ARRAY_POINTER, null, structType) | ||||||
|  |         fun arrayOfPointersFromAntlrTo(sub: BaseDataType?, identifier: List<String>?): DataType = | ||||||
|  |             DataType(BaseDataType.ARRAY_POINTER, sub, null, identifier) | ||||||
|  |  | ||||||
|  |         fun pointer(base: BaseDataType): DataType = DataType(BaseDataType.POINTER, base, null) | ||||||
|  |         fun pointer(dt: DataType): DataType = if(dt.isBasic) | ||||||
|  |                 DataType(BaseDataType.POINTER, dt.base, null) | ||||||
|  |             else | ||||||
|  |                 DataType(BaseDataType.POINTER, null, dt.subType, dt.subTypeFromAntlr) | ||||||
|  |         fun pointer(structType: ISubType): DataType = DataType(BaseDataType.POINTER, null, structType) | ||||||
|  |         fun pointerFromAntlr(identifier: List<String>): DataType = DataType(BaseDataType.POINTER, null, null, identifier) | ||||||
|  |         fun structInstance(type: ISubType?): DataType = DataType(BaseDataType.STRUCT_INSTANCE, sub=null, type) | ||||||
|  |         fun structInstanceFromAntlr(struct: List<String>): DataType = DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr = struct) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     fun elementToArray(splitwords: Boolean = true): DataType { | ||||||
|  |         return if (base == BaseDataType.UWORD || base == BaseDataType.WORD || base == BaseDataType.STR) arrayFor(base, splitwords) | ||||||
|  |         else arrayFor(base, false) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun elementType(): DataType = | ||||||
|  |         when { | ||||||
|  |             isPointerArray -> DataType(BaseDataType.POINTER, sub, subType) | ||||||
|  |             base.isArray || base==BaseDataType.STR -> forDt(sub!!) | ||||||
|  |             else -> throw IllegalArgumentException("not an array") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     fun typeForAddressOf(msb: Boolean): DataType { | ||||||
|  |         if (isUndefined) | ||||||
|  |             return if(msb) pointer(BaseDataType.UBYTE) else UWORD | ||||||
|  |         else { | ||||||
|  |             if (isBasic) | ||||||
|  |                 return pointer(base) | ||||||
|  |             if (isString) | ||||||
|  |                 return pointer(BaseDataType.UBYTE) | ||||||
|  |             if (isPointer) | ||||||
|  |                 return UWORD | ||||||
|  |             if (isArray) { | ||||||
|  |                 if (msb || isSplitWordArray) | ||||||
|  |                     return pointer(BaseDataType.UBYTE) | ||||||
|  |                 val elementDt = elementType() | ||||||
|  |                 require(elementDt.isBasic) | ||||||
|  |                 return pointer(elementDt) | ||||||
|  |             } | ||||||
|  |             if (subType != null) | ||||||
|  |                 return pointer(this) | ||||||
|  |             return UWORD | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun dereference(): DataType { | ||||||
|  |         require(isPointer || isUnsignedWord) | ||||||
|  |         return when { | ||||||
|  |             isUnsignedWord -> forDt(BaseDataType.UBYTE) | ||||||
|  |             sub!=null -> forDt(sub) | ||||||
|  |             subType!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, subType) | ||||||
|  |             subTypeFromAntlr!=null -> DataType(BaseDataType.STRUCT_INSTANCE, null, null, subTypeFromAntlr) | ||||||
|  |             else -> throw IllegalArgumentException("cannot dereference this pointer type") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun toString(): String = when(base) { | ||||||
|  |         BaseDataType.ARRAY -> { | ||||||
|  |             when(sub) { | ||||||
|  |                 BaseDataType.BOOL -> "bool[]" | ||||||
|  |                 BaseDataType.FLOAT -> "float[]" | ||||||
|  |                 BaseDataType.BYTE -> "byte[]" | ||||||
|  |                 BaseDataType.WORD -> "word[]" | ||||||
|  |                 BaseDataType.UBYTE -> "ubyte[]" | ||||||
|  |                 BaseDataType.UWORD -> "uword[]" | ||||||
|  |                 else -> throw IllegalArgumentException("invalid sub type") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         BaseDataType.ARRAY_SPLITW -> { | ||||||
|  |             when(sub) { | ||||||
|  |                 BaseDataType.WORD -> "word[] (split)" | ||||||
|  |                 BaseDataType.UWORD -> "uword[] (split)" | ||||||
|  |                 else -> throw IllegalArgumentException("invalid sub type") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         BaseDataType.POINTER -> { | ||||||
|  |             if(sub!=null) "^^${sub.name.lowercase()}" else if(subType!=null) "^^${subType!!.scopedNameString}" else "^^${subTypeFromAntlr}" | ||||||
|  |         } | ||||||
|  |         BaseDataType.ARRAY_POINTER -> { | ||||||
|  |             if(sub!=null) "^^${sub.name.lowercase()}[] (split)" else if (subType!=null) "^^${subType!!.scopedNameString}[] (split)" else "^^${subTypeFromAntlr}[] (split)" | ||||||
|  |         } | ||||||
|  |         BaseDataType.STRUCT_INSTANCE -> { | ||||||
|  |             sub?.name?.lowercase() ?: if (subType!=null) subType!!.scopedNameString else "$subTypeFromAntlr" | ||||||
|  |         } | ||||||
|  |         else -> base.name.lowercase() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun sourceString(): String = when (base) { | ||||||
|  |         BaseDataType.BOOL -> "bool" | ||||||
|  |         BaseDataType.UBYTE -> "ubyte" | ||||||
|  |         BaseDataType.BYTE -> "byte" | ||||||
|  |         BaseDataType.UWORD -> "uword" | ||||||
|  |         BaseDataType.WORD -> "word" | ||||||
|  |         BaseDataType.LONG -> "long" | ||||||
|  |         BaseDataType.FLOAT -> "float" | ||||||
|  |         BaseDataType.STR -> "str" | ||||||
|  |         BaseDataType.POINTER -> { | ||||||
|  |             when { | ||||||
|  |                 sub!=null -> "^^${sub.name.lowercase()}" | ||||||
|  |                 subType!=null -> "^^${subType!!.scopedNameString}" | ||||||
|  |                 subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}" | ||||||
|  |                 else -> "?????" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         BaseDataType.STRUCT_INSTANCE -> { | ||||||
|  |             when { | ||||||
|  |                 sub!=null -> sub.name.lowercase() | ||||||
|  |                 subType!=null -> subType!!.scopedNameString | ||||||
|  |                 subTypeFromAntlr!=null -> subTypeFromAntlr!!.joinToString(".") | ||||||
|  |                 else -> "?????" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         BaseDataType.ARRAY_POINTER -> { | ||||||
|  |             when { | ||||||
|  |                 sub!=null -> "^^${sub.name.lowercase()}[" | ||||||
|  |                 subType!=null -> "^^${subType!!.scopedNameString}[" | ||||||
|  |                 subTypeFromAntlr!=null -> "^^${subTypeFromAntlr!!.joinToString(".")}[" | ||||||
|  |                 else -> "????? [" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         BaseDataType.ARRAY -> { | ||||||
|  |             when(sub) { | ||||||
|  |                 BaseDataType.UBYTE -> "ubyte[" | ||||||
|  |                 BaseDataType.UWORD -> "@nosplit uword[" | ||||||
|  |                 BaseDataType.BOOL -> "bool[" | ||||||
|  |                 BaseDataType.BYTE -> "byte[" | ||||||
|  |                 BaseDataType.WORD -> "@nosplit word[" | ||||||
|  |                 BaseDataType.FLOAT -> "float[" | ||||||
|  |                 else -> throw IllegalArgumentException("invalid sub type") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         BaseDataType.ARRAY_SPLITW -> { | ||||||
|  |             when(sub) { | ||||||
|  |                 BaseDataType.UWORD -> "uword[" | ||||||
|  |                 BaseDataType.WORD -> "word[" | ||||||
|  |                 else -> throw IllegalArgumentException("invalid sub type") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         BaseDataType.UNDEFINED -> throw IllegalArgumentException("wrong dt") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // is the type assignable to the given other type (perhaps via a typecast) without loss of precision? | ||||||
|  |     infix fun isAssignableTo(targetType: DataType) = | ||||||
|  |         when(base) { | ||||||
|  |             BaseDataType.BOOL -> targetType.base == BaseDataType.BOOL | ||||||
|  |             BaseDataType.UBYTE -> targetType.base in arrayOf(BaseDataType.UBYTE, BaseDataType.WORD, BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT) | ||||||
|  |             BaseDataType.BYTE -> targetType.base in arrayOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT) | ||||||
|  |             BaseDataType.UWORD -> targetType.base in arrayOf(BaseDataType.UWORD, BaseDataType.LONG, BaseDataType.FLOAT, BaseDataType.POINTER, BaseDataType.ARRAY_POINTER) | ||||||
|  |             BaseDataType.WORD -> targetType.base in arrayOf(BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT) | ||||||
|  |             BaseDataType.LONG -> targetType.base in arrayOf(BaseDataType.LONG, BaseDataType.FLOAT) | ||||||
|  |             BaseDataType.FLOAT -> targetType.base in arrayOf(BaseDataType.FLOAT) | ||||||
|  |             BaseDataType.STR -> targetType.base in arrayOf(BaseDataType.STR, BaseDataType.UWORD) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE) | ||||||
|  |             BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> targetType.base in arrayOf(BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW) && targetType.sub == sub | ||||||
|  |             BaseDataType.POINTER -> { | ||||||
|  |                 when { | ||||||
|  |                     targetType.base == BaseDataType.UWORD || targetType.base == BaseDataType.LONG -> true | ||||||
|  |                     targetType.isPointer -> this.isUnsignedWord || this == targetType | ||||||
|  |                     else -> false | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             BaseDataType.STRUCT_INSTANCE -> false        // we cannot deal with actual struct instances yet in any shape or form (only getting fields from it) | ||||||
|  |             BaseDataType.ARRAY_POINTER -> false | ||||||
|  |             BaseDataType.UNDEFINED -> false | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     fun largerSizeThan(other: DataType): Boolean = base.largerSizeThan(other.base) | ||||||
|  |     fun equalsSize(other: DataType): Boolean = base.equalsSize(other.base) | ||||||
|  |  | ||||||
|  |     // note: for pointer types, size() doesn't return the size of the pointer itself but the size of the thing it points to | ||||||
|  |     fun size(memsizer: IMemSizer): Int = if(sub!=null) { | ||||||
|  |             memsizer.memorySize(sub) | ||||||
|  |         } else if(subType!=null) { | ||||||
|  |             subType!!.memsize(memsizer) | ||||||
|  |         } else { | ||||||
|  |             memsizer.memorySize(base) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     val isBasic = sub==null && subType==null && subTypeFromAntlr==null | ||||||
|  |     val isUndefined = base == BaseDataType.UNDEFINED | ||||||
|  |     val isByte = base.isByte | ||||||
|  |     val isUnsignedByte = base == BaseDataType.UBYTE | ||||||
|  |     val isSignedByte = base == BaseDataType.BYTE | ||||||
|  |     val isByteOrBool = base.isByteOrBool | ||||||
|  |     val isWord = base.isWord | ||||||
|  |     val isUnsignedWord =  base == BaseDataType.UWORD | ||||||
|  |     val isSignedWord =  base == BaseDataType.WORD | ||||||
|  |     val isInteger = base.isInteger | ||||||
|  |     val isIntegerOrBool = base.isIntegerOrBool | ||||||
|  |     val isNumeric = base.isNumeric | ||||||
|  |     val isNumericOrBool = base.isNumericOrBool | ||||||
|  |     val isSigned = base.isSigned | ||||||
|  |     val isUnsigned = !base.isSigned | ||||||
|  |     val isArray = base.isArray | ||||||
|  |     val isPointer = base.isPointer | ||||||
|  |     val isPointerToByte = base.isPointer && sub?.isByteOrBool==true | ||||||
|  |     val isPointerToWord = base.isPointer && sub?.isWord==true | ||||||
|  |     val isStructInstance = base.isStructInstance | ||||||
|  |     val isPointerArray = base.isPointerArray | ||||||
|  |     val isBoolArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BOOL | ||||||
|  |     val isByteArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE) | ||||||
|  |     val isUnsignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UBYTE | ||||||
|  |     val isSignedByteArray = base.isArray && !base.isPointerArray && sub == BaseDataType.BYTE | ||||||
|  |     val isWordArray = base.isArray && !base.isPointerArray && (sub == BaseDataType.UWORD || sub == BaseDataType.WORD) | ||||||
|  |     val isUnsignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.UWORD | ||||||
|  |     val isSignedWordArray = base.isArray && !base.isPointerArray && sub == BaseDataType.WORD | ||||||
|  |     val isFloatArray = base.isArray && !base.isPointerArray && sub == BaseDataType.FLOAT | ||||||
|  |     val isString = base == BaseDataType.STR | ||||||
|  |     val isBool = base == BaseDataType.BOOL | ||||||
|  |     val isFloat = base == BaseDataType.FLOAT | ||||||
|  |     val isLong = base == BaseDataType.LONG | ||||||
|  |     val isStringly = base == BaseDataType.STR || base == BaseDataType.UWORD || (base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE)) | ||||||
|  |     val isSplitWordArray = base.isSplitWordArray | ||||||
|  |     val isSplitUnsignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.UWORD | ||||||
|  |     val isSplitSignedWordArray = base.isSplitWordArray && !base.isPointerArray && sub == BaseDataType.WORD | ||||||
|  |     val isIterable =  base.isIterable | ||||||
|  |     val isPassByRef = base.isPassByRef | ||||||
|  |     val isPassByValue = base.isPassByValue | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| enum class CpuRegister { | enum class CpuRegister { | ||||||
|     A, |     A, | ||||||
|     X, |     X, | ||||||
| @@ -95,6 +414,20 @@ enum class RegisterOrPair { | |||||||
|         else -> throw IllegalArgumentException("no cpu hardware register for $this") |         else -> throw IllegalArgumentException("no cpu hardware register for $this") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun asScopedNameVirtualReg(type: DataType?): List<String> { | ||||||
|  |         require(this in Cx16VirtualRegisters) | ||||||
|  |         val suffix = when(type?.base) { | ||||||
|  |             BaseDataType.UBYTE, BaseDataType.BOOL -> "L" | ||||||
|  |             BaseDataType.BYTE -> "sL" | ||||||
|  |             BaseDataType.WORD -> "s" | ||||||
|  |             BaseDataType.UWORD, null -> "" | ||||||
|  |             else -> throw IllegalArgumentException("invalid register param type") | ||||||
|  |         } | ||||||
|  |         return listOf("cx16", name.lowercase()+suffix) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun isWord() = this==AX || this == AY || this==XY || this in Cx16VirtualRegisters | ||||||
|  |  | ||||||
| }       // only used in parameter and return value specs in asm subroutines | }       // only used in parameter and return value specs in asm subroutines | ||||||
|  |  | ||||||
| enum class Statusflag { | enum class Statusflag { | ||||||
| @@ -123,48 +456,6 @@ enum class BranchCondition { | |||||||
|     VC |     VC | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE) |  | ||||||
| val ByteDatatypesWithBoolean = ByteDatatypes + DataType.BOOL |  | ||||||
| val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD) |  | ||||||
| val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG) |  | ||||||
| val IntegerDatatypesWithBoolean = IntegerDatatypes + DataType.BOOL |  | ||||||
| val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.LONG, DataType.FLOAT) |  | ||||||
| val NumericDatatypesWithBoolean = NumericDatatypes + DataType.BOOL |  | ||||||
| val SignedDatatypes =  arrayOf(DataType.BYTE, DataType.WORD, DataType.LONG, DataType.FLOAT) |  | ||||||
| val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_F, DataType.ARRAY_BOOL) |  | ||||||
| val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD) |  | ||||||
| val SplitWordArrayTypes = arrayOf(DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT) |  | ||||||
| val IterableDatatypes = arrayOf( |  | ||||||
|     DataType.STR, |  | ||||||
|     DataType.ARRAY_UB, DataType.ARRAY_B, |  | ||||||
|     DataType.ARRAY_UW, DataType.ARRAY_W, |  | ||||||
|     DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT, |  | ||||||
|     DataType.ARRAY_F, DataType.ARRAY_BOOL |  | ||||||
| ) |  | ||||||
| val PassByValueDatatypes = NumericDatatypesWithBoolean |  | ||||||
| val PassByReferenceDatatypes = IterableDatatypes |  | ||||||
| val ArrayToElementTypes = mapOf( |  | ||||||
|     DataType.STR to DataType.UBYTE, |  | ||||||
|     DataType.ARRAY_B to DataType.BYTE, |  | ||||||
|     DataType.ARRAY_UB to DataType.UBYTE, |  | ||||||
|     DataType.ARRAY_W to DataType.WORD, |  | ||||||
|     DataType.ARRAY_UW to DataType.UWORD, |  | ||||||
|     DataType.ARRAY_W_SPLIT to DataType.WORD, |  | ||||||
|     DataType.ARRAY_UW_SPLIT to DataType.UWORD, |  | ||||||
|     DataType.ARRAY_F to DataType.FLOAT, |  | ||||||
|     DataType.ARRAY_BOOL to DataType.BOOL |  | ||||||
| ) |  | ||||||
| val ElementToArrayTypes = mapOf( |  | ||||||
|     DataType.BYTE to DataType.ARRAY_B, |  | ||||||
|     DataType.UBYTE to DataType.ARRAY_UB, |  | ||||||
|     DataType.WORD to DataType.ARRAY_W, |  | ||||||
|     DataType.UWORD to DataType.ARRAY_UW, |  | ||||||
|     DataType.FLOAT to DataType.ARRAY_F, |  | ||||||
|     DataType.BOOL to DataType.ARRAY_BOOL, |  | ||||||
|     DataType.STR to DataType.ARRAY_UW          // array of str is just an array of pointers |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| val Cx16VirtualRegisters = arrayOf( | val Cx16VirtualRegisters = arrayOf( | ||||||
|     RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3, |     RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3, | ||||||
|     RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7, |     RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7, | ||||||
| @@ -172,7 +463,7 @@ val Cx16VirtualRegisters = arrayOf( | |||||||
|     RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15 |     RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| val CpuRegisters = setOf( | val CpuRegisters = arrayOf( | ||||||
|     RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y, |     RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y, | ||||||
|     RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY |     RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY | ||||||
| ) | ) | ||||||
| @@ -181,7 +472,8 @@ val CpuRegisters = setOf( | |||||||
| enum class OutputType { | enum class OutputType { | ||||||
|     RAW, |     RAW, | ||||||
|     PRG, |     PRG, | ||||||
|     XEX |     XEX, | ||||||
|  |     LIBRARY | ||||||
| } | } | ||||||
|  |  | ||||||
| enum class CbmPrgLauncherType { | enum class CbmPrgLauncherType { | ||||||
| @@ -203,3 +495,9 @@ enum class ZeropageWish { | |||||||
|     DONTCARE, |     DONTCARE, | ||||||
|     NOT_IN_ZEROPAGE |     NOT_IN_ZEROPAGE | ||||||
| } | } | ||||||
|  |  | ||||||
|  | enum class SplitWish { | ||||||
|  |     DONTCARE, | ||||||
|  |     SPLIT, | ||||||
|  |     NOSPLIT | ||||||
|  | } | ||||||
| @@ -1,8 +1,43 @@ | |||||||
| package prog8.code.core | package prog8.code.core | ||||||
|  |  | ||||||
|  | import java.nio.file.Path | ||||||
|  |  | ||||||
|  | enum class CpuType { | ||||||
|  |     CPU6502, | ||||||
|  |     CPU65C02, | ||||||
|  |     VIRTUAL | ||||||
|  | } | ||||||
|  |  | ||||||
| interface ICompilationTarget: IStringEncoding, IMemSizer { | interface ICompilationTarget: IStringEncoding, IMemSizer { | ||||||
|     val name: String |     val name: String | ||||||
|     val machine: IMachineDefinition |  | ||||||
|  |     val FLOAT_MAX_NEGATIVE: Double | ||||||
|  |     val FLOAT_MAX_POSITIVE: Double | ||||||
|  |     val FLOAT_MEM_SIZE: Int | ||||||
|  |     val STARTUP_CODE_RESERVED_SIZE: UInt        // this is here, so that certain compiler targets are able to tune this | ||||||
|  |     val PROGRAM_LOAD_ADDRESS : UInt | ||||||
|  |     val PROGRAM_MEMTOP_ADDRESS: UInt | ||||||
|  |     val BSSHIGHRAM_START: UInt | ||||||
|  |     val BSSHIGHRAM_END: UInt | ||||||
|  |     val BSSGOLDENRAM_START: UInt | ||||||
|  |     val BSSGOLDENRAM_END: UInt | ||||||
|  |  | ||||||
|  |     val cpu: CpuType | ||||||
|  |     var zeropage: Zeropage | ||||||
|  |     var golden: GoldenRam | ||||||
|  |     val libraryPath: Path? | ||||||
|  |     val customLauncher: List<String> | ||||||
|  |     val additionalAssemblerOptions: List<String> | ||||||
|  |     val defaultOutputType: OutputType | ||||||
|  |  | ||||||
|  |     fun initializeMemoryAreas(compilerOptions: CompilationOptions) | ||||||
|  |     fun getFloatAsmBytes(num: Number): String | ||||||
|  |  | ||||||
|  |     fun convertFloatToBytes(num: Double): List<UByte> | ||||||
|  |     fun convertBytesToFloat(bytes: List<UByte>): Double | ||||||
|  |  | ||||||
|  |     fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) | ||||||
|  |     fun isIOAddress(address: UInt): Boolean | ||||||
|  |  | ||||||
|     override fun encodeString(str: String, encoding: Encoding): List<UByte> |     override fun encodeString(str: String, encoding: Encoding): List<UByte> | ||||||
|     override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String |     override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String | ||||||
|   | |||||||
| @@ -13,4 +13,6 @@ interface IErrorReporter { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun noErrorForLine(position: Position): Boolean |     fun noErrorForLine(position: Position): Boolean | ||||||
|  |  | ||||||
|  |     fun printSingleError(errormessage: String) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,34 +0,0 @@ | |||||||
| package prog8.code.core |  | ||||||
|  |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| enum class CpuType { |  | ||||||
|     CPU6502, |  | ||||||
|     CPU65c02, |  | ||||||
|     VIRTUAL |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface IMachineDefinition { |  | ||||||
|     val FLOAT_MAX_NEGATIVE: Double |  | ||||||
|     val FLOAT_MAX_POSITIVE: Double |  | ||||||
|     val FLOAT_MEM_SIZE: Int |  | ||||||
|     val PROGRAM_LOAD_ADDRESS : UInt |  | ||||||
|     val BSSHIGHRAM_START: UInt |  | ||||||
|     val BSSHIGHRAM_END: UInt |  | ||||||
|     val BSSGOLDENRAM_START: UInt |  | ||||||
|     val BSSGOLDENRAM_END: UInt |  | ||||||
|  |  | ||||||
|     val cpu: CpuType |  | ||||||
|     var zeropage: Zeropage |  | ||||||
|     var golden: GoldenRam |  | ||||||
|  |  | ||||||
|     fun initializeMemoryAreas(compilerOptions: CompilationOptions) |  | ||||||
|     fun getFloatAsmBytes(num: Number): String |  | ||||||
|  |  | ||||||
|     fun convertFloatToBytes(num: Double): List<UByte> |  | ||||||
|     fun convertBytesToFloat(bytes: List<UByte>): Double |  | ||||||
|  |  | ||||||
|     fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) |  | ||||||
|     fun isIOAddress(address: UInt): Boolean |  | ||||||
| } |  | ||||||
| @@ -1,6 +1,15 @@ | |||||||
| package prog8.code.core | package prog8.code.core | ||||||
|  |  | ||||||
| interface IMemSizer { | interface IMemSizer { | ||||||
|     fun memorySize(dt: DataType): Int |     fun memorySize(dt: DataType, numElements: Int?): Int | ||||||
|     fun memorySize(arrayDt: DataType, numElements: Int): Int |  | ||||||
|  |     fun memorySize(dt: BaseDataType): Int { | ||||||
|  |         if(dt.isPassByRef) | ||||||
|  |             return memorySize(DataType.UWORD, null)      // a pointer size | ||||||
|  |         try { | ||||||
|  |             return memorySize(DataType.forDt(dt), null) | ||||||
|  |         } catch (x: NoSuchElementException) { | ||||||
|  |             throw IllegalArgumentException(x.message) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,8 @@ enum class Encoding(val prefix: String) { | |||||||
|     ISO5("iso5"),               // cx16  (iso-8859-5, cyrillic) |     ISO5("iso5"),               // cx16  (iso-8859-5, cyrillic) | ||||||
|     ISO16("iso16"),             // cx16  (iso-8859-16, eastern european) |     ISO16("iso16"),             // cx16  (iso-8859-16, eastern european) | ||||||
|     CP437("cp437"),             // cx16  (ibm pc, codepage 437) |     CP437("cp437"),             // cx16  (ibm pc, codepage 437) | ||||||
|     KATAKANA("kata")                 // cx16  (katakana) |     KATAKANA("kata"),           // cx16  (katakana) | ||||||
|  |     C64OS("c64os")              // c64 (C64 OS) | ||||||
| } | } | ||||||
|  |  | ||||||
| interface IStringEncoding { | interface IStringEncoding { | ||||||
|   | |||||||
| @@ -22,9 +22,10 @@ abstract class MemoryAllocator(protected val options: CompilationOptions) { | |||||||
| abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | ||||||
|  |  | ||||||
|     abstract val SCRATCH_B1 : UInt      // temp storage for a single byte |     abstract val SCRATCH_B1 : UInt      // temp storage for a single byte | ||||||
|     abstract val SCRATCH_REG : UInt     // temp storage for a register, must be B1+1 |     abstract val SCRATCH_REG : UInt     // temp storage for a register byte, must be B1+1 | ||||||
|     abstract val SCRATCH_W1 : UInt      // temp storage 1 for a word  $fb+$fc |     abstract val SCRATCH_W1 : UInt      // temp storage 1 for a word | ||||||
|     abstract val SCRATCH_W2 : UInt      // temp storage 2 for a word  $fb+$fc |     abstract val SCRATCH_W2 : UInt      // temp storage 2 for a word | ||||||
|  |     abstract val SCRATCH_PTR : UInt     // temp storage for a pointer | ||||||
|  |  | ||||||
|  |  | ||||||
|     // the variables allocated into Zeropage. |     // the variables allocated into Zeropage. | ||||||
| @@ -38,7 +39,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | |||||||
|             for (reserved in options.zpReserved) |             for (reserved in options.zpReserved) | ||||||
|                 reserve(reserved) |                 reserve(reserved) | ||||||
|  |  | ||||||
|             free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u)) |             free.removeAll(arrayOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u, SCRATCH_PTR, SCRATCH_PTR+1u)) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -70,9 +71,10 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | |||||||
|             return Err(MemAllocationError("zero page usage has been disabled")) |             return Err(MemAllocationError("zero page usage has been disabled")) | ||||||
|  |  | ||||||
|         val size: Int = |         val size: Int = | ||||||
|                 when (datatype) { |                 when { | ||||||
|                     in IntegerDatatypesWithBoolean -> options.compTarget.memorySize(datatype) |                     datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null) | ||||||
|                     DataType.STR, in ArrayDatatypes  -> { |                     datatype.isPointer -> options.compTarget.memorySize(datatype, null) | ||||||
|  |                     datatype.isString || datatype.isArray -> { | ||||||
|                         val memsize = options.compTarget.memorySize(datatype, numElements!!) |                         val memsize = options.compTarget.memorySize(datatype, numElements!!) | ||||||
|                         if(position!=null) |                         if(position!=null) | ||||||
|                             errors.warn("allocating a large value in zeropage; str/array $memsize bytes", position) |                             errors.warn("allocating a large value in zeropage; str/array $memsize bytes", position) | ||||||
| @@ -80,9 +82,9 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | |||||||
|                             errors.warn("$name: allocating a large value in zeropage; str/array $memsize bytes", Position.DUMMY) |                             errors.warn("$name: allocating a large value in zeropage; str/array $memsize bytes", Position.DUMMY) | ||||||
|                         memsize |                         memsize | ||||||
|                     } |                     } | ||||||
|                     DataType.FLOAT -> { |                     datatype.isFloat -> { | ||||||
|                         if (options.floats) { |                         if (options.floats) { | ||||||
|                             val memsize = options.compTarget.memorySize(DataType.FLOAT) |                             val memsize = options.compTarget.memorySize(DataType.FLOAT, null) | ||||||
|                             if(position!=null) |                             if(position!=null) | ||||||
|                                 errors.warn("allocating a large value in zeropage; float $memsize bytes", position) |                                 errors.warn("allocating a large value in zeropage; float $memsize bytes", position) | ||||||
|                             else |                             else | ||||||
| @@ -94,7 +96,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|         synchronized(this) { |         synchronized(this) { | ||||||
|             if(free.size > 0) { |             if(free.isNotEmpty()) { | ||||||
|                 if(size==1) { |                 if(size==1) { | ||||||
|                     for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { |                     for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { | ||||||
|                         if(oneSeparateByteFree(candidate)) |                         if(oneSeparateByteFree(candidate)) | ||||||
| @@ -118,10 +120,11 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | |||||||
|         require(size>=0) |         require(size>=0) | ||||||
|         free.removeAll(address until address+size.toUInt()) |         free.removeAll(address until address+size.toUInt()) | ||||||
|         if(name.isNotEmpty()) { |         if(name.isNotEmpty()) { | ||||||
|             allocatedVariables[name] = when(datatype) { |             allocatedVariables[name] = when { | ||||||
|                 in NumericDatatypes, DataType.BOOL -> VarAllocation(address, datatype, size)        // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments |                 datatype.isNumericOrBool -> VarAllocation(address, datatype, size)        // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments | ||||||
|                 DataType.STR -> VarAllocation(address, datatype, size) |                 datatype.isString -> VarAllocation(address, datatype, size) | ||||||
|                 in ArrayDatatypes -> VarAllocation(address, datatype, size) |                 datatype.isArray -> VarAllocation(address, datatype, size) | ||||||
|  |                 datatype.isPointer -> VarAllocation(address, datatype, size) | ||||||
|                 else -> throw AssemblyError("invalid dt") |                 else -> throw AssemblyError("invalid dt") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -133,8 +136,6 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) { | |||||||
|         require(size>0) |         require(size>0) | ||||||
|         return free.containsAll((address until address+size.toUInt()).toList()) |         return free.containsAll((address until address+size.toUInt()).toList()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     abstract fun allocateCx16VirtualRegisters() |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -150,14 +151,13 @@ class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAlloc | |||||||
|         errors: IErrorReporter): Result<VarAllocation, MemAllocationError> { |         errors: IErrorReporter): Result<VarAllocation, MemAllocationError> { | ||||||
|  |  | ||||||
|         val size: Int = |         val size: Int = | ||||||
|             when (datatype) { |             when { | ||||||
|                 in IntegerDatatypesWithBoolean -> options.compTarget.memorySize(datatype) |                 datatype.isIntegerOrBool -> options.compTarget.memorySize(datatype, null) | ||||||
|                 DataType.STR, in ArrayDatatypes  -> { |                 datatype.isString -> numElements!! | ||||||
|                     options.compTarget.memorySize(datatype, numElements!!) |                 datatype.isArray -> options.compTarget.memorySize(datatype, numElements!!) | ||||||
|                 } |                 datatype.isFloat -> { | ||||||
|                 DataType.FLOAT -> { |  | ||||||
|                     if (options.floats) { |                     if (options.floats) { | ||||||
|                         options.compTarget.memorySize(DataType.FLOAT) |                         options.compTarget.memorySize(DataType.FLOAT, null) | ||||||
|                     } else return Err(MemAllocationError("floating point option not enabled")) |                     } else return Err(MemAllocationError("floating point option not enabled")) | ||||||
|                 } |                 } | ||||||
|                 else -> throw MemAllocationError("weird dt") |                 else -> throw MemAllocationError("weird dt") | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| package prog8.code.core | package prog8.code.core | ||||||
|  |  | ||||||
| val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor")       // note: and,or are no longer associative because of Shortcircuit/McCarthy evaluation | val AssociativeOperators = arrayOf("+", "*", "&", "|", "^", "==", "!=", "xor")       // note: and,or are not associative because of Shortcircuit/McCarthy evaluation | ||||||
| val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=") | val ComparisonOperators = arrayOf("==", "!=", "<", ">", "<=", ">=") | ||||||
| val LogicalOperators = setOf("and", "or", "xor", "not", "in") | val LogicalOperators = arrayOf("and", "or", "xor", "not", "in") | ||||||
| val BitwiseOperators = setOf("&", "|", "^", "~") | val BitwiseOperators = arrayOf("&", "|", "^", "~") | ||||||
| val PrefixOperators = setOf("+", "-", "~", "not") | val PrefixOperators = arrayOf("+", "-", "~", "not") | ||||||
|  |  | ||||||
| fun invertedComparisonOperator(operator: String) = | fun invertedComparisonOperator(operator: String) = | ||||||
|     when (operator) { |     when (operator) { | ||||||
|   | |||||||
| @@ -1,21 +1,21 @@ | |||||||
| package prog8.code.core | package prog8.code.core | ||||||
|  |  | ||||||
| import prog8.code.core.SourceCode.Companion.LIBRARYFILEPREFIX | import prog8.code.sanitize | ||||||
|  | import prog8.code.source.SourceCode | ||||||
| import java.nio.file.InvalidPathException | import java.nio.file.InvalidPathException | ||||||
| import kotlin.io.path.Path | import kotlin.io.path.Path | ||||||
| import kotlin.io.path.absolute |  | ||||||
|  |  | ||||||
| data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { | data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { | ||||||
|     override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]" |     override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]" | ||||||
|     fun toClickableStr(): String { |     fun toClickableStr(): String { | ||||||
|         if(this===DUMMY) |         if(this===DUMMY) | ||||||
|             return "" |             return "" | ||||||
|         if(file.startsWith(LIBRARYFILEPREFIX)) |         if(SourceCode.isLibraryResource(file)) | ||||||
|             return "$file:$line:$startCol:" |             return "$file:$line:$startCol:" | ||||||
|         return try { |         return try { | ||||||
|             val path = Path(file).absolute().normalize().toString() |             val path = Path(file).sanitize().toString() | ||||||
|             "file://$path:$line:$startCol:" |             "file://$path:$line:$startCol:" | ||||||
|         } catch(x: InvalidPathException) { |         } catch(_: InvalidPathException) { | ||||||
|             // this can occur on Windows when the source origin contains "invalid" characters such as ':' |             // this can occur on Windows when the source origin contains "invalid" characters such as ':' | ||||||
|             "file://$file:$line:$startCol:" |             "file://$file:$line:$startCol:" | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,258 +0,0 @@ | |||||||
| package prog8.code.optimize |  | ||||||
|  |  | ||||||
| import prog8.code.StRomSub |  | ||||||
| import prog8.code.SymbolTable |  | ||||||
| import prog8.code.ast.* |  | ||||||
| import prog8.code.core.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) { |  | ||||||
|     if (!options.optimize) |  | ||||||
|         return |  | ||||||
|     while (errors.noErrors() && |  | ||||||
|         (optimizeCommonSubExpressions(program, errors) |  | ||||||
|                 + optimizeBitTest(program, options) |  | ||||||
|                 + optimizeAssignTargets(program, st, errors)) > 0 |  | ||||||
|     ) { |  | ||||||
|         // keep rolling |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Boolean) { |  | ||||||
|     fun recurse(node: PtNode, depth: Int) { |  | ||||||
|         if(act(node, depth)) |  | ||||||
|             node.children.forEach { recurse(it, depth+1) } |  | ||||||
|     } |  | ||||||
|     recurse(root, 0) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private var tempVarCounter = 0 |  | ||||||
|  |  | ||||||
| private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int { |  | ||||||
|  |  | ||||||
|     fun extractableSubExpr(expr: PtExpression): Boolean { |  | ||||||
|         if(expr is PtArrayIndexer && expr.index.isSimple()) |  | ||||||
|             return false |  | ||||||
|         if (expr is PtMemoryByte && expr.address.isSimple()) |  | ||||||
|             return false |  | ||||||
|  |  | ||||||
|         val result = if(expr is PtBinaryExpression) |  | ||||||
|             expr.type !in ByteDatatypes || |  | ||||||
|             !(expr.left.isSimple() && expr.right.isSimple()) || |  | ||||||
|             (expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators) |  | ||||||
|         else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes) |  | ||||||
|             true |  | ||||||
|         else |  | ||||||
|             !expr.isSimple() |  | ||||||
|         return result |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // for each Binaryexpression, recurse to find a common subexpression pair therein. |  | ||||||
|     val commons = mutableMapOf<PtBinaryExpression, Pair<PtExpression, PtExpression>>() |  | ||||||
|     walkAst(program) { node: PtNode, depth: Int -> |  | ||||||
|         if(node is PtBinaryExpression) { |  | ||||||
|             val subExpressions = mutableListOf<PtExpression>() |  | ||||||
|             walkAst(node.left) { subNode: PtNode, subDepth: Int -> |  | ||||||
|                 if (subNode is PtExpression) { |  | ||||||
|                     if(extractableSubExpr(subNode)) subExpressions.add(subNode) |  | ||||||
|                     true |  | ||||||
|                 } else false |  | ||||||
|             } |  | ||||||
|             walkAst(node.right) { subNode: PtNode, subDepth: Int -> |  | ||||||
|                 if (subNode is PtExpression) { |  | ||||||
|                     if(extractableSubExpr(subNode)) subExpressions.add(subNode) |  | ||||||
|                     true |  | ||||||
|                 } else false |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             outer@for (first in subExpressions) { |  | ||||||
|                 for (second in subExpressions) { |  | ||||||
|                     if (first!==second && first isSameAs second) { |  | ||||||
|                         commons[node] = first to second |  | ||||||
|                         break@outer     // do only 1 replacement at a time per binaryexpression |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             false |  | ||||||
|         } else true |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // replace common subexpressions by a temp variable that is assigned only once. |  | ||||||
|     // TODO: check for commonalities across multiple separate expressions in the current scope, not only inside a single line |  | ||||||
|     commons.forEach { binexpr, (occurrence1, occurrence2) -> |  | ||||||
|         val (stmtContainer, stmt) = findContainingStatements(binexpr) |  | ||||||
|         val occurrence1idx = occurrence1.parent.children.indexOf(occurrence1) |  | ||||||
|         val occurrence2idx = occurrence2.parent.children.indexOf(occurrence2) |  | ||||||
|         val containerScopedName = findScopeName(stmtContainer) |  | ||||||
|         tempVarCounter++ |  | ||||||
|         val tempvarName = "prog8_subexprvar_$tempVarCounter" |  | ||||||
|         // TODO: some tempvars could be reused, if they are from different lines |  | ||||||
|  |  | ||||||
|         val datatype = occurrence1.type |  | ||||||
|         val singleReplacement1 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence1.position) |  | ||||||
|         val singleReplacement2 = PtIdentifier("$containerScopedName.$tempvarName", datatype, occurrence2.position) |  | ||||||
|         occurrence1.parent.children[occurrence1idx] = singleReplacement1 |  | ||||||
|         singleReplacement1.parent = occurrence1.parent |  | ||||||
|         occurrence2.parent.children[occurrence2idx] = singleReplacement2 |  | ||||||
|         singleReplacement2.parent = occurrence2.parent |  | ||||||
|  |  | ||||||
|         val tempassign = PtAssignment(binexpr.position).also { assign -> |  | ||||||
|             assign.add(PtAssignTarget(false, binexpr.position).also { tgt-> |  | ||||||
|                 tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position)) |  | ||||||
|             }) |  | ||||||
|             assign.add(occurrence1) |  | ||||||
|             occurrence1.parent = assign |  | ||||||
|         } |  | ||||||
|         stmtContainer.children.add(stmtContainer.children.indexOf(stmt), tempassign) |  | ||||||
|         tempassign.parent = stmtContainer |  | ||||||
|  |  | ||||||
|         val tempvar = PtVariable(tempvarName, datatype, ZeropageWish.NOT_IN_ZEROPAGE, null, null, binexpr.position) |  | ||||||
|         stmtContainer.add(0, tempvar) |  | ||||||
|         tempvar.parent = stmtContainer |  | ||||||
|  |  | ||||||
|         // errors.info("common subexpressions replaced by a tempvar, maybe simplify the expression manually", binexpr.position) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return commons.size |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int { |  | ||||||
|     var changes = 0 |  | ||||||
|     walkAst(program) { node: PtNode, depth: Int -> |  | ||||||
|         if(node is PtAssignment) { |  | ||||||
|             val value = node.value |  | ||||||
|             val functionName = when(value) { |  | ||||||
|                 is PtBuiltinFunctionCall -> value.name |  | ||||||
|                 is PtFunctionCall -> value.name |  | ||||||
|                 else -> null |  | ||||||
|             } |  | ||||||
|             if(functionName!=null) { |  | ||||||
|                 val stNode = st.lookup(functionName) |  | ||||||
|                 if (stNode is StRomSub) { |  | ||||||
|                     require(node.children.size==stNode.returns.size+1) { |  | ||||||
|                         "number of targets must match return values" |  | ||||||
|                     } |  | ||||||
|                     node.children.zip(stNode.returns).withIndex().forEach { (index, xx) -> |  | ||||||
|                         val target = xx.first as PtAssignTarget |  | ||||||
|                         val returnedRegister = xx.second.register.registerOrPair |  | ||||||
|                         if(returnedRegister!=null && !target.void && target.identifier!=null) { |  | ||||||
|                             if(isSame(target.identifier!!, xx.second.type, returnedRegister)) { |  | ||||||
|                                 // output register is already identical to target register, so it can become void |  | ||||||
|                                 val voidTarget = PtAssignTarget(true, target.position) |  | ||||||
|                                 node.children[index] = voidTarget |  | ||||||
|                                 voidTarget.parent = node |  | ||||||
|                                 changes++ |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) { |  | ||||||
|                     // all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine |  | ||||||
|                     val index = node.parent.children.indexOf(node) |  | ||||||
|                     val voidCall = PtFunctionCall(functionName, true, value.type, value.position) |  | ||||||
|                     value.children.forEach { voidCall.add(it) } |  | ||||||
|                     node.parent.children[index] = voidCall |  | ||||||
|                     voidCall.parent = node.parent |  | ||||||
|                     changes++ |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         true |  | ||||||
|     } |  | ||||||
|     return changes |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): Int { |  | ||||||
|     if(options.compTarget.machine.cpu == CpuType.VIRTUAL) |  | ||||||
|         return 0        // the special bittest optimization is not yet valid for the IR |  | ||||||
|  |  | ||||||
|     var changes = 0 |  | ||||||
|     var recurse = true |  | ||||||
|     walkAst(program) { node: PtNode, depth: Int -> |  | ||||||
|         if(node is PtIfElse) { |  | ||||||
|             val condition = node.condition as? PtBinaryExpression |  | ||||||
|             if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) { |  | ||||||
|                 if(condition.right.asConstInteger()==0) { |  | ||||||
|                     val and = condition.left as? PtBinaryExpression |  | ||||||
|                     if(and != null && and.operator=="&" && and.type == DataType.UBYTE) { |  | ||||||
|                         val variable = and.left as? PtIdentifier |  | ||||||
|                         val bitmask = and.right.asConstInteger() |  | ||||||
|                         if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) { |  | ||||||
|                             val setOrNot = if(condition.operator=="!=") "set" else "notset" |  | ||||||
|                             val index = node.parent.children.indexOf(node) |  | ||||||
|                             val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position) |  | ||||||
|                             bittestCall.add(variable) |  | ||||||
|                             if(bitmask==128) |  | ||||||
|                                 bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position)) |  | ||||||
|                             else |  | ||||||
|                                 bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position)) |  | ||||||
|                             val ifElse = PtIfElse(node.position) |  | ||||||
|                             ifElse.add(bittestCall) |  | ||||||
|                             ifElse.add(node.ifScope) |  | ||||||
|                             if(node.hasElse()) |  | ||||||
|                                 ifElse.add(node.elseScope) |  | ||||||
|                             node.parent.children[index] = ifElse |  | ||||||
|                             ifElse.parent = node.parent |  | ||||||
|                             changes++ |  | ||||||
|                             recurse = false |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         recurse |  | ||||||
|     } |  | ||||||
|     return changes |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean { |  | ||||||
|     if(returnedRegister in Cx16VirtualRegisters) { |  | ||||||
|         val regname = returnedRegister.name.lowercase() |  | ||||||
|         val identifierRegName = identifier.name.substringAfterLast('.') |  | ||||||
|         /* |  | ||||||
|             cx16.r?    UWORD |  | ||||||
|             cx16.r?s   WORD |  | ||||||
|             cx16.r?L   UBYTE |  | ||||||
|             cx16.r?H   UBYTE |  | ||||||
|             cx16.r?sL  BYTE |  | ||||||
|             cx16.r?sH  BYTE |  | ||||||
|          */ |  | ||||||
|         if(identifier.type in ByteDatatypes && type in ByteDatatypes) { |  | ||||||
|             if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) { |  | ||||||
|                 return identifierRegName.substring(2) in arrayOf("", "L", "sL")     // note: not the -H (msb) variants! |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if(identifier.type in WordDatatypes && type in WordDatatypes) { |  | ||||||
|             if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) { |  | ||||||
|                 return identifierRegName.substring(2) in arrayOf("", "s") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return false   // there are no identifiers directly corresponding to cpu registers |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun findScopeName(node: PtNode): String { |  | ||||||
|     var parent=node |  | ||||||
|     while(parent !is PtNamedNode) |  | ||||||
|         parent = parent.parent |  | ||||||
|     return parent.scopedName |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun findContainingStatements(node: PtNode): Pair<PtNode, PtNode> {      // returns (parentstatementcontainer, childstatement) |  | ||||||
|     var parent = node.parent |  | ||||||
|     var child = node |  | ||||||
|     while(true) { |  | ||||||
|         if(parent is IPtStatementContainer) { |  | ||||||
|             return parent to child |  | ||||||
|         } |  | ||||||
|         child=parent |  | ||||||
|         parent=parent.parent |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										74
									
								
								codeCore/src/prog8/code/source/ImportFileSystem.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								codeCore/src/prog8/code/source/ImportFileSystem.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | package prog8.code.source | ||||||
|  |  | ||||||
|  | import prog8.code.core.Position | ||||||
|  | import prog8.code.sanitize | ||||||
|  | import java.nio.file.Path | ||||||
|  | import java.util.* | ||||||
|  | import kotlin.io.path.Path | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // Resource caching "filesystem". | ||||||
|  | // Note that it leaves the decision to load a resource or an actual disk file to the caller. | ||||||
|  |  | ||||||
|  | object ImportFileSystem { | ||||||
|  |  | ||||||
|  |     fun expandTilde(path: String): String = if (path.startsWith("~")) { | ||||||
|  |         val userHome = System.getProperty("user.home") | ||||||
|  |         userHome + path.drop(1) | ||||||
|  |     } else { | ||||||
|  |         path | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun expandTilde(path: Path): Path = Path(expandTilde(path.toString())) | ||||||
|  |  | ||||||
|  |     fun getFile(path: Path, isLibrary: Boolean=false): SourceCode { | ||||||
|  |         val normalized = path.sanitize() | ||||||
|  |         val cached = cache[normalized.toString()] | ||||||
|  |         if (cached != null) | ||||||
|  |             return cached | ||||||
|  |         val file = SourceCode.File(normalized, isLibrary) | ||||||
|  |         cache[normalized.toString()] = file | ||||||
|  |         return file | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getResource(name: String): SourceCode { | ||||||
|  |         val cached = cache[name] | ||||||
|  |         if (cached != null) return cached | ||||||
|  |         val resource = SourceCode.Resource(name) | ||||||
|  |         cache[name] = resource | ||||||
|  |         return resource | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun retrieveSourceLine(position: Position): String { | ||||||
|  |         if(SourceCode.isLibraryResource(position.file)) { | ||||||
|  |             val cached = cache[SourceCode.withoutPrefix(position.file)] | ||||||
|  |             if(cached != null) | ||||||
|  |                 return getLine(cached, position.line) | ||||||
|  |         } | ||||||
|  |         val cached = cache[position.file] | ||||||
|  |         if(cached != null) | ||||||
|  |             return getLine(cached, position.line) | ||||||
|  |         val path = Path(position.file).sanitize() | ||||||
|  |         val cached2 = cache[path.toString()] | ||||||
|  |         if(cached2 != null) | ||||||
|  |             return getLine(cached2, position.line) | ||||||
|  |         throw NoSuchElementException("cannot get source line $position, with path $path") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun getLine(code: SourceCode, lineIndex: Int): String { | ||||||
|  |         var spans = lineSpanCache[code] | ||||||
|  |         if(spans==null) { | ||||||
|  |             val lineSpans = Regex("^", RegexOption.MULTILINE).findAll(code.text).map { it.range.first } | ||||||
|  |             val ends = lineSpans.drop(1) + code.text.length | ||||||
|  |             spans = lineSpans.zip(ends).map { (start, end) -> LineSpan(start, end) }.toList().toTypedArray() | ||||||
|  |             lineSpanCache[code] = spans | ||||||
|  |         } | ||||||
|  |         val span = spans[lineIndex - 1] | ||||||
|  |         return code.text.substring(span.start, span.end).trim() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private class LineSpan(val start: Int, val end: Int) | ||||||
|  |  | ||||||
|  |     private val cache = TreeMap<String, SourceCode>(String.CASE_INSENSITIVE_ORDER) | ||||||
|  |     private val lineSpanCache = mutableMapOf<SourceCode, Array<LineSpan>>() | ||||||
|  | } | ||||||
| @@ -1,16 +1,13 @@ | |||||||
| package prog8.code.core | package prog8.code.source | ||||||
| 
 | 
 | ||||||
| import java.io.File | import prog8.code.sanitize | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
| import java.nio.file.Path | import java.nio.file.Path | ||||||
| import java.text.Normalizer | import java.text.Normalizer | ||||||
| import kotlin.io.path.Path | import kotlin.io.path.Path | ||||||
|  | import kotlin.io.path.absolute | ||||||
| import kotlin.io.path.readText | import kotlin.io.path.readText | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| const val internedStringsModuleName = "prog8_interned_strings" |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Encapsulates - and ties together - actual source code (=text) and its [origin]. |  * Encapsulates - and ties together - actual source code (=text) and its [origin]. | ||||||
|  */ |  */ | ||||||
| @@ -26,6 +23,11 @@ sealed class SourceCode { | |||||||
|      */ |      */ | ||||||
|     abstract val isFromFilesystem: Boolean |     abstract val isFromFilesystem: Boolean | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Whether this [SourceCode] instance was created from a library module file | ||||||
|  |      */ | ||||||
|  |     abstract val isFromLibrary: Boolean | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * The logical name of the source code unit. Usually the module's name. |      * The logical name of the source code unit. Usually the module's name. | ||||||
|      */ |      */ | ||||||
| @@ -55,14 +57,21 @@ sealed class SourceCode { | |||||||
|         /** |         /** | ||||||
|          * filename prefix to designate library files that will be retreived from internal resources rather than disk |          * filename prefix to designate library files that will be retreived from internal resources rather than disk | ||||||
|          */ |          */ | ||||||
|         const val LIBRARYFILEPREFIX = "library:" |         private const val LIBRARYFILEPREFIX = "library:" | ||||||
|         const val STRINGSOURCEPREFIX = "string:" |         private const val STRINGSOURCEPREFIX = "string:" | ||||||
|         val curdir: Path = Path(".").toAbsolutePath() |         val curdir: Path = Path(".").absolute() | ||||||
|         fun relative(path: Path): Path = curdir.relativize(path.toAbsolutePath()) |         fun relative(path: Path): Path = curdir.relativize(path.sanitize()) | ||||||
|         fun isRegularFilesystemPath(pathString: String) = |         fun isRegularFilesystemPath(pathString: String) = !isLibraryResource(pathString) && !isStringResource(pathString) | ||||||
|             !(pathString.startsWith(LIBRARYFILEPREFIX) || pathString.startsWith(STRINGSOURCEPREFIX)) |  | ||||||
| 
 |  | ||||||
|         fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX) |         fun isLibraryResource(path: String) = path.startsWith(LIBRARYFILEPREFIX) | ||||||
|  |         fun isStringResource(path: String) = path.startsWith(STRINGSOURCEPREFIX) | ||||||
|  |         fun withoutPrefix(path: String): String { | ||||||
|  |             return if(isLibraryResource(path)) | ||||||
|  |                 path.removePrefix(LIBRARYFILEPREFIX) | ||||||
|  |             else if(isStringResource(path)) | ||||||
|  |                 path.removePrefix(STRINGSOURCEPREFIX) | ||||||
|  |             else | ||||||
|  |                 path | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @@ -73,6 +82,7 @@ sealed class SourceCode { | |||||||
|         override val text = origText.replace("\\R".toRegex(), "\n")      // normalize line endings |         override val text = origText.replace("\\R".toRegex(), "\n")      // normalize line endings | ||||||
|         override val isFromResources = false |         override val isFromResources = false | ||||||
|         override val isFromFilesystem = false |         override val isFromFilesystem = false | ||||||
|  |         override val isFromLibrary = false | ||||||
|         override val origin = "$STRINGSOURCEPREFIX${System.identityHashCode(text).toString(16)}" |         override val origin = "$STRINGSOURCEPREFIX${System.identityHashCode(text).toString(16)}" | ||||||
|         override val name = "<unnamed-text>" |         override val name = "<unnamed-text>" | ||||||
|     } |     } | ||||||
| @@ -80,12 +90,13 @@ sealed class SourceCode { | |||||||
|     /** |     /** | ||||||
|      * Get [SourceCode] from the file represented by the specified Path. |      * Get [SourceCode] from the file represented by the specified Path. | ||||||
|      * This immediately reads the file fully into memory. |      * This immediately reads the file fully into memory. | ||||||
|  |      * You can only get an instance of this via the ImportFileSystem object. | ||||||
|      * |      * | ||||||
|      * [origin] will be the given path in absolute and normalized form. |      * [origin] will be the given path in absolute and normalized form. | ||||||
|      * @throws NoSuchFileException if the file does not exist |      * @throws NoSuchFileException if the file does not exist | ||||||
|      * @throws FileSystemException if the file cannot be read |      * @throws FileSystemException if the file cannot be read | ||||||
|      */ |      */ | ||||||
|     class File(path: Path): SourceCode() { |     internal class File(path: Path, override val isFromLibrary: Boolean): SourceCode() { | ||||||
|         override val text: String |         override val text: String | ||||||
|         override val origin: String |         override val origin: String | ||||||
|         override val name: String |         override val name: String | ||||||
| @@ -109,12 +120,14 @@ sealed class SourceCode { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * [origin]: `library:/x/y/z.p8` for a given `pathString` of "x/y/z.p8" |      * [origin]: `library:/x/y/z.p8` for a given `pathString` of "x/y/z.p8" | ||||||
|  |      * You can only get an instance of this via the ImportFileSystem object. | ||||||
|      */ |      */ | ||||||
|     class Resource(pathString: String): SourceCode() { |     internal class Resource(pathString: String): SourceCode() { | ||||||
|         private val normalized = "/" + Path(pathString).normalize().toMutableList().joinToString("/") |         private val normalized = "/" + Path(pathString).normalize().toMutableList().joinToString("/") | ||||||
| 
 | 
 | ||||||
|         override val isFromResources = true |         override val isFromResources = true | ||||||
|         override val isFromFilesystem = false |         override val isFromFilesystem = false | ||||||
|  |         override val isFromLibrary = true | ||||||
|         override val origin = "$LIBRARYFILEPREFIX$normalized" |         override val origin = "$LIBRARYFILEPREFIX$normalized" | ||||||
|         override val text: String |         override val text: String | ||||||
|         override val name: String |         override val name: String | ||||||
| @@ -124,7 +137,7 @@ sealed class SourceCode { | |||||||
|             if (rscURL == null) { |             if (rscURL == null) { | ||||||
|                 val rscRoot = object {}.javaClass.getResource("/") |                 val rscRoot = object {}.javaClass.getResource("/") | ||||||
|                 throw NoSuchFileException( |                 throw NoSuchFileException( | ||||||
|                     File(normalized), |                     java.io.File(normalized), | ||||||
|                     reason = "looked in resources rooted at $rscRoot" |                     reason = "looked in resources rooted at $rscRoot" | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| @@ -141,37 +154,8 @@ sealed class SourceCode { | |||||||
|     class Generated(override val name: String) : SourceCode() { |     class Generated(override val name: String) : SourceCode() { | ||||||
|         override val isFromResources: Boolean = false |         override val isFromResources: Boolean = false | ||||||
|         override val isFromFilesystem: Boolean = false |         override val isFromFilesystem: Boolean = false | ||||||
|  |         override val isFromLibrary: Boolean = false | ||||||
|         override val origin: String = name |         override val origin: String = name | ||||||
|         override val text: String = "<generated code node, no text representation>" |         override val text: String = "<generated code node, no text representation>" | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| object SourceLineCache { |  | ||||||
|     private val cache = mutableMapOf<String, List<String>>() |  | ||||||
| 
 |  | ||||||
|     private fun getCachedFile(file: String): List<String> { |  | ||||||
|         val existing = cache[file] |  | ||||||
|         if(existing!=null) |  | ||||||
|             return existing |  | ||||||
|         if (SourceCode.isRegularFilesystemPath(file)) { |  | ||||||
|             val source = SourceCode.File(Path(file)) |  | ||||||
|             cache[file] = source.text.split('\n', '\r').map { it.trim() } |  | ||||||
|             return cache.getValue(file) |  | ||||||
|         } else if(file.startsWith(SourceCode.LIBRARYFILEPREFIX)) { |  | ||||||
|             val source = SourceCode.Resource(file.drop(SourceCode.LIBRARYFILEPREFIX.length)) |  | ||||||
|             cache[file] = source.text.split('\n', '\r').map { it.trim()} |  | ||||||
|             return cache.getValue(file) |  | ||||||
|         } |  | ||||||
|         return emptyList() |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     fun retrieveLine(position: Position): String? { |  | ||||||
|         if (position.line>0) { |  | ||||||
|             val lines = getCachedFile(position.file) |  | ||||||
|             if(lines.isNotEmpty()) |  | ||||||
|                 return lines[position.line-1] |  | ||||||
|         } |  | ||||||
|         return null |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| package prog8.code.target |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
| import prog8.code.target.atari.AtariMachineDefinition |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer { |  | ||||||
|     override val name = NAME |  | ||||||
|     override val machine = AtariMachineDefinition() |  | ||||||
|     override val defaultEncoding = Encoding.ATASCII |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         const val NAME = "atari" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun memorySize(dt: DataType): Int { |  | ||||||
|         return when(dt) { |  | ||||||
|             in ByteDatatypesWithBoolean -> 1 |  | ||||||
|             in WordDatatypes, in PassByReferenceDatatypes -> 2 |  | ||||||
|             DataType.FLOAT -> machine.FLOAT_MEM_SIZE |  | ||||||
|             else -> throw IllegalArgumentException("invalid datatype") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun memorySize(arrayDt: DataType, numElements: Int) = |  | ||||||
|         if(arrayDt==DataType.UWORD) |  | ||||||
|             numElements    // pointer to bytes. |  | ||||||
|         else |  | ||||||
|             memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,19 +1,81 @@ | |||||||
| package prog8.code.target | package prog8.code.target | ||||||
|  |  | ||||||
| import prog8.code.core.Encoding | import prog8.code.core.* | ||||||
| import prog8.code.core.ICompilationTarget | import prog8.code.target.encodings.Encoder | ||||||
| import prog8.code.core.IMemSizer | import prog8.code.target.zp.C128Zeropage | ||||||
| import prog8.code.core.IStringEncoding | import java.nio.file.Path | ||||||
| import prog8.code.target.c128.C128MachineDefinition |  | ||||||
| import prog8.code.target.cbm.CbmMemorySizer |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer { | class C128Target: ICompilationTarget, | ||||||
|  |     IStringEncoding by Encoder(true), | ||||||
|  |     IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) { | ||||||
|  |  | ||||||
|     override val name = NAME |     override val name = NAME | ||||||
|     override val machine = C128MachineDefinition() |  | ||||||
|     override val defaultEncoding = Encoding.PETSCII |     override val defaultEncoding = Encoding.PETSCII | ||||||
|  |     override val libraryPath = null | ||||||
|  |     override val customLauncher = emptyList<String>() | ||||||
|  |     override val additionalAssemblerOptions = emptyList<String>() | ||||||
|  |     override val defaultOutputType = OutputType.PRG | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         const val NAME = "c128" |         const val NAME = "c128" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     override val cpu = CpuType.CPU6502 | ||||||
|  |  | ||||||
|  |     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE | ||||||
|  |     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE | ||||||
|  |     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE | ||||||
|  |     override val STARTUP_CODE_RESERVED_SIZE = 20u | ||||||
|  |     override val PROGRAM_LOAD_ADDRESS = 0x1c01u | ||||||
|  |     override val PROGRAM_MEMTOP_ADDRESS = 0xc000u | ||||||
|  |  | ||||||
|  |     override val BSSHIGHRAM_START = 0u    // TODO address? | ||||||
|  |     override val BSSHIGHRAM_END = 0u      // TODO address? | ||||||
|  |     override val BSSGOLDENRAM_START = 0u  // TODO address? | ||||||
|  |     override val BSSGOLDENRAM_END = 0u    // TODO address? | ||||||
|  |  | ||||||
|  |     override lateinit var zeropage: Zeropage | ||||||
|  |     override lateinit var golden: GoldenRam | ||||||
|  |  | ||||||
|  |     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() | ||||||
|  |  | ||||||
|  |     override fun convertFloatToBytes(num: Double): List<UByte> { | ||||||
|  |         val m5 = Mflpt5.fromNumber(num) | ||||||
|  |         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun convertBytesToFloat(bytes: List<UByte>): Double { | ||||||
|  |         require(bytes.size==5) { "need 5 bytes" } | ||||||
|  |         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) | ||||||
|  |         return m5.toDouble() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { | ||||||
|  |         if(selectedEmulator!=1) { | ||||||
|  |             System.err.println("The c128 target only supports the main emulator (Vice).") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!quiet) | ||||||
|  |             println("\nStarting C-128 emulator x128...") | ||||||
|  |  | ||||||
|  |         val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString()) | ||||||
|  |         val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist, | ||||||
|  |             "-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg") | ||||||
|  |         val processb = ProcessBuilder(cmdline) | ||||||
|  |         if(!quiet) | ||||||
|  |             processb.inheritIO() | ||||||
|  |         val process: Process = processb.start() | ||||||
|  |         process.waitFor() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu | ||||||
|  |  | ||||||
|  |     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { | ||||||
|  |         zeropage = C128Zeropage(compilerOptions) | ||||||
|  |         golden = GoldenRam(compilerOptions, UIntRange.EMPTY)    // TODO does the c128 have some of this somewhere? | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,23 +1,95 @@ | |||||||
| package prog8.code.target | package prog8.code.target | ||||||
|  |  | ||||||
| import prog8.code.core.Encoding | import prog8.code.core.* | ||||||
| import prog8.code.core.ICompilationTarget | import prog8.code.target.encodings.Encoder | ||||||
| import prog8.code.core.IMemSizer | import prog8.code.target.zp.C64Zeropage | ||||||
| import prog8.code.core.IStringEncoding | import java.io.IOException | ||||||
| import prog8.code.target.c64.C64MachineDefinition | import java.nio.file.Path | ||||||
| import prog8.code.target.cbm.CbmMemorySizer |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer { | class C64Target: ICompilationTarget, | ||||||
|  |     IStringEncoding by Encoder(true), | ||||||
|  |     IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) { | ||||||
|  |  | ||||||
|     override val name = NAME |     override val name = NAME | ||||||
|     override val machine = C64MachineDefinition() |  | ||||||
|     override val defaultEncoding = Encoding.PETSCII |     override val defaultEncoding = Encoding.PETSCII | ||||||
|  |     override val libraryPath = null | ||||||
|  |     override val customLauncher = emptyList<String>() | ||||||
|  |     override val additionalAssemblerOptions = emptyList<String>() | ||||||
|  |     override val defaultOutputType = OutputType.PRG | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         const val NAME = "c64" |         const val NAME = "c64" | ||||||
|  |  | ||||||
|         fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list" |         fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     override val cpu = CpuType.CPU6502 | ||||||
|  |  | ||||||
|  |     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE | ||||||
|  |     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE | ||||||
|  |     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE | ||||||
|  |     override val STARTUP_CODE_RESERVED_SIZE = 20u | ||||||
|  |     override val PROGRAM_LOAD_ADDRESS = 0x0801u | ||||||
|  |     override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u      // $a000  if floats are used | ||||||
|  |     // note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15 | ||||||
|  |  | ||||||
|  |     override val BSSHIGHRAM_START = 0xc000u | ||||||
|  |     override val BSSHIGHRAM_END = 0xcfdfu | ||||||
|  |     override val BSSGOLDENRAM_START = 0u        // no golden ram on C64 | ||||||
|  |     override val BSSGOLDENRAM_END = 0u | ||||||
|  |  | ||||||
|  |     override lateinit var zeropage: Zeropage | ||||||
|  |     override lateinit var golden: GoldenRam | ||||||
|  |  | ||||||
|  |     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() | ||||||
|  |  | ||||||
|  |     override fun convertFloatToBytes(num: Double): List<UByte> { | ||||||
|  |         val m5 = Mflpt5.fromNumber(num) | ||||||
|  |         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun convertBytesToFloat(bytes: List<UByte>): Double { | ||||||
|  |         require(bytes.size==5) { "need 5 bytes" } | ||||||
|  |         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) | ||||||
|  |         return m5.toDouble() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { | ||||||
|  |         if(selectedEmulator!=1) { | ||||||
|  |             System.err.println("The c64 target only supports the main emulator (Vice).") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for(emulator in listOf("x64sc", "x64")) { | ||||||
|  |             if(!quiet) | ||||||
|  |                 println("\nStarting C-64 emulator $emulator...") | ||||||
|  |  | ||||||
|  |             val viceMonlist = viceMonListName(programNameWithPath.toString()) | ||||||
|  |             val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist, | ||||||
|  |                 "-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg") | ||||||
|  |             val processb = ProcessBuilder(cmdline) | ||||||
|  |             if(!quiet) | ||||||
|  |                 processb.inheritIO() | ||||||
|  |             val process: Process | ||||||
|  |             try { | ||||||
|  |                 process=processb.start() | ||||||
|  |             } catch(_: IOException) { | ||||||
|  |                 continue  // try the next emulator executable | ||||||
|  |             } | ||||||
|  |             process.waitFor() | ||||||
|  |             break | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu | ||||||
|  |  | ||||||
|  |     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { | ||||||
|  |         zeropage = C64Zeropage(compilerOptions) | ||||||
|  |         golden = GoldenRam(compilerOptions, UIntRange.EMPTY) | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -26,7 +98,6 @@ val CompilationTargets = listOf( | |||||||
|     C128Target.NAME, |     C128Target.NAME, | ||||||
|     Cx16Target.NAME, |     Cx16Target.NAME, | ||||||
|     PETTarget.NAME, |     PETTarget.NAME, | ||||||
|     AtariTarget.NAME, |  | ||||||
|     VMTarget.NAME |     VMTarget.NAME | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -35,7 +106,6 @@ fun getCompilationTargetByName(name: String) = when(name.lowercase()) { | |||||||
|     C128Target.NAME -> C128Target() |     C128Target.NAME -> C128Target() | ||||||
|     Cx16Target.NAME -> Cx16Target() |     Cx16Target.NAME -> Cx16Target() | ||||||
|     PETTarget.NAME -> PETTarget() |     PETTarget.NAME -> PETTarget() | ||||||
|     AtariTarget.NAME -> AtariTarget() |  | ||||||
|     VMTarget.NAME -> VMTarget() |     VMTarget.NAME -> VMTarget() | ||||||
|     else -> throw IllegalArgumentException("invalid compilation target") |     else -> throw IllegalArgumentException("invalid compilation target") | ||||||
| } | } | ||||||
							
								
								
									
										173
									
								
								codeCore/src/prog8/code/target/ConfigFileTarget.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								codeCore/src/prog8/code/target/ConfigFileTarget.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | |||||||
|  | package prog8.code.target | ||||||
|  |  | ||||||
|  | import prog8.code.core.* | ||||||
|  | import prog8.code.source.ImportFileSystem.expandTilde | ||||||
|  | import prog8.code.target.encodings.Encoder | ||||||
|  | import prog8.code.target.zp.ConfigurableZeropage | ||||||
|  | import java.io.IOException | ||||||
|  | import java.nio.file.Path | ||||||
|  | import java.util.* | ||||||
|  | import kotlin.io.path.Path | ||||||
|  | import kotlin.io.path.inputStream | ||||||
|  | import kotlin.io.path.isDirectory | ||||||
|  | import kotlin.io.path.nameWithoutExtension | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ConfigFileTarget( | ||||||
|  |     override val name: String, | ||||||
|  |     override val defaultEncoding: Encoding, | ||||||
|  |     override val cpu: CpuType, | ||||||
|  |     override val PROGRAM_LOAD_ADDRESS: UInt, | ||||||
|  |     override val PROGRAM_MEMTOP_ADDRESS: UInt, | ||||||
|  |     override val STARTUP_CODE_RESERVED_SIZE: UInt, | ||||||
|  |     override val BSSHIGHRAM_START: UInt, | ||||||
|  |     override val BSSHIGHRAM_END: UInt, | ||||||
|  |     override val BSSGOLDENRAM_START: UInt, | ||||||
|  |     override val BSSGOLDENRAM_END: UInt, | ||||||
|  |     override val defaultOutputType: OutputType, | ||||||
|  |     override val libraryPath: Path, | ||||||
|  |     override val customLauncher: List<String>, | ||||||
|  |     override val additionalAssemblerOptions: List<String>, | ||||||
|  |     val ioAddresses: List<UIntRange>, | ||||||
|  |     val zpScratchB1: UInt, | ||||||
|  |     val zpScratchReg: UInt, | ||||||
|  |     val zpScratchW1: UInt, | ||||||
|  |     val zpScratchW2: UInt, | ||||||
|  |     val zpScratchPtr: UInt, | ||||||
|  |     val virtualregistersStart: UInt, | ||||||
|  |     val zpFullsafe: List<UIntRange>, | ||||||
|  |     val zpKernalsafe: List<UIntRange>, | ||||||
|  |     val zpBasicsafe: List<UIntRange> | ||||||
|  | ): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) { | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |  | ||||||
|  |         private fun Properties.getString(property: String): String { | ||||||
|  |             val value = this.getProperty(property, null) | ||||||
|  |             if(value!=null) | ||||||
|  |                 return value | ||||||
|  |             throw NoSuchElementException("string property '$property' not found in config file") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private fun Properties.getInteger(property: String): UInt { | ||||||
|  |             val value = this.getProperty(property, null) | ||||||
|  |             if(value!=null) return parseInt(value) | ||||||
|  |             throw NoSuchElementException("integer property '$property' not found in config file") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private fun parseInt(value: String): UInt { | ||||||
|  |             if(value.startsWith("0x")) | ||||||
|  |                 return value.drop(2).toUInt(16) | ||||||
|  |             if(value.startsWith("$")) | ||||||
|  |                 return value.drop(1).toUInt(16) | ||||||
|  |             if(value.startsWith("%")) | ||||||
|  |                 return value.drop(1).toUInt(2) | ||||||
|  |             return value.toUInt() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private fun parseAddressRanges(key: String, props: Properties): List<UIntRange> { | ||||||
|  |             val rangesStr = props.getString(key) | ||||||
|  |             if(rangesStr.isBlank()) | ||||||
|  |                 return emptyList() | ||||||
|  |             val result = mutableListOf<UIntRange>() | ||||||
|  |             val ranges = rangesStr.split(",").map { it.trim() } | ||||||
|  |             for(r in ranges) { | ||||||
|  |                 if ('-' in r) { | ||||||
|  |                     val (fromStr, toStr) = r.split("-") | ||||||
|  |                     val from = parseInt(fromStr.trim()) | ||||||
|  |                     val to = parseInt(toStr.trim()) | ||||||
|  |                     result.add(from..to) | ||||||
|  |                 } else { | ||||||
|  |                     val address = parseInt(r) | ||||||
|  |                     result.add(address..address) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return result | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun fromConfigFile(configfile: Path): ConfigFileTarget { | ||||||
|  |             val props = Properties() | ||||||
|  |             props.load(configfile.inputStream()) | ||||||
|  |  | ||||||
|  |             val cpuString = props.getString("cpu").uppercase() | ||||||
|  |             val cpuType = try { | ||||||
|  |                 CpuType.valueOf(cpuString) | ||||||
|  |             } catch (_: IllegalArgumentException) { | ||||||
|  |                 CpuType.valueOf("CPU$cpuString") | ||||||
|  |             } | ||||||
|  |             val ioAddresses = parseAddressRanges("io_regions", props) | ||||||
|  |             val zpFullsafe = parseAddressRanges("zp_fullsafe", props) | ||||||
|  |             val zpKernalsafe = parseAddressRanges("zp_kernalsafe", props) | ||||||
|  |             val zpBasicsafe = parseAddressRanges("zp_basicsafe", props) | ||||||
|  |  | ||||||
|  |             val libraryPath = expandTilde(Path(props.getString("library"))) | ||||||
|  |             if(!libraryPath.isDirectory()) | ||||||
|  |                 throw IOException("invalid library path: $libraryPath") | ||||||
|  |  | ||||||
|  |             val customLauncherStr = props.getProperty("custom_launcher_code", null) | ||||||
|  |             val customLauncher = | ||||||
|  |                 if(customLauncherStr?.isNotBlank()==true) | ||||||
|  |                     (customLauncherStr+"\n").lines().map { it.trimEnd() } | ||||||
|  |                 else emptyList() | ||||||
|  |             val assemblerOptionsStr = props.getProperty("assembler_options", "").trim() | ||||||
|  |             val outputTypeString = props.getProperty("output_type", "PRG") | ||||||
|  |             val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase()) | ||||||
|  |  | ||||||
|  |             return ConfigFileTarget( | ||||||
|  |                 configfile.nameWithoutExtension, | ||||||
|  |                 Encoding.entries.first { it.prefix==props.getString("encoding") }, | ||||||
|  |                 cpuType, | ||||||
|  |                 props.getInteger("load_address"), | ||||||
|  |                 props.getInteger("memtop"), | ||||||
|  |                 0u,         // used only in a very specific error condition check in a certain scenario... | ||||||
|  |                 props.getInteger("bss_highram_start"), | ||||||
|  |                 props.getInteger("bss_highram_end"), | ||||||
|  |                 props.getInteger("bss_goldenram_start"), | ||||||
|  |                 props.getInteger("bss_goldenram_end"), | ||||||
|  |                 defaultOutputType, | ||||||
|  |                 libraryPath, | ||||||
|  |                 customLauncher, | ||||||
|  |                 if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "), | ||||||
|  |                 ioAddresses, | ||||||
|  |                 props.getInteger("zp_scratch_b1"), | ||||||
|  |                 props.getInteger("zp_scratch_reg"), | ||||||
|  |                 props.getInteger("zp_scratch_w1"), | ||||||
|  |                 props.getInteger("zp_scratch_w2"), | ||||||
|  |                 props.getInteger("zp_scratch_ptr"), | ||||||
|  |                 props.getInteger("virtual_registers"), | ||||||
|  |                 zpFullsafe, | ||||||
|  |                 zpKernalsafe, | ||||||
|  |                 zpBasicsafe, | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO floats are not yet supported here, just enter some values | ||||||
|  |     override val FLOAT_MAX_POSITIVE = 9.999999999e97 | ||||||
|  |     override val FLOAT_MAX_NEGATIVE = -9.999999999e97 | ||||||
|  |     override val FLOAT_MEM_SIZE = 8 | ||||||
|  |  | ||||||
|  |     override lateinit var zeropage: Zeropage | ||||||
|  |     override lateinit var golden: GoldenRam     // TODO this is not yet used | ||||||
|  |  | ||||||
|  |     override fun getFloatAsmBytes(num: Number) = TODO("floats") | ||||||
|  |     override fun convertFloatToBytes(num: Double): List<UByte> = TODO("floats") | ||||||
|  |     override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("floats") | ||||||
|  |     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { | ||||||
|  |         throw IllegalArgumentException("Custom compiler target cannot automatically launch an emulator. Do this manually.") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun isIOAddress(address: UInt): Boolean = ioAddresses.any { address in it } | ||||||
|  |  | ||||||
|  |     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { | ||||||
|  |         zeropage = ConfigurableZeropage( | ||||||
|  |             zpScratchB1, zpScratchReg, zpScratchW1, zpScratchW2, zpScratchPtr, | ||||||
|  |             virtualregistersStart, | ||||||
|  |             zpBasicsafe, | ||||||
|  |             zpKernalsafe, | ||||||
|  |             zpFullsafe, | ||||||
|  |             compilerOptions | ||||||
|  |         ) | ||||||
|  |         // note: there's no golden ram yet | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,19 +1,94 @@ | |||||||
| package prog8.code.target | package prog8.code.target | ||||||
|  |  | ||||||
| import prog8.code.core.Encoding | import prog8.code.core.* | ||||||
| import prog8.code.core.ICompilationTarget | import prog8.code.target.encodings.Encoder | ||||||
| import prog8.code.core.IMemSizer | import prog8.code.target.zp.CX16Zeropage | ||||||
| import prog8.code.core.IStringEncoding | import java.nio.file.Path | ||||||
| import prog8.code.target.cbm.CbmMemorySizer |  | ||||||
| import prog8.code.target.cx16.CX16MachineDefinition |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer { | class Cx16Target: ICompilationTarget, | ||||||
|  |     IStringEncoding by Encoder(true), | ||||||
|  |     IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) { | ||||||
|  |  | ||||||
|     override val name = NAME |     override val name = NAME | ||||||
|     override val machine = CX16MachineDefinition() |  | ||||||
|     override val defaultEncoding = Encoding.PETSCII |     override val defaultEncoding = Encoding.PETSCII | ||||||
|  |     override val libraryPath = null | ||||||
|  |     override val customLauncher = emptyList<String>() | ||||||
|  |     override val additionalAssemblerOptions = emptyList<String>() | ||||||
|  |     override val defaultOutputType = OutputType.PRG | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         const val NAME = "cx16" |         const val NAME = "cx16" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     override val cpu = CpuType.CPU65C02 | ||||||
|  |  | ||||||
|  |     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE | ||||||
|  |     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE | ||||||
|  |     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE | ||||||
|  |     override val STARTUP_CODE_RESERVED_SIZE = 20u | ||||||
|  |     override val PROGRAM_LOAD_ADDRESS = 0x0801u | ||||||
|  |     override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u | ||||||
|  |  | ||||||
|  |     override val BSSHIGHRAM_START = 0xa000u     // hiram bank 1, 8Kb, assumed to be active | ||||||
|  |     override val BSSHIGHRAM_END = 0xbfffu       // Rom starts at $c000 | ||||||
|  |     override val BSSGOLDENRAM_START = 0x0400u | ||||||
|  |     override val BSSGOLDENRAM_END = 0x07ffu | ||||||
|  |  | ||||||
|  |     override lateinit var zeropage: Zeropage | ||||||
|  |     override lateinit var golden: GoldenRam | ||||||
|  |  | ||||||
|  |     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() | ||||||
|  |  | ||||||
|  |     override fun convertFloatToBytes(num: Double): List<UByte> { | ||||||
|  |         val m5 = Mflpt5.fromNumber(num) | ||||||
|  |         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun convertBytesToFloat(bytes: List<UByte>): Double { | ||||||
|  |         require(bytes.size==5) { "need 5 bytes" } | ||||||
|  |         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) | ||||||
|  |         return m5.toDouble() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { | ||||||
|  |         val emulator: String | ||||||
|  |         val extraArgs: List<String> | ||||||
|  |  | ||||||
|  |         when(selectedEmulator) { | ||||||
|  |             1 -> { | ||||||
|  |                 emulator = "x16emu" | ||||||
|  |                 extraArgs = listOf("-debug") | ||||||
|  |             } | ||||||
|  |             2 -> { | ||||||
|  |                 emulator = "box16" | ||||||
|  |                 extraArgs = listOf("-sym", C64Target.viceMonListName(programNameWithPath.toString())) | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 System.err.println("Cx16 target only supports x16emu and box16 emulators.") | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!quiet) | ||||||
|  |             println("\nStarting Commander X16 emulator $emulator...") | ||||||
|  |  | ||||||
|  |         val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs | ||||||
|  |         val processb = ProcessBuilder(cmdline) | ||||||
|  |         if(!quiet) | ||||||
|  |             processb.inheritIO() | ||||||
|  |         processb.environment()["PULSE_LATENCY_MSEC"] = "10" | ||||||
|  |         val process: Process = processb.start() | ||||||
|  |         process.waitFor() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu | ||||||
|  |  | ||||||
|  |     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { | ||||||
|  |         zeropage = CX16Zeropage(compilerOptions) | ||||||
|  |         golden = GoldenRam(compilerOptions, BSSGOLDENRAM_START..BSSGOLDENRAM_END) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,9 @@ | |||||||
| package prog8.code.target.cbm | package prog8.code.target | ||||||
| 
 | 
 | ||||||
| import prog8.code.core.InternalCompilerException | import prog8.code.core.InternalCompilerException | ||||||
| import kotlin.math.absoluteValue | import kotlin.math.absoluteValue | ||||||
| import kotlin.math.pow | import kotlin.math.pow | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte) { | data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte) { | ||||||
| 
 | 
 | ||||||
|     companion object { |     companion object { | ||||||
| @@ -19,7 +18,7 @@ data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, va | |||||||
|             // and https://en.wikipedia.org/wiki/IEEE_754-1985 |             // and https://en.wikipedia.org/wiki/IEEE_754-1985 | ||||||
| 
 | 
 | ||||||
|             val flt = num.toDouble() |             val flt = num.toDouble() | ||||||
|             if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) |             if (flt !in FLOAT_MAX_NEGATIVE..FLOAT_MAX_POSITIVE) | ||||||
|                 throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this") |                 throw InternalCompilerException("floating point number out of 5-byte mflpt range: $this") | ||||||
|             if (flt == 0.0) |             if (flt == 0.0) | ||||||
|                 return zero |                 return zero | ||||||
							
								
								
									
										38
									
								
								codeCore/src/prog8/code/target/NormalMemSizer.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								codeCore/src/prog8/code/target/NormalMemSizer.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | package prog8.code.target | ||||||
|  |  | ||||||
|  | import prog8.code.core.BaseDataType | ||||||
|  | import prog8.code.core.DataType | ||||||
|  | import prog8.code.core.IMemSizer | ||||||
|  |  | ||||||
|  | internal class NormalMemSizer(val floatsize: Int): IMemSizer { | ||||||
|  |  | ||||||
|  |     override fun memorySize(dt: DataType, numElements: Int?): Int { | ||||||
|  |         if(dt.isPointerArray) | ||||||
|  |             return 2 * numElements!!        // array of pointers is just array of uwords | ||||||
|  |         else if(dt.isArray) { | ||||||
|  |             if(numElements==null) return 2      // treat it as a pointer size | ||||||
|  |             return when(dt.sub) { | ||||||
|  |                 BaseDataType.BOOL, BaseDataType.UBYTE, BaseDataType.BYTE -> numElements | ||||||
|  |                 BaseDataType.UWORD, BaseDataType.WORD, BaseDataType.STR -> numElements * 2 | ||||||
|  |                 BaseDataType.FLOAT-> numElements * floatsize | ||||||
|  |                 BaseDataType.UNDEFINED -> throw IllegalArgumentException("undefined has no memory size") | ||||||
|  |                 else -> throw IllegalArgumentException("invalid sub type") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if (dt.isString) { | ||||||
|  |             return numElements        // treat it as the size of the given string with the length | ||||||
|  |                 ?: 2    // treat it as the size to store a string pointer | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return when { | ||||||
|  |             dt.isByteOrBool -> 1 * (numElements ?: 1) | ||||||
|  |             dt.isFloat -> floatsize * (numElements ?: 1) | ||||||
|  |             dt.isLong -> 4 * (numElements ?: 1) | ||||||
|  |             dt.isPointer -> 2  // pointer is just a uword | ||||||
|  |             dt.isStructInstance -> dt.subType!!.memsize(this) | ||||||
|  |             dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size") | ||||||
|  |             else -> 2 * (numElements ?: 1) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,19 +1,80 @@ | |||||||
| package prog8.code.target | package prog8.code.target | ||||||
|  |  | ||||||
| import prog8.code.core.Encoding | import prog8.code.core.* | ||||||
| import prog8.code.core.ICompilationTarget | import prog8.code.target.encodings.Encoder | ||||||
| import prog8.code.core.IMemSizer | import prog8.code.target.zp.PETZeropage | ||||||
| import prog8.code.core.IStringEncoding | import java.nio.file.Path | ||||||
| import prog8.code.target.cbm.CbmMemorySizer |  | ||||||
| import prog8.code.target.pet.PETMachineDefinition |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PETTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer { | class PETTarget: ICompilationTarget, | ||||||
|  |     IStringEncoding by Encoder(true), | ||||||
|  |     IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) { | ||||||
|  |  | ||||||
|     override val name = NAME |     override val name = NAME | ||||||
|     override val machine = PETMachineDefinition() |  | ||||||
|     override val defaultEncoding = Encoding.PETSCII |     override val defaultEncoding = Encoding.PETSCII | ||||||
|  |     override val libraryPath = null | ||||||
|  |     override val customLauncher = emptyList<String>() | ||||||
|  |     override val additionalAssemblerOptions = emptyList<String>() | ||||||
|  |     override val defaultOutputType = OutputType.PRG | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         const val NAME = "pet32" |         const val NAME = "pet32" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override val cpu = CpuType.CPU6502 | ||||||
|  |  | ||||||
|  |     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE | ||||||
|  |     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE | ||||||
|  |     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE | ||||||
|  |     override val STARTUP_CODE_RESERVED_SIZE = 20u | ||||||
|  |     override val PROGRAM_LOAD_ADDRESS = 0x0401u | ||||||
|  |     override val PROGRAM_MEMTOP_ADDRESS = 0x8000u | ||||||
|  |  | ||||||
|  |     override val BSSHIGHRAM_START = 0u | ||||||
|  |     override val BSSHIGHRAM_END = 0u | ||||||
|  |     override val BSSGOLDENRAM_START = 0u | ||||||
|  |     override val BSSGOLDENRAM_END = 0u | ||||||
|  |  | ||||||
|  |     override lateinit var zeropage: Zeropage | ||||||
|  |     override lateinit var golden: GoldenRam | ||||||
|  |  | ||||||
|  |     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() | ||||||
|  |  | ||||||
|  |     override fun convertFloatToBytes(num: Double): List<UByte> { | ||||||
|  |         val m5 = Mflpt5.fromNumber(num) | ||||||
|  |         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun convertBytesToFloat(bytes: List<UByte>): Double { | ||||||
|  |         require(bytes.size==5) { "need 5 bytes" } | ||||||
|  |         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) | ||||||
|  |         return m5.toDouble() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { | ||||||
|  |         if(selectedEmulator!=1) { | ||||||
|  |             System.err.println("The pet target only supports the main emulator (Vice).") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!quiet) | ||||||
|  |             println("\nStarting PET emulator...") | ||||||
|  |  | ||||||
|  |         val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString()) | ||||||
|  |         val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist, | ||||||
|  |             "-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg") | ||||||
|  |         val processb = ProcessBuilder(cmdline) | ||||||
|  |         if(!quiet) | ||||||
|  |             processb.inheritIO() | ||||||
|  |         val process=processb.start() | ||||||
|  |         process.waitFor() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu | ||||||
|  |  | ||||||
|  |     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { | ||||||
|  |         zeropage = PETZeropage(compilerOptions) | ||||||
|  |         golden = GoldenRam(compilerOptions, UIntRange.EMPTY) | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,29 +1,109 @@ | |||||||
| package prog8.code.target | package prog8.code.target | ||||||
|  |  | ||||||
| import prog8.code.core.* | import prog8.code.core.* | ||||||
| import prog8.code.target.virtual.VirtualMachineDefinition | import prog8.code.target.encodings.Encoder | ||||||
|  | import java.nio.file.Path | ||||||
|  | import kotlin.io.path.extension | ||||||
|  | import kotlin.io.path.isReadable | ||||||
|  | import kotlin.io.path.name | ||||||
|  | import kotlin.io.path.readText | ||||||
|  |  | ||||||
|  | class VMTarget: ICompilationTarget, | ||||||
|  |     IStringEncoding by Encoder(false), | ||||||
|  |     IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) { | ||||||
|  |  | ||||||
| class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer { |  | ||||||
|     override val name = NAME |     override val name = NAME | ||||||
|     override val machine = VirtualMachineDefinition() |  | ||||||
|     override val defaultEncoding = Encoding.ISO |     override val defaultEncoding = Encoding.ISO | ||||||
|  |     override val libraryPath = null | ||||||
|  |     override val customLauncher = emptyList<String>() | ||||||
|  |     override val additionalAssemblerOptions = emptyList<String>() | ||||||
|  |     override val defaultOutputType = OutputType.PRG | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         const val NAME = "virtual" |         const val NAME = "virtual" | ||||||
|  |         const val FLOAT_MEM_SIZE = 8             // 64-bits double | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun memorySize(dt: DataType): Int { |     override val cpu = CpuType.VIRTUAL | ||||||
|         return when(dt) { |  | ||||||
|             in ByteDatatypesWithBoolean -> 1 |     override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE | ||||||
|             in WordDatatypes, in PassByReferenceDatatypes -> 2 |     override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE | ||||||
|             DataType.FLOAT -> machine.FLOAT_MEM_SIZE |     override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE | ||||||
|             else -> throw IllegalArgumentException("invalid datatype") |     override val STARTUP_CODE_RESERVED_SIZE = 0u  // not actually used | ||||||
|         } |     override val PROGRAM_LOAD_ADDRESS = 0u      // not actually used | ||||||
|  |     override val PROGRAM_MEMTOP_ADDRESS = 0xffffu  // not actually used | ||||||
|  |  | ||||||
|  |     override val BSSHIGHRAM_START = 0u          // not actually used | ||||||
|  |     override val BSSHIGHRAM_END = 0u            // not actually used | ||||||
|  |     override val BSSGOLDENRAM_START = 0u        // not actually used | ||||||
|  |     override val BSSGOLDENRAM_END = 0u          // not actually used | ||||||
|  |     override lateinit var zeropage: Zeropage    // not actually used | ||||||
|  |     override lateinit var golden: GoldenRam     // not actually used | ||||||
|  |  | ||||||
|  |     override fun getFloatAsmBytes(num: Number): String { | ||||||
|  |         // little endian binary representation | ||||||
|  |         val bits = num.toDouble().toBits().toULong() | ||||||
|  |         val hexStr = bits.toString(16).padStart(16, '0') | ||||||
|  |         val parts = hexStr.chunked(2).map { "$$it" } | ||||||
|  |         return parts.joinToString(", ") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun memorySize(arrayDt: DataType, numElements: Int) = |     override fun convertFloatToBytes(num: Double): List<UByte> { | ||||||
|         if(arrayDt==DataType.UWORD) |         val bits = num.toBits().toULong() | ||||||
|             numElements    // pointer to bytes. |         val hexStr = bits.toString(16).padStart(16, '0') | ||||||
|  |         val parts = hexStr.chunked(2).map { it.toInt(16).toUByte() } | ||||||
|  |         return parts | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun convertBytesToFloat(bytes: List<UByte>): Double { | ||||||
|  |         require(bytes.size==8) { "need 8 bytes" } | ||||||
|  |         val b0 = bytes[0].toLong() shl (8*7) | ||||||
|  |         val b1 = bytes[1].toLong() shl (8*6) | ||||||
|  |         val b2 = bytes[2].toLong() shl (8*5) | ||||||
|  |         val b3 = bytes[3].toLong() shl (8*4) | ||||||
|  |         val b4 = bytes[4].toLong() shl (8*3) | ||||||
|  |         val b5 = bytes[5].toLong() shl (8*2) | ||||||
|  |         val b6 = bytes[6].toLong() shl (8*1) | ||||||
|  |         val b7 = bytes[7].toLong() shl (8*0) | ||||||
|  |         return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path, quiet: Boolean) { | ||||||
|  |         if(!quiet) | ||||||
|  |             println("\nStarting Virtual Machine...") | ||||||
|  |  | ||||||
|  |         // to not have external module dependencies in our own module, we launch the virtual machine via reflection | ||||||
|  |         val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner | ||||||
|  |         val withExt = if(programNameWithPath.extension=="p8ir") programNameWithPath else programNameWithPath.resolveSibling("${programNameWithPath.name}.p8ir") | ||||||
|  |         if(withExt.isReadable()) | ||||||
|  |             vm.runProgram(withExt.readText(), quiet) | ||||||
|         else |         else | ||||||
|             memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements |             throw java.nio.file.NoSuchFileException(withExt.name, null, "not a .p8ir file") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun isIOAddress(address: UInt): Boolean = false | ||||||
|  |  | ||||||
|  |     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { | ||||||
|  |         zeropage = VirtualZeropage(compilerOptions) | ||||||
|  |         golden = GoldenRam(compilerOptions, UIntRange.EMPTY) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | interface IVirtualMachineRunner { | ||||||
|  |     fun runProgram(irSource: String, quiet: Boolean) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private class VirtualZeropage(options: CompilationOptions): Zeropage(options) { | ||||||
|  |     override val SCRATCH_B1: UInt | ||||||
|  |         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") | ||||||
|  |     override val SCRATCH_REG: UInt | ||||||
|  |         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") | ||||||
|  |     override val SCRATCH_W1: UInt | ||||||
|  |         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") | ||||||
|  |     override val SCRATCH_W2: UInt | ||||||
|  |         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") | ||||||
|  |     override val SCRATCH_PTR: UInt | ||||||
|  |         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") | ||||||
| } | } | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| package prog8.code.target.atari |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AtariMachineDefinition: IMachineDefinition { |  | ||||||
|  |  | ||||||
|     override val cpu = CpuType.CPU6502 |  | ||||||
|  |  | ||||||
|     override val FLOAT_MAX_POSITIVE = 9.999999999e97 |  | ||||||
|     override val FLOAT_MAX_NEGATIVE = -9.999999999e97 |  | ||||||
|     override val FLOAT_MEM_SIZE = 6 |  | ||||||
|     override val PROGRAM_LOAD_ADDRESS = 0x2000u |  | ||||||
|  |  | ||||||
|     override val BSSHIGHRAM_START = 0u    // TODO |  | ||||||
|     override val BSSHIGHRAM_END = 0u      // TODO |  | ||||||
|     override val BSSGOLDENRAM_START = 0u  // TODO |  | ||||||
|     override val BSSGOLDENRAM_END = 0u    // TODO |  | ||||||
|  |  | ||||||
|     override lateinit var zeropage: Zeropage |  | ||||||
|     override lateinit var golden: GoldenRam |  | ||||||
|  |  | ||||||
|     override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number") |  | ||||||
|     override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes") |  | ||||||
|     override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float") |  | ||||||
|  |  | ||||||
|     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) { |  | ||||||
|         val emulatorName: String |  | ||||||
|         val cmdline: List<String> |  | ||||||
|         when(selectedEmulator) { |  | ||||||
|             1 -> { |  | ||||||
|                 emulatorName = "atari800" |  | ||||||
|                 cmdline = listOf(emulatorName, "-xl", "-xl-rev", "2", "-nobasic", "-run", "${programNameWithPath}.xex") |  | ||||||
|             } |  | ||||||
|             2 -> { |  | ||||||
|                 emulatorName = "altirra" |  | ||||||
|                 cmdline = listOf("Altirra64.exe", "${programNameWithPath.normalize()}.xex") |  | ||||||
|             } |  | ||||||
|             else -> { |  | ||||||
|                 System.err.println("Atari target only supports atari800 and altirra emulators.") |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // TODO monlist? |  | ||||||
|  |  | ||||||
|         println("\nStarting Atari800XL emulator $emulatorName...") |  | ||||||
|         val processb = ProcessBuilder(cmdline).inheritIO() |  | ||||||
|         val process: Process = processb.start() |  | ||||||
|         process.waitFor() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu        // TODO |  | ||||||
|  |  | ||||||
|     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { |  | ||||||
|         zeropage = AtariZeropage(compilerOptions) |  | ||||||
|         golden = GoldenRam(compilerOptions, UIntRange.EMPTY) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,57 +0,0 @@ | |||||||
| package prog8.code.target.atari |  | ||||||
|  |  | ||||||
| import prog8.code.core.CompilationOptions |  | ||||||
| import prog8.code.core.InternalCompilerException |  | ||||||
| import prog8.code.core.Zeropage |  | ||||||
| import prog8.code.core.ZeropageType |  | ||||||
|  |  | ||||||
| class AtariZeropage(options: CompilationOptions) : Zeropage(options) { |  | ||||||
|  |  | ||||||
|     override val SCRATCH_B1 = 0xcbu      // temp storage for a single byte |  | ||||||
|     override val SCRATCH_REG = 0xccu     // temp storage for a register, must be B1+1 |  | ||||||
|     override val SCRATCH_W1 = 0xcdu      // temp storage 1 for a word  $cd+$ce |  | ||||||
|     override val SCRATCH_W2 = 0xcfu      // temp storage 2 for a word  $cf+$d0        TODO is $d0 okay to use? |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         if (options.floats) { |  | ||||||
|             throw InternalCompilerException("Atari target doesn't yet support floating point routines") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (options.floats && options.zeropage !in arrayOf( |  | ||||||
|                 ZeropageType.FLOATSAFE, |  | ||||||
|                 ZeropageType.BASICSAFE, |  | ||||||
|                 ZeropageType.DONTUSE |  | ||||||
|             )) |  | ||||||
|             throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'") |  | ||||||
|  |  | ||||||
|         when (options.zeropage) { |  | ||||||
|             ZeropageType.FULL -> { |  | ||||||
|                 // TODO all atari usable zero page locations, except the ones used by the system's IRQ routine |  | ||||||
|                 free.addAll(0x00u..0xffu) |  | ||||||
|                 // TODO atari  free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u))        // these are updated by IRQ |  | ||||||
|             } |  | ||||||
|             ZeropageType.KERNALSAFE -> { |  | ||||||
|                 free.addAll(0x80u..0xffu)       // TODO |  | ||||||
|             } |  | ||||||
|             ZeropageType.BASICSAFE, |  | ||||||
|             ZeropageType.FLOATSAFE -> { |  | ||||||
|                 free.addAll(0x80u..0xffu)       // TODO |  | ||||||
|                 free.removeAll(0xd4u .. 0xefu)      // floating point storage |  | ||||||
|             } |  | ||||||
|             ZeropageType.DONTUSE -> { |  | ||||||
|                 free.clear()  // don't use zeropage at all |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         val distinctFree = free.distinct() |  | ||||||
|         free.clear() |  | ||||||
|         free.addAll(distinctFree) |  | ||||||
|  |  | ||||||
|         removeReservedFromFreePool() |  | ||||||
|         retainAllowed() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun allocateCx16VirtualRegisters() { |  | ||||||
|         TODO("Not known if atari can put the virtual regs in ZP") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,60 +0,0 @@ | |||||||
| package prog8.code.target.c128 |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
| import prog8.code.target.C64Target |  | ||||||
| import prog8.code.target.cbm.Mflpt5 |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class C128MachineDefinition: IMachineDefinition { |  | ||||||
|  |  | ||||||
|     override val cpu = CpuType.CPU6502 |  | ||||||
|  |  | ||||||
|     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE |  | ||||||
|     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE |  | ||||||
|     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE |  | ||||||
|     override val PROGRAM_LOAD_ADDRESS = 0x1c01u |  | ||||||
|  |  | ||||||
|     override val BSSHIGHRAM_START = 0u    // TODO |  | ||||||
|     override val BSSHIGHRAM_END = 0u      // TODO |  | ||||||
|     override val BSSGOLDENRAM_START = 0u  // TODO |  | ||||||
|     override val BSSGOLDENRAM_END = 0u    // TODO |  | ||||||
|  |  | ||||||
|     override lateinit var zeropage: Zeropage |  | ||||||
|     override lateinit var golden: GoldenRam |  | ||||||
|  |  | ||||||
|     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() |  | ||||||
|  |  | ||||||
|     override fun convertFloatToBytes(num: Double): List<UByte> { |  | ||||||
|         val m5 = Mflpt5.fromNumber(num) |  | ||||||
|         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun convertBytesToFloat(bytes: List<UByte>): Double { |  | ||||||
|         require(bytes.size==5) { "need 5 bytes" } |  | ||||||
|         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) |  | ||||||
|         return m5.toDouble() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) { |  | ||||||
|         if(selectedEmulator!=1) { |  | ||||||
|             System.err.println("The c128 target only supports the main emulator (Vice).") |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         println("\nStarting C-128 emulator x128...") |  | ||||||
|         val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString()) |  | ||||||
|         val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist, |  | ||||||
|                 "-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg") |  | ||||||
|         val processb = ProcessBuilder(cmdline).inheritIO() |  | ||||||
|         val process: Process = processb.start() |  | ||||||
|         process.waitFor() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu |  | ||||||
|  |  | ||||||
|     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { |  | ||||||
|         zeropage = C128Zeropage(compilerOptions) |  | ||||||
|         golden = GoldenRam(compilerOptions, UIntRange.EMPTY)    // TODO does the c128 have some of this somewhere? |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,70 +0,0 @@ | |||||||
| package prog8.code.target.c64 |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
| import prog8.code.target.C64Target |  | ||||||
| import prog8.code.target.cbm.Mflpt5 |  | ||||||
| import java.io.IOException |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class C64MachineDefinition: IMachineDefinition { |  | ||||||
|  |  | ||||||
|     override val cpu = CpuType.CPU6502 |  | ||||||
|  |  | ||||||
|     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE |  | ||||||
|     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE |  | ||||||
|     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE |  | ||||||
|     override val PROGRAM_LOAD_ADDRESS = 0x0801u |  | ||||||
|  |  | ||||||
|     override val BSSHIGHRAM_START = 0xc000u |  | ||||||
|     override val BSSHIGHRAM_END = 0xcfffu |  | ||||||
|     override val BSSGOLDENRAM_START = 0u |  | ||||||
|     override val BSSGOLDENRAM_END = 0u |  | ||||||
|  |  | ||||||
|     override lateinit var zeropage: Zeropage |  | ||||||
|     override lateinit var golden: GoldenRam |  | ||||||
|  |  | ||||||
|     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() |  | ||||||
|  |  | ||||||
|     override fun convertFloatToBytes(num: Double): List<UByte> { |  | ||||||
|         val m5 = Mflpt5.fromNumber(num) |  | ||||||
|         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun convertBytesToFloat(bytes: List<UByte>): Double { |  | ||||||
|         require(bytes.size==5) { "need 5 bytes" } |  | ||||||
|         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) |  | ||||||
|         return m5.toDouble() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) { |  | ||||||
|         if(selectedEmulator!=1) { |  | ||||||
|             System.err.println("The c64 target only supports the main emulator (Vice).") |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         for(emulator in listOf("x64sc", "x64")) { |  | ||||||
|             println("\nStarting C-64 emulator $emulator...") |  | ||||||
|             val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString()) |  | ||||||
|             val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist, |  | ||||||
|                     "-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg") |  | ||||||
|             val processb = ProcessBuilder(cmdline).inheritIO() |  | ||||||
|             val process: Process |  | ||||||
|             try { |  | ||||||
|                 process=processb.start() |  | ||||||
|             } catch(x: IOException) { |  | ||||||
|                 continue  // try the next emulator executable |  | ||||||
|             } |  | ||||||
|             process.waitFor() |  | ||||||
|             break |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu |  | ||||||
|  |  | ||||||
|     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { |  | ||||||
|         zeropage = C64Zeropage(compilerOptions) |  | ||||||
|         golden = GoldenRam(compilerOptions, 0xc000u until 0xd000u) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| package prog8.code.target.cbm |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal object CbmMemorySizer: IMemSizer { |  | ||||||
|     override fun memorySize(dt: DataType): Int { |  | ||||||
|         return when(dt) { |  | ||||||
|             in ByteDatatypesWithBoolean -> 1 |  | ||||||
|             in WordDatatypes, in PassByReferenceDatatypes -> 2 |  | ||||||
|             DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE |  | ||||||
|             else -> throw IllegalArgumentException("invalid datatype") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun memorySize(arrayDt: DataType, numElements: Int) = |  | ||||||
|         if(arrayDt==DataType.UWORD) |  | ||||||
|             numElements    // pointer to bytes. |  | ||||||
|         else |  | ||||||
|             memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements |  | ||||||
| } |  | ||||||
| @@ -1,73 +0,0 @@ | |||||||
| package prog8.code.target.cx16 |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
| import prog8.code.target.C64Target |  | ||||||
| import prog8.code.target.cbm.Mflpt5 |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CX16MachineDefinition: IMachineDefinition { |  | ||||||
|  |  | ||||||
|     override val cpu = CpuType.CPU65c02 |  | ||||||
|  |  | ||||||
|     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE |  | ||||||
|     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE |  | ||||||
|     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE |  | ||||||
|     override val PROGRAM_LOAD_ADDRESS = 0x0801u |  | ||||||
|  |  | ||||||
|     override val BSSHIGHRAM_START = 0xa000u     // hiram bank 1, 8Kb, assumed to be active |  | ||||||
|     override val BSSHIGHRAM_END = 0xbfffu       // Rom starts at $c000 |  | ||||||
|     override val BSSGOLDENRAM_START = 0x0400u |  | ||||||
|     override val BSSGOLDENRAM_END = 0x07ffu |  | ||||||
|  |  | ||||||
|     override lateinit var zeropage: Zeropage |  | ||||||
|     override lateinit var golden: GoldenRam |  | ||||||
|  |  | ||||||
|     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() |  | ||||||
|  |  | ||||||
|     override fun convertFloatToBytes(num: Double): List<UByte> { |  | ||||||
|         val m5 = Mflpt5.fromNumber(num) |  | ||||||
|         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun convertBytesToFloat(bytes: List<UByte>): Double { |  | ||||||
|         require(bytes.size==5) { "need 5 bytes" } |  | ||||||
|         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) |  | ||||||
|         return m5.toDouble() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) { |  | ||||||
|         val emulator: String |  | ||||||
|         val extraArgs: List<String> |  | ||||||
|  |  | ||||||
|         when(selectedEmulator) { |  | ||||||
|             1 -> { |  | ||||||
|                 emulator = "x16emu" |  | ||||||
|                 extraArgs = listOf("-debug") |  | ||||||
|             } |  | ||||||
|             2 -> { |  | ||||||
|                 emulator = "box16" |  | ||||||
|                 extraArgs = listOf("-sym", C64Target.viceMonListName(programNameWithPath.toString())) |  | ||||||
|             } |  | ||||||
|             else -> { |  | ||||||
|                 System.err.println("Cx16 target only supports x16emu and box16 emulators.") |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         println("\nStarting Commander X16 emulator $emulator...") |  | ||||||
|         val cmdline = listOf(emulator, "-scale", "2", "-rtc", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs |  | ||||||
|         val processb = ProcessBuilder(cmdline).inheritIO() |  | ||||||
|         processb.environment()["PULSE_LATENCY_MSEC"] = "10" |  | ||||||
|         val process: Process = processb.start() |  | ||||||
|         process.waitFor() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu |  | ||||||
|  |  | ||||||
|     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { |  | ||||||
|         zeropage = CX16Zeropage(compilerOptions) |  | ||||||
|         golden = GoldenRam(compilerOptions, 0x0400u until 0x0800u) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -197,6 +197,7 @@ object AtasciiEncoding { | |||||||
|     fun encode(str: String): Result<List<UByte>, CharConversionException> { |     fun encode(str: String): Result<List<UByte>, CharConversionException> { | ||||||
|         val mapped = str.map { chr -> |         val mapped = str.map { chr -> | ||||||
|             when (chr) { |             when (chr) { | ||||||
|  |                 '\r' -> 0x9bu | ||||||
|                 '\u0000' -> 0u |                 '\u0000' -> 0u | ||||||
|                 in '\u8000'..'\u80ff' -> { |                 in '\u8000'..'\u80ff' -> { | ||||||
|                     // special case: take the lower 8 bit hex value directly |                     // special case: take the lower 8 bit hex value directly | ||||||
|   | |||||||
							
								
								
									
										327
									
								
								codeCore/src/prog8/code/target/encodings/C64osEncoding.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								codeCore/src/prog8/code/target/encodings/C64osEncoding.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,327 @@ | |||||||
|  | package prog8.code.target.encodings | ||||||
|  |  | ||||||
|  | import com.github.michaelbull.result.Err | ||||||
|  | import com.github.michaelbull.result.Ok | ||||||
|  | import com.github.michaelbull.result.Result | ||||||
|  | import java.io.CharConversionException | ||||||
|  |  | ||||||
|  | object C64osEncoding { | ||||||
|  |  | ||||||
|  |     // decoding:  from C64 OS Screencodes (0-255) to unicode | ||||||
|  |     // character table from: | ||||||
|  |     // https://www.c64os.com/c64os/usersguide/appendices#charactersets | ||||||
|  |  | ||||||
|  |     private val decodingC64os = charArrayOf( | ||||||
|  |         '@'     ,    //  @    0x00 -> COMMERCIAL AT | ||||||
|  |         'a'     ,    //  a    0x01 -> LATIN SMALL LETTER A | ||||||
|  |         'b'     ,    //  b    0x02 -> LATIN SMALL LETTER B | ||||||
|  |         'c'     ,    //  c    0x03 -> LATIN SMALL LETTER C | ||||||
|  |         'd'     ,    //  d    0x04 -> LATIN SMALL LETTER D | ||||||
|  |         'e'     ,    //  e    0x05 -> LATIN SMALL LETTER E | ||||||
|  |         'f'     ,    //  f    0x06 -> LATIN SMALL LETTER F | ||||||
|  |         'g'     ,    //  g    0x07 -> LATIN SMALL LETTER G | ||||||
|  |         'h'     ,    //  h    0x08 -> LATIN SMALL LETTER H | ||||||
|  |         'i'     ,    //  i    0x09 -> LATIN SMALL LETTER I | ||||||
|  |         'j'     ,    //  j    0x0A -> LATIN SMALL LETTER J | ||||||
|  |         'k'     ,    //  k    0x0B -> LATIN SMALL LETTER K | ||||||
|  |         'l'     ,    //  l    0x0C -> LATIN SMALL LETTER L | ||||||
|  |         'm'     ,    //  m    0x0D -> LATIN SMALL LETTER M | ||||||
|  |         'n'     ,    //  n    0x0E -> LATIN SMALL LETTER N | ||||||
|  |         'o'     ,    //  o    0x0F -> LATIN SMALL LETTER O | ||||||
|  |         'p'     ,    //  p    0x10 -> LATIN SMALL LETTER P | ||||||
|  |         'q'     ,    //  q    0x11 -> LATIN SMALL LETTER Q | ||||||
|  |         'r'     ,    //  r    0x12 -> LATIN SMALL LETTER R | ||||||
|  |         's'     ,    //  s    0x13 -> LATIN SMALL LETTER S | ||||||
|  |         't'     ,    //  t    0x14 -> LATIN SMALL LETTER T | ||||||
|  |         'u'     ,    //  u    0x15 -> LATIN SMALL LETTER U | ||||||
|  |         'v'     ,    //  v    0x16 -> LATIN SMALL LETTER V | ||||||
|  |         'w'     ,    //  w    0x17 -> LATIN SMALL LETTER W | ||||||
|  |         'x'     ,    //  x    0x18 -> LATIN SMALL LETTER X | ||||||
|  |         'y'     ,    //  y    0x19 -> LATIN SMALL LETTER Y | ||||||
|  |         'z'     ,    //  z    0x1A -> LATIN SMALL LETTER Z | ||||||
|  |         '['     ,    //  [    0x1B -> LEFT SQUARE BRACKET | ||||||
|  |         '\\'    ,    //  \    0x1C -> REVERSE SOLIDUS | ||||||
|  |         ']'     ,    //  ]    0x1D -> RIGHT SQUARE BRACKET | ||||||
|  |         '^'     ,    //  ^    0x1E -> CIRCUMFLEX | ||||||
|  |         '_'     ,    //  _    0x1F -> UNDERSCORE | ||||||
|  |         ' '     ,    //       0x20 -> SPACE | ||||||
|  |         '!'     ,    //  !    0x21 -> EXCLAMATION MARK | ||||||
|  |         '"'     ,    //  "    0x22 -> QUOTATION MARK | ||||||
|  |         '#'     ,    //  #    0x23 -> NUMBER SIGN | ||||||
|  |         '$'     ,    //  $    0x24 -> DOLLAR SIGN | ||||||
|  |         '%'     ,    //  %    0x25 -> PERCENT SIGN | ||||||
|  |         '&'     ,    //  &    0x26 -> AMPERSAND | ||||||
|  |         '\''    ,    //  '    0x27 -> APOSTROPHE | ||||||
|  |         '('     ,    //  (    0x28 -> LEFT PARENTHESIS | ||||||
|  |         ')'     ,    //  )    0x29 -> RIGHT PARENTHESIS | ||||||
|  |         '*'     ,    //  *    0x2A -> ASTERISK | ||||||
|  |         '+'     ,    //  +    0x2B -> PLUS SIGN | ||||||
|  |         ','     ,    //  ,    0x2C -> COMMA | ||||||
|  |         '-'     ,    //  -    0x2D -> HYPHEN-MINUS | ||||||
|  |         '.'     ,    //  .    0x2E -> FULL STOP | ||||||
|  |         '/'     ,    //  /    0x2F -> SOLIDUS | ||||||
|  |         '0'     ,    //  0    0x30 -> DIGIT ZERO | ||||||
|  |         '1'     ,    //  1    0x31 -> DIGIT ONE | ||||||
|  |         '2'     ,    //  2    0x32 -> DIGIT TWO | ||||||
|  |         '3'     ,    //  3    0x33 -> DIGIT THREE | ||||||
|  |         '4'     ,    //  4    0x34 -> DIGIT FOUR | ||||||
|  |         '5'     ,    //  5    0x35 -> DIGIT FIVE | ||||||
|  |         '6'     ,    //  6    0x36 -> DIGIT SIX | ||||||
|  |         '7'     ,    //  7    0x37 -> DIGIT SEVEN | ||||||
|  |         '8'     ,    //  8    0x38 -> DIGIT EIGHT | ||||||
|  |         '9'     ,    //  9    0x39 -> DIGIT NINE | ||||||
|  |         ':'     ,    //  :    0x3A -> COLON | ||||||
|  |         ';'     ,    //  ;    0x3B -> SEMICOLON | ||||||
|  |         '<'     ,    //  <    0x3C -> LESS-THAN SIGN | ||||||
|  |         '='     ,    //  =    0x3D -> EQUALS SIGN | ||||||
|  |         '>'     ,    //  >    0x3E -> GREATER-THAN SIGN | ||||||
|  |         '?'     ,    //  ?    0x3F -> QUESTION MARK | ||||||
|  |         '`'     ,    //  `    0x40 -> GRAVE ACCENT | ||||||
|  |         'A'     ,    //  A    0x41 -> LATIN CAPITAL LETTER A | ||||||
|  |         'B'     ,    //  B    0x42 -> LATIN CAPITAL LETTER B | ||||||
|  |         'C'     ,    //  C    0x43 -> LATIN CAPITAL LETTER C | ||||||
|  |         'D'     ,    //  D    0x44 -> LATIN CAPITAL LETTER D | ||||||
|  |         'E'     ,    //  E    0x45 -> LATIN CAPITAL LETTER E | ||||||
|  |         'F'     ,    //  F    0x46 -> LATIN CAPITAL LETTER F | ||||||
|  |         'G'     ,    //  G    0x47 -> LATIN CAPITAL LETTER G | ||||||
|  |         'H'     ,    //  H    0x48 -> LATIN CAPITAL LETTER H | ||||||
|  |         'I'     ,    //  I    0x49 -> LATIN CAPITAL LETTER I | ||||||
|  |         'J'     ,    //  J    0x4A -> LATIN CAPITAL LETTER J | ||||||
|  |         'K'     ,    //  K    0x4B -> LATIN CAPITAL LETTER K | ||||||
|  |         'L'     ,    //  L    0x4C -> LATIN CAPITAL LETTER L | ||||||
|  |         'M'     ,    //  M    0x4D -> LATIN CAPITAL LETTER M | ||||||
|  |         'N'     ,    //  N    0x4E -> LATIN CAPITAL LETTER N | ||||||
|  |         'O'     ,    //  O    0x4F -> LATIN CAPITAL LETTER O | ||||||
|  |         'P'     ,    //  P    0x50 -> LATIN CAPITAL LETTER P | ||||||
|  |         'Q'     ,    //  Q    0x51 -> LATIN CAPITAL LETTER Q | ||||||
|  |         'R'     ,    //  R    0x52 -> LATIN CAPITAL LETTER R | ||||||
|  |         'S'     ,    //  S    0x53 -> LATIN CAPITAL LETTER S | ||||||
|  |         'T'     ,    //  T    0x54 -> LATIN CAPITAL LETTER T | ||||||
|  |         'U'     ,    //  U    0x55 -> LATIN CAPITAL LETTER U | ||||||
|  |         'V'     ,    //  V    0x56 -> LATIN CAPITAL LETTER V | ||||||
|  |         'W'     ,    //  W    0x57 -> LATIN CAPITAL LETTER W | ||||||
|  |         'X'     ,    //  X    0x58 -> LATIN CAPITAL LETTER X | ||||||
|  |         'Y'     ,    //  Y    0x59 -> LATIN CAPITAL LETTER Y | ||||||
|  |         'Z'     ,    //  Z    0x5A -> LATIN CAPITAL LETTER Z | ||||||
|  |         '{'     ,    //  {    0x5B -> LEFT BRACE | ||||||
|  |         '|'     ,    //  |    0x5C -> VERTICAL BAR | ||||||
|  |         '}'     ,    //  }    0x5D -> RIGHT BRACE | ||||||
|  |         '~'     ,    //  ~    0x5E -> TILDE | ||||||
|  |         '\ufffe',    //       0x5F -> RESERVED | ||||||
|  |         '\u00a0',    //       0x60 -> NO-BREAK SPACE (TRANSPARENT) | ||||||
|  |         '\ufffe',    //       0x61 -> COMMODORE SYMBOL | ||||||
|  |         '\u2191',    //  ↑    0x62 -> UP ARROW | ||||||
|  |         '\u2193',    //  ↓    0x63 -> DOWN ARROW | ||||||
|  |         '\u2190',    //  ←    0x64 -> LEFT ARROW | ||||||
|  |         '\u2192',    //  →    0x65 -> RIGHT ARROW | ||||||
|  |         '\u231A',    //  ⌚   0x66 -> WATCH (ANALOG CLOCKFACE) | ||||||
|  |         '\u21BB',    //  ↻    0x67 -> CYCLE ARROWS | ||||||
|  |         '\u2026',    //  …    0x68 -> ELLIPSIS | ||||||
|  |         '\u25a7',    //  ▧    0x69 -> DIAGNONAL STRIPES | ||||||
|  |         '\u2610',    //  ☐    0x6A -> CHECKBOX UNCHECKED | ||||||
|  |         '\u2611',    //  ☑    0x6B -> CHECKBOX CHECKED | ||||||
|  |         '\ufffe',    //       0x6C -> RADIO BUTTON UNSELECTED | ||||||
|  |         '\ufffe',    //       0x6D -> RADIO BUTTON SELECTED | ||||||
|  |         '\ufffe',    //       0x6E -> UTILITY CLOSE BUTTON | ||||||
|  |         '\ufffe',    //       0x6F -> UTILITY TITLE BAR | ||||||
|  |         '\u00a9',    //  ©    0x70 -> COPYRIGHT | ||||||
|  |         '\u2713',    //  ✓    0x71 -> CHECKMARK | ||||||
|  |         '\u2261',    //  ≡    0x72 -> THREE HORIZONTAL STRIPES | ||||||
|  |         '\ufffe',    //       0x73 -> TICK TRACK | ||||||
|  |         '\ufffe',    //       0x74 -> TICK TRACK NUB | ||||||
|  |         '\ufffe',    //       0x75 -> TAB CORNER | ||||||
|  |         '\u2980',    //  ⦀    0x76 -> THREE VERTICAL STRIPES | ||||||
|  |         '\ufffe',    //       0x77 -> CUSTOM 1 | ||||||
|  |         '\ufffe',    //       0x78 -> CUSTOM 2 | ||||||
|  |         '\ufffe',    //       0x79 -> CUSTOM 3 | ||||||
|  |         '\ufffe',    //       0x7A -> CUSTOM 4 | ||||||
|  |         '\ufffe',    //       0x7B -> CUSTOM 5 | ||||||
|  |         '\ufffe',    //       0x7C -> CUSTOM 6 | ||||||
|  |         '\ufffe',    //       0x7D -> CUSTOM 7 | ||||||
|  |         '\ufffe',    //       0x7E -> CUSTOM 8 | ||||||
|  |         '\ufffe',    //       0x7F -> CUSTOM 9 | ||||||
|  |         '\ufffe',    //       0x80 -> REVERSED COMMERCIAL AT | ||||||
|  |         '\ufffe',    //       0x81 -> REVERSED LATIN SMALL LETTER A | ||||||
|  |         '\ufffe',    //       0x82 -> REVERSED LATIN SMALL LETTER B | ||||||
|  |         '\ufffe',    //       0x83 -> REVERSED LATIN SMALL LETTER C | ||||||
|  |         '\ufffe',    //       0x84 -> REVERSED LATIN SMALL LETTER D | ||||||
|  |         '\ufffe',    //       0x85 -> REVERSED LATIN SMALL LETTER E | ||||||
|  |         '\ufffe',    //       0x86 -> REVERSED LATIN SMALL LETTER F | ||||||
|  |         '\ufffe',    //       0x87 -> REVERSED LATIN SMALL LETTER G | ||||||
|  |         '\ufffe',    //       0x88 -> REVERSED LATIN SMALL LETTER H | ||||||
|  |         '\ufffe',    //       0x89 -> REVERSED LATIN SMALL LETTER I | ||||||
|  |         '\ufffe',    //       0x8A -> REVERSED LATIN SMALL LETTER J | ||||||
|  |         '\ufffe',    //       0x8B -> REVERSED LATIN SMALL LETTER K | ||||||
|  |         '\ufffe',    //       0x8C -> REVERSED LATIN SMALL LETTER L | ||||||
|  |         '\ufffe',    //       0x8D -> REVERSED LATIN SMALL LETTER M | ||||||
|  |         '\ufffe',    //       0x8E -> REVERSED LATIN SMALL LETTER N | ||||||
|  |         '\ufffe',    //       0x8F -> REVERSED LATIN SMALL LETTER O | ||||||
|  |         '\ufffe',    //       0x90 -> REVERSED LATIN SMALL LETTER P | ||||||
|  |         '\ufffe',    //       0x91 -> REVERSED LATIN SMALL LETTER Q | ||||||
|  |         '\ufffe',    //       0x92 -> REVERSED LATIN SMALL LETTER R | ||||||
|  |         '\ufffe',    //       0x93 -> REVERSED LATIN SMALL LETTER S | ||||||
|  |         '\ufffe',    //       0x94 -> REVERSED LATIN SMALL LETTER T | ||||||
|  |         '\ufffe',    //       0x95 -> REVERSED LATIN SMALL LETTER U | ||||||
|  |         '\ufffe',    //       0x96 -> REVERSED LATIN SMALL LETTER V | ||||||
|  |         '\ufffe',    //       0x97 -> REVERSED LATIN SMALL LETTER W | ||||||
|  |         '\ufffe',    //       0x98 -> REVERSED LATIN SMALL LETTER X | ||||||
|  |         '\ufffe',    //       0x99 -> REVERSED LATIN SMALL LETTER Y | ||||||
|  |         '\ufffe',    //       0x9A -> REVERSED LATIN SMALL LETTER Z | ||||||
|  |         '\ufffe',    //       0x9B -> REVERSED LEFT SQUARE BRACKET | ||||||
|  |         '\ufffe',    //       0x9C -> REVERSED REVERSE SOLIDUS | ||||||
|  |         '\ufffe',    //       0x9D -> REVERSED RIGHT SQUARE BRACKET | ||||||
|  |         '\ufffe',    //       0x9E -> REVERSED CIRCUMFLEX | ||||||
|  |         '\ufffe',    //       0x9F -> REVERSED UNDERSCORE | ||||||
|  |         '\ufffe',    //       0xA0 -> REVERSED SPACE | ||||||
|  |         '\ufffe',    //       0xA1 -> REVERSED EXCLAMATION MARK | ||||||
|  |         '\ufffe',    //       0xA2 -> REVERSED QUOTATION MARK | ||||||
|  |         '\ufffe',    //       0xA3 -> REVERSED NUMBER SIGN | ||||||
|  |         '\ufffe',    //       0xA4 -> REVERSED DOLLAR SIGN | ||||||
|  |         '\ufffe',    //       0xA5 -> REVERSED PERCENT SIGN | ||||||
|  |         '\ufffe',    //       0xA6 -> REVERSED AMPERSAND | ||||||
|  |         '\ufffe',    //       0xA7 -> REVERSED APOSTROPHE | ||||||
|  |         '\ufffe',    //       0xA8 -> REVERSED LEFT PARENTHESIS | ||||||
|  |         '\ufffe',    //       0xA9 -> REVERSED RIGHT PARENTHESIS | ||||||
|  |         '\ufffe',    //       0xAA -> REVERSED ASTERISK | ||||||
|  |         '\ufffe',    //       0xAB -> REVERSED PLUS SIGN | ||||||
|  |         '\ufffe',    //       0xAC -> REVERSED COMMA | ||||||
|  |         '\ufffe',    //       0xAD -> REVERSED HYPHEN-MINUS | ||||||
|  |         '\ufffe',    //       0xAE -> REVERSED FULL STOP | ||||||
|  |         '\ufffe',    //       0xAF -> REVERSED SOLIDUS | ||||||
|  |         '\ufffe',    //       0xB0 -> REVERSED DIGIT ZERO | ||||||
|  |         '\ufffe',    //       0xB1 -> REVERSED DIGIT ONE | ||||||
|  |         '\ufffe',    //       0xB2 -> REVERSED DIGIT TWO | ||||||
|  |         '\ufffe',    //       0xB3 -> REVERSED DIGIT THREE | ||||||
|  |         '\ufffe',    //       0xB4 -> REVERSED DIGIT FOUR | ||||||
|  |         '\ufffe',    //       0xB5 -> REVERSED DIGIT FIVE | ||||||
|  |         '\ufffe',    //       0xB6 -> REVERSED DIGIT SIX | ||||||
|  |         '\ufffe',    //       0xB7 -> REVERSED DIGIT SEVEN | ||||||
|  |         '\ufffe',    //       0xB8 -> REVERSED DIGIT EIGHT | ||||||
|  |         '\ufffe',    //       0xB9 -> REVERSED DIGIT NINE | ||||||
|  |         '\ufffe',    //       0xBA -> REVERSED COLON | ||||||
|  |         '\ufffe',    //       0xBB -> REVERSED SEMICOLON | ||||||
|  |         '\ufffe',    //       0xBC -> REVERSED LESS-THAN SIGN | ||||||
|  |         '\ufffe',    //       0xBD -> REVERSED EQUALS SIGN | ||||||
|  |         '\ufffe',    //       0xBE -> REVERSED GREATER-THAN SIGN | ||||||
|  |         '\ufffe',    //       0xBF -> REVERSED QUESTION MARK | ||||||
|  |         '\ufffe',    //       0xC0 -> REVERSED GRAVE ACCENT | ||||||
|  |         '\ufffe',    //       0xC1 -> REVERSED LATIN CAPITAL LETTER A | ||||||
|  |         '\ufffe',    //       0xC2 -> REVERSED LATIN CAPITAL LETTER B | ||||||
|  |         '\ufffe',    //       0xC3 -> REVERSED LATIN CAPITAL LETTER C | ||||||
|  |         '\ufffe',    //       0xC4 -> REVERSED LATIN CAPITAL LETTER D | ||||||
|  |         '\ufffe',    //       0xC5 -> REVERSED LATIN CAPITAL LETTER E | ||||||
|  |         '\ufffe',    //       0xC6 -> REVERSED LATIN CAPITAL LETTER F | ||||||
|  |         '\ufffe',    //       0xC7 -> REVERSED LATIN CAPITAL LETTER G | ||||||
|  |         '\ufffe',    //       0xC8 -> REVERSED LATIN CAPITAL LETTER H | ||||||
|  |         '\ufffe',    //       0xC9 -> REVERSED LATIN CAPITAL LETTER I | ||||||
|  |         '\ufffe',    //       0xCA -> REVERSED LATIN CAPITAL LETTER J | ||||||
|  |         '\ufffe',    //       0xCB -> REVERSED LATIN CAPITAL LETTER K | ||||||
|  |         '\ufffe',    //       0xCC -> REVERSED LATIN CAPITAL LETTER L | ||||||
|  |         '\ufffe',    //       0xCD -> REVERSED LATIN CAPITAL LETTER M | ||||||
|  |         '\ufffe',    //       0xCE -> REVERSED LATIN CAPITAL LETTER N | ||||||
|  |         '\ufffe',    //       0xCF -> REVERSED LATIN CAPITAL LETTER O | ||||||
|  |         '\ufffe',    //       0xD0 -> REVERSED LATIN CAPITAL LETTER P | ||||||
|  |         '\ufffe',    //       0xD1 -> REVERSED LATIN CAPITAL LETTER Q | ||||||
|  |         '\ufffe',    //       0xD2 -> REVERSED LATIN CAPITAL LETTER R | ||||||
|  |         '\ufffe',    //       0xD3 -> REVERSED LATIN CAPITAL LETTER S | ||||||
|  |         '\ufffe',    //       0xD4 -> REVERSED LATIN CAPITAL LETTER T | ||||||
|  |         '\ufffe',    //       0xD5 -> REVERSED LATIN CAPITAL LETTER U | ||||||
|  |         '\ufffe',    //       0xD6 -> REVERSED LATIN CAPITAL LETTER V | ||||||
|  |         '\ufffe',    //       0xD7 -> REVERSED LATIN CAPITAL LETTER W | ||||||
|  |         '\ufffe',    //       0xD8 -> REVERSED LATIN CAPITAL LETTER X | ||||||
|  |         '\ufffe',    //       0xD9 -> REVERSED LATIN CAPITAL LETTER Y | ||||||
|  |         '\ufffe',    //       0xDA -> REVERSED LATIN CAPITAL LETTER Z | ||||||
|  |         '\ufffe',    //       0xDB -> REVERSED LEFT BRACE | ||||||
|  |         '\ufffe',    //       0xDC -> REVERSED VERTICAL BAR | ||||||
|  |         '\ufffe',    //       0xDD -> REVERSED RIGHT BRACE | ||||||
|  |         '\ufffe',    //       0xDE -> REVERSED TILDE | ||||||
|  |         '\ufffe',    //       0xDF -> RESERVED | ||||||
|  |         '\ufffe',    //       0xE0 -> RESERVED | ||||||
|  |         '\ufffe',    //       0xE1 -> REVERSED COMMODORE SYMBOL | ||||||
|  |         '\ufffe',    //       0xE2 -> REVERSED UP ARROW | ||||||
|  |         '\ufffe',    //       0xE3 -> REVERSED DOWN ARROW | ||||||
|  |         '\ufffe',    //       0xE4 -> REVERSED LEFT ARROW | ||||||
|  |         '\ufffe',    //       0xE5 -> REVERSED RIGHT ARROW | ||||||
|  |         '\ufffe',    //       0xE6 -> REVERSED ANALOG CLOCKFACE | ||||||
|  |         '\ufffe',    //       0xE7 -> REVERSED CYCLE ARROWS | ||||||
|  |         '\ufffe',    //       0xE8 -> REVERSED ELLIPSIS | ||||||
|  |         '\ufffe',    //       0xE9 -> REVERSED DIAGONAL STRIPES | ||||||
|  |         '\ufffe',    //       0xEA -> REVERSED CHECKBOX UNCHECKED | ||||||
|  |         '\ufffe',    //       0xEB -> REVERSED CHECKBOX CHECKED | ||||||
|  |         '\ufffe',    //       0xEC -> REVERSED RADIO BUTTON UNSELECTED | ||||||
|  |         '\ufffe',    //       0xED -> REVERSED RADIO BUTTON SELECTED | ||||||
|  |         '\ufffe',    //       0xEE -> MEMORY CHIP ICON | ||||||
|  |         '\u21e7',    //  ⇧    0xEF -> SHIFT SYMBOL | ||||||
|  |         '\ufffe',    //       0xF0 -> REVERSED COPYRIGHT SYMBOL | ||||||
|  |         '\ufffe',    //       0xF1 -> REVERSED CHECKMARK | ||||||
|  |         '\ufffe',    //       0xF2 -> REVERSED THREE HORIZONTAL STRIPES | ||||||
|  |         '\ufffe',    //       0xF3 -> REVERSED TICK TRACK | ||||||
|  |         '\ufffe',    //       0xF4 -> REVERSED TICK TRACK NUB | ||||||
|  |         '\ufffe',    //       0xF5 -> REVERSED TAB CORNER | ||||||
|  |         '\ufffe',    //       0xF6 -> REVERSED THREE VERTICAL STRIPES | ||||||
|  |         '\ufffe',    //       0xF7 -> CUSTOM 10 | ||||||
|  |         '\ufffe',    //       0xF8 -> CUSTOM 11 | ||||||
|  |         '\ufffe',    //       0xF9 -> CUSTOM 12 | ||||||
|  |         '\ufffe',    //       0xFA -> CUSTOM 13 | ||||||
|  |         '\ufffe',    //       0xFB -> CUSTOM 14 | ||||||
|  |         '\ufffe',    //       0xFC -> CUSTOM 15 | ||||||
|  |         '\ufffe',    //       0xFD -> CUSTOM 16 | ||||||
|  |         '\ufffe',    //       0xFE -> CUSTOM 17 | ||||||
|  |         '\ufffe'     //       0xFF -> CUSTOM 18 | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     // encoding:  from unicode to C64 OS Screencodes (0-255) | ||||||
|  |     private val encodingC64os = decodingC64os.withIndex().associate{it.value to it.index} | ||||||
|  |  | ||||||
|  |     private fun replaceSpecial(chr: Char): Char = | ||||||
|  |         when(chr) { | ||||||
|  |             '\r' -> '\n'        // to make \r (carriage returrn) equivalent to \n (line feed): RETURN ($0d) | ||||||
|  |             else -> chr | ||||||
|  |         } | ||||||
|  |      | ||||||
|  |     fun encode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> { | ||||||
|  |         fun encodeChar(chr3: Char, lowercase: Boolean): UByte { | ||||||
|  |             val chr = replaceSpecial(chr3) | ||||||
|  |             val screencode = encodingC64os[chr] | ||||||
|  |             return screencode?.toUByte() ?: when (chr) { | ||||||
|  |                 '\u0000' -> 0u | ||||||
|  |                 '\n' -> 13u | ||||||
|  |                 in '\u8000'..'\u80ff' -> { | ||||||
|  |                     // special case: take the lower 8 bit hex value directly | ||||||
|  |                     (chr.code - 0x8000).toUByte() | ||||||
|  |                 } | ||||||
|  |                 else -> { | ||||||
|  |                     if(chr.isISOControl()) | ||||||
|  |                         throw CharConversionException("no c64os character for char #${chr.code}") | ||||||
|  |                     else | ||||||
|  |                         throw CharConversionException("no c64os character for char #${chr.code} '${chr}'") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return try { | ||||||
|  |             Ok(text.map { | ||||||
|  |                 try { | ||||||
|  |                     encodeChar(it, lowercase) | ||||||
|  |                 } catch (x: CharConversionException) { | ||||||
|  |                     encodeChar(it, !lowercase) | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } catch(cx: CharConversionException) { | ||||||
|  |             Err(cx) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun decode(screencode: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> { | ||||||
|  |         return try { | ||||||
|  |             Ok(screencode.map { | ||||||
|  |                 val code = it.toInt() | ||||||
|  |                 if(code<0 || code>= decodingC64os.size) | ||||||
|  |                     throw CharConversionException("c64os $code out of range 0..${decodingC64os.size-1}") | ||||||
|  |                 decodingC64os[code] | ||||||
|  |             }.joinToString("")) | ||||||
|  |         } catch(ce: CharConversionException) { | ||||||
|  |             Err(ce) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,25 +1,24 @@ | |||||||
| package prog8.code.target | package prog8.code.target.encodings | ||||||
| 
 | 
 | ||||||
| import com.github.michaelbull.result.fold | import com.github.michaelbull.result.fold | ||||||
| import prog8.code.core.Encoding | import prog8.code.core.Encoding | ||||||
| import prog8.code.core.IStringEncoding | import prog8.code.core.IStringEncoding | ||||||
| import prog8.code.core.InternalCompilerException | import prog8.code.core.InternalCompilerException | ||||||
| import prog8.code.target.encodings.* |  | ||||||
| 
 | 
 | ||||||
| 
 | class Encoder(val newlineToCarriageReturn: Boolean): IStringEncoding { | ||||||
| object Encoder: IStringEncoding { |  | ||||||
|     override val defaultEncoding: Encoding = Encoding.ISO |     override val defaultEncoding: Encoding = Encoding.ISO | ||||||
| 
 | 
 | ||||||
|     override fun encodeString(str: String, encoding: Encoding): List<UByte> { |     override fun encodeString(str: String, encoding: Encoding): List<UByte> { | ||||||
|         val coded = when(encoding) { |         val coded = when(encoding) { | ||||||
|             Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true) |             Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true) | ||||||
|             Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true) |             Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true) | ||||||
|             Encoding.ISO -> IsoEncoding.encode(str) |             Encoding.ISO -> IsoEncoding.encode(str, newlineToCarriageReturn) | ||||||
|             Encoding.ATASCII -> AtasciiEncoding.encode(str) |             Encoding.ISO5 -> IsoCyrillicEncoding.encode(str, newlineToCarriageReturn) | ||||||
|             Encoding.ISO5 -> IsoCyrillicEncoding.encode(str) |             Encoding.ISO16 -> IsoEasternEncoding.encode(str, newlineToCarriageReturn) | ||||||
|             Encoding.ISO16 -> IsoEasternEncoding.encode(str) |  | ||||||
|             Encoding.CP437 -> Cp437Encoding.encode(str) |             Encoding.CP437 -> Cp437Encoding.encode(str) | ||||||
|             Encoding.KATAKANA -> KatakanaEncoding.encode(str) |             Encoding.KATAKANA -> KatakanaEncoding.encode(str, newlineToCarriageReturn) | ||||||
|  |             Encoding.ATASCII -> AtasciiEncoding.encode(str) | ||||||
|  |             Encoding.C64OS -> C64osEncoding.encode(str) | ||||||
|             else -> throw InternalCompilerException("unsupported encoding $encoding") |             else -> throw InternalCompilerException("unsupported encoding $encoding") | ||||||
|         } |         } | ||||||
|         return coded.fold( |         return coded.fold( | ||||||
| @@ -31,12 +30,13 @@ object Encoder: IStringEncoding { | |||||||
|         val decoded = when(encoding) { |         val decoded = when(encoding) { | ||||||
|             Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true) |             Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true) | ||||||
|             Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true) |             Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true) | ||||||
|             Encoding.ISO -> IsoEncoding.decode(bytes) |             Encoding.ISO -> IsoEncoding.decode(bytes, newlineToCarriageReturn) | ||||||
|             Encoding.ATASCII -> AtasciiEncoding.decode(bytes) |             Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes, newlineToCarriageReturn) | ||||||
|             Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes) |             Encoding.ISO16 -> IsoEasternEncoding.decode(bytes, newlineToCarriageReturn) | ||||||
|             Encoding.ISO16 -> IsoEasternEncoding.decode(bytes) |  | ||||||
|             Encoding.CP437 -> Cp437Encoding.decode(bytes) |             Encoding.CP437 -> Cp437Encoding.decode(bytes) | ||||||
|             Encoding.KATAKANA -> KatakanaEncoding.decode(bytes) |             Encoding.KATAKANA -> KatakanaEncoding.decode(bytes, newlineToCarriageReturn) | ||||||
|  |             Encoding.ATASCII -> AtasciiEncoding.decode(bytes) | ||||||
|  |             Encoding.C64OS -> C64osEncoding.decode(bytes) | ||||||
|             else -> throw InternalCompilerException("unsupported encoding $encoding") |             else -> throw InternalCompilerException("unsupported encoding $encoding") | ||||||
|         } |         } | ||||||
|         return decoded.fold( |         return decoded.fold( | ||||||
| @@ -6,14 +6,16 @@ import com.github.michaelbull.result.Result | |||||||
| import java.io.CharConversionException | import java.io.CharConversionException | ||||||
| import java.nio.charset.Charset | import java.nio.charset.Charset | ||||||
|  |  | ||||||
|  |  | ||||||
| open class IsoEncodingBase(charsetName: String) { | open class IsoEncodingBase(charsetName: String) { | ||||||
|     val charset: Charset = Charset.forName(charsetName) |     val charset: Charset = Charset.forName(charsetName) | ||||||
|  |  | ||||||
|     fun encode(str: String): Result<List<UByte>, CharConversionException> { |     fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> { | ||||||
|         return try { |         return try { | ||||||
|             val mapped = str.map { chr -> |             val mapped = str.map { chr -> | ||||||
|                 when (chr) { |                 when (chr) { | ||||||
|                     '\u0000' -> 0u |                     '\u0000' -> 0u | ||||||
|  |                     '\n' -> if(newlineToCarriageReturn) 13u else 10u | ||||||
|                     in '\u8000'..'\u80ff' -> { |                     in '\u8000'..'\u80ff' -> { | ||||||
|                         // special case: take the lower 8 bit hex value directly |                         // special case: take the lower 8 bit hex value directly | ||||||
|                         (chr.code - 0x8000).toUByte() |                         (chr.code - 0x8000).toUByte() | ||||||
| @@ -27,9 +29,14 @@ open class IsoEncodingBase(charsetName: String) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> { |     fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> { | ||||||
|         return try { |         return try { | ||||||
|             Ok(String(bytes.map { it.toByte() }.toByteArray(), charset)) |             Ok(String(bytes.map { | ||||||
|  |                 when(it) { | ||||||
|  |                     13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13 | ||||||
|  |                     else -> it.toByte() | ||||||
|  |                 } | ||||||
|  |             }.toByteArray(), charset)) | ||||||
|         } catch (ce: CharConversionException) { |         } catch (ce: CharConversionException) { | ||||||
|             Err(ce) |             Err(ce) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -64,10 +64,11 @@ object JapaneseCharacterConverter { | |||||||
| object KatakanaEncoding { | object KatakanaEncoding { | ||||||
|     val charset: Charset = Charset.forName("JIS_X0201") |     val charset: Charset = Charset.forName("JIS_X0201") | ||||||
|  |  | ||||||
|     fun encode(str: String): Result<List<UByte>, CharConversionException> { |     fun encode(str: String, newlineToCarriageReturn: Boolean): Result<List<UByte>, CharConversionException> { | ||||||
|         return try { |         return try { | ||||||
|             val mapped = str.map { chr -> |             val mapped = str.map { chr -> | ||||||
|                 when (chr) { |                 when (chr) { | ||||||
|  |                     '\n' -> if(newlineToCarriageReturn) 13u else 10u | ||||||
|  |  | ||||||
|                     '\u0000' -> 0u |                     '\u0000' -> 0u | ||||||
|                     '\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves |                     '\u00a0' -> 0xa0u // $a0 isn't technically a part of JIS X 0201 spec, and so we need to handle this ourselves | ||||||
| @@ -112,9 +113,14 @@ object KatakanaEncoding { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> { |     fun decode(bytes: Iterable<UByte>, newlineToCarriageReturn: Boolean): Result<String, CharConversionException> { | ||||||
|         return try { |         return try { | ||||||
|             Ok(String(bytes.map { it.toByte() }.toByteArray(), charset)) |             Ok(String(bytes.map { | ||||||
|  |                 when(it) { | ||||||
|  |                     13u.toUByte() -> if(newlineToCarriageReturn) 10 else 13 | ||||||
|  |                     else -> it.toByte() | ||||||
|  |                 } | ||||||
|  |             }.toByteArray(), charset)) | ||||||
|         } catch (ce: CharConversionException) { |         } catch (ce: CharConversionException) { | ||||||
|             Err(ce) |             Err(ce) | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import java.io.CharConversionException | |||||||
|  |  | ||||||
| object PetsciiEncoding { | object PetsciiEncoding { | ||||||
|  |  | ||||||
|     // decoding:  from Petscii/Screencodes (0-255) to unicode |     // decoding:  from Petscii/Screencodes (0-255) to Unicode | ||||||
|     // character tables used from https://github.com/irmen/cbmcodecs2 |     // character tables used from https://github.com/irmen/cbmcodecs2 | ||||||
|  |  | ||||||
|     private val decodingPetsciiLowercase = charArrayOf( |     private val decodingPetsciiLowercase = charArrayOf( | ||||||
| @@ -21,7 +21,7 @@ object PetsciiEncoding { | |||||||
|         '\ufffe',    //       0x07 -> UNDEFINED |         '\ufffe',    //       0x07 -> UNDEFINED | ||||||
|         '\uf118',    //       0x08 -> DISABLE CHARACTER SET SWITCHING (CUS) |         '\uf118',    //       0x08 -> DISABLE CHARACTER SET SWITCHING (CUS) | ||||||
|         '\uf119',    //       0x09 -> ENABLE CHARACTER SET SWITCHING (CUS) |         '\uf119',    //       0x09 -> ENABLE CHARACTER SET SWITCHING (CUS) | ||||||
|         '\ufffe',    //       0x0A -> UNDEFINED |         '\n',        //       0x0A -> LINE FEED (RETURN) | ||||||
|         '\ufffe',    //       0x0B -> UNDEFINED |         '\ufffe',    //       0x0B -> UNDEFINED | ||||||
|         '\ufffe',    //       0x0C -> UNDEFINED |         '\ufffe',    //       0x0C -> UNDEFINED | ||||||
|         '\n'    ,    //       0x0D -> LINE FEED (RETURN) |         '\n'    ,    //       0x0D -> LINE FEED (RETURN) | ||||||
| @@ -1089,7 +1089,7 @@ object PetsciiEncoding { | |||||||
|             Ok(text.map { |             Ok(text.map { | ||||||
|                 try { |                 try { | ||||||
|                     encodeChar(it, lowercase) |                     encodeChar(it, lowercase) | ||||||
|                 } catch (x: CharConversionException) { |                 } catch (_: CharConversionException) { | ||||||
|                     encodeChar(it, !lowercase) |                     encodeChar(it, !lowercase) | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
| @@ -1117,6 +1117,8 @@ object PetsciiEncoding { | |||||||
|             val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr] |             val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr] | ||||||
|             return screencode?.toUByte() ?: when (chr) { |             return screencode?.toUByte() ?: when (chr) { | ||||||
|                 '\u0000' -> 0u |                 '\u0000' -> 0u | ||||||
|  |                 '\n' -> 141u | ||||||
|  |                 '\r' -> 141u | ||||||
|                 in '\u8000'..'\u80ff' -> { |                 in '\u8000'..'\u80ff' -> { | ||||||
|                     // special case: take the lower 8 bit hex value directly |                     // special case: take the lower 8 bit hex value directly | ||||||
|                     (chr.code - 0x8000).toUByte() |                     (chr.code - 0x8000).toUByte() | ||||||
| @@ -1135,7 +1137,7 @@ object PetsciiEncoding { | |||||||
|             Ok(text.map { |             Ok(text.map { | ||||||
|                 try { |                 try { | ||||||
|                     encodeChar(it, lowercase) |                     encodeChar(it, lowercase) | ||||||
|                 } catch (x: CharConversionException) { |                 } catch (_: CharConversionException) { | ||||||
|                     encodeChar(it, !lowercase) |                     encodeChar(it, !lowercase) | ||||||
|                 } |                 } | ||||||
|             }) |             }) | ||||||
| @@ -1157,16 +1159,16 @@ object PetsciiEncoding { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> { |     fun petscii2scr(petsciicode: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> { | ||||||
|         val code: UInt = when { |         val code: UInt = when { | ||||||
|             petscii_code <= 0x1fu -> petscii_code + 128u |             petsciicode <= 0x1fu -> petsciicode + 128u | ||||||
|             petscii_code <= 0x3fu -> petscii_code.toUInt() |             petsciicode <= 0x3fu -> petsciicode.toUInt() | ||||||
|             petscii_code <= 0x5fu -> petscii_code - 64u |             petsciicode <= 0x5fu -> petsciicode - 64u | ||||||
|             petscii_code <= 0x7fu -> petscii_code - 32u |             petsciicode <= 0x7fu -> petsciicode - 32u | ||||||
|             petscii_code <= 0x9fu -> petscii_code + 64u |             petsciicode <= 0x9fu -> petsciicode + 64u | ||||||
|             petscii_code <= 0xbfu -> petscii_code - 64u |             petsciicode <= 0xbfu -> petsciicode - 64u | ||||||
|             petscii_code <= 0xfeu -> petscii_code - 128u |             petsciicode <= 0xfeu -> petsciicode - 128u | ||||||
|             petscii_code == 255.toUByte() -> 95u |             petsciicode == 255.toUByte() -> 95u | ||||||
|             else -> return Err(CharConversionException("petscii code out of range")) |             else -> return Err(CharConversionException("petscii code out of range")) | ||||||
|         } |         } | ||||||
|         if(inverseVideo) { |         if(inverseVideo) { | ||||||
|   | |||||||
| @@ -1,61 +0,0 @@ | |||||||
| package prog8.code.target.pet |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
| import prog8.code.target.C64Target |  | ||||||
| import prog8.code.target.cbm.Mflpt5 |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PETMachineDefinition: IMachineDefinition { |  | ||||||
|  |  | ||||||
|     override val cpu = CpuType.CPU6502 |  | ||||||
|  |  | ||||||
|     override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE |  | ||||||
|     override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE |  | ||||||
|     override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE |  | ||||||
|     override val PROGRAM_LOAD_ADDRESS = 0x0401u |  | ||||||
|  |  | ||||||
|     override val BSSHIGHRAM_START = 0u |  | ||||||
|     override val BSSHIGHRAM_END = 0u |  | ||||||
|     override val BSSGOLDENRAM_START = 0u |  | ||||||
|     override val BSSGOLDENRAM_END = 0u |  | ||||||
|  |  | ||||||
|     override lateinit var zeropage: Zeropage |  | ||||||
|     override lateinit var golden: GoldenRam |  | ||||||
|  |  | ||||||
|     override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm() |  | ||||||
|  |  | ||||||
|     override fun convertFloatToBytes(num: Double): List<UByte> { |  | ||||||
|         val m5 = Mflpt5.fromNumber(num) |  | ||||||
|         return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun convertBytesToFloat(bytes: List<UByte>): Double { |  | ||||||
|         require(bytes.size==5) { "need 5 bytes" } |  | ||||||
|         val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]) |  | ||||||
|         return m5.toDouble() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) { |  | ||||||
|         if(selectedEmulator!=1) { |  | ||||||
|             System.err.println("The pet target only supports the main emulator (Vice).") |  | ||||||
|             return |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         println("\nStarting PET emulator...") |  | ||||||
|         val viceMonlist = C64Target.viceMonListName(programNameWithPath.toString()) |  | ||||||
|         val cmdline = listOf("xpet", "-model", "4032", "-ramsize", "32", "-videosize", "40", "-silent", "-moncommands", viceMonlist, |  | ||||||
|                 "-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg") |  | ||||||
|         val processb = ProcessBuilder(cmdline).inheritIO() |  | ||||||
|         val process=processb.start() |  | ||||||
|         process.waitFor() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun isIOAddress(address: UInt): Boolean = address in 0xe800u..0xe8ffu |  | ||||||
|  |  | ||||||
|     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { |  | ||||||
|         zeropage = PETZeropage(compilerOptions) |  | ||||||
|         // there's no golden ram. |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,91 +0,0 @@ | |||||||
| package prog8.code.target.virtual |  | ||||||
|  |  | ||||||
| import prog8.code.core.* |  | ||||||
| import java.nio.file.Path |  | ||||||
| import kotlin.io.path.isReadable |  | ||||||
| import kotlin.io.path.name |  | ||||||
| import kotlin.io.path.readText |  | ||||||
|  |  | ||||||
| class VirtualMachineDefinition: IMachineDefinition { |  | ||||||
|  |  | ||||||
|     override val cpu = CpuType.VIRTUAL |  | ||||||
|  |  | ||||||
|     override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble() |  | ||||||
|     override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble() |  | ||||||
|     override val FLOAT_MEM_SIZE = 8             // 64-bits double |  | ||||||
|     override val PROGRAM_LOAD_ADDRESS = 0u      // not actually used |  | ||||||
|  |  | ||||||
|     override val BSSHIGHRAM_START = 0u          // not actually used |  | ||||||
|     override val BSSHIGHRAM_END = 0u            // not actually used |  | ||||||
|     override val BSSGOLDENRAM_START = 0u        // not actually used |  | ||||||
|     override val BSSGOLDENRAM_END = 0u          // not actually used |  | ||||||
|     override lateinit var zeropage: Zeropage    // not actually used |  | ||||||
|     override lateinit var golden: GoldenRam     // not actually used |  | ||||||
|  |  | ||||||
|     override fun getFloatAsmBytes(num: Number): String { |  | ||||||
|         // little endian binary representation |  | ||||||
|         val bits = num.toDouble().toBits().toULong() |  | ||||||
|         val hexStr = bits.toString(16).padStart(16, '0') |  | ||||||
|         val parts = hexStr.chunked(2).map { "\$" + it } |  | ||||||
|         return parts.joinToString(", ") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun convertFloatToBytes(num: Double): List<UByte> { |  | ||||||
|         val bits = num.toBits().toULong() |  | ||||||
|         val hexStr = bits.toString(16).padStart(16, '0') |  | ||||||
|         val parts = hexStr.chunked(2).map { it.toInt(16).toUByte() } |  | ||||||
|         return parts |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun convertBytesToFloat(bytes: List<UByte>): Double { |  | ||||||
|         require(bytes.size==8) { "need 8 bytes" } |  | ||||||
|         val b0 = bytes[0].toLong() shl (8*7) |  | ||||||
|         val b1 = bytes[1].toLong() shl (8*6) |  | ||||||
|         val b2 = bytes[2].toLong() shl (8*5) |  | ||||||
|         val b3 = bytes[3].toLong() shl (8*4) |  | ||||||
|         val b4 = bytes[4].toLong() shl (8*3) |  | ||||||
|         val b5 = bytes[5].toLong() shl (8*2) |  | ||||||
|         val b6 = bytes[6].toLong() shl (8*1) |  | ||||||
|         val b7 = bytes[7].toLong() shl (8*0) |  | ||||||
|         return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) { |  | ||||||
|         println("\nStarting Virtual Machine...") |  | ||||||
|         // to not have external module dependencies in our own module, we launch the virtual machine via reflection |  | ||||||
|         val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner |  | ||||||
|         val filename = programNameWithPath.name |  | ||||||
|         if(programNameWithPath.isReadable()) { |  | ||||||
|             vm.runProgram(programNameWithPath.readText()) |  | ||||||
|         } else { |  | ||||||
|             val withExt = programNameWithPath.resolveSibling("$filename.p8ir") |  | ||||||
|             if(withExt.isReadable()) |  | ||||||
|                 vm.runProgram(withExt.readText()) |  | ||||||
|             else |  | ||||||
|                 throw NoSuchFileException(withExt.toFile(), reason="not a .p8ir file") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun isIOAddress(address: UInt): Boolean = false |  | ||||||
|  |  | ||||||
|     override fun initializeMemoryAreas(compilerOptions: CompilationOptions) { |  | ||||||
|         zeropage = VirtualZeropage(compilerOptions) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface IVirtualMachineRunner { |  | ||||||
|     fun runProgram(irSource: String) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private class VirtualZeropage(options: CompilationOptions): Zeropage(options) { |  | ||||||
|     override val SCRATCH_B1: UInt |  | ||||||
|         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") |  | ||||||
|     override val SCRATCH_REG: UInt |  | ||||||
|         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") |  | ||||||
|     override val SCRATCH_W1: UInt |  | ||||||
|         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") |  | ||||||
|     override val SCRATCH_W2: UInt |  | ||||||
|         get() = throw IllegalStateException("virtual shouldn't use this zeropage variable") |  | ||||||
|  |  | ||||||
|     override fun allocateCx16VirtualRegisters() { /* there is no actual zero page in this target to allocate thing in */ } |  | ||||||
| } |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package prog8.code.target.c128 | package prog8.code.target.zp | ||||||
| 
 | 
 | ||||||
| import prog8.code.core.CompilationOptions | import prog8.code.core.CompilationOptions | ||||||
| import prog8.code.core.InternalCompilerException | import prog8.code.core.InternalCompilerException | ||||||
| @@ -11,17 +11,12 @@ import prog8.code.core.ZeropageType | |||||||
| class C128Zeropage(options: CompilationOptions) : Zeropage(options) { | class C128Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||||
| 
 | 
 | ||||||
|     override val SCRATCH_B1 = 0x74u      // temp storage for a single byte |     override val SCRATCH_B1 = 0x74u      // temp storage for a single byte | ||||||
|     override val SCRATCH_REG = 0x75u     // temp storage for a register, must be B1+1 |     override val SCRATCH_REG = 0x75u     // temp storage for a register byte, must be B1+1 | ||||||
|     override val SCRATCH_W1 = 0xfbu      // temp storage 1 for a word  $fb+$fc |     override val SCRATCH_W1 = 0xfbu      // temp storage 1 for a word  $fb+$fc | ||||||
|     override val SCRATCH_W2 = 0xfdu      // temp storage 2 for a word  $fd+$fe |     override val SCRATCH_W2 = 0xfdu      // temp storage 2 for a word  $fd+$fe | ||||||
|  |     override val SCRATCH_PTR = 0x0bu     // temp storage for a pointer $0b+$0c | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
|         if (options.floats) { |  | ||||||
|             throw InternalCompilerException("C128 target doesn't yet support floating point routines") |  | ||||||
|             // note: in git commit labeled 'c128: remove floats module' the floats.p8 and floats.asm files are removed, |  | ||||||
|             //       they could be retrieved again at a later time if the compiler somehow *does* store the fp variables in bank1. |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (options.floats && options.zeropage !in arrayOf( |         if (options.floats && options.zeropage !in arrayOf( | ||||||
|                 ZeropageType.FLOATSAFE, |                 ZeropageType.FLOATSAFE, | ||||||
|                 ZeropageType.BASICSAFE, |                 ZeropageType.BASICSAFE, | ||||||
| @@ -33,18 +28,18 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
|             ZeropageType.FULL -> { |             ZeropageType.FULL -> { | ||||||
|                 // $00/$01 are data port IO registers, // $02-$09 are storage locations for JSRFAR and such |                 // $00/$01 are data port IO registers, // $02-$09 are storage locations for JSRFAR and such | ||||||
|                 free.addAll(0x0au..0xffu) |                 free.addAll(0x0au..0xffu) | ||||||
|                 free.removeAll(listOf(0x90u, 0x91u, 0xa0u, 0xa1u, 0xa2u, 0xc0u, 0xccu, 0xcdu, 0xd0u, 0xd1u, 0xd2u, 0xd3u, 0xd4u, 0xd5u, 0xf7u))        // these are updated/used by IRQ |                 free.removeAll(arrayOf(0x90u, 0x91u, 0xa0u, 0xa1u, 0xa2u, 0xc0u, 0xccu, 0xcdu, 0xd0u, 0xd1u, 0xd2u, 0xd3u, 0xd4u, 0xd5u, 0xf7u))        // these are updated/used by IRQ | ||||||
|             } |             } | ||||||
|             ZeropageType.KERNALSAFE -> { |             ZeropageType.KERNALSAFE -> { | ||||||
|                 free.addAll(0x0au..0x8fu)       // BASIC variables |                 free.addAll(0x0au..0x8fu)       // BASIC variables | ||||||
|                 free.addAll(listOf(0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu, |                 free.addAll(arrayOf(0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu, | ||||||
|                     0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u)) |                     0xb0u, 0xb1u, 0xb4u, 0xb5u, 0xb6u)) | ||||||
|             } |             } | ||||||
|             ZeropageType.FLOATSAFE, |             ZeropageType.FLOATSAFE, | ||||||
|             ZeropageType.BASICSAFE -> { |             ZeropageType.BASICSAFE -> { | ||||||
|                 free.addAll(listOf(0x0bu, 0x0cu, 0x0du, 0x0eu, 0x0fu, 0x10u, 0x11u, 0x12u, 0x16u, 0x17u, 0x18u, 0x19u, 0x1au)) |                 free.addAll(arrayOf(0x0bu, 0x0cu, 0x0du, 0x0eu, 0x0fu, 0x10u, 0x11u, 0x12u, 0x16u, 0x17u, 0x18u, 0x19u, 0x1au)) | ||||||
|                 free.addAll(0x1bu..0x23u) |                 free.addAll(0x1bu..0x23u) | ||||||
|                 free.addAll(listOf(0x3fu, 0x40u, 0x41u, 0x42u, 0x43u, 0x44u, 0x47u, 0x48u, 0x49u, 0x4au, 0x4bu, 0x4cu, 0x4fu, |                 free.addAll(arrayOf(0x3fu, 0x40u, 0x41u, 0x42u, 0x43u, 0x44u, 0x47u, 0x48u, 0x49u, 0x4au, 0x4bu, 0x4cu, 0x4fu, | ||||||
|                     0x55u, 0x56u, 0x57u, 0x58u, |                     0x55u, 0x56u, 0x57u, 0x58u, | ||||||
|                     0x74u, 0x75u, 0x78u, 0x80u, 0x83u, 0x87u, 0x88u, 0x89u, 0x8au, 0x8bu, 0x8cu, 0x8du, 0x8eu, 0x8fu, |                     0x74u, 0x75u, 0x78u, 0x80u, 0x83u, 0x87u, 0x88u, 0x89u, 0x8au, 0x8bu, 0x8cu, 0x8du, 0x8eu, 0x8fu, | ||||||
|                     0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu, |                     0x92u, 0x96u, 0x9bu, 0x9cu, 0x9eu, 0x9fu, 0xa4u, 0xa7u, 0xa8u, 0xa9u, 0xaau, 0xabu, | ||||||
| @@ -53,7 +48,7 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
| 
 | 
 | ||||||
|                 // if(options.zeropage==ZeropageType.BASICSAFE) { |                 // if(options.zeropage==ZeropageType.BASICSAFE) { | ||||||
|                     // can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0 |                     // can also clobber the FP locations (unconditionally, because the C128 target doesn't support floating point calculations in prog8 at this time0 | ||||||
|                     free.addAll(listOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu, |                     free.addAll(arrayOf(0x14u, 0x28u, 0x29u, 0x2au, 0x2bu, 0x2cu, | ||||||
|                         0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u, |                         0x50u, 0x51u, 0x52u, 0x53u, 0x54u, 0x59u, 0x5au, 0x5bu, 0x5cu, 0x5du, 0x5eu, 0x5fu, 0x60u, 0x61u, 0x62u, | ||||||
|                         0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u, |                         0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u, | ||||||
|                         0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u)) |                         0x6au, 0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x71u)) | ||||||
| @@ -71,8 +66,4 @@ class C128Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
|         removeReservedFromFreePool() |         removeReservedFromFreePool() | ||||||
|         retainAllowed() |         retainAllowed() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     override fun allocateCx16VirtualRegisters() { |  | ||||||
|         TODO("Not known if C128 can put the virtual regs in ZP") |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package prog8.code.target.c64 | package prog8.code.target.zp | ||||||
| 
 | 
 | ||||||
| import prog8.code.core.* | import prog8.code.core.* | ||||||
| 
 | 
 | ||||||
| @@ -6,9 +6,10 @@ import prog8.code.core.* | |||||||
| class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||||
| 
 | 
 | ||||||
|     override val SCRATCH_B1 = 0x02u      // temp storage for a single byte |     override val SCRATCH_B1 = 0x02u      // temp storage for a single byte | ||||||
|     override val SCRATCH_REG = 0x03u     // temp storage for a register, must be B1+1 |     override val SCRATCH_REG = 0x03u     // temp storage for a register byte, must be B1+1 | ||||||
|     override val SCRATCH_W1 = 0xfbu      // temp storage 1 for a word  $fb+$fc |     override val SCRATCH_W1 = 0xfbu      // temp storage 1 for a word  $fb+$fc | ||||||
|     override val SCRATCH_W2 = 0xfdu      // temp storage 2 for a word  $fd+$fe |     override val SCRATCH_W2 = 0xfdu      // temp storage 2 for a word  $fd+$fe | ||||||
|  |     override val SCRATCH_PTR = 0x9bu     // temp storage for a pointer $9b+$9c | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
| @@ -21,11 +22,10 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
| 
 | 
 | ||||||
|         if (options.zeropage == ZeropageType.FULL) { |         if (options.zeropage == ZeropageType.FULL) { | ||||||
|             free.addAll(0x02u..0xffu) |             free.addAll(0x02u..0xffu) | ||||||
|             free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1+1u, SCRATCH_W2, SCRATCH_W2+1u)) |             free.removeAll(arrayOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u))        // these are updated by IRQ | ||||||
|             free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u))        // these are updated by IRQ |  | ||||||
|         } else { |         } else { | ||||||
|             if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) { |             if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) { | ||||||
|                 free.addAll(listOf( |                 free.addAll(arrayOf( | ||||||
|                         0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, |                         0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, | ||||||
|                         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, |                         0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, | ||||||
|                         0x20, 0x21, 0x22, 0x23, 0x24, 0x25, |                         0x20, 0x21, 0x22, 0x23, 0x24, 0x25, | ||||||
| @@ -43,7 +43,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
| 
 | 
 | ||||||
|             if (options.zeropage == ZeropageType.FLOATSAFE) { |             if (options.zeropage == ZeropageType.FLOATSAFE) { | ||||||
|                 // remove the zeropage locations used for floating point operations from the free list |                 // remove the zeropage locations used for floating point operations from the free list | ||||||
|                 free.removeAll(listOf( |                 free.removeAll(arrayOf( | ||||||
|                         0x03, 0x04, 0x05, 0x06, 0x10, 0x11, 0x12, |                         0x03, 0x04, 0x05, 0x06, 0x10, 0x11, 0x12, | ||||||
|                         0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, |                         0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, | ||||||
|                         0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, |                         0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, | ||||||
| @@ -56,7 +56,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
|             if(options.zeropage != ZeropageType.DONTUSE) { |             if(options.zeropage != ZeropageType.DONTUSE) { | ||||||
|                 // add the free Zp addresses |                 // add the free Zp addresses | ||||||
|                 // these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O* |                 // these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O* | ||||||
|                 free.addAll(listOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e, |                 free.addAll(arrayOf(0x02, 0x03, 0x04, 0x05, 0x06, 0x0a, 0x0e, | ||||||
|                         0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6, |                         0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa6, | ||||||
|                         0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()}) |                         0xb0, 0xb1, 0xbe, 0xbf, 0xf9).map{it.toUInt()}) | ||||||
|             } else { |             } else { | ||||||
| @@ -79,7 +79,7 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
|         retainAllowed() |         retainAllowed() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun allocateCx16VirtualRegisters() { |     private fun allocateCx16VirtualRegisters() { | ||||||
|         // Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses. |         // Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses. | ||||||
|         // However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well. |         // However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well. | ||||||
|         // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) |         // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package prog8.code.target.cx16 | package prog8.code.target.zp | ||||||
| 
 | 
 | ||||||
| import prog8.code.core.* | import prog8.code.core.* | ||||||
| 
 | 
 | ||||||
| @@ -6,9 +6,10 @@ import prog8.code.core.* | |||||||
| class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { | class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||||
| 
 | 
 | ||||||
|     override val SCRATCH_B1 = 0x7au      // temp storage for a single byte |     override val SCRATCH_B1 = 0x7au      // temp storage for a single byte | ||||||
|     override val SCRATCH_REG = 0x7bu     // temp storage for a register, must be B1+1 |     override val SCRATCH_REG = 0x7bu     // temp storage for a register byte, must be B1+1 | ||||||
|     override val SCRATCH_W1 = 0x7cu      // temp storage 1 for a word  $7c+$7d |     override val SCRATCH_W1 = 0x7cu      // temp storage 1 for a word  $7c+$7d | ||||||
|     override val SCRATCH_W2 = 0x7eu      // temp storage 2 for a word  $7e+$7f |     override val SCRATCH_W2 = 0x7eu      // temp storage 2 for a word  $7e+$7f | ||||||
|  |     override val SCRATCH_PTR = 0x22u     // temp storage for a pointer $22+$23 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
| @@ -52,7 +53,7 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun allocateCx16VirtualRegisters() { |     private fun allocateCx16VirtualRegisters() { | ||||||
|         // Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses. |         // Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses. | ||||||
|         // However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well. |         // However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well. | ||||||
|         // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) |         // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) | ||||||
							
								
								
									
										63
									
								
								codeCore/src/prog8/code/target/zp/ConfigurableZeropage.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								codeCore/src/prog8/code/target/zp/ConfigurableZeropage.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | package prog8.code.target.zp | ||||||
|  |  | ||||||
|  | import prog8.code.core.CompilationOptions | ||||||
|  | import prog8.code.core.DataType | ||||||
|  | import prog8.code.core.Zeropage | ||||||
|  | import prog8.code.core.ZeropageType | ||||||
|  |  | ||||||
|  | class ConfigurableZeropage( | ||||||
|  |     override val SCRATCH_B1: UInt,      // temp storage for a single byte | ||||||
|  |     override val SCRATCH_REG: UInt,     // temp storage for a register byte, must be B1+1 | ||||||
|  |     override val SCRATCH_W1: UInt,      // temp storage 1 for a word | ||||||
|  |     override val SCRATCH_W2: UInt,      // temp storage 2 for a word | ||||||
|  |     override val SCRATCH_PTR: UInt,     // temp storage for a pointer | ||||||
|  |  | ||||||
|  |     val virtualRegistersStart: UInt,        // location of 32 bytes for the r0-r15 virtual registers | ||||||
|  |     basicsafe: List<UIntRange>, | ||||||
|  |     kernalsafe: List<UIntRange>, | ||||||
|  |     fullsafe: List<UIntRange>, | ||||||
|  |     options: CompilationOptions | ||||||
|  | ) : Zeropage(options) { | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         if (options.floats) { | ||||||
|  |             TODO("floats in configurable target zp") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(SCRATCH_REG!=SCRATCH_B1+1u) | ||||||
|  |             throw IllegalArgumentException("Zero page scratch variable REG should be B1+1") | ||||||
|  |  | ||||||
|  |         when (options.zeropage) { | ||||||
|  |             ZeropageType.DONTUSE -> { /* don't use any zeropage at all */ } | ||||||
|  |             ZeropageType.FULL -> fullsafe.forEach { free.addAll(it) } | ||||||
|  |             ZeropageType.BASICSAFE -> basicsafe.forEach { free.addAll(it) } | ||||||
|  |             ZeropageType.KERNALSAFE -> kernalsafe.forEach { free.addAll(it) } | ||||||
|  |             ZeropageType.FLOATSAFE -> TODO("floatsafe in configurable target zp") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val distinctFree = free.distinct() | ||||||
|  |         free.clear() | ||||||
|  |         free.addAll(distinctFree) | ||||||
|  |  | ||||||
|  |         removeReservedFromFreePool() | ||||||
|  |         allocateCx16VirtualRegisters() | ||||||
|  |         retainAllowed() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun allocateCx16VirtualRegisters() { | ||||||
|  |         // Note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses. | ||||||
|  |         // However, to be able for the compiler to "see" them as zeropage variables, we have to register them here as well. | ||||||
|  |         // This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer) | ||||||
|  |         for(reg in 0..15) { | ||||||
|  |             val address = virtualRegistersStart + (2*reg).toUInt() | ||||||
|  |             if(address<=0xffu) { | ||||||
|  |                 allocatedVariables["cx16.r${reg}"]   = VarAllocation(address, DataType.UWORD, 2)       // cx16.r0 .. cx16.r15 | ||||||
|  |                 allocatedVariables["cx16.r${reg}s"]  = VarAllocation(address, DataType.WORD, 2)        // cx16.r0s .. cx16.r15s | ||||||
|  |                 allocatedVariables["cx16.r${reg}L"]  = VarAllocation(address, DataType.UBYTE, 1)       // cx16.r0L .. cx16.r15L | ||||||
|  |                 allocatedVariables["cx16.r${reg}H"]  = VarAllocation(address+1u, DataType.UBYTE, 1)    // cx16.r0H .. cx16.r15H | ||||||
|  |                 allocatedVariables["cx16.r${reg}sL"] = VarAllocation(address, DataType.BYTE, 1)        // cx16.r0sL .. cx16.r15sL | ||||||
|  |                 allocatedVariables["cx16.r${reg}sH"] = VarAllocation(address+1u, DataType.BYTE, 1)     // cx16.r0sH .. cx16.r15sH | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package prog8.code.target.pet | package prog8.code.target.zp | ||||||
| 
 | 
 | ||||||
| import prog8.code.core.CompilationOptions | import prog8.code.core.CompilationOptions | ||||||
| import prog8.code.core.InternalCompilerException | import prog8.code.core.InternalCompilerException | ||||||
| @@ -11,15 +11,12 @@ import prog8.code.core.ZeropageType | |||||||
| class PETZeropage(options: CompilationOptions) : Zeropage(options) { | class PETZeropage(options: CompilationOptions) : Zeropage(options) { | ||||||
| 
 | 
 | ||||||
|     override val SCRATCH_B1 = 0xb3u      // temp storage for a single byte |     override val SCRATCH_B1 = 0xb3u      // temp storage for a single byte | ||||||
|     override val SCRATCH_REG = 0xb4u     // temp storage for a register, must be B1+1 |     override val SCRATCH_REG = 0xb4u     // temp storage for a register byte, must be B1+1 | ||||||
|     override val SCRATCH_W1 = 0xb6u      // temp storage 1 for a word |     override val SCRATCH_W1 = 0xb6u      // temp storage 1 for a word | ||||||
|     override val SCRATCH_W2 = 0xb8u      // temp storage 2 for a word |     override val SCRATCH_W2 = 0xb8u      // temp storage 2 for a word | ||||||
|  |     override val SCRATCH_PTR = 0xb1u     // temp storage for a pointer $b1+$b2 | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
|         if (options.floats) { |  | ||||||
|             throw InternalCompilerException("PET target doesn't yet support floating point routines") |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (options.floats && options.zeropage !in arrayOf( |         if (options.floats && options.zeropage !in arrayOf( | ||||||
|                 ZeropageType.FLOATSAFE, |                 ZeropageType.FLOATSAFE, | ||||||
|                 ZeropageType.BASICSAFE, |                 ZeropageType.BASICSAFE, | ||||||
| @@ -38,7 +35,7 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
|             } |             } | ||||||
|             ZeropageType.FLOATSAFE, |             ZeropageType.FLOATSAFE, | ||||||
|             ZeropageType.BASICSAFE -> { |             ZeropageType.BASICSAFE -> { | ||||||
|                 free.addAll(0xb3u..0xbau)       // TODO more? |                 free.addAll(0xb1u..0xbau)       // TODO more? | ||||||
|             } |             } | ||||||
|             ZeropageType.DONTUSE -> { |             ZeropageType.DONTUSE -> { | ||||||
|                 free.clear()  // don't use zeropage at all |                 free.clear()  // don't use zeropage at all | ||||||
| @@ -52,8 +49,4 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) { | |||||||
|         removeReservedFromFreePool() |         removeReservedFromFreePool() | ||||||
|         retainAllowed() |         retainAllowed() | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     override fun allocateCx16VirtualRegisters() { |  | ||||||
|         TODO("Not known if PET can put the virtual regs in ZP") |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -1,62 +0,0 @@ | |||||||
| plugins { |  | ||||||
|     id 'java' |  | ||||||
|     id 'application' |  | ||||||
|     id "org.jetbrains.kotlin.jvm" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| java { |  | ||||||
|     targetCompatibility = JavaLanguageVersion.of(javaVersion) |  | ||||||
|     sourceCompatibility = JavaLanguageVersion.of(javaVersion) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| compileKotlin { |  | ||||||
|     kotlinOptions { |  | ||||||
|         jvmTarget = javaVersion |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| compileTestKotlin { |  | ||||||
|     kotlinOptions { |  | ||||||
|         jvmTarget = javaVersion |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| dependencies { |  | ||||||
|     implementation project(':codeCore') |  | ||||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" |  | ||||||
|     // implementation "org.jetbrains.kotlin:kotlin-reflect" |  | ||||||
|     implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0" |  | ||||||
|  |  | ||||||
|     testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.1' |  | ||||||
|     testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' |  | ||||||
|     testRuntimeOnly 'org.junit.platform:junit-platform-launcher' |  | ||||||
| } |  | ||||||
|  |  | ||||||
| sourceSets { |  | ||||||
|     main { |  | ||||||
|         java { |  | ||||||
|             srcDir "${project.projectDir}/src" |  | ||||||
|         } |  | ||||||
|         resources { |  | ||||||
|             srcDir "${project.projectDir}/res" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     test { |  | ||||||
|         java { |  | ||||||
|             srcDir "${project.projectDir}/test" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| test { |  | ||||||
|     // Enable JUnit 5 (Gradle 4.6+). |  | ||||||
|     useJUnitPlatform() |  | ||||||
|  |  | ||||||
|     // Always run tests, even when nothing changed. |  | ||||||
|     dependsOn 'cleanTest' |  | ||||||
|  |  | ||||||
|     // Show test results. |  | ||||||
|     testLogging { |  | ||||||
|         events "skipped", "failed" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										43
									
								
								codeGenCpu6502/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								codeGenCpu6502/build.gradle.kts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | plugins { | ||||||
|  |     kotlin("jvm") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | dependencies { | ||||||
|  |     implementation(project(":codeCore")) | ||||||
|  |     implementation(project(":simpleAst")) | ||||||
|  |     // implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") | ||||||
|  |     // implementation "org.jetbrains.kotlin:kotlin-reflect" | ||||||
|  |     implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.1.0") | ||||||
|  |  | ||||||
|  |     testImplementation("io.kotest:kotest-runner-junit5-jvm:5.9.1") | ||||||
|  |     testImplementation("io.kotest:kotest-framework-datatest:5.9.1") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sourceSets { | ||||||
|  |     main { | ||||||
|  |         java { | ||||||
|  |             setSrcDirs(listOf("$projectDir/src")) | ||||||
|  |         } | ||||||
|  |         resources { | ||||||
|  |             setSrcDirs(listOf("$projectDir/res")) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     test { | ||||||
|  |         java { | ||||||
|  |             setSrcDirs(listOf("$projectDir/test")) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tasks.test { | ||||||
|  |     // Enable JUnit 5 (Gradle 4.6+). | ||||||
|  |     useJUnitPlatform() | ||||||
|  |  | ||||||
|  |     // Always run tests, even when nothing changed. | ||||||
|  |     dependsOn("cleanTest") | ||||||
|  |  | ||||||
|  |     // Show test results. | ||||||
|  |     testLogging { | ||||||
|  |         events("skipped", "failed") | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -11,8 +11,9 @@ | |||||||
|     <orderEntry type="sourceFolder" forTests="false" /> |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|     <orderEntry type="library" name="KotlinJavaRuntime" level="project" /> |     <orderEntry type="library" name="KotlinJavaRuntime" level="project" /> | ||||||
|     <orderEntry type="module" module-name="codeCore" /> |     <orderEntry type="module" module-name="codeCore" /> | ||||||
|  |     <orderEntry type="module" module-name="simpleAst" /> | ||||||
|     <orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" /> |     <orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" /> | ||||||
|     <orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" /> |  | ||||||
|     <orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" /> |     <orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" /> | ||||||
|  |     <orderEntry type="library" name="io.kotest.framework.datatest" level="project" /> | ||||||
|   </component> |   </component> | ||||||
| </module> | </module> | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,15 +1,16 @@ | |||||||
| package prog8.codegen.cpu6502 | package prog8.codegen.cpu6502 | ||||||
|  |  | ||||||
|  | import prog8.code.GENERATED_LABEL_PREFIX | ||||||
| import prog8.code.StConstant | import prog8.code.StConstant | ||||||
| import prog8.code.StMemVar | import prog8.code.StMemVar | ||||||
| import prog8.code.SymbolTable | import prog8.code.SymbolTable | ||||||
| import prog8.code.core.IMachineDefinition | import prog8.code.core.ICompilationTarget | ||||||
|  |  | ||||||
|  |  | ||||||
| // note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations | // note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, symbolTable: SymbolTable): Int { | internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable): Int { | ||||||
|  |  | ||||||
|     var numberOfOptimizations = 0 |     var numberOfOptimizations = 0 | ||||||
|  |  | ||||||
| @@ -72,6 +73,13 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin | |||||||
|         numberOfOptimizations++ |         numberOfOptimizations++ | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     mods = optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen) | ||||||
|  |     if(mods.isNotEmpty()) { | ||||||
|  |         apply(mods, lines) | ||||||
|  |         linesByFourteen = getLinesBy(lines, 14) | ||||||
|  |         numberOfOptimizations++ | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return numberOfOptimizations |     return numberOfOptimizations | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -122,7 +130,7 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) = | |||||||
|  |  | ||||||
| private fun optimizeSameAssignments( | private fun optimizeSameAssignments( | ||||||
|     linesByFourteen: Sequence<List<IndexedValue<String>>>, |     linesByFourteen: Sequence<List<IndexedValue<String>>>, | ||||||
|     machine: IMachineDefinition, |     machine: ICompilationTarget, | ||||||
|     symbolTable: SymbolTable |     symbolTable: SymbolTable | ||||||
| ): List<Modification> { | ): List<Modification> { | ||||||
|  |  | ||||||
| @@ -361,15 +369,16 @@ or *_afterif labels. | |||||||
| This gets generated after certain if conditions, and only the branch instruction is needed in these cases. | This gets generated after certain if conditions, and only the branch instruction is needed in these cases. | ||||||
|          */ |          */ | ||||||
|  |  | ||||||
|  |         val autoLabelPrefix = GENERATED_LABEL_PREFIX | ||||||
|         if(first=="beq  +" && second=="lda  #1" && third=="+") { |         if(first=="beq  +" && second=="lda  #1" && third=="+") { | ||||||
|             if((fourth.startsWith("beq  label_") || fourth.startsWith("bne  label_")) && |             if((fourth.startsWith("beq  $autoLabelPrefix") || fourth.startsWith("bne  $autoLabelPrefix")) && | ||||||
|                 (fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) { |                 (fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) { | ||||||
|                 mods.add(Modification(lines[0].index, true, null)) |                 mods.add(Modification(lines[0].index, true, null)) | ||||||
|                 mods.add(Modification(lines[1].index, true, null)) |                 mods.add(Modification(lines[1].index, true, null)) | ||||||
|                 mods.add(Modification(lines[2].index, true, null)) |                 mods.add(Modification(lines[2].index, true, null)) | ||||||
|             } |             } | ||||||
|             else if(fourth.startsWith("label_") && (fourth.endsWith("_shortcut") || fourth.endsWith("_shortcut:"))) { |             else if(fourth.startsWith(autoLabelPrefix) && (fourth.endsWith("_shortcut") || fourth.endsWith("_shortcut:"))) { | ||||||
|                 if((fifth.startsWith("beq  label_") || fifth.startsWith("bne  label_")) && |                 if((fifth.startsWith("beq  $autoLabelPrefix") || fifth.startsWith("bne  $autoLabelPrefix")) && | ||||||
|                     (fifth.endsWith("_shortcut") || fifth.endsWith("_afterif") || fifth.endsWith("_shortcut:") || fifth.endsWith("_afterif:"))) { |                     (fifth.endsWith("_shortcut") || fifth.endsWith("_afterif") || fifth.endsWith("_shortcut:") || fifth.endsWith("_afterif:"))) { | ||||||
|                     mods.add(Modification(lines[0].index, true, null)) |                     mods.add(Modification(lines[0].index, true, null)) | ||||||
|                     mods.add(Modification(lines[1].index, true, null)) |                     mods.add(Modification(lines[1].index, true, null)) | ||||||
| @@ -384,7 +393,7 @@ This gets generated after certain if conditions, and only the branch instruction | |||||||
|  |  | ||||||
| private fun optimizeStoreLoadSame( | private fun optimizeStoreLoadSame( | ||||||
|     linesByFour: Sequence<List<IndexedValue<String>>>, |     linesByFour: Sequence<List<IndexedValue<String>>>, | ||||||
|     machine: IMachineDefinition, |     machine: ICompilationTarget, | ||||||
|     symbolTable: SymbolTable |     symbolTable: SymbolTable | ||||||
| ): List<Modification> { | ): List<Modification> { | ||||||
|     val mods = mutableListOf<Modification>() |     val mods = mutableListOf<Modification>() | ||||||
| @@ -406,7 +415,7 @@ private fun optimizeStoreLoadSame( | |||||||
|                     // a branch instruction follows, we can only remove the load instruction if |                     // a branch instruction follows, we can only remove the load instruction if | ||||||
|                     // another load instruction of the same register precedes the store instruction |                     // another load instruction of the same register precedes the store instruction | ||||||
|                     // (otherwise wrong cpu flags are used) |                     // (otherwise wrong cpu flags are used) | ||||||
|                     val loadinstruction = second.substring(0, 3) |                     val loadinstruction = second.take(3) | ||||||
|                     lines[0].value.trimStart().startsWith(loadinstruction) |                     lines[0].value.trimStart().startsWith(loadinstruction) | ||||||
|                 } |                 } | ||||||
|                 else { |                 else { | ||||||
| @@ -444,20 +453,31 @@ private fun optimizeStoreLoadSame( | |||||||
|  |  | ||||||
|  |  | ||||||
|         // lda X + sta X,  ldy X + sty X,   ldx X + stx X  -> the second instruction can be eliminated |         // lda X + sta X,  ldy X + sty X,   ldx X + stx X  -> the second instruction can be eliminated | ||||||
|         if ((first.startsWith("lda ") && second.startsWith("sta ")) || |         if (first.startsWith("lda ") && second.startsWith("sta ") || | ||||||
|             (first.startsWith("ldx ") && second.startsWith("stx ")) || |             first.startsWith("ldx ") && second.startsWith("stx ") || | ||||||
|             (first.startsWith("ldy ") && second.startsWith("sty ")) |             first.startsWith("ldy ") && second.startsWith("sty ") | ||||||
|         ) { |         ) { | ||||||
|             val firstLoc = first.substring(4).trimStart() |             val firstLoc = first.substring(4).trimStart() | ||||||
|             val secondLoc = second.substring(4).trimStart() |             val secondLoc = second.substring(4).trimStart() | ||||||
|             if (firstLoc == secondLoc) |             if (firstLoc == secondLoc) | ||||||
|                 mods.add(Modification(lines[2].index, true, null)) |                 mods.add(Modification(lines[2].index, true, null)) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         //  all 3 registers:  lda VALUE + sta SOMEWHERE + lda VALUE  -> last load can be eliminated | ||||||
|  |         if (first.startsWith("lda ") && second.startsWith("sta ") && third.startsWith("lda ") || | ||||||
|  |             first.startsWith("ldx ") && second.startsWith("stx ") && third.startsWith("ldx ") || | ||||||
|  |             first.startsWith("ldy ") && second.startsWith("sty ") && third.startsWith("ldy ") | ||||||
|  |         ) { | ||||||
|  |             val firstVal = first.substring(4).trimStart() | ||||||
|  |             val thirdVal = third.substring(4).trimStart() | ||||||
|  |             if (firstVal == thirdVal) | ||||||
|  |                 mods.add(Modification(lines[3].index, true, null)) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|     return mods |     return mods | ||||||
| } | } | ||||||
|  |  | ||||||
| private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""") | private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_.$]*)""") | ||||||
|  |  | ||||||
| private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? { | private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? { | ||||||
|     // try to get the constant value address, could return null if it's a symbol instead |     // try to get the constant value address, could return null if it's a symbol instead | ||||||
| @@ -506,9 +526,11 @@ private fun optimizeIncDec(linesByFour: Sequence<List<IndexedValue<String>>>): L | |||||||
|  |  | ||||||
| private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> { | private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> { | ||||||
|     // jsr Sub + rts -> jmp Sub |     // jsr Sub + rts -> jmp Sub | ||||||
|  |     // jmp Sub + rts -> jmp Sub | ||||||
|     // rts + jmp -> remove jmp |     // rts + jmp -> remove jmp | ||||||
|     // rts + bxx -> remove bxx |     // rts + bxx -> remove bxx | ||||||
|     // lda  + cmp #0 -> remove cmp,  same for cpy and cpx. |     // lda  + cmp #0 -> remove cmp,  same for cpy and cpx. | ||||||
|  |     // bra/jmp + bra/jmp -> remove second bra/jmp   (bra bra / jmp jmp are not removed because this is likely a jump table!) | ||||||
|     // and some other optimizations. |     // and some other optimizations. | ||||||
|  |  | ||||||
|     val mods = mutableListOf<Modification>() |     val mods = mutableListOf<Modification>() | ||||||
| @@ -516,10 +538,17 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe | |||||||
|         val first = lines[0].value |         val first = lines[0].value | ||||||
|         val second = lines[1].value |         val second = lines[1].value | ||||||
|         val third = lines[2].value |         val third = lines[2].value | ||||||
|         if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) { |  | ||||||
|  |         if(!haslabel(second)) { | ||||||
|  |             if ((" jmp" in first || "\tjmp" in first ) && (" rts" in second || "\trts" in second)) { | ||||||
|  |                 mods += Modification(lines[1].index, true, null) | ||||||
|  |             } | ||||||
|  |             else if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) { | ||||||
|  |                 if("floats.pushFAC" !in first && "floats.popFAC" !in first) {       // these 2 routines depend on being called with JSR!! | ||||||
|                     mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp")) |                     mods += Modification(lines[0].index, false, lines[0].value.replace("jsr", "jmp")) | ||||||
|                     mods += Modification(lines[1].index, true, null) |                     mods += Modification(lines[1].index, true, null) | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             else if (" rts" in first || "\trts" in first) { |             else if (" rts" in first || "\trts" in first) { | ||||||
|                 if (" jmp" in second || "\tjmp" in second) |                 if (" jmp" in second || "\tjmp" in second) | ||||||
|                     mods += Modification(lines[1].index, true, null) |                     mods += Modification(lines[1].index, true, null) | ||||||
| @@ -543,7 +572,6 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe | |||||||
|                     mods += Modification(lines[1].index, true, null) |                     mods += Modification(lines[1].index, true, null) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         if (!haslabel(second)) { |  | ||||||
|             if ((" lda" in first || "\tlda" in first) && (" cmp  #0" in second || "\tcmp  #0" in second) || |             if ((" lda" in first || "\tlda" in first) && (" cmp  #0" in second || "\tcmp  #0" in second) || | ||||||
|                 (" ldx" in first || "\tldx" in first) && (" cpx  #0" in second || "\tcpx  #0" in second) || |                 (" ldx" in first || "\tldx" in first) && (" cpx  #0" in second || "\tcpx  #0" in second) || | ||||||
|                 (" ldy" in first || "\tldy" in first) && (" cpy  #0" in second || "\tcpy  #0" in second) |                 (" ldy" in first || "\tldy" in first) && (" cpy  #0" in second || "\tcpy  #0" in second) | ||||||
| @@ -558,6 +586,15 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // only remove bra followed by jmp or jmp followed by bra | ||||||
|  |             // bra bra or jmp jmp is likely part of a jump table, which should keep all entries! | ||||||
|  |             if((" bra" in first || "\tbra" in first) && (" jmp" in second || "\tjmp" in second)) { | ||||||
|  |                 mods.add(Modification(lines[1].index, true, null)) | ||||||
|  |             } | ||||||
|  |             if((" jmp" in first || "\tjmp" in first) && (" bra" in second || "\tbra" in second)) { | ||||||
|  |                 mods.add(Modification(lines[1].index, true, null)) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /* |         /* | ||||||
| @@ -675,33 +712,43 @@ private fun optimizeUselessPushPopStack(linesByFour: Sequence<List<IndexedValue< | |||||||
|         optimize('x', lines) |         optimize('x', lines) | ||||||
|         optimize('y', lines) |         optimize('y', lines) | ||||||
|  |  | ||||||
|         val first = lines[1].value.trimStart() |         val first = lines[0].value.trimStart() | ||||||
|         val second = lines[2].value.trimStart() |         val second = lines[1].value.trimStart() | ||||||
|         val third = lines[3].value.trimStart() |         val third = lines[2].value.trimStart() | ||||||
|  |         val fourth = lines[3].value.trimStart() | ||||||
|  |  | ||||||
|         // phy + ldy + pla -> tya + ldy |         // phy + ldy + pla -> tya + ldy | ||||||
|         // phx + ldx + pla -> txa + ldx |         // phx + ldx + pla -> txa + ldx | ||||||
|         // pha + lda + pla -> nop |         // pha + lda + pla -> nop | ||||||
|         if(first=="phy" && second.startsWith("ldy ") && third=="pla") { |         // pha + tya + tay + pla -> nop | ||||||
|             mods.add(Modification(lines[3].index, true, null)) |         // pha + txa + tax + pla -> nop | ||||||
|             mods.add(Modification(lines[1].index, false, "  tya")) |         when (first) { | ||||||
|  |             "phy" if second.startsWith("ldy ") && third=="pla" -> { | ||||||
|  |                 mods.add(Modification(lines[2].index, true, null)) | ||||||
|  |                 mods.add(Modification(lines[0].index, false, "  tya")) | ||||||
|             } |             } | ||||||
|         else if(first=="phx" && second.startsWith("ldx ") && third=="pla") { |             "phx" if second.startsWith("ldx ") && third=="pla" -> { | ||||||
|             mods.add(Modification(lines[3].index, true, null)) |                 mods.add(Modification(lines[2].index, true, null)) | ||||||
|             mods.add(Modification(lines[1].index, false, "  txa")) |                 mods.add(Modification(lines[0].index, false, "  txa")) | ||||||
|             } |             } | ||||||
|         else if(first=="pha" && second.startsWith("lda ") && third=="pla") { |             "pha" if second.startsWith("lda ") && third=="pla" -> { | ||||||
|  |                 mods.add(Modification(lines[0].index, true, null)) | ||||||
|  |                 mods.add(Modification(lines[1].index, true, null)) | ||||||
|  |                 mods.add(Modification(lines[2].index, true, null)) | ||||||
|  |             } | ||||||
|  |             "pha" if ((second=="tya" && third=="tay") || (second=="txa" && third=="tax")) && fourth=="pla" -> { | ||||||
|  |                 mods.add(Modification(lines[0].index, true, null)) | ||||||
|                 mods.add(Modification(lines[1].index, true, null)) |                 mods.add(Modification(lines[1].index, true, null)) | ||||||
|                 mods.add(Modification(lines[2].index, true, null)) |                 mods.add(Modification(lines[2].index, true, null)) | ||||||
|                 mods.add(Modification(lines[3].index, true, null)) |                 mods.add(Modification(lines[3].index, true, null)) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     return mods |     return mods | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun optimizeTSBtoRegularOr(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> { | private fun optimizeTSBtoRegularOr(linesByFour: Sequence<List<IndexedValue<String>>>): List<Modification> { | ||||||
|     // Asm peephole:   lda var2 / tsb var1 / lda var1  Replace this with this to save 1 cycle:   lda var1 / ora var2 / sta var1 |     // Asm peephole:   lda var2 / tsb var1 / lda var1  Replace this with this to save 1 cycle:   lda var1 / ora var2 / sta var1 | ||||||
|     val mods = mutableListOf<Modification>() |     val mods = mutableListOf<Modification>() | ||||||
| @@ -746,3 +793,120 @@ private fun optimizeUnneededTempvarInAdd(linesByFour: Sequence<List<IndexedValue | |||||||
|  |  | ||||||
|     return mods |     return mods | ||||||
| } | } | ||||||
|  |  | ||||||
|  | private fun optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen: Sequence<List<IndexedValue<String>>>): List<Modification> { | ||||||
|  |     /* | ||||||
|  |         ; FIRST SEQUYENCE: P8ZP_SCRATCH_PTR += AY : | ||||||
|  |         clc | ||||||
|  |         adc  P8ZP_SCRATCH_PTR | ||||||
|  |         pha | ||||||
|  |         tya | ||||||
|  |         adc  P8ZP_SCRATCH_PTR+1 | ||||||
|  |         tay | ||||||
|  |         pla | ||||||
|  |         sta  P8ZP_SCRATCH_PTR | ||||||
|  |         sty  P8ZP_SCRATCH_PTR+1 | ||||||
|  |  | ||||||
|  | 	    -> | ||||||
|  |  | ||||||
|  |         clc | ||||||
|  |     	adc  P8ZP_SCRATCH_PTR | ||||||
|  |     	sta  P8ZP_SCRATCH_PTR | ||||||
|  |     	tya | ||||||
|  |     	adc  P8ZP_SCRATCH_PTR+1 | ||||||
|  |     	sta  P8ZP_SCRATCH_PTR+1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     	also SECOND SEQUENCE: | ||||||
|  |  | ||||||
|  |         ldx  VALUE/  ldy  VALUE | ||||||
|  | 	    sta  SOMEWHERE_WITHOUT_,x_OR_,y | ||||||
|  | 	    txa /   tya | ||||||
|  |     	ldy  #1 | ||||||
|  |     	sta  SOMEWHERE | ||||||
|  |      	 --> | ||||||
|  |     	sta  SOMEWHERE_WITHOUT_,x_OR_,y | ||||||
|  |     	lda  VALUE | ||||||
|  |     	ldy  #1 | ||||||
|  |     	sta  SOMEWHERE | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         also THIRD SEQUENCE: | ||||||
|  |  | ||||||
|  |         ldx  VALUE | ||||||
|  | 	    ldy  #0 | ||||||
|  | 	    sta  SOMEWHERE_WITHOUT_,x | ||||||
|  | 	    txa | ||||||
|  | 	    iny | ||||||
|  | 	    sta  SOMEWHERE | ||||||
|  |          --> | ||||||
|  | 	    ldy  #0 | ||||||
|  | 	    sta  SOMEWHERE_WITHOUT_,x | ||||||
|  | 	    lda  VALUE | ||||||
|  | 	    iny | ||||||
|  | 	    sta  SOMEWHERE | ||||||
|  |     */ | ||||||
|  |     val mods = mutableListOf<Modification>() | ||||||
|  |     for (lines in linesByFourteen) { | ||||||
|  |         val first = lines[0].value.trimStart() | ||||||
|  |         val second = lines[1].value.trimStart() | ||||||
|  |         val third = lines[2].value.trimStart() | ||||||
|  |         val fourth = lines[3].value.trimStart() | ||||||
|  |         val fifth = lines[4].value.trimStart() | ||||||
|  |         val sixth = lines[5].value.trimStart() | ||||||
|  |         val seventh = lines[6].value.trimStart() | ||||||
|  |         val eight = lines[7].value.trimStart() | ||||||
|  |         val ninth = lines[8].value.trimStart() | ||||||
|  |  | ||||||
|  |         // FIRST SEQUENCE | ||||||
|  |         if(first=="clc" && second.startsWith("adc") && third=="pha" && fourth=="tya" && | ||||||
|  |             fifth.startsWith("adc") && sixth=="tay" && seventh=="pla" && eight.startsWith("sta") && ninth.startsWith("sty")) { | ||||||
|  |             val var2 = second.substring(4) | ||||||
|  |             val var5 = fifth.substring(4).substringBefore('+') | ||||||
|  |             val var8 = eight.substring(4) | ||||||
|  |             val var9 = ninth.substring(4).substringBefore('+') | ||||||
|  |             if(var2==var5 && var2==var8 && var2==var9) { | ||||||
|  |                 if(fifth.endsWith("$var5+1") && ninth.endsWith("$var9+1")) { | ||||||
|  |                     mods.add(Modification(lines[2].index, false, "  sta  $var2")) | ||||||
|  |                     mods.add(Modification(lines[5].index, false, "  sta  $var2+1")) | ||||||
|  |                     mods.add(Modification(lines[6].index, true, null)) | ||||||
|  |                     mods.add(Modification(lines[7].index, true, null)) | ||||||
|  |                     mods.add(Modification(lines[8].index, true, null)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // SECOND SEQUENCE | ||||||
|  |         if(first.startsWith("ldx ") && second.startsWith("sta ") && | ||||||
|  |             third=="txa" && fourth.startsWith("ldy ") && fifth.startsWith("sta ") | ||||||
|  |         ) { | ||||||
|  |             if(",x" !in second) { | ||||||
|  |                 val value = first.substring(4) | ||||||
|  |                 mods.add(Modification(lines[0].index, true, null)) | ||||||
|  |                 mods.add(Modification(lines[2].index, false, "  lda  $value")) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(first.startsWith("ldy ") && second.startsWith("sta ") && | ||||||
|  |             third=="tya" && fourth.startsWith("ldy ") && fifth.startsWith("sta ") | ||||||
|  |         ) { | ||||||
|  |             if(",y" !in second) { | ||||||
|  |                 val value = first.substring(4) | ||||||
|  |                 mods.add(Modification(lines[0].index, true, null)) | ||||||
|  |                 mods.add(Modification(lines[2].index, false, "  lda  $value")) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // THIRD SEQUENCE | ||||||
|  |         if(first.startsWith("ldx ") && second.startsWith("ldy ") && third.startsWith("sta ") && | ||||||
|  |             fourth=="txa" && fifth=="iny" && sixth.startsWith("sta ") | ||||||
|  |         ) { | ||||||
|  |             if(",x" !in third) { | ||||||
|  |                 val value = first.substring(4) | ||||||
|  |                 mods.add(Modification(lines[0].index, true, null)) | ||||||
|  |                 mods.add(Modification(lines[3].index, false, "  lda  $value")) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |     return mods | ||||||
|  | } | ||||||
| @@ -11,18 +11,22 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> { | |||||||
|     //  1) cx16 virtual word registers, |     //  1) cx16 virtual word registers, | ||||||
|     //  2) paired CPU registers, |     //  2) paired CPU registers, | ||||||
|     //  3) single CPU registers (order Y,X,A), |     //  3) single CPU registers (order Y,X,A), | ||||||
|     //  4) CPU Carry status flag |     //  4) floating point registers (FAC1, FAC2), | ||||||
|  |     //  5) CPU Carry status flag | ||||||
|     val args = sub.parameters.withIndex() |     val args = sub.parameters.withIndex() | ||||||
|     val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters } |     val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters } | ||||||
|     val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY) |     val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY) | ||||||
|     val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters } |     val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters } | ||||||
|     val (singleRegs, rest) = args3.partition { it.value.first.registerOrPair != null } |     val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null } | ||||||
|  |     val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2  } | ||||||
|  |  | ||||||
|     cx16regs.forEach { order += it.index } |     cx16regs.forEach { order += it.index } | ||||||
|     pairedRegs.forEach { order += it.index } |     pairedRegs.forEach { order += it.index } | ||||||
|     singleRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index } |     singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index } | ||||||
|     require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null}) |     require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null}) | ||||||
|  |     floatRegs.forEach { order += it.index } | ||||||
|     rest.forEach { order += it.index } |     rest.forEach { order += it.index } | ||||||
|     require(order.size==sub.parameters.size) |     require(order.size==sub.parameters.size) | ||||||
|  |  | ||||||
|     return order |     return order | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,14 @@ | |||||||
| package prog8.codegen.cpu6502 | package prog8.codegen.cpu6502 | ||||||
|  |  | ||||||
| import prog8.code.core.* | import prog8.code.GENERATED_LABEL_PREFIX | ||||||
|  | import prog8.code.IAssemblyProgram | ||||||
|  | import prog8.code.core.CompilationOptions | ||||||
|  | import prog8.code.core.ICompilationTarget | ||||||
|  | import prog8.code.core.IErrorReporter | ||||||
|  | import prog8.code.core.OutputType | ||||||
|  | import prog8.code.target.C128Target | ||||||
| import prog8.code.target.C64Target | import prog8.code.target.C64Target | ||||||
|  | import prog8.code.target.PETTarget | ||||||
| import java.nio.file.Path | import java.nio.file.Path | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -21,15 +28,20 @@ internal class AssemblyProgram( | |||||||
|  |  | ||||||
|         val assemblerCommand: List<String> |         val assemblerCommand: List<String> | ||||||
|  |  | ||||||
|         when (compTarget.name) { |         fun addRemainingOptions(command: MutableList<String>, program: Path, assembly: Path): List<String> { | ||||||
|             in setOf("c64", "c128", "cx16", "pet32") -> { |             if(options.compTarget.additionalAssemblerOptions.isNotEmpty()) | ||||||
|  |                 command.addAll(options.compTarget.additionalAssemblerOptions) | ||||||
|  |  | ||||||
|  |             command.addAll(listOf("--output", program.toString(), assembly.toString())) | ||||||
|  |             return command | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         when(options.output) { | ||||||
|  |             OutputType.PRG -> { | ||||||
|                 // CBM machines .prg generation. |                 // CBM machines .prg generation. | ||||||
|  |  | ||||||
|                 // add "-Wlong-branch"  to see warnings about conversion of branch instructions to jumps (default = do this silently) |                 val command = mutableListOf("64tass", "--cbm-prg", "--ascii", "--case-sensitive", "--long-branch", | ||||||
|                 val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", |                     "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") | ||||||
|                     "-Wall", // "-Wno-strict-bool", "-Werror", |  | ||||||
|                     "--dump-labels", "--vice-labels", "--labels=$viceMonListFile" |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|                 if(options.warnSymbolShadowing) |                 if(options.warnSymbolShadowing) | ||||||
|                     command.add("-Wshadow") |                     command.add("-Wshadow") | ||||||
| @@ -40,34 +52,18 @@ internal class AssemblyProgram( | |||||||
|                     command.add("--quiet") |                     command.add("--quiet") | ||||||
|  |  | ||||||
|                 if(options.asmListfile) { |                 if(options.asmListfile) { | ||||||
|                     command.addAll(listOf("--list=$listFile", "--no-monitor")) |                     command.add("--list=$listFile") | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 val outFile = when (options.output) { |                 assemblerCommand = addRemainingOptions(command, prgFile, assemblyFile) | ||||||
|                     OutputType.PRG -> { |                 if(!options.quiet) | ||||||
|                         command.add("--cbm-prg") |  | ||||||
|                     println("\nCreating prg for target ${compTarget.name}.") |                     println("\nCreating prg for target ${compTarget.name}.") | ||||||
|                         prgFile |  | ||||||
|             } |             } | ||||||
|                     OutputType.RAW -> { |             OutputType.XEX -> { | ||||||
|                         command.add("--nostart") |  | ||||||
|                         println("\nCreating raw binary for target ${compTarget.name}.") |  | ||||||
|                         binFile |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("invalid output type") |  | ||||||
|                 } |  | ||||||
|                 command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString())) |  | ||||||
|                 assemblerCommand = command |  | ||||||
|  |  | ||||||
|             } |  | ||||||
|             "atari" -> { |  | ||||||
|                 // Atari800XL .xex generation. |                 // Atari800XL .xex generation. | ||||||
|  |  | ||||||
|                 // TODO are these options okay for atari? |                 val command = mutableListOf("64tass", "--atari-xex", "--case-sensitive", "--long-branch", | ||||||
|                 val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", |                     "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") | ||||||
|                     "-Wall", // "-Werror", "-Wno-strict-bool" |  | ||||||
|                     "--no-monitor" |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|                 if(options.warnSymbolShadowing) |                 if(options.warnSymbolShadowing) | ||||||
|                     command.add("-Wshadow") |                     command.add("-Wshadow") | ||||||
| @@ -80,28 +76,67 @@ internal class AssemblyProgram( | |||||||
|                 if(options.asmListfile) |                 if(options.asmListfile) | ||||||
|                     command.add("--list=$listFile") |                     command.add("--list=$listFile") | ||||||
|  |  | ||||||
|                 val outFile = when (options.output) { |                 assemblerCommand = addRemainingOptions(command,xexFile, assemblyFile) | ||||||
|                     OutputType.XEX -> { |                 if(!options.quiet) | ||||||
|                         command.add("--atari-xex") |  | ||||||
|                     println("\nCreating xex for target ${compTarget.name}.") |                     println("\nCreating xex for target ${compTarget.name}.") | ||||||
|                         xexFile |  | ||||||
|             } |             } | ||||||
|             OutputType.RAW -> { |             OutputType.RAW -> { | ||||||
|                         command.add("--nostart") |                 // Neo6502/headerless raw program generation. | ||||||
|  |                 val command = mutableListOf("64tass", "--nostart", "--case-sensitive", "--long-branch", | ||||||
|  |                     "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") | ||||||
|  |  | ||||||
|  |                 if(options.warnSymbolShadowing) | ||||||
|  |                     command.add("-Wshadow") | ||||||
|  |                 else | ||||||
|  |                     command.add("-Wno-shadow") | ||||||
|  |  | ||||||
|  |                 if(options.asmQuiet) | ||||||
|  |                     command.add("--quiet") | ||||||
|  |  | ||||||
|  |                 if(options.asmListfile) | ||||||
|  |                     command.add("--list=$listFile") | ||||||
|  |  | ||||||
|  |                 assemblerCommand = addRemainingOptions(command, binFile, assemblyFile) | ||||||
|  |                 if(!options.quiet) | ||||||
|                     println("\nCreating raw binary for target ${compTarget.name}.") |                     println("\nCreating raw binary for target ${compTarget.name}.") | ||||||
|                         binFile |  | ||||||
|             } |             } | ||||||
|                     else -> throw AssemblyError("invalid output type") |             OutputType.LIBRARY -> { | ||||||
|                 } |                 // CBM machines library (.bin) generation (with or without 2 byte load address header depending on the compilation target machine) | ||||||
|                 command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString())) |  | ||||||
|                 assemblerCommand = command |                 val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", | ||||||
|             } |                     "-Wall", "-Wno-implied-reg", "--no-monitor", "--dump-labels", "--vice-labels", "--labels=$viceMonListFile") | ||||||
|             else -> throw AssemblyError("invalid compilation target") |  | ||||||
|  |                 if(options.warnSymbolShadowing) | ||||||
|  |                     command.add("-Wshadow") | ||||||
|  |                 else | ||||||
|  |                     command.add("-Wno-shadow") | ||||||
|  |  | ||||||
|  |                 if(options.asmQuiet) | ||||||
|  |                     command.add("--quiet") | ||||||
|  |  | ||||||
|  |                 if(options.asmListfile) | ||||||
|  |                     command.add("--list=$listFile") | ||||||
|  |  | ||||||
|  |                 if(compTarget.name in listOf(C64Target.NAME, C128Target.NAME, PETTarget.NAME)) { | ||||||
|  |                     if(!options.quiet) | ||||||
|  |                         println("\nCreating binary library file with header for target ${compTarget.name}.") | ||||||
|  |                     command.add("--cbm-prg") | ||||||
|  |                 } else { | ||||||
|  |                     if(!options.quiet) | ||||||
|  |                         println("\nCreating binary library file without header for target ${compTarget.name}.") | ||||||
|  |                     command.add("--nostart")       // should be headerless bin, because basic has problems doing a normal LOAD"lib",8,1 - need to use BLOAD | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|         val proc = ProcessBuilder(assemblerCommand).inheritIO().start() |                 assemblerCommand = addRemainingOptions(command, binFile, assemblyFile) | ||||||
|         val result = proc.waitFor() |             } | ||||||
|         if (result == 0 && compTarget.name!="atari") { |         } | ||||||
|  |  | ||||||
|  |         val proc = ProcessBuilder(assemblerCommand) | ||||||
|  |         if(!options.quiet) | ||||||
|  |             proc.inheritIO() | ||||||
|  |         val process = proc.start() | ||||||
|  |         val result = process.waitFor() | ||||||
|  |         if (result == 0) { | ||||||
|             removeGeneratedLabelsFromMonlist() |             removeGeneratedLabelsFromMonlist() | ||||||
|             generateBreakpointList() |             generateBreakpointList() | ||||||
|         } |         } | ||||||
| @@ -109,7 +144,7 @@ internal class AssemblyProgram( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun removeGeneratedLabelsFromMonlist() { |     private fun removeGeneratedLabelsFromMonlist() { | ||||||
|         val pattern = Regex("""al (\w+) \S+prog8_label_.+?""") |         val pattern = Regex("""al (\w+) \S+$GENERATED_LABEL_PREFIX.+?""") | ||||||
|         val lines = viceMonListFile.toFile().readLines() |         val lines = viceMonListFile.toFile().readLines() | ||||||
|         viceMonListFile.toFile().outputStream().bufferedWriter().use { |         viceMonListFile.toFile().outputStream().bufferedWriter().use { | ||||||
|             for (line in lines) { |             for (line in lines) { | ||||||
| @@ -126,7 +161,7 @@ internal class AssemblyProgram( | |||||||
|         for (line in viceMonListFile.toFile().readLines()) { |         for (line in viceMonListFile.toFile().readLines()) { | ||||||
|             val match = pattern.matchEntire(line) |             val match = pattern.matchEntire(line) | ||||||
|             if (match != null) |             if (match != null) | ||||||
|                 breakpoints.add("break \$" + match.groupValues[1]) |                 breakpoints.add("break $" + match.groupValues[1]) | ||||||
|         } |         } | ||||||
|         val num = breakpoints.size |         val num = breakpoints.size | ||||||
|         breakpoints.add(0, "; breakpoint list now follows") |         breakpoints.add(0, "; breakpoint list now follows") | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,40 +0,0 @@ | |||||||
| package prog8.codegen.cpu6502 |  | ||||||
|  |  | ||||||
| import prog8.code.ast.IPtSubroutine |  | ||||||
| import prog8.code.ast.PtAsmSub |  | ||||||
| import prog8.code.ast.PtSub |  | ||||||
| import prog8.code.core.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> { |  | ||||||
|     when(this) { |  | ||||||
|         is PtAsmSub -> { |  | ||||||
|             return returns |  | ||||||
|         } |  | ||||||
|         is PtSub -> { |  | ||||||
|             // for non-asm subroutines, determine the return registers based on the type of the return value |  | ||||||
|             return if(returntype==null) |  | ||||||
|                 emptyList() |  | ||||||
|             else { |  | ||||||
|                 val register = when (returntype!!) { |  | ||||||
|                     in ByteDatatypesWithBoolean -> RegisterOrStatusflag(RegisterOrPair.A, null) |  | ||||||
|                     in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null) |  | ||||||
|                     DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null) |  | ||||||
|                     else -> RegisterOrStatusflag(RegisterOrPair.AY, null) |  | ||||||
|                 } |  | ||||||
|                 listOf(Pair(register, returntype!!)) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun PtSub.returnRegister(): RegisterOrStatusflag? { |  | ||||||
|     return when(returntype) { |  | ||||||
|         in ByteDatatypesWithBoolean -> RegisterOrStatusflag(RegisterOrPair.A, null) |  | ||||||
|         in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null) |  | ||||||
|         DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null) |  | ||||||
|         null -> null |  | ||||||
|         else -> RegisterOrStatusflag(RegisterOrPair.AY, null) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -33,63 +33,100 @@ internal class ForLoopsAsmGen( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) { |     private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) { | ||||||
|         val loopLabel = asmgen.makeLabel("for_loop") |         if(range.step.asConstInteger()!! < -1) { | ||||||
|         val endLabel = asmgen.makeLabel("for_end") |  | ||||||
|         val modifiedLabel = asmgen.makeLabel("for_modified") |  | ||||||
|         val modifiedLabel2 = asmgen.makeLabel("for_modifiedb") |  | ||||||
|         asmgen.loopEndLabels.push(endLabel) |  | ||||||
|         val stepsize=range.step.asConstInteger()!! |  | ||||||
|  |  | ||||||
|         if(stepsize < -1) { |  | ||||||
|             val limit = range.to.asConstInteger() |             val limit = range.to.asConstInteger() | ||||||
|             if(limit==0) |             if(limit==0) | ||||||
|                 throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping") |                 throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping") | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         when(iterableDt) { |         when { | ||||||
|             DataType.ARRAY_B, DataType.ARRAY_UB -> { |             iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range) | ||||||
|                 if (stepsize==1 || stepsize==-1) { |             iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range) | ||||||
|  |             else -> throw AssemblyError("range expression can only be byte or word") | ||||||
|  |         } | ||||||
|  |  | ||||||
|                     // bytes array, step 1 or -1 |         asmgen.loopEndLabels.removeLast() | ||||||
|  |     } | ||||||
|  |  | ||||||
|                     val incdec = if(stepsize==1) "inc" else "dec" |     private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) { | ||||||
|                     // loop over byte range via loopvar |         val stepsize = range.step.asConstInteger()!! | ||||||
|  |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|  |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|  |         asmgen.loopEndLabels.add(endLabel) | ||||||
|         val varname = asmgen.asmVariableName(stmt.variable) |         val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|                     asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt)) |  | ||||||
|                     asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false) |         asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType()) | ||||||
|  |         when (stepsize) { | ||||||
|  |             -1 if range.to.asConstInteger()==0 -> { | ||||||
|  |                 // simple loop downto 0 step -1 | ||||||
|  |                 asmgen.out(loopLabel) | ||||||
|  |                 asmgen.translate(stmt.statements) | ||||||
|  |                 asmgen.out(""" | ||||||
|  |                     dec  $varname | ||||||
|  |                     lda  $varname | ||||||
|  |                     cmp  #255 | ||||||
|  |                     bne  $loopLabel""") | ||||||
|  |             } | ||||||
|  |             -1 if range.to.asConstInteger()==1 -> { | ||||||
|  |                 // simple loop downto 1 step -1 | ||||||
|  |                 asmgen.out(loopLabel) | ||||||
|  |                 asmgen.translate(stmt.statements) | ||||||
|  |                 asmgen.out(""" | ||||||
|  |                     dec  $varname | ||||||
|  |                     bne  $loopLabel""") | ||||||
|  |             } | ||||||
|  |             1, -1 -> forOverBytesRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt) | ||||||
|  |             else -> forOverBytesRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun forOverBytesRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) { | ||||||
|  |         // bytes range, step 1 or -1 | ||||||
|  |  | ||||||
|  |         val stepsize = range.step.asConstInteger()!! | ||||||
|  |         val incdec = if(stepsize==1) "inc" else "dec" | ||||||
|  |  | ||||||
|  |         if(asmgen.options.romable) { | ||||||
|  |             // cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway) | ||||||
|  |             // so we need to store the loop end value in a newly allocated temporary variable | ||||||
|  |             val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range) | ||||||
|  |             asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A) | ||||||
|  |             asmgen.out("  sta  $toValueVar") | ||||||
|             // pre-check for end already reached |             // pre-check for end already reached | ||||||
|                     if(iterableDt==DataType.ARRAY_B) { |             if(iterableDt.isSignedByteArray) { | ||||||
|                         asmgen.out("  sta  $modifiedLabel+1") |                 if(stepsize<0) { | ||||||
|                         if(stepsize<0) |  | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                         clc |                         clc | ||||||
|                         sbc  $varname |                         sbc  $varname | ||||||
|                         bvc  + |                         bvc  + | ||||||
|                                 eor  #${'$'}80 |                         eor  #$80 | ||||||
| +                       bpl  $endLabel""") | +                       bpl  $endLabel""") | ||||||
|                         else |                 } | ||||||
|  |                 else { | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                         sec |                         sec | ||||||
|                         sbc  $varname |                         sbc  $varname | ||||||
|                         bvc  + |                         bvc  + | ||||||
|                                 eor  #${'$'}80 |                         eor  #$80 | ||||||
| +                       bmi  $endLabel""") | +                       bmi  $endLabel""") | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|                         if(stepsize<0) |                 if(stepsize<0) { | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                         cmp  $varname |                         cmp  $varname | ||||||
|                         beq  + |                         beq  + | ||||||
|                         bcs  $endLabel |                         bcs  $endLabel | ||||||
| +""") | +""") | ||||||
|                         else |                 } | ||||||
|  |                 else { | ||||||
|                     asmgen.out("  cmp  $varname  |  bcc  $endLabel") |                     asmgen.out("  cmp  $varname  |  bcc  $endLabel") | ||||||
|                         asmgen.out("  sta  $modifiedLabel+1") |                 } | ||||||
|             } |             } | ||||||
|             asmgen.out(loopLabel) |             asmgen.out(loopLabel) | ||||||
|                     asmgen.translate(stmt.statements) |             asmgen.translate(forloop.statements) | ||||||
|             asmgen.out(""" |             asmgen.out(""" | ||||||
|                 lda  $varname |                 lda  $varname | ||||||
| $modifiedLabel          cmp  #0         ; modified  |                 cmp  $toValueVar | ||||||
|                 beq  $endLabel |                 beq  $endLabel | ||||||
|                 $incdec  $varname""") |                 $incdec  $varname""") | ||||||
|             asmgen.jmp(loopLabel) |             asmgen.jmp(loopLabel) | ||||||
| @@ -97,28 +134,75 @@ $modifiedLabel          cmp  #0         ; modified | |||||||
|  |  | ||||||
|         } else { |         } else { | ||||||
|  |  | ||||||
|                     // bytes, step >= 2 or <= -2 |             // use self-modifying code to store the loop end comparison value | ||||||
|  |             val modifiedLabel = asmgen.makeLabel("for_modified") | ||||||
|                     // loop over byte range via loopvar |             asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A) | ||||||
|                     val varname = asmgen.asmVariableName(stmt.variable) |  | ||||||
|                     asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt)) |  | ||||||
|                     asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A, false) |  | ||||||
|             // pre-check for end already reached |             // pre-check for end already reached | ||||||
|                     if(iterableDt==DataType.ARRAY_B) { |             if(iterableDt.isSignedByteArray) { | ||||||
|  |                 asmgen.out("  sta  $modifiedLabel+1") | ||||||
|  |                 if(stepsize<0) { | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         clc | ||||||
|  |                         sbc  $varname | ||||||
|  |                         bvc  + | ||||||
|  |                         eor  #$80 | ||||||
|  | +                       bpl  $endLabel""") | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         sec | ||||||
|  |                         sbc  $varname | ||||||
|  |                         bvc  + | ||||||
|  |                         eor  #$80 | ||||||
|  | +                       bmi  $endLabel""") | ||||||
|  |             } else { | ||||||
|  |                 if(stepsize<0) { | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         cmp  $varname | ||||||
|  |                         beq  + | ||||||
|  |                         bcs  $endLabel | ||||||
|  | +""") | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     asmgen.out("  cmp  $varname  |  bcc  $endLabel") | ||||||
|  |                 } | ||||||
|  |                 asmgen.out("  sta  $modifiedLabel+1") | ||||||
|  |             } | ||||||
|  |             asmgen.out(loopLabel) | ||||||
|  |             asmgen.translate(forloop.statements) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  | $modifiedLabel  cmp  #0         ; modified  | ||||||
|  |                 beq  $endLabel | ||||||
|  |                 $incdec  $varname""") | ||||||
|  |             asmgen.jmp(loopLabel) | ||||||
|  |             asmgen.out(endLabel) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun forOverBytesRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) { | ||||||
|  |         // bytes range, step >= 2 or <= -2 | ||||||
|  |  | ||||||
|  |         val stepsize = range.step.asConstInteger()!! | ||||||
|  |  | ||||||
|  |         val modifiedLabel = asmgen.makeLabel("for_modified") | ||||||
|  |         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.A) | ||||||
|  |         // pre-check for end already reached | ||||||
|  |         if(iterableDt.isSignedByteArray) { | ||||||
|             asmgen.out("  sta  $modifiedLabel+1") |             asmgen.out("  sta  $modifiedLabel+1") | ||||||
|             if(stepsize<0) |             if(stepsize<0) | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     clc |                     clc | ||||||
|                     sbc  $varname |                     sbc  $varname | ||||||
|                     bvc  + |                     bvc  + | ||||||
|                                 eor  #${'$'}80 |                     eor  #$80 | ||||||
| +                   bpl  $endLabel""") | +                   bpl  $endLabel""") | ||||||
|             else |             else | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     sec |                     sec | ||||||
|                     sbc  $varname |                     sbc  $varname | ||||||
|                     bvc  + |                     bvc  + | ||||||
|                                 eor  #${'$'}80 |                     eor  #$80 | ||||||
| +                   bmi  $endLabel""") | +                   bmi  $endLabel""") | ||||||
|         } else { |         } else { | ||||||
|             if(stepsize<0) |             if(stepsize<0) | ||||||
| @@ -127,12 +211,15 @@ $modifiedLabel          cmp  #0         ; modified | |||||||
|                     beq  + |                     beq  + | ||||||
|                     bcs  $endLabel |                     bcs  $endLabel | ||||||
| +""") | +""") | ||||||
|                         else |             else { | ||||||
|                 asmgen.out("  cmp  $varname  |  bcc  $endLabel") |                 asmgen.out("  cmp  $varname  |  bcc  $endLabel") | ||||||
|  |             } | ||||||
|             asmgen.out("  sta  $modifiedLabel+1") |             asmgen.out("  sta  $modifiedLabel+1") | ||||||
|         } |         } | ||||||
|         asmgen.out(loopLabel) |         asmgen.out(loopLabel) | ||||||
|                     asmgen.translate(stmt.statements) |         asmgen.translate(forloop.statements) | ||||||
|  |  | ||||||
|  |         asmgen.romableError("self-modifying code (forloop over bytes range)", forloop.position)  // TODO fix romable; there is self-modifying code below | ||||||
|         if(stepsize>0) { |         if(stepsize>0) { | ||||||
|             asmgen.out(""" |             asmgen.out(""" | ||||||
|                 lda  $varname |                 lda  $varname | ||||||
| @@ -153,22 +240,103 @@ $modifiedLabel              cmp  #0     ; modified | |||||||
|         } |         } | ||||||
|         asmgen.out(endLabel) |         asmgen.out(endLabel) | ||||||
|     } |     } | ||||||
|             } |  | ||||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { |  | ||||||
|                 when { |  | ||||||
|  |  | ||||||
|                     // words, step 1 or -1 |     private fun forOverNonconstWordRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) { | ||||||
|  |         val stepsize = range.step.asConstInteger()!! | ||||||
|                     stepsize == 1 || stepsize == -1 -> { |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|  |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|  |         asmgen.loopEndLabels.add(endLabel) | ||||||
|         val varname = asmgen.asmVariableName(stmt.variable) |         val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|         assignLoopvarWord(stmt, range) |         assignLoopvarWord(stmt, range) | ||||||
|  |         if(stepsize==-1 && range.to.asConstInteger()==0) { | ||||||
|  |             // simple loop downto 0 step -1 (words) | ||||||
|  |             asmgen.out(loopLabel) | ||||||
|  |             asmgen.translate(stmt.statements) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  |                 bne  ++ | ||||||
|  |                 lda  $varname+1 | ||||||
|  |                 beq  $endLabel | ||||||
|  | +               lda  $varname | ||||||
|  |                 bne  + | ||||||
|  |                 dec  $varname+1 | ||||||
|  | +               dec  $varname""") | ||||||
|  |             asmgen.jmp(loopLabel) | ||||||
|  |             asmgen.out(endLabel) | ||||||
|  |         } | ||||||
|  |         else if (stepsize==-1 && range.to.asConstInteger()==1) { | ||||||
|  |             // simple loop downto 1 step -1 (words) | ||||||
|  |             asmgen.out(loopLabel) | ||||||
|  |             asmgen.translate(stmt.statements) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  |                 cmp  #1 | ||||||
|  |                 bne  + | ||||||
|  |                 lda  $varname+1 | ||||||
|  |                 beq  $endLabel | ||||||
|  | +               lda  $varname | ||||||
|  |                 bne  + | ||||||
|  |                 dec  $varname+1 | ||||||
|  | +               dec  $varname""") | ||||||
|  |             asmgen.jmp(loopLabel) | ||||||
|  |             asmgen.out(endLabel) | ||||||
|  |         } | ||||||
|  |         else if (stepsize == 1 || stepsize == -1) | ||||||
|  |             forOverWordsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt) | ||||||
|  |         else if (stepsize > 0) | ||||||
|  |             forOverWordsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt) | ||||||
|  |         else | ||||||
|  |             forOverWordsRangeStepGreaterOneDescending(range, varname, iterableDt, loopLabel, endLabel, stmt) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun forOverWordsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) { | ||||||
|  |         // words range, step 1 or -1 | ||||||
|  |         val stepsize = range.step.asConstInteger()!! | ||||||
|  |  | ||||||
|  |         if(asmgen.options.romable) { | ||||||
|  |             // cannot use self-modifying code, cannot use cpu stack (because loop can be interrupted halfway) | ||||||
|  |             // so we need to store the loop end value in a newly allocated temporary variable | ||||||
|  |             val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range) | ||||||
|  |             asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||||
|  |             precheckFromToWord(iterableDt, stepsize, varname, endLabel) | ||||||
|  |             asmgen.out("  sta  $toValueVar") | ||||||
|  |             asmgen.out("  sty  $toValueVar+1") | ||||||
|  |             asmgen.out(loopLabel) | ||||||
|  |             asmgen.translate(forloop.statements) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname+1 | ||||||
|  |                 cmp  $toValueVar+1 | ||||||
|  |                 bne  + | ||||||
|  |                 lda  $varname | ||||||
|  |                 cmp  $toValueVar | ||||||
|  |                 beq  $endLabel""") | ||||||
|  |             if(stepsize==1) { | ||||||
|  |                 asmgen.out(""" | ||||||
|  | +                   inc  $varname | ||||||
|  |                     bne  $loopLabel | ||||||
|  |                     inc  $varname+1""") | ||||||
|  |                 asmgen.jmp(loopLabel) | ||||||
|  |             } else { | ||||||
|  |                 asmgen.out(""" | ||||||
|  | +                   lda  $varname | ||||||
|  |                     bne  + | ||||||
|  |                     dec  $varname+1 | ||||||
|  | +                   dec  $varname""") | ||||||
|  |                 asmgen.jmp(loopLabel) | ||||||
|  |             } | ||||||
|  |             asmgen.out(endLabel) | ||||||
|  |  | ||||||
|  |         } else { | ||||||
|  |  | ||||||
|  |             val modifiedLabel = asmgen.makeLabel("for_modified") | ||||||
|  |             val modifiedLabel2 = asmgen.makeLabel("for_modifiedb") | ||||||
|             asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) |             asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||||
|             precheckFromToWord(iterableDt, stepsize, varname, endLabel) |             precheckFromToWord(iterableDt, stepsize, varname, endLabel) | ||||||
|             asmgen.out(""" |             asmgen.out(""" | ||||||
|                 sty  $modifiedLabel+1 |                 sty  $modifiedLabel+1 | ||||||
|                 sta  $modifiedLabel2+1 |                 sta  $modifiedLabel2+1 | ||||||
| $loopLabel""") | $loopLabel""") | ||||||
|                         asmgen.translate(stmt.statements) |             asmgen.translate(forloop.statements) | ||||||
|             asmgen.out(""" |             asmgen.out(""" | ||||||
|                 lda  $varname+1 |                 lda  $varname+1 | ||||||
| $modifiedLabel  cmp  #0    ; modified  | $modifiedLabel  cmp  #0    ; modified  | ||||||
| @@ -192,11 +360,13 @@ $modifiedLabel2 cmp  #0    ; modified | |||||||
|             } |             } | ||||||
|             asmgen.out(endLabel) |             asmgen.out(endLabel) | ||||||
|         } |         } | ||||||
|                     stepsize > 0 -> { |     } | ||||||
|  |  | ||||||
|  |     private fun forOverWordsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, stmt: PtForLoop) { | ||||||
|         // (u)words, step >= 2 |         // (u)words, step >= 2 | ||||||
|                         val varname = asmgen.asmVariableName(stmt.variable) |         val stepsize = range.step.asConstInteger()!! | ||||||
|                         assignLoopvarWord(stmt, range) |         val modifiedLabel = asmgen.makeLabel("for_modified") | ||||||
|  |         val modifiedLabel2 = asmgen.makeLabel("for_modifiedb") | ||||||
|         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) |         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||||
|         precheckFromToWord(iterableDt, stepsize, varname, endLabel) |         precheckFromToWord(iterableDt, stepsize, varname, endLabel) | ||||||
|         asmgen.out(""" |         asmgen.out(""" | ||||||
| @@ -205,7 +375,8 @@ $modifiedLabel2 cmp  #0    ; modified | |||||||
| $loopLabel""") | $loopLabel""") | ||||||
|         asmgen.translate(stmt.statements) |         asmgen.translate(stmt.statements) | ||||||
|  |  | ||||||
|                         if (iterableDt == DataType.ARRAY_UW) { |         asmgen.romableError("self-modifying code (forloop over word range)", stmt.position)  // TODO fix romable; there is self-modifying code below | ||||||
|  |         if (iterableDt.isUnsignedWordArray) { | ||||||
|             asmgen.out(""" |             asmgen.out(""" | ||||||
|                 lda  $varname |                 lda  $varname | ||||||
|                 clc |                 clc | ||||||
| @@ -241,19 +412,20 @@ $modifiedLabel  lda  #0   ; modified | |||||||
| $endLabel""") | $endLabel""") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|                     else -> { |  | ||||||
|  |  | ||||||
|  |     private fun forOverWordsRangeStepGreaterOneDescending(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, stmt: PtForLoop) { | ||||||
|         // (u)words, step <= -2 |         // (u)words, step <= -2 | ||||||
|                         val varname = asmgen.asmVariableName(stmt.variable) |         val stepsize = range.step.asConstInteger()!! | ||||||
|                         assignLoopvarWord(stmt, range) |         val modifiedLabel = asmgen.makeLabel("for_modified") | ||||||
|  |         val modifiedLabel2 = asmgen.makeLabel("for_modifiedb") | ||||||
|         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) |         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||||
|         precheckFromToWord(iterableDt, stepsize, varname, endLabel) |         precheckFromToWord(iterableDt, stepsize, varname, endLabel) | ||||||
|  |         asmgen.romableError("self-modifying code (forloop over words range)", stmt.position)  // TODO fix romable; there is self-modifying code below | ||||||
|         asmgen.out(""" |         asmgen.out(""" | ||||||
|                 sty  $modifiedLabel+1 |                 sty  $modifiedLabel+1 | ||||||
|                 sta  $modifiedLabel2+1 |                 sta  $modifiedLabel2+1 | ||||||
| $loopLabel""") | $loopLabel""") | ||||||
|         asmgen.translate(stmt.statements) |         asmgen.translate(stmt.statements) | ||||||
|  |  | ||||||
|         asmgen.out(""" |         asmgen.out(""" | ||||||
|                 lda  $varname |                 lda  $varname | ||||||
|                 sec |                 sec | ||||||
| @@ -272,18 +444,11 @@ $modifiedLabel  sbc  #0    ; modified | |||||||
| +               bpl  $loopLabel                 | +               bpl  $loopLabel                 | ||||||
| $endLabel""") | $endLabel""") | ||||||
|     } |     } | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("range expression can only be byte or word") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         asmgen.loopEndLabels.pop() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) { |     private fun precheckFromToWord(iterableDt: DataType, stepsize: Int, fromVar: String, endLabel: String) { | ||||||
|         // pre-check for end already reached. |         // pre-check for end already reached. | ||||||
|         // 'to' is in AY, do NOT clobber this! |         // 'to' is in AY, do NOT clobber this! | ||||||
|         if(iterableDt==DataType.ARRAY_W) { |         if(iterableDt.isSignedWordArray) { | ||||||
|             if(stepsize<0) |             if(stepsize<0) | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     sta  P8ZP_SCRATCH_W2        ; to |                     sta  P8ZP_SCRATCH_W2        ; to | ||||||
| @@ -293,7 +458,7 @@ $endLabel""") | |||||||
|                     lda  $fromVar+1 |                     lda  $fromVar+1 | ||||||
|                     sbc  P8ZP_SCRATCH_W2+1 |                     sbc  P8ZP_SCRATCH_W2+1 | ||||||
|                     bvc  + |                     bvc  + | ||||||
| 	                eor  #${'$'}80 |                     eor  #$80 | ||||||
| +                   bmi  $endLabel | +                   bmi  $endLabel | ||||||
|                     lda  P8ZP_SCRATCH_W2 |                     lda  P8ZP_SCRATCH_W2 | ||||||
|                     ldy  P8ZP_SCRATCH_W2+1""") |                     ldy  P8ZP_SCRATCH_W2+1""") | ||||||
| @@ -304,7 +469,7 @@ $endLabel""") | |||||||
|                     tya |                     tya | ||||||
|                     sbc  $fromVar+1 |                     sbc  $fromVar+1 | ||||||
|                     bvc  + |                     bvc  + | ||||||
| 	                eor  #${'$'}80 |                     eor  #$80 | ||||||
| +                   bmi  $endLabel | +                   bmi  $endLabel | ||||||
|                     lda  P8ZP_SCRATCH_REG""") |                     lda  P8ZP_SCRATCH_REG""") | ||||||
|         } else { |         } else { | ||||||
| @@ -333,40 +498,58 @@ $endLabel""") | |||||||
|     private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) { |     private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) { | ||||||
|         val loopLabel = asmgen.makeLabel("for_loop") |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|         val endLabel = asmgen.makeLabel("for_end") |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|         asmgen.loopEndLabels.push(endLabel) |         asmgen.loopEndLabels.add(endLabel) | ||||||
|         val iterableName = asmgen.asmVariableName(ident) |         val iterableName = asmgen.asmVariableName(ident) | ||||||
|         val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) { |         val numElements: UInt = when(val symbol = asmgen.symbolTable.lookup(ident.name)) { | ||||||
|             is StStaticVariable -> symbol.length!! |             is StStaticVariable -> symbol.length!! | ||||||
|             is StMemVar -> symbol.length!! |             is StMemVar -> symbol.length!! | ||||||
|             else -> 0 |             else -> 0u | ||||||
|         } |         } | ||||||
|         when(iterableDt) { |         when { | ||||||
|             DataType.STR -> { |             iterableDt.isString -> { | ||||||
|  |                 if(asmgen.options.romable) { | ||||||
|  |                     val indexVar = asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt) | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                     lda  #<$iterableName |                         ldy  #0 | ||||||
|                     ldy  #>$iterableName |                         sty  $indexVar | ||||||
|                     sta  $loopLabel+1 | $loopLabel              lda  $iterableName,y | ||||||
|                     sty  $loopLabel+2 |  | ||||||
| $loopLabel          lda  ${65535.toHex()}       ; modified |  | ||||||
|                         beq  $endLabel |                         beq  $endLabel | ||||||
|                         sta  ${asmgen.asmVariableName(stmt.variable)}""") |                         sta  ${asmgen.asmVariableName(stmt.variable)}""") | ||||||
|                     asmgen.translate(stmt.statements) |                     asmgen.translate(stmt.statements) | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                     inc  $loopLabel+1 |                         inc  $indexVar | ||||||
|                     bne  $loopLabel |                         ldy  $indexVar | ||||||
|                     inc  $loopLabel+2 |  | ||||||
|                         bne  $loopLabel |                         bne  $loopLabel | ||||||
| $endLabel""") | $endLabel""") | ||||||
|             } |                 } else { | ||||||
|             DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_BOOL -> { |  | ||||||
|                     val indexVar = asmgen.makeLabel("for_index") |                     val indexVar = asmgen.makeLabel("for_index") | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                         ldy  #0 |                         ldy  #0 | ||||||
|  |                         sty  $indexVar | ||||||
|  | $loopLabel              lda  $iterableName,y | ||||||
|  |                         beq  $endLabel | ||||||
|  |                         sta  ${asmgen.asmVariableName(stmt.variable)}""") | ||||||
|  |                     asmgen.translate(stmt.statements) | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         inc  $indexVar | ||||||
|  |                         ldy  $indexVar | ||||||
|  |                         bne  $loopLabel | ||||||
|  | $indexVar   .byte  0                         | ||||||
|  | $endLabel""") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             iterableDt.isByteArray || iterableDt.isBoolArray -> { | ||||||
|  |                 val indexVar = if(asmgen.options.romable) | ||||||
|  |                     asmgen.createTempVarReused(iterableDt.elementType().base, false, stmt) | ||||||
|  |                 else | ||||||
|  |                     asmgen.makeLabel("for_index") | ||||||
|  |                 asmgen.out(""" | ||||||
|  |                     ldy  #0 | ||||||
| $loopLabel          sty  $indexVar | $loopLabel          sty  $indexVar | ||||||
|                     lda  $iterableName,y |                     lda  $iterableName,y | ||||||
|                     sta  ${asmgen.asmVariableName(stmt.variable)}""") |                     sta  ${asmgen.asmVariableName(stmt.variable)}""") | ||||||
|                 asmgen.translate(stmt.statements) |                 asmgen.translate(stmt.statements) | ||||||
|                 if(numElements<=255) { |                 if(numElements<=255u) { | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                         ldy  $indexVar |                         ldy  $indexVar | ||||||
|                         iny |                         iny | ||||||
| @@ -381,8 +564,9 @@ $loopLabel          sty  $indexVar | |||||||
|                         bne  $loopLabel |                         bne  $loopLabel | ||||||
|                         beq  $endLabel""") |                         beq  $endLabel""") | ||||||
|                 } |                 } | ||||||
|                 if(numElements>=16) { |                 if(!asmgen.options.romable) { | ||||||
|                     // allocate index var on ZP if possible |                     if(numElements>=16u) { | ||||||
|  |                         // allocate index var on ZP if possible, otherwise inline | ||||||
|                         val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) |                         val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) | ||||||
|                         result.fold( |                         result.fold( | ||||||
|                             success = { (address, _, _)-> asmgen.out("""$indexVar = $address  ; auto zp UBYTE""") }, |                             success = { (address, _, _)-> asmgen.out("""$indexVar = $address  ; auto zp UBYTE""") }, | ||||||
| @@ -391,51 +575,14 @@ $loopLabel          sty  $indexVar | |||||||
|                     } else { |                     } else { | ||||||
|                         asmgen.out("$indexVar    .byte  0") |                         asmgen.out("$indexVar    .byte  0") | ||||||
|                     } |                     } | ||||||
|                 asmgen.out(endLabel) |  | ||||||
|             } |  | ||||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { |  | ||||||
|                 val length = numElements * 2 |  | ||||||
|                 val indexVar = asmgen.makeLabel("for_index") |  | ||||||
|                 val loopvarName = asmgen.asmVariableName(stmt.variable) |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     ldy  #0 |  | ||||||
| $loopLabel          sty  $indexVar |  | ||||||
|                     lda  $iterableName,y |  | ||||||
|                     sta  $loopvarName |  | ||||||
|                     lda  $iterableName+1,y |  | ||||||
|                     sta  $loopvarName+1""") |  | ||||||
|                 asmgen.translate(stmt.statements) |  | ||||||
|                 if(length<=127) { |  | ||||||
|                     asmgen.out(""" |  | ||||||
|                         ldy  $indexVar |  | ||||||
|                         iny |  | ||||||
|                         iny |  | ||||||
|                         cpy  #$length |  | ||||||
|                         beq  $endLabel |  | ||||||
|                         bne  $loopLabel""") |  | ||||||
|                 } else { |  | ||||||
|                     // length is 128 words, 256 bytes |  | ||||||
|                     asmgen.out(""" |  | ||||||
|                         ldy  $indexVar |  | ||||||
|                         iny |  | ||||||
|                         iny |  | ||||||
|                         bne  $loopLabel |  | ||||||
|                         beq  $endLabel""") |  | ||||||
|                 } |  | ||||||
|                 if(length>=16) { |  | ||||||
|                     // allocate index var on ZP if possible |  | ||||||
|                     val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) |  | ||||||
|                     result.fold( |  | ||||||
|                         success = { (address,_,_)-> asmgen.out("""$indexVar = $address  ; auto zp UBYTE""") }, |  | ||||||
|                         failure = { asmgen.out("$indexVar    .byte  0") } |  | ||||||
|                     ) |  | ||||||
|                 } else { |  | ||||||
|                     asmgen.out("$indexVar    .byte  0") |  | ||||||
|                 } |                 } | ||||||
|                 asmgen.out(endLabel) |                 asmgen.out(endLabel) | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_UW_SPLIT, DataType.ARRAY_W_SPLIT -> { |             iterableDt.isSplitWordArray -> { | ||||||
|                 val indexVar = asmgen.makeLabel("for_index") |                 val indexVar = if(asmgen.options.romable) | ||||||
|  |                     asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt) | ||||||
|  |                 else | ||||||
|  |                     asmgen.makeLabel("for_index") | ||||||
|                 val loopvarName = asmgen.asmVariableName(stmt.variable) |                 val loopvarName = asmgen.asmVariableName(stmt.variable) | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     ldy  #0 |                     ldy  #0 | ||||||
| @@ -445,7 +592,7 @@ $loopLabel          sty  $indexVar | |||||||
|                     lda  ${iterableName}_msb,y |                     lda  ${iterableName}_msb,y | ||||||
|                     sta  $loopvarName+1""") |                     sta  $loopvarName+1""") | ||||||
|                 asmgen.translate(stmt.statements) |                 asmgen.translate(stmt.statements) | ||||||
|                 if(numElements<=255) { |                 if(numElements<=255u) { | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                         ldy  $indexVar |                         ldy  $indexVar | ||||||
|                         iny |                         iny | ||||||
| @@ -460,8 +607,9 @@ $loopLabel          sty  $indexVar | |||||||
|                         bne  $loopLabel |                         bne  $loopLabel | ||||||
|                         beq  $endLabel""") |                         beq  $endLabel""") | ||||||
|                 } |                 } | ||||||
|                 if(numElements>=16) { |                 if(!asmgen.options.romable) { | ||||||
|                     // allocate index var on ZP if possible |                     if(numElements>=16u) { | ||||||
|  |                         // allocate index var on ZP if possible, otherwise inline | ||||||
|                         val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) |                         val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) | ||||||
|                         result.fold( |                         result.fold( | ||||||
|                             success = { (address, _, _)-> asmgen.out("""$indexVar = $address  ; auto zp UBYTE""") }, |                             success = { (address, _, _)-> asmgen.out("""$indexVar = $address  ; auto zp UBYTE""") }, | ||||||
| @@ -470,24 +618,72 @@ $loopLabel          sty  $indexVar | |||||||
|                     } else { |                     } else { | ||||||
|                         asmgen.out("$indexVar    .byte  0") |                         asmgen.out("$indexVar    .byte  0") | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|                 asmgen.out(endLabel) |                 asmgen.out(endLabel) | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_F -> { |             iterableDt.isWordArray -> { | ||||||
|  |                 val indexVar = if(asmgen.options.romable) | ||||||
|  |                     asmgen.createTempVarReused(BaseDataType.UBYTE, false, stmt) | ||||||
|  |                 else | ||||||
|  |                     asmgen.makeLabel("for_index") | ||||||
|  |                 val loopvarName = asmgen.asmVariableName(stmt.variable) | ||||||
|  |                 asmgen.out(""" | ||||||
|  |                     ldy  #0 | ||||||
|  | $loopLabel          sty  $indexVar | ||||||
|  |                     lda  $iterableName,y | ||||||
|  |                     sta  $loopvarName | ||||||
|  |                     lda  $iterableName+1,y | ||||||
|  |                     sta  $loopvarName+1""") | ||||||
|  |                 asmgen.translate(stmt.statements) | ||||||
|  |                 if(numElements<=127u) { | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         ldy  $indexVar | ||||||
|  |                         iny | ||||||
|  |                         iny | ||||||
|  |                         cpy  #${numElements*2u} | ||||||
|  |                         beq  $endLabel | ||||||
|  |                         bne  $loopLabel""") | ||||||
|  |                 } else { | ||||||
|  |                     // array size is 128 words, 256 bytes | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         ldy  $indexVar | ||||||
|  |                         iny | ||||||
|  |                         iny | ||||||
|  |                         bne  $loopLabel | ||||||
|  |                         beq  $endLabel""") | ||||||
|  |                 } | ||||||
|  |                 if(!asmgen.options.romable) { | ||||||
|  |                     if(numElements>=16u) { | ||||||
|  |                         // allocate index var on ZP if possible, otherwise inline | ||||||
|  |                         val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) | ||||||
|  |                         result.fold( | ||||||
|  |                             success = { (address, _, _)-> asmgen.out("""$indexVar = $address  ; auto zp UBYTE""") }, | ||||||
|  |                             failure = { asmgen.out("$indexVar    .byte  0") } | ||||||
|  |                         ) | ||||||
|  |                     } else { | ||||||
|  |                         asmgen.out("$indexVar    .byte  0") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 asmgen.out(endLabel) | ||||||
|  |             } | ||||||
|  |             iterableDt.isFloatArray -> { | ||||||
|                 throw AssemblyError("for loop with floating point variables is not supported") |                 throw AssemblyError("for loop with floating point variables is not supported") | ||||||
|             } |             } | ||||||
|             else -> throw AssemblyError("can't iterate over $iterableDt") |             else -> throw AssemblyError("can't iterate over $iterableDt") | ||||||
|         } |         } | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.removeLast() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) { |     private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) { | ||||||
|         if (range.isEmpty() || range.step==0) |         if (range.isEmpty() || range.step==0) | ||||||
|             throw AssemblyError("empty range or step 0") |             throw AssemblyError("empty range or step 0") | ||||||
|         if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) { |         if(iterableDt.isByteArray) { | ||||||
|  |             if(range.last==range.first) return translateForSimpleByteRangeAsc(stmt, range) | ||||||
|             if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range) |             if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range) | ||||||
|             if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range) |             if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range, iterableDt.isUnsignedByteArray) | ||||||
|         } |         } | ||||||
|         else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) { |         else if(iterableDt.isWordArray) { | ||||||
|  |             if(range.last==range.first) return translateForSimpleWordRangeAsc(stmt, range) | ||||||
|             if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range) |             if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range) | ||||||
|             if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range) |             if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range) | ||||||
|         } |         } | ||||||
| @@ -495,9 +691,9 @@ $loopLabel          sty  $indexVar | |||||||
|         // not one of the easy cases, generate more complex code... |         // not one of the easy cases, generate more complex code... | ||||||
|         val loopLabel = asmgen.makeLabel("for_loop") |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|         val endLabel = asmgen.makeLabel("for_end") |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|         asmgen.loopEndLabels.push(endLabel) |         asmgen.loopEndLabels.add(endLabel) | ||||||
|         when(iterableDt) { |         when { | ||||||
|             DataType.ARRAY_B, DataType.ARRAY_UB -> { |             iterableDt.isByteArray -> { | ||||||
|                 // loop over byte range via loopvar, step >= 2 or <= -2 |                 // loop over byte range via loopvar, step >= 2 or <= -2 | ||||||
|                 val varname = asmgen.asmVariableName(stmt.variable) |                 val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
| @@ -507,7 +703,7 @@ $loopLabel""") | |||||||
|                 asmgen.translate(stmt.statements) |                 asmgen.translate(stmt.statements) | ||||||
|                 when (range.step) { |                 when (range.step) { | ||||||
|                     0, 1, -1 -> { |                     0, 1, -1 -> { | ||||||
|                         throw AssemblyError("step 0, 1 and -1 should have been handled specifically  $stmt") |                         throw AssemblyError("step 0, 1 and -1 should have been handled specifically  $range ${stmt.position}") | ||||||
|                     } |                     } | ||||||
|                     2 -> { |                     2 -> { | ||||||
|                         if(range.last==255 || range.last==254) { |                         if(range.last==255 || range.last==254) { | ||||||
| @@ -562,7 +758,7 @@ $loopLabel""") | |||||||
|                 } |                 } | ||||||
|                 asmgen.out(endLabel) |                 asmgen.out(endLabel) | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { |             iterableDt.isWordArray && !iterableDt.isSplitWordArray -> { | ||||||
|                 // loop over word range via loopvar, step >= 2 or <= -2 |                 // loop over word range via loopvar, step >= 2 or <= -2 | ||||||
|                 val varname = asmgen.asmVariableName(stmt.variable) |                 val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|                 when (range.step) { |                 when (range.step) { | ||||||
| @@ -601,13 +797,13 @@ $loopLabel""") | |||||||
|             } |             } | ||||||
|             else -> throw AssemblyError("range expression can only be byte or word") |             else -> throw AssemblyError("range expression can only be byte or word") | ||||||
|         } |         } | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.removeLast() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) { |     private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) { | ||||||
|         val loopLabel = asmgen.makeLabel("for_loop") |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|         val endLabel = asmgen.makeLabel("for_end") |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|         asmgen.loopEndLabels.push(endLabel) |         asmgen.loopEndLabels.add(endLabel) | ||||||
|         val varname = asmgen.asmVariableName(stmt.variable) |         val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|         asmgen.out(""" |         asmgen.out(""" | ||||||
|                 lda  #${range.first} |                 lda  #${range.first} | ||||||
| @@ -627,13 +823,11 @@ $endLabel""") | |||||||
|                 bne  $loopLabel |                 bne  $loopLabel | ||||||
| $endLabel""") | $endLabel""") | ||||||
|         } |         } | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.removeLast() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) { |     private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression, unsigned: Boolean) { | ||||||
|         val loopLabel = asmgen.makeLabel("for_loop") |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|         val endLabel = asmgen.makeLabel("for_end") |  | ||||||
|         asmgen.loopEndLabels.push(endLabel) |  | ||||||
|         val varname = asmgen.asmVariableName(stmt.variable) |         val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|         asmgen.out(""" |         asmgen.out(""" | ||||||
|             lda  #${range.first} |             lda  #${range.first} | ||||||
| @@ -642,35 +836,37 @@ $loopLabel""") | |||||||
|         asmgen.translate(stmt.statements) |         asmgen.translate(stmt.statements) | ||||||
|         when (range.last) { |         when (range.last) { | ||||||
|             0 -> { |             0 -> { | ||||||
|  |                 if(!unsigned || range.first<=127) { | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|  |                         dec  $varname | ||||||
|  |                         bpl  $loopLabel""") | ||||||
|  |                 } else { | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         dec  $varname | ||||||
|                         lda  $varname |                         lda  $varname | ||||||
|                     beq  $endLabel |                         cmp  #255 | ||||||
|                     dec  $varname""") |                         bne  $loopLabel""") | ||||||
|                 asmgen.jmp(loopLabel) |                 } | ||||||
|                 asmgen.out(endLabel) |  | ||||||
|             } |             } | ||||||
|             1 -> { |             1 -> { | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     dec  $varname |                     dec  $varname | ||||||
|                     bne  $loopLabel |                     bne  $loopLabel""") | ||||||
| $endLabel""") |  | ||||||
|             } |             } | ||||||
|             else -> { |             else -> { | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     dec  $varname |                     dec  $varname | ||||||
|                     lda  $varname |                     lda  $varname | ||||||
|                     cmp  #${range.last-1} |                     cmp  #${range.last-1} | ||||||
|                     bne  $loopLabel |                     bne  $loopLabel""") | ||||||
| $endLabel""") |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         asmgen.loopEndLabels.pop() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) { |     private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) { | ||||||
|         val loopLabel = asmgen.makeLabel("for_loop") |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|         val endLabel = asmgen.makeLabel("for_end") |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|         asmgen.loopEndLabels.push(endLabel) |         asmgen.loopEndLabels.add(endLabel) | ||||||
|         val varname = asmgen.asmVariableName(stmt.variable) |         val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|         asmgen.out(""" |         asmgen.out(""" | ||||||
|             lda  #<${range.first} |             lda  #<${range.first} | ||||||
| @@ -691,13 +887,13 @@ $loopLabel""") | |||||||
|             inc  $varname+1""") |             inc  $varname+1""") | ||||||
|         asmgen.jmp(loopLabel) |         asmgen.jmp(loopLabel) | ||||||
|         asmgen.out(endLabel) |         asmgen.out(endLabel) | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.removeLast() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) { |     private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) { | ||||||
|         val loopLabel = asmgen.makeLabel("for_loop") |         val loopLabel = asmgen.makeLabel("for_loop") | ||||||
|         val endLabel = asmgen.makeLabel("for_end") |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|         asmgen.loopEndLabels.push(endLabel) |         asmgen.loopEndLabels.add(endLabel) | ||||||
|         val varname = asmgen.asmVariableName(stmt.variable) |         val varname = asmgen.asmVariableName(stmt.variable) | ||||||
|         asmgen.out(""" |         asmgen.out(""" | ||||||
|             lda  #<${range.first} |             lda  #<${range.first} | ||||||
| @@ -706,20 +902,29 @@ $loopLabel""") | |||||||
|             sty  $varname+1 |             sty  $varname+1 | ||||||
| $loopLabel""") | $loopLabel""") | ||||||
|         asmgen.translate(stmt.statements) |         asmgen.translate(stmt.statements) | ||||||
|  |         if(range.last==0) { | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  |                 bne  ++ | ||||||
|  |                 lda  $varname+1 | ||||||
|  |                 beq  $endLabel""") | ||||||
|  |         } else { | ||||||
|             asmgen.out(""" |             asmgen.out(""" | ||||||
|                 lda  $varname |                 lda  $varname | ||||||
|                 cmp  #<${range.last} |                 cmp  #<${range.last} | ||||||
|                 bne  + |                 bne  + | ||||||
|                 lda  $varname+1 |                 lda  $varname+1 | ||||||
|                 cmp  #>${range.last} |                 cmp  #>${range.last} | ||||||
|             beq  $endLabel |                 beq  $endLabel""") | ||||||
|  |         } | ||||||
|  |         asmgen.out(""" | ||||||
| +           lda  $varname | +           lda  $varname | ||||||
|             bne  + |             bne  + | ||||||
|             dec  $varname+1 |             dec  $varname+1 | ||||||
| +           dec  $varname""") | +           dec  $varname""") | ||||||
|         asmgen.jmp(loopLabel) |         asmgen.jmp(loopLabel) | ||||||
|         asmgen.out(endLabel) |         asmgen.out(endLabel) | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.removeLast() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) = |     private fun assignLoopvarWord(stmt: PtForLoop, range: PtRange) = | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package prog8.codegen.cpu6502 | package prog8.codegen.cpu6502 | ||||||
|  |  | ||||||
|  | import prog8.code.StNodeType | ||||||
| import prog8.code.ast.* | import prog8.code.ast.* | ||||||
| import prog8.code.core.* | import prog8.code.core.* | ||||||
| import prog8.codegen.cpu6502.assignment.AsmAssignSource | import prog8.codegen.cpu6502.assignment.AsmAssignSource | ||||||
| @@ -15,9 +16,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|         // just ignore any result values from the function call. |         // just ignore any result values from the function call. | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     internal fun optimizeIntArgsViaRegisters(sub: PtSub) = |     internal fun optimizeIntArgsViaCpuRegisters(params: List<PtSubroutineParameter>): Boolean { | ||||||
|         (sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypesWithBoolean) |         // When the parameter(s) are passed via an explicit register or register pair, | ||||||
|                 || (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypesWithBoolean && sub.parameters[1].type in ByteDatatypesWithBoolean) |         // we consider them NOT to be optimized into (possibly different) CPU registers. | ||||||
|  |         // Just load them in whatever the register spec says. | ||||||
|  |         return when (params.size) { | ||||||
|  |             1 -> params[0].register == null && (params[0].type.isIntegerOrBool || params[0].type.isPointer) | ||||||
|  |             2 -> params[0].type.isByteOrBool && params[1].type.isByteOrBool && params[0].register == null && params[1].register == null | ||||||
|  |             else -> false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     internal fun translateFunctionCall(call: PtFunctionCall) { |     internal fun translateFunctionCall(call: PtFunctionCall) { | ||||||
|         // Output only the code to set up the parameters and perform the actual call |         // Output only the code to set up the parameters and perform the actual call | ||||||
| @@ -25,8 +33,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|         // NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!! |         // NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!! | ||||||
|         //       (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this) |         //       (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this) | ||||||
|  |  | ||||||
|         val symbol = asmgen.symbolTable.lookup(call.name) |         val symbol = asmgen.symbolTable.lookup(call.name)!! | ||||||
|         val sub = symbol?.astNode as IPtSubroutine |         if(symbol.type == StNodeType.LABEL) { | ||||||
|  |             require(call.void) | ||||||
|  |             asmgen.out("  jsr  ${asmgen.asmSymbolName(symbol.scopedNameString)}") | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val sub = symbol.astNode as IPtSubroutine | ||||||
|         val subAsmName = asmgen.asmSymbolName(call.name) |         val subAsmName = asmgen.asmSymbolName(call.name) | ||||||
|  |  | ||||||
|         if(sub is PtAsmSub) { |         if(sub is PtAsmSub) { | ||||||
| @@ -39,28 +53,111 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|                 asmgen.out("  \t; inlined routine follows: ${sub.name}") |                 asmgen.out("  \t; inlined routine follows: ${sub.name}") | ||||||
|                 sub.children.forEach { asmgen.translate(it as PtInlineAssembly) } |                 sub.children.forEach { asmgen.translate(it as PtInlineAssembly) } | ||||||
|                 asmgen.out("  \t; inlined routine end: ${sub.name}") |                 asmgen.out("  \t; inlined routine end: ${sub.name}") | ||||||
|  |             } else { | ||||||
|  |                 val bank = sub.address?.constbank?.toString() | ||||||
|  |                 if(bank==null) { | ||||||
|  |                     val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!) | ||||||
|  |                     if(varbank!=null) { | ||||||
|  |                         if(asmgen.options.romable) | ||||||
|  |                             TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM  ${call.position}") | ||||||
|  |  | ||||||
|  |                         // self-modifying code: set jsrfar bank argument | ||||||
|  |                         when(asmgen.options.compTarget.name) { | ||||||
|  |                             "cx16" -> { | ||||||
|  |                                 // JSRFAR can jump to a banked RAM address as well! | ||||||
|  |                                 asmgen.out(""" | ||||||
|  |                                 php | ||||||
|  |                                 pha | ||||||
|  |                                 lda  $varbank | ||||||
|  |                                 sta  + | ||||||
|  |                                 pla | ||||||
|  |                                 plp | ||||||
|  |                                 jsr  cx16.JSRFAR | ||||||
|  |                                 .word  $subAsmName    ; ${sub.address!!.address.toHex()} | ||||||
|  | +                               .byte  0    ; modified""" | ||||||
|  |                                 ) | ||||||
|  |                             } | ||||||
|  |                             "c64" -> { | ||||||
|  |                                 asmgen.out(""" | ||||||
|  |                                 php | ||||||
|  |                                 pha | ||||||
|  |                                 lda  $varbank | ||||||
|  |                                 sta  + | ||||||
|  |                                 pla | ||||||
|  |                                 plp | ||||||
|  |                                 jsr  c64.x16jsrfar | ||||||
|  |                                 .word  $subAsmName    ; ${sub.address!!.address.toHex()} | ||||||
|  | +                               .byte  0    ; modified""" | ||||||
|  |                                 ) | ||||||
|  |                             } | ||||||
|  |                             "c128" -> { | ||||||
|  |                                 asmgen.out(""" | ||||||
|  |                                 php | ||||||
|  |                                 pha | ||||||
|  |                                 lda  $varbank | ||||||
|  |                                 sta  + | ||||||
|  |                                 pla | ||||||
|  |                                 plp | ||||||
|  |                                 jsr  c128.x16jsrfar | ||||||
|  |                                 .word  $subAsmName    ; ${sub.address!!.address.toHex()} | ||||||
|  | +                               .byte  0    ; modified""" | ||||||
|  |                                 ) | ||||||
|  |                             } | ||||||
|  |                             else -> throw AssemblyError("callfar is not supported on the selected compilation target") | ||||||
|  |                         } | ||||||
|                     } else { |                     } else { | ||||||
|                         asmgen.out("  jsr  $subAsmName") |                         asmgen.out("  jsr  $subAsmName") | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|         else if(sub is PtSub) { |                 else { | ||||||
|             if(optimizeIntArgsViaRegisters(sub)) { |                     when(asmgen.options.compTarget.name) { | ||||||
|                 if(sub.parameters.size==1) { |                         "cx16" -> { | ||||||
|                     val register = if (sub.parameters[0].type in ByteDatatypesWithBoolean) RegisterOrPair.A else RegisterOrPair.AY |                             // JSRFAR can jump to a banked RAM address as well! | ||||||
|                     argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register) |                             asmgen.out(""" | ||||||
|                 } else { |                                 jsr cx16.JSRFAR | ||||||
|                     // 2 byte params, second in Y, first in A |                                 .word  $subAsmName    ; ${sub.address!!.address.toHex()} | ||||||
|                     argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], RegisterOrPair.A) |                                 .byte  $bank""" | ||||||
|                     if(asmgen.needAsaveForExpr(call.args[1])) |                             ) | ||||||
|                         asmgen.out("  pha") |  | ||||||
|                     argumentViaRegister(sub, IndexedValue(1, sub.parameters[1]), call.args[1], RegisterOrPair.Y) |  | ||||||
|                     if(asmgen.needAsaveForExpr(call.args[1])) |  | ||||||
|                         asmgen.out("  pla") |  | ||||||
|                         } |                         } | ||||||
|  |                         "c64" -> { | ||||||
|  |                             asmgen.out(""" | ||||||
|  |                                 jsr  c64.x16jsrfar | ||||||
|  |                                 .word  $subAsmName    ; ${sub.address!!.address.toHex()} | ||||||
|  |                                 .byte  $bank""" | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  |                         "c128" -> { | ||||||
|  |                             asmgen.out(""" | ||||||
|  |                                 jsr  c128.x16jsrfar | ||||||
|  |                                 .word  $subAsmName    ; ${sub.address!!.address.toHex()} | ||||||
|  |                                 .byte  $bank""" | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  |                         else -> throw AssemblyError("callfar is not supported on the selected compilation target") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if(sub is PtSub) { | ||||||
|  |             val parameters = sub.signature.children.filterIsInstance<PtSubroutineParameter>() | ||||||
|  |             if(optimizeIntArgsViaCpuRegisters(parameters)) { | ||||||
|  |                 // Note that if the args fit into cpu registers, we don't concern ourselves here | ||||||
|  |                 // if they should be put into regular subroutine parameter variables, or the R0-R15 register variables. | ||||||
|  |                 // That is now up to the subroutine itself. | ||||||
|  |                 useCpuRegistersForArgs(call.args, sub) | ||||||
|             } else { |             } else { | ||||||
|                 // arguments via variables |                 // arguments via variables | ||||||
|                 for(arg in sub.parameters.withIndex().zip(call.args)) |                 val paramValues = parameters.zip(call.args) | ||||||
|                     argumentViaVariable(sub, arg.first.value, arg.second) |                 val (normalParams, registerParams) = paramValues.partition { (it.first.register == null) } | ||||||
|  |                 if (normalParams.isNotEmpty()) { | ||||||
|  |                     for (p in normalParams) | ||||||
|  |                         argumentViaVariable(sub, p.first, p.second) | ||||||
|  |                 } | ||||||
|  |                 if (registerParams.isNotEmpty()) { | ||||||
|  |                     // the R0-R15 'registers' are not really registers. They're just special variables. | ||||||
|  |                     for (p in registerParams) | ||||||
|  |                         argumentViaVariable(sub, p.first, p.second) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             asmgen.out("  jsr  $subAsmName") |             asmgen.out("  jsr  $subAsmName") | ||||||
|         } |         } | ||||||
| @@ -69,11 +166,45 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|         // remember: dealing with the X register and/or dealing with return values is the responsibility of the caller |         // remember: dealing with the X register and/or dealing with return values is the responsibility of the caller | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun useCpuRegistersForArgs(args: List<PtExpression>, sub: PtSub) { | ||||||
|  |         val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>() | ||||||
|  |         when(params.size) { | ||||||
|  |             1 -> { | ||||||
|  |                 val register = if (params[0].type.isByteOrBool) RegisterOrPair.A else RegisterOrPair.AY | ||||||
|  |                 argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], register) | ||||||
|  |             } | ||||||
|  |             2 -> { | ||||||
|  |                 if(params[0].type.isByteOrBool && params[1].type.isByteOrBool) { | ||||||
|  |                     // 2 byte params, second in Y, first in A | ||||||
|  |                     if(asmgen.needAsaveForExpr(args[0]) && !asmgen.needAsaveForExpr(args[1])) { | ||||||
|  |                         // first 0 then 1 | ||||||
|  |                         argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A) | ||||||
|  |                         argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y) | ||||||
|  |                     } else if(!asmgen.needAsaveForExpr(args[0]) && asmgen.needAsaveForExpr(args[1])) { | ||||||
|  |                         // first 1 then 0 | ||||||
|  |                         argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y) | ||||||
|  |                         argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A) | ||||||
|  |                     } else { | ||||||
|  |                         argumentViaRegister(sub, IndexedValue(0, params[0]), args[0], RegisterOrPair.A) | ||||||
|  |                         if (asmgen.needAsaveForExpr(args[1])) | ||||||
|  |                             asmgen.out("  pha") | ||||||
|  |                         argumentViaRegister(sub, IndexedValue(1, params[1]), args[1], RegisterOrPair.Y) | ||||||
|  |                         if (asmgen.needAsaveForExpr(args[1])) | ||||||
|  |                             asmgen.out("  pla") | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     throw AssemblyError("cannot use registers for word+byte") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else -> throw AssemblyError("cannot use cpu registers for >2 arguments") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean { |     private fun usesOtherRegistersWhileEvaluating(arg: PtExpression): Boolean { | ||||||
|         return when(arg) { |         return when(arg) { | ||||||
|             is PtBuiltinFunctionCall -> { |             is PtBuiltinFunctionCall -> { | ||||||
|                 if (arg.name == "lsb" || arg.name == "msb") |                 if (arg.name in arrayOf("lsb", "msb", "lsw", "msw")) | ||||||
|                     return usesOtherRegistersWhileEvaluating(arg.args[0]) |                     return usesOtherRegistersWhileEvaluating(arg.args[0]) | ||||||
|                 if (arg.name == "mkword") |                 if (arg.name == "mkword") | ||||||
|                     return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) |                     return usesOtherRegistersWhileEvaluating(arg.args[0]) || usesOtherRegistersWhileEvaluating(arg.args[1]) | ||||||
| @@ -82,7 +213,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|             is PtAddressOf -> false |             is PtAddressOf -> false | ||||||
|             is PtIdentifier -> false |             is PtIdentifier -> false | ||||||
|             is PtIrRegister -> false |             is PtIrRegister -> false | ||||||
|             is PtMemoryByte -> true     // TODO might not actually need extra registers if the value has to end up in A |             is PtMemoryByte -> arg.address !is PtNumber && arg.address !is PtIdentifier | ||||||
|             is PtNumber -> false |             is PtNumber -> false | ||||||
|             is PtBool -> false |             is PtBool -> false | ||||||
|             else -> true |             else -> true | ||||||
| @@ -104,9 +235,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|                 val param = sub.parameters[it] |                 val param = sub.parameters[it] | ||||||
|                 val arg = call.args[it] |                 val arg = call.args[it] | ||||||
|                 registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) { |                 registersUsed += if(usesOtherRegistersWhileEvaluating(arg)) { | ||||||
|                     if(!registersUsed.any{it.statusflag!=null || it.registerOrPair in CpuRegisters}) |                     if(!registersUsed.any{r -> r.statusflag!=null || r.registerOrPair in CpuRegisters}) | ||||||
|                         argumentViaRegister(sub, IndexedValue(it, param.second), arg) |                         argumentViaRegister(sub, IndexedValue(it, param.second), arg) | ||||||
|                     else if(registersUsed.any {it.statusflag!=null}) { |                     else if(registersUsed.any { r-> r.statusflag!=null }) { | ||||||
|                         throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}") |                         throw AssemblyError("call argument evaluation problem: can't save cpu statusregister parameter ${call.position}") | ||||||
|                     } |                     } | ||||||
|                     else { |                     else { | ||||||
| @@ -131,9 +262,16 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|         if(!isArgumentTypeCompatible(value.type, parameter.type)) |         if(!isArgumentTypeCompatible(value.type, parameter.type)) | ||||||
|             throw AssemblyError("argument type incompatible") |             throw AssemblyError("argument type incompatible") | ||||||
|  |  | ||||||
|  |         val reg = parameter.register | ||||||
|  |         if(reg!=null) { | ||||||
|  |             require(reg in Cx16VirtualRegisters) { "can only use R0-R15 'registers' here" } | ||||||
|  |             val varName = "cx16.${reg.name.lowercase()}" | ||||||
|  |             asmgen.assignExpressionToVariable(value, varName, parameter.type) | ||||||
|  |         } else { | ||||||
|             val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name) |             val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name) | ||||||
|             asmgen.assignExpressionToVariable(value, varName, parameter.type) |             asmgen.assignExpressionToVariable(value, varName, parameter.type) | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag { |     private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null): RegisterOrStatusflag { | ||||||
|         // pass argument via a register parameter |         // pass argument via a register parameter | ||||||
| @@ -148,7 +286,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|         val register = paramRegister.registerOrPair |         val register = paramRegister.registerOrPair | ||||||
|         val requiredDt = parameter.value.type |         val requiredDt = parameter.value.type | ||||||
|         if(requiredDt!=value.type) { |         if(requiredDt!=value.type) { | ||||||
|             if(value.type largerThan requiredDt) |             if(value.type.largerSizeThan(requiredDt)) | ||||||
|                 throw AssemblyError("can only convert byte values to word param types") |                 throw AssemblyError("can only convert byte values to word param types") | ||||||
|         } |         } | ||||||
|         if (statusflag!=null) { |         if (statusflag!=null) { | ||||||
| @@ -187,25 +325,24 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|         else { |         else { | ||||||
|             // via register or register pair |             // via register or register pair | ||||||
|             register!! |             register!! | ||||||
|             if(requiredDt largerThan value.type) { |             if(requiredDt.largerSizeThan(value.type)) { | ||||||
|                 // we need to sign extend the source, do this via temporary word variable |                 // we need to sign extend the source, do this via temporary word variable | ||||||
|                 asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE) |                 asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE) | ||||||
|                 asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type) |                 asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type.base) | ||||||
|                 asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY) |                 asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY) | ||||||
|             } else { |             } else { | ||||||
|                 val scope = value.definingISub() |                 val scope = value.definingISub() | ||||||
|                 val target: AsmAssignTarget = |                 val target: AsmAssignTarget = | ||||||
|                     if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters)) |                     if(parameter.value.type.isByte && register.isWord()) | ||||||
|                         AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register) |                         AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, parameter.value.type, scope, value.position, register = register) | ||||||
|                     else { |                     else { | ||||||
|                         val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD |                         AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen) | ||||||
|                         AsmAssignTarget.fromRegisters(register, signed, value.position, scope, asmgen) |  | ||||||
|                     } |                     } | ||||||
|                 val src = if(value.type in PassByReferenceDatatypes) { |                 val src = if(value.type.isPassByRef) { | ||||||
|                     if(value is PtIdentifier) { |                     if(value is PtIdentifier) { | ||||||
|                         val addr = PtAddressOf(Position.DUMMY) |                         val addr = PtAddressOf(value.type.typeForAddressOf(false), false, Position.DUMMY) | ||||||
|                         addr.add(value) |                         addr.add(value) | ||||||
|                         addr.parent = sub as PtNode |                         addr.parent = scope as PtNode | ||||||
|                         AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target) |                         AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target) | ||||||
|                     } else { |                     } else { | ||||||
|                         AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) |                         AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) | ||||||
| @@ -213,7 +350,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|                 } else { |                 } else { | ||||||
|                     AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) |                     AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) | ||||||
|                 } |                 } | ||||||
|                 asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), scope) |                 asmgen.translateNormalAssignment(AsmAssignment(src, listOf(target), program.memsizer, target.position), scope) | ||||||
|             } |             } | ||||||
|             return RegisterOrStatusflag(register, null) |             return RegisterOrStatusflag(register, null) | ||||||
|         } |         } | ||||||
| @@ -222,18 +359,18 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as | |||||||
|     private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { |     private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { | ||||||
|         if(argType isAssignableTo paramType) |         if(argType isAssignableTo paramType) | ||||||
|             return true |             return true | ||||||
|         if(argType==DataType.BOOL && paramType==DataType.BOOL) |         if(argType.isBool && paramType.isBool) | ||||||
|             return true |             return true | ||||||
|         if(argType in ByteDatatypes && paramType in ByteDatatypes) |         if(argType.isByte && paramType.isByte) | ||||||
|             return true |             return true | ||||||
|         if(argType in WordDatatypes && paramType in WordDatatypes) |         if(argType.isWord && paramType.isWord) | ||||||
|             return true |             return true | ||||||
|  |  | ||||||
|         // we have a special rule for some types. |         // we have a special rule for some types. | ||||||
|         // strings are assignable to UWORD, for example, and vice versa |         // strings are assignable to UWORD, for example, and vice versa | ||||||
|         if(argType==DataType.STR && paramType==DataType.UWORD) |         if(argType.isString && paramType.isUnsignedWord) | ||||||
|             return true |             return true | ||||||
|         if(argType==DataType.UWORD && paramType == DataType.STR) |         if(argType.isUnsignedWord && paramType.isString) | ||||||
|             return true |             return true | ||||||
|  |  | ||||||
|         return false |         return false | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										427
									
								
								codeGenCpu6502/src/prog8/codegen/cpu6502/IfExpressionAsmGen.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										427
									
								
								codeGenCpu6502/src/prog8/codegen/cpu6502/IfExpressionAsmGen.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,427 @@ | |||||||
|  | package prog8.codegen.cpu6502 | ||||||
|  |  | ||||||
|  | import prog8.code.ast.* | ||||||
|  | import prog8.code.core.* | ||||||
|  | import prog8.codegen.cpu6502.assignment.AsmAssignTarget | ||||||
|  | import prog8.codegen.cpu6502.assignment.AssignmentAsmGen | ||||||
|  | import prog8.codegen.cpu6502.assignment.TargetStorageKind | ||||||
|  |  | ||||||
|  | internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, private val assignmentAsmGen: AssignmentAsmGen, private val errors: IErrorReporter) { | ||||||
|  |  | ||||||
|  |     internal fun assignIfExpression(target: AsmAssignTarget, expr: PtIfExpression) { | ||||||
|  |         require(target.datatype==expr.type || | ||||||
|  |                 target.datatype.isUnsignedWord && (expr.type.isString || expr.type.isPointer) || | ||||||
|  |                 target.datatype.isPointer && (expr.type.isUnsignedWord || expr.type.isPointer || expr.type.isString)) | ||||||
|  |         val falseLabel = asmgen.makeLabel("ifexpr_false") | ||||||
|  |         val endLabel = asmgen.makeLabel("ifexpr_end") | ||||||
|  |         evalIfExpressionConditonAndBranchWhenFalse(expr.condition, falseLabel) | ||||||
|  |         when { | ||||||
|  |             expr.type.isByteOrBool -> { | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A) | ||||||
|  |                 asmgen.jmp(endLabel) | ||||||
|  |                 asmgen.out(falseLabel) | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A) | ||||||
|  |                 asmgen.out(endLabel) | ||||||
|  |                 assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false) | ||||||
|  |             } | ||||||
|  |             expr.type.isWord || expr.type.isString -> { | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY) | ||||||
|  |                 asmgen.jmp(endLabel) | ||||||
|  |                 asmgen.out(falseLabel) | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY) | ||||||
|  |                 asmgen.out(endLabel) | ||||||
|  |                 assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY) | ||||||
|  |             } | ||||||
|  |             expr.type.isFloat -> { | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true) | ||||||
|  |                 asmgen.jmp(endLabel) | ||||||
|  |                 asmgen.out(falseLabel) | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true) | ||||||
|  |                 asmgen.out(endLabel) | ||||||
|  |                 asmgen.assignRegister(RegisterOrPair.FAC1, target) | ||||||
|  |             } | ||||||
|  |             else -> throw AssemblyError("weird dt") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal fun assignBranchCondExpression(target: AsmAssignTarget, expr: PtBranchCondExpression) { | ||||||
|  |         require(target.datatype==expr.type || | ||||||
|  |                 target.datatype.isUnsignedWord && (expr.type.isString || expr.type.isPointer) || | ||||||
|  |                 target.datatype.isPointer && (expr.type.isUnsignedWord || expr.type.isPointer || expr.type.isString)) | ||||||
|  |  | ||||||
|  |         if(target.kind==TargetStorageKind.REGISTER && target.datatype.isUnsignedByte && target.register==RegisterOrPair.A) { | ||||||
|  |             if(expr.condition==BranchCondition.CC) { | ||||||
|  |                 if(expr.truevalue.asConstInteger()==0 && expr.falsevalue.asConstInteger()==1) { | ||||||
|  |                     asmgen.out("  lda  #0 |  rol a") | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 else if(expr.truevalue.asConstInteger()==1 && expr.falsevalue.asConstInteger()==0) { | ||||||
|  |                     asmgen.out("  lda  #0 |  rol a |  eor  #1") | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else if(expr.condition==BranchCondition.CS) { | ||||||
|  |                 if(expr.truevalue.asConstInteger()==0 && expr.falsevalue.asConstInteger()==1) { | ||||||
|  |                     asmgen.out("  lda  #0 |  rol a |  eor  #1") | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 else if(expr.truevalue.asConstInteger()==1 && expr.falsevalue.asConstInteger()==0) { | ||||||
|  |                     asmgen.out("  lda  #0 |  rol a") | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val trueLabel = asmgen.makeLabel("branchexpr_true") | ||||||
|  |         val endLabel = asmgen.makeLabel("branchexpr_end") | ||||||
|  |         val branch = asmgen.branchInstruction(expr.condition, false) | ||||||
|  |  | ||||||
|  |         asmgen.out("  $branch  $trueLabel") | ||||||
|  |  | ||||||
|  |         when { | ||||||
|  |             expr.type.isByteOrBool -> { | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A) | ||||||
|  |                 asmgen.jmp(endLabel) | ||||||
|  |                 asmgen.out(trueLabel) | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A) | ||||||
|  |                 asmgen.out(endLabel) | ||||||
|  |                 assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false) | ||||||
|  |             } | ||||||
|  |             expr.type.isWord || expr.type.isString -> { | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY) | ||||||
|  |                 asmgen.jmp(endLabel) | ||||||
|  |                 asmgen.out(trueLabel) | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY) | ||||||
|  |                 asmgen.out(endLabel) | ||||||
|  |                 assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY) | ||||||
|  |             } | ||||||
|  |             expr.type.isFloat -> { | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true) | ||||||
|  |                 asmgen.jmp(endLabel) | ||||||
|  |                 asmgen.out(trueLabel) | ||||||
|  |                 asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true) | ||||||
|  |                 asmgen.out(endLabel) | ||||||
|  |                 asmgen.assignRegister(RegisterOrPair.FAC1, target) | ||||||
|  |             } | ||||||
|  |             else -> throw AssemblyError("weird dt") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun evalIfExpressionConditonAndBranchWhenFalse(condition: PtExpression, falseLabel: String) { | ||||||
|  |         when (condition) { | ||||||
|  |             is PtBinaryExpression -> { | ||||||
|  |                 val rightDt = condition.right.type | ||||||
|  |                 return when { | ||||||
|  |                     rightDt.isByteOrBool -> translateIfExpressionByteConditionBranch(condition, falseLabel) | ||||||
|  |                     rightDt.isWord -> translateIfExpressionWordConditionBranch(condition, falseLabel) | ||||||
|  |                     rightDt.isFloat -> translateIfExpressionFloatConditionBranch(condition, falseLabel) | ||||||
|  |                     else -> throw AssemblyError("weird dt") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             is PtPrefix if condition.operator=="not" -> { | ||||||
|  |                 throw AssemblyError("not prefix in ifexpression should have been replaced by swapped values") | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             else -> { | ||||||
|  |                 // the condition is "simple" enough to just assign its 0/1 value to a register and branch on that | ||||||
|  |                 asmgen.assignConditionValueToRegisterAndTest(condition) | ||||||
|  |                 asmgen.out("  beq  $falseLabel") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateIfExpressionByteConditionBranch(condition: PtBinaryExpression, falseLabel: String) { | ||||||
|  |         val signed = condition.left.type.isSigned | ||||||
|  |         val constValue = condition.right.asConstInteger() | ||||||
|  |         if(constValue==0) { | ||||||
|  |             return translateIfCompareWithZeroByteBranch(condition, signed, falseLabel) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         when(condition.operator) { | ||||||
|  |             "==" -> { | ||||||
|  |                 // if X==value | ||||||
|  |                 asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed) | ||||||
|  |                 asmgen.cmpAwithByteValue(condition.right, false) | ||||||
|  |                 asmgen.out("  bne  $falseLabel") | ||||||
|  |             } | ||||||
|  |             "!=" -> { | ||||||
|  |                 // if X!=value | ||||||
|  |                 asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed) | ||||||
|  |                 asmgen.cmpAwithByteValue(condition.right, false) | ||||||
|  |                 asmgen.out("  beq  $falseLabel") | ||||||
|  |             } | ||||||
|  |             in LogicalOperators -> { | ||||||
|  |                 val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, condition.definingISub(), condition.position, register=RegisterOrPair.A) | ||||||
|  |                 if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) { | ||||||
|  |                     asmgen.out("  beq  $falseLabel") | ||||||
|  |                 } else { | ||||||
|  |                     errors.warn("SLOW FALLBACK FOR 'IFEXPR' CODEGEN - ask for support", condition.position)      //  should not occur ;-) | ||||||
|  |                     asmgen.assignConditionValueToRegisterAndTest(condition) | ||||||
|  |                     asmgen.out("  beq  $falseLabel") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 // TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does | ||||||
|  |                 // TODO: special cases for <, <=, >, >= above. | ||||||
|  |                 asmgen.assignConditionValueToRegisterAndTest(condition) | ||||||
|  |                 asmgen.out("  beq  $falseLabel") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateIfExpressionWordConditionBranch(condition: PtBinaryExpression, falseLabel: String) { | ||||||
|  |         // TODO can we reuse this whole thing from IfElse ? | ||||||
|  |         val constValue = condition.right.asConstInteger() | ||||||
|  |         if(constValue!=null) { | ||||||
|  |             if (constValue == 0) { | ||||||
|  |                 when (condition.operator) { | ||||||
|  |                     "==" -> return translateWordExprIsZero(condition.left, falseLabel) | ||||||
|  |                     "!=" -> return translateWordExprIsNotZero(condition.left, falseLabel) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (constValue != 0) { | ||||||
|  |                 when (condition.operator) { | ||||||
|  |                     "==" -> return translateWordExprEqualsNumber(condition.left, constValue, falseLabel) | ||||||
|  |                     "!=" -> return translateWordExprNotEqualsNumber(condition.left, constValue, falseLabel) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         val variable = condition.right as? PtIdentifier | ||||||
|  |         if(variable!=null) { | ||||||
|  |             when (condition.operator) { | ||||||
|  |                 "==" -> return translateWordExprEqualsVariable(condition.left, variable, falseLabel) | ||||||
|  |                 "!=" -> return translateWordExprNotEqualsVariable(condition.left, variable, falseLabel) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does | ||||||
|  |         asmgen.assignConditionValueToRegisterAndTest(condition) | ||||||
|  |         asmgen.out("  beq  $falseLabel") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateIfExpressionFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) { | ||||||
|  |         val constValue = (condition.right as? PtNumber)?.number | ||||||
|  |         if(constValue==0.0) { | ||||||
|  |             if (condition.operator == "==") { | ||||||
|  |                 // if FL==0.0 | ||||||
|  |                 asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true) | ||||||
|  |                 asmgen.out("  jsr  floats.SIGN |  cmp  #0 |  bne  $elseLabel") | ||||||
|  |                 return | ||||||
|  |             } else if(condition.operator=="!=") { | ||||||
|  |                 // if FL!=0.0 | ||||||
|  |                 asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true) | ||||||
|  |                 asmgen.out("  jsr  floats.SIGN |  cmp  #0 |  beq  $elseLabel") | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         when(condition.operator) { | ||||||
|  |             "==" -> { | ||||||
|  |                 asmgen.translateFloatsEqualsConditionIntoA(condition.left, condition.right) | ||||||
|  |                 asmgen.out("  beq  $elseLabel") | ||||||
|  |             } | ||||||
|  |             "!=" -> { | ||||||
|  |                 asmgen.translateFloatsEqualsConditionIntoA(condition.left, condition.right) | ||||||
|  |                 asmgen.out("  bne  $elseLabel") | ||||||
|  |             } | ||||||
|  |             "<" -> { | ||||||
|  |                 asmgen.translateFloatsLessConditionIntoA(condition.left, condition.right, false) | ||||||
|  |                 asmgen.out("  beq  $elseLabel") | ||||||
|  |             } | ||||||
|  |             "<=" -> { | ||||||
|  |                 asmgen.translateFloatsLessConditionIntoA(condition.left, condition.right, true) | ||||||
|  |                 asmgen.out("  beq  $elseLabel") | ||||||
|  |             } | ||||||
|  |             ">" -> { | ||||||
|  |                 asmgen.translateFloatsLessConditionIntoA(condition.left, condition.right, true) | ||||||
|  |                 asmgen.out("  bne  $elseLabel") | ||||||
|  |             } | ||||||
|  |             ">=" -> { | ||||||
|  |                 asmgen.translateFloatsLessConditionIntoA(condition.left, condition.right, false) | ||||||
|  |                 asmgen.out("  bne  $elseLabel") | ||||||
|  |             } | ||||||
|  |             else -> throw AssemblyError("expected comparison operator") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateWordExprNotEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) { | ||||||
|  |         // if w!=variable | ||||||
|  |         // TODO reuse code from ifElse? | ||||||
|  |         val varRight = asmgen.asmVariableName(variable) | ||||||
|  |         if(expr is PtIdentifier) { | ||||||
|  |             val varLeft = asmgen.asmVariableName(expr) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varLeft | ||||||
|  |                 cmp  $varRight | ||||||
|  |                 bne  + | ||||||
|  |                 lda  $varLeft+1 | ||||||
|  |                 cmp  $varRight+1 | ||||||
|  |                 beq  $falseLabel | ||||||
|  | +""") | ||||||
|  |         } else { | ||||||
|  |             asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 cmp  $varRight | ||||||
|  |                 bne  + | ||||||
|  |                 cpy  $varRight+1 | ||||||
|  |                 beq  $falseLabel | ||||||
|  | +""") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateWordExprEqualsVariable(expr: PtExpression, variable: PtIdentifier, falseLabel: String) { | ||||||
|  |         // if w==variable | ||||||
|  |         // TODO reuse code from ifElse? | ||||||
|  |         val varRight = asmgen.asmVariableName(variable) | ||||||
|  |         if(expr is PtIdentifier) { | ||||||
|  |             val varLeft = asmgen.asmVariableName(expr) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varLeft | ||||||
|  |                 cmp  $varRight | ||||||
|  |                 bne  $falseLabel | ||||||
|  |                 lda  $varLeft+1 | ||||||
|  |                 cmp  $varRight+1 | ||||||
|  |                 bne  $falseLabel""") | ||||||
|  |         } else { | ||||||
|  |             asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 cmp  $varRight | ||||||
|  |                 bne  $falseLabel | ||||||
|  |                 cpy  $varRight+1 | ||||||
|  |                 bne  $falseLabel""") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateWordExprNotEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) { | ||||||
|  |         // if w!=number | ||||||
|  |         // TODO reuse code from ifElse? | ||||||
|  |         if(expr is PtIdentifier) { | ||||||
|  |             val varname = asmgen.asmVariableName(expr) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  |                 cmp  #<$number | ||||||
|  |                 bne  + | ||||||
|  |                 lda  $varname+1 | ||||||
|  |                 cmp  #>$number | ||||||
|  |                 beq  $falseLabel | ||||||
|  | +""") | ||||||
|  |         } else { | ||||||
|  |             asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 cmp  #<$number | ||||||
|  |                 bne  + | ||||||
|  |                 cpy  #>$number | ||||||
|  |                 beq  $falseLabel | ||||||
|  | +""") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateWordExprEqualsNumber(expr: PtExpression, number: Int, falseLabel: String) { | ||||||
|  |         // if w==number | ||||||
|  |         // TODO reuse code from ifElse? | ||||||
|  |         if(expr is PtIdentifier) { | ||||||
|  |             val varname = asmgen.asmVariableName(expr) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  |                 cmp  #<$number | ||||||
|  |                 bne  $falseLabel | ||||||
|  |                 lda  $varname+1 | ||||||
|  |                 cmp  #>$number | ||||||
|  |                 bne  $falseLabel""") | ||||||
|  |         } else { | ||||||
|  |             asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY) | ||||||
|  |             asmgen.out(                """ | ||||||
|  |                 cmp  #<$number | ||||||
|  |                 bne  $falseLabel | ||||||
|  |                 cpy  #>$number | ||||||
|  |                 bne  $falseLabel""") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateWordExprIsNotZero(expr: PtExpression, falseLabel: String) { | ||||||
|  |         // if w!=0 | ||||||
|  |         // TODO reuse code from ifElse? | ||||||
|  |         if(expr is PtIdentifier) { | ||||||
|  |             val varname = asmgen.asmVariableName(expr) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  |                 ora  $varname+1 | ||||||
|  |                 beq  $falseLabel""") | ||||||
|  |         } else { | ||||||
|  |             asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY) | ||||||
|  |             asmgen.out("  sty  P8ZP_SCRATCH_REG |  ora  P8ZP_SCRATCH_REG |  beq  $falseLabel") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateWordExprIsZero(expr: PtExpression, falseLabel: String) { | ||||||
|  |         // if w==0 | ||||||
|  |         // TODO reuse code from ifElse? | ||||||
|  |         if(expr is PtIdentifier) { | ||||||
|  |             val varname = asmgen.asmVariableName(expr) | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  $varname | ||||||
|  |                 ora  $varname+1 | ||||||
|  |                 bne  $falseLabel""") | ||||||
|  |         } else { | ||||||
|  |             asmgen.assignExpressionToRegister(expr, RegisterOrPair.AY) | ||||||
|  |             asmgen.out("  sty  P8ZP_SCRATCH_REG |  ora  P8ZP_SCRATCH_REG |  bne  $falseLabel") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) { | ||||||
|  |         // optimized code for byte comparisons with 0 | ||||||
|  |  | ||||||
|  |         val useBIT = asmgen.checkIfConditionCanUseBIT(condition) | ||||||
|  |         if(useBIT!=null) { | ||||||
|  |             // use a BIT instruction to test for bit 7 or 6 set/clear | ||||||
|  |             val (testForBitSet, variable, bitmask) = useBIT | ||||||
|  |             when (bitmask) { | ||||||
|  |                 128 -> { | ||||||
|  |                     // test via bit + N flag | ||||||
|  |                     asmgen.out("  bit  ${variable.name}") | ||||||
|  |                     if(testForBitSet) asmgen.out("  bpl  $falseLabel") | ||||||
|  |                     else asmgen.out("  bmi  $falseLabel") | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 64 -> { | ||||||
|  |                     // test via bit + V flag | ||||||
|  |                     asmgen.out("  bit  ${variable.name}") | ||||||
|  |                     if(testForBitSet) asmgen.out("  bvc  $falseLabel") | ||||||
|  |                     else asmgen.out("  bvs  $falseLabel") | ||||||
|  |                     return | ||||||
|  |                 } | ||||||
|  |                 else -> throw AssemblyError("BIT can only work on bits 7 and 6") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         asmgen.assignConditionValueToRegisterAndTest(condition.left) | ||||||
|  |         when (condition.operator) { | ||||||
|  |             "==" -> asmgen.out("  bne  $falseLabel") | ||||||
|  |             "!=" -> asmgen.out("  beq  $falseLabel") | ||||||
|  |             ">" -> { | ||||||
|  |                 if(signed) asmgen.out("  bmi  $falseLabel |  beq  $falseLabel") | ||||||
|  |                 else asmgen.out("  beq  $falseLabel") | ||||||
|  |             } | ||||||
|  |             ">=" -> { | ||||||
|  |                 if(signed) asmgen.out("  bmi  $falseLabel") | ||||||
|  |                 else { /* always true for unsigned */ } | ||||||
|  |             } | ||||||
|  |             "<" -> { | ||||||
|  |                 if(signed) asmgen.out("  bpl  $falseLabel") | ||||||
|  |                 else asmgen.jmp(falseLabel) | ||||||
|  |             } | ||||||
|  |             "<=" -> { | ||||||
|  |                 if(signed) { | ||||||
|  |                     // inverted '>' | ||||||
|  |                     asmgen.out(""" | ||||||
|  |                         beq  + | ||||||
|  |                         bpl  $falseLabel | ||||||
|  | +""") | ||||||
|  |                 } else asmgen.out("  bne  $falseLabel") | ||||||
|  |             } | ||||||
|  |             else -> throw AssemblyError("expected comparison operator") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -32,10 +32,6 @@ internal class ProgramAndVarsGen( | |||||||
|  |  | ||||||
|     internal fun generate() { |     internal fun generate() { | ||||||
|         header() |         header() | ||||||
|         val allBlocks = program.allBlocks() |  | ||||||
|  |  | ||||||
|         if(allBlocks.first().name != "p8b_main" && allBlocks.first().name != "main") |  | ||||||
|             throw AssemblyError("first block should be 'main' or 'p8b_main'") |  | ||||||
|  |  | ||||||
|         if(errors.noErrors())  { |         if(errors.noErrors())  { | ||||||
|             program.allBlocks().forEach { block2asm(it) } |             program.allBlocks().forEach { block2asm(it) } | ||||||
| @@ -43,22 +39,22 @@ internal class ProgramAndVarsGen( | |||||||
|             // the global list of all floating point constants for the whole program |             // the global list of all floating point constants for the whole program | ||||||
|             asmgen.out("; global float constants") |             asmgen.out("; global float constants") | ||||||
|             for (flt in allocator.globalFloatConsts) { |             for (flt in allocator.globalFloatConsts) { | ||||||
|                 val floatFill = compTarget.machine.getFloatAsmBytes(flt.key) |                 val floatFill = compTarget.getFloatAsmBytes(flt.key) | ||||||
|                 val floatvalue = flt.key |                 val floatvalue = flt.key | ||||||
|                 asmgen.out("${flt.value}\t.byte  $floatFill  ; float $floatvalue") |                 asmgen.out("${flt.value}\t.byte  $floatFill  ; float $floatvalue") | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             structInstances2asm() | ||||||
|             memorySlabs() |             memorySlabs() | ||||||
|             tempVars() |  | ||||||
|             footer() |             footer() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun header() { |     private fun header() { | ||||||
|         val ourName = this.javaClass.name |         val ourName = this.javaClass.name | ||||||
|         val cpu = when(compTarget.machine.cpu) { |         val cpu = when(compTarget.cpu) { | ||||||
|             CpuType.CPU6502 -> "6502" |             CpuType.CPU6502 -> "6502" | ||||||
|             CpuType.CPU65c02 -> "w65c02" |             CpuType.CPU65C02 -> "w65c02" | ||||||
|             else -> "unsupported" |             else -> "unsupported" | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -75,6 +71,13 @@ internal class ProgramAndVarsGen( | |||||||
|         asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}") |         asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}") | ||||||
|         asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1}    ; word") |         asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1}    ; word") | ||||||
|         asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2}    ; word") |         asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2}    ; word") | ||||||
|  |         asmgen.out("P8ZP_SCRATCH_PTR = ${zp.SCRATCH_PTR}  ; word") | ||||||
|  |         if(compTarget.name=="c64") { | ||||||
|  |             if(options.floats) | ||||||
|  |                 asmgen.out("PROG8_C64_BANK_CONFIG=31  ; basic+IO+kernal") | ||||||
|  |             else | ||||||
|  |                 asmgen.out("PROG8_C64_BANK_CONFIG=30  ; IO+kernal, no basic") | ||||||
|  |         } | ||||||
|         asmgen.out(".weak")   // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!) |         asmgen.out(".weak")   // hack to allow user to override the following two with command line redefinition (however, just use '-esa' command line option instead!) | ||||||
|         asmgen.out(".endweak") |         asmgen.out(".endweak") | ||||||
|  |  | ||||||
| @@ -85,43 +88,90 @@ internal class ProgramAndVarsGen( | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if(options.compTarget.customLauncher.isNotEmpty()) { | ||||||
|  |             asmgen.out("; ---- custom launcher assembler program ----") | ||||||
|  |             asmgen.out("* = ${options.loadAddress.toHex()}") | ||||||
|  |             asmgen.out("prog8_program_start\t; start of program label") | ||||||
|  |             for(line in options.compTarget.customLauncher) { | ||||||
|  |                 asmgen.out(line) | ||||||
|  |             } | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(options.output == OutputType.LIBRARY) { | ||||||
|  |  | ||||||
|  |             asmgen.out("; ---- library assembler program ----") | ||||||
|  |             asmgen.out("* = ${options.loadAddress.toHex()}") | ||||||
|  |             asmgen.out("prog8_program_start\t; start of program label") | ||||||
|  |             asmgen.out("    jmp  p8b_main.p8s_start") | ||||||
|  |             // note: the jmp above has 2 effects: | ||||||
|  |             // 1. it prevents 64tass from stripping away all procs as unused code | ||||||
|  |             // 2. it functions as the first entrypoint of the library, required anyway, to run the variable initialization/bss clear bootstrap code. | ||||||
|  |  | ||||||
|  |         } else { | ||||||
|  |  | ||||||
|             when (options.output) { |             when (options.output) { | ||||||
|  |                 OutputType.LIBRARY -> { } | ||||||
|                 OutputType.RAW -> { |                 OutputType.RAW -> { | ||||||
|                     asmgen.out("; ---- raw assembler program ----") |                     asmgen.out("; ---- raw assembler program ----") | ||||||
|                     asmgen.out("* = ${options.loadAddress.toHex()}") |                     asmgen.out("* = ${options.loadAddress.toHex()}") | ||||||
|  |                     asmgen.out("prog8_program_start\t; start of program label") | ||||||
|  |                     asmgen.out("  cld") | ||||||
|  |                     asmgen.out("  tsx  ; save stackpointer for sys.exit()") | ||||||
|  |                     asmgen.out("  stx  prog8_lib.orig_stackpointer") | ||||||
|  |                     if (!options.noSysInit) | ||||||
|  |                         asmgen.out("  jsr  p8_sys_startup.init_system") | ||||||
|  |                     asmgen.out("  jsr  p8_sys_startup.init_system_phase2") | ||||||
|                 } |                 } | ||||||
|                 OutputType.PRG -> { |                 OutputType.PRG -> { | ||||||
|                     when (options.launcher) { |                     when (options.launcher) { | ||||||
|                         CbmPrgLauncherType.BASIC -> { |                         CbmPrgLauncherType.BASIC -> { | ||||||
|                         if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) { |                             if (options.loadAddress != options.compTarget.PROGRAM_LOAD_ADDRESS) { | ||||||
|                             errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position) |                                 errors.err( | ||||||
|  |                                     "BASIC output must have load address ${options.compTarget.PROGRAM_LOAD_ADDRESS.toHex()}", | ||||||
|  |                                     program.position | ||||||
|  |                                 ) | ||||||
|                             } |                             } | ||||||
|                             asmgen.out("; ---- basic program with sys call ----") |                             asmgen.out("; ---- basic program with sys call ----") | ||||||
|                             asmgen.out("* = ${options.loadAddress.toHex()}") |                             asmgen.out("* = ${options.loadAddress.toHex()}") | ||||||
|  |                             asmgen.out("prog8_program_start\t; start of program label") | ||||||
|                             val year = LocalDate.now().year |                             val year = LocalDate.now().year | ||||||
|                             asmgen.out("  .word  (+), $year") |                             asmgen.out("  .word  (+), $year") | ||||||
|                             asmgen.out("  .null  $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'") |                             asmgen.out("  .null  $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'") | ||||||
|                             asmgen.out("+\t.word  0") |                             asmgen.out("+\t.word  0") | ||||||
|                         asmgen.out("prog8_entrypoint\t; assembly code starts here") |                             asmgen.out("prog8_entrypoint") | ||||||
|  |                             asmgen.out("  cld") | ||||||
|  |                             asmgen.out("  tsx  ; save stackpointer for sys.exit()") | ||||||
|  |                             asmgen.out("  stx  prog8_lib.orig_stackpointer") | ||||||
|                             if (!options.noSysInit) |                             if (!options.noSysInit) | ||||||
|                             asmgen.out("  jsr  sys.init_system") |                                 asmgen.out("  jsr  p8_sys_startup.init_system") | ||||||
|                         asmgen.out("  jsr  sys.init_system_phase2") |                             asmgen.out("  jsr  p8_sys_startup.init_system_phase2") | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         CbmPrgLauncherType.NONE -> { |                         CbmPrgLauncherType.NONE -> { | ||||||
|  |                             // this is the same as RAW | ||||||
|                             asmgen.out("; ---- program without basic sys call ----") |                             asmgen.out("; ---- program without basic sys call ----") | ||||||
|                             asmgen.out("* = ${options.loadAddress.toHex()}") |                             asmgen.out("* = ${options.loadAddress.toHex()}") | ||||||
|  |                             asmgen.out("prog8_program_start\t; start of program label") | ||||||
|  |                             asmgen.out("  cld") | ||||||
|  |                             asmgen.out("  tsx  ; save stackpointer for sys.exit()") | ||||||
|  |                             asmgen.out("  stx  prog8_lib.orig_stackpointer") | ||||||
|                             if (!options.noSysInit) |                             if (!options.noSysInit) | ||||||
|                             asmgen.out("  jsr  sys.init_system") |                                 asmgen.out("  jsr  p8_sys_startup.init_system") | ||||||
|                         asmgen.out("  jsr  sys.init_system_phase2") |                             asmgen.out("  jsr  p8_sys_startup.init_system_phase2") | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 OutputType.XEX -> { |                 OutputType.XEX -> { | ||||||
|                     asmgen.out("; ---- atari xex program ----") |                     asmgen.out("; ---- atari xex program ----") | ||||||
|                     asmgen.out("* = ${options.loadAddress.toHex()}") |                     asmgen.out("* = ${options.loadAddress.toHex()}") | ||||||
|  |                     asmgen.out("prog8_program_start\t; start of program label") | ||||||
|  |                     asmgen.out("  cld") | ||||||
|  |                     asmgen.out("  tsx  ; save stackpointer for sys.exit()") | ||||||
|  |                     asmgen.out("  stx  prog8_lib.orig_stackpointer") | ||||||
|                     if (!options.noSysInit) |                     if (!options.noSysInit) | ||||||
|                     asmgen.out("  jsr  sys.init_system") |                         asmgen.out("  jsr  p8_sys_startup.init_system") | ||||||
|                 asmgen.out("  jsr  sys.init_system_phase2") |                     asmgen.out("  jsr  p8_sys_startup.init_system_phase2") | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -139,132 +189,125 @@ internal class ProgramAndVarsGen( | |||||||
|                     if (options.floats) |                     if (options.floats) | ||||||
|                         asmgen.out("  lda  #4 |  sta  $01")    // to use floats, make sure Basic rom is banked in |                         asmgen.out("  lda  #4 |  sta  $01")    // to use floats, make sure Basic rom is banked in | ||||||
|                     asmgen.out("  jsr  p8b_main.p8s_start") |                     asmgen.out("  jsr  p8b_main.p8s_start") | ||||||
|                 asmgen.out("  jmp  sys.cleanup_at_exit") |                     asmgen.out("  jmp  p8_sys_startup.cleanup_at_exit") | ||||||
|                 } |                 } | ||||||
|                 "c64" -> { |                 "c64" -> { | ||||||
|                 asmgen.out("  jsr  p8b_main.p8s_start |  lda  #31 |  sta  $01") |                     asmgen.out("  jsr  p8b_main.p8s_start") | ||||||
|                 asmgen.out("  jmp  sys.cleanup_at_exit") |                     asmgen.out("  jmp  p8_sys_startup.cleanup_at_exit") | ||||||
|                 } |                 } | ||||||
|                 "c128" -> { |                 "c128" -> { | ||||||
|                 asmgen.out("  jsr  p8b_main.p8s_start |  lda  #0 |  sta ${"$"}ff00") |                     asmgen.out("  jsr  p8b_main.p8s_start") | ||||||
|                 asmgen.out("  jmp  sys.cleanup_at_exit") |                     asmgen.out("  jmp  p8_sys_startup.cleanup_at_exit") | ||||||
|  |                 } | ||||||
|  |                 else -> { | ||||||
|  |                     asmgen.out("  jsr  p8b_main.p8s_start") | ||||||
|  |                     asmgen.out("  jmp  p8_sys_startup.cleanup_at_exit") | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             else -> asmgen.jmp("p8b_main.p8s_start") |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun memorySlabs() { |     private fun memorySlabs() { | ||||||
|         if(symboltable.allMemorySlabs.isNotEmpty()) { |         if(symboltable.allMemorySlabs.isNotEmpty()) { | ||||||
|             asmgen.out("; memory slabs\n  .section slabs_BSS") |             asmgen.out("; memory slabs\n  .section BSS_SLABS") | ||||||
|             asmgen.out("prog8_slabs\t.block") |             asmgen.out("prog8_slabs\t.block") | ||||||
|             for (slab in symboltable.allMemorySlabs) { |             for (slab in symboltable.allMemorySlabs) { | ||||||
|                 if (slab.align > 1u) |                 if (slab.align > 1u) | ||||||
|                     asmgen.out("\t.align  ${slab.align.toHex()}") |                     asmgen.out("\t.align  ${slab.align.toHex()}") | ||||||
|                 asmgen.out("${slab.name}\t.fill  ${slab.size}") |                 asmgen.out("${slab.name}\t.fill  ${slab.size}") | ||||||
|             } |             } | ||||||
|             asmgen.out("\t.bend\n  .send slabs_BSS") |             asmgen.out("\t.bend\n  .send BSS_SLABS") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun tempVars() { |  | ||||||
|         asmgen.out("; expression temp vars\n  .section BSS") |  | ||||||
|         for((dt, count) in asmgen.tempVarsCounters) { |  | ||||||
|             if(count>0) { |  | ||||||
|                 for(num in 1..count) { |  | ||||||
|                     val name = asmgen.buildTempVarName(dt, num) |  | ||||||
|                     when (dt) { |  | ||||||
|                         DataType.BOOL  -> asmgen.out("$name    .byte  ?") |  | ||||||
|                         DataType.BYTE  -> asmgen.out("$name    .char  ?") |  | ||||||
|                         DataType.UBYTE -> asmgen.out("$name    .byte  ?") |  | ||||||
|                         DataType.WORD  -> asmgen.out("$name    .sint  ?") |  | ||||||
|                         DataType.UWORD -> asmgen.out("$name    .word  ?") |  | ||||||
|                         DataType.FLOAT -> asmgen.out("$name    .fill  ${options.compTarget.machine.FLOAT_MEM_SIZE}") |  | ||||||
|                         else -> throw AssemblyError("weird dt for extravar $dt") |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         asmgen.out("  .send BSS") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun footer() { |     private fun footer() { | ||||||
|  |         asmgen.out("  .dsection STRUCTINSTANCES\n") | ||||||
|  |  | ||||||
|         var relocateBssVars = false |         var relocateBssVars = false | ||||||
|         var relocateBssSlabs = false |         var relocateBssSlabs = false | ||||||
|         var relocatedBssStart = 0u |         var relocatedBssStart = 0u | ||||||
|         var relocatedBssEnd = 0u |         var relocatedBssEnd = 0u | ||||||
|  |  | ||||||
|         if(options.varsGolden) { |         if(options.varsGolden) { | ||||||
|             if(options.compTarget.machine.BSSGOLDENRAM_START == 0u || |             if(options.compTarget.BSSGOLDENRAM_START == 0u || | ||||||
|                 options.compTarget.machine.BSSGOLDENRAM_END == 0u || |                 options.compTarget.BSSGOLDENRAM_END == 0u || | ||||||
|                 options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) { |                 options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) { | ||||||
|                 throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available") |                 throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available") | ||||||
|             } |             } | ||||||
|             relocateBssVars = true |             relocateBssVars = true | ||||||
|             relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START |             relocatedBssStart = options.compTarget.BSSGOLDENRAM_START | ||||||
|             relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END |             relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END | ||||||
|         } |         } | ||||||
|         else if(options.varsHighBank!=null) { |         else if(options.varsHighBank!=null) { | ||||||
|             if(options.compTarget.machine.BSSHIGHRAM_START == 0u || |             if(options.compTarget.BSSHIGHRAM_START == 0u || | ||||||
|                 options.compTarget.machine.BSSHIGHRAM_END == 0u || |                 options.compTarget.BSSHIGHRAM_END == 0u || | ||||||
|                 options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) { |                 options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) { | ||||||
|                 throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available") |                 throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available") | ||||||
|             } |             } | ||||||
|             if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank) |             if(options.slabsHighBank!=null && options.varsHighBank!=options.slabsHighBank) | ||||||
|                 throw AssemblyError("slabs and vars high bank must be the same") |                 throw AssemblyError("slabs and vars high bank must be the same") | ||||||
|             relocateBssVars = true |             relocateBssVars = true | ||||||
|             relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START |             relocatedBssStart = options.compTarget.BSSHIGHRAM_START | ||||||
|             relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END |             relocatedBssEnd = options.compTarget.BSSHIGHRAM_END | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if(options.slabsGolden) { |         if(options.slabsGolden) { | ||||||
|             if(options.compTarget.machine.BSSGOLDENRAM_START == 0u || |             if(options.compTarget.BSSGOLDENRAM_START == 0u || | ||||||
|                 options.compTarget.machine.BSSGOLDENRAM_END == 0u || |                 options.compTarget.BSSGOLDENRAM_END == 0u || | ||||||
|                 options.compTarget.machine.BSSGOLDENRAM_END <= options.compTarget.machine.BSSGOLDENRAM_START) { |                 options.compTarget.BSSGOLDENRAM_END <= options.compTarget.BSSGOLDENRAM_START) { | ||||||
|                 throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available") |                 throw AssemblyError("current compilation target hasn't got the golden ram area properly defined or it is simply not available") | ||||||
|             } |             } | ||||||
|             relocateBssSlabs = true |             relocateBssSlabs = true | ||||||
|             relocatedBssStart = options.compTarget.machine.BSSGOLDENRAM_START |             relocatedBssStart = options.compTarget.BSSGOLDENRAM_START | ||||||
|             relocatedBssEnd = options.compTarget.machine.BSSGOLDENRAM_END |             relocatedBssEnd = options.compTarget.BSSGOLDENRAM_END | ||||||
|         } |         } | ||||||
|         else if(options.slabsHighBank!=null) { |         else if(options.slabsHighBank!=null) { | ||||||
|             if(options.compTarget.machine.BSSHIGHRAM_START == 0u || |             if(options.compTarget.BSSHIGHRAM_START == 0u || | ||||||
|                 options.compTarget.machine.BSSHIGHRAM_END == 0u || |                 options.compTarget.BSSHIGHRAM_END == 0u || | ||||||
|                 options.compTarget.machine.BSSHIGHRAM_END <= options.compTarget.machine.BSSHIGHRAM_START) { |                 options.compTarget.BSSHIGHRAM_END <= options.compTarget.BSSHIGHRAM_START) { | ||||||
|                 throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available") |                 throw AssemblyError("current compilation target hasn't got the high ram area properly defined or it is simply not available") | ||||||
|             } |             } | ||||||
|             if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank) |             if(options.varsHighBank!=null && options.varsHighBank!=options.slabsHighBank) | ||||||
|                 throw AssemblyError("slabs and vars high bank must be the same") |                 throw AssemblyError("slabs and vars high bank must be the same") | ||||||
|             relocateBssSlabs = true |             relocateBssSlabs = true | ||||||
|             relocatedBssStart = options.compTarget.machine.BSSHIGHRAM_START |             relocatedBssStart = options.compTarget.BSSHIGHRAM_START | ||||||
|             relocatedBssEnd = options.compTarget.machine.BSSHIGHRAM_END |             relocatedBssEnd = options.compTarget.BSSHIGHRAM_END | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         asmgen.out("; bss sections") |         asmgen.out("; bss sections") | ||||||
|         asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}") |         asmgen.out("PROG8_VARSHIGH_RAMBANK = ${options.varsHighBank ?: 1}") | ||||||
|         if(relocateBssVars) { |         if(relocateBssVars) { | ||||||
|             if(!relocateBssSlabs) |             if(!relocateBssSlabs) | ||||||
|                 asmgen.out("  .dsection slabs_BSS") |                 asmgen.out("  .dsection BSS_SLABS") | ||||||
|             asmgen.out("prog8_program_end\t; end of program label for progend()") |             asmgen.out("prog8_program_end\t; end of program label for progend()") | ||||||
|             asmgen.out("  * = ${relocatedBssStart.toHex()}") |             asmgen.out("  * = ${relocatedBssStart.toHex()}") | ||||||
|  |             asmgen.out("  .dsection BSS_NOCLEAR") | ||||||
|             asmgen.out("prog8_bss_section_start") |             asmgen.out("prog8_bss_section_start") | ||||||
|             asmgen.out("  .dsection BSS") |             asmgen.out("  .dsection BSS") | ||||||
|             if(relocateBssSlabs) |             if(relocateBssSlabs) | ||||||
|                 asmgen.out("  .dsection slabs_BSS") |                 asmgen.out("  .dsection BSS_SLABS") | ||||||
|             asmgen.out("  .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"") |             asmgen.out("  .cerror * > ${relocatedBssEnd.toHex()}, \"too many variables/data for BSS section\"") | ||||||
|             asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start") |             asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start") | ||||||
|         } else { |         } else { | ||||||
|  |             asmgen.out("  .dsection BSS_NOCLEAR") | ||||||
|             asmgen.out("prog8_bss_section_start") |             asmgen.out("prog8_bss_section_start") | ||||||
|             asmgen.out("  .dsection BSS") |             asmgen.out("  .dsection BSS") | ||||||
|             asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start") |             asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start") | ||||||
|             if(!relocateBssSlabs) |             if(!relocateBssSlabs) | ||||||
|                 asmgen.out("  .dsection slabs_BSS") |                 asmgen.out("  .dsection BSS_SLABS") | ||||||
|             asmgen.out("prog8_program_end\t; end of program label for progend()") |             asmgen.out("prog8_program_end\t; end of program label for progend()") | ||||||
|             if(relocateBssSlabs) { |             if(relocateBssSlabs) { | ||||||
|                 asmgen.out("  * = ${relocatedBssStart.toHex()}") |                 asmgen.out("  * = ${relocatedBssStart.toHex()}") | ||||||
|                 asmgen.out("  .dsection slabs_BSS") |                 asmgen.out("  .dsection BSS_SLABS") | ||||||
|                 asmgen.out("  .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for slabs_BSS section\"") |                 asmgen.out("  .cerror * > ${relocatedBssEnd.toHex()}, \"too many data for BSS_SLABS section\"") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if(relocatedBssEnd >= options.memtopAddress) | ||||||
|  |             options.memtopAddress = relocatedBssEnd+1u | ||||||
|  |  | ||||||
|  |         asmgen.out("  ; memtop check") | ||||||
|  |         asmgen.out("  .cerror * >= ${options.memtopAddress.toHex()}, \"Program too long by \", * - ${(options.memtopAddress-1u).toHex()}, \" bytes, memtop=${options.memtopAddress.toHex()}\"") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun block2asm(block: PtBlock) { |     private fun block2asm(block: PtBlock) { | ||||||
| @@ -272,12 +315,6 @@ internal class ProgramAndVarsGen( | |||||||
|         asmgen.out("; ---- block: '${block.name}' ----") |         asmgen.out("; ---- block: '${block.name}' ----") | ||||||
|         if(block.options.address!=null) |         if(block.options.address!=null) | ||||||
|             asmgen.out("* = ${block.options.address!!.toHex()}") |             asmgen.out("* = ${block.options.address!!.toHex()}") | ||||||
|         else { |  | ||||||
|             if(block.options.alignment==PtBlock.BlockAlignment.WORD) |  | ||||||
|                 asmgen.out("\t.align 2") |  | ||||||
|             else if(block.options.alignment==PtBlock.BlockAlignment.PAGE) |  | ||||||
|                 asmgen.out("\t.align $100") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         asmgen.out("${block.name}\t" + (if(block.options.forceOutput) ".block" else ".proc")) |         asmgen.out("${block.name}\t" + (if(block.options.forceOutput) ".block" else ".proc")) | ||||||
|         asmgen.outputSourceLine(block) |         asmgen.outputSourceLine(block) | ||||||
| @@ -295,9 +332,10 @@ internal class ProgramAndVarsGen( | |||||||
|         if (initializers.isNotEmpty()) { |         if (initializers.isNotEmpty()) { | ||||||
|             asmgen.out("prog8_init_vars\t.block") |             asmgen.out("prog8_init_vars\t.block") | ||||||
|             initializers.forEach { assign -> |             initializers.forEach { assign -> | ||||||
|                 if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name)) |                 val constvalue = assign.value as? PtNumber | ||||||
|  |                 if(constvalue==null || constvalue.number!=0.0 || allocator.isZpVar(assign.target.identifier!!.name)) | ||||||
|                     asmgen.translate(assign) |                     asmgen.translate(assign) | ||||||
|                 // the other variables that should be set to zero are done so as part of the BSS section. |                 // the other variables that should be set to zero are done so as part of the BSS section clear. | ||||||
|             } |             } | ||||||
|             asmgen.out("  rts\n  .bend") |             asmgen.out("  rts\n  .bend") | ||||||
|         } |         } | ||||||
| @@ -314,7 +352,7 @@ internal class ProgramAndVarsGen( | |||||||
|         val varsInBlock = getVars(scope) |         val varsInBlock = getVars(scope) | ||||||
|  |  | ||||||
|         // Zeropage Variables |         // Zeropage Variables | ||||||
|         val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet() |         val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet() | ||||||
|         zeropagevars2asm(varnames) |         zeropagevars2asm(varnames) | ||||||
|  |  | ||||||
|         // MemDefs and Consts |         // MemDefs and Consts | ||||||
| @@ -328,11 +366,93 @@ internal class ProgramAndVarsGen( | |||||||
|  |  | ||||||
|         // normal statically allocated variables |         // normal statically allocated variables | ||||||
|         val variables = varsInBlock |         val variables = varsInBlock | ||||||
|             .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) } |             .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) } | ||||||
|             .map { it.value as StStaticVariable } |             .map { it.value as StStaticVariable } | ||||||
|         nonZpVariables2asm(variables) |         nonZpVariables2asm(variables) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun asmTypeString(dt: DataType): String { | ||||||
|  |         return when { | ||||||
|  |             dt.isBool || dt.isUnsignedByte -> ".byte" | ||||||
|  |             dt.isSignedByte -> ".char" | ||||||
|  |             dt.isUnsignedWord || dt.isPointer -> ".word" | ||||||
|  |             dt.isSignedWord -> ".sint" | ||||||
|  |             dt.isFloat -> ".byte" | ||||||
|  |             else -> { | ||||||
|  |                 throw AssemblyError("weird dt") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun structInstances2asm() { | ||||||
|  |  | ||||||
|  |         fun initValues(instance: StStructInstance): List<String> { | ||||||
|  |             val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct | ||||||
|  |             return structtype.fields.zip(instance.initialValues).map { (field, value) -> | ||||||
|  |                 if(field.first.isFloat) { | ||||||
|  |                     "["+compTarget.getFloatAsmBytes(value.number!!)+"]" | ||||||
|  |                 } else { | ||||||
|  |                     when { | ||||||
|  |                         value.number!=null -> { | ||||||
|  |                             if(field.first.isPointer) | ||||||
|  |                                 "$"+value.number!!.toInt().toString(16) | ||||||
|  |                             else if(field.first.isInteger) | ||||||
|  |                                 value.number!!.toInt().toString() | ||||||
|  |                             else | ||||||
|  |                                 value.number.toString() | ||||||
|  |                         } | ||||||
|  |                         value.addressOfSymbol!=null -> value.addressOfSymbol!! | ||||||
|  |                         value.boolean!=null -> if(value.boolean==true) "1" else "0" | ||||||
|  |                         else -> throw AssemblyError("weird struct initial value $value") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         asmgen.out("; struct types") | ||||||
|  |         symboltable.allStructInstances.distinctBy { it.structName }.forEach { | ||||||
|  |             val structtype: StStruct = symboltable.lookup(it.structName) as StStruct | ||||||
|  |             val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" } | ||||||
|  |             asmgen.out("${it.structName}    .struct $structargs\n") | ||||||
|  |             structtype.fields.withIndex().forEach { (index, field) -> | ||||||
|  |                 val dt = field.first | ||||||
|  |                 val varname = "f${index}" | ||||||
|  |                 val type = when { | ||||||
|  |                     dt.isBool || dt.isUnsignedByte -> ".byte" | ||||||
|  |                     dt.isSignedByte -> ".char" | ||||||
|  |                     dt.isUnsignedWord || dt.isPointer -> ".word" | ||||||
|  |                     dt.isSignedWord -> ".sint" | ||||||
|  |                     dt.isFloat -> ".byte"   // TODO check that float bytes are passed as an array parameter | ||||||
|  |                     else -> throw AssemblyError("weird dt") | ||||||
|  |                 } | ||||||
|  |                 asmgen.out("p8v_${field.second}  $type  \\$varname")        // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST | ||||||
|  |             } | ||||||
|  |             asmgen.out("    .endstruct\n") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val (instancesNoInit, instances) = symboltable.allStructInstances.partition { it.initialValues.isEmpty() } | ||||||
|  |         asmgen.out("; struct instances without initialization values, as BSS zeroed at startup\n") | ||||||
|  |         asmgen.out("    .section BSS\n") | ||||||
|  |         instancesNoInit.forEach { | ||||||
|  |             val structtype: StStruct = symboltable.lookup(it.structName) as StStruct | ||||||
|  |             val zerovalues = structtype.fields.map { field -> | ||||||
|  |                 if(field.first.isFloat) { | ||||||
|  |                     val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" } | ||||||
|  |                     "[${floatbytes.joinToString(",")}]" | ||||||
|  |                 } | ||||||
|  |                 else "?" | ||||||
|  |             } | ||||||
|  |             asmgen.out("${it.name}    .dstruct  ${it.structName}, ${zerovalues.joinToString(",")}\n") | ||||||
|  |         } | ||||||
|  |         asmgen.out("    .send BSS\n") | ||||||
|  |  | ||||||
|  |         asmgen.out("; struct instances with initialization values\n") | ||||||
|  |         asmgen.out("    .section STRUCTINSTANCES\n") | ||||||
|  |         instances.forEach { asmgen.out("${it.name}    .dstruct  ${it.structName}, ${initValues(it).joinToString(",")}\n") } | ||||||
|  |         asmgen.out("    .send STRUCTINSTANCES\n") | ||||||
|  |     } | ||||||
|  |  | ||||||
|     internal fun translateAsmSubroutine(sub: PtAsmSub) { |     internal fun translateAsmSubroutine(sub: PtAsmSub) { | ||||||
|         if(sub.inline) { |         if(sub.inline) { | ||||||
|             return      // subroutine gets inlined at call site. |             return      // subroutine gets inlined at call site. | ||||||
| @@ -382,7 +502,7 @@ internal class ProgramAndVarsGen( | |||||||
|         val varsInSubroutine = getVars(scope) |         val varsInSubroutine = getVars(scope) | ||||||
|  |  | ||||||
|         // Zeropage Variables |         // Zeropage Variables | ||||||
|         val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet() |         val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedNameString }.toSet() | ||||||
|         zeropagevars2asm(varnames) |         zeropagevars2asm(varnames) | ||||||
|  |  | ||||||
|         // MemDefs and Consts |         // MemDefs and Consts | ||||||
| @@ -400,22 +520,35 @@ internal class ProgramAndVarsGen( | |||||||
|         if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main")) |         if((sub.name=="start" || sub.name=="p8s_start") && (sub.definingBlock()!!.name=="main" || sub.definingBlock()!!.name=="p8b_main")) | ||||||
|             entrypointInitialization() |             entrypointInitialization() | ||||||
|  |  | ||||||
|         if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) { |         val params = sub.signature.children.filterIsInstance<PtSubroutineParameter>() | ||||||
|             asmgen.out("; simple int arg(s) passed via register(s)") |         if(functioncallAsmGen.optimizeIntArgsViaCpuRegisters(params)) { | ||||||
|             if(sub.parameters.size==1) { |             asmgen.out("; simple int arg(s) passed via cpu register(s)") | ||||||
|                 val dt = sub.parameters[0].type |  | ||||||
|                 val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name) |             fun varname(param: PtSubroutineParameter): String = | ||||||
|                 if(dt in ByteDatatypesWithBoolean) |                 if(param.register==null) | ||||||
|                     asmgen.assignRegister(RegisterOrPair.A, target) |                     param.name | ||||||
|                 else |                 else | ||||||
|                     asmgen.assignRegister(RegisterOrPair.AY, target) |                     param.register!!.asScopedNameVirtualReg(param.type).joinToString(".") | ||||||
|             } else { |  | ||||||
|                 require(sub.parameters.size==2) |             when(params.size) { | ||||||
|                 // 2 simple byte args, first in A, second in Y |                 1 -> { | ||||||
|                 val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, sub.parameters[0].position, variableAsmName = sub.parameters[0].name) |                     val dt = params[0].type | ||||||
|                 val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, sub.parameters[1].position, variableAsmName = sub.parameters[1].name) |                     val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, params[0].position, variableAsmName = varname(params[0])) | ||||||
|  |                     if(dt.isByteOrBool) | ||||||
|  |                         asmgen.assignRegister(RegisterOrPair.A, target)     // single byte in A | ||||||
|  |                     else | ||||||
|  |                         asmgen.assignRegister(RegisterOrPair.AY, target)    // word in AY | ||||||
|  |                 } | ||||||
|  |                 2 -> { | ||||||
|  |                     val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, params[0].type, sub, params[0].position, variableAsmName = varname(params[0])) | ||||||
|  |                     val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, params[1].type, sub, params[1].position, variableAsmName = varname(params[1])) | ||||||
|  |                     if(params[0].type.isByteOrBool && params[1].type.isByteOrBool) { | ||||||
|  |                         // 2 byte args, first in A, second in Y | ||||||
|                         asmgen.assignRegister(RegisterOrPair.A, target1) |                         asmgen.assignRegister(RegisterOrPair.A, target1) | ||||||
|                         asmgen.assignRegister(RegisterOrPair.Y, target2) |                         asmgen.assignRegister(RegisterOrPair.Y, target2) | ||||||
|  |                     } else throw AssemblyError("cannot use registers for word+byte args") | ||||||
|  |                 } | ||||||
|  |                 else -> throw AssemblyError("cannot use registers for >2 args") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -423,27 +556,27 @@ internal class ProgramAndVarsGen( | |||||||
|         sub.children.forEach { asmgen.translate(it) } |         sub.children.forEach { asmgen.translate(it) } | ||||||
|  |  | ||||||
|         asmgen.out("; variables") |         asmgen.out("; variables") | ||||||
|         asmgen.out("    .section BSS") |         asmgen.out("    .section BSS_NOCLEAR")      // these extra vars are initialized before use | ||||||
|         val asmGenInfo = asmgen.subroutineExtra(sub) |         val asmGenInfo = asmgen.subroutineExtra(sub) | ||||||
|         for((dt, name, addr) in asmGenInfo.extraVars) { |         for((dt, name, addr) in asmGenInfo.extraVars) { | ||||||
|             if(addr!=null) |             if(addr!=null) | ||||||
|                 asmgen.out("$name = $addr") |                 asmgen.out("$name = $addr") | ||||||
|             else when(dt) { |             else when(dt) { | ||||||
|                 DataType.UBYTE -> asmgen.out("$name    .byte  ?") |                 BaseDataType.UBYTE, BaseDataType.BYTE, BaseDataType.BOOL -> asmgen.out("$name    .byte  ?") | ||||||
|                 DataType.UWORD -> asmgen.out("$name    .word  ?") |                 BaseDataType.UWORD, BaseDataType.WORD -> asmgen.out("$name    .word  ?") | ||||||
|                 DataType.FLOAT -> asmgen.out("$name    .fill  ${options.compTarget.machine.FLOAT_MEM_SIZE}") |                 BaseDataType.FLOAT -> asmgen.out("$name    .fill  ${options.compTarget.FLOAT_MEM_SIZE}") | ||||||
|                 else -> throw AssemblyError("weird dt for extravar $dt") |                 else -> throw AssemblyError("weird dt for extravar $dt") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if(asmGenInfo.usedFloatEvalResultVar1) |         if(asmGenInfo.usedFloatEvalResultVar1) | ||||||
|             asmgen.out("$subroutineFloatEvalResultVar1    .fill  ${options.compTarget.machine.FLOAT_MEM_SIZE}") |             asmgen.out("$subroutineFloatEvalResultVar1    .fill  ${options.compTarget.FLOAT_MEM_SIZE}") | ||||||
|         if(asmGenInfo.usedFloatEvalResultVar2) |         if(asmGenInfo.usedFloatEvalResultVar2) | ||||||
|             asmgen.out("$subroutineFloatEvalResultVar2    .fill  ${options.compTarget.machine.FLOAT_MEM_SIZE}") |             asmgen.out("$subroutineFloatEvalResultVar2    .fill  ${options.compTarget.FLOAT_MEM_SIZE}") | ||||||
|         asmgen.out("  .send BSS") |         asmgen.out("  .send BSS_NOCLEAR") | ||||||
|  |  | ||||||
|         // normal statically allocated variables |         // normal statically allocated variables | ||||||
|         val variables = varsInSubroutine |         val variables = varsInSubroutine | ||||||
|             .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) } |             .filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedNameString) } | ||||||
|             .map { it.value as StStaticVariable } |             .map { it.value as StStaticVariable } | ||||||
|         nonZpVariables2asm(variables) |         nonZpVariables2asm(variables) | ||||||
|  |  | ||||||
| @@ -451,22 +584,11 @@ internal class ProgramAndVarsGen( | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun entrypointInitialization() { |     private fun entrypointInitialization() { | ||||||
|         asmgen.out("; program startup initialization") |         // zero out the BSS area first, before setting the variable init values | ||||||
|         asmgen.out("  cld |  tsx |  stx  prog8_lib.orig_stackpointer    ; required for sys.exit()") |         // this is mainly to make sure the arrays are all zero'd out at program startup | ||||||
|         // set full BSS area to zero |         asmgen.out("  jsr  prog8_lib.program_startup_clear_bss") | ||||||
|         asmgen.out(""" |  | ||||||
|     .if  prog8_bss_section_size>0 |  | ||||||
|     ; reset all variables in BSS section to zero |  | ||||||
| 	lda  #<prog8_bss_section_start |  | ||||||
| 	ldy  #>prog8_bss_section_start |  | ||||||
| 	sta  P8ZP_SCRATCH_W1 |  | ||||||
| 	sty  P8ZP_SCRATCH_W1+1 |  | ||||||
| 	ldx  #<prog8_bss_section_size |  | ||||||
| 	ldy  #>prog8_bss_section_size |  | ||||||
| 	lda  #0 |  | ||||||
| 	jsr  prog8_lib.memset |  | ||||||
|     .endif""") |  | ||||||
|  |  | ||||||
|  |         // initialize block-level (global) variables at program start | ||||||
|         blockVariableInitializers.forEach { |         blockVariableInitializers.forEach { | ||||||
|             if (it.value.isNotEmpty()) |             if (it.value.isNotEmpty()) | ||||||
|                 asmgen.out("  jsr  ${it.key.name}.prog8_init_vars") |                 asmgen.out("  jsr  ${it.key.name}.prog8_init_vars") | ||||||
| @@ -509,17 +631,15 @@ internal class ProgramAndVarsGen( | |||||||
|  |  | ||||||
|         stringVarsWithInitInZp.forEach { |         stringVarsWithInitInZp.forEach { | ||||||
|             val varname = asmgen.asmVariableName(it.name)+"_init_value" |             val varname = asmgen.asmVariableName(it.name)+"_init_value" | ||||||
|             outputStringvar(varname, it.value.second, it.value.first) |             outputStringvar(varname, 0u, it.value.second, it.value.first) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         arrayVarsWithInitInZp.forEach { |         arrayVarsWithInitInZp.forEach { | ||||||
|             val varname = asmgen.asmVariableName(it.name)+"_init_value" |             val varname = asmgen.asmVariableName(it.name)+"_init_value" | ||||||
|             arrayVariable2asm(varname, it.alloc.dt, it.value, null) |             arrayVariable2asm(varname, it.alloc.dt, 0u, it.value, null) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         asmgen.out("""+         |         asmgen.out("+") | ||||||
|                     clv |  | ||||||
|                     clc""") |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private class ZpStringWithInitial( |     private class ZpStringWithInitial( | ||||||
| @@ -536,24 +656,24 @@ internal class ProgramAndVarsGen( | |||||||
|  |  | ||||||
|     private fun getZpStringVarsWithInitvalue(): Collection<ZpStringWithInitial> { |     private fun getZpStringVarsWithInitvalue(): Collection<ZpStringWithInitial> { | ||||||
|         val result = mutableListOf<ZpStringWithInitial>() |         val result = mutableListOf<ZpStringWithInitial>() | ||||||
|         val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR } |         val vars = allocator.zeropageVars.filter { it.value.dt.isString } | ||||||
|         for (variable in vars) { |         for (variable in vars) { | ||||||
|             val scopedName = variable.key |             val scopedName = variable.key | ||||||
|             val svar = symboltable.lookup(scopedName) as? StStaticVariable |             val svar = symboltable.lookup(scopedName) as? StStaticVariable | ||||||
|             if(svar?.onetimeInitializationStringValue!=null) |             if(svar?.initializationStringValue!=null) | ||||||
|                 result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!)) |                 result.add(ZpStringWithInitial(scopedName, variable.value, svar.initializationStringValue!!)) | ||||||
|         } |         } | ||||||
|         return result |         return result | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun getZpArrayVarsWithInitvalue(): Collection<ZpArrayWithInitial> { |     private fun getZpArrayVarsWithInitvalue(): Collection<ZpArrayWithInitial> { | ||||||
|         val result = mutableListOf<ZpArrayWithInitial>() |         val result = mutableListOf<ZpArrayWithInitial>() | ||||||
|         val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes } |         val vars = allocator.zeropageVars.filter { it.value.dt.isArray } | ||||||
|         for (variable in vars) { |         for (variable in vars) { | ||||||
|             val scopedName = variable.key |             val scopedName = variable.key | ||||||
|             val svar = symboltable.lookup(scopedName) as? StStaticVariable |             val svar = symboltable.lookup(scopedName) as? StStaticVariable | ||||||
|             if(svar?.onetimeInitializationArrayValue!=null) |             if(svar?.initializationArrayValue!=null) | ||||||
|                 result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!)) |                 result.add(ZpArrayWithInitial(scopedName, variable.value, svar.initializationArrayValue!!)) | ||||||
|         } |         } | ||||||
|         return result |         return result | ||||||
|     } |     } | ||||||
| @@ -564,7 +684,7 @@ internal class ProgramAndVarsGen( | |||||||
|             if (scopedName.startsWith("cx16.r")) |             if (scopedName.startsWith("cx16.r")) | ||||||
|                 continue        // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped |                 continue        // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped | ||||||
|             val variable = symboltable.flat.getValue(scopedName) as StStaticVariable |             val variable = symboltable.flat.getValue(scopedName) as StStaticVariable | ||||||
|             if(variable.dt in SplitWordArrayTypes) { |             if(variable.dt.isSplitWordArray) { | ||||||
|                 val lsbAddr = zpvar.address |                 val lsbAddr = zpvar.address | ||||||
|                 val msbAddr = zpvar.address + (zpvar.size/2).toUInt() |                 val msbAddr = zpvar.address + (zpvar.size/2).toUInt() | ||||||
|                 asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)") |                 asmgen.out("${scopedName.substringAfterLast('.')}_lsb \t= $lsbAddr \t; zp ${zpvar.dt} (lsbs)") | ||||||
| @@ -579,87 +699,153 @@ internal class ProgramAndVarsGen( | |||||||
|         asmgen.out("") |         asmgen.out("") | ||||||
|         val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized } |         val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized } | ||||||
|         if(varsNoInit.isNotEmpty()) { |         if(varsNoInit.isNotEmpty()) { | ||||||
|             asmgen.out("; non-zeropage variables without initialization value") |             asmgen.out("; non-zeropage variables") | ||||||
|             asmgen.out("  .section BSS") |             val (dirty, clean) = varsNoInit.partition { it.dirty } | ||||||
|             varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach { |  | ||||||
|  |             fun generate(section: String, variables: List<StStaticVariable>) { | ||||||
|  |                 asmgen.out("  .section $section") | ||||||
|  |                 val (notAligned, aligned) = variables.partition { it.align == 0u } | ||||||
|  |                 notAligned.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt.base }).forEach { | ||||||
|                     uninitializedVariable2asm(it) |                     uninitializedVariable2asm(it) | ||||||
|                 } |                 } | ||||||
|             asmgen.out("  .send BSS") |                 aligned.sortedWith(compareBy<StStaticVariable> { it.align }.thenBy { it.name }.thenBy { it.dt.base }) | ||||||
|  |                     .forEach { uninitializedVariable2asm(it) } | ||||||
|  |                 asmgen.out("  .send $section") | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if(clean.isNotEmpty()) { | ||||||
|  |                 // clean vars end up in BSS so they're at least cleared to 0 at startup | ||||||
|  |                 generate("BSS", clean) | ||||||
|  |             } | ||||||
|  |             if(dirty.isNotEmpty()) { | ||||||
|  |                 // Dirty vars actually are ALSO are put into BSS so they're cleared to 0 at program startup, | ||||||
|  |                 // but NOT at each entry of the subroutine they're declared in. | ||||||
|  |                 // This saves the STZ's instructions in the subroutine, while still having deterministic start state. | ||||||
|  |                 // So there is no actual difference here when compared to the way non-dirty variables are allocated. | ||||||
|  |                 generate("BSS", dirty) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if(varsWithInit.isNotEmpty()) { |         if(varsWithInit.isNotEmpty()) { | ||||||
|             asmgen.out("; non-zeropage variables") |             asmgen.out("; non-zeropage variables with init value") | ||||||
|             val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR } |             val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt.isString } | ||||||
|             stringvars.forEach { |             val (notAlignedStrings, alignedStrings) = stringvars.partition { it.align==0u } | ||||||
|  |             val (notAlignedOther, alignedOther) = othervars.partition { it.align==0u } | ||||||
|  |             notAlignedStrings.forEach { | ||||||
|                 outputStringvar( |                 outputStringvar( | ||||||
|                     it.name, |                     it.name, | ||||||
|                     it.onetimeInitializationStringValue!!.second, |                     it.align, | ||||||
|                     it.onetimeInitializationStringValue!!.first |                     it.initializationStringValue!!.second, | ||||||
|  |                     it.initializationStringValue!!.first | ||||||
|                 ) |                 ) | ||||||
|  |                 asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false)     // TODO print warning with position of the var | ||||||
|             } |             } | ||||||
|             othervars.sortedBy { it.type }.forEach { |             alignedStrings.sortedBy { it.align }.forEach { | ||||||
|  |                 outputStringvar( | ||||||
|  |                     it.name, | ||||||
|  |                     it.align, | ||||||
|  |                     it.initializationStringValue!!.second, | ||||||
|  |                     it.initializationStringValue!!.first | ||||||
|  |                 ) | ||||||
|  |                 asmgen.romableError("string (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false)     // TODO print warning with position of the var | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             notAlignedOther.sortedBy { it.type }.forEach { | ||||||
|                 staticVariable2asm(it) |                 staticVariable2asm(it) | ||||||
|  |                 if(it.dt.isArray) | ||||||
|  |                     asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false)     // TODO print warning with position of the var | ||||||
|  |                 else | ||||||
|  |                     asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY)       // TODO print warning with position of the var | ||||||
|  |             } | ||||||
|  |             alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach { | ||||||
|  |                 staticVariable2asm(it) | ||||||
|  |                 if(it.dt.isArray) | ||||||
|  |                     asmgen.romableError("array (${it.dt} ${it.name}) can only be used as read-only in ROMable code", Position.DUMMY, false)     // TODO print warning with position of the var | ||||||
|  |                 else | ||||||
|  |                     asmgen.romableError("inlined variable (${it.dt} ${it.name})", Position.DUMMY)       // TODO print warning with position of the var | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun uninitializedVariable2asm(variable: StStaticVariable) { |     private fun uninitializedVariable2asm(variable: StStaticVariable) { | ||||||
|         when (variable.dt) { |         val dt = variable.dt | ||||||
|             DataType.BOOL, DataType.UBYTE -> asmgen.out("${variable.name}\t.byte  ?") |         when { | ||||||
|             DataType.BYTE -> asmgen.out("${variable.name}\t.char  ?") |             dt.isBool || dt.isUnsignedByte -> asmgen.out("${variable.name}\t.byte  ?") | ||||||
|             DataType.UWORD -> asmgen.out("${variable.name}\t.word  ?") |             dt.isSignedByte -> asmgen.out("${variable.name}\t.char  ?") | ||||||
|             DataType.WORD -> asmgen.out("${variable.name}\t.sint  ?") |             dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word  ?") | ||||||
|             DataType.FLOAT -> asmgen.out("${variable.name}\t.fill  ${compTarget.machine.FLOAT_MEM_SIZE}") |             dt.isSignedWord -> asmgen.out("${variable.name}\t.sint  ?") | ||||||
|             in SplitWordArrayTypes -> { |             dt.isFloat -> asmgen.out("${variable.name}\t.fill  ${compTarget.FLOAT_MEM_SIZE}") | ||||||
|                 val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!) / 2 |             dt.isSplitWordArray -> { | ||||||
|  |                 alignVar(variable.align) | ||||||
|  |                 val numbytesPerHalf = compTarget.memorySize(variable.dt, variable.length!!.toInt()) / 2 | ||||||
|                 asmgen.out("${variable.name}_lsb\t.fill  $numbytesPerHalf") |                 asmgen.out("${variable.name}_lsb\t.fill  $numbytesPerHalf") | ||||||
|                 asmgen.out("${variable.name}_msb\t.fill  $numbytesPerHalf") |                 asmgen.out("${variable.name}_msb\t.fill  $numbytesPerHalf") | ||||||
|             } |             } | ||||||
|             in ArrayDatatypes -> { |             dt.isArray -> { | ||||||
|                 val numbytes = compTarget.memorySize(variable.dt, variable.length!!) |                 alignVar(variable.align) | ||||||
|  |                 val numbytes = compTarget.memorySize(variable.dt, variable.length!!.toInt()) | ||||||
|                 asmgen.out("${variable.name}\t.fill  $numbytes") |                 asmgen.out("${variable.name}\t.fill  $numbytes") | ||||||
|             } |             } | ||||||
|  |             dt.isPointer -> asmgen.out("${variable.name}\t.word  ?")        // a pointer is just an uword address | ||||||
|  |             dt.isPointerArray -> { | ||||||
|  |                 TODO("pointers are not supported yet") | ||||||
|  |             } | ||||||
|             else -> { |             else -> { | ||||||
|                 throw AssemblyError("weird dt") |                 throw AssemblyError("weird dt") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun alignVar(align: UInt) { | ||||||
|  |         if(align > 1u) | ||||||
|  |             asmgen.out("  .align  ${align.toHex()}") | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun staticVariable2asm(variable: StStaticVariable) { |     private fun staticVariable2asm(variable: StStaticVariable) { | ||||||
|         val initialValue: Number = |         if(!variable.dt.isArray && !variable.dt.isString) { | ||||||
|             if(variable.onetimeInitializationNumericValue!=null) { |             throw AssemblyError("static variables with an initialization value can only be an array or a string, not ${variable.dt} (${variable.name} ${variable.astNode?.position}") | ||||||
|                 if(variable.dt== DataType.FLOAT) |             // because numeric variables are in the BSS section and get initialized via assignment statements | ||||||
|                     variable.onetimeInitializationNumericValue!! |         } | ||||||
|                 else |  | ||||||
|                     variable.onetimeInitializationNumericValue!!.toInt() |  | ||||||
|             } else 0 |  | ||||||
|  |  | ||||||
|         when (variable.dt) { | //        val initialValue: Number = | ||||||
|             DataType.BOOL, DataType.UBYTE -> asmgen.out("${variable.name}\t.byte  ${initialValue.toHex()}") | //            if(variable.initializationNumericValue!=null) { | ||||||
|             DataType.BYTE -> asmgen.out("${variable.name}\t.char  $initialValue") | //                if(variable.dt.isFloat) | ||||||
|             DataType.UWORD -> asmgen.out("${variable.name}\t.word  ${initialValue.toHex()}") | //                    variable.initializationNumericValue!! | ||||||
|             DataType.WORD -> asmgen.out("${variable.name}\t.sint  $initialValue") | //                else | ||||||
|             DataType.FLOAT -> { | //                    variable.initializationNumericValue!!.toInt() | ||||||
|                 if(initialValue==0) { | //            } else 0 | ||||||
|                     asmgen.out("${variable.name}\t.byte  0,0,0,0,0  ; float") | // | ||||||
|                 } else { |  | ||||||
|                     val floatFill = compTarget.machine.getFloatAsmBytes(initialValue) |         val dt=variable.dt | ||||||
|                     asmgen.out("${variable.name}\t.byte  $floatFill  ; float $initialValue") |         when { | ||||||
|                 } | //            dt.isBool || dt.isUnsignedByte -> asmgen.out("${variable.name}\t.byte  ${initialValue.toHex()}") | ||||||
|             } | //            dt.isSignedByte -> asmgen.out("${variable.name}\t.char  $initialValue") | ||||||
|             DataType.STR -> { | //            dt.isUnsignedWord -> asmgen.out("${variable.name}\t.word  ${initialValue.toHex()}") | ||||||
|  | //            dt.isSignedWord -> asmgen.out("${variable.name}\t.sint  $initialValue") | ||||||
|  | //            dt.isFloat -> { | ||||||
|  | //                if(initialValue==0) { | ||||||
|  | //                    asmgen.out("${variable.name}\t.byte  0,0,0,0,0  ; float") | ||||||
|  | //                } else { | ||||||
|  | //                    val floatFill = compTarget.getFloatAsmBytes(initialValue) | ||||||
|  | //                    asmgen.out("${variable.name}\t.byte  $floatFill  ; float $initialValue") | ||||||
|  | //                } | ||||||
|  | //            } | ||||||
|  |             dt.isString -> { | ||||||
|                 throw AssemblyError("all string vars should have been interned into prog") |                 throw AssemblyError("all string vars should have been interned into prog") | ||||||
|             } |             } | ||||||
|             in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length) |             dt.isArray -> { | ||||||
|  |                 arrayVariable2asm(variable.name, variable.dt, variable.align, variable.initializationArrayValue, variable.length?.toInt()) | ||||||
|  |             } | ||||||
|             else -> { |             else -> { | ||||||
|                 throw AssemblyError("weird dt") |                 throw AssemblyError("weird dt") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun arrayVariable2asm(varname: String, dt: DataType, value: StArray?, orNumberOfZeros: Int?) { |     private fun arrayVariable2asm(varname: String, dt: DataType, align: UInt, value: StArray?, orNumberOfZeros: Int?) { | ||||||
|         when(dt) { |         alignVar(align) | ||||||
|             DataType.ARRAY_UB, DataType.ARRAY_BOOL -> { |         when { | ||||||
|  |             dt.isUnsignedByteArray || dt.isBoolArray -> { | ||||||
|                 val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) |                 val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) | ||||||
|                 if (data.size <= 16) |                 if (data.size <= 16) | ||||||
|                     asmgen.out("$varname\t.byte  ${data.joinToString()}") |                     asmgen.out("$varname\t.byte  ${data.joinToString()}") | ||||||
| @@ -669,7 +855,7 @@ internal class ProgramAndVarsGen( | |||||||
|                         asmgen.out("  .byte  " + chunk.joinToString()) |                         asmgen.out("  .byte  " + chunk.joinToString()) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_B -> { |             dt.isSignedByteArray -> { | ||||||
|                 val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) |                 val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) | ||||||
|                 if (data.size <= 16) |                 if (data.size <= 16) | ||||||
|                     asmgen.out("$varname\t.char  ${data.joinToString()}") |                     asmgen.out("$varname\t.char  ${data.joinToString()}") | ||||||
| @@ -679,7 +865,20 @@ internal class ProgramAndVarsGen( | |||||||
|                         asmgen.out("  .char  " + chunk.joinToString()) |                         asmgen.out("  .char  " + chunk.joinToString()) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_UW -> { |             dt.isSplitWordArray -> { | ||||||
|  |                 if(dt.elementType().isUnsignedWord) { | ||||||
|  |                     val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) | ||||||
|  |                     asmgen.out("_array_$varname := ${data.joinToString()}") | ||||||
|  |                     asmgen.out("${varname}_lsb\t.byte <_array_$varname") | ||||||
|  |                     asmgen.out("${varname}_msb\t.byte >_array_$varname") | ||||||
|  |                 } else { | ||||||
|  |                     val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) | ||||||
|  |                     asmgen.out("_array_$varname := ${data.joinToString()}") | ||||||
|  |                     asmgen.out("${varname}_lsb\t.byte <_array_$varname") | ||||||
|  |                     asmgen.out("${varname}_msb\t.byte >_array_$varname") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             dt.isUnsignedWordArray -> { | ||||||
|                 val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) |                 val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) | ||||||
|                 if (data.size <= 16) |                 if (data.size <= 16) | ||||||
|                     asmgen.out("$varname\t.word  ${data.joinToString()}") |                     asmgen.out("$varname\t.word  ${data.joinToString()}") | ||||||
| @@ -689,7 +888,7 @@ internal class ProgramAndVarsGen( | |||||||
|                         asmgen.out("  .word  " + chunk.joinToString()) |                         asmgen.out("  .word  " + chunk.joinToString()) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_W -> { |             dt.isSignedWordArray -> { | ||||||
|                 val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) |                 val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) | ||||||
|                 if (data.size <= 16) |                 if (data.size <= 16) | ||||||
|                     asmgen.out("$varname\t.sint  ${data.joinToString()}") |                     asmgen.out("$varname\t.sint  ${data.joinToString()}") | ||||||
| @@ -699,22 +898,10 @@ internal class ProgramAndVarsGen( | |||||||
|                         asmgen.out("  .sint  " + chunk.joinToString()) |                         asmgen.out("  .sint  " + chunk.joinToString()) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_UW_SPLIT -> { |             dt.isFloatArray -> { | ||||||
|                 val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) |  | ||||||
|                 asmgen.out("_array_$varname := ${data.joinToString()}") |  | ||||||
|                 asmgen.out("${varname}_lsb\t.byte <_array_$varname") |  | ||||||
|                 asmgen.out("${varname}_msb\t.byte >_array_$varname") |  | ||||||
|             } |  | ||||||
|             DataType.ARRAY_W_SPLIT -> { |  | ||||||
|                 val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) |  | ||||||
|                 asmgen.out("_array_$varname := ${data.joinToString()}") |  | ||||||
|                 asmgen.out("${varname}_lsb\t.byte <_array_$varname") |  | ||||||
|                 asmgen.out("${varname}_msb\t.byte >_array_$varname") |  | ||||||
|             } |  | ||||||
|             DataType.ARRAY_F -> { |  | ||||||
|                 val array = value ?: zeroFilledArray(orNumberOfZeros!!) |                 val array = value ?: zeroFilledArray(orNumberOfZeros!!) | ||||||
|                 val floatFills = array.map { |                 val floatFills = array.map { | ||||||
|                     compTarget.machine.getFloatAsmBytes(it.number!!) |                     compTarget.getFloatAsmBytes(it.number!!) | ||||||
|                 } |                 } | ||||||
|                 asmgen.out(varname) |                 asmgen.out(varname) | ||||||
|                 for (f in array.zip(floatFills)) |                 for (f in array.zip(floatFills)) | ||||||
| @@ -737,7 +924,7 @@ internal class ProgramAndVarsGen( | |||||||
|             asmgen.out("  ${it.name} = ${it.address.toHex()}") |             asmgen.out("  ${it.name} = ${it.address.toHex()}") | ||||||
|         } |         } | ||||||
|         consts.sortedBy { it.name }.forEach { |         consts.sortedBy { it.name }.forEach { | ||||||
|             if(it.dt==DataType.FLOAT) |             if(it.dt==BaseDataType.FLOAT) | ||||||
|                 asmgen.out("  ${it.name} = ${it.value}") |                 asmgen.out("  ${it.name} = ${it.value}") | ||||||
|             else |             else | ||||||
|                 asmgen.out("  ${it.name} = ${it.value.toHex()}") |                 asmgen.out("  ${it.name} = ${it.value.toHex()}") | ||||||
| @@ -749,11 +936,16 @@ internal class ProgramAndVarsGen( | |||||||
|             .filter { it is PtAsmSub && it.address!=null } |             .filter { it is PtAsmSub && it.address!=null } | ||||||
|             .forEach { asmsub -> |             .forEach { asmsub -> | ||||||
|                 asmsub as PtAsmSub |                 asmsub as PtAsmSub | ||||||
|                 asmgen.out("  ${asmsub.name} = ${asmsub.address!!.toHex()}") |                 val address = asmsub.address!! | ||||||
|  |                 val bank = if(address.constbank!=null) "; @bank ${address.constbank}" | ||||||
|  |                     else if(address.varbank!=null) "; @bank ${address.varbank?.name}" | ||||||
|  |                     else "" | ||||||
|  |                 asmgen.out("  ${asmsub.name} = ${address.address.toHex()} $bank") | ||||||
|             } |             } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun outputStringvar(varname: String, encoding: Encoding, value: String) { |     private fun outputStringvar(varname: String, align: UInt, encoding: Encoding, value: String) { | ||||||
|  |         alignVar(align) | ||||||
|         asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"", false) |         asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"", false) | ||||||
|         val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte()) |         val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte()) | ||||||
|         val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') } |         val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') } | ||||||
| @@ -763,8 +955,8 @@ internal class ProgramAndVarsGen( | |||||||
|  |  | ||||||
|     private fun makeArrayFillDataUnsigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> { |     private fun makeArrayFillDataUnsigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> { | ||||||
|         val array = value ?: zeroFilledArray(orNumberOfZeros!!) |         val array = value ?: zeroFilledArray(orNumberOfZeros!!) | ||||||
|         return when (dt) { |         return when { | ||||||
|             DataType.ARRAY_BOOL -> |             dt.isBoolArray -> | ||||||
|                 // byte array can never contain pointer-to types, so treat values as all integers |                 // byte array can never contain pointer-to types, so treat values as all integers | ||||||
|                 array.map { |                 array.map { | ||||||
|                     if(it.boolean!=null) |                     if(it.boolean!=null) | ||||||
| @@ -774,18 +966,23 @@ internal class ProgramAndVarsGen( | |||||||
|                         if(number==0.0) "0" else "1" |                         if(number==0.0) "0" else "1" | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             DataType.ARRAY_UB -> |             dt.isUnsignedByteArray -> | ||||||
|                 // byte array can never contain pointer-to types, so treat values as all integers |                 // byte array can never contain pointer-to types, so treat values as all integers | ||||||
|                 array.map { |                 array.map { | ||||||
|                     val number = it.number!!.toInt() |                     val number = it.number!!.toInt() | ||||||
|                     "$"+number.toString(16).padStart(2, '0') |                     "$"+number.toString(16).padStart(2, '0') | ||||||
|                 } |                 } | ||||||
|             DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map { |             dt.isArray && dt.elementType().isUnsignedWord -> array.map { | ||||||
|                 if(it.number!=null) { |                 if(it.number!=null) { | ||||||
|                     "$" + it.number!!.toInt().toString(16).padStart(4, '0') |                     "$" + it.number!!.toInt().toString(16).padStart(4, '0') | ||||||
|                 } |                 } | ||||||
|                 else if(it.addressOfSymbol!=null) { |                 else if(it.addressOfSymbol!=null) { | ||||||
|                     asmgen.asmSymbolName(it.addressOfSymbol!!) |                     val addrOfSymbol = it.addressOfSymbol!! | ||||||
|  |                     val symbol = symboltable.lookup(addrOfSymbol)!! | ||||||
|  |                     if(symbol is StStaticVariable && symbol.dt.isSplitWordArray) | ||||||
|  |                         asmgen.asmSymbolName(addrOfSymbol+"_lsb")  // the _lsb split array comes first in memory | ||||||
|  |                     else | ||||||
|  |                         asmgen.asmSymbolName(addrOfSymbol) | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                     throw AssemblyError("weird array elt") |                     throw AssemblyError("weird array elt") | ||||||
| @@ -796,14 +993,14 @@ internal class ProgramAndVarsGen( | |||||||
|  |  | ||||||
|     private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> { |     private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> { | ||||||
|         val array = value ?: zeroFilledArray(orNumberOfZeros!!) |         val array = value ?: zeroFilledArray(orNumberOfZeros!!) | ||||||
|         return when (dt) { |         return when { | ||||||
|             // byte array can never contain pointer-to types, so treat values as all integers |             // byte array can never contain pointer-to types, so treat values as all integers | ||||||
|             DataType.ARRAY_UB -> |             dt.isUnsignedByteArray -> | ||||||
|                 array.map { |                 array.map { | ||||||
|                     val number = it.number!!.toInt() |                     val number = it.number!!.toInt() | ||||||
|                     "$"+number.toString(16).padStart(2, '0') |                     "$"+number.toString(16).padStart(2, '0') | ||||||
|                 } |                 } | ||||||
|             DataType.ARRAY_B -> |             dt.isSignedByteArray -> | ||||||
|                 array.map { |                 array.map { | ||||||
|                     val number = it.number!!.toInt() |                     val number = it.number!!.toInt() | ||||||
|                     val hexnum = number.absoluteValue.toString(16).padStart(2, '0') |                     val hexnum = number.absoluteValue.toString(16).padStart(2, '0') | ||||||
| @@ -812,11 +1009,11 @@ internal class ProgramAndVarsGen( | |||||||
|                     else |                     else | ||||||
|                         "-$$hexnum" |                         "-$$hexnum" | ||||||
|                 } |                 } | ||||||
|             DataType.ARRAY_UW, DataType.ARRAY_UW_SPLIT -> array.map { |             dt.isArray && (dt.elementType().isUnsignedWord || dt.elementType().isPointer) -> array.map { | ||||||
|                 val number = it.number!!.toInt() |                 val number = it.number!!.toInt() | ||||||
|                 "$" + number.toString(16).padStart(4, '0') |                 "$" + number.toString(16).padStart(4, '0') | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_W, DataType.ARRAY_W_SPLIT -> array.map { |             dt.isArray && dt.elementType().isSignedWord -> array.map { | ||||||
|                 val number = it.number!!.toInt() |                 val number = it.number!!.toInt() | ||||||
|                 val hexnum = number.absoluteValue.toString(16).padStart(4, '0') |                 val hexnum = number.absoluteValue.toString(16).padStart(4, '0') | ||||||
|                 if(number>=0) |                 if(number>=0) | ||||||
|   | |||||||
| @@ -2,10 +2,7 @@ package prog8.codegen.cpu6502 | |||||||
|  |  | ||||||
| import com.github.michaelbull.result.fold | import com.github.michaelbull.result.fold | ||||||
| import com.github.michaelbull.result.onSuccess | import com.github.michaelbull.result.onSuccess | ||||||
| import prog8.code.StNode | import prog8.code.* | ||||||
| import prog8.code.StNodeType |  | ||||||
| import prog8.code.StStaticVariable |  | ||||||
| import prog8.code.SymbolTable |  | ||||||
| import prog8.code.core.* | import prog8.code.core.* | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -14,7 +11,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable, | |||||||
|                                  private val errors: IErrorReporter |                                  private val errors: IErrorReporter | ||||||
| ) { | ) { | ||||||
|  |  | ||||||
|     private val zeropage = options.compTarget.machine.zeropage |     private val zeropage = options.compTarget.zeropage | ||||||
|     internal val globalFloatConsts = mutableMapOf<Double, String>()     // all float values in the entire program (value -> varname) |     internal val globalFloatConsts = mutableMapOf<Double, String>()     // all float values in the entire program (value -> varname) | ||||||
|     internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation> |     internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation> | ||||||
|  |  | ||||||
| @@ -23,7 +20,13 @@ internal class VariableAllocator(private val symboltable: SymbolTable, | |||||||
|         zeropageVars = zeropage.allocatedVariables |         zeropageVars = zeropage.allocatedVariables | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     internal fun isZpVar(scopedName: String) = scopedName in zeropageVars |     internal fun isZpVar(scopedName: String): Boolean { | ||||||
|  |         if(scopedName in zeropageVars) | ||||||
|  |             return true | ||||||
|  |  | ||||||
|  |         val v = symboltable.lookup(scopedName) | ||||||
|  |         return if(v is StMemVar) v.address <= 255u else false | ||||||
|  |     } | ||||||
|  |  | ||||||
|     internal fun getFloatAsmConst(number: Double): String { |     internal fun getFloatAsmConst(number: Double): String { | ||||||
|         val asmName = globalFloatConsts[number] |         val asmName = globalFloatConsts[number] | ||||||
| @@ -49,18 +52,18 @@ internal class VariableAllocator(private val symboltable: SymbolTable, | |||||||
|         val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE } |         val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE } | ||||||
|         val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE } |         val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE } | ||||||
|         val varsNotZp = allVariables.filter { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE } |         val varsNotZp = allVariables.filter { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE } | ||||||
|         val varsDontCare = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE } |         val (varsDontCareWithoutAlignment, varsDontCareWithAlignment) = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }.partition { it.align == 0u } | ||||||
|         require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables) |         require(varsDontCareWithAlignment.size + varsDontCareWithoutAlignment.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables) | ||||||
|  |  | ||||||
|         var numVariablesAllocatedInZP = 0 |         var numVariablesAllocatedInZP = 0 | ||||||
|         var numberOfNonIntegerVariables = 0 |         var numberOfNonIntegerVariables = 0 | ||||||
|  |  | ||||||
|         varsRequiringZp.forEach { variable -> |         varsRequiringZp.forEach { variable -> | ||||||
|             val result = zeropage.allocate( |             val result = zeropage.allocate( | ||||||
|                 variable.scopedName, |                 variable.scopedNameString, | ||||||
|                 variable.dt, |                 variable.dt, | ||||||
|                 variable.length, |                 variable.length?.toInt(), | ||||||
|                 variable.astNode.position, |                 variable.astNode?.position ?: Position.DUMMY, | ||||||
|                 errors |                 errors | ||||||
|             ) |             ) | ||||||
|             result.fold( |             result.fold( | ||||||
| @@ -68,7 +71,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable, | |||||||
|                     numVariablesAllocatedInZP++ |                     numVariablesAllocatedInZP++ | ||||||
|                 }, |                 }, | ||||||
|                 failure = { |                 failure = { | ||||||
|                     errors.err(it.message!!, variable.astNode.position) |                     errors.err(it.message!!, variable.astNode?.position ?: Position.DUMMY) | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| @@ -76,10 +79,10 @@ internal class VariableAllocator(private val symboltable: SymbolTable, | |||||||
|         if(errors.noErrors()) { |         if(errors.noErrors()) { | ||||||
|             varsPreferringZp.forEach { variable -> |             varsPreferringZp.forEach { variable -> | ||||||
|                 val result = zeropage.allocate( |                 val result = zeropage.allocate( | ||||||
|                     variable.scopedName, |                     variable.scopedNameString, | ||||||
|                     variable.dt, |                     variable.dt, | ||||||
|                     variable.length, |                     variable.length?.toInt(), | ||||||
|                     variable.astNode.position, |                     variable.astNode?.position ?: Position.DUMMY, | ||||||
|                     errors |                     errors | ||||||
|                 ) |                 ) | ||||||
|                 result.onSuccess { numVariablesAllocatedInZP++ } |                 result.onSuccess { numVariablesAllocatedInZP++ } | ||||||
| @@ -89,17 +92,17 @@ internal class VariableAllocator(private val symboltable: SymbolTable, | |||||||
|             // try to allocate the "don't care" interger variables into the zeropage until it is full. |             // try to allocate the "don't care" interger variables into the zeropage until it is full. | ||||||
|             // TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? |             // TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? | ||||||
|             if(errors.noErrors()) { |             if(errors.noErrors()) { | ||||||
|                 val sortedList = varsDontCare.sortedByDescending { it.scopedName } |                 val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString } | ||||||
|                 for (variable in sortedList) { |                 for (variable in sortedList) { | ||||||
|                     if(variable.dt in IntegerDatatypesWithBoolean) { |                     if(variable.dt.isIntegerOrBool || variable.dt.isPointer) { | ||||||
|                         if(zeropage.free.isEmpty()) { |                         if(zeropage.free.isEmpty()) { | ||||||
|                             break |                             break | ||||||
|                         } else { |                         } else { | ||||||
|                             val result = zeropage.allocate( |                             val result = zeropage.allocate( | ||||||
|                                 variable.scopedName, |                                 variable.scopedNameString, | ||||||
|                                 variable.dt, |                                 variable.dt, | ||||||
|                                 variable.length, |                                 variable.length?.toInt(), | ||||||
|                                 variable.astNode.position, |                                 variable.astNode?.position ?: Position.DUMMY, | ||||||
|                                 errors |                                 errors | ||||||
|                             ) |                             ) | ||||||
|                             result.onSuccess { numVariablesAllocatedInZP++ } |                             result.onSuccess { numVariablesAllocatedInZP++ } | ||||||
| @@ -124,6 +127,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable, | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         collect(st) |         collect(st) | ||||||
|         return vars.sortedBy { it.dt } |         return vars.sortedBy { it.dt.base } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ package prog8.codegen.cpu6502.assignment | |||||||
|  |  | ||||||
| import prog8.code.ast.PtBinaryExpression | import prog8.code.ast.PtBinaryExpression | ||||||
| import prog8.code.ast.PtExpression | import prog8.code.ast.PtExpression | ||||||
| import prog8.code.core.* | import prog8.code.core.AssemblyError | ||||||
|  | import prog8.code.core.ComparisonOperators | ||||||
|  | import prog8.code.core.DataType | ||||||
|  | import prog8.code.core.RegisterOrPair | ||||||
| import prog8.code.target.C64Target | import prog8.code.target.C64Target | ||||||
| import prog8.code.target.Cx16Target | import prog8.code.target.Cx16Target | ||||||
| import prog8.codegen.cpu6502.AsmGen6502Internal | import prog8.codegen.cpu6502.AsmGen6502Internal | ||||||
| @@ -17,32 +20,40 @@ internal class AnyExprAsmGen( | |||||||
|     private val asmgen: AsmGen6502Internal |     private val asmgen: AsmGen6502Internal | ||||||
| ) { | ) { | ||||||
|     fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { |     fun assignAnyExpressionUsingStack(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { | ||||||
|         when(expr.type) { |         if(expr.operator==".") | ||||||
|             in ByteDatatypesWithBoolean -> { |             throw AssemblyError("pointer deref expression should have been handled elsewhere ${expr.position}") | ||||||
|                 if(expr.left.type in ByteDatatypesWithBoolean && expr.right.type in ByteDatatypesWithBoolean) |         when { | ||||||
|  |             expr.type.isByteOrBool -> { | ||||||
|  |                 if(expr.left.type.isByteOrBool && expr.right.type.isByteOrBool) | ||||||
|                     return assignByteBinExpr(expr, assign) |                     return assignByteBinExpr(expr, assign) | ||||||
|                 if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) { |                 if (expr.left.type.isWord && expr.right.type.isWord) { | ||||||
|                     require(expr.operator in ComparisonOperators) |                     require(expr.operator in ComparisonOperators) | ||||||
|                     throw AssemblyError("words operands comparison -> byte, should have been handled elsewhere") |                     throw AssemblyError("words operands comparison -> byte, should have been handled elsewhere") | ||||||
|                 } |                 } | ||||||
|                 if (expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) { |                 if (expr.left.type.isFloat && expr.right.type.isFloat) { | ||||||
|                     require(expr.operator in ComparisonOperators) |                     require(expr.operator in ComparisonOperators) | ||||||
|                     return assignFloatBinExpr(expr, assign) |                     return assignFloatBinExpr(expr, assign) | ||||||
|                 } |                 } | ||||||
|                 throw AssemblyError("weird expr operand types: ${expr.left.type} and ${expr.right.type}") |                 throw AssemblyError("weird expr operand types: ${expr.left.type} and ${expr.right.type}") | ||||||
|             } |             } | ||||||
|             in WordDatatypes -> { |             expr.type.isWord -> { | ||||||
|                 require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) { |                 require(expr.left.type.isWord && expr.right.type.isWord) { | ||||||
|                     "both operands must be words" |                     "both operands must be words" | ||||||
|                 } |                 } | ||||||
|                 throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}") |                 throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}") | ||||||
|             } |             } | ||||||
|             DataType.FLOAT -> { |             expr.type.isFloat -> { | ||||||
|                 require(expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) { |                 require(expr.left.type.isFloat && expr.right.type.isFloat) { | ||||||
|                     "both operands must be floats" |                     "both operands must be floats" | ||||||
|                 } |                 } | ||||||
|                 return assignFloatBinExpr(expr, assign) |                 return assignFloatBinExpr(expr, assign) | ||||||
|             } |             } | ||||||
|  |             expr.type.isPointer -> { | ||||||
|  |                 require((expr.left.type.isPointer || expr.left.type.isUnsignedWord) && (expr.right.type.isPointer || expr.right.type.isUnsignedWord)) { | ||||||
|  |                     "both operands must be pointers or uwords" | ||||||
|  |                 } | ||||||
|  |                 throw AssemblyError("expression should have been handled otherwise: pointer ${expr.operator} at ${expr.position}") | ||||||
|  |             } | ||||||
|             else -> throw AssemblyError("weird expression type in assignment") |             else -> throw AssemblyError("weird expression type in assignment") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -50,7 +61,7 @@ internal class AnyExprAsmGen( | |||||||
|     private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { |     private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean { | ||||||
|         when(expr.operator) { |         when(expr.operator) { | ||||||
|             "+" -> { |             "+" -> { | ||||||
|                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) |                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A) | ||||||
|                 asmgen.out("  pha") |                 asmgen.out("  pha") | ||||||
|                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) |                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) | ||||||
|                 asmgen.out("  pla |  clc |  adc  P8ZP_SCRATCH_B1") |                 asmgen.out("  pla |  clc |  adc  P8ZP_SCRATCH_B1") | ||||||
| @@ -58,7 +69,7 @@ internal class AnyExprAsmGen( | |||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|             "-" -> { |             "-" -> { | ||||||
|                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) |                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A) | ||||||
|                 asmgen.out("  pha") |                 asmgen.out("  pha") | ||||||
|                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) |                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) | ||||||
|                 asmgen.out("  pla |  sec |  sbc  P8ZP_SCRATCH_B1") |                 asmgen.out("  pla |  sec |  sbc  P8ZP_SCRATCH_B1") | ||||||
| @@ -73,7 +84,7 @@ internal class AnyExprAsmGen( | |||||||
|             "and" -> TODO("logical and (with optional shortcircuit) ${expr.position}") |             "and" -> TODO("logical and (with optional shortcircuit) ${expr.position}") | ||||||
|             "or" -> TODO("logical or (with optional shortcircuit) ${expr.position}") |             "or" -> TODO("logical or (with optional shortcircuit) ${expr.position}") | ||||||
|             "&" -> { |             "&" -> { | ||||||
|                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) |                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A) | ||||||
|                 asmgen.out("  pha") |                 asmgen.out("  pha") | ||||||
|                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) |                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) | ||||||
|                 asmgen.out("  pla |  and  P8ZP_SCRATCH_B1") |                 asmgen.out("  pla |  and  P8ZP_SCRATCH_B1") | ||||||
| @@ -81,7 +92,7 @@ internal class AnyExprAsmGen( | |||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|             "|" -> { |             "|" -> { | ||||||
|                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) |                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A) | ||||||
|                 asmgen.out("  pha") |                 asmgen.out("  pha") | ||||||
|                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) |                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) | ||||||
|                 asmgen.out("  pla |  ora  P8ZP_SCRATCH_B1") |                 asmgen.out("  pla |  ora  P8ZP_SCRATCH_B1") | ||||||
| @@ -89,7 +100,7 @@ internal class AnyExprAsmGen( | |||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|             "^", "xor" -> { |             "^", "xor" -> { | ||||||
|                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false) |                 asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A) | ||||||
|                 asmgen.out("  pha") |                 asmgen.out("  pha") | ||||||
|                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) |                 asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) | ||||||
|                 asmgen.out("  pla |  eor  P8ZP_SCRATCH_B1") |                 asmgen.out("  pla |  eor  P8ZP_SCRATCH_B1") | ||||||
| @@ -175,25 +186,22 @@ internal class AnyExprAsmGen( | |||||||
|     private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) { |     private fun assignFloatOperandsToFACandARG(left: PtExpression, right: PtExpression) { | ||||||
|         when(asmgen.options.compTarget.name) { |         when(asmgen.options.compTarget.name) { | ||||||
|             C64Target.NAME -> { |             C64Target.NAME -> { | ||||||
|                 // c64 has a quirk: always make sure FAC2 is loaded last (done using CONUPK)  otherwise the result will be corrupt on C64 |                 // C64 math library has a quirk: you have always make sure FAC2/ARG is loaded last (done using CONUPK) | ||||||
|                 // this requires some more forced copying around of float values in certain cases |                 // otherwise the result of certain floating point operations such as FDIVT will be wrong. | ||||||
|                 if (right.isSimple()) { |                 // see https://www.c64-wiki.com/wiki/CONUPK | ||||||
|                     asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true) |                 // Unfortunately this means we have to push and pop an intermediary floating point value to and from memory. | ||||||
|                     asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true) |  | ||||||
|                 } else { |  | ||||||
|                 asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true) |                 asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC1, true) | ||||||
|                 asmgen.pushFAC1() |                 asmgen.pushFAC1() | ||||||
|                 asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true) |                 asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true) | ||||||
|                 asmgen.popFAC2() |                 asmgen.popFAC2() | ||||||
|             } |             } | ||||||
|             } |  | ||||||
|             Cx16Target.NAME -> { |             Cx16Target.NAME -> { | ||||||
|                 asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true) |                 asmgen.assignExpressionToRegister(left, RegisterOrPair.FAC1, true) | ||||||
|                 if (!right.isSimple()) asmgen.pushFAC1() |                 if (!right.isSimple()) asmgen.pushFAC1() | ||||||
|                 asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true) |                 asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true) | ||||||
|                 if (!right.isSimple()) asmgen.popFAC1() |                 if (!right.isSimple()) asmgen.popFAC1() | ||||||
|             } |             } | ||||||
|             else -> TODO("don't know how to evaluate float expression for selected compilation target") |             else -> TODO("don't know how to evaluate float expression for selected compilation target  ${left.position}") | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,14 +3,15 @@ package prog8.codegen.cpu6502.assignment | |||||||
| import prog8.code.ast.* | import prog8.code.ast.* | ||||||
| import prog8.code.core.* | import prog8.code.core.* | ||||||
| import prog8.codegen.cpu6502.AsmGen6502Internal | import prog8.codegen.cpu6502.AsmGen6502Internal | ||||||
| import prog8.codegen.cpu6502.returnsWhatWhere |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal enum class TargetStorageKind { | internal enum class TargetStorageKind { | ||||||
|     VARIABLE, |     VARIABLE,       // non-pointer variable | ||||||
|     ARRAY, |     ARRAY, | ||||||
|     MEMORY, |     MEMORY, | ||||||
|     REGISTER |     REGISTER, | ||||||
|  |     POINTER,        // wherever the pointer variable points to | ||||||
|  |     VOID            // assign nothing - used in multi-value assigns for void placeholders | ||||||
| } | } | ||||||
|  |  | ||||||
| internal enum class SourceStorageKind { | internal enum class SourceStorageKind { | ||||||
| @@ -32,6 +33,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind, | |||||||
|                                val array: PtArrayIndexer? = null, |                                val array: PtArrayIndexer? = null, | ||||||
|                                val memory: PtMemoryByte? = null, |                                val memory: PtMemoryByte? = null, | ||||||
|                                val register: RegisterOrPair? = null, |                                val register: RegisterOrPair? = null, | ||||||
|  |                                val pointer: PtPointerDeref? = null, | ||||||
|                                val origAstTarget: PtAssignTarget? = null |                                val origAstTarget: PtAssignTarget? = null | ||||||
|                                ) |                                ) | ||||||
| { | { | ||||||
| @@ -39,16 +41,36 @@ internal class AsmAssignTarget(val kind: TargetStorageKind, | |||||||
|     val asmVarname: String by lazy { |     val asmVarname: String by lazy { | ||||||
|         if (array == null) |         if (array == null) | ||||||
|             variableAsmName!! |             variableAsmName!! | ||||||
|         else |         else { | ||||||
|             asmgen.asmVariableName(array.variable) |             if(array.variable==null) | ||||||
|  |                 TODO("asmVarname for array with pointer") | ||||||
|  |             asmgen.asmVariableName(array.variable!!) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         if(register!=null && datatype !in NumericDatatypesWithBoolean) |         if(register!=null && !datatype.isNumericOrBool) | ||||||
|             throw AssemblyError("must be numeric type") |             throw AssemblyError("must be numeric type") | ||||||
|  |         if(kind==TargetStorageKind.REGISTER) | ||||||
|  |             require(register!=null) | ||||||
|  |         else | ||||||
|  |             require(register==null) | ||||||
|  |         if(kind==TargetStorageKind.POINTER) | ||||||
|  |             require(pointer!=null) | ||||||
|  |         if(pointer!=null) | ||||||
|  |             require(kind==TargetStorageKind.POINTER) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|  |         fun fromAstAssignmentMulti(targets: List<PtAssignTarget>, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): List<AsmAssignTarget> { | ||||||
|  |             return targets.map { | ||||||
|  |                 if(it.void) | ||||||
|  |                     AsmAssignTarget(TargetStorageKind.VOID, asmgen, DataType.UNDEFINED, null, it.position) | ||||||
|  |                 else | ||||||
|  |                     fromAstAssignment(it, definingSub, asmgen) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget { |         fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget { | ||||||
|             with(target) { |             with(target) { | ||||||
|                 when { |                 when { | ||||||
| @@ -68,6 +90,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind, | |||||||
|                     } |                     } | ||||||
|                     array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget =  this) |                     array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, target.position, array = array, origAstTarget =  this) | ||||||
|                     memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory =  memory, origAstTarget =  this) |                     memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, target.position, memory =  memory, origAstTarget =  this) | ||||||
|  |                     pointerDeref != null -> return AsmAssignTarget(TargetStorageKind.POINTER, asmgen, type, definingSub, target.position, pointer = pointerDeref, origAstTarget =  this) | ||||||
|                     else -> throw AssemblyError("weird target") |                     else -> throw AssemblyError("weird target") | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -77,12 +100,20 @@ internal class AsmAssignTarget(val kind: TargetStorageKind, | |||||||
|                 when(registers) { |                 when(registers) { | ||||||
|                     RegisterOrPair.A, |                     RegisterOrPair.A, | ||||||
|                     RegisterOrPair.X, |                     RegisterOrPair.X, | ||||||
|                     RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.BYTE else DataType.UBYTE, scope, pos, register = registers) |                     RegisterOrPair.Y -> { | ||||||
|  |                         val dt = if(signed) DataType.BYTE else DataType.UBYTE | ||||||
|  |                         AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers) | ||||||
|  |                     } | ||||||
|                     RegisterOrPair.AX, |                     RegisterOrPair.AX, | ||||||
|                     RegisterOrPair.AY, |                     RegisterOrPair.AY, | ||||||
|                     RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, pos, register = registers) |                     RegisterOrPair.XY -> { | ||||||
|  |                         val dt = if(signed) DataType.WORD else DataType.UWORD | ||||||
|  |                         AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers) | ||||||
|  |                     } | ||||||
|                     RegisterOrPair.FAC1, |                     RegisterOrPair.FAC1, | ||||||
|                     RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers) |                     RegisterOrPair.FAC2 -> { | ||||||
|  |                         AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.FLOAT, scope, pos, register = registers) | ||||||
|  |                     } | ||||||
|                     RegisterOrPair.R0, |                     RegisterOrPair.R0, | ||||||
|                     RegisterOrPair.R1, |                     RegisterOrPair.R1, | ||||||
|                     RegisterOrPair.R2, |                     RegisterOrPair.R2, | ||||||
| @@ -98,7 +129,10 @@ internal class AsmAssignTarget(val kind: TargetStorageKind, | |||||||
|                     RegisterOrPair.R12, |                     RegisterOrPair.R12, | ||||||
|                     RegisterOrPair.R13, |                     RegisterOrPair.R13, | ||||||
|                     RegisterOrPair.R14, |                     RegisterOrPair.R14, | ||||||
|                     RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, if(signed) DataType.WORD else DataType.UWORD, scope, pos, register = registers) |                     RegisterOrPair.R15 -> { | ||||||
|  |                         val dt = if(signed) DataType.WORD else DataType.UWORD | ||||||
|  |                         AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, scope, pos, register = registers) | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -114,14 +148,16 @@ internal class AsmAssignTarget(val kind: TargetStorageKind, | |||||||
|                 left is PtIdentifier && left.name==scopedName |                 left is PtIdentifier && left.name==scopedName | ||||||
|             } |             } | ||||||
|             TargetStorageKind.ARRAY -> { |             TargetStorageKind.ARRAY -> { | ||||||
|                 left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords |                 left is PtArrayIndexer && left isSameAs array!! && left.splitWords==array.splitWords && (left.pointerderef==null && array.pointerderef==null || left.pointerderef!! isSameAs array.pointerderef!!) | ||||||
|             } |             } | ||||||
|             TargetStorageKind.MEMORY -> { |             TargetStorageKind.MEMORY -> { | ||||||
|                 left isSameAs memory!! |                 left isSameAs memory!! | ||||||
|             } |             } | ||||||
|             TargetStorageKind.REGISTER -> { |             TargetStorageKind.POINTER -> { | ||||||
|                 false |                 TODO("is pointer deref target same as expression? ${this.position}") | ||||||
|             } |             } | ||||||
|  |             TargetStorageKind.REGISTER -> false | ||||||
|  |             TargetStorageKind.VOID -> false | ||||||
|         } |         } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -141,8 +177,11 @@ internal class AsmAssignSource(val kind: SourceStorageKind, | |||||||
|     val asmVarname: String |     val asmVarname: String | ||||||
|         get() = if(array==null) |         get() = if(array==null) | ||||||
|             variableAsmName!! |             variableAsmName!! | ||||||
|         else |         else { | ||||||
|             asmgen.asmVariableName(array.variable) |             if(array.variable==null) | ||||||
|  |                 TODO("asmVarname for array with pointer") | ||||||
|  |             asmgen.asmVariableName(array.variable!!) | ||||||
|  |         } | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
|         fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource { |         fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource { | ||||||
| @@ -163,7 +202,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind, | |||||||
|                         throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}") |                         throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}") | ||||||
|                     val varName=asmgen.asmVariableName(value) |                     val varName=asmgen.asmVariableName(value) | ||||||
|                     // special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system |                     // special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system | ||||||
|                     if(value.type == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) { |                     if(value.type.isUnsignedWord && varName.lowercase().startsWith("cx16.r")) { | ||||||
|                         val regStr = varName.lowercase().substring(5) |                         val regStr = varName.lowercase().substring(5) | ||||||
|                         val reg = RegisterOrPair.valueOf(regStr.uppercase()) |                         val reg = RegisterOrPair.valueOf(regStr.uppercase()) | ||||||
|                         AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, value.type, register = reg) |                         AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, value.type, register = reg) | ||||||
| @@ -183,9 +222,12 @@ internal class AsmAssignSource(val kind: SourceStorageKind, | |||||||
|                 is PtFunctionCall -> { |                 is PtFunctionCall -> { | ||||||
|                     val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}") |                     val symbol = asmgen.symbolTable.lookup(value.name) ?: throw AssemblyError("lookup error ${value.name}") | ||||||
|                     val sub = symbol.astNode as IPtSubroutine |                     val sub = symbol.astNode as IPtSubroutine | ||||||
|                     val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second |                     val returnType = | ||||||
|  |                         if(sub is PtSub && sub.signature.returns.size>1) | ||||||
|  |                             DataType.UNDEFINED      // TODO list of types instead? | ||||||
|  |                         else | ||||||
|  |                             sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second | ||||||
|                             ?: throw AssemblyError("can't translate zero return values in assignment") |                             ?: throw AssemblyError("can't translate zero return values in assignment") | ||||||
|  |  | ||||||
|                     AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value) |                     AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value) | ||||||
|                 } |                 } | ||||||
|                 else -> { |                 else -> { | ||||||
| @@ -202,9 +244,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind, | |||||||
|                 AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, boolean, expression) |                 AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, boolean, expression) | ||||||
|  |  | ||||||
|         if(target.datatype!=datatype) { |         if(target.datatype!=datatype) { | ||||||
|             if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) { |             if(target.datatype.isByte && datatype.isByte) { | ||||||
|                 return withAdjustedDt(target.datatype) |                 return withAdjustedDt(target.datatype) | ||||||
|             } else if(target.datatype in WordDatatypes && datatype in WordDatatypes) { |             } else if(target.datatype.isWord && datatype.isWord) { | ||||||
|                 return withAdjustedDt(target.datatype) |                 return withAdjustedDt(target.datatype) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -215,26 +257,30 @@ internal class AsmAssignSource(val kind: SourceStorageKind, | |||||||
|  |  | ||||||
|  |  | ||||||
| internal sealed class AsmAssignmentBase(val source: AsmAssignSource, | internal sealed class AsmAssignmentBase(val source: AsmAssignSource, | ||||||
|                                         val target: AsmAssignTarget, |                                         val targets: List<AsmAssignTarget>, | ||||||
|                                         val memsizer: IMemSizer, |                                         val memsizer: IMemSizer, | ||||||
|                                         val position: Position) { |                                         val position: Position) { | ||||||
|     init { |     init { | ||||||
|         if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) |         targets.forEach { target -> | ||||||
|             require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" } |             if (!source.datatype.isArray && !source.datatype.isUndefined && !target.datatype.isArray && !target.datatype.isUndefined) | ||||||
|             require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) { |                 require(memsizer.memorySize(source.datatype, null) <= memsizer.memorySize(target.datatype, null)) { | ||||||
|                     "source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}" |                     "source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}" | ||||||
|                 } |                 } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     val target: AsmAssignTarget | ||||||
|  |         get() = targets.single() | ||||||
|  | } | ||||||
|  |  | ||||||
| internal class AsmAssignment(source: AsmAssignSource, | internal class AsmAssignment(source: AsmAssignSource, | ||||||
|                              target: AsmAssignTarget, |                              targets: List<AsmAssignTarget>, | ||||||
|                              memsizer: IMemSizer, |                              memsizer: IMemSizer, | ||||||
|                              position: Position): AsmAssignmentBase(source, target, memsizer, position) |                              position: Position): AsmAssignmentBase(source, targets, memsizer, position) | ||||||
|  |  | ||||||
| internal class AsmAugmentedAssignment(source: AsmAssignSource, | internal class AsmAugmentedAssignment(source: AsmAssignSource, | ||||||
|                                       val operator: String, |                                       val operator: String, | ||||||
|                                       target: AsmAssignTarget, |                                       target: AsmAssignTarget, | ||||||
|                                       memsizer: IMemSizer, |                                       memsizer: IMemSizer, | ||||||
|                                       position: Position): AsmAssignmentBase(source, target, memsizer, position) |                                       position: Position): AsmAssignmentBase(source, listOf(target), memsizer, position) | ||||||
|  |  | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -4,16 +4,27 @@ import prog8.code.core.* | |||||||
|  |  | ||||||
|  |  | ||||||
| internal object DummyMemsizer : IMemSizer { | internal object DummyMemsizer : IMemSizer { | ||||||
|     override fun memorySize(dt: DataType) = when(dt) { |     override fun memorySize(dt: DataType, numElements: Int?): Int { | ||||||
|         in ByteDatatypesWithBoolean -> 1 |         if(dt.isPointerArray) | ||||||
|         DataType.FLOAT -> 5 |             return 2 * numElements!! | ||||||
|         else -> 2 |         else if(dt.isArray) { | ||||||
|  |             require(numElements != null) | ||||||
|  |             return when(dt.sub) { | ||||||
|  |                 BaseDataType.BOOL, BaseDataType.BYTE, BaseDataType.UBYTE -> numElements | ||||||
|  |                 BaseDataType.UWORD, BaseDataType.WORD -> numElements*2 | ||||||
|  |                 BaseDataType.FLOAT -> numElements*5 | ||||||
|  |                 else -> throw IllegalArgumentException("invalid sub type") | ||||||
|             } |             } | ||||||
|     override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) { |         } | ||||||
|         DataType.ARRAY_UW -> numElements*2 |         return when { | ||||||
|         DataType.ARRAY_W -> numElements*2 |             dt.isByteOrBool -> 1 * (numElements ?: 1) | ||||||
|         DataType.ARRAY_F -> numElements*5 |             dt.isFloat -> 5 * (numElements ?: 1) | ||||||
|         else -> numElements |             else -> 2 * (numElements ?: 1) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun memorySize(dt: BaseDataType): Int { | ||||||
|  |         return memorySize(DataType.forDt(dt), null) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -59,6 +70,7 @@ internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: | |||||||
|  |  | ||||||
|     override fun noErrors(): Boolean  = errors.isEmpty() |     override fun noErrors(): Boolean  = errors.isEmpty() | ||||||
|     override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it } |     override fun noErrorForLine(position: Position) = !errors.any { ":${position.line}:" in it } | ||||||
|  |     override fun printSingleError(errormessage: String) { /* prints nothing in tests */ } | ||||||
|  |  | ||||||
|     override fun report() { |     override fun report() { | ||||||
|         infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") } |         infos.forEach { println("UNITTEST COMPILATION REPORT: INFO: $it") } | ||||||
|   | |||||||
| @@ -5,11 +5,15 @@ import io.kotest.assertions.withClue | |||||||
| import io.kotest.core.spec.style.FunSpec | import io.kotest.core.spec.style.FunSpec | ||||||
| import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual | import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual | ||||||
| import io.kotest.matchers.shouldBe | import io.kotest.matchers.shouldBe | ||||||
|  | import prog8.code.StMemVar | ||||||
|  | import prog8.code.SymbolTable | ||||||
| import prog8.code.SymbolTableMaker | import prog8.code.SymbolTableMaker | ||||||
| import prog8.code.ast.* | import prog8.code.ast.* | ||||||
| import prog8.code.core.* | import prog8.code.core.* | ||||||
|  | import prog8.code.source.SourceCode | ||||||
| import prog8.code.target.C64Target | import prog8.code.target.C64Target | ||||||
| import prog8.codegen.cpu6502.AsmGen6502 | import prog8.codegen.cpu6502.AsmGen6502 | ||||||
|  | import prog8.codegen.cpu6502.VariableAllocator | ||||||
| import java.nio.file.Files | import java.nio.file.Files | ||||||
| import kotlin.io.path.Path | import kotlin.io.path.Path | ||||||
|  |  | ||||||
| @@ -25,8 +29,10 @@ class TestCodegen: FunSpec({ | |||||||
|             zpAllowed = CompilationOptions.AllZeropageAllowed, |             zpAllowed = CompilationOptions.AllZeropageAllowed, | ||||||
|             floats = true, |             floats = true, | ||||||
|             noSysInit = false, |             noSysInit = false, | ||||||
|  |             romable = false, | ||||||
|             compTarget = target, |             compTarget = target, | ||||||
|             loadAddress = target.machine.PROGRAM_LOAD_ADDRESS |             loadAddress = target.PROGRAM_LOAD_ADDRESS, | ||||||
|  |             memtopAddress = 0xffffu | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -43,26 +49,62 @@ class TestCodegen: FunSpec({ | |||||||
| //        xx += cx16.r0 | //        xx += cx16.r0 | ||||||
| //    } | //    } | ||||||
| //} | //} | ||||||
|         val codegen = AsmGen6502(prefixSymbols = false) |         val codegen = AsmGen6502(prefixSymbols = false, 0) | ||||||
|         val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) |         val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) | ||||||
|         val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) |         val block = PtBlock("main",false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY) | ||||||
|         val sub = PtSub("start", emptyList(), null, Position.DUMMY) |         val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY) | ||||||
|         sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY)) |         sub.add(PtVariable( | ||||||
|         sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY)) |             "pi", | ||||||
|         sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY)) |             DataType.UBYTE, | ||||||
|         sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY)) |             ZeropageWish.DONTCARE, | ||||||
|  |             0u, | ||||||
|  |             false, | ||||||
|  |             null, | ||||||
|  |             null, | ||||||
|  |             Position.DUMMY | ||||||
|  |         )) | ||||||
|  |         sub.add(PtVariable( | ||||||
|  |             "particleX", | ||||||
|  |             DataType.arrayFor(BaseDataType.UBYTE), | ||||||
|  |             ZeropageWish.DONTCARE, | ||||||
|  |             0u, | ||||||
|  |             false, | ||||||
|  |             null, | ||||||
|  |             3u, | ||||||
|  |             Position.DUMMY | ||||||
|  |         )) | ||||||
|  |         sub.add(PtVariable( | ||||||
|  |             "particleDX", | ||||||
|  |             DataType.arrayFor(BaseDataType.UBYTE), | ||||||
|  |             ZeropageWish.DONTCARE, | ||||||
|  |             0u, | ||||||
|  |             false, | ||||||
|  |             null, | ||||||
|  |             3u, | ||||||
|  |             Position.DUMMY | ||||||
|  |         )) | ||||||
|  |         sub.add(PtVariable( | ||||||
|  |             "xx", | ||||||
|  |             DataType.WORD, | ||||||
|  |             ZeropageWish.DONTCARE, | ||||||
|  |             0u, | ||||||
|  |             false, | ||||||
|  |             null, | ||||||
|  |             null, | ||||||
|  |             Position.DUMMY | ||||||
|  |         )) | ||||||
|  |  | ||||||
|         val assign = PtAugmentedAssign("+=", Position.DUMMY) |         val assign = PtAugmentedAssign("+=", Position.DUMMY) | ||||||
|         val target = PtAssignTarget(false, Position.DUMMY).also { |         val target = PtAssignTarget(false, Position.DUMMY).also { | ||||||
|             val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx -> |             val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx -> | ||||||
|                 idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY)) |                 idx.add(PtIdentifier("main.start.particleX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY)) | ||||||
|                 idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY)) |                 idx.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY)) | ||||||
|             } |             } | ||||||
|             it.add(targetIdx) |             it.add(targetIdx) | ||||||
|         } |         } | ||||||
|         val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY) |         val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY) | ||||||
|         value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY)) |         value.add(PtIdentifier("main.start.particleDX", DataType.arrayFor(BaseDataType.UBYTE), Position.DUMMY)) | ||||||
|         value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY)) |         value.add(PtNumber(BaseDataType.UBYTE, 2.0, Position.DUMMY)) | ||||||
|         assign.add(target) |         assign.add(target) | ||||||
|         assign.add(value) |         assign.add(value) | ||||||
|         sub.add(assign) |         sub.add(assign) | ||||||
| @@ -80,7 +122,7 @@ class TestCodegen: FunSpec({ | |||||||
|             it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY)) |             it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY)) | ||||||
|         } |         } | ||||||
|         numberAssign.add(numberAssignTarget) |         numberAssign.add(numberAssignTarget) | ||||||
|         numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY)) |         numberAssign.add(PtNumber(BaseDataType.WORD, 42.0, Position.DUMMY)) | ||||||
|         sub.add(numberAssign) |         sub.add(numberAssign) | ||||||
|  |  | ||||||
|         val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY) |         val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY) | ||||||
| @@ -125,5 +167,15 @@ class TestCodegen: FunSpec({ | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     test("memory mapped zp var is correctly considered to be zp var") { | ||||||
|  |         val program = PtProgram("test", DummyMemsizer, DummyStringEncoder) | ||||||
|  |         val st = SymbolTable(program) | ||||||
|  |         st.add(StMemVar("zpmemvar", DataType.WORD, 0x20u, null, null)) | ||||||
|  |         st.add(StMemVar("normalmemvar", DataType.WORD, 0x9000u, null, null)) | ||||||
|  |         val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests()) | ||||||
|  |         allocator.isZpVar("zpmemvar") shouldBe true | ||||||
|  |         allocator.isZpVar("normalmemvar") shouldBe false | ||||||
|  |     } | ||||||
| }) | }) | ||||||
|  |  | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user