mirror of
				https://github.com/irmen/prog8.git
				synced 2025-10-31 00:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			755 Commits
		
	
	
		
			v4.4
			...
			v7.0-beta2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | d4b69ac79c | ||
|  | e61a2d7083 | ||
|  | c03f6604af | ||
|  | 572bb38ddb | ||
|  | 42c5c0cb9f | ||
|  | e145d2255e | ||
|  | 442fa07dd4 | ||
|  | 31ae9e1243 | ||
|  | d7f83f8df2 | ||
|  | 29e2d4e0c8 | ||
|  | 2732d2c844 | ||
|  | c4a037b277 | ||
|  | 0e614ad6fc | ||
|  | ca1a8cd617 | ||
|  | ba96a637be | ||
|  | c2cac772e3 | ||
|  | 6b7216f4ec | ||
|  | e4fb5946dd | ||
|  | ca61248861 | ||
|  | 68d7b4649e | ||
|  | 0416aacbbd | ||
|  | bc731e6f8e | ||
|  | ae5d7705bb | ||
|  | b9bd541532 | ||
|  | 83639c2535 | ||
|  | 25d80f4df1 | ||
|  | 74f918d911 | ||
|  | a20efa56eb | ||
|  | f4d83075be | ||
|  | 254592c383 | ||
|  | ee23ac0537 | ||
|  | a48cf0bb24 | ||
|  | dae59238cd | ||
|  | 8736da1a21 | ||
|  | 09a1de69e7 | ||
|  | 63d67bc6cb | ||
|  | 7099245204 | ||
|  | 4d097d2139 | ||
|  | 6485bf9ad9 | ||
|  | b7c5b1bfc7 | ||
|  | 2b7546e827 | ||
|  | 3549ccf4b3 | ||
|  | e2f5752d9a | ||
|  | 1a59019fc8 | ||
|  | 7bac7bdc3e | ||
|  | 19fe58dbac | ||
|  | 0a5b30e21c | ||
|  | 664818fd29 | ||
|  | d5214e2505 | ||
|  | d906fcea0e | ||
|  | 29c8e8b740 | ||
|  | 71fec4c555 | ||
|  | 5ee36c897d | ||
|  | 4aba0c7405 | ||
|  | ed7479c854 | ||
|  | 8d3d5f726a | ||
|  | a9a7068818 | ||
|  | 1bde7c7718 | ||
|  | 17068130bb | ||
|  | 81a91d62cb | ||
|  | 2575263438 | ||
|  | 7f0e25cb50 | ||
|  | a1e4e9c50f | ||
|  | 98eff2701b | ||
|  | 8b84f87217 | ||
|  | 306a1b7bc2 | ||
|  | 481214c46e | ||
|  | a5961cbeab | ||
|  | 3bf335e0a0 | ||
|  | 68f696d165 | ||
|  | 1170aed026 | ||
|  | bf1b2066b6 | ||
|  | 4c080afb76 | ||
|  | ee1c43ca91 | ||
|  | 1c2e6f9e4c | ||
|  | dd379430d9 | ||
|  | 42033ebd35 | ||
|  | a086d6e009 | ||
|  | c70bbdab26 | ||
|  | 3d956ef554 | ||
|  | 329f491c30 | ||
|  | e93701f50e | ||
|  | e680de05ea | ||
|  | 56fec674c5 | ||
|  | 54d92a027a | ||
|  | 319ac3a641 | ||
|  | 0a03c46351 | ||
|  | ae1b62e147 | ||
|  | 8d567f6b06 | ||
|  | b1ef09675b | ||
|  | 2b7b925090 | ||
|  | e0454e95db | ||
|  | 91e421d961 | ||
|  | c853afe769 | ||
|  | 1a64cb38d5 | ||
|  | ccebd22856 | ||
|  | a1f3b82333 | ||
|  | 3dda29781e | ||
|  | a9d297ee31 | ||
|  | e5ff61f201 | ||
|  | d116eb7655 | ||
|  | bc726c6334 | ||
|  | 123473dfc8 | ||
|  | d9eccd4fba | ||
|  | 5b890847e5 | ||
|  | 64c85b9617 | ||
|  | 3e3b0bcd8b | ||
|  | 4c1eb1b12a | ||
|  | 530d03d284 | ||
|  | 619fa9b65e | ||
|  | 0032235933 | ||
|  | 61d1f1ea87 | ||
|  | 238d27acdc | ||
|  | 2f62271453 | ||
|  | 75d5117a2d | ||
|  | b4700af2f5 | ||
|  | 374e2b311d | ||
|  | 49036abbaf | ||
|  | 38ccbac97c | ||
|  | 6b4896b8f5 | ||
|  | d582d1cc42 | ||
|  | 9e2b8a2aa9 | ||
|  | 6fdc733941 | ||
|  | 422b390c48 | ||
|  | 67a9d1285c | ||
|  | 8e26e38ecc | ||
|  | 02e12d8575 | ||
|  | fe2954ce08 | ||
|  | 1fe4439395 | ||
|  | 2ff04d2abd | ||
|  | 3f30d3aa89 | ||
|  | 129e17b33a | ||
|  | bf2d8c3f4b | ||
|  | b29f04ce01 | ||
|  | d185ebad48 | ||
|  | 605df7c91c | ||
|  | ec60cad8bb | ||
|  | 6aa0f5a392 | ||
|  | 4cae2c56ec | ||
|  | d840975054 | ||
|  | 1b14da6c03 | ||
|  | 292640b17a | ||
|  | 112a7b09f2 | ||
|  | 863ec9ce8a | ||
|  | 2eb346a205 | ||
|  | 8092355acb | ||
|  | e7ef2ed31b | ||
|  | af4de6d2fc | ||
|  | 69f73dd779 | ||
|  | 9706b46012 | ||
|  | 6d75dd3bb8 | ||
|  | bd295ffc99 | ||
|  | 07ce3e3c9d | ||
|  | cbc3e37a89 | ||
|  | 3626828ceb | ||
|  | 24b77fb5a5 | ||
|  | 1505fe686a | ||
|  | 0991131fa8 | ||
|  | 2e928bd3c2 | ||
|  | ca868ae19e | ||
|  | 3e286dd14c | ||
|  | 11247d52b1 | ||
|  | 1dbc902513 | ||
|  | 330e691b78 | ||
|  | 6780d4f562 | ||
|  | b30b8b7368 | ||
|  | 3df182b8c3 | ||
|  | 7f21d89fea | ||
|  | 2b267b4ba1 | ||
|  | ef64881528 | ||
|  | 9a6bd760bd | ||
|  | 00b9766aea | ||
|  | 6381d2b6ac | ||
|  | d2ab5f230d | ||
|  | 824b41d457 | ||
|  | b5523c7077 | ||
|  | eb3594b18c | ||
|  | 852d85d010 | ||
|  | 5e0aef04fe | ||
|  | a00c693f93 | ||
|  | c943da1448 | ||
|  | b630fae580 | ||
|  | 38e40084f1 | ||
|  | bf23ad78e6 | ||
|  | ded1d19737 | ||
|  | 496a3b0d2c | ||
|  | 6922333755 | ||
|  | a00c39e9cf | ||
|  | 1c1da8e38e | ||
|  | 50a306f492 | ||
|  | 6995ee2d17 | ||
|  | 6c60ea9cac | ||
|  | 2431ed811a | ||
|  | 6bd205c02a | ||
|  | 62ec77e148 | ||
|  | 9120e1de88 | ||
|  | 60e169bd87 | ||
|  | e4bca5fe47 | ||
|  | a1729b65ab | ||
|  | 2950d26c8e | ||
|  | 4f8d4a9585 | ||
|  | d787795759 | ||
|  | cf74e73e27 | ||
|  | 2770254fd9 | ||
|  | de04bd8cfa | ||
|  | 076a547f91 | ||
|  | dffd0a2706 | ||
|  | 6c66f86103 | ||
|  | 26502c949a | ||
|  | 8dfe510883 | ||
|  | 96ba9f5902 | ||
|  | 3a6ba0ab71 | ||
|  | 32d894d6b6 | ||
|  | 543efa4299 | ||
|  | eba0708099 | ||
|  | 51e6bf0d45 | ||
|  | 07b5c44a54 | ||
|  | 9fe32c1c34 | ||
|  | 0e0278c84a | ||
|  | dea775a9cd | ||
|  | 7e3e18a5c7 | ||
|  | 8e3ebc84f0 | ||
|  | e6079dfd71 | ||
|  | 2b435fe6a5 | ||
|  | 4e640b11fd | ||
|  | 8b1e1e68fa | ||
|  | fd11927708 | ||
|  | cd500fee8c | ||
|  | 1bd32c0f19 | ||
|  | 7aefca3de0 | ||
|  | f275ed96ea | ||
|  | d14dac3872 | ||
|  | b0213b0565 | ||
|  | c677f0a875 | ||
|  | 6e65cb2c0a | ||
|  | e65c5402d7 | ||
|  | 334f86480a | ||
|  | 0e62f5b759 | ||
|  | edf9a500d3 | ||
|  | 001d01fdaf | ||
|  | a95677564e | ||
|  | 4aca8bb8df | ||
|  | 5540482888 | ||
|  | 00d735249b | ||
|  | b5289511ba | ||
|  | b6ded8501f | ||
|  | 781915d2cf | ||
|  | f4cef3eaf2 | ||
|  | d23c2eed86 | ||
|  | 15695a304e | ||
|  | 6319269976 | ||
|  | 0ed3d951a7 | ||
|  | 2aa39757b4 | ||
|  | 39d32a3600 | ||
|  | 219d17de34 | ||
|  | 9bb5b454e4 | ||
|  | 2412f8c531 | ||
|  | 8701d684e6 | ||
|  | b543cc34cd | ||
|  | 791dbbab9b | ||
|  | ac0b1da3fc | ||
|  | 2f97aedc3c | ||
|  | ab544ee965 | ||
|  | fa527f8624 | ||
|  | 92ee0aefee | ||
|  | 99759ae853 | ||
|  | 81930312ff | ||
|  | 194fbcdd91 | ||
|  | 1e3930aae2 | ||
|  | 62dda4d891 | ||
|  | 2b870fb9f7 | ||
|  | 53f0318187 | ||
|  | 5e6e711f33 | ||
|  | 78af2cd4dc | ||
|  | 02cb237623 | ||
|  | cc0f19653e | ||
|  | 4fff150c7b | ||
|  | f6136891cc | ||
|  | 1e22170302 | ||
|  | bdda6f502a | ||
|  | 1bbd77fddb | ||
|  | 321fdd10d1 | ||
|  | 9867dfcdeb | ||
|  | 7c09ac632c | ||
|  | 3502f65332 | ||
|  | 628390c3b5 | ||
|  | bc37097df2 | ||
|  | 7d98275763 | ||
|  | d6ffb549f6 | ||
|  | bcd0db984d | ||
|  | d9244f22c2 | ||
|  | c97d76dbf2 | ||
|  | 9e05e97d7f | ||
|  | 1070dedd7c | ||
|  | ccd1516637 | ||
|  | f1f51a01c6 | ||
|  | be75b8dbe5 | ||
|  | 02fae0e722 | ||
|  | e35b739579 | ||
|  | 34aa6cc8a2 | ||
|  | d7a6b20028 | ||
|  | eb2d5bb1f8 | ||
|  | cefef3d1be | ||
|  | cc96ab7a9b | ||
|  | 49ea31c0a4 | ||
|  | f1478d776b | ||
|  | 40e4cfb686 | ||
|  | 76f459ee95 | ||
|  | c478718019 | ||
|  | c27248a58b | ||
|  | 51bc539468 | ||
|  | 2395863e7e | ||
|  | 69c459c8ac | ||
|  | c8855b2b10 | ||
|  | a910c0fddb | ||
|  | fd55611cac | ||
|  | 52f6be2bb0 | ||
|  | 857f930dc2 | ||
|  | dd2c436dc6 | ||
|  | 9f047ba752 | ||
|  | 9d4ec4a9b2 | ||
|  | cdc6d9aa65 | ||
|  | 997bc21feb | ||
|  | 975af4764d | ||
|  | bf69219f98 | ||
|  | f34f9329f1 | ||
|  | 90271d0dcd | ||
|  | 195cd7597d | ||
|  | 4a81406262 | ||
|  | f9fd426843 | ||
|  | e612056ecd | ||
|  | 6f0103398b | ||
|  | afb60db382 | ||
|  | 5731b876ff | ||
|  | 055f917a2e | ||
|  | 4ed7fb771c | ||
|  | c328e9018c | ||
|  | b270f6f713 | ||
|  | 5c13918f11 | ||
|  | 40cc216557 | ||
|  | 1481f92cb0 | ||
|  | 76d54fbe5c | ||
|  | 9f72779cdc | ||
|  | 3dcef89a74 | ||
|  | 46373717b6 | ||
|  | 7277c08fa6 | ||
|  | 04e75455c4 | ||
|  | 8ac17ae14e | ||
|  | cb5d6ddf80 | ||
|  | e0794db33a | ||
|  | b128b79132 | ||
|  | 79e6d4b8dd | ||
|  | b9ddde0f12 | ||
|  | a0ec37b35b | ||
|  | 506ac8014c | ||
|  | 72b4198301 | ||
|  | 24eee0cb34 | ||
|  | 9fc0c3f849 | ||
|  | db314ed903 | ||
|  | 1ef9b8be61 | ||
|  | 79782ad547 | ||
|  | 4b6d045df1 | ||
|  | b4d1d545a8 | ||
|  | f61682cdc7 | ||
|  | d61420f1c6 | ||
|  | 3d09d605e1 | ||
|  | 025dde264a | ||
|  | 87cee7a0fd | ||
|  | 61784a03bb | ||
|  | 9d9ca0f08d | ||
|  | 58f37513e7 | ||
|  | ee7f9d457d | ||
|  | bec2224c3d | ||
|  | 4305984168 | ||
|  | 07dd64958f | ||
|  | 76101d7f8d | ||
|  | 7d6a0ab256 | ||
|  | 4309a0dc68 | ||
|  | dde6919446 | ||
|  | 54fc9c91ac | ||
|  | 41658c97a3 | ||
|  | 45c9cc97d9 | ||
|  | 6fa7debee5 | ||
|  | ee9f662016 | ||
|  | 3550e1214c | ||
|  | 8dcb43ad1c | ||
|  | e6a1442296 | ||
|  | cb65480c6c | ||
|  | 3e7c7ab497 | ||
|  | f0930d8a18 | ||
|  | 5a846bdeb5 | ||
|  | baf9dfb46c | ||
|  | edd3a22848 | ||
|  | 583428b19c | ||
|  | 08d44ae553 | ||
|  | b3b2541c1e | ||
|  | 8e927e0b73 | ||
|  | 8e3e996f4a | ||
|  | b6fa361bcc | ||
|  | ca83092aed | ||
|  | 3cda92331e | ||
|  | c989abe265 | ||
|  | 89230ade7a | ||
|  | b4931c9a1f | ||
|  | ddfcf45d40 | ||
|  | ee12236d53 | ||
|  | df6698c98f | ||
|  | c3b82f2cfa | ||
|  | 64c89f1c8f | ||
|  | e09b65ea94 | ||
|  | c81952c356 | ||
|  | f80e462d25 | ||
|  | 51f32677b7 | ||
|  | 4b366358c4 | ||
|  | 3378586098 | ||
|  | 6777d952c1 | ||
|  | 6c8b18ddbd | ||
|  | 69780ecde9 | ||
|  | 9e2c52e1ec | ||
|  | 6cb0e6a936 | ||
|  | dd82e550d5 | ||
|  | cdcda27d07 | ||
|  | ffffcdd50a | ||
|  | d37d62574c | ||
|  | f2380457d6 | ||
|  | efa42d5d96 | ||
|  | e17c18b653 | ||
|  | 7607d3d64a | ||
|  | d7d7147d43 | ||
|  | b40e1eabb9 | ||
|  | 3b8e18004c | ||
|  | 4c03950c28 | ||
|  | 170a0183f8 | ||
|  | c62ff16f8b | ||
|  | ab495fe6e1 | ||
|  | c2a8dc23d0 | ||
|  | 6734ae3c88 | ||
|  | 4c1c595f14 | ||
|  | 9002c67639 | ||
|  | b91aabd3c0 | ||
|  | 3307f673f6 | ||
|  | 07b00bec61 | ||
|  | e0d2b60d8b | ||
|  | 45bfecee73 | ||
|  | 80e3a11268 | ||
|  | 38a6c6a866 | ||
|  | 8f224afed9 | ||
|  | 48a4c46a6c | ||
|  | 7d08380c7f | ||
|  | b3b3cf3807 | ||
|  | f0f6150e18 | ||
|  | dc600cc3ed | ||
|  | ae648b8a0a | ||
|  | 583af3bd4f | ||
|  | d65cfbf093 | ||
|  | 118aed2e31 | ||
|  | 85abf4d123 | ||
|  | 44b8291540 | ||
|  | d6444bba66 | ||
|  | 5a2f8fdfe1 | ||
|  | bba4f84503 | ||
|  | 684e081399 | ||
|  | 96c700ee46 | ||
|  | 5f15794c3b | ||
|  | a40b3134f4 | ||
|  | c70b4daf87 | ||
|  | 928611eb20 | ||
|  | f1d55c688a | ||
|  | d22df22f7d | ||
|  | 061e1be0a4 | ||
|  | 950bc4b937 | ||
|  | dcb81e6bea | ||
|  | daaa83ee7d | ||
|  | b7c1450121 | ||
|  | 787f52d1f8 | ||
|  | 50213f146a | ||
|  | 7f2aea60c9 | ||
|  | 168621f7c2 | ||
|  | 8b630798d8 | ||
|  | 52e8a44517 | ||
|  | 59f33658ad | ||
|  | e0315bffdc | ||
|  | cd28d0c0e0 | ||
|  | 0baa2c8b23 | ||
|  | 4977d1fbd5 | ||
|  | 3b7a92f1b4 | ||
|  | f6920172dd | ||
|  | 93bfc8f5f4 | ||
|  | 39b7655264 | ||
|  | 8b75ceb412 | ||
|  | c39fc4010d | ||
|  | 8df778a515 | ||
|  | 5134ea76bf | ||
|  | 3ba37df29d | ||
|  | e221d674d9 | ||
|  | 251f947293 | ||
|  | 41e1e1cbb0 | ||
|  | da1bc351d2 | ||
|  | 43c0afdea0 | ||
|  | add5bfa2ec | ||
|  | 34babfb5de | ||
|  | 4f6c45c86c | ||
|  | e6220a464c | ||
|  | 8dcd49934a | ||
|  | bedc3bdb56 | ||
|  | 83ceb0fde9 | ||
|  | 1d299c56e0 | ||
|  | 0d735c2ccc | ||
|  | 4094f89d4a | ||
|  | cf1e8b194a | ||
|  | 74e5644f55 | ||
|  | b5dc5fc615 | ||
|  | 7a7270d769 | ||
|  | 7549ddcd2b | ||
|  | 08f0303178 | ||
|  | 0d7a291b81 | ||
|  | 2265ae9600 | ||
|  | cba502e87a | ||
|  | ac94236614 | ||
|  | ddf1be2a13 | ||
|  | b7694686c2 | ||
|  | 63332c0530 | ||
|  | 8a504f8eee | ||
|  | 106fc5daa4 | ||
|  | 7accb73993 | ||
|  | e9aa6a0956 | ||
|  | df20467e03 | ||
|  | ecbd9d739e | ||
|  | 8af17c295a | ||
|  | 329b28cad1 | ||
|  | 452c29574d | ||
|  | 5bedc1b333 | ||
|  | 0bf6d2f72c | ||
|  | c09b8af491 | ||
|  | 260bcd3a55 | ||
|  | 6b5211ad12 | ||
|  | a92ec14989 | ||
|  | b3348eb22b | ||
|  | bec5a261e5 | ||
|  | 4b53641e1d | ||
|  | 00071d53d5 | ||
|  | 6902834568 | ||
|  | fa2d87f3dd | ||
|  | 44019d1a61 | ||
|  | 6f74fb49bd | ||
|  | a303b39cf0 | ||
|  | 3e63a29c59 | ||
|  | 261c0fc9b6 | ||
|  | 895b30f7e5 | ||
|  | b985604e22 | ||
|  | f7953e4ef3 | ||
|  | 63483d1f0e | ||
|  | 8b981f03bf | ||
|  | d0d0910bf2 | ||
|  | 57ac820767 | ||
|  | b8bda867b6 | ||
|  | 05d3a2450c | ||
|  | d40788adfa | ||
|  | 83fbf86b1c | ||
|  | e876008427 | ||
|  | 2b43353eb4 | ||
|  | a74403c347 | ||
|  | 2f4c6c8697 | ||
|  | 238d8197f5 | ||
|  | 53a600d87b | ||
|  | 2a0ffaf45d | ||
|  | 936b046ed9 | ||
|  | 378dcfe351 | ||
|  | 0a330b9288 | ||
|  | a88b40d6c1 | ||
|  | 09f25ffbd9 | ||
|  | ab1232d742 | ||
|  | a7f56fe0fc | ||
|  | 58a9452c36 | ||
|  | 6d8c4f403f | ||
|  | 88b80fed90 | ||
|  | acdbd0c391 | ||
|  | d9a8cfed8c | ||
|  | 122796fbba | ||
|  | 510ca042c9 | ||
|  | 125f6205f2 | ||
|  | 8136f3df5c | ||
|  | 38d06a7e94 | ||
|  | 49db10539a | ||
|  | 8efe4c6267 | ||
|  | 04d8bb8fbf | ||
|  | 08aa13c90c | ||
|  | d1febc0208 | ||
|  | 5980e58ac6 | ||
|  | e1dc283d4b | ||
|  | 8be234973c | ||
|  | 7def8ff2cd | ||
|  | 340b1c2e42 | ||
|  | 7e0f7ba438 | ||
|  | fefd9b52a8 | ||
|  | afd155ac4f | ||
|  | ee724eb4f1 | ||
|  | 2f1f20ea11 | ||
|  | 063bcf17d8 | ||
|  | 72509eef44 | ||
|  | 2da28864e9 | ||
|  | 4278f64682 | ||
|  | 59ae3c3fcd | ||
|  | 7fa21fbdff | ||
|  | e95af7498e | ||
|  | 79c75adac1 | ||
|  | d212f69d89 | ||
|  | edf5e69d39 | ||
|  | 574eb0d174 | ||
|  | 8bd4914e2f | ||
|  | 5ebaaff64b | ||
|  | 5c9e0c9f51 | ||
|  | 8132edbb08 | ||
|  | d29ce78c86 | ||
|  | 94bc9d7a69 | ||
|  | e8faec0932 | ||
|  | 69ca4fe304 | ||
|  | cd99fe46fd | ||
|  | 4825b4dc68 | ||
|  | 8d0607ef58 | ||
|  | 225295a7d8 | ||
|  | 4cd74daf53 | ||
|  | 6eb9118197 | ||
|  | d0bd2f522c | ||
|  | 661c757236 | ||
|  | aaa20093ef | ||
|  | 1eecdd6fa3 | ||
|  | 800b5b2a43 | ||
|  | 9d17421c66 | ||
|  | 0edd50e956 | ||
|  | 288d4f08b3 | ||
|  | 526e4b8bdc | ||
|  | e0c5ccc16b | ||
|  | ebc2c614d7 | ||
|  | 29f5a85158 | ||
|  | 8af2380a47 | ||
|  | 431f2a2088 | ||
|  | e05ea887f6 | ||
|  | 95c0425151 | ||
|  | 47cbc7b1f9 | ||
|  | e7b75d591c | ||
|  | 99f7d469f4 | ||
|  | 8a6ef17fbf | ||
|  | 5f337a0bd9 | ||
|  | 87862f772a | ||
|  | 3ab641aa21 | ||
|  | 3efa8da8e0 | ||
|  | 3e28ed4fe4 | ||
|  | 44949460ed | ||
|  | 83cc19ad6f | ||
|  | 66bb98c479 | ||
|  | ff3f985658 | ||
|  | 2ba6c9ccbe | ||
|  | 3eaf111e7d | ||
|  | 30da26b9a9 | ||
|  | e35ad0cc8f | ||
|  | 1a36302cf1 | ||
|  | 82a28bb555 | ||
|  | c1ce0be451 | ||
|  | c0a5f8fef0 | ||
|  | 702cf304d0 | ||
|  | 4dee8b6048 | ||
|  | ec665e0cc1 | ||
|  | aec3b82476 | ||
|  | e83796b5b9 | ||
|  | 8eb69d6eda | ||
|  | 74b5124a42 | ||
|  | b9706a180b | ||
|  | 8aeb8a9bb7 | ||
|  | 8f2e166a22 | ||
|  | fdd91170dc | ||
|  | c40ddb061b | ||
|  | 353d6cfc55 | ||
|  | f37564c49c | ||
|  | 157484d94b | ||
|  | 7626c9fff7 | ||
|  | 1f55f9fc49 | ||
|  | 2554bc7ef8 | ||
|  | 7cb4100419 | ||
|  | 2d3b7eb878 | ||
|  | 4d01a78731 | ||
|  | a03e36828a | ||
|  | 260fb65b06 | ||
|  | 9fb8526136 | ||
|  | 26fc5ff5e2 | ||
|  | 5060f0bb19 | ||
|  | beaf6d449b | ||
|  | 4d68b508a2 | ||
|  | cd825e386d | ||
|  | 095c8b2309 | ||
|  | 8b6eb74c58 | ||
|  | aba437e5a2 | ||
|  | efe3ed499b | ||
|  | 5595564a1f | ||
|  | 439761cb67 | ||
|  | bee6c65293 | ||
|  | 10145b946b | ||
|  | ebf4b50059 | ||
|  | 07cce3b3fc | ||
|  | f2c19afd95 | ||
|  | d159e70e1c | ||
|  | ac693a2541 | ||
|  | 1e988116ce | ||
|  | ec9e722927 | ||
|  | 4cd5e8c378 | ||
|  | b759d5e06a | ||
|  | 1469033c1e | ||
|  | c15fd75df7 | ||
|  | 73524e01a6 | ||
|  | 9e54e11113 | ||
|  | 01ac5f29db | ||
|  | 67a2241e32 | ||
|  | 72b6dc3de7 | ||
|  | 6f5b645995 | ||
|  | 458ad1de57 | ||
|  | 216f48b7c1 | ||
|  | b2d1757e5a | ||
|  | 6e53eb9d5c | ||
|  | e5ee5be9c5 | ||
|  | bd237b2b95 | ||
|  | d31cf766eb | ||
|  | 56d530ff04 | ||
|  | 0bbb2240f2 | ||
|  | 1c8e4dba73 | ||
|  | 4a9956c4a4 | ||
|  | 59c0e6ae32 | ||
|  | 94c30fc21e | ||
|  | 8bb3b3be20 | ||
|  | 85e3c2c5a2 | ||
|  | 4be381c597 | ||
|  | 6ff5470cf1 | ||
|  | 151dcfdef9 | ||
|  | c282b4cb9f | ||
|  | c426f4626c | ||
|  | 0e3c92626e | ||
|  | 5099525e24 | ||
|  | e22b4cbb67 | ||
|  | 2b48828179 | ||
|  | 3e181362dd | ||
|  | 71fd98e39e | ||
|  | 71cd8b6d51 | ||
|  | ad75fcbf7e | ||
|  | f8b04a6357 | ||
|  | d8fcbb78d3 | ||
|  | 8408bf3789 | ||
|  | 3e1185658e | ||
|  | d778cdcd61 | ||
|  | 90b303fc03 | ||
|  | eb86b1270d | ||
|  | a1f0cc878b | ||
|  | f2e2720b15 | ||
|  | ec8cfe1591 | ||
|  | 22eac159e5 | ||
|  | 956b0c3fa7 | ||
|  | a6427e0949 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -29,3 +29,5 @@ parsetab.py | |||||||
|  |  | ||||||
| .gradle | .gradle | ||||||
| /prog8compiler.jar | /prog8compiler.jar | ||||||
|  | sd*.img | ||||||
|  | *.d64 | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="CompilerConfiguration"> | ||||||
|  |     <option name="BUILD_PROCESS_HEAP_SIZE" value="1200" /> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										7
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							| @@ -3,14 +3,11 @@ | |||||||
|     <option name="myName" value="Project Default" /> |     <option name="myName" value="Project Default" /> | ||||||
|     <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true"> |     <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||||
|       <Languages> |       <Languages> | ||||||
|         <language minSize="100" isEnabled="false" name="JavaScript" /> |  | ||||||
|         <language isEnabled="false" name="Groovy" /> |  | ||||||
|         <language isEnabled="false" name="Style Sheets" /> |  | ||||||
|         <language minSize="70" name="Kotlin" /> |         <language minSize="70" name="Kotlin" /> | ||||||
|         <language isEnabled="false" name="TypeScript" /> |         <language isEnabled="false" name="Groovy" /> | ||||||
|         <language isEnabled="false" name="ActionScript" /> |  | ||||||
|       </Languages> |       </Languages> | ||||||
|     </inspection_tool> |     </inspection_tool> | ||||||
|  |     <inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" /> | ||||||
|     <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true"> |     <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true"> | ||||||
|       <option name="processCode" value="false" /> |       <option name="processCode" value="false" /> | ||||||
|       <option name="processLiterals" value="true" /> |       <option name="processLiterals" value="true" /> | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="Kotlin2JvmCompilerArguments"> |   <component name="Kotlin2JvmCompilerArguments"> | ||||||
|     <option name="jvmTarget" value="1.8" /> |     <option name="jvmTarget" value="11" /> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
							
								
								
									
										9
									
								
								.idea/libraries/antlr_4_7_2_complete.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/antlr_4_7_2_complete.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,9 +0,0 @@ | |||||||
| <component name="libraryTable"> |  | ||||||
|   <library name="antlr-4.7.2-complete"> |  | ||||||
|     <CLASSES> |  | ||||||
|       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.7.2-complete.jar!/" /> |  | ||||||
|     </CLASSES> |  | ||||||
|     <JAVADOC /> |  | ||||||
|     <SOURCES /> |  | ||||||
|   </library> |  | ||||||
| </component> |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| <component name="libraryTable"> | <component name="libraryTable"> | ||||||
|   <library name="antlr-4.8-complete"> |   <library name="antlr-4.9-complete"> | ||||||
|     <CLASSES> |     <CLASSES> | ||||||
|       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" /> |       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9-complete.jar!/" /> | ||||||
|     </CLASSES> |     </CLASSES> | ||||||
|     <JAVADOC /> |     <JAVADOC /> | ||||||
|     <SOURCES /> |     <SOURCES /> | ||||||
							
								
								
									
										9
									
								
								.idea/libraries/antlr_runtime_4_7_2.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/antlr_runtime_4_7_2.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,9 +0,0 @@ | |||||||
| <component name="libraryTable"> |  | ||||||
|   <library name="antlr-runtime-4.7.2"> |  | ||||||
|     <CLASSES> |  | ||||||
|       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.7.2.jar!/" /> |  | ||||||
|     </CLASSES> |  | ||||||
|     <JAVADOC /> |  | ||||||
|     <SOURCES /> |  | ||||||
|   </library> |  | ||||||
| </component> |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| <component name="libraryTable"> | <component name="libraryTable"> | ||||||
|   <library name="antlr-runtime-4.8"> |   <library name="antlr-runtime-4.9"> | ||||||
|     <CLASSES> |     <CLASSES> | ||||||
|       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" /> |       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.jar!/" /> | ||||||
|     </CLASSES> |     </CLASSES> | ||||||
|     <JAVADOC /> |     <JAVADOC /> | ||||||
|     <SOURCES /> |     <SOURCES /> | ||||||
							
								
								
									
										9
									
								
								.idea/libraries/dbus_java_3_2_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/dbus_java_3_2_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="dbus-java-3.2.4"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
							
								
								
									
										10
									
								
								.idea/libraries/javax_json_api_1_1_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/libraries/javax_json_api_1_1_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="javax.json-api-1.1.4"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" /> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| <component name="libraryTable"> | <component name="libraryTable"> | ||||||
|   <library name="kotlinx-cli-jvm-0.1.0-dev-5"> |   <library name="kotlinx-cli-jvm"> | ||||||
|     <CLASSES> |     <CLASSES> | ||||||
|       <root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" /> |       <root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" /> | ||||||
|     </CLASSES> |     </CLASSES> | ||||||
|     <JAVADOC /> |     <JAVADOC /> | ||||||
|     <SOURCES /> |     <SOURCES /> | ||||||
							
								
								
									
										10
									
								
								.idea/libraries/slf4j_api_1_7_30.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/libraries/slf4j_api_1_7_30.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="slf4j-api-1.7.30"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-api-1.7.30.jar!/" /> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-simple-1.7.30.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
							
								
								
									
										12
									
								
								.idea/libraries/takes_http.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.idea/libraries/takes_http.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="takes-http"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/cactoos-0.42.jar!/" /> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-lang3-3.7.jar!/" /> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-text-1.4.jar!/" /> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/takes-1.19.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
							
								
								
									
										2
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @@ -16,7 +16,7 @@ | |||||||
|       </list> |       </list> | ||||||
|     </option> |     </option> | ||||||
|   </component> |   </component> | ||||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK"> |   <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK"> | ||||||
|     <output url="file://$PROJECT_DIR$/out" /> |     <output url="file://$PROJECT_DIR$/out" /> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
							
								
								
									
										3
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							| @@ -3,8 +3,11 @@ | |||||||
|   <component name="ProjectModuleManager"> |   <component name="ProjectModuleManager"> | ||||||
|     <modules> |     <modules> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" /> |       <module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" /> |       <module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" /> | ||||||
|       <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$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.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" /> | ||||||
|     </modules> |     </modules> | ||||||
|   </component> |   </component> | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,3 +1,10 @@ | |||||||
|  |  | ||||||
|  | This sofware license is for Prog8 the compiler + associated libraries. | ||||||
|  | The software generated by running the compiler is excluded from this. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                     GNU GENERAL PUBLIC LICENSE |                     GNU GENERAL PUBLIC LICENSE | ||||||
|                        Version 3, 29 June 2007 |                        Version 3, 29 June 2007 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										62
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								README.md
									
									
									
									
									
								
							| @@ -7,9 +7,6 @@ Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors | |||||||
|  |  | ||||||
| *Written by Irmen de Jong (irmen@razorvine.net)* | *Written by Irmen de Jong (irmen@razorvine.net)* | ||||||
|  |  | ||||||
| *Software license: GNU GPL 3.0, see file LICENSE* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's | This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's | ||||||
| as used in many home computers from that era. It is a medium to low level programming language, | as used in many home computers from that era. It is a medium to low level programming language, | ||||||
| which aims to provide many conveniences over raw assembly code (even when using a macro assembler). | which aims to provide many conveniences over raw assembly code (even when using a macro assembler). | ||||||
| @@ -19,38 +16,49 @@ Documentation | |||||||
| Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at: | Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at: | ||||||
| https://prog8.readthedocs.io/ | https://prog8.readthedocs.io/ | ||||||
|  |  | ||||||
|  | Software license | ||||||
|  | ---------------- | ||||||
|  | GNU GPL 3.0, see file LICENSE | ||||||
|  |  | ||||||
| What use Prog8 provide? | - prog8 (the compiler + libraries) is licensed under GNU GPL 3.0 | ||||||
| ----------------------- | - *exception:* the resulting files created by running the compiler are free to use in whatever way desired. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | What does Prog8 provide? | ||||||
|  | ------------------------ | ||||||
|  |  | ||||||
| - reduction of source code length over raw assembly | - reduction of source code length over raw assembly | ||||||
|  | - fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8. | ||||||
| - modularity, symbol scoping, subroutines | - modularity, symbol scoping, subroutines | ||||||
| - various data types other than just bytes (16-bit words, floats, strings) | - various data types other than just bytes (16-bit words, floats, strings) | ||||||
| - automatic variable allocations, automatic string and array variables and string sharing | - floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do) | ||||||
| - subroutines with an input- and output parameter signature | - 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. | ||||||
| - constant folding in expressions | - automatic static variable allocations, automatic string and array variables and string sharing | ||||||
|  | - subroutines with input parameters and result values | ||||||
|  | - high-level program optimizations | ||||||
|  | - small program boilerplate/compilersupport overhead | ||||||
|  | - programs can be run multiple times without reloading because of automatic variable (re)initializations. | ||||||
| - conditional branches | - 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 | ||||||
| - structs to group together sets of variables and manipulate them at once | - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse`` | ||||||
| - floating point operations  (requires the C64 Basic ROM routines for this) | - various powerful built-in libraries to do I/O, number conversions, graphics and more   | ||||||
| - abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses | - convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses | ||||||
| - various code optimizations (code structure, logical and numerical expressions, unused code removal...) |  | ||||||
| - 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 | ||||||
| - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse`` | - supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64. | ||||||
|  | - encode strings and characters into petscii or screencodes as desired (C64/Cx16) | ||||||
|  |  | ||||||
| *Rapid edit-compile-run-debug cycle:* | *Rapid edit-compile-run-debug cycle:* | ||||||
|  |  | ||||||
| - use a modern PC to do the work on | - use a modern PC to do the work on, use nice editors and enjoy quick compilation times | ||||||
| - very quick compilation times |  | ||||||
| - can automatically run the program in the Vice emulator after succesful compilation | - can automatically run the program in the Vice emulator after succesful compilation | ||||||
| - breakpoints, that let the Vice emulator drop into the monitor if execution hits them | - breakpoints, that let the Vice emulator drop into the monitor if execution hits them | ||||||
| - source code labels automatically loaded in Vice emulator so it can show them in disassembly | - source code labels automatically loaded in Vice emulator so it can show them in disassembly | ||||||
|  |  | ||||||
| *Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): | *Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): | ||||||
|  |  | ||||||
| - "c64": Commodore-64  (6510 CPU = almost a 6502)  premium support. | - "c64": Commodore-64  (6510 CPU = almost a 6502) | ||||||
| - "cx16": [CommanderX16](https://www.commanderx16.com)  (65c02 CPU)  experimental support. | - "cx16": [CommanderX16](https://www.commanderx16.com)  (65c02 CPU) | ||||||
| - If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both 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 both machines (just change the compiler target flag)! | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -61,7 +69,7 @@ Additional required tools | |||||||
| A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project. | A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project. | ||||||
| For other platforms it is very easy to compile it yourself (make ; make install). | For other platforms it is very easy to compile it yourself (make ; make install). | ||||||
|  |  | ||||||
| A **Java runtime (jre or jdk), version 8 or newer**  is required to run a prepackaged version of the compiler. | A **Java runtime (jre or jdk), version 11 or newer**  is required to run a prepackaged version of the compiler. | ||||||
| If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance, | If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance, | ||||||
| IntelliJ IDEA with the Kotlin plugin). | IntelliJ IDEA with the Kotlin plugin). | ||||||
|  |  | ||||||
| @@ -84,9 +92,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | |||||||
|         ubyte candidate_prime = 2       ; is increased in the loop |         ubyte candidate_prime = 2       ; is increased in the loop | ||||||
|      |      | ||||||
|         sub start() { |         sub start() { | ||||||
|             ; clear the sieve, to reset starting situation on subsequent runs |             sys.memset(sieve, 256, false)   ; clear the sieve | ||||||
|             memset(sieve, 256, false)    |  | ||||||
|             ; calculate primes |  | ||||||
|             txt.print("prime numbers up to 255:\n\n") |             txt.print("prime numbers up to 255:\n\n") | ||||||
|             ubyte amount=0 |             ubyte amount=0 | ||||||
|             repeat { |             repeat { | ||||||
| @@ -97,17 +103,17 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | |||||||
|                 txt.print(", ") |                 txt.print(", ") | ||||||
|                 amount++ |                 amount++ | ||||||
|             } |             } | ||||||
|             txt.chrout('\n') |             txt.nl() | ||||||
|             txt.print("number of primes (expected 54): ") |             txt.print("number of primes (expected 54): ") | ||||||
|             txt.print_ub(amount) |             txt.print_ub(amount) | ||||||
|             txt.chrout('\n') |             txt.nl() | ||||||
|         } |         } | ||||||
|      |      | ||||||
|         sub find_next_prime() -> ubyte { |         sub find_next_prime() -> ubyte { | ||||||
|             while sieve[candidate_prime] { |             while sieve[candidate_prime] { | ||||||
|                 candidate_prime++ |                 candidate_prime++ | ||||||
|                 if candidate_prime==0 |                 if candidate_prime==0 | ||||||
|                     return 0        ; we wrapped; no more primes available in the sieve |                     return 0        ; we wrapped; no more primes | ||||||
|             } |             } | ||||||
|      |      | ||||||
|             ; found next one, mark the multiples and return it. |             ; found next one, mark the multiples and return it. | ||||||
| @@ -124,6 +130,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| when compiled an ran on a C-64 you'll get: | when compiled an ran on a C-64 you'll get: | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -140,7 +147,8 @@ If you want to play a video game, a fully working Tetris clone is included in th | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| The CommanderX16 compiler target is quite capable already too, here's a well known space ship  | There are a couple of examples specially made for the CommanderX16 compiler target. | ||||||
| animated in 3D with hidden line removal, in the CommanderX16 emulator: | For instance here's a well known space ship animated in 3D with hidden line removal, | ||||||
|  | in the CommanderX16 emulator: | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,41 +1,28 @@ | |||||||
| buildscript { |  | ||||||
|     dependencies { |  | ||||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| plugins { | plugins { | ||||||
|     // id "org.jetbrains.kotlin.jvm" version "1.4.10" |  | ||||||
|     id 'application' |  | ||||||
|     id 'org.jetbrains.dokka' version "0.9.18" |  | ||||||
|     id 'com.github.johnrengelman.shadow' version '5.2.0' |  | ||||||
|     id 'java' |     id 'java' | ||||||
|  |     id 'application' | ||||||
|  |     id "org.jetbrains.kotlin.jvm" version "1.5.10" | ||||||
|  |     id 'com.github.johnrengelman.shadow' version '6.1.0' | ||||||
| } | } | ||||||
|  |  | ||||||
| apply plugin: "kotlin" | targetCompatibility = 11 | ||||||
| apply plugin: "java" | sourceCompatibility = 11 | ||||||
|  |  | ||||||
| targetCompatibility = 1.8 |  | ||||||
| sourceCompatibility = 1.8 |  | ||||||
|  |  | ||||||
| repositories { | repositories { | ||||||
|     mavenLocal() |     mavenLocal() | ||||||
|     mavenCentral() |     mavenCentral() | ||||||
|     jcenter() |     maven { url "https://kotlin.bintray.com/kotlinx" } | ||||||
|     maven { url "https://dl.bintray.com/orangy/maven/" } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| def prog8version = rootProject.file('compiler/res/version.txt').text.trim() | def prog8version = rootProject.file('compiler/res/version.txt').text.trim() | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation project(':parser') |     implementation project(':compilerAst') | ||||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" |     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" | ||||||
|     // implementation "org.jetbrains.kotlin:kotlin-reflect" |     // implementation "org.jetbrains.kotlin:kotlin-reflect" | ||||||
|     implementation 'org.antlr:antlr4-runtime:4.8' |     implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2' | ||||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5' |     // implementation 'net.razorvine:ksim65:1.8' | ||||||
|     // implementation 'net.razorvine:ksim65:1.6' |     // implementation "com.github.hypfvieh:dbus-java:3.2.4" | ||||||
|     // implementation "com.github.hypfvieh:dbus-java:3.2.0" |  | ||||||
|     implementation project(':parser') |  | ||||||
|  |  | ||||||
|     testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" |     testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" | ||||||
|     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' |     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' | ||||||
| @@ -45,7 +32,8 @@ dependencies { | |||||||
|  |  | ||||||
| compileKotlin { | compileKotlin { | ||||||
|     kotlinOptions { |     kotlinOptions { | ||||||
|         jvmTarget = "1.8" |         jvmTarget = "11" | ||||||
|  |         useIR = true | ||||||
|         // verbose = true |         // verbose = true | ||||||
|         // freeCompilerArgs += "-XXLanguage:+NewInference" |         // freeCompilerArgs += "-XXLanguage:+NewInference" | ||||||
|     } |     } | ||||||
| @@ -53,7 +41,8 @@ compileKotlin { | |||||||
|  |  | ||||||
| compileTestKotlin { | compileTestKotlin { | ||||||
|     kotlinOptions { |     kotlinOptions { | ||||||
|         jvmTarget = "1.8" |         jvmTarget = "11" | ||||||
|  |         useIR = true | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -76,7 +65,8 @@ sourceSets { | |||||||
| startScripts.enabled = true | startScripts.enabled = true | ||||||
|  |  | ||||||
| application { | application { | ||||||
|     mainClassName = 'prog8.CompilerMainKt' |     mainClass = 'prog8.CompilerMainKt' | ||||||
|  |     mainClassName = 'prog8.CompilerMainKt'  // deprecated | ||||||
|     applicationName = 'p8compile' |     applicationName = 'p8compile' | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -106,11 +96,6 @@ test { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| dokka { |  | ||||||
|     outputFormat = 'html' |  | ||||||
|     outputDirectory = "$buildDir/kdoc" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| task wrapper(type: Wrapper) { | task wrapper(type: Wrapper) { | ||||||
|     gradleVersion = '6.1.1' |     gradleVersion = '6.7' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,10 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <module type="JAVA_MODULE" version="4"> | <module type="JAVA_MODULE" version="4"> | ||||||
|  |   <component name="FacetManager"> | ||||||
|  |     <facet type="Python" name="Python"> | ||||||
|  |       <configuration sdkName="Python 3.9" /> | ||||||
|  |     </facet> | ||||||
|  |   </component> | ||||||
|   <component name="NewModuleRootManager" inherit-compiler-output="true"> |   <component name="NewModuleRootManager" inherit-compiler-output="true"> | ||||||
|     <exclude-output /> |     <exclude-output /> | ||||||
|     <content url="file://$MODULE_DIR$"> |     <content url="file://$MODULE_DIR$"> | ||||||
| @@ -8,12 +13,12 @@ | |||||||
|       <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> |       <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/build" /> |       <excludeFolder url="file://$MODULE_DIR$/build" /> | ||||||
|     </content> |     </content> | ||||||
|     <orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" /> |     <orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" /> | ||||||
|     <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="parser" /> |  | ||||||
|     <orderEntry type="library" name="unittest-libs" level="project" /> |     <orderEntry type="library" name="unittest-libs" level="project" /> | ||||||
|     <orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" /> |     <orderEntry type="library" name="kotlinx-cli-jvm" level="project" /> | ||||||
|     <orderEntry type="library" name="antlr-runtime-4.8" level="project" /> |     <orderEntry type="module" module-name="compilerAst" /> | ||||||
|  |     <orderEntry type="library" name="Python 3.9 interpreter library" level="application" /> | ||||||
|   </component> |   </component> | ||||||
| </module> | </module> | ||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								compiler/lib/kotlinx-cli-jvm-0.3.1.jar
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								compiler/lib/kotlinx-cli-jvm-0.3.1.jar
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										20
									
								
								compiler/res/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								compiler/res/.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | root = true | ||||||
|  |  | ||||||
|  | [*] | ||||||
|  | charset = utf-8 | ||||||
|  | end_of_line = lf | ||||||
|  | indent_size = 4 | ||||||
|  | indent_style = space | ||||||
|  | insert_final_newline = true | ||||||
|  | max_line_length = 120 | ||||||
|  | tab_width = 8 | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  | ij_smart_tabs = true | ||||||
|  |  | ||||||
|  | [*.p8] | ||||||
|  | indent_size = 4 | ||||||
|  | indent_style = space | ||||||
|  |  | ||||||
|  | [*.asm] | ||||||
|  | indent_size = 8 | ||||||
|  | indent_style = tab | ||||||
| @@ -2,6 +2,8 @@ | |||||||
|  |  | ||||||
| FL_ONE_const	.byte  129     			; 1.0 | FL_ONE_const	.byte  129     			; 1.0 | ||||||
| FL_ZERO_const	.byte  0,0,0,0,0		; 0.0 | FL_ZERO_const	.byte  0,0,0,0,0		; 0.0 | ||||||
|  | FL_LOG2_const	.byte  $80, $31, $72, $17, $f8	; log(2) | ||||||
|  |  | ||||||
|  |  | ||||||
| floats_store_reg	.byte  0		; temp storage | floats_store_reg	.byte  0		; temp storage | ||||||
|  |  | ||||||
| @@ -55,13 +57,76 @@ w2float		.proc | |||||||
| 		jmp  ub2float._fac_to_mem | 		jmp  ub2float._fac_to_mem | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | cast_from_uw	.proc | ||||||
|  | 		; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2) | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  GIVUAYFAY | ||||||
|  | 		jmp  ub2float._fac_to_mem | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | cast_from_w	.proc | ||||||
|  | 		; -- word in A/Y into float var at (P8ZP_SCRATCH_W2) | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  GIVAYFAY | ||||||
|  | 		jmp  ub2float._fac_to_mem | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | cast_from_ub	.proc | ||||||
|  | 		; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2) | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FREADUY | ||||||
|  | 		jmp  ub2float._fac_to_mem | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | cast_from_b	.proc | ||||||
|  | 		; -- byte in A into float var at (P8ZP_SCRATCH_W2) | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FREADSA | ||||||
|  | 		jmp  ub2float._fac_to_mem | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | cast_as_uw_into_ya	.proc               ; also used for float 2 ub | ||||||
|  | 		; -- cast float at A/Y to uword into Y/A | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		jmp  cast_FAC1_as_uw_into_ya | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | cast_as_w_into_ay	.proc               ; also used for float 2 b | ||||||
|  | 		; -- cast float at A/Y to word into A/Y | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		jmp  cast_FAC1_as_w_into_ay | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | cast_FAC1_as_uw_into_ya	.proc               ; also used for float 2 ub | ||||||
|  | 		; -- cast fac1 to uword into Y/A | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  GETADR     ; into Y/A | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | cast_FAC1_as_w_into_ay	.proc               ; also used for float 2 b | ||||||
|  | 		; -- cast fac1 to word into A/Y | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  AYINT | ||||||
|  | 		ldy  $64 | ||||||
|  | 		lda  $65 | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| stack_b2float	.proc | stack_b2float	.proc | ||||||
| 		; -- b2float operating on the stack | 		; -- b2float operating on the stack | ||||||
| 		inx | 		inx | ||||||
| 		lda  P8ESTACK_LO,x | 		lda  P8ESTACK_LO,x | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		jsr  FREADSA | 		jsr  FREADSA | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| stack_w2float	.proc | stack_w2float	.proc | ||||||
| @@ -71,7 +136,7 @@ stack_w2float	.proc | |||||||
| 		lda  P8ESTACK_HI,x | 		lda  P8ESTACK_HI,x | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		jsr  GIVAYF | 		jsr  GIVAYF | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| stack_ub2float	.proc | stack_ub2float	.proc | ||||||
| @@ -82,7 +147,7 @@ stack_ub2float	.proc | |||||||
| 		tay | 		tay | ||||||
| 		lda  #0 | 		lda  #0 | ||||||
| 		jsr  GIVAYF | 		jsr  GIVAYF | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| stack_uw2float	.proc | stack_uw2float	.proc | ||||||
| @@ -92,7 +157,7 @@ stack_uw2float	.proc | |||||||
| 		ldy  P8ESTACK_HI,x | 		ldy  P8ESTACK_HI,x | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		jsr  GIVUAYFAY | 		jsr  GIVUAYFAY | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| stack_float2w	.proc               ; also used for float2b | stack_float2w	.proc               ; also used for float2b | ||||||
| @@ -146,22 +211,6 @@ push_float	.proc | |||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| func_rndf	.proc |  | ||||||
| 		; -- put a random floating point value on the stack |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		lda  #1 |  | ||||||
| 		jsr  FREADSA |  | ||||||
| 		jsr  RND		; rng into fac1 |  | ||||||
| 		ldx  #<_rndf_rnum5 |  | ||||||
| 		ldy  #>_rndf_rnum5 |  | ||||||
| 		jsr  MOVMF	; fac1 to mem X/Y |  | ||||||
| 		ldx  P8ZP_SCRATCH_REG |  | ||||||
| 		lda  #<_rndf_rnum5 |  | ||||||
| 		ldy  #>_rndf_rnum5 |  | ||||||
| 		jmp  push_float |  | ||||||
| _rndf_rnum5	.byte  0,0,0,0,0 |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| pop_float	.proc | pop_float	.proc | ||||||
| 		; ---- pops mflpt5 from stack to memory A/Y | 		; ---- pops mflpt5 from stack to memory A/Y | ||||||
| 		; (frees 3 stack positions = 6 bytes of which 1 is padding) | 		; (frees 3 stack positions = 6 bytes of which 1 is padding) | ||||||
| @@ -198,27 +247,6 @@ pop_float_fac1	.proc | |||||||
| 		jmp  MOVFM | 		jmp  MOVFM | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| pop_float_fac2	.proc |  | ||||||
| 		; -- pops float from stack into FAC2 |  | ||||||
| 		lda  #<fmath_float1 |  | ||||||
| 		ldy  #>fmath_float1 |  | ||||||
| 		jsr  pop_float |  | ||||||
| 		lda  #<fmath_float1 |  | ||||||
| 		ldy  #>fmath_float1 |  | ||||||
| 		jmp  CONUPK |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| pop_float_to_indexed_var	.proc |  | ||||||
| 		; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack |  | ||||||
| 		sta  P8ZP_SCRATCH_W1 |  | ||||||
| 		sty  P8ZP_SCRATCH_W1+1 |  | ||||||
| 		jsr  prog8_lib.pop_index_times_5 |  | ||||||
| 		jsr  prog8_lib.add_a_to_zpword |  | ||||||
| 		lda  P8ZP_SCRATCH_W1 |  | ||||||
| 		ldy  P8ZP_SCRATCH_W1+1 |  | ||||||
| 		jmp  pop_float |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| copy_float	.proc | copy_float	.proc | ||||||
| 		; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1, | 		; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1, | ||||||
| 		;    into the 5 bytes pointed to by A/Y.  Clobbers A,Y. | 		;    into the 5 bytes pointed to by A/Y.  Clobbers A,Y. | ||||||
| @@ -284,9 +312,11 @@ pop_2_floats_f2_in_fac1	.proc | |||||||
| fmath_float1	.byte 0,0,0,0,0	; storage for a mflpt5 value | fmath_float1	.byte 0,0,0,0,0	; storage for a mflpt5 value | ||||||
| fmath_float2	.byte 0,0,0,0,0	; storage for a mflpt5 value | fmath_float2	.byte 0,0,0,0,0	; storage for a mflpt5 value | ||||||
|  |  | ||||||
| push_fac1_as_result	.proc |  | ||||||
| 		; -- push the float in FAC1 onto the stack, and return from calculation | push_fac1	.proc | ||||||
| 		ldx  #<fmath_float1 | 		; -- push the float in FAC1 onto the stack | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | _internal	ldx  #<fmath_float1 | ||||||
| 		ldy  #>fmath_float1 | 		ldy  #>fmath_float1 | ||||||
| 		jsr  MOVMF | 		jsr  MOVMF | ||||||
| 		lda  #<fmath_float1 | 		lda  #<fmath_float1 | ||||||
| @@ -295,6 +325,7 @@ push_fac1_as_result	.proc | |||||||
| 		jmp  push_float | 		jmp  push_float | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| pow_f		.proc | pow_f		.proc | ||||||
| 		; -- push f1 ** f2 on stack | 		; -- push f1 ** f2 on stack | ||||||
| 		lda  #<fmath_float2 | 		lda  #<fmath_float2 | ||||||
| @@ -310,8 +341,7 @@ pow_f		.proc | |||||||
| 		lda  #<fmath_float2 | 		lda  #<fmath_float2 | ||||||
| 		ldy  #>fmath_float2 | 		ldy  #>fmath_float2 | ||||||
| 		jsr  FPWR | 		jsr  FPWR | ||||||
| 		ldx  P8ZP_SCRATCH_REG | 		jmp  push_fac1._internal | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| div_f		.proc | div_f		.proc | ||||||
| @@ -321,7 +351,7 @@ div_f		.proc | |||||||
| 		lda  #<fmath_float1 | 		lda  #<fmath_float1 | ||||||
| 		ldy  #>fmath_float1 | 		ldy  #>fmath_float1 | ||||||
| 		jsr  FDIV | 		jsr  FDIV | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| add_f		.proc | add_f		.proc | ||||||
| @@ -331,7 +361,7 @@ add_f		.proc | |||||||
| 		lda  #<fmath_float1 | 		lda  #<fmath_float1 | ||||||
| 		ldy  #>fmath_float1 | 		ldy  #>fmath_float1 | ||||||
| 		jsr  FADD | 		jsr  FADD | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| sub_f		.proc | sub_f		.proc | ||||||
| @@ -341,7 +371,7 @@ sub_f		.proc | |||||||
| 		lda  #<fmath_float1 | 		lda  #<fmath_float1 | ||||||
| 		ldy  #>fmath_float1 | 		ldy  #>fmath_float1 | ||||||
| 		jsr  FSUB | 		jsr  FSUB | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| mul_f		.proc | mul_f		.proc | ||||||
| @@ -351,7 +381,7 @@ mul_f		.proc | |||||||
| 		lda  #<fmath_float1 | 		lda  #<fmath_float1 | ||||||
| 		ldy  #>fmath_float1 | 		ldy  #>fmath_float1 | ||||||
| 		jsr  FMULT | 		jsr  FMULT | ||||||
| 		jmp  push_fac1_as_result | 		jmp  push_fac1._internal | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| neg_f		.proc | neg_f		.proc | ||||||
| @@ -362,11 +392,98 @@ neg_f		.proc | |||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| abs_f		.proc | var_fac1_less_f	.proc | ||||||
| 		; -- strip the sign bit on the stack | 		; -- is the float in FAC1 < the variable AY? | ||||||
| 		lda  P8ESTACK_HI+3,x | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		and  #$7f | 		jsr  FCOMP | ||||||
| 		sta  P8ESTACK_HI+3,x | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		cmp  #255 | ||||||
|  | 		beq  + | ||||||
|  | 		lda  #0 | ||||||
|  | 		rts | ||||||
|  | +		lda  #1 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | var_fac1_lesseq_f	.proc | ||||||
|  | 		; -- is the float in FAC1 <= the variable AY? | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		cmp  #0 | ||||||
|  | 		beq  + | ||||||
|  | 		cmp  #255 | ||||||
|  | 		beq  + | ||||||
|  | 		lda  #0 | ||||||
|  | 		rts | ||||||
|  | +		lda  #1 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | var_fac1_greater_f	.proc | ||||||
|  | 		; -- is the float in FAC1 > the variable AY? | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		cmp  #1 | ||||||
|  | 		beq  + | ||||||
|  | 		lda  #0 | ||||||
|  | 		rts | ||||||
|  | +		lda  #1 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | var_fac1_greatereq_f	.proc | ||||||
|  | 		; -- is the float in FAC1 >= the variable AY? | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		cmp  #0 | ||||||
|  | 		beq  + | ||||||
|  | 		cmp  #1 | ||||||
|  | 		beq  + | ||||||
|  | 		lda  #0 | ||||||
|  | 		rts | ||||||
|  | +		lda  #1 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | var_fac1_notequal_f	.proc | ||||||
|  | 		; -- are the floats numbers in FAC1 and the variable AY *not* identical? | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		and  #1 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | vars_equal_f	.proc | ||||||
|  | 		; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical? | ||||||
|  | 		sta  P8ZP_SCRATCH_W2 | ||||||
|  | 		sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		ldy  #0 | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		cmp  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		bne  _false | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		cmp  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		bne  _false | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		cmp  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		bne  _false | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		cmp  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		bne  _false | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		cmp  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		bne  _false | ||||||
|  | 		lda  #1 | ||||||
|  | 		rts | ||||||
|  | _false		lda  #0 | ||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| @@ -407,6 +524,40 @@ notequal_f	.proc | |||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  | vars_less_f	.proc | ||||||
|  | 		; -- is float in AY < float in P8ZP_SCRATCH_W2 ? | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		lda  P8ZP_SCRATCH_W2 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		cmp  #255 | ||||||
|  | 		bne  + | ||||||
|  | 		lda  #1 | ||||||
|  | 		rts | ||||||
|  | +		lda  #0 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | vars_lesseq_f	.proc | ||||||
|  | 		; -- is float in AY <= float in P8ZP_SCRATCH_W2 ? | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		lda  P8ZP_SCRATCH_W2 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		cmp  #255 | ||||||
|  | 		bne  + | ||||||
|  | -		lda  #1 | ||||||
|  | 		rts | ||||||
|  | +		cmp  #0 | ||||||
|  | 		beq  - | ||||||
|  | 		lda  #0 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
| less_f		.proc | less_f		.proc | ||||||
| 		; -- is f1 < f2? | 		; -- is f1 < f2? | ||||||
| 		jsr  compare_floats | 		jsr  compare_floats | ||||||
| @@ -468,252 +619,33 @@ _return_true	lda  #1 | |||||||
| 		bne  _return_result | 		bne  _return_result | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| func_sin	.proc | set_array_float_from_fac1	.proc | ||||||
| 		; -- push sin(f) back onto stack | 		; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1) | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  SIN |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_cos	.proc |  | ||||||
| 		; -- push cos(f) back onto stack |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  COS |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_tan	.proc |  | ||||||
| 		; -- push tan(f) back onto stack |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  TAN |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_atan	.proc |  | ||||||
| 		; -- push atan(f) back onto stack |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  ATN |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_ln		.proc |  | ||||||
| 		; -- push ln(f) back onto stack |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  LOG |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_log2	.proc |  | ||||||
| 		; -- push log base 2, ln(f)/ln(2), back onto stack |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  LOG |  | ||||||
| 		jsr  MOVEF |  | ||||||
| 		lda  #<c64.FL_LOG2 |  | ||||||
| 		ldy  #>c64.FL_LOG2 |  | ||||||
| 		jsr  MOVFM |  | ||||||
| 		jsr  FDIVT |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_sqrt	.proc |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  SQR |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_rad	.proc |  | ||||||
| 		; -- convert degrees to radians (d * pi / 180) |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		lda  #<_pi_div_180 |  | ||||||
| 		ldy  #>_pi_div_180 |  | ||||||
| 		jsr  FMULT |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| _pi_div_180	.byte 123, 14, 250, 53, 18		; pi / 180 |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_deg	.proc |  | ||||||
| 		; -- convert radians to degrees (d * (1/ pi * 180)) |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		lda  #<_one_over_pi_div_180 |  | ||||||
| 		ldy  #>_one_over_pi_div_180 |  | ||||||
| 		jsr  FMULT |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| _one_over_pi_div_180	.byte 134, 101, 46, 224, 211		; 1 / (pi * 180) |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_round	.proc |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  FADDH |  | ||||||
| 		jsr  INT |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_floor	.proc |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jsr  INT |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_ceil	.proc |  | ||||||
| 		; -- ceil: tr = int(f); if tr==f -> return  else return tr+1 |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		ldx  #<fmath_float1 |  | ||||||
| 		ldy  #>fmath_float1 |  | ||||||
| 		jsr  MOVMF |  | ||||||
| 		jsr  INT |  | ||||||
| 		lda  #<fmath_float1 |  | ||||||
| 		ldy  #>fmath_float1 |  | ||||||
| 		jsr  FCOMP |  | ||||||
| 		cmp  #0 |  | ||||||
| 		beq  + |  | ||||||
| 		lda  #<FL_ONE_const |  | ||||||
| 		ldy  #>FL_ONE_const |  | ||||||
| 		jsr  FADD |  | ||||||
| +		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_any_f	.proc |  | ||||||
| 		inx |  | ||||||
| 		lda  P8ESTACK_LO,x	; array size |  | ||||||
| 		sta  P8ZP_SCRATCH_B1 | 		sta  P8ZP_SCRATCH_B1 | ||||||
| 		asl  a | 		asl  a | ||||||
| 		asl  a | 		asl  a | ||||||
| 		clc | 		clc | ||||||
| 		adc  P8ZP_SCRATCH_B1	; times 5 because of float | 		adc  P8ZP_SCRATCH_B1 | ||||||
| 		jmp  prog8_lib.func_any_b._entry |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_all_f	.proc |  | ||||||
| 		inx |  | ||||||
| 		jsr  prog8_lib.peek_address |  | ||||||
| 		lda  P8ESTACK_LO,x	; array size |  | ||||||
| 		sta  P8ZP_SCRATCH_B1 |  | ||||||
| 		asl  a |  | ||||||
| 		asl  a |  | ||||||
| 		clc |  | ||||||
| 		adc  P8ZP_SCRATCH_B1	; times 5 because of float |  | ||||||
| 		tay |  | ||||||
| 		dey |  | ||||||
| -		lda  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		clc |  | ||||||
| 		dey |  | ||||||
| 		adc  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		dey |  | ||||||
| 		adc  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		dey |  | ||||||
| 		adc  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		dey |  | ||||||
| 		adc  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		dey |  | ||||||
| 		cmp  #0 |  | ||||||
| 		beq  + |  | ||||||
| 		cpy  #255 |  | ||||||
|         	bne  - |  | ||||||
| 		lda  #1 |  | ||||||
| 		sta  P8ESTACK_LO+1,x |  | ||||||
| 		rts |  | ||||||
| +		sta  P8ESTACK_LO+1,x |  | ||||||
| 		rts |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_max_f	.proc |  | ||||||
| 		lda  #255 |  | ||||||
| 		sta  _minmax_cmp+1 |  | ||||||
| 		lda  #<_largest_neg_float |  | ||||||
| 		ldy  #>_largest_neg_float |  | ||||||
| _minmax_entry	jsr  MOVFM |  | ||||||
| 		jsr  prog8_lib.pop_array_and_lengthmin1Y |  | ||||||
| 		stx  floats_store_reg |  | ||||||
| -		sty  P8ZP_SCRATCH_REG |  | ||||||
| 		lda  P8ZP_SCRATCH_W1 |  | ||||||
| 		ldy  P8ZP_SCRATCH_W1+1 | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
| 		jsr  FCOMP |  | ||||||
| _minmax_cmp	cmp  #255			; modified |  | ||||||
| 		bne  + |  | ||||||
| 		lda  P8ZP_SCRATCH_W1 |  | ||||||
| 		ldy  P8ZP_SCRATCH_W1+1 |  | ||||||
| 		jsr  MOVFM |  | ||||||
| +		lda  P8ZP_SCRATCH_W1 |  | ||||||
| 		clc | 		clc | ||||||
| 		adc  #5 | 		adc  P8ZP_SCRATCH_W1 | ||||||
| 		sta  P8ZP_SCRATCH_W1 |  | ||||||
| 		bcc  + | 		bcc  + | ||||||
| 		inc  P8ZP_SCRATCH_W1+1 | 		iny | ||||||
| +		ldy  P8ZP_SCRATCH_REG | +		stx  floats_store_reg | ||||||
| 		dey | 		tax | ||||||
| 		cpy  #255 | 		jsr  MOVMF | ||||||
| 		bne  - |  | ||||||
| 		ldx  floats_store_reg | 		ldx  floats_store_reg | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| _largest_neg_float	.byte 255,255,255,255,255		; largest negative float -1.7014118345e+38 |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_min_f	.proc |  | ||||||
| 		lda  #1 |  | ||||||
| 		sta  func_max_f._minmax_cmp+1 |  | ||||||
| 		lda  #<_largest_pos_float |  | ||||||
| 		ldy  #>_largest_pos_float |  | ||||||
| 		jmp  func_max_f._minmax_entry |  | ||||||
| _largest_pos_float	.byte  255,127,255,255,255		; largest positive float |  | ||||||
| 		rts |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| func_sum_f	.proc |  | ||||||
| 		lda  #<FL_ZERO_const |  | ||||||
| 		ldy  #>FL_ZERO_const |  | ||||||
| 		jsr  MOVFM |  | ||||||
| 		jsr  prog8_lib.pop_array_and_lengthmin1Y |  | ||||||
| 		stx  floats_store_reg |  | ||||||
| -		sty  P8ZP_SCRATCH_REG |  | ||||||
| 		lda  P8ZP_SCRATCH_W1 |  | ||||||
| 		ldy  P8ZP_SCRATCH_W1+1 |  | ||||||
| 		jsr  FADD |  | ||||||
| 		ldy  P8ZP_SCRATCH_REG |  | ||||||
| 		dey |  | ||||||
| 		cpy  #255 |  | ||||||
| 		beq  + |  | ||||||
| 		lda  P8ZP_SCRATCH_W1 |  | ||||||
| 		clc |  | ||||||
| 		adc  #5 |  | ||||||
| 		sta  P8ZP_SCRATCH_W1 |  | ||||||
| 		bcc  - |  | ||||||
| 		inc  P8ZP_SCRATCH_W1+1 |  | ||||||
| 		bne  - |  | ||||||
| +		ldx  floats_store_reg |  | ||||||
| 		stx  P8ZP_SCRATCH_REG |  | ||||||
| 		jmp  push_fac1_as_result |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| sign_f		.proc |  | ||||||
| 		jsr  pop_float_fac1 |  | ||||||
| 		jsr  SIGN |  | ||||||
| 		sta  P8ESTACK_LO,x |  | ||||||
| 		dex |  | ||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| set_0_array_float	.proc | set_0_array_float	.proc | ||||||
| 		; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1) | 		; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1) | ||||||
| 		inx | 		sta  P8ZP_SCRATCH_B1 | ||||||
| 		lda  P8ESTACK_LO,x |  | ||||||
| 		asl  a | 		asl  a | ||||||
| 		asl  a | 		asl  a | ||||||
| 		clc | 		clc | ||||||
| 		adc  P8ESTACK_LO,x | 		adc  P8ZP_SCRATCH_B1 | ||||||
| 		tay | 		tay | ||||||
| 		lda  #0 | 		lda  #0 | ||||||
| 		sta  (P8ZP_SCRATCH_W1),y | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
| @@ -730,13 +662,12 @@ set_0_array_float	.proc | |||||||
|  |  | ||||||
|  |  | ||||||
| set_array_float		.proc | set_array_float		.proc | ||||||
| 		; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2) | 		; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2) | ||||||
| 		inx | 		sta  P8ZP_SCRATCH_B1 | ||||||
| 		lda  P8ESTACK_LO,x |  | ||||||
| 		asl  a | 		asl  a | ||||||
| 		asl  a | 		asl  a | ||||||
| 		clc | 		clc | ||||||
| 		adc  P8ESTACK_LO,x | 		adc  P8ZP_SCRATCH_B1 | ||||||
| 		adc  P8ZP_SCRATCH_W2 | 		adc  P8ZP_SCRATCH_W2 | ||||||
| 		ldy  P8ZP_SCRATCH_W2+1 | 		ldy  P8ZP_SCRATCH_W2+1 | ||||||
| 		bcc  + | 		bcc  + | ||||||
| @@ -747,16 +678,3 @@ set_array_float		.proc | |||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| swap_floats	.proc |  | ||||||
| 		; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2 |  | ||||||
| 		ldy  #4 |  | ||||||
| -               lda  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		pha |  | ||||||
| 		lda  (P8ZP_SCRATCH_W2),y |  | ||||||
| 		sta  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		pla |  | ||||||
| 		sta  (P8ZP_SCRATCH_W2),y |  | ||||||
| 		dey |  | ||||||
| 		bpl  - |  | ||||||
| 		rts |  | ||||||
| 		.pend |  | ||||||
|   | |||||||
| @@ -19,23 +19,6 @@ floats { | |||||||
| 		; note: the fac1 and fac2 are working registers and take 6 bytes each, | 		; note: the fac1 and fac2 are working registers and take 6 bytes each, | ||||||
| 		; floats in memory  (and rom) are stored in 5-byte MFLPT packed format. | 		; floats in memory  (and rom) are stored in 5-byte MFLPT packed format. | ||||||
|  |  | ||||||
| 		; constants in five-byte "mflpt" format in the BASIC ROM |  | ||||||
| 		&float  FL_PIVAL	= $aea8  ; 3.1415926... |  | ||||||
| 		&float  FL_N32768	= $b1a5  ; -32768 |  | ||||||
| 		&float  FL_FONE		= $b9bc  ; 1 |  | ||||||
| 		&float  FL_SQRHLF	= $b9d6  ; SQR(2) / 2 |  | ||||||
| 		&float  FL_SQRTWO	= $b9db  ; SQR(2) |  | ||||||
| 		&float  FL_NEGHLF	= $b9e0  ; -.5 |  | ||||||
| 		&float  FL_LOG2		= $b9e5  ; LOG(2) |  | ||||||
| 		&float  FL_TENC		= $baf9  ; 10 |  | ||||||
| 		&float  FL_NZMIL	= $bdbd  ; 1e9 (1 billion) |  | ||||||
| 		&float  FL_FHALF	= $bf11  ; .5 |  | ||||||
| 		&float  FL_LOGEB2	= $bfbf  ; 1 / LOG(2) |  | ||||||
| 		&float  FL_PIHALF	= $e2e0  ; PI / 2 |  | ||||||
| 		&float  FL_TWOPI	= $e2e5  ; 2 * PI |  | ||||||
| 		&float  FL_FR4		= $e2ea  ; .25 |  | ||||||
| 		; oddly enough, 0.0 isn't available in the kernel. |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; note: fac1/2 might get clobbered even if not mentioned in the function's name. | ; note: fac1/2 might get clobbered even if not mentioned in the function's name. | ||||||
| ; note: for subtraction and division, the left operand is in fac2, the right operand in fac1. | ; note: for subtraction and division, the left operand is in fac2, the right operand in fac1. | ||||||
| @@ -100,7 +83,7 @@ romsub $bc58 = ABS()                                        ; fac1 = ABS(fac1) | |||||||
| romsub $bf71 = SQR() clobbers(A,X,Y)                        ; fac1 = SQRT(fac1) | romsub $bf71 = SQR() clobbers(A,X,Y)                        ; fac1 = SQRT(fac1) | ||||||
| romsub $bf74 = SQRA() clobbers(A,X,Y)                       ; fac1 = SQRT(fac2) | romsub $bf74 = SQRA() clobbers(A,X,Y)                       ; fac1 = SQRT(fac2) | ||||||
| romsub $bfed = EXP() clobbers(A,X,Y)                        ; fac1 = EXP(fac1)  (e ** fac1) | romsub $bfed = EXP() clobbers(A,X,Y)                        ; fac1 = EXP(fac1)  (e ** fac1) | ||||||
| romsub $bfb4 = NEGOP() clobbers(A)                          ; switch the sign of fac1 | romsub $bfb4 = NEGOP() clobbers(A)                          ; switch the sign of fac1 (fac1 = -fac1) | ||||||
| romsub $e097 = RND() clobbers(A,X,Y)                        ; fac1 = RND(fac1) float random number generator | romsub $e097 = RND() clobbers(A,X,Y)                        ; fac1 = RND(fac1) float random number generator | ||||||
| romsub $e264 = COS() clobbers(A,X,Y)                        ; fac1 = COS(fac1) | romsub $e264 = COS() clobbers(A,X,Y)                        ; fac1 = COS(fac1) | ||||||
| romsub $e26b = SIN() clobbers(A,X,Y)                        ; fac1 = SIN(fac1) | romsub $e26b = SIN() clobbers(A,X,Y)                        ; fac1 = SIN(fac1) | ||||||
| @@ -212,6 +195,7 @@ sub  print_f  (float value) { | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| %asminclude "library:c64/floats.asm", "" | %asminclude "library:c64/floats.asm" | ||||||
|  | %asminclude "library:c64/floats_funcs.asm" | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										437
									
								
								compiler/res/prog8lib/c64/floats_funcs.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										437
									
								
								compiler/res/prog8lib/c64/floats_funcs.asm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,437 @@ | |||||||
|  | ; --- floating point builtin functions | ||||||
|  |  | ||||||
|  |  | ||||||
|  | abs_f_stack	.proc | ||||||
|  | 		; -- push abs(AY) on stack | ||||||
|  | 		jsr  floats.MOVFM | ||||||
|  | 		jsr  floats.ABS | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | abs_f_fac1	.proc | ||||||
|  | 		; -- FAC1 = abs(AY) | ||||||
|  | 		jsr  floats.MOVFM | ||||||
|  | 		jmp  floats.ABS | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_atan_stack	.proc | ||||||
|  | 		jsr  func_atan_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_atan_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  ATN | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ceil_stack	.proc | ||||||
|  | 		jsr  func_ceil_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ceil_fac1	.proc | ||||||
|  | 		; -- ceil: tr = int(f); if tr==f -> return  else return tr+1 | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		ldx  #<fmath_float1 | ||||||
|  | 		ldy  #>fmath_float1 | ||||||
|  | 		jsr  MOVMF | ||||||
|  | 		jsr  INT | ||||||
|  | 		lda  #<fmath_float1 | ||||||
|  | 		ldy  #>fmath_float1 | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		cmp  #0 | ||||||
|  | 		beq  + | ||||||
|  | 		lda  #<FL_ONE_const | ||||||
|  | 		ldy  #>FL_ONE_const | ||||||
|  | 		jsr  FADD | ||||||
|  | +		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_floor_stack	.proc | ||||||
|  | 		jsr  func_floor_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_floor_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  INT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_round_stack	.proc | ||||||
|  | 		jsr  func_round_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_round_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FADDH | ||||||
|  | 		jsr  INT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sin_stack	.proc | ||||||
|  | 		jsr  func_sin_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sin_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  SIN | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_cos_stack	.proc | ||||||
|  | 		jsr  func_cos_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_cos_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  COS | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_tan_stack	.proc | ||||||
|  | 		jsr  func_tan_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_tan_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  TAN | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rad_stack	.proc | ||||||
|  | 		jsr  func_rad_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rad_fac1	.proc | ||||||
|  | 		; -- convert degrees to radians (d * pi / 180) | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  #<_pi_div_180 | ||||||
|  | 		ldy  #>_pi_div_180 | ||||||
|  | 		jsr  FMULT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | _pi_div_180	.byte 123, 14, 250, 53, 18		; pi / 180 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_deg_stack	.proc | ||||||
|  | 		jsr  func_deg_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_deg_fac1	.proc | ||||||
|  | 		; -- convert radians to degrees (d * (1/ pi * 180)) | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  #<_one_over_pi_div_180 | ||||||
|  | 		ldy  #>_one_over_pi_div_180 | ||||||
|  | 		jsr  FMULT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | _one_over_pi_div_180	.byte 134, 101, 46, 224, 211		; 1 / (pi * 180) | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ln_stack	.proc | ||||||
|  | 		jsr  func_ln_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ln_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  LOG | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_log2_stack	.proc | ||||||
|  | 		jsr  func_log2_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_log2_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  LOG | ||||||
|  | 		jsr  MOVEF | ||||||
|  | 		lda  #<FL_LOG2_const | ||||||
|  | 		ldy  #>FL_LOG2_const | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		jsr  FDIVT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sign_f_stack	.proc | ||||||
|  | 		jsr  func_sign_f_into_A | ||||||
|  | 		sta  P8ESTACK_LO,x | ||||||
|  | 		dex | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sign_f_into_A	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		jmp  SIGN | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sqrt_stack	.proc | ||||||
|  | 		jsr  func_sqrt_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sqrt_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  SQR | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rndf_stack	.proc | ||||||
|  | 		jsr  func_rndf_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rndf_fac1	.proc | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  #1 | ||||||
|  | 		jsr  FREADSA | ||||||
|  | 		jsr  RND		; rng into fac1 | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_swap_f	.proc | ||||||
|  | 		; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2 | ||||||
|  | 		ldy  #4 | ||||||
|  | -               lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		dey | ||||||
|  | 		bpl  - | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_reverse_f	.proc | ||||||
|  | 		; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A) | ||||||
|  | _left_index = P8ZP_SCRATCH_W2 | ||||||
|  | _right_index = P8ZP_SCRATCH_W2+1 | ||||||
|  | _loop_count = P8ZP_SCRATCH_REG | ||||||
|  | 		pha | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		sec | ||||||
|  | 		sbc  #5 | ||||||
|  | 		sta  _right_index | ||||||
|  | 		lda  #0 | ||||||
|  | 		sta  _left_index | ||||||
|  | 		pla | ||||||
|  | 		lsr  a | ||||||
|  | 		sta  _loop_count | ||||||
|  | _loop		; push the left indexed float on the stack | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		; copy right index float to left index float | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		; pop the float off the stack into the right index float | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		lda  _right_index | ||||||
|  | 		sec | ||||||
|  | 		sbc  #9 | ||||||
|  | 		sta  _right_index | ||||||
|  | 		dec  _loop_count | ||||||
|  | 		bne  _loop | ||||||
|  | 		rts | ||||||
|  |  | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | a_times_5	.proc | ||||||
|  | 		sta  P8ZP_SCRATCH_B1 | ||||||
|  | 		asl  a | ||||||
|  | 		asl  a | ||||||
|  | 		clc | ||||||
|  | 		adc  P8ZP_SCRATCH_B1 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_any_f_into_A	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_any_b_into_A | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_all_f_into_A	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_all_b_into_A | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_any_f_stack	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_any_b_stack | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_all_f_stack	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_all_b_stack | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_max_f_stack	.proc | ||||||
|  | 		jsr  func_max_f_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_max_f_fac1	.proc | ||||||
|  | 		; -- max(array) -> fac1,  array in P8ZP_SCRATCH_W1, num elts in A | ||||||
|  | _loop_count = P8ZP_SCRATCH_REG | ||||||
|  | 		stx  floats_store_reg | ||||||
|  | 		sta  _loop_count | ||||||
|  | 		lda  #255 | ||||||
|  | 		sta  _minmax_cmp+1		; modifying | ||||||
|  | 		lda  #<_largest_neg_float | ||||||
|  | 		ldy  #>_largest_neg_float | ||||||
|  | _minmax_entry	jsr  MOVFM | ||||||
|  | -		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		jsr  FCOMP | ||||||
|  | _minmax_cmp	cmp  #255			; modified | ||||||
|  | 		bne  + | ||||||
|  | 		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		jsr  MOVFM | ||||||
|  | +		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		clc | ||||||
|  | 		adc  #5 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		bcc  + | ||||||
|  | 		inc  P8ZP_SCRATCH_W1+1 | ||||||
|  | +		dec  _loop_count | ||||||
|  | 		bne  - | ||||||
|  | 		ldx  floats_store_reg | ||||||
|  | 		rts | ||||||
|  | _largest_neg_float	.byte 255,255,255,255,255		; largest negative float -1.7014118345e+38 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_min_f_stack	.proc | ||||||
|  | 		jsr  func_min_f_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_min_f_fac1	.proc | ||||||
|  | 		; -- min(array) -> fac1,  array in P8ZP_SCRATCH_W1, num elts in A | ||||||
|  | 		sta  func_max_f_fac1._loop_count | ||||||
|  | 		lda  #1 | ||||||
|  | 		sta  func_max_f_fac1._minmax_cmp+1 | ||||||
|  | 		lda  #<_largest_pos_float | ||||||
|  | 		ldy  #>_largest_pos_float | ||||||
|  | 		jmp  func_max_f_fac1._minmax_entry | ||||||
|  | _largest_pos_float	.byte  255,127,255,255,255		; largest positive float | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | func_sum_f_stack	.proc | ||||||
|  | 		jsr  func_sum_f_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sum_f_fac1	.proc | ||||||
|  | 		; -- sum(array) -> fac1,  array in P8ZP_SCRATCH_W1, num elts in A | ||||||
|  | _loop_count = P8ZP_SCRATCH_REG | ||||||
|  | 		stx  floats_store_reg | ||||||
|  | 		sta  _loop_count | ||||||
|  | 		lda  #<FL_ZERO_const | ||||||
|  | 		ldy  #>FL_ZERO_const | ||||||
|  | 		jsr  MOVFM | ||||||
|  | -		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		jsr  FADD | ||||||
|  | 		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		clc | ||||||
|  | 		adc  #5 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		bcc  + | ||||||
|  | 		inc  P8ZP_SCRATCH_W1+1 | ||||||
|  | +		dec  _loop_count | ||||||
|  | 		bne  - | ||||||
|  | 		ldx  floats_store_reg | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
| @@ -2,7 +2,7 @@ | |||||||
| %import textio | %import textio | ||||||
|  |  | ||||||
| ; bitmap pixel graphics module for the C64 | ; bitmap pixel graphics module for the C64 | ||||||
| ; only black/white monchrome 320x200 for now | ; only black/white monochrome 320x200 for now | ||||||
| ; assumes bitmap screen memory is $2000-$3fff | ; assumes bitmap screen memory is $2000-$3fff | ||||||
|  |  | ||||||
| graphics { | graphics { | ||||||
| @@ -12,35 +12,55 @@ graphics { | |||||||
|  |  | ||||||
|     sub enable_bitmap_mode() { |     sub enable_bitmap_mode() { | ||||||
|         ; enable bitmap screen, erase it and set colors to black/white. |         ; enable bitmap screen, erase it and set colors to black/white. | ||||||
|         c64.SCROLY |= %00100000 |         c64.SCROLY = %00111011 | ||||||
|  |         c64.SCROLX = %00001000 | ||||||
|         c64.VMCSB = (c64.VMCSB & %11110000) | %00001000   ; $2000-$3fff |         c64.VMCSB = (c64.VMCSB & %11110000) | %00001000   ; $2000-$3fff | ||||||
|         clear_screen(1, 0) |         clear_screen(1, 0) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     sub disable_bitmap_mode() { | ||||||
|  |         ; enables text mode, erase the text screen, color white | ||||||
|  |         c64.SCROLY = %00011011 | ||||||
|  |         c64.SCROLX = %00001000 | ||||||
|  |         c64.VMCSB = (c64.VMCSB & %11110000) | %00000100   ; $1000-$2fff | ||||||
|  |         txt.fill_screen(' ', 1) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     sub clear_screen(ubyte pixelcolor, ubyte bgcolor) { |     sub clear_screen(ubyte pixelcolor, ubyte bgcolor) { | ||||||
|         memset(BITMAP_ADDRESS, 320*200/8, 0) |         sys.memset(BITMAP_ADDRESS, 320*200/8, 0) | ||||||
|         txt.fill_screen(pixelcolor << 4 | bgcolor, 0) |         txt.fill_screen(pixelcolor << 4 | bgcolor, 0) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { |     sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { | ||||||
|         ; Bresenham algorithm. |         ; Bresenham algorithm. | ||||||
|         ; This code special cases various quadrant loops to allow simple ++ and -- operations. |         ; This code special-cases various quadrant loops to allow simple ++ and -- operations. | ||||||
|         ; TODO rewrite this in optimized assembly |  | ||||||
|         if y1>y2 { |         if y1>y2 { | ||||||
|             ; make sure dy is always positive to avoid 8 instead of just 4 special cases |             ; make sure dy is always positive to have only 4 instead of 8 special cases | ||||||
|             swap(x1, x2) |             swap(x1, x2) | ||||||
|             swap(y1, y2) |             swap(y1, y2) | ||||||
|         } |         } | ||||||
|  |         word @zp dx = (x2 as word)-x1 | ||||||
|  |         word @zp dy = (y2 as word)-y1 | ||||||
|  |  | ||||||
|  |         if dx==0 { | ||||||
|  |             vertical_line(x1, y1, abs(dy) as ubyte +1) | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         if dy==0 { | ||||||
|  |             if x1>x2 | ||||||
|  |                 x1=x2 | ||||||
|  |             horizontal_line(x1, y1, abs(dx) as uword +1) | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|         word @zp d = 0 |         word @zp d = 0 | ||||||
|         ubyte positive_ix = true |         ubyte positive_ix = true | ||||||
|         word @zp dx = x2-x1 |  | ||||||
|         word @zp dy = y2-y1 |  | ||||||
|         if dx < 0 { |         if dx < 0 { | ||||||
|             dx = -dx |             dx = -dx | ||||||
|             positive_ix = false |             positive_ix = false | ||||||
|         } |         } | ||||||
|         dx *= 2 |         word @zp dx2 = dx*2 | ||||||
|         dy *= 2 |         word @zp dy2 = dy*2 | ||||||
|         internal_plotx = x1 |         internal_plotx = x1 | ||||||
|  |  | ||||||
|         if dx >= dy { |         if dx >= dy { | ||||||
| @@ -50,10 +70,10 @@ graphics { | |||||||
|                     if internal_plotx==x2 |                     if internal_plotx==x2 | ||||||
|                         return |                         return | ||||||
|                     internal_plotx++ |                     internal_plotx++ | ||||||
|                     d += dy |                     d += dy2 | ||||||
|                     if d > dx { |                     if d > dx { | ||||||
|                         y1++ |                         y1++ | ||||||
|                         d -= dx |                         d -= dx2 | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
| @@ -62,10 +82,10 @@ graphics { | |||||||
|                     if internal_plotx==x2 |                     if internal_plotx==x2 | ||||||
|                         return |                         return | ||||||
|                     internal_plotx-- |                     internal_plotx-- | ||||||
|                     d += dy |                     d += dy2 | ||||||
|                     if d > dx { |                     if d > dx { | ||||||
|                         y1++ |                         y1++ | ||||||
|                         d -= dx |                         d -= dx2 | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -77,10 +97,10 @@ graphics { | |||||||
|                     if y1 == y2 |                     if y1 == y2 | ||||||
|                         return |                         return | ||||||
|                     y1++ |                     y1++ | ||||||
|                     d += dx |                     d += dx2 | ||||||
|                     if d > dy { |                     if d > dy { | ||||||
|                         internal_plotx++ |                         internal_plotx++ | ||||||
|                         d -= dy |                         d -= dy2 | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
| @@ -89,88 +109,183 @@ graphics { | |||||||
|                     if y1 == y2 |                     if y1 == y2 | ||||||
|                         return |                         return | ||||||
|                     y1++ |                     y1++ | ||||||
|                     d += dx |                     d += dx2 | ||||||
|                     if d > dy { |                     if d > dy { | ||||||
|                         internal_plotx-- |                         internal_plotx-- | ||||||
|                         d -= dy |                         d -= dy2 | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     sub rect(uword x, ubyte y, uword width, ubyte height) { | ||||||
|  |         if width==0 or height==0 | ||||||
|  |             return | ||||||
|  |         horizontal_line(x, y, width) | ||||||
|  |         if height==1 | ||||||
|  |             return | ||||||
|  |         horizontal_line(x, y+height-1, width) | ||||||
|  |         vertical_line(x, y+1, height-2) | ||||||
|  |         if width==1 | ||||||
|  |             return | ||||||
|  |         vertical_line(x+width-1, y+1, height-2) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub fillrect(uword x, ubyte y, uword width, ubyte height) { | ||||||
|  |         if width==0 | ||||||
|  |             return | ||||||
|  |         repeat height { | ||||||
|  |             horizontal_line(x, y, width) | ||||||
|  |             y++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub horizontal_line(uword x, ubyte y, uword length) { | ||||||
|  |         if length<8 { | ||||||
|  |             internal_plotx=x | ||||||
|  |             repeat lsb(length) { | ||||||
|  |                 internal_plot(y) | ||||||
|  |                 internal_plotx++ | ||||||
|  |             } | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ubyte separate_pixels = lsb(x) & 7 | ||||||
|  |         uword addr = get_y_lookup(y) + (x&$fff8) | ||||||
|  |  | ||||||
|  |         if separate_pixels { | ||||||
|  |             %asm {{ | ||||||
|  |                 lda  addr | ||||||
|  |                 sta  P8ZP_SCRATCH_W1 | ||||||
|  |                 lda  addr+1 | ||||||
|  |                 sta  P8ZP_SCRATCH_W1+1 | ||||||
|  |                 ldy  separate_pixels | ||||||
|  |                 lda  _filled_right,y | ||||||
|  |                 eor  #255 | ||||||
|  |                 ldy  #0 | ||||||
|  |                 ora  (P8ZP_SCRATCH_W1),y | ||||||
|  |                 sta  (P8ZP_SCRATCH_W1),y | ||||||
|  |             }} | ||||||
|  |             addr += 8 | ||||||
|  |             length += separate_pixels | ||||||
|  |             length -= 8 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if length { | ||||||
|  |             %asm {{ | ||||||
|  |                 lda  length | ||||||
|  |                 and  #7 | ||||||
|  |                 sta  separate_pixels | ||||||
|  |                 stx  P8ZP_SCRATCH_REG | ||||||
|  |                 lsr  length+1 | ||||||
|  |                 ror  length | ||||||
|  |                 lsr  length+1 | ||||||
|  |                 ror  length | ||||||
|  |                 lsr  length+1 | ||||||
|  |                 ror  length | ||||||
|  |                 lda  addr | ||||||
|  |                 sta  _modified+1 | ||||||
|  |                 lda  addr+1 | ||||||
|  |                 sta  _modified+2 | ||||||
|  |                 lda  length | ||||||
|  |                 ora  length+1 | ||||||
|  |                 beq  _zero | ||||||
|  |                 ldy  length | ||||||
|  |                 ldx  #$ff | ||||||
|  | _modified       stx  $ffff      ; modified | ||||||
|  |                 lda  _modified+1 | ||||||
|  |                 clc | ||||||
|  |                 adc  #8 | ||||||
|  |                 sta  _modified+1 | ||||||
|  |                 bcc  + | ||||||
|  |                 inc  _modified+2 | ||||||
|  | +               dey | ||||||
|  |                 bne  _modified | ||||||
|  | _zero           ldx  P8ZP_SCRATCH_REG | ||||||
|  |  | ||||||
|  |                 ldy  separate_pixels | ||||||
|  |                 beq  _zero2 | ||||||
|  |                 lda  _modified+1 | ||||||
|  |                 sta  P8ZP_SCRATCH_W1 | ||||||
|  |                 lda  _modified+2 | ||||||
|  |                 sta  P8ZP_SCRATCH_W1+1 | ||||||
|  |                 lda  _filled_right,y | ||||||
|  |                 ldy  #0 | ||||||
|  |                 ora  (P8ZP_SCRATCH_W1),y | ||||||
|  |                 sta  (P8ZP_SCRATCH_W1),y | ||||||
|  |                 jmp  _zero2 | ||||||
|  | _filled_right   .byte  0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110 | ||||||
|  | _zero2 | ||||||
|  |             }} | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub vertical_line(uword x, ubyte y, ubyte height) { | ||||||
|  |         internal_plotx = x | ||||||
|  |         repeat height { | ||||||
|  |             internal_plot(y) | ||||||
|  |             y++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     sub circle(uword xcenter, ubyte ycenter, ubyte radius) { |     sub circle(uword xcenter, ubyte ycenter, ubyte radius) { | ||||||
|         ; Midpoint algorithm |         ; Midpoint algorithm | ||||||
|  |         if radius==0 | ||||||
|  |             return | ||||||
|         ubyte @zp ploty |         ubyte @zp ploty | ||||||
|         ubyte @zp xx = radius |  | ||||||
|         ubyte @zp yy = 0 |         ubyte @zp yy = 0 | ||||||
|         byte @zp decisionOver2 = 1-xx as byte |         word @zp decisionOver2 = (1 as word)-radius | ||||||
|  |  | ||||||
|         while xx>=yy { |         while radius>=yy { | ||||||
|             internal_plotx = xcenter + xx |             internal_plotx = xcenter + radius | ||||||
|             ploty = ycenter + yy |             ploty = ycenter + yy | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             internal_plotx = xcenter - xx |             internal_plotx = xcenter - radius | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             internal_plotx = xcenter + xx |             internal_plotx = xcenter + radius | ||||||
|             ploty = ycenter - yy |             ploty = ycenter - yy | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             internal_plotx = xcenter - xx |             internal_plotx = xcenter - radius | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             internal_plotx = xcenter + yy |             internal_plotx = xcenter + yy | ||||||
|             ploty = ycenter + xx |             ploty = ycenter + radius | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             internal_plotx = xcenter - yy |             internal_plotx = xcenter - yy | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             internal_plotx = xcenter + yy |             internal_plotx = xcenter + yy | ||||||
|             ploty = ycenter - xx |             ploty = ycenter - radius | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             internal_plotx = xcenter - yy |             internal_plotx = xcenter - yy | ||||||
|             internal_plot(ploty) |             internal_plot(ploty) | ||||||
|             yy++ |             yy++ | ||||||
|             if decisionOver2<=0 |             if decisionOver2<=0 | ||||||
|                 decisionOver2 += 2*yy+1 |                 decisionOver2 += (yy as word)*2+1 | ||||||
|             else { |             else { | ||||||
|                 xx-- |                 radius-- | ||||||
|                 decisionOver2 += 2*(yy-xx)+1 |                 decisionOver2 += (yy as word -radius)*2+1 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     sub disc(uword xcenter, ubyte ycenter, ubyte radius) { |     sub disc(uword xcenter, ubyte ycenter, ubyte radius) { | ||||||
|         ; Midpoint algorithm, filled |         ; Midpoint algorithm, filled | ||||||
|         ubyte xx = radius |         if radius==0 | ||||||
|         ubyte yy = 0 |             return | ||||||
|         byte decisionOver2 = 1-xx as byte |         ubyte @zp yy = 0 | ||||||
|  |         word decisionOver2 = (1 as word)-radius | ||||||
|  |  | ||||||
|         while xx>=yy { |         while radius>=yy { | ||||||
|             ubyte ycenter_plus_yy = ycenter + yy |             horizontal_line(xcenter-radius, ycenter+yy, radius*2+1) | ||||||
|             ubyte ycenter_min_yy = ycenter - yy |             horizontal_line(xcenter-radius, ycenter-yy, radius*2+1) | ||||||
|             ubyte ycenter_plus_xx = ycenter + xx |             horizontal_line(xcenter-yy, ycenter+radius, yy*2+1) | ||||||
|             ubyte ycenter_min_xx = ycenter - xx |             horizontal_line(xcenter-yy, ycenter-radius, yy*2+1) | ||||||
|  |  | ||||||
|             for internal_plotx in xcenter to xcenter+xx { |  | ||||||
|                 internal_plot(ycenter_plus_yy) |  | ||||||
|                 internal_plot(ycenter_min_yy) |  | ||||||
|             } |  | ||||||
|             for internal_plotx in xcenter-xx to xcenter-1 { |  | ||||||
|                 internal_plot(ycenter_plus_yy) |  | ||||||
|                 internal_plot(ycenter_min_yy) |  | ||||||
|             } |  | ||||||
|             for internal_plotx in xcenter to xcenter+yy { |  | ||||||
|                 internal_plot(ycenter_plus_xx) |  | ||||||
|                 internal_plot(ycenter_min_xx) |  | ||||||
|             } |  | ||||||
|             for internal_plotx in xcenter-yy to xcenter { |  | ||||||
|                 internal_plot(ycenter_plus_xx) |  | ||||||
|                 internal_plot(ycenter_min_xx) |  | ||||||
|             } |  | ||||||
|             yy++ |             yy++ | ||||||
|             if decisionOver2<=0 |             if decisionOver2<=0 | ||||||
|                 decisionOver2 += 2*yy+1 |                 decisionOver2 += (yy as word)*2+1 | ||||||
|             else { |             else { | ||||||
|                 xx-- |                 radius-- | ||||||
|                 decisionOver2 += 2*(yy-xx)+1 |                 decisionOver2 += (yy as word -radius)*2+1 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -183,11 +298,11 @@ graphics { | |||||||
| ;        @(addr) |= ormask[lsb(px) & 7] | ;        @(addr) |= ormask[lsb(px) & 7] | ||||||
| ;    } | ;    } | ||||||
|  |  | ||||||
|     asmsub  plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) { |     inline asmsub  plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) { | ||||||
|         %asm {{ |         %asm {{ | ||||||
|             stx  internal_plotx |             stx  graphics.internal_plotx | ||||||
|             sty  internal_plotx+1 |             sty  graphics.internal_plotx+1 | ||||||
|             jmp  internal_plot |             jsr  graphics.internal_plot | ||||||
|         }} |         }} | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -239,6 +354,17 @@ _y_lookup_hi    .byte  >_plot_y_values | |||||||
|         }} |         }} | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     asmsub get_y_lookup(ubyte y @Y) -> uword @AY { | ||||||
|  |         %asm {{ | ||||||
|  |             lda  internal_plot._y_lookup_lo,y | ||||||
|  |             pha | ||||||
|  |             lda  internal_plot._y_lookup_hi,y | ||||||
|  |             tay | ||||||
|  |             pla | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ c64 { | |||||||
|         &ubyte  TIME_HI         = $a0       ; software jiffy clock, hi byte |         &ubyte  TIME_HI         = $a0       ; software jiffy clock, hi byte | ||||||
|         &ubyte  TIME_MID        = $a1       ;  .. mid byte |         &ubyte  TIME_MID        = $a1       ;  .. mid byte | ||||||
|         &ubyte  TIME_LO         = $a2       ;    .. lo byte. Updated by IRQ every 1/60 sec |         &ubyte  TIME_LO         = $a2       ;    .. lo byte. Updated by IRQ every 1/60 sec | ||||||
|         &ubyte  STATUS          = $90       ; kernel status variable for I/O |         &ubyte  STATUS          = $90       ; kernal status variable for I/O | ||||||
|         &ubyte  STKEY           = $91       ; various keyboard statuses (updated by IRQ) |         &ubyte  STKEY           = $91       ; various keyboard statuses (updated by IRQ) | ||||||
|         &ubyte  SFDX            = $cb       ; current key pressed (matrix value) (updated by IRQ) |         &ubyte  SFDX            = $cb       ; current key pressed (matrix value) (updated by IRQ) | ||||||
|  |  | ||||||
| @@ -178,7 +178,7 @@ c64 { | |||||||
|  |  | ||||||
| ; ---- C64 ROM kernal routines ---- | ; ---- C64 ROM kernal routines ---- | ||||||
|  |  | ||||||
| romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y)      ; print null-terminated string (use c64scr.print instead) | romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y)      ; print null-terminated string (use txt.print instead) | ||||||
| romsub $E544 = CLEARSCR() clobbers(A,X,Y)                       ; clear the screen | romsub $E544 = CLEARSCR() clobbers(A,X,Y)                       ; clear the screen | ||||||
| romsub $E566 = HOMECRSR() clobbers(A,X,Y)                       ; cursor to top left of screen | romsub $E566 = HOMECRSR() clobbers(A,X,Y)                       ; cursor to top left of screen | ||||||
| romsub $EA31 = IRQDFRT() clobbers(A,X,Y)                        ; default IRQ routine | romsub $EA31 = IRQDFRT() clobbers(A,X,Y)                        ; default IRQ routine | ||||||
| @@ -202,29 +202,64 @@ romsub $FFAE = UNLSN() clobbers(A)                              ; command serial | |||||||
| romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A)             ; command serial bus device to LISTEN | romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A)             ; command serial bus device to LISTEN | ||||||
| romsub $FFB4 = TALK(ubyte device @ A) clobbers(A)               ; command serial bus device to TALK | romsub $FFB4 = TALK(ubyte device @ A) clobbers(A)               ; command serial bus device to TALK | ||||||
| romsub $FFB7 = READST() -> ubyte @ A                            ; read I/O status word | romsub $FFB7 = READST() -> ubyte @ A                            ; read I/O status word | ||||||
| romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y)   ; set logical file parameters | romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y)   ; set logical file parameters | ||||||
| romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY)     ; set filename parameters | romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY)     ; set filename parameters | ||||||
| romsub $FFC0 = OPEN() clobbers(A,X,Y)                           ; (via 794 ($31A)) open a logical file | romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A      ; (via 794 ($31A)) open a logical file | ||||||
| romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y)         ; (via 796 ($31C)) close a logical file | romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y)         ; (via 796 ($31C)) close a logical file | ||||||
| romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X)           ; (via 798 ($31E)) define an input channel | romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc    ; (via 798 ($31E)) define an input channel | ||||||
| romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X)          ; (via 800 ($320)) define an output channel | romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X)          ; (via 800 ($320)) define an output channel | ||||||
| romsub $FFCC = CLRCHN() clobbers(A,X)                           ; (via 802 ($322)) restore default devices | romsub $FFCC = CLRCHN() clobbers(A,X)                           ; (via 802 ($322)) restore default devices | ||||||
| romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A                 ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read. | romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A   ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read. | ||||||
| romsub $FFD2 = CHROUT(ubyte char @ A)                           ; (via 806 ($326)) output a character | romsub $FFD2 = CHROUT(ubyte char @ A)                           ; (via 806 ($326)) output a character | ||||||
| romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y     ; (via 816 ($330)) load from device | romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY     ; (via 816 ($330)) load from device | ||||||
| romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A          ; (via 818 ($332)) save to a device | romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A          ; (via 818 ($332)) save to a device | ||||||
| romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y)      ; set the software clock | romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y)      ; set the software clock | ||||||
| romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y       ; read the software clock | romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y       ; read the software clock (A=lo,X=mid,Y=high) | ||||||
| romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc   ; (via 808 ($328)) check the STOP key | romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A      ; (via 808 ($328)) check the STOP key (and some others in A) | ||||||
| romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A               ; (via 810 ($32A)) get a character | romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A    ; (via 810 ($32A)) get a character | ||||||
| romsub $FFE7 = CLALL() clobbers(A,X)                            ; (via 812 ($32C)) close all files | romsub $FFE7 = CLALL() clobbers(A,X)                            ; (via 812 ($32C)) close all files | ||||||
| romsub $FFEA = UDTIM() clobbers(A,X)                            ; update the software clock | romsub $FFEA = UDTIM() clobbers(A,X)                            ; update the software clock | ||||||
| romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y                 ; read number of screen rows and columns | romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y                 ; read number of screen rows and columns | ||||||
| romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y       ; read/set position of cursor on screen.  Use c64scr.plot for a 'safe' wrapper that preserves X. | romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y       ; read/set position of cursor on screen.  Use txt.plot for a 'safe' wrapper that preserves X. | ||||||
| romsub $FFF3 = IOBASE() -> uword @ XY                           ; read base address of I/O devices | romsub $FFF3 = IOBASE() -> uword @ XY                           ; read base address of I/O devices | ||||||
|  |  | ||||||
| ; ---- end of C64 ROM kernal routines ---- | ; ---- end of C64 ROM kernal routines ---- | ||||||
|  |  | ||||||
|  | ; ---- utilities ----- | ||||||
|  |  | ||||||
|  | asmsub STOP2() -> ubyte @A  { | ||||||
|  |     ; -- check if STOP key was pressed, returns true if so.  More convenient to use than STOP() because that only sets the carry status flag. | ||||||
|  |     %asm {{ | ||||||
|  |         txa | ||||||
|  |         pha | ||||||
|  |         jsr  c64.STOP | ||||||
|  |         beq  + | ||||||
|  |         pla | ||||||
|  |         tax | ||||||
|  |         lda  #0 | ||||||
|  |         rts | ||||||
|  | +       pla | ||||||
|  |         tax | ||||||
|  |         lda  #1 | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub RDTIM16() -> uword @AY { | ||||||
|  |     ; --  like RDTIM() but only returning the lower 16 bits in AY for convenience | ||||||
|  |     %asm {{ | ||||||
|  |         stx  P8ZP_SCRATCH_REG | ||||||
|  |         jsr  c64.RDTIM | ||||||
|  |         pha | ||||||
|  |         txa | ||||||
|  |         tay | ||||||
|  |         pla | ||||||
|  |         ldx  P8ZP_SCRATCH_REG | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ---- C64 specific system utility routines: ---- | ; ---- C64 specific system utility routines: ---- | ||||||
|  |  | ||||||
| @@ -259,17 +294,13 @@ asmsub  init_system()  { | |||||||
|     }} |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  reset_system()  { | asmsub  init_system_phase2()  { | ||||||
|     ; Soft-reset the system back to Basic prompt. |  | ||||||
|     %asm {{ |     %asm {{ | ||||||
|         sei |         rts     ; no phase 2 steps on the C64 | ||||||
|         lda  #14 |  | ||||||
|         sta  $01        ; bank the kernal in |  | ||||||
|         jmp  (c64.RESET_VEC) |  | ||||||
|     }} |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  disable_runstop_and_charsetswitch() { | asmsub  disable_runstop_and_charsetswitch() clobbers(A) { | ||||||
|     %asm {{ |     %asm {{ | ||||||
|         lda  #$80 |         lda  #$80 | ||||||
|         sta  657    ; disable charset switching |         sta  657    ; disable charset switching | ||||||
| @@ -279,27 +310,13 @@ asmsub  disable_runstop_and_charsetswitch() { | |||||||
|     }} |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  set_irqvec_excl() clobbers(A)  { | asmsub  set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A)  { | ||||||
| 	%asm {{ |  | ||||||
| 		sei |  | ||||||
| 		lda  #<_irq_handler |  | ||||||
| 		sta  c64.CINV |  | ||||||
| 		lda  #>_irq_handler |  | ||||||
| 		sta  c64.CINV+1 |  | ||||||
| 		cli |  | ||||||
| 		rts |  | ||||||
| _irq_handler	jsr  set_irqvec._irq_handler_init |  | ||||||
| 		jsr  irq.irq |  | ||||||
| 		jsr  set_irqvec._irq_handler_end |  | ||||||
| 		lda  #$ff |  | ||||||
| 		sta  c64.VICIRQ			; acknowledge raster irq |  | ||||||
| 		lda  c64.CIA1ICR		; acknowledge CIA1 interrupt |  | ||||||
| 		jmp  c64.IRQDFEND		; end irq processing - don't call kernel |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| asmsub  set_irqvec() clobbers(A)  { |  | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
|  | 	        sta  _modified+1 | ||||||
|  | 	        sty  _modified+2 | ||||||
|  | 	        lda  #0 | ||||||
|  | 	        adc  #0 | ||||||
|  | 	        sta  _use_kernal | ||||||
| 		sei | 		sei | ||||||
| 		lda  #<_irq_handler | 		lda  #<_irq_handler | ||||||
| 		sta  c64.CINV | 		sta  c64.CINV | ||||||
| @@ -308,9 +325,23 @@ asmsub  set_irqvec() clobbers(A)  { | |||||||
| 		cli | 		cli | ||||||
| 		rts | 		rts | ||||||
| _irq_handler    jsr  _irq_handler_init | _irq_handler    jsr  _irq_handler_init | ||||||
| 		jsr  irq.irq | _modified	jsr  $ffff                      ; modified | ||||||
| 		jsr  _irq_handler_end | 		jsr  _irq_handler_end | ||||||
| 		jmp  c64.IRQDFRT		; continue with normal kernel irq routine | 		lda  _use_kernal | ||||||
|  | 		bne  + | ||||||
|  | 		lda  #$ff | ||||||
|  | 		sta  c64.VICIRQ			; acknowledge raster irq | ||||||
|  | 		lda  c64.CIA1ICR		; acknowledge CIA1 interrupt | ||||||
|  | 		; end irq processing - don't use kernal's irq handling | ||||||
|  | 		pla | ||||||
|  | 		tay | ||||||
|  | 		pla | ||||||
|  | 		tax | ||||||
|  | 		pla | ||||||
|  | 		rti | ||||||
|  | +		jmp  c64.IRQDFRT		; continue with normal kernal irq routine | ||||||
|  |  | ||||||
|  | _use_kernal     .byte  0 | ||||||
|  |  | ||||||
| _irq_handler_init | _irq_handler_init | ||||||
| 		; save all zp scratch registers and the X register as these might be clobbered by the irq routine | 		; save all zp scratch registers and the X register as these might be clobbered by the irq routine | ||||||
| @@ -363,7 +394,7 @@ IRQ_SCRATCH_ZPWORD2	.word  0 | |||||||
| 		}} | 		}} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  restore_irqvec() { | asmsub  restore_irq() clobbers(A) { | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		sei | 		sei | ||||||
| 		lda  #<c64.IRQDFRT | 		lda  #<c64.IRQDFRT | ||||||
| @@ -379,8 +410,15 @@ asmsub  restore_irqvec() { | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  set_rasterirq(uword rasterpos @ AY) clobbers(A) { | asmsub  set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) { | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
|  | 	        sta  _modified+1 | ||||||
|  | 	        sty  _modified+2 | ||||||
|  | 	        lda  #0 | ||||||
|  | 	        adc  #0 | ||||||
|  | 	        sta  set_irq._use_kernal | ||||||
|  | 		lda  cx16.r0 | ||||||
|  | 		ldy  cx16.r0+1 | ||||||
| 		sei | 		sei | ||||||
| 		jsr  _setup_raster_irq | 		jsr  _setup_raster_irq | ||||||
| 		lda  #<_raster_irq_handler | 		lda  #<_raster_irq_handler | ||||||
| @@ -391,12 +429,21 @@ asmsub  set_rasterirq(uword rasterpos @ AY) clobbers(A) { | |||||||
| 		rts | 		rts | ||||||
|  |  | ||||||
| _raster_irq_handler | _raster_irq_handler | ||||||
| 		jsr  set_irqvec._irq_handler_init | 		jsr  set_irq._irq_handler_init | ||||||
| 		jsr  irq.irq | _modified	jsr  $ffff              ; modified | ||||||
| 		jsr  set_irqvec._irq_handler_end | 		jsr  set_irq._irq_handler_end | ||||||
|                 lda  #$ff |                 lda  #$ff | ||||||
|                 sta  c64.VICIRQ			; acknowledge raster irq |                 sta  c64.VICIRQ			; acknowledge raster irq | ||||||
| 		jmp  c64.IRQDFRT | 		lda  set_irq._use_kernal | ||||||
|  | 		bne  + | ||||||
|  | 		; end irq processing - don't use kernal's irq handling | ||||||
|  | 		pla | ||||||
|  | 		tay | ||||||
|  | 		pla | ||||||
|  | 		tax | ||||||
|  | 		pla | ||||||
|  | 		rti | ||||||
|  | +		jmp  c64.IRQDFRT                ; continue with kernal irq routine | ||||||
|  |  | ||||||
| _setup_raster_irq | _setup_raster_irq | ||||||
| 		pha | 		pha | ||||||
| @@ -420,29 +467,273 @@ _setup_raster_irq | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) { | ; ---- end of C64 specific system utility routines ---- | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sys { | ||||||
|  |     ; ------- lowlevel system routines -------- | ||||||
|  |  | ||||||
|  |     const ubyte target = 64         ;  compilation target specifier.  64 = C64,  16 = CommanderX16. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     asmsub  reset_system()  { | ||||||
|  |         ; Soft-reset the system back to initial power-on Basic prompt. | ||||||
|         %asm {{ |         %asm {{ | ||||||
|             sei |             sei | ||||||
| 		jsr  set_rasterirq._setup_raster_irq |             lda  #14 | ||||||
| 		lda  #<_raster_irq_handler |             sta  $01        ; bank the kernal in | ||||||
| 		sta  c64.CINV |             jmp  (c64.RESET_VEC) | ||||||
| 		lda  #>_raster_irq_handler |  | ||||||
| 		sta  c64.CINV+1 |  | ||||||
| 		cli |  | ||||||
| 		rts |  | ||||||
|  |  | ||||||
| _raster_irq_handler |  | ||||||
| 		jsr  set_irqvec._irq_handler_init |  | ||||||
| 		jsr  irq.irq |  | ||||||
| 		jsr  set_irqvec._irq_handler_end |  | ||||||
| 		lda  #$ff |  | ||||||
| 		sta  c64.VICIRQ			; acknowledge raster irq |  | ||||||
| 		jmp  c64.IRQDFEND		; end irq processing - don't call kernel |  | ||||||
|  |  | ||||||
|         }} |         }} | ||||||
|     } |     } | ||||||
|  |  | ||||||
| ; ---- end of C64 specific system utility routines ---- |     sub wait(uword jiffies) { | ||||||
|  |         ; --- wait approximately the given number of jiffies (1/60th seconds) | ||||||
|  |         ;     note: the system irq handler has to be active for this to work as it depends on the system jiffy clock | ||||||
|  |         repeat jiffies { | ||||||
|  |             ubyte jiff = lsb(c64.RDTIM16()) | ||||||
|  |             while jiff==lsb(c64.RDTIM16()) { | ||||||
|  |                 ; wait until 1 jiffy has passed | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub waitvsync() clobbers(A) { | ||||||
|  |         ; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling. | ||||||
|  |         ;     note: a more accurate way to wait for vsync is to set up a vsync irq handler instead. | ||||||
|  |         %asm {{ | ||||||
|  | -           lda  c64.RASTER | ||||||
|  |             beq  - | ||||||
|  | -           lda  c64.RASTER | ||||||
|  |             bne  - | ||||||
|  |             bit  c64.SCROLY | ||||||
|  |             bmi  - | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub waitrastborder() { | ||||||
|  |         ; --- busy wait till the raster position has reached the bottom screen border (approximately) | ||||||
|  |         ;     note: a more accurate way to do this is by using a raster irq handler instead. | ||||||
|  |         %asm {{ | ||||||
|  | -           bit  c64.SCROLY | ||||||
|  |             bpl  - | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) { | ||||||
|  |         %asm {{ | ||||||
|  |             ldx  cx16.r0 | ||||||
|  |             stx  P8ZP_SCRATCH_W1        ; source in ZP | ||||||
|  |             ldx  cx16.r0+1 | ||||||
|  |             stx  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldx  cx16.r1 | ||||||
|  |             stx  P8ZP_SCRATCH_W2        ; target in ZP | ||||||
|  |             ldx  cx16.r1+1 | ||||||
|  |             stx  P8ZP_SCRATCH_W2+1 | ||||||
|  |             cpy  #0 | ||||||
|  |             bne  _longcopy | ||||||
|  |  | ||||||
|  |             ; copy <= 255 bytes | ||||||
|  |             tay | ||||||
|  |             bne  _copyshort | ||||||
|  |             rts     ; nothing to copy | ||||||
|  |  | ||||||
|  | _copyshort | ||||||
|  |             ; decrease source and target pointers so we can simply index by Y | ||||||
|  |             lda  P8ZP_SCRATCH_W1 | ||||||
|  |             bne  + | ||||||
|  |             dec  P8ZP_SCRATCH_W1+1 | ||||||
|  | +           dec  P8ZP_SCRATCH_W1 | ||||||
|  |             lda  P8ZP_SCRATCH_W2 | ||||||
|  |             bne  + | ||||||
|  |             dec  P8ZP_SCRATCH_W2+1 | ||||||
|  | +           dec  P8ZP_SCRATCH_W2 | ||||||
|  |  | ||||||
|  | -           lda  (P8ZP_SCRATCH_W1),y | ||||||
|  |             sta  (P8ZP_SCRATCH_W2),y | ||||||
|  |             dey | ||||||
|  |             bne  - | ||||||
|  |             rts | ||||||
|  |  | ||||||
|  | _longcopy | ||||||
|  |             sta  P8ZP_SCRATCH_B1        ; lsb(count) = remainder in last page | ||||||
|  |             tya | ||||||
|  |             tax                         ; x = num pages (1+) | ||||||
|  |             ldy  #0 | ||||||
|  | -           lda  (P8ZP_SCRATCH_W1),y | ||||||
|  |             sta  (P8ZP_SCRATCH_W2),y | ||||||
|  |             iny | ||||||
|  |             bne  - | ||||||
|  |             inc  P8ZP_SCRATCH_W1+1 | ||||||
|  |             inc  P8ZP_SCRATCH_W2+1 | ||||||
|  |             dex | ||||||
|  |             bne  - | ||||||
|  |             ldy  P8ZP_SCRATCH_B1 | ||||||
|  |             bne  _copyshort | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) { | ||||||
|  |         %asm {{ | ||||||
|  |             ldy  cx16.r0 | ||||||
|  |             sty  P8ZP_SCRATCH_W1 | ||||||
|  |             ldy  cx16.r0+1 | ||||||
|  |             sty  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldx  cx16.r1 | ||||||
|  |             ldy  cx16.r1+1 | ||||||
|  |             jmp  prog8_lib.memset | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) { | ||||||
|  |         %asm {{ | ||||||
|  |             ldx  cx16.r0 | ||||||
|  |             stx  P8ZP_SCRATCH_W1 | ||||||
|  |             ldx  cx16.r0+1 | ||||||
|  |             stx  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldx  cx16.r1 | ||||||
|  |             stx  P8ZP_SCRATCH_W2 | ||||||
|  |             ldx  cx16.r1+1 | ||||||
|  |             stx  P8ZP_SCRATCH_W2+1 | ||||||
|  |             jmp  prog8_lib.memsetw | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     inline asmsub rsave() { | ||||||
|  |         ; save cpu status flag and all registers A, X, Y. | ||||||
|  |         ; see http://6502.org/tutorials/register_preservation.html | ||||||
|  |         %asm {{ | ||||||
|  |             php | ||||||
|  |             sta  P8ZP_SCRATCH_REG | ||||||
|  |             pha | ||||||
|  |             txa | ||||||
|  |             pha | ||||||
|  |             tya | ||||||
|  |             pha | ||||||
|  |             lda  P8ZP_SCRATCH_REG | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub rrestore() { | ||||||
|  |         ; restore all registers and cpu status flag | ||||||
|  |         %asm {{ | ||||||
|  |             pla | ||||||
|  |             tay | ||||||
|  |             pla | ||||||
|  |             tax | ||||||
|  |             pla | ||||||
|  |             plp | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub read_flags() -> ubyte @A { | ||||||
|  |         %asm {{ | ||||||
|  |             php | ||||||
|  |             pla | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub clear_carry() { | ||||||
|  |         %asm {{ | ||||||
|  |             clc | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub set_carry() { | ||||||
|  |         %asm {{ | ||||||
|  |             sec | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub clear_irqd() { | ||||||
|  |         %asm {{ | ||||||
|  |             cli | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub set_irqd() { | ||||||
|  |         %asm {{ | ||||||
|  |             sei | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub exit(ubyte returnvalue @A) { | ||||||
|  |         ; -- immediately exit the program with a return code in the A register | ||||||
|  |         %asm {{ | ||||||
|  |             jsr  c64.CLRCHN		; reset i/o channels | ||||||
|  |             ldx  prog8_lib.orig_stackpointer | ||||||
|  |             txs | ||||||
|  |             rts		; return to original caller | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub progend() -> uword @AY { | ||||||
|  |         %asm {{ | ||||||
|  |             lda  #<prog8_program_end | ||||||
|  |             ldy  #>prog8_program_end | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | cx16 { | ||||||
|  |  | ||||||
|  |     ; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage | ||||||
|  |     ; they are simulated on the C64 as well but their location in memory is different | ||||||
|  |     ; (because there's no room for them in the zeropage) | ||||||
|  |     ; they are allocated at the bottom of the eval-stack (should be ample space unless | ||||||
|  |     ; you're doing insane nesting of expressions...) | ||||||
|  |     &uword r0  = $cf00 | ||||||
|  |     &uword r1  = $cf02 | ||||||
|  |     &uword r2  = $cf04 | ||||||
|  |     &uword r3  = $cf06 | ||||||
|  |     &uword r4  = $cf08 | ||||||
|  |     &uword r5  = $cf0a | ||||||
|  |     &uword r6  = $cf0c | ||||||
|  |     &uword r7  = $cf0e | ||||||
|  |     &uword r8  = $cf10 | ||||||
|  |     &uword r9  = $cf12 | ||||||
|  |     &uword r10 = $cf14 | ||||||
|  |     &uword r11 = $cf16 | ||||||
|  |     &uword r12 = $cf18 | ||||||
|  |     &uword r13 = $cf1a | ||||||
|  |     &uword r14 = $cf1c | ||||||
|  |     &uword r15 = $cf1e | ||||||
|  |  | ||||||
|  |     &ubyte r0L  = $cf00 | ||||||
|  |     &ubyte r1L  = $cf02 | ||||||
|  |     &ubyte r2L  = $cf04 | ||||||
|  |     &ubyte r3L  = $cf06 | ||||||
|  |     &ubyte r4L  = $cf08 | ||||||
|  |     &ubyte r5L  = $cf0a | ||||||
|  |     &ubyte r6L  = $cf0c | ||||||
|  |     &ubyte r7L  = $cf0e | ||||||
|  |     &ubyte r8L  = $cf10 | ||||||
|  |     &ubyte r9L  = $cf12 | ||||||
|  |     &ubyte r10L = $cf14 | ||||||
|  |     &ubyte r11L = $cf16 | ||||||
|  |     &ubyte r12L = $cf18 | ||||||
|  |     &ubyte r13L = $cf1a | ||||||
|  |     &ubyte r14L = $cf1c | ||||||
|  |     &ubyte r15L = $cf1e | ||||||
|  |  | ||||||
|  |     &ubyte r0H  = $cf01 | ||||||
|  |     &ubyte r1H  = $cf03 | ||||||
|  |     &ubyte r2H  = $cf05 | ||||||
|  |     &ubyte r3H  = $cf07 | ||||||
|  |     &ubyte r4H  = $cf09 | ||||||
|  |     &ubyte r5H  = $cf0b | ||||||
|  |     &ubyte r6H  = $cf0d | ||||||
|  |     &ubyte r7H  = $cf0f | ||||||
|  |     &ubyte r8H  = $cf11 | ||||||
|  |     &ubyte r9H  = $cf13 | ||||||
|  |     &ubyte r10H = $cf15 | ||||||
|  |     &ubyte r11H = $cf17 | ||||||
|  |     &ubyte r12H = $cf19 | ||||||
|  |     &ubyte r13H = $cf1b | ||||||
|  |     &ubyte r14H = $cf1d | ||||||
|  |     &ubyte r15H = $cf1f | ||||||
|  | } | ||||||
|   | |||||||
| @@ -16,7 +16,30 @@ const ubyte DEFAULT_HEIGHT = 25 | |||||||
|  |  | ||||||
|  |  | ||||||
| sub  clear_screen() { | sub  clear_screen() { | ||||||
|     clear_screenchars(' ') |     txt.chrout(147) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sub home() { | ||||||
|  |     txt.chrout(19) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sub nl() { | ||||||
|  |     txt.chrout('\n') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sub spc() { | ||||||
|  |     txt.chrout(' ') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub column(ubyte col @A) clobbers(A, X, Y) { | ||||||
|  |     ; ---- set the cursor on the given column (starting with 0) on the current line | ||||||
|  |     %asm {{ | ||||||
|  |         sec | ||||||
|  |         jsr  c64.PLOT | ||||||
|  |         tay | ||||||
|  |         clc | ||||||
|  |         jmp  c64.PLOT | ||||||
|  |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A)  { | asmsub  fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A)  { | ||||||
| @@ -38,13 +61,13 @@ asmsub  clear_screenchars (ubyte char @ A) clobbers(Y)  { | |||||||
| 	; ---- clear the character screen with the given fill character (leaves colors) | 	; ---- clear the character screen with the given fill character (leaves colors) | ||||||
| 	;      (assumes screen matrix is at the default address) | 	;      (assumes screen matrix is at the default address) | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		ldy  #0 | 		ldy  #250 | ||||||
| _loop		sta  c64.Screen,y | -		sta  c64.Screen+250*0-1,y | ||||||
| 		sta  c64.Screen+$0100,y | 		sta  c64.Screen+250*1-1,y | ||||||
| 		sta  c64.Screen+$0200,y | 		sta  c64.Screen+250*2-1,y | ||||||
| 		sta  c64.Screen+$02e8,y | 		sta  c64.Screen+250*3-1,y | ||||||
| 		iny | 		dey | ||||||
| 		bne  _loop | 		bne  - | ||||||
| 		rts | 		rts | ||||||
|         }} |         }} | ||||||
| } | } | ||||||
| @@ -53,13 +76,13 @@ asmsub  clear_screencolors (ubyte color @ A) clobbers(Y)  { | |||||||
| 	; ---- clear the character screen colors with the given color (leaves characters). | 	; ---- clear the character screen colors with the given color (leaves characters). | ||||||
| 	;      (assumes color matrix is at the default address) | 	;      (assumes color matrix is at the default address) | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		ldy  #0 | 		ldy  #250 | ||||||
| _loop		sta  c64.Colors,y | -		sta  c64.Colors+250*0-1,y | ||||||
| 		sta  c64.Colors+$0100,y | 		sta  c64.Colors+250*1-1,y | ||||||
| 		sta  c64.Colors+$0200,y | 		sta  c64.Colors+250*2-1,y | ||||||
| 		sta  c64.Colors+$02e8,y | 		sta  c64.Colors+250*3-1,y | ||||||
| 		iny | 		dey | ||||||
| 		bne  _loop | 		bne  - | ||||||
| 		rts | 		rts | ||||||
|         }} |         }} | ||||||
| } | } | ||||||
| @@ -83,28 +106,30 @@ asmsub  scroll_left  (ubyte alsocolors @ Pc) clobbers(A, Y)  { | |||||||
|  |  | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		bcs  + | 		bcc _scroll_screen | ||||||
| 		jmp  _scroll_screen |  | ||||||
|  |  | ||||||
| +               ; scroll the color memory | +               ; scroll the screen and the color memory | ||||||
| 		ldx  #0 |  | ||||||
| 		ldy  #38 |  | ||||||
| - |  | ||||||
| 	.for row=0, row<=24, row+=1 |  | ||||||
| 		lda  c64.Colors + 40*row + 1,x |  | ||||||
| 		sta  c64.Colors + 40*row,x |  | ||||||
| 	.next |  | ||||||
| 		inx |  | ||||||
| 		dey |  | ||||||
| 		bpl  - |  | ||||||
|  |  | ||||||
| _scroll_screen  ; scroll the screen memory |  | ||||||
| 		ldx  #0 | 		ldx  #0 | ||||||
| 		ldy  #38 | 		ldy  #38 | ||||||
| - | - | ||||||
|         .for row=0, row<=24, row+=1 |         .for row=0, row<=24, row+=1 | ||||||
|             lda  c64.Screen + 40*row + 1,x |             lda  c64.Screen + 40*row + 1,x | ||||||
| 		sta  c64.Screen + 40*row,x |             sta  c64.Screen + 40*row + 0,x | ||||||
|  |             lda  c64.Colors + 40*row + 1,x | ||||||
|  |             sta  c64.Colors + 40*row + 0,x | ||||||
|  |         .next | ||||||
|  | 		inx | ||||||
|  | 		dey | ||||||
|  | 		bpl  - | ||||||
|  | 		rts | ||||||
|  |  | ||||||
|  | _scroll_screen  ; scroll only the screen memory | ||||||
|  | 		ldx  #0 | ||||||
|  | 		ldy  #38 | ||||||
|  | - | ||||||
|  |         .for row=0, row<=24, row+=1 | ||||||
|  |             lda  c64.Screen + 40*row + 1,x | ||||||
|  |             sta  c64.Screen + 40*row + 0,x | ||||||
|         .next |         .next | ||||||
| 		inx | 		inx | ||||||
| 		dey | 		dey | ||||||
| @@ -121,20 +146,22 @@ asmsub  scroll_right  (ubyte alsocolors @ Pc) clobbers(A)  { | |||||||
| 	;      Carry flag determines if screen color data must be scrolled too | 	;      Carry flag determines if screen color data must be scrolled too | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		bcs  + | 		bcc  _scroll_screen | ||||||
| 		jmp  _scroll_screen |  | ||||||
|  |  | ||||||
| +               ; scroll the color memory | +               ; scroll the screen and the color memory | ||||||
| 		ldx  #38 | 		ldx  #38 | ||||||
| - | - | ||||||
|         .for row=0, row<=24, row+=1 |         .for row=0, row<=24, row+=1 | ||||||
|  |             lda  c64.Screen + 40*row + 0,x | ||||||
|  |             sta  c64.Screen + 40*row + 1,x | ||||||
|             lda  c64.Colors + 40*row + 0,x |             lda  c64.Colors + 40*row + 0,x | ||||||
|             sta  c64.Colors + 40*row + 1,x |             sta  c64.Colors + 40*row + 1,x | ||||||
|         .next |         .next | ||||||
| 		dex | 		dex | ||||||
| 		bpl  - | 		bpl  - | ||||||
|  | 		rts | ||||||
|  |  | ||||||
| _scroll_screen  ; scroll the screen memory | _scroll_screen  ; scroll only the screen memory | ||||||
| 		ldx  #38 | 		ldx  #38 | ||||||
| - | - | ||||||
|         .for row=0, row<=24, row+=1 |         .for row=0, row<=24, row+=1 | ||||||
| @@ -155,20 +182,22 @@ asmsub  scroll_up  (ubyte alsocolors @ Pc) clobbers(A)  { | |||||||
| 	;      Carry flag determines if screen color data must be scrolled too | 	;      Carry flag determines if screen color data must be scrolled too | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		bcs  + | 		bcc  _scroll_screen | ||||||
| 		jmp  _scroll_screen |  | ||||||
|  |  | ||||||
| +               ; scroll the color memory | +               ; scroll the screen and the color memory | ||||||
| 		ldx #39 | 		ldx #39 | ||||||
| - | - | ||||||
|         .for row=1, row<=24, row+=1 |         .for row=1, row<=24, row+=1 | ||||||
|  |             lda  c64.Screen + 40*row,x | ||||||
|  |             sta  c64.Screen + 40*(row-1),x | ||||||
|             lda  c64.Colors + 40*row,x |             lda  c64.Colors + 40*row,x | ||||||
|             sta  c64.Colors + 40*(row-1),x |             sta  c64.Colors + 40*(row-1),x | ||||||
|         .next |         .next | ||||||
| 		dex | 		dex | ||||||
| 		bpl  - | 		bpl  - | ||||||
|  | 		rts | ||||||
|  |  | ||||||
| _scroll_screen  ; scroll the screen memory | _scroll_screen  ; scroll only the screen memory | ||||||
| 		ldx #39 | 		ldx #39 | ||||||
| - | - | ||||||
|         .for row=1, row<=24, row+=1 |         .for row=1, row<=24, row+=1 | ||||||
| @@ -189,20 +218,22 @@ asmsub  scroll_down  (ubyte alsocolors @ Pc) clobbers(A)  { | |||||||
| 	;      Carry flag determines if screen color data must be scrolled too | 	;      Carry flag determines if screen color data must be scrolled too | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		bcs  + | 		bcc  _scroll_screen | ||||||
| 		jmp  _scroll_screen |  | ||||||
|  |  | ||||||
| +               ; scroll the color memory | +               ; scroll the screen and the color memory | ||||||
| 		ldx #39 | 		ldx #39 | ||||||
| - | - | ||||||
|         .for row=23, row>=0, row-=1 |         .for row=23, row>=0, row-=1 | ||||||
|             lda  c64.Colors + 40*row,x |             lda  c64.Colors + 40*row,x | ||||||
|             sta  c64.Colors + 40*(row+1),x |             sta  c64.Colors + 40*(row+1),x | ||||||
|  |             lda  c64.Screen + 40*row,x | ||||||
|  |             sta  c64.Screen + 40*(row+1),x | ||||||
|         .next |         .next | ||||||
| 		dex | 		dex | ||||||
| 		bpl  - | 		bpl  - | ||||||
|  | 		rts | ||||||
|  |  | ||||||
| _scroll_screen  ; scroll the screen memory | _scroll_screen  ; scroll only the screen memory | ||||||
| 		ldx #39 | 		ldx #39 | ||||||
| - | - | ||||||
|         .for row=23, row>=0, row-=1 |         .for row=23, row>=0, row-=1 | ||||||
| @@ -555,7 +586,7 @@ _colormod	sta  $ffff		; modified | |||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  plot  (ubyte col @ Y, ubyte row @ A) clobbers(A) { | asmsub  plot  (ubyte col @ Y, ubyte row @ A) clobbers(A) { | ||||||
| 	; ---- safe wrapper around PLOT kernel routine, to save the X register. | 	; ---- safe wrapper around PLOT kernal routine, to save the X register. | ||||||
| 	%asm  {{ | 	%asm  {{ | ||||||
| 		stx  P8ZP_SCRATCH_REG | 		stx  P8ZP_SCRATCH_REG | ||||||
| 		tax | 		tax | ||||||
|   | |||||||
| @@ -1,13 +1,501 @@ | |||||||
| ; Prog8 definitions for number conversions routines. | ; Number conversions routines. | ||||||
| ; | ; | ||||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||||
| ; |  | ||||||
| ; indent format: TABS, size=8 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| conv { | conv { | ||||||
|  |  | ||||||
| ; ----- number conversions to decimal strings | ; ----- number conversions to decimal strings ---- | ||||||
|  |  | ||||||
|  |     str  string_out = "????????????????"       ; result buffer for the string conversion routines | ||||||
|  |  | ||||||
|  | asmsub  str_ub0  (ubyte value @ A) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total) | ||||||
|  | 	%asm {{ | ||||||
|  |             phx | ||||||
|  |             jsr  conv.ubyte2decimal | ||||||
|  |             sty  string_out | ||||||
|  |             sta  string_out+1 | ||||||
|  |             stx  string_out+2 | ||||||
|  |             lda  #0 | ||||||
|  |             sta  string_out+3 | ||||||
|  |             plx | ||||||
|  |             rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_ub  (ubyte value @ A) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the ubyte in A in decimal string form, without left padding 0s | ||||||
|  | 	%asm {{ | ||||||
|  | 		phx | ||||||
|  | 		ldy  #0 | ||||||
|  | 		sty  P8ZP_SCRATCH_B1 | ||||||
|  | 		jsr  conv.ubyte2decimal | ||||||
|  | _output_byte_digits | ||||||
|  |                 ; hundreds? | ||||||
|  | 		cpy  #'0' | ||||||
|  | 		beq  + | ||||||
|  | 		pha | ||||||
|  | 		tya | ||||||
|  | 		ldy  P8ZP_SCRATCH_B1 | ||||||
|  | 		sta  string_out,y | ||||||
|  | 		pla | ||||||
|  | 		inc  P8ZP_SCRATCH_B1 | ||||||
|  | 		; tens? | ||||||
|  | +		ldy  P8ZP_SCRATCH_B1 | ||||||
|  |                 cmp  #'0' | ||||||
|  | 		beq  + | ||||||
|  | 		sta  string_out,y | ||||||
|  | 		iny | ||||||
|  | +               ; ones. | ||||||
|  |                 txa | ||||||
|  |                 sta  string_out,y | ||||||
|  |                 iny | ||||||
|  |                 lda  #0 | ||||||
|  |                 sta  string_out,y | ||||||
|  |                 plx | ||||||
|  |                 rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_b  (byte value @ A) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the byte in A in decimal string form, without left padding 0s | ||||||
|  | 	%asm {{ | ||||||
|  |             phx | ||||||
|  |             ldy  #0 | ||||||
|  |             sty  P8ZP_SCRATCH_B1 | ||||||
|  |             cmp  #0 | ||||||
|  |             bpl  + | ||||||
|  |             pha | ||||||
|  |             lda  #'-' | ||||||
|  |             sta  string_out | ||||||
|  |             inc  P8ZP_SCRATCH_B1 | ||||||
|  |             pla | ||||||
|  | +	    jsr  conv.byte2decimal | ||||||
|  |             bra  str_ub._output_byte_digits | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_ubhex  (ubyte value @ A) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the ubyte in A in hex string form | ||||||
|  | 	%asm {{ | ||||||
|  |             jsr  conv.ubyte2hex | ||||||
|  |             sta  string_out | ||||||
|  |             sty  string_out+1 | ||||||
|  |             lda  #0 | ||||||
|  |             sta  string_out+2 | ||||||
|  |             rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_ubbin  (ubyte value @ A) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the ubyte in A in binary string form | ||||||
|  | 	%asm {{ | ||||||
|  | 	    sta  P8ZP_SCRATCH_B1 | ||||||
|  | 	    ldy  #0 | ||||||
|  | 	    sty  string_out+8 | ||||||
|  | 	    ldy  #7 | ||||||
|  | -	    lsr  P8ZP_SCRATCH_B1 | ||||||
|  |             bcc  + | ||||||
|  |             lda  #'1' | ||||||
|  |             bne  _digit | ||||||
|  | +           lda  #'0' | ||||||
|  | _digit      sta  string_out,y | ||||||
|  |             dey | ||||||
|  | 	    bpl  - | ||||||
|  | 	    rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_uwbin  (uword value @ AY) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the uword in A/Y in binary string form | ||||||
|  | 	%asm {{ | ||||||
|  | 	    sta  P8ZP_SCRATCH_REG | ||||||
|  | 	    tya | ||||||
|  | 	    jsr  str_ubbin | ||||||
|  | 	    ldy  #0 | ||||||
|  | 	    sty  string_out+16 | ||||||
|  | 	    ldy  #7 | ||||||
|  | -	    lsr  P8ZP_SCRATCH_REG | ||||||
|  |             bcc  + | ||||||
|  |             lda  #'1' | ||||||
|  |             bne  _digit | ||||||
|  | +           lda  #'0' | ||||||
|  | _digit      sta  string_out+8,y | ||||||
|  |             dey | ||||||
|  | 	    bpl  - | ||||||
|  | 	    rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_uwhex  (uword value @ AY) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the uword in A/Y in hexadecimal string form (4 digits) | ||||||
|  | 	%asm {{ | ||||||
|  |             pha | ||||||
|  |             tya | ||||||
|  |             jsr  conv.ubyte2hex | ||||||
|  |             sta  string_out | ||||||
|  |             sty  string_out+1 | ||||||
|  |             pla | ||||||
|  |             jsr  conv.ubyte2hex | ||||||
|  |             sta  string_out+2 | ||||||
|  |             sty  string_out+3 | ||||||
|  |             lda  #0 | ||||||
|  |             sta  string_out+4 | ||||||
|  |             rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_uw0  (uword value @ AY) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total) | ||||||
|  | 	%asm {{ | ||||||
|  | 	    phx | ||||||
|  | 	    jsr  conv.uword2decimal | ||||||
|  | 	    ldy  #0 | ||||||
|  | -           lda  conv.uword2decimal.decTenThousands,y | ||||||
|  |             sta  string_out,y | ||||||
|  |             beq  + | ||||||
|  |             iny | ||||||
|  |             bne  - | ||||||
|  | +           plx | ||||||
|  | 	    rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_uw  (uword value @ AY) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the uword in A/Y in decimal string form, without left padding 0s | ||||||
|  | 	%asm {{ | ||||||
|  | 	    phx | ||||||
|  | 	    jsr  conv.uword2decimal | ||||||
|  | 	    ldx  #0 | ||||||
|  | _output_digits | ||||||
|  | 	    ldy  #0 | ||||||
|  | -           lda  conv.uword2decimal.decTenThousands,y | ||||||
|  |             beq  _allzero | ||||||
|  |             cmp  #'0' | ||||||
|  |             bne  _gotdigit | ||||||
|  |             iny | ||||||
|  |             bne  - | ||||||
|  | _gotdigit   sta  string_out,x | ||||||
|  |             inx | ||||||
|  |             iny | ||||||
|  |             lda  conv.uword2decimal.decTenThousands,y | ||||||
|  |             bne  _gotdigit | ||||||
|  | _end        lda  #0 | ||||||
|  |             sta  string_out,x | ||||||
|  |             plx | ||||||
|  |             rts | ||||||
|  |  | ||||||
|  | _allzero    lda  #'0' | ||||||
|  |             sta  string_out,x | ||||||
|  |             inx | ||||||
|  |             bne  _end | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str_w  (word value @ AY) clobbers(A,Y)  { | ||||||
|  | 	; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's | ||||||
|  | 	%asm {{ | ||||||
|  | 	    cpy  #0 | ||||||
|  | 	    bpl  str_uw | ||||||
|  | 	    phx | ||||||
|  | 	    pha | ||||||
|  | 	    lda  #'-' | ||||||
|  | 	    sta  string_out | ||||||
|  |             tya | ||||||
|  |             eor  #255 | ||||||
|  |             tay | ||||||
|  |             pla | ||||||
|  |             eor  #255 | ||||||
|  |             clc | ||||||
|  |             adc  #1 | ||||||
|  |             bcc  + | ||||||
|  |             iny | ||||||
|  | +	    jsr  conv.uword2decimal | ||||||
|  | 	    ldx  #1 | ||||||
|  | 	    bne  str_uw._output_digits | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ; ---- string conversion to numbers ----- | ||||||
|  |  | ||||||
|  | asmsub  any2uword(str string @AY) clobbers(Y) -> ubyte @A { | ||||||
|  | 	; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format. | ||||||
|  | 	;    (the latter two require a $ or % prefix to be recognised) | ||||||
|  | 	;    (any non-digit character will terminate the number string that is parsed) | ||||||
|  | 	;    returns amount of processed characters in A, and the parsed number will be in cx16.r15. | ||||||
|  | 	;    if the string was invalid, 0 will be returned in A. | ||||||
|  | 	%asm {{ | ||||||
|  | 	pha | ||||||
|  | 	sta  P8ZP_SCRATCH_W1 | ||||||
|  | 	sty  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	ldy  #0 | ||||||
|  | 	lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 	ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	cmp  #'$' | ||||||
|  | 	beq  _hex | ||||||
|  | 	cmp  #'%' | ||||||
|  | 	beq  _bin | ||||||
|  | 	pla | ||||||
|  | 	jsr  str2uword | ||||||
|  | 	jmp  _result | ||||||
|  | _hex	pla | ||||||
|  | 	jsr  hex2uword | ||||||
|  | 	jmp  _result | ||||||
|  | _bin	pla | ||||||
|  | 	jsr  bin2uword | ||||||
|  | _result | ||||||
|  |         pha | ||||||
|  |         lda  cx16.r15 | ||||||
|  |         sta  P8ZP_SCRATCH_B1        ; result value | ||||||
|  |         pla | ||||||
|  |         sta  cx16.r15 | ||||||
|  |         sty  cx16.r15+1 | ||||||
|  |         lda  P8ZP_SCRATCH_B1 | ||||||
|  |         rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline asmsub  str2ubyte(str string @AY) clobbers(Y) -> ubyte @A { | ||||||
|  | 	; -- returns in A the unsigned byte value of the string number argument in AY | ||||||
|  | 	;    the number may NOT be preceded by a + sign and may NOT contain spaces | ||||||
|  | 	;    (any non-digit character will terminate the number string that is parsed) | ||||||
|  | 	;    result in A,  number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error) | ||||||
|  | 	%asm {{ | ||||||
|  | 		jsr  conv.str2uword | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline asmsub  str2byte(str string @AY) clobbers(Y) -> ubyte @A { | ||||||
|  | 	; -- returns in A the signed byte value of the string number argument in AY | ||||||
|  | 	;    the number may be preceded by a + or - sign but may NOT contain spaces | ||||||
|  | 	;    (any non-digit character will terminate the number string that is parsed) | ||||||
|  | 	;    result in A,  number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error) | ||||||
|  | 	%asm {{ | ||||||
|  | 		jsr  conv.str2word | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str2uword(str string @AY) -> uword @AY { | ||||||
|  | 	; -- returns the unsigned word value of the string number argument in AY | ||||||
|  | 	;    the number may NOT be preceded by a + sign and may NOT contain spaces | ||||||
|  | 	;    (any non-digit character will terminate the number string that is parsed) | ||||||
|  | 	;    result in AY,  number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error) | ||||||
|  | 	%asm {{ | ||||||
|  | _result = P8ZP_SCRATCH_W1 | ||||||
|  |         	sta  P8ZP_SCRATCH_W2 | ||||||
|  |         	sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		ldy  #0 | ||||||
|  | 		sty  _result | ||||||
|  | 		sty  _result+1 | ||||||
|  | 		sty  cx16.r15+1 | ||||||
|  | _loop | ||||||
|  | 		lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		sec | ||||||
|  | 		sbc  #48 | ||||||
|  | 		bpl  _digit | ||||||
|  | _done | ||||||
|  | 		sty  cx16.r15 | ||||||
|  | 		lda  _result | ||||||
|  | 		ldy  _result+1 | ||||||
|  | 		rts | ||||||
|  | _digit | ||||||
|  | 		cmp  #10 | ||||||
|  | 		bcs  _done | ||||||
|  | 		; add digit to result | ||||||
|  | 		pha | ||||||
|  | 		jsr  _result_times_10 | ||||||
|  | 		pla | ||||||
|  | 		clc | ||||||
|  | 		adc  _result | ||||||
|  | 		sta  _result | ||||||
|  | 		bcc  + | ||||||
|  | 		inc  _result+1 | ||||||
|  | +		iny | ||||||
|  | 		bne  _loop | ||||||
|  | 		; never reached | ||||||
|  |  | ||||||
|  | _result_times_10     ; (W*4 + W)*2 | ||||||
|  | 		lda  _result+1 | ||||||
|  | 		sta  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  _result | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		clc | ||||||
|  | 		adc  _result | ||||||
|  | 		sta  _result | ||||||
|  | 		lda  P8ZP_SCRATCH_REG | ||||||
|  | 		adc  _result+1 | ||||||
|  | 		asl  _result | ||||||
|  | 		rol  a | ||||||
|  | 		sta  _result+1 | ||||||
|  | 		rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  str2word(str string @AY) -> word @AY { | ||||||
|  | 	; -- returns the signed word value of the string number argument in AY | ||||||
|  | 	;    the number may be preceded by a + or - sign but may NOT contain spaces | ||||||
|  | 	;    (any non-digit character will terminate the number string that is parsed) | ||||||
|  | 	;    result in AY,  number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error) | ||||||
|  | 	%asm {{ | ||||||
|  | _result = P8ZP_SCRATCH_W1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W2 | ||||||
|  | 		sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		ldy  #0 | ||||||
|  | 		sty  _result | ||||||
|  | 		sty  _result+1 | ||||||
|  | 		sty  _negative | ||||||
|  | 		sty  cx16.r15+1 | ||||||
|  | 		lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		cmp  #'+' | ||||||
|  | 		bne  + | ||||||
|  | 		iny | ||||||
|  | +		cmp  #'-' | ||||||
|  | 		bne  _parse | ||||||
|  | 		inc  _negative | ||||||
|  | 		iny | ||||||
|  | _parse		lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		sec | ||||||
|  | 		sbc  #48 | ||||||
|  | 		bpl  _digit | ||||||
|  | _done | ||||||
|  | 		sty  cx16.r15 | ||||||
|  | 		lda  _negative | ||||||
|  | 		beq  + | ||||||
|  | 		sec | ||||||
|  | 		lda  #0 | ||||||
|  | 		sbc  _result | ||||||
|  | 		sta  _result | ||||||
|  | 		lda  #0 | ||||||
|  | 		sbc  _result+1 | ||||||
|  | 		sta  _result+1 | ||||||
|  | +		lda  _result | ||||||
|  | 		ldy  _result+1 | ||||||
|  | 		rts | ||||||
|  | _digit | ||||||
|  | 		cmp  #10 | ||||||
|  | 		bcs  _done | ||||||
|  | 		; add digit to result | ||||||
|  | 		pha | ||||||
|  | 		jsr  str2uword._result_times_10 | ||||||
|  | 		pla | ||||||
|  | 		clc | ||||||
|  | 		adc  _result | ||||||
|  | 		sta  _result | ||||||
|  | 		bcc  + | ||||||
|  | 		inc  _result+1 | ||||||
|  | +		iny | ||||||
|  | 		bne  _parse | ||||||
|  | 		; never reached | ||||||
|  | _negative	.byte  0 | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  hex2uword(str string @AY) -> uword @AY { | ||||||
|  | 	; -- hexadecimal string (with or without '$') to uword. | ||||||
|  | 	;    string may be in petscii or c64-screencode encoding. | ||||||
|  | 	;    stops parsing at the first character that's not a hex digit (except leading $) | ||||||
|  | 	;    result in AY,  number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error) | ||||||
|  | 	%asm {{ | ||||||
|  | 	sta  P8ZP_SCRATCH_W2 | ||||||
|  | 	sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 	ldy  #0 | ||||||
|  | 	sty  P8ZP_SCRATCH_W1 | ||||||
|  | 	sty  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	sty  cx16.r15+1 | ||||||
|  | 	lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 	beq  _stop | ||||||
|  | 	cmp  #'$' | ||||||
|  | 	bne  _loop | ||||||
|  | 	iny | ||||||
|  | _loop | ||||||
|  | 	lda  #0 | ||||||
|  | 	sta  P8ZP_SCRATCH_B1 | ||||||
|  | 	lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 	beq  _stop | ||||||
|  | 	cmp  #7                 ; screencode letters A-F are 1-6 | ||||||
|  | 	bcc  _add_letter | ||||||
|  | 	cmp  #'g' | ||||||
|  | 	bcs  _stop | ||||||
|  | 	cmp  #'a' | ||||||
|  | 	bcs  _add_letter | ||||||
|  | 	cmp  #'0' | ||||||
|  | 	bcc  _stop | ||||||
|  | 	cmp  #'9'+1 | ||||||
|  | 	bcs  _stop | ||||||
|  | _calc | ||||||
|  | 	asl  P8ZP_SCRATCH_W1 | ||||||
|  | 	rol  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	asl  P8ZP_SCRATCH_W1 | ||||||
|  | 	rol  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	asl  P8ZP_SCRATCH_W1 | ||||||
|  | 	rol  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	asl  P8ZP_SCRATCH_W1 | ||||||
|  | 	rol  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	and  #$0f | ||||||
|  | 	clc | ||||||
|  | 	adc  P8ZP_SCRATCH_B1 | ||||||
|  | 	ora  P8ZP_SCRATCH_W1 | ||||||
|  | 	sta  P8ZP_SCRATCH_W1 | ||||||
|  | 	iny | ||||||
|  | 	bne  _loop | ||||||
|  | _stop | ||||||
|  | 	sty  cx16.r15 | ||||||
|  | 	lda  P8ZP_SCRATCH_W1 | ||||||
|  | 	ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	rts | ||||||
|  | _add_letter | ||||||
|  | 	pha | ||||||
|  | 	lda  #9 | ||||||
|  | 	sta  P8ZP_SCRATCH_B1 | ||||||
|  | 	pla | ||||||
|  | 	jmp  _calc | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  bin2uword(str string @AY) -> uword @AY { | ||||||
|  | 	; -- binary string (with or without '%') to uword. | ||||||
|  | 	;    stops parsing at the first character that's not a 0 or 1. (except leading %) | ||||||
|  | 	;    result in AY,  number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error) | ||||||
|  | 	%asm {{ | ||||||
|  | 	sta  P8ZP_SCRATCH_W2 | ||||||
|  | 	sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 	ldy  #0 | ||||||
|  | 	sty  P8ZP_SCRATCH_W1 | ||||||
|  | 	sty  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	sty  cx16.r15+1 | ||||||
|  | 	lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 	beq  _stop | ||||||
|  | 	cmp  #'%' | ||||||
|  | 	bne  _loop | ||||||
|  | 	iny | ||||||
|  | _loop | ||||||
|  | 	lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 	cmp  #'0' | ||||||
|  | 	bcc  _stop | ||||||
|  | 	cmp  #'2' | ||||||
|  | 	bcs  _stop | ||||||
|  | _first  asl  P8ZP_SCRATCH_W1 | ||||||
|  | 	rol  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	and  #1 | ||||||
|  | 	ora  P8ZP_SCRATCH_W1 | ||||||
|  | 	sta  P8ZP_SCRATCH_W1 | ||||||
|  | 	iny | ||||||
|  | 	bne  _loop | ||||||
|  | _stop | ||||||
|  | 	sty  cx16.r15 | ||||||
|  | 	lda  P8ZP_SCRATCH_W1 | ||||||
|  | 	ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 	rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ; ----- low level number conversions to decimal strings ---- | ||||||
|  |  | ||||||
| asmsub  ubyte2decimal  (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X  { | asmsub  ubyte2decimal  (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X  { | ||||||
| 	; ---- A to decimal string in Y/A/X  (100s in Y, 10s in A, 1s in X) | 	; ---- A to decimal string in Y/A/X  (100s in Y, 10s in A, 1s in X) | ||||||
| @@ -193,10 +681,6 @@ decOnes   		.byte  0 | |||||||
|     }} |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ----- utility functions ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| asmsub  byte2decimal  (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X  { | asmsub  byte2decimal  (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X  { | ||||||
| 	; ---- A (signed byte) to decimal string in Y/A/X  (100s in Y, 10s in A, 1s in X) | 	; ---- A (signed byte) to decimal string in Y/A/X  (100s in Y, 10s in A, 1s in X) | ||||||
| 	;      note: if the number is negative, you have to deal with the '-' yourself! | 	;      note: if the number is negative, you have to deal with the '-' yourself! | ||||||
| @@ -249,113 +733,4 @@ output	.text  "0000", $00      ; 0-terminated output buffer (to make printing ea | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub str2uword(str string @ AY) -> uword @ AY { |  | ||||||
| 	; -- returns the unsigned word value of the string number argument in AY |  | ||||||
| 	;    the number may NOT be preceded by a + sign and may NOT contain spaces |  | ||||||
| 	;    (any non-digit character will terminate the number string that is parsed) |  | ||||||
| 	%asm {{ |  | ||||||
| _result = P8ZP_SCRATCH_W2 |  | ||||||
| 		sta  _mod+1 |  | ||||||
| 		sty  _mod+2 |  | ||||||
| 		ldy  #0 |  | ||||||
| 		sty  _result |  | ||||||
| 		sty  _result+1 |  | ||||||
| _mod		lda  $ffff,y		; modified |  | ||||||
| 		sec |  | ||||||
| 		sbc  #48 |  | ||||||
| 		bpl  + |  | ||||||
| _done		; return result |  | ||||||
| 		lda  _result |  | ||||||
| 		ldy  _result+1 |  | ||||||
| 		rts |  | ||||||
| +		cmp  #10 |  | ||||||
| 		bcs  _done |  | ||||||
| 		; add digit to result |  | ||||||
| 		pha |  | ||||||
| 		jsr  _result_times_10 |  | ||||||
| 		pla |  | ||||||
| 		clc |  | ||||||
| 		adc  _result |  | ||||||
| 		sta  _result |  | ||||||
| 		bcc  + |  | ||||||
| 		inc  _result+1 |  | ||||||
| +		iny |  | ||||||
| 		bne  _mod |  | ||||||
| 		; never reached |  | ||||||
|  |  | ||||||
| _result_times_10     ; (W*4 + W)*2 |  | ||||||
| 		lda  _result+1 |  | ||||||
| 		sta  P8ZP_SCRATCH_REG |  | ||||||
| 		lda  _result |  | ||||||
| 		asl  a |  | ||||||
| 		rol  P8ZP_SCRATCH_REG |  | ||||||
| 		asl  a |  | ||||||
| 		rol  P8ZP_SCRATCH_REG |  | ||||||
| 		clc |  | ||||||
| 		adc  _result |  | ||||||
| 		sta  _result |  | ||||||
| 		lda  P8ZP_SCRATCH_REG |  | ||||||
| 		adc  _result+1 |  | ||||||
| 		asl  _result |  | ||||||
| 		rol  a |  | ||||||
| 		sta  _result+1 |  | ||||||
| 		rts |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| asmsub str2word(str string @ AY) -> word @ AY { |  | ||||||
| 	; -- returns the signed word value of the string number argument in AY |  | ||||||
| 	;    the number may be preceded by a + or - sign but may NOT contain spaces |  | ||||||
| 	;    (any non-digit character will terminate the number string that is parsed) |  | ||||||
| 	%asm {{ |  | ||||||
| _result = P8ZP_SCRATCH_W2 |  | ||||||
| 		sta  P8ZP_SCRATCH_W1 |  | ||||||
| 		sty  P8ZP_SCRATCH_W1+1 |  | ||||||
| 		ldy  #0 |  | ||||||
| 		sty  _result |  | ||||||
| 		sty  _result+1 |  | ||||||
| 		sty  _negative |  | ||||||
| 		lda  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		cmp  #'+' |  | ||||||
| 		bne  + |  | ||||||
| 		iny |  | ||||||
| +		cmp  #'-' |  | ||||||
| 		bne  _parse |  | ||||||
| 		inc  _negative |  | ||||||
| 		iny |  | ||||||
| _parse		lda  (P8ZP_SCRATCH_W1),y |  | ||||||
| 		sec |  | ||||||
| 		sbc  #48 |  | ||||||
| 		bpl  _digit |  | ||||||
| _done		; return result |  | ||||||
| 		lda  _negative |  | ||||||
| 		beq  + |  | ||||||
| 		sec |  | ||||||
| 		lda  #0 |  | ||||||
| 		sbc  _result |  | ||||||
| 		sta  _result |  | ||||||
| 		lda  #0 |  | ||||||
| 		sbc  _result+1 |  | ||||||
| 		sta  _result+1 |  | ||||||
| +		lda  _result |  | ||||||
| 		ldy  _result+1 |  | ||||||
| 		rts |  | ||||||
| _digit		cmp  #10 |  | ||||||
| 		bcs  _done |  | ||||||
| 		; add digit to result |  | ||||||
| 		pha |  | ||||||
| 		jsr  str2uword._result_times_10 |  | ||||||
| 		pla |  | ||||||
| 		clc |  | ||||||
| 		adc  _result |  | ||||||
| 		sta  _result |  | ||||||
| 		bcc  + |  | ||||||
| 		inc  _result+1 |  | ||||||
| +		iny |  | ||||||
| 		bne  _parse |  | ||||||
| 		; never reached |  | ||||||
| _negative	.byte  0 |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -8,7 +8,9 @@ | |||||||
| %option enable_floats | %option enable_floats | ||||||
|  |  | ||||||
| floats { | floats { | ||||||
| 	; ---- this block contains C-64 floating point related functions ---- | 	; ---- this block contains C-64 compatible floating point related functions ---- | ||||||
|  | 	;      the addresses are from cx16 V39 emulator and roms! they won't work on older versions. | ||||||
|  |  | ||||||
|  |  | ||||||
|         const float  PI     = 3.141592653589793 |         const float  PI     = 3.141592653589793 | ||||||
|         const float  TWOPI  = 6.283185307179586 |         const float  TWOPI  = 6.283185307179586 | ||||||
| @@ -43,45 +45,44 @@ romsub $fe1e = NORMAL() clobbers(A,X,Y)                     ; normalize fac1 (?) | |||||||
| romsub $fe24 = LOG() clobbers(A,X,Y)                        ; fac1 = LN(fac1)  (natural log) | romsub $fe24 = LOG() clobbers(A,X,Y)                        ; fac1 = LN(fac1)  (natural log) | ||||||
| romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y)      ; fac1 *= mflpt value from A/Y | romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y)      ; fac1 *= mflpt value from A/Y | ||||||
| romsub $fe2a = FMULTT() clobbers(A,X,Y)                     ; fac1 *= fac2 | romsub $fe2a = FMULTT() clobbers(A,X,Y)                     ; fac1 *= fac2 | ||||||
| romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y)       ; load mflpt value from memory  in A/Y into fac2 | romsub $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y)     ; load mflpt value from memory  in A/Y into fac2 | ||||||
| romsub $fe36 = MUL10() clobbers(A,X,Y)                      ; fac1 *= 10 | romsub $fe33 = MUL10() clobbers(A,X,Y)                      ; fac1 *= 10 | ||||||
| romsub $fe3c = DIV10() clobbers(A,X,Y)                      ; fac1 /= 10 , CAUTION: result is always positive! | romsub $fe36 = DIV10() clobbers(A,X,Y)                      ; fac1 /= 10 , CAUTION: result is always positive! | ||||||
| romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 = mflpt in A/Y / fac1  (remainder in fac2) | romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 = mflpt in A/Y / fac1  (remainder in fac2) | ||||||
| romsub $fe42 = FDIVT() clobbers(A,X,Y)                      ; fac1 = fac2/fac1  (remainder in fac2)  mind the order of the operands | romsub $fe3c = FDIVT() clobbers(A,X,Y)                      ; fac1 = fac2/fac1  (remainder in fac2)  mind the order of the operands | ||||||
|  |  | ||||||
| romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y)        ; load mflpt value from memory  in A/Y into fac1 | romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y)      ; load mflpt value from memory  in A/Y into fac1 | ||||||
| romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y)        ; store fac1 to memory  X/Y as 5-byte mflpt | romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y)      ; store fac1 to memory  X/Y as 5-byte mflpt | ||||||
| romsub $fe4e = MOVFA() clobbers(A,X)                        ; copy fac2 to fac1 | romsub $fe48 = MOVFA() clobbers(A,X)                        ; copy fac2 to fac1 | ||||||
| romsub $fe51 = MOVAF() clobbers(A,X)                        ; copy fac1 to fac2  (rounded) | romsub $fe4b = MOVAF() clobbers(A,X)                        ; copy fac1 to fac2  (rounded) | ||||||
| romsub $fe54 = MOVEF() clobbers(A,X)                        ; copy fac1 to fac2 | romsub $fe4e = MOVEF() clobbers(A,X)                        ; copy fac1 to fac2 | ||||||
| romsub $fe5a = SIGN() -> ubyte @ A                          ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive | romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A            ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive | ||||||
| romsub $fe5d = SGN() clobbers(A,X,Y)                        ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1) | romsub $fe57 = SGN() clobbers(A,X,Y)                        ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1) | ||||||
| romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y)      ; 8 bit signed A -> float in fac1 | romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y)      ; 8 bit signed A -> float in fac1 | ||||||
| romsub $fe6c = ABS()                                        ; fac1 = ABS(fac1) | romsub $fe66 = ABS() clobbers(A,X,Y)                        ; fac1 = ABS(fac1) | ||||||
| romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A   ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than | romsub $fe69 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A   ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than | ||||||
| romsub $fe78 = INT() clobbers(A,X,Y)                        ; INT() truncates, use FADDH first to round instead of trunc | romsub $fe72 = INT() clobbers(A,X,Y)                        ; INT() truncates, use FADDH first to round instead of trunc | ||||||
| romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y)           ; fac1 += signed byte in A | romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y)           ; fac1 += signed byte in A | ||||||
| romsub $fe81 = FOUT() clobbers(X) -> uword @ AY             ; fac1 -> string, address returned in AY | romsub $fe7b = FOUT() clobbers(X) -> uword @ AY             ; fac1 -> string, address returned in AY | ||||||
| romsub $fe8a = SQR() clobbers(A,X,Y)                        ; fac1 = SQRT(fac1) | romsub $fe81 = SQR() clobbers(A,X,Y)                        ; fac1 = SQRT(fac1) | ||||||
| romsub $fe8d = FPWRT() clobbers(A,X,Y)                      ; fac1 = fac2 ** fac1 | romsub $fe84 = FPWRT() clobbers(A,X,Y)                      ; fac1 = fac2 ** fac1 | ||||||
| romsub $fe93 = NEGOP() clobbers(A)                          ; switch the sign of fac1 | romsub $fe8a = NEGOP() clobbers(A)                          ; switch the sign of fac1 (fac1 = -fac1) | ||||||
| romsub $fe96 = EXP() clobbers(A,X,Y)                        ; fac1 = EXP(fac1)  (e ** fac1) | romsub $fe8d = EXP() clobbers(A,X,Y)                        ; fac1 = EXP(fac1)  (e ** fac1) | ||||||
| romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y)                ; fac1 = RND(A) float random number generator | romsub $fe96 = RND() clobbers(A,X,Y)                        ; fac1 = RND(fac1) float random number generator | ||||||
| romsub $fea2 = RND() clobbers(A,X,Y)                        ; fac1 = RND(fac1) float random number generator | romsub $fe99 = COS() clobbers(A,X,Y)                        ; fac1 = COS(fac1) | ||||||
| romsub $fea5 = COS() clobbers(A,X,Y)                        ; fac1 = COS(fac1) | romsub $fe9c = SIN() clobbers(A,X,Y)                        ; fac1 = SIN(fac1) | ||||||
| romsub $fea8 = SIN() clobbers(A,X,Y)                        ; fac1 = SIN(fac1) | romsub $fe9f = TAN() clobbers(A,X,Y)                        ; fac1 = TAN(fac1) | ||||||
| romsub $feab = TAN() clobbers(A,X,Y)                        ; fac1 = TAN(fac1) | romsub $fea2 = ATN() clobbers(A,X,Y)                        ; fac1 = ATN(fac1) | ||||||
| romsub $feae = ATN() clobbers(A,X,Y)                        ; fac1 = ATN(fac1) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| asmsub  GIVUAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | asmsub  GIVUAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | ||||||
| 	; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1 | 	; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1 | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
|         phx |         phx | ||||||
|         sta  P8ZP_SCRATCH_REG |         sta  _tmp | ||||||
|         sty  P8ZP_SCRATCH_B1 |         sty  P8ZP_SCRATCH_B1 | ||||||
|         tya |         tya | ||||||
|         ldy  P8ZP_SCRATCH_REG |         ldy  _tmp | ||||||
|         jsr  GIVAYF                 ; load it as signed... correct afterwards |         jsr  GIVAYF                 ; load it as signed... correct afterwards | ||||||
|         lda  P8ZP_SCRATCH_B1 |         lda  P8ZP_SCRATCH_B1 | ||||||
| 	    bpl  + | 	    bpl  + | ||||||
| @@ -90,6 +91,7 @@ asmsub  GIVUAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | |||||||
| 	    jsr  FADD | 	    jsr  FADD | ||||||
| +	    plx | +	    plx | ||||||
|         rts |         rts | ||||||
|  | _tmp        .byte 0 | ||||||
| _flt65536    .byte 145,0,0,0,0       ; 65536.0 | _flt65536    .byte 145,0,0,0,0       ; 65536.0 | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
| @@ -98,9 +100,9 @@ _flt65536    .byte 145,0,0,0,0       ; 65536.0 | |||||||
| asmsub  GIVAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | asmsub  GIVAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | ||||||
| 	; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1 | 	; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1 | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		sta  P8ZP_SCRATCH_REG | 		sta  P8ZP_SCRATCH_B1 | ||||||
| 		tya | 		tya | ||||||
| 		ldy  P8ZP_SCRATCH_REG | 		ldy  P8ZP_SCRATCH_B1 | ||||||
| 		jmp  GIVAYF		; this uses the inverse order, Y/A | 		jmp  GIVAYF		; this uses the inverse order, Y/A | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
| @@ -109,9 +111,9 @@ asmsub  FTOSWRDAY  () clobbers(X) -> uword @ AY  { | |||||||
| 	; ---- fac1 to signed word in A/Y | 	; ---- fac1 to signed word in A/Y | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 		jsr  FTOSWORDYA	; note the inverse Y/A order | 		jsr  FTOSWORDYA	; note the inverse Y/A order | ||||||
| 		sta  P8ZP_SCRATCH_REG | 		sta  P8ZP_SCRATCH_B1 | ||||||
| 		tya | 		tya | ||||||
| 		ldy  P8ZP_SCRATCH_REG | 		ldy  P8ZP_SCRATCH_B1 | ||||||
| 		rts | 		rts | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
| @@ -127,6 +129,14 @@ asmsub  GETADRAY  () clobbers(X) -> uword @ AY  { | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | asmsub  FREADUY (ubyte value @Y) { | ||||||
|  |     ; -- 8 bit unsigned Y -> float in fac1 | ||||||
|  |     %asm {{ | ||||||
|  |         lda  #0 | ||||||
|  |         jmp  GIVAYF | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
| sub  print_f  (float value) { | sub  print_f  (float value) { | ||||||
| 	; ---- prints the floating point value (without a newline). | 	; ---- prints the floating point value (without a newline). | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| @@ -148,6 +158,7 @@ sub  print_f  (float value) { | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| %asminclude "library:c64/floats.asm", "" | %asminclude "library:c64/floats.asm" | ||||||
|  | %asminclude "library:c64/floats_funcs.asm" | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										991
									
								
								compiler/res/prog8lib/cx16/gfx2.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										991
									
								
								compiler/res/prog8lib/cx16/gfx2.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,991 @@ | |||||||
|  | %target cx16 | ||||||
|  |  | ||||||
|  | ; Bitmap pixel graphics routines for the CommanderX16 | ||||||
|  | ; Custom routines to use the full-screen 640x480 and 320x240 screen modes. | ||||||
|  | ; (These modes are not supported by the documented GRAPH_xxxx kernal routines) | ||||||
|  | ; | ||||||
|  | ; No text layer is currently shown, text can be drawn as part of the bitmap itself. | ||||||
|  | ; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead. | ||||||
|  | ; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself. | ||||||
|  | ; Note: this library implements code for various resolutions and color depths. This takes up memory. | ||||||
|  | ;       If you're memory constrained you should probably not use this built-in library, | ||||||
|  | ;       but make a copy in your project only containing the code for the required resolution. | ||||||
|  | ; | ||||||
|  | ; | ||||||
|  | ; SCREEN MODE LIST: | ||||||
|  | ;   mode 0 = reset back to default text mode | ||||||
|  | ;   mode 1 = bitmap 320 x 240 monochrome | ||||||
|  | ;   mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented) | ||||||
|  | ;   mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented) | ||||||
|  | ;   mode 4 = bitmap 320 x 240 x 256c | ||||||
|  | ;   mode 5 = bitmap 640 x 480 monochrome | ||||||
|  | ;   mode 6 = bitmap 640 x 480 x 4c | ||||||
|  | ;   higher color dephts in highres are not supported due to lack of VRAM | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver? | ||||||
|  |  | ||||||
|  | gfx2 { | ||||||
|  |  | ||||||
|  |     ; read-only control variables: | ||||||
|  |     ubyte active_mode = 0 | ||||||
|  |     uword width = 0 | ||||||
|  |     uword height = 0 | ||||||
|  |     ubyte bpp = 0 | ||||||
|  |     ubyte monochrome_dont_stipple_flag = false            ; set to false to enable stippling mode in monochrome displaymodes | ||||||
|  |  | ||||||
|  |     sub screen_mode(ubyte mode) { | ||||||
|  |         when mode { | ||||||
|  |             1 -> { | ||||||
|  |                 ; lores monochrome | ||||||
|  |                 cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000      ; enable only layer 1 | ||||||
|  |                 cx16.VERA_DC_HSCALE = 64 | ||||||
|  |                 cx16.VERA_DC_VSCALE = 64 | ||||||
|  |                 cx16.VERA_L1_CONFIG = %00000100 | ||||||
|  |                 cx16.VERA_L1_MAPBASE = 0 | ||||||
|  |                 cx16.VERA_L1_TILEBASE = 0 | ||||||
|  |                 width = 320 | ||||||
|  |                 height = 240 | ||||||
|  |                 bpp = 1 | ||||||
|  |             } | ||||||
|  |             ; TODO modes 2, 3 not yet implemented | ||||||
|  |             4 -> { | ||||||
|  |                 ; lores 256c | ||||||
|  |                 cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000      ; enable only layer 1 | ||||||
|  |                 cx16.VERA_DC_HSCALE = 64 | ||||||
|  |                 cx16.VERA_DC_VSCALE = 64 | ||||||
|  |                 cx16.VERA_L1_CONFIG = %00000111 | ||||||
|  |                 cx16.VERA_L1_MAPBASE = 0 | ||||||
|  |                 cx16.VERA_L1_TILEBASE = 0 | ||||||
|  |                 width = 320 | ||||||
|  |                 height = 240 | ||||||
|  |                 bpp = 8 | ||||||
|  |             } | ||||||
|  |             5 -> { | ||||||
|  |                 ; highres monochrome | ||||||
|  |                 cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000      ; enable only layer 1 | ||||||
|  |                 cx16.VERA_DC_HSCALE = 128 | ||||||
|  |                 cx16.VERA_DC_VSCALE = 128 | ||||||
|  |                 cx16.VERA_L1_CONFIG = %00000100 | ||||||
|  |                 cx16.VERA_L1_MAPBASE = 0 | ||||||
|  |                 cx16.VERA_L1_TILEBASE = %00000001 | ||||||
|  |                 width = 640 | ||||||
|  |                 height = 480 | ||||||
|  |                 bpp = 1 | ||||||
|  |             } | ||||||
|  |             6 -> { | ||||||
|  |                 ; highres 4c | ||||||
|  |                 cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000      ; enable only layer 1 | ||||||
|  |                 cx16.VERA_DC_HSCALE = 128 | ||||||
|  |                 cx16.VERA_DC_VSCALE = 128 | ||||||
|  |                 cx16.VERA_L1_CONFIG = %00000101 | ||||||
|  |                 cx16.VERA_L1_MAPBASE = 0 | ||||||
|  |                 cx16.VERA_L1_TILEBASE = %00000001 | ||||||
|  |                 width = 640 | ||||||
|  |                 height = 480 | ||||||
|  |                 bpp = 2 | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 ; back to default text mode and colors | ||||||
|  |                 cx16.VERA_CTRL = %10000000      ; reset VERA and palette | ||||||
|  |                 c64.CINT()      ; back to text mode | ||||||
|  |                 width = 0 | ||||||
|  |                 height = 0 | ||||||
|  |                 bpp = 0 | ||||||
|  |                 mode = 0 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         active_mode = mode | ||||||
|  |         if bpp | ||||||
|  |             clear_screen() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub clear_screen() { | ||||||
|  |         monochrome_stipple(false) | ||||||
|  |         position(0, 0) | ||||||
|  |         when active_mode { | ||||||
|  |             1 -> { | ||||||
|  |                 ; lores monochrome | ||||||
|  |                 repeat 240/2/8 | ||||||
|  |                     cs_innerloop640() | ||||||
|  |             } | ||||||
|  |             ; TODO mode 2, 3 | ||||||
|  |             4 -> { | ||||||
|  |                 ; lores 256c | ||||||
|  |                 repeat 240/2 | ||||||
|  |                     cs_innerloop640() | ||||||
|  |             } | ||||||
|  |             5 -> { | ||||||
|  |                 ; highres monochrome | ||||||
|  |                 repeat 480/8 | ||||||
|  |                     cs_innerloop640() | ||||||
|  |             } | ||||||
|  |             6 -> { | ||||||
|  |                 ; highres 4c | ||||||
|  |                 repeat 480/4 | ||||||
|  |                     cs_innerloop640() | ||||||
|  |             } | ||||||
|  |             ; modes 7 and 8 not supported due to lack of VRAM | ||||||
|  |         } | ||||||
|  |         position(0, 0) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub monochrome_stipple(ubyte enable) { | ||||||
|  |         monochrome_dont_stipple_flag = not enable | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub rect(uword x, uword y, uword width, uword height, ubyte color) { | ||||||
|  |         if width==0 or height==0 | ||||||
|  |             return | ||||||
|  |         horizontal_line(x, y, width, color) | ||||||
|  |         if height==1 | ||||||
|  |             return | ||||||
|  |         horizontal_line(x, y+height-1, width, color) | ||||||
|  |         vertical_line(x, y+1, height-2, color) | ||||||
|  |         if width==1 | ||||||
|  |             return | ||||||
|  |         vertical_line(x+width-1, y+1, height-2, color) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub fillrect(uword x, uword y, uword width, uword height, ubyte color) { | ||||||
|  |         if width==0 | ||||||
|  |             return | ||||||
|  |         repeat height { | ||||||
|  |             horizontal_line(x, y, width, color) | ||||||
|  |             y++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub horizontal_line(uword x, uword y, uword length, ubyte color) { | ||||||
|  |         if length==0 | ||||||
|  |             return | ||||||
|  |         when active_mode { | ||||||
|  |             1, 5 -> { | ||||||
|  |                 ; monochrome modes, either resolution | ||||||
|  |                 ubyte separate_pixels = (8-lsb(x)) & 7 | ||||||
|  |                 if separate_pixels as uword > length | ||||||
|  |                     separate_pixels = lsb(length) | ||||||
|  |                 repeat separate_pixels { | ||||||
|  |                     ; TODO optimize this by writing a masked byte in 1 go | ||||||
|  |                     plot(x, y, color) | ||||||
|  |                     x++ | ||||||
|  |                 } | ||||||
|  |                 length -= separate_pixels | ||||||
|  |                 if length { | ||||||
|  |                     position(x, y) | ||||||
|  |                     separate_pixels = lsb(length) & 7 | ||||||
|  |                     x += length & $fff8 | ||||||
|  |                     %asm {{ | ||||||
|  |                         lsr  length+1 | ||||||
|  |                         ror  length | ||||||
|  |                         lsr  length+1 | ||||||
|  |                         ror  length | ||||||
|  |                         lsr  length+1 | ||||||
|  |                         ror  length | ||||||
|  |                         lda  color | ||||||
|  |                         bne  + | ||||||
|  |                         ldy  #0     ; black | ||||||
|  |                         bra  _loop | ||||||
|  | +                       lda  monochrome_dont_stipple_flag | ||||||
|  |                         beq  _stipple | ||||||
|  |                         ldy  #255       ; don't stipple | ||||||
|  |                         bra  _loop | ||||||
|  | _stipple                lda  y | ||||||
|  |                         and  #1         ; determine stipple pattern to use | ||||||
|  |                         bne  + | ||||||
|  |                         ldy  #%01010101 | ||||||
|  |                         bra  _loop | ||||||
|  | +                       ldy  #%10101010 | ||||||
|  | _loop                   lda  length | ||||||
|  |                         ora  length+1 | ||||||
|  |                         beq  _done | ||||||
|  |                         sty  cx16.VERA_DATA0 | ||||||
|  |                         lda  length | ||||||
|  |                         bne  + | ||||||
|  |                         dec  length+1 | ||||||
|  | +                       dec  length | ||||||
|  |                         bra  _loop | ||||||
|  | _done | ||||||
|  |                     }} | ||||||
|  |                     repeat separate_pixels { | ||||||
|  |                         ; TODO optimize this by writing a masked byte in 1 go | ||||||
|  |                         plot(x, y, color) | ||||||
|  |                         x++ | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 cx16.VERA_ADDR_H &= %00000111   ; vera auto-increment off again | ||||||
|  |             } | ||||||
|  |             4 -> { | ||||||
|  |                 ; lores 256c | ||||||
|  |                 position(x, y) | ||||||
|  |                 %asm {{ | ||||||
|  |                     lda  color | ||||||
|  |                     phx | ||||||
|  |                     ldx  length+1 | ||||||
|  |                     beq  + | ||||||
|  |                     ldy  #0 | ||||||
|  | -                   sta  cx16.VERA_DATA0 | ||||||
|  |                     iny | ||||||
|  |                     bne  - | ||||||
|  |                     dex | ||||||
|  |                     bne  - | ||||||
|  | +                   ldy  length     ; remaining | ||||||
|  |                     beq  + | ||||||
|  | -                   sta  cx16.VERA_DATA0 | ||||||
|  |                     dey | ||||||
|  |                     bne  - | ||||||
|  | +                   plx | ||||||
|  |                 }} | ||||||
|  |             } | ||||||
|  |             6 -> { | ||||||
|  |                 ; highres 4c | ||||||
|  |                 ; TODO also mostly usable for lores 4c? | ||||||
|  |                 color &= 3 | ||||||
|  |                 ubyte[4] colorbits | ||||||
|  |                 ubyte ii | ||||||
|  |                 for ii in 3 downto 0 { | ||||||
|  |                     colorbits[ii] = color | ||||||
|  |                     color <<= 2 | ||||||
|  |                 } | ||||||
|  |                 void addr_mul_24_for_highres_4c(y, x)      ; 24 bits result is in r0 and r1L (highest byte) | ||||||
|  |                 %asm {{ | ||||||
|  |                     lda  cx16.VERA_ADDR_H | ||||||
|  |                     and  #%00000111         ; no auto advance | ||||||
|  |                     sta  cx16.VERA_ADDR_H | ||||||
|  |                     stz  cx16.VERA_CTRL     ; setup vera addr 0 | ||||||
|  |                     lda  cx16.r1 | ||||||
|  |                     and  #1 | ||||||
|  |                     sta  cx16.VERA_ADDR_H | ||||||
|  |                     lda  cx16.r0 | ||||||
|  |                     sta  cx16.VERA_ADDR_L | ||||||
|  |                     lda  cx16.r0+1 | ||||||
|  |                     sta  cx16.VERA_ADDR_M | ||||||
|  |                     phx | ||||||
|  |                     ldx  x | ||||||
|  |                 }} | ||||||
|  |  | ||||||
|  |                 repeat length { | ||||||
|  |                     %asm {{ | ||||||
|  |                         txa | ||||||
|  |                         and  #3 | ||||||
|  |                         tay | ||||||
|  |                         lda  cx16.VERA_DATA0 | ||||||
|  |                         and  gfx2.plot.mask4c,y | ||||||
|  |                         ora  colorbits,y | ||||||
|  |                         sta  cx16.VERA_DATA0 | ||||||
|  |                         cpy  #%00000011         ; next vera byte? | ||||||
|  |                         bne  ++ | ||||||
|  |                         inc  cx16.VERA_ADDR_L | ||||||
|  |                         bne  ++ | ||||||
|  |                         inc  cx16.VERA_ADDR_M | ||||||
|  | +                       bne  + | ||||||
|  |                         inc  cx16.VERA_ADDR_H | ||||||
|  | +                       inx                     ; next pixel | ||||||
|  |                     }} | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 %asm {{ | ||||||
|  |                     plx | ||||||
|  |                 }} | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub vertical_line(uword x, uword y, uword height, ubyte color) { | ||||||
|  |         when active_mode { | ||||||
|  |             1, 5 -> { | ||||||
|  |                 ; monochrome, lo-res | ||||||
|  |                 cx16.r15L = gfx2.plot.bits[x as ubyte & 7]           ; bitmask | ||||||
|  |                 if color { | ||||||
|  |                     if monochrome_dont_stipple_flag { | ||||||
|  |                         ; draw continuous line. | ||||||
|  |                         position2(x,y,true) | ||||||
|  |                         if active_mode==1 | ||||||
|  |                             set_both_strides(11)    ; 40 increment = 1 line in 320 px monochrome | ||||||
|  |                         else | ||||||
|  |                             set_both_strides(12)    ; 80 increment = 1 line in 640 px monochrome | ||||||
|  |                         repeat height { | ||||||
|  |                             %asm {{ | ||||||
|  |                                 lda  cx16.VERA_DATA0 | ||||||
|  |                                 ora  cx16.r15L | ||||||
|  |                                 sta  cx16.VERA_DATA1 | ||||||
|  |                             }} | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         ; draw stippled line. | ||||||
|  |                         if x&1 { | ||||||
|  |                             y++ | ||||||
|  |                             height-- | ||||||
|  |                         } | ||||||
|  |                         position2(x,y,true) | ||||||
|  |                         if active_mode==1 | ||||||
|  |                             set_both_strides(12)    ; 80 increment = 2 line in 320 px monochrome | ||||||
|  |                         else | ||||||
|  |                             set_both_strides(13)    ; 160 increment = 2 line in 640 px monochrome | ||||||
|  |                         repeat height/2 { | ||||||
|  |                             %asm {{ | ||||||
|  |                                 lda  cx16.VERA_DATA0 | ||||||
|  |                                 ora  cx16.r15L | ||||||
|  |                                 sta  cx16.VERA_DATA1 | ||||||
|  |                             }} | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     position2(x,y,true) | ||||||
|  |                     cx16.r15 = ~cx16.r15    ; erase pixels | ||||||
|  |                     if active_mode==1 | ||||||
|  |                         set_both_strides(11)    ; 40 increment = 1 line in 320 px monochrome | ||||||
|  |                     else | ||||||
|  |                         set_both_strides(12)    ; 80 increment = 1 line in 640 px monochrome | ||||||
|  |                     repeat height { | ||||||
|  |                         %asm {{ | ||||||
|  |                             lda  cx16.VERA_DATA0 | ||||||
|  |                             and  cx16.r15L | ||||||
|  |                             sta  cx16.VERA_DATA1 | ||||||
|  |                         }} | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             4 -> { | ||||||
|  |                 ; lores 256c | ||||||
|  |                 ; set vera auto-increment to 320 pixel increment (=next line) | ||||||
|  |                 position(x,y) | ||||||
|  |                 cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4) | ||||||
|  |                 %asm {{ | ||||||
|  |                     ldy  height | ||||||
|  |                     beq  + | ||||||
|  |                     lda  color | ||||||
|  | -                   sta  cx16.VERA_DATA0 | ||||||
|  |                     dey | ||||||
|  |                     bne  - | ||||||
|  | + | ||||||
|  |                 }} | ||||||
|  |             } | ||||||
|  |             6 -> { | ||||||
|  |                 ; highres 4c | ||||||
|  |                 ; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible | ||||||
|  |                 if height==0 | ||||||
|  |                     return | ||||||
|  |                 position2(x,y,true) | ||||||
|  |                 set_both_strides(13)    ; 160 increment = 1 line in 640 px 4c mode | ||||||
|  |                 color &= 3 | ||||||
|  |                 color <<= gfx2.plot.shift4c[lsb(x) & 3] | ||||||
|  |                 ubyte mask = gfx2.plot.mask4c[lsb(x) & 3] | ||||||
|  |                 repeat height { | ||||||
|  |                     %asm {{ | ||||||
|  |                         lda  cx16.VERA_DATA0 | ||||||
|  |                         and  mask | ||||||
|  |                         ora  color | ||||||
|  |                         sta  cx16.VERA_DATA1 | ||||||
|  |                     }} | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         sub set_both_strides(ubyte stride) { | ||||||
|  |             stride <<= 4 | ||||||
|  |             cx16.VERA_CTRL = 0 | ||||||
|  |             cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride | ||||||
|  |             cx16.VERA_CTRL = 1 | ||||||
|  |             cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | stride | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) { | ||||||
|  |         ; Bresenham algorithm. | ||||||
|  |         ; This code special-cases various quadrant loops to allow simple ++ and -- operations. | ||||||
|  |         if y1>y2 { | ||||||
|  |             ; make sure dy is always positive to have only 4 instead of 8 special cases | ||||||
|  |             swap(x1, x2) | ||||||
|  |             swap(y1, y2) | ||||||
|  |         } | ||||||
|  |         word @zp dx = (x2 as word)-x1 | ||||||
|  |         word @zp dy = (y2 as word)-y1 | ||||||
|  |  | ||||||
|  |         if dx==0 { | ||||||
|  |             vertical_line(x1, y1, abs(dy) as uword +1, color) | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         if dy==0 { | ||||||
|  |             if x1>x2 | ||||||
|  |                 x1=x2 | ||||||
|  |             horizontal_line(x1, y1, abs(dx) as uword +1, color) | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         word @zp d = 0 | ||||||
|  |         cx16.r13 = true      ; 'positive_ix' | ||||||
|  |         if dx < 0 { | ||||||
|  |             dx = -dx | ||||||
|  |             cx16.r13 = false | ||||||
|  |         } | ||||||
|  |         word @zp dx2 = dx*2 | ||||||
|  |         word @zp dy2 = dy*2 | ||||||
|  |         cx16.r14 = x1       ; internal plot X | ||||||
|  |  | ||||||
|  |         if dx >= dy { | ||||||
|  |             if cx16.r13 { | ||||||
|  |                 repeat { | ||||||
|  |                     plot(cx16.r14, y1, color) | ||||||
|  |                     if cx16.r14==x2 | ||||||
|  |                         return | ||||||
|  |                     cx16.r14++ | ||||||
|  |                     d += dy2 | ||||||
|  |                     if d > dx { | ||||||
|  |                         y1++ | ||||||
|  |                         d -= dx2 | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 repeat { | ||||||
|  |                     plot(cx16.r14, y1, color) | ||||||
|  |                     if cx16.r14==x2 | ||||||
|  |                         return | ||||||
|  |                     cx16.r14-- | ||||||
|  |                     d += dy2 | ||||||
|  |                     if d > dx { | ||||||
|  |                         y1++ | ||||||
|  |                         d -= dx2 | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             if cx16.r13 { | ||||||
|  |                 repeat { | ||||||
|  |                     plot(cx16.r14, y1, color) | ||||||
|  |                     if y1 == y2 | ||||||
|  |                         return | ||||||
|  |                     y1++ | ||||||
|  |                     d += dx2 | ||||||
|  |                     if d > dy { | ||||||
|  |                         cx16.r14++ | ||||||
|  |                         d -= dy2 | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 repeat { | ||||||
|  |                     plot(cx16.r14, y1, color) | ||||||
|  |                     if y1 == y2 | ||||||
|  |                         return | ||||||
|  |                     y1++ | ||||||
|  |                     d += dx2 | ||||||
|  |                     if d > dy { | ||||||
|  |                         cx16.r14-- | ||||||
|  |                         d -= dy2 | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) { | ||||||
|  |         ; Midpoint algorithm. | ||||||
|  |         if radius==0 | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         ubyte @zp xx = radius | ||||||
|  |         ubyte @zp yy = 0 | ||||||
|  |         word @zp decisionOver2 = (1 as word)-xx | ||||||
|  |         ; R14 = internal plot X | ||||||
|  |         ; R15 = internal plot Y | ||||||
|  |  | ||||||
|  |         while xx>=yy { | ||||||
|  |             cx16.r14 = xcenter + xx | ||||||
|  |             cx16.r15 = ycenter + yy | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |             cx16.r14 = xcenter - xx | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |             cx16.r14 = xcenter + xx | ||||||
|  |             cx16.r15 = ycenter - yy | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |             cx16.r14 = xcenter - xx | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |             cx16.r14 = xcenter + yy | ||||||
|  |             cx16.r15 = ycenter + xx | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |             cx16.r14 = xcenter - yy | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |             cx16.r14 = xcenter + yy | ||||||
|  |             cx16.r15 = ycenter - xx | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |             cx16.r14 = xcenter - yy | ||||||
|  |             plot(cx16.r14, cx16.r15, color) | ||||||
|  |  | ||||||
|  |             yy++ | ||||||
|  |             if decisionOver2<=0 | ||||||
|  |                 decisionOver2 += (yy as word)*2+1 | ||||||
|  |             else { | ||||||
|  |                 xx-- | ||||||
|  |                 decisionOver2 += (yy as word -xx)*2+1 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) { | ||||||
|  |         ; Midpoint algorithm, filled | ||||||
|  |         if radius==0 | ||||||
|  |             return | ||||||
|  |         ubyte @zp yy = 0 | ||||||
|  |         word @zp decisionOver2 = (1 as word)-radius | ||||||
|  |  | ||||||
|  |         while radius>=yy { | ||||||
|  |             horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color) | ||||||
|  |             horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color) | ||||||
|  |             horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color) | ||||||
|  |             horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color) | ||||||
|  |             yy++ | ||||||
|  |             if decisionOver2<=0 | ||||||
|  |                 decisionOver2 += (yy as word)*2+1 | ||||||
|  |             else { | ||||||
|  |                 radius-- | ||||||
|  |                 decisionOver2 += (yy as word -radius)*2+1 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub plot(uword @zp x, uword y, ubyte color) { | ||||||
|  |         ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1] | ||||||
|  |         ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100] | ||||||
|  |         ubyte[4] shift4c = [6,4,2,0] | ||||||
|  |  | ||||||
|  |         when active_mode { | ||||||
|  |             1 -> { | ||||||
|  |                 ; lores monochrome | ||||||
|  |                 %asm {{ | ||||||
|  |                     lda  x | ||||||
|  |                     eor  y | ||||||
|  |                     ora  monochrome_dont_stipple_flag | ||||||
|  |                     and  #1 | ||||||
|  |                 }} | ||||||
|  |                 if_nz { | ||||||
|  |                     cx16.r0L = lsb(x) & 7       ; xbits | ||||||
|  |                     x /= 8 | ||||||
|  |                     x += y*(320/8) | ||||||
|  |                     %asm {{ | ||||||
|  |                         stz  cx16.VERA_CTRL | ||||||
|  |                         stz  cx16.VERA_ADDR_H | ||||||
|  |                         lda  x+1 | ||||||
|  |                         sta  cx16.VERA_ADDR_M | ||||||
|  |                         lda  x | ||||||
|  |                         sta  cx16.VERA_ADDR_L | ||||||
|  |                         ldy  cx16.r0L       ; xbits | ||||||
|  |                         lda  bits,y | ||||||
|  |                         ldy  color | ||||||
|  |                         beq  + | ||||||
|  |                         tsb  cx16.VERA_DATA0 | ||||||
|  |                         bra  ++ | ||||||
|  | +                       trb  cx16.VERA_DATA0 | ||||||
|  | + | ||||||
|  |                     }} | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             ; TODO mode 2,3 | ||||||
|  |             4 -> { | ||||||
|  |                 ; lores 256c | ||||||
|  |                 void addr_mul_24_for_lores_256c(y, x)      ; 24 bits result is in r0 and r1L (highest byte) | ||||||
|  |                 %asm {{ | ||||||
|  |                     stz  cx16.VERA_CTRL | ||||||
|  |                     lda  cx16.r1 | ||||||
|  |                     ora  #%00010000     ; enable auto-increment so next_pixel() can be used after this | ||||||
|  |                     sta  cx16.VERA_ADDR_H | ||||||
|  |                     lda  cx16.r0+1 | ||||||
|  |                     sta  cx16.VERA_ADDR_M | ||||||
|  |                     lda  cx16.r0 | ||||||
|  |                     sta  cx16.VERA_ADDR_L | ||||||
|  |                     lda  color | ||||||
|  |                     sta  cx16.VERA_DATA0 | ||||||
|  |                 }} | ||||||
|  |             } | ||||||
|  |             5 -> { | ||||||
|  |                 ; highres monochrome | ||||||
|  |                 %asm {{ | ||||||
|  |                     lda  x | ||||||
|  |                     eor  y | ||||||
|  |                     ora  monochrome_dont_stipple_flag | ||||||
|  |                     and  #1 | ||||||
|  |                 }} | ||||||
|  |                 if_nz { | ||||||
|  |                     cx16.r0L = lsb(x) & 7       ; xbits | ||||||
|  |                     x /= 8 | ||||||
|  |                     x += y*(640/8) | ||||||
|  |                     %asm {{ | ||||||
|  |                         stz  cx16.VERA_CTRL | ||||||
|  |                         stz  cx16.VERA_ADDR_H | ||||||
|  |                         lda  x+1 | ||||||
|  |                         sta  cx16.VERA_ADDR_M | ||||||
|  |                         lda  x | ||||||
|  |                         sta  cx16.VERA_ADDR_L | ||||||
|  |                         ldy  cx16.r0L           ; xbits | ||||||
|  |                         lda  bits,y | ||||||
|  |                         ldy  color | ||||||
|  |                         beq  + | ||||||
|  |                         tsb  cx16.VERA_DATA0 | ||||||
|  |                         bra  ++ | ||||||
|  | +                       trb  cx16.VERA_DATA0 | ||||||
|  | + | ||||||
|  |                     }} | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             6 -> { | ||||||
|  |                 ; highres 4c | ||||||
|  |                 ; TODO also mostly usable for lores 4c? | ||||||
|  |                 void addr_mul_24_for_highres_4c(y, x)      ; 24 bits result is in r0 and r1L (highest byte) | ||||||
|  |                 cx16.r2L = lsb(x) & 3       ; xbits | ||||||
|  |                 color &= 3 | ||||||
|  |                 color <<= shift4c[cx16.r2L] | ||||||
|  |                 %asm {{ | ||||||
|  |                     stz  cx16.VERA_CTRL | ||||||
|  |                     lda  cx16.r1L | ||||||
|  |                     sta  cx16.VERA_ADDR_H | ||||||
|  |                     lda  cx16.r0H | ||||||
|  |                     sta  cx16.VERA_ADDR_M | ||||||
|  |                     lda  cx16.r0L | ||||||
|  |                     sta  cx16.VERA_ADDR_L | ||||||
|  |                     ldy  cx16.r2L           ; xbits | ||||||
|  |                     lda  mask4c,y | ||||||
|  |                     and  cx16.VERA_DATA0 | ||||||
|  |                     ora  color | ||||||
|  |                     sta  cx16.VERA_DATA0 | ||||||
|  |                 }} | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub position(uword @zp x, uword y) { | ||||||
|  |         ubyte bank | ||||||
|  |         when active_mode { | ||||||
|  |             1 -> { | ||||||
|  |                 ; lores monochrome | ||||||
|  |                 cx16.r0 = y*(320/8) + x/8 | ||||||
|  |                 cx16.vaddr(0, cx16.r0, 0, 1) | ||||||
|  |             } | ||||||
|  |             ; TODO modes 2,3 | ||||||
|  |             4 -> { | ||||||
|  |                 ; lores 256c | ||||||
|  |                 void addr_mul_24_for_lores_256c(y, x)      ; 24 bits result is in r0 and r1L (highest byte) | ||||||
|  |                 bank = lsb(cx16.r1) | ||||||
|  |                 cx16.vaddr(bank, cx16.r0, 0, 1) | ||||||
|  |             } | ||||||
|  |             5 -> { | ||||||
|  |                 ; highres monochrome | ||||||
|  |                 cx16.r0 = y*(640/8) + x/8 | ||||||
|  |                 cx16.vaddr(0, cx16.r0, 0, 1) | ||||||
|  |             } | ||||||
|  |             6 -> { | ||||||
|  |                 ; highres 4c | ||||||
|  |                 void addr_mul_24_for_highres_4c(y, x)      ; 24 bits result is in r0 and r1L (highest byte) | ||||||
|  |                 bank = lsb(cx16.r1) | ||||||
|  |                 cx16.vaddr(bank, cx16.r0, 0, 1) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub position2(uword @zp x, uword y, ubyte also_port_1) { | ||||||
|  |         position(x, y) | ||||||
|  |         if also_port_1 { | ||||||
|  |             when active_mode { | ||||||
|  |                 1, 5 -> cx16.vaddr(0, cx16.r0, 1, 1) | ||||||
|  |                 ; TODO modes 2, 3 | ||||||
|  |                 4, 6 -> { | ||||||
|  |                     ubyte bank = lsb(cx16.r1) | ||||||
|  |                     cx16.vaddr(bank, cx16.r0, 1, 1) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub next_pixel(ubyte color @A) { | ||||||
|  |         ; -- sets the next pixel byte to the graphics chip. | ||||||
|  |         ;    for 8 bpp screens this will plot 1 pixel. | ||||||
|  |         ;    for 1 bpp screens it will plot 8 pixels at once (color = bit pattern). | ||||||
|  |         ;    for 2 bpp screens it will plot 4 pixels at once (color = bit pattern). | ||||||
|  |         %asm {{ | ||||||
|  |             sta  cx16.VERA_DATA0 | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, Y)  { | ||||||
|  |         ; -- sets the next bunch of pixels from a prepared array of bytes. | ||||||
|  |         ;    for 8 bpp screens this will plot 1 pixel per byte. | ||||||
|  |         ;    for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte). | ||||||
|  |         ;    for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte). | ||||||
|  |         %asm {{ | ||||||
|  |             phx | ||||||
|  |             sta  P8ZP_SCRATCH_W1 | ||||||
|  |             sty  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldx  cx16.r0+1 | ||||||
|  |             beq  + | ||||||
|  |             ldy  #0 | ||||||
|  | -           lda  (P8ZP_SCRATCH_W1),y | ||||||
|  |             sta  cx16.VERA_DATA0 | ||||||
|  |             iny | ||||||
|  |             bne  - | ||||||
|  |             inc  P8ZP_SCRATCH_W1+1       ; next page of 256 pixels | ||||||
|  |             dex | ||||||
|  |             bne  - | ||||||
|  |  | ||||||
|  | +           ldx  cx16.r0           ; remaining pixels | ||||||
|  |             beq  + | ||||||
|  |             ldy  #0 | ||||||
|  | -           lda  (P8ZP_SCRATCH_W1),y | ||||||
|  |             sta  cx16.VERA_DATA0 | ||||||
|  |             iny | ||||||
|  |             dex | ||||||
|  |             bne  - | ||||||
|  | +           plx | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) { | ||||||
|  |         ; this is only useful in 256 color mode where one pixel equals one byte value. | ||||||
|  |         %asm {{ | ||||||
|  |             phx | ||||||
|  |             ldx  #8 | ||||||
|  | -           asl  cx16.r0 | ||||||
|  |             bcc  + | ||||||
|  |             sta  cx16.VERA_DATA0 | ||||||
|  |             bra  ++ | ||||||
|  | +           sty  cx16.VERA_DATA0 | ||||||
|  | +           dex | ||||||
|  |             bne  - | ||||||
|  |             plx | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const ubyte charset_orig_bank = $0 | ||||||
|  |     const uword charset_orig_addr = $f800        ; in bank 0, so $0f800 | ||||||
|  |     const ubyte charset_bank = $1 | ||||||
|  |     const uword charset_addr = $f000       ; in bank 1, so $1f000 | ||||||
|  |  | ||||||
|  |     sub text_charset(ubyte charset) { | ||||||
|  |         ; -- make a copy of the selected character set to use with text() | ||||||
|  |         ;    the charset number is the same as for the cx16.screen_set_charset() ROM function. | ||||||
|  |         ;    1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase. | ||||||
|  |         cx16.screen_set_charset(charset, 0) | ||||||
|  |         cx16.vaddr(charset_orig_bank, charset_orig_addr, 0, 1) | ||||||
|  |         cx16.vaddr(charset_bank, charset_addr, 1, 1) | ||||||
|  |         repeat 256*8 { | ||||||
|  |             cx16.VERA_DATA1 = cx16.VERA_DATA0 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub text(uword @zp x, uword y, ubyte color, uword sctextptr) { | ||||||
|  |         ; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!). | ||||||
|  |         ;    You must also have called text_charset() first to select and prepare the character set to use. | ||||||
|  |         ;    NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to multiples of 8 !  TODO allow per-pixel horizontal positioning | ||||||
|  |         uword chardataptr | ||||||
|  |         when active_mode { | ||||||
|  |             1, 5 -> { | ||||||
|  |                 ; monochrome mode, either resolution | ||||||
|  |                 cx16.r2 = 40 | ||||||
|  |                 if active_mode==5 | ||||||
|  |                     cx16.r2 = 80 | ||||||
|  |                 while @(sctextptr) { | ||||||
|  |                     chardataptr = charset_addr + (@(sctextptr) as uword)*8 | ||||||
|  |                     cx16.vaddr(charset_bank, chardataptr, 1, 1) | ||||||
|  |                     position(x,y) | ||||||
|  |                     %asm {{ | ||||||
|  |                         lda  cx16.VERA_ADDR_H | ||||||
|  |                         and  #%111              ; don't auto-increment, we have to do that manually because of the ora | ||||||
|  |                         sta  cx16.VERA_ADDR_H | ||||||
|  |                         lda  color | ||||||
|  |                         sta  P8ZP_SCRATCH_B1 | ||||||
|  |                         ldy  #8 | ||||||
|  | -                       lda  P8ZP_SCRATCH_B1 | ||||||
|  |                         bne  +                  ; white color, plot normally | ||||||
|  |                         lda  cx16.VERA_DATA1 | ||||||
|  |                         eor  #255               ; black color, keep only the other pixels | ||||||
|  |                         and  cx16.VERA_DATA0 | ||||||
|  |                         bra  ++ | ||||||
|  | +                       lda  cx16.VERA_DATA0 | ||||||
|  |                         ora  cx16.VERA_DATA1 | ||||||
|  | +                       sta  cx16.VERA_DATA0 | ||||||
|  |                         lda  cx16.VERA_ADDR_L | ||||||
|  |                         clc | ||||||
|  |                         adc  cx16.r2 | ||||||
|  |                         sta  cx16.VERA_ADDR_L | ||||||
|  |                         bcc  + | ||||||
|  |                         inc  cx16.VERA_ADDR_M | ||||||
|  | +                       lda  x | ||||||
|  |                         clc | ||||||
|  |                         adc  #1 | ||||||
|  |                         sta  x | ||||||
|  |                         bcc  + | ||||||
|  |                         inc  x+1 | ||||||
|  | +                       dey | ||||||
|  |                         bne  - | ||||||
|  |                     }} | ||||||
|  |                     sctextptr++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             4 -> { | ||||||
|  |                 ; lores 256c | ||||||
|  |                 while @(sctextptr) { | ||||||
|  |                     chardataptr = charset_addr + (@(sctextptr) as uword)*8 | ||||||
|  |                     cx16.vaddr(charset_bank, chardataptr, 1, 1) | ||||||
|  |                     repeat 8 { | ||||||
|  |                         ; TODO rewrite this inner loop fully in assembly | ||||||
|  |                         position(x,y) | ||||||
|  |                         y++ | ||||||
|  |                         %asm {{ | ||||||
|  |                             phx | ||||||
|  |                             ldx  #1 | ||||||
|  |                             lda  cx16.VERA_DATA1 | ||||||
|  |                             sta  P8ZP_SCRATCH_B1 | ||||||
|  |                             ldy  #8 | ||||||
|  | -                           asl  P8ZP_SCRATCH_B1 | ||||||
|  |                             bcc  + | ||||||
|  |                             stx  cx16.VERA_DATA0    ; write a pixel | ||||||
|  |                             bra  ++ | ||||||
|  | +                           lda  cx16.VERA_DATA0    ; don't write a pixel, but do advance to the next address | ||||||
|  | +                           dey | ||||||
|  |                             bne  - | ||||||
|  |                             plx | ||||||
|  |                         }} | ||||||
|  |                     } | ||||||
|  |                     x+=8 | ||||||
|  |                     y-=8 | ||||||
|  |                     sctextptr++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             6 -> { | ||||||
|  |                 ; hires 4c | ||||||
|  |                 while @(sctextptr) { | ||||||
|  |                     chardataptr = charset_addr + (@(sctextptr) as uword)*8 | ||||||
|  |                     repeat 8 { | ||||||
|  |                         ; TODO rewrite this inner loop fully in assembly | ||||||
|  |                         ubyte charbits = cx16.vpeek(charset_bank, chardataptr) | ||||||
|  |                         repeat 8 { | ||||||
|  |                             charbits <<= 1 | ||||||
|  |                             if_cs | ||||||
|  |                                 plot(x, y, color) | ||||||
|  |                             x++ | ||||||
|  |                         } | ||||||
|  |                         x-=8 | ||||||
|  |                         chardataptr++ | ||||||
|  |                         y++ | ||||||
|  |                     } | ||||||
|  |                     x+=8 | ||||||
|  |                     y-=8 | ||||||
|  |                     sctextptr++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub cs_innerloop640() clobbers(Y) { | ||||||
|  |         %asm {{ | ||||||
|  |             ldy  #80 | ||||||
|  | -           stz  cx16.VERA_DATA0 | ||||||
|  |             stz  cx16.VERA_DATA0 | ||||||
|  |             stz  cx16.VERA_DATA0 | ||||||
|  |             stz  cx16.VERA_DATA0 | ||||||
|  |             stz  cx16.VERA_DATA0 | ||||||
|  |             stz  cx16.VERA_DATA0 | ||||||
|  |             stz  cx16.VERA_DATA0 | ||||||
|  |             stz  cx16.VERA_DATA0 | ||||||
|  |             dey | ||||||
|  |             bne  - | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub addr_mul_24_for_highres_4c(uword yy @R2, uword xx @R3)  clobbers(A, Y) -> uword @R0, uword @R1 { | ||||||
|  |         ; yy * 160 + xx/4  (24 bits calculation) | ||||||
|  |         ; 24 bits result is in r0 and r1L (highest byte) | ||||||
|  |         %asm {{ | ||||||
|  |             ldy  #5 | ||||||
|  | -           asl  cx16.r2 | ||||||
|  |             rol  cx16.r2+1 | ||||||
|  |             dey | ||||||
|  |             bne  - | ||||||
|  |             lda  cx16.r2 | ||||||
|  |             sta  cx16.r0 | ||||||
|  |             lda  cx16.r2+1 | ||||||
|  |             sta  cx16.r0+1 | ||||||
|  |             asl  cx16.r0 | ||||||
|  |             rol  cx16.r0+1 | ||||||
|  |             asl  cx16.r0 | ||||||
|  |             rol  cx16.r0+1 | ||||||
|  |  | ||||||
|  |             ; xx >>= 2  (xx=R3) | ||||||
|  |             lsr  cx16.r3+1 | ||||||
|  |             ror  cx16.r3 | ||||||
|  |             lsr  cx16.r3+1 | ||||||
|  |             ror  cx16.r3 | ||||||
|  |  | ||||||
|  |             ; add r2 and xx (r3) to r0 (24-bits) | ||||||
|  |             stz  cx16.r1 | ||||||
|  |             clc | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             adc  cx16.r2 | ||||||
|  |             sta  cx16.r0 | ||||||
|  |             lda  cx16.r0+1 | ||||||
|  |             adc  cx16.r2+1 | ||||||
|  |             sta  cx16.r0+1 | ||||||
|  |             bcc  + | ||||||
|  |             inc  cx16.r1 | ||||||
|  | +           clc | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             adc  cx16.r3 | ||||||
|  |             sta  cx16.r0 | ||||||
|  |             lda  cx16.r0+1 | ||||||
|  |             adc  cx16.r3+1 | ||||||
|  |             sta  cx16.r0+1 | ||||||
|  |             bcc  + | ||||||
|  |             inc  cx16.r1 | ||||||
|  | + | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1  { | ||||||
|  |         ; yy * 320 + xx (24 bits calculation) | ||||||
|  |         %asm {{ | ||||||
|  |             sta  P8ZP_SCRATCH_W1 | ||||||
|  |             sty  P8ZP_SCRATCH_W1+1 | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             sta  P8ZP_SCRATCH_B1 | ||||||
|  |             lda  cx16.r0+1 | ||||||
|  |             sta  cx16.r1 | ||||||
|  |             sta  P8ZP_SCRATCH_REG | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             asl  a | ||||||
|  |             rol  P8ZP_SCRATCH_REG | ||||||
|  |             asl  a | ||||||
|  |             rol  P8ZP_SCRATCH_REG | ||||||
|  |             asl  a | ||||||
|  |             rol  P8ZP_SCRATCH_REG | ||||||
|  |             asl  a | ||||||
|  |             rol  P8ZP_SCRATCH_REG | ||||||
|  |             asl  a | ||||||
|  |             rol  P8ZP_SCRATCH_REG | ||||||
|  |             asl  a | ||||||
|  |             rol  P8ZP_SCRATCH_REG | ||||||
|  |             sta  cx16.r0 | ||||||
|  |             lda  P8ZP_SCRATCH_B1 | ||||||
|  |             clc | ||||||
|  |             adc  P8ZP_SCRATCH_REG | ||||||
|  |             sta  cx16.r0+1 | ||||||
|  |             bcc  + | ||||||
|  |             inc  cx16.r1 | ||||||
|  | +           ; now add the value to this 24-bits number | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             clc | ||||||
|  |             adc  P8ZP_SCRATCH_W1 | ||||||
|  |             sta  cx16.r0 | ||||||
|  |             lda  cx16.r0+1 | ||||||
|  |             adc  P8ZP_SCRATCH_W1+1 | ||||||
|  |             sta  cx16.r0+1 | ||||||
|  |             bcc  + | ||||||
|  |             inc  cx16.r1 | ||||||
|  | +           lda  cx16.r1 | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,9 +1,13 @@ | |||||||
| %target cx16 | %target cx16 | ||||||
| %import syslib | %import syslib | ||||||
|  | %import textio | ||||||
|  |  | ||||||
| ; bitmap pixel graphics module for the CommanderX16 | ; Bitmap pixel graphics module for the CommanderX16 | ||||||
| ; wraps the graphics functions that are in ROM. | ; wraps the graphics functions that are in ROM. | ||||||
| ; only black/white monchrome 320x200 for now. | ; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom) | ||||||
|  | ; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific) | ||||||
|  | ; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module. | ||||||
|  |  | ||||||
|  |  | ||||||
| graphics { | graphics { | ||||||
|     const uword WIDTH = 320 |     const uword WIDTH = 320 | ||||||
| @@ -12,22 +16,42 @@ graphics { | |||||||
|     sub enable_bitmap_mode() { |     sub enable_bitmap_mode() { | ||||||
|         ; enable bitmap screen, erase it and set colors to black/white. |         ; enable bitmap screen, erase it and set colors to black/white. | ||||||
|         void cx16.screen_set_mode($80) |         void cx16.screen_set_mode($80) | ||||||
|         cx16.r0 = 0 |         cx16.GRAPH_init(0) | ||||||
|         cx16.GRAPH_init() |  | ||||||
|         clear_screen(1, 0) |         clear_screen(1, 0) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     sub disable_bitmap_mode() { | ||||||
|  |         ; enables text mode, erase the text screen, color white | ||||||
|  |         void cx16.screen_set_mode(2) | ||||||
|  |         txt.fill_screen(' ', 1)     ; doesn't seem to fully clear the text screen after returning from gfx mode | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     sub clear_screen(ubyte pixelcolor, ubyte bgcolor) { |     sub clear_screen(ubyte pixelcolor, ubyte bgcolor) { | ||||||
|         cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor) |         cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor) | ||||||
|         cx16.GRAPH_clear() |         cx16.GRAPH_clear() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { |     sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { | ||||||
|         cx16.r0 = x1 |         cx16.GRAPH_draw_line(x1, y1, x2, y2) | ||||||
|         cx16.r1 = y1 |     } | ||||||
|         cx16.r2 = x2 |  | ||||||
|         cx16.r3 = y2 |     sub fillrect(uword x, uword y, uword width, uword height) { | ||||||
|         cx16.GRAPH_draw_line() |         cx16.GRAPH_draw_rect(x, y, width, height, 0, 1) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub rect(uword x, uword y, uword width, uword height) { | ||||||
|  |         cx16.GRAPH_draw_rect(x, y, width, height, 0, 0) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub horizontal_line(uword x, uword y, uword length) { | ||||||
|  |         if length | ||||||
|  |             cx16.GRAPH_draw_line(x, y, x+length-1, y) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub vertical_line(uword x, uword y, uword height) { | ||||||
|  |         if height | ||||||
|  |             cx16.GRAPH_draw_line(x, y, x, y+height-1) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     sub circle(uword xcenter, ubyte ycenter, ubyte radius) { |     sub circle(uword xcenter, ubyte ycenter, ubyte radius) { | ||||||
| @@ -35,122 +59,80 @@ graphics { | |||||||
|         ;cx16.r1 = ycenter - radius/2 |         ;cx16.r1 = ycenter - radius/2 | ||||||
|         ;cx16.r2 = radius*2 |         ;cx16.r2 = radius*2 | ||||||
|         ;cx16.r3 = radius*2 |         ;cx16.r3 = radius*2 | ||||||
|         ;cx16.GRAPH_draw_oval(false)          ; TODO  currently is not implemented on cx16, does a BRK |         ;cx16.GRAPH_draw_oval(false)          ; currently this call is not implemented on cx16, does a BRK | ||||||
|  |  | ||||||
|         ; Midpoint algorithm |         ; Midpoint algorithm | ||||||
|  |         if radius==0 | ||||||
|  |             return | ||||||
|         ubyte @zp xx = radius |         ubyte @zp xx = radius | ||||||
|         ubyte @zp yy = 0 |         ubyte @zp yy = 0 | ||||||
|         byte @zp decisionOver2 = 1-xx as byte |         word @zp decisionOver2 = (1 as word)-xx | ||||||
|  |  | ||||||
|         while xx>=yy { |         while xx>=yy { | ||||||
|             cx16.r0 = xcenter + xx |             cx16.r0 = xcenter + xx | ||||||
|             cx16.r1 = ycenter + yy |             cx16.r1 = ycenter + yy | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             cx16.r0 = xcenter - xx |             cx16.r0 = xcenter - xx | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             cx16.r0 = xcenter + xx |             cx16.r0 = xcenter + xx | ||||||
|             cx16.r1 = ycenter - yy |             cx16.r1 = ycenter - yy | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             cx16.r0 = xcenter - xx |             cx16.r0 = xcenter - xx | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             cx16.r0 = xcenter + yy |             cx16.r0 = xcenter + yy | ||||||
|             cx16.r1 = ycenter + xx |             cx16.r1 = ycenter + xx | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             cx16.r0 = xcenter - yy |             cx16.r0 = xcenter - yy | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             cx16.r0 = xcenter + yy |             cx16.r0 = xcenter + yy | ||||||
|             cx16.r1 = ycenter - xx |             cx16.r1 = ycenter - xx | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             cx16.r0 = xcenter - yy |             cx16.r0 = xcenter - yy | ||||||
|             cx16.FB_cursor_position() |             cx16.FB_cursor_position2() | ||||||
|             cx16.FB_set_pixel(1) |             cx16.FB_set_pixel(1) | ||||||
|             yy++ |             yy++ | ||||||
|             if decisionOver2<=0 { |             if decisionOver2<=0 { | ||||||
|                 decisionOver2 += 2*yy+1 |                 decisionOver2 += (yy as word)*2+1 | ||||||
|             } else { |             } else { | ||||||
|                 xx-- |                 xx-- | ||||||
|                 decisionOver2 += 2*(yy-xx)+1 |                 decisionOver2 += (yy as word -xx)*2+1 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     sub disc(uword xcenter, ubyte ycenter, ubyte radius) { |     sub disc(uword xcenter, ubyte ycenter, ubyte radius) { | ||||||
| ;        cx16.r0 = xcenter - radius/2 |         if radius==0 | ||||||
| ;        cx16.r1 = ycenter - radius/2 |             return | ||||||
| ;        cx16.r2 = radius*2 |         ubyte @zp yy = 0 | ||||||
| ;        cx16.r3 = radius*2 |         word decisionOver2 = (1 as word)-radius | ||||||
| ;        cx16.GRAPH_draw_oval(true)          ; TODO  currently is not implemented on cx16, does a BRK |  | ||||||
|  |  | ||||||
|         ubyte xx = radius |         while radius>=yy { | ||||||
|         ubyte yy = 0 |             horizontal_line(xcenter-radius, ycenter+yy, radius*2+1) | ||||||
|         byte decisionOver2 = 1-xx as byte |             horizontal_line(xcenter-radius, ycenter-yy, radius*2+1) | ||||||
|  |             horizontal_line(xcenter-yy, ycenter+radius, yy*2+1) | ||||||
|         while xx>=yy { |             horizontal_line(xcenter-yy, ycenter-radius, yy*2+1) | ||||||
|             ubyte ycenter_plus_yy = ycenter + yy |  | ||||||
|             ubyte ycenter_min_yy = ycenter - yy |  | ||||||
|             ubyte ycenter_plus_xx = ycenter + xx |  | ||||||
|             ubyte ycenter_min_xx = ycenter - xx |  | ||||||
|             uword @zp plotx |  | ||||||
|  |  | ||||||
|             for plotx in xcenter to xcenter+xx { |  | ||||||
|                 cx16.r0 = plotx |  | ||||||
|                 cx16.r1 = ycenter_plus_yy |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|                 cx16.r1 = ycenter_min_yy |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|             } |  | ||||||
|             for plotx in xcenter-xx to xcenter-1 { |  | ||||||
|                 cx16.r0 = plotx |  | ||||||
|                 cx16.r1 = ycenter_plus_yy |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|                 cx16.r1 = ycenter_min_yy |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|             } |  | ||||||
|             for plotx in xcenter to xcenter+yy { |  | ||||||
|                 cx16.r0 = plotx |  | ||||||
|                 cx16.r1 = ycenter_plus_xx |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|                 cx16.r1 = ycenter_min_xx |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|             } |  | ||||||
|             for plotx in xcenter-yy to xcenter { |  | ||||||
|                 cx16.r0 = plotx |  | ||||||
|                 cx16.r1 = ycenter_plus_xx |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|                 cx16.r1 = ycenter_min_xx |  | ||||||
|                 cx16.FB_cursor_position() |  | ||||||
|                 cx16.FB_set_pixel(1) |  | ||||||
|             } |  | ||||||
|             yy++ |             yy++ | ||||||
|             if decisionOver2<=0 |             if decisionOver2<=0 | ||||||
|                 decisionOver2 += 2*yy+1 |                 decisionOver2 += (yy as word)*2+1 | ||||||
|             else { |             else { | ||||||
|                 xx-- |                 radius-- | ||||||
|                 decisionOver2 += 2*(yy-xx)+1 |                 decisionOver2 += (yy as word -radius)*2+1 | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     sub  plot(uword plotx, ubyte ploty) { |     inline asmsub  plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) { | ||||||
|         cx16.r0 = plotx |         %asm {{ | ||||||
|         cx16.r1 = ploty |             jsr  cx16.FB_cursor_position | ||||||
|         cx16.FB_cursor_position() |             lda  #1 | ||||||
|         cx16.FB_set_pixel(1) |             jsr  cx16.FB_set_pixel | ||||||
|  |         }} | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										191
									
								
								compiler/res/prog8lib/cx16/palette.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								compiler/res/prog8lib/cx16/palette.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | |||||||
|  | %target cx16 | ||||||
|  |  | ||||||
|  | ; Manipulate the Commander X16's display color palette. | ||||||
|  | ; Should you want to restore the default palette, you have to reinitialize the Vera yourself. | ||||||
|  |  | ||||||
|  | palette { | ||||||
|  |  | ||||||
|  |     uword vera_palette_ptr | ||||||
|  |     ubyte c | ||||||
|  |  | ||||||
|  |     sub set_color(ubyte index, uword color) { | ||||||
|  |         vera_palette_ptr = $fa00+index*2 | ||||||
|  |         cx16.vpoke(1, vera_palette_ptr, lsb(color)) | ||||||
|  |         vera_palette_ptr++ | ||||||
|  |         cx16.vpoke(1, vera_palette_ptr, msb(color)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_rgb4(uword palette_bytes_ptr, uword num_colors) { | ||||||
|  |         ; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         repeat num_colors { | ||||||
|  |             cx16.vpoke(1, vera_palette_ptr+1, @(palette_bytes_ptr)) | ||||||
|  |             palette_bytes_ptr++ | ||||||
|  |             cx16.vpoke(1, vera_palette_ptr, @(palette_bytes_ptr)) | ||||||
|  |             palette_bytes_ptr++ | ||||||
|  |             vera_palette_ptr+=2 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_rgb(uword palette_words_ptr, uword num_colors) { | ||||||
|  |         ; 1 word per color entry (in little endian format so $gb0r) | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         repeat num_colors*2 { | ||||||
|  |             cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr)) | ||||||
|  |             palette_words_ptr++ | ||||||
|  |             vera_palette_ptr++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_rgb8(uword palette_bytes_ptr, uword num_colors) { | ||||||
|  |         ; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel. | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         ubyte red | ||||||
|  |         ubyte greenblue | ||||||
|  |         repeat num_colors { | ||||||
|  |             red = @(palette_bytes_ptr) >> 4 | ||||||
|  |             palette_bytes_ptr++ | ||||||
|  |             greenblue = @(palette_bytes_ptr) & %11110000 | ||||||
|  |             palette_bytes_ptr++ | ||||||
|  |             greenblue |= @(palette_bytes_ptr) >> 4    ; add Blue | ||||||
|  |             palette_bytes_ptr++ | ||||||
|  |             cx16.vpoke(1, vera_palette_ptr, greenblue) | ||||||
|  |             vera_palette_ptr++ | ||||||
|  |             cx16.vpoke(1, vera_palette_ptr, red) | ||||||
|  |             vera_palette_ptr++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) { | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB))   ; G,B | ||||||
|  |         vera_palette_ptr++ | ||||||
|  |         cx16.vpoke(1, vera_palette_ptr, msb(screencolorRGB))   ; R | ||||||
|  |         vera_palette_ptr++ | ||||||
|  |         repeat 255 { | ||||||
|  |             cx16.vpoke(1, vera_palette_ptr, lsb(drawcolorRGB)) ; G,B | ||||||
|  |             vera_palette_ptr++ | ||||||
|  |             cx16.vpoke(1, vera_palette_ptr, msb(drawcolorRGB)) ; R | ||||||
|  |             vera_palette_ptr++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline sub set_all_black() { | ||||||
|  |         set_monochrome($000, $000) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline sub set_all_white() { | ||||||
|  |         set_monochrome($fff, $fff) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_grayscale() { | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         repeat 16 { | ||||||
|  |             c=0 | ||||||
|  |             repeat 16 { | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, c) | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, c) | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |                 c += $11 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     uword[] C64_colorpalette_dark = [   ; this is a darker palette with more contrast | ||||||
|  |         $000,  ; 0 = black | ||||||
|  |         $FFF,  ; 1 = white | ||||||
|  |         $632,  ; 2 = red | ||||||
|  |         $7AB,  ; 3 = cyan | ||||||
|  |         $638,  ; 4 = purple | ||||||
|  |         $584,  ; 5 = green | ||||||
|  |         $327,  ; 6 = blue | ||||||
|  |         $BC6,  ; 7 = yellow | ||||||
|  |         $642,  ; 8 = orange | ||||||
|  |         $430,  ; 9 = brown | ||||||
|  |         $965,  ; 10 = light red | ||||||
|  |         $444,  ; 11 = dark grey | ||||||
|  |         $666,  ; 12 = medium grey | ||||||
|  |         $9D8,  ; 13 = light green | ||||||
|  |         $65B,  ; 14 = light blue | ||||||
|  |         $999   ; 15 = light grey | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     uword[] C64_colorpalette_pepto = [  ; # this is Pepto's Commodore-64 palette  http://www.pepto.de/projects/colorvic/ | ||||||
|  |         $000,  ; 0 = black | ||||||
|  |         $FFF,  ; 1 = white | ||||||
|  |         $833,  ; 2 = red | ||||||
|  |         $7cc,  ; 3 = cyan | ||||||
|  |         $839,  ; 4 = purple | ||||||
|  |         $5a4,  ; 5 = green | ||||||
|  |         $229,  ; 6 = blue | ||||||
|  |         $ef7,  ; 7 = yellow | ||||||
|  |         $852,  ; 8 = orange | ||||||
|  |         $530,  ; 9 = brown | ||||||
|  |         $c67,  ; 10 = light red | ||||||
|  |         $444,  ; 11 = dark grey | ||||||
|  |         $777,  ; 12 = medium grey | ||||||
|  |         $af9,  ; 13 = light green | ||||||
|  |         $76e,  ; 14 = light blue | ||||||
|  |         $bbb   ; 15 = light grey | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     uword[] C64_colorpalette_light = [  ; this is a lighter palette | ||||||
|  |         $000,  ; 0 = black | ||||||
|  |         $FFF,  ; 1 = white | ||||||
|  |         $944,  ; 2 = red | ||||||
|  |         $7CC,  ; 3 = cyan | ||||||
|  |         $95A,  ; 4 = purple | ||||||
|  |         $6A5,  ; 5 = green | ||||||
|  |         $549,  ; 6 = blue | ||||||
|  |         $CD8,  ; 7 = yellow | ||||||
|  |         $963,  ; 8 = orange | ||||||
|  |         $650,  ; 9 = brown | ||||||
|  |         $C77,  ; 10 = light red | ||||||
|  |         $666,  ; 11 = dark grey | ||||||
|  |         $888,  ; 12 = medium grey | ||||||
|  |         $AE9,  ; 13 = light green | ||||||
|  |         $87C,  ; 14 = light blue | ||||||
|  |         $AAA   ; 15 = light grey | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     sub set_c64pepto() { | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         repeat 16 { | ||||||
|  |             for c in 0 to 15 { | ||||||
|  |                 uword cc = C64_colorpalette_pepto[c] | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, lsb(cc))     ; G, B | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, msb(cc))     ; R | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_c64light() { | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         repeat 16 { | ||||||
|  |             for c in 0 to 15 { | ||||||
|  |                 uword cc = C64_colorpalette_light[c] | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, lsb(cc))     ; G, B | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, msb(cc))     ; R | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub set_c64dark() { | ||||||
|  |         vera_palette_ptr = $fa00 | ||||||
|  |         repeat 16 { | ||||||
|  |             for c in 0 to 15 { | ||||||
|  |                 uword cc = C64_colorpalette_dark[c] | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, lsb(cc))     ; G, B | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |                 cx16.vpoke(1, vera_palette_ptr, msb(cc))     ; R | ||||||
|  |                 vera_palette_ptr++ | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -12,9 +12,9 @@ c64 { | |||||||
|  |  | ||||||
| ; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ---- | ; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ---- | ||||||
|  |  | ||||||
| ; STROUT --> use screen.print | ; STROUT --> use txt.print | ||||||
| ; CLEARSCR -> use screen.clear_screen | ; CLEARSCR -> use txt.clear_screen | ||||||
| ; HOMECRSR -> use screen.plot | ; HOMECRSR -> use txt.plot | ||||||
|  |  | ||||||
| romsub $FF81 = CINT() clobbers(A,X,Y)                           ; (alias: SCINIT) initialize screen editor and video chip | romsub $FF81 = CINT() clobbers(A,X,Y)                           ; (alias: SCINIT) initialize screen editor and video chip | ||||||
| romsub $FF84 = IOINIT() clobbers(A, X)                          ; initialize I/O devices (CIA, SID, IRQ) | romsub $FF84 = IOINIT() clobbers(A, X)                          ; initialize I/O devices (CIA, SID, IRQ) | ||||||
| @@ -24,7 +24,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc)  clobbers(A,Y)     ; r | |||||||
| romsub $FF90 = SETMSG(ubyte value @ A)                          ; set Kernal message control flag | romsub $FF90 = SETMSG(ubyte value @ A)                          ; set Kernal message control flag | ||||||
| romsub $FF93 = SECOND(ubyte address @ A) clobbers(A)            ; (alias: LSTNSA) send secondary address after LISTEN | romsub $FF93 = SECOND(ubyte address @ A) clobbers(A)            ; (alias: LSTNSA) send secondary address after LISTEN | ||||||
| romsub $FF96 = TKSA(ubyte address @ A) clobbers(A)              ; (alias: TALKSA) send secondary address after TALK | romsub $FF96 = TKSA(ubyte address @ A) clobbers(A)              ; (alias: TALKSA) send secondary address after TALK | ||||||
| romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> ubyte @A, uword @ XY     ; read/set top of memory  pointer, returns number of banks in A | romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY     ; read/set top of memory  pointer.   NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A !  See cx16.numbanks() | ||||||
| romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY     ; read/set bottom of memory  pointer | romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY     ; read/set bottom of memory  pointer | ||||||
| romsub $FF9F = SCNKEY() clobbers(A,X,Y)                         ; scan the keyboard | romsub $FF9F = SCNKEY() clobbers(A,X,Y)                         ; scan the keyboard | ||||||
| romsub $FFA2 = SETTMO(ubyte timeout @ A)                        ; set time-out flag for IEEE bus | romsub $FFA2 = SETTMO(ubyte timeout @ A)                        ; set time-out flag for IEEE bus | ||||||
| @@ -35,35 +35,67 @@ romsub $FFAE = UNLSN()  clobbers(A)                              ; command seria | |||||||
| romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A)             ; command serial bus device to LISTEN | romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A)             ; command serial bus device to LISTEN | ||||||
| romsub $FFB4 = TALK(ubyte device @ A) clobbers(A)               ; command serial bus device to TALK | romsub $FFB4 = TALK(ubyte device @ A) clobbers(A)               ; command serial bus device to TALK | ||||||
| romsub $FFB7 = READST() -> ubyte @ A                            ; read I/O status word | romsub $FFB7 = READST() -> ubyte @ A                            ; read I/O status word | ||||||
| romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y)   ; set logical file parameters | romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y)   ; set logical file parameters | ||||||
| romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY)     ; set filename parameters | romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY)     ; set filename parameters | ||||||
| romsub $FFC0 = OPEN()  clobbers(A,X,Y)                           ; (via 794 ($31A)) open a logical file | romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A      ; (via 794 ($31A)) open a logical file | ||||||
| romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y)         ; (via 796 ($31C)) close a logical file | romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y)         ; (via 796 ($31C)) close a logical file | ||||||
| romsub $FFC6 = CHKIN(ubyte logical @ X)  clobbers(A,X)           ; (via 798 ($31E)) define an input channel | romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc    ; (via 798 ($31E)) define an input channel | ||||||
| romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X)          ; (via 800 ($320)) define an output channel | romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X)          ; (via 800 ($320)) define an output channel | ||||||
| romsub $FFCC = CLRCHN() clobbers(A,X)                           ; (via 802 ($322)) restore default devices | romsub $FFCC = CLRCHN() clobbers(A,X)                           ; (via 802 ($322)) restore default devices | ||||||
| romsub $FFCF = CHRIN()  clobbers(Y) -> ubyte @ A                 ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read. | romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A   ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read. | ||||||
| romsub $FFD2 = CHROUT(ubyte char @ A)                           ; (via 806 ($326)) output a character | romsub $FFD2 = CHROUT(ubyte char @ A)                           ; (via 806 ($326)) output a character | ||||||
| romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y     ; (via 816 ($330)) load from device | romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY     ; (via 816 ($330)) load from device | ||||||
| romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A          ; (via 818 ($332)) save to a device | romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A          ; (via 818 ($332)) save to a device | ||||||
| romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y)      ; set the software clock | romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y)      ; set the software clock | ||||||
| romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y       ; read the software clock | romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y       ; read the software clock (A=lo,X=mid,Y=high) | ||||||
| romsub $FFE1 = STOP()  clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc   ; (via 808 ($328)) check the STOP key | romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A      ; (via 808 ($328)) check the STOP key (and some others in A) | ||||||
| romsub $FFE4 = GETIN()  clobbers(X,Y) -> ubyte @ A               ; (via 810 ($32A)) get a character | romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A    ; (via 810 ($32A)) get a character | ||||||
| romsub $FFE7 = CLALL() clobbers(A,X)                            ; (via 812 ($32C)) close all files | romsub $FFE7 = CLALL() clobbers(A,X)                            ; (via 812 ($32C)) close all files | ||||||
| romsub $FFEA = UDTIM() clobbers(A,X)                            ; update the software clock | romsub $FFEA = UDTIM() clobbers(A,X)                            ; update the software clock | ||||||
| romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y                 ; read number of screen rows and columns | romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y                 ; read number of screen rows and columns | ||||||
| romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y       ; read/set position of cursor on screen.  Use screen.plot for a 'safe' wrapper that preserves X. | romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y       ; read/set position of cursor on screen.  Use txt.plot for a 'safe' wrapper that preserves X. | ||||||
| romsub $FFF3 = IOBASE() -> uword @ XY                           ; read base address of I/O devices | romsub $FFF3 = IOBASE() -> uword @ XY                           ; read base address of I/O devices | ||||||
|  |  | ||||||
|  | ; ---- utility | ||||||
|  |  | ||||||
|  | asmsub STOP2() -> ubyte @A  { | ||||||
|  |     ; -- check if STOP key was pressed, returns true if so.  More convenient to use than STOP() because that only sets the carry status flag. | ||||||
|  |     %asm {{ | ||||||
|  |         phx | ||||||
|  |         jsr  c64.STOP | ||||||
|  |         beq  + | ||||||
|  |         plx | ||||||
|  |         lda  #0 | ||||||
|  |         rts | ||||||
|  | +       plx | ||||||
|  |         lda  #1 | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub RDTIM16() -> uword @AY { | ||||||
|  |     ; --  like RDTIM() but only returning the lower 16 bits in AY for convenience | ||||||
|  |     %asm {{ | ||||||
|  |         phx | ||||||
|  |         jsr  c64.RDTIM | ||||||
|  |         pha | ||||||
|  |         txa | ||||||
|  |         tay | ||||||
|  |         pla | ||||||
|  |         plx | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| cx16 { | cx16 { | ||||||
|  |  | ||||||
| ; 65c02 hardware vectors: | ; irq and hardware vectors: | ||||||
|     &uword  NMI_VEC         = $FFFA     ; 6502 nmi vector, determined by the kernal if banked in |     &uword  CINV            = $0314     ; IRQ vector (in ram) | ||||||
|     &uword  RESET_VEC       = $FFFC     ; 6502 reset vector, determined by the kernal if banked in |     &uword  NMI_VEC         = $FFFA     ; 65c02 nmi vector, determined by the kernal if banked in | ||||||
|     &uword  IRQ_VEC         = $FFFE     ; 6502 interrupt vector, determined by the kernal if banked in |     &uword  RESET_VEC       = $FFFC     ; 65c02 reset vector, determined by the kernal if banked in | ||||||
|  |     &uword  IRQ_VEC         = $FFFE     ; 65c02 interrupt vector, determined by the kernal if banked in | ||||||
|  |  | ||||||
|  |  | ||||||
| ; the sixteen virtual 16-bit registers | ; the sixteen virtual 16-bit registers | ||||||
| @@ -84,6 +116,41 @@ cx16 { | |||||||
|     &uword r14 = $001e |     &uword r14 = $001e | ||||||
|     &uword r15 = $0020 |     &uword r15 = $0020 | ||||||
|  |  | ||||||
|  |     &ubyte r0L  = $0002 | ||||||
|  |     &ubyte r1L  = $0004 | ||||||
|  |     &ubyte r2L  = $0006 | ||||||
|  |     &ubyte r3L  = $0008 | ||||||
|  |     &ubyte r4L  = $000a | ||||||
|  |     &ubyte r5L  = $000c | ||||||
|  |     &ubyte r6L  = $000e | ||||||
|  |     &ubyte r7L  = $0010 | ||||||
|  |     &ubyte r8L  = $0012 | ||||||
|  |     &ubyte r9L  = $0014 | ||||||
|  |     &ubyte r10L = $0016 | ||||||
|  |     &ubyte r11L = $0018 | ||||||
|  |     &ubyte r12L = $001a | ||||||
|  |     &ubyte r13L = $001c | ||||||
|  |     &ubyte r14L = $001e | ||||||
|  |     &ubyte r15L = $0020 | ||||||
|  |  | ||||||
|  |     &ubyte r0H  = $0003 | ||||||
|  |     &ubyte r1H  = $0005 | ||||||
|  |     &ubyte r2H  = $0007 | ||||||
|  |     &ubyte r3H  = $0009 | ||||||
|  |     &ubyte r4H  = $000b | ||||||
|  |     &ubyte r5H  = $000d | ||||||
|  |     &ubyte r6H  = $000f | ||||||
|  |     &ubyte r7H  = $0011 | ||||||
|  |     &ubyte r8H  = $0013 | ||||||
|  |     &ubyte r9H  = $0015 | ||||||
|  |     &ubyte r10H = $0017 | ||||||
|  |     &ubyte r11H = $0019 | ||||||
|  |     &ubyte r12H = $001b | ||||||
|  |     &ubyte r13H = $001d | ||||||
|  |     &ubyte r14H = $001f | ||||||
|  |     &ubyte r15H = $0021 | ||||||
|  |  | ||||||
|  |  | ||||||
| ; VERA registers | ; VERA registers | ||||||
|  |  | ||||||
|     const uword VERA_BASE       = $9F20 |     const uword VERA_BASE       = $9F20 | ||||||
| @@ -129,7 +196,7 @@ cx16 { | |||||||
|  |  | ||||||
| ; I/O | ; I/O | ||||||
|  |  | ||||||
|     const uword  via1  = $9f60                  ;VIA 6522 #1 |     const uword  via1   = $9f00                  ;VIA 6522 #1 | ||||||
|     &ubyte  d1prb	= via1+0 |     &ubyte  d1prb	= via1+0 | ||||||
|     &ubyte  d1pra	= via1+1 |     &ubyte  d1pra	= via1+1 | ||||||
|     &ubyte  d1ddrb	= via1+2 |     &ubyte  d1ddrb	= via1+2 | ||||||
| @@ -147,7 +214,7 @@ cx16 { | |||||||
|     &ubyte  d1ier	= via1+14 |     &ubyte  d1ier	= via1+14 | ||||||
|     &ubyte  d1ora	= via1+15 |     &ubyte  d1ora	= via1+15 | ||||||
|  |  | ||||||
|     const uword  via2  = $9f70                  ;VIA 6522 #2 |     const uword  via2   = $9f10                  ;VIA 6522 #2 | ||||||
|     &ubyte  d2prb	= via2+0 |     &ubyte  d2prb	= via2+0 | ||||||
|     &ubyte  d2pra	= via2+1 |     &ubyte  d2pra	= via2+1 | ||||||
|     &ubyte  d2ddrb	= via2+2 |     &ubyte  d2ddrb	= via2+2 | ||||||
| @@ -165,6 +232,11 @@ cx16 { | |||||||
|     &ubyte  d2ier	= via2+14 |     &ubyte  d2ier	= via2+14 | ||||||
|     &ubyte  d2ora	= via2+15 |     &ubyte  d2ora	= via2+15 | ||||||
|  |  | ||||||
|  |     &ubyte  ym2151adr	= $9f40 | ||||||
|  |     &ubyte  ym2151dat	= $9f41 | ||||||
|  |  | ||||||
|  |     const uword  extdev	= $9f60 | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ---- Commander X-16 additions on top of C64 kernal routines ---- | ; ---- Commander X-16 additions on top of C64 kernal routines ---- | ||||||
| ; spelling of the names is taken from the Commander X-16 rom sources | ; spelling of the names is taken from the Commander X-16 rom sources | ||||||
| @@ -190,72 +262,296 @@ romsub $ff6b = mouse_get(ubyte zpdataptr @X)  clobbers(A) | |||||||
| romsub $ff71 = mouse_scan()  clobbers(A, X, Y) | romsub $ff71 = mouse_scan()  clobbers(A, X, Y) | ||||||
| romsub $ff53 = joystick_scan()  clobbers(A, X, Y) | romsub $ff53 = joystick_scan()  clobbers(A, X, Y) | ||||||
| romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y | romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y | ||||||
| romsub $ff4d = clock_set_date_time()  clobbers(A, X, Y)      ; args: r0, r1, r2, r3L | romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3)  clobbers(A, X, Y) | ||||||
| romsub $ff50 = clock_get_date_time()  clobbers(A, X, Y)      ; outout args: r0, r1, r2, r3L | romsub $ff50 = clock_get_date_time()  clobbers(A, X, Y)  -> uword @R0, uword @R1, uword @R2, ubyte @R3   ; result registers see clock_set_date_time() | ||||||
|  |  | ||||||
| ; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered | ; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered | ||||||
|  |  | ||||||
| ; high level graphics & fonts | ; high level graphics & fonts | ||||||
| romsub $ff20 = GRAPH_init()  clobbers(A,X,Y)           ; uses vectors=r0 | romsub $ff20 = GRAPH_init(uword vectors @R0)  clobbers(A,X,Y) | ||||||
| romsub $ff23 = GRAPH_clear()  clobbers(A,X,Y) | romsub $ff23 = GRAPH_clear()  clobbers(A,X,Y) | ||||||
| romsub $ff26 = GRAPH_set_window()  clobbers(A,X,Y)      ; uses x=r0, y=r1, width=r2, height=r3 | romsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3)  clobbers(A,X,Y) | ||||||
| romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y)  clobbers (A,X,Y) | romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y)  clobbers (A,X,Y) | ||||||
| romsub $ff2c = GRAPH_draw_line()  clobbers(A,X,Y)       ; uses x1=r0, y1=r1, x2=r2, y2=r3 | romsub $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3)  clobbers(A,X,Y) | ||||||
| romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc)  clobbers(A,X,Y)   ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4 | romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, ubyte fill @Pc)  clobbers(A,X,Y) | ||||||
| romsub $ff32 = GRAPH_move_rect()  clobbers(A,X,Y)       ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5 | romsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5)  clobbers(A,X,Y) | ||||||
| romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc)  clobbers(A,X,Y)       ; uses x=r0, y=r1, width=r2, height=r3 | romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc)  clobbers(A,X,Y) | ||||||
| romsub $ff38 = GRAPH_draw_image()  clobbers(A,X,Y)      ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4 | romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4)  clobbers(A,X,Y) | ||||||
| romsub $ff3b = GRAPH_set_font()  clobbers(A,X,Y)         ; uses ptr=r0 | romsub $ff3b = GRAPH_set_font(uword fontptr @R0)  clobbers(A,X,Y) | ||||||
| romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc)  clobbers(A,X,Y) | romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc)  clobbers(A,X,Y) | ||||||
| romsub $ff41 = GRAPH_put_char(ubyte char @A)  clobbers(A,X,Y)   ; uses x=r0, y=r1 | romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte char @A)  clobbers(A,X,Y) | ||||||
|  | romsub $ff41 = GRAPH_put_next_char(ubyte char @A)  clobbers(A,X,Y)     ; alias for the routine above that doesn't reset the position of the initial character | ||||||
|  |  | ||||||
| ; framebuffer | ; framebuffer | ||||||
| romsub $fef6 = FB_init()  clobbers(A,X,Y) | romsub $fef6 = FB_init()  clobbers(A,X,Y) | ||||||
| romsub $fef9 = FB_get_info()  clobbers(X,Y) -> byte @A    ; also outputs width=r0, height=r1 | romsub $fef9 = FB_get_info()  clobbers(X,Y) -> byte @A, uword @R0, uword @R1    ; width=r0, height=r1 | ||||||
| romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X)  clobbers(A,X,Y)      ; also uses pointer=r0 | romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X)  clobbers(A,X,Y) | ||||||
| romsub $feff = FB_cursor_position()  clobbers(A,X,Y)    ; uses x=r0, y=r1 | romsub $feff = FB_cursor_position(uword x @R0, uword y @R1)  clobbers(A,X,Y) | ||||||
| romsub $ff02 = FB_cursor_next_line()  clobbers(A,X,Y)   ; uses x=r0 | romsub $feff = FB_cursor_position2()  clobbers(A,X,Y)           ;  alias for the previous routine, but avoiding having to respecify both x and y every time | ||||||
|  | romsub $ff02 = FB_cursor_next_line(uword x @R0)  clobbers(A,X,Y) | ||||||
| romsub $ff05 = FB_get_pixel()  clobbers(X,Y) -> ubyte @A | romsub $ff05 = FB_get_pixel()  clobbers(X,Y) -> ubyte @A | ||||||
| romsub $ff08 = FB_get_pixels()  clobbers(A,X,Y)         ; uses ptr=r0, count=r1 | romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1)  clobbers(A,X,Y) | ||||||
| romsub $ff0b = FB_set_pixel(ubyte color @A)  clobbers(A,X,Y) | romsub $ff0b = FB_set_pixel(ubyte color @A)  clobbers(A,X,Y) | ||||||
| romsub $ff0e = FB_set_pixels()  clobbers(A,X,Y)         ; uses ptr=r0, count=r1 | romsub $ff0e = FB_set_pixels(uword pointer @R0, uword count @R1)  clobbers(A,X,Y) | ||||||
| romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X)  clobbers(A,X,Y) | romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X)  clobbers(A,X,Y) | ||||||
| romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y)  clobbers(A,X,Y)  ; also uses mask=r0L | romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @R0, ubyte mask @A, ubyte color1 @X, ubyte color2 @Y)  clobbers(A,X,Y) | ||||||
| romsub $ff17 = FB_fill_pixels(ubyte color @A)  clobbers(A,X,Y)   ; also uses count=r0, step=r1 | romsub $ff17 = FB_fill_pixels(uword count @R0, uword pstep @R1, ubyte color @A)  clobbers(A,X,Y) | ||||||
| romsub $ff1a = FB_filter_pixels()  clobbers(A,X,Y)      ; uses ptr=r0, count=r1 | romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1)  clobbers(A,X,Y) | ||||||
| romsub $ff1d = FB_move_pixels()  clobbers(A,X,Y)        ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4 | romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4)  clobbers(A,X,Y) | ||||||
|  |  | ||||||
| ; misc | ; misc | ||||||
| romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc)  clobbers(A,X,Y) -> ubyte @Pc  ; also uses pixels=r0, mask=r1, bpp=r2L | romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc)  clobbers(A,X,Y) -> ubyte @Pc | ||||||
| romsub $fef3 = sprite_set_position(ubyte number @A)  clobbers(A,X,Y)  ; also uses x=r0 and y=r1 | romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A)  clobbers(A,X,Y) | ||||||
| romsub $fee4 = memory_fill(ubyte value @A)  clobbers(A,X,Y)      ; uses address=r0, num_bytes=r1 | romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A)  clobbers(A,X,Y) | ||||||
| romsub $fee7 = memory_copy()  clobbers(A,X,Y)                    ; uses source=r0, target=r1, num_bytes=r2 | romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2)  clobbers(A,X,Y) | ||||||
| romsub $feea = memory_crc()  clobbers(A,X,Y)                     ; uses address=r0, num_bytes=r1     result->r2 | romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1)  clobbers(A,X,Y) -> uword @R2 | ||||||
| romsub $feed = memory_decompress()  clobbers(A,X,Y)              ; uses input=r0, output=r1     result->r1 | romsub $feed = memory_decompress(uword input @R0, uword output @R1)  clobbers(A,X,Y) -> uword @R1       ; last address +1 is result in R1 | ||||||
| romsub $fedb = console_init()  clobbers(A,X,Y)           ; uses x=r0, y=r1, width=r2, height=r3 | romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3)  clobbers(A,X,Y) | ||||||
| romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc)  clobbers(A,X,Y) | romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc)  clobbers(A,X,Y) | ||||||
| romsub $fee1 = console_get_char()  clobbers(X,Y) -> ubyte @A | romsub $fee1 = console_get_char()  clobbers(X,Y) -> ubyte @A | ||||||
| romsub $fed8 = console_put_image()  clobbers(A,X,Y)      ; uses ptr=r0, width=r1, height=r2 | romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2)  clobbers(A,X,Y) | ||||||
| romsub $fed5 = console_set_paging_message()  clobbers(A,X,Y)     ; uses messageptr=r0 | romsub $fed5 = console_set_paging_message(uword msgptr @R0)  clobbers(A,X,Y) | ||||||
| romsub $fed2 = kbdbuf_put(ubyte key @A)  clobbers(A,X,Y) | romsub $fed2 = kbdbuf_put(ubyte key @A)  clobbers(A,X,Y) | ||||||
| romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y | romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y | ||||||
| romsub $fecc = monitor()  clobbers(A,X,Y) | romsub $fecc = monitor()  clobbers(A,X,Y) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ---- end of kernal routines ---- | ; ---- end of kernal routines ---- | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ; ---- utilities ----- | ||||||
|  |  | ||||||
|  | inline asmsub rombank(ubyte rombank @A) { | ||||||
|  |     ; -- set the rom banks | ||||||
|  |     %asm {{ | ||||||
|  |         sta  $01            ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38) | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline asmsub rambank(ubyte rambank @A) { | ||||||
|  |     ; -- set the ram bank | ||||||
|  |     %asm {{ | ||||||
|  |         sta  $00            ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38) | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub numbanks() -> ubyte @A { | ||||||
|  |     ; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb) | ||||||
|  |     %asm {{ | ||||||
|  |         phx | ||||||
|  |         sec | ||||||
|  |         jsr  c64.MEMTOP | ||||||
|  |         plx | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A { | ||||||
|  |         ; -- get a byte from VERA's video memory | ||||||
|  |         ;    note: inefficient when reading multiple sequential bytes! | ||||||
|  |         %asm {{ | ||||||
|  |                 pha | ||||||
|  |                 lda  #1 | ||||||
|  |                 sta  cx16.VERA_CTRL | ||||||
|  |                 pla | ||||||
|  |                 and  #1 | ||||||
|  |                 sta  cx16.VERA_ADDR_H | ||||||
|  |                 sty  cx16.VERA_ADDR_M | ||||||
|  |                 stx  cx16.VERA_ADDR_L | ||||||
|  |                 lda  cx16.VERA_DATA1 | ||||||
|  |                 rts | ||||||
|  |             }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) { | ||||||
|  |         ; -- setup the VERA's data address register 0 or 1 | ||||||
|  |         %asm {{ | ||||||
|  |             and  #1 | ||||||
|  |             pha | ||||||
|  |             lda  cx16.r1 | ||||||
|  |             and  #1 | ||||||
|  |             sta  cx16.VERA_CTRL | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             sta  cx16.VERA_ADDR_L | ||||||
|  |             lda  cx16.r0+1 | ||||||
|  |             sta  cx16.VERA_ADDR_M | ||||||
|  |             pla | ||||||
|  |             cpy  #0 | ||||||
|  |             bmi  ++ | ||||||
|  |             beq  + | ||||||
|  |             ora  #%00010000 | ||||||
|  | +           sta  cx16.VERA_ADDR_H | ||||||
|  |             rts | ||||||
|  | +           ora  #%00011000 | ||||||
|  |             sta  cx16.VERA_ADDR_H | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) { | ||||||
|  |     ; -- write a single byte to VERA's video memory | ||||||
|  |     ;    note: inefficient when writing multiple sequential bytes! | ||||||
|  |     %asm {{ | ||||||
|  |         stz  cx16.VERA_CTRL | ||||||
|  |         and  #1 | ||||||
|  |         sta  cx16.VERA_ADDR_H | ||||||
|  |         lda  cx16.r0 | ||||||
|  |         sta  cx16.VERA_ADDR_L | ||||||
|  |         lda  cx16.r0+1 | ||||||
|  |         sta  cx16.VERA_ADDR_M | ||||||
|  |         sty  cx16.VERA_DATA0 | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) { | ||||||
|  |     ; -- or a single byte to the value already in the VERA's video memory at that location | ||||||
|  |     ;    note: inefficient when writing multiple sequential bytes! | ||||||
|  |     %asm {{ | ||||||
|  |         stz  cx16.VERA_CTRL | ||||||
|  |         and  #1 | ||||||
|  |         sta  cx16.VERA_ADDR_H | ||||||
|  |         lda  cx16.r0 | ||||||
|  |         sta  cx16.VERA_ADDR_L | ||||||
|  |         lda  cx16.r0+1 | ||||||
|  |         sta  cx16.VERA_ADDR_M | ||||||
|  |         tya | ||||||
|  |         ora  cx16.VERA_DATA0 | ||||||
|  |         sta  cx16.VERA_DATA0 | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) { | ||||||
|  |     ; -- and a single byte to the value already in the VERA's video memory at that location | ||||||
|  |     ;    note: inefficient when writing multiple sequential bytes! | ||||||
|  |     %asm {{ | ||||||
|  |         stz  cx16.VERA_CTRL | ||||||
|  |         and  #1 | ||||||
|  |         sta  cx16.VERA_ADDR_H | ||||||
|  |         lda  cx16.r0 | ||||||
|  |         sta  cx16.VERA_ADDR_L | ||||||
|  |         lda  cx16.r0+1 | ||||||
|  |         sta  cx16.VERA_ADDR_M | ||||||
|  |         tya | ||||||
|  |         and  cx16.VERA_DATA0 | ||||||
|  |         sta  cx16.VERA_DATA0 | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) { | ||||||
|  |     ; -- xor a single byte to the value already in the VERA's video memory at that location | ||||||
|  |     ;    note: inefficient when writing multiple sequential bytes! | ||||||
|  |     %asm {{ | ||||||
|  |         stz  cx16.VERA_CTRL | ||||||
|  |         and  #1 | ||||||
|  |         sta  cx16.VERA_ADDR_H | ||||||
|  |         lda  cx16.r0 | ||||||
|  |         sta  cx16.VERA_ADDR_L | ||||||
|  |         lda  cx16.r0+1 | ||||||
|  |         sta  cx16.VERA_ADDR_M | ||||||
|  |         tya | ||||||
|  |         eor  cx16.VERA_DATA0 | ||||||
|  |         sta  cx16.VERA_DATA0 | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A { | ||||||
|  |     ; -- like the basic command VLOAD "filename",device,bank,address | ||||||
|  |     ;    loads a file into video memory in the given bank:address, returns success in A | ||||||
|  |     ;    !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly, | ||||||
|  |     ;               it works fine when loading from local filesystem | ||||||
|  |     %asm {{ | ||||||
|  |         ; -- load a file into video ram | ||||||
|  |         phx | ||||||
|  |         pha | ||||||
|  |         tya | ||||||
|  |         tax | ||||||
|  |         lda  #1 | ||||||
|  |         ldy  #0 | ||||||
|  |         jsr  c64.SETLFS | ||||||
|  |         lda  cx16.r0 | ||||||
|  |         ldy  cx16.r0+1 | ||||||
|  |         jsr  prog8_lib.strlen | ||||||
|  |         tya | ||||||
|  |         ldx  cx16.r0 | ||||||
|  |         ldy  cx16.r0+1 | ||||||
|  |         jsr  c64.SETNAM | ||||||
|  |         pla | ||||||
|  |         clc | ||||||
|  |         adc  #2 | ||||||
|  |         ldx  cx16.r1 | ||||||
|  |         ldy  cx16.r1+1 | ||||||
|  |         stz  P8ZP_SCRATCH_B1 | ||||||
|  |         jsr  c64.LOAD | ||||||
|  |         bcs  + | ||||||
|  |         inc  P8ZP_SCRATCH_B1 | ||||||
|  | +       jsr  c64.CLRCHN | ||||||
|  |         lda  #1 | ||||||
|  |         jsr  c64.CLOSE | ||||||
|  |         plx | ||||||
|  |         lda  P8ZP_SCRATCH_B1 | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX  { | ||||||
|  |     ; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values. | ||||||
|  |     ; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203 | ||||||
|  |     ; TODO once that issue is resolved, this routine can be redefined as:  romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX | ||||||
|  |     %asm {{ | ||||||
|  |         sei | ||||||
|  |         jsr  cx16.joystick_get | ||||||
|  |         cli | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | sub FB_set_pixels_from_buf(uword buffer, uword count) { | ||||||
|  |     %asm {{ | ||||||
|  |             ; -- This is replacement code for the normal FB_set_pixels subroutine in ROM | ||||||
|  |             ;    However that routine contains a bug in the current v38 ROM that makes it crash when count > 255. | ||||||
|  |             ;    So the code below replaces that. Once the ROM is patched this routine is no longer necessary. | ||||||
|  |             ;    See https://github.com/commanderx16/x16-rom/issues/179 | ||||||
|  |             phx | ||||||
|  |             lda  buffer | ||||||
|  |             ldy  buffer+1 | ||||||
|  |             sta  P8ZP_SCRATCH_W1 | ||||||
|  |             sty  P8ZP_SCRATCH_W1+1 | ||||||
|  |             jsr  _pixels | ||||||
|  |             plx | ||||||
|  |             rts | ||||||
|  |  | ||||||
|  | _pixels     lda  count+1 | ||||||
|  |             beq  + | ||||||
|  |             ldx  #0 | ||||||
|  | -           jsr  _loop | ||||||
|  |             inc  P8ZP_SCRATCH_W1+1 | ||||||
|  |             dec  count+1 | ||||||
|  |             bne  - | ||||||
|  |  | ||||||
|  | +           ldx  count | ||||||
|  | _loop       ldy  #0 | ||||||
|  | -           lda  (P8ZP_SCRATCH_W1),y | ||||||
|  |             sta  cx16.VERA_DATA0 | ||||||
|  |             iny | ||||||
|  |             dex | ||||||
|  |             bne  - | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ; ---- system stuff ----- | ||||||
| asmsub  init_system()  { | asmsub  init_system()  { | ||||||
|     ; Initializes the machine to a sane starting state. |     ; Initializes the machine to a sane starting state. | ||||||
|     ; Called automatically by the loader program logic. |     ; Called automatically by the loader program logic. | ||||||
|     %asm {{ |     %asm {{ | ||||||
|         sei |         sei | ||||||
|         cld |         cld | ||||||
|         ;stz  $00 |  | ||||||
|         ;stz  $01 |  | ||||||
|         ;stz  d1prb      ; select rom bank 0 |  | ||||||
|         lda  #$80 |         lda  #$80 | ||||||
|         sta  VERA_CTRL |         sta  VERA_CTRL | ||||||
|  |         stz  $01        ; select rom bank 0 (enable kernal) | ||||||
|         jsr  c64.IOINIT |         jsr  c64.IOINIT | ||||||
|         jsr  c64.RESTOR |         jsr  c64.RESTOR | ||||||
|         jsr  c64.CINT |         jsr  c64.CINT | ||||||
| @@ -277,15 +573,311 @@ asmsub init_system()  { | |||||||
|     }} |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  reset_system()  { | asmsub  init_system_phase2()  { | ||||||
|     ; Soft-reset the system back to Basic prompt. |  | ||||||
|     %asm {{ |     %asm {{ | ||||||
|         sei |         sei | ||||||
|         lda  #14 |         lda  cx16.CINV | ||||||
|         sta  $01 |         sta  restore_irq._orig_irqvec | ||||||
|         stz  cx16.d1prb         ; bank the kernal in |         lda  cx16.CINV+1 | ||||||
|         jmp  (cx16.RESET_VEC) |         sta  restore_irq._orig_irqvec+1 | ||||||
|  |         cli | ||||||
|  |         rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A)  { | ||||||
|  | 	%asm {{ | ||||||
|  | 	        sta  _modified+1 | ||||||
|  | 	        sty  _modified+2 | ||||||
|  | 	        lda  #0 | ||||||
|  | 	        adc  #0 | ||||||
|  | 	        sta  _use_kernal | ||||||
|  | 		sei | ||||||
|  | 		lda  #<_irq_handler | ||||||
|  | 		sta  cx16.CINV | ||||||
|  | 		lda  #>_irq_handler | ||||||
|  | 		sta  cx16.CINV+1 | ||||||
|  |                 lda  cx16.VERA_IEN | ||||||
|  |                 ora  #%00000001     ; enable the vsync irq | ||||||
|  |                 sta  cx16.VERA_IEN | ||||||
|  | 		cli | ||||||
|  | 		rts | ||||||
|  |  | ||||||
|  | _irq_handler    jsr  _irq_handler_init | ||||||
|  | _modified	jsr  $ffff                      ; modified | ||||||
|  | 		jsr  _irq_handler_end | ||||||
|  | 		lda  _use_kernal | ||||||
|  | 		bne  + | ||||||
|  | 		; end irq processing - don't use kernal's irq handling | ||||||
|  | 		lda  cx16.VERA_ISR | ||||||
|  | 		ora  #1 | ||||||
|  | 		sta  cx16.VERA_ISR      ; clear Vera Vsync irq status | ||||||
|  | 		ply | ||||||
|  | 		plx | ||||||
|  | 		pla | ||||||
|  | 		rti | ||||||
|  | +		jmp  (restore_irq._orig_irqvec)   ; continue with normal kernal irq routine | ||||||
|  |  | ||||||
|  | _use_kernal     .byte  0 | ||||||
|  |  | ||||||
|  | _irq_handler_init | ||||||
|  | 		; save all zp scratch registers and the X register as these might be clobbered by the irq routine | ||||||
|  | 		stx  IRQ_X_REG | ||||||
|  | 		lda  P8ZP_SCRATCH_B1 | ||||||
|  | 		sta  IRQ_SCRATCH_ZPB1 | ||||||
|  | 		lda  P8ZP_SCRATCH_REG | ||||||
|  | 		sta  IRQ_SCRATCH_ZPREG | ||||||
|  | 		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		sta  IRQ_SCRATCH_ZPWORD1 | ||||||
|  | 		lda  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		sta  IRQ_SCRATCH_ZPWORD1+1 | ||||||
|  | 		lda  P8ZP_SCRATCH_W2 | ||||||
|  | 		sta  IRQ_SCRATCH_ZPWORD2 | ||||||
|  | 		lda  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		sta  IRQ_SCRATCH_ZPWORD2+1 | ||||||
|  | 		; stack protector; make sure we don't clobber the top of the evaluation stack | ||||||
|  | 		dex | ||||||
|  | 		dex | ||||||
|  | 		dex | ||||||
|  | 		dex | ||||||
|  | 		dex | ||||||
|  | 		dex | ||||||
|  | 		cld | ||||||
|  | 		rts | ||||||
|  |  | ||||||
|  | _irq_handler_end | ||||||
|  | 		; restore all zp scratch registers and the X register | ||||||
|  | 		lda  IRQ_SCRATCH_ZPB1 | ||||||
|  | 		sta  P8ZP_SCRATCH_B1 | ||||||
|  | 		lda  IRQ_SCRATCH_ZPREG | ||||||
|  | 		sta  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  IRQ_SCRATCH_ZPWORD1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		lda  IRQ_SCRATCH_ZPWORD1+1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		lda  IRQ_SCRATCH_ZPWORD2 | ||||||
|  | 		sta  P8ZP_SCRATCH_W2 | ||||||
|  | 		lda  IRQ_SCRATCH_ZPWORD2+1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		ldx  IRQ_X_REG | ||||||
|  | 		rts | ||||||
|  |  | ||||||
|  | IRQ_X_REG		.byte  0 | ||||||
|  | IRQ_SCRATCH_ZPB1	.byte  0 | ||||||
|  | IRQ_SCRATCH_ZPREG	.byte  0 | ||||||
|  | IRQ_SCRATCH_ZPWORD1	.word  0 | ||||||
|  | IRQ_SCRATCH_ZPWORD2	.word  0 | ||||||
|  |  | ||||||
|  | 		}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | asmsub  restore_irq() clobbers(A) { | ||||||
|  | 	%asm {{ | ||||||
|  | 	    sei | ||||||
|  | 	    lda  _orig_irqvec | ||||||
|  | 	    sta  cx16.CINV | ||||||
|  | 	    lda  _orig_irqvec+1 | ||||||
|  | 	    sta  cx16.CINV+1 | ||||||
|  | 	    lda  cx16.VERA_IEN | ||||||
|  | 	    and  #%11110000     ; disable all Vera IRQs | ||||||
|  | 	    ora  #%00000001     ; enable only the vsync Irq | ||||||
|  | 	    sta  cx16.VERA_IEN | ||||||
|  | 	    cli | ||||||
|  | 	    rts | ||||||
|  | _orig_irqvec    .word  0 | ||||||
|  |         }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) { | ||||||
|  | 	%asm {{ | ||||||
|  |             sta  _modified+1 | ||||||
|  |             sty  _modified+2 | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             ldy  cx16.r0+1 | ||||||
|  |             sei | ||||||
|  |             lda  cx16.VERA_IEN | ||||||
|  |             and  #%11110000     ; clear other IRQs | ||||||
|  |             ora  #%00000010     ; enable the line (raster) irq | ||||||
|  |             sta  cx16.VERA_IEN | ||||||
|  |             lda  cx16.r0 | ||||||
|  |             ldy  cx16.r0+1 | ||||||
|  |             jsr  set_rasterline | ||||||
|  |             lda  #<_raster_irq_handler | ||||||
|  |             sta  cx16.CINV | ||||||
|  |             lda  #>_raster_irq_handler | ||||||
|  |             sta  cx16.CINV+1 | ||||||
|  |             cli | ||||||
|  |             rts | ||||||
|  |  | ||||||
|  | _raster_irq_handler | ||||||
|  |             jsr  set_irq._irq_handler_init | ||||||
|  | _modified   jsr  $ffff                      ; modified | ||||||
|  |             jsr  set_irq._irq_handler_end | ||||||
|  |             ; end irq processing - don't use kernal's irq handling | ||||||
|  |             lda  cx16.VERA_ISR | ||||||
|  |             ora  #%00000010 | ||||||
|  |             sta  cx16.VERA_ISR      ; clear Vera line irq status | ||||||
|  |             ply | ||||||
|  |             plx | ||||||
|  |             pla | ||||||
|  |             rti | ||||||
|  |         }} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  set_rasterline(uword line @AY) { | ||||||
|  |     %asm {{ | ||||||
|  |         sta  cx16.VERA_IRQ_LINE_L | ||||||
|  |         lda  cx16.VERA_IEN | ||||||
|  |         and  #%01111111 | ||||||
|  |         sta  cx16.VERA_IEN | ||||||
|  |         tya | ||||||
|  |         lsr  a | ||||||
|  |         ror  a | ||||||
|  |         and  #%10000000 | ||||||
|  |         ora  cx16.VERA_IEN | ||||||
|  |         sta  cx16.VERA_IEN | ||||||
|  |         rts | ||||||
|     }} |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | sys { | ||||||
|  |     ; ------- lowlevel system routines -------- | ||||||
|  |  | ||||||
|  |     const ubyte target = 16         ;  compilation target specifier.  64 = C64,  16 = CommanderX16. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     asmsub reset_system() { | ||||||
|  |         ; Soft-reset the system back to initial power-on Basic prompt. | ||||||
|  |         %asm {{ | ||||||
|  |             sei | ||||||
|  |             stz  $01            ; bank the kernal in | ||||||
|  |             jmp  (cx16.RESET_VEC) | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub wait(uword jiffies @AY) { | ||||||
|  |         ; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1) | ||||||
|  |         ;     note: regular system vsync irq handler must be running, and no nother irqs | ||||||
|  |         %asm {{ | ||||||
|  | -           wai             ; wait for irq (assume it was vsync) | ||||||
|  |             cmp  #0 | ||||||
|  |             bne  + | ||||||
|  |             dey | ||||||
|  | +           dec  a | ||||||
|  |             bne  - | ||||||
|  |             cpy  #0 | ||||||
|  |             bne  - | ||||||
|  |             rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub waitvsync()  { | ||||||
|  |         ; --- suspend execution until the next vsync has occurred, without depending on custom irq handling. | ||||||
|  |         ;     note: system vsync irq handler has to be active for this routine to work (and no other irqs). | ||||||
|  |         ;     note: a more accurate way to wait for vsync is to set up a vsync irq handler instead. | ||||||
|  |         %asm {{ | ||||||
|  |             wai | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) { | ||||||
|  |         %asm {{ | ||||||
|  |             sta  cx16.r2 | ||||||
|  |             sty  cx16.r2+1 | ||||||
|  |             jsr  cx16.memory_copy | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) { | ||||||
|  |         %asm {{ | ||||||
|  |             jsr  cx16.memory_fill | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers (A,X,Y) { | ||||||
|  |         %asm {{ | ||||||
|  |             ldx  cx16.r0 | ||||||
|  |             stx  P8ZP_SCRATCH_W1 | ||||||
|  |             ldx  cx16.r0+1 | ||||||
|  |             stx  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldx  cx16.r1 | ||||||
|  |             stx  P8ZP_SCRATCH_W2 | ||||||
|  |             ldx  cx16.r1+1 | ||||||
|  |             stx  P8ZP_SCRATCH_W2+1 | ||||||
|  |             jmp  prog8_lib.memsetw | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub rsave() { | ||||||
|  |         ; save cpu status flag and all registers A, X, Y. | ||||||
|  |         ; see http://6502.org/tutorials/register_preservation.html | ||||||
|  |         %asm {{ | ||||||
|  |             php | ||||||
|  |             pha | ||||||
|  |             phy | ||||||
|  |             phx | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub rrestore() { | ||||||
|  |         ; restore all registers and cpu status flag | ||||||
|  |         %asm {{ | ||||||
|  |             plx | ||||||
|  |             ply | ||||||
|  |             pla | ||||||
|  |             plp | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub read_flags() -> ubyte @A { | ||||||
|  |         %asm {{ | ||||||
|  |             php | ||||||
|  |             pla | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub clear_carry() { | ||||||
|  |         %asm {{ | ||||||
|  |             clc | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub set_carry() { | ||||||
|  |         %asm {{ | ||||||
|  |             sec | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub clear_irqd() { | ||||||
|  |         %asm {{ | ||||||
|  |             cli | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub set_irqd() { | ||||||
|  |         %asm {{ | ||||||
|  |             sei | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub exit(ubyte returnvalue @A) { | ||||||
|  |         ; -- immediately exit the program with a return code in the A register | ||||||
|  |         %asm {{ | ||||||
|  |             jsr  c64.CLRCHN		; reset i/o channels | ||||||
|  |             ldx  prog8_lib.orig_stackpointer | ||||||
|  |             txs | ||||||
|  |             rts		; return to original caller | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline asmsub progend() -> uword @AY { | ||||||
|  |         %asm {{ | ||||||
|  |             lda  #<prog8_program_end | ||||||
|  |             ldy  #>prog8_program_end | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -16,7 +16,30 @@ const ubyte DEFAULT_HEIGHT = 60 | |||||||
|  |  | ||||||
|  |  | ||||||
| sub clear_screen() { | sub clear_screen() { | ||||||
|     clear_screenchars(' ') |     txt.chrout(147) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sub home() { | ||||||
|  |     txt.chrout(19) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sub nl() { | ||||||
|  |     txt.chrout('\n') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sub spc() { | ||||||
|  |     txt.chrout(' ') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub column(ubyte col @A) clobbers(A, X, Y) { | ||||||
|  |     ; ---- set the cursor on the given column (starting with 0) on the current line | ||||||
|  |     %asm {{ | ||||||
|  |         sec | ||||||
|  |         jsr  c64.PLOT | ||||||
|  |         tay | ||||||
|  |         clc | ||||||
|  |         jmp  c64.PLOT | ||||||
|  |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A)  { | asmsub  fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A)  { | ||||||
| @@ -134,13 +157,16 @@ _la     lda  #0                     ; modified | |||||||
| ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b] | ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b] | ||||||
|  |  | ||||||
| sub color (ubyte txtcol) { | sub color (ubyte txtcol) { | ||||||
|     c64.CHROUT(color_to_charcode[txtcol & 15]) |     txtcol &= 15 | ||||||
|  |     c64.CHROUT(color_to_charcode[txtcol]) | ||||||
| } | } | ||||||
|  |  | ||||||
| sub color2 (ubyte txtcol, ubyte bgcol) { | sub color2 (ubyte txtcol, ubyte bgcol) { | ||||||
|     c64.CHROUT(color_to_charcode[bgcol & 15]) |     txtcol &= 15 | ||||||
|  |     bgcol &= 15 | ||||||
|  |     c64.CHROUT(color_to_charcode[bgcol]) | ||||||
|     c64.CHROUT(1)       ; switch fg and bg colors |     c64.CHROUT(1)       ; switch fg and bg colors | ||||||
|     c64.CHROUT(color_to_charcode[txtcol & 15]) |     c64.CHROUT(color_to_charcode[txtcol]) | ||||||
| } | } | ||||||
|  |  | ||||||
| sub lowercase() { | sub lowercase() { | ||||||
| @@ -151,10 +177,9 @@ sub uppercase() { | |||||||
|     cx16.screen_set_charset(2, 0)  ; uppercase charset |     cx16.screen_set_charset(2, 0)  ; uppercase charset | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  scroll_left  (ubyte dummy @ Pc) clobbers(A, Y)  { | asmsub  scroll_left() clobbers(A, Y)  { | ||||||
| 	; ---- scroll the whole screen 1 character to the left | 	; ---- scroll the whole screen 1 character to the left | ||||||
| 	;      contents of the rightmost column are unchanged, you should clear/refill this yourself | 	;      contents of the rightmost column are unchanged, you should clear/refill this yourself | ||||||
| 	;      Carry flag is a dummy on the cx16 |  | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 	    phx | 	    phx | ||||||
| 	    jsr  c64.SCREEN | 	    jsr  c64.SCREEN | ||||||
| @@ -195,10 +220,9 @@ _lx     ldx  #0                ; modified | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  scroll_right  (ubyte dummy @ Pc) clobbers(A)  { | asmsub  scroll_right() clobbers(A)  { | ||||||
| 	; ---- scroll the whole screen 1 character to the right | 	; ---- scroll the whole screen 1 character to the right | ||||||
| 	;      contents of the leftmost column are unchanged, you should clear/refill this yourself | 	;      contents of the leftmost column are unchanged, you should clear/refill this yourself | ||||||
| 	;      Carry flag is a dummy on the cx16 |  | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 	    phx | 	    phx | ||||||
| 	    jsr  c64.SCREEN | 	    jsr  c64.SCREEN | ||||||
| @@ -247,10 +271,9 @@ _lx     ldx  #0                 ; modified | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  scroll_up  (ubyte dummy @ Pc) clobbers(A, Y)  { | asmsub  scroll_up() clobbers(A, Y)  { | ||||||
| 	; ---- scroll the whole screen 1 character up | 	; ---- scroll the whole screen 1 character up | ||||||
| 	;      contents of the bottom row are unchanged, you should refill/clear this yourself | 	;      contents of the bottom row are unchanged, you should refill/clear this yourself | ||||||
| 	;      Carry flag is a dummy on the cx16 |  | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 	    phx | 	    phx | ||||||
| 	    jsr  c64.SCREEN | 	    jsr  c64.SCREEN | ||||||
| @@ -297,10 +320,9 @@ _nextline | |||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| asmsub  scroll_down  (ubyte dummy @ Pc) clobbers(A, Y)  { | asmsub  scroll_down() clobbers(A, Y)  { | ||||||
| 	; ---- scroll the whole screen 1 character down | 	; ---- scroll the whole screen 1 character down | ||||||
| 	;      contents of the top row are unchanged, you should refill/clear this yourself | 	;      contents of the top row are unchanged, you should refill/clear this yourself | ||||||
| 	;      Carry flag is a dummy on the cx16 |  | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
| 	    phx | 	    phx | ||||||
| 	    jsr  c64.SCREEN | 	    jsr  c64.SCREEN | ||||||
| @@ -398,7 +420,7 @@ _print_byte_digits | |||||||
| 		jsr  c64.CHROUT | 		jsr  c64.CHROUT | ||||||
| 		pla | 		pla | ||||||
| 		jsr  c64.CHROUT | 		jsr  c64.CHROUT | ||||||
| 		jmp  _ones | 		bra  _ones | ||||||
| +       pla | +       pla | ||||||
|         cmp  #'0' |         cmp  #'0' | ||||||
|         beq  _ones |         beq  _ones | ||||||
| @@ -421,7 +443,7 @@ asmsub  print_b  (byte value @ A) clobbers(A,Y)  { | |||||||
| 		jsr  c64.CHROUT | 		jsr  c64.CHROUT | ||||||
| +		pla | +		pla | ||||||
| 		jsr  conv.byte2decimal | 		jsr  conv.byte2decimal | ||||||
| 		jmp  print_ub._print_byte_digits | 		bra  print_ub._print_byte_digits | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -472,7 +494,7 @@ asmsub  print_uwbin  (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y)  { | |||||||
| 		jsr  print_ubbin | 		jsr  print_ubbin | ||||||
| 		pla | 		pla | ||||||
| 		clc | 		clc | ||||||
| 		jmp  print_ubbin | 		bra  print_ubbin | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -485,7 +507,7 @@ asmsub  print_uwhex  (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y)  { | |||||||
| 		jsr  print_ubhex | 		jsr  print_ubhex | ||||||
| 		pla | 		pla | ||||||
| 		clc | 		clc | ||||||
| 		jmp  print_ubhex | 		bra  print_ubhex | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -548,7 +570,7 @@ asmsub  print_w  (word value @ AY) clobbers(A,Y)  { | |||||||
| 		adc  #1 | 		adc  #1 | ||||||
| 		bcc  + | 		bcc  + | ||||||
| 		iny | 		iny | ||||||
| +		jmp  print_uw | +		bra  print_uw | ||||||
| 	}} | 	}} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -604,6 +626,8 @@ asmsub  getchr  (ubyte col @A, ubyte row @Y) -> ubyte @ A { | |||||||
|  |  | ||||||
| asmsub  setclr  (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A)  { | asmsub  setclr  (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A)  { | ||||||
| 	; ---- set the color in A on the screen matrix at the given position | 	; ---- set the color in A on the screen matrix at the given position | ||||||
|  | 	;      note: on the CommanderX16 this allows you to set both Fg and Bg colors; | ||||||
|  | 	;            use the high nybble in A to set the Bg color! | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
|             pha |             pha | ||||||
|             txa |             txa | ||||||
| @@ -635,6 +659,8 @@ asmsub  getclr  (ubyte col @A, ubyte row @Y) -> ubyte @ A { | |||||||
|  |  | ||||||
| sub  setcc  (ubyte column, ubyte row, ubyte char, ubyte charcolor)  { | sub  setcc  (ubyte column, ubyte row, ubyte char, ubyte charcolor)  { | ||||||
| 	; ---- set char+color at the given position on the screen | 	; ---- set char+color at the given position on the screen | ||||||
|  | 	;      note: color handling is the same as on the C64: it only sets the foreground color. | ||||||
|  | 	;            use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors. | ||||||
| 	%asm {{ | 	%asm {{ | ||||||
|             phx |             phx | ||||||
|             lda  column |             lda  column | ||||||
| @@ -663,8 +689,35 @@ sub  setcc  (ubyte column, ubyte row, ubyte char, ubyte charcolor)  { | |||||||
|     }} |     }} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | sub  setcc2  (ubyte column, ubyte row, ubyte char, ubyte colors)  { | ||||||
|  | 	; ---- set char+color at the given position on the screen | ||||||
|  | 	;      note: on the CommanderX16 this allows you to set both Fg and Bg colors; | ||||||
|  | 	;            use the high nybble in A to set the Bg color! | ||||||
|  | 	%asm {{ | ||||||
|  |             phx | ||||||
|  |             lda  column | ||||||
|  |             asl  a | ||||||
|  |             tax | ||||||
|  |             ldy  row | ||||||
|  |             stz  cx16.VERA_CTRL | ||||||
|  |             stz  cx16.VERA_ADDR_H | ||||||
|  |             stx  cx16.VERA_ADDR_L | ||||||
|  |             sty  cx16.VERA_ADDR_M | ||||||
|  |             lda  char | ||||||
|  |             sta  cx16.VERA_DATA0 | ||||||
|  |             inx | ||||||
|  |             stz  cx16.VERA_ADDR_H | ||||||
|  |             stx  cx16.VERA_ADDR_L | ||||||
|  |             sty  cx16.VERA_ADDR_M | ||||||
|  |             lda  colors | ||||||
|  |             sta  cx16.VERA_DATA0 | ||||||
|  |             plx | ||||||
|  |             rts | ||||||
|  |     }} | ||||||
|  | } | ||||||
|  |  | ||||||
| asmsub  plot  (ubyte col @ Y, ubyte row @ A) clobbers(A) { | asmsub  plot  (ubyte col @ Y, ubyte row @ A) clobbers(A) { | ||||||
| 	; ---- safe wrapper around PLOT kernel routine, to save the X register. | 	; ---- safe wrapper around PLOT kernal routine, to save the X register. | ||||||
| 	%asm  {{ | 	%asm  {{ | ||||||
| 		phx | 		phx | ||||||
| 		tax | 		tax | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								compiler/res/prog8lib/cx16logo.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								compiler/res/prog8lib/cx16logo.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | ; routine to draw the Commander X16's log in petscii. | ||||||
|  |  | ||||||
|  | %import textio | ||||||
|  |  | ||||||
|  | cx16logo { | ||||||
|  |     sub logo_at(ubyte column, ubyte row) { | ||||||
|  |         uword strptr | ||||||
|  |         for strptr in logo_lines { | ||||||
|  |             txt.plot(column, row) | ||||||
|  |             txt.print(strptr) | ||||||
|  |             row++ | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub logo() { | ||||||
|  |         uword strptr | ||||||
|  |         for strptr in logo_lines | ||||||
|  |             txt.print(strptr) | ||||||
|  |         txt.nl() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     str[] logo_lines = [ | ||||||
|  |             "\uf10d\uf11a\uf139\uf11b     \uf11a\uf13a\uf11b\n", | ||||||
|  |             "\uf10b\uf11a▎\uf139\uf11b   \uf11a\uf13a\uf130\uf11b\n", | ||||||
|  |             "\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n", | ||||||
|  |             "\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n", | ||||||
|  |             "\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n", | ||||||
|  |             "\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n", | ||||||
|  |             "\uf101\uf130\uf13a   \uf139▎\uf100" | ||||||
|  |         ] | ||||||
|  | } | ||||||
							
								
								
									
										492
									
								
								compiler/res/prog8lib/diskio.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										492
									
								
								compiler/res/prog8lib/diskio.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,492 @@ | |||||||
|  | ; C64 and Cx16 disk drive I/O routines. | ||||||
|  | ; | ||||||
|  | ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||||
|  |  | ||||||
|  | %import textio | ||||||
|  | %import string | ||||||
|  | %import syslib | ||||||
|  |  | ||||||
|  | diskio { | ||||||
|  |  | ||||||
|  |     sub directory(ubyte drivenumber) -> ubyte { | ||||||
|  |         ; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success. | ||||||
|  |  | ||||||
|  |         c64.SETNAM(1, "$") | ||||||
|  |         c64.SETLFS(13, drivenumber, 0) | ||||||
|  |         void c64.OPEN()          ; open 13,8,0,"$" | ||||||
|  |         if_cs | ||||||
|  |             goto io_error | ||||||
|  |         void c64.CHKIN(13)        ; use #13 as input channel | ||||||
|  |         if_cs | ||||||
|  |             goto io_error | ||||||
|  |  | ||||||
|  |         repeat 4 { | ||||||
|  |             void c64.CHRIN()     ; skip the 4 prologue bytes | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ; while not key pressed / EOF encountered, read data. | ||||||
|  |         ubyte status = c64.READST() | ||||||
|  |         while not status { | ||||||
|  |             ubyte low = c64.CHRIN() | ||||||
|  |             ubyte high = c64.CHRIN() | ||||||
|  |             txt.print_uw(mkword(high, low)) | ||||||
|  |             txt.spc() | ||||||
|  |             ubyte @zp char | ||||||
|  |             repeat { | ||||||
|  |                 char = c64.CHRIN() | ||||||
|  |                 if char==0 | ||||||
|  |                     break | ||||||
|  |                 txt.chrout(char) | ||||||
|  |             } | ||||||
|  |             txt.nl() | ||||||
|  |             void c64.CHRIN()     ; skip 2 bytes | ||||||
|  |             void c64.CHRIN() | ||||||
|  |             status = c64.READST() | ||||||
|  |             if c64.STOP2() | ||||||
|  |                 break | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | io_error: | ||||||
|  |         status = c64.READST() | ||||||
|  |         c64.CLRCHN()        ; restore default i/o devices | ||||||
|  |         c64.CLOSE(13) | ||||||
|  |  | ||||||
|  |         if status and status & $40 == 0 {            ; bit 6=end of file | ||||||
|  |             txt.print("\ni/o error, status: ") | ||||||
|  |             txt.print_ub(status) | ||||||
|  |             txt.nl() | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ; internal variables for the iterative file lister / loader | ||||||
|  |     ubyte list_skip_disk_name | ||||||
|  |     uword list_pattern | ||||||
|  |     uword list_blocks | ||||||
|  |     ubyte iteration_in_progress = false | ||||||
|  |     ubyte @zp first_byte | ||||||
|  |     ubyte have_first_byte | ||||||
|  |     str   list_filename = "?" * 32 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ; ----- get a list of files (uses iteration functions internally) ----- | ||||||
|  |  | ||||||
|  |     sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte { | ||||||
|  |         ; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. | ||||||
|  |         uword names_buffer = memory("filenames", 512) | ||||||
|  |         uword buffer_start = names_buffer | ||||||
|  |         ubyte files_found = 0 | ||||||
|  |         if lf_start_list(drivenumber, pattern_ptr) { | ||||||
|  |             while lf_next_entry() { | ||||||
|  |                 @(name_ptrs) = lsb(names_buffer) | ||||||
|  |                 name_ptrs++ | ||||||
|  |                 @(name_ptrs) = msb(names_buffer) | ||||||
|  |                 name_ptrs++ | ||||||
|  |                 names_buffer += string.copy(diskio.list_filename, names_buffer) + 1 | ||||||
|  |                 files_found++ | ||||||
|  |                 if names_buffer - buffer_start > 512-18 | ||||||
|  |                     break | ||||||
|  |                 if files_found == max_names | ||||||
|  |                     break | ||||||
|  |             } | ||||||
|  |             lf_end_list() | ||||||
|  |         } | ||||||
|  |         return files_found | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ; ----- iterative file lister functions (uses io channel 12) ----- | ||||||
|  |  | ||||||
|  |     sub lf_start_list(ubyte drivenumber, uword pattern_ptr) -> ubyte { | ||||||
|  |         ; -- start an iterative file listing with optional pattern matching. | ||||||
|  |         ;    note: only a single iteration loop can be active at a time! | ||||||
|  |         lf_end_list() | ||||||
|  |         list_pattern = pattern_ptr | ||||||
|  |         list_skip_disk_name = true | ||||||
|  |         iteration_in_progress = true | ||||||
|  |  | ||||||
|  |         c64.SETNAM(1, "$") | ||||||
|  |         c64.SETLFS(12, drivenumber, 0) | ||||||
|  |         void c64.OPEN()          ; open 12,8,0,"$" | ||||||
|  |         if_cs | ||||||
|  |             goto io_error | ||||||
|  |         void c64.CHKIN(12)        ; use #12 as input channel | ||||||
|  |         if_cs | ||||||
|  |             goto io_error | ||||||
|  |  | ||||||
|  |         repeat 4 { | ||||||
|  |             void c64.CHRIN()     ; skip the 4 prologue bytes | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if c64.READST()==0 | ||||||
|  |             return true | ||||||
|  |  | ||||||
|  | io_error: | ||||||
|  |         lf_end_list() | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub lf_next_entry() -> ubyte { | ||||||
|  |         ; -- retrieve the next entry from an iterative file listing session. | ||||||
|  |         ;    results will be found in list_blocks and list_filename. | ||||||
|  |         ;    if it returns false though, there are no more entries (or an error occurred). | ||||||
|  |  | ||||||
|  |         if not iteration_in_progress | ||||||
|  |             return false | ||||||
|  |  | ||||||
|  |         repeat { | ||||||
|  |             void c64.CHKIN(12)        ; use #12 as input channel again | ||||||
|  |  | ||||||
|  |             uword nameptr = &list_filename | ||||||
|  |             ubyte blocks_lsb = c64.CHRIN() | ||||||
|  |             ubyte blocks_msb = c64.CHRIN() | ||||||
|  |  | ||||||
|  |             if c64.READST() | ||||||
|  |                 goto close_end | ||||||
|  |  | ||||||
|  |             list_blocks = mkword(blocks_msb, blocks_lsb) | ||||||
|  |  | ||||||
|  |             ; read until the filename starts after the first " | ||||||
|  |             while c64.CHRIN()!='\"'  { | ||||||
|  |                 if c64.READST() | ||||||
|  |                     goto close_end | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ; read the filename | ||||||
|  |             repeat { | ||||||
|  |                 ubyte char = c64.CHRIN() | ||||||
|  |                 if char==0 | ||||||
|  |                     break | ||||||
|  |                 if char=='\"' | ||||||
|  |                     break | ||||||
|  |                 @(nameptr) = char | ||||||
|  |                 nameptr++ | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             @(nameptr) = 0 | ||||||
|  |  | ||||||
|  |             while c64.CHRIN() { | ||||||
|  |                 ; read the rest of the entry until the end | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             void c64.CHRIN()     ; skip 2 bytes | ||||||
|  |             void c64.CHRIN() | ||||||
|  |  | ||||||
|  |             if not list_skip_disk_name { | ||||||
|  |                 if not list_pattern | ||||||
|  |                     return true | ||||||
|  |                 if prog8_lib.pattern_match(list_filename, list_pattern) | ||||||
|  |                     return true | ||||||
|  |             } | ||||||
|  |             list_skip_disk_name = false | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | close_end: | ||||||
|  |         lf_end_list() | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub lf_end_list() { | ||||||
|  |         ; -- end an iterative file listing session (close channels). | ||||||
|  |         if iteration_in_progress { | ||||||
|  |             c64.CLRCHN() | ||||||
|  |             c64.CLOSE(12) | ||||||
|  |             iteration_in_progress = false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ; ----- iterative file loader functions (uses io channel 11) ----- | ||||||
|  |  | ||||||
|  |     sub f_open(ubyte drivenumber, uword filenameptr) -> ubyte { | ||||||
|  |         ; -- open a file for iterative reading with f_read | ||||||
|  |         ;    note: only a single iteration loop can be active at a time! | ||||||
|  |         f_close() | ||||||
|  |  | ||||||
|  |         c64.SETNAM(string.length(filenameptr), filenameptr) | ||||||
|  |         c64.SETLFS(11, drivenumber, 0) | ||||||
|  |         void c64.OPEN()          ; open 11,8,0,"filename" | ||||||
|  |         if_cc { | ||||||
|  |             iteration_in_progress = true | ||||||
|  |             have_first_byte = false | ||||||
|  |             void c64.CHKIN(11)        ; use #11 as input channel | ||||||
|  |             if_cc { | ||||||
|  |                 first_byte = c64.CHRIN()   ; read first byte to test for file not found | ||||||
|  |                 if not c64.READST() { | ||||||
|  |                     have_first_byte = true | ||||||
|  |                     return true | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         f_close() | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub f_read(uword bufferpointer, uword num_bytes) -> uword { | ||||||
|  |         ; -- read from the currently open file, up to the given number of bytes. | ||||||
|  |         ;    returns the actual number of bytes read.  (checks for End-of-file and error conditions) | ||||||
|  |         if not iteration_in_progress or not num_bytes | ||||||
|  |             return 0 | ||||||
|  |  | ||||||
|  |         list_blocks = 0     ; we reuse this variable for the total number of bytes read | ||||||
|  |         if have_first_byte { | ||||||
|  |             have_first_byte=false | ||||||
|  |             @(bufferpointer) = first_byte | ||||||
|  |             bufferpointer++ | ||||||
|  |             list_blocks++ | ||||||
|  |             num_bytes-- | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         void c64.CHKIN(11)        ; use #11 as input channel again | ||||||
|  |         %asm {{ | ||||||
|  |             lda  bufferpointer | ||||||
|  |             sta  _in_buffer+1 | ||||||
|  |             lda  bufferpointer+1 | ||||||
|  |             sta  _in_buffer+2 | ||||||
|  |         }} | ||||||
|  |         repeat num_bytes { | ||||||
|  |             %asm {{ | ||||||
|  |                 jsr  c64.CHRIN | ||||||
|  |                 sta  cx16.r5 | ||||||
|  | _in_buffer      sta  $ffff | ||||||
|  |                 inc  _in_buffer+1 | ||||||
|  |                 bne  + | ||||||
|  |                 inc  _in_buffer+2 | ||||||
|  | +               inc  list_blocks | ||||||
|  |                 bne  + | ||||||
|  |                 inc  list_blocks+1 | ||||||
|  | + | ||||||
|  |             }} | ||||||
|  |  | ||||||
|  |             if cx16.r5==$0d {   ; chance on I/o error status? | ||||||
|  |                 first_byte = c64.READST() | ||||||
|  |                 if first_byte & $40 | ||||||
|  |                     f_close()       ; end of file, close it | ||||||
|  |                 if first_byte | ||||||
|  |                     return list_blocks | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return list_blocks | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub f_read_all(uword bufferpointer) -> uword { | ||||||
|  |         ; -- read the full contents of the file, returns number of bytes read. | ||||||
|  |         if not iteration_in_progress | ||||||
|  |             return 0 | ||||||
|  |  | ||||||
|  |         list_blocks = 0     ; we reuse this variable for the total number of bytes read | ||||||
|  |         if have_first_byte { | ||||||
|  |             have_first_byte=false | ||||||
|  |             @(bufferpointer) = first_byte | ||||||
|  |             bufferpointer++ | ||||||
|  |             list_blocks++ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         while not c64.READST() { | ||||||
|  |             list_blocks += f_read(bufferpointer, 256) | ||||||
|  |             bufferpointer += 256 | ||||||
|  |         } | ||||||
|  |         return list_blocks | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y { | ||||||
|  |         ; Routine to read text lines from a text file. Lines must be less than 255 characters. | ||||||
|  |         ; Reads characters from the input file UNTIL a newline or return character (or EOF). | ||||||
|  |         ; The line read will be 0-terminated in the buffer (and not contain the end of line character). | ||||||
|  |         ; The length of the line is returned in Y. Note that an empty line is okay and is length 0! | ||||||
|  |         ; I/O error status should be checked by the caller itself via READST() routine. | ||||||
|  |         %asm {{ | ||||||
|  |             sta  P8ZP_SCRATCH_W1 | ||||||
|  |             sty  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldx  #11 | ||||||
|  |             jsr  c64.CHKIN              ; use channel 11 again for input | ||||||
|  |             ldy  #0 | ||||||
|  |             lda  have_first_byte | ||||||
|  |             beq  _loop | ||||||
|  |             lda  #0 | ||||||
|  |             sta  have_first_byte | ||||||
|  |             lda  first_byte | ||||||
|  |             sta  (P8ZP_SCRATCH_W1),y | ||||||
|  |             iny | ||||||
|  | _loop       jsr  c64.CHRIN | ||||||
|  |             sta  (P8ZP_SCRATCH_W1),y | ||||||
|  |             beq  _end | ||||||
|  |             iny | ||||||
|  |             cmp  #$0a | ||||||
|  |             beq  _line_end | ||||||
|  |             cmp  #$0d | ||||||
|  |             bne  _loop | ||||||
|  | _line_end   dey     ; get rid of the trailing end-of-line char | ||||||
|  |             lda  #0 | ||||||
|  |             sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | _end        rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     sub f_close() { | ||||||
|  |         ; -- end an iterative file loading session (close channels). | ||||||
|  |         if iteration_in_progress { | ||||||
|  |             c64.CLRCHN() | ||||||
|  |             c64.CLOSE(11) | ||||||
|  |             iteration_in_progress = false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ; ----- iterative file saver functions (uses io channel 14) ----- | ||||||
|  |  | ||||||
|  |     sub f_open_w(ubyte drivenumber, uword filenameptr) -> ubyte { | ||||||
|  |         ; -- open a file for iterative writing with f_write | ||||||
|  |         f_close_w() | ||||||
|  |  | ||||||
|  |         c64.SETNAM(string.length(filenameptr), filenameptr) | ||||||
|  |         c64.SETLFS(14, drivenumber, 1) | ||||||
|  |         void c64.OPEN()          ; open 14,8,1,"filename" | ||||||
|  |         if_cc { | ||||||
|  |             void c64.CHKOUT(14)        ; use #14 as input channel | ||||||
|  |             return not c64.READST() | ||||||
|  |         } | ||||||
|  |         f_close_w() | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub f_write(uword bufferpointer, uword num_bytes) -> ubyte { | ||||||
|  |         ; -- write the given umber of bytes to the currently open file | ||||||
|  |         if num_bytes!=0 { | ||||||
|  |             void c64.CHKOUT(14)        ; use #14 as input channel again | ||||||
|  |             repeat num_bytes { | ||||||
|  |                 c64.CHROUT(@(bufferpointer)) | ||||||
|  |                 bufferpointer++ | ||||||
|  |             } | ||||||
|  |             return not c64.READST() | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub f_close_w() { | ||||||
|  |         ; -- end an iterative file writing session (close channels). | ||||||
|  |         c64.CLRCHN() | ||||||
|  |         c64.CLOSE(14) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ; ---- other functions ---- | ||||||
|  |  | ||||||
|  |     sub status(ubyte drivenumber) -> uword { | ||||||
|  |         ; -- retrieve the disk drive's current status message | ||||||
|  |         uword messageptr = &filename | ||||||
|  |         c64.SETNAM(0, filename) | ||||||
|  |         c64.SETLFS(15, drivenumber, 15) | ||||||
|  |         void c64.OPEN()          ; open 15,8,15 | ||||||
|  |         if_cs | ||||||
|  |             goto io_error | ||||||
|  |         void c64.CHKIN(15)        ; use #15 as input channel | ||||||
|  |         if_cs | ||||||
|  |             goto io_error | ||||||
|  |  | ||||||
|  |         while not c64.READST() { | ||||||
|  |             @(messageptr) = c64.CHRIN() | ||||||
|  |             messageptr++ | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @(messageptr) = 0 | ||||||
|  | done: | ||||||
|  |         c64.CLRCHN()        ; restore default i/o devices | ||||||
|  |         c64.CLOSE(15) | ||||||
|  |         return filename | ||||||
|  |  | ||||||
|  | io_error: | ||||||
|  |         filename = "?disk error" | ||||||
|  |         goto done | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte { | ||||||
|  |         c64.SETNAM(string.length(filenameptr), filenameptr) | ||||||
|  |         c64.SETLFS(1, drivenumber, 0) | ||||||
|  |         uword end_address = address + size | ||||||
|  |         first_byte = 0      ; result var reuse | ||||||
|  |  | ||||||
|  |         %asm {{ | ||||||
|  |             lda  address | ||||||
|  |             sta  P8ZP_SCRATCH_W1 | ||||||
|  |             lda  address+1 | ||||||
|  |             sta  P8ZP_SCRATCH_W1+1 | ||||||
|  |             stx  P8ZP_SCRATCH_REG | ||||||
|  |             lda  #<P8ZP_SCRATCH_W1 | ||||||
|  |             ldx  end_address | ||||||
|  |             ldy  end_address+1 | ||||||
|  |             jsr  c64.SAVE | ||||||
|  |             php | ||||||
|  |             ldx  P8ZP_SCRATCH_REG | ||||||
|  |             plp | ||||||
|  |         }} | ||||||
|  |  | ||||||
|  |         if_cc | ||||||
|  |             first_byte = c64.READST()==0 | ||||||
|  |  | ||||||
|  |         c64.CLRCHN() | ||||||
|  |         c64.CLOSE(1) | ||||||
|  |  | ||||||
|  |         return first_byte | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword { | ||||||
|  |         c64.SETNAM(string.length(filenameptr), filenameptr) | ||||||
|  |         ubyte secondary = 1 | ||||||
|  |         uword end_of_load = 0 | ||||||
|  |         if address_override | ||||||
|  |             secondary = 0 | ||||||
|  |         c64.SETLFS(1, drivenumber, secondary) | ||||||
|  |         %asm {{ | ||||||
|  |             stx  P8ZP_SCRATCH_REG | ||||||
|  |             lda  #0 | ||||||
|  |             ldx  address_override | ||||||
|  |             ldy  address_override+1 | ||||||
|  |             jsr  c64.LOAD | ||||||
|  |             bcs  + | ||||||
|  |             stx  end_of_load | ||||||
|  |             sty  end_of_load+1 | ||||||
|  | +           ldx  P8ZP_SCRATCH_REG | ||||||
|  |         }} | ||||||
|  |  | ||||||
|  |         c64.CLRCHN() | ||||||
|  |         c64.CLOSE(1) | ||||||
|  |  | ||||||
|  |         if end_of_load | ||||||
|  |             return end_of_load - address_override | ||||||
|  |  | ||||||
|  |         return 0 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     str filename = "0:??????????????????????????????????????" | ||||||
|  |  | ||||||
|  |     sub delete(ubyte drivenumber, uword filenameptr) { | ||||||
|  |         ; -- delete a file on the drive | ||||||
|  |         filename[0] = 's' | ||||||
|  |         filename[1] = ':' | ||||||
|  |         ubyte flen = string.copy(filenameptr, &filename+2) | ||||||
|  |         c64.SETNAM(flen+2, filename) | ||||||
|  |         c64.SETLFS(1, drivenumber, 15) | ||||||
|  |         void c64.OPEN() | ||||||
|  |         c64.CLRCHN() | ||||||
|  |         c64.CLOSE(1) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) { | ||||||
|  |         ; -- rename a file on the drive | ||||||
|  |         filename[0] = 'r' | ||||||
|  |         filename[1] = ':' | ||||||
|  |         ubyte flen_new = string.copy(newfileptr, &filename+2) | ||||||
|  |         filename[flen_new+2] = '=' | ||||||
|  |         ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new) | ||||||
|  |         c64.SETNAM(3+flen_new+flen_old, filename) | ||||||
|  |         c64.SETLFS(1, drivenumber, 15) | ||||||
|  |         void c64.OPEN() | ||||||
|  |         c64.CLRCHN() | ||||||
|  |         c64.CLOSE(1) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,11 +1,8 @@ | |||||||
| ; Prog8 internal Math library routines - always included by the compiler | ; Internal Math library routines - always included by the compiler | ||||||
| ; Generic machine independent 6502 code. | ; Generic machine independent 6502 code. | ||||||
| ; | ; | ||||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||||
| ; | ; | ||||||
| ; indent format: TABS, size=8 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ;  some more interesting routines can be found here: | ;  some more interesting routines can be found here: | ||||||
| ;	http://6502org.wikidot.com/software-math | ;	http://6502org.wikidot.com/software-math | ||||||
| ;	http://codebase64.org/doku.php?id=base:6502_6510_maths | ;	http://codebase64.org/doku.php?id=base:6502_6510_maths | ||||||
| @@ -248,67 +245,44 @@ randseed	.proc | |||||||
|  |  | ||||||
|  |  | ||||||
| randbyte        .proc | randbyte        .proc | ||||||
| 	; -- 8-bit pseudo random number generator into A | 	; -- 8 bit pseudo random number generator into A (by just reusing randword) | ||||||
| 		lda  _seed | 		jmp  randword | ||||||
| 		beq  _eor |  | ||||||
| 		asl  a |  | ||||||
| 		beq  _done	; if the input was $80, skip the EOR |  | ||||||
| 		bcc  _done |  | ||||||
| _eor		eor  #$1d	; xor with magic value see below for possible values |  | ||||||
| _done		sta  _seed |  | ||||||
| 		rts |  | ||||||
|  |  | ||||||
| _seed		.byte  $3a |  | ||||||
|  |  | ||||||
| 		; possible 'magic' eor bytes are: |  | ||||||
| 		; $1d, $2b, $2d, $4d, $5f, $63, $65, $69 |  | ||||||
| 		; $71, $87, $8d, $a9, $c3, $cf, $e7, $f5 |  | ||||||
|  |  | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| randword	.proc | randword	.proc | ||||||
| 	; -- 16 bit pseudo random number generator into AY | 	; -- 16 bit pseudo random number generator into AY | ||||||
|  |  | ||||||
| magic_eor = $3f1d | 		; rand64k       ;Factors of 65535: 3 5 17 257 | ||||||
| 		; possible magic eor words are: | 		lda sr1+1 | ||||||
| 		; $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109 |  | ||||||
|  		; $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb |  | ||||||
|  |  | ||||||
| 		lda  _seed |  | ||||||
| 		beq  _lowZero	; $0000 and $8000 are special values to test for |  | ||||||
|  |  | ||||||
|  		; Do a normal shift |  | ||||||
| 		asl  _seed |  | ||||||
| 		lda  _seed+1 |  | ||||||
| 		rol  a |  | ||||||
| 		bcc  _noEor |  | ||||||
|  |  | ||||||
| _doEor		; high byte is in A |  | ||||||
| 		eor  #>magic_eor |  | ||||||
|   		sta  _seed+1 |  | ||||||
|   		lda  _seed |  | ||||||
|   		eor  #<magic_eor |  | ||||||
|   		sta  _seed |  | ||||||
|   		ldy  _seed+1 |  | ||||||
|   		rts |  | ||||||
|  |  | ||||||
| _lowZero	lda  _seed+1 |  | ||||||
| 		beq  _doEor	; High byte is also zero, so apply the EOR |  | ||||||
| 				; For speed, you could store 'magic' into 'seed' directly |  | ||||||
| 				; instead of running the EORs |  | ||||||
|  |  | ||||||
|  		; wasn't zero, check for $8000 |  | ||||||
| 		asl a | 		asl a | ||||||
| 		beq  _noEor	; if $00 is left after the shift, then it was $80 | 		asl a | ||||||
| 		bcs  _doEor	; else, do the EOR based on the carry bit as usual | 		eor sr1+1 | ||||||
|  | 		asl a | ||||||
| _noEor		sta  _seed+1 | 		eor sr1+1 | ||||||
| 		tay | 		asl a | ||||||
| 		lda  _seed | 		asl a | ||||||
|  | 		eor sr1+1 | ||||||
|  | 		asl a | ||||||
|  | 		rol sr1         ;shift this left, "random" bit comes from low | ||||||
|  | 		rol sr1+1 | ||||||
|  | 		; rand32k       ;Factors of 32767: 7 31 151 are independent and can be combined | ||||||
|  | 		lda sr2+1 | ||||||
|  | 		asl a | ||||||
|  | 		eor sr2+1 | ||||||
|  | 		asl a | ||||||
|  | 		asl a | ||||||
|  | 		ror sr2         ;shift this right, random bit comes from high - nicer when eor with sr1 | ||||||
|  | 		rol sr2+1 | ||||||
|  | 		lda sr1+1         ;can be left out | ||||||
|  | 		eor sr2+1         ;if you dont use | ||||||
|  | 		tay               ;y as suggested | ||||||
|  | 		lda sr1           ;mix up lowbytes of SR1 | ||||||
|  | 		eor sr2           ;and SR2 to combine both | ||||||
| 		rts | 		rts | ||||||
|  |  | ||||||
| _seed		.word	$2c9e | sr1     	.word $a55a | ||||||
|  | sr2     	.word $7653 | ||||||
|  |  | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -774,6 +748,38 @@ stack_mul_word_100	.proc | |||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  | stack_mul_word_320	.proc | ||||||
|  | 		; stackW = stackLo * 256 + stackLo * 64	 (stackHi doesn't matter) | ||||||
|  | 		ldy  P8ESTACK_LO+1,x | ||||||
|  | 		lda  #0 | ||||||
|  | 		sta  P8ESTACK_HI+1,x | ||||||
|  | 		tya | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ESTACK_HI+1,x | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ESTACK_HI+1,x | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ESTACK_HI+1,x | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ESTACK_HI+1,x | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ESTACK_HI+1,x | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ESTACK_HI+1,x | ||||||
|  | 		sta  P8ESTACK_LO+1,x | ||||||
|  | 		tya | ||||||
|  | 		clc | ||||||
|  | 		adc  P8ESTACK_HI+1,x | ||||||
|  | 		sta  P8ESTACK_HI+1,x | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | stack_mul_word_640	.proc | ||||||
|  | 		; stackW = (stackLo * 2 * 320)    (stackHi doesn't matter) | ||||||
|  | 		asl  P8ESTACK_LO+1,x | ||||||
|  | 		jmp  stack_mul_word_320 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : --------- | ; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : --------- | ||||||
| mul_byte_3	.proc | mul_byte_3	.proc | ||||||
| @@ -781,7 +787,7 @@ mul_byte_3	.proc | |||||||
| 		sta  P8ZP_SCRATCH_REG | 		sta  P8ZP_SCRATCH_REG | ||||||
| 		asl  a | 		asl  a | ||||||
| 		clc | 		clc | ||||||
| 		adc  P8P_P8ZP_SCRATCH_REG | 		adc  P8ZP_SCRATCH_REG | ||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
| @@ -1178,8 +1184,6 @@ mul_word_40	.proc | |||||||
| 		rol  a | 		rol  a | ||||||
| 		asl  P8ZP_SCRATCH_W1 | 		asl  P8ZP_SCRATCH_W1 | ||||||
| 		rol  a | 		rol  a | ||||||
| 		asl  P8ZP_SCRATCH_W1 |  | ||||||
| 		rol  a |  | ||||||
| 		tay | 		tay | ||||||
| 		lda  P8ZP_SCRATCH_W1 | 		lda  P8ZP_SCRATCH_W1 | ||||||
| 		rts | 		rts | ||||||
| @@ -1241,48 +1245,42 @@ mul_word_100	.proc | |||||||
| 		rts | 		rts | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  | mul_word_320	.proc | ||||||
|  | 		; AY = A * 256 + A * 64	 (msb in Y doesn't matter) | ||||||
|  | 		sta  P8ZP_SCRATCH_B1 | ||||||
|  | 		ldy  #0 | ||||||
|  | 		sty  P8ZP_SCRATCH_REG | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		asl  a | ||||||
|  | 		rol  P8ZP_SCRATCH_REG | ||||||
|  | 		pha | ||||||
|  | 		clc | ||||||
|  | 		lda  P8ZP_SCRATCH_B1 | ||||||
|  | 		adc  P8ZP_SCRATCH_REG | ||||||
|  | 		tay | ||||||
|  | 		pla | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | mul_word_640	.proc | ||||||
|  | 		; AY = (A * 2 * 320) (msb in Y doesn't matter) | ||||||
|  | 		asl  a | ||||||
|  | 		jmp  mul_word_320 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ----------- end optimized multiplications ----------- | ; ----------- end optimized multiplications ----------- | ||||||
|  |  | ||||||
|  |  | ||||||
| sign_b		.proc |  | ||||||
| 		lda  P8ESTACK_LO+1,x |  | ||||||
| 		beq  _sign_zero |  | ||||||
| 		bmi  _sign_neg |  | ||||||
| _sign_pos	lda  #1 |  | ||||||
| 		sta  P8ESTACK_LO+1,x |  | ||||||
| 		rts |  | ||||||
| _sign_neg	lda  #-1 |  | ||||||
| _sign_zero	sta  P8ESTACK_LO+1,x |  | ||||||
| 		rts |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| sign_ub		.proc |  | ||||||
| 		lda  P8ESTACK_LO+1,x |  | ||||||
| 		beq  sign_b._sign_zero |  | ||||||
| 		bne  sign_b._sign_pos |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| sign_w		.proc |  | ||||||
| 		lda  P8ESTACK_HI+1,x |  | ||||||
| 		bmi  sign_b._sign_neg |  | ||||||
| 		beq  sign_ub |  | ||||||
| 		bne  sign_b._sign_pos |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
| sign_uw		.proc |  | ||||||
| 		lda  P8ESTACK_HI+1,x |  | ||||||
| 		beq  _sign_possibly_zero |  | ||||||
| _sign_pos	lda  #1 |  | ||||||
| 		sta  P8ESTACK_LO+1,x |  | ||||||
| 		rts |  | ||||||
| _sign_possibly_zero	lda  P8ESTACK_LO+1,x |  | ||||||
| 		bne  _sign_pos |  | ||||||
| 		sta  P8ESTACK_LO+1,x |  | ||||||
| 		rts |  | ||||||
| 		.pend |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; bit shifts. | ; bit shifts. | ||||||
| ; anything below 3 is done inline. anything above 7 is done via other optimizations. | ; anything below 3 is done inline. anything above 7 is done via other optimizations. | ||||||
|  |  | ||||||
| @@ -1340,6 +1338,33 @@ shift_left_w_3	.proc | |||||||
| 		jmp  shift_left_w_7._shift3 | 		jmp  shift_left_w_7._shift3 | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | shift_left_w	.proc | ||||||
|  | 		; -- variable number of shifts left | ||||||
|  | 		inx | ||||||
|  | 		ldy  P8ESTACK_LO,x | ||||||
|  | 		bne  _shift | ||||||
|  | 		rts | ||||||
|  | _shift		asl  P8ESTACK_LO+1,x | ||||||
|  | 		rol  P8ESTACK_HI+1,x | ||||||
|  | 		dey | ||||||
|  | 		bne  _shift | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | shift_right_uw	.proc | ||||||
|  | 		; -- uword variable number of shifts right | ||||||
|  | 		inx | ||||||
|  | 		ldy  P8ESTACK_LO,x | ||||||
|  | 		bne  _shift | ||||||
|  | 		rts | ||||||
|  | _shift		lsr  P8ESTACK_HI+1,x | ||||||
|  | 		ror  P8ESTACK_LO+1,x | ||||||
|  | 		dey | ||||||
|  | 		bne  _shift | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
| shift_right_uw_7	.proc | shift_right_uw_7	.proc | ||||||
| 		lda  P8ESTACK_LO+1,x | 		lda  P8ESTACK_LO+1,x | ||||||
| 		sta  P8ZP_SCRATCH_B1 | 		sta  P8ZP_SCRATCH_B1 | ||||||
| @@ -1469,3 +1494,105 @@ shift_right_w_3	.proc | |||||||
| 		jmp  shift_right_w_7._shift3 | 		jmp  shift_right_w_7._shift3 | ||||||
| 		.pend | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | shift_right_w	.proc | ||||||
|  | 		; -- signed word variable number of shifts right | ||||||
|  | 		inx | ||||||
|  | 		ldy  P8ESTACK_LO,x | ||||||
|  | 		bne  _shift | ||||||
|  | 		rts | ||||||
|  | _shift		lda  P8ESTACK_HI+1,x | ||||||
|  | 		asl  a | ||||||
|  | 		ror  P8ESTACK_HI+1,x | ||||||
|  | 		ror  P8ESTACK_LO+1,x | ||||||
|  | 		dey | ||||||
|  | 		bne  _shift | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ; support for bit shifting that is too large to be unrolled: | ||||||
|  |  | ||||||
|  | lsr_byte_A	.proc | ||||||
|  | 		; -- lsr signed byte in A times the value in Y (assume >0) | ||||||
|  | 		cmp  #0 | ||||||
|  | 		bmi  _negative | ||||||
|  | -		lsr  a | ||||||
|  | 		dey | ||||||
|  | 		bne  - | ||||||
|  | 		rts | ||||||
|  | _negative	lsr  a | ||||||
|  | 		ora  #$80 | ||||||
|  | 		dey | ||||||
|  | 		bne  _negative | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | square          .proc | ||||||
|  | ; -- calculate square root of signed word in AY, result in AY | ||||||
|  | ; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm | ||||||
|  | ; using this routine is about twice as fast as doing a regular multiplication. | ||||||
|  | ; | ||||||
|  | ; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in | ||||||
|  | ; Numberl/Numberh.  The result is always in the range 0 to 65025 and is held in | ||||||
|  | ; Squarel/Squareh | ||||||
|  | ; | ||||||
|  | ; The maximum input range is only +/-255 and no checking is done to ensure that | ||||||
|  | ; this is so. | ||||||
|  | ; | ||||||
|  | ; This routine is useful if you are trying to draw circles as for any circle | ||||||
|  | ; | ||||||
|  | ; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and | ||||||
|  | ; r is the circle radius | ||||||
|  |  | ||||||
|  | numberl = P8ZP_SCRATCH_W1       ; number to square low byte | ||||||
|  | numberh = P8ZP_SCRATCH_W1+1     ; number to square high byte | ||||||
|  | squarel = P8ZP_SCRATCH_W2       ; square low byte | ||||||
|  | squareh = P8ZP_SCRATCH_W2+1     ; square high byte | ||||||
|  | tempsq = P8ZP_SCRATCH_B1        ; temp byte for intermediate result | ||||||
|  |  | ||||||
|  | 	sta  numberl | ||||||
|  | 	sty  numberh | ||||||
|  | 	stx  P8ZP_SCRATCH_REG | ||||||
|  |  | ||||||
|  |         lda     #$00        ; clear a | ||||||
|  |         sta     squarel     ; clear square low byte | ||||||
|  |                             ; (no need to clear the high byte, it gets shifted out) | ||||||
|  |         lda	numberl     ; get number low byte | ||||||
|  | 	ldx	numberh     ; get number high  byte | ||||||
|  | 	bpl	_nonneg      ; if +ve don't negate it | ||||||
|  |                             ; else do a two's complement | ||||||
|  | 	eor	#$ff        ; invert | ||||||
|  |         sec	            ; +1 | ||||||
|  | 	adc	#$00        ; and add it | ||||||
|  |  | ||||||
|  | _nonneg: | ||||||
|  | 	sta	tempsq      ; save abs(number) | ||||||
|  | 	ldx	#$08        ; set bit count | ||||||
|  |  | ||||||
|  | _nextr2bit: | ||||||
|  | 	asl	squarel     ; low byte *2 | ||||||
|  | 	rol	squareh     ; high byte *2+carry from low | ||||||
|  | 	asl	a           ; shift number byte | ||||||
|  | 	bcc	_nosqadd     ; don't do add if c = 0 | ||||||
|  | 	tay                 ; save a | ||||||
|  | 	clc                 ; clear carry for add | ||||||
|  | 	lda	tempsq      ; get number | ||||||
|  | 	adc	squarel     ; add number^2 low byte | ||||||
|  | 	sta	squarel     ; save number^2 low byte | ||||||
|  | 	lda	#$00        ; clear a | ||||||
|  | 	adc	squareh     ; add number^2 high byte | ||||||
|  | 	sta	squareh     ; save number^2 high byte | ||||||
|  | 	tya                 ; get a back | ||||||
|  |  | ||||||
|  | _nosqadd: | ||||||
|  | 	dex                 ; decrement bit count | ||||||
|  | 	bne	_nextr2bit   ; go do next bit | ||||||
|  |  | ||||||
|  | 	lda  squarel | ||||||
|  | 	ldy  squareh | ||||||
|  | 	ldx  P8ZP_SCRATCH_REG | ||||||
|  | 	rts | ||||||
|  |  | ||||||
|  | 		.pend | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| ; Prog8 internal Math library routines - always included by the compiler | ; Internal Math library routines - always included by the compiler | ||||||
| ; | ; | ||||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||||
| ; |  | ||||||
| ; indent format: TABS, size=8 |  | ||||||
|  |  | ||||||
| math { | math { | ||||||
| 	%asminclude "library:math.asm", "" | 	%asminclude "library:math.asm" | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1107
									
								
								compiler/res/prog8lib/prog8_funcs.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1107
									
								
								compiler/res/prog8lib/prog8_funcs.asm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1085
									
								
								compiler/res/prog8lib/prog8_lib.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1085
									
								
								compiler/res/prog8lib/prog8_lib.asm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										84
									
								
								compiler/res/prog8lib/prog8_lib.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								compiler/res/prog8lib/prog8_lib.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | ; Internal library routines - always included by the compiler | ||||||
|  | ; | ||||||
|  | ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||||
|  |  | ||||||
|  | prog8_lib { | ||||||
|  | 	%asminclude "library:prog8_lib.asm" | ||||||
|  | 	%asminclude "library:prog8_funcs.asm" | ||||||
|  |  | ||||||
|  | 	uword @zp retval_interm_uw      ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||||
|  | 	word @zp retval_interm_w        ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||||
|  | 	ubyte @zp retval_interm_ub      ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||||
|  | 	byte @zp retval_interm_b        ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||||
|  |  | ||||||
|  | 	asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A { | ||||||
|  | 		%asm {{ | ||||||
|  | ; pattern matching of a string. | ||||||
|  | ; Input:  cx16.r0:  A NUL-terminated, <255-length pattern | ||||||
|  | ;              AY:  A NUL-terminated, <255-length string | ||||||
|  | ; | ||||||
|  | ; Output: A = 1 if the string matches the pattern, A = 0 if not. | ||||||
|  | ; | ||||||
|  | ; Notes:  Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack. | ||||||
|  | ; | ||||||
|  | ; see http://6502.org/source/strings/patmatch.htm | ||||||
|  |  | ||||||
|  | str = P8ZP_SCRATCH_W1 | ||||||
|  |  | ||||||
|  | 	stx  P8ZP_SCRATCH_REG | ||||||
|  | 	sta  str | ||||||
|  | 	sty  str+1 | ||||||
|  | 	lda  cx16.r0 | ||||||
|  | 	sta  modify_pattern1+1 | ||||||
|  | 	sta  modify_pattern2+1 | ||||||
|  | 	lda  cx16.r0+1 | ||||||
|  | 	sta  modify_pattern1+2 | ||||||
|  | 	sta  modify_pattern2+2 | ||||||
|  | 	jsr  _match | ||||||
|  | 	lda  #0 | ||||||
|  | 	adc  #0 | ||||||
|  | 	ldx  P8ZP_SCRATCH_REG | ||||||
|  | 	rts | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _match | ||||||
|  | 	ldx #$00        ; x is an index in the pattern | ||||||
|  | 	ldy #$ff        ; y is an index in the string | ||||||
|  | modify_pattern1 | ||||||
|  | next    lda $ffff,x   ; look at next pattern character    MODIFIED | ||||||
|  | 	cmp #'*'     ; is it a star? | ||||||
|  | 	beq star        ; yes, do the complicated stuff | ||||||
|  | 	iny             ; no, let's look at the string | ||||||
|  | 	cmp #'?'     ; is the pattern caracter a ques? | ||||||
|  | 	bne reg         ; no, it's a regular character | ||||||
|  | 	lda (str),y     ; yes, so it will match anything | ||||||
|  | 	beq fail        ;  except the end of string | ||||||
|  | reg     cmp (str),y     ; are both characters the same? | ||||||
|  | 	bne fail        ; no, so no match | ||||||
|  | 	inx             ; yes, keep checking | ||||||
|  | 	cmp #0          ; are we at end of string? | ||||||
|  | 	bne next        ; not yet, loop | ||||||
|  | found   rts             ; success, return with c=1 | ||||||
|  |  | ||||||
|  | star    inx             ; skip star in pattern | ||||||
|  | modify_pattern2 | ||||||
|  | 	cmp $ffff,x   	; string of stars equals one star	MODIFIED | ||||||
|  | 	beq star        ;  so skip them also | ||||||
|  | stloop  txa             ; we first try to match with * = "" | ||||||
|  | 	pha             ;  and grow it by 1 character every | ||||||
|  | 	tya             ;  time we loop | ||||||
|  | 	pha             ; save x and y on stack | ||||||
|  | 	jsr next        ; recursive call | ||||||
|  | 	pla             ; restore x and y | ||||||
|  | 	tay | ||||||
|  | 	pla | ||||||
|  | 	tax | ||||||
|  | 	bcs found       ; we found a match, return with c=1 | ||||||
|  | 	iny             ; no match yet, try to grow * string | ||||||
|  | 	lda (str),y     ; are we at the end of string? | ||||||
|  | 	bne stloop      ; not yet, add a character | ||||||
|  | fail    clc             ; yes, no match found, return with c=0 | ||||||
|  | 	rts | ||||||
|  | 		}} | ||||||
|  | 	} | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,9 +0,0 @@ | |||||||
| ; Prog8 internal library routines - always included by the compiler |  | ||||||
| ; |  | ||||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 |  | ||||||
| ; |  | ||||||
| ; indent format: TABS, size=8 |  | ||||||
|  |  | ||||||
| prog8_lib { |  | ||||||
| 	%asminclude "library:prog8lib.asm", "" |  | ||||||
| } |  | ||||||
							
								
								
									
										235
									
								
								compiler/res/prog8lib/string.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								compiler/res/prog8lib/string.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,235 @@ | |||||||
|  | ; 0-terminated string manipulation routines. | ||||||
|  | ; | ||||||
|  | ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | string { | ||||||
|  |  | ||||||
|  |     asmsub length(uword string @AY) clobbers(A) -> ubyte @Y { | ||||||
|  |         ; Returns the number of bytes in the string. | ||||||
|  |         ; This value is determined during runtime and counts upto the first terminating 0 byte in the string, | ||||||
|  |         ; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof! | ||||||
|  |  | ||||||
|  |         %asm {{ | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		ldy  #0 | ||||||
|  | -		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		beq  + | ||||||
|  | 		iny | ||||||
|  | 		bne  - | ||||||
|  | +		rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub left(uword source @R0, ubyte length @A, uword target @R1) clobbers(A, Y) { | ||||||
|  |         ; Copies the left side of the source string of the given length to target string. | ||||||
|  |         ; It is assumed the target string buffer is large enough to contain the result. | ||||||
|  |         ; Also, you have to make sure yourself that length is smaller or equal to the length of the source string. | ||||||
|  |         ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). | ||||||
|  |         %asm {{ | ||||||
|  |                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||||
|  | 		ldy  cx16.r0 | ||||||
|  | 		sty  P8ZP_SCRATCH_W1 | ||||||
|  | 		ldy  cx16.r0+1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		ldy  cx16.r1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W2 | ||||||
|  | 		ldy  cx16.r1+1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		tay | ||||||
|  | 		lda  #0 | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		cpy  #0 | ||||||
|  | 		bne  _loop | ||||||
|  | 		rts | ||||||
|  | _loop		dey | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		cpy  #0 | ||||||
|  | 		bne  _loop | ||||||
|  | +		rts | ||||||
|  |         }} | ||||||
|  | ;                asmgen.out("  jsr  prog8_lib.func_leftstr") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub right(uword source @R0, ubyte length @A, uword target @R1) clobbers(A,Y) { | ||||||
|  |         ; Copies the right side of the source string of the given length to target string. | ||||||
|  |         ; It is assumed the target string buffer is large enough to contain the result. | ||||||
|  |         ; Also, you have to make sure yourself that length is smaller or equal to the length of the source string. | ||||||
|  |         ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). | ||||||
|  |         %asm {{ | ||||||
|  |                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||||
|  |                 sta  P8ZP_SCRATCH_B1 | ||||||
|  |                 lda  cx16.r0 | ||||||
|  |                 ldy  cx16.r0+1 | ||||||
|  |                 jsr  string.length | ||||||
|  |                 tya | ||||||
|  |                 sec | ||||||
|  |                 sbc  P8ZP_SCRATCH_B1 | ||||||
|  |                 clc | ||||||
|  |                 adc  cx16.r0 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		lda  cx16.r0+1 | ||||||
|  | 		adc  #0 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		ldy  cx16.r1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W2 | ||||||
|  | 		ldy  cx16.r1+1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_B1 | ||||||
|  | 		lda  #0 | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		cpy  #0 | ||||||
|  | 		bne  _loop | ||||||
|  | 		rts | ||||||
|  | _loop		dey | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		cpy  #0 | ||||||
|  | 		bne  _loop | ||||||
|  | +		rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub slice(uword source @R0, ubyte start @A, ubyte length @Y, uword target @R1) clobbers(A, Y) { | ||||||
|  |         ; Copies a segment from the source string, starting at the given index, | ||||||
|  |         ;  and of the given length to target string. | ||||||
|  |         ; It is assumed the target string buffer is large enough to contain the result. | ||||||
|  |         ; Also, you have to make sure yourself that start and length are within bounds of the strings. | ||||||
|  |         ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). | ||||||
|  |         %asm {{ | ||||||
|  |                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||||
|  | 		; substr(source, target, start, length) | ||||||
|  | 		sta  P8ZP_SCRATCH_B1 | ||||||
|  | 		lda  cx16.r0 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		lda  cx16.r0+1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		lda  cx16.r1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W2 | ||||||
|  | 		lda  cx16.r1+1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W2+1 | ||||||
|  |  | ||||||
|  | 		; adjust src location | ||||||
|  | 		clc | ||||||
|  | 		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		adc  P8ZP_SCRATCH_B1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		bcc  + | ||||||
|  | 		inc  P8ZP_SCRATCH_W1+1 | ||||||
|  | +		lda  #0 | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		beq  _startloop | ||||||
|  | -		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | _startloop	dey | ||||||
|  | 		cpy  #$ff | ||||||
|  | 		bne  - | ||||||
|  | 		rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub find(uword string @R0, ubyte character @A) -> uword @AY { | ||||||
|  |         ; Locates the first position of the given character in the string, | ||||||
|  |         ;  returns the string starting with this character or $0000 if the character is not found. | ||||||
|  |         %asm {{ | ||||||
|  |                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||||
|  |                 sta  P8ZP_SCRATCH_B1 | ||||||
|  | 		lda  cx16.r0 | ||||||
|  | 		ldy  cx16.r0+1 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		ldy  #0 | ||||||
|  | -		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		beq  _notfound | ||||||
|  | 		cmp  P8ZP_SCRATCH_B1 | ||||||
|  | 		beq  _found | ||||||
|  | 		iny | ||||||
|  | 		bne  - | ||||||
|  | _notfound	lda  #0 | ||||||
|  | 		ldy  #0 | ||||||
|  | 		rts | ||||||
|  | _found		sty  P8ZP_SCRATCH_B1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		clc | ||||||
|  | 		adc  P8ZP_SCRATCH_B1 | ||||||
|  | 		bcc  + | ||||||
|  | 		iny | ||||||
|  | +		rts | ||||||
|  |  | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub copy(uword source @R0, uword target @AY) clobbers(A) -> ubyte @Y { | ||||||
|  |         ; Copy a string to another, overwriting that one. | ||||||
|  |         ; Returns the length of the string that was copied. | ||||||
|  |         ; Often you don’t have to call this explicitly and can just write string1 = string2 | ||||||
|  |         ; but this function is useful if you’re dealing with addresses for instance. | ||||||
|  |         %asm {{ | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		sty  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		lda  cx16.r0 | ||||||
|  | 		ldy  cx16.r0+1 | ||||||
|  | 		jmp  prog8_lib.strcpy | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A { | ||||||
|  |         ; Compares two strings for sorting. | ||||||
|  |         ; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2. | ||||||
|  |         ; Note that you can also directly compare strings and string values with eachother using | ||||||
|  |         ; comparison operators ==, < etcetera (it will use strcmp for you under water automatically). | ||||||
|  |         %asm {{ | ||||||
|  | 		sta  P8ZP_SCRATCH_W2 | ||||||
|  | 		sty  P8ZP_SCRATCH_W2+1 | ||||||
|  | 		lda  cx16.r0 | ||||||
|  | 		ldy  cx16.r0+1 | ||||||
|  | 		jmp  prog8_lib.strcmp_mem | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub lower(uword st @AY) -> ubyte @Y { | ||||||
|  |         ; Lowercases the petscii string in-place. Returns length of the string. | ||||||
|  |         ; (for efficiency, non-letter characters > 128 will also not be left intact, | ||||||
|  |         ;  but regular text doesn't usually contain those characters anyway.) | ||||||
|  |         %asm {{ | ||||||
|  |             sta  P8ZP_SCRATCH_W1 | ||||||
|  |             sty  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldy  #0 | ||||||
|  | -           lda  (P8ZP_SCRATCH_W1),y | ||||||
|  |             beq  _done | ||||||
|  |             and  #$7f | ||||||
|  |             cmp  #97 | ||||||
|  |             bcc  + | ||||||
|  |             cmp  #123 | ||||||
|  |             bcs  + | ||||||
|  |             and  #%11011111 | ||||||
|  | +           sta  (P8ZP_SCRATCH_W1),y | ||||||
|  |             iny | ||||||
|  |             bne  - | ||||||
|  | _done       rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     asmsub upper(uword st @AY) -> ubyte @Y { | ||||||
|  |         ; Uppercases the petscii string in-place. Returns length of the string. | ||||||
|  |         %asm {{ | ||||||
|  |             sta  P8ZP_SCRATCH_W1 | ||||||
|  |             sty  P8ZP_SCRATCH_W1+1 | ||||||
|  |             ldy  #0 | ||||||
|  | -           lda  (P8ZP_SCRATCH_W1),y | ||||||
|  |             beq  _done | ||||||
|  |             cmp  #65 | ||||||
|  |             bcc  + | ||||||
|  |             cmp  #91 | ||||||
|  |             bcs  + | ||||||
|  |             ora  #%00100000 | ||||||
|  | +           sta  (P8ZP_SCRATCH_W1),y | ||||||
|  |             iny | ||||||
|  |             bne  - | ||||||
|  | _done       rts | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								compiler/res/prog8lib/test_stack.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								compiler/res/prog8lib/test_stack.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | ; utility debug code to print the X (evalstack) and SP (cpu stack) registers. | ||||||
|  |  | ||||||
|  | %import textio | ||||||
|  |  | ||||||
|  | test_stack { | ||||||
|  |  | ||||||
|  |     asmsub test() { | ||||||
|  |         %asm {{ | ||||||
|  | 	stx  _saveX | ||||||
|  | 	lda  #13 | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  #'-' | ||||||
|  | 	ldy  #12 | ||||||
|  | -	jsr  txt.chrout | ||||||
|  | 	dey | ||||||
|  | 	bne  - | ||||||
|  | 	lda  #13 | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  #'x' | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  #'=' | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  _saveX | ||||||
|  | 	jsr  txt.print_ub | ||||||
|  | 	lda  #' ' | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  #'s' | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  #'p' | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  #'=' | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	tsx | ||||||
|  | 	txa | ||||||
|  | 	jsr  txt.print_ub | ||||||
|  | 	lda  #13 | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	lda  #'-' | ||||||
|  | 	ldy  #12 | ||||||
|  | -	jsr  txt.chrout | ||||||
|  | 	dey | ||||||
|  | 	bne  - | ||||||
|  | 	lda  #13 | ||||||
|  | 	jsr  txt.chrout | ||||||
|  | 	ldx  _saveX | ||||||
|  | 	rts | ||||||
|  | _saveX	.byte 0 | ||||||
|  |         }} | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1 +1 @@ | |||||||
| 4.4 | 7.0-BETA2 | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ import prog8.compiler.CompilationResult | |||||||
| import prog8.compiler.compileProgram | import prog8.compiler.compileProgram | ||||||
| import prog8.compiler.target.C64Target | import prog8.compiler.target.C64Target | ||||||
| import prog8.compiler.target.Cx16Target | import prog8.compiler.target.Cx16Target | ||||||
| import prog8.compiler.target.CompilationTarget |  | ||||||
| import prog8.parser.ParsingFailedError | import prog8.parser.ParsingFailedError | ||||||
|  | import java.io.File | ||||||
| import java.nio.file.FileSystems | import java.nio.file.FileSystems | ||||||
| import java.nio.file.Path | import java.nio.file.Path | ||||||
| import java.nio.file.StandardWatchEventKinds | import java.nio.file.StandardWatchEventKinds | ||||||
| @@ -32,19 +32,21 @@ fun pathFrom(stringPath: String, vararg rest: String): Path  = FileSystems.getDe | |||||||
|  |  | ||||||
|  |  | ||||||
| private fun compileMain(args: Array<String>) { | private fun compileMain(args: Array<String>) { | ||||||
|     val cli = CommandLineInterface("prog8compiler") |     val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM) | ||||||
|     val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation") |     val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation") | ||||||
|     val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".") |     val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") | ||||||
|     val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code") |     val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") | ||||||
|     val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations") |     val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") | ||||||
|     val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed") |     val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed") | ||||||
|     val compilationTarget by cli.flagValueArgument("-target", "compilertarget", |     val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") | ||||||
|             "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name) |     val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name) | ||||||
|     val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1) |     val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator) | ||||||
|  |     val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|         cli.parse(args) |         cli.parse(args) | ||||||
|     } catch (e: Exception) { |     } catch (e: IllegalStateException) { | ||||||
|  |         System.err.println(e.message) | ||||||
|         exitProcess(1) |         exitProcess(1) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -54,32 +56,55 @@ private fun compileMain(args: Array<String>) { | |||||||
|         exitProcess(1) |         exitProcess(1) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if(watchMode && moduleFiles.size<=1) { |     val faultyOption = moduleFiles.firstOrNull { it.startsWith('-') } | ||||||
|  |     if(faultyOption!=null) { | ||||||
|  |         System.err.println("Unknown command line option given: $faultyOption") | ||||||
|  |         exitProcess(1) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val libdirs = libDirs.toMutableList() | ||||||
|  |     if(libdirs.firstOrNull()!=".") | ||||||
|  |         libdirs.add(0, ".") | ||||||
|  |  | ||||||
|  |     if(watchMode==true) { | ||||||
|         val watchservice = FileSystems.getDefault().newWatchService() |         val watchservice = FileSystems.getDefault().newWatchService() | ||||||
|  |         val allImportedFiles = mutableSetOf<Path>() | ||||||
|  |  | ||||||
|         while(true) { |         while(true) { | ||||||
|             val filepath = pathFrom(moduleFiles.single()).normalize() |             println("Continuous watch mode active. Modules: $moduleFiles") | ||||||
|             println("Continuous watch mode active. Main module: $filepath") |             val results = mutableListOf<CompilationResult>() | ||||||
|  |             for(filepathRaw in moduleFiles) { | ||||||
|  |                 val filepath = pathFrom(filepathRaw).normalize() | ||||||
|  |                 val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) | ||||||
|  |                 results.add(compilationResult) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             val allNewlyImportedFiles = results.flatMap { it.importedFiles } | ||||||
|  |             allImportedFiles.addAll(allNewlyImportedFiles) | ||||||
|  |  | ||||||
|             try { |  | ||||||
|                 val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath) |  | ||||||
|             println("Imported files (now watching:)") |             println("Imported files (now watching:)") | ||||||
|                 for (importedFile in compilationResult.importedFiles) { |             for (importedFile in allImportedFiles) { | ||||||
|                 print("  ") |                 print("  ") | ||||||
|                 println(importedFile) |                 println(importedFile) | ||||||
|                     importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY) |                 val watchDir = importedFile.parent ?: Path.of(".") | ||||||
|  |                 watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY) | ||||||
|             } |             } | ||||||
|             println("[${LocalDateTime.now().withNano(0)}]  Waiting for file changes.") |             println("[${LocalDateTime.now().withNano(0)}]  Waiting for file changes.") | ||||||
|  |  | ||||||
|  |             var recompile=false | ||||||
|  |             while(!recompile) { | ||||||
|                 val event = watchservice.take() |                 val event = watchservice.take() | ||||||
|                 for (changed in event.pollEvents()) { |                 for (changed in event.pollEvents()) { | ||||||
|                     val changedPath = changed.context() as Path |                     val changedPath = changed.context() as Path | ||||||
|  |                     if(allImportedFiles.any { it.fileName == changedPath.fileName }) { | ||||||
|                         println("  change detected: $changedPath") |                         println("  change detected: $changedPath") | ||||||
|  |                         recompile = true | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 event.reset() |                 event.reset() | ||||||
|                 println("\u001b[H\u001b[2J")      // clear the screen |  | ||||||
|             } catch (x: Exception) { |  | ||||||
|                 throw x |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             println("\u001b[H\u001b[2J")      // clear the screen | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } else { |     } else { | ||||||
| @@ -87,7 +112,7 @@ private fun compileMain(args: Array<String>) { | |||||||
|             val filepath = pathFrom(filepathRaw).normalize() |             val filepath = pathFrom(filepathRaw).normalize() | ||||||
|             val compilationResult: CompilationResult |             val compilationResult: CompilationResult | ||||||
|             try { |             try { | ||||||
|                 compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath) |                 compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) | ||||||
|                 if(!compilationResult.success) |                 if(!compilationResult.success) | ||||||
|                     exitProcess(1) |                     exitProcess(1) | ||||||
|             } catch (x: ParsingFailedError) { |             } catch (x: ParsingFailedError) { | ||||||
| @@ -96,11 +121,11 @@ private fun compileMain(args: Array<String>) { | |||||||
|                 exitProcess(1) |                 exitProcess(1) | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (startEmulator) { |             if (startEmulator==true) { | ||||||
|                 if (compilationResult.programName.isEmpty()) |                 if (compilationResult.programName.isEmpty()) | ||||||
|                     println("\nCan't start emulator because no program was assembled.") |                     println("\nCan't start emulator because no program was assembled.") | ||||||
|                 else if(startEmulator) { |                 else { | ||||||
|                     CompilationTarget.instance.machine.launchEmulator(compilationResult.programName) |                     compilationResult.compTarget.machine.launchEmulator(compilationResult.programName) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,70 +0,0 @@ | |||||||
| package prog8.ast.base |  | ||||||
|  |  | ||||||
| import prog8.ast.Module |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.processing.* |  | ||||||
| import prog8.compiler.CompilationOptions |  | ||||||
| import prog8.compiler.BeforeAsmGenerationAstChanger |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) { |  | ||||||
|     val checker = AstChecker(this, compilerOptions, errors) |  | ||||||
|     checker.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) { |  | ||||||
|     val fixer = BeforeAsmGenerationAstChanger(this, errors) |  | ||||||
|     fixer.visit(this) |  | ||||||
|     fixer.applyModifications() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.reorderStatements() { |  | ||||||
|     val reorder = StatementReorderer(this) |  | ||||||
|     reorder.visit(this) |  | ||||||
|     reorder.applyModifications() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.addTypecasts(errors: ErrorReporter) { |  | ||||||
|     val caster = TypecastsAdder(this, errors) |  | ||||||
|     caster.visit(this) |  | ||||||
|     caster.applyModifications() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.verifyFunctionArgTypes() { |  | ||||||
|     val fixer = VerifyFunctionArgTypes(this) |  | ||||||
|     fixer.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Module.checkImportedValid() { |  | ||||||
|     val imr = ImportedModuleDirectiveRemover() |  | ||||||
|     imr.visit(this, this.parent) |  | ||||||
|     imr.applyModifications() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.checkRecursion(errors: ErrorReporter) { |  | ||||||
|     val checker = AstRecursionChecker(namespace, errors) |  | ||||||
|     checker.visit(this) |  | ||||||
|     checker.processMessages(name) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.checkIdentifiers(errors: ErrorReporter) { |  | ||||||
|  |  | ||||||
|     val checker2 = AstIdentifiersChecker(this, errors) |  | ||||||
|     checker2.visit(this) |  | ||||||
|  |  | ||||||
|     if(errors.isEmpty()) { |  | ||||||
|         val transforms = AstVariousTransforms(this) |  | ||||||
|         transforms.visit(this) |  | ||||||
|         transforms.applyModifications() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (modules.map { it.name }.toSet().size != modules.size) { |  | ||||||
|         throw FatalAstException("modules should all be unique") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.variousCleanups() { |  | ||||||
|     val process = VariousCleanups() |  | ||||||
|     process.visit(this) |  | ||||||
|     process.applyModifications() |  | ||||||
| } |  | ||||||
| @@ -1,118 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.INameScope |  | ||||||
| import prog8.ast.base.ErrorReporter |  | ||||||
| import prog8.ast.base.Position |  | ||||||
| import prog8.ast.expressions.FunctionCall |  | ||||||
| import prog8.ast.statements.FunctionCallStatement |  | ||||||
| import prog8.ast.statements.Subroutine |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class AstRecursionChecker(private val namespace: INameScope, |  | ||||||
|                                    private val errors: ErrorReporter) : IAstVisitor { |  | ||||||
|     private val callGraph = DirectedGraph<INameScope>() |  | ||||||
|  |  | ||||||
|     fun processMessages(modulename: String) { |  | ||||||
|         val cycle = callGraph.checkForCycle() |  | ||||||
|         if(cycle.isEmpty()) |  | ||||||
|             return |  | ||||||
|         val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" } |  | ||||||
|         errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCallStatement: FunctionCallStatement) { |  | ||||||
|         val scope = functionCallStatement.definingScope() |  | ||||||
|         val targetStatement = functionCallStatement.target.targetStatement(namespace) |  | ||||||
|         if(targetStatement!=null) { |  | ||||||
|             val targetScope = when (targetStatement) { |  | ||||||
|                 is Subroutine -> targetStatement |  | ||||||
|                 else -> targetStatement.definingScope() |  | ||||||
|             } |  | ||||||
|             callGraph.add(scope, targetScope) |  | ||||||
|         } |  | ||||||
|         super.visit(functionCallStatement) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCall: FunctionCall) { |  | ||||||
|         val scope = functionCall.definingScope() |  | ||||||
|         val targetStatement = functionCall.target.targetStatement(namespace) |  | ||||||
|         if(targetStatement!=null) { |  | ||||||
|             val targetScope = when (targetStatement) { |  | ||||||
|                 is Subroutine -> targetStatement |  | ||||||
|                 else -> targetStatement.definingScope() |  | ||||||
|             } |  | ||||||
|             callGraph.add(scope, targetScope) |  | ||||||
|         } |  | ||||||
|         super.visit(functionCall) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private class DirectedGraph<VT> { |  | ||||||
|         private val graph = mutableMapOf<VT, MutableSet<VT>>() |  | ||||||
|         private var uniqueVertices = mutableSetOf<VT>() |  | ||||||
|         val numVertices : Int |  | ||||||
|             get() = uniqueVertices.size |  | ||||||
|  |  | ||||||
|         fun add(from: VT, to: VT) { |  | ||||||
|             var targets = graph[from] |  | ||||||
|             if(targets==null) { |  | ||||||
|                 targets = mutableSetOf() |  | ||||||
|                 graph[from] = targets |  | ||||||
|             } |  | ||||||
|             targets.add(to) |  | ||||||
|             uniqueVertices.add(from) |  | ||||||
|             uniqueVertices.add(to) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun print() { |  | ||||||
|             println("#vertices: $numVertices") |  | ||||||
|             graph.forEach { (from, to) -> |  | ||||||
|                 println("$from   CALLS:") |  | ||||||
|                 to.forEach { println("   $it") } |  | ||||||
|             } |  | ||||||
|             val cycle = checkForCycle() |  | ||||||
|             if(cycle.isNotEmpty()) { |  | ||||||
|                 println("CYCLIC!  $cycle") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun checkForCycle(): MutableList<VT> { |  | ||||||
|             val visited = uniqueVertices.associateWith { false }.toMutableMap() |  | ||||||
|             val recStack = uniqueVertices.associateWith { false }.toMutableMap() |  | ||||||
|             val cycle = mutableListOf<VT>() |  | ||||||
|             for(node in uniqueVertices) { |  | ||||||
|                 if(isCyclicUntil(node, visited, recStack, cycle)) |  | ||||||
|                     return cycle |  | ||||||
|             } |  | ||||||
|             return mutableListOf() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         private fun isCyclicUntil(node: VT, |  | ||||||
|                                   visited: MutableMap<VT, Boolean>, |  | ||||||
|                                   recStack: MutableMap<VT, Boolean>, |  | ||||||
|                                   cycleNodes: MutableList<VT>): Boolean { |  | ||||||
|  |  | ||||||
|             if(recStack[node]==true) return true |  | ||||||
|             if(visited[node]==true) return false |  | ||||||
|  |  | ||||||
|             // mark current node as visited and add to recursion stack |  | ||||||
|             visited[node] = true |  | ||||||
|             recStack[node] = true |  | ||||||
|  |  | ||||||
|             // recurse for all neighbours |  | ||||||
|             val neighbors = graph[node] |  | ||||||
|             if(neighbors!=null) { |  | ||||||
|                 for (neighbour in neighbors) { |  | ||||||
|                     if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) { |  | ||||||
|                         cycleNodes.add(node) |  | ||||||
|                         return true |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // pop node from recursion stack |  | ||||||
|             recStack[node] = false |  | ||||||
|             return false |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,124 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.Node |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class AstVariousTransforms(private val program: Program) : AstWalker() { |  | ||||||
|     private val noModifications = emptyList<IAstModification>() |  | ||||||
|  |  | ||||||
|     override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> { |  | ||||||
|         // is it a struct variable? then define all its struct members as mangled names, |  | ||||||
|         //    and include the original decl as well. |  | ||||||
|         if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) { |  | ||||||
|             val decls = decl.flattenStructMembers() |  | ||||||
|             decls.add(decl) |  | ||||||
|             val result = AnonymousScope(decls, decl.position) |  | ||||||
|             return listOf(IAstModification.ReplaceNode( |  | ||||||
|                     decl, result, parent |  | ||||||
|             )) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { |  | ||||||
|         // For non-kernel subroutines and non-asm parameters: |  | ||||||
|         // inject subroutine params as local variables (if they're not there yet). |  | ||||||
|         val symbolsInSub = subroutine.allDefinedSymbols() |  | ||||||
|         val namesInSub = symbolsInSub.map{ it.first }.toSet() |  | ||||||
|         if(subroutine.asmAddress==null) { |  | ||||||
|             if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) { |  | ||||||
|                 val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet() |  | ||||||
|                 if(!vars.containsAll(subroutine.parameters.map{it.name})) { |  | ||||||
|                     return subroutine.parameters |  | ||||||
|                             .filter { it.name !in namesInSub } |  | ||||||
|                             .map { |  | ||||||
|                                 val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position) |  | ||||||
|                                 IAstModification.InsertFirst(vardecl, subroutine) |  | ||||||
|                             } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { |  | ||||||
|         when { |  | ||||||
|             expr.left is StringLiteralValue -> |  | ||||||
|                 return listOf(IAstModification.ReplaceNode( |  | ||||||
|                         expr, |  | ||||||
|                         processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr), |  | ||||||
|                         parent |  | ||||||
|                 )) |  | ||||||
|             expr.right is StringLiteralValue -> |  | ||||||
|                 return listOf(IAstModification.ReplaceNode( |  | ||||||
|                         expr, |  | ||||||
|                         processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr), |  | ||||||
|                         parent |  | ||||||
|                 )) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { |  | ||||||
|         if(string.parent !is VarDecl) { |  | ||||||
|             // replace the literal string by a identifier reference to a new local vardecl |  | ||||||
|             val vardecl = VarDecl.createAuto(string) |  | ||||||
|             val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position) |  | ||||||
|             return listOf( |  | ||||||
|                     IAstModification.ReplaceNode(string, identifier, parent), |  | ||||||
|                     IAstModification.InsertFirst(vardecl, string.definingScope() as Node) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { |  | ||||||
|         val vardecl = array.parent as? VarDecl |  | ||||||
|         if(vardecl!=null) { |  | ||||||
|             // adjust the datatype of the array (to an educated guess) |  | ||||||
|             val arrayDt = array.type |  | ||||||
|             if(!arrayDt.istype(vardecl.datatype)) { |  | ||||||
|                 val cast = array.cast(vardecl.datatype) |  | ||||||
|                 if (cast != null && cast!=array) |  | ||||||
|                     return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             val arrayDt = array.guessDatatype(program) |  | ||||||
|             if(arrayDt.isKnown) { |  | ||||||
|                 // this array literal is part of an expression, turn it into an identifier reference |  | ||||||
|                 val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT)) |  | ||||||
|                 if(litval2!=null && litval2!=array) { |  | ||||||
|                     val vardecl2 = VarDecl.createAuto(litval2) |  | ||||||
|                     val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) |  | ||||||
|                     return listOf( |  | ||||||
|                             IAstModification.ReplaceNode(array, identifier, parent), |  | ||||||
|                             IAstModification.InsertFirst(vardecl2, array.definingScope() as Node) |  | ||||||
|                     ) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression { |  | ||||||
|         val constvalue = operand.constValue(program) |  | ||||||
|         if(constvalue!=null) { |  | ||||||
|             if (expr.operator == "*") { |  | ||||||
|                 // repeat a string a number of times |  | ||||||
|                 return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if(expr.operator == "+" && operand is StringLiteralValue) { |  | ||||||
|             // concatenate two strings |  | ||||||
|             return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position) |  | ||||||
|         } |  | ||||||
|         return expr |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.Node |  | ||||||
| import prog8.ast.statements.Directive |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class ImportedModuleDirectiveRemover: AstWalker() { |  | ||||||
|     /** |  | ||||||
|      * Most global directives don't apply for imported modules, so remove them |  | ||||||
|      */ |  | ||||||
|  |  | ||||||
|     private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address") |  | ||||||
|     private val noModifications = emptyList<IAstModification>() |  | ||||||
|  |  | ||||||
|     override fun before(directive: Directive, parent: Node): Iterable<IAstModification> { |  | ||||||
|         if(directive.directive in moduleLevelDirectives) { |  | ||||||
|             return listOf(IAstModification.Remove(directive, parent)) |  | ||||||
|         } |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,232 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class StatementReorderer(val program: Program) : AstWalker() { |  | ||||||
|     // Reorders the statements in a way the compiler needs. |  | ||||||
|     // - 'main' block must be the very first statement UNLESS it has an address set. |  | ||||||
|     // - library blocks are put last. |  | ||||||
|     // - blocks are ordered by address, where blocks without address are placed last. |  | ||||||
|     // - in every scope, most directives and vardecls are moved to the top. |  | ||||||
|     // - the 'start' subroutine is moved to the top. |  | ||||||
|     // - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement. |  | ||||||
|     // - (syntax desugaring) struct value assignment is expanded into several struct member assignments. |  | ||||||
|     // - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest> |  | ||||||
|     // - sorts the choices in when statement. |  | ||||||
|     // - insert AddressOf (&) expression where required (string params to a UWORD function param etc). |  | ||||||
|  |  | ||||||
|     private val noModifications = emptyList<IAstModification>() |  | ||||||
|     private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") |  | ||||||
|  |  | ||||||
|     override fun after(module: Module, parent: Node): Iterable<IAstModification> { |  | ||||||
|         val (blocks, other) = module.statements.partition { it is Block } |  | ||||||
|         module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList() |  | ||||||
|  |  | ||||||
|         val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" } |  | ||||||
|         if(mainBlock!=null && mainBlock.address==null) { |  | ||||||
|             module.statements.remove(mainBlock) |  | ||||||
|             module.statements.add(0, mainBlock) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         reorderVardeclsAndDirectives(module.statements) |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) { |  | ||||||
|         val varDecls = statements.filterIsInstance<VarDecl>() |  | ||||||
|         statements.removeAll(varDecls) |  | ||||||
|         statements.addAll(0, varDecls) |  | ||||||
|  |  | ||||||
|         val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove} |  | ||||||
|         statements.removeAll(directives) |  | ||||||
|         statements.addAll(0, directives) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun before(block: Block, parent: Node): Iterable<IAstModification> { |  | ||||||
|         parent as Module |  | ||||||
|         if(block.isInLibrary) { |  | ||||||
|             return listOf( |  | ||||||
|                     IAstModification.Remove(block, parent), |  | ||||||
|                     IAstModification.InsertLast(block, parent) |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         reorderVardeclsAndDirectives(block.statements) |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { |  | ||||||
|         if(subroutine.name=="start" && parent is Block) { |  | ||||||
|             if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") { |  | ||||||
|                 return listOf( |  | ||||||
|                         IAstModification.Remove(subroutine, parent), |  | ||||||
|                         IAstModification.InsertFirst(subroutine, parent) |  | ||||||
|                 ) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> { |  | ||||||
|         val choices = whenStatement.choiceValues(program).sortedBy { |  | ||||||
|             it.first?.first() ?: Int.MAX_VALUE |  | ||||||
|         } |  | ||||||
|         whenStatement.choices.clear() |  | ||||||
|         choices.mapTo(whenStatement.choices) { it.second } |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> { |  | ||||||
|         val valueType = assignment.value.inferType(program) |  | ||||||
|         val targetType = assignment.target.inferType(program, assignment) |  | ||||||
|         if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) { |  | ||||||
|             val assignments = if (assignment.value is ArrayLiteralValue) { |  | ||||||
|                 flattenStructAssignmentFromStructLiteral(assignment, program)    //  'structvar = [ ..... ] ' |  | ||||||
|             } else { |  | ||||||
|                 flattenStructAssignmentFromIdentifier(assignment, program)    //   'structvar1 = structvar2' |  | ||||||
|             } |  | ||||||
|             if(assignments.isNotEmpty()) { |  | ||||||
|                 val modifications = mutableListOf<IAstModification>() |  | ||||||
|                 assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) } |  | ||||||
|                 modifications.add(IAstModification.Remove(assignment, parent)) |  | ||||||
|                 return modifications |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { |  | ||||||
|         // rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand |  | ||||||
|         val binExpr = assignment.value as? BinaryExpression |  | ||||||
|         if(binExpr!=null) { |  | ||||||
|             if(binExpr.left isSameAs assignment.target) { |  | ||||||
|                 // A = A <operator> 5, unchanged |  | ||||||
|                 return noModifications |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(binExpr.operator in associativeOperators) { |  | ||||||
|                 if (binExpr.right isSameAs assignment.target) { |  | ||||||
|                     // A = v <associative-operator> A  ==>  A = A <associative-operator> v |  | ||||||
|                     return listOf(IAstModification.SwapOperands(binExpr)) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 val leftBinExpr = binExpr.left as? BinaryExpression |  | ||||||
|                 if(leftBinExpr?.operator == binExpr.operator) { |  | ||||||
|                     return if(leftBinExpr.left isSameAs assignment.target) { |  | ||||||
|                         // A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y) |  | ||||||
|                         val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position) |  | ||||||
|                         val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position) |  | ||||||
|                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) |  | ||||||
|                     } else { |  | ||||||
|                         // A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y) |  | ||||||
|                         val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position) |  | ||||||
|                         val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position) |  | ||||||
|                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 val rightBinExpr = binExpr.right as? BinaryExpression |  | ||||||
|                 if(rightBinExpr?.operator == binExpr.operator) { |  | ||||||
|                     return if(rightBinExpr.left isSameAs assignment.target) { |  | ||||||
|                         // A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y) |  | ||||||
|                         val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position) |  | ||||||
|                         val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position) |  | ||||||
|                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) |  | ||||||
|                     } else { |  | ||||||
|                         // A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y) |  | ||||||
|                         val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position) |  | ||||||
|                         val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position) |  | ||||||
|                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> { |  | ||||||
|         val identifier = structAssignment.target.identifier!! |  | ||||||
|         val identifierName = identifier.nameInSource.single() |  | ||||||
|         val targetVar = identifier.targetVarDecl(program.namespace)!! |  | ||||||
|         val struct = targetVar.struct!! |  | ||||||
|  |  | ||||||
|         val slv = structAssignment.value as? ArrayLiteralValue |  | ||||||
|         if(slv==null || slv.value.size != struct.numberOfElements) |  | ||||||
|             throw FatalAstException("element count mismatch") |  | ||||||
|  |  | ||||||
|         return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) -> |  | ||||||
|             targetDecl as VarDecl |  | ||||||
|             val mangled = mangledStructMemberName(identifierName, targetDecl.name) |  | ||||||
|             val idref = IdentifierReference(listOf(mangled), structAssignment.position) |  | ||||||
|             val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), |  | ||||||
|                     sourceValue, sourceValue.position) |  | ||||||
|             assign.linkParents(structAssignment) |  | ||||||
|             assign |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> { |  | ||||||
|         val identifier = structAssignment.target.identifier!! |  | ||||||
|         val identifierName = identifier.nameInSource.single() |  | ||||||
|         val targetVar = identifier.targetVarDecl(program.namespace)!! |  | ||||||
|         val struct = targetVar.struct!! |  | ||||||
|         when (structAssignment.value) { |  | ||||||
|             is IdentifierReference -> { |  | ||||||
|                 val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!! |  | ||||||
|                 when { |  | ||||||
|                     sourceVar.struct!=null -> { |  | ||||||
|                         // struct memberwise copy |  | ||||||
|                         val sourceStruct = sourceVar.struct!! |  | ||||||
|                         if(sourceStruct!==targetVar.struct) { |  | ||||||
|                             // structs are not the same in assignment |  | ||||||
|                             return listOf()     // error will be printed elsewhere |  | ||||||
|                         } |  | ||||||
|                         if(struct.statements.size!=sourceStruct.statements.size) |  | ||||||
|                             return listOf()     // error will be printed elsewhere |  | ||||||
|                         return struct.statements.zip(sourceStruct.statements).map { member -> |  | ||||||
|                             val targetDecl = member.first as VarDecl |  | ||||||
|                             val sourceDecl = member.second as VarDecl |  | ||||||
|                             if(targetDecl.name != sourceDecl.name) |  | ||||||
|                                 throw FatalAstException("struct member mismatch") |  | ||||||
|                             val mangled = mangledStructMemberName(identifierName, targetDecl.name) |  | ||||||
|                             val idref = IdentifierReference(listOf(mangled), structAssignment.position) |  | ||||||
|                             val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name) |  | ||||||
|                             val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position) |  | ||||||
|                             val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position) |  | ||||||
|                             assign.linkParents(structAssignment) |  | ||||||
|                             assign |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     sourceVar.isArray -> { |  | ||||||
|                         val array = (sourceVar.value as ArrayLiteralValue).value |  | ||||||
|                         if(struct.statements.size!=array.size) |  | ||||||
|                             return listOf()     // error will be printed elsewhere |  | ||||||
|                         return struct.statements.zip(array).map { |  | ||||||
|                             val decl = it.first as VarDecl |  | ||||||
|                             val mangled = mangledStructMemberName(identifierName, decl.name) |  | ||||||
|                             val targetName = IdentifierReference(listOf(mangled), structAssignment.position) |  | ||||||
|                             val target = AssignTarget(targetName, null, null, structAssignment.position) |  | ||||||
|                             val assign = Assignment(target, it.second, structAssignment.position) |  | ||||||
|                             assign.linkParents(structAssignment) |  | ||||||
|                             assign |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     else -> { |  | ||||||
|                         throw FatalAstException("can only assign arrays or structs to structs") |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             is ArrayLiteralValue -> { |  | ||||||
|                 throw IllegalArgumentException("not going to flatten a structLv assignment here") |  | ||||||
|             } |  | ||||||
|             else -> throw FatalAstException("strange struct value") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.INameScope |  | ||||||
| import prog8.ast.Node |  | ||||||
| import prog8.ast.expressions.NumericLiteralValue |  | ||||||
| import prog8.ast.expressions.TypecastExpression |  | ||||||
| import prog8.ast.statements.AnonymousScope |  | ||||||
| import prog8.ast.statements.NopStatement |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class VariousCleanups: AstWalker() { |  | ||||||
|     private val noModifications = emptyList<IAstModification>() |  | ||||||
|  |  | ||||||
|     override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> { |  | ||||||
|         return listOf(IAstModification.Remove(nopStatement, parent)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { |  | ||||||
|         return if(parent is INameScope) |  | ||||||
|             listOf(ScopeFlatten(scope, parent as INameScope)) |  | ||||||
|         else |  | ||||||
|             noModifications |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification { |  | ||||||
|         override fun perform() { |  | ||||||
|             val idx = into.statements.indexOf(scope) |  | ||||||
|             if(idx>=0) { |  | ||||||
|                 into.statements.addAll(idx+1, scope.statements) |  | ||||||
|                 into.statements.remove(scope) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { |  | ||||||
|         if(typecast.expression is NumericLiteralValue) { |  | ||||||
|             val value = (typecast.expression as NumericLiteralValue).cast(typecast.type) |  | ||||||
|             if(value.isValid) |  | ||||||
|                 return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent)) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return noModifications |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,75 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.IFunctionCall |  | ||||||
| import prog8.ast.INameScope |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.DataType |  | ||||||
| import prog8.ast.expressions.FunctionCall |  | ||||||
| import prog8.ast.statements.BuiltinFunctionStatementPlaceholder |  | ||||||
| import prog8.ast.statements.FunctionCallStatement |  | ||||||
| import prog8.ast.statements.Subroutine |  | ||||||
| import prog8.compiler.CompilerException |  | ||||||
| import prog8.functions.BuiltinFunctions |  | ||||||
|  |  | ||||||
| class VerifyFunctionArgTypes(val program: Program) : IAstVisitor { |  | ||||||
|  |  | ||||||
|     override fun visit(functionCall: FunctionCall) { |  | ||||||
|         val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program) |  | ||||||
|         if(error!=null) |  | ||||||
|             throw CompilerException(error) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCallStatement: FunctionCallStatement) { |  | ||||||
|         val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program) |  | ||||||
|         if (error!=null) |  | ||||||
|             throw CompilerException(error) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|  |  | ||||||
|         private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean { |  | ||||||
|             if(argDt==paramDt) |  | ||||||
|                 return true |  | ||||||
|  |  | ||||||
|             // there are some exceptions that are considered compatible, such as STR <> UWORD |  | ||||||
|             if(argDt==DataType.STR && paramDt==DataType.UWORD || |  | ||||||
|                     argDt==DataType.UWORD && paramDt==DataType.STR) |  | ||||||
|                 return true |  | ||||||
|  |  | ||||||
|             return false |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? { |  | ||||||
|             val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) } |  | ||||||
|             val target = call.target.targetStatement(scope) |  | ||||||
|             if (target is Subroutine) { |  | ||||||
|                 // asmsub types are not checked specifically at this time |  | ||||||
|                 if(call.args.size != target.parameters.size) |  | ||||||
|                     return "invalid number of arguments" |  | ||||||
|                 val paramtypes = target.parameters.map { it.type } |  | ||||||
|                 val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) } |  | ||||||
|                 if(mismatch>=0) { |  | ||||||
|                     val actual = argtypes[mismatch].toString() |  | ||||||
|                     val expected = paramtypes[mismatch].toString() |  | ||||||
|                     return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected" |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else if (target is BuiltinFunctionStatementPlaceholder) { |  | ||||||
|                 val func = BuiltinFunctions.getValue(target.name) |  | ||||||
|                 if(call.args.size != func.parameters.size) |  | ||||||
|                     return "invalid number of arguments" |  | ||||||
|                 val paramtypes = func.parameters.map { it.possibleDatatypes } |  | ||||||
|                 for (x in argtypes.zip(paramtypes).withIndex()) { |  | ||||||
|                     val anyCompatible = x.value.second.any { argTypeCompatible(x.value.first, it) } |  | ||||||
|                     if (!anyCompatible) { |  | ||||||
|                         val actual = x.value.first.toString() |  | ||||||
|                         val expected = x.value.second.toString() |  | ||||||
|                         return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected" |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -5,26 +5,31 @@ import prog8.ast.Node | |||||||
| import prog8.ast.Program | import prog8.ast.Program | ||||||
| import prog8.ast.base.* | import prog8.ast.base.* | ||||||
| import prog8.ast.expressions.* | import prog8.ast.expressions.* | ||||||
| import prog8.ast.processing.AstWalker |  | ||||||
| import prog8.ast.processing.IAstModification |  | ||||||
| import prog8.ast.statements.* | import prog8.ast.statements.* | ||||||
|  | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  | import prog8.ast.walk.IAstVisitor | ||||||
|  | import prog8.compiler.target.ICompilationTarget | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() { | internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() { | ||||||
|  |  | ||||||
|     private val noModifications = emptyList<IAstModification>() |  | ||||||
|  |  | ||||||
|     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { |     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||||
|         subroutineVariables.add(Pair(decl.name, decl)) |         subroutineVariables.add(decl.name to decl) | ||||||
|         if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) { |         if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) { | ||||||
|             // a numeric vardecl without an initial value is initialized with zero, |             // A numeric vardecl without an initial value is initialized with zero, | ||||||
|             // unless there's already an assignment below, that initializes the value |             // unless there's already an assignment below, that initializes the value. | ||||||
|  |             // This allows you to restart the program and have the same starting values of the variables | ||||||
|  |             if(decl.allowInitializeWithZero) | ||||||
|  |             { | ||||||
|                 val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment |                 val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment | ||||||
|             if(nextAssign!=null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY))) |                 if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)) | ||||||
|                     decl.value = null |                     decl.value = null | ||||||
|             else |                 else { | ||||||
|                     decl.value = decl.zeroElementValue() |                     decl.value = decl.zeroElementValue() | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         return noModifications |         return noModifications | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -34,7 +39,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|         // But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF. |         // But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF. | ||||||
|         if(!assignment.isAugmentable |         if(!assignment.isAugmentable | ||||||
|                 && assignment.target.identifier != null |                 && assignment.target.identifier != null | ||||||
|                 && assignment.target.isInRegularRAM(program.namespace)) { |                 && compTarget.isInRegularRAM(assignment.target, program)) { | ||||||
|             val binExpr = assignment.value as? BinaryExpression |             val binExpr = assignment.value as? BinaryExpression | ||||||
|             if (binExpr != null && binExpr.operator !in comparisonOperators) { |             if (binExpr != null && binExpr.operator !in comparisonOperators) { | ||||||
|                 if (binExpr.left !is BinaryExpression) { |                 if (binExpr.left !is BinaryExpression) { | ||||||
| @@ -46,14 +51,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|                             // use the other part of the expression to split. |                             // use the other part of the expression to split. | ||||||
|                             val assignRight = Assignment(assignment.target, binExpr.right, assignment.position) |                             val assignRight = Assignment(assignment.target, binExpr.right, assignment.position) | ||||||
|                             return listOf( |                             return listOf( | ||||||
|                                     IAstModification.InsertBefore(assignment, assignRight, parent), |                                     IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()), | ||||||
|                                     IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr), |                                     IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr), | ||||||
|                                     IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) |                                     IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) | ||||||
|                         } |                         } | ||||||
|                     } else { |                     } else { | ||||||
|                         val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position) |                         val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position) | ||||||
|                         return listOf( |                         return listOf( | ||||||
|                                 IAstModification.InsertBefore(assignment, assignLeft, parent), |                                 IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()), | ||||||
|                                 IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) |                                 IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| @@ -63,30 +68,37 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>() |     private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>() | ||||||
|  |     private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>() | ||||||
|  |  | ||||||
|     override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { |     override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { | ||||||
|         subroutineVariables.clear() |         subroutineVariables.clear() | ||||||
|  |         addedIfConditionVars.clear() | ||||||
|         return noModifications |         return noModifications | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { |     override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { | ||||||
|         val decls = scope.statements.filterIsInstance<VarDecl>() |         val decls = scope.statements.filterIsInstance<VarDecl>().filter { it.type == VarDeclType.VAR } | ||||||
|         subroutineVariables.addAll(decls.map { Pair(it.name, it) }) |         subroutineVariables.addAll(decls.map { it.name to it }) | ||||||
|  |  | ||||||
|         val sub = scope.definingSubroutine() |         val sub = scope.definingSubroutine() | ||||||
|         if (sub != null) { |         if (sub != null) { | ||||||
|             // move vardecls of the scope into the upper scope. Make sure the position remains the same! |             // move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same! | ||||||
|             val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes } |             val replacements = mutableListOf<IAstModification>() | ||||||
|             val replaceVardecls =numericVarsWithValue.map { |             val movements = mutableListOf<IAstModification.InsertFirst>() | ||||||
|                 val initValue = it.value!!  // assume here that value has always been set by now |  | ||||||
|                 it.value = null     // make sure no value init assignment for this vardecl will be created later (would be superfluous) |             for(decl in decls) { | ||||||
|                 val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position) |                 if(decl.value!=null && decl.datatype in NumericDatatypes) { | ||||||
|                 val assign = Assignment(target, initValue, it.position) |                     val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) | ||||||
|                 initValue.parent = assign |                     val assign = Assignment(target, decl.value!!, decl.position) | ||||||
|                 IAstModification.ReplaceNode(it, assign, scope) |                     replacements.add(IAstModification.ReplaceNode(decl, assign, scope)) | ||||||
|  |                     decl.value = null | ||||||
|  |                     decl.allowInitializeWithZero = false | ||||||
|  |                 } else { | ||||||
|  |                     replacements.add(IAstModification.Remove(decl, scope)) | ||||||
|                 } |                 } | ||||||
|             val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) } |                 movements.add(IAstModification.InsertFirst(decl, sub)) | ||||||
|             return replaceVardecls + moveVardeclsUp |             } | ||||||
|  |             return replacements + movements | ||||||
|         } |         } | ||||||
|         return noModifications |         return noModifications | ||||||
|     } |     } | ||||||
| @@ -103,11 +115,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|         } |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|         // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine. |         // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine. | ||||||
|         // and if an assembly block doesn't contain a rts/rti, and some other situations. |         // and if an assembly block doesn't contain a rts/rti, and some other situations. | ||||||
|         val mods = mutableListOf<IAstModification>() |         val mods = mutableListOf<IAstModification>() | ||||||
|         val returnStmt = Return(null, subroutine.position) |         val returnStmt = Return(null, subroutine.position) | ||||||
|         if (subroutine.asmAddress == null |         if (subroutine.asmAddress == null | ||||||
|  |                 && !subroutine.inline | ||||||
|                 && subroutine.statements.isNotEmpty() |                 && subroutine.statements.isNotEmpty() | ||||||
|                 && subroutine.amountOfRtsInAsm() == 0 |                 && subroutine.amountOfRtsInAsm() == 0 | ||||||
|                 && subroutine.statements.lastOrNull { it !is VarDecl } !is Return |                 && subroutine.statements.lastOrNull { it !is VarDecl } !is Return | ||||||
| @@ -124,7 +137,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|                 && outerStatements[subroutineStmtIdx - 1] !is Subroutine |                 && outerStatements[subroutineStmtIdx - 1] !is Subroutine | ||||||
|                 && outerStatements[subroutineStmtIdx - 1] !is Return |                 && outerStatements[subroutineStmtIdx - 1] !is Return | ||||||
|                 && outerScope !is Block) { |                 && outerScope !is Block) { | ||||||
|             mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node) |             mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope) | ||||||
|         } |         } | ||||||
|         return mods |         return mods | ||||||
|     } |     } | ||||||
| @@ -133,7 +146,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|         // see if we can remove superfluous typecasts (outside of expressions) |         // see if we can remove superfluous typecasts (outside of expressions) | ||||||
|         // such as casting byte<->ubyte,  word<->uword |         // such as casting byte<->ubyte,  word<->uword | ||||||
|         // Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of. |         // Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of. | ||||||
|         val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT) |         val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|         if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes |         if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes | ||||||
|                 || typecast.type in WordDatatypes && sourceDt in WordDatatypes) { |                 || typecast.type in WordDatatypes && sourceDt in WordDatatypes) { | ||||||
|             if(typecast.parent !is Expression) { |             if(typecast.parent !is Expression) { | ||||||
| @@ -149,23 +162,21 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|         // The only place for now where we can do this is for: |         // The only place for now where we can do this is for: | ||||||
|         //    asmsub register pair parameter. |         //    asmsub register pair parameter. | ||||||
|  |  | ||||||
|         if(typecast.type in WordDatatypes) { |  | ||||||
|             val fcall = typecast.parent as? IFunctionCall |  | ||||||
|             if (fcall != null) { |  | ||||||
|                 val sub = fcall.target.targetStatement(program.namespace) as? Subroutine |  | ||||||
|                 if (sub != null && sub.isAsmSubroutine) { |  | ||||||
|                     return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent)) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(sourceDt in PassByReferenceDatatypes) { |         if(sourceDt in PassByReferenceDatatypes) { | ||||||
|             if(typecast.type==DataType.UWORD) { |             if(typecast.type==DataType.UWORD) { | ||||||
|  |                 if(typecast.expression is IdentifierReference) { | ||||||
|                     return listOf(IAstModification.ReplaceNode( |                     return listOf(IAstModification.ReplaceNode( | ||||||
|                             typecast, |                             typecast, | ||||||
|                             AddressOf(typecast.expression as IdentifierReference, typecast.position), |                             AddressOf(typecast.expression as IdentifierReference, typecast.position), | ||||||
|                             parent |                             parent | ||||||
|                     )) |                     )) | ||||||
|  |                 } else if(typecast.expression is IFunctionCall) { | ||||||
|  |                     return listOf(IAstModification.ReplaceNode( | ||||||
|  |                             typecast, | ||||||
|  |                             typecast.expression, | ||||||
|  |                             parent | ||||||
|  |                     )) | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|                 errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position) |                 errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position) | ||||||
|             } |             } | ||||||
| @@ -181,9 +192,55 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|             val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position) |             val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position) | ||||||
|             return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement)) |             return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement)) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if((binExpr.operator=="==" || binExpr.operator=="!=") && | ||||||
|  |             (binExpr.left as? NumericLiteralValue)?.number==0 && | ||||||
|  |             (binExpr.right as? NumericLiteralValue)?.number!=0) | ||||||
|  |             throw CompilerException("if 0==X should have been swapped to if X==0") | ||||||
|  |  | ||||||
|  |         // split the conditional expression into separate variables if the operand(s) is not simple. | ||||||
|  |         // DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE    IF X {...}  or  IF NOT X {...} | ||||||
|  | //        val modifications = mutableListOf<IAstModification>() | ||||||
|  | //        if(!binExpr.left.isSimple) { | ||||||
|  | //            val sub = binExpr.definingSubroutine()!! | ||||||
|  | //            val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left) | ||||||
|  | //            if(isNew) | ||||||
|  | //                modifications.add(IAstModification.InsertFirst(variable, sub)) | ||||||
|  | //            modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope)) | ||||||
|  | //            modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr)) | ||||||
|  | //            addedIfConditionVars.add(Pair(sub, variable.name)) | ||||||
|  | //        } | ||||||
|  | //        if(!binExpr.right.isSimple) { | ||||||
|  | //            val sub = binExpr.definingSubroutine()!! | ||||||
|  | //            val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right) | ||||||
|  | //            if(isNew) | ||||||
|  | //                modifications.add(IAstModification.InsertFirst(variable, sub)) | ||||||
|  | //            modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope)) | ||||||
|  | //            modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr)) | ||||||
|  | //            addedIfConditionVars.add(Pair(sub, variable.name)) | ||||||
|  | //        } | ||||||
|  | //        return modifications | ||||||
|         return noModifications |         return noModifications | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | //    private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> { | ||||||
|  | //        val dt = operand.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  | //        val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}" | ||||||
|  | //        val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position) | ||||||
|  | //        val assign = Assignment(tgt, operand, operand.position) | ||||||
|  | //        if(Pair(sub, varname) in addedIfConditionVars) { | ||||||
|  | //            val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position) | ||||||
|  | //            return Triple(vardecl, false, assign) | ||||||
|  | //        } | ||||||
|  | //        val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl? | ||||||
|  | //        return if (existing == null) { | ||||||
|  | //            val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position) | ||||||
|  | //            Triple(vardecl, true, assign) | ||||||
|  | //        } else { | ||||||
|  | //            Triple(existing, false, assign) | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
|  |  | ||||||
|     override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> { |     override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> { | ||||||
|         val binExpr = untilLoop.condition as? BinaryExpression |         val binExpr = untilLoop.condition as? BinaryExpression | ||||||
|         if(binExpr==null || binExpr.operator !in comparisonOperators) { |         if(binExpr==null || binExpr.operator !in comparisonOperators) { | ||||||
| @@ -203,4 +260,95 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | |||||||
|         } |         } | ||||||
|         return noModifications |         return noModifications | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(functionCallStatement.target.nameInSource==listOf("cmp")) { | ||||||
|  |             // if the datatype of the arguments of cmp() are different, cast the byte one to word. | ||||||
|  |             val arg1 = functionCallStatement.args[0] | ||||||
|  |             val arg2 = functionCallStatement.args[1] | ||||||
|  |             val dt1 = arg1.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |             val dt2 = arg2.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |             if(dt1 in ByteDatatypes) { | ||||||
|  |                 if(dt2 in ByteDatatypes) | ||||||
|  |                     return noModifications | ||||||
|  |                 val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position) | ||||||
|  |                 return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement)) | ||||||
|  |             } else { | ||||||
|  |                 if(dt2 in WordDatatypes) | ||||||
|  |                     return noModifications | ||||||
|  |                 val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position) | ||||||
|  |                 return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |  | ||||||
|  |         val containingStatement = getContainingStatement(arrayIndexedExpression) | ||||||
|  |         if(getComplexArrayIndexedExpressions(containingStatement).size > 1) { | ||||||
|  |             errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position) | ||||||
|  |             return noModifications | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         val index = arrayIndexedExpression.indexer.indexExpr | ||||||
|  |         if(index !is NumericLiteralValue && index !is IdentifierReference) { | ||||||
|  |             // replace complex indexing expression with a temp variable to hold the computed index first | ||||||
|  |             return getAutoIndexerVarFor(arrayIndexedExpression) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun getComplexArrayIndexedExpressions(stmt: Statement): List<ArrayIndexedExpression> { | ||||||
|  |  | ||||||
|  |         class Searcher : IAstVisitor { | ||||||
|  |             val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>() | ||||||
|  |             override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { | ||||||
|  |                 val ix = arrayIndexedExpression.indexer.indexExpr | ||||||
|  |                 if(ix !is NumericLiteralValue && ix !is IdentifierReference) | ||||||
|  |                     complexArrayIndexedExpressions.add(arrayIndexedExpression) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             override fun visit(branchStatement: BranchStatement) {} | ||||||
|  |  | ||||||
|  |             override fun visit(forLoop: ForLoop) {} | ||||||
|  |  | ||||||
|  |             override fun visit(ifStatement: IfStatement) { | ||||||
|  |                 ifStatement.condition.accept(this) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             override fun visit(untilLoop: UntilLoop) { | ||||||
|  |                 untilLoop.condition.accept(this) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val searcher = Searcher() | ||||||
|  |         stmt.accept(searcher) | ||||||
|  |         return searcher.complexArrayIndexedExpressions | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun getContainingStatement(expression: Expression): Statement { | ||||||
|  |         var node: Node = expression | ||||||
|  |         while(node !is Statement) | ||||||
|  |             node = node.parent | ||||||
|  |  | ||||||
|  |         return node | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> { | ||||||
|  |         val modifications = mutableListOf<IAstModification>() | ||||||
|  |         val statement = expr.containingStatement() | ||||||
|  |         val dt = expr.indexer.indexExpr.inferType(program) | ||||||
|  |         val register = if(dt.istype(DataType.UBYTE) || dt.istype(DataType.BYTE)) "r9L" else "r9" | ||||||
|  |         // replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...) | ||||||
|  |         // assign the indexing expression to the helper variable, but only if that hasn't been done already | ||||||
|  |         val target = AssignTarget(IdentifierReference(listOf("cx16", register), expr.indexer.position), null, null, expr.indexer.position) | ||||||
|  |         val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position) | ||||||
|  |         modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope())) | ||||||
|  |         modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer)) | ||||||
|  |         return modifications | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,30 @@ | |||||||
| package prog8.compiler | package prog8.compiler | ||||||
|  |  | ||||||
|  | import prog8.ast.AstToSourceCode | ||||||
|  | import prog8.ast.IBuiltinFunctions | ||||||
|  | import prog8.ast.IMemSizer | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.AstException | ||||||
|  | import prog8.ast.base.Position | ||||||
|  | import prog8.ast.expressions.Expression | ||||||
|  | import prog8.ast.expressions.NumericLiteralValue | ||||||
|  | import prog8.ast.statements.Directive | ||||||
|  | import prog8.compiler.astprocessing.* | ||||||
|  | import prog8.compiler.functions.* | ||||||
|  | import prog8.compiler.target.C64Target | ||||||
|  | import prog8.compiler.target.Cx16Target | ||||||
|  | import prog8.compiler.target.ICompilationTarget | ||||||
|  | import prog8.compiler.target.asmGeneratorFor | ||||||
|  | import prog8.optimizer.* | ||||||
|  | import prog8.parser.ModuleImporter | ||||||
|  | import prog8.parser.ParsingFailedError | ||||||
|  | import prog8.parser.moduleName | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.io.InputStream | import java.io.InputStream | ||||||
| import java.nio.file.Path | import java.nio.file.Path | ||||||
| import kotlin.math.abs | import kotlin.system.exitProcess | ||||||
|  | import kotlin.system.measureTimeMillis | ||||||
|  |  | ||||||
|  |  | ||||||
| enum class OutputType { | enum class OutputType { | ||||||
|     RAW, |     RAW, | ||||||
| @@ -28,27 +49,299 @@ data class CompilationOptions(val output: OutputType, | |||||||
|                               val zeropage: ZeropageType, |                               val zeropage: ZeropageType, | ||||||
|                               val zpReserved: List<IntRange>, |                               val zpReserved: List<IntRange>, | ||||||
|                               val floats: Boolean, |                               val floats: Boolean, | ||||||
|                               val noSysInit: Boolean) |                               val noSysInit: Boolean, | ||||||
|  |                               val compTarget: ICompilationTarget) { | ||||||
|  |     var slowCodegenWarnings = false | ||||||
|  |     var optimize = false | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| class CompilerException(message: String?) : Exception(message) | class CompilerException(message: String?) : Exception(message) | ||||||
|  |  | ||||||
| fun Number.toHex(): String { | class CompilationResult(val success: Boolean, | ||||||
|     //  0..15 -> "0".."15" |                         val programAst: Program, | ||||||
|     //  16..255 -> "$10".."$ff" |                         val programName: String, | ||||||
|     //  256..65536 -> "$0100".."$ffff" |                         val compTarget: ICompilationTarget, | ||||||
|     // negative values are prefixed with '-'. |                         val importedFiles: List<Path>) | ||||||
|     val integer = this.toInt() |  | ||||||
|     if(integer<0) |  | ||||||
|         return '-' + abs(integer).toHex() | fun compileProgram(filepath: Path, | ||||||
|     return when (integer) { |                    optimize: Boolean, | ||||||
|         in 0 until 16 -> integer.toString() |                    writeAssembly: Boolean, | ||||||
|         in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0') |                    slowCodegenWarnings: Boolean, | ||||||
|         in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0') |                    compilationTarget: String, | ||||||
|         else -> throw CompilerException("number too large for 16 bits $this") |                    libdirs: List<String>, | ||||||
|  |                    outputDir: Path): CompilationResult { | ||||||
|  |     var programName = "" | ||||||
|  |     lateinit var programAst: Program | ||||||
|  |     lateinit var importedFiles: List<Path> | ||||||
|  |     val errors = ErrorReporter() | ||||||
|  |  | ||||||
|  |     val compTarget = | ||||||
|  |         when(compilationTarget) { | ||||||
|  |             C64Target.name -> C64Target | ||||||
|  |             Cx16Target.name -> Cx16Target | ||||||
|  |             else -> { | ||||||
|  |                 System.err.println("invalid compilation target") | ||||||
|  |                 exitProcess(1) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |         val totalTime = measureTimeMillis { | ||||||
|  |             // import main module and everything it needs | ||||||
|  |             val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, libdirs) | ||||||
|  |             compilationOptions.slowCodegenWarnings = slowCodegenWarnings | ||||||
|  |             compilationOptions.optimize = optimize | ||||||
|  |             programAst = ast | ||||||
|  |             importedFiles = imported | ||||||
|  |             processAst(programAst, errors, compilationOptions) | ||||||
|  |             if (compilationOptions.optimize) | ||||||
|  |                 optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions) | ||||||
|  |             postprocessAst(programAst, errors, compilationOptions) | ||||||
|  |  | ||||||
|  |             // printAst(programAst) | ||||||
|  |  | ||||||
|  |             if(writeAssembly) | ||||||
|  |                 programName = writeAssembly(programAst, errors, outputDir, compilationOptions) | ||||||
|  |         } | ||||||
|  |         System.out.flush() | ||||||
|  |         System.err.flush() | ||||||
|  |         println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") | ||||||
|  |         return CompilationResult(true, programAst, programName, compTarget, importedFiles) | ||||||
|  |  | ||||||
|  |     } catch (px: ParsingFailedError) { | ||||||
|  |         System.err.print("\u001b[91m")  // bright red | ||||||
|  |         System.err.println(px.message) | ||||||
|  |         System.err.print("\u001b[0m")  // reset | ||||||
|  |     } catch (ax: AstException) { | ||||||
|  |         System.err.print("\u001b[91m")  // bright red | ||||||
|  |         System.err.println(ax.toString()) | ||||||
|  |         System.err.print("\u001b[0m")  // reset | ||||||
|  |     } catch (x: Exception) { | ||||||
|  |         print("\u001b[91m")  // bright red | ||||||
|  |         println("\n* internal error *") | ||||||
|  |         print("\u001b[0m")  // reset | ||||||
|  |         System.out.flush() | ||||||
|  |         throw x | ||||||
|  |     } catch (x: NotImplementedError) { | ||||||
|  |         print("\u001b[91m")  // bright red | ||||||
|  |         println("\n* internal error: missing feature/code *") | ||||||
|  |         print("\u001b[0m")  // reset | ||||||
|  |         System.out.flush() | ||||||
|  |         throw x | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget) | ||||||
|  |     return CompilationResult(false, failedProgram, programName, compTarget, emptyList()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions { | ||||||
|  |     lateinit var program: Program | ||||||
|  |  | ||||||
|  |     override val names = functions.keys | ||||||
|  |     override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet() | ||||||
|  |  | ||||||
|  |     override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? { | ||||||
|  |         val func = BuiltinFunctions[name] | ||||||
|  |         if(func!=null) { | ||||||
|  |             val exprfunc = func.constExpressionFunc | ||||||
|  |             if(exprfunc!=null) { | ||||||
|  |                 return try { | ||||||
|  |                     exprfunc(args, position, program, memsizer) | ||||||
|  |                 } catch(x: NotConstArgumentException) { | ||||||
|  |                     // const-evaluating the builtin function call failed. | ||||||
|  |                     null | ||||||
|  |                 } catch(x: CannotEvaluateException) { | ||||||
|  |                     // const-evaluating the builtin function call failed. | ||||||
|  |                     null | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else if(func.known_returntype==null) | ||||||
|  |                 return null  // builtin function $name can't be used here because it doesn't return a value | ||||||
|  |         } | ||||||
|  |         return null | ||||||
|  |     } | ||||||
|  |     override fun returnType(name: String, args: MutableList<Expression>) = | ||||||
|  |         builtinFunctionReturnType(name, args, program) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private fun parseImports(filepath: Path, | ||||||
|  |                          errors: IErrorReporter, | ||||||
|  |                          compTarget: ICompilationTarget, | ||||||
|  |                          libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> { | ||||||
|  |     val compilationTargetName = compTarget.name | ||||||
|  |     println("Compiler target: $compilationTargetName. Parsing...") | ||||||
|  |     val bf = BuiltinFunctionsFacade(BuiltinFunctions) | ||||||
|  |     val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget) | ||||||
|  |     bf.program = programAst | ||||||
|  |  | ||||||
|  |     val importer = ModuleImporter(programAst, compTarget, compilationTargetName, libdirs) | ||||||
|  |     importer.importModule(filepath) | ||||||
|  |     errors.report() | ||||||
|  |  | ||||||
|  |     val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source } | ||||||
|  |     val compilerOptions = determineCompilationOptions(programAst, compTarget) | ||||||
|  |     if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) | ||||||
|  |         throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.") | ||||||
|  |  | ||||||
|  |     // depending on the machine and compiler options we may have to include some libraries | ||||||
|  |     for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName)) | ||||||
|  |         importer.importLibraryModule(lib) | ||||||
|  |  | ||||||
|  |     // always import prog8_lib and math | ||||||
|  |     importer.importLibraryModule("math") | ||||||
|  |     importer.importLibraryModule("prog8_lib") | ||||||
|  |     errors.report() | ||||||
|  |     return Triple(programAst, compilerOptions, importedFiles) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions { | ||||||
|  |     val mainModule = program.mainModule | ||||||
|  |     val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } | ||||||
|  |             as? Directive)?.args?.single()?.name?.uppercase() | ||||||
|  |     val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } | ||||||
|  |             as? Directive)?.args?.single()?.name?.uppercase() | ||||||
|  |     val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" } | ||||||
|  |             as? Directive)?.args?.single()?.name?.uppercase() | ||||||
|  |     val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" } | ||||||
|  |         .flatMap { (it as Directive).args }.toSet() | ||||||
|  |     val floatsEnabled = allOptions.any { it.name == "enable_floats" } | ||||||
|  |     val noSysInit = allOptions.any { it.name == "no_sysinit" } | ||||||
|  |     var zpType: ZeropageType = | ||||||
|  |         if (zpoption == null) | ||||||
|  |             if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE | ||||||
|  |         else | ||||||
|  |             try { | ||||||
|  |                 ZeropageType.valueOf(zpoption) | ||||||
|  |             } catch (x: IllegalArgumentException) { | ||||||
|  |                 ZeropageType.KERNALSAFE | ||||||
|  |                 // error will be printed by the astchecker | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |     if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) { | ||||||
|  |         System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target") | ||||||
|  |         zpType = ZeropageType.BASICSAFE | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val zpReserved = mainModule.statements | ||||||
|  |         .asSequence() | ||||||
|  |         .filter { it is Directive && it.directive == "%zpreserved" } | ||||||
|  |         .map { (it as Directive).args } | ||||||
|  |         .map { it[0].int!!..it[1].int!! } | ||||||
|  |         .toList() | ||||||
|  |  | ||||||
|  |     if (outputType != null && !OutputType.values().any { it.name == outputType }) { | ||||||
|  |         System.err.println("invalid output type $outputType") | ||||||
|  |         exitProcess(1) | ||||||
|  |     } | ||||||
|  |     if (launcherType != null && !LauncherType.values().any { it.name == launcherType }) { | ||||||
|  |         System.err.println("invalid launcher type $launcherType") | ||||||
|  |         exitProcess(1) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return CompilationOptions( | ||||||
|  |         if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), | ||||||
|  |         if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), | ||||||
|  |         zpType, zpReserved, floatsEnabled, noSysInit, | ||||||
|  |         compTarget | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { | ||||||
|  |     // perform initial syntax checks and processings | ||||||
|  |     println("Processing for target ${compilerOptions.compTarget.name}...") | ||||||
|  |     programAst.checkIdentifiers(errors, compilerOptions) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.constantFold(errors, compilerOptions.compTarget) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.reorderStatements(errors) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.addTypecasts(errors) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.variousCleanups(programAst, errors) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.checkIdentifiers(errors, compilerOptions) | ||||||
|  |     errors.report() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) { | ||||||
|  |     // optimize the parse tree | ||||||
|  |     println("Optimizing...") | ||||||
|  |  | ||||||
|  |     val remover = UnusedCodeRemover(programAst, errors, compTarget) | ||||||
|  |     remover.visit(programAst) | ||||||
|  |     remover.applyModifications() | ||||||
|  |  | ||||||
|  |     while (true) { | ||||||
|  |         // keep optimizing expressions and statements until no more steps remain | ||||||
|  |         val optsDone1 = programAst.simplifyExpressions() | ||||||
|  |         val optsDone2 = programAst.splitBinaryExpressions(compTarget) | ||||||
|  |         val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget) | ||||||
|  |         programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away | ||||||
|  |         errors.report() | ||||||
|  |         if (optsDone1 + optsDone2 + optsDone3 == 0) | ||||||
|  |             break | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val inliner = SubroutineInliner(programAst, errors, options) | ||||||
|  |     inliner.visit(programAst) | ||||||
|  |     errors.report() | ||||||
|  |     if(errors.noErrors()) { | ||||||
|  |         inliner.applyModifications() | ||||||
|  |         inliner.fixCallsToInlinedSubroutines() | ||||||
|  |         val remover2 = UnusedCodeRemover(programAst, errors, compTarget) | ||||||
|  |         remover2.visit(programAst) | ||||||
|  |         remover2.applyModifications() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     errors.report() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { | ||||||
|  |     programAst.addTypecasts(errors) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.variousCleanups(programAst, errors) | ||||||
|  |     programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)          // check if final tree is still valid | ||||||
|  |     errors.report() | ||||||
|  |     val callGraph = CallGraph(programAst) | ||||||
|  |     callGraph.checkRecursiveCalls(errors) | ||||||
|  |     errors.report() | ||||||
|  |     programAst.verifyFunctionArgTypes() | ||||||
|  |     programAst.moveMainAndStartToFirst() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | private fun writeAssembly(programAst: Program, | ||||||
|  |                           errors: IErrorReporter, | ||||||
|  |                           outputDir: Path, | ||||||
|  |                           compilerOptions: CompilationOptions): String { | ||||||
|  |     // asm generation directly from the Ast | ||||||
|  |     programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget) | ||||||
|  |     errors.report() | ||||||
|  |  | ||||||
|  |     // printAst(programAst) | ||||||
|  |  | ||||||
|  |     compilerOptions.compTarget.machine.initializeZeropage(compilerOptions) | ||||||
|  |     val assembly = asmGeneratorFor(compilerOptions.compTarget, | ||||||
|  |             programAst, | ||||||
|  |             errors, | ||||||
|  |             compilerOptions.compTarget.machine.zeropage, | ||||||
|  |             compilerOptions, | ||||||
|  |             outputDir).compileToAssembly() | ||||||
|  |     assembly.assemble(compilerOptions) | ||||||
|  |     errors.report() | ||||||
|  |     return assembly.name | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fun printAst(programAst: Program) { | ||||||
|  |     println() | ||||||
|  |     val printer = AstToSourceCode(::print, programAst) | ||||||
|  |     printer.visit(programAst) | ||||||
|  |     println() | ||||||
|  | } | ||||||
|  |  | ||||||
| fun loadAsmIncludeFile(filename: String, source: Path): String { | fun loadAsmIncludeFile(filename: String, source: Path): String { | ||||||
|     return if (filename.startsWith("library:")) { |     return if (filename.startsWith("library:")) { | ||||||
|         val resource = tryGetEmbeddedResource(filename.substring(8)) |         val resource = tryGetEmbeddedResource(filename.substring(8)) | ||||||
|   | |||||||
| @@ -1,9 +1,18 @@ | |||||||
| package prog8.ast.base | package prog8.compiler | ||||||
| 
 | 
 | ||||||
|  | import prog8.ast.base.Position | ||||||
| import prog8.parser.ParsingFailedError | import prog8.parser.ParsingFailedError | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ErrorReporter { | interface IErrorReporter { | ||||||
|  |     fun err(msg: String, position: Position) | ||||||
|  |     fun warn(msg: String, position: Position) | ||||||
|  |     fun noErrors(): Boolean | ||||||
|  |     fun report() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | internal class ErrorReporter: IErrorReporter { | ||||||
|     private enum class MessageSeverity { |     private enum class MessageSeverity { | ||||||
|         WARNING, |         WARNING, | ||||||
|         ERROR |         ERROR | ||||||
| @@ -13,10 +22,14 @@ class ErrorReporter { | |||||||
|     private val messages = mutableListOf<CompilerMessage>() |     private val messages = mutableListOf<CompilerMessage>() | ||||||
|     private val alreadyReportedMessages = mutableSetOf<String>() |     private val alreadyReportedMessages = mutableSetOf<String>() | ||||||
| 
 | 
 | ||||||
|     fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position)) |     override fun err(msg: String, position: Position) { | ||||||
|     fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position)) |         messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position)) | ||||||
|  |     } | ||||||
|  |     override fun warn(msg: String, position: Position) { | ||||||
|  |         messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position)) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     fun handle() { |     override fun report() { | ||||||
|         var numErrors = 0 |         var numErrors = 0 | ||||||
|         var numWarnings = 0 |         var numWarnings = 0 | ||||||
|         messages.forEach { |         messages.forEach { | ||||||
| @@ -40,5 +53,5 @@ class ErrorReporter { | |||||||
|             throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.") |             throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun isEmpty() = messages.isEmpty() |     override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR } | ||||||
| } | } | ||||||
| @@ -1,242 +0,0 @@ | |||||||
| package prog8.compiler |  | ||||||
|  |  | ||||||
| import prog8.ast.AstToSourceCode |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.statements.Directive |  | ||||||
| import prog8.compiler.target.C64Target |  | ||||||
| import prog8.compiler.target.CompilationTarget |  | ||||||
| import prog8.compiler.target.Cx16Target |  | ||||||
| import prog8.optimizer.* |  | ||||||
| import prog8.optimizer.UnusedCodeRemover |  | ||||||
| import prog8.optimizer.constantFold |  | ||||||
| import prog8.optimizer.optimizeStatements |  | ||||||
| import prog8.optimizer.simplifyExpressions |  | ||||||
| import prog8.parser.ModuleImporter |  | ||||||
| import prog8.parser.ParsingFailedError |  | ||||||
| import prog8.parser.moduleName |  | ||||||
| import java.nio.file.Path |  | ||||||
| import kotlin.system.exitProcess |  | ||||||
| import kotlin.system.measureTimeMillis |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CompilationResult(val success: Boolean, |  | ||||||
|                         val programAst: Program, |  | ||||||
|                         val programName: String, |  | ||||||
|                         val importedFiles: List<Path>) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fun compileProgram(filepath: Path, |  | ||||||
|                    optimize: Boolean, |  | ||||||
|                    writeAssembly: Boolean, |  | ||||||
|                    compilationTarget: String, |  | ||||||
|                    outputDir: Path): CompilationResult { |  | ||||||
|     var programName = "" |  | ||||||
|     lateinit var programAst: Program |  | ||||||
|     lateinit var importedFiles: List<Path> |  | ||||||
|     val errors = ErrorReporter() |  | ||||||
|  |  | ||||||
|     when(compilationTarget) { |  | ||||||
|         C64Target.name -> CompilationTarget.instance = C64Target |  | ||||||
|         Cx16Target.name -> CompilationTarget.instance = Cx16Target |  | ||||||
|         else -> { |  | ||||||
|             System.err.println("invalid compilation target") |  | ||||||
|             exitProcess(1) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|         val totalTime = measureTimeMillis { |  | ||||||
|             // import main module and everything it needs |  | ||||||
|             val (ast, compilationOptions, imported) = parseImports(filepath, errors) |  | ||||||
|             programAst = ast |  | ||||||
|             importedFiles = imported |  | ||||||
|             processAst(programAst, errors, compilationOptions) |  | ||||||
|             if (optimize) |  | ||||||
|                 optimizeAst(programAst, errors) |  | ||||||
|             postprocessAst(programAst, errors, compilationOptions) |  | ||||||
|  |  | ||||||
|             // printAst(programAst) |  | ||||||
|  |  | ||||||
|             if(writeAssembly) |  | ||||||
|                 programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions) |  | ||||||
|         } |  | ||||||
|         System.out.flush() |  | ||||||
|         System.err.flush() |  | ||||||
|         println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") |  | ||||||
|         return CompilationResult(true, programAst, programName, importedFiles) |  | ||||||
|  |  | ||||||
|     } catch (px: ParsingFailedError) { |  | ||||||
|         System.err.print("\u001b[91m")  // bright red |  | ||||||
|         System.err.println(px.message) |  | ||||||
|         System.err.print("\u001b[0m")  // reset |  | ||||||
|     } catch (ax: AstException) { |  | ||||||
|         System.err.print("\u001b[91m")  // bright red |  | ||||||
|         System.err.println(ax.toString()) |  | ||||||
|         System.err.print("\u001b[0m")  // reset |  | ||||||
|     } catch (x: Exception) { |  | ||||||
|         print("\u001b[91m")  // bright red |  | ||||||
|         println("\n* internal error *") |  | ||||||
|         print("\u001b[0m")  // reset |  | ||||||
|         System.out.flush() |  | ||||||
|         throw x |  | ||||||
|     } catch (x: NotImplementedError) { |  | ||||||
|         print("\u001b[91m")  // bright red |  | ||||||
|         println("\n* internal error: missing feature/code *") |  | ||||||
|         print("\u001b[0m")  // reset |  | ||||||
|         System.out.flush() |  | ||||||
|         throw x |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> { |  | ||||||
|     println("Compiler target: ${CompilationTarget.instance.name}. Parsing...") |  | ||||||
|     val importer = ModuleImporter() |  | ||||||
|     val programAst = Program(moduleName(filepath.fileName), mutableListOf()) |  | ||||||
|     importer.importModule(programAst, filepath) |  | ||||||
|     errors.handle() |  | ||||||
|  |  | ||||||
|     val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source } |  | ||||||
|  |  | ||||||
|     val compilerOptions = determineCompilationOptions(programAst) |  | ||||||
|     if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) |  | ||||||
|         throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.") |  | ||||||
|  |  | ||||||
|     // depending on the machine and compiler options we may have to include some libraries |  | ||||||
|     CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst) |  | ||||||
|  |  | ||||||
|     // always import prog8lib and math |  | ||||||
|     importer.importLibraryModule(programAst, "math") |  | ||||||
|     importer.importLibraryModule(programAst, "prog8lib") |  | ||||||
|     errors.handle() |  | ||||||
|     return Triple(programAst, compilerOptions, importedFiles) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun determineCompilationOptions(program: Program): CompilationOptions { |  | ||||||
|     val mainModule = program.modules.first() |  | ||||||
|     val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } |  | ||||||
|             as? Directive)?.args?.single()?.name?.toUpperCase() |  | ||||||
|     val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } |  | ||||||
|             as? Directive)?.args?.single()?.name?.toUpperCase() |  | ||||||
|     mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" } |  | ||||||
|             as? Directive)?.args?.single()?.int ?: 0 |  | ||||||
|     val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" } |  | ||||||
|             as? Directive)?.args?.single()?.name?.toUpperCase() |  | ||||||
|     val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet() |  | ||||||
|     val floatsEnabled = allOptions.any { it.name == "enable_floats" } |  | ||||||
|     val noSysInit = allOptions.any { it.name == "no_sysinit" } |  | ||||||
|     var zpType: ZeropageType = |  | ||||||
|             if (zpoption == null) |  | ||||||
|                 if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE |  | ||||||
|             else |  | ||||||
|                 try { |  | ||||||
|                     ZeropageType.valueOf(zpoption) |  | ||||||
|                 } catch (x: IllegalArgumentException) { |  | ||||||
|                     ZeropageType.KERNALSAFE |  | ||||||
|                     // error will be printed by the astchecker |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|     if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) { |  | ||||||
|         System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe") |  | ||||||
|         zpType = ZeropageType.BASICSAFE |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val zpReserved = mainModule.statements |  | ||||||
|             .asSequence() |  | ||||||
|             .filter { it is Directive && it.directive == "%zpreserved" } |  | ||||||
|             .map { (it as Directive).args } |  | ||||||
|             .map { it[0].int!!..it[1].int!! } |  | ||||||
|             .toList() |  | ||||||
|  |  | ||||||
|     if(outputType!=null && !OutputType.values().any {it.name==outputType}) { |  | ||||||
|         System.err.println("invalid output type $outputType") |  | ||||||
|         exitProcess(1) |  | ||||||
|     } |  | ||||||
|     if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) { |  | ||||||
|         System.err.println("invalid launcher type $launcherType") |  | ||||||
|         exitProcess(1) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return CompilationOptions( |  | ||||||
|             if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), |  | ||||||
|             if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), |  | ||||||
|             zpType, zpReserved, floatsEnabled, noSysInit |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) { |  | ||||||
|     // perform initial syntax checks and processings |  | ||||||
|     println("Processing for target ${CompilationTarget.instance.name}...") |  | ||||||
|     programAst.checkIdentifiers(errors) |  | ||||||
|     errors.handle() |  | ||||||
|     programAst.constantFold(errors) |  | ||||||
|     errors.handle() |  | ||||||
|     programAst.reorderStatements() |  | ||||||
|     programAst.addTypecasts(errors) |  | ||||||
|     errors.handle() |  | ||||||
|     programAst.variousCleanups() |  | ||||||
|     programAst.checkValid(compilerOptions, errors) |  | ||||||
|     errors.handle() |  | ||||||
|     programAst.checkIdentifiers(errors) |  | ||||||
|     errors.handle() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun optimizeAst(programAst: Program, errors: ErrorReporter) { |  | ||||||
|     // optimize the parse tree |  | ||||||
|     println("Optimizing...") |  | ||||||
|     while (true) { |  | ||||||
|         // keep optimizing expressions and statements until no more steps remain |  | ||||||
|         val optsDone1 = programAst.simplifyExpressions() |  | ||||||
|         val optsDone2 = programAst.optimizeStatements(errors) |  | ||||||
|         programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away |  | ||||||
|         errors.handle() |  | ||||||
|         if (optsDone1 + optsDone2 == 0) |  | ||||||
|             break |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val remover = UnusedCodeRemover(programAst, errors) |  | ||||||
|     remover.visit(programAst) |  | ||||||
|     remover.applyModifications() |  | ||||||
|     errors.handle() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) { |  | ||||||
|     programAst.addTypecasts(errors) |  | ||||||
|     errors.handle() |  | ||||||
|     programAst.variousCleanups() |  | ||||||
|     programAst.checkValid(compilerOptions, errors)          // check if final tree is still valid |  | ||||||
|     errors.handle() |  | ||||||
|     programAst.checkRecursion(errors)         // check if there are recursive subroutine calls |  | ||||||
|     errors.handle() |  | ||||||
|     programAst.verifyFunctionArgTypes() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path, |  | ||||||
|                           optimize: Boolean, compilerOptions: CompilationOptions): String { |  | ||||||
|     // asm generation directly from the Ast, |  | ||||||
|     programAst.processAstBeforeAsmGeneration(errors) |  | ||||||
|     errors.handle() |  | ||||||
|  |  | ||||||
|     // printAst(programAst) |  | ||||||
|  |  | ||||||
|     CompilationTarget.instance.machine.initializeZeropage(compilerOptions) |  | ||||||
|     val assembly = CompilationTarget.instance.asmGenerator( |  | ||||||
|             programAst, |  | ||||||
|             errors, |  | ||||||
|             CompilationTarget.instance.machine.zeropage, |  | ||||||
|             compilerOptions, |  | ||||||
|             outputDir).compileToAssembly(optimize) |  | ||||||
|     assembly.assemble(compilerOptions) |  | ||||||
|     errors.handle() |  | ||||||
|     return assembly.name |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun printAst(programAst: Program) { |  | ||||||
|     println() |  | ||||||
|     val printer = AstToSourceCode(::print, programAst) |  | ||||||
|     printer.visit(programAst) |  | ||||||
|     println() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @@ -19,9 +19,31 @@ abstract class Zeropage(protected val options: CompilationOptions) { | |||||||
|  |  | ||||||
|     val allowedDatatypes = NumericDatatypes |     val allowedDatatypes = NumericDatatypes | ||||||
|  |  | ||||||
|     fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size |     fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size | ||||||
|  |     fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty() | ||||||
|  |     fun availableWords(): Int { | ||||||
|  |         if(options.zeropage==ZeropageType.DONTUSE) | ||||||
|  |             return 0 | ||||||
|  |  | ||||||
|     fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int { |         val words = free.windowed(2).filter { it[0] == it[1]-1 } | ||||||
|  |         var nonOverlappingWordsCount = 0 | ||||||
|  |         var prevMsbLoc = -1 | ||||||
|  |         for(w in words) { | ||||||
|  |             if(w[0]!=prevMsbLoc) { | ||||||
|  |                 nonOverlappingWordsCount++ | ||||||
|  |                 prevMsbLoc = w[1] | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return nonOverlappingWordsCount | ||||||
|  |     } | ||||||
|  |     fun hasWordAvailable(): Boolean { | ||||||
|  |         if(options.zeropage==ZeropageType.DONTUSE) | ||||||
|  |             return false | ||||||
|  |  | ||||||
|  |         return free.windowed(2).any { it[0] == it[1] - 1 } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int { | ||||||
|         assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"} |         assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"} | ||||||
|  |  | ||||||
|         if(options.zeropage==ZeropageType.DONTUSE) |         if(options.zeropage==ZeropageType.DONTUSE) | ||||||
| @@ -64,7 +86,7 @@ abstract class Zeropage(protected val options: CompilationOptions) { | |||||||
|  |  | ||||||
|     private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { |     private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { | ||||||
|         free.removeAll(address until address+size) |         free.removeAll(address until address+size) | ||||||
|         allocations[address] = Pair(name ?: "<unnamed>", datatype) |         allocations[address] = (name ?: "<unnamed>") to datatype | ||||||
|         return address |         return address | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										104
									
								
								compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								compiler/src/prog8/compiler/astprocessing/AstExtensions.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.FatalAstException | ||||||
|  | import prog8.ast.statements.Directive | ||||||
|  | import prog8.compiler.BeforeAsmGenerationAstChanger | ||||||
|  | import prog8.compiler.CompilationOptions | ||||||
|  | import prog8.compiler.IErrorReporter | ||||||
|  | import prog8.compiler.target.ICompilationTarget | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) { | ||||||
|  |     val checker = AstChecker(this, compilerOptions, errors, compTarget) | ||||||
|  |     checker.visit(this) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) { | ||||||
|  |     val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget) | ||||||
|  |     fixer.visit(this) | ||||||
|  |     while(errors.noErrors() && fixer.applyModifications()>0) { | ||||||
|  |         fixer.visit(this) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal fun Program.reorderStatements(errors: IErrorReporter) { | ||||||
|  |     val reorder = StatementReorderer(this, errors) | ||||||
|  |     reorder.visit(this) | ||||||
|  |     if(errors.noErrors()) { | ||||||
|  |         reorder.applyModifications() | ||||||
|  |         reorder.visit(this) | ||||||
|  |         if(errors.noErrors()) | ||||||
|  |             reorder.applyModifications() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal fun Program.addTypecasts(errors: IErrorReporter) { | ||||||
|  |     val caster = TypecastsAdder(this, errors) | ||||||
|  |     caster.visit(this) | ||||||
|  |     caster.applyModifications() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal fun Program.verifyFunctionArgTypes() { | ||||||
|  |     val fixer = VerifyFunctionArgTypes(this) | ||||||
|  |     fixer.visit(this) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) { | ||||||
|  |  | ||||||
|  |     val checker2 = AstIdentifiersChecker(this, errors, options.compTarget) | ||||||
|  |     checker2.visit(this) | ||||||
|  |  | ||||||
|  |     if(errors.noErrors()) { | ||||||
|  |         val transforms = AstVariousTransforms(this) | ||||||
|  |         transforms.visit(this) | ||||||
|  |         transforms.applyModifications() | ||||||
|  |         val lit2decl = LiteralsToAutoVars(this) | ||||||
|  |         lit2decl.visit(this) | ||||||
|  |         lit2decl.applyModifications() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (modules.map { it.name }.toSet().size != modules.size) { | ||||||
|  |         throw FatalAstException("modules should all be unique") | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) { | ||||||
|  |     val process = VariousCleanups(program, errors) | ||||||
|  |     process.visit(this) | ||||||
|  |     if(errors.noErrors()) | ||||||
|  |         process.applyModifications() | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal fun Program.moveMainAndStartToFirst() { | ||||||
|  |     // the module containing the program entrypoint is moved to the first in the sequence. | ||||||
|  |     // the "main" block containing the entrypoint is moved to the top in there, | ||||||
|  |     // and finally the entrypoint subroutine "start" itself is moved to the top in that block. | ||||||
|  |  | ||||||
|  |     val directives = modules[0].statements.filterIsInstance<Directive>() | ||||||
|  |     val start = this.entrypoint() | ||||||
|  |     val mod = start.definingModule() | ||||||
|  |     val block = start.definingBlock() | ||||||
|  |     if(!modules.remove(mod)) | ||||||
|  |         throw FatalAstException("module wrong") | ||||||
|  |     modules.add(0, mod) | ||||||
|  |     mod.remove(block) | ||||||
|  |     var afterDirective = mod.statements.indexOfFirst { it !is Directive } | ||||||
|  |     if(afterDirective<0) | ||||||
|  |         mod.statements.add(block) | ||||||
|  |     else | ||||||
|  |         mod.statements.add(afterDirective, block) | ||||||
|  |     block.remove(start) | ||||||
|  |     afterDirective = block.statements.indexOfFirst { it !is Directive } | ||||||
|  |     if(afterDirective<0) | ||||||
|  |         block.statements.add(start) | ||||||
|  |     else | ||||||
|  |         block.statements.add(afterDirective, start) | ||||||
|  |  | ||||||
|  |     // overwrite the directives in the module containing the entrypoint | ||||||
|  |     for(directive in directives) { | ||||||
|  |         modules[0].statements.removeAll { it is Directive && it.directive == directive.directive } | ||||||
|  |         modules[0].statements.add(0, directive) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,14 +1,16 @@ | |||||||
| package prog8.ast.processing | package prog8.compiler.astprocessing | ||||||
| 
 | 
 | ||||||
| import prog8.ast.Module | import prog8.ast.Module | ||||||
| import prog8.ast.Program | import prog8.ast.Program | ||||||
| import prog8.ast.base.* | import prog8.ast.base.Position | ||||||
| import prog8.ast.expressions.* | import prog8.ast.expressions.StringLiteralValue | ||||||
| import prog8.ast.statements.* | import prog8.ast.statements.* | ||||||
| import prog8.compiler.target.CompilationTarget | import prog8.ast.walk.IAstVisitor | ||||||
| import prog8.functions.BuiltinFunctions | import prog8.compiler.IErrorReporter | ||||||
|  | import prog8.compiler.functions.BuiltinFunctions | ||||||
|  | import prog8.compiler.target.ICompilationTarget | ||||||
| 
 | 
 | ||||||
| internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor { | internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor { | ||||||
|     private var blocks = mutableMapOf<String, Block>() |     private var blocks = mutableMapOf<String, Block>() | ||||||
| 
 | 
 | ||||||
|     private fun nameError(name: String, position: Position, existing: Statement) { |     private fun nameError(name: String, position: Position, existing: Statement) { | ||||||
| @@ -22,7 +24,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun visit(block: Block) { |     override fun visit(block: Block) { | ||||||
|         if(block.name in CompilationTarget.instance.machine.opcodeNames) |         if(block.name in compTarget.machine.opcodeNames) | ||||||
|             errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position) |             errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position) | ||||||
| 
 | 
 | ||||||
|         val existing = blocks[block.name] |         val existing = blocks[block.name] | ||||||
| @@ -31,14 +33,21 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | |||||||
|         else |         else | ||||||
|             blocks[block.name] = block |             blocks[block.name] = block | ||||||
| 
 | 
 | ||||||
|  |         if(!block.isInLibrary) { | ||||||
|  |             val libraries = program.modules.filter { it.isLibraryModule } | ||||||
|  |             val libraryBlockNames = libraries.flatMap { it.statements.filterIsInstance<Block>().map { b -> b.name } } | ||||||
|  |             if(block.name in libraryBlockNames) | ||||||
|  |                 errors.err("block is already defined in an included library module", block.position) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         super.visit(block) |         super.visit(block) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun visit(directive: Directive) { |     override fun visit(directive: Directive) { | ||||||
|         if(directive.directive=="%target") { |         if(directive.directive=="%target") { | ||||||
|             val compatibleTarget = directive.args.single().name |             val compatibleTarget = directive.args.single().name | ||||||
|             if (compatibleTarget != CompilationTarget.instance.name) |             if (compatibleTarget != compTarget.name) | ||||||
|                 errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position) |                 errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         super.visit(directive) |         super.visit(directive) | ||||||
| @@ -50,41 +59,23 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | |||||||
|         if(decl.name in BuiltinFunctions) |         if(decl.name in BuiltinFunctions) | ||||||
|             errors.err("builtin function cannot be redefined", decl.position) |             errors.err("builtin function cannot be redefined", decl.position) | ||||||
| 
 | 
 | ||||||
|         if(decl.name in CompilationTarget.instance.machine.opcodeNames) |         if(decl.name in compTarget.machine.opcodeNames) | ||||||
|             errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) |             errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) | ||||||
| 
 | 
 | ||||||
|         if(decl.datatype==DataType.STRUCT) { |  | ||||||
|             if (decl.structHasBeenFlattened) |  | ||||||
|                 return super.visit(decl)    // don't do this multiple times |  | ||||||
| 
 |  | ||||||
|             if (decl.struct == null) { |  | ||||||
|                 errors.err("undefined struct type", decl.position) |  | ||||||
|                 return super.visit(decl) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes }) |  | ||||||
|                 return super.visit(decl)     // a non-numeric member, not supported. proper error is given by AstChecker later |  | ||||||
| 
 |  | ||||||
|             if (decl.value is NumericLiteralValue) { |  | ||||||
|                 errors.err("you cannot initialize a struct using a single value", decl.position) |  | ||||||
|                 return super.visit(decl) |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (decl.value != null && decl.value !is ArrayLiteralValue) { |  | ||||||
|                 errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position) |  | ||||||
|                 return super.visit(decl) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         val existing = program.namespace.lookup(listOf(decl.name), decl) |         val existing = program.namespace.lookup(listOf(decl.name), decl) | ||||||
|         if (existing != null && existing !== decl) |         if (existing != null && existing !== decl) | ||||||
|             nameError(decl.name, decl.position, existing) |             nameError(decl.name, decl.position, existing) | ||||||
| 
 | 
 | ||||||
|  |         if(decl.definingBlock().name==decl.name) | ||||||
|  |             nameError(decl.name, decl.position, decl.definingBlock()) | ||||||
|  |         if(decl.definingSubroutine()?.name==decl.name) | ||||||
|  |             nameError(decl.name, decl.position, decl.definingSubroutine()!!) | ||||||
|  | 
 | ||||||
|         super.visit(decl) |         super.visit(decl) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun visit(subroutine: Subroutine) { |     override fun visit(subroutine: Subroutine) { | ||||||
|         if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) { |         if(subroutine.name in compTarget.machine.opcodeNames) { | ||||||
|             errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) |             errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) | ||||||
|         } else if(subroutine.name in BuiltinFunctions) { |         } else if(subroutine.name in BuiltinFunctions) { | ||||||
|             // the builtin functions can't be redefined |             // the builtin functions can't be redefined | ||||||
| @@ -98,7 +89,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | |||||||
|             if (existing != null && existing !== subroutine) |             if (existing != null && existing !== subroutine) | ||||||
|                 nameError(subroutine.name, subroutine.position, existing) |                 nameError(subroutine.name, subroutine.position, existing) | ||||||
| 
 | 
 | ||||||
|             // check that there are no local variables, labels, or other subs that redefine the subroutine's parameters |             // check that there are no local variables, labels, or other subs that redefine the subroutine's parameters. Blocks are okay. | ||||||
|             val symbolsInSub = subroutine.allDefinedSymbols() |             val symbolsInSub = subroutine.allDefinedSymbols() | ||||||
|             val namesInSub = symbolsInSub.map{ it.first }.toSet() |             val namesInSub = symbolsInSub.map{ it.first }.toSet() | ||||||
|             val paramNames = subroutine.parameters.map { it.name }.toSet() |             val paramNames = subroutine.parameters.map { it.name }.toSet() | ||||||
| @@ -107,9 +98,9 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | |||||||
|                 val labelOrVar = subroutine.getLabelOrVariable(name) |                 val labelOrVar = subroutine.getLabelOrVariable(name) | ||||||
|                 if(labelOrVar!=null && labelOrVar.position != subroutine.position) |                 if(labelOrVar!=null && labelOrVar.position != subroutine.position) | ||||||
|                     nameError(name, labelOrVar.position, subroutine) |                     nameError(name, labelOrVar.position, subroutine) | ||||||
|                 val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name} |                 val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name} | ||||||
|                 if(sub!=null) |                 if(sub!=null) | ||||||
|                     nameError(name, sub.position, subroutine) |                     nameError(name, subroutine.position, sub) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { |             if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { | ||||||
| @@ -121,7 +112,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun visit(label: Label) { |     override fun visit(label: Label) { | ||||||
|         if(label.name in CompilationTarget.instance.machine.opcodeNames) |         if(label.name in compTarget.machine.opcodeNames) | ||||||
|             errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) |             errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) | ||||||
| 
 | 
 | ||||||
|         if(label.name in BuiltinFunctions) { |         if(label.name in BuiltinFunctions) { | ||||||
| @@ -143,19 +134,9 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun visit(string: StringLiteralValue) { |     override fun visit(string: StringLiteralValue) { | ||||||
|         if (string.value.length !in 1..255) |         if (string.value.length > 255) | ||||||
|             errors.err("string literal length must be between 1 and 255", string.position) |             errors.err("string literal length max is 255", string.position) | ||||||
| 
 | 
 | ||||||
|         super.visit(string) |         super.visit(string) | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     override fun visit(structDecl: StructDecl) { |  | ||||||
|         for(member in structDecl.statements){ |  | ||||||
|             val decl = member as? VarDecl |  | ||||||
|             if(decl!=null && decl.datatype !in NumericDatatypes) |  | ||||||
|                 errors.err("structs can only contain numerical types", decl.position) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         super.visit(structDecl) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -0,0 +1,120 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | import prog8.ast.Node | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.DataType | ||||||
|  | import prog8.ast.expressions.ArrayIndexedExpression | ||||||
|  | import prog8.ast.expressions.BinaryExpression | ||||||
|  | import prog8.ast.expressions.DirectMemoryRead | ||||||
|  | import prog8.ast.expressions.StringLiteralValue | ||||||
|  | import prog8.ast.statements.* | ||||||
|  | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class AstVariousTransforms(private val program: Program) : AstWalker() { | ||||||
|  |  | ||||||
|  |     override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { | ||||||
|  |         // For non-kernal subroutines and non-asm parameters: | ||||||
|  |         // inject subroutine params as local variables (if they're not there yet). | ||||||
|  |         val symbolsInSub = subroutine.allDefinedSymbols() | ||||||
|  |         val namesInSub = symbolsInSub.map{ it.first }.toSet() | ||||||
|  |         if(subroutine.asmAddress==null) { | ||||||
|  |             if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) { | ||||||
|  |                 val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet() | ||||||
|  |                 if(!vars.containsAll(subroutine.parameters.map{it.name})) { | ||||||
|  |                     return subroutine.parameters | ||||||
|  |                             .filter { it.name !in namesInSub } | ||||||
|  |                             .map { | ||||||
|  |                                 val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position) | ||||||
|  |                                 IAstModification.InsertFirst(vardecl, subroutine) | ||||||
|  |                             } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |         val leftStr = expr.left as? StringLiteralValue | ||||||
|  |         val rightStr = expr.right as? StringLiteralValue | ||||||
|  |         if(expr.operator == "+") { | ||||||
|  |             val concatenatedString = concatString(expr) | ||||||
|  |             if(concatenatedString!=null) | ||||||
|  |                 return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent)) | ||||||
|  |         } | ||||||
|  |         else if(expr.operator == "*") { | ||||||
|  |             if (leftStr!=null) { | ||||||
|  |                 val amount = expr.right.constValue(program) | ||||||
|  |                 if(amount!=null) { | ||||||
|  |                     val string = leftStr.value.repeat(amount.number.toInt()) | ||||||
|  |                     val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position) | ||||||
|  |                     return listOf(IAstModification.ReplaceNode(expr, strval, parent)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else if (rightStr!=null) { | ||||||
|  |                 val amount = expr.right.constValue(program) | ||||||
|  |                 if(amount!=null) { | ||||||
|  |                     val string = rightStr.value.repeat(amount.number.toInt()) | ||||||
|  |                     val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position) | ||||||
|  |                     return listOf(IAstModification.ReplaceNode(expr, strval, parent)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun concatString(expr: BinaryExpression): StringLiteralValue? { | ||||||
|  |         val rightStrval = expr.right as? StringLiteralValue | ||||||
|  |         val leftStrval = expr.left as? StringLiteralValue | ||||||
|  |         return when { | ||||||
|  |             expr.operator!="+" -> null | ||||||
|  |             expr.left is BinaryExpression && rightStrval!=null -> { | ||||||
|  |                 val subStrVal = concatString(expr.left as BinaryExpression) | ||||||
|  |                 if(subStrVal==null) | ||||||
|  |                     null | ||||||
|  |                 else | ||||||
|  |                     StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position) | ||||||
|  |             } | ||||||
|  |             expr.right is BinaryExpression && leftStrval!=null -> { | ||||||
|  |                 val subStrVal = concatString(expr.right as BinaryExpression) | ||||||
|  |                 if(subStrVal==null) | ||||||
|  |                     null | ||||||
|  |                 else | ||||||
|  |                     StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position) | ||||||
|  |             } | ||||||
|  |             leftStrval!=null && rightStrval!=null -> { | ||||||
|  |                 StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position) | ||||||
|  |             } | ||||||
|  |             else -> null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal fun replacePointerVarIndexWithMemread(program: Program, arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |     val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program) | ||||||
|  |     if(arrayVar!=null && arrayVar.datatype ==  DataType.UWORD) { | ||||||
|  |         // rewrite   pointervar[index]  into  @(pointervar+index) | ||||||
|  |         val indexer = arrayIndexedExpression.indexer | ||||||
|  |         val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position) | ||||||
|  |         return if(parent is AssignTarget) { | ||||||
|  |             // we're part of the target of an assignment, we have to actually change the assign target itself | ||||||
|  |             val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position) | ||||||
|  |             val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position) | ||||||
|  |             listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent)) | ||||||
|  |         } else { | ||||||
|  |             val memread = DirectMemoryRead(add, arrayIndexedExpression.position) | ||||||
|  |             listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return emptyList() | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | import prog8.ast.Node | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.DataType | ||||||
|  | import prog8.ast.expressions.ArrayLiteralValue | ||||||
|  | import prog8.ast.expressions.IdentifierReference | ||||||
|  | import prog8.ast.expressions.StringLiteralValue | ||||||
|  | import prog8.ast.statements.VarDecl | ||||||
|  | import prog8.ast.statements.WhenChoice | ||||||
|  | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { | ||||||
|  |  | ||||||
|  |     override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(string.parent !is VarDecl && string.parent !is WhenChoice) { | ||||||
|  |             // replace the literal string by a identifier reference to the interned string | ||||||
|  |             val scopedName = program.internString(string) | ||||||
|  |             val identifier = IdentifierReference(scopedName, string.position) | ||||||
|  |             return listOf(IAstModification.ReplaceNode(string, identifier, parent)) | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { | ||||||
|  |         val vardecl = array.parent as? VarDecl | ||||||
|  |         if(vardecl!=null) { | ||||||
|  |             // adjust the datatype of the array (to an educated guess) | ||||||
|  |             val arrayDt = array.type | ||||||
|  |             if(!arrayDt.istype(vardecl.datatype)) { | ||||||
|  |                 val cast = array.cast(vardecl.datatype) | ||||||
|  |                 if (cast != null && cast !== array) | ||||||
|  |                     return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             val arrayDt = array.guessDatatype(program) | ||||||
|  |             if(arrayDt.isKnown) { | ||||||
|  |                 // this array literal is part of an expression, turn it into an identifier reference | ||||||
|  |                 val litval2 = array.cast(arrayDt.typeOrElse(DataType.UNDEFINED)) | ||||||
|  |                 if(litval2!=null) { | ||||||
|  |                     val vardecl2 = VarDecl.createAuto(litval2) | ||||||
|  |                     val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) | ||||||
|  |                     return listOf( | ||||||
|  |                             IAstModification.ReplaceNode(array, identifier, parent), | ||||||
|  |                             IAstModification.InsertFirst(vardecl2, array.definingScope()) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package prog8.ast.processing | package prog8.compiler.astprocessing | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
							
								
								
									
										289
									
								
								compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,289 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | import prog8.ast.IFunctionCall | ||||||
|  | import prog8.ast.Module | ||||||
|  | import prog8.ast.Node | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.DataType | ||||||
|  | import prog8.ast.base.FatalAstException | ||||||
|  | import prog8.ast.expressions.* | ||||||
|  | import prog8.ast.statements.* | ||||||
|  | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  | import prog8.compiler.IErrorReporter | ||||||
|  | import prog8.compiler.functions.BuiltinFunctions | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() { | ||||||
|  |     // Reorders the statements in a way the compiler needs. | ||||||
|  |     // - 'main' block must be the very first statement UNLESS it has an address set. | ||||||
|  |     // - library blocks are put last. | ||||||
|  |     // - blocks are ordered by address, where blocks without address are placed last. | ||||||
|  |     // - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!) | ||||||
|  |     // - the 'start' subroutine is moved to the top. | ||||||
|  |     // - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement. | ||||||
|  |     // - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest> | ||||||
|  |     // - sorts the choices in when statement. | ||||||
|  |     // - insert AddressOf (&) expression where required (string params to a UWORD function param etc). | ||||||
|  |  | ||||||
|  |     private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") | ||||||
|  |  | ||||||
|  |     override fun after(module: Module, parent: Node): Iterable<IAstModification> { | ||||||
|  |         val (blocks, other) = module.statements.partition { it is Block } | ||||||
|  |         module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList() | ||||||
|  |  | ||||||
|  |         val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" } | ||||||
|  |         if(mainBlock!=null && mainBlock.address==null) { | ||||||
|  |             module.statements.remove(mainBlock) | ||||||
|  |             module.statements.add(0, mainBlock) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         reorderVardeclsAndDirectives(module.statements) | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) { | ||||||
|  |         val varDecls = statements.filterIsInstance<VarDecl>() | ||||||
|  |         statements.removeAll(varDecls) | ||||||
|  |         statements.addAll(0, varDecls) | ||||||
|  |  | ||||||
|  |         val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove} | ||||||
|  |         statements.removeAll(directives) | ||||||
|  |         statements.addAll(0, directives) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun before(block: Block, parent: Node): Iterable<IAstModification> { | ||||||
|  |         parent as Module | ||||||
|  |         if(block.isInLibrary) { | ||||||
|  |             return listOf( | ||||||
|  |                     IAstModification.Remove(block, parent), | ||||||
|  |                     IAstModification.InsertLast(block, parent) | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         reorderVardeclsAndDirectives(block.statements) | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(subroutine.name=="start" && parent is Block) { | ||||||
|  |             if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") { | ||||||
|  |                 return listOf( | ||||||
|  |                         IAstModification.Remove(subroutine, parent), | ||||||
|  |                         IAstModification.InsertFirst(subroutine, parent) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val subs = subroutine.statements.filterIsInstance<Subroutine>() | ||||||
|  |         if(subs.isNotEmpty()) { | ||||||
|  |             // all subroutines defined within this subroutine are moved to the end | ||||||
|  |             return subs.map { IAstModification.Remove(it, subroutine) } + | ||||||
|  |                     subs.map { IAstModification.InsertLast(it, subroutine) } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return replacePointerVarIndexWithMemread(program, arrayIndexedExpression, parent) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |  | ||||||
|  |         // ConstValue <associativeoperator> X -->  X <associativeoperator> ConstValue | ||||||
|  |         // (this should be done by the ExpressionSimplifier when optimizing is enabled, | ||||||
|  |         //  but the current assembly code generator for IF statements now also depends on it so we do it here regardless of optimization.) | ||||||
|  |         if (expr.left.constValue(program) != null && expr.operator in associativeOperators && expr.right.constValue(program) == null) | ||||||
|  |             return listOf(IAstModification.SwapOperands(expr)) | ||||||
|  |  | ||||||
|  |         // when using a simple bit shift and assigning it to a variable of a different type, | ||||||
|  |         // try to make the bit shifting 'wide enough' to fall into the variable's type. | ||||||
|  |         // with this, for instance, uword x = 1 << 10  will result in 1024 rather than 0 (the ubyte result). | ||||||
|  |         if(expr.operator=="<<" || expr.operator==">>") { | ||||||
|  |             val leftDt = expr.left.inferType(program) | ||||||
|  |             when (parent) { | ||||||
|  |                 is Assignment -> { | ||||||
|  |                     val targetDt = parent.target.inferType(program) | ||||||
|  |                     if(leftDt != targetDt) { | ||||||
|  |                         val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.UNDEFINED), true, parent.position) | ||||||
|  |                         return listOf(IAstModification.ReplaceNode(expr.left, cast, expr)) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 is VarDecl -> { | ||||||
|  |                     if(!leftDt.istype(parent.datatype)) { | ||||||
|  |                         val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position) | ||||||
|  |                         return listOf(IAstModification.ReplaceNode(expr.left, cast, expr)) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 is IFunctionCall -> { | ||||||
|  |                     val argnum = parent.args.indexOf(expr) | ||||||
|  |                     when (val callee = parent.target.targetStatement(program)) { | ||||||
|  |                         is Subroutine -> { | ||||||
|  |                             val paramType = callee.parameters[argnum].type | ||||||
|  |                             if(leftDt isAssignableTo paramType) { | ||||||
|  |                                 val cast = TypecastExpression(expr.left, paramType, true, parent.position) | ||||||
|  |                                 return listOf(IAstModification.ReplaceNode(expr.left, cast, expr)) | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         is BuiltinFunctionStatementPlaceholder -> { | ||||||
|  |                             val func = BuiltinFunctions.getValue(callee.name) | ||||||
|  |                             val paramTypes = func.parameters[argnum].possibleDatatypes | ||||||
|  |                             for(type in paramTypes) { | ||||||
|  |                                 if(leftDt isAssignableTo type) { | ||||||
|  |                                     val cast = TypecastExpression(expr.left, type, true, parent.position) | ||||||
|  |                                     return listOf(IAstModification.ReplaceNode(expr.left, cast, expr)) | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         else -> throw FatalAstException("weird callee") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else -> return noModifications | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else if(expr.operator in logicalOperators) { | ||||||
|  |             // make sure that logical expressions like "var and other-logical-expression | ||||||
|  |             // is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and | ||||||
|  |             // generating the wrong results later | ||||||
|  |  | ||||||
|  |             fun wrapped(expr: Expression): Expression = | ||||||
|  |                 BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position) | ||||||
|  |  | ||||||
|  |             fun isLogicalExpr(expr: Expression?): Boolean { | ||||||
|  |                 if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators)) | ||||||
|  |                     return true | ||||||
|  |                 if(expr is PrefixExpression && expr.operator in logicalOperators) | ||||||
|  |                     return true | ||||||
|  |                 return false | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return if(isLogicalExpr(expr.left)) { | ||||||
|  |                 if(isLogicalExpr(expr.right)) | ||||||
|  |                     noModifications | ||||||
|  |                 else | ||||||
|  |                     listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)) | ||||||
|  |             } else { | ||||||
|  |                 if(isLogicalExpr(expr.right)) | ||||||
|  |                     listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr)) | ||||||
|  |                 else { | ||||||
|  |                     listOf( | ||||||
|  |                         IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr), | ||||||
|  |                         IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> { | ||||||
|  |         val choices = whenStatement.choiceValues(program).sortedBy { | ||||||
|  |             it.first?.first() ?: Int.MAX_VALUE | ||||||
|  |         } | ||||||
|  |         whenStatement.choices.clear() | ||||||
|  |         choices.mapTo(whenStatement.choices) { it.second } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> { | ||||||
|  |         val valueType = assignment.value.inferType(program) | ||||||
|  |         val targetType = assignment.target.inferType(program) | ||||||
|  |  | ||||||
|  |         if(targetType.isArray() && valueType.isArray() ) { | ||||||
|  |             if (assignment.value is ArrayLiteralValue) { | ||||||
|  |                 errors.err("cannot assign array literal here, use separate assignment per element", assignment.position) | ||||||
|  |             } else { | ||||||
|  |                 return copyArrayValue(assignment) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { | ||||||
|  |         // rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand | ||||||
|  |         val binExpr = assignment.value as? BinaryExpression | ||||||
|  |         if(binExpr!=null) { | ||||||
|  |             if(binExpr.left isSameAs assignment.target) { | ||||||
|  |                 // A = A <operator> 5, unchanged | ||||||
|  |                 return noModifications | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if(binExpr.operator in associativeOperators) { | ||||||
|  |                 if (binExpr.right isSameAs assignment.target) { | ||||||
|  |                     // A = v <associative-operator> A  ==>  A = A <associative-operator> v | ||||||
|  |                     return listOf(IAstModification.SwapOperands(binExpr)) | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 val leftBinExpr = binExpr.left as? BinaryExpression | ||||||
|  |                 if(leftBinExpr?.operator == binExpr.operator) { | ||||||
|  |                     return if(leftBinExpr.left isSameAs assignment.target) { | ||||||
|  |                         // A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y) | ||||||
|  |                         val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position) | ||||||
|  |                         val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position) | ||||||
|  |                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) | ||||||
|  |                     } else { | ||||||
|  |                         // A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y) | ||||||
|  |                         val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position) | ||||||
|  |                         val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position) | ||||||
|  |                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 val rightBinExpr = binExpr.right as? BinaryExpression | ||||||
|  |                 if(rightBinExpr?.operator == binExpr.operator) { | ||||||
|  |                     return if(rightBinExpr.left isSameAs assignment.target) { | ||||||
|  |                         // A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y) | ||||||
|  |                         val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position) | ||||||
|  |                         val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position) | ||||||
|  |                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) | ||||||
|  |                     } else { | ||||||
|  |                         // A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y) | ||||||
|  |                         val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position) | ||||||
|  |                         val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position) | ||||||
|  |                         listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment)) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun copyArrayValue(assign: Assignment): List<IAstModification> { | ||||||
|  |         val identifier = assign.target.identifier!! | ||||||
|  |         val targetVar = identifier.targetVarDecl(program)!! | ||||||
|  |  | ||||||
|  |         if(targetVar.arraysize==null) | ||||||
|  |             errors.err("array has no defined size", assign.position) | ||||||
|  |  | ||||||
|  |         if(assign.value !is IdentifierReference) { | ||||||
|  |             errors.err("invalid array value to assign to other array", assign.value.position) | ||||||
|  |             return noModifications | ||||||
|  |         } | ||||||
|  |         val sourceIdent = assign.value as IdentifierReference | ||||||
|  |         val sourceVar = sourceIdent.targetVarDecl(program)!! | ||||||
|  |         if(!sourceVar.isArray) { | ||||||
|  |             errors.err("value must be an array", sourceIdent.position) | ||||||
|  |         } else { | ||||||
|  |             if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex()) | ||||||
|  |                 errors.err("element count mismatch", assign.position) | ||||||
|  |             if (sourceVar.datatype != targetVar.datatype) | ||||||
|  |                 errors.err("element type mismatch", assign.position) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!errors.noErrors()) | ||||||
|  |             return noModifications | ||||||
|  |  | ||||||
|  |         val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position), | ||||||
|  |             mutableListOf( | ||||||
|  |                 AddressOf(sourceIdent, assign.position), | ||||||
|  |                 AddressOf(identifier, assign.position), | ||||||
|  |                 NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position) | ||||||
|  |             ), | ||||||
|  |             true, | ||||||
|  |             assign.position | ||||||
|  |         ) | ||||||
|  |         return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,28 +1,37 @@ | |||||||
| package prog8.ast.processing | package prog8.compiler.astprocessing | ||||||
| 
 | 
 | ||||||
| import prog8.ast.IFunctionCall | import prog8.ast.IFunctionCall | ||||||
| import prog8.ast.INameScope |  | ||||||
| import prog8.ast.Node | import prog8.ast.Node | ||||||
| import prog8.ast.Program | import prog8.ast.Program | ||||||
| import prog8.ast.base.* | import prog8.ast.base.* | ||||||
| import prog8.ast.expressions.* | import prog8.ast.expressions.* | ||||||
| import prog8.ast.statements.* | import prog8.ast.statements.* | ||||||
| import prog8.functions.BuiltinFunctions | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  | import prog8.compiler.IErrorReporter | ||||||
|  | import prog8.compiler.functions.BuiltinFunctions | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() { | class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() { | ||||||
|     /* |     /* | ||||||
|      * Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type. |      * Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type. | ||||||
|      * (this includes function call arguments) |      * (this includes function call arguments) | ||||||
|      */ |      */ | ||||||
| 
 | 
 | ||||||
|     private val noModifications = emptyList<IAstModification>() |  | ||||||
| 
 |  | ||||||
|     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { |     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||||
|         val declValue = decl.value |         val declValue = decl.value | ||||||
|         if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) { |         if(decl.type==VarDeclType.VAR && declValue!=null) { | ||||||
|             val valueDt = declValue.inferType(program) |             val valueDt = declValue.inferType(program) | ||||||
|             if(!valueDt.istype(decl.datatype)) { |             if(!valueDt.istype(decl.datatype)) { | ||||||
|  | 
 | ||||||
|  |                 // don't add a typecast on an array initializer value | ||||||
|  |                 if(valueDt.isInteger() && decl.datatype in ArrayDatatypes) | ||||||
|  |                     return noModifications | ||||||
|  | 
 | ||||||
|  |                 // don't add a typecast if the initializer value is inherently not assignable | ||||||
|  |                 if(valueDt isNotAssignableTo decl.datatype) | ||||||
|  |                     return noModifications | ||||||
|  | 
 | ||||||
|                 return listOf(IAstModification.ReplaceNode( |                 return listOf(IAstModification.ReplaceNode( | ||||||
|                         declValue, |                         declValue, | ||||||
|                         TypecastExpression(declValue, decl.datatype, true, declValue.position), |                         TypecastExpression(declValue, decl.datatype, true, declValue.position), | ||||||
| @@ -38,7 +47,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | |||||||
|         val rightDt = expr.right.inferType(program) |         val rightDt = expr.right.inferType(program) | ||||||
|         if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) { |         if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) { | ||||||
|             // determine common datatype and add typecast as required to make left and right equal types |             // determine common datatype and add typecast as required to make left and right equal types | ||||||
|             val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right) |             val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.UNDEFINED), rightDt.typeOrElse(DataType.UNDEFINED), expr.left, expr.right) | ||||||
|             if(toFix!=null) { |             if(toFix!=null) { | ||||||
|                 return when { |                 return when { | ||||||
|                     toFix===expr.left -> listOf(IAstModification.ReplaceNode( |                     toFix===expr.left -> listOf(IAstModification.ReplaceNode( | ||||||
| @@ -55,12 +64,15 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | |||||||
|     override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { |     override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { | ||||||
|         // see if a typecast is needed to convert the value's type into the proper target type |         // see if a typecast is needed to convert the value's type into the proper target type | ||||||
|         val valueItype = assignment.value.inferType(program) |         val valueItype = assignment.value.inferType(program) | ||||||
|         val targetItype = assignment.target.inferType(program, assignment) |         val targetItype = assignment.target.inferType(program) | ||||||
|         if(targetItype.isKnown && valueItype.isKnown) { |         if(targetItype.isKnown && valueItype.isKnown) { | ||||||
|             val targettype = targetItype.typeOrElse(DataType.STRUCT) |             val targettype = targetItype.typeOrElse(DataType.UNDEFINED) | ||||||
|             val valuetype = valueItype.typeOrElse(DataType.STRUCT) |             val valuetype = valueItype.typeOrElse(DataType.UNDEFINED) | ||||||
|             if (valuetype != targettype) { |             if (valuetype != targettype) { | ||||||
|                 if (valuetype isAssignableTo targettype) { |                 if (valuetype isAssignableTo targettype) { | ||||||
|  |                     if(valuetype in IterableDatatypes && targettype==DataType.UWORD) | ||||||
|  |                         // special case, don't typecast STR/arrays to UWORD, we support those assignments "directly" | ||||||
|  |                         return noModifications | ||||||
|                     return listOf(IAstModification.ReplaceNode( |                     return listOf(IAstModification.ReplaceNode( | ||||||
|                             assignment.value, |                             assignment.value, | ||||||
|                             TypecastExpression(assignment.value, targettype, true, assignment.value.position), |                             TypecastExpression(assignment.value, targettype, true, assignment.value.position), | ||||||
| @@ -98,41 +110,43 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { |     override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { | ||||||
|         return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope()) |         return afterFunctionCallArgs(functionCallStatement) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { |     override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { | ||||||
|         return afterFunctionCallArgs(functionCall, functionCall.definingScope()) |         return afterFunctionCallArgs(functionCall) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> { |     private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> { | ||||||
|         // see if a typecast is needed to convert the arguments into the required parameter's type |         // see if a typecast is needed to convert the arguments into the required parameter's type | ||||||
|         val modifications = mutableListOf<IAstModification>() |         val modifications = mutableListOf<IAstModification>() | ||||||
| 
 | 
 | ||||||
|         when(val sub = call.target.targetStatement(scope)) { |         when(val sub = call.target.targetStatement(program)) { | ||||||
|             is Subroutine -> { |             is Subroutine -> { | ||||||
|                 for(arg in sub.parameters.zip(call.args.withIndex())) { |                 sub.parameters.zip(call.args).forEachIndexed { index, pair -> | ||||||
|                     val argItype = arg.second.value.inferType(program) |                     val argItype = pair.second.inferType(program) | ||||||
|                     if(argItype.isKnown) { |                     if(argItype.isKnown) { | ||||||
|                         val argtype = argItype.typeOrElse(DataType.STRUCT) |                         val argtype = argItype.typeOrElse(DataType.UNDEFINED) | ||||||
|                         val requiredType = arg.first.type |                         val requiredType = pair.first.type | ||||||
|                         if (requiredType != argtype) { |                         if (requiredType != argtype) { | ||||||
|                             if (argtype isAssignableTo requiredType) { |                             if (argtype isAssignableTo requiredType) { | ||||||
|                                 modifications += IAstModification.ReplaceNode( |                                 modifications += IAstModification.ReplaceNode( | ||||||
|                                         call.args[arg.second.index], |                                         call.args[index], | ||||||
|                                         TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position), |                                         TypecastExpression(pair.second, requiredType, true, pair.second.position), | ||||||
|                                         call as Node) |                                         call as Node) | ||||||
|                             } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { |                             } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { | ||||||
|                                 // we allow STR/ARRAY values in place of UWORD parameters. Take their address instead. |                                 // we allow STR/ARRAY values in place of UWORD parameters. Take their address instead. | ||||||
|  |                                 if(pair.second is IdentifierReference) { | ||||||
|                                     modifications += IAstModification.ReplaceNode( |                                     modifications += IAstModification.ReplaceNode( | ||||||
|                                         call.args[arg.second.index], |                                             call.args[index], | ||||||
|                                         AddressOf(arg.second.value as IdentifierReference, arg.second.value.position), |                                             AddressOf(pair.second as IdentifierReference, pair.second.position), | ||||||
|                                             call as Node) |                                             call as Node) | ||||||
|                             } else if(arg.second.value is NumericLiteralValue) { |                                 } | ||||||
|                                 val cast = (arg.second.value as NumericLiteralValue).cast(requiredType) |                             } else if(pair.second is NumericLiteralValue) { | ||||||
|  |                                 val cast = (pair.second as NumericLiteralValue).cast(requiredType) | ||||||
|                                 if(cast.isValid) |                                 if(cast.isValid) | ||||||
|                                     modifications += IAstModification.ReplaceNode( |                                     modifications += IAstModification.ReplaceNode( | ||||||
|                                             call.args[arg.second.index], |                                             call.args[index], | ||||||
|                                             cast.valueOrZero(), |                                             cast.valueOrZero(), | ||||||
|                                             call as Node) |                                             call as Node) | ||||||
|                             } |                             } | ||||||
| @@ -142,17 +156,16 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | |||||||
|             } |             } | ||||||
|             is BuiltinFunctionStatementPlaceholder -> { |             is BuiltinFunctionStatementPlaceholder -> { | ||||||
|                 val func = BuiltinFunctions.getValue(sub.name) |                 val func = BuiltinFunctions.getValue(sub.name) | ||||||
|                 for (arg in func.parameters.zip(call.args.withIndex())) { |                 func.parameters.zip(call.args).forEachIndexed { index, pair -> | ||||||
|                     val argItype = arg.second.value.inferType(program) |                     val argItype = pair.second.inferType(program) | ||||||
|                     if (argItype.isKnown) { |                     if (argItype.isKnown) { | ||||||
|                         val argtype = argItype.typeOrElse(DataType.STRUCT) |                         val argtype = argItype.typeOrElse(DataType.UNDEFINED) | ||||||
|                         if (arg.first.possibleDatatypes.any { argtype == it }) |                         if (pair.first.possibleDatatypes.all { argtype != it }) { | ||||||
|                             continue |                             for (possibleType in pair.first.possibleDatatypes) { | ||||||
|                         for (possibleType in arg.first.possibleDatatypes) { |  | ||||||
|                                 if (argtype isAssignableTo possibleType) { |                                 if (argtype isAssignableTo possibleType) { | ||||||
|                                     modifications += IAstModification.ReplaceNode( |                                     modifications += IAstModification.ReplaceNode( | ||||||
|                                         call.args[arg.second.index], |                                             call.args[index], | ||||||
|                                         TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position), |                                             TypecastExpression(pair.second, possibleType, true, pair.second.position), | ||||||
|                                             call as Node) |                                             call as Node) | ||||||
|                                     break |                                     break | ||||||
|                                 } |                                 } | ||||||
| @@ -160,6 +173,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | |||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             else -> { } |             else -> { } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										121
									
								
								compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | import prog8.ast.IFunctionCall | ||||||
|  | import prog8.ast.INameScope | ||||||
|  | import prog8.ast.Node | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.FatalAstException | ||||||
|  | import prog8.ast.base.Position | ||||||
|  | import prog8.ast.expressions.* | ||||||
|  | import prog8.ast.statements.* | ||||||
|  | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  | import prog8.compiler.IErrorReporter | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() { | ||||||
|  |  | ||||||
|  |     override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return listOf(IAstModification.Remove(nopStatement, parent as INameScope)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return if(parent is INameScope) | ||||||
|  |             listOf(ScopeFlatten(scope, parent as INameScope)) | ||||||
|  |         else | ||||||
|  |             noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification { | ||||||
|  |         override fun perform() { | ||||||
|  |             val idx = into.statements.indexOf(scope) | ||||||
|  |             if(idx>=0) { | ||||||
|  |                 into.statements.addAll(idx+1, scope.statements) | ||||||
|  |                 scope.statements.forEach { it.parent = into as Node } | ||||||
|  |                 into.statements.remove(scope) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return before(functionCall as IFunctionCall, parent, functionCall.position) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> { | ||||||
|  |         if(functionCall.target.nameInSource==listOf("peek")) { | ||||||
|  |             // peek(a) is synonymous with @(a) | ||||||
|  |             val memread = DirectMemoryRead(functionCall.args.single(), position) | ||||||
|  |             return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent)) | ||||||
|  |         } | ||||||
|  |         if(functionCall.target.nameInSource==listOf("poke")) { | ||||||
|  |             // poke(a, v) is synonymous with @(a) = v | ||||||
|  |             val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position) | ||||||
|  |             val assign = Assignment(tgt, functionCall.args[1], position) | ||||||
|  |             return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent)) | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(typecast.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $typecast") | ||||||
|  |  | ||||||
|  |         if(typecast.expression is NumericLiteralValue) { | ||||||
|  |             val value = (typecast.expression as NumericLiteralValue).cast(typecast.type) | ||||||
|  |             if(value.isValid) | ||||||
|  |                 return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent)) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         val sourceDt = typecast.expression.inferType(program) | ||||||
|  |         if(sourceDt.istype(typecast.type)) | ||||||
|  |             return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent)) | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(subroutine.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $subroutine") | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(assignment.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $assignment") | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(assignTarget.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $assignTarget") | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(decl.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $decl") | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(scope.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $scope") | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(returnStmt.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $returnStmt") | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> { | ||||||
|  |         if(identifier.parent!==parent) | ||||||
|  |             throw FatalAstException("parent node mismatch at $identifier") | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,95 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | import prog8.ast.IFunctionCall | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.DataType | ||||||
|  | import prog8.ast.expressions.Expression | ||||||
|  | import prog8.ast.expressions.FunctionCall | ||||||
|  | import prog8.ast.expressions.TypecastExpression | ||||||
|  | import prog8.ast.statements.* | ||||||
|  | import prog8.ast.walk.IAstVisitor | ||||||
|  | import prog8.compiler.CompilerException | ||||||
|  | import prog8.compiler.functions.BuiltinFunctions | ||||||
|  |  | ||||||
|  | class VerifyFunctionArgTypes(val program: Program) : IAstVisitor { | ||||||
|  |  | ||||||
|  |     override fun visit(functionCall: FunctionCall) { | ||||||
|  |         val error = checkTypes(functionCall as IFunctionCall, program) | ||||||
|  |         if(error!=null) | ||||||
|  |             throw CompilerException(error) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun visit(functionCallStatement: FunctionCallStatement) { | ||||||
|  |         val error = checkTypes(functionCallStatement as IFunctionCall, program) | ||||||
|  |         if (error!=null) | ||||||
|  |             throw CompilerException(error) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |  | ||||||
|  |         private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean { | ||||||
|  |             if(argDt==paramDt) | ||||||
|  |                 return true | ||||||
|  |  | ||||||
|  |             // there are some exceptions that are considered compatible, such as STR <> UWORD | ||||||
|  |             if(argDt==DataType.STR && paramDt==DataType.UWORD || | ||||||
|  |                     argDt==DataType.UWORD && paramDt==DataType.STR) | ||||||
|  |                 return true | ||||||
|  |  | ||||||
|  |             return false | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun checkTypes(call: IFunctionCall, program: Program): String? { | ||||||
|  |             val argITypes = call.args.map { it.inferType(program) } | ||||||
|  |             val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown } | ||||||
|  |             if(firstUnknownDt>=0) | ||||||
|  |                 return "argument ${firstUnknownDt+1} invalid argument type" | ||||||
|  |             val argtypes = argITypes.map { it.typeOrElse(DataType.UNDEFINED) } | ||||||
|  |             val target = call.target.targetStatement(program) | ||||||
|  |             if (target is Subroutine) { | ||||||
|  |                 if(call.args.size != target.parameters.size) | ||||||
|  |                     return "invalid number of arguments" | ||||||
|  |                 val paramtypes = target.parameters.map { it.type } | ||||||
|  |                 val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) } | ||||||
|  |                 if(mismatch>=0) { | ||||||
|  |                     val actual = argtypes[mismatch].toString() | ||||||
|  |                     val expected = paramtypes[mismatch].toString() | ||||||
|  |                     return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected" | ||||||
|  |                 } | ||||||
|  |                 if(target.isAsmSubroutine) { | ||||||
|  |                     if(target.asmReturnvaluesRegisters.size>1) { | ||||||
|  |                         // multiple return values will NOT work inside an expression. | ||||||
|  |                         // they MIGHT work in a regular assignment or just a function call statement. | ||||||
|  |                         val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null | ||||||
|  |                         if (call !is FunctionCallStatement) { | ||||||
|  |                             val checkParent = | ||||||
|  |                                 if(parent is TypecastExpression) | ||||||
|  |                                     parent.parent | ||||||
|  |                                 else | ||||||
|  |                                     parent | ||||||
|  |                             if (checkParent !is Assignment && checkParent !is VarDecl) { | ||||||
|  |                                 return "can't use subroutine call that returns multiple return values here" | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else if (target is BuiltinFunctionStatementPlaceholder) { | ||||||
|  |                 val func = BuiltinFunctions.getValue(target.name) | ||||||
|  |                 if(call.args.size != func.parameters.size) | ||||||
|  |                     return "invalid number of arguments" | ||||||
|  |                 val paramtypes = func.parameters.map { it.possibleDatatypes } | ||||||
|  |                 argtypes.zip(paramtypes).forEachIndexed { index, pair -> | ||||||
|  |                     val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) } | ||||||
|  |                     if (!anyCompatible) { | ||||||
|  |                         val actual = pair.first.toString() | ||||||
|  |                         val expected = pair.second.toString() | ||||||
|  |                         return "argument ${index + 1} type mismatch, was: $actual expected: $expected" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return null | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,9 +1,9 @@ | |||||||
| package prog8.functions | package prog8.compiler.functions | ||||||
| 
 | 
 | ||||||
|  | import prog8.ast.IMemSizer | ||||||
| import prog8.ast.Program | import prog8.ast.Program | ||||||
| import prog8.ast.base.* | import prog8.ast.base.* | ||||||
| import prog8.ast.expressions.* | import prog8.ast.expressions.* | ||||||
| import prog8.ast.statements.StructDecl |  | ||||||
| import prog8.ast.statements.VarDecl | import prog8.ast.statements.VarDecl | ||||||
| import prog8.compiler.CompilerException | import prog8.compiler.CompilerException | ||||||
| import kotlin.math.* | import kotlin.math.* | ||||||
| @@ -12,103 +12,148 @@ import kotlin.math.* | |||||||
| class FParam(val name: String, val possibleDatatypes: Set<DataType>) | class FParam(val name: String, val possibleDatatypes: Set<DataType>) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue | typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FSignature(val pure: Boolean,      // does it have side effects? | class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean) | ||||||
|  | class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean) | ||||||
|  | class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) { | ||||||
|  |     override fun toString(): String { | ||||||
|  |         val paramConvs =  params.mapIndexed { index, it -> | ||||||
|  |             when { | ||||||
|  |                 it.reg!=null -> "$index:${it.reg}" | ||||||
|  |                 it.variable -> "$index:variable" | ||||||
|  |                 else -> "$index:???" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         val returnConv = | ||||||
|  |                 when { | ||||||
|  |                     returns.reg!=null -> returns.reg.toString() | ||||||
|  |                     returns.floatFac1 -> "floatFAC1" | ||||||
|  |                     else -> "<no returnvalue>" | ||||||
|  |                 } | ||||||
|  |         return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class FSignature(val name: String, | ||||||
|  |                  val pure: Boolean,      // does it have side effects? | ||||||
|                  val parameters: List<FParam>, |                  val parameters: List<FParam>, | ||||||
|                  val returntype: DataType?, |                  val known_returntype: DataType?,     // specify return type if fixed, otherwise null if it depends on the arguments | ||||||
|                  val constExpressionFunc: ConstExpressionCaller? = null) |                  val constExpressionFunc: ConstExpressionCaller? = null) { | ||||||
| 
 | 
 | ||||||
|  |     fun callConvention(actualParamTypes: List<DataType>): CallConvention { | ||||||
|  |         val returns = when(known_returntype) { | ||||||
|  |             DataType.UBYTE, DataType.BYTE -> ReturnConvention(known_returntype, RegisterOrPair.A, false) | ||||||
|  |             DataType.UWORD, DataType.WORD -> ReturnConvention(known_returntype, RegisterOrPair.AY, false) | ||||||
|  |             DataType.FLOAT -> ReturnConvention(known_returntype, null, true) | ||||||
|  |             in PassByReferenceDatatypes -> ReturnConvention(known_returntype!!, RegisterOrPair.AY, false) | ||||||
|  |             else -> { | ||||||
|  |                 val paramType = actualParamTypes.first() | ||||||
|  |                 if(pure) | ||||||
|  |                     // return type depends on arg type | ||||||
|  |                     when(paramType) { | ||||||
|  |                         DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false) | ||||||
|  |                         DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false) | ||||||
|  |                         DataType.FLOAT -> ReturnConvention(paramType, null, true) | ||||||
|  |                         in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false) | ||||||
|  |                         else -> ReturnConvention(paramType, null, false) | ||||||
|  |                     } | ||||||
|  |                 else { | ||||||
|  |                     ReturnConvention(paramType, null, false) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return when { | ||||||
|  |             actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns) | ||||||
|  |             actualParamTypes.size==1 -> { | ||||||
|  |                 // one parameter? via register/registerpair | ||||||
|  |                 val paramConv = when(val paramType = actualParamTypes[0]) { | ||||||
|  |                     DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false) | ||||||
|  |                     DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false) | ||||||
|  |                     DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false) | ||||||
|  |                     in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false) | ||||||
|  |                     else -> ParamConvention(paramType, null, false) | ||||||
|  |                 } | ||||||
|  |                 CallConvention(listOf(paramConv), returns) | ||||||
|  |             } | ||||||
|  |             else -> { | ||||||
|  |                 // multiple parameters? via variables | ||||||
|  |                 val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) } | ||||||
|  |                 CallConvention(paramConvs, returns) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| val BuiltinFunctions = mapOf( | @Suppress("UNUSED_ANONYMOUS_PARAMETER") | ||||||
|  | private val functionSignatures: List<FSignature> = listOf( | ||||||
|         // this set of function have no return value and operate in-place: |         // this set of function have no return value and operate in-place: | ||||||
|     "rol"         to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), |     FSignature("rol"         , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||||
|     "ror"         to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), |     FSignature("ror"         , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||||
|     "rol2"        to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), |     FSignature("rol2"        , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||||
|     "ror2"        to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), |     FSignature("ror2"        , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||||
|     "sort"        to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null), |     FSignature("sort"        , false, listOf(FParam("array", ArrayDatatypes)), null), | ||||||
|     "reverse"     to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null), |     FSignature("reverse"     , false, listOf(FParam("array", ArrayDatatypes)), null), | ||||||
|  |     FSignature("cmp"         , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null), | ||||||
|         // these few have a return value depending on the argument(s): |         // these few have a return value depending on the argument(s): | ||||||
|     "max"         to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) },    // type depends on args |     FSignature("max"         , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) },    // type depends on args | ||||||
|     "min"         to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) },    // type depends on args |     FSignature("min"         , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) },    // type depends on args | ||||||
|     "sum"         to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) },      // type depends on args |     FSignature("sum"         , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) },      // type depends on args | ||||||
|     "abs"         to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs),      // type depends on argument |     FSignature("abs"         , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs),      // type depends on argument | ||||||
|     "len"         to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen),    // type is UBYTE or UWORD depending on actual length |     FSignature("len"         , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen),    // type is UBYTE or UWORD depending on actual length | ||||||
|     "sizeof"      to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof), |     FSignature("sizeof"      , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof), | ||||||
|         // normal functions follow: |         // normal functions follow: | ||||||
|     "sgn"         to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ), |     FSignature("sgn"         , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ), | ||||||
|     "sin"         to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) }, |     FSignature("sin"         , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) }, | ||||||
|     "sin8"        to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), |     FSignature("sin8"        , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), | ||||||
|     "sin8u"       to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), |     FSignature("sin8u"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), | ||||||
|     "sin16"       to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ), |     FSignature("sin16"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ), | ||||||
|     "sin16u"      to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ), |     FSignature("sin16u"      , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ), | ||||||
|     "cos"         to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) }, |     FSignature("cos"         , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) }, | ||||||
|     "cos8"        to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ), |     FSignature("cos8"        , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ), | ||||||
|     "cos8u"       to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ), |     FSignature("cos8u"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ), | ||||||
|     "cos16"       to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ), |     FSignature("cos16"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ), | ||||||
|     "cos16u"      to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ), |     FSignature("cos16u"      , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ), | ||||||
|     "tan"         to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) }, |     FSignature("tan"         , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) }, | ||||||
|     "atan"        to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) }, |     FSignature("atan"        , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) }, | ||||||
|     "ln"          to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) }, |     FSignature("ln"          , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) }, | ||||||
|     "log2"        to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) }, |     FSignature("log2"        , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) }, | ||||||
|     "sqrt16"      to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } }, |     FSignature("sqrt16"      , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } }, | ||||||
|     "sqrt"        to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) }, |     FSignature("sqrt"        , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) }, | ||||||
|     "rad"         to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) }, |     FSignature("rad"         , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) }, | ||||||
|     "deg"         to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) }, |     FSignature("deg"         , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) }, | ||||||
|     "round"       to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) }, |     FSignature("round"       , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) }, | ||||||
|     "floor"       to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) }, |     FSignature("floor"       , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) }, | ||||||
|     "ceil"        to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) }, |     FSignature("ceil"        , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) }, | ||||||
|     "any"         to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) }, |     FSignature("any"         , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) }, | ||||||
|     "all"         to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) }, |     FSignature("all"         , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) }, | ||||||
|     "lsb"         to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }}, |     FSignature("lsb"         , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } }, | ||||||
|     "msb"         to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}}, |     FSignature("msb"         , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} }, | ||||||
|     "mkword"      to FSignature(true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), |     FSignature("mkword"      , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), | ||||||
|     "rnd"         to FSignature(true, emptyList(), DataType.UBYTE), |     FSignature("peek"        , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE), | ||||||
|     "rndw"        to FSignature(true, emptyList(), DataType.UWORD), |     FSignature("peekw"       , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD), | ||||||
|     "rndf"        to FSignature(true, emptyList(), DataType.FLOAT), |     FSignature("poke"        , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null), | ||||||
|     "exit"        to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null), |     FSignature("pokew"       , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null), | ||||||
|     "rsave"       to FSignature(false, emptyList(), null), |     FSignature("rnd"         , false, emptyList(), DataType.UBYTE), | ||||||
|     "rrestore"    to FSignature(false, emptyList(), null), |     FSignature("rndw"        , false, emptyList(), DataType.UWORD), | ||||||
|     "set_carry"   to FSignature(false, emptyList(), null), |     FSignature("rndf"        , false, emptyList(), DataType.FLOAT), | ||||||
|     "clear_carry" to FSignature(false, emptyList(), null), |     FSignature("memory"      , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD), | ||||||
|     "set_irqd"    to FSignature(false, emptyList(), null), |     FSignature("swap"        , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null), | ||||||
|     "clear_irqd"  to FSignature(false, emptyList(), null), |     FSignature("callfar"     , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null), | ||||||
|     "read_flags"  to FSignature(false, emptyList(), DataType.UBYTE), |     FSignature("callrom"     , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null), | ||||||
|     "swap"        to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null), | 
 | ||||||
|     "memcopy"     to FSignature(false, listOf( |  | ||||||
|                             FParam("from", IterableDatatypes + DataType.UWORD), |  | ||||||
|                             FParam("to", IterableDatatypes + DataType.UWORD), |  | ||||||
|                             FParam("numbytes", setOf(DataType.UBYTE))), null), |  | ||||||
|     "memset"      to FSignature(false, listOf( |  | ||||||
|                             FParam("address", IterableDatatypes + DataType.UWORD), |  | ||||||
|                             FParam("numbytes", setOf(DataType.UWORD)), |  | ||||||
|                             FParam("bytevalue", ByteDatatypes)), null), |  | ||||||
|     "memsetw"     to FSignature(false, listOf( |  | ||||||
|                             FParam("address", IterableDatatypes + DataType.UWORD), |  | ||||||
|                             FParam("numwords", setOf(DataType.UWORD)), |  | ||||||
|                             FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null), |  | ||||||
|     "strlen"      to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen), |  | ||||||
|     "substr"      to FSignature(false, listOf( |  | ||||||
|             FParam("source", IterableDatatypes + DataType.UWORD), |  | ||||||
|             FParam("target", IterableDatatypes + DataType.UWORD), |  | ||||||
|             FParam("start", setOf(DataType.UBYTE)), |  | ||||||
|             FParam("length", setOf(DataType.UBYTE))), null), |  | ||||||
|     "leftstr"      to FSignature(false, listOf( |  | ||||||
|             FParam("source", IterableDatatypes + DataType.UWORD), |  | ||||||
|             FParam("target", IterableDatatypes + DataType.UWORD), |  | ||||||
|             FParam("length", setOf(DataType.UBYTE))), null), |  | ||||||
|     "rightstr"      to FSignature(false, listOf( |  | ||||||
|             FParam("source", IterableDatatypes + DataType.UWORD), |  | ||||||
|             FParam("target", IterableDatatypes + DataType.UWORD), |  | ||||||
|             FParam("length", setOf(DataType.UBYTE))), null) |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | val BuiltinFunctions = functionSignatures.associateBy { it.name } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!! | fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!! | ||||||
| 
 | 
 | ||||||
| fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!! | fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!! | ||||||
| 
 | 
 | ||||||
| fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() } | fun builtinSum(array: List<Number>): Number = array.sumOf { it.toDouble() } | ||||||
| 
 | 
 | ||||||
| fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0 | fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0 | ||||||
| 
 | 
 | ||||||
| @@ -119,7 +164,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program: | |||||||
| 
 | 
 | ||||||
|     fun datatypeFromIterableArg(arglist: Expression): DataType { |     fun datatypeFromIterableArg(arglist: Expression): DataType { | ||||||
|         if(arglist is ArrayLiteralValue) { |         if(arglist is ArrayLiteralValue) { | ||||||
|             val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet() |             val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.UNDEFINED)}.toSet() | ||||||
|             if(dt.any { it !in NumericDatatypes }) { |             if(dt.any { it !in NumericDatatypes }) { | ||||||
|                 throw FatalAstException("fuction $function only accepts array of numeric values") |                 throw FatalAstException("fuction $function only accepts array of numeric values") | ||||||
|             } |             } | ||||||
| @@ -133,9 +178,9 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program: | |||||||
|             val idt = arglist.inferType(program) |             val idt = arglist.inferType(program) | ||||||
|             if(!idt.isKnown) |             if(!idt.isKnown) | ||||||
|                 throw FatalAstException("couldn't determine type of iterable $arglist") |                 throw FatalAstException("couldn't determine type of iterable $arglist") | ||||||
|             return when(val dt = idt.typeOrElse(DataType.STRUCT)) { |             return when(val dt = idt.typeOrElse(DataType.UNDEFINED)) { | ||||||
|                 DataType.STR, in NumericDatatypes -> dt |                 DataType.STR, in NumericDatatypes -> dt | ||||||
|                 in ArrayDatatypes -> ArrayElementTypes.getValue(dt) |                 in ArrayDatatypes -> ArrayToElementTypes.getValue(dt) | ||||||
|                 else -> throw FatalAstException("function '$function' requires one argument which is an iterable") |                 else -> throw FatalAstException("function '$function' requires one argument which is an iterable") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -143,23 +188,23 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program: | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     val func = BuiltinFunctions.getValue(function) |     val func = BuiltinFunctions.getValue(function) | ||||||
|     if(func.returntype!=null) |     if(func.known_returntype!=null) | ||||||
|         return InferredTypes.knownFor(func.returntype) |         return InferredTypes.knownFor(func.known_returntype) | ||||||
|     // function has return values, but the return type depends on the arguments |  | ||||||
| 
 | 
 | ||||||
|  |     // function has return values, but the return type depends on the arguments | ||||||
|     return when (function) { |     return when (function) { | ||||||
|         "abs" -> { |         "abs" -> { | ||||||
|             val dt = args.single().inferType(program) |             val dt = args.single().inferType(program) | ||||||
|             if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes) |             return if(dt.isNumeric()) | ||||||
|                 return dt |                 dt | ||||||
|             else |             else | ||||||
|                 throw FatalAstException("weird datatype passed to abs $dt") |                 InferredTypes.InferredType.unknown() | ||||||
|         } |         } | ||||||
|         "max", "min" -> { |         "max", "min" -> { | ||||||
|             when(val dt = datatypeFromIterableArg(args.single())) { |             when(val dt = datatypeFromIterableArg(args.single())) { | ||||||
|                 DataType.STR -> InferredTypes.knownFor(DataType.UBYTE) |                 DataType.STR -> InferredTypes.knownFor(DataType.UBYTE) | ||||||
|                 in NumericDatatypes -> InferredTypes.knownFor(dt) |                 in NumericDatatypes -> InferredTypes.knownFor(dt) | ||||||
|                 in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt)) |                 in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(dt)) | ||||||
|                 else -> InferredTypes.unknown() |                 else -> InferredTypes.unknown() | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -228,7 +273,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P | |||||||
|     return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position) |     return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinAbs(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     // 1 arg, type = float or int, result type= isSameAs as argument type |     // 1 arg, type = float or int, result type= isSameAs as argument type | ||||||
|     if(args.size!=1) |     if(args.size!=1) | ||||||
|         throw SyntaxError("abs requires one numeric argument", position) |         throw SyntaxError("abs requires one numeric argument", position) | ||||||
| @@ -241,7 +287,7 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     // 1 arg, type = anything, result type = ubyte |     // 1 arg, type = anything, result type = ubyte | ||||||
|     if(args.size!=1) |     if(args.size!=1) | ||||||
|         throw SyntaxError("sizeof requires one argument", position) |         throw SyntaxError("sizeof requires one argument", position) | ||||||
| @@ -250,56 +296,30 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P | |||||||
| 
 | 
 | ||||||
|     val dt = args[0].inferType(program) |     val dt = args[0].inferType(program) | ||||||
|     if(dt.isKnown) { |     if(dt.isKnown) { | ||||||
|         val target = (args[0] as IdentifierReference).targetStatement(program.namespace) |         val target = (args[0] as IdentifierReference).targetStatement(program) | ||||||
|                 ?: throw CannotEvaluateException("sizeof", "no target") |                 ?: throw CannotEvaluateException("sizeof", "no target") | ||||||
| 
 | 
 | ||||||
|         fun structSize(target: StructDecl) = |  | ||||||
|                 NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position) |  | ||||||
| 
 |  | ||||||
|         return when { |         return when { | ||||||
|             dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> { |             dt.isArray() -> { | ||||||
|                 val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size") |                 val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size") | ||||||
|                 val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT)) |                 val elementDt = ArrayToElementTypes.getValue(dt.typeOrElse(DataType.UNDEFINED)) | ||||||
|                 numericLiteral(elementDt.memorySize() * length, position) |                 numericLiteral(memsizer.memorySize(elementDt) * length, position) | ||||||
|             } |  | ||||||
|             dt.istype(DataType.STRUCT) -> { |  | ||||||
|                 when (target) { |  | ||||||
|                     is VarDecl -> structSize(target.struct!!) |  | ||||||
|                     is StructDecl -> structSize(target) |  | ||||||
|                     else -> throw CompilerException("weird struct type $target") |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|             dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position) |             dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position) | ||||||
|             else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position) |             else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.UNDEFINED)), position) | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         throw SyntaxError("sizeof invalid argument type", position) |         throw SyntaxError("sizeof invalid argument type", position) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|     if (args.size != 1) | private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|         throw SyntaxError("strlen requires one argument", position) |  | ||||||
|     val argument=args[0] |  | ||||||
|     if(argument is StringLiteralValue) |  | ||||||
|         return NumericLiteralValue.optimalInteger(argument.value.length, argument.position) |  | ||||||
|     val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace) |  | ||||||
|     if(vardecl!=null) { |  | ||||||
|         if(vardecl.datatype!=DataType.STR) |  | ||||||
|             throw SyntaxError("strlen must have string argument", position) |  | ||||||
|         if(vardecl.autogeneratedDontRemove) { |  | ||||||
|             return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     throw NotConstArgumentException() |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { |  | ||||||
|     // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. |     // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. | ||||||
|     if(args.size!=1) |     if(args.size!=1) | ||||||
|         throw SyntaxError("len requires one argument", position) |         throw SyntaxError("len requires one argument", position) | ||||||
| 
 | 
 | ||||||
|     val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace) |     val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program) | ||||||
|     var arraySize = directMemVar?.arraysize?.constIndex() |     var arraySize = directMemVar?.arraysize?.constIndex() | ||||||
|     if(arraySize != null) |     if(arraySize != null) | ||||||
|         return NumericLiteralValue.optimalInteger(arraySize, position) |         return NumericLiteralValue.optimalInteger(arraySize, position) | ||||||
| @@ -307,7 +327,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog | |||||||
|         return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position) |         return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position) | ||||||
|     if(args[0] !is IdentifierReference) |     if(args[0] !is IdentifierReference) | ||||||
|         throw SyntaxError("len argument should be an identifier", position) |         throw SyntaxError("len argument should be an identifier", position) | ||||||
|     val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace) |     val target = (args[0] as IdentifierReference).targetVarDecl(program) | ||||||
|             ?: throw CannotEvaluateException("len", "no target vardecl") |             ?: throw CannotEvaluateException("len", "no target vardecl") | ||||||
| 
 | 
 | ||||||
|     return when(target.datatype) { |     return when(target.datatype) { | ||||||
| @@ -318,17 +338,17 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog | |||||||
|             NumericLiteralValue.optimalInteger(arraySize, args[0].position) |             NumericLiteralValue.optimalInteger(arraySize, args[0].position) | ||||||
|         } |         } | ||||||
|         DataType.STR -> { |         DataType.STR -> { | ||||||
|             val refLv = target.value as StringLiteralValue |             val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown") | ||||||
|             NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position) |             NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position) | ||||||
|         } |         } | ||||||
|         DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position) |  | ||||||
|         in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position) |         in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position) | ||||||
|         else -> throw CompilerException("weird datatype") |         else -> throw CompilerException("weird datatype") | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 2) |     if (args.size != 2) | ||||||
|         throw SyntaxError("mkword requires msb and lsb arguments", position) |         throw SyntaxError("mkword requires msb and lsb arguments", position) | ||||||
|     val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -337,7 +357,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P | |||||||
|     return NumericLiteralValue(DataType.UWORD, result, position) |     return NumericLiteralValue(DataType.UWORD, result, position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("sin8 requires one argument", position) |         throw SyntaxError("sin8 requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -345,7 +366,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro | |||||||
|     return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position) |     return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("sin8u requires one argument", position) |         throw SyntaxError("sin8u requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -353,7 +375,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr | |||||||
|     return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position) |     return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("cos8 requires one argument", position) |         throw SyntaxError("cos8 requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -361,7 +384,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro | |||||||
|     return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position) |     return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("cos8u requires one argument", position) |         throw SyntaxError("cos8u requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -369,7 +393,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr | |||||||
|     return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position) |     return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("sin16 requires one argument", position) |         throw SyntaxError("sin16 requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -377,7 +402,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr | |||||||
|     return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position) |     return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("sin16u requires one argument", position) |         throw SyntaxError("sin16u requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -385,7 +411,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P | |||||||
|     return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position) |     return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("cos16 requires one argument", position) |         throw SyntaxError("cos16 requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -393,7 +420,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr | |||||||
|     return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position) |     return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("cos16u requires one argument", position) |         throw SyntaxError("cos16u requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -401,7 +429,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P | |||||||
|     return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position) |     return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { | @Suppress("UNUSED_PARAMETER") | ||||||
|  | private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||||
|     if (args.size != 1) |     if (args.size != 1) | ||||||
|         throw SyntaxError("sgn requires one argument", position) |         throw SyntaxError("sgn requires one argument", position) | ||||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() |     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| package prog8.compiler.target |  | ||||||
|  |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.ErrorReporter |  | ||||||
| import prog8.compiler.CompilationOptions |  | ||||||
| import prog8.compiler.Zeropage |  | ||||||
| import prog8.compiler.target.c64.C64MachineDefinition |  | ||||||
| import prog8.compiler.target.c64.Petscii |  | ||||||
| import prog8.compiler.target.c64.codegen.AsmGen |  | ||||||
| import prog8.compiler.target.cx16.CX16MachineDefinition |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal interface CompilationTarget { |  | ||||||
|     val name: String |  | ||||||
|     val machine: IMachineDefinition |  | ||||||
|     fun encodeString(str: String, altEncoding: Boolean): List<Short> |  | ||||||
|     fun decodeString(bytes: List<Short>, altEncoding: Boolean): String |  | ||||||
|     fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         lateinit var instance: CompilationTarget |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal object C64Target: CompilationTarget { |  | ||||||
|     override val name = "c64" |  | ||||||
|     override val machine = C64MachineDefinition |  | ||||||
|     override fun encodeString(str: String, altEncoding: Boolean) = |  | ||||||
|             if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) |  | ||||||
|     override fun decodeString(bytes: List<Short>, altEncoding: Boolean) = |  | ||||||
|             if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) |  | ||||||
|     override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) = |  | ||||||
|             AsmGen(program, errors, zp, options, path) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal object Cx16Target: CompilationTarget { |  | ||||||
|     override val name = "cx16" |  | ||||||
|     override val machine = CX16MachineDefinition |  | ||||||
|     override fun encodeString(str: String, altEncoding: Boolean) = |  | ||||||
|             if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) |  | ||||||
|     override fun decodeString(bytes: List<Short>, altEncoding: Boolean) = |  | ||||||
|             if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) |  | ||||||
|     override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) = |  | ||||||
|             AsmGen(program, errors, zp, options, path) |  | ||||||
| } |  | ||||||
| @@ -3,10 +3,12 @@ package prog8.compiler.target | |||||||
| import prog8.compiler.CompilationOptions | import prog8.compiler.CompilationOptions | ||||||
|  |  | ||||||
| internal interface IAssemblyGenerator { | internal interface IAssemblyGenerator { | ||||||
|     fun compileToAssembly(optimize: Boolean): IAssemblyProgram |     fun compileToAssembly(): IAssemblyProgram | ||||||
| } | } | ||||||
|  |  | ||||||
| internal const val generatedLabelPrefix = "_prog8_label_" | internal const val generatedLabelPrefix = "_prog8_label_" | ||||||
|  | internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1" | ||||||
|  | internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2" | ||||||
|  |  | ||||||
| internal interface IAssemblyProgram { | internal interface IAssemblyProgram { | ||||||
|     val name: String |     val name: String | ||||||
|   | |||||||
							
								
								
									
										136
									
								
								compiler/src/prog8/compiler/target/ICompilationTarget.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								compiler/src/prog8/compiler/target/ICompilationTarget.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | package prog8.compiler.target | ||||||
|  |  | ||||||
|  | import prog8.ast.IMemSizer | ||||||
|  | import prog8.ast.IStringEncoding | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.* | ||||||
|  | import prog8.ast.expressions.IdentifierReference | ||||||
|  | import prog8.ast.expressions.NumericLiteralValue | ||||||
|  | import prog8.ast.statements.AssignTarget | ||||||
|  | import prog8.compiler.CompilationOptions | ||||||
|  | import prog8.compiler.IErrorReporter | ||||||
|  | import prog8.compiler.Zeropage | ||||||
|  | import prog8.compiler.target.c64.C64MachineDefinition | ||||||
|  | import prog8.compiler.target.cbm.Petscii | ||||||
|  | import prog8.compiler.target.cpu6502.codegen.AsmGen | ||||||
|  | import prog8.compiler.target.cx16.CX16MachineDefinition | ||||||
|  | import java.io.CharConversionException | ||||||
|  | import java.nio.file.Path | ||||||
|  |  | ||||||
|  |  | ||||||
|  | interface ICompilationTarget: IStringEncoding, IMemSizer { | ||||||
|  |     val name: String | ||||||
|  |     val machine: IMachineDefinition | ||||||
|  |     override fun encodeString(str: String, altEncoding: Boolean): List<Short> | ||||||
|  |     override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String | ||||||
|  |  | ||||||
|  |     fun isInRegularRAM(target: AssignTarget, program: Program): Boolean { | ||||||
|  |         val memAddr = target.memoryAddress | ||||||
|  |         val arrayIdx = target.arrayindexed | ||||||
|  |         val ident = target.identifier | ||||||
|  |         when { | ||||||
|  |             memAddr != null -> { | ||||||
|  |                 return when (memAddr.addressExpression) { | ||||||
|  |                     is NumericLiteralValue -> { | ||||||
|  |                         machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt()) | ||||||
|  |                     } | ||||||
|  |                     is IdentifierReference -> { | ||||||
|  |                         val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program) | ||||||
|  |                         if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue) | ||||||
|  |                             machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt()) | ||||||
|  |                         else | ||||||
|  |                             false | ||||||
|  |                     } | ||||||
|  |                     else -> false | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             arrayIdx != null -> { | ||||||
|  |                 val targetStmt = arrayIdx.arrayvar.targetVarDecl(program) | ||||||
|  |                 return if (targetStmt?.type == VarDeclType.MEMORY) { | ||||||
|  |                     val addr = targetStmt.value as? NumericLiteralValue | ||||||
|  |                     if (addr != null) | ||||||
|  |                         machine.isRegularRAMaddress(addr.number.toInt()) | ||||||
|  |                     else | ||||||
|  |                         false | ||||||
|  |                 } else true | ||||||
|  |             } | ||||||
|  |             ident != null -> { | ||||||
|  |                 val decl = ident.targetVarDecl(program)!! | ||||||
|  |                 return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue) | ||||||
|  |                     machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt()) | ||||||
|  |                 else | ||||||
|  |                     true | ||||||
|  |             } | ||||||
|  |             else -> return true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal object C64Target: ICompilationTarget { | ||||||
|  |     override val name = "c64" | ||||||
|  |     override val machine = C64MachineDefinition | ||||||
|  |     override fun encodeString(str: String, altEncoding: Boolean) = | ||||||
|  |         try { | ||||||
|  |             if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) | ||||||
|  |         } catch (x: CharConversionException) { | ||||||
|  |             throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}") | ||||||
|  |         } | ||||||
|  |     override fun decodeString(bytes: List<Short>, altEncoding: Boolean) = | ||||||
|  |         try { | ||||||
|  |             if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) | ||||||
|  |         } catch (x: CharConversionException) { | ||||||
|  |             throw CharConversionException("can't decode string: ${x.message}") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     override fun memorySize(dt: DataType): Int { | ||||||
|  |         return when(dt) { | ||||||
|  |             in ByteDatatypes -> 1 | ||||||
|  |             in WordDatatypes -> 2 | ||||||
|  |             DataType.FLOAT -> machine.FLOAT_MEM_SIZE | ||||||
|  |             in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE | ||||||
|  |             else -> -9999999 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal object Cx16Target: ICompilationTarget { | ||||||
|  |     override val name = "cx16" | ||||||
|  |     override val machine = CX16MachineDefinition | ||||||
|  |     override fun encodeString(str: String, altEncoding: Boolean) = | ||||||
|  |         try { | ||||||
|  |             if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) | ||||||
|  |         } catch (x: CharConversionException) { | ||||||
|  |             throw CharConversionException("can't convert string to target machine's char encoding: ${x.message}") | ||||||
|  |         } | ||||||
|  |     override fun decodeString(bytes: List<Short>, altEncoding: Boolean) = | ||||||
|  |         try { | ||||||
|  |             if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) | ||||||
|  |         } catch (x: CharConversionException) { | ||||||
|  |             throw CharConversionException("can't decode string: ${x.message}") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     override fun memorySize(dt: DataType): Int { | ||||||
|  |         return when(dt) { | ||||||
|  |             in ByteDatatypes -> 1 | ||||||
|  |             in WordDatatypes -> 2 | ||||||
|  |             DataType.FLOAT -> machine.FLOAT_MEM_SIZE | ||||||
|  |             in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE | ||||||
|  |             else -> -9999999 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal fun asmGeneratorFor( | ||||||
|  |     compTarget: ICompilationTarget, | ||||||
|  |     program: Program, | ||||||
|  |     errors: IErrorReporter, | ||||||
|  |     zp: Zeropage, | ||||||
|  |     options: CompilationOptions, | ||||||
|  |     outputDir: Path | ||||||
|  | ): IAssemblyGenerator | ||||||
|  | { | ||||||
|  |     // at the moment we only have one code generation backend (for 6502 and 65c02) | ||||||
|  |     return AsmGen(program, errors, zp, options, compTarget, outputDir) | ||||||
|  | } | ||||||
| @@ -1,22 +1,20 @@ | |||||||
| package prog8.compiler.target | package prog8.compiler.target | ||||||
|  |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.compiler.CompilationOptions | import prog8.compiler.CompilationOptions | ||||||
| import prog8.compiler.Zeropage | import prog8.compiler.Zeropage | ||||||
| import prog8.parser.ModuleImporter |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal interface IMachineFloat { | interface IMachineFloat { | ||||||
|     fun toDouble(): Double |     fun toDouble(): Double | ||||||
|     fun makeFloatFillAsm(): String |     fun makeFloatFillAsm(): String | ||||||
| } | } | ||||||
|  |  | ||||||
| internal enum class CpuType { | enum class CpuType { | ||||||
|     CPU6502, |     CPU6502, | ||||||
|     CPU65c02 |     CPU65c02 | ||||||
| } | } | ||||||
|  |  | ||||||
| internal interface IMachineDefinition { | interface IMachineDefinition { | ||||||
|     val FLOAT_MAX_NEGATIVE: Double |     val FLOAT_MAX_NEGATIVE: Double | ||||||
|     val FLOAT_MAX_POSITIVE: Double |     val FLOAT_MAX_POSITIVE: Double | ||||||
|     val FLOAT_MEM_SIZE: Int |     val FLOAT_MEM_SIZE: Int | ||||||
| @@ -32,8 +30,8 @@ internal interface IMachineDefinition { | |||||||
|  |  | ||||||
|     fun initializeZeropage(compilerOptions: CompilationOptions) |     fun initializeZeropage(compilerOptions: CompilationOptions) | ||||||
|     fun getFloat(num: Number): IMachineFloat |     fun getFloat(num: Number): IMachineFloat | ||||||
|     fun getFloatRomConst(number: Double): String? |  | ||||||
|     fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) |     fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> | ||||||
|     fun launchEmulator(programName: String) |     fun launchEmulator(programName: String) | ||||||
|     fun isRegularRAMaddress(address: Int): Boolean |     fun isRegularRAMaddress(address: Int): Boolean | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +1,10 @@ | |||||||
| package prog8.compiler.target.c64 | package prog8.compiler.target.c64 | ||||||
|  |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.compiler.* | import prog8.compiler.* | ||||||
| import prog8.compiler.target.CpuType | import prog8.compiler.target.CpuType | ||||||
| import prog8.compiler.target.IMachineDefinition | import prog8.compiler.target.IMachineDefinition | ||||||
| import prog8.compiler.target.IMachineFloat | import prog8.compiler.target.IMachineFloat | ||||||
| import prog8.parser.ModuleImporter |  | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
| import java.math.RoundingMode |  | ||||||
| import kotlin.math.absoluteValue | import kotlin.math.absoluteValue | ||||||
| import kotlin.math.pow | import kotlin.math.pow | ||||||
|  |  | ||||||
| @@ -24,7 +21,6 @@ internal object C64MachineDefinition: IMachineDefinition { | |||||||
|     override val RAW_LOAD_ADDRESS = 0xc000 |     override val RAW_LOAD_ADDRESS = 0xc000 | ||||||
|  |  | ||||||
|     // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) |     // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) | ||||||
|     // and some heavily used string constants derived from the two values above |  | ||||||
|     override val ESTACK_LO = 0xce00        //  $ce00-$ceff inclusive |     override val ESTACK_LO = 0xce00        //  $ce00-$ceff inclusive | ||||||
|     override val ESTACK_HI = 0xcf00        //  $ce00-$ceff inclusive |     override val ESTACK_HI = 0xcf00        //  $ce00-$ceff inclusive | ||||||
|  |  | ||||||
| @@ -32,51 +28,18 @@ internal object C64MachineDefinition: IMachineDefinition { | |||||||
|  |  | ||||||
|     override fun getFloat(num: Number) = Mflpt5.fromNumber(num) |     override fun getFloat(num: Number) = Mflpt5.fromNumber(num) | ||||||
|  |  | ||||||
|     override fun getFloatRomConst(number: Double): String? { |     override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> { | ||||||
|         // try to match the ROM float constants to save memory |         return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) | ||||||
|         val mflpt5 = Mflpt5.fromNumber(number) |             listOf("syslib") | ||||||
|         val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4) |         else | ||||||
|         when { |             emptyList() | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const"        // not a ROM const |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const"        // not a ROM const |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI" |  | ||||||
|             floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4" |  | ||||||
|             else -> { |  | ||||||
|                 // attempt to correct for a few rounding issues |  | ||||||
|                 when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) { |  | ||||||
|                     3.1415926536 -> return "floats.FL_PIVAL" |  | ||||||
|                     1.4142135624 -> return "floats.FL_SQRTWO" |  | ||||||
|                     0.7071067812 -> return "floats.FL_SQRHLF" |  | ||||||
|                     0.6931471806 -> return "floats.FL_LOG2" |  | ||||||
|                     else -> {} |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return null |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) { |  | ||||||
|         if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) |  | ||||||
|             importer.importLibraryModule(program, "syslib") |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun launchEmulator(programName: String) { |     override fun launchEmulator(programName: String) { | ||||||
|         for(emulator in listOf("x64sc", "x64")) { |         for(emulator in listOf("x64sc", "x64")) { | ||||||
|             println("\nStarting C-64 emulator $emulator...") |             println("\nStarting C-64 emulator $emulator...") | ||||||
|             val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list", |             val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list", | ||||||
|                     "-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg") |                     "-autostartprgmode", "1", "-autostart-warp", "-autostart", "$programName.prg") | ||||||
|             val processb = ProcessBuilder(cmdline).inheritIO() |             val processb = ProcessBuilder(cmdline).inheritIO() | ||||||
|             val process: Process |             val process: Process | ||||||
|             try { |             try { | ||||||
| @@ -110,7 +73,7 @@ internal object C64MachineDefinition: IMachineDefinition { | |||||||
|     internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) { |     internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||||
|  |  | ||||||
|         override val SCRATCH_B1 = 0x02      // temp storage for a single byte |         override val SCRATCH_B1 = 0x02      // temp storage for a single byte | ||||||
|         override val SCRATCH_REG = 0x03     // temp storage for a register |         override val SCRATCH_REG = 0x03     // temp storage for a register, must be B1+1 | ||||||
|         override val SCRATCH_W1 = 0xfb      // temp storage 1 for a word  $fb+$fc |         override val SCRATCH_W1 = 0xfb      // temp storage 1 for a word  $fb+$fc | ||||||
|         override val SCRATCH_W2 = 0xfd      // temp storage 2 for a word  $fb+$fc |         override val SCRATCH_W2 = 0xfd      // temp storage 2 for a word  $fb+$fc | ||||||
|  |  | ||||||
| @@ -126,8 +89,10 @@ internal object C64MachineDefinition: IMachineDefinition { | |||||||
|                 free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6))        // these are updated by IRQ |                 free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6))        // 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(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, |                     free.addAll(listOf( | ||||||
|                             0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, |                             0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, | ||||||
|  |                             0x16, 0x17, 0x18, 0x19, 0x1a, | ||||||
|  |                             0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, | ||||||
|                             0x22, 0x23, 0x24, 0x25, |                             0x22, 0x23, 0x24, 0x25, | ||||||
|                             0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, |                             0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, | ||||||
|                             0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53, |                             0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53, | ||||||
| @@ -137,27 +102,28 @@ internal object C64MachineDefinition: IMachineDefinition { | |||||||
|                             0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, |                             0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, | ||||||
|                             0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, |                             0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, | ||||||
|                             0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff |                             0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff | ||||||
|                             // 0x90-0xfa is 'kernel work storage area' |                             // 0x90-0xfa is 'kernal work storage area' | ||||||
|                     )) |                     )) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (options.zeropage == ZeropageType.FLOATSAFE) { |                 if (options.zeropage == ZeropageType.FLOATSAFE) { | ||||||
|                     // remove the zero page locations used for floating point operations from the free list |                     // remove the zero page locations used for floating point operations from the free list | ||||||
|                     free.removeAll(listOf( |                     free.removeAll(listOf( | ||||||
|  |                             0x22, 0x23, 0x24, 0x25, | ||||||
|                             0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a, |                             0x10, 0x11, 0x12, 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, | ||||||
|                             0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, |                             0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, | ||||||
|                             0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, |                             0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, | ||||||
|                             0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf |                             0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff | ||||||
|                     )) |                     )) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if(options.zeropage!=ZeropageType.DONTUSE) { |                 if(options.zeropage!=ZeropageType.DONTUSE) { | ||||||
|                     // add the other free Zp addresses, |                     // add the free Zp addresses | ||||||
|                     // these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully: |                     // 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(0x04, 0x05, 0x06, 0x0a, 0x0e, |                     free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e, | ||||||
|                             0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa, |                             0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6, | ||||||
|                             0xb5, 0xb6, 0xf7, 0xf8, 0xf9)) |                             0xb0, 0xb1, 0xbe, 0xbf, 0xf9)) | ||||||
|                 } else { |                 } else { | ||||||
|                     // don't use the zeropage at all |                     // don't use the zeropage at all | ||||||
|                     free.clear() |                     free.clear() | ||||||
|   | |||||||
| @@ -1,699 +0,0 @@ | |||||||
| package prog8.compiler.target.c64.codegen |  | ||||||
|  |  | ||||||
| import prog8.ast.IFunctionCall |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.DirectMemoryWrite |  | ||||||
| import prog8.ast.statements.FunctionCallStatement |  | ||||||
| import prog8.compiler.AssemblyError |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.AsmAssignment |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind |  | ||||||
| import prog8.compiler.toHex |  | ||||||
| import prog8.functions.FSignature |  | ||||||
|  |  | ||||||
| internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) { |  | ||||||
|  |  | ||||||
|     internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) { |  | ||||||
|         translateFunctioncall(fcall, func, false) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) { |  | ||||||
|         translateFunctioncall(fcall, func, true) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) { |  | ||||||
|         val functionName = fcall.target.nameInSource.last() |  | ||||||
|         if (discardResult) { |  | ||||||
|             if (func.pure) |  | ||||||
|                 return  // can just ignore the whole function call altogether |  | ||||||
|             else if (func.returntype != null) |  | ||||||
|                 throw AssemblyError("discarding result of non-pure function $fcall") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         when (functionName) { |  | ||||||
|             "msb" -> funcMsb(fcall) |  | ||||||
|             "lsb" -> funcLsb(fcall) |  | ||||||
|             "mkword" -> funcMkword(fcall, func) |  | ||||||
|             "abs" -> funcAbs(fcall, func) |  | ||||||
|             "swap" -> funcSwap(fcall) |  | ||||||
|             "strlen" -> funcStrlen(fcall) |  | ||||||
|             "min", "max", "sum" -> funcMinMaxSum(fcall, functionName) |  | ||||||
|             "any", "all" -> funcAnyAll(fcall, functionName) |  | ||||||
|             "sgn" -> funcSgn(fcall, func) |  | ||||||
|             "sin", "cos", "tan", "atan", |  | ||||||
|             "ln", "log2", "sqrt", "rad", |  | ||||||
|             "deg", "round", "floor", "ceil", |  | ||||||
|             "rdnf" -> funcVariousFloatFuncs(fcall, func, functionName) |  | ||||||
|             "rol" -> funcRol(fcall) |  | ||||||
|             "rol2" -> funcRol2(fcall) |  | ||||||
|             "ror" -> funcRor(fcall) |  | ||||||
|             "ror2" -> funcRor2(fcall) |  | ||||||
|             "sort" -> funcSort(fcall) |  | ||||||
|             "reverse" -> funcReverse(fcall) |  | ||||||
|             "rsave" -> { |  | ||||||
|                 // save cpu status flag and all registers A, X, Y. |  | ||||||
|                 // see http://6502.org/tutorials/register_preservation.html |  | ||||||
|                 asmgen.out(" php |  sta  P8ZP_SCRATCH_REG | pha  | txa  | pha  | tya  | pha  | lda  P8ZP_SCRATCH_REG") |  | ||||||
|             } |  | ||||||
|             "rrestore" -> { |  | ||||||
|                 // restore all registers and cpu status flag |  | ||||||
|                 asmgen.out(" pla |  tay |  pla |  tax |  pla |  plp") |  | ||||||
|             } |  | ||||||
|             "clear_carry" -> asmgen.out("  clc") |  | ||||||
|             "set_carry" -> asmgen.out("  sec") |  | ||||||
|             "clear_irqd" -> asmgen.out("  cli") |  | ||||||
|             "set_irqd" -> asmgen.out("  sei") |  | ||||||
|             else -> { |  | ||||||
|                 translateFunctionArguments(fcall.args, func) |  | ||||||
|                 asmgen.out("  jsr  prog8_lib.func_$functionName") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcReverse(fcall: IFunctionCall) { |  | ||||||
|         val variable = fcall.args.single() |  | ||||||
|         if (variable is IdentifierReference) { |  | ||||||
|             val decl = variable.targetVarDecl(program.namespace)!! |  | ||||||
|             val varName = asmgen.asmVariableName(variable) |  | ||||||
|             val numElements = decl.arraysize!!.constIndex() |  | ||||||
|             when (decl.datatype) { |  | ||||||
|                 DataType.ARRAY_UB, DataType.ARRAY_B -> { |  | ||||||
|                     asmgen.out(""" |  | ||||||
|                                     lda  #<$varName |  | ||||||
|                                     ldy  #>$varName |  | ||||||
|                                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                                     sty  P8ZP_SCRATCH_W1+1 |  | ||||||
|                                     lda  #$numElements |  | ||||||
|                                     jsr  prog8_lib.reverse_b |  | ||||||
|                                 """) |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_UW, DataType.ARRAY_W -> { |  | ||||||
|                     asmgen.out(""" |  | ||||||
|                                     lda  #<$varName |  | ||||||
|                                     ldy  #>$varName |  | ||||||
|                                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                                     sty  P8ZP_SCRATCH_W1+1 |  | ||||||
|                                     lda  #$numElements |  | ||||||
|                                     jsr  prog8_lib.reverse_w |  | ||||||
|                                 """) |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_F -> { |  | ||||||
|                     asmgen.out(""" |  | ||||||
|                                     lda  #<$varName |  | ||||||
|                                     ldy  #>$varName |  | ||||||
|                                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                                     sty  P8ZP_SCRATCH_W1+1 |  | ||||||
|                                     lda  #$numElements |  | ||||||
|                                     jsr  prog8_lib.reverse_f |  | ||||||
|                                 """) |  | ||||||
|                 } |  | ||||||
|                 else -> throw AssemblyError("weird type") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcSort(fcall: IFunctionCall) { |  | ||||||
|         val variable = fcall.args.single() |  | ||||||
|         if (variable is IdentifierReference) { |  | ||||||
|             val decl = variable.targetVarDecl(program.namespace)!! |  | ||||||
|             val varName = asmgen.asmVariableName(variable) |  | ||||||
|             val numElements = decl.arraysize!!.constIndex() |  | ||||||
|             when (decl.datatype) { |  | ||||||
|                 DataType.ARRAY_UB, DataType.ARRAY_B -> { |  | ||||||
|                     asmgen.out(""" |  | ||||||
|                                     lda  #<$varName |  | ||||||
|                                     ldy  #>$varName |  | ||||||
|                                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                                     sty  P8ZP_SCRATCH_W1+1 |  | ||||||
|                                     lda  #$numElements |  | ||||||
|                                     sta  P8ZP_SCRATCH_B1 |  | ||||||
|                                 """) |  | ||||||
|                     asmgen.out(if (decl.datatype == DataType.ARRAY_UB) "  jsr  prog8_lib.sort_ub" else "  jsr  prog8_lib.sort_b") |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_UW, DataType.ARRAY_W -> { |  | ||||||
|                     asmgen.out(""" |  | ||||||
|                                     lda  #<$varName |  | ||||||
|                                     ldy  #>$varName |  | ||||||
|                                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                                     sty  P8ZP_SCRATCH_W1+1 |  | ||||||
|                                     lda  #$numElements |  | ||||||
|                                     sta  P8ZP_SCRATCH_B1 |  | ||||||
|                                 """) |  | ||||||
|                     asmgen.out(if (decl.datatype == DataType.ARRAY_UW) "  jsr  prog8_lib.sort_uw" else "  jsr  prog8_lib.sort_w") |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported") |  | ||||||
|                 else -> throw AssemblyError("weird type") |  | ||||||
|             } |  | ||||||
|         } else |  | ||||||
|             throw AssemblyError("weird type") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcRor2(fcall: IFunctionCall) { |  | ||||||
|         val what = fcall.args.single() |  | ||||||
|         val dt = what.inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             DataType.UBYTE -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.ror2_array_ub") |  | ||||||
|                     } |  | ||||||
|                     is DirectMemoryRead -> { |  | ||||||
|                         if (what.addressExpression is NumericLiteralValue) { |  | ||||||
|                             val number = (what.addressExpression as NumericLiteralValue).number |  | ||||||
|                             asmgen.out("  lda  ${number.toHex()} |  lsr  a |  bcc  + |  ora  #\$80 |+  |  sta  ${number.toHex()}") |  | ||||||
|                         } else { |  | ||||||
|                             asmgen.translateExpression(what.addressExpression) |  | ||||||
|                             asmgen.out("  jsr  prog8_lib.ror2_mem_ub") |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  lda  $variable |  lsr  a |  bcc  + |  ora  #\$80 |+  |  sta  $variable") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.UWORD -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.ror2_array_uw") |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  lsr  $variable+1 |  ror  $variable |  bcc  + |  lda  $variable+1 |  ora  #\$80 |  sta  $variable+1 |+  ") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("weird type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcRor(fcall: IFunctionCall) { |  | ||||||
|         val what = fcall.args.single() |  | ||||||
|         val dt = what.inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             DataType.UBYTE -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.ror_array_ub") |  | ||||||
|                     } |  | ||||||
|                     is DirectMemoryRead -> { |  | ||||||
|                         if (what.addressExpression is NumericLiteralValue) { |  | ||||||
|                             val number = (what.addressExpression as NumericLiteralValue).number |  | ||||||
|                             asmgen.out("  ror  ${number.toHex()}") |  | ||||||
|                         } else { |  | ||||||
|                             asmgen.translateExpression(what.addressExpression) |  | ||||||
|                             asmgen.out(""" |  | ||||||
|                         inx |  | ||||||
|                         lda  P8ESTACK_LO,x |  | ||||||
|                         sta  (+) + 1 |  | ||||||
|                         lda  P8ESTACK_HI,x |  | ||||||
|                         sta  (+) + 2 |  | ||||||
|     +                   ror  ${'$'}ffff            ; modified                     |  | ||||||
|                                         """) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  ror  $variable") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.UWORD -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.ror_array_uw") |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  ror  $variable+1 |  ror  $variable") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("weird type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcRol2(fcall: IFunctionCall) { |  | ||||||
|         val what = fcall.args.single() |  | ||||||
|         val dt = what.inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             DataType.UBYTE -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.rol2_array_ub") |  | ||||||
|                     } |  | ||||||
|                     is DirectMemoryRead -> { |  | ||||||
|                         if (what.addressExpression is NumericLiteralValue) { |  | ||||||
|                             val number = (what.addressExpression as NumericLiteralValue).number |  | ||||||
|                             asmgen.out("  lda  ${number.toHex()} |  cmp  #\$80 |  rol  a |  sta  ${number.toHex()}") |  | ||||||
|                         } else { |  | ||||||
|                             asmgen.translateExpression(what.addressExpression) |  | ||||||
|                             asmgen.out("  jsr  prog8_lib.rol2_mem_ub") |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  lda  $variable |  cmp  #\$80 |  rol  a |  sta  $variable") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.UWORD -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.rol2_array_uw") |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  asl  $variable |  rol  $variable+1 |  bcc  + |  inc  $variable |+  ") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("weird type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcRol(fcall: IFunctionCall) { |  | ||||||
|         val what = fcall.args.single() |  | ||||||
|         val dt = what.inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             DataType.UBYTE -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.rol_array_ub") |  | ||||||
|                     } |  | ||||||
|                     is DirectMemoryRead -> { |  | ||||||
|                         if (what.addressExpression is NumericLiteralValue) { |  | ||||||
|                             val number = (what.addressExpression as NumericLiteralValue).number |  | ||||||
|                             asmgen.out("  rol  ${number.toHex()}") |  | ||||||
|                         } else { |  | ||||||
|                             asmgen.translateExpression(what.addressExpression) |  | ||||||
|                             asmgen.out(""" |  | ||||||
|                         inx |  | ||||||
|                         lda  P8ESTACK_LO,x |  | ||||||
|                         sta  (+) + 1 |  | ||||||
|                         lda  P8ESTACK_HI,x |  | ||||||
|                         sta  (+) + 2 |  | ||||||
|     +                   rol  ${'$'}ffff            ; modified                     |  | ||||||
|                                         """) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  rol  $variable") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.UWORD -> { |  | ||||||
|                 when (what) { |  | ||||||
|                     is ArrayIndexedExpression -> { |  | ||||||
|                         asmgen.translateExpression(what.identifier) |  | ||||||
|                         asmgen.translateExpression(what.arrayspec.index) |  | ||||||
|                         asmgen.out("  jsr  prog8_lib.rol_array_uw") |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val variable = asmgen.asmVariableName(what) |  | ||||||
|                         asmgen.out("  rol  $variable |  rol  $variable+1") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("weird type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("weird type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) { |  | ||||||
|         translateFunctionArguments(fcall.args, func) |  | ||||||
|         asmgen.out("  jsr  floats.func_$functionName") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcSgn(fcall: IFunctionCall, func: FSignature) { |  | ||||||
|         translateFunctionArguments(fcall.args, func) |  | ||||||
|         val dt = fcall.args.single().inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             DataType.UBYTE -> asmgen.out("  jsr  math.sign_ub") |  | ||||||
|             DataType.BYTE -> asmgen.out("  jsr  math.sign_b") |  | ||||||
|             DataType.UWORD -> asmgen.out("  jsr  math.sign_uw") |  | ||||||
|             DataType.WORD -> asmgen.out("  jsr  math.sign_w") |  | ||||||
|             DataType.FLOAT -> asmgen.out("  jsr  floats.sign_f") |  | ||||||
|             else -> throw AssemblyError("weird type $dt") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcAnyAll(fcall: IFunctionCall, functionName: String) { |  | ||||||
|         outputPushAddressAndLenghtOfArray(fcall.args[0]) |  | ||||||
|         val dt = fcall.args.single().inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out("  jsr  prog8_lib.func_${functionName}_b") |  | ||||||
|             DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out("  jsr  prog8_lib.func_${functionName}_w") |  | ||||||
|             DataType.ARRAY_F -> asmgen.out("  jsr  floats.func_${functionName}_f") |  | ||||||
|             else -> throw AssemblyError("weird type $dt") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcMinMaxSum(fcall: IFunctionCall, functionName: String) { |  | ||||||
|         outputPushAddressAndLenghtOfArray(fcall.args[0]) |  | ||||||
|         val dt = fcall.args.single().inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             DataType.ARRAY_UB, DataType.STR -> asmgen.out("  jsr  prog8_lib.func_${functionName}_ub") |  | ||||||
|             DataType.ARRAY_B -> asmgen.out("  jsr  prog8_lib.func_${functionName}_b") |  | ||||||
|             DataType.ARRAY_UW -> asmgen.out("  jsr  prog8_lib.func_${functionName}_uw") |  | ||||||
|             DataType.ARRAY_W -> asmgen.out("  jsr  prog8_lib.func_${functionName}_w") |  | ||||||
|             DataType.ARRAY_F -> asmgen.out("  jsr  floats.func_${functionName}_f") |  | ||||||
|             else -> throw AssemblyError("weird type $dt") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcStrlen(fcall: IFunctionCall) { |  | ||||||
|         val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference) |  | ||||||
|         asmgen.out(""" |  | ||||||
|             lda  #<$name |  | ||||||
|             ldy  #>$name |  | ||||||
|             jsr  prog8_lib.strlen |  | ||||||
|             sta  P8ESTACK_LO,x |  | ||||||
|             dex""") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcSwap(fcall: IFunctionCall) { |  | ||||||
|         val first = fcall.args[0] |  | ||||||
|         val second = fcall.args[1] |  | ||||||
|  |  | ||||||
|         // optimized simple case: swap two variables |  | ||||||
|         if(first is IdentifierReference && second is IdentifierReference) { |  | ||||||
|             val firstName = asmgen.asmVariableName(first) |  | ||||||
|             val secondName = asmgen.asmVariableName(second) |  | ||||||
|             val dt = first.inferType(program) |  | ||||||
|             if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) { |  | ||||||
|                 asmgen.out(" ldy  $firstName |  lda  $secondName |  sta  $firstName |  sty  $secondName") |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
|             if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     ldy  $firstName |  | ||||||
|                     lda  $secondName |  | ||||||
|                     sta  $firstName |  | ||||||
|                     sty  $secondName |  | ||||||
|                     ldy  $firstName+1 |  | ||||||
|                     lda  $secondName+1 |  | ||||||
|                     sta  $firstName+1 |  | ||||||
|                     sty  $secondName+1 |  | ||||||
|                 """) |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
|             if(dt.istype(DataType.FLOAT)) { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     lda  #<$firstName |  | ||||||
|                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                     lda  #>$firstName |  | ||||||
|                     sta  P8ZP_SCRATCH_W1+1 |  | ||||||
|                     lda  #<$secondName |  | ||||||
|                     sta  P8ZP_SCRATCH_W2 |  | ||||||
|                     lda  #>$secondName |  | ||||||
|                     sta  P8ZP_SCRATCH_W2+1 |  | ||||||
|                     jsr  floats.swap_floats |  | ||||||
|                 """) |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // optimized simple case: swap two memory locations |  | ||||||
|         if(first is DirectMemoryRead && second is DirectMemoryRead) { |  | ||||||
|             val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex() |  | ||||||
|             val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex() |  | ||||||
|             val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null |  | ||||||
|             val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null |  | ||||||
|  |  | ||||||
|             when { |  | ||||||
|                 addr1!=null && addr2!=null -> { |  | ||||||
|                     asmgen.out("  ldy  $addr1 |  lda  $addr2 |  sta  $addr1 |  sty  $addr2") |  | ||||||
|                     return |  | ||||||
|                 } |  | ||||||
|                 addr1!=null && name2!=null -> { |  | ||||||
|                     asmgen.out("  ldy  $addr1 |  lda  $name2 |  sta  $addr1 |  sty  $name2") |  | ||||||
|                     return |  | ||||||
|                 } |  | ||||||
|                 name1!=null && addr2 != null -> { |  | ||||||
|                     asmgen.out("  ldy  $name1 |  lda  $addr2 |  sta  $name1 |  sty  $addr2") |  | ||||||
|                     return |  | ||||||
|                 } |  | ||||||
|                 name1!=null && name2!=null -> { |  | ||||||
|                     asmgen.out("  ldy  $name1 |  lda  $name2 |  sta  $name1 |  sty  $name2") |  | ||||||
|                     return |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) { |  | ||||||
|             val indexValue1 = first.arrayspec.index as? NumericLiteralValue |  | ||||||
|             val indexName1 = first.arrayspec.index as? IdentifierReference |  | ||||||
|             val indexValue2 = second.arrayspec.index as? NumericLiteralValue |  | ||||||
|             val indexName2 = second.arrayspec.index as? IdentifierReference |  | ||||||
|             val arrayVarName1 = asmgen.asmVariableName(first.identifier) |  | ||||||
|             val arrayVarName2 = asmgen.asmVariableName(second.identifier) |  | ||||||
|             val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT) |  | ||||||
|  |  | ||||||
|             if(indexValue1!=null && indexValue2!=null) { |  | ||||||
|                 swapArrayValues(elementDt, arrayVarName1, indexValue1, arrayVarName2, indexValue2) |  | ||||||
|                 return |  | ||||||
|             } else if(indexName1!=null && indexName2!=null) { |  | ||||||
|                 swapArrayValues(elementDt, arrayVarName1, indexName1, arrayVarName2, indexName2) |  | ||||||
|                 return |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // all other types of swap() calls are done via the evaluation stack |  | ||||||
|         fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget { |  | ||||||
|             return when (expr) { |  | ||||||
|                 is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, variable=expr) |  | ||||||
|                 is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, array = expr) |  | ||||||
|                 is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, memory = DirectMemoryWrite(expr.addressExpression, expr.position)) |  | ||||||
|                 else -> throw AssemblyError("invalid expression object $expr") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         asmgen.translateExpression(first) |  | ||||||
|         asmgen.translateExpression(second) |  | ||||||
|         val datatype = first.inferType(program).typeOrElse(DataType.STRUCT) |  | ||||||
|         val assignFirst = AsmAssignment( |  | ||||||
|                 AsmAssignSource(SourceStorageKind.STACK, program, datatype), |  | ||||||
|                 targetFromExpr(first, datatype), |  | ||||||
|                 false, first.position |  | ||||||
|         ) |  | ||||||
|         val assignSecond = AsmAssignment( |  | ||||||
|                 AsmAssignSource(SourceStorageKind.STACK, program, datatype), |  | ||||||
|                 targetFromExpr(second, datatype), |  | ||||||
|                 false, second.position |  | ||||||
|         ) |  | ||||||
|         asmgen.translateNormalAssignment(assignFirst) |  | ||||||
|         asmgen.translateNormalAssignment(assignSecond) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) { |  | ||||||
|         val index1 = indexValue1.number.toInt() * elementDt.memorySize() |  | ||||||
|         val index2 = indexValue2.number.toInt() * elementDt.memorySize() |  | ||||||
|         when(elementDt) { |  | ||||||
|             DataType.UBYTE, DataType.BYTE -> { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     lda  $arrayVarName1+$index1 |  | ||||||
|                     ldy  $arrayVarName2+$index2 |  | ||||||
|                     sta  $arrayVarName2+$index2 |  | ||||||
|                     sty  $arrayVarName1+$index1 |  | ||||||
|                 """) |  | ||||||
|             } |  | ||||||
|             DataType.UWORD, DataType.WORD -> { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     lda  $arrayVarName1+$index1 |  | ||||||
|                     ldy  $arrayVarName2+$index2 |  | ||||||
|                     sta  $arrayVarName2+$index2 |  | ||||||
|                     sty  $arrayVarName1+$index1 |  | ||||||
|                     lda  $arrayVarName1+$index1+1 |  | ||||||
|                     ldy  $arrayVarName2+$index2+1 |  | ||||||
|                     sta  $arrayVarName2+$index2+1 |  | ||||||
|                     sty  $arrayVarName1+$index1+1 |  | ||||||
|                 """) |  | ||||||
|             } |  | ||||||
|             DataType.FLOAT -> { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     lda  #<(${arrayVarName1}+$index1) |  | ||||||
|                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                     lda  #>(${arrayVarName1}+$index1) |  | ||||||
|                     sta  P8ZP_SCRATCH_W1+1 |  | ||||||
|                     lda  #<(${arrayVarName2}+$index2) |  | ||||||
|                     sta  P8ZP_SCRATCH_W2 |  | ||||||
|                     lda  #>(${arrayVarName2}+$index2) |  | ||||||
|                     sta  P8ZP_SCRATCH_W2+1 |  | ||||||
|                     jsr  floats.swap_floats |  | ||||||
|                 """) |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("invalid aray elt type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexName2: IdentifierReference) { |  | ||||||
|         val idxAsmName1 = asmgen.asmVariableName(indexName1) |  | ||||||
|         val idxAsmName2 = asmgen.asmVariableName(indexName2) |  | ||||||
|         when(elementDt) { |  | ||||||
|             DataType.UBYTE, DataType.BYTE -> { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     stx  P8ZP_SCRATCH_REG |  | ||||||
|                     ldx  $idxAsmName1 |  | ||||||
|                     ldy  $idxAsmName2 |  | ||||||
|                     lda  $arrayVarName1,x |  | ||||||
|                     pha |  | ||||||
|                     lda  $arrayVarName2,y |  | ||||||
|                     sta  $arrayVarName1,x |  | ||||||
|                     pla |  | ||||||
|                     sta  $arrayVarName2,y |  | ||||||
|                     ldx  P8ZP_SCRATCH_REG |  | ||||||
|                 """) |  | ||||||
|             } |  | ||||||
|             DataType.UWORD, DataType.WORD -> { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     stx  P8ZP_SCRATCH_REG |  | ||||||
|                     lda  $idxAsmName1 |  | ||||||
|                     asl  a |  | ||||||
|                     tax |  | ||||||
|                     lda  $idxAsmName2 |  | ||||||
|                     asl  a |  | ||||||
|                     tay |  | ||||||
|                     lda  $arrayVarName1,x |  | ||||||
|                     pha |  | ||||||
|                     lda  $arrayVarName2,y |  | ||||||
|                     sta  $arrayVarName1,x |  | ||||||
|                     pla |  | ||||||
|                     sta  $arrayVarName2,y |  | ||||||
|                     lda  $arrayVarName1+1,x |  | ||||||
|                     pha |  | ||||||
|                     lda  $arrayVarName2+1,y |  | ||||||
|                     sta  $arrayVarName1+1,x |  | ||||||
|                     pla |  | ||||||
|                     sta  $arrayVarName2+1,y                     |  | ||||||
|                     ldx  P8ZP_SCRATCH_REG |  | ||||||
|                 """) |  | ||||||
|             } |  | ||||||
|             DataType.FLOAT -> { |  | ||||||
|                 asmgen.out(""" |  | ||||||
|                     lda  #>$arrayVarName1 |  | ||||||
|                     sta  P8ZP_SCRATCH_W1+1 |  | ||||||
|                     lda  $idxAsmName1 |  | ||||||
|                     asl  a |  | ||||||
|                     asl  a |  | ||||||
|                     clc |  | ||||||
|                     adc  $idxAsmName1 |  | ||||||
|                     adc  #<$arrayVarName1 |  | ||||||
|                     sta  P8ZP_SCRATCH_W1 |  | ||||||
|                     bcc  + |  | ||||||
|                     inc  P8ZP_SCRATCH_W1+1 |  | ||||||
| +                   lda  #>$arrayVarName2 |  | ||||||
|                     sta  P8ZP_SCRATCH_W2+1 |  | ||||||
|                     lda  $idxAsmName2 |  | ||||||
|                     asl  a |  | ||||||
|                     asl  a |  | ||||||
|                     clc |  | ||||||
|                     adc  $idxAsmName2 |  | ||||||
|                     adc  #<$arrayVarName2 |  | ||||||
|                     sta  P8ZP_SCRATCH_W2 |  | ||||||
|                     bcc  + |  | ||||||
|                     inc  P8ZP_SCRATCH_W2+1 |  | ||||||
| +                   jsr  floats.swap_floats                                    |  | ||||||
|                 """) |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("invalid aray elt type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcAbs(fcall: IFunctionCall, func: FSignature) { |  | ||||||
|         translateFunctionArguments(fcall.args, func) |  | ||||||
|         val dt = fcall.args.single().inferType(program) |  | ||||||
|         when (dt.typeOrElse(DataType.STRUCT)) { |  | ||||||
|             in ByteDatatypes -> asmgen.out("  jsr  prog8_lib.abs_b") |  | ||||||
|             in WordDatatypes -> asmgen.out("  jsr  prog8_lib.abs_w") |  | ||||||
|             DataType.FLOAT -> asmgen.out("  jsr  floats.abs_f") |  | ||||||
|             else -> throw AssemblyError("weird type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcMkword(fcall: IFunctionCall, func: FSignature) { |  | ||||||
|         // trick: push the args in reverse order (msb first, lsb second) this saves some instructions |  | ||||||
|         asmgen.translateExpression(fcall.args[1]) |  | ||||||
|         asmgen.translateExpression(fcall.args[0]) |  | ||||||
|         asmgen.out("  inx | lda  P8ESTACK_LO,x  | sta  P8ESTACK_HI+1,x") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcMsb(fcall: IFunctionCall) { |  | ||||||
|         val arg = fcall.args.single() |  | ||||||
|         if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes) |  | ||||||
|             throw AssemblyError("msb required word argument") |  | ||||||
|         if (arg is NumericLiteralValue) |  | ||||||
|             throw AssemblyError("msb(const) should have been const-folded away") |  | ||||||
|         if (arg is IdentifierReference) { |  | ||||||
|             val sourceName = asmgen.asmVariableName(arg) |  | ||||||
|             asmgen.out("  lda  $sourceName+1 |  sta  P8ESTACK_LO,x |  dex") |  | ||||||
|         } else { |  | ||||||
|             asmgen.translateExpression(arg) |  | ||||||
|             asmgen.out("  lda  P8ESTACK_HI+1,x |  sta  P8ESTACK_LO+1,x") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun funcLsb(fcall: IFunctionCall) { |  | ||||||
|         val arg = fcall.args.single() |  | ||||||
|         if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes) |  | ||||||
|             throw AssemblyError("lsb required word argument") |  | ||||||
|         if (arg is NumericLiteralValue) |  | ||||||
|             throw AssemblyError("lsb(const) should have been const-folded away") |  | ||||||
|         if (arg is IdentifierReference) { |  | ||||||
|             val sourceName = asmgen.asmVariableName(arg) |  | ||||||
|             asmgen.out("  lda  $sourceName |  sta  P8ESTACK_LO,x |  dex") |  | ||||||
|         } else { |  | ||||||
|             asmgen.translateExpression(arg) |  | ||||||
|             // just ignore any high-byte |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun outputPushAddressAndLenghtOfArray(arg: Expression) { |  | ||||||
|         arg as IdentifierReference |  | ||||||
|         val identifierName = asmgen.asmVariableName(arg) |  | ||||||
|         val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!! |  | ||||||
|         asmgen.out(""" |  | ||||||
|                     lda  #<$identifierName |  | ||||||
|                     sta  P8ESTACK_LO,x |  | ||||||
|                     lda  #>$identifierName |  | ||||||
|                     sta  P8ESTACK_HI,x |  | ||||||
|                     dex |  | ||||||
|                     lda  #$size |  | ||||||
|                     sta  P8ESTACK_LO,x |  | ||||||
|                     dex |  | ||||||
|                     """) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) { |  | ||||||
|         args.forEach { |  | ||||||
|             asmgen.translateExpression(it) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,249 +0,0 @@ | |||||||
| package prog8.compiler.target.c64.codegen |  | ||||||
|  |  | ||||||
| import prog8.ast.IFunctionCall |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.RegisterOrStatusflag |  | ||||||
| import prog8.ast.statements.Subroutine |  | ||||||
| import prog8.ast.statements.SubroutineParameter |  | ||||||
| import prog8.compiler.AssemblyError |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.* |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { |  | ||||||
|  |  | ||||||
|     internal fun translateFunctionCall(stmt: IFunctionCall) { |  | ||||||
|         // output the code to setup the parameters and perform the actual call |  | ||||||
|         // does NOT output the code to deal with the result values! |  | ||||||
|         val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") |  | ||||||
|         val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam() |  | ||||||
|         if(saveX) |  | ||||||
|             asmgen.saveRegister(CpuRegister.X) |  | ||||||
|  |  | ||||||
|         val subName = asmgen.asmSymbolName(stmt.target) |  | ||||||
|         if(stmt.args.isNotEmpty()) { |  | ||||||
|             if(sub.asmParameterRegisters.isEmpty()) { |  | ||||||
|                 // via variables |  | ||||||
|                 for(arg in sub.parameters.withIndex().zip(stmt.args)) { |  | ||||||
|                     argumentViaVariable(sub, arg.first, arg.second) |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 // via registers |  | ||||||
|                 if(sub.parameters.size==1) { |  | ||||||
|                     // just a single parameter, no risk of clobbering registers |  | ||||||
|                     argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0]) |  | ||||||
|                 } else { |  | ||||||
|                     // multiple register arguments, risk of register clobbering. |  | ||||||
|                     // evaluate arguments onto the stack, and load the registers from the evaluated values on the stack. |  | ||||||
|                     when { |  | ||||||
|                         stmt.args.all {it is AddressOf || |  | ||||||
|                                 it is NumericLiteralValue || |  | ||||||
|                                 it is StringLiteralValue || |  | ||||||
|                                 it is ArrayLiteralValue || |  | ||||||
|                                 it is IdentifierReference} -> { |  | ||||||
|                             // no risk of clobbering for these simple argument types. Optimize the register loading. |  | ||||||
|                             for(arg in sub.parameters.withIndex().zip(stmt.args)) { |  | ||||||
|                                 argumentViaRegister(sub, arg.first, arg.second) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         else -> { |  | ||||||
|                             // Risk of clobbering due to complex expression args. Work via the stack. |  | ||||||
|                             registerArgsViaStackEvaluation(stmt, sub) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         asmgen.out("  jsr  $subName") |  | ||||||
|  |  | ||||||
|         if(saveX) |  | ||||||
|             asmgen.restoreRegister(CpuRegister.X) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) { |  | ||||||
|         // this is called when one or more of the arguments are 'complex' and |  | ||||||
|         // cannot be assigned to a register easily or risk clobbering other registers. |  | ||||||
|  |  | ||||||
|         if(sub.parameters.isEmpty()) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         // 1. load all arguments reversed onto the stack: first arg goes last (is on top). |  | ||||||
|         for (arg in stmt.args.reversed()) |  | ||||||
|             asmgen.translateExpression(arg) |  | ||||||
|  |  | ||||||
|         var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null |  | ||||||
|         var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null |  | ||||||
|         var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null |  | ||||||
|  |  | ||||||
|         asmgen.out("  inx")     // align estack pointer |  | ||||||
|  |  | ||||||
|         for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) { |  | ||||||
|             when { |  | ||||||
|                 argi.value.second.stack -> TODO("asmsub @stack parameter") |  | ||||||
|                 argi.value.second.statusflag == Statusflag.Pc -> { |  | ||||||
|                     require(argForCarry == null) |  | ||||||
|                     argForCarry = argi |  | ||||||
|                 } |  | ||||||
|                 argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter") |  | ||||||
|                 argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> { |  | ||||||
|                     require(argForXregister==null) |  | ||||||
|                     argForXregister = argi |  | ||||||
|                 } |  | ||||||
|                 argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> { |  | ||||||
|                     require(argForAregister == null) |  | ||||||
|                     argForAregister = argi |  | ||||||
|                 } |  | ||||||
|                 argi.value.second.registerOrPair == RegisterOrPair.Y -> { |  | ||||||
|                     asmgen.out("  ldy  P8ESTACK_LO+${argi.index},x") |  | ||||||
|                 } |  | ||||||
|                 else -> throw AssemblyError("weird argument") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(argForCarry!=null) { |  | ||||||
|             asmgen.out(""" |  | ||||||
|                 lda  P8ESTACK_LO+${argForCarry.index},x |  | ||||||
|                 beq  + |  | ||||||
|                 sec |  | ||||||
|                 bcs  ++ |  | ||||||
| +               clc |  | ||||||
| +               php""")             // push the status flags |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(argForAregister!=null) { |  | ||||||
|             when(argForAregister.value.second.registerOrPair) { |  | ||||||
|                 RegisterOrPair.A -> asmgen.out("  lda  P8ESTACK_LO+${argForAregister.index},x") |  | ||||||
|                 RegisterOrPair.AY -> asmgen.out("  lda  P8ESTACK_LO+${argForAregister.index},x |  ldy  P8ESTACK_HI+${argForAregister.index},x") |  | ||||||
|                 else -> throw AssemblyError("weird arg") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(argForXregister!=null) { |  | ||||||
|  |  | ||||||
|             if(argForAregister!=null) |  | ||||||
|                 asmgen.out("  pha") |  | ||||||
|             when(argForXregister.value.second.registerOrPair) { |  | ||||||
|                 RegisterOrPair.X -> asmgen.out("  lda  P8ESTACK_LO+${argForXregister.index},x |  tax") |  | ||||||
|                 RegisterOrPair.AX -> asmgen.out("  ldy  P8ESTACK_LO+${argForXregister.index},x |  lda  P8ESTACK_HI+${argForXregister.index},x |  tax |  tya") |  | ||||||
|                 RegisterOrPair.XY -> asmgen.out("  ldy  P8ESTACK_HI+${argForXregister.index},x |  lda  P8ESTACK_LO+${argForXregister.index},x |  tax") |  | ||||||
|                 else -> throw AssemblyError("weird arg") |  | ||||||
|             } |  | ||||||
|             if(argForAregister!=null) |  | ||||||
|                 asmgen.out("  pla") |  | ||||||
|         } else { |  | ||||||
|             repeat(sub.parameters.size - 1) { asmgen.out("  inx") }       // unwind stack |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(argForCarry!=null) |  | ||||||
|             asmgen.out("  plp")       // set the carry flag back to correct value |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) { |  | ||||||
|         // pass parameter via a regular variable (not via registers) |  | ||||||
|         val valueIDt = value.inferType(program) |  | ||||||
|         if(!valueIDt.isKnown) |  | ||||||
|             throw AssemblyError("arg type unknown") |  | ||||||
|         val valueDt = valueIDt.typeOrElse(DataType.STRUCT) |  | ||||||
|         if(!isArgumentTypeCompatible(valueDt, parameter.value.type)) |  | ||||||
|             throw AssemblyError("argument type incompatible") |  | ||||||
|  |  | ||||||
|         val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".") |  | ||||||
|         val identifier = IdentifierReference(scopedParamVar, sub.position) |  | ||||||
|         identifier.linkParents(value.parent) |  | ||||||
|         val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier) |  | ||||||
|         val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt) |  | ||||||
|         val asgn = AsmAssignment(source, tgt, false, Position.DUMMY) |  | ||||||
|         asmgen.translateNormalAssignment(asgn) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) { |  | ||||||
|         // pass argument via a register parameter |  | ||||||
|         val valueIDt = value.inferType(program) |  | ||||||
|         if(!valueIDt.isKnown) |  | ||||||
|             throw AssemblyError("arg type unknown") |  | ||||||
|         val valueDt = valueIDt.typeOrElse(DataType.STRUCT) |  | ||||||
|         if(!isArgumentTypeCompatible(valueDt, parameter.value.type)) |  | ||||||
|             throw AssemblyError("argument type incompatible") |  | ||||||
|  |  | ||||||
|         val paramRegister = sub.asmParameterRegisters[parameter.index] |  | ||||||
|         val statusflag = paramRegister.statusflag |  | ||||||
|         val register = paramRegister.registerOrPair |  | ||||||
|         val stack = paramRegister.stack |  | ||||||
|         when { |  | ||||||
|             stack -> { |  | ||||||
|                 // push arg onto the stack |  | ||||||
|                 // note: argument order is reversed (first argument will be deepest on the stack) |  | ||||||
|                 asmgen.translateExpression(value) |  | ||||||
|             } |  | ||||||
|             statusflag!=null -> { |  | ||||||
|                 if (statusflag == Statusflag.Pc) { |  | ||||||
|                     // this param needs to be set last, right before the jsr |  | ||||||
|                     // for now, this is already enforced on the subroutine definition by the Ast Checker |  | ||||||
|                     when(value) { |  | ||||||
|                         is NumericLiteralValue -> { |  | ||||||
|                             val carrySet = value.number.toInt() != 0 |  | ||||||
|                             asmgen.out(if(carrySet) "  sec" else "  clc") |  | ||||||
|                         } |  | ||||||
|                         is IdentifierReference -> { |  | ||||||
|                             val sourceName = asmgen.asmVariableName(value) |  | ||||||
|                             asmgen.out(""" |  | ||||||
|             pha |  | ||||||
|             lda  $sourceName |  | ||||||
|             beq  + |  | ||||||
|             sec   |  | ||||||
|             bcs  ++ |  | ||||||
| +           clc |  | ||||||
| +           pla |  | ||||||
| """) |  | ||||||
|                         } |  | ||||||
|                         else -> { |  | ||||||
|                             asmgen.translateExpression(value) |  | ||||||
|                             asmgen.out(""" |  | ||||||
|             inx |  | ||||||
|             pha |  | ||||||
|             lda  P8ESTACK_LO,x |  | ||||||
|             beq  + |  | ||||||
|             sec   |  | ||||||
|             bcs  ++ |  | ||||||
| +           clc |  | ||||||
| +           pla |  | ||||||
| """) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 else throw AssemblyError("can only use Carry as status flag parameter") |  | ||||||
|             } |  | ||||||
|             else -> { |  | ||||||
|                 // via register or register pair |  | ||||||
|                 val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen) |  | ||||||
|                 val src = if(valueDt in PassByReferenceDatatypes) { |  | ||||||
|                     val addr = AddressOf(value as IdentifierReference, Position.DUMMY) |  | ||||||
|                     AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target) |  | ||||||
|                 } else { |  | ||||||
|                     AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target) |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY)) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { |  | ||||||
|         if(argType isAssignableTo paramType) |  | ||||||
|             return true |  | ||||||
|         if(argType in ByteDatatypes && paramType in ByteDatatypes) |  | ||||||
|             return true |  | ||||||
|         if(argType in WordDatatypes && paramType in WordDatatypes) |  | ||||||
|             return true |  | ||||||
|  |  | ||||||
|         // we have a special rule for some types. |  | ||||||
|         // strings are assignable to UWORD, for example, and vice versa |  | ||||||
|         if(argType==DataType.STR && paramType==DataType.UWORD) |  | ||||||
|             return true |  | ||||||
|         if(argType==DataType.UWORD && paramType == DataType.STR) |  | ||||||
|             return true |  | ||||||
|  |  | ||||||
|         return false |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,135 +0,0 @@ | |||||||
| package prog8.compiler.target.c64.codegen |  | ||||||
|  |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.IdentifierReference |  | ||||||
| import prog8.ast.expressions.NumericLiteralValue |  | ||||||
| import prog8.ast.statements.PostIncrDecr |  | ||||||
| import prog8.compiler.AssemblyError |  | ||||||
| import prog8.compiler.toHex |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { |  | ||||||
|     internal fun translate(stmt: PostIncrDecr) { |  | ||||||
|         val incr = stmt.operator=="++" |  | ||||||
|         val targetIdent = stmt.target.identifier |  | ||||||
|         val targetMemory = stmt.target.memoryAddress |  | ||||||
|         val targetArrayIdx = stmt.target.arrayindexed |  | ||||||
|         when { |  | ||||||
|             targetIdent!=null -> { |  | ||||||
|                 val what = asmgen.asmVariableName(targetIdent) |  | ||||||
|                 when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) { |  | ||||||
|                     in ByteDatatypes -> asmgen.out(if (incr) "  inc  $what" else "  dec  $what") |  | ||||||
|                     in WordDatatypes -> { |  | ||||||
|                         if(incr) |  | ||||||
|                             asmgen.out(" inc  $what |  bne  + |  inc  $what+1 |+") |  | ||||||
|                         else |  | ||||||
|                             asmgen.out(""" |  | ||||||
|         lda  $what |  | ||||||
|         bne  + |  | ||||||
|         dec  $what+1 |  | ||||||
| +       dec  $what  |  | ||||||
| """) |  | ||||||
|                     } |  | ||||||
|                     DataType.FLOAT -> { |  | ||||||
|                         asmgen.out("  lda  #<$what |  ldy  #>$what") |  | ||||||
|                         asmgen.out(if(incr) "  jsr  floats.inc_var_f" else "  jsr  floats.dec_var_f") |  | ||||||
|                     } |  | ||||||
|                     else -> throw AssemblyError("need numeric type") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             targetMemory!=null -> { |  | ||||||
|                 when (val addressExpr = targetMemory.addressExpression) { |  | ||||||
|                     is NumericLiteralValue -> { |  | ||||||
|                         val what = addressExpr.number.toHex() |  | ||||||
|                         asmgen.out(if(incr) "  inc  $what" else "  dec  $what") |  | ||||||
|                     } |  | ||||||
|                     is IdentifierReference -> { |  | ||||||
|                         val what = asmgen.asmVariableName(addressExpr) |  | ||||||
|                         asmgen.out("  lda  $what |  sta  (+) +1 |  lda  $what+1 |  sta  (+) +2") |  | ||||||
|                         if(incr) |  | ||||||
|                             asmgen.out("+\tinc  ${'$'}ffff\t; modified") |  | ||||||
|                         else |  | ||||||
|                             asmgen.out("+\tdec  ${'$'}ffff\t; modified") |  | ||||||
|                     } |  | ||||||
|                     else -> { |  | ||||||
|                         asmgen.translateExpression(addressExpr) |  | ||||||
|                         asmgen.out(""" |  | ||||||
|                             inx |  | ||||||
|                             lda  P8ESTACK_LO,x |  | ||||||
|                             sta  (+) + 1 |  | ||||||
|                             lda  P8ESTACK_HI,x |  | ||||||
|                             sta  (+) + 2 |  | ||||||
|                         """) |  | ||||||
|                         if(incr) |  | ||||||
|                             asmgen.out("+\tinc  ${'$'}ffff\t; modified") |  | ||||||
|                         else |  | ||||||
|                             asmgen.out("+\tdec  ${'$'}ffff\t; modified") |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             targetArrayIdx!=null -> { |  | ||||||
|                 val index = targetArrayIdx.arrayspec.index |  | ||||||
|                 val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier) |  | ||||||
|                 val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT) |  | ||||||
|                 when(index) { |  | ||||||
|                     is NumericLiteralValue -> { |  | ||||||
|                         val indexValue = index.number.toInt() * elementDt.memorySize() |  | ||||||
|                         when(elementDt) { |  | ||||||
|                             in ByteDatatypes -> asmgen.out(if (incr) "  inc  $asmArrayvarname+$indexValue" else "  dec  $asmArrayvarname+$indexValue") |  | ||||||
|                             in WordDatatypes -> { |  | ||||||
|                                 if(incr) |  | ||||||
|                                     asmgen.out(" inc  $asmArrayvarname+$indexValue |  bne  + |  inc  $asmArrayvarname+$indexValue+1 |+") |  | ||||||
|                                 else |  | ||||||
|                                     asmgen.out(""" |  | ||||||
|         lda  $asmArrayvarname+$indexValue |  | ||||||
|         bne  + |  | ||||||
|         dec  $asmArrayvarname+$indexValue+1 |  | ||||||
| +       dec  $asmArrayvarname+$indexValue  |  | ||||||
| """) |  | ||||||
|                             } |  | ||||||
|                             DataType.FLOAT -> { |  | ||||||
|                                 asmgen.out("  lda  #<$asmArrayvarname+$indexValue |  ldy  #>$asmArrayvarname+$indexValue") |  | ||||||
|                                 asmgen.out(if(incr) "  jsr  floats.inc_var_f" else "  jsr  floats.dec_var_f") |  | ||||||
|                             } |  | ||||||
|                             else -> throw AssemblyError("need numeric type") |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     else -> { |  | ||||||
|                         asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A) |  | ||||||
|                         asmgen.saveRegister(CpuRegister.X) |  | ||||||
|                         asmgen.out("  tax") |  | ||||||
|                         when(elementDt) { |  | ||||||
|                             in ByteDatatypes -> { |  | ||||||
|                                 asmgen.out(if(incr) "  inc  $asmArrayvarname,x" else "  dec  $asmArrayvarname,x") |  | ||||||
|                             } |  | ||||||
|                             in WordDatatypes -> { |  | ||||||
|                                 if(incr) |  | ||||||
|                                     asmgen.out(" inc  $asmArrayvarname,x |  bne  + |  inc  $asmArrayvarname+1,x |+") |  | ||||||
|                                 else |  | ||||||
|                                     asmgen.out(""" |  | ||||||
|         lda  $asmArrayvarname,x |  | ||||||
|         bne  + |  | ||||||
|         dec  $asmArrayvarname+1,x |  | ||||||
| +       dec  $asmArrayvarname  |  | ||||||
| """) |  | ||||||
|                             } |  | ||||||
|                             DataType.FLOAT -> { |  | ||||||
|                                 asmgen.out(""" |  | ||||||
|                         ldy  #>$asmArrayvarname |  | ||||||
|                         clc |  | ||||||
|                         adc  #<$asmArrayvarname |  | ||||||
|                         bcc  + |  | ||||||
|                         iny |  | ||||||
| +                       jsr  floats.inc_var_f""") |  | ||||||
|                             } |  | ||||||
|                             else -> throw AssemblyError("weird array elt dt") |  | ||||||
|                         } |  | ||||||
|                         asmgen.restoreRegister(CpuRegister.X) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("weird target type ${stmt.target}") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,165 +0,0 @@ | |||||||
| package prog8.compiler.target.c64.codegen.assignment |  | ||||||
|  |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.AssignTarget |  | ||||||
| import prog8.ast.statements.Assignment |  | ||||||
| import prog8.ast.statements.DirectMemoryWrite |  | ||||||
| import prog8.compiler.AssemblyError |  | ||||||
| import prog8.compiler.target.c64.codegen.AsmGen |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal enum class TargetStorageKind { |  | ||||||
|     VARIABLE, |  | ||||||
|     ARRAY, |  | ||||||
|     MEMORY, |  | ||||||
|     REGISTER, |  | ||||||
|     STACK |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal enum class SourceStorageKind { |  | ||||||
|     LITERALNUMBER, |  | ||||||
|     VARIABLE, |  | ||||||
|     ARRAY, |  | ||||||
|     MEMORY, |  | ||||||
|     REGISTER, |  | ||||||
|     STACK,              // value is already present on stack |  | ||||||
|     EXPRESSION,         // expression in ast-form, still to be evaluated |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal class AsmAssignTarget(val kind: TargetStorageKind, |  | ||||||
|                                program: Program, |  | ||||||
|                                asmgen: AsmGen, |  | ||||||
|                                val datatype: DataType, |  | ||||||
|                                val variable: IdentifierReference? = null, |  | ||||||
|                                val array: ArrayIndexedExpression? = null, |  | ||||||
|                                val memory: DirectMemoryWrite? = null, |  | ||||||
|                                val register: RegisterOrPair? = null, |  | ||||||
|                                val origAstTarget: AssignTarget? = null |  | ||||||
|                                ) |  | ||||||
| { |  | ||||||
|     val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0} |  | ||||||
|     val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() } |  | ||||||
|     val vardecl by lazy { variable!!.targetVarDecl(program.namespace)!! } |  | ||||||
|     val asmVarname by lazy { |  | ||||||
|         if(variable!=null) |  | ||||||
|             asmgen.asmVariableName(variable) |  | ||||||
|         else |  | ||||||
|             asmgen.asmVariableName(array!!.identifier) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     lateinit var origAssign: AsmAssignment |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         if(variable!=null && vardecl.type == VarDeclType.CONST) |  | ||||||
|             throw AssemblyError("can't assign to a constant") |  | ||||||
|         if(register!=null && datatype !in IntegerDatatypes) |  | ||||||
|             throw AssemblyError("register must be integer type") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) { |  | ||||||
|             val dt = inferType(program, assign).typeOrElse(DataType.STRUCT) |  | ||||||
|             when { |  | ||||||
|                 identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, origAstTarget =  this) |  | ||||||
|                 arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget =  this) |  | ||||||
|                 memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory =  memoryAddress, origAstTarget =  this) |  | ||||||
|                 else -> throw AssemblyError("weird target") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget = |  | ||||||
|                 when(registers) { |  | ||||||
|                     RegisterOrPair.A, |  | ||||||
|                     RegisterOrPair.X, |  | ||||||
|                     RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers) |  | ||||||
|                     RegisterOrPair.AX, |  | ||||||
|                     RegisterOrPair.AY, |  | ||||||
|                     RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers) |  | ||||||
|                 } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal class AsmAssignSource(val kind: SourceStorageKind, |  | ||||||
|                                private val program: Program, |  | ||||||
|                                val datatype: DataType, |  | ||||||
|                                val variable: IdentifierReference? = null, |  | ||||||
|                                val array: ArrayIndexedExpression? = null, |  | ||||||
|                                val memory: DirectMemoryRead? = null, |  | ||||||
|                                val register: CpuRegister? = null, |  | ||||||
|                                val number: NumericLiteralValue? = null, |  | ||||||
|                                val expression: Expression? = null |  | ||||||
| ) |  | ||||||
| { |  | ||||||
|     val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0} |  | ||||||
|     val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() } |  | ||||||
|     val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         fun fromAstSource(value: Expression, program: Program): AsmAssignSource { |  | ||||||
|             val cv = value.constValue(program) |  | ||||||
|             if(cv!=null) |  | ||||||
|                 return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv) |  | ||||||
|  |  | ||||||
|             return when(value) { |  | ||||||
|                 is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv) |  | ||||||
|                 is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation") |  | ||||||
|                 is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation") |  | ||||||
|                 is IdentifierReference -> { |  | ||||||
|                     val dt = value.inferType(program).typeOrElse(DataType.STRUCT) |  | ||||||
|                     AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value) |  | ||||||
|                 } |  | ||||||
|                 is DirectMemoryRead -> { |  | ||||||
|                     AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value) |  | ||||||
|                 } |  | ||||||
|                 is ArrayIndexedExpression -> { |  | ||||||
|                     val dt = value.inferType(program).typeOrElse(DataType.STRUCT) |  | ||||||
|                     AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value) |  | ||||||
|                 } |  | ||||||
|                 else -> { |  | ||||||
|                     val dt = value.inferType(program).typeOrElse(DataType.STRUCT) |  | ||||||
|                     AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun getAstValue(): Expression = when(kind) { |  | ||||||
|         SourceStorageKind.LITERALNUMBER -> number!! |  | ||||||
|         SourceStorageKind.VARIABLE -> variable!! |  | ||||||
|         SourceStorageKind.ARRAY -> array!! |  | ||||||
|         SourceStorageKind.MEMORY -> memory!! |  | ||||||
|         SourceStorageKind.EXPRESSION -> expression!! |  | ||||||
|         SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node") |  | ||||||
|         SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun withAdjustedDt(newType: DataType) = |  | ||||||
|             AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression) |  | ||||||
|  |  | ||||||
|     fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource { |  | ||||||
|         // allow some signed/unsigned relaxations |  | ||||||
|         if(target.datatype!=datatype) { |  | ||||||
|             if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) { |  | ||||||
|                 return withAdjustedDt(target.datatype) |  | ||||||
|             } else if(target.datatype in WordDatatypes && datatype in WordDatatypes) { |  | ||||||
|                 return withAdjustedDt(target.datatype) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return this |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class AsmAssignment(val source: AsmAssignSource, |  | ||||||
|                              val target: AsmAssignTarget, |  | ||||||
|                              val isAugmentable: Boolean, |  | ||||||
|                              val position: Position) { |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) |  | ||||||
|             require(source.datatype.memorySize() == target.datatype.memorySize()) { "source and target datatype must be same storage class" } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,14 +1,13 @@ | |||||||
| package prog8.compiler.target.c64 | package prog8.compiler.target.cbm | ||||||
| 
 | 
 | ||||||
| import prog8.compiler.CompilationOptions | import prog8.compiler.CompilationOptions | ||||||
| import prog8.compiler.OutputType | import prog8.compiler.OutputType | ||||||
| import prog8.compiler.target.CompilationTarget |  | ||||||
| import prog8.compiler.target.IAssemblyProgram | import prog8.compiler.target.IAssemblyProgram | ||||||
| import prog8.compiler.target.generatedLabelPrefix | import prog8.compiler.target.generatedLabelPrefix | ||||||
| import java.nio.file.Path | import java.nio.file.Path | ||||||
| import kotlin.system.exitProcess | import kotlin.system.exitProcess | ||||||
| 
 | 
 | ||||||
| class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram { | class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram { | ||||||
|     private val assemblyFile = outputDir.resolve("$name.asm") |     private val assemblyFile = outputDir.resolve("$name.asm") | ||||||
|     private val prgFile = outputDir.resolve("$name.prg") |     private val prgFile = outputDir.resolve("$name.prg") | ||||||
|     private val binFile = outputDir.resolve("$name.bin") |     private val binFile = outputDir.resolve("$name.bin") | ||||||
| @@ -23,12 +22,12 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro | |||||||
|         val outFile = when (options.output) { |         val outFile = when (options.output) { | ||||||
|             OutputType.PRG -> { |             OutputType.PRG -> { | ||||||
|                 command.add("--cbm-prg") |                 command.add("--cbm-prg") | ||||||
|                 println("\nCreating prg for target ${CompilationTarget.instance.name}.") |                 println("\nCreating prg for target $compTarget.") | ||||||
|                 prgFile |                 prgFile | ||||||
|             } |             } | ||||||
|             OutputType.RAW -> { |             OutputType.RAW -> { | ||||||
|                 command.add("--nostart") |                 command.add("--nostart") | ||||||
|                 println("\nCreating raw binary for target ${CompilationTarget.instance.name}.") |                 println("\nCreating raw binary for target $compTarget.") | ||||||
|                 binFile |                 binFile | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| package prog8.compiler.target.c64 | package prog8.compiler.target.cbm | ||||||
| 
 | 
 | ||||||
|  | import prog8.ast.antlr.escape | ||||||
| import java.io.CharConversionException | import java.io.CharConversionException | ||||||
| 
 | 
 | ||||||
| object Petscii { | object Petscii { | ||||||
| @@ -1049,41 +1050,90 @@ object Petscii { | |||||||
|     private val encodingScreencodeLowercase = decodingScreencodeLowercase.withIndex().associate{it.value to it.index} |     private val encodingScreencodeLowercase = decodingScreencodeLowercase.withIndex().associate{it.value to it.index} | ||||||
|     private val encodingScreencodeUppercase = decodingScreencodeUppercase.withIndex().associate{it.value to it.index} |     private val encodingScreencodeUppercase = decodingScreencodeUppercase.withIndex().associate{it.value to it.index} | ||||||
| 
 | 
 | ||||||
|  |     private fun replaceSpecial(chr: Char): Char = | ||||||
|  |         // characters often used in C like source code can be translated with a little bit of fantasy: | ||||||
|  |         when(chr) { | ||||||
|  |             '^' -> '↑' | ||||||
|  |             '_' -> '▁' | ||||||
|  |             '{' -> '┤' | ||||||
|  |             '}' -> '├' | ||||||
|  |             '|' -> '│' | ||||||
|  |             '\\' -> '╲' | ||||||
|  |             else -> chr | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|     fun encodePetscii(text: String, lowercase: Boolean = false): List<Short> { |     fun encodePetscii(text: String, lowercase: Boolean = false): List<Short> { | ||||||
|         val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase |         fun encodeChar(chr3: Char, lowercase: Boolean): Short { | ||||||
|         return text.map { |             val chr = replaceSpecial(chr3) | ||||||
|             val petscii = lookup[it] |             val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr] | ||||||
|             petscii?.toShort() ?: if(it=='\u0000') |             return screencode?.toShort() ?: when (chr) { | ||||||
|                 0.toShort() |                 '\u0000' -> 0.toShort() | ||||||
|             else { |                 in '\u8000'..'\u80ff' -> { | ||||||
|  |                     // special case: take the lower 8 bit hex value directly | ||||||
|  |                     (chr.code - 0x8000).toShort() | ||||||
|  |                 } | ||||||
|  |                 else -> { | ||||||
|                     val case = if (lowercase) "lower" else "upper" |                     val case = if (lowercase) "lower" else "upper" | ||||||
|                 throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})") |                     throw CharConversionException("no ${case}Petscii character for '${escape(chr.toString())}' (${chr.code})") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return text.map{ | ||||||
|  |             try { | ||||||
|  |                 encodeChar(it, lowercase) | ||||||
|  |             } catch (x: CharConversionException) { | ||||||
|  |                 encodeChar(it, !lowercase) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String { |     fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String { | ||||||
|         val decodeTable = if(lowercase) decodingPetsciiLowercase else decodingPetsciiUppercase |         return petscii.map { | ||||||
|         return petscii.map { decodeTable[it.toInt()] }.joinToString("") |             val code = it.toInt() | ||||||
|  |             try { | ||||||
|  |                 if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code] | ||||||
|  |             } catch(x: CharConversionException) { | ||||||
|  |                 if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code] | ||||||
|  |             } | ||||||
|  |         }.joinToString("") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun encodeScreencode(text: String, lowercase: Boolean = false): List<Short> { |     fun encodeScreencode(text: String, lowercase: Boolean = false): List<Short> { | ||||||
|         val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase |         fun encodeChar(chr3: Char, lowercase: Boolean): Short { | ||||||
|         return text.map{ |             val chr = replaceSpecial(chr3) | ||||||
|             val screencode = lookup[it] |             val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr] | ||||||
|             screencode?.toShort() ?: if(it=='\u0000') |             return screencode?.toShort() ?: when (chr) { | ||||||
|                 0.toShort() |                 '\u0000' -> 0.toShort() | ||||||
|             else { |                 in '\u8000'..'\u80ff' -> { | ||||||
|  |                     // special case: take the lower 8 bit hex value directly | ||||||
|  |                     (chr.code - 0x8000).toShort() | ||||||
|  |                 } | ||||||
|  |                 else -> { | ||||||
|                     val case = if (lowercase) "lower" else "upper" |                     val case = if (lowercase) "lower" else "upper" | ||||||
|                 throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})") |                     throw CharConversionException("no ${case}Screencode character for '${escape(chr.toString())}' (${chr.code})") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return text.map{ | ||||||
|  |             try { | ||||||
|  |                 encodeChar(it, lowercase) | ||||||
|  |             } catch (x: CharConversionException) { | ||||||
|  |                 encodeChar(it, !lowercase) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String { |     fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String { | ||||||
|         val decodeTable = if(lowercase) decodingScreencodeLowercase else decodingScreencodeUppercase |         return screencode.map { | ||||||
|         return screencode.map { decodeTable[it.toInt()] }.joinToString("") |             val code = it.toInt() | ||||||
|  |             try { | ||||||
|  |                 if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code] | ||||||
|  |             } catch (x: CharConversionException) { | ||||||
|  |                 if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code] | ||||||
|  |             } | ||||||
|  |         }.joinToString("") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short { |     fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short { | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| package prog8.compiler.target.c64.codegen | package prog8.compiler.target.cpu6502.codegen | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations | // note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations | ||||||
| @@ -179,8 +179,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { | private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { | ||||||
|     // TODO not sure if this is correct in all situations....: |     // sta X + lda X,  sty X + ldy X,   stx X + ldx X  -> the second instruction can OFTEN be eliminated | ||||||
|     // sta X + lda X,  sty X + ldy X,   stx X + ldx X  -> the second instruction can be eliminated |     // TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM) | ||||||
|     val mods = mutableListOf<Modification>() |     val mods = mutableListOf<Modification>() | ||||||
|     for (pair in linesByFour) { |     for (pair in linesByFour) { | ||||||
|         val first = pair[0].value.trimStart() |         val first = pair[0].value.trimStart() | ||||||
| @@ -196,13 +196,17 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>) | |||||||
|                 (first.startsWith("sty ") && second.startsWith("ldy ")) || |                 (first.startsWith("sty ") && second.startsWith("ldy ")) || | ||||||
|                 (first.startsWith("stx ") && second.startsWith("ldx ")) |                 (first.startsWith("stx ") && second.startsWith("ldx ")) | ||||||
|         ) { |         ) { | ||||||
|             val firstLoc = first.substring(4) |             val third = pair[2].value.trimStart() | ||||||
|             val secondLoc = second.substring(4) |             if(!third.startsWith("b")) { | ||||||
|  |                 // no branch instruction follows, we can potentiall remove the load instruction | ||||||
|  |                 val firstLoc = first.substring(4).trimStart() | ||||||
|  |                 val secondLoc = second.substring(4).trimStart() | ||||||
|                 if (firstLoc == secondLoc) { |                 if (firstLoc == secondLoc) { | ||||||
|                     mods.add(Modification(pair[1].index, true, null)) |                     mods.add(Modification(pair[1].index, true, null)) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|     return mods |     return mods | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,16 +1,14 @@ | |||||||
| package prog8.compiler.target.c64.codegen | package prog8.compiler.target.cpu6502.codegen | ||||||
| 
 | 
 | ||||||
| import prog8.ast.Program | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.ArrayToElementTypes | ||||||
| import prog8.ast.base.DataType | import prog8.ast.base.DataType | ||||||
|  | import prog8.ast.base.RegisterOrPair | ||||||
| import prog8.ast.expressions.IdentifierReference | import prog8.ast.expressions.IdentifierReference | ||||||
| import prog8.ast.expressions.RangeExpr | import prog8.ast.expressions.RangeExpr | ||||||
| import prog8.ast.statements.ForLoop | import prog8.ast.statements.ForLoop | ||||||
|  | import prog8.ast.toHex | ||||||
| import prog8.compiler.AssemblyError | import prog8.compiler.AssemblyError | ||||||
| import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.AsmAssignment |  | ||||||
| import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind |  | ||||||
| import prog8.compiler.toHex |  | ||||||
| import kotlin.math.absoluteValue | import kotlin.math.absoluteValue | ||||||
| 
 | 
 | ||||||
| internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { | internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { | ||||||
| @@ -18,18 +16,18 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen: | |||||||
|     internal fun translate(stmt: ForLoop) { |     internal fun translate(stmt: ForLoop) { | ||||||
|         val iterableDt = stmt.iterable.inferType(program) |         val iterableDt = stmt.iterable.inferType(program) | ||||||
|         if(!iterableDt.isKnown) |         if(!iterableDt.isKnown) | ||||||
|             throw AssemblyError("can't determine iterable dt") |             throw AssemblyError("unknown dt") | ||||||
|         when(stmt.iterable) { |         when(stmt.iterable) { | ||||||
|             is RangeExpr -> { |             is RangeExpr -> { | ||||||
|                 val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() |                 val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() | ||||||
|                 if(range==null) { |                 if(range==null) { | ||||||
|                     translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr) |                     translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as RangeExpr) | ||||||
|                 } else { |                 } else { | ||||||
|                     translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range) |                     translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), range) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             is IdentifierReference -> { |             is IdentifierReference -> { | ||||||
|                 translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference) |                 translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as IdentifierReference) | ||||||
|             } |             } | ||||||
|             else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable") |             else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable") | ||||||
|         } |         } | ||||||
| @@ -42,6 +40,13 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen: | |||||||
|         val modifiedLabel2 = asmgen.makeLabel("for_modifiedb") |         val modifiedLabel2 = asmgen.makeLabel("for_modifiedb") | ||||||
|         asmgen.loopEndLabels.push(endLabel) |         asmgen.loopEndLabels.push(endLabel) | ||||||
|         val stepsize=range.step.constValue(program)!!.number.toInt() |         val stepsize=range.step.constValue(program)!!.number.toInt() | ||||||
|  | 
 | ||||||
|  |         if(stepsize < -1) { | ||||||
|  |             val limit = range.to.constValue(program)?.number?.toDouble() | ||||||
|  |             if(limit==0.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") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         when(iterableDt) { |         when(iterableDt) { | ||||||
|             DataType.ARRAY_B, DataType.ARRAY_UB -> { |             DataType.ARRAY_B, DataType.ARRAY_UB -> { | ||||||
|                 if (stepsize==1 || stepsize==-1) { |                 if (stepsize==1 || stepsize==-1) { | ||||||
| @@ -51,23 +56,17 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen: | |||||||
|                     val incdec = if(stepsize==1) "inc" else "dec" |                     val incdec = if(stepsize==1) "inc" else "dec" | ||||||
|                     // loop over byte range via loopvar |                     // loop over byte range via loopvar | ||||||
|                     val varname = asmgen.asmVariableName(stmt.loopVar) |                     val varname = asmgen.asmVariableName(stmt.loopVar) | ||||||
|                     asmgen.translateExpression(range.to) |                     asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null) | ||||||
|                     asmgen.translateExpression(range.from) |                     asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null) | ||||||
|                     asmgen.out(""" |                     asmgen.out(loopLabel) | ||||||
|                 inx |  | ||||||
|                 lda  P8ESTACK_LO,x |  | ||||||
|                 sta  $varname |  | ||||||
|                 lda  P8ESTACK_LO+1,x |  | ||||||
|                 sta  $modifiedLabel+1 |  | ||||||
| $loopLabel""") |  | ||||||
|                     asmgen.translate(stmt.body) |                     asmgen.translate(stmt.body) | ||||||
|                     asmgen.out(""" |                     asmgen.out(""" | ||||||
|                         lda  $varname |                         lda  $varname | ||||||
| $modifiedLabel          cmp  #0         ; modified  | $modifiedLabel          cmp  #0         ; modified  | ||||||
|                         beq  $endLabel |                         beq  $endLabel | ||||||
|                 $incdec  $varname |                         $incdec  $varname""") | ||||||
|                 jmp  $loopLabel |                     asmgen.jmp(loopLabel) | ||||||
| $endLabel       inx""") |                     asmgen.out(endLabel) | ||||||
| 
 | 
 | ||||||
|                 } else { |                 } else { | ||||||
| 
 | 
 | ||||||
| @@ -75,36 +74,29 @@ $endLabel       inx""") | |||||||
| 
 | 
 | ||||||
|                     // loop over byte range via loopvar |                     // loop over byte range via loopvar | ||||||
|                     val varname = asmgen.asmVariableName(stmt.loopVar) |                     val varname = asmgen.asmVariableName(stmt.loopVar) | ||||||
|                     asmgen.translateExpression(range.to) |                     asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null) | ||||||
|                     asmgen.translateExpression(range.from) |                     asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null) | ||||||
|                     asmgen.out(""" |                     asmgen.out(loopLabel) | ||||||
|                 inx |  | ||||||
|                 lda  P8ESTACK_LO,x |  | ||||||
|                 sta  $varname |  | ||||||
|                 lda  P8ESTACK_LO+1,x |  | ||||||
|                 sta  $modifiedLabel+1 |  | ||||||
| $loopLabel""") |  | ||||||
|                     asmgen.translate(stmt.body) |                     asmgen.translate(stmt.body) | ||||||
|                     asmgen.out(""" |  | ||||||
|                 lda  $varname""") |  | ||||||
|                     if(stepsize>0) { |                     if(stepsize>0) { | ||||||
|                         asmgen.out(""" |                         asmgen.out(""" | ||||||
|  |                             lda  $varname | ||||||
|                             clc |                             clc | ||||||
|                             adc  #$stepsize |                             adc  #$stepsize | ||||||
|                             sta  $varname |                             sta  $varname | ||||||
| $modifiedLabel              cmp  #0    ; modified | $modifiedLabel              cmp  #0    ; modified | ||||||
|                 bcc  $loopLabel |                             bmi  $loopLabel | ||||||
|                             beq  $loopLabel""") |                             beq  $loopLabel""") | ||||||
|                     } else { |                     } else { | ||||||
|                         asmgen.out(""" |                         asmgen.out(""" | ||||||
|  |                             lda  $varname | ||||||
|                             sec |                             sec | ||||||
|                             sbc  #${stepsize.absoluteValue} |                             sbc  #${stepsize.absoluteValue} | ||||||
|                             sta  $varname |                             sta  $varname | ||||||
| $modifiedLabel              cmp  #0     ; modified | $modifiedLabel              cmp  #0     ; modified | ||||||
|                 bcs  $loopLabel""") |                             bpl  $loopLabel""") | ||||||
|                     } |                     } | ||||||
|                     asmgen.out(""" |                     asmgen.out(endLabel) | ||||||
| $endLabel       inx""") |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { |             DataType.ARRAY_W, DataType.ARRAY_UW -> { | ||||||
| @@ -113,13 +105,11 @@ $endLabel       inx""") | |||||||
|                     // words, step 1 or -1 |                     // words, step 1 or -1 | ||||||
| 
 | 
 | ||||||
|                     stepsize == 1 || stepsize == -1 -> { |                     stepsize == 1 || stepsize == -1 -> { | ||||||
|                         asmgen.translateExpression(range.to) |  | ||||||
|                         assignLoopvar(stmt, range) |  | ||||||
|                         val varname = asmgen.asmVariableName(stmt.loopVar) |                         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||||
|  |                         assignLoopvar(stmt, range) | ||||||
|  |                         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||||
|                         asmgen.out(""" |                         asmgen.out(""" | ||||||
|                             lda  P8ESTACK_HI+1,x |                             sty  $modifiedLabel+1 | ||||||
|                             sta  $modifiedLabel+1 |  | ||||||
|                             lda  P8ESTACK_LO+1,x |  | ||||||
|                             sta  $modifiedLabel2+1 |                             sta  $modifiedLabel2+1 | ||||||
| $loopLabel""") | $loopLabel""") | ||||||
|                         asmgen.translate(stmt.body) |                         asmgen.translate(stmt.body) | ||||||
| @@ -134,33 +124,28 @@ $modifiedLabel2 cmp  #0    ; modified | |||||||
|                             asmgen.out(""" |                             asmgen.out(""" | ||||||
| +               inc  $varname | +               inc  $varname | ||||||
|                 bne  $loopLabel |                 bne  $loopLabel | ||||||
|                 inc  $varname+1 |                 inc  $varname+1""") | ||||||
|                 jmp  $loopLabel |                             asmgen.jmp(loopLabel) | ||||||
|                             """) |  | ||||||
|                         } else { |                         } else { | ||||||
|                             asmgen.out(""" |                             asmgen.out(""" | ||||||
| +               lda  $varname | +               lda  $varname | ||||||
|                 bne  + |                 bne  + | ||||||
|                 dec  $varname+1 |                 dec  $varname+1 | ||||||
| +               dec  $varname | +               dec  $varname""") | ||||||
|                 jmp  $loopLabel""") |                             asmgen.jmp(loopLabel) | ||||||
|                         } |                         } | ||||||
|                         asmgen.out(endLabel) |                         asmgen.out(endLabel) | ||||||
|                         asmgen.out(" inx") |  | ||||||
|                     } |                     } | ||||||
|                     stepsize > 0 -> { |                     stepsize > 0 -> { | ||||||
| 
 | 
 | ||||||
|                         // (u)words, step >= 2 |                         // (u)words, step >= 2 | ||||||
|                         asmgen.translateExpression(range.to) |  | ||||||
|                         asmgen.out(""" |  | ||||||
|                             lda  P8ESTACK_HI+1,x |  | ||||||
|                             sta  $modifiedLabel+1 |  | ||||||
|                             lda  P8ESTACK_LO+1,x |  | ||||||
|                             sta  $modifiedLabel2+1 |  | ||||||
|                         """) |  | ||||||
|                         assignLoopvar(stmt, range) |  | ||||||
|                         val varname = asmgen.asmVariableName(stmt.loopVar) |                         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||||
|                         asmgen.out(loopLabel) |                         assignLoopvar(stmt, range) | ||||||
|  |                         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||||
|  |                         asmgen.out(""" | ||||||
|  |                             sty  $modifiedLabel+1 | ||||||
|  |                             sta  $modifiedLabel2+1 | ||||||
|  | $loopLabel""") | ||||||
|                         asmgen.translate(stmt.body) |                         asmgen.translate(stmt.body) | ||||||
| 
 | 
 | ||||||
|                         if (iterableDt == DataType.ARRAY_UW) { |                         if (iterableDt == DataType.ARRAY_UW) { | ||||||
| @@ -179,7 +164,7 @@ $modifiedLabel2 lda  #0     ; modified | |||||||
|                 cmp  $varname |                 cmp  $varname | ||||||
|                 bcc  $endLabel |                 bcc  $endLabel | ||||||
|                 bcs  $loopLabel |                 bcs  $loopLabel | ||||||
| $endLabel       inx""") | $endLabel""") | ||||||
|                         } else { |                         } else { | ||||||
|                             asmgen.out(""" |                             asmgen.out(""" | ||||||
|                 lda  $varname |                 lda  $varname | ||||||
| @@ -196,22 +181,19 @@ $modifiedLabel  lda  #0   ; modified | |||||||
|                 bvc  + |                 bvc  + | ||||||
|                 eor  #$80 |                 eor  #$80 | ||||||
| +               bpl  $loopLabel                 | +               bpl  $loopLabel                 | ||||||
| $endLabel       inx""") | $endLabel""") | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     else -> { |                     else -> { | ||||||
| 
 | 
 | ||||||
|                         // (u)words, step <= -2 |                         // (u)words, step <= -2 | ||||||
|                         asmgen.translateExpression(range.to) |  | ||||||
|                         asmgen.out(""" |  | ||||||
|                             lda  P8ESTACK_HI+1,x |  | ||||||
|                             sta  $modifiedLabel+1 |  | ||||||
|                             lda  P8ESTACK_LO+1,x |  | ||||||
|                             sta  $modifiedLabel2+1 |  | ||||||
|                         """) |  | ||||||
|                         assignLoopvar(stmt, range) |  | ||||||
|                         val varname = asmgen.asmVariableName(stmt.loopVar) |                         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||||
|                         asmgen.out(loopLabel) |                         assignLoopvar(stmt, range) | ||||||
|  |                         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||||
|  |                         asmgen.out(""" | ||||||
|  |                             sty  $modifiedLabel+1 | ||||||
|  |                             sta  $modifiedLabel2+1 | ||||||
|  | $loopLabel""") | ||||||
|                         asmgen.translate(stmt.body) |                         asmgen.translate(stmt.body) | ||||||
| 
 | 
 | ||||||
|                         if(iterableDt==DataType.ARRAY_UW) { |                         if(iterableDt==DataType.ARRAY_UW) { | ||||||
| @@ -229,7 +211,7 @@ $modifiedLabel  cmp  #0    ; modified | |||||||
|                 lda  $varname |                 lda  $varname | ||||||
| $modifiedLabel2 cmp  #0    ; modified  | $modifiedLabel2 cmp  #0    ; modified  | ||||||
|                 bcs  $loopLabel |                 bcs  $loopLabel | ||||||
| $endLabel       inx""") | $endLabel""") | ||||||
|                         } else { |                         } else { | ||||||
|                             asmgen.out(""" |                             asmgen.out(""" | ||||||
|                 lda  $varname |                 lda  $varname | ||||||
| @@ -247,7 +229,7 @@ $modifiedLabel  sbc  #0    ; modified | |||||||
|                 bvc  + |                 bvc  + | ||||||
|                 eor  #$80 |                 eor  #$80 | ||||||
| +               bpl  $loopLabel                 | +               bpl  $loopLabel                 | ||||||
| $endLabel       inx""") | $endLabel""") | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| @@ -263,7 +245,7 @@ $endLabel       inx""") | |||||||
|         val endLabel = asmgen.makeLabel("for_end") |         val endLabel = asmgen.makeLabel("for_end") | ||||||
|         asmgen.loopEndLabels.push(endLabel) |         asmgen.loopEndLabels.push(endLabel) | ||||||
|         val iterableName = asmgen.asmVariableName(ident) |         val iterableName = asmgen.asmVariableName(ident) | ||||||
|         val decl = ident.targetVarDecl(program.namespace)!! |         val decl = ident.targetVarDecl(program)!! | ||||||
|         when(iterableDt) { |         when(iterableDt) { | ||||||
|             DataType.STR -> { |             DataType.STR -> { | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
| @@ -306,7 +288,7 @@ $loopLabel          sty  $indexVar | |||||||
|                         bne  $loopLabel |                         bne  $loopLabel | ||||||
|                         beq  $endLabel""") |                         beq  $endLabel""") | ||||||
|                 } |                 } | ||||||
|                 if(length>=16 && asmgen.zeropage.available() > 0) { |                 if(length>=16 && asmgen.zeropage.hasByteAvailable()) { | ||||||
|                     // allocate index var on ZP |                     // allocate index var on ZP | ||||||
|                     val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) |                     val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) | ||||||
|                     asmgen.out("""$indexVar = $zpAddr  ; auto zp UBYTE""") |                     asmgen.out("""$indexVar = $zpAddr  ; auto zp UBYTE""") | ||||||
| @@ -345,7 +327,7 @@ $loopLabel          sty  $indexVar | |||||||
|                         bne  $loopLabel |                         bne  $loopLabel | ||||||
|                         beq  $endLabel""") |                         beq  $endLabel""") | ||||||
|                 } |                 } | ||||||
|                 if(length>=16 && asmgen.zeropage.available() > 0) { |                 if(length>=16 && asmgen.zeropage.hasByteAvailable()) { | ||||||
|                     // allocate index var on ZP |                     // allocate index var on ZP | ||||||
|                     val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) |                     val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) | ||||||
|                     asmgen.out("""$indexVar = $zpAddr  ; auto zp UBYTE""") |                     asmgen.out("""$indexVar = $zpAddr  ; auto zp UBYTE""") | ||||||
| @@ -393,7 +375,7 @@ $loopLabel""") | |||||||
|                         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  $stmt") | ||||||
|                     } |                     } | ||||||
|                     2 -> { |                     2 -> { | ||||||
|                         if(range.last==255) { |                         if(range.last==255 || range.last==254) { | ||||||
|                             asmgen.out(""" |                             asmgen.out(""" | ||||||
|                                 inc  $varname |                                 inc  $varname | ||||||
|                                 beq  $endLabel |                                 beq  $endLabel | ||||||
| @@ -401,34 +383,34 @@ $loopLabel""") | |||||||
|                                 bne  $loopLabel""") |                                 bne  $loopLabel""") | ||||||
|                         } else { |                         } else { | ||||||
|                             asmgen.out(""" |                             asmgen.out(""" | ||||||
|  |                                 inc  $varname | ||||||
|  |                                 inc  $varname | ||||||
|                                 lda  $varname |                                 lda  $varname | ||||||
|                                 cmp  #${range.last} |                                 cmp  #${range.last+2} | ||||||
|                                 beq  $endLabel |                                 bne  $loopLabel""") | ||||||
|                                 inc  $varname |  | ||||||
|                                 inc  $varname |  | ||||||
|                                 jmp  $loopLabel""") |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     -2 -> { |                     -2 -> { | ||||||
|                         when (range.last) { |                         when (range.last) { | ||||||
|                             0 -> asmgen.out(""" |                             0 -> { | ||||||
|  |                                 asmgen.out(""" | ||||||
|                                     lda  $varname |                                     lda  $varname | ||||||
|                                     beq  $endLabel |                                     beq  $endLabel | ||||||
|                                     dec  $varname |                                     dec  $varname | ||||||
|                                 dec  $varname |                                     dec  $varname""") | ||||||
|                                 jmp  $loopLabel""") |                                 asmgen.jmp(loopLabel) | ||||||
|  |                             } | ||||||
|                             1 -> asmgen.out(""" |                             1 -> asmgen.out(""" | ||||||
|                                     dec  $varname |                                     dec  $varname | ||||||
|                                     beq  $endLabel |                                     beq  $endLabel | ||||||
|                                     dec  $varname |                                     dec  $varname | ||||||
|                                     bne  $loopLabel""") |                                     bne  $loopLabel""") | ||||||
|                             else -> asmgen.out(""" |                             else -> asmgen.out(""" | ||||||
|  |                                     dec  $varname | ||||||
|  |                                     dec  $varname | ||||||
|                                     lda  $varname |                                     lda  $varname | ||||||
|                                 cmp  #${range.last} |                                     cmp  #${range.last-2} | ||||||
|                                 beq  $endLabel |                                     bne  $loopLabel""") | ||||||
|                                 dec  $varname |  | ||||||
|                                 dec  $varname |  | ||||||
|                                 jmp  $loopLabel""") |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     else -> { |                     else -> { | ||||||
| @@ -439,8 +421,8 @@ $loopLabel""") | |||||||
|                             beq  $endLabel |                             beq  $endLabel | ||||||
|                             clc |                             clc | ||||||
|                             adc  #${range.step} |                             adc  #${range.step} | ||||||
|                             sta  $varname |                             sta  $varname""") | ||||||
|                             jmp  $loopLabel""") |                         asmgen.jmp(loopLabel) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 asmgen.out(endLabel) |                 asmgen.out(endLabel) | ||||||
| @@ -476,9 +458,9 @@ $loopLabel""") | |||||||
|                             sta  $varname |                             sta  $varname | ||||||
|                             lda  $varname+1 |                             lda  $varname+1 | ||||||
|                             adc  #>${range.step} |                             adc  #>${range.step} | ||||||
|                             sta  $varname+1 |                             sta  $varname+1""") | ||||||
|                             jmp  $loopLabel |                         asmgen.jmp(loopLabel) | ||||||
| $endLabel""") |                         asmgen.out(endLabel) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -504,11 +486,10 @@ $loopLabel""") | |||||||
| $endLabel""") | $endLabel""") | ||||||
|         } else { |         } else { | ||||||
|             asmgen.out(""" |             asmgen.out(""" | ||||||
|                 lda  $varname |  | ||||||
|                 cmp  #${range.last} |  | ||||||
|                 beq  $endLabel |  | ||||||
|                 inc  $varname |                 inc  $varname | ||||||
|                 jmp  $loopLabel |                 lda  $varname | ||||||
|  |                 cmp  #${range.last+1} | ||||||
|  |                 bne  $loopLabel | ||||||
| $endLabel""") | $endLabel""") | ||||||
|         } |         } | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.pop() | ||||||
| @@ -529,23 +510,22 @@ $loopLabel""") | |||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     lda  $varname |                     lda  $varname | ||||||
|                     beq  $endLabel |                     beq  $endLabel | ||||||
|                     dec  $varname |                     dec  $varname""") | ||||||
|                     jmp  $loopLabel |                 asmgen.jmp(loopLabel) | ||||||
| $endLabel""") |                 asmgen.out(endLabel) | ||||||
|             } |             } | ||||||
|             1 -> { |             1 -> { | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     dec  $varname |                     dec  $varname | ||||||
|                     jmp  $loopLabel |                     bne  $loopLabel | ||||||
| $endLabel""") | $endLabel""") | ||||||
|             } |             } | ||||||
|             else -> { |             else -> { | ||||||
|                 asmgen.out(""" |                 asmgen.out(""" | ||||||
|                     lda  $varname |  | ||||||
|                     cmp  #${range.last} |  | ||||||
|                     beq  $endLabel |  | ||||||
|                     dec  $varname |                     dec  $varname | ||||||
|                     jmp  $loopLabel |                     lda  $varname | ||||||
|  |                     cmp  #${range.last-1} | ||||||
|  |                     bne  $loopLabel | ||||||
| $endLabel""") | $endLabel""") | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -570,13 +550,12 @@ $loopLabel""") | |||||||
|             bne  + |             bne  + | ||||||
|             lda  $varname+1 |             lda  $varname+1 | ||||||
|             cmp  #>${range.last} |             cmp  #>${range.last} | ||||||
|             bne  + |  | ||||||
|             beq  $endLabel |             beq  $endLabel | ||||||
| +           inc  $varname | +           inc  $varname | ||||||
|             bne  $loopLabel |             bne  $loopLabel | ||||||
|             inc  $varname+1 |             inc  $varname+1""") | ||||||
|             jmp  $loopLabel |         asmgen.jmp(loopLabel) | ||||||
| $endLabel""") |         asmgen.out(endLabel) | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.pop() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -598,21 +577,16 @@ $loopLabel""") | |||||||
|             bne  + |             bne  + | ||||||
|             lda  $varname+1 |             lda  $varname+1 | ||||||
|             cmp  #>${range.last} |             cmp  #>${range.last} | ||||||
|             bne  + |  | ||||||
|             beq  $endLabel |             beq  $endLabel | ||||||
| +           lda  $varname | +           lda  $varname | ||||||
|             bne  + |             bne  + | ||||||
|             dec  $varname+1 |             dec  $varname+1 | ||||||
| +           dec  $varname | +           dec  $varname""") | ||||||
|             jmp  $loopLabel |         asmgen.jmp(loopLabel) | ||||||
| $endLabel""") |         asmgen.out(endLabel) | ||||||
|         asmgen.loopEndLabels.pop() |         asmgen.loopEndLabels.pop() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) { |     private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) = | ||||||
|         val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), variable=stmt.loopVar) |         asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.UNDEFINED), stmt.definingSubroutine()) | ||||||
|         val src = AsmAssignSource.fromAstSource(range.from, program).adjustDataTypeToTarget(target) |  | ||||||
|         val assign = AsmAssignment(src, target, false, range.position) |  | ||||||
|         asmgen.translateNormalAssignment(assign) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -0,0 +1,364 @@ | |||||||
|  | package prog8.compiler.target.cpu6502.codegen | ||||||
|  |  | ||||||
|  | import prog8.ast.IFunctionCall | ||||||
|  | import prog8.ast.Node | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.* | ||||||
|  | import prog8.ast.expressions.* | ||||||
|  | import prog8.ast.statements.* | ||||||
|  | import prog8.compiler.AssemblyError | ||||||
|  | import prog8.compiler.target.CpuType | ||||||
|  | import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource | ||||||
|  | import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget | ||||||
|  | import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment | ||||||
|  | import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { | ||||||
|  |  | ||||||
|  |     internal fun translateFunctionCallStatement(stmt: IFunctionCall) { | ||||||
|  |         saveXbeforeCall(stmt) | ||||||
|  |         translateFunctionCall(stmt) | ||||||
|  |         restoreXafterCall(stmt) | ||||||
|  |         // just ignore any result values from the function call. | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal fun saveXbeforeCall(stmt: IFunctionCall) { | ||||||
|  |         val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}") | ||||||
|  |         if(sub.shouldSaveX()) { | ||||||
|  |             val regSaveOnStack = sub.asmAddress==null       // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls | ||||||
|  |             val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA() | ||||||
|  |             if(regSaveOnStack) | ||||||
|  |                 asmgen.saveRegisterStack(CpuRegister.X, keepAonEntry) | ||||||
|  |             else | ||||||
|  |                 asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine()!!) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal fun restoreXafterCall(stmt: IFunctionCall) { | ||||||
|  |         val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}") | ||||||
|  |         if(sub.shouldSaveX()) { | ||||||
|  |             val regSaveOnStack = sub.asmAddress==null       // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls | ||||||
|  |             val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA() | ||||||
|  |  | ||||||
|  |             if(regSaveOnStack) | ||||||
|  |                 asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn) | ||||||
|  |             else | ||||||
|  |                 asmgen.restoreRegisterLocal(CpuRegister.X) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     internal fun translateFunctionCall(stmt: IFunctionCall) { | ||||||
|  |         // Output only the code to setup the parameters and perform the actual call | ||||||
|  |         // NOTE: does NOT output the code to deal with the result values! | ||||||
|  |         // 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) | ||||||
|  |  | ||||||
|  |         val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}") | ||||||
|  |         val subName = asmgen.asmSymbolName(stmt.target) | ||||||
|  |         if(stmt.args.isNotEmpty()) { | ||||||
|  |  | ||||||
|  |             if(sub.asmParameterRegisters.isEmpty()) { | ||||||
|  |                 // via variables | ||||||
|  |                 for(arg in sub.parameters.withIndex().zip(stmt.args)) { | ||||||
|  |                     argumentViaVariable(sub, arg.first, arg.second) | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 // via registers | ||||||
|  |                 if(sub.parameters.size==1) { | ||||||
|  |                     // just a single parameter, no risk of clobbering registers | ||||||
|  |                     argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0]) | ||||||
|  |                 } else { | ||||||
|  |  | ||||||
|  |                     fun isNoClobberRisk(expr: Expression): Boolean { | ||||||
|  |                         if(expr is AddressOf || | ||||||
|  |                                 expr is NumericLiteralValue || | ||||||
|  |                                 expr is StringLiteralValue || | ||||||
|  |                                 expr is ArrayLiteralValue || | ||||||
|  |                                 expr is IdentifierReference) | ||||||
|  |                             return true | ||||||
|  |  | ||||||
|  |                         if(expr is FunctionCall) { | ||||||
|  |                             if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb")) | ||||||
|  |                                 return isNoClobberRisk(expr.args[0]) | ||||||
|  |                             if(expr.target.nameInSource==listOf("mkword")) | ||||||
|  |                                 return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1]) | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         return false | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     when { | ||||||
|  |                         stmt.args.all {isNoClobberRisk(it)} -> { | ||||||
|  |                             // There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values. | ||||||
|  |                             // register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag. | ||||||
|  |                             val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters) | ||||||
|  |                             val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters } | ||||||
|  |                             val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null } | ||||||
|  |                             for(arg in cx16virtualRegs) | ||||||
|  |                                 argumentViaRegister(sub, arg.first.first, arg.first.second) | ||||||
|  |                             for(arg in cpuRegs) | ||||||
|  |                                 argumentViaRegister(sub, arg.first.first, arg.first.second) | ||||||
|  |                             for(arg in statusRegs) | ||||||
|  |                                 argumentViaRegister(sub, arg.first.first, arg.first.second) | ||||||
|  |                         } | ||||||
|  |                         else -> { | ||||||
|  |                             // Risk of clobbering due to complex expression args. Evaluate first, then assign registers. | ||||||
|  |                             registerArgsViaStackEvaluation(stmt, sub) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!sub.inline || !asmgen.options.optimize) { | ||||||
|  |             asmgen.out("  jsr  $subName") | ||||||
|  |         } else { | ||||||
|  |             // inline the subroutine. | ||||||
|  |             // we do this by copying the subroutine's statements at the call site. | ||||||
|  |             // NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine | ||||||
|  |             // (this condition has been enforced by an ast check earlier) | ||||||
|  |             asmgen.out("  \t; inlined routine follows: ${sub.name}") | ||||||
|  |             val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive } | ||||||
|  |             statements.forEach { | ||||||
|  |                 if(it is Return) { | ||||||
|  |                     asmgen.translate(it, false)     // don't use RTS for the inlined return statement | ||||||
|  |                 } else { | ||||||
|  |                     if(!sub.inline || it !is VarDecl) | ||||||
|  |                         asmgen.translate(it) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             asmgen.out("  \t; inlined routine end: ${sub.name}") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // remember: dealing with the X register and/or dealing with return values is the responsibility of the caller | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) { | ||||||
|  |         // this is called when one or more of the arguments are 'complex' and | ||||||
|  |         // cannot be assigned to a register easily or risk clobbering other registers. | ||||||
|  |  | ||||||
|  |         if(sub.parameters.isEmpty()) | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         // 1. load all arguments reversed onto the stack: first arg goes last (is on top). | ||||||
|  |         for (arg in stmt.args.reversed()) | ||||||
|  |             asmgen.translateExpression(arg) | ||||||
|  |  | ||||||
|  |         var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null | ||||||
|  |         var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null | ||||||
|  |         var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null | ||||||
|  |  | ||||||
|  |         asmgen.out("  inx")     // align estack pointer | ||||||
|  |  | ||||||
|  |         for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) { | ||||||
|  |             val plusIdxStr = if(argi.index==0) "" else "+${argi.index}" | ||||||
|  |             when { | ||||||
|  |                 argi.value.second.statusflag == Statusflag.Pc -> { | ||||||
|  |                     require(argForCarry == null) | ||||||
|  |                     argForCarry = argi | ||||||
|  |                 } | ||||||
|  |                 argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter") | ||||||
|  |                 argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> { | ||||||
|  |                     require(argForXregister==null) | ||||||
|  |                     argForXregister = argi | ||||||
|  |                 } | ||||||
|  |                 argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> { | ||||||
|  |                     require(argForAregister == null) | ||||||
|  |                     argForAregister = argi | ||||||
|  |                 } | ||||||
|  |                 argi.value.second.registerOrPair == RegisterOrPair.Y -> { | ||||||
|  |                     asmgen.out("  ldy  P8ESTACK_LO$plusIdxStr,x") | ||||||
|  |                 } | ||||||
|  |                 argi.value.second.registerOrPair in Cx16VirtualRegisters -> { | ||||||
|  |                     // immediately output code to load the virtual register, to avoid clobbering the A register later | ||||||
|  |                     when (sub.parameters[argi.index].type) { | ||||||
|  |                         in ByteDatatypes -> { | ||||||
|  |                             // only load the lsb of the virtual register | ||||||
|  |                             asmgen.out( | ||||||
|  |                                 """ | ||||||
|  |                                 lda  P8ESTACK_LO$plusIdxStr,x | ||||||
|  |                                 sta  cx16.${argi.value.second.registerOrPair.toString().lowercase()} | ||||||
|  |                             """) | ||||||
|  |                             if (asmgen.isTargetCpu(CpuType.CPU65c02)) | ||||||
|  |                                 asmgen.out( | ||||||
|  |                                     "  stz  cx16.${ | ||||||
|  |                                         argi.value.second.registerOrPair.toString().lowercase() | ||||||
|  |                                     }+1") | ||||||
|  |                             else | ||||||
|  |                                 asmgen.out( | ||||||
|  |                                     "  lda  #0 |  sta  cx16.${ | ||||||
|  |                                         argi.value.second.registerOrPair.toString().lowercase() | ||||||
|  |                                     }+1") | ||||||
|  |                         } | ||||||
|  |                         in WordDatatypes, in IterableDatatypes -> | ||||||
|  |                             asmgen.out( | ||||||
|  |                                 """ | ||||||
|  |                                 lda  P8ESTACK_LO$plusIdxStr,x | ||||||
|  |                                 sta  cx16.${argi.value.second.registerOrPair.toString().lowercase()} | ||||||
|  |                                 lda  P8ESTACK_HI$plusIdxStr,x | ||||||
|  |                                 sta  cx16.${ | ||||||
|  |                                     argi.value.second.registerOrPair.toString().lowercase() | ||||||
|  |                                 }+1 | ||||||
|  |                             """) | ||||||
|  |                         else -> throw AssemblyError("weird dt") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else -> throw AssemblyError("weird argument") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(argForCarry!=null) { | ||||||
|  |             val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}" | ||||||
|  |             asmgen.out(""" | ||||||
|  |                 lda  P8ESTACK_LO$plusIdxStr,x | ||||||
|  |                 beq  + | ||||||
|  |                 sec | ||||||
|  |                 bcs  ++ | ||||||
|  | +               clc | ||||||
|  | +               php""")             // push the status flags | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(argForAregister!=null) { | ||||||
|  |             val plusIdxStr = if(argForAregister.index==0) "" else "+${argForAregister.index}" | ||||||
|  |             when(argForAregister.value.second.registerOrPair) { | ||||||
|  |                 RegisterOrPair.A -> asmgen.out("  lda  P8ESTACK_LO$plusIdxStr,x") | ||||||
|  |                 RegisterOrPair.AY -> asmgen.out("  lda  P8ESTACK_LO$plusIdxStr,x |  ldy  P8ESTACK_HI$plusIdxStr,x") | ||||||
|  |                 else -> throw AssemblyError("weird arg") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(argForXregister!=null) { | ||||||
|  |             val plusIdxStr = if(argForXregister.index==0) "" else "+${argForXregister.index}" | ||||||
|  |  | ||||||
|  |             if(argForAregister!=null) | ||||||
|  |                 asmgen.out("  pha") | ||||||
|  |             when(argForXregister.value.second.registerOrPair) { | ||||||
|  |                 RegisterOrPair.X -> asmgen.out("  lda  P8ESTACK_LO$plusIdxStr,x |  tax") | ||||||
|  |                 RegisterOrPair.AX -> asmgen.out("  ldy  P8ESTACK_LO$plusIdxStr,x |  lda  P8ESTACK_HI$plusIdxStr,x |  tax |  tya") | ||||||
|  |                 RegisterOrPair.XY -> asmgen.out("  ldy  P8ESTACK_HI$plusIdxStr,x |  lda  P8ESTACK_LO$plusIdxStr,x |  tax") | ||||||
|  |                 else -> throw AssemblyError("weird arg") | ||||||
|  |             } | ||||||
|  |             if(argForAregister!=null) | ||||||
|  |                 asmgen.out("  pla") | ||||||
|  |         } else { | ||||||
|  |             repeat(sub.parameters.size - 1) { asmgen.out("  inx") }       // unwind stack | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(argForCarry!=null) | ||||||
|  |             asmgen.out("  plp")       // set the carry flag back to correct value | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) { | ||||||
|  |         // pass parameter via a regular variable (not via registers) | ||||||
|  |         val valueIDt = value.inferType(program) | ||||||
|  |         if(!valueIDt.isKnown) | ||||||
|  |             throw AssemblyError("unknown dt") | ||||||
|  |         val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED) | ||||||
|  |         if(!isArgumentTypeCompatible(valueDt, parameter.value.type)) | ||||||
|  |             throw AssemblyError("argument type incompatible") | ||||||
|  |  | ||||||
|  |         val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name) | ||||||
|  |         asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) { | ||||||
|  |         // pass argument via a register parameter | ||||||
|  |         val valueIDt = value.inferType(program) | ||||||
|  |         if(!valueIDt.isKnown) | ||||||
|  |             throw AssemblyError("unknown dt") | ||||||
|  |         val valueDt = valueIDt.typeOrElse(DataType.UNDEFINED) | ||||||
|  |         if(!isArgumentTypeCompatible(valueDt, parameter.value.type)) | ||||||
|  |             throw AssemblyError("argument type incompatible") | ||||||
|  |  | ||||||
|  |         val paramRegister = sub.asmParameterRegisters[parameter.index] | ||||||
|  |         val statusflag = paramRegister.statusflag | ||||||
|  |         val register = paramRegister.registerOrPair | ||||||
|  |         val requiredDt = parameter.value.type | ||||||
|  |         if(requiredDt!=valueDt) { | ||||||
|  |             if(valueDt largerThan requiredDt) | ||||||
|  |                 throw AssemblyError("can only convert byte values to word param types") | ||||||
|  |         } | ||||||
|  |         if (statusflag!=null) { | ||||||
|  |             if(requiredDt!=valueDt) | ||||||
|  |                 throw AssemblyError("for statusflag, byte value is required") | ||||||
|  |             if (statusflag == Statusflag.Pc) { | ||||||
|  |                 // this param needs to be set last, right before the jsr | ||||||
|  |                 // for now, this is already enforced on the subroutine definition by the Ast Checker | ||||||
|  |                 when(value) { | ||||||
|  |                     is NumericLiteralValue -> { | ||||||
|  |                         val carrySet = value.number.toInt() != 0 | ||||||
|  |                         asmgen.out(if(carrySet) "  sec" else "  clc") | ||||||
|  |                     } | ||||||
|  |                     is IdentifierReference -> { | ||||||
|  |                         val sourceName = asmgen.asmVariableName(value) | ||||||
|  |                         asmgen.out(""" | ||||||
|  |                 pha | ||||||
|  |                 lda  $sourceName | ||||||
|  |                 beq  + | ||||||
|  |                 sec   | ||||||
|  |                 bcs  ++ | ||||||
|  |     +           clc | ||||||
|  |     +           pla | ||||||
|  |     """) | ||||||
|  |                     } | ||||||
|  |                     else -> { | ||||||
|  |                         asmgen.assignExpressionToRegister(value, RegisterOrPair.A) | ||||||
|  |                         asmgen.out(""" | ||||||
|  |                                     beq  + | ||||||
|  |                                     sec | ||||||
|  |                                     bcs  ++ | ||||||
|  |     +                               clc | ||||||
|  |     +""") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else throw AssemblyError("can only use Carry as status flag parameter") | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             // via register or register pair | ||||||
|  |             register!! | ||||||
|  |             if(requiredDt largerThan valueDt) { | ||||||
|  |                 // we need to sign extend the source, do this via temporary word variable | ||||||
|  |                 val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1") | ||||||
|  |                 asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub) | ||||||
|  |                 asmgen.signExtendVariableLsb(scratchVar, valueDt) | ||||||
|  |                 asmgen.assignVariableToRegister(scratchVar, register) | ||||||
|  |             } else { | ||||||
|  |                 val target: AsmAssignTarget = | ||||||
|  |                     if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters)) | ||||||
|  |                         AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register) | ||||||
|  |                     else | ||||||
|  |                         AsmAssignTarget.fromRegisters(register, sub, program, asmgen) | ||||||
|  |                 val src = if(valueDt in PassByReferenceDatatypes) { | ||||||
|  |                     if(value is IdentifierReference) { | ||||||
|  |                         val addr = AddressOf(value, Position.DUMMY) | ||||||
|  |                         AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target) | ||||||
|  |                     } else { | ||||||
|  |                         AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) | ||||||
|  |                 } | ||||||
|  |                 asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { | ||||||
|  |         if(argType isAssignableTo paramType) | ||||||
|  |             return true | ||||||
|  |         if(argType in ByteDatatypes && paramType in ByteDatatypes) | ||||||
|  |             return true | ||||||
|  |         if(argType in WordDatatypes && paramType in WordDatatypes) | ||||||
|  |             return true | ||||||
|  |  | ||||||
|  |         // we have a special rule for some types. | ||||||
|  |         // strings are assignable to UWORD, for example, and vice versa | ||||||
|  |         if(argType==DataType.STR && paramType==DataType.UWORD) | ||||||
|  |             return true | ||||||
|  |         if(argType==DataType.UWORD && paramType == DataType.STR) | ||||||
|  |             return true | ||||||
|  |  | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,128 @@ | |||||||
|  | package prog8.compiler.target.cpu6502.codegen | ||||||
|  |  | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.* | ||||||
|  | import prog8.ast.expressions.IdentifierReference | ||||||
|  | import prog8.ast.expressions.NumericLiteralValue | ||||||
|  | import prog8.ast.statements.PostIncrDecr | ||||||
|  | import prog8.ast.toHex | ||||||
|  | import prog8.compiler.AssemblyError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { | ||||||
|  |     internal fun translate(stmt: PostIncrDecr) { | ||||||
|  |         val incr = stmt.operator=="++" | ||||||
|  |         val targetIdent = stmt.target.identifier | ||||||
|  |         val targetMemory = stmt.target.memoryAddress | ||||||
|  |         val targetArrayIdx = stmt.target.arrayindexed | ||||||
|  |         val scope = stmt.definingSubroutine() | ||||||
|  |         when { | ||||||
|  |             targetIdent!=null -> { | ||||||
|  |                 val what = asmgen.asmVariableName(targetIdent) | ||||||
|  |                 when (stmt.target.inferType(program).typeOrElse(DataType.UNDEFINED)) { | ||||||
|  |                     in ByteDatatypes -> asmgen.out(if (incr) "  inc  $what" else "  dec  $what") | ||||||
|  |                     in WordDatatypes -> { | ||||||
|  |                         if(incr) | ||||||
|  |                             asmgen.out(" inc  $what |  bne  + |  inc  $what+1 |+") | ||||||
|  |                         else | ||||||
|  |                             asmgen.out(""" | ||||||
|  |         lda  $what | ||||||
|  |         bne  + | ||||||
|  |         dec  $what+1 | ||||||
|  | +       dec  $what  | ||||||
|  | """) | ||||||
|  |                     } | ||||||
|  |                     DataType.FLOAT -> { | ||||||
|  |                         asmgen.out("  lda  #<$what |  ldy  #>$what") | ||||||
|  |                         asmgen.out(if(incr) "  jsr  floats.inc_var_f" else "  jsr  floats.dec_var_f") | ||||||
|  |                     } | ||||||
|  |                     else -> throw AssemblyError("need numeric type") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             targetMemory!=null -> { | ||||||
|  |                 when (val addressExpr = targetMemory.addressExpression) { | ||||||
|  |                     is NumericLiteralValue -> { | ||||||
|  |                         val what = addressExpr.number.toHex() | ||||||
|  |                         asmgen.out(if(incr) "  inc  $what" else "  dec  $what") | ||||||
|  |                     } | ||||||
|  |                     is IdentifierReference -> { | ||||||
|  |                         val what = asmgen.asmVariableName(addressExpr) | ||||||
|  |                         asmgen.out("  lda  $what |  sta  (+) +1 |  lda  $what+1 |  sta  (+) +2") | ||||||
|  |                         if(incr) | ||||||
|  |                             asmgen.out("+\tinc  ${'$'}ffff\t; modified") | ||||||
|  |                         else | ||||||
|  |                             asmgen.out("+\tdec  ${'$'}ffff\t; modified") | ||||||
|  |                     } | ||||||
|  |                     else -> { | ||||||
|  |                         asmgen.assignExpressionToRegister(addressExpr, RegisterOrPair.AY) | ||||||
|  |                         asmgen.out("  sta  (+) + 1 |  sty  (+) + 2") | ||||||
|  |                         if(incr) | ||||||
|  |                             asmgen.out("+\tinc  ${'$'}ffff\t; modified") | ||||||
|  |                         else | ||||||
|  |                             asmgen.out("+\tdec  ${'$'}ffff\t; modified") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             targetArrayIdx!=null -> { | ||||||
|  |                 val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar) | ||||||
|  |                 val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |                 val constIndex = targetArrayIdx.indexer.constIndex() | ||||||
|  |                 if(constIndex!=null) { | ||||||
|  |                     val indexValue = constIndex * program.memsizer.memorySize(elementDt) | ||||||
|  |                     when(elementDt) { | ||||||
|  |                         in ByteDatatypes -> asmgen.out(if (incr) "  inc  $asmArrayvarname+$indexValue" else "  dec  $asmArrayvarname+$indexValue") | ||||||
|  |                         in WordDatatypes -> { | ||||||
|  |                             if(incr) | ||||||
|  |                                 asmgen.out(" inc  $asmArrayvarname+$indexValue |  bne  + |  inc  $asmArrayvarname+$indexValue+1 |+") | ||||||
|  |                             else | ||||||
|  |                                 asmgen.out(""" | ||||||
|  |     lda  $asmArrayvarname+$indexValue | ||||||
|  |     bne  + | ||||||
|  |     dec  $asmArrayvarname+$indexValue+1 | ||||||
|  | +       dec  $asmArrayvarname+$indexValue  | ||||||
|  | """) | ||||||
|  |                         } | ||||||
|  |                         DataType.FLOAT -> { | ||||||
|  |                             asmgen.out("  lda  #<$asmArrayvarname+$indexValue |  ldy  #>$asmArrayvarname+$indexValue") | ||||||
|  |                             asmgen.out(if(incr) "  jsr  floats.inc_var_f" else "  jsr  floats.dec_var_f") | ||||||
|  |                         } | ||||||
|  |                         else -> throw AssemblyError("need numeric type") | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A) | ||||||
|  |                     asmgen.saveRegisterLocal(CpuRegister.X, scope!!) | ||||||
|  |                     asmgen.out("  tax") | ||||||
|  |                     when(elementDt) { | ||||||
|  |                         in ByteDatatypes -> { | ||||||
|  |                             asmgen.out(if(incr) "  inc  $asmArrayvarname,x" else "  dec  $asmArrayvarname,x") | ||||||
|  |                         } | ||||||
|  |                         in WordDatatypes -> { | ||||||
|  |                             if(incr) | ||||||
|  |                                 asmgen.out(" inc  $asmArrayvarname,x |  bne  + |  inc  $asmArrayvarname+1,x |+") | ||||||
|  |                             else | ||||||
|  |                                 asmgen.out(""" | ||||||
|  |     lda  $asmArrayvarname,x | ||||||
|  |     bne  + | ||||||
|  |     dec  $asmArrayvarname+1,x | ||||||
|  | +       dec  $asmArrayvarname,x  | ||||||
|  | """) | ||||||
|  |                         } | ||||||
|  |                         DataType.FLOAT -> { | ||||||
|  |                             asmgen.out(""" | ||||||
|  |                     ldy  #>$asmArrayvarname | ||||||
|  |                     clc | ||||||
|  |                     adc  #<$asmArrayvarname | ||||||
|  |                     bcc  + | ||||||
|  |                     iny | ||||||
|  | +                       jsr  floats.inc_var_f""") | ||||||
|  |                         } | ||||||
|  |                         else -> throw AssemblyError("weird array elt dt") | ||||||
|  |                     } | ||||||
|  |                     asmgen.restoreRegisterLocal(CpuRegister.X) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,214 @@ | |||||||
|  | package prog8.compiler.target.cpu6502.codegen.assignment | ||||||
|  |  | ||||||
|  | import prog8.ast.IMemSizer | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.* | ||||||
|  | import prog8.ast.expressions.* | ||||||
|  | import prog8.ast.statements.* | ||||||
|  | import prog8.compiler.AssemblyError | ||||||
|  | import prog8.compiler.target.cpu6502.codegen.AsmGen | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal enum class TargetStorageKind { | ||||||
|  |     VARIABLE, | ||||||
|  |     ARRAY, | ||||||
|  |     MEMORY, | ||||||
|  |     REGISTER, | ||||||
|  |     STACK | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal enum class SourceStorageKind { | ||||||
|  |     LITERALNUMBER, | ||||||
|  |     VARIABLE, | ||||||
|  |     ARRAY, | ||||||
|  |     MEMORY, | ||||||
|  |     REGISTER, | ||||||
|  |     STACK,              // value is already present on stack | ||||||
|  |     EXPRESSION,         // expression in ast-form, still to be evaluated | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal class AsmAssignTarget(val kind: TargetStorageKind, | ||||||
|  |                                private val program: Program, | ||||||
|  |                                private val asmgen: AsmGen, | ||||||
|  |                                val datatype: DataType, | ||||||
|  |                                val scope: Subroutine?, | ||||||
|  |                                private val variableAsmName: String? = null, | ||||||
|  |                                val array: ArrayIndexedExpression? = null, | ||||||
|  |                                val memory: DirectMemoryWrite? = null, | ||||||
|  |                                val register: RegisterOrPair? = null, | ||||||
|  |                                val origAstTarget: AssignTarget? = null | ||||||
|  |                                ) | ||||||
|  | { | ||||||
|  |     val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0} | ||||||
|  |     val constArrayIndexValue by lazy { array?.indexer?.constIndex() } | ||||||
|  |     val asmVarname: String | ||||||
|  |         get() = if(array==null) | ||||||
|  |             variableAsmName!! | ||||||
|  |         else | ||||||
|  |             asmgen.asmVariableName(array.arrayvar) | ||||||
|  |  | ||||||
|  |     lateinit var origAssign: AsmAssignment | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         if(register!=null && datatype !in NumericDatatypes) | ||||||
|  |             throw AssemblyError("register must be integer or float type") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) { | ||||||
|  |             val idt = inferType(program) | ||||||
|  |             if(!idt.isKnown) | ||||||
|  |                 throw AssemblyError("unknown dt") | ||||||
|  |             val dt = idt.typeOrElse(DataType.UNDEFINED) | ||||||
|  |             when { | ||||||
|  |                 identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget =  this) | ||||||
|  |                 arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget =  this) | ||||||
|  |                 memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory =  memoryAddress, origAstTarget =  this) | ||||||
|  |                 else -> throw AssemblyError("weird target") | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget = | ||||||
|  |                 when(registers) { | ||||||
|  |                     RegisterOrPair.A, | ||||||
|  |                     RegisterOrPair.X, | ||||||
|  |                     RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers) | ||||||
|  |                     RegisterOrPair.AX, | ||||||
|  |                     RegisterOrPair.AY, | ||||||
|  |                     RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers) | ||||||
|  |                     RegisterOrPair.FAC1, | ||||||
|  |                     RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers) | ||||||
|  |                     RegisterOrPair.R0, | ||||||
|  |                     RegisterOrPair.R1, | ||||||
|  |                     RegisterOrPair.R2, | ||||||
|  |                     RegisterOrPair.R3, | ||||||
|  |                     RegisterOrPair.R4, | ||||||
|  |                     RegisterOrPair.R5, | ||||||
|  |                     RegisterOrPair.R6, | ||||||
|  |                     RegisterOrPair.R7, | ||||||
|  |                     RegisterOrPair.R8, | ||||||
|  |                     RegisterOrPair.R9, | ||||||
|  |                     RegisterOrPair.R10, | ||||||
|  |                     RegisterOrPair.R11, | ||||||
|  |                     RegisterOrPair.R12, | ||||||
|  |                     RegisterOrPair.R13, | ||||||
|  |                     RegisterOrPair.R14, | ||||||
|  |                     RegisterOrPair.R15 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers) | ||||||
|  |                 } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | internal class AsmAssignSource(val kind: SourceStorageKind, | ||||||
|  |                                private val program: Program, | ||||||
|  |                                private val asmgen: AsmGen, | ||||||
|  |                                val datatype: DataType, | ||||||
|  |                                private val variableAsmName: String? = null, | ||||||
|  |                                val array: ArrayIndexedExpression? = null, | ||||||
|  |                                val memory: DirectMemoryRead? = null, | ||||||
|  |                                val register: RegisterOrPair? = null, | ||||||
|  |                                val number: NumericLiteralValue? = null, | ||||||
|  |                                val expression: Expression? = null | ||||||
|  | ) | ||||||
|  | { | ||||||
|  |     val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0} | ||||||
|  |     val constArrayIndexValue by lazy { array?.indexer?.constIndex() } | ||||||
|  |  | ||||||
|  |     val asmVarname: String | ||||||
|  |         get() = if(array==null) | ||||||
|  |             variableAsmName!! | ||||||
|  |         else | ||||||
|  |             asmgen.asmVariableName(array.arrayvar) | ||||||
|  |  | ||||||
|  |     companion object { | ||||||
|  |         fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource = fromAstSource(indexer.indexExpr, program, asmgen) | ||||||
|  |  | ||||||
|  |         fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource { | ||||||
|  |             val cv = value.constValue(program) | ||||||
|  |             if(cv!=null) | ||||||
|  |                 return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv) | ||||||
|  |  | ||||||
|  |             return when(value) { | ||||||
|  |                 is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv) | ||||||
|  |                 is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation") | ||||||
|  |                 is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation") | ||||||
|  |                 is IdentifierReference -> { | ||||||
|  |                     val dt = value.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |                     val varName=asmgen.asmVariableName(value) | ||||||
|  |                     // special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system | ||||||
|  |                     if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) { | ||||||
|  |                         val regStr = varName.lowercase().substring(5) | ||||||
|  |                         val reg = RegisterOrPair.valueOf(regStr.uppercase()) | ||||||
|  |                         AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg) | ||||||
|  |                     } else { | ||||||
|  |                         AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 is DirectMemoryRead -> { | ||||||
|  |                     AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value) | ||||||
|  |                 } | ||||||
|  |                 is ArrayIndexedExpression -> { | ||||||
|  |                     val dt = value.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |                     AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value) | ||||||
|  |                 } | ||||||
|  |                 is FunctionCall -> { | ||||||
|  |                     when (val sub = value.target.targetStatement(program)) { | ||||||
|  |                         is Subroutine -> { | ||||||
|  |                             val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first | ||||||
|  |                                     ?: throw AssemblyError("can't translate zero return values in assignment") | ||||||
|  |  | ||||||
|  |                             AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value) | ||||||
|  |                         } | ||||||
|  |                         is BuiltinFunctionStatementPlaceholder -> { | ||||||
|  |                             val returnType = value.inferType(program) | ||||||
|  |                             if(!returnType.isKnown) | ||||||
|  |                                 throw AssemblyError("unknown dt") | ||||||
|  |                             AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.UNDEFINED), expression = value) | ||||||
|  |                         } | ||||||
|  |                         else -> { | ||||||
|  |                             throw AssemblyError("weird call") | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 else -> { | ||||||
|  |                     val dt = value.inferType(program) | ||||||
|  |                     if(!dt.isKnown) | ||||||
|  |                         throw AssemblyError("unknown dt") | ||||||
|  |                     AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.UNDEFINED), expression = value) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource { | ||||||
|  |         // allow some signed/unsigned relaxations | ||||||
|  |  | ||||||
|  |         fun withAdjustedDt(newType: DataType) = | ||||||
|  |                 AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression) | ||||||
|  |  | ||||||
|  |         if(target.datatype!=datatype) { | ||||||
|  |             if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) { | ||||||
|  |                 return withAdjustedDt(target.datatype) | ||||||
|  |             } else if(target.datatype in WordDatatypes && datatype in WordDatatypes) { | ||||||
|  |                 return withAdjustedDt(target.datatype) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return this | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class AsmAssignment(val source: AsmAssignSource, | ||||||
|  |                              val target: AsmAssignTarget, | ||||||
|  |                              val isAugmentable: Boolean, | ||||||
|  |                              memsizer: IMemSizer, | ||||||
|  |                              val position: Position) { | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |         if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) | ||||||
|  |             require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype" } | ||||||
|  |             require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) { | ||||||
|  |                 "source storage size must be less or equal to target datatype storage size" | ||||||
|  |             } | ||||||
|  |     } | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,11 +1,9 @@ | |||||||
| package prog8.compiler.target.cx16 | package prog8.compiler.target.cx16 | ||||||
|  |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.compiler.* | import prog8.compiler.* | ||||||
| import prog8.compiler.target.CpuType | import prog8.compiler.target.CpuType | ||||||
| import prog8.compiler.target.IMachineDefinition | import prog8.compiler.target.IMachineDefinition | ||||||
| import prog8.compiler.target.c64.C64MachineDefinition | import prog8.compiler.target.c64.C64MachineDefinition | ||||||
| import prog8.parser.ModuleImporter |  | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
|  |  | ||||||
| internal object CX16MachineDefinition: IMachineDefinition { | internal object CX16MachineDefinition: IMachineDefinition { | ||||||
| @@ -21,25 +19,23 @@ internal object CX16MachineDefinition: IMachineDefinition { | |||||||
|     override val RAW_LOAD_ADDRESS = 0x8000 |     override val RAW_LOAD_ADDRESS = 0x8000 | ||||||
|  |  | ||||||
|     // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) |     // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) | ||||||
|     // and some heavily used string constants derived from the two values above |  | ||||||
|     override val ESTACK_LO = 0x0400        //  $0400-$04ff inclusive |     override val ESTACK_LO = 0x0400        //  $0400-$04ff inclusive | ||||||
|     override val ESTACK_HI = 0x0500        //  $0500-$05ff inclusive |     override val ESTACK_HI = 0x0500        //  $0500-$05ff inclusive | ||||||
|  |  | ||||||
|     override lateinit var zeropage: Zeropage |     override lateinit var zeropage: Zeropage | ||||||
|  |  | ||||||
|     override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num) |     override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num) | ||||||
|  |     override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> { | ||||||
|     override fun getFloatRomConst(number: Double): String? = null       // Cx16 has no pulblic ROM float locations |         return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) | ||||||
|     override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) { |             listOf("syslib") | ||||||
|         if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) |         else | ||||||
|             importer.importLibraryModule(program, "syslib") |             emptyList() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun launchEmulator(programName: String) { |     override fun launchEmulator(programName: String) { | ||||||
|         for(emulator in listOf("x16emu")) { |         for(emulator in listOf("x16emu")) { | ||||||
|             println("\nStarting Commander X16 emulator $emulator...") |             println("\nStarting Commander X16 emulator $emulator...") | ||||||
|             val cmdline = listOf(emulator, "-rom", "/usr/share/x16-rom/rom.bin", "-scale", "2", |             val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "$programName.prg") | ||||||
|                     "-run", "-prg", programName + ".prg") |  | ||||||
|             val processb = ProcessBuilder(cmdline).inheritIO() |             val processb = ProcessBuilder(cmdline).inheritIO() | ||||||
|             val process: Process |             val process: Process | ||||||
|             try { |             try { | ||||||
| @@ -74,8 +70,8 @@ internal object CX16MachineDefinition: IMachineDefinition { | |||||||
|  |  | ||||||
|     internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { |     internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||||
|  |  | ||||||
|         override val SCRATCH_B1 = 0x79      // temp storage for a single byte |         override val SCRATCH_B1 = 0x7a      // temp storage for a single byte | ||||||
|         override val SCRATCH_REG = 0x7a     // temp storage for a register |         override val SCRATCH_REG = 0x7b     // temp storage for a register, must be B1+1 | ||||||
|         override val SCRATCH_W1 = 0x7c      // temp storage 1 for a word  $7c+$7d |         override val SCRATCH_W1 = 0x7c      // temp storage 1 for a word  $7c+$7d | ||||||
|         override val SCRATCH_W2 = 0x7e      // temp storage 2 for a word  $7e+$7f |         override val SCRATCH_W2 = 0x7e      // temp storage 2 for a word  $7e+$7f | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								compiler/src/prog8/optimizer/BinExprSplitter.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								compiler/src/prog8/optimizer/BinExprSplitter.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | package prog8.optimizer | ||||||
|  |  | ||||||
|  | import prog8.ast.INameScope | ||||||
|  | import prog8.ast.Node | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.expressions.BinaryExpression | ||||||
|  | import prog8.ast.expressions.augmentAssignmentOperators | ||||||
|  | import prog8.ast.statements.AssignTarget | ||||||
|  | import prog8.ast.statements.Assignment | ||||||
|  | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  | import prog8.compiler.target.ICompilationTarget | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() { | ||||||
|  |  | ||||||
|  | //    override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||||
|  | // TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: | ||||||
|  | //        if(decl.type==VarDeclType.VAR ) { | ||||||
|  | //            val binExpr = decl.value as? BinaryExpression | ||||||
|  | //            if (binExpr != null && binExpr.operator in augmentAssignmentOperators) { | ||||||
|  | //                // split into a vardecl with just the left expression, and an aug. assignment with the right expression. | ||||||
|  | //                val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position) | ||||||
|  | //                val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) | ||||||
|  | //                val assign = Assignment(target, augExpr, binExpr.position) | ||||||
|  | //                println("SPLIT VARDECL $decl") | ||||||
|  | //                return listOf( | ||||||
|  | //                        IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl), | ||||||
|  | //                        IAstModification.InsertAfter(decl, assign, parent) | ||||||
|  | //                ) | ||||||
|  | //            } | ||||||
|  | //        } | ||||||
|  | //        return noModifications | ||||||
|  | //    } | ||||||
|  |  | ||||||
|  |     override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { | ||||||
|  |  | ||||||
|  |         val binExpr = assignment.value as? BinaryExpression | ||||||
|  |         if (binExpr != null) { | ||||||
|  | /* | ||||||
|  |  | ||||||
|  | Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack, | ||||||
|  | by attempting to splitting it up into individual simple steps. | ||||||
|  | We only consider a binary expression *one* level deep (so the operands must not be a combined expression) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | X =      BinExpr                                    X   =   LeftExpr | ||||||
|  |         <operator>                                     followed by | ||||||
|  |           /   \             IF 'X' not used         X   =   BinExpr | ||||||
|  |          /     \             IN expression ==>             <operator> | ||||||
|  |         /       \                                           /   \ | ||||||
|  |     LeftExpr.  RightExpr.                                  /     \ | ||||||
|  |                                                           X     RightExpr. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  */ | ||||||
|  |             if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program)) { | ||||||
|  |                 if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right) | ||||||
|  |                     return noModifications | ||||||
|  |  | ||||||
|  |                 if(binExpr.right.isSimple && !assignment.isAugmentable) { | ||||||
|  |                     val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position) | ||||||
|  |                     val targetExpr = assignment.target.toExpression() | ||||||
|  |                     val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position) | ||||||
|  |                     return listOf( | ||||||
|  |                         IAstModification.ReplaceNode(binExpr, augExpr, assignment), | ||||||
|  |                         IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // TODO further unraveling of binary expression trees into flat statements. | ||||||
|  |             // however this should probably be done in a more generic way to also service | ||||||
|  |             // the expressiontrees that are not used in an assignment statement... | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun isSimpleTarget(target: AssignTarget, program: Program) = | ||||||
|  |             if (target.identifier!=null || target.memoryAddress!=null) | ||||||
|  |                 compTarget.isInRegularRAM(target, program) | ||||||
|  |             else | ||||||
|  |                 false | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,211 +1,200 @@ | |||||||
| package prog8.optimizer | package prog8.optimizer | ||||||
|  |  | ||||||
| import prog8.ast.INameScope |  | ||||||
| import prog8.ast.Module | import prog8.ast.Module | ||||||
| import prog8.ast.Node | import prog8.ast.Node | ||||||
| import prog8.ast.Program | import prog8.ast.Program | ||||||
| import prog8.ast.base.DataType | import prog8.ast.base.Position | ||||||
| import prog8.ast.base.ParentSentinel | import prog8.ast.base.VarDeclType | ||||||
|  | import prog8.ast.expressions.AddressOf | ||||||
| import prog8.ast.expressions.FunctionCall | import prog8.ast.expressions.FunctionCall | ||||||
| import prog8.ast.expressions.IdentifierReference | import prog8.ast.expressions.IdentifierReference | ||||||
| import prog8.ast.processing.IAstVisitor |  | ||||||
| import prog8.ast.statements.* | import prog8.ast.statements.* | ||||||
| import prog8.compiler.loadAsmIncludeFile | import prog8.ast.walk.IAstVisitor | ||||||
|  | import prog8.compiler.IErrorReporter | ||||||
| private val alwaysKeepSubroutines = setOf( |  | ||||||
|         Pair("main", "start"), |  | ||||||
|         Pair("irq", "irq") |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) |  | ||||||
| private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CallGraph(private val program: Program) : IAstVisitor { | class CallGraph(private val program: Program) : IAstVisitor { | ||||||
|  |  | ||||||
|     val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() } |     val imports = mutableMapOf<Module, Set<Module>>().withDefault { setOf() } | ||||||
|     val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() } |     val importedBy = mutableMapOf<Module, Set<Module>>().withDefault { setOf() } | ||||||
|     val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() } |     val calls = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { setOf() } | ||||||
|     val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() } |     val calledBy = mutableMapOf<Subroutine, Set<Node>>().withDefault { setOf() } | ||||||
|  |     private val allIdentifiersAndTargets = mutableMapOf<Pair<IdentifierReference, Position>, Statement>() | ||||||
|     // TODO  add dataflow graph: what statements use what variables - can be used to eliminate unused vars |     private val allAssemblyNodes = mutableListOf<InlineAssembly>() | ||||||
|     val usedSymbols = mutableSetOf<Statement>() |  | ||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         visit(program) |         visit(program) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) { |     private val usedSubroutines: Set<Subroutine> by lazy { | ||||||
|         fun findSubs(scope: INameScope) { |         calledBy.keys + program.entrypoint() | ||||||
|             scope.statements.forEach { |  | ||||||
|                 if (it is Subroutine) |  | ||||||
|                     sub(it) |  | ||||||
|                 if (it is INameScope) |  | ||||||
|                     findSubs(it) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         findSubs(scope) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun visit(program: Program) { |     private val usedBlocks: Set<Block> by lazy { | ||||||
|         super.visit(program) |         val blocksFromSubroutines = usedSubroutines.map { it.definingBlock() } | ||||||
|  |         val blocksFromLibraries = program.allBlocks().filter { it.isInLibrary } | ||||||
|  |         val used = mutableSetOf<Block>() | ||||||
|  |  | ||||||
|         program.modules.forEach { |         allIdentifiersAndTargets.forEach { | ||||||
|             it.importedBy.clear() |             if(it.key.first.definingBlock() in blocksFromSubroutines) { | ||||||
|             it.imports.clear() |                 val target = it.value.definingBlock() | ||||||
|  |                 used.add(target) | ||||||
|             it.importedBy.addAll(importedBy.getValue(it)) |             } | ||||||
|             it.imports.addAll(imports.getValue(it)) |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         val rootmodule = program.modules.first() |         used + blocksFromLibraries + program.entrypoint().definingBlock() | ||||||
|         rootmodule.importedBy.add(rootmodule)       // don't discard root module |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun visit(block: Block) { |     private val usedModules: Set<Module> by lazy { | ||||||
|         if (block.definingModule().isLibraryModule) { |         usedBlocks.map { it.definingModule() }.toSet() | ||||||
|             // make sure the block is not removed |  | ||||||
|             addNodeAndParentScopes(block) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         super.visit(block) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun visit(directive: Directive) { |     override fun visit(directive: Directive) { | ||||||
|         val thisModule = directive.definingModule() |         val thisModule = directive.definingModule() | ||||||
|         if (directive.directive == "%import") { |         if (directive.directive == "%import") { | ||||||
|             val importedModule: Module = program.modules.single { it.name == directive.args[0].name } |             val importedModule: Module = program.modules.single { it.name == directive.args[0].name } | ||||||
|             imports[thisModule] = imports.getValue(thisModule).plus(importedModule) |             imports[thisModule] = imports.getValue(thisModule) + importedModule | ||||||
|             importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule) |             importedBy[importedModule] = importedBy.getValue(importedModule) + thisModule | ||||||
|         } else if (directive.directive == "%asminclude") { |  | ||||||
|             val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source) |  | ||||||
|             val scope = directive.definingScope() |  | ||||||
|             scanAssemblyCode(asm, directive, scope) |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         super.visit(directive) |         super.visit(directive) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun visit(identifier: IdentifierReference) { |  | ||||||
|         // track symbol usage |  | ||||||
|         val target = identifier.targetStatement(this.program.namespace) |  | ||||||
|         if (target != null) { |  | ||||||
|             addNodeAndParentScopes(target) |  | ||||||
|         } |  | ||||||
|         super.visit(identifier) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun addNodeAndParentScopes(stmt: Statement) { |  | ||||||
|         usedSymbols.add(stmt) |  | ||||||
|         var node: Node = stmt |  | ||||||
|         do { |  | ||||||
|             if (node is INameScope && node is Statement) { |  | ||||||
|                 usedSymbols.add(node) |  | ||||||
|             } |  | ||||||
|             node = node.parent |  | ||||||
|         } while (node !is Module && node !is ParentSentinel) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(subroutine: Subroutine) { |  | ||||||
|         if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines |  | ||||||
|                 || subroutine.definingModule().isLibraryModule) { |  | ||||||
|             // make sure the entrypoint is mentioned in the used symbols |  | ||||||
|             addNodeAndParentScopes(subroutine) |  | ||||||
|         } |  | ||||||
|         super.visit(subroutine) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(decl: VarDecl) { |  | ||||||
|         if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) { |  | ||||||
|             // make sure autogenerated vardecls are in the used symbols and are never removed as 'unused' |  | ||||||
|             addNodeAndParentScopes(decl) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (decl.datatype == DataType.STRUCT) |  | ||||||
|             addNodeAndParentScopes(decl) |  | ||||||
|  |  | ||||||
|         super.visit(decl) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCall: FunctionCall) { |     override fun visit(functionCall: FunctionCall) { | ||||||
|         val otherSub = functionCall.target.targetSubroutine(program.namespace) |         val otherSub = functionCall.target.targetSubroutine(program) | ||||||
|         if (otherSub != null) { |         if (otherSub != null) { | ||||||
|             functionCall.definingSubroutine()?.let { thisSub -> |             functionCall.definingSubroutine()?.let { thisSub -> | ||||||
|                 calls[thisSub] = calls.getValue(thisSub).plus(otherSub) |                 calls[thisSub] = calls.getValue(thisSub) + otherSub | ||||||
|                 calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall) |                 calledBy[otherSub] = calledBy.getValue(otherSub) + functionCall | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         super.visit(functionCall) |         super.visit(functionCall) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun visit(functionCallStatement: FunctionCallStatement) { |     override fun visit(functionCallStatement: FunctionCallStatement) { | ||||||
|         val otherSub = functionCallStatement.target.targetSubroutine(program.namespace) |         val otherSub = functionCallStatement.target.targetSubroutine(program) | ||||||
|         if (otherSub != null) { |         if (otherSub != null) { | ||||||
|             functionCallStatement.definingSubroutine()?.let { thisSub -> |             functionCallStatement.definingSubroutine()?.let { thisSub -> | ||||||
|                 calls[thisSub] = calls.getValue(thisSub).plus(otherSub) |                 calls[thisSub] = calls.getValue(thisSub) + otherSub | ||||||
|                 calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement) |                 calledBy[otherSub] = calledBy.getValue(otherSub) + functionCallStatement | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         super.visit(functionCallStatement) |         super.visit(functionCallStatement) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     override fun visit(addressOf: AddressOf) { | ||||||
|  |         val otherSub = addressOf.identifier.targetSubroutine(program) | ||||||
|  |         if(otherSub!=null) { | ||||||
|  |             addressOf.definingSubroutine()?.let { thisSub -> | ||||||
|  |                 calls[thisSub] = calls.getValue(thisSub) + otherSub | ||||||
|  |                 calledBy[otherSub] = calledBy.getValue(otherSub) + thisSub | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         super.visit(addressOf) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     override fun visit(jump: Jump) { |     override fun visit(jump: Jump) { | ||||||
|         val otherSub = jump.identifier?.targetSubroutine(program.namespace) |         val otherSub = jump.identifier?.targetSubroutine(program) | ||||||
|         if (otherSub != null) { |         if (otherSub != null) { | ||||||
|             jump.definingSubroutine()?.let { thisSub -> |             jump.definingSubroutine()?.let { thisSub -> | ||||||
|                 calls[thisSub] = calls.getValue(thisSub).plus(otherSub) |                 calls[thisSub] = calls.getValue(thisSub) + otherSub | ||||||
|                 calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump) |                 calledBy[otherSub] = calledBy.getValue(otherSub) + jump | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         super.visit(jump) |         super.visit(jump) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun visit(structDecl: StructDecl) { |     override fun visit(identifier: IdentifierReference) { | ||||||
|         usedSymbols.add(structDecl) |         allIdentifiersAndTargets[Pair(identifier, identifier.position)] = identifier.targetStatement(program)!! | ||||||
|         usedSymbols.addAll(structDecl.statements) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun visit(inlineAssembly: InlineAssembly) { |     override fun visit(inlineAssembly: InlineAssembly) { | ||||||
|         // parse inline asm for subroutine calls (jmp, jsr) |         allAssemblyNodes.add(inlineAssembly) | ||||||
|         val scope = inlineAssembly.definingScope() |  | ||||||
|         scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope) |  | ||||||
|         super.visit(inlineAssembly) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) { |     fun checkRecursiveCalls(errors: IErrorReporter) { | ||||||
|         asm.lines().forEach { line -> |         val cycles = recursionCycles() | ||||||
|             val matches = asmJumpRx.matchEntire(line) |         if(cycles.any()) { | ||||||
|             if (matches != null) { |             errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY) | ||||||
|                 val jumptarget = matches.groups[2]?.value |             val printed = mutableSetOf<Subroutine>() | ||||||
|                 if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) { |             for(chain in cycles) { | ||||||
|                     val node = program.namespace.lookup(jumptarget.split('.'), context) |                 if(chain[0] !in printed) { | ||||||
|                     if (node is Subroutine) { |                     val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" } | ||||||
|                         calls[scope] = calls.getValue(scope).plus(node) |                     errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY) | ||||||
|                         calledBy[node] = calledBy.getValue(node).plus(context) |                     printed.add(chain[0]) | ||||||
|                     } else if (jumptarget.contains('.')) { |  | ||||||
|                         // maybe only the first part already refers to a subroutine |  | ||||||
|                         val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context) |  | ||||||
|                         if (node2 is Subroutine) { |  | ||||||
|                             calls[scope] = calls.getValue(scope).plus(node2) |  | ||||||
|                             calledBy[node2] = calledBy.getValue(node2).plus(context) |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 val matches2 = asmRefRx.matchEntire(line) |  | ||||||
|                 if (matches2 != null) { |  | ||||||
|                     val target = matches2.groups[2]?.value |  | ||||||
|                     if (target != null && (target[0].isLetter() || target[0] == '_')) { |  | ||||||
|                         if (target.contains('.')) { |  | ||||||
|                             val node = program.namespace.lookup(listOf(target.substringBefore('.')), context) |  | ||||||
|                             if (node is Subroutine) { |  | ||||||
|                                 calls[scope] = calls.getValue(scope).plus(node) |  | ||||||
|                                 calledBy[node] = calledBy.getValue(node).plus(context) |  | ||||||
|                             } |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun recursionCycles(): List<List<Subroutine>> { | ||||||
|  |         val chains = mutableListOf<MutableList<Subroutine>>() | ||||||
|  |         for(caller in calls.keys) { | ||||||
|  |             val visited = calls.keys.associateWith { false }.toMutableMap() | ||||||
|  |             val recStack = calls.keys.associateWith { false }.toMutableMap() | ||||||
|  |             val chain = mutableListOf<Subroutine>() | ||||||
|  |             if(hasCycle(caller, visited, recStack, chain)) | ||||||
|  |                 chains.add(chain) | ||||||
|  |         } | ||||||
|  |         return chains | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean { | ||||||
|  |         // mark current node as visited and add to recursion stack | ||||||
|  |         if(recStack[sub]==true) | ||||||
|  |             return true | ||||||
|  |         if(visited[sub]==true) | ||||||
|  |             return false | ||||||
|  |  | ||||||
|  |         // mark visited and add to recursion stack | ||||||
|  |         visited[sub] = true | ||||||
|  |         recStack[sub] = true | ||||||
|  |  | ||||||
|  |         // recurse for all neighbours | ||||||
|  |         for(called in calls.getValue(sub)) { | ||||||
|  |             if(hasCycle(called, visited, recStack, chain)) { | ||||||
|  |                 chain.add(called) | ||||||
|  |                 return true | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // pop from recursion stack | ||||||
|  |         recStack[sub] = false | ||||||
|  |         return false | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun unused(module: Module) = module !in usedModules | ||||||
|  |  | ||||||
|  |     fun unused(sub: Subroutine): Boolean { | ||||||
|  |         return sub !in usedSubroutines && !nameInAssemblyCode(sub.name) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun unused(block: Block): Boolean { | ||||||
|  |         return block !in usedBlocks && !nameInAssemblyCode(block.name) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun unused(decl: VarDecl): Boolean { | ||||||
|  |         if(decl.type!=VarDeclType.VAR || decl.autogeneratedDontRemove || decl.sharedWithAsm) | ||||||
|  |             return false | ||||||
|  |  | ||||||
|  |         if(decl.definingBlock() !in usedBlocks) | ||||||
|  |             return false | ||||||
|  |  | ||||||
|  |         val allReferencedVardecls = allIdentifiersAndTargets.filter { it.value is VarDecl }.map { it.value }.toSet() | ||||||
|  |         return decl !in allReferencedVardecls // Don't check assembly just for occurrences of variables, if they're not used in prog8 itself, just kill them | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { it.assembly.contains(name) } | ||||||
|  |  | ||||||
|  |     inline fun unused(label: Label) = false   // just always output labels | ||||||
|  |  | ||||||
|  |     fun unused(stmt: ISymbolStatement): Boolean { | ||||||
|  |         return when(stmt) { | ||||||
|  |             is Subroutine -> unused(stmt) | ||||||
|  |             is Block -> unused(stmt) | ||||||
|  |             is VarDecl -> unused(stmt) | ||||||
|  |             is Label -> false   // just always output labels | ||||||
|  |             else -> false | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ import kotlin.math.pow | |||||||
| class ConstExprEvaluator { | class ConstExprEvaluator { | ||||||
|  |  | ||||||
|     fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression { |     fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression { | ||||||
|  |         try { | ||||||
|             return when(operator) { |             return when(operator) { | ||||||
|                 "+" -> plus(left, right) |                 "+" -> plus(left, right) | ||||||
|                 "-" -> minus(left, right) |                 "-" -> minus(left, right) | ||||||
| @@ -32,6 +33,9 @@ class ConstExprEvaluator { | |||||||
|                 ">>" -> shiftedright(left, right) |                 ">>" -> shiftedright(left, right) | ||||||
|                 else -> throw FatalAstException("const evaluation for invalid operator $operator") |                 else -> throw FatalAstException("const evaluation for invalid operator $operator") | ||||||
|             } |             } | ||||||
|  |         } catch (ax: FatalAstException) { | ||||||
|  |             throw ExpressionError(ax.message, left.position) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression { |     private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression { | ||||||
|   | |||||||
| @@ -4,13 +4,16 @@ import prog8.ast.Node | |||||||
| import prog8.ast.Program | import prog8.ast.Program | ||||||
| import prog8.ast.base.* | import prog8.ast.base.* | ||||||
| import prog8.ast.expressions.* | import prog8.ast.expressions.* | ||||||
| import prog8.ast.processing.AstWalker | import prog8.ast.statements.Assignment | ||||||
| import prog8.ast.processing.IAstModification | import prog8.ast.statements.ForLoop | ||||||
| import prog8.ast.statements.* | import prog8.ast.statements.VarDecl | ||||||
|  | import prog8.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  | import prog8.compiler.target.ICompilationTarget | ||||||
|  | import kotlin.math.pow | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() { | internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() { | ||||||
|     private val noModifications = emptyList<IAstModification>() |  | ||||||
|  |  | ||||||
|     override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { |     override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { | ||||||
|         // @( &thing )  -->  thing |         // @( &thing )  -->  thing | ||||||
| @@ -97,7 +100,45 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke | |||||||
|     override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { |     override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { | ||||||
|         val leftconst = expr.left.constValue(program) |         val leftconst = expr.left.constValue(program) | ||||||
|         val rightconst = expr.right.constValue(program) |         val rightconst = expr.right.constValue(program) | ||||||
|  |         val modifications = mutableListOf<IAstModification>() | ||||||
|  |  | ||||||
|  |         if(expr.operator == "**" && leftconst!=null) { | ||||||
|  |             // optimize various simple cases of ** : | ||||||
|  |             //  optimize away 1 ** x into just 1 and 0 ** x into just 0 | ||||||
|  |             //  optimize 2 ** x into (1<<x)  if both operands are integer. | ||||||
|  |             val leftDt = leftconst.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |             when (leftconst.number.toDouble()) { | ||||||
|  |                 0.0 -> { | ||||||
|  |                     val value = NumericLiteralValue(leftDt, 0, expr.position) | ||||||
|  |                     modifications += IAstModification.ReplaceNode(expr, value, parent) | ||||||
|  |                 } | ||||||
|  |                 1.0 -> { | ||||||
|  |                     val value = NumericLiteralValue(leftDt, 1, expr.position) | ||||||
|  |                     modifications += IAstModification.ReplaceNode(expr, value, parent) | ||||||
|  |                 } | ||||||
|  |                 2.0 -> { | ||||||
|  |                     if(rightconst!=null) { | ||||||
|  |                         val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position) | ||||||
|  |                         modifications += IAstModification.ReplaceNode(expr, value, parent) | ||||||
|  |                     } else { | ||||||
|  |                         val rightDt = expr.right.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |                         if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) { | ||||||
|  |                             val targetDt = | ||||||
|  |                                 when (parent) { | ||||||
|  |                                     is Assignment -> parent.target.inferType(program).typeOrElse(DataType.UNDEFINED) | ||||||
|  |                                     is VarDecl -> parent.datatype | ||||||
|  |                                     else -> leftDt | ||||||
|  |                                 } | ||||||
|  |                             val one = NumericLiteralValue(targetDt, 1, expr.position) | ||||||
|  |                             val shift = BinaryExpression(one, "<<", expr.right, expr.position) | ||||||
|  |                             modifications += IAstModification.ReplaceNode(expr, shift, parent) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(expr.inferType(program).istype(DataType.FLOAT)) { | ||||||
|             val subExpr: BinaryExpression? = when { |             val subExpr: BinaryExpression? = when { | ||||||
|                 leftconst != null -> expr.right as? BinaryExpression |                 leftconst != null -> expr.right as? BinaryExpression | ||||||
|                 rightconst != null -> expr.left as? BinaryExpression |                 rightconst != null -> expr.left as? BinaryExpression | ||||||
| @@ -108,10 +149,14 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke | |||||||
|                 val subrightconst = subExpr.right.constValue(program) |                 val subrightconst = subExpr.right.constValue(program) | ||||||
|                 if ((subleftconst != null && subrightconst == null) || (subleftconst == null && subrightconst != null)) { |                 if ((subleftconst != null && subrightconst == null) || (subleftconst == null && subrightconst != null)) { | ||||||
|                     // try reordering. |                     // try reordering. | ||||||
|                 val change = groupTwoConstsTogether(expr, subExpr, |                     val change = groupTwoFloatConstsTogether( | ||||||
|  |                         expr, subExpr, | ||||||
|                         leftconst != null, rightconst != null, |                         leftconst != null, rightconst != null, | ||||||
|                         subleftconst != null, subrightconst != null) |                         subleftconst != null, subrightconst != null | ||||||
|                 return change?.let { listOf(it) } ?: noModifications |                     ) | ||||||
|  |                     if (change != null) | ||||||
|  |                         modifications += change | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -119,10 +164,10 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke | |||||||
|         if(leftconst != null && rightconst != null) { |         if(leftconst != null && rightconst != null) { | ||||||
|             val evaluator = ConstExprEvaluator() |             val evaluator = ConstExprEvaluator() | ||||||
|             val result = evaluator.evaluate(leftconst, expr.operator, rightconst) |             val result = evaluator.evaluate(leftconst, expr.operator, rightconst) | ||||||
|             return listOf(IAstModification.ReplaceNode(expr, result, parent)) |             modifications += IAstModification.ReplaceNode(expr, result, parent) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return noModifications |         return modifications | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { |     override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { | ||||||
| @@ -142,7 +187,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke | |||||||
|         } else { |         } else { | ||||||
|             val arrayDt = array.guessDatatype(program) |             val arrayDt = array.guessDatatype(program) | ||||||
|             if (arrayDt.isKnown) { |             if (arrayDt.isKnown) { | ||||||
|                 val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT)) |                 val newArray = array.cast(arrayDt.typeOrElse(DataType.UNDEFINED)) | ||||||
|                 if (newArray != null && newArray != array) |                 if (newArray != null && newArray != array) | ||||||
|                     return listOf(IAstModification.ReplaceNode(array, newArray, parent)) |                     return listOf(IAstModification.ReplaceNode(array, newArray, parent)) | ||||||
|             } |             } | ||||||
| @@ -178,7 +223,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke | |||||||
|                     range.step |                     range.step | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|             return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position) |             return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // adjust the datatype of a range expression in for loops to the loop variable. |         // adjust the datatype of a range expression in for loops to the loop variable. | ||||||
| @@ -187,7 +232,8 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke | |||||||
|         val rangeTo = iterableRange.to as? NumericLiteralValue |         val rangeTo = iterableRange.to as? NumericLiteralValue | ||||||
|         if(rangeFrom==null || rangeTo==null) return noModifications |         if(rangeFrom==null || rangeTo==null) return noModifications | ||||||
|  |  | ||||||
|         val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!! |         val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar) | ||||||
|  |  | ||||||
|         val stepLiteral = iterableRange.step as? NumericLiteralValue |         val stepLiteral = iterableRange.step as? NumericLiteralValue | ||||||
|         when(loopvar.datatype) { |         when(loopvar.datatype) { | ||||||
|             DataType.UBYTE -> { |             DataType.UBYTE -> { | ||||||
| @@ -258,13 +304,15 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun groupTwoConstsTogether(expr: BinaryExpression, |     private fun groupTwoFloatConstsTogether(expr: BinaryExpression, | ||||||
|        subExpr: BinaryExpression, |        subExpr: BinaryExpression, | ||||||
|        leftIsConst: Boolean, |        leftIsConst: Boolean, | ||||||
|        rightIsConst: Boolean, |        rightIsConst: Boolean, | ||||||
|        subleftIsConst: Boolean, |        subleftIsConst: Boolean, | ||||||
|        subrightIsConst: Boolean): IAstModification? |        subrightIsConst: Boolean): IAstModification? | ||||||
|     { |     { | ||||||
|  |         // NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS | ||||||
|  |  | ||||||
|         // todo: this implements only a small set of possible reorderings at this time |         // todo: this implements only a small set of possible reorderings at this time | ||||||
|         if(expr.operator==subExpr.operator) { |         if(expr.operator==subExpr.operator) { | ||||||
|             // both operators are the same. |             // both operators are the same. | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user