mirror of
				https://github.com/irmen/prog8.git
				synced 2025-10-31 00:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			1027 Commits
		
	
	
		
			v3.0
			...
			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 | ||
|  | 22031f39b0 | ||
|  | c4673d3a67 | ||
|  | e83e021541 | ||
|  | c1f2ecd413 | ||
|  | 46fbe01df9 | ||
|  | 8647a8290e | ||
|  | bac51f4b31 | ||
|  | 582aab180a | ||
|  | 5fb714fcb2 | ||
|  | 3994de77d0 | ||
|  | 24c8d1f1f4 | ||
|  | 110f877dcc | ||
|  | 9cd3a9f8e8 | ||
|  | 1464050bf5 | ||
|  | 95e9e1b550 | ||
|  | bda1c1c1eb | ||
|  | d020a7974a | ||
|  | a51fad3aab | ||
|  | 3cd32778bb | ||
|  | 8d67056f84 | ||
|  | e986973b5e | ||
|  | 448c934cba | ||
|  | 96ef7ba55d | ||
|  | 4372de1e7e | ||
|  | af0fb88adf | ||
|  | 066233eee8 | ||
|  | b6f85d10b0 | ||
|  | 6f75413c09 | ||
|  | d45fe4ce74 | ||
|  | e828c013e6 | ||
|  | 988459f744 | ||
|  | 7c701bdf3f | ||
|  | 446fc35d5c | ||
|  | bec9cc7047 | ||
|  | 961380acb6 | ||
|  | 84c0685a60 | ||
|  | 629222f103 | ||
|  | 8c448e5bc2 | ||
|  | b5fa6c2d0a | ||
|  | 680b2df08a | ||
|  | 09bd47f98b | ||
|  | 7f69f9ce4f | ||
|  | 4179b4e543 | ||
|  | 66364554c4 | ||
|  | 43f2448789 | ||
|  | 130cee1e70 | ||
|  | b976360248 | ||
|  | 225bfc4164 | ||
|  | d7ceda4d82 | ||
|  | 14d091e60a | ||
|  | 2809668ef4 | ||
|  | bafb86e00b | ||
|  | f5db31b8ff | ||
|  | e1d0dbed0c | ||
|  | 1d1fe364d0 | ||
|  | 2b9316c4ff | ||
|  | c50cbbb526 | ||
|  | b93d9ecd7e | ||
|  | 96243db88b | ||
|  | 4daf75a8cc | ||
|  | 8c63d7cf5b | ||
|  | 6f78a32e64 | ||
|  | af6731c9c8 | ||
|  | 25cf0d2b94 | ||
|  | 9389791d91 | ||
|  | aa8191d0a1 | ||
|  | 0d5c78e875 | ||
|  | e8679ae03b | ||
|  | d1d224b7fc | ||
|  | df995f7bc9 | ||
|  | af39502450 | ||
|  | ffa38955d6 | ||
|  | 8d82fb6d8f | ||
|  | 306770331a | ||
|  | d3f433c8cf | ||
|  | cf49cbd1f8 | ||
|  | 8a99e75299 | ||
|  | 2dbf849c82 | ||
|  | ba3dce0b4c | ||
|  | ca9588380a | ||
|  | ae2619602d | ||
|  | de06353194 | ||
|  | 3ff3f5e1cc | ||
|  | 4b747859b3 | ||
|  | 2201765366 | ||
|  | dfa1d5e398 | ||
|  | ce9a90f626 | ||
|  | 2deb18beb2 | ||
|  | 0f7454059c | ||
|  | f9ba09ac4d | ||
|  | 4e74873eae | ||
|  | f0cd03d14f | ||
|  | f2b069c562 | ||
|  | bc89306dc1 | ||
|  | bf4da1655b | ||
|  | d819aa270f | ||
|  | e6d945f835 | ||
|  | 4fe408f1fd | ||
|  | c376e42092 | ||
|  | 63a653cdf0 | ||
|  | 5d900800f2 | ||
|  | def06dbc0b | ||
|  | 9b66a597bb | ||
|  | f1ee3b4e60 | ||
|  | 6395e39d63 | ||
|  | 2a6d9d7e31 | ||
|  | 32a7cd31da | ||
|  | dd4a56cb5f | ||
|  | d110d1cb5f | ||
|  | 48858019b7 | ||
|  | aff6b1fca5 | ||
|  | d260182ef3 | ||
|  | e39a38b0d9 | ||
|  | 82d7179c92 | ||
|  | f42746ba06 | ||
|  | 1f69deaccd | ||
|  | ea8b7ab193 | ||
|  | 9938959026 | ||
|  | d5e5485d2e | ||
|  | 97b9c8f320 | ||
|  | 35aebbc209 | ||
|  | 81f7419f70 | ||
|  | 2f951bd54d | ||
|  | 18f5963b09 | ||
|  | 836509c1d1 | ||
|  | 949d536e42 | ||
|  | f69b17e165 | ||
|  | 49a0584c54 | ||
|  | e21aa2c8f0 | ||
|  | 40071b1431 | ||
|  | 02e29e6990 | ||
|  | e19de0901e | ||
|  | 137d506e42 | ||
|  | 90c4a26d52 | ||
|  | f378a8997b | ||
|  | 1377bed988 | ||
|  | 8f9f947c42 | ||
|  | 37f6c2858f | ||
|  | 13d7f239ab | ||
|  | a6f3c84e28 | ||
|  | fe4e0e9835 | ||
|  | 809917f13b | ||
|  | 2b35498370 | ||
|  | f45eabdd9e | ||
|  | 438f3ee8d2 | ||
|  | 4bea31f051 | ||
|  | 5eae7a2b93 | ||
|  | 364ef3e55c | ||
|  | e61818f194 | ||
|  | 0f9ce319d4 | ||
|  | 5d90871789 | ||
|  | 88a9e09918 | ||
|  | c50ecf6055 | ||
|  | a18de75da9 | ||
|  | e112dfd910 | ||
|  | 9154d8bd37 | ||
|  | 0b55372b3b | ||
|  | 3ad7fb010f | ||
|  | 3f64d1bb5a | ||
|  | a6f564ad88 | ||
|  | d97da3bb7b | ||
|  | a77d3c92ad | ||
|  | 6d17e5307c | ||
|  | c2205e473a | ||
|  | 4ffb194847 | ||
|  | 744cd6ec42 | ||
|  | f08fc18ab5 | ||
|  | 462af76770 | ||
|  | 9cec554f7c | ||
|  | 08b25e610d | ||
|  | e896d5a1a6 | ||
|  | b939562062 | ||
|  | 256781bba5 | ||
|  | 19705196d6 | ||
|  | 3ce692bb10 | ||
|  | 78bdbde3ae | ||
|  | 8d8c066447 | ||
|  | 5da9379c37 | ||
|  | 032d20ff37 | ||
|  | d19b17cbfe | ||
|  | 4a4f8ff5db | ||
|  | 60a9209a14 | ||
|  | 0f9e167df3 | ||
|  | 2e2b8c498e | ||
|  | 144199730f | ||
|  | 4bb4eab3b2 | ||
|  | cf9151f669 | ||
|  | aef4598cec | ||
|  | 3ada0fdf84 | ||
|  | a5d97b326e | ||
|  | 2640015fb1 | ||
|  | 6cd42ddafe | ||
|  | 1f17c22132 | ||
|  | 5c62f612cc | ||
|  | b9ca1c2e2c | ||
|  | 93b2ff2e52 | ||
|  | 3991d23a69 | ||
|  | 1be139759c | ||
|  | d0674ad688 | ||
|  | ffb47458ff | ||
|  | 84ec1be8a4 | ||
|  | f4dafec645 | ||
|  | 97ce72521d | ||
|  | d2f0e74879 | ||
|  | d9e3895c45 | ||
|  | 5075901830 | ||
|  | f1193bb5a0 | ||
|  | d3dc279105 | ||
|  | acc942f690 | ||
|  | e947067dcf | ||
|  | bd9ebf4603 | ||
|  | f41192a52a | ||
|  | ff54d6abd7 | ||
|  | f40bcc219f | ||
|  | 679965410a | ||
|  | c6e13ae2a3 | ||
|  | 20cdcc673b | ||
|  | 89f46222d9 | ||
|  | b27cbfac5e | ||
|  | 31c946aeeb | ||
|  | bfc8a26381 | ||
|  | 9d98746501 | ||
|  | 63b03ba70c | ||
|  | 70bab76b36 | ||
|  | 15d24d4308 | ||
|  | 9ec62eb045 | ||
|  | 12f841e30d | ||
|  | 335599ed22 | ||
|  | 0b717f9e76 | ||
|  | e941f6ecca | ||
|  | ef7744dbda | ||
|  | c83a61c460 | ||
|  | 335684caf7 | ||
|  | 8d6220ce51 | ||
|  | 39ea5c5f99 | ||
|  | b03597ac13 | ||
|  | 58f323c087 | ||
|  | 513a68584c | ||
|  | 88d5c68b32 | ||
|  | 14f9382cf9 | ||
|  | cffb582568 | ||
|  | e1812ce16c | ||
|  | 7a3163f59a | ||
|  | 6f3b2749b0 | ||
|  | c144d4e501 | ||
|  | edfd9d55ba | ||
|  | 774897260e | ||
|  | 65ba91411d | ||
|  | 9cbb8e1a64 | ||
|  | 53e9ad5088 | ||
|  | cf6ea63fa6 | ||
|  | 1de0ebb7bc | ||
|  | 77c1376d6d | ||
|  | 353f1954a5 | ||
|  | 8bf3406cf8 | ||
|  | 936bf9a05c | ||
|  | 4487499663 | ||
|  | 3976cc26a2 | ||
|  | e6ff87ecd0 | ||
|  | c0887b5f08 | ||
|  | f14dda4eca | ||
|  | bd7f75c130 | ||
|  | fbe3ce008b | ||
|  | 7ac6c8f2d1 | ||
|  | fdfbb7bdf0 | ||
|  | 1c16bbb742 | ||
|  | 9735527062 | ||
|  | 402827497e | ||
|  | f81aa0d867 | ||
|  | d32a970101 | ||
|  | cd651aa416 | ||
|  | 8a3189123a | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -29,3 +29,5 @@ parsetab.py | ||||
|  | ||||
| .gradle | ||||
| /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" /> | ||||
|     <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||
|       <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 isEnabled="false" name="TypeScript" /> | ||||
|         <language isEnabled="false" name="ActionScript" /> | ||||
|         <language isEnabled="false" name="Groovy" /> | ||||
|       </Languages> | ||||
|     </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"> | ||||
|       <option name="processCode" value="false" /> | ||||
|       <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"?> | ||||
| <project version="4"> | ||||
|   <component name="Kotlin2JvmCompilerArguments"> | ||||
|     <option name="jvmTarget" value="1.8" /> | ||||
|     <option name="jvmTarget" value="11" /> | ||||
|   </component> | ||||
| </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"> | ||||
|   <library name="antlr-4.8-complete"> | ||||
|   <library name="antlr-4.9-complete"> | ||||
|     <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> | ||||
|     <JAVADOC /> | ||||
|     <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"> | ||||
|   <library name="antlr-runtime-4.8"> | ||||
|   <library name="antlr-runtime-4.9"> | ||||
|     <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> | ||||
|     <JAVADOC /> | ||||
|     <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"> | ||||
|   <library name="kotlinx-cli-jvm-0.1.0-dev-5"> | ||||
|   <library name="kotlinx-cli-jvm"> | ||||
|     <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> | ||||
|     <JAVADOC /> | ||||
|     <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> | ||||
|     </option> | ||||
|   </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" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										3
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							| @@ -3,8 +3,11 @@ | ||||
|   <component name="ProjectModuleManager"> | ||||
|     <modules> | ||||
|       <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$/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" /> | ||||
|     </modules> | ||||
|   </component> | ||||
|   | ||||
| @@ -4,8 +4,8 @@ sudo: false | ||||
| # dist: xenial | ||||
|  | ||||
| before_install: | ||||
|   - chmod +x gradlew | ||||
|   - chmod +x ./gradlew | ||||
|  | ||||
| script: | ||||
|   - gradle test | ||||
|   - ./gradlew test | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 | ||||
|                        Version 3, 29 June 2007 | ||||
|  | ||||
|   | ||||
							
								
								
									
										115
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								README.md
									
									
									
									
									
								
							| @@ -2,61 +2,80 @@ | ||||
| [](https://travis-ci.org/irmen/prog8) | ||||
| [](https://prog8.readthedocs.io/) | ||||
|  | ||||
| Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors | ||||
| =========================================================================== | ||||
| Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors | ||||
| ============================================================================ | ||||
|  | ||||
| *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 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, | ||||
| 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). | ||||
|  | ||||
| - reduction of source code length | ||||
| Documentation | ||||
| ------------- | ||||
| Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at: | ||||
| https://prog8.readthedocs.io/ | ||||
|  | ||||
| Software license | ||||
| ---------------- | ||||
| GNU GPL 3.0, see file LICENSE | ||||
|  | ||||
| - 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 | ||||
| - 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 | ||||
| - various data types other than just bytes (16-bit words, floats, strings) | ||||
| - automatic variable allocations, automatic string and array variables and string sharing | ||||
| - subroutines with an input- and output parameter signature | ||||
| - constant folding in expressions | ||||
| - floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do) | ||||
| - strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents. | ||||
| - automatic static variable allocations, automatic string and array variables and string sharing | ||||
| - 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 | ||||
| - '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 | ||||
| - floating point operations  (requires the C64 Basic ROM routines for this) | ||||
| - abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses | ||||
| - various code optimizations (code structure, logical and numerical expressions, unused code removal...) | ||||
| - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse`` | ||||
| - various powerful built-in libraries to do I/O, number conversions, graphics and more   | ||||
| - convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses | ||||
| - 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 | ||||
| - very quick compilation times | ||||
| - use a modern PC to do the work on, use nice editors and enjoy quick compilation times | ||||
| - 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 | ||||
| - source code labels automatically loaded in Vice emulator so it can show them in disassembly | ||||
|  | ||||
| Prog8 is mainly targeted at the Commodore-64 machine at this time. | ||||
| Contributions to add support for other 8-bit (or other?!) machines are welcome. | ||||
| *Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): | ||||
|  | ||||
| Documentation/manual | ||||
| -------------------- | ||||
| https://prog8.readthedocs.io/ | ||||
| - "c64": Commodore-64  (6510 CPU = almost a 6502) | ||||
| - "cx16": [CommanderX16](https://www.commanderx16.com)  (65c02 CPU) | ||||
| - 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)! | ||||
|  | ||||
| Required tools | ||||
| -------------- | ||||
|  | ||||
|  | ||||
| Additional required tools | ||||
| ------------------------- | ||||
|  | ||||
| [64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path. | ||||
| 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). | ||||
|  | ||||
| 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, | ||||
| IntelliJ IDEA with the Kotlin plugin). | ||||
|  | ||||
| It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence | ||||
| of the [Vice emulator](http://vice-emu.sourceforge.net/) | ||||
| It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence | ||||
| of the [Vice emulator](http://vice-emu.sourceforge.net/)  for the C64 target, | ||||
| and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target. | ||||
|  | ||||
|  | ||||
| Example code | ||||
| @@ -64,44 +83,43 @@ Example code | ||||
|  | ||||
| This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | ||||
|  | ||||
|     %import c64utils | ||||
|     %import textio | ||||
|     %zeropage basicsafe | ||||
|      | ||||
|     main { | ||||
|      | ||||
|         ubyte[256] sieve | ||||
|         ubyte candidate_prime = 2 | ||||
|         ubyte candidate_prime = 2       ; is increased in the loop | ||||
|      | ||||
|         sub start() { | ||||
|             memset(sieve, 256, false) | ||||
|  | ||||
|             c64scr.print("prime numbers up to 255:\n\n") | ||||
|             sys.memset(sieve, 256, false)   ; clear the sieve | ||||
|             txt.print("prime numbers up to 255:\n\n") | ||||
|             ubyte amount=0 | ||||
|             while true { | ||||
|             repeat { | ||||
|                 ubyte prime = find_next_prime() | ||||
|                 if prime==0 | ||||
|                     break | ||||
|                 c64scr.print_ub(prime) | ||||
|                 c64scr.print(", ") | ||||
|                 txt.print_ub(prime) | ||||
|                 txt.print(", ") | ||||
|                 amount++ | ||||
|             } | ||||
|             c64.CHROUT('\n') | ||||
|             c64scr.print("number of primes (expected 54): ") | ||||
|             c64scr.print_ub(amount) | ||||
|             c64.CHROUT('\n') | ||||
|             txt.nl() | ||||
|             txt.print("number of primes (expected 54): ") | ||||
|             txt.print_ub(amount) | ||||
|             txt.nl() | ||||
|         } | ||||
|      | ||||
|  | ||||
|         sub find_next_prime() -> ubyte { | ||||
|  | ||||
|             while sieve[candidate_prime] { | ||||
|                 candidate_prime++ | ||||
|                 if candidate_prime==0 | ||||
|                     return 0 | ||||
|                     return 0        ; we wrapped; no more primes | ||||
|             } | ||||
|      | ||||
|             ; found next one, mark the multiples and return it. | ||||
|             sieve[candidate_prime] = true | ||||
|             uword multiple = candidate_prime | ||||
|      | ||||
|             while multiple < len(sieve) { | ||||
|                 sieve[lsb(multiple)] = true | ||||
|                 multiple += candidate_prime | ||||
| @@ -111,11 +129,12 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| when compiled an ran on a C-64 you'll get: | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this: | ||||
|  | ||||
|  | ||||
| @@ -127,3 +146,9 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube: | ||||
| If you want to play a video game, a fully working Tetris clone is included in the examples: | ||||
|  | ||||
|  | ||||
|  | ||||
| There are a couple of examples specially made for the CommanderX16 compiler target. | ||||
| 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.3.72" | ||||
|     } | ||||
| } | ||||
|  | ||||
| plugins { | ||||
|     // id "org.jetbrains.kotlin.jvm" version "1.3.72" | ||||
|     id 'application' | ||||
|     id 'org.jetbrains.dokka' version "0.9.18" | ||||
|     id 'com.github.johnrengelman.shadow' version '5.2.0' | ||||
|     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" | ||||
| apply plugin: "java" | ||||
|  | ||||
| targetCompatibility = 1.8 | ||||
| sourceCompatibility = 1.8 | ||||
| targetCompatibility = 11 | ||||
| sourceCompatibility = 11 | ||||
|  | ||||
| repositories { | ||||
|     mavenLocal() | ||||
|     mavenCentral() | ||||
|     jcenter() | ||||
|     maven { url "https://dl.bintray.com/orangy/maven/" } | ||||
|     maven { url "https://kotlin.bintray.com/kotlinx" } | ||||
| } | ||||
|  | ||||
| def prog8version = rootProject.file('compiler/res/version.txt').text.trim() | ||||
|  | ||||
| dependencies { | ||||
|     implementation project(':parser') | ||||
|     implementation project(':compilerAst') | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" | ||||
|     // implementation "org.jetbrains.kotlin:kotlin-reflect" | ||||
|     implementation 'org.antlr:antlr4-runtime:4.8' | ||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5' | ||||
|     // implementation 'net.razorvine:ksim65:1.6' | ||||
|     // implementation "com.github.hypfvieh:dbus-java:3.2.0" | ||||
|     implementation project(':parser') | ||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2' | ||||
|     // implementation 'net.razorvine:ksim65:1.8' | ||||
|     // implementation "com.github.hypfvieh:dbus-java:3.2.4" | ||||
|  | ||||
|     testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" | ||||
|     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' | ||||
| @@ -45,7 +32,8 @@ dependencies { | ||||
|  | ||||
| compileKotlin { | ||||
|     kotlinOptions { | ||||
|         jvmTarget = "1.8" | ||||
|         jvmTarget = "11" | ||||
|         useIR = true | ||||
|         // verbose = true | ||||
|         // freeCompilerArgs += "-XXLanguage:+NewInference" | ||||
|     } | ||||
| @@ -53,7 +41,8 @@ compileKotlin { | ||||
|  | ||||
| compileTestKotlin { | ||||
|     kotlinOptions { | ||||
|         jvmTarget = "1.8" | ||||
|         jvmTarget = "11" | ||||
|         useIR = true | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -76,7 +65,8 @@ sourceSets { | ||||
| startScripts.enabled = true | ||||
|  | ||||
| application { | ||||
|     mainClassName = 'prog8.CompilerMainKt' | ||||
|     mainClass = 'prog8.CompilerMainKt' | ||||
|     mainClassName = 'prog8.CompilerMainKt'  // deprecated | ||||
|     applicationName = 'p8compile' | ||||
| } | ||||
|  | ||||
| @@ -106,7 +96,6 @@ test { | ||||
| } | ||||
|  | ||||
|  | ||||
| dokka { | ||||
|     outputFormat = 'html' | ||||
|     outputDirectory = "$buildDir/kdoc" | ||||
| task wrapper(type: Wrapper) { | ||||
|     gradleVersion = '6.7' | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <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"> | ||||
|     <exclude-output /> | ||||
|     <content url="file://$MODULE_DIR$"> | ||||
| @@ -8,12 +13,12 @@ | ||||
|       <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> | ||||
|       <excludeFolder url="file://$MODULE_DIR$/build" /> | ||||
|     </content> | ||||
|     <orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" /> | ||||
|     <orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|     <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="kotlinx-cli-jvm-0.1.0-dev-5" level="project" /> | ||||
|     <orderEntry type="library" name="antlr-runtime-4.8" level="project" /> | ||||
|     <orderEntry type="library" name="kotlinx-cli-jvm" level="project" /> | ||||
|     <orderEntry type="module" module-name="compilerAst" /> | ||||
|     <orderEntry type="library" name="Python 3.9 interpreter library" level="application" /> | ||||
|   </component> | ||||
| </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 | ||||
							
								
								
									
										680
									
								
								compiler/res/prog8lib/c64/floats.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										680
									
								
								compiler/res/prog8lib/c64/floats.asm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,680 @@ | ||||
| ; --- low level floating point assembly routines for the C64 | ||||
|  | ||||
| FL_ONE_const	.byte  129     			; 1.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 | ||||
|  | ||||
|  | ||||
| ub2float	.proc | ||||
| 		; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y | ||||
| 		;    clobbers A, Y | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		sta  P8ZP_SCRATCH_W2 | ||||
| 		sty  P8ZP_SCRATCH_W2+1 | ||||
| 		ldy  P8ZP_SCRATCH_B1 | ||||
| 		lda  #0 | ||||
| 		jsr  GIVAYF | ||||
| _fac_to_mem	ldx  P8ZP_SCRATCH_W2 | ||||
| 		ldy  P8ZP_SCRATCH_W2+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| b2float		.proc | ||||
| 		; -- convert byte in SCRATCH_ZPB1 to float at address A/Y | ||||
| 		;    clobbers A, Y | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		sta  P8ZP_SCRATCH_W2 | ||||
| 		sty  P8ZP_SCRATCH_W2+1 | ||||
| 		lda  P8ZP_SCRATCH_B1 | ||||
| 		jsr  FREADSA | ||||
| 		jmp  ub2float._fac_to_mem | ||||
| 		.pend | ||||
|  | ||||
| uw2float	.proc | ||||
| 		; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		sta  P8ZP_SCRATCH_W2 | ||||
| 		sty  P8ZP_SCRATCH_W2+1 | ||||
| 		lda  P8ZP_SCRATCH_W1 | ||||
| 		ldy  P8ZP_SCRATCH_W1+1 | ||||
| 		jsr  GIVUAYFAY | ||||
| 		jmp  ub2float._fac_to_mem | ||||
| 		.pend | ||||
|  | ||||
| w2float		.proc | ||||
| 		; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		sta  P8ZP_SCRATCH_W2 | ||||
| 		sty  P8ZP_SCRATCH_W2+1 | ||||
| 		ldy  P8ZP_SCRATCH_W1 | ||||
| 		lda  P8ZP_SCRATCH_W1+1 | ||||
| 		jsr  GIVAYF | ||||
| 		jmp  ub2float._fac_to_mem | ||||
| 		.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 | ||||
| 		; -- b2float operating on the stack | ||||
| 		inx | ||||
| 		lda  P8ESTACK_LO,x | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  FREADSA | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| stack_w2float	.proc | ||||
| 		; -- w2float operating on the stack | ||||
| 		inx | ||||
| 		ldy  P8ESTACK_LO,x | ||||
| 		lda  P8ESTACK_HI,x | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  GIVAYF | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| stack_ub2float	.proc | ||||
| 		; -- ub2float operating on the stack | ||||
| 		inx | ||||
| 		lda  P8ESTACK_LO,x | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		tay | ||||
| 		lda  #0 | ||||
| 		jsr  GIVAYF | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| stack_uw2float	.proc | ||||
| 		; -- uw2float operating on the stack | ||||
| 		inx | ||||
| 		lda  P8ESTACK_LO,x | ||||
| 		ldy  P8ESTACK_HI,x | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  GIVUAYFAY | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| stack_float2w	.proc               ; also used for float2b | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  AYINT | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		lda  $64 | ||||
| 		sta  P8ESTACK_HI,x | ||||
| 		lda  $65 | ||||
| 		sta  P8ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| stack_float2uw	.proc               ; also used for float2ub | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  GETADR | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		sta  P8ESTACK_HI,x | ||||
| 		tya | ||||
| 		sta  P8ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| push_float	.proc | ||||
| 		; ---- push mflpt5 in A/Y onto stack | ||||
| 		; (taking 3 stack positions = 6 bytes of which 1 is padding) | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #0 | ||||
| 		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  P8ESTACK_LO,x | ||||
| 		iny | ||||
| 		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  P8ESTACK_HI,x | ||||
| 		dex | ||||
| 		iny | ||||
| 		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  P8ESTACK_LO,x | ||||
| 		iny | ||||
| 		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  P8ESTACK_HI,x | ||||
| 		dex | ||||
| 		iny | ||||
| 		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  P8ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| pop_float	.proc | ||||
| 		; ---- pops mflpt5 from stack to memory A/Y | ||||
| 		; (frees 3 stack positions = 6 bytes of which 1 is padding) | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #4 | ||||
| 		inx | ||||
| 		lda  P8ESTACK_LO,x | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		dey | ||||
| 		inx | ||||
| 		lda  P8ESTACK_HI,x | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		dey | ||||
| 		lda  P8ESTACK_LO,x | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		dey | ||||
| 		inx | ||||
| 		lda  P8ESTACK_HI,x | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		dey | ||||
| 		lda  P8ESTACK_LO,x | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| pop_float_fac1	.proc | ||||
| 		; -- pops float from stack into FAC1 | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jmp  MOVFM | ||||
| 		.pend | ||||
|  | ||||
| copy_float	.proc | ||||
| 		; -- 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. | ||||
| 		sta  _target+1 | ||||
| 		sty  _target+2 | ||||
| 		ldy  #4 | ||||
| _loop		lda  (P8ZP_SCRATCH_W1),y | ||||
| _target		sta  $ffff,y			; modified | ||||
| 		dey | ||||
| 		bpl  _loop | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| inc_var_f	.proc | ||||
| 		; -- add 1 to float pointed to by A/Y | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  MOVFM | ||||
| 		lda  #<FL_ONE_const | ||||
| 		ldy  #>FL_ONE_const | ||||
| 		jsr  FADD | ||||
| 		ldx  P8ZP_SCRATCH_W1 | ||||
| 		ldy  P8ZP_SCRATCH_W1+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| dec_var_f	.proc | ||||
| 		; -- subtract 1 from float pointed to by A/Y | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		lda  #<FL_ONE_const | ||||
| 		ldy  #>FL_ONE_const | ||||
| 		jsr  MOVFM | ||||
| 		lda  P8ZP_SCRATCH_W1 | ||||
| 		ldy  P8ZP_SCRATCH_W1+1 | ||||
| 		jsr  FSUB | ||||
| 		ldx  P8ZP_SCRATCH_W1 | ||||
| 		ldy  P8ZP_SCRATCH_W1+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| pop_2_floats_f2_in_fac1	.proc | ||||
| 		; -- pop 2 floats from stack, load the second one in FAC1 as well | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jmp  MOVFM | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
| push_fac1	.proc | ||||
| 		; -- push the float in FAC1 onto the stack | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| _internal	ldx  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVMF | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		jmp  push_float | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| pow_f		.proc | ||||
| 		; -- push f1 ** f2 on stack | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  CONUPK		; fac2 = float1 | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  FPWR | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| div_f		.proc | ||||
| 		; -- push f1/f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FDIV | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| add_f		.proc | ||||
| 		; -- push f1+f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FADD | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| sub_f		.proc | ||||
| 		; -- push f1-f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FSUB | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| mul_f		.proc | ||||
| 		; -- push f1*f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FMULT | ||||
| 		jmp  push_fac1._internal | ||||
| 		.pend | ||||
|  | ||||
| neg_f		.proc | ||||
| 		; -- toggle the sign bit on the stack | ||||
| 		lda  P8ESTACK_HI+3,x | ||||
| 		eor  #$80 | ||||
| 		sta  P8ESTACK_HI+3,x | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| var_fac1_less_f	.proc | ||||
| 		; -- is the float in FAC1 < the variable AY? | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  FCOMP | ||||
| 		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 | ||||
| 		.pend | ||||
|  | ||||
| equal_f		.proc | ||||
| 		; -- are the two mflpt5 numbers on the stack identical? | ||||
| 		inx | ||||
| 		inx | ||||
| 		inx | ||||
| 		inx | ||||
| 		lda  P8ESTACK_LO-3,x | ||||
| 		cmp  P8ESTACK_LO,x | ||||
| 		bne  _equals_false | ||||
| 		lda  P8ESTACK_LO-2,x | ||||
| 		cmp  P8ESTACK_LO+1,x | ||||
| 		bne  _equals_false | ||||
| 		lda  P8ESTACK_LO-1,x | ||||
| 		cmp  P8ESTACK_LO+2,x | ||||
| 		bne  _equals_false | ||||
| 		lda  P8ESTACK_HI-2,x | ||||
| 		cmp  P8ESTACK_HI+1,x | ||||
| 		bne  _equals_false | ||||
| 		lda  P8ESTACK_HI-1,x | ||||
| 		cmp  P8ESTACK_HI+2,x | ||||
| 		bne  _equals_false | ||||
| _equals_true	lda  #1 | ||||
| _equals_store	inx | ||||
| 		sta  P8ESTACK_LO+1,x | ||||
| 		rts | ||||
| _equals_false	lda  #0 | ||||
| 		beq  _equals_store | ||||
| 		.pend | ||||
|  | ||||
| notequal_f	.proc | ||||
| 		; -- are the two mflpt5 numbers on the stack different? | ||||
| 		jsr  equal_f | ||||
| 		eor  #1		; invert the result | ||||
| 		sta  P8ESTACK_LO+1,x | ||||
| 		rts | ||||
| 		.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 | ||||
| 		; -- is f1 < f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #255 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| lesseq_f	.proc | ||||
| 		; -- is f1 <= f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #255 | ||||
| 		beq  compare_floats._return_true | ||||
| 		cmp  #0 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| greater_f	.proc | ||||
| 		; -- is f1 > f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #1 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| greatereq_f	.proc | ||||
| 		; -- is f1 >= f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #1 | ||||
| 		beq  compare_floats._return_true | ||||
| 		cmp  #0 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| compare_floats	.proc | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVFM		; fac1 = flt1 | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  FCOMP		; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2) | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| _return_false	lda  #0 | ||||
| _return_result  sta  P8ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| _return_true	lda  #1 | ||||
| 		bne  _return_result | ||||
| 		.pend | ||||
|  | ||||
| set_array_float_from_fac1	.proc | ||||
| 		; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1) | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  P8ZP_SCRATCH_B1 | ||||
| 		ldy  P8ZP_SCRATCH_W1+1 | ||||
| 		clc | ||||
| 		adc  P8ZP_SCRATCH_W1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		stx  floats_store_reg | ||||
| 		tax | ||||
| 		jsr  MOVMF | ||||
| 		ldx  floats_store_reg | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| set_0_array_float	.proc | ||||
| 		; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1) | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  P8ZP_SCRATCH_B1 | ||||
| 		tay | ||||
| 		lda  #0 | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		iny | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		iny | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		iny | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		iny | ||||
| 		sta  (P8ZP_SCRATCH_W1),y | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| set_array_float		.proc | ||||
| 		; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2) | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  P8ZP_SCRATCH_B1 | ||||
| 		adc  P8ZP_SCRATCH_W2 | ||||
| 		ldy  P8ZP_SCRATCH_W2+1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		jmp  copy_float | ||||
| 			; -- 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. | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| @@ -4,10 +4,10 @@ | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
| 
 | ||||
| %target c64 | ||||
| %option enable_floats | ||||
| 
 | ||||
| 
 | ||||
| c64flt { | ||||
| floats { | ||||
| 	; ---- this block contains C-64 floating point related functions ---- | ||||
| 
 | ||||
|         const float  PI     = 3.141592653589793 | ||||
| @@ -19,29 +19,10 @@ c64flt { | ||||
| 		; 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. | ||||
| 
 | ||||
| 		; 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. | ||||
|         float   FL_ZERO     = 0.0    ; 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: for subtraction and division, the left operand is in fac2, the right operand in fac1. | ||||
| 
 | ||||
| ; checked functions below: | ||||
| romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y)        ; load mflpt value from memory  in A/Y into fac1 | ||||
| romsub $bba6 = FREADMEM() clobbers(A,Y)                     ; load mflpt value from memory  in $22/$23 into fac1 | ||||
| romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y)       ; load mflpt value from memory  in A/Y into fac2 | ||||
| @@ -52,22 +33,22 @@ romsub $bc0f = MOVEF() clobbers(A,X)                        ; copy fac1 to fac2 | ||||
| romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y)        ; store fac1 to memory  X/Y as 5-byte mflpt | ||||
| 
 | ||||
| ; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY) | ||||
| ; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order) | ||||
| ; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order) | ||||
| romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A       ; note: calls AYINT. | ||||
| 
 | ||||
| ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15) | ||||
| ; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order) | ||||
| ; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order) | ||||
| romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A | ||||
| 
 | ||||
| romsub $bc9b = QINT() clobbers(A,X,Y)           ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST. | ||||
| romsub $b1bf = AYINT() clobbers(A,X,Y)          ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY) | ||||
| 
 | ||||
| ; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1 | ||||
| ; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order) | ||||
| ; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1 | ||||
| ; there is also c64flt.FREADS32  that reads from 98-101 ($62-$65) MSB FIRST | ||||
| ; there is also c64flt.FREADUS32  that reads from 98-101 ($62-$65) MSB FIRST | ||||
| ; there is also c64flt.FREADS24AXY  that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes) | ||||
| ; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order) | ||||
| ; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1 | ||||
| ; there is also floats.FREADS32  that reads from 98-101 ($62-$65) MSB FIRST | ||||
| ; there is also floats.FREADUS32  that reads from 98-101 ($62-$65) MSB FIRST | ||||
| ; there is also floats.FREADS24AXY  that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes) | ||||
| romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y) | ||||
| 
 | ||||
| romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y)     ; 8 bit unsigned Y -> float in fac1 | ||||
| @@ -91,6 +72,7 @@ romsub $bb12 = FDIVT() clobbers(A,X,Y)                      ; fac1 = fac2/fac1 | ||||
| romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 = mflpt in A/Y / fac1  (remainder in fac2) | ||||
| romsub $bf7b = FPWRT() clobbers(A,X,Y)                      ; fac1 = fac2 ** fac1 | ||||
| romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 = fac2 ** mflpt from A/Y | ||||
| romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y)     ; fac1 += signed byte in A | ||||
| 
 | ||||
| romsub $aed4 = NOTOP() clobbers(A,X,Y)                      ; fac1 = NOT(fac1) | ||||
| romsub $bccc = INT() clobbers(A,X,Y)                        ; INT() truncates, use FADDH first to round instead of trunc | ||||
| @@ -101,7 +83,7 @@ romsub $bc58 = ABS()                                        ; fac1 = ABS(fac1) | ||||
| romsub $bf71 = SQR() clobbers(A,X,Y)                        ; fac1 = SQRT(fac1) | ||||
| romsub $bf74 = SQRA() clobbers(A,X,Y)                       ; fac1 = SQRT(fac2) | ||||
| 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 $e264 = COS() clobbers(A,X,Y)                        ; fac1 = COS(fac1) | ||||
| romsub $e26b = SIN() clobbers(A,X,Y)                        ; fac1 = SIN(fac1) | ||||
| @@ -163,9 +145,9 @@ asmsub  GIVUAYFAY  (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 | ||||
| 	%asm {{ | ||||
| 		sta  c64.SCRATCH_ZPREG | ||||
| 		sta  P8ZP_SCRATCH_REG | ||||
| 		tya | ||||
| 		ldy  c64.SCRATCH_ZPREG | ||||
| 		ldy  P8ZP_SCRATCH_REG | ||||
| 		jmp  GIVAYF		; this uses the inverse order, Y/A | ||||
| 	}} | ||||
| } | ||||
| @@ -174,9 +156,9 @@ asmsub  FTOSWRDAY  () clobbers(X) -> uword @ AY  { | ||||
| 	; ---- fac1 to signed word in A/Y | ||||
| 	%asm {{ | ||||
| 		jsr  FTOSWORDYA	; note the inverse Y/A order | ||||
| 		sta  c64.SCRATCH_ZPREG | ||||
| 		sta  P8ZP_SCRATCH_REG | ||||
| 		tya | ||||
| 		ldy  c64.SCRATCH_ZPREG | ||||
| 		ldy  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
| @@ -185,41 +167,35 @@ asmsub  GETADRAY  () clobbers(X) -> uword @ AY  { | ||||
| 	; ---- fac1 to unsigned word in A/Y | ||||
| 	%asm {{ | ||||
| 		jsr  GETADR		; this uses the inverse order, Y/A | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		tya | ||||
| 		ldy  c64.SCRATCH_ZPB1 | ||||
| 		ldy  P8ZP_SCRATCH_B1 | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
| 
 | ||||
| sub  print_f  (float value) { | ||||
| 	; ---- prints the floating point value (without a newline) using basic rom routines. | ||||
| 	; ---- prints the floating point value (without a newline). | ||||
| 	%asm {{ | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		stx  floats_store_reg | ||||
| 		lda  #<value | ||||
| 		ldy  #>value | ||||
| 		jsr  MOVFM		; load float into fac1 | ||||
| 		jsr  FOUT		; fac1 to string in A/Y | ||||
| 		jsr  c64.STROUT			; print string in A/Y | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #0 | ||||
| -		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		beq  + | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		ldx  floats_store_reg | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
| 
 | ||||
| sub  print_fln  (float value) { | ||||
| 	; ---- prints the floating point value (with a newline at the end) using basic rom routines | ||||
| 	%asm {{ | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<value | ||||
| 		ldy  #>value | ||||
| 		jsr  MOVFM		; load float into fac1 | ||||
| 		jsr  FPRINTLN		; print fac1 with newline | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		rts | ||||
| 	}} | ||||
| %asminclude "library:c64/floats.asm" | ||||
| %asminclude "library:c64/floats_funcs.asm" | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| %asminclude "library:c64floats.asm", "" | ||||
| 
 | ||||
| }  ; ------ end of block c64flt | ||||
							
								
								
									
										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 | ||||
							
								
								
									
										370
									
								
								compiler/res/prog8lib/c64/graphics.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								compiler/res/prog8lib/c64/graphics.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,370 @@ | ||||
| %target c64 | ||||
| %import textio | ||||
|  | ||||
| ; bitmap pixel graphics module for the C64 | ||||
| ; only black/white monochrome 320x200 for now | ||||
| ; assumes bitmap screen memory is $2000-$3fff | ||||
|  | ||||
| graphics { | ||||
|     const uword BITMAP_ADDRESS = $2000 | ||||
|     const uword WIDTH = 320 | ||||
|     const ubyte HEIGHT = 200 | ||||
|  | ||||
|     sub enable_bitmap_mode() { | ||||
|         ; enable bitmap screen, erase it and set colors to black/white. | ||||
|         c64.SCROLY = %00111011 | ||||
|         c64.SCROLX = %00001000 | ||||
|         c64.VMCSB = (c64.VMCSB & %11110000) | %00001000   ; $2000-$3fff | ||||
|         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) { | ||||
|         sys.memset(BITMAP_ADDRESS, 320*200/8, 0) | ||||
|         txt.fill_screen(pixelcolor << 4 | bgcolor, 0) | ||||
|     } | ||||
|  | ||||
|     sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { | ||||
|         ; 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 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 | ||||
|         ubyte positive_ix = true | ||||
|         if dx < 0 { | ||||
|             dx = -dx | ||||
|             positive_ix = false | ||||
|         } | ||||
|         word @zp dx2 = dx*2 | ||||
|         word @zp dy2 = dy*2 | ||||
|         internal_plotx = x1 | ||||
|  | ||||
|         if dx >= dy { | ||||
|             if positive_ix { | ||||
|                 repeat { | ||||
|                     internal_plot(y1) | ||||
|                     if internal_plotx==x2 | ||||
|                         return | ||||
|                     internal_plotx++ | ||||
|                     d += dy2 | ||||
|                     if d > dx { | ||||
|                         y1++ | ||||
|                         d -= dx2 | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 repeat { | ||||
|                     internal_plot(y1) | ||||
|                     if internal_plotx==x2 | ||||
|                         return | ||||
|                     internal_plotx-- | ||||
|                     d += dy2 | ||||
|                     if d > dx { | ||||
|                         y1++ | ||||
|                         d -= dx2 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             if positive_ix { | ||||
|                 repeat { | ||||
|                     internal_plot(y1) | ||||
|                     if y1 == y2 | ||||
|                         return | ||||
|                     y1++ | ||||
|                     d += dx2 | ||||
|                     if d > dy { | ||||
|                         internal_plotx++ | ||||
|                         d -= dy2 | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 repeat { | ||||
|                     internal_plot(y1) | ||||
|                     if y1 == y2 | ||||
|                         return | ||||
|                     y1++ | ||||
|                     d += dx2 | ||||
|                     if d > dy { | ||||
|                         internal_plotx-- | ||||
|                         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) { | ||||
|         ; Midpoint algorithm | ||||
|         if radius==0 | ||||
|             return | ||||
|         ubyte @zp ploty | ||||
|         ubyte @zp yy = 0 | ||||
|         word @zp decisionOver2 = (1 as word)-radius | ||||
|  | ||||
|         while radius>=yy { | ||||
|             internal_plotx = xcenter + radius | ||||
|             ploty = ycenter + yy | ||||
|             internal_plot(ploty) | ||||
|             internal_plotx = xcenter - radius | ||||
|             internal_plot(ploty) | ||||
|             internal_plotx = xcenter + radius | ||||
|             ploty = ycenter - yy | ||||
|             internal_plot(ploty) | ||||
|             internal_plotx = xcenter - radius | ||||
|             internal_plot(ploty) | ||||
|             internal_plotx = xcenter + yy | ||||
|             ploty = ycenter + radius | ||||
|             internal_plot(ploty) | ||||
|             internal_plotx = xcenter - yy | ||||
|             internal_plot(ploty) | ||||
|             internal_plotx = xcenter + yy | ||||
|             ploty = ycenter - radius | ||||
|             internal_plot(ploty) | ||||
|             internal_plotx = xcenter - yy | ||||
|             internal_plot(ploty) | ||||
|             yy++ | ||||
|             if decisionOver2<=0 | ||||
|                 decisionOver2 += (yy as word)*2+1 | ||||
|             else { | ||||
|                 radius-- | ||||
|                 decisionOver2 += (yy as word -radius)*2+1 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     sub disc(uword xcenter, ubyte ycenter, ubyte radius) { | ||||
|         ; Midpoint algorithm, filled | ||||
|         if radius==0 | ||||
|             return | ||||
|         ubyte @zp yy = 0 | ||||
|         word decisionOver2 = (1 as word)-radius | ||||
|  | ||||
|         while radius>=yy { | ||||
|             horizontal_line(xcenter-radius, ycenter+yy, radius*2+1) | ||||
|             horizontal_line(xcenter-radius, ycenter-yy, radius*2+1) | ||||
|             horizontal_line(xcenter-yy, ycenter+radius, yy*2+1) | ||||
|             horizontal_line(xcenter-yy, ycenter-radius, yy*2+1) | ||||
|             yy++ | ||||
|             if decisionOver2<=0 | ||||
|                 decisionOver2 += (yy as word)*2+1 | ||||
|             else { | ||||
|                 radius-- | ||||
|                 decisionOver2 += (yy as word -radius)*2+1 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| ; here is the non-asm code for the plot routine below: | ||||
| ;    sub plot_nonasm(uword px, ubyte py) { | ||||
| ;        ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1] | ||||
| ;        uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000) | ||||
| ;        @(addr) |= ormask[lsb(px) & 7] | ||||
| ;    } | ||||
|  | ||||
|     inline asmsub  plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) { | ||||
|         %asm {{ | ||||
|             stx  graphics.internal_plotx | ||||
|             sty  graphics.internal_plotx+1 | ||||
|             jsr  graphics.internal_plot | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     ; for efficiency of internal algorithms here is the internal plot routine | ||||
|     ; that takes the plotx coordinate in a separate variable instead of the XY register pair: | ||||
|  | ||||
|     uword internal_plotx     ; 0..319        ; separate 'parameter' for internal_plot() | ||||
|  | ||||
|     asmsub  internal_plot(ubyte ploty @A) clobbers (A, X, Y) {      ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register | ||||
|         %asm {{ | ||||
|         tay | ||||
|         lda  internal_plotx+1 | ||||
|         sta  P8ZP_SCRATCH_W2+1 | ||||
|         lsr  a            ; 0 | ||||
|         sta  P8ZP_SCRATCH_W2 | ||||
|         lda  internal_plotx | ||||
|         pha | ||||
|         and  #7 | ||||
|         tax | ||||
|  | ||||
|         lda  _y_lookup_lo,y | ||||
|         clc | ||||
|         adc  P8ZP_SCRATCH_W2 | ||||
|         sta  P8ZP_SCRATCH_W2 | ||||
|         lda  _y_lookup_hi,y | ||||
|         adc  P8ZP_SCRATCH_W2+1 | ||||
|         sta  P8ZP_SCRATCH_W2+1 | ||||
|  | ||||
|         pla     ; internal_plotx | ||||
|         and  #%11111000 | ||||
|         tay | ||||
|         lda  (P8ZP_SCRATCH_W2),y | ||||
|         ora  _ormask,x | ||||
|         sta  (P8ZP_SCRATCH_W2),y | ||||
|         rts | ||||
|  | ||||
| _ormask     .byte 128, 64, 32, 16, 8, 4, 2, 1 | ||||
|  | ||||
| ; note: this can be even faster if we also have a 256 byte x-lookup table, but hey. | ||||
| ; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics | ||||
| ; the y lookup tables encodes this formula:  BITMAP_ADDRESS + 320*(py>>3) + (py & 7)    (y from 0..199) | ||||
| ; We use the 64tass syntax for range expressions to calculate this table on assembly time. | ||||
|  | ||||
| _plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7) | ||||
|  | ||||
| _y_lookup_lo    .byte  <_plot_y_values | ||||
| _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 | ||||
|         }} | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
							
								
								
									
										739
									
								
								compiler/res/prog8lib/c64/syslib.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										739
									
								
								compiler/res/prog8lib/c64/syslib.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,739 @@ | ||||
| ; Prog8 definitions for the Commodore-64 | ||||
| ; Including memory registers, I/O registers, Basic and Kernal subroutines. | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %target c64 | ||||
|  | ||||
| c64 { | ||||
|         &ubyte  TIME_HI         = $a0       ; software jiffy clock, hi byte | ||||
|         &ubyte  TIME_MID        = $a1       ;  .. mid byte | ||||
|         &ubyte  TIME_LO         = $a2       ;    .. lo byte. Updated by IRQ every 1/60 sec | ||||
|         &ubyte  STATUS          = $90       ; kernal status variable for I/O | ||||
|         &ubyte  STKEY           = $91       ; various keyboard statuses (updated by IRQ) | ||||
|         &ubyte  SFDX            = $cb       ; current key pressed (matrix value) (updated by IRQ) | ||||
|  | ||||
|         &ubyte  COLOR           = $0286     ; cursor color | ||||
|         &ubyte  HIBASE          = $0288     ; screen base address / 256 (hi-byte of screen memory address) | ||||
|         &uword  CINV            = $0314     ; IRQ vector | ||||
|         &uword  NMI_VEC         = $FFFA     ; 6502 nmi vector, determined by the kernal if banked in | ||||
|         &uword  RESET_VEC       = $FFFC     ; 6502 reset vector, determined by the kernal if banked in | ||||
|         &uword  IRQ_VEC         = $FFFE     ; 6502 interrupt vector, determined by the kernal if banked in | ||||
|  | ||||
|         ; the default addresses for the character screen chars and colors | ||||
|         const  uword  Screen    = $0400     ; to have this as an array[40*25] the compiler would have to support array size > 255 | ||||
|         const  uword  Colors    = $d800     ; to have this as an array[40*25] the compiler would have to support array size > 255 | ||||
|  | ||||
|         ; the default locations of the 8 sprite pointers (store address of sprite / 64) | ||||
|         &ubyte  SPRPTR0         = 2040 | ||||
|         &ubyte  SPRPTR1         = 2041 | ||||
|         &ubyte  SPRPTR2         = 2042 | ||||
|         &ubyte  SPRPTR3         = 2043 | ||||
|         &ubyte  SPRPTR4         = 2044 | ||||
|         &ubyte  SPRPTR5         = 2045 | ||||
|         &ubyte  SPRPTR6         = 2046 | ||||
|         &ubyte  SPRPTR7         = 2047 | ||||
|         &ubyte[8]  SPRPTR       = 2040      ; the 8 sprite pointers as an array. | ||||
|  | ||||
|  | ||||
| ; ---- VIC-II 6567/6569/856x registers ---- | ||||
|  | ||||
|         &ubyte  SP0X            = $d000 | ||||
|         &ubyte  SP0Y            = $d001 | ||||
|         &ubyte  SP1X            = $d002 | ||||
|         &ubyte  SP1Y            = $d003 | ||||
|         &ubyte  SP2X            = $d004 | ||||
|         &ubyte  SP2Y            = $d005 | ||||
|         &ubyte  SP3X            = $d006 | ||||
|         &ubyte  SP3Y            = $d007 | ||||
|         &ubyte  SP4X            = $d008 | ||||
|         &ubyte  SP4Y            = $d009 | ||||
|         &ubyte  SP5X            = $d00a | ||||
|         &ubyte  SP5Y            = $d00b | ||||
|         &ubyte  SP6X            = $d00c | ||||
|         &ubyte  SP6Y            = $d00d | ||||
|         &ubyte  SP7X            = $d00e | ||||
|         &ubyte  SP7Y            = $d00f | ||||
|         &ubyte[16]  SPXY        = $d000        ; the 8 sprite X and Y registers as an array. | ||||
|         &uword[8]  SPXYW        = $d000        ; the 8 sprite X and Y registers as a combined xy word array. | ||||
|  | ||||
|         &ubyte  MSIGX           = $d010 | ||||
|         &ubyte  SCROLY          = $d011 | ||||
|         &ubyte  RASTER          = $d012 | ||||
|         &ubyte  LPENX           = $d013 | ||||
|         &ubyte  LPENY           = $d014 | ||||
|         &ubyte  SPENA           = $d015 | ||||
|         &ubyte  SCROLX          = $d016 | ||||
|         &ubyte  YXPAND          = $d017 | ||||
|         &ubyte  VMCSB           = $d018 | ||||
|         &ubyte  VICIRQ          = $d019 | ||||
|         &ubyte  IREQMASK        = $d01a | ||||
|         &ubyte  SPBGPR          = $d01b | ||||
|         &ubyte  SPMC            = $d01c | ||||
|         &ubyte  XXPAND          = $d01d | ||||
|         &ubyte  SPSPCL          = $d01e | ||||
|         &ubyte  SPBGCL          = $d01f | ||||
|  | ||||
|         &ubyte  EXTCOL          = $d020        ; border color | ||||
|         &ubyte  BGCOL0          = $d021        ; screen color | ||||
|         &ubyte  BGCOL1          = $d022 | ||||
|         &ubyte  BGCOL2          = $d023 | ||||
|         &ubyte  BGCOL4          = $d024 | ||||
|         &ubyte  SPMC0           = $d025 | ||||
|         &ubyte  SPMC1           = $d026 | ||||
|         &ubyte  SP0COL          = $d027 | ||||
|         &ubyte  SP1COL          = $d028 | ||||
|         &ubyte  SP2COL          = $d029 | ||||
|         &ubyte  SP3COL          = $d02a | ||||
|         &ubyte  SP4COL          = $d02b | ||||
|         &ubyte  SP5COL          = $d02c | ||||
|         &ubyte  SP6COL          = $d02d | ||||
|         &ubyte  SP7COL          = $d02e | ||||
|         &ubyte[8]  SPCOL        = $d027 | ||||
|  | ||||
|  | ||||
| ; ---- end of VIC-II registers ---- | ||||
|  | ||||
| ; ---- CIA 6526 1 & 2 registers ---- | ||||
|  | ||||
|         &ubyte  CIA1PRA         = $DC00        ; CIA 1 DRA, keyboard column drive (and joystick control port #2) | ||||
|         &ubyte  CIA1PRB         = $DC01        ; CIA 1 DRB, keyboard row port (and joystick control port #1) | ||||
|         &ubyte  CIA1DDRA        = $DC02        ; CIA 1 DDRA, keyboard column | ||||
|         &ubyte  CIA1DDRB        = $DC03        ; CIA 1 DDRB, keyboard row | ||||
|         &ubyte  CIA1TAL         = $DC04        ; CIA 1 timer A low byte | ||||
|         &ubyte  CIA1TAH         = $DC05        ; CIA 1 timer A high byte | ||||
|         &ubyte  CIA1TBL         = $DC06        ; CIA 1 timer B low byte | ||||
|         &ubyte  CIA1TBH         = $DC07        ; CIA 1 timer B high byte | ||||
|         &ubyte  CIA1TOD10       = $DC08        ; time of day, 1/10 sec. | ||||
|         &ubyte  CIA1TODSEC      = $DC09        ; time of day, seconds | ||||
|         &ubyte  CIA1TODMMIN     = $DC0A        ; time of day, minutes | ||||
|         &ubyte  CIA1TODHR       = $DC0B        ; time of day, hours | ||||
|         &ubyte  CIA1SDR         = $DC0C        ; Serial Data Register | ||||
|         &ubyte  CIA1ICR         = $DC0D | ||||
|         &ubyte  CIA1CRA         = $DC0E | ||||
|         &ubyte  CIA1CRB         = $DC0F | ||||
|  | ||||
|         &ubyte  CIA2PRA         = $DD00        ; CIA 2 DRA, serial port and video address | ||||
|         &ubyte  CIA2PRB         = $DD01        ; CIA 2 DRB, RS232 port / USERPORT | ||||
|         &ubyte  CIA2DDRA        = $DD02        ; CIA 2 DDRA, serial port and video address | ||||
|         &ubyte  CIA2DDRB        = $DD03        ; CIA 2 DDRB, RS232 port / USERPORT | ||||
|         &ubyte  CIA2TAL         = $DD04        ; CIA 2 timer A low byte | ||||
|         &ubyte  CIA2TAH         = $DD05        ; CIA 2 timer A high byte | ||||
|         &ubyte  CIA2TBL         = $DD06        ; CIA 2 timer B low byte | ||||
|         &ubyte  CIA2TBH         = $DD07        ; CIA 2 timer B high byte | ||||
|         &ubyte  CIA2TOD10       = $DD08        ; time of day, 1/10 sec. | ||||
|         &ubyte  CIA2TODSEC      = $DD09        ; time of day, seconds | ||||
|         &ubyte  CIA2TODMIN      = $DD0A        ; time of day, minutes | ||||
|         &ubyte  CIA2TODHR       = $DD0B        ; time of day, hours | ||||
|         &ubyte  CIA2SDR         = $DD0C        ; Serial Data Register | ||||
|         &ubyte  CIA2ICR         = $DD0D | ||||
|         &ubyte  CIA2CRA         = $DD0E | ||||
|         &ubyte  CIA2CRB         = $DD0F | ||||
|  | ||||
| ; ---- end of CIA registers ---- | ||||
|  | ||||
| ; ---- SID 6581/8580 registers ---- | ||||
|  | ||||
|         &ubyte  FREQLO1         = $D400        ; channel 1 freq lo | ||||
|         &ubyte  FREQHI1         = $D401        ; channel 1 freq hi | ||||
|         &uword  FREQ1           = $D400        ; channel 1 freq (word) | ||||
|         &ubyte  PWLO1           = $D402        ; channel 1 pulse width lo (7-0) | ||||
|         &ubyte  PWHI1           = $D403        ; channel 1 pulse width hi (11-8) | ||||
|         &uword  PW1             = $D402        ; channel 1 pulse width (word) | ||||
|         &ubyte  CR1             = $D404        ; channel 1 voice control register | ||||
|         &ubyte  AD1             = $D405        ; channel 1 attack & decay | ||||
|         &ubyte  SR1             = $D406        ; channel 1 sustain & release | ||||
|         &ubyte  FREQLO2         = $D407        ; channel 2 freq lo | ||||
|         &ubyte  FREQHI2         = $D408        ; channel 2 freq hi | ||||
|         &uword  FREQ2           = $D407        ; channel 2 freq (word) | ||||
|         &ubyte  PWLO2           = $D409        ; channel 2 pulse width lo (7-0) | ||||
|         &ubyte  PWHI2           = $D40A        ; channel 2 pulse width hi (11-8) | ||||
|         &uword  PW2             = $D409        ; channel 2 pulse width (word) | ||||
|         &ubyte  CR2             = $D40B        ; channel 2 voice control register | ||||
|         &ubyte  AD2             = $D40C        ; channel 2 attack & decay | ||||
|         &ubyte  SR2             = $D40D        ; channel 2 sustain & release | ||||
|         &ubyte  FREQLO3         = $D40E        ; channel 3 freq lo | ||||
|         &ubyte  FREQHI3         = $D40F        ; channel 3 freq hi | ||||
|         &uword  FREQ3           = $D40E        ; channel 3 freq (word) | ||||
|         &ubyte  PWLO3           = $D410        ; channel 3 pulse width lo (7-0) | ||||
|         &ubyte  PWHI3           = $D411        ; channel 3 pulse width hi (11-8) | ||||
|         &uword  PW3             = $D410        ; channel 3 pulse width (word) | ||||
|         &ubyte  CR3             = $D412        ; channel 3 voice control register | ||||
|         &ubyte  AD3             = $D413        ; channel 3 attack & decay | ||||
|         &ubyte  SR3             = $D414        ; channel 3 sustain & release | ||||
|         &ubyte  FCLO            = $D415        ; filter cutoff lo (2-0) | ||||
|         &ubyte  FCHI            = $D416        ; filter cutoff hi (10-3) | ||||
|         &uword  FC              = $D415        ; filter cutoff (word) | ||||
|         &ubyte  RESFILT         = $D417        ; filter resonance and routing | ||||
|         &ubyte  MVOL            = $D418        ; filter mode and main volume control | ||||
|         &ubyte  POTX            = $D419        ; potentiometer X | ||||
|         &ubyte  POTY            = $D41A        ; potentiometer Y | ||||
|         &ubyte  OSC3            = $D41B        ; channel 3 oscillator value read | ||||
|         &ubyte  ENV3            = $D41C        ; channel 3 envelope value read | ||||
|  | ||||
| ; ---- end of SID registers ---- | ||||
|  | ||||
|  | ||||
| ; ---- C64 ROM kernal routines ---- | ||||
|  | ||||
| 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 $E566 = HOMECRSR() clobbers(A,X,Y)                       ; cursor to top left of screen | ||||
| romsub $EA31 = IRQDFRT() clobbers(A,X,Y)                        ; default IRQ routine | ||||
| romsub $EA81 = IRQDFEND() clobbers(A,X,Y)                       ; default IRQ end/cleanup | ||||
| 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 $FF87 = RAMTAS() clobbers(A,X,Y)                         ; initialize RAM, tape buffer, screen | ||||
| romsub $FF8A = RESTOR() clobbers(A,X,Y)                         ; restore default I/O vectors | ||||
| romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y)     ; read/set I/O vector table | ||||
| 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 $FF96 = TKSA(ubyte address @ A) clobbers(A)              ; (alias: TALKSA) send secondary address after TALK | ||||
| romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY     ; read/set top 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 $FFA2 = SETTMO(ubyte timeout @ A)                        ; set time-out flag for IEEE bus | ||||
| romsub $FFA5 = ACPTR() -> ubyte @ A                             ; (alias: IECIN) input byte from serial bus | ||||
| romsub $FFA8 = CIOUT(ubyte databyte @ A)                        ; (alias: IECOUT) output byte to serial bus | ||||
| romsub $FFAB = UNTLK() clobbers(A)                              ; command serial bus device to UNTALK | ||||
| romsub $FFAE = UNLSN() clobbers(A)                              ; command serial bus device to UNLISTEN | ||||
| 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 $FFB7 = READST() -> ubyte @ A                            ; read I/O status word | ||||
| 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 $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 $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 $FFCC = CLRCHN() clobbers(A,X)                           ; (via 802 ($322)) restore default devices | ||||
| 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 $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 $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 (A=lo,X=mid,Y=high) | ||||
| 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 @Pc, ubyte @ A    ; (via 810 ($32A)) get a character | ||||
| romsub $FFE7 = CLALL() clobbers(A,X)                            ; (via 812 ($32C)) close all files | ||||
| 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 $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 | ||||
|  | ||||
| ; ---- 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: ---- | ||||
|  | ||||
| asmsub  init_system()  { | ||||
|     ; Initializes the machine to a sane starting state. | ||||
|     ; Called automatically by the loader program logic. | ||||
|     ; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in, | ||||
|     ; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set. | ||||
|     ; Also a different color scheme is chosen to identify ourselves a little. | ||||
|     ; Uppercase charset is activated, and all three registers set to 0, status flags cleared. | ||||
|     %asm {{ | ||||
|         sei | ||||
|         cld | ||||
|         lda  #%00101111 | ||||
|         sta  $00 | ||||
|         lda  #%00100111 | ||||
|         sta  $01 | ||||
|         jsr  c64.IOINIT | ||||
|         jsr  c64.RESTOR | ||||
|         jsr  c64.CINT | ||||
|         lda  #6 | ||||
|         sta  c64.EXTCOL | ||||
|         lda  #7 | ||||
|         sta  c64.COLOR | ||||
|         lda  #0 | ||||
|         sta  c64.BGCOL0 | ||||
|         jsr  disable_runstop_and_charsetswitch | ||||
|         clc | ||||
|         clv | ||||
|         cli | ||||
|         rts | ||||
|     }} | ||||
| } | ||||
|  | ||||
| asmsub  init_system_phase2()  { | ||||
|     %asm {{ | ||||
|         rts     ; no phase 2 steps on the C64 | ||||
|     }} | ||||
| } | ||||
|  | ||||
| asmsub  disable_runstop_and_charsetswitch() clobbers(A) { | ||||
|     %asm {{ | ||||
|         lda  #$80 | ||||
|         sta  657    ; disable charset switching | ||||
|         lda  #239 | ||||
|         sta  808    ; disable run/stop key | ||||
|         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  c64.CINV | ||||
| 		lda  #>_irq_handler | ||||
| 		sta  c64.CINV+1 | ||||
| 		cli | ||||
| 		rts | ||||
| _irq_handler    jsr  _irq_handler_init | ||||
| _modified	jsr  $ffff                      ; modified | ||||
| 		jsr  _irq_handler_end | ||||
| 		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 | ||||
| 		; 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  #<c64.IRQDFRT | ||||
| 		sta  c64.CINV | ||||
| 		lda  #>c64.IRQDFRT | ||||
| 		sta  c64.CINV+1 | ||||
| 		lda  #0 | ||||
| 		sta  c64.IREQMASK	; disable raster irq | ||||
| 		lda  #%10000001 | ||||
| 		sta  c64.CIA1ICR	; restore CIA1 irq | ||||
| 		cli | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) { | ||||
| 	%asm {{ | ||||
| 	        sta  _modified+1 | ||||
| 	        sty  _modified+2 | ||||
| 	        lda  #0 | ||||
| 	        adc  #0 | ||||
| 	        sta  set_irq._use_kernal | ||||
| 		lda  cx16.r0 | ||||
| 		ldy  cx16.r0+1 | ||||
| 		sei | ||||
| 		jsr  _setup_raster_irq | ||||
| 		lda  #<_raster_irq_handler | ||||
| 		sta  c64.CINV | ||||
| 		lda  #>_raster_irq_handler | ||||
| 		sta  c64.CINV+1 | ||||
| 		cli | ||||
| 		rts | ||||
|  | ||||
| _raster_irq_handler | ||||
| 		jsr  set_irq._irq_handler_init | ||||
| _modified	jsr  $ffff              ; modified | ||||
| 		jsr  set_irq._irq_handler_end | ||||
|                 lda  #$ff | ||||
|                 sta  c64.VICIRQ			; acknowledge raster irq | ||||
| 		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 | ||||
| 		pha | ||||
| 		lda  #%01111111 | ||||
| 		sta  c64.CIA1ICR    ; "switch off" interrupts signals from cia-1 | ||||
| 		sta  c64.CIA2ICR    ; "switch off" interrupts signals from cia-2 | ||||
| 		and  c64.SCROLY | ||||
| 		sta  c64.SCROLY     ; clear most significant bit of raster position | ||||
| 		lda  c64.CIA1ICR    ; ack previous irq | ||||
| 		lda  c64.CIA2ICR    ; ack previous irq | ||||
| 		pla | ||||
| 		sta  c64.RASTER     ; set the raster line number where interrupt should occur | ||||
| 		cpy  #0 | ||||
| 		beq  + | ||||
| 		lda  c64.SCROLY | ||||
| 		ora  #%10000000 | ||||
| 		sta  c64.SCROLY     ; set most significant bit of raster position | ||||
| +		lda  #%00000001 | ||||
| 		sta  c64.IREQMASK   ;enable raster interrupt signals from vic | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| ; ---- 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 {{ | ||||
|             sei | ||||
|             lda  #14 | ||||
|             sta  $01        ; bank the kernal in | ||||
|             jmp  (c64.RESET_VEC) | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     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 | ||||
| } | ||||
							
								
								
									
										618
									
								
								compiler/res/prog8lib/c64/textio.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										618
									
								
								compiler/res/prog8lib/c64/textio.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,618 @@ | ||||
| ; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64 | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %target c64 | ||||
| %import syslib | ||||
| %import conv | ||||
|  | ||||
|  | ||||
| txt { | ||||
|  | ||||
| const ubyte DEFAULT_WIDTH = 40 | ||||
| const ubyte DEFAULT_HEIGHT = 25 | ||||
|  | ||||
|  | ||||
| sub  clear_screen() { | ||||
|     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)  { | ||||
| 	; ---- fill the character screen with the given fill character and character color. | ||||
| 	;      (assumes screen and color matrix are at their default addresses) | ||||
|  | ||||
| 	%asm {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		jsr  clear_screencolors | ||||
| 		pla | ||||
| 		jsr  clear_screenchars | ||||
| 		rts | ||||
|         }} | ||||
|  | ||||
| } | ||||
|  | ||||
| asmsub  clear_screenchars (ubyte char @ A) clobbers(Y)  { | ||||
| 	; ---- clear the character screen with the given fill character (leaves colors) | ||||
| 	;      (assumes screen matrix is at the default address) | ||||
| 	%asm {{ | ||||
| 		ldy  #250 | ||||
| -		sta  c64.Screen+250*0-1,y | ||||
| 		sta  c64.Screen+250*1-1,y | ||||
| 		sta  c64.Screen+250*2-1,y | ||||
| 		sta  c64.Screen+250*3-1,y | ||||
| 		dey | ||||
| 		bne  - | ||||
| 		rts | ||||
|         }} | ||||
| } | ||||
|  | ||||
| asmsub  clear_screencolors (ubyte color @ A) clobbers(Y)  { | ||||
| 	; ---- clear the character screen colors with the given color (leaves characters). | ||||
| 	;      (assumes color matrix is at the default address) | ||||
| 	%asm {{ | ||||
| 		ldy  #250 | ||||
| -		sta  c64.Colors+250*0-1,y | ||||
| 		sta  c64.Colors+250*1-1,y | ||||
| 		sta  c64.Colors+250*2-1,y | ||||
| 		sta  c64.Colors+250*3-1,y | ||||
| 		dey | ||||
| 		bne  - | ||||
| 		rts | ||||
|         }} | ||||
| } | ||||
|  | ||||
| sub color (ubyte txtcol) { | ||||
|     c64.COLOR = txtcol | ||||
| } | ||||
|  | ||||
| sub lowercase() { | ||||
|     c64.VMCSB |= 2 | ||||
| } | ||||
|  | ||||
| sub uppercase() { | ||||
|     c64.VMCSB &= ~2 | ||||
| } | ||||
|  | ||||
| asmsub  scroll_left  (ubyte alsocolors @ Pc) clobbers(A, Y)  { | ||||
| 	; ---- scroll the whole screen 1 character to the left | ||||
| 	;      contents of the rightmost column are unchanged, you should clear/refill this yourself | ||||
| 	;      Carry flag determines if screen color data must be scrolled too | ||||
|  | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		bcc _scroll_screen | ||||
|  | ||||
| +               ; scroll the screen and the color 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 | ||||
|             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 | ||||
| 		inx | ||||
| 		dey | ||||
| 		bpl  - | ||||
|  | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  scroll_right  (ubyte alsocolors @ Pc) clobbers(A)  { | ||||
| 	; ---- scroll the whole screen 1 character to the right | ||||
| 	;      contents of the leftmost column are unchanged, you should clear/refill this yourself | ||||
| 	;      Carry flag determines if screen color data must be scrolled too | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		bcc  _scroll_screen | ||||
|  | ||||
| +               ; scroll the screen and the color memory | ||||
| 		ldx  #38 | ||||
| - | ||||
|         .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 | ||||
|             sta  c64.Colors + 40*row + 1,x | ||||
|         .next | ||||
| 		dex | ||||
| 		bpl  - | ||||
| 		rts | ||||
|  | ||||
| _scroll_screen  ; scroll only the screen memory | ||||
| 		ldx  #38 | ||||
| - | ||||
|         .for row=0, row<=24, row+=1 | ||||
|             lda  c64.Screen + 40*row + 0,x | ||||
|             sta  c64.Screen + 40*row + 1,x | ||||
|         .next | ||||
| 		dex | ||||
| 		bpl  - | ||||
|  | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  scroll_up  (ubyte alsocolors @ Pc) clobbers(A)  { | ||||
| 	; ---- scroll the whole screen 1 character up | ||||
| 	;      contents of the bottom row are unchanged, you should refill/clear this yourself | ||||
| 	;      Carry flag determines if screen color data must be scrolled too | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		bcc  _scroll_screen | ||||
|  | ||||
| +               ; scroll the screen and the color memory | ||||
| 		ldx #39 | ||||
| - | ||||
|         .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 | ||||
|             sta  c64.Colors + 40*(row-1),x | ||||
|         .next | ||||
| 		dex | ||||
| 		bpl  - | ||||
| 		rts | ||||
|  | ||||
| _scroll_screen  ; scroll only the screen memory | ||||
| 		ldx #39 | ||||
| - | ||||
|         .for row=1, row<=24, row+=1 | ||||
|             lda  c64.Screen + 40*row,x | ||||
|             sta  c64.Screen + 40*(row-1),x | ||||
|         .next | ||||
| 		dex | ||||
| 		bpl  - | ||||
|  | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  scroll_down  (ubyte alsocolors @ Pc) clobbers(A)  { | ||||
| 	; ---- scroll the whole screen 1 character down | ||||
| 	;      contents of the top row are unchanged, you should refill/clear this yourself | ||||
| 	;      Carry flag determines if screen color data must be scrolled too | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		bcc  _scroll_screen | ||||
|  | ||||
| +               ; scroll the screen and the color memory | ||||
| 		ldx #39 | ||||
| - | ||||
|         .for row=23, row>=0, row-=1 | ||||
|             lda  c64.Colors + 40*row,x | ||||
|             sta  c64.Colors + 40*(row+1),x | ||||
|             lda  c64.Screen + 40*row,x | ||||
|             sta  c64.Screen + 40*(row+1),x | ||||
|         .next | ||||
| 		dex | ||||
| 		bpl  - | ||||
| 		rts | ||||
|  | ||||
| _scroll_screen  ; scroll only the screen memory | ||||
| 		ldx #39 | ||||
| - | ||||
|         .for row=23, row>=0, row-=1 | ||||
|             lda  c64.Screen + 40*row,x | ||||
|             sta  c64.Screen + 40*(row+1),x | ||||
|         .next | ||||
| 		dex | ||||
| 		bpl  - | ||||
|  | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| romsub $FFD2 = chrout(ubyte char @ A)    ; for consistency. You can also use c64.CHROUT directly ofcourse. | ||||
|  | ||||
| asmsub  print (str text @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print null terminated string from A/Y | ||||
| 	; note: the compiler contains an optimization that will replace | ||||
| 	;       a call to this subroutine with a string argument of just one char, | ||||
| 	;       by just one call to c64.CHROUT of that single char. | ||||
| 	%asm {{ | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		sty  P8ZP_SCRATCH_REG | ||||
| 		ldy  #0 | ||||
| -		lda  (P8ZP_SCRATCH_B1),y | ||||
| 		beq  + | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ub0  (ubyte value @ A) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total) | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  conv.ubyte2decimal | ||||
| 		pha | ||||
| 		tya | ||||
| 		jsr  c64.CHROUT | ||||
| 		pla | ||||
| 		jsr  c64.CHROUT | ||||
| 		txa | ||||
| 		jsr  c64.CHROUT | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ub  (ubyte value @ A) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in decimal form, without left padding 0s | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  conv.ubyte2decimal | ||||
| _print_byte_digits | ||||
| 		pha | ||||
| 		cpy  #'0' | ||||
| 		beq  + | ||||
| 		tya | ||||
| 		jsr  c64.CHROUT | ||||
| 		pla | ||||
| 		jsr  c64.CHROUT | ||||
| 		jmp  _ones | ||||
| +       pla | ||||
|         cmp  #'0' | ||||
|         beq  _ones | ||||
|         jsr  c64.CHROUT | ||||
| _ones   txa | ||||
| 		jsr  c64.CHROUT | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_b  (byte value @ A) clobbers(A,Y)  { | ||||
| 	; ---- print the byte in A in decimal form, without left padding 0s | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		pha | ||||
| 		cmp  #0 | ||||
| 		bpl  + | ||||
| 		lda  #'-' | ||||
| 		jsr  c64.CHROUT | ||||
| +		pla | ||||
| 		jsr  conv.byte2decimal | ||||
| 		jmp  print_ub._print_byte_digits | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ubhex  (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well) | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		bcc  + | ||||
| 		pha | ||||
| 		lda  #'$' | ||||
| 		jsr  c64.CHROUT | ||||
| 		pla | ||||
| +		jsr  conv.ubyte2hex | ||||
| 		jsr  c64.CHROUT | ||||
| 		tya | ||||
| 		jsr  c64.CHROUT | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ubbin  (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well) | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		bcc  + | ||||
| 		lda  #'%' | ||||
| 		jsr  c64.CHROUT | ||||
| +		ldy  #8 | ||||
| -		lda  #'0' | ||||
| 		asl  P8ZP_SCRATCH_B1 | ||||
| 		bcc  + | ||||
| 		lda  #'1' | ||||
| +		jsr  c64.CHROUT | ||||
| 		dey | ||||
| 		bne  - | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uwbin  (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well) | ||||
| 	%asm {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		jsr  print_ubbin | ||||
| 		pla | ||||
| 		clc | ||||
| 		jmp  print_ubbin | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uwhex  (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in hexadecimal form (4 digits) | ||||
| 	;      (if Carry is set, a radix prefix '$' is printed as well) | ||||
| 	%asm {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		jsr  print_ubhex | ||||
| 		pla | ||||
| 		clc | ||||
| 		jmp  print_ubhex | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uw0  (uword value @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total) | ||||
| 	%asm {{ | ||||
| 	    stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  conv.uword2decimal | ||||
| 		ldy  #0 | ||||
| -		lda  conv.uword2decimal.decTenThousands,y | ||||
|         beq  + | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uw  (uword value @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in decimal form, without left padding 0s | ||||
| 	%asm {{ | ||||
| 	    stx  P8ZP_SCRATCH_REG | ||||
| 		jsr  conv.uword2decimal | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		ldy  #0 | ||||
| -		lda  conv.uword2decimal.decTenThousands,y | ||||
| 		beq  _allzero | ||||
| 		cmp  #'0' | ||||
| 		bne  _gotdigit | ||||
| 		iny | ||||
| 		bne  - | ||||
|  | ||||
| _gotdigit | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		lda  conv.uword2decimal.decTenThousands,y | ||||
| 		bne  _gotdigit | ||||
| 		rts | ||||
| _allzero | ||||
|         lda  #'0' | ||||
|         jmp  c64.CHROUT | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_w  (word value @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print the (signed) word in A/Y in decimal form, without left padding 0's | ||||
| 	%asm {{ | ||||
| 		cpy  #0 | ||||
| 		bpl  + | ||||
| 		pha | ||||
| 		lda  #'-' | ||||
| 		jsr  c64.CHROUT | ||||
| 		tya | ||||
| 		eor  #255 | ||||
| 		tay | ||||
| 		pla | ||||
| 		eor  #255 | ||||
| 		clc | ||||
| 		adc  #1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		jmp  print_uw | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  input_chars  (uword buffer @ AY) clobbers(A) -> ubyte @ Y  { | ||||
| 	; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well) | ||||
| 	;      It assumes the keyboard is selected as I/O channel! | ||||
|  | ||||
| 	%asm {{ | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #0				; char counter = 0 | ||||
| -		jsr  c64.CHRIN | ||||
| 		cmp  #$0d			; return (ascii 13) pressed? | ||||
| 		beq  +				; yes, end. | ||||
| 		sta  (P8ZP_SCRATCH_W1),y	; else store char in buffer | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		lda  #0 | ||||
| 		sta  (P8ZP_SCRATCH_W1),y	; finish string with 0 byte | ||||
| 		rts | ||||
|  | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  setchr  (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y)  { | ||||
| 	; ---- sets the character in the screen matrix at the given position | ||||
| 	%asm {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		asl  a | ||||
| 		tay | ||||
| 		lda  _screenrows+1,y | ||||
| 		sta  _mod+2 | ||||
| 		txa | ||||
| 		clc | ||||
| 		adc  _screenrows,y | ||||
| 		sta  _mod+1 | ||||
| 		bcc  + | ||||
| 		inc  _mod+2 | ||||
| +		pla | ||||
| _mod		sta  $ffff		; modified | ||||
| 		rts | ||||
|  | ||||
| _screenrows	.word  $0400 + range(0, 1000, 40) | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  getchr  (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A { | ||||
| 	; ---- get the character in the screen matrix at the given location | ||||
| 	%asm  {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		asl  a | ||||
| 		tay | ||||
| 		lda  setchr._screenrows+1,y | ||||
| 		sta  _mod+2 | ||||
| 		pla | ||||
| 		clc | ||||
| 		adc  setchr._screenrows,y | ||||
| 		sta  _mod+1 | ||||
| 		bcc  _mod | ||||
| 		inc  _mod+2 | ||||
| _mod		lda  $ffff		; modified | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  setclr  (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y)  { | ||||
| 	; ---- set the color in A on the screen matrix at the given position | ||||
| 	%asm {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		asl  a | ||||
| 		tay | ||||
| 		lda  _colorrows+1,y | ||||
| 		sta  _mod+2 | ||||
| 		txa | ||||
| 		clc | ||||
| 		adc  _colorrows,y | ||||
| 		sta  _mod+1 | ||||
| 		bcc  + | ||||
| 		inc  _mod+2 | ||||
| +		pla | ||||
| _mod		sta  $ffff		; modified | ||||
| 		rts | ||||
|  | ||||
| _colorrows	.word  $d800 + range(0, 1000, 40) | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  getclr  (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A { | ||||
| 	; ---- get the color in the screen color matrix at the given location | ||||
| 	%asm  {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		asl  a | ||||
| 		tay | ||||
| 		lda  setclr._colorrows+1,y | ||||
| 		sta  _mod+2 | ||||
| 		pla | ||||
| 		clc | ||||
| 		adc  setclr._colorrows,y | ||||
| 		sta  _mod+1 | ||||
| 		bcc  _mod | ||||
| 		inc  _mod+2 | ||||
| _mod		lda  $ffff		; modified | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| sub  setcc  (ubyte column, ubyte row, ubyte char, ubyte charcolor)  { | ||||
| 	; ---- set char+color at the given position on the screen | ||||
| 	%asm {{ | ||||
| 		lda  row | ||||
| 		asl  a | ||||
| 		tay | ||||
| 		lda  setchr._screenrows+1,y | ||||
| 		sta  _charmod+2 | ||||
| 		adc  #$d4 | ||||
| 		sta  _colormod+2 | ||||
| 		lda  setchr._screenrows,y | ||||
| 		clc | ||||
| 		adc  column | ||||
| 		sta  _charmod+1 | ||||
| 		sta  _colormod+1 | ||||
| 		bcc  + | ||||
| 		inc  _charmod+2 | ||||
| 		inc  _colormod+2 | ||||
| +		lda  char | ||||
| _charmod	sta  $ffff		; modified | ||||
| 		lda  charcolor | ||||
| _colormod	sta  $ffff		; modified | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  plot  (ubyte col @ Y, ubyte row @ A) clobbers(A) { | ||||
| 	; ---- safe wrapper around PLOT kernal routine, to save the X register. | ||||
| 	%asm  {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		tax | ||||
| 		clc | ||||
| 		jsr  c64.PLOT | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub width() clobbers(X,Y) -> ubyte @A { | ||||
|     ; -- returns the text screen width (number of columns) | ||||
|     %asm {{ | ||||
|         jsr  c64.SCREEN | ||||
|         txa | ||||
|         rts | ||||
|     }} | ||||
| } | ||||
|  | ||||
| asmsub height() clobbers(X, Y) -> ubyte @A { | ||||
|     ; -- returns the text screen height (number of rows) | ||||
|     %asm {{ | ||||
|         jsr  c64.SCREEN | ||||
|         tya | ||||
|         rts | ||||
|     }} | ||||
| } | ||||
|  | ||||
| } | ||||
| @@ -1,798 +0,0 @@ | ||||
| ; --- low level floating point assembly routines for the C64 | ||||
|  | ||||
| ub2float	.proc | ||||
| 		; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y | ||||
| 		;    clobbers A, Y | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		sta  c64.SCRATCH_ZPWORD2 | ||||
| 		sty  c64.SCRATCH_ZPWORD2+1 | ||||
| 		ldy  c64.SCRATCH_ZPB1 | ||||
| 		jsr  FREADUY | ||||
| _fac_to_mem	ldx  c64.SCRATCH_ZPWORD2 | ||||
| 		ldy  c64.SCRATCH_ZPWORD2+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| b2float		.proc | ||||
| 		; -- convert byte in SCRATCH_ZPB1 to float at address A/Y | ||||
| 		;    clobbers A, Y | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		sta  c64.SCRATCH_ZPWORD2 | ||||
| 		sty  c64.SCRATCH_ZPWORD2+1 | ||||
| 		lda  c64.SCRATCH_ZPB1 | ||||
| 		jsr  FREADSA | ||||
| 		jmp  ub2float._fac_to_mem | ||||
| 		.pend | ||||
|  | ||||
| uw2float	.proc | ||||
| 		; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		sta  c64.SCRATCH_ZPWORD2 | ||||
| 		sty  c64.SCRATCH_ZPWORD2+1 | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  GIVUAYFAY | ||||
| 		jmp  ub2float._fac_to_mem | ||||
| 		.pend | ||||
|  | ||||
| w2float		.proc | ||||
| 		; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		sta  c64.SCRATCH_ZPWORD2 | ||||
| 		sty  c64.SCRATCH_ZPWORD2+1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1 | ||||
| 		lda  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  GIVAYF | ||||
| 		jmp  ub2float._fac_to_mem | ||||
| 		.pend | ||||
|  | ||||
| stack_b2float	.proc | ||||
| 		; -- b2float operating on the stack | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  FREADSA | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| stack_w2float	.proc | ||||
| 		; -- w2float operating on the stack | ||||
| 		inx | ||||
| 		ldy  c64.ESTACK_LO,x | ||||
| 		lda  c64.ESTACK_HI,x | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  GIVAYF | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| stack_ub2float	.proc | ||||
| 		; -- ub2float operating on the stack | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		tay | ||||
| 		jsr  FREADUY | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| stack_uw2float	.proc | ||||
| 		; -- uw2float operating on the stack | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		ldy  c64.ESTACK_HI,x | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  GIVUAYFAY | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| stack_float2w	.proc               ; also used for float2b | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  AYINT | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		lda  $64 | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		lda  $65 | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| stack_float2uw	.proc               ; also used for float2ub | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  GETADR | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		tya | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| push_float	.proc | ||||
| 		; ---- push mflpt5 in A/Y onto stack | ||||
| 		; (taking 3 stack positions = 6 bytes of which 1 is padding) | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		ldy  #0 | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		dex | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		dex | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| func_rndf	.proc | ||||
| 		; -- put a random floating point value on the stack | ||||
| 		stx  c64.SCRATCH_ZPREG | ||||
| 		lda  #1 | ||||
| 		jsr  FREADSA | ||||
| 		jsr  RND		; rng into fac1 | ||||
| 		ldx  #<_rndf_rnum5 | ||||
| 		ldy  #>_rndf_rnum5 | ||||
| 		jsr  MOVMF	; fac1 to mem X/Y | ||||
| 		ldx  c64.SCRATCH_ZPREG | ||||
| 		lda  #<_rndf_rnum5 | ||||
| 		ldy  #>_rndf_rnum5 | ||||
| 		jmp  push_float | ||||
| _rndf_rnum5	.byte  0,0,0,0,0 | ||||
| 		.pend | ||||
|  | ||||
| push_float_from_indexed_var	.proc | ||||
| 		; -- push the float from the array at A/Y with index on stack, onto the stack. | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  prog8_lib.pop_index_times_5 | ||||
| 		jsr  prog8_lib.add_a_to_zpword | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jmp  push_float | ||||
| 		.pend | ||||
|  | ||||
| pop_float	.proc | ||||
| 		; ---- pops mflpt5 from stack to memory A/Y | ||||
| 		; (frees 3 stack positions = 6 bytes of which 1 is padding) | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		ldy  #4 | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_HI,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_HI,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| pop_float_fac1	.proc | ||||
| 		; -- pops float from stack into FAC1 | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jmp  MOVFM | ||||
| 		.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  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  prog8_lib.pop_index_times_5 | ||||
| 		jsr  prog8_lib.add_a_to_zpword | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jmp  pop_float | ||||
| 		.pend | ||||
|  | ||||
| copy_float	.proc | ||||
| 		; -- 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. | ||||
| 		sta  c64.SCRATCH_ZPWORD2 | ||||
| 		sty  c64.SCRATCH_ZPWORD2+1 | ||||
| 		ldy  #0 | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| inc_var_f	.proc | ||||
| 		; -- add 1 to float pointed to by A/Y | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  MOVFM | ||||
| 		lda  #<FL_FONE | ||||
| 		ldy  #>FL_FONE | ||||
| 		jsr  FADD | ||||
| 		ldx  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| dec_var_f	.proc | ||||
| 		; -- subtract 1 from float pointed to by A/Y | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<FL_FONE | ||||
| 		ldy  #>FL_FONE | ||||
| 		jsr  MOVFM | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  FSUB | ||||
| 		ldx  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| inc_indexed_var_f	.proc | ||||
| 		; -- add 1 to float in array pointed to by A/Y, at index X | ||||
| 		pha | ||||
| 		txa | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		pla | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		jmp  inc_var_f | ||||
| 		.pend | ||||
|  | ||||
| dec_indexed_var_f	.proc | ||||
| 		; -- subtract 1 to float in array pointed to by A/Y, at index X | ||||
| 		pha | ||||
| 		txa | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		pla | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		jmp  dec_var_f | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| pop_2_floats_f2_in_fac1	.proc | ||||
| 		; -- pop 2 floats from stack, load the second one in FAC1 as well | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jmp  MOVFM | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| 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 | ||||
|  | ||||
| push_fac1_as_result	.proc | ||||
| 		; -- push the float in FAC1 onto the stack, and return from calculation | ||||
| 		ldx  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVMF | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		jmp  push_float | ||||
| 		.pend | ||||
|  | ||||
| pow_f		.proc | ||||
| 		; -- push f1 ** f2 on stack | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  CONUPK		; fac2 = float1 | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  FPWR | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| div_f		.proc | ||||
| 		; -- push f1/f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FDIV | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| add_f		.proc | ||||
| 		; -- push f1+f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FADD | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| sub_f		.proc | ||||
| 		; -- push f1-f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FSUB | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| mul_f		.proc | ||||
| 		; -- push f1*f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FMULT | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| neg_f		.proc | ||||
| 		; -- push -flt back on stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  NEGOP | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| abs_f		.proc | ||||
| 		; -- push abs(float) on stack (as float) | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  ABS | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| equal_f		.proc | ||||
| 		; -- are the two mflpt5 numbers on the stack identical? | ||||
| 		inx | ||||
| 		inx | ||||
| 		inx | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO-3,x | ||||
| 		cmp  c64.ESTACK_LO,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_LO-2,x | ||||
| 		cmp  c64.ESTACK_LO+1,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_LO-1,x | ||||
| 		cmp  c64.ESTACK_LO+2,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_HI-2,x | ||||
| 		cmp  c64.ESTACK_HI+1,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_HI-1,x | ||||
| 		cmp  c64.ESTACK_HI+2,x | ||||
| 		bne  _equals_false | ||||
| _equals_true	lda  #1 | ||||
| _equals_store	inx | ||||
| 		sta  c64.ESTACK_LO+1,x | ||||
| 		rts | ||||
| _equals_false	lda  #0 | ||||
| 		beq  _equals_store | ||||
| 		.pend | ||||
|  | ||||
| notequal_f	.proc | ||||
| 		; -- are the two mflpt5 numbers on the stack different? | ||||
| 		jsr  equal_f | ||||
| 		eor  #1		; invert the result | ||||
| 		sta  c64.ESTACK_LO+1,x | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| less_f		.proc | ||||
| 		; -- is f1 < f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #255 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| lesseq_f	.proc | ||||
| 		; -- is f1 <= f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #255 | ||||
| 		beq  compare_floats._return_true | ||||
| 		cmp  #0 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| greater_f	.proc | ||||
| 		; -- is f1 > f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #1 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| greatereq_f	.proc | ||||
| 		; -- is f1 >= f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #1 | ||||
| 		beq  compare_floats._return_true | ||||
| 		cmp  #0 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| compare_floats	.proc | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVFM		; fac1 = flt1 | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		stx  c64.SCRATCH_ZPREG | ||||
| 		jsr  FCOMP		; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2) | ||||
| 		ldx  c64.SCRATCH_ZPREG | ||||
| 		rts | ||||
| _return_false	lda  #0 | ||||
| _return_result  sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| _return_true	lda  #1 | ||||
| 		bne  _return_result | ||||
| 		.pend | ||||
|  | ||||
| func_sin	.proc | ||||
| 		; -- push sin(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  SIN | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_cos	.proc | ||||
| 		; -- push cos(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  COS | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_tan	.proc | ||||
| 		; -- push tan(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  TAN | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_atan	.proc | ||||
| 		; -- push atan(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  ATN | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_ln		.proc | ||||
| 		; -- push ln(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		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  c64.SCRATCH_ZPREGX | ||||
| 		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  c64.SCRATCH_ZPREGX | ||||
| 		jsr  SQR | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_rad	.proc | ||||
| 		; -- convert degrees to radians (d * pi / 180) | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		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  c64.SCRATCH_ZPREGX | ||||
| 		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  c64.SCRATCH_ZPREGX | ||||
| 		jsr  FADDH | ||||
| 		jsr  INT | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_floor	.proc | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		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  c64.SCRATCH_ZPREGX | ||||
| 		ldx  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVMF | ||||
| 		jsr  INT | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FCOMP | ||||
| 		cmp  #0 | ||||
| 		beq  + | ||||
| 		lda  #<FL_FONE | ||||
| 		ldy  #>FL_FONE | ||||
| 		jsr  FADD | ||||
| +		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_any_f	.proc | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x	; array size | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1	; times 5 because of float | ||||
| 		jmp  prog8_lib.func_any_b._entry | ||||
| 		.pend | ||||
|  | ||||
| func_all_f	.proc | ||||
| 		inx | ||||
| 		jsr  prog8_lib.peek_address | ||||
| 		lda  c64.ESTACK_LO,x	; array size | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1	; times 5 because of float | ||||
| 		tay | ||||
| 		dey | ||||
| -		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		clc | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		cmp  #0 | ||||
| 		beq  + | ||||
| 		cpy  #255 | ||||
|         	bne  - | ||||
| 		lda  #1 | ||||
| 		sta  c64.ESTACK_LO+1,x | ||||
| 		rts | ||||
| +		sta  c64.ESTACK_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  c64.SCRATCH_ZPREGX | ||||
| -		sty  c64.SCRATCH_ZPREG | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  FCOMP | ||||
| _minmax_cmp	cmp  #255			; modified | ||||
| 		bne  + | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  MOVFM | ||||
| +		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		clc | ||||
| 		adc  #5 | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		bcc  + | ||||
| 		inc  c64.SCRATCH_ZPWORD1+1 | ||||
| +		ldy  c64.SCRATCH_ZPREG | ||||
| 		dey | ||||
| 		cpy  #255 | ||||
| 		bne  - | ||||
| 		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 | ||||
| 		ldy  #>FL_ZERO | ||||
| 		jsr  MOVFM | ||||
| 		jsr  prog8_lib.pop_array_and_lengthmin1Y | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| -		sty  c64.SCRATCH_ZPREG | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  FADD | ||||
| 		ldy  c64.SCRATCH_ZPREG | ||||
| 		dey | ||||
| 		cpy  #255 | ||||
| 		beq  + | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		clc | ||||
| 		adc  #5 | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		bcc  - | ||||
| 		inc  c64.SCRATCH_ZPWORD1+1 | ||||
| 		bne  - | ||||
| +		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| sign_f		.proc | ||||
| 		jsr  pop_float_fac1 | ||||
| 		jsr  SIGN | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| set_0_array_float	.proc | ||||
| 		; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1) | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.ESTACK_LO,x | ||||
| 		tay | ||||
| 		lda  #0 | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		iny | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		iny | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		iny | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		iny | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| set_array_float		.proc | ||||
| 		; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2) | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.ESTACK_LO,x | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPWORD2 | ||||
| 		ldy  c64.SCRATCH_ZPWORD2+1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		jmp  copy_float | ||||
| 			; -- 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. | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| swap_floats	.proc | ||||
| 		; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2 | ||||
| 		ldy  #4 | ||||
| -               lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		pha | ||||
| 		lda  (c64.SCRATCH_ZPWORD2),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		pla | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		dey | ||||
| 		bpl  - | ||||
| 		rts | ||||
| 		.pend | ||||
| @@ -1,243 +0,0 @@ | ||||
| ; Prog8 definitions for the Commodore-64 | ||||
| ; Including memory registers, I/O registers, Basic and Kernal subroutines. | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
|  | ||||
| c64 { | ||||
|         const  uword  ESTACK_LO = $ce00     ; evaluation stack (lsb) | ||||
|         const  uword  ESTACK_HI = $cf00     ; evaluation stack (msb) | ||||
|         &ubyte  SCRATCH_ZPB1    = $02       ; scratch byte 1 in ZP | ||||
|         &ubyte  SCRATCH_ZPREG   = $03       ; scratch register in ZP | ||||
|         &ubyte  SCRATCH_ZPREGX  = $fa       ; temp storage for X register (stack pointer) | ||||
|         &uword  SCRATCH_ZPWORD1 = $fb       ; scratch word in ZP ($fb/$fc) | ||||
|         &uword  SCRATCH_ZPWORD2 = $fd       ; scratch word in ZP ($fd/$fe) | ||||
|  | ||||
|  | ||||
|         &ubyte  TIME_HI         = $a0       ; software jiffy clock, hi byte | ||||
|         &ubyte  TIME_MID        = $a1       ;  .. mid byte | ||||
|         &ubyte  TIME_LO         = $a2       ;    .. lo byte. Updated by IRQ every 1/60 sec | ||||
|         &ubyte  STKEY           = $91       ; various keyboard statuses (updated by IRQ) | ||||
|         &ubyte  SFDX            = $cb       ; current key pressed (matrix value) (updated by IRQ) | ||||
|  | ||||
|         &ubyte  COLOR           = $0286     ; cursor color | ||||
|         &ubyte  HIBASE          = $0288     ; screen base address / 256 (hi-byte of screen memory address) | ||||
|         &uword  CINV            = $0314     ; IRQ vector | ||||
|         &uword  NMI_VEC         = $FFFA     ; 6502 nmi vector, determined by the kernal if banked in | ||||
|         &uword  RESET_VEC       = $FFFC     ; 6502 reset vector, determined by the kernal if banked in | ||||
|         &uword  IRQ_VEC         = $FFFE     ; 6502 interrupt vector, determined by the kernal if banked in | ||||
|  | ||||
|         ; the default addresses for the character screen chars and colors | ||||
|         const  uword  Screen    = $0400     ; to have this as an array[40*25] the compiler would have to support array size > 255 | ||||
|         const  uword  Colors    = $d800     ; to have this as an array[40*25] the compiler would have to support array size > 255 | ||||
|  | ||||
|         ; the default locations of the 8 sprite pointers (store address of sprite / 64) | ||||
|         &ubyte  SPRPTR0         = 2040 | ||||
|         &ubyte  SPRPTR1         = 2041 | ||||
|         &ubyte  SPRPTR2         = 2042 | ||||
|         &ubyte  SPRPTR3         = 2043 | ||||
|         &ubyte  SPRPTR4         = 2044 | ||||
|         &ubyte  SPRPTR5         = 2045 | ||||
|         &ubyte  SPRPTR6         = 2046 | ||||
|         &ubyte  SPRPTR7         = 2047 | ||||
|         &ubyte[8]  SPRPTR       = 2040      ; the 8 sprite pointers as an array. | ||||
|  | ||||
|  | ||||
| ; ---- VIC-II 6567/6569/856x registers ---- | ||||
|  | ||||
|         &ubyte  SP0X            = $d000 | ||||
|         &ubyte  SP0Y            = $d001 | ||||
|         &ubyte  SP1X            = $d002 | ||||
|         &ubyte  SP1Y            = $d003 | ||||
|         &ubyte  SP2X            = $d004 | ||||
|         &ubyte  SP2Y            = $d005 | ||||
|         &ubyte  SP3X            = $d006 | ||||
|         &ubyte  SP3Y            = $d007 | ||||
|         &ubyte  SP4X            = $d008 | ||||
|         &ubyte  SP4Y            = $d009 | ||||
|         &ubyte  SP5X            = $d00a | ||||
|         &ubyte  SP5Y            = $d00b | ||||
|         &ubyte  SP6X            = $d00c | ||||
|         &ubyte  SP6Y            = $d00d | ||||
|         &ubyte  SP7X            = $d00e | ||||
|         &ubyte  SP7Y            = $d00f | ||||
|         &ubyte[16]  SPXY        = $d000        ; the 8 sprite X and Y registers as an array. | ||||
|         &uword[8]  SPXYW        = $d000        ; the 8 sprite X and Y registers as a combined xy word array. | ||||
|  | ||||
|         &ubyte  MSIGX           = $d010 | ||||
|         &ubyte  SCROLY          = $d011 | ||||
|         &ubyte  RASTER          = $d012 | ||||
|         &ubyte  LPENX           = $d013 | ||||
|         &ubyte  LPENY           = $d014 | ||||
|         &ubyte  SPENA           = $d015 | ||||
|         &ubyte  SCROLX          = $d016 | ||||
|         &ubyte  YXPAND          = $d017 | ||||
|         &ubyte  VMCSB           = $d018 | ||||
|         &ubyte  VICIRQ          = $d019 | ||||
|         &ubyte  IREQMASK        = $d01a | ||||
|         &ubyte  SPBGPR          = $d01b | ||||
|         &ubyte  SPMC            = $d01c | ||||
|         &ubyte  XXPAND          = $d01d | ||||
|         &ubyte  SPSPCL          = $d01e | ||||
|         &ubyte  SPBGCL          = $d01f | ||||
|  | ||||
|         &ubyte  EXTCOL          = $d020        ; border color | ||||
|         &ubyte  BGCOL0          = $d021        ; screen color | ||||
|         &ubyte  BGCOL1          = $d022 | ||||
|         &ubyte  BGCOL2          = $d023 | ||||
|         &ubyte  BGCOL4          = $d024 | ||||
|         &ubyte  SPMC0           = $d025 | ||||
|         &ubyte  SPMC1           = $d026 | ||||
|         &ubyte  SP0COL          = $d027 | ||||
|         &ubyte  SP1COL          = $d028 | ||||
|         &ubyte  SP2COL          = $d029 | ||||
|         &ubyte  SP3COL          = $d02a | ||||
|         &ubyte  SP4COL          = $d02b | ||||
|         &ubyte  SP5COL          = $d02c | ||||
|         &ubyte  SP6COL          = $d02d | ||||
|         &ubyte  SP7COL          = $d02e | ||||
|         &ubyte[8]  SPCOL        = $d027 | ||||
|  | ||||
|  | ||||
| ; ---- end of VIC-II registers ---- | ||||
|  | ||||
| ; ---- CIA 6526 1 & 2 registers ---- | ||||
|  | ||||
|         &ubyte  CIA1PRA         = $DC00        ; CIA 1 DRA, keyboard column drive (and joystick control port #2) | ||||
|         &ubyte  CIA1PRB         = $DC01        ; CIA 1 DRB, keyboard row port (and joystick control port #1) | ||||
|         &ubyte  CIA1DDRA        = $DC02        ; CIA 1 DDRA, keyboard column | ||||
|         &ubyte  CIA1DDRB        = $DC03        ; CIA 1 DDRB, keyboard row | ||||
|         &ubyte  CIA1TAL         = $DC04        ; CIA 1 timer A low byte | ||||
|         &ubyte  CIA1TAH         = $DC05        ; CIA 1 timer A high byte | ||||
|         &ubyte  CIA1TBL         = $DC06        ; CIA 1 timer B low byte | ||||
|         &ubyte  CIA1TBH         = $DC07        ; CIA 1 timer B high byte | ||||
|         &ubyte  CIA1TOD10       = $DC08        ; time of day, 1/10 sec. | ||||
|         &ubyte  CIA1TODSEC      = $DC09        ; time of day, seconds | ||||
|         &ubyte  CIA1TODMMIN     = $DC0A        ; time of day, minutes | ||||
|         &ubyte  CIA1TODHR       = $DC0B        ; time of day, hours | ||||
|         &ubyte  CIA1SDR         = $DC0C        ; Serial Data Register | ||||
|         &ubyte  CIA1ICR         = $DC0D | ||||
|         &ubyte  CIA1CRA         = $DC0E | ||||
|         &ubyte  CIA1CRB         = $DC0F | ||||
|  | ||||
|         &ubyte  CIA2PRA         = $DD00        ; CIA 2 DRA, serial port and video address | ||||
|         &ubyte  CIA2PRB         = $DD01        ; CIA 2 DRB, RS232 port / USERPORT | ||||
|         &ubyte  CIA2DDRA        = $DD02        ; CIA 2 DDRA, serial port and video address | ||||
|         &ubyte  CIA2DDRB        = $DD03        ; CIA 2 DDRB, RS232 port / USERPORT | ||||
|         &ubyte  CIA2TAL         = $DD04        ; CIA 2 timer A low byte | ||||
|         &ubyte  CIA2TAH         = $DD05        ; CIA 2 timer A high byte | ||||
|         &ubyte  CIA2TBL         = $DD06        ; CIA 2 timer B low byte | ||||
|         &ubyte  CIA2TBH         = $DD07        ; CIA 2 timer B high byte | ||||
|         &ubyte  CIA2TOD10       = $DD08        ; time of day, 1/10 sec. | ||||
|         &ubyte  CIA2TODSEC      = $DD09        ; time of day, seconds | ||||
|         &ubyte  CIA2TODMIN      = $DD0A        ; time of day, minutes | ||||
|         &ubyte  CIA2TODHR       = $DD0B        ; time of day, hours | ||||
|         &ubyte  CIA2SDR         = $DD0C        ; Serial Data Register | ||||
|         &ubyte  CIA2ICR         = $DD0D | ||||
|         &ubyte  CIA2CRA         = $DD0E | ||||
|         &ubyte  CIA2CRB         = $DD0F | ||||
|  | ||||
| ; ---- end of CIA registers ---- | ||||
|  | ||||
| ; ---- SID 6581/8580 registers ---- | ||||
|  | ||||
|         &ubyte  FREQLO1         = $D400        ; channel 1 freq lo | ||||
|         &ubyte  FREQHI1         = $D401        ; channel 1 freq hi | ||||
|         &uword  FREQ1           = $D400        ; channel 1 freq (word) | ||||
|         &ubyte  PWLO1           = $D402        ; channel 1 pulse width lo (7-0) | ||||
|         &ubyte  PWHI1           = $D403        ; channel 1 pulse width hi (11-8) | ||||
|         &uword  PW1             = $D402        ; channel 1 pulse width (word) | ||||
|         &ubyte  CR1             = $D404        ; channel 1 voice control register | ||||
|         &ubyte  AD1             = $D405        ; channel 1 attack & decay | ||||
|         &ubyte  SR1             = $D406        ; channel 1 sustain & release | ||||
|         &ubyte  FREQLO2         = $D407        ; channel 2 freq lo | ||||
|         &ubyte  FREQHI2         = $D408        ; channel 2 freq hi | ||||
|         &uword  FREQ2           = $D407        ; channel 2 freq (word) | ||||
|         &ubyte  PWLO2           = $D409        ; channel 2 pulse width lo (7-0) | ||||
|         &ubyte  PWHI2           = $D40A        ; channel 2 pulse width hi (11-8) | ||||
|         &uword  PW2             = $D409        ; channel 2 pulse width (word) | ||||
|         &ubyte  CR2             = $D40B        ; channel 2 voice control register | ||||
|         &ubyte  AD2             = $D40C        ; channel 2 attack & decay | ||||
|         &ubyte  SR2             = $D40D        ; channel 2 sustain & release | ||||
|         &ubyte  FREQLO3         = $D40E        ; channel 3 freq lo | ||||
|         &ubyte  FREQHI3         = $D40F        ; channel 3 freq hi | ||||
|         &uword  FREQ3           = $D40E        ; channel 3 freq (word) | ||||
|         &ubyte  PWLO3           = $D410        ; channel 3 pulse width lo (7-0) | ||||
|         &ubyte  PWHI3           = $D411        ; channel 3 pulse width hi (11-8) | ||||
|         &uword  PW3             = $D410        ; channel 3 pulse width (word) | ||||
|         &ubyte  CR3             = $D412        ; channel 3 voice control register | ||||
|         &ubyte  AD3             = $D413        ; channel 3 attack & decay | ||||
|         &ubyte  SR3             = $D414        ; channel 3 sustain & release | ||||
|         &ubyte  FCLO            = $D415        ; filter cutoff lo (2-0) | ||||
|         &ubyte  FCHI            = $D416        ; filter cutoff hi (10-3) | ||||
|         &uword  FC              = $D415        ; filter cutoff (word) | ||||
|         &ubyte  RESFILT         = $D417        ; filter resonance and routing | ||||
|         &ubyte  MVOL            = $D418        ; filter mode and main volume control | ||||
|         &ubyte  POTX            = $D419        ; potentiometer X | ||||
|         &ubyte  POTY            = $D41A        ; potentiometer Y | ||||
|         &ubyte  OSC3            = $D41B        ; channel 3 oscillator value read | ||||
|         &ubyte  ENV3            = $D41C        ; channel 3 envelope value read | ||||
|  | ||||
| ; ---- end of SID registers ---- | ||||
|  | ||||
|  | ||||
|  | ||||
| ; ---- C64 basic routines ---- | ||||
|  | ||||
| romsub $E544 = CLEARSCR() clobbers(A,X,Y)       ; clear the screen | ||||
| romsub $E566 = HOMECRSR() clobbers(A,X,Y)       ; cursor to top left of screen | ||||
|  | ||||
|  | ||||
| ; ---- end of C64 basic routines ---- | ||||
|  | ||||
|  | ||||
| ; ---- C64 kernal routines ---- | ||||
|  | ||||
| romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y)      ; print null-terminated string (use c64scr.print instead) | ||||
| romsub $EA31 = IRQDFRT() clobbers(A,X,Y)                        ; default IRQ routine | ||||
| romsub $EA81 = IRQDFEND() clobbers(A,X,Y)                       ; default IRQ end/cleanup | ||||
| 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 $FF87 = RAMTAS() clobbers(A,X,Y)                         ; initialize RAM, tape buffer, screen | ||||
| romsub $FF8A = RESTOR() clobbers(A,X,Y)                         ; restore default I/O vectors | ||||
| romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y)     ; read/set I/O vector table | ||||
| 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 $FF96 = TKSA(ubyte address @ A) clobbers(A)              ; (alias: TALKSA) send secondary address after TALK | ||||
| romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY     ; read/set top 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 $FFA2 = SETTMO(ubyte timeout @ A)                        ; set time-out flag for IEEE bus | ||||
| romsub $FFA5 = ACPTR() -> ubyte @ A                             ; (alias: IECIN) input byte from serial bus | ||||
| romsub $FFA8 = CIOUT(ubyte databyte @ A)                        ; (alias: IECOUT) output byte to serial bus | ||||
| romsub $FFAB = UNTLK() clobbers(A)                              ; command serial bus device to UNTALK | ||||
| romsub $FFAE = UNLSN() clobbers(A)                              ; command serial bus device to UNLISTEN | ||||
| 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 $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 $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 $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 $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 $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 $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 $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 $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y       ; read the software clock | ||||
| romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc   ; (via 808 ($328)) check the STOP key | ||||
| romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A               ; (via 810 ($32A)) get a character | ||||
| romsub $FFE7 = CLALL() clobbers(A,X)                            ; (via 812 ($32C)) close all files | ||||
| 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 $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 $FFF3 = IOBASE() -> uword @ XY                           ; read base address of I/O devices | ||||
|  | ||||
| ; ---- end of C64 kernal routines ---- | ||||
|  | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										736
									
								
								compiler/res/prog8lib/conv.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										736
									
								
								compiler/res/prog8lib/conv.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,736 @@ | ||||
| ; Number conversions routines. | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
|  | ||||
|  | ||||
| conv { | ||||
|  | ||||
| ; ----- 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  { | ||||
| 	; ---- A to decimal string in Y/A/X  (100s in Y, 10s in A, 1s in X) | ||||
| 	%asm {{ | ||||
| 		ldy  #uword2decimal.ASCII_0_OFFSET | ||||
| 		bne  uword2decimal.hex_try200 | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  uword2decimal  (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X  { | ||||
| 	;  ---- convert 16 bit uword in A/Y to decimal | ||||
| 	;  output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes | ||||
| 	;  (these are terminated by a zero byte so they can be easily printed) | ||||
| 	;  also returns Y = 100's, A = 10's, X = 1's | ||||
|  | ||||
| 	%asm {{ | ||||
|  | ||||
| ;Convert 16 bit Hex to Decimal (0-65535) Rev 2 | ||||
| ;By Omegamatrix    Further optimizations by tepples | ||||
| ; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15 | ||||
|  | ||||
| ;HexToDec99 | ||||
| ; start in A | ||||
| ; end with A = 10's, decOnes (also in X) | ||||
|  | ||||
| ;HexToDec255 | ||||
| ; start in A | ||||
| ; end with Y = 100's, A = 10's, decOnes (also in X) | ||||
|  | ||||
| ;HexToDec999 | ||||
| ; start with A = high byte, Y = low byte | ||||
| ; end with Y = 100's, A = 10's, decOnes (also in X) | ||||
| ; requires 1 extra temp register on top of decOnes, could combine | ||||
| ; these two if HexToDec65535 was eliminated... | ||||
|  | ||||
| ;HexToDec65535 | ||||
| ; start with A/Y (low/high) as 16 bit value | ||||
| ; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X) | ||||
| ; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed) | ||||
|  | ||||
|  | ||||
| ASCII_0_OFFSET 	= $30 | ||||
| temp       	    = P8ZP_SCRATCH_B1	; byte in zeropage | ||||
| hexHigh      	= P8ZP_SCRATCH_W1	; byte in zeropage | ||||
| hexLow       	= P8ZP_SCRATCH_W1+1	; byte in zeropage | ||||
|  | ||||
|  | ||||
| HexToDec65535; SUBROUTINE | ||||
|     sty    hexHigh               ;3  @9 | ||||
|     sta    hexLow                ;3  @12 | ||||
|     tya | ||||
|     tax                          ;2  @14 | ||||
|     lsr    a                     ;2  @16 | ||||
|     lsr    a                     ;2  @18   integer divide 1024 (result 0-63) | ||||
|  | ||||
|     cpx    #$A7                  ;2  @20   account for overflow of multiplying 24 from 43,000 ($A7F8) onward, | ||||
|     adc    #1                    ;2  @22   we can just round it to $A700, and the divide by 1024 is fine... | ||||
|  | ||||
|     ;at this point we have a number 1-65 that we have to times by 24, | ||||
|     ;add to original sum, and Mod 1024 to get a remainder 0-999 | ||||
|  | ||||
|  | ||||
|     sta    temp                  ;3  @25 | ||||
|     asl    a                     ;2  @27 | ||||
|     adc    temp                  ;3  @30  x3 | ||||
|     tay                          ;2  @32 | ||||
|     lsr    a                     ;2  @34 | ||||
|     lsr    a                     ;2  @36 | ||||
|     lsr    a                     ;2  @38 | ||||
|     lsr    a                     ;2  @40 | ||||
|     lsr    a                     ;2  @42 | ||||
|     tax                          ;2  @44 | ||||
|     tya                          ;2  @46 | ||||
|     asl    a                     ;2  @48 | ||||
|     asl    a                     ;2  @50 | ||||
|     asl    a                     ;2  @52 | ||||
|     clc                          ;2  @54 | ||||
|     adc    hexLow                ;3  @57 | ||||
|     sta    hexLow                ;3  @60 | ||||
|     txa                          ;2  @62 | ||||
|     adc    hexHigh               ;3  @65 | ||||
|     sta    hexHigh               ;3  @68 | ||||
|     ror    a                     ;2  @70 | ||||
|     lsr    a                     ;2  @72 | ||||
|     tay                          ;2  @74    integer divide 1,000 (result 0-65) | ||||
|  | ||||
|     lsr    a                     ;2  @76    split the 1,000 and 10,000 digit | ||||
|     tax                          ;2  @78 | ||||
|     lda    ShiftedBcdTab,x       ;4  @82 | ||||
|     tax                          ;2  @84 | ||||
|     rol    a                     ;2  @86 | ||||
|     and    #$0F                  ;2  @88 | ||||
|     ora    #ASCII_0_OFFSET | ||||
|     sta    decThousands          ;3  @91 | ||||
|     txa                          ;2  @93 | ||||
|     lsr    a                     ;2  @95 | ||||
|     lsr    a                     ;2  @97 | ||||
|     lsr    a                     ;2  @99 | ||||
|     ora    #ASCII_0_OFFSET | ||||
|     sta    decTenThousands       ;3  @102 | ||||
|  | ||||
|     lda    hexLow                ;3  @105 | ||||
|     cpy    temp                  ;3  @108 | ||||
|     bmi    _doSubtract           ;2³ @110/111 | ||||
|     beq    _useZero               ;2³ @112/113 | ||||
|     adc    #23 + 24              ;2  @114 | ||||
| _doSubtract | ||||
|     sbc    #23                   ;2  @116 | ||||
|     sta    hexLow                ;3  @119 | ||||
| _useZero | ||||
|     lda    hexHigh               ;3  @122 | ||||
|     sbc    #0                    ;2  @124 | ||||
|  | ||||
| Start100s | ||||
|     and    #$03                  ;2  @126 | ||||
|     tax                          ;2  @128   0,1,2,3 | ||||
|     cmp    #2                    ;2  @130 | ||||
|     rol    a                     ;2  @132   0,2,5,7 | ||||
|     ora    #ASCII_0_OFFSET | ||||
|     tay                          ;2  @134   Y = Hundreds digit | ||||
|  | ||||
|     lda    hexLow                ;3  @137 | ||||
|     adc    Mod100Tab,x           ;4  @141    adding remainder of 256, 512, and 256+512 (all mod 100) | ||||
|     bcs    hex_doSub200             ;2³ @143/144 | ||||
|  | ||||
| hex_try200 | ||||
|     cmp    #200                  ;2  @145 | ||||
|     bcc    hex_try100               ;2³ @147/148 | ||||
| hex_doSub200 | ||||
|     iny                          ;2  @149 | ||||
|     iny                          ;2  @151 | ||||
|     sbc    #200                  ;2  @153 | ||||
| hex_try100 | ||||
|     cmp    #100                  ;2  @155 | ||||
|     bcc    HexToDec99            ;2³ @157/158 | ||||
|     iny                          ;2  @159 | ||||
|     sbc    #100                  ;2  @161 | ||||
|  | ||||
| HexToDec99; SUBROUTINE | ||||
|     lsr    a                     ;2  @163 | ||||
|     tax                          ;2  @165 | ||||
|     lda    ShiftedBcdTab,x       ;4  @169 | ||||
|     tax                          ;2  @171 | ||||
|     rol    a                     ;2  @173 | ||||
|     and    #$0F                  ;2  @175 | ||||
|     ora    #ASCII_0_OFFSET | ||||
|     sta    decOnes               ;3  @178 | ||||
|     txa                          ;2  @180 | ||||
|     lsr    a                     ;2  @182 | ||||
|     lsr    a                     ;2  @184 | ||||
|     lsr    a                     ;2  @186 | ||||
|     ora    #ASCII_0_OFFSET | ||||
|  | ||||
|     ; irmen: load X with ones, and store Y and A too, for easy printing afterwards | ||||
|     sty  decHundreds | ||||
|     sta  decTens | ||||
|     ldx  decOnes | ||||
|     rts                          ;6  @192   Y=hundreds, A = tens digit, X=ones digit | ||||
|  | ||||
|  | ||||
| HexToDec999; SUBROUTINE | ||||
|     sty    hexLow                ;3  @9 | ||||
|     jmp    Start100s             ;3  @12 | ||||
|  | ||||
| Mod100Tab | ||||
|     .byte 0,56,12,56+12 | ||||
|  | ||||
| ShiftedBcdTab | ||||
|     .byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C | ||||
|     .byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C | ||||
|     .byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C | ||||
|     .byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C | ||||
|     .byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C | ||||
|  | ||||
| decTenThousands   	.byte  0 | ||||
| decThousands    	.byte  0 | ||||
| decHundreds		.byte  0 | ||||
| decTens			.byte  0 | ||||
| decOnes   		.byte  0 | ||||
| 			.byte  0		; zero-terminate the decimal output string | ||||
|  | ||||
|     }} | ||||
| } | ||||
|  | ||||
| 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) | ||||
| 	;      note: if the number is negative, you have to deal with the '-' yourself! | ||||
| 	%asm {{ | ||||
| 		cmp  #0 | ||||
| 		bpl  + | ||||
| 		eor  #255 | ||||
| 		clc | ||||
| 		adc  #1 | ||||
| +		jmp  ubyte2decimal | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  ubyte2hex  (ubyte value @A) -> ubyte @A, ubyte @Y  { | ||||
| 	; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y) | ||||
| 	%asm {{ | ||||
| 		stx  P8ZP_SCRATCH_REG | ||||
| 		pha | ||||
| 		and  #$0f | ||||
| 		tax | ||||
| 		ldy  _hex_digits,x | ||||
| 		pla | ||||
| 		lsr  a | ||||
| 		lsr  a | ||||
| 		lsr  a | ||||
| 		lsr  a | ||||
| 		tax | ||||
| 		lda  _hex_digits,x | ||||
| 		ldx  P8ZP_SCRATCH_REG | ||||
| 		rts | ||||
|  | ||||
| _hex_digits	.text "0123456789abcdef"	; can probably be reused for other stuff as well | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  uword2hex  (uword value @AY) clobbers(A,Y)  { | ||||
| 	; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated) | ||||
| 	%asm {{ | ||||
| 		sta  P8ZP_SCRATCH_REG | ||||
| 		tya | ||||
| 		jsr  ubyte2hex | ||||
| 		sta  output | ||||
| 		sty  output+1 | ||||
| 		lda  P8ZP_SCRATCH_REG | ||||
| 		jsr  ubyte2hex | ||||
| 		sta  output+2 | ||||
| 		sty  output+3 | ||||
| 		rts | ||||
| output		.text  "0000", $00      ; 0-terminated output buffer (to make printing easier) | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| } | ||||
							
								
								
									
										164
									
								
								compiler/res/prog8lib/cx16/floats.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								compiler/res/prog8lib/cx16/floats.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,164 @@ | ||||
| ; Prog8 definitions for floating point handling on the CommanderX16 | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %target cx16 | ||||
| %option enable_floats | ||||
|  | ||||
| floats { | ||||
| 	; ---- 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  TWOPI  = 6.283185307179586 | ||||
|  | ||||
|  | ||||
| ; ---- ROM float functions ---- | ||||
|  | ||||
| 		; 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. | ||||
|  | ||||
| ; 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. | ||||
|  | ||||
| romsub $fe00 = AYINT() clobbers(A,X,Y)          ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY) | ||||
|  | ||||
| ; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1 | ||||
| ; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1 | ||||
| ; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order) | ||||
| romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y) | ||||
|  | ||||
| ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15) | ||||
| ; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order) | ||||
| romsub $fe06 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A | ||||
|  | ||||
| romsub $fe09 = FADDH() clobbers(A,X,Y)                      ; fac1 += 0.5, for rounding- call this before INT | ||||
| romsub $fe0c = FSUB(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 = mflpt from A/Y - fac1 | ||||
| romsub $fe0f = FSUBT() clobbers(A,X,Y)                      ; fac1 = fac2-fac1   mind the order of the operands | ||||
| romsub $fe12 = FADD(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 += mflpt value from A/Y | ||||
| romsub $fe15 = FADDT() clobbers(A,X,Y)                      ; fac1 += fac2 | ||||
| romsub $fe1b = ZEROFC() clobbers(A,X,Y)                     ; fac1 = 0 | ||||
| romsub $fe1e = NORMAL() clobbers(A,X,Y)                     ; normalize fac1 (?) | ||||
| 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 $fe2a = FMULTT() clobbers(A,X,Y)                     ; fac1 *= fac2 | ||||
| romsub $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y)     ; load mflpt value from memory  in A/Y into fac2 | ||||
| romsub $fe33 = MUL10() clobbers(A,X,Y)                      ; fac1 *= 10 | ||||
| romsub $fe36 = DIV10() clobbers(A,X,Y)                      ; fac1 /= 10 , CAUTION: result is always positive! | ||||
| romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 = mflpt in A/Y / fac1  (remainder in fac2) | ||||
| romsub $fe3c = FDIVT() clobbers(A,X,Y)                      ; fac1 = fac2/fac1  (remainder in fac2)  mind the order of the operands | ||||
|  | ||||
| romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y)      ; load mflpt value from memory  in A/Y into fac1 | ||||
| romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y)      ; store fac1 to memory  X/Y as 5-byte mflpt | ||||
| romsub $fe48 = MOVFA() clobbers(A,X)                        ; copy fac2 to fac1 | ||||
| romsub $fe4b = MOVAF() clobbers(A,X)                        ; copy fac1 to fac2  (rounded) | ||||
| romsub $fe4e = MOVEF() clobbers(A,X)                        ; copy fac1 to fac2 | ||||
| romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A            ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive | ||||
| romsub $fe57 = SGN() clobbers(A,X,Y)                        ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1) | ||||
| romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y)      ; 8 bit signed A -> float in fac1 | ||||
| romsub $fe66 = ABS() clobbers(A,X,Y)                        ; fac1 = ABS(fac1) | ||||
| 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 $fe72 = INT() clobbers(A,X,Y)                        ; INT() truncates, use FADDH first to round instead of trunc | ||||
| romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y)           ; fac1 += signed byte in A | ||||
| romsub $fe7b = FOUT() clobbers(X) -> uword @ AY             ; fac1 -> string, address returned in AY | ||||
| romsub $fe81 = SQR() clobbers(A,X,Y)                        ; fac1 = SQRT(fac1) | ||||
| romsub $fe84 = FPWRT() clobbers(A,X,Y)                      ; fac1 = fac2 ** fac1 | ||||
| romsub $fe8a = NEGOP() clobbers(A)                          ; switch the sign of fac1 (fac1 = -fac1) | ||||
| romsub $fe8d = EXP() clobbers(A,X,Y)                        ; fac1 = EXP(fac1)  (e ** fac1) | ||||
| romsub $fe96 = RND() clobbers(A,X,Y)                        ; fac1 = RND(fac1) float random number generator | ||||
| romsub $fe99 = COS() clobbers(A,X,Y)                        ; fac1 = COS(fac1) | ||||
| romsub $fe9c = SIN() clobbers(A,X,Y)                        ; fac1 = SIN(fac1) | ||||
| romsub $fe9f = TAN() clobbers(A,X,Y)                        ; fac1 = TAN(fac1) | ||||
| romsub $fea2 = ATN() clobbers(A,X,Y)                        ; fac1 = ATN(fac1) | ||||
|  | ||||
|  | ||||
| asmsub  GIVUAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | ||||
| 	; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1 | ||||
| 	%asm {{ | ||||
|         phx | ||||
|         sta  _tmp | ||||
|         sty  P8ZP_SCRATCH_B1 | ||||
|         tya | ||||
|         ldy  _tmp | ||||
|         jsr  GIVAYF                 ; load it as signed... correct afterwards | ||||
|         lda  P8ZP_SCRATCH_B1 | ||||
| 	    bpl  + | ||||
| 	    lda  #<_flt65536 | ||||
| 	    ldy  #>_flt65536 | ||||
| 	    jsr  FADD | ||||
| +	    plx | ||||
|         rts | ||||
| _tmp        .byte 0 | ||||
| _flt65536    .byte 145,0,0,0,0       ; 65536.0 | ||||
| 	}} | ||||
| } | ||||
|  | ||||
|  | ||||
| asmsub  GIVAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | ||||
| 	; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1 | ||||
| 	%asm {{ | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		tya | ||||
| 		ldy  P8ZP_SCRATCH_B1 | ||||
| 		jmp  GIVAYF		; this uses the inverse order, Y/A | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  FTOSWRDAY  () clobbers(X) -> uword @ AY  { | ||||
| 	; ---- fac1 to signed word in A/Y | ||||
| 	%asm {{ | ||||
| 		jsr  FTOSWORDYA	; note the inverse Y/A order | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		tya | ||||
| 		ldy  P8ZP_SCRATCH_B1 | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  GETADRAY  () clobbers(X) -> uword @ AY  { | ||||
| 	; ---- fac1 to unsigned word in A/Y | ||||
| 	%asm {{ | ||||
| 		jsr  GETADR		; this uses the inverse order, Y/A | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		tya | ||||
| 		ldy  P8ZP_SCRATCH_B1 | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  FREADUY (ubyte value @Y) { | ||||
|     ; -- 8 bit unsigned Y -> float in fac1 | ||||
|     %asm {{ | ||||
|         lda  #0 | ||||
|         jmp  GIVAYF | ||||
|     }} | ||||
| } | ||||
|  | ||||
| sub  print_f  (float value) { | ||||
| 	; ---- prints the floating point value (without a newline). | ||||
| 	%asm {{ | ||||
| 		phx | ||||
| 		lda  #<value | ||||
| 		ldy  #>value | ||||
| 		jsr  MOVFM		; load float into fac1 | ||||
| 		jsr  FOUT		; fac1 to string in A/Y | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #0 | ||||
| -		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		beq  + | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		plx | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| %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 | ||||
|         }} | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										138
									
								
								compiler/res/prog8lib/cx16/graphics.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								compiler/res/prog8lib/cx16/graphics.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| %target cx16 | ||||
| %import syslib | ||||
| %import textio | ||||
|  | ||||
| ; Bitmap pixel graphics module for the CommanderX16 | ||||
| ; wraps the graphics functions that are in ROM. | ||||
| ; 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 { | ||||
|     const uword WIDTH = 320 | ||||
|     const ubyte HEIGHT = 200 | ||||
|  | ||||
|     sub enable_bitmap_mode() { | ||||
|         ; enable bitmap screen, erase it and set colors to black/white. | ||||
|         void cx16.screen_set_mode($80) | ||||
|         cx16.GRAPH_init(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) { | ||||
|         cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor) | ||||
|         cx16.GRAPH_clear() | ||||
|     } | ||||
|  | ||||
|     sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { | ||||
|         cx16.GRAPH_draw_line(x1, y1, x2, y2) | ||||
|     } | ||||
|  | ||||
|     sub fillrect(uword x, uword y, uword width, uword height) { | ||||
|         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) { | ||||
|         ;cx16.r0 = xcenter - radius/2 | ||||
|         ;cx16.r1 = ycenter - radius/2 | ||||
|         ;cx16.r2 = radius*2 | ||||
|         ;cx16.r3 = radius*2 | ||||
|         ;cx16.GRAPH_draw_oval(false)          ; currently this call is not implemented on cx16, does a BRK | ||||
|  | ||||
|         ; Midpoint algorithm | ||||
|         if radius==0 | ||||
|             return | ||||
|         ubyte @zp xx = radius | ||||
|         ubyte @zp yy = 0 | ||||
|         word @zp decisionOver2 = (1 as word)-xx | ||||
|  | ||||
|         while xx>=yy { | ||||
|             cx16.r0 = xcenter + xx | ||||
|             cx16.r1 = ycenter + yy | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             cx16.r0 = xcenter - xx | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             cx16.r0 = xcenter + xx | ||||
|             cx16.r1 = ycenter - yy | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             cx16.r0 = xcenter - xx | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             cx16.r0 = xcenter + yy | ||||
|             cx16.r1 = ycenter + xx | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             cx16.r0 = xcenter - yy | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             cx16.r0 = xcenter + yy | ||||
|             cx16.r1 = ycenter - xx | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             cx16.r0 = xcenter - yy | ||||
|             cx16.FB_cursor_position2() | ||||
|             cx16.FB_set_pixel(1) | ||||
|             yy++ | ||||
|             if decisionOver2<=0 { | ||||
|                 decisionOver2 += (yy as word)*2+1 | ||||
|             } else { | ||||
|                 xx-- | ||||
|                 decisionOver2 += (yy as word -xx)*2+1 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     sub disc(uword xcenter, ubyte ycenter, ubyte radius) { | ||||
|         if radius==0 | ||||
|             return | ||||
|         ubyte @zp yy = 0 | ||||
|         word decisionOver2 = (1 as word)-radius | ||||
|  | ||||
|         while radius>=yy { | ||||
|             horizontal_line(xcenter-radius, ycenter+yy, radius*2+1) | ||||
|             horizontal_line(xcenter-radius, ycenter-yy, radius*2+1) | ||||
|             horizontal_line(xcenter-yy, ycenter+radius, yy*2+1) | ||||
|             horizontal_line(xcenter-yy, ycenter-radius, yy*2+1) | ||||
|             yy++ | ||||
|             if decisionOver2<=0 | ||||
|                 decisionOver2 += (yy as word)*2+1 | ||||
|             else { | ||||
|                 radius-- | ||||
|                 decisionOver2 += (yy as word -radius)*2+1 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     inline asmsub  plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) { | ||||
|         %asm {{ | ||||
|             jsr  cx16.FB_cursor_position | ||||
|             lda  #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++ | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										883
									
								
								compiler/res/prog8lib/cx16/syslib.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										883
									
								
								compiler/res/prog8lib/cx16/syslib.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,883 @@ | ||||
| ; Prog8 definitions for the CommanderX16 | ||||
| ; Including memory registers, I/O registers, Basic and Kernal subroutines. | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %target cx16 | ||||
|  | ||||
|  | ||||
| c64 { | ||||
|  | ||||
| ; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ---- | ||||
|  | ||||
| ; STROUT --> use txt.print | ||||
| ; CLEARSCR -> use txt.clear_screen | ||||
| ; HOMECRSR -> use txt.plot | ||||
|  | ||||
| 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 $FF87 = RAMTAS() clobbers(A,X,Y)                         ; initialize RAM, tape buffer, screen | ||||
| romsub $FF8A = RESTOR() clobbers(A,X,Y)                         ; restore default I/O vectors | ||||
| romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y)     ; read/set I/O vector table | ||||
| 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 $FF96 = TKSA(ubyte address @ A) clobbers(A)              ; (alias: TALKSA) send secondary address after TALK | ||||
| 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 $FF9F = SCNKEY() clobbers(A,X,Y)                         ; scan the keyboard | ||||
| romsub $FFA2 = SETTMO(ubyte timeout @ A)                        ; set time-out flag for IEEE bus | ||||
| romsub $FFA5 = ACPTR() -> ubyte @ A                             ; (alias: IECIN) input byte from serial bus | ||||
| romsub $FFA8 = CIOUT(ubyte databyte @ A)                        ; (alias: IECOUT) output byte to serial bus | ||||
| romsub $FFAB = UNTLK() clobbers(A)                              ; command serial bus device to UNTALK | ||||
| romsub $FFAE = UNLSN() clobbers(A)                              ; command serial bus device to UNLISTEN | ||||
| 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 $FFB7 = READST() -> ubyte @ A                            ; read I/O status word | ||||
| 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 $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 $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 $FFCC = CLRCHN() clobbers(A,X)                           ; (via 802 ($322)) restore default devices | ||||
| 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 $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 $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 (A=lo,X=mid,Y=high) | ||||
| 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 @Pc, ubyte @ A    ; (via 810 ($32A)) get a character | ||||
| romsub $FFE7 = CLALL() clobbers(A,X)                            ; (via 812 ($32C)) close all files | ||||
| 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 $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 | ||||
|  | ||||
| ; ---- 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 { | ||||
|  | ||||
| ; irq and hardware vectors: | ||||
|     &uword  CINV            = $0314     ; IRQ vector (in ram) | ||||
|     &uword  NMI_VEC         = $FFFA     ; 65c02 nmi 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 | ||||
|     &uword r0  = $0002 | ||||
|     &uword r1  = $0004 | ||||
|     &uword r2  = $0006 | ||||
|     &uword r3  = $0008 | ||||
|     &uword r4  = $000a | ||||
|     &uword r5  = $000c | ||||
|     &uword r6  = $000e | ||||
|     &uword r7  = $0010 | ||||
|     &uword r8  = $0012 | ||||
|     &uword r9  = $0014 | ||||
|     &uword r10 = $0016 | ||||
|     &uword r11 = $0018 | ||||
|     &uword r12 = $001a | ||||
|     &uword r13 = $001c | ||||
|     &uword r14 = $001e | ||||
|     &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 | ||||
|  | ||||
|     const uword VERA_BASE       = $9F20 | ||||
|     &ubyte  VERA_ADDR_L         = VERA_BASE + $0000 | ||||
|     &ubyte  VERA_ADDR_M         = VERA_BASE + $0001 | ||||
|     &ubyte  VERA_ADDR_H         = VERA_BASE + $0002 | ||||
|     &ubyte  VERA_DATA0          = VERA_BASE + $0003 | ||||
|     &ubyte  VERA_DATA1          = VERA_BASE + $0004 | ||||
|     &ubyte  VERA_CTRL           = VERA_BASE + $0005 | ||||
|     &ubyte  VERA_IEN            = VERA_BASE + $0006 | ||||
|     &ubyte  VERA_ISR            = VERA_BASE + $0007 | ||||
|     &ubyte  VERA_IRQ_LINE_L     = VERA_BASE + $0008 | ||||
|     &ubyte  VERA_DC_VIDEO       = VERA_BASE + $0009 | ||||
|     &ubyte  VERA_DC_HSCALE      = VERA_BASE + $000A | ||||
|     &ubyte  VERA_DC_VSCALE      = VERA_BASE + $000B | ||||
|     &ubyte  VERA_DC_BORDER      = VERA_BASE + $000C | ||||
|     &ubyte  VERA_DC_HSTART      = VERA_BASE + $0009 | ||||
|     &ubyte  VERA_DC_HSTOP       = VERA_BASE + $000A | ||||
|     &ubyte  VERA_DC_VSTART      = VERA_BASE + $000B | ||||
|     &ubyte  VERA_DC_VSTOP       = VERA_BASE + $000C | ||||
|     &ubyte  VERA_L0_CONFIG      = VERA_BASE + $000D | ||||
|     &ubyte  VERA_L0_MAPBASE     = VERA_BASE + $000E | ||||
|     &ubyte  VERA_L0_TILEBASE    = VERA_BASE + $000F | ||||
|     &ubyte  VERA_L0_HSCROLL_L   = VERA_BASE + $0010 | ||||
|     &ubyte  VERA_L0_HSCROLL_H   = VERA_BASE + $0011 | ||||
|     &ubyte  VERA_L0_VSCROLL_L   = VERA_BASE + $0012 | ||||
|     &ubyte  VERA_L0_VSCROLL_H   = VERA_BASE + $0013 | ||||
|     &ubyte  VERA_L1_CONFIG      = VERA_BASE + $0014 | ||||
|     &ubyte  VERA_L1_MAPBASE     = VERA_BASE + $0015 | ||||
|     &ubyte  VERA_L1_TILEBASE    = VERA_BASE + $0016 | ||||
|     &ubyte  VERA_L1_HSCROLL_L   = VERA_BASE + $0017 | ||||
|     &ubyte  VERA_L1_HSCROLL_H   = VERA_BASE + $0018 | ||||
|     &ubyte  VERA_L1_VSCROLL_L   = VERA_BASE + $0019 | ||||
|     &ubyte  VERA_L1_VSCROLL_H   = VERA_BASE + $001A | ||||
|     &ubyte  VERA_AUDIO_CTRL     = VERA_BASE + $001B | ||||
|     &ubyte  VERA_AUDIO_RATE     = VERA_BASE + $001C | ||||
|     &ubyte  VERA_AUDIO_DATA     = VERA_BASE + $001D | ||||
|     &ubyte  VERA_SPI_DATA       = VERA_BASE + $001E | ||||
|     &ubyte  VERA_SPI_CTRL       = VERA_BASE + $001F | ||||
| ; VERA_PSG_BASE     = $1F9C0 | ||||
| ; VERA_PALETTE_BASE = $1FA00 | ||||
| ; VERA_SPRITES_BASE = $1FC00 | ||||
|  | ||||
| ; I/O | ||||
|  | ||||
|     const uword  via1   = $9f00                  ;VIA 6522 #1 | ||||
|     &ubyte  d1prb	= via1+0 | ||||
|     &ubyte  d1pra	= via1+1 | ||||
|     &ubyte  d1ddrb	= via1+2 | ||||
|     &ubyte  d1ddra	= via1+3 | ||||
|     &ubyte  d1t1l	= via1+4 | ||||
|     &ubyte  d1t1h	= via1+5 | ||||
|     &ubyte  d1t1ll	= via1+6 | ||||
|     &ubyte  d1t1lh	= via1+7 | ||||
|     &ubyte  d1t2l	= via1+8 | ||||
|     &ubyte  d1t2h	= via1+9 | ||||
|     &ubyte  d1sr	= via1+10 | ||||
|     &ubyte  d1acr	= via1+11 | ||||
|     &ubyte  d1pcr	= via1+12 | ||||
|     &ubyte  d1ifr	= via1+13 | ||||
|     &ubyte  d1ier	= via1+14 | ||||
|     &ubyte  d1ora	= via1+15 | ||||
|  | ||||
|     const uword  via2   = $9f10                  ;VIA 6522 #2 | ||||
|     &ubyte  d2prb	= via2+0 | ||||
|     &ubyte  d2pra	= via2+1 | ||||
|     &ubyte  d2ddrb	= via2+2 | ||||
|     &ubyte  d2ddra	= via2+3 | ||||
|     &ubyte  d2t1l	= via2+4 | ||||
|     &ubyte  d2t1h	= via2+5 | ||||
|     &ubyte  d2t1ll	= via2+6 | ||||
|     &ubyte  d2t1lh	= via2+7 | ||||
|     &ubyte  d2t2l	= via2+8 | ||||
|     &ubyte  d2t2h	= via2+9 | ||||
|     &ubyte  d2sr	= via2+10 | ||||
|     &ubyte  d2acr	= via2+11 | ||||
|     &ubyte  d2pcr	= via2+12 | ||||
|     &ubyte  d2ifr	= via2+13 | ||||
|     &ubyte  d2ier	= via2+14 | ||||
|     &ubyte  d2ora	= via2+15 | ||||
|  | ||||
|     &ubyte  ym2151adr	= $9f40 | ||||
|     &ubyte  ym2151dat	= $9f41 | ||||
|  | ||||
|     const uword  extdev	= $9f60 | ||||
|  | ||||
|  | ||||
| ; ---- Commander X-16 additions on top of C64 kernal routines ---- | ||||
| ; spelling of the names is taken from the Commander X-16 rom sources | ||||
|  | ||||
| ; supported C128 additions | ||||
| romsub $ff4a = close_all(ubyte device @A)  clobbers(A,X,Y) | ||||
| romsub $ff59 = lkupla(ubyte la @A)  clobbers(A,X,Y) | ||||
| romsub $ff5c = lkupsa(ubyte sa @Y)  clobbers(A,X,Y) | ||||
| romsub $ff5f = screen_set_mode(ubyte mode @A)  clobbers(A, X, Y) -> ubyte @Pc | ||||
| romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY)  clobbers(A,X,Y)      ; incompatible with C128  dlchr() | ||||
| ; not yet supported: romsub $ff65 = pfkey()  clobbers(A,X,Y) | ||||
| romsub $ff6e = jsrfar() | ||||
| romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y)  clobbers(X) -> ubyte @A | ||||
| romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y)  clobbers(X) | ||||
| romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y)  clobbers(X) | ||||
| romsub $ff7d = primm() | ||||
|  | ||||
| ; X16 additions | ||||
| romsub $ff44 = macptr()  clobbers(A,X,Y) | ||||
| romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc)  clobbers(A,X,Y) | ||||
| romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X)  clobbers (A, X, Y) | ||||
| romsub $ff6b = mouse_get(ubyte zpdataptr @X)  clobbers(A) | ||||
| romsub $ff71 = mouse_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 $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)  -> 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 | ||||
|  | ||||
| ; high level graphics & fonts | ||||
| romsub $ff20 = GRAPH_init(uword vectors @R0)  clobbers(A,X,Y) | ||||
| romsub $ff23 = GRAPH_clear()  clobbers(A,X,Y) | ||||
| 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 $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(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(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(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc)  clobbers(A,X,Y) | ||||
| 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(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 $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 | ||||
| romsub $fef6 = FB_init()  clobbers(A,X,Y) | ||||
| romsub $fef9 = FB_get_info()  clobbers(X,Y) -> byte @A, uword @R0, uword @R1    ; width=r0, height=r1 | ||||
| romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X)  clobbers(A,X,Y) | ||||
| romsub $feff = FB_cursor_position(uword x @R0, uword y @R1)  clobbers(A,X,Y) | ||||
| 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 $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 $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 $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(uword count @R0, uword pstep @R1, ubyte color @A)  clobbers(A,X,Y) | ||||
| romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1)  clobbers(A,X,Y) | ||||
| 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 | ||||
| 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(uword x @R0, uword y @R1, ubyte number @A)  clobbers(A,X,Y) | ||||
| romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A)  clobbers(A,X,Y) | ||||
| romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2)  clobbers(A,X,Y) | ||||
| romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1)  clobbers(A,X,Y) -> uword @R2 | ||||
| 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(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 $fee1 = console_get_char()  clobbers(X,Y) -> ubyte @A | ||||
| romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2)  clobbers(A,X,Y) | ||||
| 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 $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y | ||||
| romsub $fecc = monitor()  clobbers(A,X,Y) | ||||
|  | ||||
| ; ---- 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()  { | ||||
|     ; Initializes the machine to a sane starting state. | ||||
|     ; Called automatically by the loader program logic. | ||||
|     %asm {{ | ||||
|         sei | ||||
|         cld | ||||
|         lda  #$80 | ||||
|         sta  VERA_CTRL | ||||
|         stz  $01        ; select rom bank 0 (enable kernal) | ||||
|         jsr  c64.IOINIT | ||||
|         jsr  c64.RESTOR | ||||
|         jsr  c64.CINT | ||||
|         lda  #$90       ; black | ||||
|         jsr  c64.CHROUT | ||||
|         lda  #1         ; swap fg/bg | ||||
|         jsr  c64.CHROUT | ||||
|         lda  #$9e       ; yellow | ||||
|         jsr  c64.CHROUT | ||||
|         lda  #147       ; clear screen | ||||
|         jsr  c64.CHROUT | ||||
|         lda  #0 | ||||
|         tax | ||||
|         tay | ||||
|         clc | ||||
|         clv | ||||
|         cli | ||||
|         rts | ||||
|     }} | ||||
| } | ||||
|  | ||||
| asmsub  init_system_phase2()  { | ||||
|     %asm {{ | ||||
|         sei | ||||
|         lda  cx16.CINV | ||||
|         sta  restore_irq._orig_irqvec | ||||
|         lda  cx16.CINV+1 | ||||
|         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 | ||||
|         }} | ||||
|     } | ||||
| } | ||||
							
								
								
									
										749
									
								
								compiler/res/prog8lib/cx16/textio.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										749
									
								
								compiler/res/prog8lib/cx16/textio.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,749 @@ | ||||
| ; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16 | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %target cx16 | ||||
| %import syslib | ||||
| %import conv | ||||
|  | ||||
|  | ||||
| txt { | ||||
|  | ||||
| const ubyte DEFAULT_WIDTH = 80 | ||||
| const ubyte DEFAULT_HEIGHT = 60 | ||||
|  | ||||
|  | ||||
| sub clear_screen() { | ||||
|     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)  { | ||||
| 	; ---- fill the character screen with the given fill character and character color. | ||||
| 	%asm {{ | ||||
| 	    sty  _ly+1 | ||||
|         phx | ||||
|         pha | ||||
|         jsr  c64.SCREEN             ; get dimensions in X/Y | ||||
|         txa | ||||
|         lsr  a | ||||
|         lsr  a | ||||
|         sta  _lx+1 | ||||
|         stz  cx16.VERA_CTRL | ||||
|         lda  #%00010000 | ||||
|         sta  cx16.VERA_ADDR_H       ; enable auto increment by 1, bank 0. | ||||
|         stz  cx16.VERA_ADDR_L       ; start at (0,0) | ||||
|         stz  cx16.VERA_ADDR_M | ||||
|         pla | ||||
| _lx     ldx  #0                     ; modified | ||||
|         phy | ||||
| _ly     ldy  #1                     ; modified | ||||
| -       sta  cx16.VERA_DATA0 | ||||
|         sty  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         sty  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         sty  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         sty  cx16.VERA_DATA0 | ||||
|         dex | ||||
|         bne  - | ||||
|         ply | ||||
|         dey | ||||
|         beq  + | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         inc  cx16.VERA_ADDR_M       ; next line | ||||
|         bra  _lx | ||||
| +       plx | ||||
|         rts | ||||
|         }} | ||||
| } | ||||
|  | ||||
| asmsub  clear_screenchars (ubyte char @ A) clobbers(Y)  { | ||||
| 	; ---- clear the character screen with the given fill character (leaves colors) | ||||
| 	;      (assumes screen matrix is at the default address) | ||||
| 	%asm {{ | ||||
|         phx | ||||
|         pha | ||||
|         jsr  c64.SCREEN             ; get dimensions in X/Y | ||||
|         txa | ||||
|         lsr  a | ||||
|         lsr  a | ||||
|         sta  _lx+1 | ||||
|         stz  cx16.VERA_CTRL | ||||
|         lda  #%00100000 | ||||
|         sta  cx16.VERA_ADDR_H       ; enable auto increment by 2, bank 0. | ||||
|         stz  cx16.VERA_ADDR_L       ; start at (0,0) | ||||
|         stz  cx16.VERA_ADDR_M | ||||
|         pla | ||||
| _lx     ldx  #0                     ; modified | ||||
| -       sta  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         dex | ||||
|         bne  - | ||||
|         dey | ||||
|         beq  + | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         inc  cx16.VERA_ADDR_M       ; next line | ||||
|         bra  _lx | ||||
| +       plx | ||||
|         rts | ||||
|         }} | ||||
| } | ||||
|  | ||||
| asmsub  clear_screencolors (ubyte color @ A) clobbers(Y)  { | ||||
| 	; ---- clear the character screen colors with the given color (leaves characters). | ||||
| 	;      (assumes color matrix is at the default address) | ||||
| 	%asm {{ | ||||
|         phx | ||||
|         sta  _la+1 | ||||
|         jsr  c64.SCREEN             ; get dimensions in X/Y | ||||
|         txa | ||||
|         lsr  a | ||||
|         lsr  a | ||||
|         sta  _lx+1 | ||||
|         stz  cx16.VERA_CTRL | ||||
|         lda  #%00100000 | ||||
|         sta  cx16.VERA_ADDR_H       ; enable auto increment by 2, bank 0. | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_ADDR_L       ; start at (1,0) | ||||
|         stz  cx16.VERA_ADDR_M | ||||
| _lx     ldx  #0                     ; modified | ||||
| _la     lda  #0                     ; modified | ||||
| -       sta  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA0 | ||||
|         dex | ||||
|         bne  - | ||||
|         dey | ||||
|         beq  + | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_ADDR_L | ||||
|         inc  cx16.VERA_ADDR_M       ; next line | ||||
|         bra  _lx | ||||
| +       plx | ||||
|         rts | ||||
|         }} | ||||
| } | ||||
|  | ||||
|  | ||||
| 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) { | ||||
|     txtcol &= 15 | ||||
|     c64.CHROUT(color_to_charcode[txtcol]) | ||||
| } | ||||
|  | ||||
| sub color2 (ubyte txtcol, ubyte bgcol) { | ||||
|     txtcol &= 15 | ||||
|     bgcol &= 15 | ||||
|     c64.CHROUT(color_to_charcode[bgcol]) | ||||
|     c64.CHROUT(1)       ; switch fg and bg colors | ||||
|     c64.CHROUT(color_to_charcode[txtcol]) | ||||
| } | ||||
|  | ||||
| sub lowercase() { | ||||
|     cx16.screen_set_charset(3, 0)  ; lowercase charset | ||||
| } | ||||
|  | ||||
| sub uppercase() { | ||||
|     cx16.screen_set_charset(2, 0)  ; uppercase charset | ||||
| } | ||||
|  | ||||
| asmsub  scroll_left() clobbers(A, Y)  { | ||||
| 	; ---- scroll the whole screen 1 character to the left | ||||
| 	;      contents of the rightmost column are unchanged, you should clear/refill this yourself | ||||
| 	%asm {{ | ||||
| 	    phx | ||||
| 	    jsr  c64.SCREEN | ||||
| 	    dex | ||||
| 	    stx  _lx+1 | ||||
|         dey | ||||
|         sty  P8ZP_SCRATCH_B1    ; number of rows to scroll | ||||
|  | ||||
| _nextline | ||||
|         stz  cx16.VERA_CTRL     ; data port 0: source column | ||||
|         lda  #%00010000         ; auto increment 1 | ||||
|         sta  cx16.VERA_ADDR_H | ||||
|         lda  #2 | ||||
|         sta  cx16.VERA_ADDR_L   ; begin in column 1 | ||||
|         ldy  P8ZP_SCRATCH_B1 | ||||
|         sty  cx16.VERA_ADDR_M | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_CTRL     ; data port 1: destination column | ||||
|         lda  #%00010000         ; auto increment 1 | ||||
|         sta  cx16.VERA_ADDR_H | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         sty  cx16.VERA_ADDR_M | ||||
|  | ||||
| _lx     ldx  #0                ; modified | ||||
| -       lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1    ; copy char | ||||
|         lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1    ; copy color | ||||
|         dex | ||||
|         bne  - | ||||
|         dec  P8ZP_SCRATCH_B1 | ||||
|         bpl  _nextline | ||||
|  | ||||
|         lda  #0 | ||||
|         sta  cx16.VERA_CTRL | ||||
| 	    plx | ||||
| 	    rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  scroll_right() clobbers(A)  { | ||||
| 	; ---- scroll the whole screen 1 character to the right | ||||
| 	;      contents of the leftmost column are unchanged, you should clear/refill this yourself | ||||
| 	%asm {{ | ||||
| 	    phx | ||||
| 	    jsr  c64.SCREEN | ||||
| 	    dex | ||||
| 	    stx  _lx+1 | ||||
| 	    txa | ||||
| 	    asl  a | ||||
| 	    dea | ||||
| 	    sta  _rcol+1 | ||||
| 	    ina | ||||
| 	    ina | ||||
| 	    sta  _rcol2+1 | ||||
|         dey | ||||
|         sty  P8ZP_SCRATCH_B1    ; number of rows to scroll | ||||
|  | ||||
| _nextline | ||||
|         stz  cx16.VERA_CTRL     ; data port 0: source column | ||||
|         lda  #%00011000         ; auto decrement 1 | ||||
|         sta  cx16.VERA_ADDR_H | ||||
| _rcol   lda  #79*2-1             ; modified | ||||
|         sta  cx16.VERA_ADDR_L   ; begin in rightmost column minus one | ||||
|         ldy  P8ZP_SCRATCH_B1 | ||||
|         sty  cx16.VERA_ADDR_M | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_CTRL     ; data port 1: destination column | ||||
|         lda  #%00011000         ; auto decrement 1 | ||||
|         sta  cx16.VERA_ADDR_H | ||||
| _rcol2  lda  #79*2+1            ; modified | ||||
|         sta  cx16.VERA_ADDR_L | ||||
|         sty  cx16.VERA_ADDR_M | ||||
|  | ||||
| _lx     ldx  #0                 ; modified | ||||
| -       lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1    ; copy char | ||||
|         lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1    ; copy color | ||||
|         dex | ||||
|         bne  - | ||||
|         dec  P8ZP_SCRATCH_B1 | ||||
|         bpl  _nextline | ||||
|  | ||||
|         lda  #0 | ||||
|         sta  cx16.VERA_CTRL | ||||
| 	    plx | ||||
| 	    rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  scroll_up() clobbers(A, Y)  { | ||||
| 	; ---- scroll the whole screen 1 character up | ||||
| 	;      contents of the bottom row are unchanged, you should refill/clear this yourself | ||||
| 	%asm {{ | ||||
| 	    phx | ||||
| 	    jsr  c64.SCREEN | ||||
| 	    stx  _nextline+1 | ||||
| 	    dey | ||||
|         sty  P8ZP_SCRATCH_B1 | ||||
|         stz  cx16.VERA_CTRL         ; data port 0 is source | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_ADDR_M       ; start at second line | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         lda  #%00010000 | ||||
|         sta  cx16.VERA_ADDR_H       ; enable auto increment by 1, bank 0. | ||||
|  | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_CTRL         ; data port 1 is destination | ||||
|         stz  cx16.VERA_ADDR_M       ; start at top line | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         lda  #%00010000 | ||||
|         sta  cx16.VERA_ADDR_H       ; enable auto increment by 1, bank 0. | ||||
|  | ||||
| _nextline | ||||
|         ldx  #80        ; modified | ||||
| -       lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1        ; copy char | ||||
|         lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1        ; copy color | ||||
|         dex | ||||
|         bne  - | ||||
|         dec  P8ZP_SCRATCH_B1 | ||||
|         beq  + | ||||
|         stz  cx16.VERA_CTRL         ; data port 0 | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         inc  cx16.VERA_ADDR_M | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_CTRL         ; data port 1 | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         inc  cx16.VERA_ADDR_M | ||||
|         bra  _nextline | ||||
|  | ||||
| +       lda  #0 | ||||
|         sta  cx16.VERA_CTRL | ||||
| 	    plx | ||||
| 	    rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  scroll_down() clobbers(A, Y)  { | ||||
| 	; ---- scroll the whole screen 1 character down | ||||
| 	;      contents of the top row are unchanged, you should refill/clear this yourself | ||||
| 	%asm {{ | ||||
| 	    phx | ||||
| 	    jsr  c64.SCREEN | ||||
| 	    stx  _nextline+1 | ||||
| 	    dey | ||||
|         sty  P8ZP_SCRATCH_B1 | ||||
|         stz  cx16.VERA_CTRL         ; data port 0 is source | ||||
|         dey | ||||
|         sty  cx16.VERA_ADDR_M       ; start at line before bottom line | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         lda  #%00010000 | ||||
|         sta  cx16.VERA_ADDR_H       ; enable auto increment by 1, bank 0. | ||||
|  | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_CTRL         ; data port 1 is destination | ||||
|         iny | ||||
|         sty  cx16.VERA_ADDR_M       ; start at bottom line | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         lda  #%00010000 | ||||
|         sta  cx16.VERA_ADDR_H       ; enable auto increment by 1, bank 0. | ||||
|  | ||||
| _nextline | ||||
|         ldx  #80        ; modified | ||||
| -       lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1        ; copy char | ||||
|         lda  cx16.VERA_DATA0 | ||||
|         sta  cx16.VERA_DATA1        ; copy color | ||||
|         dex | ||||
|         bne  - | ||||
|         dec  P8ZP_SCRATCH_B1 | ||||
|         beq  + | ||||
|         stz  cx16.VERA_CTRL         ; data port 0 | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         dec  cx16.VERA_ADDR_M | ||||
|         lda  #1 | ||||
|         sta  cx16.VERA_CTRL         ; data port 1 | ||||
|         stz  cx16.VERA_ADDR_L | ||||
|         dec  cx16.VERA_ADDR_M | ||||
|         bra  _nextline | ||||
|  | ||||
| +       lda  #0 | ||||
|         sta  cx16.VERA_CTRL | ||||
| 	    plx | ||||
| 	    rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| romsub $FFD2 = chrout(ubyte char @ A)    ; for consistency. You can also use c64.CHROUT directly ofcourse. | ||||
|  | ||||
| asmsub  print (str text @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print null terminated string from A/Y | ||||
| 	; note: the compiler contains an optimization that will replace | ||||
| 	;       a call to this subroutine with a string argument of just one char, | ||||
| 	;       by just one call to c64.CHROUT of that single char. | ||||
| 	%asm {{ | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		sty  P8ZP_SCRATCH_REG | ||||
| 		ldy  #0 | ||||
| -		lda  (P8ZP_SCRATCH_B1),y | ||||
| 		beq  + | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ub0  (ubyte value @ A) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total) | ||||
| 	%asm {{ | ||||
| 		phx | ||||
| 		jsr  conv.ubyte2decimal | ||||
| 		pha | ||||
| 		tya | ||||
| 		jsr  c64.CHROUT | ||||
| 		pla | ||||
| 		jsr  c64.CHROUT | ||||
| 		txa | ||||
| 		jsr  c64.CHROUT | ||||
| 		plx | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ub  (ubyte value @ A) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in decimal form, without left padding 0s | ||||
| 	%asm {{ | ||||
| 		phx | ||||
| 		jsr  conv.ubyte2decimal | ||||
| _print_byte_digits | ||||
| 		pha | ||||
| 		cpy  #'0' | ||||
| 		beq  + | ||||
| 		tya | ||||
| 		jsr  c64.CHROUT | ||||
| 		pla | ||||
| 		jsr  c64.CHROUT | ||||
| 		bra  _ones | ||||
| +       pla | ||||
|         cmp  #'0' | ||||
|         beq  _ones | ||||
|         jsr  c64.CHROUT | ||||
| _ones   txa | ||||
| 		jsr  c64.CHROUT | ||||
| 		plx | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_b  (byte value @ A) clobbers(A,Y)  { | ||||
| 	; ---- print the byte in A in decimal form, without left padding 0s | ||||
| 	%asm {{ | ||||
| 		phx | ||||
| 		pha | ||||
| 		cmp  #0 | ||||
| 		bpl  + | ||||
| 		lda  #'-' | ||||
| 		jsr  c64.CHROUT | ||||
| +		pla | ||||
| 		jsr  conv.byte2decimal | ||||
| 		bra  print_ub._print_byte_digits | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ubhex  (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well) | ||||
| 	%asm {{ | ||||
| 		phx | ||||
| 		bcc  + | ||||
| 		pha | ||||
| 		lda  #'$' | ||||
| 		jsr  c64.CHROUT | ||||
| 		pla | ||||
| +		jsr  conv.ubyte2hex | ||||
| 		jsr  c64.CHROUT | ||||
| 		tya | ||||
| 		jsr  c64.CHROUT | ||||
| 		plx | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_ubbin  (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well) | ||||
| 	%asm {{ | ||||
| 		phx | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		bcc  + | ||||
| 		lda  #'%' | ||||
| 		jsr  c64.CHROUT | ||||
| +		ldy  #8 | ||||
| -		lda  #'0' | ||||
| 		asl  P8ZP_SCRATCH_B1 | ||||
| 		bcc  + | ||||
| 		lda  #'1' | ||||
| +		jsr  c64.CHROUT | ||||
| 		dey | ||||
| 		bne  - | ||||
| 		plx | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uwbin  (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well) | ||||
| 	%asm {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		jsr  print_ubbin | ||||
| 		pla | ||||
| 		clc | ||||
| 		bra  print_ubbin | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uwhex  (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in hexadecimal form (4 digits) | ||||
| 	;      (if Carry is set, a radix prefix '$' is printed as well) | ||||
| 	%asm {{ | ||||
| 		pha | ||||
| 		tya | ||||
| 		jsr  print_ubhex | ||||
| 		pla | ||||
| 		clc | ||||
| 		bra  print_ubhex | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uw0  (uword value @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total) | ||||
| 	%asm {{ | ||||
| 	    phx | ||||
| 		jsr  conv.uword2decimal | ||||
| 		ldy  #0 | ||||
| -		lda  conv.uword2decimal.decTenThousands,y | ||||
|         beq  + | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		plx | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_uw  (uword value @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print the uword in A/Y in decimal form, without left padding 0s | ||||
| 	%asm {{ | ||||
| 	    phx | ||||
| 		jsr  conv.uword2decimal | ||||
| 		plx | ||||
| 		ldy  #0 | ||||
| -		lda  conv.uword2decimal.decTenThousands,y | ||||
| 		beq  _allzero | ||||
| 		cmp  #'0' | ||||
| 		bne  _gotdigit | ||||
| 		iny | ||||
| 		bne  - | ||||
|  | ||||
| _gotdigit | ||||
| 		jsr  c64.CHROUT | ||||
| 		iny | ||||
| 		lda  conv.uword2decimal.decTenThousands,y | ||||
| 		bne  _gotdigit | ||||
| 		rts | ||||
| _allzero | ||||
|         lda  #'0' | ||||
|         jmp  c64.CHROUT | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  print_w  (word value @ AY) clobbers(A,Y)  { | ||||
| 	; ---- print the (signed) word in A/Y in decimal form, without left padding 0's | ||||
| 	%asm {{ | ||||
| 		cpy  #0 | ||||
| 		bpl  + | ||||
| 		pha | ||||
| 		lda  #'-' | ||||
| 		jsr  c64.CHROUT | ||||
| 		tya | ||||
| 		eor  #255 | ||||
| 		tay | ||||
| 		pla | ||||
| 		eor  #255 | ||||
| 		clc | ||||
| 		adc  #1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		bra  print_uw | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  input_chars  (uword buffer @ AY) clobbers(A) -> ubyte @ Y  { | ||||
| 	; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well) | ||||
| 	;      It assumes the keyboard is selected as I/O channel! | ||||
|  | ||||
| 	%asm {{ | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #0				; char counter = 0 | ||||
| -		jsr  c64.CHRIN | ||||
| 		cmp  #$0d			; return (ascii 13) pressed? | ||||
| 		beq  +				; yes, end. | ||||
| 		sta  (P8ZP_SCRATCH_W1),y	; else store char in buffer | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		lda  #0 | ||||
| 		sta  (P8ZP_SCRATCH_W1),y	; finish string with 0 byte | ||||
| 		rts | ||||
|  | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  setchr  (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A)  { | ||||
| 	; ---- sets the character in the screen matrix at the given position | ||||
| 	%asm {{ | ||||
|             pha | ||||
|             txa | ||||
|             asl  a | ||||
|             stz  cx16.VERA_CTRL | ||||
|             stz  cx16.VERA_ADDR_H | ||||
|             sta  cx16.VERA_ADDR_L | ||||
|             sty  cx16.VERA_ADDR_M | ||||
|             pla | ||||
|             sta  cx16.VERA_DATA0 | ||||
|             rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  getchr  (ubyte col @A, ubyte row @Y) -> ubyte @ A { | ||||
| 	; ---- get the character in the screen matrix at the given location | ||||
| 	%asm  {{ | ||||
|             asl  a | ||||
|             stz  cx16.VERA_CTRL | ||||
|             stz  cx16.VERA_ADDR_H | ||||
|             sta  cx16.VERA_ADDR_L | ||||
|             sty  cx16.VERA_ADDR_M | ||||
|             lda  cx16.VERA_DATA0 | ||||
|             rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 	;      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 {{ | ||||
|             pha | ||||
|             txa | ||||
|             asl  a | ||||
|             ina | ||||
|             stz  cx16.VERA_CTRL | ||||
|             stz  cx16.VERA_ADDR_H | ||||
|             sta  cx16.VERA_ADDR_L | ||||
|             sty  cx16.VERA_ADDR_M | ||||
|             pla | ||||
|             sta  cx16.VERA_DATA0 | ||||
|             rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub  getclr  (ubyte col @A, ubyte row @Y) -> ubyte @ A { | ||||
| 	; ---- get the color in the screen color matrix at the given location | ||||
| 	%asm  {{ | ||||
|             asl  a | ||||
|             ina | ||||
|             stz  cx16.VERA_CTRL | ||||
|             stz  cx16.VERA_ADDR_H | ||||
|             sta  cx16.VERA_ADDR_L | ||||
|             sty  cx16.VERA_ADDR_M | ||||
|             lda  cx16.VERA_DATA0 | ||||
|             rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| sub  setcc  (ubyte column, ubyte row, ubyte char, ubyte charcolor)  { | ||||
| 	; ---- 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 {{ | ||||
|             phx | ||||
|             lda  column | ||||
|             asl  a | ||||
|             tax | ||||
|             ldy  row | ||||
|             lda  charcolor | ||||
|             and  #$0f | ||||
|             sta  P8ZP_SCRATCH_B1 | ||||
|             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  cx16.VERA_DATA0 | ||||
|             and  #$f0 | ||||
|             ora  P8ZP_SCRATCH_B1 | ||||
|             sta  cx16.VERA_DATA0 | ||||
|             plx | ||||
|             rts | ||||
|     }} | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| 	; ---- safe wrapper around PLOT kernal routine, to save the X register. | ||||
| 	%asm  {{ | ||||
| 		phx | ||||
| 		tax | ||||
| 		clc | ||||
| 		jsr  c64.PLOT | ||||
| 		plx | ||||
| 		rts | ||||
| 	}} | ||||
| } | ||||
|  | ||||
| asmsub width() clobbers(X,Y) -> ubyte @A { | ||||
|     ; -- returns the text screen width (number of columns) | ||||
|     %asm {{ | ||||
|         jsr  c64.SCREEN | ||||
|         txa | ||||
|         rts | ||||
|     }} | ||||
| } | ||||
|  | ||||
| asmsub height() clobbers(X, Y) -> ubyte @A { | ||||
|     ; -- returns the text screen height (number of rows) | ||||
|     %asm {{ | ||||
|         jsr  c64.SCREEN | ||||
|         tya | ||||
|         rts | ||||
|     }} | ||||
| } | ||||
|  | ||||
| } | ||||
							
								
								
									
										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) | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,11 +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 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %import c64lib | ||||
|  | ||||
| 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,11 +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 | ||||
|  | ||||
| %import c64lib | ||||
|  | ||||
| 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 @@ | ||||
| 3.0 | ||||
| 7.0-BETA2 | ||||
|   | ||||
| @@ -4,12 +4,10 @@ import kotlinx.cli.* | ||||
| import prog8.ast.base.AstException | ||||
| import prog8.compiler.CompilationResult | ||||
| import prog8.compiler.compileProgram | ||||
| import prog8.compiler.target.CompilationTarget | ||||
| import prog8.compiler.target.c64.C64MachineDefinition | ||||
| import prog8.compiler.target.c64.Petscii | ||||
| import prog8.compiler.target.c64.codegen.AsmGen | ||||
| import prog8.compiler.target.C64Target | ||||
| import prog8.compiler.target.Cx16Target | ||||
| import prog8.parser.ParsingFailedError | ||||
| import java.io.IOException | ||||
| import java.io.File | ||||
| import java.nio.file.FileSystems | ||||
| import java.nio.file.Path | ||||
| import java.nio.file.StandardWatchEventKinds | ||||
| @@ -34,73 +32,79 @@ fun pathFrom(stringPath: String, vararg rest: String): Path  = FileSystems.getDe | ||||
|  | ||||
|  | ||||
| private fun compileMain(args: Array<String>) { | ||||
|     val cli = CommandLineInterface("prog8compiler") | ||||
|     val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation") | ||||
|     val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".") | ||||
|     val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code") | ||||
|     val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations") | ||||
|     val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed") | ||||
|     val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64") | ||||
|     val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1) | ||||
|     val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM) | ||||
|     val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation") | ||||
|     val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") | ||||
|     val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") | ||||
|     val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") | ||||
|     val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed") | ||||
|     val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") | ||||
|     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 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 { | ||||
|         cli.parse(args) | ||||
|     } catch (e: Exception) { | ||||
|     } catch (e: IllegalStateException) { | ||||
|         System.err.println(e.message) | ||||
|         exitProcess(1) | ||||
|     } | ||||
|  | ||||
|     when(compilationTarget) { | ||||
|         "c64" -> { | ||||
|             with(CompilationTarget) { | ||||
|                 name = "c64" | ||||
|                 machine = C64MachineDefinition | ||||
|                 encodeString = { str, altEncoding -> | ||||
|                     if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true) | ||||
|                 } | ||||
|                 decodeString = { bytes, altEncoding -> | ||||
|                     if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true) | ||||
|                 } | ||||
|                 asmGenerator = ::AsmGen | ||||
|             } | ||||
|         } | ||||
|         else -> { | ||||
|             System.err.println("invalid compilation target") | ||||
|             exitProcess(1) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     val outputPath = pathFrom(outputDir) | ||||
|     if(!outputPath.toFile().isDirectory) { | ||||
|         System.err.println("Output path doesn't exist") | ||||
|         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 allImportedFiles = mutableSetOf<Path>() | ||||
|  | ||||
|         while(true) { | ||||
|             val filepath = pathFrom(moduleFiles.single()).normalize() | ||||
|             println("Continuous watch mode active. Main module: $filepath") | ||||
|             println("Continuous watch mode active. Modules: $moduleFiles") | ||||
|             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, outputDir=outputPath) | ||||
|             println("Imported files (now watching:)") | ||||
|                 for (importedFile in compilationResult.importedFiles) { | ||||
|             for (importedFile in allImportedFiles) { | ||||
|                 print("  ") | ||||
|                 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.") | ||||
|  | ||||
|             var recompile=false | ||||
|             while(!recompile) { | ||||
|                 val event = watchservice.take() | ||||
|                 for (changed in event.pollEvents()) { | ||||
|                     val changedPath = changed.context() as Path | ||||
|                     if(allImportedFiles.any { it.fileName == changedPath.fileName }) { | ||||
|                         println("  change detected: $changedPath") | ||||
|                         recompile = true | ||||
|                     } | ||||
|                 } | ||||
|                 event.reset() | ||||
|                 println("\u001b[H\u001b[2J")      // clear the screen | ||||
|             } catch (x: Exception) { | ||||
|                 throw x | ||||
|             } | ||||
|  | ||||
|             println("\u001b[H\u001b[2J")      // clear the screen | ||||
|         } | ||||
|  | ||||
|     } else { | ||||
| @@ -108,7 +112,7 @@ private fun compileMain(args: Array<String>) { | ||||
|             val filepath = pathFrom(filepathRaw).normalize() | ||||
|             val compilationResult: CompilationResult | ||||
|             try { | ||||
|                 compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath) | ||||
|                 compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) | ||||
|                 if(!compilationResult.success) | ||||
|                     exitProcess(1) | ||||
|             } catch (x: ParsingFailedError) { | ||||
| @@ -117,24 +121,11 @@ private fun compileMain(args: Array<String>) { | ||||
|                 exitProcess(1) | ||||
|             } | ||||
|  | ||||
|             if (startEmulator) { | ||||
|             if (startEmulator==true) { | ||||
|                 if (compilationResult.programName.isEmpty()) | ||||
|                     println("\nCan't start emulator because no program was assembled.") | ||||
|                 else if(startEmulator) { | ||||
|                     for(emulator in listOf("x64sc", "x64")) { | ||||
|                         println("\nStarting C-64 emulator $emulator...") | ||||
|                         val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list", | ||||
|                                 "-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg") | ||||
|                         val processb = ProcessBuilder(cmdline).inheritIO() | ||||
|                         val process: Process | ||||
|                         try { | ||||
|                             process=processb.start() | ||||
|                         } catch(x: IOException) { | ||||
|                             continue  // try the next emulator executable | ||||
|                         } | ||||
|                         process.waitFor() | ||||
|                         break | ||||
|                     } | ||||
|                 else { | ||||
|                     compilationResult.compTarget.machine.launchEmulator(compilationResult.programName) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -1,82 +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 | ||||
| import prog8.optimizer.AssignmentTransformer | ||||
|  | ||||
|  | ||||
| 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 Program.transformAssignments(errors: ErrorReporter) { | ||||
|     val transform = AssignmentTransformer(this, errors) | ||||
|     transform.visit(this) | ||||
|     while(transform.optimizationsDone>0 && errors.isEmpty()) { | ||||
|         transform.applyModifications() | ||||
|         transform.optimizationsDone = 0 | ||||
|         transform.visit(this) | ||||
|     } | ||||
|     transform.applyModifications() | ||||
| } | ||||
|  | ||||
| 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,176 +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 after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { | ||||
|         if(functionCallStatement.target.nameInSource == listOf("swap")) { | ||||
|             // if x and y are both just identifiers, do not rewrite (there should be asm generation for that) | ||||
|             // otherwise: | ||||
|             // rewrite swap(x,y) as follows: | ||||
|             // - declare a temp variable of the same datatype | ||||
|             // - temp = x, x = y, y= temp | ||||
|             val first = functionCallStatement.args[0] | ||||
|             val second = functionCallStatement.args[1] | ||||
|             if(first !is IdentifierReference && second !is IdentifierReference) { | ||||
|                 val dt = first.inferType(program).typeOrElse(DataType.STRUCT) | ||||
|                 val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}" | ||||
|                 val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position) | ||||
|                 val tempvar = IdentifierReference(listOf(tempname), first.position) | ||||
|                 val assignTemp = Assignment( | ||||
|                         AssignTarget(tempvar, null, null, first.position), | ||||
|                         null, | ||||
|                         first, | ||||
|                         first.position | ||||
|                 ) | ||||
|                 val assignFirst = Assignment( | ||||
|                         AssignTarget.fromExpr(first), | ||||
|                         null, | ||||
|                         second, | ||||
|                         first.position | ||||
|                 ) | ||||
|                 val assignSecond = Assignment( | ||||
|                         AssignTarget.fromExpr(second), | ||||
|                         null, | ||||
|                         tempvar, | ||||
|                         first.position | ||||
|                 ) | ||||
|                 val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position) | ||||
|                 return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent)) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { | ||||
|         if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") { | ||||
|             // lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte" | ||||
|             val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position) | ||||
|             return listOf(IAstModification.ReplaceNode( | ||||
|                     functionCall, typecast, parent | ||||
|             )) | ||||
|         } | ||||
|  | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     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,184 +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) augmented assignment is turned into regular assignment. | ||||
|     // - (syntax desugaring) struct value assignment is expanded into several struct member assignments. | ||||
|     // - 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(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||
|         val declValue = decl.value | ||||
|         if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) { | ||||
|             val declConstValue = declValue.constValue(program) | ||||
|             if(declConstValue==null) { | ||||
|                 // move the vardecl (without value) to the scope and replace this with a regular assignment | ||||
|                 decl.value = null | ||||
|                 val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) | ||||
|                 val assign = Assignment(target, null, declValue, decl.position) | ||||
|                 return listOf( | ||||
|                         IAstModification.ReplaceNode(decl, assign, parent), | ||||
|                         IAstModification.InsertFirst(decl, decl.definingScope() as Node) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|         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> { | ||||
|         if(assignment.aug_op!=null) { | ||||
|             return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent)) | ||||
|         } | ||||
|  | ||||
|         val valueType = assignment.value.inferType(program) | ||||
|         val targetType = assignment.target.inferType(program, assignment) | ||||
|         if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) { | ||||
|             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 | ||||
|     } | ||||
|  | ||||
|     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), | ||||
|                     null, 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)!! | ||||
|                 if (sourceVar.struct == null) | ||||
|                     throw FatalAstException("can only assign arrays or structs to structs") | ||||
|                 // 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 | ||||
|                 } | ||||
|                 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), | ||||
|                             null, sourceIdref, member.second.position) | ||||
|                     assign.linkParents(structAssignment) | ||||
|                     assign | ||||
|                 } | ||||
|             } | ||||
|             is ArrayLiteralValue -> { | ||||
|                 throw IllegalArgumentException("not going to flatten a structLv assignment here") | ||||
|             } | ||||
|             else -> throw FatalAstException("strange struct value") | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,43 +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) | ||||
|             return listOf(IAstModification.ReplaceNode(typecast, value, parent)) | ||||
|         } | ||||
|  | ||||
|         return noModifications | ||||
|     } | ||||
| } | ||||
| @@ -1,42 +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) | ||||
|             = checkTypes(functionCall as IFunctionCall, functionCall.definingScope()) | ||||
|  | ||||
|     override fun visit(functionCallStatement: FunctionCallStatement) | ||||
|             = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope()) | ||||
|  | ||||
|     private fun checkTypes(call: IFunctionCall, scope: INameScope) { | ||||
|         val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) } | ||||
|         val target = call.target.targetStatement(scope) | ||||
|         when(target) { | ||||
|             is Subroutine -> { | ||||
|                 val paramtypes = target.parameters.map { it.type } | ||||
|                 if(argtypes!=paramtypes) | ||||
|                     throw CompilerException("parameter type mismatch $call") | ||||
|             } | ||||
|             is BuiltinFunctionStatementPlaceholder -> { | ||||
|                 val func = BuiltinFunctions.getValue(target.name) | ||||
|                 val paramtypes = func.parameters.map { it.possibleDatatypes } | ||||
|                 for(x in argtypes.zip(paramtypes)) { | ||||
|                     if(x.first !in x.second) | ||||
|                         throw CompilerException("parameter type mismatch $call") | ||||
|                 } | ||||
|             } | ||||
|             else -> {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,61 +1,126 @@ | ||||
| package prog8.compiler | ||||
|  | ||||
| import prog8.ast.IFunctionCall | ||||
| import prog8.ast.Node | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.processing.AstWalker | ||||
| import prog8.ast.processing.IAstModification | ||||
| 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() { | ||||
|  | ||||
|     private val noModifications = emptyList<IAstModification>() | ||||
| internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() { | ||||
|  | ||||
|     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||
|         if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) { | ||||
|             // a numeric vardecl without an initial value is initialized with zero. | ||||
|         subroutineVariables.add(decl.name to decl) | ||||
|         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, | ||||
|             // 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 | ||||
|                 if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY)) | ||||
|                     decl.value = null | ||||
|                 else { | ||||
|                     decl.value = decl.zeroElementValue() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { | ||||
|         // Try to replace A = B <operator> Something  by A= B, A = A <operator> Something | ||||
|         // this triggers the more efficent augmented assignment code generation more often. | ||||
|         // But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF. | ||||
|         if(!assignment.isAugmentable | ||||
|                 && assignment.target.identifier != null | ||||
|                 && compTarget.isInRegularRAM(assignment.target, program)) { | ||||
|             val binExpr = assignment.value as? BinaryExpression | ||||
|             if (binExpr != null && binExpr.operator !in comparisonOperators) { | ||||
|                 if (binExpr.left !is BinaryExpression) { | ||||
|                     if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) { | ||||
|                         // the right part of the expression contains the target variable itself. | ||||
|                         // we can't 'split' it trivially because the variable will be changed halfway through. | ||||
|                         if(binExpr.operator in associativeOperators) { | ||||
|                             // A = <something-without-A>  <associativeoperator>  <otherthing-with-A> | ||||
|                             // use the other part of the expression to split. | ||||
|                             val assignRight = Assignment(assignment.target, binExpr.right, assignment.position) | ||||
|                             return listOf( | ||||
|                                     IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()), | ||||
|                                     IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr), | ||||
|                                     IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) | ||||
|                         } | ||||
|                     } else { | ||||
|                         val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position) | ||||
|                         return listOf( | ||||
|                                 IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()), | ||||
|                                 IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>() | ||||
|     private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>() | ||||
|  | ||||
|     override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { | ||||
|         subroutineVariables.clear() | ||||
|         addedIfConditionVars.clear() | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     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 { it.name to it }) | ||||
|  | ||||
|         val sub = scope.definingSubroutine() | ||||
|         if (sub != null) { | ||||
|             val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name } | ||||
|             var conflicts = false | ||||
|             decls.forEach { | ||||
|                 val existing = existingVariables[it.name] | ||||
|                 if (existing != null) { | ||||
|                     errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position) | ||||
|                     conflicts = true | ||||
|             // move any remaining vardecls of the scope into the upper scope. Make sure the position remains the same! | ||||
|             val replacements = mutableListOf<IAstModification>() | ||||
|             val movements = mutableListOf<IAstModification.InsertFirst>() | ||||
|  | ||||
|             for(decl in decls) { | ||||
|                 if(decl.value!=null && decl.datatype in NumericDatatypes) { | ||||
|                     val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) | ||||
|                     val assign = Assignment(target, decl.value!!, decl.position) | ||||
|                     replacements.add(IAstModification.ReplaceNode(decl, assign, scope)) | ||||
|                     decl.value = null | ||||
|                     decl.allowInitializeWithZero = false | ||||
|                 } else { | ||||
|                     replacements.add(IAstModification.Remove(decl, scope)) | ||||
|                 } | ||||
|                 movements.add(IAstModification.InsertFirst(decl, sub)) | ||||
|             } | ||||
|             if (!conflicts) { | ||||
|                 val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes } | ||||
|                 return numericVarsWithValue.map { | ||||
|                     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) | ||||
|                     val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position) | ||||
|                     val assign = Assignment(target, null, initValue, it.position) | ||||
|                     initValue.parent = assign | ||||
|                     IAstModification.InsertFirst(assign, scope) | ||||
|                 } +  decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } + | ||||
|                      decls.map { IAstModification.InsertFirst(it, sub) }    // move it up to the subroutine | ||||
|             } | ||||
|             return replacements + movements | ||||
|         } | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { | ||||
|         // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine. | ||||
|         val firstDeclarations = mutableMapOf<String, VarDecl>() | ||||
|         for(decl in subroutineVariables) { | ||||
|             val existing = firstDeclarations[decl.first] | ||||
|             if(existing!=null && existing !== decl.second) { | ||||
|                 errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position) | ||||
|             } else { | ||||
|                 firstDeclarations[decl.first] = decl.second | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         // 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. | ||||
|         val mods = mutableListOf<IAstModification>() | ||||
|         val returnStmt = Return(null, subroutine.position) | ||||
|         if (subroutine.asmAddress == null | ||||
|                 && !subroutine.inline | ||||
|                 && subroutine.statements.isNotEmpty() | ||||
|                 && subroutine.amountOfRtsInAsm() == 0 | ||||
|                 && subroutine.statements.lastOrNull { it !is VarDecl } !is Return | ||||
| @@ -72,9 +137,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | ||||
|                 && outerStatements[subroutineStmtIdx - 1] !is Subroutine | ||||
|                 && outerStatements[subroutineStmtIdx - 1] !is Return | ||||
|                 && outerScope !is Block) { | ||||
|             mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node) | ||||
|             mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope) | ||||
|         } | ||||
|  | ||||
|         return mods | ||||
|     } | ||||
|  | ||||
| @@ -82,20 +146,37 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | ||||
|         // see if we can remove superfluous typecasts (outside of expressions) | ||||
|         // 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. | ||||
|         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 | ||||
|                 || typecast.type in WordDatatypes && sourceDt in WordDatatypes) { | ||||
|             if(typecast.parent !is Expression) { | ||||
|                 return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent)) | ||||
|             } | ||||
|         } | ||||
|         else if(sourceDt in PassByReferenceDatatypes) { | ||||
|  | ||||
|  | ||||
|         // Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires | ||||
|         // that the types of assignment values and their target are the same, | ||||
|         // and that the types of both operands of a binaryexpression node are the same. | ||||
|         // So, it is not easily possible to remove the typecasts that are there to make these conditions true. | ||||
|         // The only place for now where we can do this is for: | ||||
|         //    asmsub register pair parameter. | ||||
|  | ||||
|         if(sourceDt in PassByReferenceDatatypes) { | ||||
|             if(typecast.type==DataType.UWORD) { | ||||
|                 if(typecast.expression is IdentifierReference) { | ||||
|                     return listOf(IAstModification.ReplaceNode( | ||||
|                             typecast, | ||||
|                             AddressOf(typecast.expression as IdentifierReference, typecast.position), | ||||
|                             parent | ||||
|                     )) | ||||
|                 } else if(typecast.expression is IFunctionCall) { | ||||
|                     return listOf(IAstModification.ReplaceNode( | ||||
|                             typecast, | ||||
|                             typecast.expression, | ||||
|                             parent | ||||
|                     )) | ||||
|                 } | ||||
|             } else { | ||||
|                 errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position) | ||||
|             } | ||||
| @@ -103,4 +184,171 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E | ||||
|  | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> { | ||||
|         val binExpr = ifStatement.condition as? BinaryExpression | ||||
|         if(binExpr==null || binExpr.operator !in comparisonOperators) { | ||||
|             // if x  ->  if x!=0,    if x+5  ->  if x+5 != 0 | ||||
|             val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position) | ||||
|             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 | ||||
|     } | ||||
|  | ||||
| //    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> { | ||||
|         val binExpr = untilLoop.condition as? BinaryExpression | ||||
|         if(binExpr==null || binExpr.operator !in comparisonOperators) { | ||||
|             // until x  ->  until x!=0,    until x+5  ->  until x+5 != 0 | ||||
|             val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position) | ||||
|             return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop)) | ||||
|         } | ||||
|         return noModifications | ||||
|     } | ||||
|  | ||||
|     override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> { | ||||
|         val binExpr = whileLoop.condition as? BinaryExpression | ||||
|         if(binExpr==null || binExpr.operator !in comparisonOperators) { | ||||
|             // while x  ->  while x!=0,    while x+5  ->  while x+5 != 0 | ||||
|             val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position) | ||||
|             return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop)) | ||||
|         } | ||||
|         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 | ||||
|  | ||||
| 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.InputStream | ||||
| import java.nio.file.Path | ||||
| import kotlin.math.abs | ||||
| import kotlin.system.exitProcess | ||||
| import kotlin.system.measureTimeMillis | ||||
|  | ||||
|  | ||||
| enum class OutputType { | ||||
|     RAW, | ||||
| @@ -27,27 +48,300 @@ data class CompilationOptions(val output: OutputType, | ||||
|                               val launcher: LauncherType, | ||||
|                               val zeropage: ZeropageType, | ||||
|                               val zpReserved: List<IntRange>, | ||||
|                               val floats: Boolean) | ||||
|                               val floats: Boolean, | ||||
|                               val noSysInit: Boolean, | ||||
|                               val compTarget: ICompilationTarget) { | ||||
|     var slowCodegenWarnings = false | ||||
|     var optimize = false | ||||
| } | ||||
|  | ||||
|  | ||||
| class CompilerException(message: String?) : Exception(message) | ||||
|  | ||||
| fun Number.toHex(): String { | ||||
|     //  0..15 -> "0".."15" | ||||
|     //  16..255 -> "$10".."$ff" | ||||
|     //  256..65536 -> "$0100".."$ffff" | ||||
|     // negative values are prefixed with '-'. | ||||
|     val integer = this.toInt() | ||||
|     if(integer<0) | ||||
|         return '-' + abs(integer).toHex() | ||||
|     return when (integer) { | ||||
|         in 0 until 16 -> integer.toString() | ||||
|         in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0') | ||||
|         in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0') | ||||
|         else -> throw CompilerException("number too large for 16 bits $this") | ||||
| class CompilationResult(val success: Boolean, | ||||
|                         val programAst: Program, | ||||
|                         val programName: String, | ||||
|                         val compTarget: ICompilationTarget, | ||||
|                         val importedFiles: List<Path>) | ||||
|  | ||||
|  | ||||
| fun compileProgram(filepath: Path, | ||||
|                    optimize: Boolean, | ||||
|                    writeAssembly: Boolean, | ||||
|                    slowCodegenWarnings: Boolean, | ||||
|                    compilationTarget: String, | ||||
|                    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 { | ||||
|     return if (filename.startsWith("library:")) { | ||||
|         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 | ||||
| 
 | ||||
| 
 | ||||
| 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 { | ||||
|         WARNING, | ||||
|         ERROR | ||||
| @@ -13,10 +22,14 @@ class ErrorReporter { | ||||
|     private val messages = mutableListOf<CompilerMessage>() | ||||
|     private val alreadyReportedMessages = mutableSetOf<String>() | ||||
| 
 | ||||
|     fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position)) | ||||
|     fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position)) | ||||
|     override fun err(msg: String, position: 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 numWarnings = 0 | ||||
|         messages.forEach { | ||||
| @@ -24,7 +37,7 @@ class ErrorReporter { | ||||
|                 MessageSeverity.ERROR -> System.err.print("\u001b[91m")  // bright red | ||||
|                 MessageSeverity.WARNING -> System.err.print("\u001b[93m")  // bright yellow | ||||
|             } | ||||
|             val msg = "${it.position} ${it.severity} ${it.message}".trim() | ||||
|             val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim() | ||||
|             if(msg !in alreadyReportedMessages) { | ||||
|                 System.err.println(msg) | ||||
|                 alreadyReportedMessages.add(msg) | ||||
| @@ -40,5 +53,5 @@ class ErrorReporter { | ||||
|             throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.") | ||||
|     } | ||||
| 
 | ||||
|     fun isEmpty() = messages.isEmpty() | ||||
|     override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR } | ||||
| } | ||||
| @@ -1,216 +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.CompilationTarget | ||||
| 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.measureTimeMillis | ||||
|  | ||||
|  | ||||
| class CompilationResult(val success: Boolean, | ||||
|                         val programAst: Program, | ||||
|                         val programName: String, | ||||
|                         val importedFiles: List<Path>) | ||||
|  | ||||
|  | ||||
| fun compileProgram(filepath: Path, | ||||
|                    optimize: Boolean, | ||||
|                    writeAssembly: Boolean, | ||||
|                    outputDir: Path): CompilationResult { | ||||
|     var programName = "" | ||||
|     lateinit var programAst: Program | ||||
|     lateinit var importedFiles: List<Path> | ||||
|     val errors = ErrorReporter() | ||||
|  | ||||
|     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("Parsing...") | ||||
|     val importer = ModuleImporter(errors) | ||||
|     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.") | ||||
|  | ||||
|     // if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries | ||||
|     if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) { | ||||
|         importer.importLibraryModule(programAst, "c64lib") | ||||
|         importer.importLibraryModule(programAst, "c64utils") | ||||
|     } | ||||
|  | ||||
|     // 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 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 | ||||
|                 } | ||||
|     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() | ||||
|  | ||||
|     return CompilationOptions( | ||||
|             if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), | ||||
|             if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), | ||||
|             zpType, zpReserved, floatsEnabled | ||||
|     ) | ||||
| } | ||||
|  | ||||
| private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) { | ||||
|     // perform initial syntax checks and processings | ||||
|     println("Processing...") | ||||
|     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 could give rise to more constants that can be folded away: | ||||
|         errors.handle() | ||||
|         if (optsDone1 + optsDone2 == 0) | ||||
|             break | ||||
|     } | ||||
|  | ||||
|     val remover = UnusedCodeRemover() | ||||
|     remover.visit(programAst) | ||||
|     remover.applyModifications() | ||||
| } | ||||
|  | ||||
| private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) { | ||||
|     programAst.transformAssignments(errors) | ||||
|     errors.handle() | ||||
|     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, | ||||
|     val zeropage = CompilationTarget.machine.getZeropage(compilerOptions) | ||||
|     programAst.processAstBeforeAsmGeneration(errors) | ||||
|     errors.handle() | ||||
|  | ||||
|     // printAst(programAst) | ||||
|  | ||||
|     val assembly = CompilationTarget.asmGenerator( | ||||
|             programAst, | ||||
|             errors, | ||||
|             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() | ||||
| } | ||||
|  | ||||
| @@ -8,15 +8,43 @@ class ZeropageDepletedError(message: String) : Exception(message) | ||||
|  | ||||
| abstract class Zeropage(protected val options: CompilationOptions) { | ||||
|  | ||||
|     abstract val SCRATCH_B1 : Int      // temp storage for a single byte | ||||
|     abstract val SCRATCH_REG : Int     // temp storage for a register | ||||
|     abstract val SCRATCH_W1 : Int      // temp storage 1 for a word  $fb+$fc | ||||
|     abstract val SCRATCH_W2 : Int      // temp storage 2 for a word  $fb+$fc | ||||
|  | ||||
|  | ||||
|     private val allocations = mutableMapOf<Int, Pair<String, DataType>>() | ||||
|     val free = mutableListOf<Int>()     // subclasses must set this to the appropriate free locations. | ||||
|  | ||||
|     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 { | ||||
|         assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"} | ||||
|         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"} | ||||
|  | ||||
|         if(options.zeropage==ZeropageType.DONTUSE) | ||||
|             throw CompilerException("zero page usage has been disabled") | ||||
| @@ -39,13 +67,13 @@ abstract class Zeropage(protected val options: CompilationOptions) { | ||||
|  | ||||
|         if(free.size > 0) { | ||||
|             if(size==1) { | ||||
|                 for(candidate in free.min()!! .. free.max()!!+1) { | ||||
|                 for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) { | ||||
|                     if(loneByte(candidate)) | ||||
|                         return makeAllocation(candidate, 1, datatype, scopedname) | ||||
|                 } | ||||
|                 return makeAllocation(free[0], 1, datatype, scopedname) | ||||
|             } | ||||
|             for(candidate in free.min()!! .. free.max()!!+1) { | ||||
|             for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) { | ||||
|                 if (sequentialFree(candidate, size)) | ||||
|                     return makeAllocation(candidate, size, datatype, scopedname) | ||||
|             } | ||||
| @@ -58,18 +86,10 @@ abstract class Zeropage(protected val options: CompilationOptions) { | ||||
|  | ||||
|     private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { | ||||
|         free.removeAll(address until address+size) | ||||
|         allocations[address] = Pair(name ?: "<unnamed>", datatype) | ||||
|         allocations[address] = (name ?: "<unnamed>") to datatype | ||||
|         return address | ||||
|     } | ||||
|  | ||||
|     private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free | ||||
|     private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList()) | ||||
|  | ||||
|     enum class ExitProgramStrategy { | ||||
|         CLEAN_EXIT, | ||||
|         SYSTEM_RESET | ||||
|     } | ||||
|  | ||||
|     abstract val exitProgramStrategy: ExitProgramStrategy | ||||
|  | ||||
| } | ||||
|   | ||||
										
											
												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.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.base.Position | ||||
| import prog8.ast.expressions.StringLiteralValue | ||||
| import prog8.ast.statements.* | ||||
| import prog8.compiler.target.CompilationTarget | ||||
| import prog8.functions.BuiltinFunctions | ||||
| import prog8.ast.walk.IAstVisitor | ||||
| 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 fun nameError(name: String, position: Position, existing: Statement) { | ||||
| @@ -22,56 +24,58 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | ||||
|     } | ||||
| 
 | ||||
|     override fun visit(block: Block) { | ||||
|         if(block.name in compTarget.machine.opcodeNames) | ||||
|             errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position) | ||||
| 
 | ||||
|         val existing = blocks[block.name] | ||||
|         if(existing!=null) | ||||
|             nameError(block.name, block.position, existing) | ||||
|         else | ||||
|             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) | ||||
|     } | ||||
| 
 | ||||
|     override fun visit(directive: Directive) { | ||||
|         if(directive.directive=="%target") { | ||||
|             val compatibleTarget = directive.args.single().name | ||||
|             if (compatibleTarget != compTarget.name) | ||||
|                 errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position) | ||||
|         } | ||||
| 
 | ||||
|         super.visit(directive) | ||||
|     } | ||||
| 
 | ||||
|     override fun visit(decl: VarDecl) { | ||||
|         decl.datatypeErrors.forEach { errors.err(it.message, it.position) } | ||||
| 
 | ||||
|         if(decl.name in BuiltinFunctions) | ||||
|             errors.err("builtin function cannot be redefined", decl.position) | ||||
| 
 | ||||
|         if(decl.name in CompilationTarget.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) | ||||
| 
 | ||||
|         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) | ||||
|         if (existing != null && existing !== decl) | ||||
|             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) | ||||
|     } | ||||
| 
 | ||||
|     override fun visit(subroutine: Subroutine) { | ||||
|         if(subroutine.name in CompilationTarget.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) | ||||
|         } else if(subroutine.name in BuiltinFunctions) { | ||||
|             // the builtin functions can't be redefined | ||||
| @@ -85,15 +89,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | ||||
|             if (existing != null && existing !== subroutine) | ||||
|                 nameError(subroutine.name, subroutine.position, existing) | ||||
| 
 | ||||
|             // does the parameter redefine a variable declared elsewhere? | ||||
|             for(param in subroutine.parameters) { | ||||
|                 val existingVar = subroutine.lookup(listOf(param.name), subroutine) | ||||
|                 if (existingVar != null && existingVar.parent !== subroutine) { | ||||
|                     nameError(param.name, param.position, existingVar) | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // 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 namesInSub = symbolsInSub.map{ it.first }.toSet() | ||||
|             val paramNames = subroutine.parameters.map { it.name }.toSet() | ||||
| @@ -102,9 +98,9 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | ||||
|                 val labelOrVar = subroutine.getLabelOrVariable(name) | ||||
|                 if(labelOrVar!=null && labelOrVar.position != subroutine.position) | ||||
|                     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) | ||||
|                     nameError(name, sub.position, subroutine) | ||||
|                     nameError(name, subroutine.position, sub) | ||||
|             } | ||||
| 
 | ||||
|             if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { | ||||
| @@ -116,7 +112,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | ||||
|     } | ||||
| 
 | ||||
|     override fun visit(label: Label) { | ||||
|         if(label.name in CompilationTarget.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) | ||||
| 
 | ||||
|         if(label.name in BuiltinFunctions) { | ||||
| @@ -138,19 +134,9 @@ internal class AstIdentifiersChecker(private val program: Program, private val e | ||||
|     } | ||||
| 
 | ||||
|     override fun visit(string: StringLiteralValue) { | ||||
|         if (string.value.length !in 1..255) | ||||
|             errors.err("string literal length must be between 1 and 255", string.position) | ||||
|         if (string.value.length > 255) | ||||
|             errors.err("string literal length max is 255", string.position) | ||||
| 
 | ||||
|         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,29 +1,53 @@ | ||||
| package prog8.ast.processing | ||||
| package prog8.compiler.astprocessing | ||||
| 
 | ||||
| import prog8.ast.IFunctionCall | ||||
| import prog8.ast.INameScope | ||||
| import prog8.ast.Node | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| 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. | ||||
|      * (this includes function call arguments) | ||||
|      */ | ||||
| 
 | ||||
|     private val noModifications = emptyList<IAstModification>() | ||||
|     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||
|         val declValue = decl.value | ||||
|         if(decl.type==VarDeclType.VAR && declValue!=null) { | ||||
|             val valueDt = declValue.inferType(program) | ||||
|             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( | ||||
|                         declValue, | ||||
|                         TypecastExpression(declValue, decl.datatype, true, declValue.position), | ||||
|                         decl | ||||
|                 )) | ||||
|             } | ||||
|         } | ||||
|         return noModifications | ||||
|     } | ||||
| 
 | ||||
|     override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { | ||||
|         val leftDt = expr.left.inferType(program) | ||||
|         val rightDt = expr.right.inferType(program) | ||||
|         if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) { | ||||
|             // 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) { | ||||
|                 return when { | ||||
|                     toFix===expr.left -> listOf(IAstModification.ReplaceNode( | ||||
| @@ -40,19 +64,27 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | ||||
|     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 | ||||
|         val valueItype = assignment.value.inferType(program) | ||||
|         val targetItype = assignment.target.inferType(program, assignment) | ||||
|         val targetItype = assignment.target.inferType(program) | ||||
|         if(targetItype.isKnown && valueItype.isKnown) { | ||||
|             val targettype = targetItype.typeOrElse(DataType.STRUCT) | ||||
|             val valuetype = valueItype.typeOrElse(DataType.STRUCT) | ||||
|             val targettype = targetItype.typeOrElse(DataType.UNDEFINED) | ||||
|             val valuetype = valueItype.typeOrElse(DataType.UNDEFINED) | ||||
|             if (valuetype != 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( | ||||
|                             assignment.value, | ||||
|                             TypecastExpression(assignment.value, targettype, true, assignment.value.position), | ||||
|                             assignment)) | ||||
|                 } else { | ||||
|                     fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> = | ||||
|                         listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent)) | ||||
|                     fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> { | ||||
|                         val cast = cvalue.cast(targettype) | ||||
|                         return if(cast.isValid) | ||||
|                             listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent)) | ||||
|                         else | ||||
|                             emptyList() | ||||
|                     } | ||||
|                     val cvalue = assignment.value.constValue(program) | ||||
|                     if(cvalue!=null) { | ||||
|                         val number = cvalue.number.toDouble() | ||||
| @@ -78,46 +110,45 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | ||||
|     } | ||||
| 
 | ||||
|     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> { | ||||
|         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 | ||||
|         val modifications = mutableListOf<IAstModification>() | ||||
| 
 | ||||
|         when(val sub = call.target.targetStatement(scope)) { | ||||
|         when(val sub = call.target.targetStatement(program)) { | ||||
|             is Subroutine -> { | ||||
|                 for(arg in sub.parameters.zip(call.args.withIndex())) { | ||||
|                     val argItype = arg.second.value.inferType(program) | ||||
|                 sub.parameters.zip(call.args).forEachIndexed { index, pair -> | ||||
|                     val argItype = pair.second.inferType(program) | ||||
|                     if(argItype.isKnown) { | ||||
|                         val argtype = argItype.typeOrElse(DataType.STRUCT) | ||||
|                         val requiredType = arg.first.type | ||||
|                         val argtype = argItype.typeOrElse(DataType.UNDEFINED) | ||||
|                         val requiredType = pair.first.type | ||||
|                         if (requiredType != argtype) { | ||||
|                             if (argtype isAssignableTo requiredType) { | ||||
|                                 modifications += IAstModification.ReplaceNode( | ||||
|                                         call.args[arg.second.index], | ||||
|                                         TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position), | ||||
|                                         call.args[index], | ||||
|                                         TypecastExpression(pair.second, requiredType, true, pair.second.position), | ||||
|                                         call as Node) | ||||
|                             } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { | ||||
|                                 // we allow STR/ARRAY values in place of UWORD parameters. Take their address instead. | ||||
|                                 if(pair.second is IdentifierReference) { | ||||
|                                     modifications += IAstModification.ReplaceNode( | ||||
|                                         call.args[arg.second.index], | ||||
|                                         AddressOf(arg.second.value as IdentifierReference, arg.second.value.position), | ||||
|                                             call.args[index], | ||||
|                                             AddressOf(pair.second as IdentifierReference, pair.second.position), | ||||
|                                             call as Node) | ||||
|                             } else if(arg.second.value is NumericLiteralValue) { | ||||
|                                 try { | ||||
|                                     val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType) | ||||
|                                     modifications += IAstModification.ReplaceNode( | ||||
|                                             call.args[arg.second.index], | ||||
|                                             castedValue, | ||||
|                                             call as Node) | ||||
|                                 } catch (x: ExpressionError) { | ||||
|                                     // no cast possible | ||||
|                                 } | ||||
|                             } else if(pair.second is NumericLiteralValue) { | ||||
|                                 val cast = (pair.second as NumericLiteralValue).cast(requiredType) | ||||
|                                 if(cast.isValid) | ||||
|                                     modifications += IAstModification.ReplaceNode( | ||||
|                                             call.args[index], | ||||
|                                             cast.valueOrZero(), | ||||
|                                             call as Node) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| @@ -125,25 +156,25 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | ||||
|             } | ||||
|             is BuiltinFunctionStatementPlaceholder -> { | ||||
|                 val func = BuiltinFunctions.getValue(sub.name) | ||||
|                 for (arg in func.parameters.zip(call.args.withIndex())) { | ||||
|                     val argItype = arg.second.value.inferType(program) | ||||
|                 func.parameters.zip(call.args).forEachIndexed { index, pair -> | ||||
|                     val argItype = pair.second.inferType(program) | ||||
|                     if (argItype.isKnown) { | ||||
|                         val argtype = argItype.typeOrElse(DataType.STRUCT) | ||||
|                         if (arg.first.possibleDatatypes.any { argtype == it }) | ||||
|                             continue | ||||
|                         for (possibleType in arg.first.possibleDatatypes) { | ||||
|                         val argtype = argItype.typeOrElse(DataType.UNDEFINED) | ||||
|                         if (pair.first.possibleDatatypes.all { argtype != it }) { | ||||
|                             for (possibleType in pair.first.possibleDatatypes) { | ||||
|                                 if (argtype isAssignableTo possibleType) { | ||||
|                                     modifications += IAstModification.ReplaceNode( | ||||
|                                         call.args[arg.second.index], | ||||
|                                         TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position), | ||||
|                                             call.args[index], | ||||
|                                             TypecastExpression(pair.second, possibleType, true, pair.second.position), | ||||
|                                             call as Node) | ||||
|                                     break | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             null -> { } | ||||
|             else -> throw FatalAstException("call to something weird $sub   ${call.target}") | ||||
|             } | ||||
|             else -> { } | ||||
|         } | ||||
| 
 | ||||
|         return modifications | ||||
| @@ -152,7 +183,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | ||||
|     override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { | ||||
|         // warn about any implicit type casts to Float, because that may not be intended | ||||
|         if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) { | ||||
|             errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) | ||||
|             errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position) | ||||
|         } | ||||
|         return noModifications | ||||
|     } | ||||
| @@ -161,7 +192,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | ||||
|         // make sure the memory address is an uword | ||||
|         val dt = memread.addressExpression.inferType(program) | ||||
|         if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) { | ||||
|             val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD) | ||||
|             val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero() | ||||
|                     ?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position) | ||||
|             return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread)) | ||||
|         } | ||||
| @@ -172,7 +203,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | ||||
|         // make sure the memory address is an uword | ||||
|         val dt = memwrite.addressExpression.inferType(program) | ||||
|         if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) { | ||||
|             val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD) | ||||
|             val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero() | ||||
|                     ?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position) | ||||
|             return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite)) | ||||
|         } | ||||
| @@ -189,7 +220,9 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke | ||||
|                 if (returnValue.inferType(program).istype(subReturnType)) | ||||
|                     return noModifications | ||||
|                 if (returnValue is NumericLiteralValue) { | ||||
|                     returnStmt.value = returnValue.cast(subroutine.returntypes.single()) | ||||
|                     val cast = returnValue.cast(subroutine.returntypes.single()) | ||||
|                     if(cast.isValid) | ||||
|                         returnStmt.value = cast.valueOrZero() | ||||
|                 } else { | ||||
|                     return listOf(IAstModification.ReplaceNode( | ||||
|                             returnValue, | ||||
							
								
								
									
										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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										456
									
								
								compiler/src/prog8/compiler/functions/BuiltinFunctions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										456
									
								
								compiler/src/prog8/compiler/functions/BuiltinFunctions.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,456 @@ | ||||
| package prog8.compiler.functions | ||||
|  | ||||
| import prog8.ast.IMemSizer | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.statements.VarDecl | ||||
| import prog8.compiler.CompilerException | ||||
| import kotlin.math.* | ||||
|  | ||||
|  | ||||
| class FParam(val name: String, val possibleDatatypes: Set<DataType>) | ||||
|  | ||||
|  | ||||
| typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue | ||||
|  | ||||
|  | ||||
| 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 known_returntype: DataType?,     // specify return type if fixed, otherwise null if it depends on the arguments | ||||
|                  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) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_ANONYMOUS_PARAMETER") | ||||
| private val functionSignatures: List<FSignature> = listOf( | ||||
|         // this set of function have no return value and operate in-place: | ||||
|     FSignature("rol"         , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||
|     FSignature("ror"         , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||
|     FSignature("rol2"        , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||
|     FSignature("ror2"        , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), | ||||
|     FSignature("sort"        , 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): | ||||
|     FSignature("max"         , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) },    // 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 | ||||
|     FSignature("sum"         , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) },      // type depends on args | ||||
|     FSignature("abs"         , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs),      // type depends on argument | ||||
|     FSignature("len"         , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen),    // type is UBYTE or UWORD depending on actual length | ||||
|     FSignature("sizeof"      , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof), | ||||
|         // normal functions follow: | ||||
|     FSignature("sgn"         , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ), | ||||
|     FSignature("sin"         , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) }, | ||||
|     FSignature("sin8"        , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), | ||||
|     FSignature("sin8u"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), | ||||
|     FSignature("sin16"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ), | ||||
|     FSignature("sin16u"      , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ), | ||||
|     FSignature("cos"         , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) }, | ||||
|     FSignature("cos8"        , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ), | ||||
|     FSignature("cos8u"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ), | ||||
|     FSignature("cos16"       , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ), | ||||
|     FSignature("cos16u"      , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ), | ||||
|     FSignature("tan"         , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) }, | ||||
|     FSignature("atan"        , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) }, | ||||
|     FSignature("ln"          , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) }, | ||||
|     FSignature("log2"        , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) }, | ||||
|     FSignature("sqrt16"      , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } }, | ||||
|     FSignature("sqrt"        , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) }, | ||||
|     FSignature("rad"         , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) }, | ||||
|     FSignature("deg"         , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) }, | ||||
|     FSignature("round"       , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) }, | ||||
|     FSignature("floor"       , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) }, | ||||
|     FSignature("ceil"        , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) }, | ||||
|     FSignature("any"         , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) }, | ||||
|     FSignature("all"         , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) }, | ||||
|     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 } }, | ||||
|     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} }, | ||||
|     FSignature("mkword"      , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), | ||||
|     FSignature("peek"        , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE), | ||||
|     FSignature("peekw"       , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD), | ||||
|     FSignature("poke"        , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null), | ||||
|     FSignature("pokew"       , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null), | ||||
|     FSignature("rnd"         , false, emptyList(), DataType.UBYTE), | ||||
|     FSignature("rndw"        , false, emptyList(), DataType.UWORD), | ||||
|     FSignature("rndf"        , false, emptyList(), DataType.FLOAT), | ||||
|     FSignature("memory"      , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD), | ||||
|     FSignature("swap"        , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null), | ||||
|     FSignature("callfar"     , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null), | ||||
|     FSignature("callrom"     , false, listOf(FParam("bank", setOf(DataType.UBYTE)), FParam("address", setOf(DataType.UWORD)), FParam("arg", setOf(DataType.UWORD))), null), | ||||
|  | ||||
| ) | ||||
|  | ||||
| val BuiltinFunctions = functionSignatures.associateBy { it.name } | ||||
|  | ||||
|  | ||||
| fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!! | ||||
|  | ||||
| fun builtinMin(array: List<Number>): Number = array.minByOrNull { 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 builtinAll(array: List<Number>): Number = if(array.all { it.toDouble()!=0.0 }) 1 else 0 | ||||
|  | ||||
|  | ||||
| fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType { | ||||
|  | ||||
|     fun datatypeFromIterableArg(arglist: Expression): DataType { | ||||
|         if(arglist is ArrayLiteralValue) { | ||||
|             val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.UNDEFINED)}.toSet() | ||||
|             if(dt.any { it !in NumericDatatypes }) { | ||||
|                 throw FatalAstException("fuction $function only accepts array of numeric values") | ||||
|             } | ||||
|             if(DataType.FLOAT in dt) return DataType.FLOAT | ||||
|             if(DataType.UWORD in dt) return DataType.UWORD | ||||
|             if(DataType.WORD in dt) return DataType.WORD | ||||
|             if(DataType.BYTE in dt) return DataType.BYTE | ||||
|             return DataType.UBYTE | ||||
|         } | ||||
|         if(arglist is IdentifierReference) { | ||||
|             val idt = arglist.inferType(program) | ||||
|             if(!idt.isKnown) | ||||
|                 throw FatalAstException("couldn't determine type of iterable $arglist") | ||||
|             return when(val dt = idt.typeOrElse(DataType.UNDEFINED)) { | ||||
|                 DataType.STR, in NumericDatatypes -> dt | ||||
|                 in ArrayDatatypes -> ArrayToElementTypes.getValue(dt) | ||||
|                 else -> throw FatalAstException("function '$function' requires one argument which is an iterable") | ||||
|             } | ||||
|         } | ||||
|         throw FatalAstException("function '$function' requires one argument which is an iterable") | ||||
|     } | ||||
|  | ||||
|     val func = BuiltinFunctions.getValue(function) | ||||
|     if(func.known_returntype!=null) | ||||
|         return InferredTypes.knownFor(func.known_returntype) | ||||
|  | ||||
|     // function has return values, but the return type depends on the arguments | ||||
|     return when (function) { | ||||
|         "abs" -> { | ||||
|             val dt = args.single().inferType(program) | ||||
|             return if(dt.isNumeric()) | ||||
|                 dt | ||||
|             else | ||||
|                 InferredTypes.InferredType.unknown() | ||||
|         } | ||||
|         "max", "min" -> { | ||||
|             when(val dt = datatypeFromIterableArg(args.single())) { | ||||
|                 DataType.STR -> InferredTypes.knownFor(DataType.UBYTE) | ||||
|                 in NumericDatatypes -> InferredTypes.knownFor(dt) | ||||
|                 in ArrayDatatypes -> InferredTypes.knownFor(ArrayToElementTypes.getValue(dt)) | ||||
|                 else -> InferredTypes.unknown() | ||||
|             } | ||||
|         } | ||||
|         "sum" -> { | ||||
|             when(datatypeFromIterableArg(args.single())) { | ||||
|                 DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD) | ||||
|                 DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD) | ||||
|                 DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT) | ||||
|                 DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD) | ||||
|                 DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD) | ||||
|                 DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT) | ||||
|                 DataType.STR -> InferredTypes.knownFor(DataType.UWORD) | ||||
|                 else -> InferredTypes.unknown() | ||||
|             } | ||||
|         } | ||||
|         "len" -> { | ||||
|             // a length can be >255 so in that case, the result is an UWORD instead of an UBYTE | ||||
|             // but to avoid a lot of code duplication we simply assume UWORD in all cases for now | ||||
|             return InferredTypes.knownFor(DataType.UWORD) | ||||
|         } | ||||
|         else -> return InferredTypes.unknown() | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| class NotConstArgumentException: AstException("not a const argument to a built-in function") | ||||
| class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg") | ||||
|  | ||||
|  | ||||
| private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue { | ||||
|     if(args.size!=1) | ||||
|         throw SyntaxError("built-in function requires one floating point argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val float = constval.number.toDouble() | ||||
|     return numericLiteral(function(float), args[0].position) | ||||
| } | ||||
|  | ||||
| private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue { | ||||
|     if(args.size!=1) | ||||
|         throw SyntaxError("built-in function requires one floating point argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val float = constval.number.toDouble() | ||||
|     return NumericLiteralValue(DataType.WORD, function(float).toInt(), args[0].position) | ||||
| } | ||||
|  | ||||
| private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Number): NumericLiteralValue { | ||||
|     if(args.size!=1) | ||||
|         throw SyntaxError("built-in function requires one integer argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD) | ||||
|         throw SyntaxError("built-in function requires one integer argument", position) | ||||
|  | ||||
|     val integer = constval.number.toInt() | ||||
|     return numericLiteral(function(integer).toInt(), args[0].position) | ||||
| } | ||||
|  | ||||
| private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Number>)->Number): NumericLiteralValue { | ||||
|     if(args.size!=1) | ||||
|         throw SyntaxError("builtin function requires one non-scalar argument", position) | ||||
|  | ||||
|     val array= args[0] as? ArrayLiteralValue ?: throw NotConstArgumentException() | ||||
|     val constElements = array.value.map{it.constValue(program)?.number} | ||||
|     if(constElements.contains(null)) | ||||
|         throw NotConstArgumentException() | ||||
|  | ||||
|     return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position) | ||||
| } | ||||
|  | ||||
| @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 | ||||
|     if(args.size!=1) | ||||
|         throw SyntaxError("abs requires one numeric argument", position) | ||||
|  | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     return when (constval.type) { | ||||
|         in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position) | ||||
|         DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position) | ||||
|         else -> throw SyntaxError("abs requires one numeric argument", position) | ||||
|     } | ||||
| } | ||||
|  | ||||
| private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     // 1 arg, type = anything, result type = ubyte | ||||
|     if(args.size!=1) | ||||
|         throw SyntaxError("sizeof requires one argument", position) | ||||
|     if(args[0] !is IdentifierReference) | ||||
|         throw SyntaxError("sizeof argument should be an identifier", position) | ||||
|  | ||||
|     val dt = args[0].inferType(program) | ||||
|     if(dt.isKnown) { | ||||
|         val target = (args[0] as IdentifierReference).targetStatement(program) | ||||
|                 ?: throw CannotEvaluateException("sizeof", "no target") | ||||
|  | ||||
|         return when { | ||||
|             dt.isArray() -> { | ||||
|                 val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size") | ||||
|                 val elementDt = ArrayToElementTypes.getValue(dt.typeOrElse(DataType.UNDEFINED)) | ||||
|                 numericLiteral(memsizer.memorySize(elementDt) * length, position) | ||||
|             } | ||||
|             dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position) | ||||
|             else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.UNDEFINED)), position) | ||||
|         } | ||||
|     } else { | ||||
|         throw SyntaxError("sizeof invalid argument type", position) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     // 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) | ||||
|         throw SyntaxError("len requires one argument", position) | ||||
|  | ||||
|     val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program) | ||||
|     var arraySize = directMemVar?.arraysize?.constIndex() | ||||
|     if(arraySize != null) | ||||
|         return NumericLiteralValue.optimalInteger(arraySize, position) | ||||
|     if(args[0] is ArrayLiteralValue) | ||||
|         return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position) | ||||
|     if(args[0] !is IdentifierReference) | ||||
|         throw SyntaxError("len argument should be an identifier", position) | ||||
|     val target = (args[0] as IdentifierReference).targetVarDecl(program) | ||||
|             ?: throw CannotEvaluateException("len", "no target vardecl") | ||||
|  | ||||
|     return when(target.datatype) { | ||||
|         DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> { | ||||
|             arraySize = target.arraysize?.constIndex() | ||||
|             if(arraySize==null) | ||||
|                 throw CannotEvaluateException("len", "arraysize unknown") | ||||
|             NumericLiteralValue.optimalInteger(arraySize, args[0].position) | ||||
|         } | ||||
|         DataType.STR -> { | ||||
|             val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown") | ||||
|             NumericLiteralValue.optimalInteger(refLv.value.length, 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") | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 2) | ||||
|         throw SyntaxError("mkword requires msb and lsb arguments", position) | ||||
|     val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt() | ||||
|     return NumericLiteralValue(DataType.UWORD, result, position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("sin8 requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("sin8u requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("cos8 requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("cos8u requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("sin16 requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("sin16u requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("cos16 requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("cos16u requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     val rad = constval.number.toDouble() /256.0 * 2.0 * PI | ||||
|     return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position) | ||||
| } | ||||
|  | ||||
| @Suppress("UNUSED_PARAMETER") | ||||
| private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue { | ||||
|     if (args.size != 1) | ||||
|         throw SyntaxError("sgn requires one argument", position) | ||||
|     val constval = args[0].constValue(program) ?: throw NotConstArgumentException() | ||||
|     return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position) | ||||
| } | ||||
|  | ||||
| private fun numericLiteral(value: Number, position: Position): NumericLiteralValue { | ||||
|     val floatNum=value.toDouble() | ||||
|     val tweakedValue: Number = | ||||
|             if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535)) | ||||
|                 floatNum.toInt()  // we have an integer disguised as a float. | ||||
|             else | ||||
|                 floatNum | ||||
|  | ||||
|     return when(tweakedValue) { | ||||
|         is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position) | ||||
|         is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position) | ||||
|         is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position) | ||||
|         is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position) | ||||
|         is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position) | ||||
|         else -> throw FatalAstException("invalid number type ${value::class}") | ||||
|     } | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| package prog8.compiler.target | ||||
|  | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.ErrorReporter | ||||
| import prog8.compiler.CompilationOptions | ||||
| import prog8.compiler.Zeropage | ||||
| import java.nio.file.Path | ||||
|  | ||||
|  | ||||
| internal interface CompilationTarget { | ||||
|     companion object { | ||||
|         lateinit var name: String | ||||
|         lateinit var machine: IMachineDefinition | ||||
|         lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short> | ||||
|         lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String | ||||
|         lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator | ||||
|     } | ||||
| } | ||||
| @@ -3,10 +3,12 @@ package prog8.compiler.target | ||||
| import prog8.compiler.CompilationOptions | ||||
|  | ||||
| internal interface IAssemblyGenerator { | ||||
|     fun compileToAssembly(optimize: Boolean): IAssemblyProgram | ||||
|     fun compileToAssembly(): IAssemblyProgram | ||||
| } | ||||
|  | ||||
| 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 { | ||||
|     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) | ||||
| } | ||||
| @@ -4,12 +4,34 @@ import prog8.compiler.CompilationOptions | ||||
| import prog8.compiler.Zeropage | ||||
|  | ||||
|  | ||||
| interface IMachineFloat { | ||||
|     fun toDouble(): Double | ||||
|     fun makeFloatFillAsm(): String | ||||
| } | ||||
|  | ||||
| enum class CpuType { | ||||
|     CPU6502, | ||||
|     CPU65c02 | ||||
| } | ||||
|  | ||||
| interface IMachineDefinition { | ||||
|     val FLOAT_MAX_NEGATIVE: Double | ||||
|     val FLOAT_MAX_POSITIVE: Double | ||||
|     val FLOAT_MEM_SIZE: Int | ||||
|     val POINTER_MEM_SIZE: Int | ||||
|     val ESTACK_LO: Int | ||||
|     val ESTACK_HI: Int | ||||
|     val BASIC_LOAD_ADDRESS : Int | ||||
|     val RAW_LOAD_ADDRESS : Int | ||||
|  | ||||
|     val opcodeNames: Set<String> | ||||
|     var zeropage: Zeropage | ||||
|     val cpu: CpuType | ||||
|  | ||||
|     fun getZeropage(compilerOptions: CompilationOptions): Zeropage | ||||
|     fun initializeZeropage(compilerOptions: CompilationOptions) | ||||
|     fun getFloat(num: Number): IMachineFloat | ||||
|  | ||||
|     fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> | ||||
|     fun launchEmulator(programName: String) | ||||
|     fun isRegularRAMaddress(address: Int): Boolean | ||||
| } | ||||
|   | ||||
| @@ -1,34 +1,62 @@ | ||||
| package prog8.compiler.target.c64 | ||||
|  | ||||
| import prog8.compiler.CompilationOptions | ||||
| import prog8.compiler.CompilerException | ||||
| import prog8.compiler.Zeropage | ||||
| import prog8.compiler.ZeropageType | ||||
| import prog8.compiler.* | ||||
| import prog8.compiler.target.CpuType | ||||
| import prog8.compiler.target.IMachineDefinition | ||||
| import prog8.compiler.target.IMachineFloat | ||||
| import java.io.IOException | ||||
| import kotlin.math.absoluteValue | ||||
| import kotlin.math.pow | ||||
|  | ||||
| object C64MachineDefinition: IMachineDefinition { | ||||
| internal object C64MachineDefinition: IMachineDefinition { | ||||
|  | ||||
|     override val cpu = CpuType.CPU6502 | ||||
|  | ||||
|     // 5-byte cbm MFLPT format limitations: | ||||
|     override val FLOAT_MAX_POSITIVE = 1.7014118345e+38         // bytes: 255,127,255,255,255 | ||||
|     override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38        // bytes: 255,255,255,255,255 | ||||
|     override val FLOAT_MEM_SIZE = 5 | ||||
|     const val BASIC_LOAD_ADDRESS = 0x0801 | ||||
|     const val RAW_LOAD_ADDRESS = 0xc000 | ||||
|     override val POINTER_MEM_SIZE = 2 | ||||
|     override val BASIC_LOAD_ADDRESS = 0x0801 | ||||
|     override val RAW_LOAD_ADDRESS = 0xc000 | ||||
|  | ||||
|     // 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 | ||||
|     const val ESTACK_LO_VALUE       = 0xce00        //  $ce00-$ceff inclusive | ||||
|     const val ESTACK_HI_VALUE       = 0xcf00        //  $cf00-$cfff inclusive | ||||
|     const val ESTACK_LO_HEX         = "\$ce00" | ||||
|     const val ESTACK_LO_PLUS1_HEX   = "\$ce01" | ||||
|     const val ESTACK_LO_PLUS2_HEX   = "\$ce02" | ||||
|     const val ESTACK_HI_HEX         = "\$cf00" | ||||
|     const val ESTACK_HI_PLUS1_HEX   = "\$cf01" | ||||
|     const val ESTACK_HI_PLUS2_HEX   = "\$cf02" | ||||
|     override val ESTACK_LO = 0xce00        //  $ce00-$ceff inclusive | ||||
|     override val ESTACK_HI = 0xcf00        //  $ce00-$ceff inclusive | ||||
|  | ||||
|     override fun getZeropage(compilerOptions: CompilationOptions) = C64Zeropage(compilerOptions) | ||||
|     override lateinit var zeropage: Zeropage | ||||
|  | ||||
|     override fun getFloat(num: Number) = Mflpt5.fromNumber(num) | ||||
|  | ||||
|     override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> { | ||||
|         return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) | ||||
|             listOf("syslib") | ||||
|         else | ||||
|             emptyList() | ||||
|     } | ||||
|  | ||||
|     override fun launchEmulator(programName: String) { | ||||
|         for(emulator in listOf("x64sc", "x64")) { | ||||
|             println("\nStarting C-64 emulator $emulator...") | ||||
|             val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list", | ||||
|                     "-autostartprgmode", "1", "-autostart-warp", "-autostart", "$programName.prg") | ||||
|             val processb = ProcessBuilder(cmdline).inheritIO() | ||||
|             val process: Process | ||||
|             try { | ||||
|                 process=processb.start() | ||||
|             } catch(x: IOException) { | ||||
|                 continue  // try the next emulator executable | ||||
|             } | ||||
|             process.waitFor() | ||||
|             break | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff | ||||
|  | ||||
|     override fun initializeZeropage(compilerOptions: CompilationOptions) { | ||||
|         zeropage = C64Zeropage(compilerOptions) | ||||
|     } | ||||
|  | ||||
|     // 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names | ||||
|     override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs", | ||||
| @@ -42,20 +70,12 @@ object C64MachineDefinition: IMachineDefinition { | ||||
|             "sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa") | ||||
|  | ||||
|  | ||||
|     class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||
|     internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||
|  | ||||
|         companion object { | ||||
|             const val SCRATCH_B1 = 0x02 | ||||
|             const val SCRATCH_REG = 0x03    // temp storage for a register | ||||
|             const val SCRATCH_REG_X = 0xfa    // temp storage for register X (the evaluation stack pointer) | ||||
|             const val SCRATCH_W1 = 0xfb     // $fb+$fc | ||||
|             const val SCRATCH_W2 = 0xfd     // $fd+$fe | ||||
|         } | ||||
|  | ||||
|         override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) { | ||||
|             ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT | ||||
|             ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET | ||||
|         } | ||||
|         override val SCRATCH_B1 = 0x02      // temp storage for a single byte | ||||
|         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_W2 = 0xfd      // temp storage 2 for a word  $fb+$fc | ||||
|  | ||||
|  | ||||
|         init { | ||||
| @@ -65,12 +85,14 @@ object C64MachineDefinition: IMachineDefinition { | ||||
|             if (options.zeropage == ZeropageType.FULL) { | ||||
|                 free.addAll(0x04..0xf9) | ||||
|                 free.add(0xff) | ||||
|                 free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1)) | ||||
|                 free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1)) | ||||
|                 free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6))        // these are updated by IRQ | ||||
|             } else { | ||||
|                 if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) { | ||||
|                     free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, | ||||
|                             0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, | ||||
|                     free.addAll(listOf( | ||||
|                             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, | ||||
|                             0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, | ||||
|                             0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53, | ||||
| @@ -80,45 +102,44 @@ object C64MachineDefinition: IMachineDefinition { | ||||
|                             0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, | ||||
|                             0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, | ||||
|                             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) { | ||||
|                     // remove the zero page locations used for floating point operations from the free list | ||||
|                     free.removeAll(listOf( | ||||
|                             0x12, 0x26, 0x27, 0x28, 0x29, 0x2a, | ||||
|                             0x22, 0x23, 0x24, 0x25, | ||||
|                             0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a, | ||||
|                             0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, | ||||
|                             0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, | ||||
|                             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) { | ||||
|                     // add the other free Zp addresses, | ||||
|                     // these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully: | ||||
|                     // add the free Zp addresses | ||||
|                     // these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O* | ||||
|                     free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e, | ||||
|                             0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa, | ||||
|                             0xb5, 0xb6, 0xf7, 0xf8, 0xf9)) | ||||
|                             0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6, | ||||
|                             0xb0, 0xb1, 0xbe, 0xbf, 0xf9)) | ||||
|                 } else { | ||||
|                     // don't use the zeropage at all | ||||
|                     free.clear() | ||||
|                 } | ||||
|             } | ||||
|             assert(SCRATCH_B1 !in free) | ||||
|             assert(SCRATCH_REG !in free) | ||||
|             assert(SCRATCH_REG_X !in free) | ||||
|             assert(SCRATCH_W1 !in free) | ||||
|             assert(SCRATCH_W2 !in free) | ||||
|             require(SCRATCH_B1 !in free) | ||||
|             require(SCRATCH_REG !in free) | ||||
|             require(SCRATCH_W1 !in free) | ||||
|             require(SCRATCH_W2 !in free) | ||||
|  | ||||
|             for (reserved in options.zpReserved) | ||||
|                 reserve(reserved) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) { | ||||
|     internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat { | ||||
|  | ||||
|         companion object { | ||||
|             val zero = Mflpt5(0, 0, 0, 0, 0) | ||||
| @@ -165,7 +186,7 @@ object C64MachineDefinition: IMachineDefinition { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         fun toDouble(): Double { | ||||
|         override fun toDouble(): Double { | ||||
|             if (this == zero) return 0.0 | ||||
|             val exp = b0 - 128 | ||||
|             val sign = (b1.toInt() and 0x80) > 0 | ||||
| @@ -173,5 +194,14 @@ object C64MachineDefinition: IMachineDefinition { | ||||
|             val result = number.toDouble() * (2.0).pow(exp) / 0x100000000 | ||||
|             return if (sign) -result else result | ||||
|         } | ||||
|  | ||||
|         override fun makeFloatFillAsm(): String { | ||||
|             val b0 = "$" + b0.toString(16).padStart(2, '0') | ||||
|             val b1 = "$" + b1.toString(16).padStart(2, '0') | ||||
|             val b2 = "$" + b2.toString(16).padStart(2, '0') | ||||
|             val b3 = "$" + b3.toString(16).padStart(2, '0') | ||||
|             val b4 = "$" + b4.toString(16).padStart(2, '0') | ||||
|             return "$b0, $b1, $b2, $b3, $b4" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,630 +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.FunctionCallStatement | ||||
| import prog8.compiler.AssemblyError | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX | ||||
| 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) | ||||
|             "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) | ||||
|             "lsl" -> funcLsl(fcall) | ||||
|             "lsr" -> funcLsr(fcall) | ||||
|             "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  ${C64Zeropage.SCRATCH_REG} | pha  | txa  | pha  | tya  | pha  | lda  ${C64Zeropage.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.asmIdentifierName(variable) | ||||
|             val numElements = decl.arraysize!!.size() | ||||
|             when (decl.datatype) { | ||||
|                 DataType.ARRAY_UB, DataType.ARRAY_B -> { | ||||
|                     asmgen.out(""" | ||||
|                                     lda  #<$varName | ||||
|                                     ldy  #>$varName | ||||
|                                     sta  ${C64Zeropage.SCRATCH_W1} | ||||
|                                     sty  ${C64Zeropage.SCRATCH_W1 + 1} | ||||
|                                     lda  #$numElements | ||||
|                                     jsr  prog8_lib.reverse_b | ||||
|                                 """) | ||||
|                 } | ||||
|                 DataType.ARRAY_UW, DataType.ARRAY_W -> { | ||||
|                     asmgen.out(""" | ||||
|                                     lda  #<$varName | ||||
|                                     ldy  #>$varName | ||||
|                                     sta  ${C64Zeropage.SCRATCH_W1} | ||||
|                                     sty  ${C64Zeropage.SCRATCH_W1 + 1} | ||||
|                                     lda  #$numElements | ||||
|                                     jsr  prog8_lib.reverse_w | ||||
|                                 """) | ||||
|                 } | ||||
|                 DataType.ARRAY_F -> { | ||||
|                     asmgen.out(""" | ||||
|                                     lda  #<$varName | ||||
|                                     ldy  #>$varName | ||||
|                                     sta  ${C64Zeropage.SCRATCH_W1} | ||||
|                                     sty  ${C64Zeropage.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.asmIdentifierName(variable) | ||||
|             val numElements = decl.arraysize!!.size() | ||||
|             when (decl.datatype) { | ||||
|                 DataType.ARRAY_UB, DataType.ARRAY_B -> { | ||||
|                     asmgen.out(""" | ||||
|                                     lda  #<$varName | ||||
|                                     ldy  #>$varName | ||||
|                                     sta  ${C64Zeropage.SCRATCH_W1} | ||||
|                                     sty  ${C64Zeropage.SCRATCH_W1 + 1} | ||||
|                                     lda  #$numElements | ||||
|                                     sta  ${C64Zeropage.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  ${C64Zeropage.SCRATCH_W1} | ||||
|                                     sty  ${C64Zeropage.SCRATCH_W1 + 1} | ||||
|                                     lda  #$numElements | ||||
|                                     sta  ${C64Zeropage.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.asmIdentifierName(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.asmIdentifierName(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  $ESTACK_LO_HEX,x | ||||
|                         sta  (+) + 1 | ||||
|                         lda  $ESTACK_HI_HEX,x | ||||
|                         sta  (+) + 2 | ||||
|     +                   ror  ${'$'}ffff            ; modified                     | ||||
|                                         """) | ||||
|                         } | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         val variable = asmgen.asmIdentifierName(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.asmIdentifierName(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.asmIdentifierName(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.asmIdentifierName(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  $ESTACK_LO_HEX,x | ||||
|                         sta  (+) + 1 | ||||
|                         lda  $ESTACK_HI_HEX,x | ||||
|                         sta  (+) + 2 | ||||
|     +                   rol  ${'$'}ffff            ; modified                     | ||||
|                                         """) | ||||
|                         } | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         val variable = asmgen.asmIdentifierName(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.asmIdentifierName(what) | ||||
|                         asmgen.out("  rol  $variable |  rol  $variable+1") | ||||
|                     } | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("weird type") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun funcLsr(fcall: IFunctionCall) { | ||||
|         val what = fcall.args.single() | ||||
|         val dt = what.inferType(program) | ||||
|         when (dt.typeOrElse(DataType.STRUCT)) { | ||||
|             DataType.UBYTE -> { | ||||
|                 when (what) { | ||||
|                     is IdentifierReference -> asmgen.out("  lsr  ${asmgen.asmIdentifierName(what)}") | ||||
|                     is DirectMemoryRead -> { | ||||
|                         if (what.addressExpression is NumericLiteralValue) { | ||||
|                             val number = (what.addressExpression as NumericLiteralValue).number | ||||
|                             asmgen.out("  lsr  ${number.toHex()}") | ||||
|                         } else { | ||||
|                             asmgen.translateExpression(what.addressExpression) | ||||
|                             asmgen.out(""" | ||||
|                         inx | ||||
|                         lda  $ESTACK_LO_HEX,x | ||||
|                         sta  (+) + 1 | ||||
|                         lda  $ESTACK_HI_HEX,x | ||||
|                         sta  (+) + 2 | ||||
|     +                   lsr  ${'$'}ffff            ; modified                     | ||||
|                                         """) | ||||
|                         } | ||||
|                     } | ||||
|                     is ArrayIndexedExpression -> { | ||||
|                         asmgen.translateExpression(what.identifier) | ||||
|                         asmgen.translateExpression(what.arrayspec.index) | ||||
|                         asmgen.out("  jsr  prog8_lib.lsr_array_ub") | ||||
|                     } | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             DataType.BYTE -> { | ||||
|                 when (what) { | ||||
|                     is ArrayIndexedExpression -> { | ||||
|                         asmgen.translateExpression(what.identifier) | ||||
|                         asmgen.translateExpression(what.arrayspec.index) | ||||
|                         asmgen.out("  jsr  prog8_lib.lsr_array_b") | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         val variable = asmgen.asmIdentifierName(what) | ||||
|                         asmgen.out("  lda  $variable |  asl  a |  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.lsr_array_uw") | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         val variable = asmgen.asmIdentifierName(what) | ||||
|                         asmgen.out(" lsr  $variable+1 |  ror  $variable") | ||||
|                     } | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             DataType.WORD -> { | ||||
|                 when (what) { | ||||
|                     is ArrayIndexedExpression -> { | ||||
|                         asmgen.translateExpression(what.identifier) | ||||
|                         asmgen.translateExpression(what.arrayspec.index) | ||||
|                         asmgen.out("  jsr  prog8_lib.lsr_array_w") | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         val variable = asmgen.asmIdentifierName(what) | ||||
|                         asmgen.out("  lda  $variable+1 |  asl a  |  ror  $variable+1 |  ror  $variable") | ||||
|                     } | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("weird type") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun funcLsl(fcall: IFunctionCall) { | ||||
|         val what = fcall.args.single() | ||||
|         val dt = what.inferType(program) | ||||
|         when (dt.typeOrElse(DataType.STRUCT)) { | ||||
|             in ByteDatatypes -> { | ||||
|                 when (what) { | ||||
|                     is IdentifierReference -> asmgen.out("  asl  ${asmgen.asmIdentifierName(what)}") | ||||
|                     is DirectMemoryRead -> { | ||||
|                         if (what.addressExpression is NumericLiteralValue) { | ||||
|                             val number = (what.addressExpression as NumericLiteralValue).number | ||||
|                             asmgen.out("  asl  ${number.toHex()}") | ||||
|                         } else { | ||||
|                             asmgen.translateExpression(what.addressExpression) | ||||
|                             asmgen.out(""" | ||||
|                         inx | ||||
|                         lda  $ESTACK_LO_HEX,x | ||||
|                         sta  (+) + 1 | ||||
|                         lda  $ESTACK_HI_HEX,x | ||||
|                         sta  (+) + 2 | ||||
|     +                   asl  ${'$'}ffff            ; modified                     | ||||
|                                         """) | ||||
|                         } | ||||
|                     } | ||||
|                     is ArrayIndexedExpression -> { | ||||
|                         asmgen.translateExpression(what.identifier) | ||||
|                         asmgen.translateExpression(what.arrayspec.index) | ||||
|                         asmgen.out("  jsr  prog8_lib.lsl_array_b") | ||||
|                     } | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             in WordDatatypes -> { | ||||
|                 when (what) { | ||||
|                     is ArrayIndexedExpression -> { | ||||
|                         asmgen.translateExpression(what.identifier) | ||||
|                         asmgen.translateExpression(what.arrayspec.index) | ||||
|                         asmgen.out("  jsr  prog8_lib.lsl_array_w") | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         val variable = asmgen.asmIdentifierName(what) | ||||
|                         asmgen.out(" asl  $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  c64flt.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  c64flt.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  c64flt.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  c64flt.func_${functionName}_f") | ||||
|             else -> throw AssemblyError("weird type $dt") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun funcStrlen(fcall: IFunctionCall) { | ||||
|         outputPushAddressOfIdentifier(fcall.args[0]) | ||||
|         asmgen.out("  jsr  prog8_lib.func_strlen") | ||||
|     } | ||||
|  | ||||
|     private fun funcSwap(fcall: IFunctionCall) { | ||||
|         val first = fcall.args[0] | ||||
|         val second = fcall.args[1] | ||||
|         if(first is IdentifierReference && second is IdentifierReference) { | ||||
|             val firstName = asmgen.asmIdentifierName(first) | ||||
|             val secondName = asmgen.asmIdentifierName(second) | ||||
|             val dt = first.inferType(program) | ||||
|             if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) { | ||||
|                 asmgen.out(" ldy  $firstName |  lda  $secondName |  sta  $firstName |  tya |  sta  $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  ${C64Zeropage.SCRATCH_W1} | ||||
|                     lda  #>$firstName | ||||
|                     sta  ${C64Zeropage.SCRATCH_W1+1} | ||||
|                     lda  #<$secondName | ||||
|                     sta  ${C64Zeropage.SCRATCH_W2} | ||||
|                     lda  #>$secondName | ||||
|                     sta  ${C64Zeropage.SCRATCH_W2+1} | ||||
|                     jsr  c64flt.swap_floats | ||||
|                 """) | ||||
|                 return | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // other types of swap() calls should have been replaced by a different statement sequence involving a temp variable | ||||
|         throw AssemblyError("no asm generation for swap funccall $fcall") | ||||
|     } | ||||
|  | ||||
|     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  c64flt.abs_f") | ||||
|             else -> throw AssemblyError("weird type") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun funcMkword(fcall: IFunctionCall, func: FSignature) { | ||||
|         translateFunctionArguments(fcall.args, func) | ||||
|         asmgen.out("  inx | lda  $ESTACK_LO_HEX,x  | sta  $ESTACK_HI_PLUS1_HEX,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("should have been const-folded") | ||||
|         if (arg is IdentifierReference) { | ||||
|             val sourceName = asmgen.asmIdentifierName(arg) | ||||
|             asmgen.out("  lda  $sourceName+1 |  sta  $ESTACK_LO_HEX,x |  dex") | ||||
|         } else { | ||||
|             asmgen.translateExpression(arg) | ||||
|             asmgen.out("  lda  $ESTACK_HI_PLUS1_HEX,x |  sta  $ESTACK_LO_PLUS1_HEX,x") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun outputPushAddressAndLenghtOfArray(arg: Expression) { | ||||
|         arg as IdentifierReference | ||||
|         val identifierName = asmgen.asmIdentifierName(arg) | ||||
|         val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!! | ||||
|         asmgen.out(""" | ||||
|                     lda  #<$identifierName | ||||
|                     sta  $ESTACK_LO_HEX,x | ||||
|                     lda  #>$identifierName | ||||
|                     sta  $ESTACK_HI_HEX,x | ||||
|                     dex | ||||
|                     lda  #$size | ||||
|                     sta  $ESTACK_LO_HEX,x | ||||
|                     dex | ||||
|                     """) | ||||
|     } | ||||
|  | ||||
|     private fun outputPushAddressOfIdentifier(arg: Expression) { | ||||
|         val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference) | ||||
|         asmgen.out(""" | ||||
|                     lda  #<$identifierName | ||||
|                     sta  $ESTACK_LO_HEX,x | ||||
|                     lda  #>$identifierName | ||||
|                     sta  $ESTACK_HI_HEX,x | ||||
|                     dex | ||||
|                     """) | ||||
|     } | ||||
|  | ||||
|     private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) { | ||||
|         args.forEach { | ||||
|             asmgen.translateExpression(it) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,500 +0,0 @@ | ||||
| package prog8.compiler.target.c64.codegen | ||||
|  | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.compiler.AssemblyError | ||||
| import prog8.compiler.target.c64.C64MachineDefinition | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX | ||||
| import prog8.compiler.toHex | ||||
| import prog8.functions.BuiltinFunctions | ||||
| import kotlin.math.absoluteValue | ||||
|  | ||||
| internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) { | ||||
|  | ||||
|     internal fun translateExpression(expression: Expression) { | ||||
|         when(expression) { | ||||
|             is PrefixExpression -> translateExpression(expression) | ||||
|             is BinaryExpression -> translateExpression(expression) | ||||
|             is ArrayIndexedExpression -> translatePushFromArray(expression) | ||||
|             is TypecastExpression -> translateExpression(expression) | ||||
|             is AddressOf -> translateExpression(expression) | ||||
|             is DirectMemoryRead -> translateExpression(expression) | ||||
|             is NumericLiteralValue -> translateExpression(expression) | ||||
|             is IdentifierReference -> translateExpression(expression) | ||||
|             is FunctionCall -> translateExpression(expression) | ||||
|             is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable") | ||||
|             is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateExpression(expression: FunctionCall) { | ||||
|         val functionName = expression.target.nameInSource.last() | ||||
|         val builtinFunc = BuiltinFunctions[functionName] | ||||
|         if (builtinFunc != null) { | ||||
|             asmgen.translateFunctioncallExpression(expression, builtinFunc) | ||||
|         } else { | ||||
|             val sub = expression.target.targetSubroutine(program.namespace)!! | ||||
|             asmgen.translateFunctionCall(expression) | ||||
|             val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters) | ||||
|             for ((_, reg) in returns) { | ||||
|                 if (!reg.stack) { | ||||
|                     // result value in cpu or status registers, put it on the stack | ||||
|                     if (reg.registerOrPair != null) { | ||||
|                         when (reg.registerOrPair) { | ||||
|                             RegisterOrPair.A -> asmgen.out("  sta  $ESTACK_LO_HEX,x |  dex") | ||||
|                             RegisterOrPair.Y -> asmgen.out("  tya |  sta  $ESTACK_LO_HEX,x |  dex") | ||||
|                             RegisterOrPair.AY -> asmgen.out("  sta  $ESTACK_LO_HEX,x |  tya |  sta  $ESTACK_HI_HEX,x |  dex") | ||||
|                             RegisterOrPair.X -> { | ||||
|                                 // return value in X register has been discarded, just push a zero | ||||
|                                 asmgen.out("  lda  #0 |  sta  $ESTACK_LO_HEX,x |  dex") | ||||
|                             } | ||||
|                             RegisterOrPair.AX -> { | ||||
|                                 // return value in X register has been discarded, just push a zero in this place | ||||
|                                 asmgen.out("  sta  $ESTACK_LO_HEX,x |  lda  #0 |  sta  $ESTACK_HI_HEX,x |  dex") | ||||
|                             } | ||||
|                             RegisterOrPair.XY -> { | ||||
|                                 // return value in X register has been discarded, just push a zero in this place | ||||
|                                 asmgen.out("  lda  #0 |  sta  $ESTACK_LO_HEX,x |  tya |  sta  $ESTACK_HI_HEX,x |  dex") | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     // return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateExpression(expr: TypecastExpression) { | ||||
|         translateExpression(expr.expression) | ||||
|         when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) { | ||||
|             DataType.UBYTE -> { | ||||
|                 when(expr.type) { | ||||
|                     DataType.UBYTE, DataType.BYTE -> {} | ||||
|                     DataType.UWORD, DataType.WORD -> asmgen.out("  lda  #0  |  sta  $ESTACK_HI_PLUS1_HEX,x") | ||||
|                     DataType.FLOAT -> asmgen.out(" jsr  c64flt.stack_ub2float") | ||||
|                     in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             DataType.BYTE -> { | ||||
|                 when(expr.type) { | ||||
|                     DataType.UBYTE, DataType.BYTE -> {} | ||||
|                     DataType.UWORD, DataType.WORD -> asmgen.out("  lda  $ESTACK_LO_PLUS1_HEX,x  |  ${asmgen.signExtendAtoMsb("$ESTACK_HI_PLUS1_HEX,x")}") | ||||
|                     DataType.FLOAT -> asmgen.out(" jsr  c64flt.stack_b2float") | ||||
|                     in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             DataType.UWORD -> { | ||||
|                 when(expr.type) { | ||||
|                     DataType.BYTE, DataType.UBYTE -> {} | ||||
|                     DataType.WORD, DataType.UWORD -> {} | ||||
|                     DataType.FLOAT -> asmgen.out(" jsr  c64flt.stack_uw2float") | ||||
|                     in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             DataType.WORD -> { | ||||
|                 when(expr.type) { | ||||
|                     DataType.BYTE, DataType.UBYTE -> {} | ||||
|                     DataType.WORD, DataType.UWORD -> {} | ||||
|                     DataType.FLOAT -> asmgen.out(" jsr  c64flt.stack_w2float") | ||||
|                     in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             DataType.FLOAT -> { | ||||
|                 when(expr.type) { | ||||
|                     DataType.UBYTE -> asmgen.out(" jsr  c64flt.stack_float2uw") | ||||
|                     DataType.BYTE -> asmgen.out(" jsr  c64flt.stack_float2w") | ||||
|                     DataType.UWORD -> asmgen.out(" jsr  c64flt.stack_float2uw") | ||||
|                     DataType.WORD -> asmgen.out(" jsr  c64flt.stack_float2w") | ||||
|                     DataType.FLOAT -> {} | ||||
|                     in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type") | ||||
|             else -> throw AssemblyError("weird type") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateExpression(expr: AddressOf) { | ||||
|         val name = asmgen.asmIdentifierName(expr.identifier) | ||||
|         asmgen.out("  lda  #<$name |  sta  $ESTACK_LO_HEX,x |  lda  #>$name  |  sta  $ESTACK_HI_HEX,x  | dex") | ||||
|     } | ||||
|  | ||||
|     private fun translateExpression(expr: DirectMemoryRead) { | ||||
|         when(expr.addressExpression) { | ||||
|             is NumericLiteralValue -> { | ||||
|                 val address = (expr.addressExpression as NumericLiteralValue).number.toInt() | ||||
|                 asmgen.out("  lda  ${address.toHex()} |  sta  $ESTACK_LO_HEX,x |  dex") | ||||
|             } | ||||
|             is IdentifierReference -> { | ||||
|                 // the identifier is a pointer variable, so read the value from the address in it | ||||
|                 val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference) | ||||
|                 asmgen.out(""" | ||||
|         lda  $sourceName | ||||
|         sta  (+) +1 | ||||
|         lda  $sourceName+1 | ||||
|         sta  (+) +2 | ||||
| +       lda  ${'$'}ffff     ; modified | ||||
|         sta  $ESTACK_LO_HEX,x | ||||
|         dex""") | ||||
|             } | ||||
|             else -> { | ||||
|                 translateExpression(expr.addressExpression) | ||||
|                 asmgen.out("  jsr  prog8_lib.read_byte_from_address") | ||||
|                 asmgen.out("  sta  $ESTACK_LO_PLUS1_HEX,x") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateExpression(expr: NumericLiteralValue) { | ||||
|         when(expr.type) { | ||||
|             DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda  #${expr.number.toHex()}  | sta  $ESTACK_LO_HEX,x  | dex") | ||||
|             DataType.UWORD, DataType.WORD -> asmgen.out(""" | ||||
|                 lda  #<${expr.number.toHex()} | ||||
|                 sta  $ESTACK_LO_HEX,x | ||||
|                 lda  #>${expr.number.toHex()} | ||||
|                 sta  $ESTACK_HI_HEX,x | ||||
|                 dex | ||||
|             """) | ||||
|             DataType.FLOAT -> { | ||||
|                 val floatConst = asmgen.getFloatConst(expr.number.toDouble()) | ||||
|                 asmgen.out(" lda  #<$floatConst |  ldy  #>$floatConst |  jsr  c64flt.push_float") | ||||
|             } | ||||
|             else -> throw AssemblyError("weird type") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateExpression(expr: IdentifierReference) { | ||||
|         val varname = asmgen.asmIdentifierName(expr) | ||||
|         when(expr.inferType(program).typeOrElse(DataType.STRUCT)) { | ||||
|             DataType.UBYTE, DataType.BYTE -> { | ||||
|                 asmgen.out("  lda  $varname  |  sta  $ESTACK_LO_HEX,x  |  dex") | ||||
|             } | ||||
|             DataType.UWORD, DataType.WORD -> { | ||||
|                 asmgen.out("  lda  $varname  |  sta  $ESTACK_LO_HEX,x  |  lda  $varname+1 |  sta  $ESTACK_HI_HEX,x |  dex") | ||||
|             } | ||||
|             DataType.FLOAT -> { | ||||
|                 asmgen.out(" lda  #<$varname |  ldy  #>$varname|  jsr  c64flt.push_float") | ||||
|             } | ||||
|             in IterableDatatypes -> { | ||||
|                 asmgen.out("  lda  #<$varname  |  sta  $ESTACK_LO_HEX,x  |  lda  #>$varname |  sta  $ESTACK_HI_HEX,x |  dex") | ||||
|             } | ||||
|             else -> throw AssemblyError("stack push weird variable type $expr") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40) | ||||
|     private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40) | ||||
|     private val powersOfTwo = setOf(0,1,2,4,8,16,32,64,128,256) | ||||
|  | ||||
|     private fun translateExpression(expr: BinaryExpression) { | ||||
|         val leftIDt = expr.left.inferType(program) | ||||
|         val rightIDt = expr.right.inferType(program) | ||||
|         if(!leftIDt.isKnown || !rightIDt.isKnown) | ||||
|             throw AssemblyError("can't infer type of both expression operands") | ||||
|  | ||||
|         val leftDt = leftIDt.typeOrElse(DataType.STRUCT) | ||||
|         val rightDt = rightIDt.typeOrElse(DataType.STRUCT) | ||||
|         // see if we can apply some optimized routines | ||||
|         when(expr.operator) { | ||||
|             ">>" -> { | ||||
|                 // bit-shifts are always by a constant number (for now) | ||||
|                 translateExpression(expr.left) | ||||
|                 val amount = expr.right.constValue(program)!!.number.toInt() | ||||
|                 when (leftDt) { | ||||
|                     DataType.UBYTE -> { | ||||
|                         if(amount<=2) | ||||
|                             repeat(amount) { asmgen.out(" lsr  $ESTACK_LO_PLUS1_HEX,x") } | ||||
|                         else { | ||||
|                             asmgen.out(" lda  $ESTACK_LO_PLUS1_HEX,x") | ||||
|                             repeat(amount) { asmgen.out(" lsr  a") } | ||||
|                             asmgen.out(" sta  $ESTACK_LO_PLUS1_HEX,x") | ||||
|                         } | ||||
|                     } | ||||
|                     DataType.BYTE -> { | ||||
|                         if(amount<=2) | ||||
|                             repeat(amount) { asmgen.out(" lda  $ESTACK_LO_PLUS1_HEX,x |  asl  a |  ror  $ESTACK_LO_PLUS1_HEX,x") } | ||||
|                         else { | ||||
|                             asmgen.out(" lda  $ESTACK_LO_PLUS1_HEX,x |  sta  ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}") | ||||
|                             repeat(amount) { asmgen.out(" asl  a |  ror  ${C64MachineDefinition.C64Zeropage.SCRATCH_B1} |  lda  ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}") } | ||||
|                             asmgen.out(" sta  $ESTACK_LO_PLUS1_HEX,x") | ||||
|                         } | ||||
|                     } | ||||
|                     DataType.UWORD -> { | ||||
|                         var left = amount | ||||
|                         while(left>=7) { | ||||
|                             asmgen.out(" jsr  math.shift_right_uw_7") | ||||
|                             left -= 7 | ||||
|                         } | ||||
|                         if (left in 0..2) | ||||
|                             repeat(left) { asmgen.out(" lsr  $ESTACK_HI_PLUS1_HEX,x |  ror  $ESTACK_LO_PLUS1_HEX,x") } | ||||
|                         else | ||||
|                             asmgen.out(" jsr  math.shift_right_uw_$left") | ||||
|                     } | ||||
|                     DataType.WORD -> { | ||||
|                         var left = amount | ||||
|                         while(left>=7) { | ||||
|                             asmgen.out(" jsr  math.shift_right_w_7") | ||||
|                             left -= 7 | ||||
|                         } | ||||
|                         if (left in 0..2) | ||||
|                             repeat(left) { asmgen.out(" lda  $ESTACK_HI_PLUS1_HEX,x |  asl a  |  ror  $ESTACK_HI_PLUS1_HEX,x |  ror  $ESTACK_LO_PLUS1_HEX,x") } | ||||
|                         else | ||||
|                             asmgen.out(" jsr  math.shift_right_w_$left") | ||||
|                     } | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|                 return | ||||
|             } | ||||
|             "<<" -> { | ||||
|                 // bit-shifts are always by a constant number (for now) | ||||
|                 translateExpression(expr.left) | ||||
|                 val amount = expr.right.constValue(program)!!.number.toInt() | ||||
|                 if (leftDt in ByteDatatypes) { | ||||
|                     if(amount<=2) | ||||
|                         repeat(amount) { asmgen.out("  asl  $ESTACK_LO_PLUS1_HEX,x") } | ||||
|                     else { | ||||
|                         asmgen.out("  lda  $ESTACK_LO_PLUS1_HEX,x") | ||||
|                         repeat(amount) { asmgen.out("  asl  a") } | ||||
|                         asmgen.out("  sta  $ESTACK_LO_PLUS1_HEX,x") | ||||
|                     } | ||||
|                 } | ||||
|                 else { | ||||
|                     var left=amount | ||||
|                     while(left>=7) { | ||||
|                         asmgen.out(" jsr  math.shift_left_w_7") | ||||
|                         left -= 7 | ||||
|                     } | ||||
|                     if (left in 0..2) | ||||
|                         repeat(left) { asmgen.out("  asl  $ESTACK_LO_PLUS1_HEX,x |  rol  $ESTACK_HI_PLUS1_HEX,x") } | ||||
|                     else | ||||
|                         asmgen.out(" jsr  math.shift_left_w_$left") | ||||
|                 } | ||||
|                 return | ||||
|             } | ||||
|             "*" -> { | ||||
|                 val value = expr.right.constValue(program) | ||||
|                 if(value!=null) { | ||||
|                     if(rightDt in IntegerDatatypes) { | ||||
|                         val amount = value.number.toInt() | ||||
|                         when(rightDt) { | ||||
|                             DataType.UBYTE -> { | ||||
|                                 if(amount in optimizedByteMultiplications) { | ||||
|                                     translateExpression(expr.left) | ||||
|                                     asmgen.out(" jsr  math.mul_byte_$amount") | ||||
|                                     return | ||||
|                                 } | ||||
|                             } | ||||
|                             DataType.BYTE -> { | ||||
|                                 if(amount in optimizedByteMultiplications) { | ||||
|                                     translateExpression(expr.left) | ||||
|                                     asmgen.out(" jsr  math.mul_byte_$amount") | ||||
|                                     return | ||||
|                                 } | ||||
|                                 if(amount.absoluteValue in optimizedByteMultiplications) { | ||||
|                                     translateExpression(expr.left) | ||||
|                                     asmgen.out(" jsr  prog8_lib.neg_b |  jsr  math.mul_byte_${amount.absoluteValue}") | ||||
|                                     return | ||||
|                                 } | ||||
|                             } | ||||
|                             DataType.UWORD -> { | ||||
|                                 if(amount in optimizedWordMultiplications) { | ||||
|                                     translateExpression(expr.left) | ||||
|                                     asmgen.out(" jsr  math.mul_word_$amount") | ||||
|                                     return | ||||
|                                 } | ||||
|                             } | ||||
|                             DataType.WORD -> { | ||||
|                                 if(amount in optimizedWordMultiplications) { | ||||
|                                     translateExpression(expr.left) | ||||
|                                     asmgen.out(" jsr  math.mul_word_$amount") | ||||
|                                     return | ||||
|                                 } | ||||
|                                 if(amount.absoluteValue in optimizedWordMultiplications) { | ||||
|                                     translateExpression(expr.left) | ||||
|                                     asmgen.out(" jsr  prog8_lib.neg_w |  jsr  math.mul_word_${amount.absoluteValue}") | ||||
|                                     return | ||||
|                                 } | ||||
|                             } | ||||
|                             else -> {} | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // the general, non-optimized cases | ||||
|         translateExpression(expr.left) | ||||
|         translateExpression(expr.right) | ||||
|         if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes) | ||||
|                 || (leftDt in WordDatatypes && rightDt !in WordDatatypes)) | ||||
|             throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") | ||||
|  | ||||
|         when (leftDt) { | ||||
|             in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt) | ||||
|             in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt) | ||||
|             DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator) | ||||
|             else -> throw AssemblyError("non-numerical datatype") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateExpression(expr: PrefixExpression) { | ||||
|         translateExpression(expr.expression) | ||||
|         val type = expr.inferType(program).typeOrElse(DataType.STRUCT) | ||||
|         when(expr.operator) { | ||||
|             "+" -> {} | ||||
|             "-" -> { | ||||
|                 when(type) { | ||||
|                     in ByteDatatypes -> asmgen.out("  jsr  prog8_lib.neg_b") | ||||
|                     in WordDatatypes -> asmgen.out("  jsr  prog8_lib.neg_w") | ||||
|                     DataType.FLOAT -> asmgen.out("  jsr  c64flt.neg_f") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             "~" -> { | ||||
|                 when(type) { | ||||
|                     in ByteDatatypes -> | ||||
|                         asmgen.out(""" | ||||
|                             lda  $ESTACK_LO_PLUS1_HEX,x | ||||
|                             eor  #255 | ||||
|                             sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                             """) | ||||
|                     in WordDatatypes -> asmgen.out("  jsr  prog8_lib.inv_word") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             "not" -> { | ||||
|                 when(type) { | ||||
|                     in ByteDatatypes -> asmgen.out("  jsr  prog8_lib.not_byte") | ||||
|                     in WordDatatypes -> asmgen.out("  jsr  prog8_lib.not_word") | ||||
|                     else -> throw AssemblyError("weird type") | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("invalid prefix operator ${expr.operator}") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translatePushFromArray(arrayExpr: ArrayIndexedExpression) { | ||||
|         // assume *reading* from an array | ||||
|         val index = arrayExpr.arrayspec.index | ||||
|         val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype | ||||
|         val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier) | ||||
|         if(index is NumericLiteralValue) { | ||||
|             val elementDt = ArrayElementTypes.getValue(arrayDt) | ||||
|             val indexValue = index.number.toInt() * elementDt.memorySize() | ||||
|             when(elementDt) { | ||||
|                 in ByteDatatypes -> { | ||||
|                     asmgen.out("  lda  $arrayVarName+$indexValue |  sta  $ESTACK_LO_HEX,x |  dex") | ||||
|                 } | ||||
|                 in WordDatatypes -> { | ||||
|                     asmgen.out("  lda  $arrayVarName+$indexValue |  sta  $ESTACK_LO_HEX,x |  lda  $arrayVarName+$indexValue+1 |  sta  $ESTACK_HI_HEX,x |  dex") | ||||
|                 } | ||||
|                 DataType.FLOAT -> { | ||||
|                     asmgen.out("  lda  #<$arrayVarName+$indexValue |  ldy  #>$arrayVarName+$indexValue |  jsr  c64flt.push_float") | ||||
|                 } | ||||
|                 else -> throw AssemblyError("weird type") | ||||
|             } | ||||
|         } else { | ||||
|             asmgen.translateArrayIndexIntoA(arrayExpr) | ||||
|             asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateBinaryOperatorBytes(operator: String, types: DataType) { | ||||
|         when(operator) { | ||||
|             "**" -> throw AssemblyError("** operator requires floats") | ||||
|             "*" -> asmgen.out("  jsr  prog8_lib.mul_byte")  //  the optimized routines should have been checked earlier | ||||
|             "/" -> asmgen.out(if(types==DataType.UBYTE) "  jsr  prog8_lib.idiv_ub" else "  jsr  prog8_lib.idiv_b") | ||||
|             "%" -> { | ||||
|                 if(types==DataType.BYTE) | ||||
|                     throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead") | ||||
|                 asmgen.out("  jsr prog8_lib.remainder_ub") | ||||
|             } | ||||
|             "+" -> asmgen.out(""" | ||||
|                 lda  $ESTACK_LO_PLUS2_HEX,x | ||||
|                 clc | ||||
|                 adc  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 inx | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 """) | ||||
|             "-" -> asmgen.out(""" | ||||
|                 lda  $ESTACK_LO_PLUS2_HEX,x | ||||
|                 sec | ||||
|                 sbc  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 inx | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 """) | ||||
|             "<<", ">>" -> throw AssemblyError("bit-shifts not via stack") | ||||
|             "<" -> asmgen.out(if(types==DataType.UBYTE) "  jsr  prog8_lib.less_ub" else "  jsr  prog8_lib.less_b") | ||||
|             ">" -> asmgen.out(if(types==DataType.UBYTE) "  jsr  prog8_lib.greater_ub" else "  jsr  prog8_lib.greater_b") | ||||
|             "<=" -> asmgen.out(if(types==DataType.UBYTE) "  jsr  prog8_lib.lesseq_ub" else "  jsr  prog8_lib.lesseq_b") | ||||
|             ">=" -> asmgen.out(if(types==DataType.UBYTE) "  jsr  prog8_lib.greatereq_ub" else "  jsr  prog8_lib.greatereq_b") | ||||
|             "==" -> asmgen.out("  jsr  prog8_lib.equal_b") | ||||
|             "!=" -> asmgen.out("  jsr  prog8_lib.notequal_b") | ||||
|             "&" -> asmgen.out("  jsr  prog8_lib.bitand_b") | ||||
|             "^" -> asmgen.out("  jsr  prog8_lib.bitxor_b") | ||||
|             "|" -> asmgen.out("  jsr  prog8_lib.bitor_b") | ||||
|             "and" -> asmgen.out("  jsr  prog8_lib.and_b") | ||||
|             "or" -> asmgen.out("  jsr  prog8_lib.or_b") | ||||
|             "xor" -> asmgen.out("  jsr  prog8_lib.xor_b") | ||||
|             else -> throw AssemblyError("invalid operator $operator") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateBinaryOperatorWords(operator: String, types: DataType) { | ||||
|         when(operator) { | ||||
|             "**" -> throw AssemblyError("** operator requires floats") | ||||
|             "*" -> asmgen.out("  jsr  prog8_lib.mul_word") | ||||
|             "/" -> asmgen.out(if(types==DataType.UWORD) "  jsr  prog8_lib.idiv_uw" else "  jsr  prog8_lib.idiv_w") | ||||
|             "%" -> { | ||||
|                 if(types==DataType.WORD) | ||||
|                     throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead") | ||||
|                 asmgen.out("  jsr prog8_lib.remainder_uw") | ||||
|             } | ||||
|             "+" -> asmgen.out("  jsr  prog8_lib.add_w") | ||||
|             "-" -> asmgen.out("  jsr  prog8_lib.sub_w") | ||||
|             "<<" -> throw AssemblyError("<< should not operate via stack") | ||||
|             ">>" -> throw AssemblyError(">> should not operate via stack") | ||||
|             "<" -> asmgen.out(if(types==DataType.UWORD) "  jsr  prog8_lib.less_uw" else "  jsr  prog8_lib.less_w") | ||||
|             ">" -> asmgen.out(if(types==DataType.UWORD) "  jsr  prog8_lib.greater_uw" else "  jsr  prog8_lib.greater_w") | ||||
|             "<=" -> asmgen.out(if(types==DataType.UWORD) "  jsr  prog8_lib.lesseq_uw" else "  jsr  prog8_lib.lesseq_w") | ||||
|             ">=" -> asmgen.out(if(types==DataType.UWORD) "  jsr  prog8_lib.greatereq_uw" else "  jsr  prog8_lib.greatereq_w") | ||||
|             "==" -> asmgen.out("  jsr  prog8_lib.equal_w") | ||||
|             "!=" -> asmgen.out("  jsr  prog8_lib.notequal_w") | ||||
|             "&" -> asmgen.out("  jsr  prog8_lib.bitand_w") | ||||
|             "^" -> asmgen.out("  jsr  prog8_lib.bitxor_w") | ||||
|             "|" -> asmgen.out("  jsr  prog8_lib.bitor_w") | ||||
|             "and" -> asmgen.out("  jsr  prog8_lib.and_w") | ||||
|             "or" -> asmgen.out("  jsr  prog8_lib.or_w") | ||||
|             "xor" -> asmgen.out("  jsr  prog8_lib.xor_w") | ||||
|             else -> throw AssemblyError("invalid operator $operator") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateBinaryOperatorFloats(operator: String) { | ||||
|         when(operator) { | ||||
|             "**" -> asmgen.out(" jsr  c64flt.pow_f") | ||||
|             "*" -> asmgen.out("  jsr  c64flt.mul_f") | ||||
|             "/" -> asmgen.out("  jsr  c64flt.div_f") | ||||
|             "+" -> asmgen.out("  jsr  c64flt.add_f") | ||||
|             "-" -> asmgen.out("  jsr  c64flt.sub_f") | ||||
|             "<" -> asmgen.out("  jsr  c64flt.less_f") | ||||
|             ">" -> asmgen.out("  jsr  c64flt.greater_f") | ||||
|             "<=" -> asmgen.out("  jsr  c64flt.lesseq_f") | ||||
|             ">=" -> asmgen.out("  jsr  c64flt.greatereq_f") | ||||
|             "==" -> asmgen.out("  jsr  c64flt.equal_f") | ||||
|             "!=" -> asmgen.out("  jsr  c64flt.notequal_f") | ||||
|             "%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype") | ||||
|             else -> throw AssemblyError("invalid operator $operator") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,554 +0,0 @@ | ||||
| package prog8.compiler.target.c64.codegen | ||||
|  | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.DataType | ||||
| import prog8.ast.expressions.IdentifierReference | ||||
| import prog8.ast.expressions.RangeExpr | ||||
| import prog8.ast.statements.AssignTarget | ||||
| import prog8.ast.statements.Assignment | ||||
| import prog8.ast.statements.ForLoop | ||||
| import prog8.compiler.AssemblyError | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX | ||||
| import prog8.compiler.toHex | ||||
| import kotlin.math.absoluteValue | ||||
|  | ||||
| // todo choose more efficient comparisons to avoid needless lda's | ||||
| // todo optimize common case when step == 2 or -2 | ||||
|  | ||||
|  | ||||
| internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { | ||||
|  | ||||
|     internal fun translate(stmt: ForLoop) { | ||||
|         val iterableDt = stmt.iterable.inferType(program) | ||||
|         if(!iterableDt.isKnown) | ||||
|             throw AssemblyError("can't determine iterable dt") | ||||
|         when(stmt.iterable) { | ||||
|             is RangeExpr -> { | ||||
|                 val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() | ||||
|                 if(range==null) { | ||||
|                     translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr) | ||||
|                 } else { | ||||
|                     translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range) | ||||
|                 } | ||||
|             } | ||||
|             is IdentifierReference -> { | ||||
|                 translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference) | ||||
|             } | ||||
|             else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         val continueLabel = asmgen.makeLabel("for_continue") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         asmgen.loopContinueLabels.push(continueLabel) | ||||
|         val stepsize=range.step.constValue(program)!!.number.toInt() | ||||
|         when(iterableDt) { | ||||
|             DataType.ARRAY_B, DataType.ARRAY_UB -> { | ||||
|                 if (stepsize==1 || stepsize==-1) { | ||||
|  | ||||
|                     // bytes, step 1 or -1 | ||||
|  | ||||
|                     val incdec = if(stepsize==1) "inc" else "dec" | ||||
|                     // loop over byte range via loopvar | ||||
|                     val varname = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                     asmgen.translateExpression(range.to) | ||||
|                     asmgen.translateExpression(range.from) | ||||
|                     asmgen.out(""" | ||||
|                 inx | ||||
|                 lda  ${ESTACK_LO_HEX},x | ||||
|                 sta  $varname | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  lda  $varname | ||||
|                 cmp  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 beq  $endLabel | ||||
|                 $incdec  $varname | ||||
|                 jmp  $loopLabel | ||||
| $endLabel       inx""") | ||||
|                 } | ||||
|                 else { | ||||
|  | ||||
|                     // bytes, step >= 2 or <= -2 | ||||
|  | ||||
|                     // loop over byte range via loopvar | ||||
|                     val varname = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                     asmgen.translateExpression(range.to) | ||||
|                     asmgen.translateExpression(range.from) | ||||
|                     asmgen.out(""" | ||||
|                 inx | ||||
|                 lda  ${ESTACK_LO_HEX},x | ||||
|                 sta  $varname | ||||
| $loopLabel""") | ||||
|                     asmgen.translate(stmt.body) | ||||
|                     asmgen.out(""" | ||||
| $continueLabel  lda  $varname""") | ||||
|                     if(stepsize>0) { | ||||
|                         asmgen.out(""" | ||||
|                 clc | ||||
|                 adc  #$stepsize | ||||
|                 sta  $varname | ||||
|                 cmp  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 bcc  $loopLabel | ||||
|                 beq  $loopLabel""") | ||||
|                     } else { | ||||
|                         asmgen.out(""" | ||||
|                 sec | ||||
|                 sbc  #${stepsize.absoluteValue} | ||||
|                 sta  $varname | ||||
|                 cmp  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 bcs  $loopLabel""") | ||||
|                     } | ||||
|                     asmgen.out(""" | ||||
| $endLabel       inx""") | ||||
|                 } | ||||
|             } | ||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { | ||||
|                 when { | ||||
|  | ||||
|                     // words, step 1 or -1 | ||||
|  | ||||
|                     stepsize == 1 || stepsize == -1 -> { | ||||
|                         asmgen.translateExpression(range.to) | ||||
|                         val varname = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                         val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position), | ||||
|                                 null, range.from, range.position) | ||||
|                         assignLoopvar.linkParents(stmt) | ||||
|                         asmgen.translate(assignLoopvar) | ||||
|                         asmgen.out(loopLabel) | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
|                 lda  $varname+1 | ||||
|                 cmp  $ESTACK_HI_PLUS1_HEX,x | ||||
|                 bne  + | ||||
|                 lda  $varname | ||||
|                 cmp  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 beq  $endLabel""") | ||||
|                         if(stepsize==1) { | ||||
|                             asmgen.out(""" | ||||
| +               inc  $varname | ||||
|                 bne  + | ||||
|                 inc  $varname+1             | ||||
|                             """) | ||||
|                         } else { | ||||
|                             asmgen.out(""" | ||||
| +               lda  $varname | ||||
|                 bne  + | ||||
|                 dec  $varname+1 | ||||
| +               dec  $varname""") | ||||
|                         } | ||||
|                         asmgen.out(""" | ||||
| +               jmp  $loopLabel | ||||
| $endLabel       inx""") | ||||
|                     } | ||||
|                     stepsize > 0 -> { | ||||
|  | ||||
|                         // (u)words, step >= 2 | ||||
|  | ||||
|                         asmgen.translateExpression(range.to) | ||||
|                         val varname = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                         val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position), | ||||
|                                 null, range.from, range.position) | ||||
|                         assignLoopvar.linkParents(stmt) | ||||
|                         asmgen.translate(assignLoopvar) | ||||
|                         asmgen.out(loopLabel) | ||||
|                         asmgen.translate(stmt.body) | ||||
|  | ||||
|                         if (iterableDt == DataType.ARRAY_UW) { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 clc | ||||
|                 adc  #<$stepsize | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 adc  #>$stepsize | ||||
|                 sta  $varname+1 | ||||
|                 lda  $ESTACK_HI_PLUS1_HEX,x | ||||
|                 cmp  $varname+1 | ||||
|                 bcc  $endLabel | ||||
|                 bne  $loopLabel | ||||
|                 lda  $varname | ||||
|                 cmp  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 bcc  $endLabel | ||||
|                 bcs  $loopLabel | ||||
| $endLabel       inx""") | ||||
|                         } else { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 clc | ||||
|                 adc  #<$stepsize | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 adc  #>$stepsize | ||||
|                 sta  $varname+1 | ||||
|                 lda  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 cmp  $varname | ||||
|                 lda  $ESTACK_HI_PLUS1_HEX,x | ||||
|                 sbc  $varname+1 | ||||
|                 bvc  + | ||||
|                 eor  #$80 | ||||
| +               bpl  $loopLabel                 | ||||
| $endLabel       inx""") | ||||
|                         } | ||||
|                     } | ||||
|                     else -> { | ||||
|  | ||||
|                         // (u)words, step <= -2 | ||||
|                         asmgen.translateExpression(range.to) | ||||
|                         val varname = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                         val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position), | ||||
|                                 null, range.from, range.position) | ||||
|                         assignLoopvar.linkParents(stmt) | ||||
|                         asmgen.translate(assignLoopvar) | ||||
|                         asmgen.out(loopLabel) | ||||
|                         asmgen.translate(stmt.body) | ||||
|  | ||||
|                         if(iterableDt==DataType.ARRAY_UW) { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 sec | ||||
|                 sbc  #<${stepsize.absoluteValue} | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 sbc  #>${stepsize.absoluteValue} | ||||
|                 sta  $varname+1 | ||||
|                 cmp  $ESTACK_HI_PLUS1_HEX,x | ||||
|                 bcc  $endLabel | ||||
|                 bne  $loopLabel | ||||
|                 lda  $varname | ||||
|                 cmp  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 bcs  $loopLabel | ||||
| $endLabel       inx""") | ||||
|                         } else { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 sec | ||||
|                 sbc  #<${stepsize.absoluteValue} | ||||
|                 sta  $varname | ||||
|                 pha | ||||
|                 lda  $varname+1 | ||||
|                 sbc  #>${stepsize.absoluteValue} | ||||
|                 sta  $varname+1 | ||||
|                 pla | ||||
|                 cmp  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 lda  $varname+1 | ||||
|                 sbc  $ESTACK_HI_PLUS1_HEX,x | ||||
|                 bvc  + | ||||
|                 eor  #$80 | ||||
| +               bpl  $loopLabel                 | ||||
| $endLabel       inx""") | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("range expression can only be byte or word") | ||||
|         } | ||||
|  | ||||
|         asmgen.loopEndLabels.pop() | ||||
|         asmgen.loopContinueLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         val continueLabel = asmgen.makeLabel("for_continue") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         asmgen.loopContinueLabels.push(continueLabel) | ||||
|         val iterableName = asmgen.asmIdentifierName(ident) | ||||
|         val decl = ident.targetVarDecl(program.namespace)!! | ||||
|         when(iterableDt) { | ||||
|             DataType.STR -> { | ||||
|                 asmgen.out(""" | ||||
|                     lda  #<$iterableName | ||||
|                     ldy  #>$iterableName | ||||
|                     sta  $loopLabel+1 | ||||
|                     sty  $loopLabel+2 | ||||
| $loopLabel          lda  ${65535.toHex()}       ; modified | ||||
|                     beq  $endLabel""") | ||||
|                 asmgen.out("  sta  ${asmgen.asmIdentifierName(stmt.loopVar)}") | ||||
|                 asmgen.translate(stmt.body) | ||||
|                 asmgen.out(""" | ||||
| $continueLabel      inc  $loopLabel+1 | ||||
|                     bne  $loopLabel | ||||
|                     inc  $loopLabel+2 | ||||
|                     bne  $loopLabel | ||||
| $endLabel""") | ||||
|             } | ||||
|             DataType.ARRAY_UB, DataType.ARRAY_B -> { | ||||
|                 // TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases | ||||
|                 val length = decl.arraysize!!.size()!! | ||||
|                 val counterLabel = asmgen.makeLabel("for_counter") | ||||
|                 val modifiedLabel = asmgen.makeLabel("for_modified") | ||||
|                 asmgen.out(""" | ||||
|                     lda  #<$iterableName | ||||
|                     ldy  #>$iterableName | ||||
|                     sta  $modifiedLabel+1 | ||||
|                     sty  $modifiedLabel+2 | ||||
|                     ldy  #0 | ||||
| $loopLabel          sty  $counterLabel | ||||
| $modifiedLabel      lda  ${65535.toHex()},y       ; modified""") | ||||
|                 asmgen.out("  sta  ${asmgen.asmIdentifierName(stmt.loopVar)}") | ||||
|                 asmgen.translate(stmt.body) | ||||
|                 asmgen.out(""" | ||||
| $continueLabel      ldy  $counterLabel | ||||
|                     iny | ||||
|                     cpy  #${length and 255} | ||||
|                     beq  $endLabel | ||||
|                     jmp  $loopLabel | ||||
| $counterLabel       .byte  0 | ||||
| $endLabel""") | ||||
|             } | ||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { | ||||
|                 // TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases | ||||
|                 val length = decl.arraysize!!.size()!! * 2 | ||||
|                 val counterLabel = asmgen.makeLabel("for_counter") | ||||
|                 val modifiedLabel = asmgen.makeLabel("for_modified") | ||||
|                 val modifiedLabel2 = asmgen.makeLabel("for_modified2") | ||||
|                 val loopvarName = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                 asmgen.out(""" | ||||
|                     lda  #<$iterableName | ||||
|                     ldy  #>$iterableName | ||||
|                     sta  $modifiedLabel+1 | ||||
|                     sty  $modifiedLabel+2 | ||||
|                     lda  #<$iterableName+1 | ||||
|                     ldy  #>$iterableName+1 | ||||
|                     sta  $modifiedLabel2+1 | ||||
|                     sty  $modifiedLabel2+2 | ||||
|                     ldy  #0 | ||||
| $loopLabel          sty  $counterLabel | ||||
| $modifiedLabel      lda  ${65535.toHex()},y       ; modified | ||||
|                     sta  $loopvarName | ||||
| $modifiedLabel2     lda  ${65535.toHex()},y       ; modified | ||||
|                     sta  $loopvarName+1""") | ||||
|                 asmgen.translate(stmt.body) | ||||
|                 asmgen.out(""" | ||||
| $continueLabel      ldy  $counterLabel | ||||
|                     iny | ||||
|                     iny | ||||
|                     cpy  #${length and 255} | ||||
|                     beq  $endLabel | ||||
|                     jmp  $loopLabel | ||||
| $counterLabel       .byte  0 | ||||
| $endLabel""") | ||||
|             } | ||||
|             DataType.ARRAY_F -> { | ||||
|                 throw AssemblyError("for loop with floating point variables is not supported") | ||||
|             } | ||||
|             else -> throw AssemblyError("can't iterate over $iterableDt") | ||||
|         } | ||||
|         asmgen.loopEndLabels.pop() | ||||
|         asmgen.loopContinueLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) { | ||||
|         // TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter var in such cases | ||||
|         if (range.isEmpty()) | ||||
|             throw AssemblyError("empty range") | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         val continueLabel = asmgen.makeLabel("for_continue") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         asmgen.loopContinueLabels.push(continueLabel) | ||||
|         when(iterableDt) { | ||||
|             DataType.ARRAY_B, DataType.ARRAY_UB -> { | ||||
|                 val counterLabel = asmgen.makeLabel("for_counter") | ||||
|                 // loop over byte range via loopvar | ||||
|                 val varname = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                 when { | ||||
|                     range.step==1 -> { | ||||
|                         // step = 1 | ||||
|                         asmgen.out(""" | ||||
|                 lda  #${range.first} | ||||
|                 sta  $varname | ||||
|                 lda  #${range.last-range.first+1 and 255} | ||||
|                 sta  $counterLabel | ||||
| $loopLabel""") | ||||
|                             asmgen.translate(stmt.body) | ||||
|                             asmgen.out(""" | ||||
| $continueLabel  dec  $counterLabel | ||||
|                 beq  $endLabel | ||||
|                 inc  $varname | ||||
|                 jmp  $loopLabel | ||||
| $counterLabel   .byte  0                 | ||||
| $endLabel""") | ||||
|                     } | ||||
|                     range.step==-1 -> { | ||||
|                         // step = -1 | ||||
|                         asmgen.out(""" | ||||
|                 lda  #${range.first} | ||||
|                 sta  $varname | ||||
|                 lda  #${range.first-range.last+1 and 255} | ||||
|                 sta  $counterLabel | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  dec  $counterLabel | ||||
|                 beq  $endLabel | ||||
|                 dec  $varname | ||||
|                 jmp  $loopLabel | ||||
| $counterLabel   .byte  0                 | ||||
| $endLabel""") | ||||
|                     } | ||||
|                     range.step >= 2 -> { | ||||
|                         // step >= 2 | ||||
|                         asmgen.out(""" | ||||
|                 lda  #${(range.last-range.first) / range.step + 1} | ||||
|                 sta  $counterLabel | ||||
|                 lda  #${range.first} | ||||
|                 sta  $varname | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  dec  $counterLabel | ||||
|                 beq  $endLabel | ||||
|                 lda  $varname | ||||
|                 clc | ||||
|                 adc  #${range.step} | ||||
|                 sta  $varname | ||||
|                 jmp  $loopLabel | ||||
| $counterLabel   .byte  0                 | ||||
| $endLabel""") | ||||
|                     } | ||||
|                     else -> { | ||||
|                         // step <= -2 | ||||
|                         asmgen.out(""" | ||||
|                 lda  #${(range.first-range.last) / range.step.absoluteValue + 1} | ||||
|                 sta  $counterLabel | ||||
|                 lda  #${range.first} | ||||
|                 sta  $varname | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  dec  $counterLabel | ||||
|                 beq  $endLabel | ||||
|                 lda  $varname | ||||
|                 sec | ||||
|                 sbc  #${range.step.absoluteValue} | ||||
|                 sta  $varname | ||||
|                 jmp  $loopLabel | ||||
| $counterLabel   .byte  0                 | ||||
| $endLabel""") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { | ||||
|                 // loop over word range via loopvar | ||||
|                 val varname = asmgen.asmIdentifierName(stmt.loopVar) | ||||
|                 when { | ||||
|                     range.step == 1 -> { | ||||
|                         // word, step = 1 | ||||
|                         val lastValue = range.last+1 | ||||
|                         asmgen.out(""" | ||||
|                 lda  #<${range.first} | ||||
|                 ldy  #>${range.first} | ||||
|                 sta  $varname | ||||
|                 sty  $varname+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  inc  $varname | ||||
|                 bne  + | ||||
|                 inc  $varname+1 | ||||
| +               lda  $varname | ||||
|                 cmp  #<$lastValue | ||||
|                 bne  + | ||||
|                 lda  $varname+1 | ||||
|                 cmp  #>$lastValue | ||||
|                 beq  $endLabel | ||||
| +               jmp  $loopLabel | ||||
| $endLabel""") | ||||
|                     } | ||||
|                     range.step == -1 -> { | ||||
|                         // word, step = 1 | ||||
|                         val lastValue = range.last-1 | ||||
|                         asmgen.out(""" | ||||
|                 lda  #<${range.first} | ||||
|                 ldy  #>${range.first} | ||||
|                 sta  $varname | ||||
|                 sty  $varname+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  lda  $varname | ||||
|                 bne  + | ||||
|                 dec  $varname+1 | ||||
| +               dec  $varname | ||||
|                 lda  $varname | ||||
|                 cmp  #<$lastValue | ||||
|                 bne  + | ||||
|                 lda  $varname+1 | ||||
|                 cmp  #>$lastValue | ||||
|                 beq  $endLabel | ||||
| +               jmp  $loopLabel | ||||
| $endLabel""") | ||||
|                     } | ||||
|                     range.step >= 2 -> { | ||||
|                         // word, step >= 2 | ||||
|                         // note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence | ||||
|                         val lastValue = range.last+range.step | ||||
|                         asmgen.out(""" | ||||
|                 lda  #<${range.first} | ||||
|                 ldy  #>${range.first} | ||||
|                 sta  $varname | ||||
|                 sty  $varname+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  clc | ||||
|                 lda  $varname | ||||
|                 adc  #<${range.step} | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 adc  #>${range.step} | ||||
|                 sta  $varname+1 | ||||
|                 lda  $varname | ||||
|                 cmp  #<$lastValue | ||||
|                 bne  + | ||||
|                 lda  $varname+1 | ||||
|                 cmp  #>$lastValue | ||||
|                 beq  $endLabel | ||||
| +               jmp  $loopLabel | ||||
| $endLabel""") | ||||
|                     } | ||||
|                     else -> { | ||||
|                         // step <= -2 | ||||
|                         // note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence | ||||
|                         val lastValue = range.last+range.step | ||||
|                         asmgen.out(""" | ||||
|                 lda  #<${range.first} | ||||
|                 ldy  #>${range.first} | ||||
|                 sta  $varname | ||||
|                 sty  $varname+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
| $continueLabel  sec | ||||
|                 lda  $varname | ||||
|                 sbc  #<${range.step.absoluteValue} | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 sbc  #>${range.step.absoluteValue} | ||||
|                 sta  $varname+1 | ||||
|                 lda  $varname | ||||
|                 cmp  #<$lastValue | ||||
|                 bne  + | ||||
|                 lda  $varname+1 | ||||
|                 cmp  #>$lastValue | ||||
|                 beq  $endLabel | ||||
| +               jmp  $loopLabel | ||||
| $endLabel""") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("range expression can only be byte or word") | ||||
|         } | ||||
|         asmgen.loopEndLabels.pop() | ||||
|         asmgen.loopContinueLabels.pop() | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,300 +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.AssignTarget | ||||
| import prog8.ast.statements.Subroutine | ||||
| import prog8.ast.statements.SubroutineParameter | ||||
| import prog8.compiler.AssemblyError | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX | ||||
| import prog8.compiler.toHex | ||||
|  | ||||
|  | ||||
| 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() | ||||
|         if(saveX) | ||||
|             asmgen.out("  stx  c64.SCRATCH_ZPREGX")        // we only save X for now (required! is the eval stack pointer), screw A and Y... | ||||
|  | ||||
|         val subName = asmgen.asmIdentifierName(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. | ||||
|                             argsViaStackEvaluation(stmt, sub) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         asmgen.out("  jsr  $subName") | ||||
|  | ||||
|         if(saveX) | ||||
|             asmgen.out("  ldx  c64.SCRATCH_ZPREGX")        // restore X again | ||||
|     } | ||||
|  | ||||
|     private fun argsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) { | ||||
|         for (arg in stmt.args.reversed()) | ||||
|             asmgen.translateExpression(arg) | ||||
|         for (regparam in sub.asmParameterRegisters) { | ||||
|             when (regparam.registerOrPair) { | ||||
|                 RegisterOrPair.A -> asmgen.out(" inx |  lda  $ESTACK_LO_HEX,x") | ||||
|                 RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead") | ||||
|                 RegisterOrPair.Y -> asmgen.out(" inx |  ldy  $ESTACK_LO_HEX,x") | ||||
|                 RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead") | ||||
|                 RegisterOrPair.AY -> asmgen.out(" inx |  lda  $ESTACK_LO_HEX,x |  ldy  $ESTACK_HI_HEX,x") | ||||
|                 RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead") | ||||
|                 null -> { | ||||
|                 } | ||||
|             } | ||||
|             when (regparam.statusflag) { | ||||
|                 Statusflag.Pc -> asmgen.out(""" | ||||
|                         inx | ||||
|                         pha | ||||
|                         lda  $ESTACK_LO_HEX,x | ||||
|                         beq  + | ||||
|                         sec   | ||||
|                         bcs  ++ | ||||
|             +           clc | ||||
|             +           pla | ||||
|             """) | ||||
|                 null -> { | ||||
|                 } | ||||
|                 else -> throw AssemblyError("can only use Carry as status flag parameter") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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(!argumentTypeCompatible(valueDt, parameter.value.type)) | ||||
|             throw AssemblyError("argument type incompatible") | ||||
|  | ||||
|         val paramVar = parameter.value | ||||
|         val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".") | ||||
|         val target = AssignTarget(IdentifierReference(scopedParamVar, sub.position), null, null, sub.position) | ||||
|         target.linkParents(value.parent) | ||||
|         when (value) { | ||||
|             is NumericLiteralValue -> { | ||||
|                 // optimize when the argument is a constant literal | ||||
|                 when(parameter.value.type) { | ||||
|                     in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort()) | ||||
|                     in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt()) | ||||
|                     DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble()) | ||||
|                     in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?")    // TODO huh | ||||
|                     else -> throw AssemblyError("weird parameter datatype") | ||||
|                 } | ||||
|             } | ||||
|             is IdentifierReference -> { | ||||
|                 // optimize when the argument is a variable | ||||
|                 when (parameter.value.type) { | ||||
|                     in ByteDatatypes -> asmgen.assignFromByteVariable(target, value) | ||||
|                     in WordDatatypes -> asmgen.assignFromWordVariable(target, value) | ||||
|                     DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value) | ||||
|                     in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?")     // TODO huh | ||||
|                     else -> throw AssemblyError("weird parameter datatype") | ||||
|                 } | ||||
|             } | ||||
|             is DirectMemoryRead -> { | ||||
|                 when(value.addressExpression) { | ||||
|                     is NumericLiteralValue -> { | ||||
|                         val address = (value.addressExpression as NumericLiteralValue).number.toInt() | ||||
|                         asmgen.assignFromMemoryByte(target, address, null) | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference) | ||||
|                     } | ||||
|                     else -> { | ||||
|                         asmgen.translateExpression(value.addressExpression) | ||||
|                         asmgen.out("  jsr  prog8_lib.read_byte_from_address |  inx") | ||||
|                         asmgen.assignFromRegister(target, CpuRegister.A) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else -> { | ||||
|                 asmgen.translateExpression(value) | ||||
|                 asmgen.assignFromEvalResult(target) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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(!argumentTypeCompatible(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.asmIdentifierName(value) | ||||
|                             asmgen.out(""" | ||||
|             lda  $sourceName | ||||
|             beq  + | ||||
|             sec   | ||||
|             bcs  ++ | ||||
| +           clc | ||||
| + | ||||
| """) | ||||
|                         } | ||||
|                         else -> { | ||||
|                             asmgen.translateExpression(value) | ||||
|                             asmgen.out(""" | ||||
|             inx | ||||
|             pha | ||||
|             lda  $ESTACK_LO_HEX,x | ||||
|             beq  + | ||||
|             sec   | ||||
|             bcs  ++ | ||||
| +           clc | ||||
| +           pla | ||||
| """) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 else throw AssemblyError("can only use Carry as status flag parameter") | ||||
|             } | ||||
|             register!=null && register.name.length==1 -> { | ||||
|                 when (value) { | ||||
|                     is NumericLiteralValue -> { | ||||
|                         asmgen.assignToRegister(CpuRegister.valueOf(register.name), value.number.toShort(), null) | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         asmgen.assignToRegister(CpuRegister.valueOf(register.name), null, value) | ||||
|                     } | ||||
|                     else -> { | ||||
|                         asmgen.translateExpression(value) | ||||
|                         when(register) { | ||||
|                             RegisterOrPair.A -> asmgen.out("  inx | lda  $ESTACK_LO_HEX,x") | ||||
|                             RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead") | ||||
|                             RegisterOrPair.Y -> asmgen.out("  inx | ldy  $ESTACK_LO_HEX,x") | ||||
|                             else -> throw AssemblyError("cannot assign to register pair") | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             register!=null && register.name.length==2 -> { | ||||
|                 // register pair as a 16-bit value (only possible for subroutine parameters) | ||||
|                 when (value) { | ||||
|                     is NumericLiteralValue -> { | ||||
|                         // optimize when the argument is a constant literal | ||||
|                         val hex = value.number.toHex() | ||||
|                         when (register) { | ||||
|                             RegisterOrPair.AX -> asmgen.out("  lda  #<$hex  |  ldx  #>$hex") | ||||
|                             RegisterOrPair.AY -> asmgen.out("  lda  #<$hex  |  ldy  #>$hex") | ||||
|                             RegisterOrPair.XY -> asmgen.out("  ldx  #<$hex  |  ldy  #>$hex") | ||||
|                             else -> {} | ||||
|                         } | ||||
|                     } | ||||
|                     is AddressOf -> { | ||||
|                         // optimize when the argument is an address of something | ||||
|                         val sourceName = asmgen.asmIdentifierName(value.identifier) | ||||
|                         when (register) { | ||||
|                             RegisterOrPair.AX -> asmgen.out("  lda  #<$sourceName  |  ldx  #>$sourceName") | ||||
|                             RegisterOrPair.AY -> asmgen.out("  lda  #<$sourceName  |  ldy  #>$sourceName") | ||||
|                             RegisterOrPair.XY -> asmgen.out("  ldx  #<$sourceName  |  ldy  #>$sourceName") | ||||
|                             else -> {} | ||||
|                         } | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         val sourceName = asmgen.asmIdentifierName(value) | ||||
|                         if(valueDt in PassByReferenceDatatypes) { | ||||
|                             when (register) { | ||||
|                                 RegisterOrPair.AX -> asmgen.out("  lda  #<$sourceName  |  ldx  #>$sourceName") | ||||
|                                 RegisterOrPair.AY -> asmgen.out("  lda  #<$sourceName  |  ldy  #>$sourceName") | ||||
|                                 RegisterOrPair.XY -> asmgen.out("  ldx  #<$sourceName  |  ldy  #>$sourceName") | ||||
|                                 else -> {} | ||||
|                             } | ||||
|                         } else { | ||||
|                             when (register) { | ||||
|                                 RegisterOrPair.AX -> asmgen.out("  lda  $sourceName  |  ldx  $sourceName+1") | ||||
|                                 RegisterOrPair.AY -> asmgen.out("  lda  $sourceName  |  ldy  $sourceName+1") | ||||
|                                 RegisterOrPair.XY -> asmgen.out("  ldx  $sourceName  |  ldy  $sourceName+1") | ||||
|                                 else -> {} | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     else -> { | ||||
|                         asmgen.translateExpression(value) | ||||
|                         if (register == RegisterOrPair.AX || register == RegisterOrPair.XY) | ||||
|                             throw AssemblyError("can't use X register here - use a variable") | ||||
|                         else if (register == RegisterOrPair.AY) | ||||
|                             asmgen.out("  inx |  lda  $ESTACK_LO_HEX,x  |  ldy  $ESTACK_HI_HEX,x") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun argumentTypeCompatible(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,128 +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.target.c64.C64MachineDefinition.C64Zeropage | ||||
| 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.asmIdentifierName(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  c64flt.inc_var_f" else "  jsr  c64flt.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.asmIdentifierName(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 -> throw AssemblyError("weird target type $targetMemory") | ||||
|                 } | ||||
|             } | ||||
|             targetArrayIdx!=null -> { | ||||
|                 val index = targetArrayIdx.arrayspec.index | ||||
|                 val what = asmgen.asmIdentifierName(targetArrayIdx.identifier) | ||||
|                 val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) | ||||
|                 val elementDt = ArrayElementTypes.getValue(arrayDt) | ||||
|                 when(index) { | ||||
|                     is NumericLiteralValue -> { | ||||
|                         val indexValue = index.number.toInt() * elementDt.memorySize() | ||||
|                         when(elementDt) { | ||||
|                             in ByteDatatypes -> asmgen.out(if (incr) "  inc  $what+$indexValue" else "  dec  $what+$indexValue") | ||||
|                             in WordDatatypes -> { | ||||
|                                 if(incr) | ||||
|                                     asmgen.out(" inc  $what+$indexValue |  bne  + |  inc  $what+$indexValue+1 |+") | ||||
|                                 else | ||||
|                                     asmgen.out(""" | ||||
|         lda  $what+$indexValue | ||||
|         bne  + | ||||
|         dec  $what+$indexValue+1 | ||||
| +       dec  $what+$indexValue  | ||||
| """) | ||||
|                             } | ||||
|                             DataType.FLOAT -> { | ||||
|                                 asmgen.out("  lda  #<$what+$indexValue |  ldy  #>$what+$indexValue") | ||||
|                                 asmgen.out(if(incr) "  jsr  c64flt.inc_var_f" else "  jsr  c64flt.dec_var_f") | ||||
|                             } | ||||
|                             else -> throw AssemblyError("need numeric type") | ||||
|                         } | ||||
|                     } | ||||
|                     is IdentifierReference -> { | ||||
|                         asmgen.translateArrayIndexIntoA(targetArrayIdx) | ||||
|                         incrDecrArrayvalueWithIndexA(incr, arrayDt, what) | ||||
|                     } | ||||
|                     else -> { | ||||
|                         asmgen.translateArrayIndexIntoA(targetArrayIdx) | ||||
|                         incrDecrArrayvalueWithIndexA(incr, arrayDt, what) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("weird target type ${stmt.target}") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) { | ||||
|         asmgen.out("  stx  ${C64Zeropage.SCRATCH_REG_X} |  tax") | ||||
|         when(arrayDt) { | ||||
|             DataType.STR, | ||||
|             DataType.ARRAY_UB, DataType.ARRAY_B -> { | ||||
|                 asmgen.out(if(incr) "  inc  $arrayVarName,x" else "  dec  $arrayVarName,x") | ||||
|             } | ||||
|             DataType.ARRAY_UW, DataType.ARRAY_W -> { | ||||
|                 if(incr) | ||||
|                     asmgen.out(" inc  $arrayVarName,x |  bne  + |  inc  $arrayVarName+1,x |+") | ||||
|                 else | ||||
|                     asmgen.out(""" | ||||
|         lda  $arrayVarName,x | ||||
|         bne  + | ||||
|         dec  $arrayVarName+1,x | ||||
| +       dec  $arrayVarName  | ||||
| """) | ||||
|             } | ||||
|             DataType.ARRAY_F -> { | ||||
|                 asmgen.out("  lda  #<$arrayVarName |  ldy  #>$arrayVarName") | ||||
|                 asmgen.out(if(incr) "  jsr  c64flt.inc_indexed_var_f" else "  jsr  c64flt.dec_indexed_var_f") | ||||
|             } | ||||
|             else -> throw AssemblyError("weird array dt") | ||||
|         } | ||||
|         asmgen.out("  ldx  ${C64Zeropage.SCRATCH_REG_X}") | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package prog8.compiler.target.c64 | ||||
| package prog8.compiler.target.cbm | ||||
| 
 | ||||
| import prog8.compiler.CompilationOptions | ||||
| import prog8.compiler.OutputType | ||||
| @@ -7,27 +7,27 @@ import prog8.compiler.target.generatedLabelPrefix | ||||
| import java.nio.file.Path | ||||
| 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 prgFile = outputDir.resolve("$name.prg") | ||||
|     private val binFile = outputDir.resolve("$name.bin") | ||||
|     private val viceMonListFile = outputDir.resolve("$name.vice-mon-list") | ||||
| 
 | ||||
|     override fun assemble(options: CompilationOptions) { | ||||
|         // add "-Wlong-branch"  to see warnings about conversion of branch instructions to jumps | ||||
|         // add "-Wlong-branch"  to see warnings about conversion of branch instructions to jumps (default = do this silently) | ||||
|         val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", | ||||
|                 "-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch", | ||||
|                 "-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror", | ||||
|                 "--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor") | ||||
| 
 | ||||
|         val outFile = when (options.output) { | ||||
|             OutputType.PRG -> { | ||||
|                 command.add("--cbm-prg") | ||||
|                 println("\nCreating C-64 prg.") | ||||
|                 println("\nCreating prg for target $compTarget.") | ||||
|                 prgFile | ||||
|             } | ||||
|             OutputType.RAW -> { | ||||
|                 command.add("--nostart") | ||||
|                 println("\nCreating raw binary.") | ||||
|                 println("\nCreating raw binary for target $compTarget.") | ||||
|                 binFile | ||||
|             } | ||||
|         } | ||||
| @@ -1,5 +1,6 @@ | ||||
| package prog8.compiler.target.c64 | ||||
| package prog8.compiler.target.cbm | ||||
| 
 | ||||
| import prog8.ast.antlr.escape | ||||
| import java.io.CharConversionException | ||||
| 
 | ||||
| object Petscii { | ||||
| @@ -1049,41 +1050,90 @@ object Petscii { | ||||
|     private val encodingScreencodeLowercase = decodingScreencodeLowercase.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> { | ||||
|         val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase | ||||
|         return text.map { | ||||
|             val petscii = lookup[it] | ||||
|             petscii?.toShort() ?: if(it=='\u0000') | ||||
|                 0.toShort() | ||||
|             else { | ||||
|         fun encodeChar(chr3: Char, lowercase: Boolean): Short { | ||||
|             val chr = replaceSpecial(chr3) | ||||
|             val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr] | ||||
|             return screencode?.toShort() ?: when (chr) { | ||||
|                 '\u0000' -> 0.toShort() | ||||
|                 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" | ||||
|                 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 { | ||||
|         val decodeTable = if(lowercase) decodingPetsciiLowercase else decodingPetsciiUppercase | ||||
|         return petscii.map { decodeTable[it.toInt()] }.joinToString("") | ||||
|         return petscii.map { | ||||
|             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> { | ||||
|         val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase | ||||
|         return text.map{ | ||||
|             val screencode = lookup[it] | ||||
|             screencode?.toShort() ?: if(it=='\u0000') | ||||
|                 0.toShort() | ||||
|             else { | ||||
|         fun encodeChar(chr3: Char, lowercase: Boolean): Short { | ||||
|             val chr = replaceSpecial(chr3) | ||||
|             val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr] | ||||
|             return screencode?.toShort() ?: when (chr) { | ||||
|                 '\u0000' -> 0.toShort() | ||||
|                 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" | ||||
|                 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 { | ||||
|         val decodeTable = if(lowercase) decodingScreencodeLowercase else decodingScreencodeUppercase | ||||
|         return screencode.map { decodeTable[it.toInt()] }.joinToString("") | ||||
|         return screencode.map { | ||||
|             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 { | ||||
							
								
								
									
										1474
									
								
								compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1474
									
								
								compiler/src/prog8/compiler/target/cpu6502/codegen/AsmGen.kt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,7 +1,4 @@ | ||||
| package prog8.compiler.target.c64.codegen | ||||
| 
 | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX | ||||
| import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX | ||||
| package prog8.compiler.target.cpu6502.codegen | ||||
| 
 | ||||
| 
 | ||||
| // note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations | ||||
| @@ -87,10 +84,10 @@ private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): | ||||
|     // the repeated lda can be removed | ||||
|     val mods = mutableListOf<Modification>() | ||||
|     for(lines in linesByFour) { | ||||
|         if(lines[0].value.trim()=="lda  $ESTACK_LO_PLUS1_HEX,x" && | ||||
|         if(lines[0].value.trim()=="lda  P8ESTACK_LO+1,x" && | ||||
|                 lines[1].value.trim().startsWith("cmp ") && | ||||
|                 lines[2].value.trim().startsWith("beq ") && | ||||
|                 lines[3].value.trim()=="lda  $ESTACK_LO_PLUS1_HEX,x") { | ||||
|                 lines[3].value.trim()=="lda  P8ESTACK_LO+1,x") { | ||||
|             mods.add(Modification(lines[3].index, true, null)) // remove the second lda | ||||
|         } | ||||
|     } | ||||
| @@ -102,10 +99,10 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S | ||||
|     // this is a lot harder for word values because the instruction sequence varies. | ||||
|     val mods = mutableListOf<Modification>() | ||||
|     for(lines in linesByFour) { | ||||
|         if(lines[0].value.trim()=="sta  $ESTACK_LO_HEX,x" && | ||||
|         if(lines[0].value.trim()=="sta  P8ESTACK_LO,x" && | ||||
|                 lines[1].value.trim()=="dex" && | ||||
|                 lines[2].value.trim()=="inx" && | ||||
|                 lines[3].value.trim()=="lda  $ESTACK_LO_HEX,x") { | ||||
|                 lines[3].value.trim()=="lda  P8ESTACK_LO,x") { | ||||
|             mods.add(Modification(lines[1].index, true, null)) | ||||
|             mods.add(Modification(lines[2].index, true, null)) | ||||
|             mods.add(Modification(lines[3].index, true, null)) | ||||
| @@ -154,7 +151,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri | ||||
|         } | ||||
| 
 | ||||
|         if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") && | ||||
|                 fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("jsr  c64flt.copy_float")) { | ||||
|                 fifth.startsWith("lda") && sixth.startsWith("ldy") && | ||||
|                 (seventh.startsWith("jsr  floats.copy_float") || seventh.startsWith("jsr  cx16flt.copy_float"))) { | ||||
| 
 | ||||
|             val nineth = pair[8].value.trimStart() | ||||
|             val tenth = pair[9].value.trimStart() | ||||
| @@ -164,7 +162,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri | ||||
|             val fourteenth = pair[13].value.trimStart() | ||||
| 
 | ||||
|             if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") && | ||||
|                     twelveth.startsWith("lda") && thirteenth.startsWith("ldy") && fourteenth.startsWith("jsr  c64flt.copy_float")) { | ||||
|                     twelveth.startsWith("lda") && thirteenth.startsWith("ldy") && | ||||
|                     (fourteenth.startsWith("jsr  floats.copy_float") || fourteenth.startsWith("jsr  cx16flt.copy_float"))) { | ||||
| 
 | ||||
|                 if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) { | ||||
|                     // identical float init | ||||
| @@ -180,7 +179,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri | ||||
| } | ||||
| 
 | ||||
| private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { | ||||
|     // sta X + lda X,  sty X + ldy X,   stx X + ldx X  -> the second instruction can be eliminated | ||||
|     // sta X + lda X,  sty X + ldy X,   stx X + ldx X  -> the second instruction can OFTEN 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>() | ||||
|     for (pair in linesByFour) { | ||||
|         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("stx ") && second.startsWith("ldx ")) | ||||
|         ) { | ||||
|             val firstLoc = first.substring(4) | ||||
|             val secondLoc = second.substring(4) | ||||
|             val third = pair[2].value.trimStart() | ||||
|             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) { | ||||
|                     mods.add(Modification(pair[1].index, true, null)) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return mods | ||||
| } | ||||
| 
 | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -0,0 +1,592 @@ | ||||
| package prog8.compiler.target.cpu6502.codegen | ||||
|  | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.ArrayToElementTypes | ||||
| import prog8.ast.base.DataType | ||||
| import prog8.ast.base.RegisterOrPair | ||||
| import prog8.ast.expressions.IdentifierReference | ||||
| import prog8.ast.expressions.RangeExpr | ||||
| import prog8.ast.statements.ForLoop | ||||
| import prog8.ast.toHex | ||||
| import prog8.compiler.AssemblyError | ||||
| import kotlin.math.absoluteValue | ||||
|  | ||||
| internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { | ||||
|  | ||||
|     internal fun translate(stmt: ForLoop) { | ||||
|         val iterableDt = stmt.iterable.inferType(program) | ||||
|         if(!iterableDt.isKnown) | ||||
|             throw AssemblyError("unknown dt") | ||||
|         when(stmt.iterable) { | ||||
|             is RangeExpr -> { | ||||
|                 val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() | ||||
|                 if(range==null) { | ||||
|                     translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), stmt.iterable as RangeExpr) | ||||
|                 } else { | ||||
|                     translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.UNDEFINED), range) | ||||
|                 } | ||||
|             } | ||||
|             is 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") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         val modifiedLabel = asmgen.makeLabel("for_modified") | ||||
|         val modifiedLabel2 = asmgen.makeLabel("for_modifiedb") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         val stepsize=range.step.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) { | ||||
|             DataType.ARRAY_B, DataType.ARRAY_UB -> { | ||||
|                 if (stepsize==1 || stepsize==-1) { | ||||
|  | ||||
|                     // bytes array, step 1 or -1 | ||||
|  | ||||
|                     val incdec = if(stepsize==1) "inc" else "dec" | ||||
|                     // loop over byte range via loopvar | ||||
|                     val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|                     asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null) | ||||
|                     asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null) | ||||
|                     asmgen.out(loopLabel) | ||||
|                     asmgen.translate(stmt.body) | ||||
|                     asmgen.out(""" | ||||
|                         lda  $varname | ||||
| $modifiedLabel          cmp  #0         ; modified  | ||||
|                         beq  $endLabel | ||||
|                         $incdec  $varname""") | ||||
|                     asmgen.jmp(loopLabel) | ||||
|                     asmgen.out(endLabel) | ||||
|  | ||||
|                 } else { | ||||
|  | ||||
|                     // bytes, step >= 2 or <= -2 | ||||
|  | ||||
|                     // loop over byte range via loopvar | ||||
|                     val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|                     asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null) | ||||
|                     asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null) | ||||
|                     asmgen.out(loopLabel) | ||||
|                     asmgen.translate(stmt.body) | ||||
|                     if(stepsize>0) { | ||||
|                         asmgen.out(""" | ||||
|                             lda  $varname | ||||
|                             clc | ||||
|                             adc  #$stepsize | ||||
|                             sta  $varname | ||||
| $modifiedLabel              cmp  #0    ; modified | ||||
|                             bmi  $loopLabel | ||||
|                             beq  $loopLabel""") | ||||
|                     } else { | ||||
|                         asmgen.out(""" | ||||
|                             lda  $varname | ||||
|                             sec | ||||
|                             sbc  #${stepsize.absoluteValue} | ||||
|                             sta  $varname | ||||
| $modifiedLabel              cmp  #0     ; modified | ||||
|                             bpl  $loopLabel""") | ||||
|                     } | ||||
|                     asmgen.out(endLabel) | ||||
|                 } | ||||
|             } | ||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { | ||||
|                 when { | ||||
|  | ||||
|                     // words, step 1 or -1 | ||||
|  | ||||
|                     stepsize == 1 || stepsize == -1 -> { | ||||
|                         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|                         assignLoopvar(stmt, range) | ||||
|                         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||
|                         asmgen.out(""" | ||||
|                             sty  $modifiedLabel+1 | ||||
|                             sta  $modifiedLabel2+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
|                 lda  $varname+1 | ||||
| $modifiedLabel  cmp  #0    ; modified  | ||||
|                 bne  + | ||||
|                 lda  $varname | ||||
| $modifiedLabel2 cmp  #0    ; modified  | ||||
|                 beq  $endLabel""") | ||||
|                         if(stepsize==1) { | ||||
|                             asmgen.out(""" | ||||
| +               inc  $varname | ||||
|                 bne  $loopLabel | ||||
|                 inc  $varname+1""") | ||||
|                             asmgen.jmp(loopLabel) | ||||
|                         } else { | ||||
|                             asmgen.out(""" | ||||
| +               lda  $varname | ||||
|                 bne  + | ||||
|                 dec  $varname+1 | ||||
| +               dec  $varname""") | ||||
|                             asmgen.jmp(loopLabel) | ||||
|                         } | ||||
|                         asmgen.out(endLabel) | ||||
|                     } | ||||
|                     stepsize > 0 -> { | ||||
|  | ||||
|                         // (u)words, step >= 2 | ||||
|                         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|                         assignLoopvar(stmt, range) | ||||
|                         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||
|                         asmgen.out(""" | ||||
|                             sty  $modifiedLabel+1 | ||||
|                             sta  $modifiedLabel2+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|  | ||||
|                         if (iterableDt == DataType.ARRAY_UW) { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 clc | ||||
|                 adc  #<$stepsize | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 adc  #>$stepsize | ||||
|                 sta  $varname+1 | ||||
| $modifiedLabel  cmp  #0     ; modified | ||||
|                 bcc  $loopLabel | ||||
|                 bne  $endLabel | ||||
| $modifiedLabel2 lda  #0     ; modified | ||||
|                 cmp  $varname | ||||
|                 bcc  $endLabel | ||||
|                 bcs  $loopLabel | ||||
| $endLabel""") | ||||
|                         } else { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 clc | ||||
|                 adc  #<$stepsize | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 adc  #>$stepsize | ||||
|                 sta  $varname+1 | ||||
| $modifiedLabel2 lda  #0   ; modified | ||||
|                 cmp  $varname | ||||
| $modifiedLabel  lda  #0   ; modified | ||||
|                 sbc  $varname+1 | ||||
|                 bvc  + | ||||
|                 eor  #$80 | ||||
| +               bpl  $loopLabel                 | ||||
| $endLabel""") | ||||
|                         } | ||||
|                     } | ||||
|                     else -> { | ||||
|  | ||||
|                         // (u)words, step <= -2 | ||||
|                         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|                         assignLoopvar(stmt, range) | ||||
|                         asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY) | ||||
|                         asmgen.out(""" | ||||
|                             sty  $modifiedLabel+1 | ||||
|                             sta  $modifiedLabel2+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|  | ||||
|                         if(iterableDt==DataType.ARRAY_UW) { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 sec | ||||
|                 sbc  #<${stepsize.absoluteValue} | ||||
|                 sta  $varname | ||||
|                 lda  $varname+1 | ||||
|                 sbc  #>${stepsize.absoluteValue} | ||||
|                 sta  $varname+1 | ||||
| $modifiedLabel  cmp  #0    ; modified                 | ||||
|                 bcc  $endLabel | ||||
|                 bne  $loopLabel | ||||
|                 lda  $varname | ||||
| $modifiedLabel2 cmp  #0    ; modified  | ||||
|                 bcs  $loopLabel | ||||
| $endLabel""") | ||||
|                         } else { | ||||
|                             asmgen.out(""" | ||||
|                 lda  $varname | ||||
|                 sec | ||||
|                 sbc  #<${stepsize.absoluteValue} | ||||
|                 sta  $varname | ||||
|                 pha | ||||
|                 lda  $varname+1 | ||||
|                 sbc  #>${stepsize.absoluteValue} | ||||
|                 sta  $varname+1 | ||||
|                 pla | ||||
| $modifiedLabel2 cmp  #0    ; modified  | ||||
|                 lda  $varname+1 | ||||
| $modifiedLabel  sbc  #0    ; modified | ||||
|                 bvc  + | ||||
|                 eor  #$80 | ||||
| +               bpl  $loopLabel                 | ||||
| $endLabel""") | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("range expression can only be byte or word") | ||||
|         } | ||||
|  | ||||
|         asmgen.loopEndLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         val iterableName = asmgen.asmVariableName(ident) | ||||
|         val decl = ident.targetVarDecl(program)!! | ||||
|         when(iterableDt) { | ||||
|             DataType.STR -> { | ||||
|                 asmgen.out(""" | ||||
|                     lda  #<$iterableName | ||||
|                     ldy  #>$iterableName | ||||
|                     sta  $loopLabel+1 | ||||
|                     sty  $loopLabel+2 | ||||
| $loopLabel          lda  ${65535.toHex()}       ; modified | ||||
|                     beq  $endLabel | ||||
|                     sta  ${asmgen.asmVariableName(stmt.loopVar)}""") | ||||
|                 asmgen.translate(stmt.body) | ||||
|                 asmgen.out(""" | ||||
|                     inc  $loopLabel+1 | ||||
|                     bne  $loopLabel | ||||
|                     inc  $loopLabel+2 | ||||
|                     bne  $loopLabel | ||||
| $endLabel""") | ||||
|             } | ||||
|             DataType.ARRAY_UB, DataType.ARRAY_B -> { | ||||
|                 val length = decl.arraysize!!.constIndex()!! | ||||
|                 val indexVar = asmgen.makeLabel("for_index") | ||||
|                 asmgen.out(""" | ||||
|                     ldy  #0 | ||||
| $loopLabel          sty  $indexVar | ||||
|                     lda  $iterableName,y | ||||
|                     sta  ${asmgen.asmVariableName(stmt.loopVar)}""") | ||||
|                 asmgen.translate(stmt.body) | ||||
|                 if(length<=255) { | ||||
|                     asmgen.out(""" | ||||
|                         ldy  $indexVar | ||||
|                         iny | ||||
|                         cpy  #$length | ||||
|                         beq  $endLabel | ||||
|                         bne  $loopLabel""") | ||||
|                 } else { | ||||
|                     // length is 256 | ||||
|                     asmgen.out(""" | ||||
|                         ldy  $indexVar | ||||
|                         iny | ||||
|                         bne  $loopLabel | ||||
|                         beq  $endLabel""") | ||||
|                 } | ||||
|                 if(length>=16 && asmgen.zeropage.hasByteAvailable()) { | ||||
|                     // allocate index var on ZP | ||||
|                     val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) | ||||
|                     asmgen.out("""$indexVar = $zpAddr  ; auto zp UBYTE""") | ||||
|                 } else { | ||||
|                     asmgen.out(""" | ||||
| $indexVar           .byte  0""") | ||||
|                 } | ||||
|                 asmgen.out(endLabel) | ||||
|             } | ||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { | ||||
|                 val length = decl.arraysize!!.constIndex()!! * 2 | ||||
|                 val indexVar = asmgen.makeLabel("for_index") | ||||
|                 val loopvarName = asmgen.asmVariableName(stmt.loopVar) | ||||
|                 asmgen.out(""" | ||||
|                     ldy  #0 | ||||
| $loopLabel          sty  $indexVar | ||||
|                     lda  $iterableName,y | ||||
|                     sta  $loopvarName | ||||
|                     lda  $iterableName+1,y | ||||
|                     sta  $loopvarName+1""") | ||||
|                 asmgen.translate(stmt.body) | ||||
|                 if(length<=127) { | ||||
|                     asmgen.out(""" | ||||
|                         ldy  $indexVar | ||||
|                         iny | ||||
|                         iny | ||||
|                         cpy  #$length | ||||
|                         beq  $endLabel | ||||
|                         bne  $loopLabel""") | ||||
|                 } else { | ||||
|                     // length is 128 words, 256 bytes | ||||
|                     asmgen.out(""" | ||||
|                         ldy  $indexVar | ||||
|                         iny | ||||
|                         iny | ||||
|                         bne  $loopLabel | ||||
|                         beq  $endLabel""") | ||||
|                 } | ||||
|                 if(length>=16 && asmgen.zeropage.hasByteAvailable()) { | ||||
|                     // allocate index var on ZP | ||||
|                     val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) | ||||
|                     asmgen.out("""$indexVar = $zpAddr  ; auto zp UBYTE""") | ||||
|                 } else { | ||||
|                     asmgen.out(""" | ||||
| $indexVar           .byte  0""") | ||||
|                 } | ||||
|                 asmgen.out(endLabel) | ||||
|             } | ||||
|             DataType.ARRAY_F -> { | ||||
|                 throw AssemblyError("for loop with floating point variables is not supported") | ||||
|             } | ||||
|             else -> throw AssemblyError("can't iterate over $iterableDt") | ||||
|         } | ||||
|         asmgen.loopEndLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) { | ||||
|         if (range.isEmpty() || range.step==0) | ||||
|             throw AssemblyError("empty range or step 0") | ||||
|         if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) { | ||||
|             if(range.step==1 && range.last>range.first) return translateForSimpleByteRangeAsc(stmt, range) | ||||
|             if(range.step==-1 && range.last<range.first) return translateForSimpleByteRangeDesc(stmt, range) | ||||
|         } | ||||
|         else if(iterableDt==DataType.ARRAY_W || iterableDt==DataType.ARRAY_UW) { | ||||
|             if(range.step==1 && range.last>range.first) return translateForSimpleWordRangeAsc(stmt, range) | ||||
|             if(range.step==-1 && range.last<range.first) return translateForSimpleWordRangeDesc(stmt, range) | ||||
|         } | ||||
|  | ||||
|         // not one of the easy cases, generate more complex code... | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         when(iterableDt) { | ||||
|             DataType.ARRAY_B, DataType.ARRAY_UB -> { | ||||
|                 // loop over byte range via loopvar, step >= 2 or <= -2 | ||||
|                 val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|                 asmgen.out(""" | ||||
|                             lda  #${range.first} | ||||
|                             sta  $varname | ||||
| $loopLabel""") | ||||
|                 asmgen.translate(stmt.body) | ||||
|                 when (range.step) { | ||||
|                     0, 1, -1 -> { | ||||
|                         throw AssemblyError("step 0, 1 and -1 should have been handled specifically  $stmt") | ||||
|                     } | ||||
|                     2 -> { | ||||
|                         if(range.last==255 || range.last==254) { | ||||
|                             asmgen.out(""" | ||||
|                                 inc  $varname | ||||
|                                 beq  $endLabel | ||||
|                                 inc  $varname | ||||
|                                 bne  $loopLabel""") | ||||
|                         } else { | ||||
|                             asmgen.out(""" | ||||
|                                 inc  $varname | ||||
|                                 inc  $varname | ||||
|                                 lda  $varname | ||||
|                                 cmp  #${range.last+2} | ||||
|                                 bne  $loopLabel""") | ||||
|                         } | ||||
|                     } | ||||
|                     -2 -> { | ||||
|                         when (range.last) { | ||||
|                             0 -> { | ||||
|                                 asmgen.out(""" | ||||
|                                     lda  $varname | ||||
|                                     beq  $endLabel | ||||
|                                     dec  $varname | ||||
|                                     dec  $varname""") | ||||
|                                 asmgen.jmp(loopLabel) | ||||
|                             } | ||||
|                             1 -> asmgen.out(""" | ||||
|                                     dec  $varname | ||||
|                                     beq  $endLabel | ||||
|                                     dec  $varname | ||||
|                                     bne  $loopLabel""") | ||||
|                             else -> asmgen.out(""" | ||||
|                                     dec  $varname | ||||
|                                     dec  $varname | ||||
|                                     lda  $varname | ||||
|                                     cmp  #${range.last-2} | ||||
|                                     bne  $loopLabel""") | ||||
|                         } | ||||
|                     } | ||||
|                     else -> { | ||||
|                         // step <= -3 or >= 3 | ||||
|                         asmgen.out(""" | ||||
|                             lda  $varname | ||||
|                             cmp  #${range.last} | ||||
|                             beq  $endLabel | ||||
|                             clc | ||||
|                             adc  #${range.step} | ||||
|                             sta  $varname""") | ||||
|                         asmgen.jmp(loopLabel) | ||||
|                     } | ||||
|                 } | ||||
|                 asmgen.out(endLabel) | ||||
|             } | ||||
|             DataType.ARRAY_W, DataType.ARRAY_UW -> { | ||||
|                 // loop over word range via loopvar, step >= 2 or <= -2 | ||||
|                 val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|                 when (range.step) { | ||||
|                     0, 1, -1 -> { | ||||
|                         throw AssemblyError("step 0, 1 and -1 should have been handled specifically  $stmt") | ||||
|                     } | ||||
|                     else -> { | ||||
|                         // word, step >= 2 or <= -2 | ||||
|                         // note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence | ||||
|                         asmgen.out(""" | ||||
|                             lda  #<${range.first} | ||||
|                             ldy  #>${range.first} | ||||
|                             sta  $varname | ||||
|                             sty  $varname+1 | ||||
| $loopLabel""") | ||||
|                         asmgen.translate(stmt.body) | ||||
|                         asmgen.out(""" | ||||
|                             lda  $varname | ||||
|                             cmp  #<${range.last} | ||||
|                             bne  + | ||||
|                             lda  $varname+1 | ||||
|                             cmp  #>${range.last} | ||||
|                             bne  + | ||||
|                             beq  $endLabel | ||||
| +                           lda  $varname | ||||
|                             clc | ||||
|                             adc  #<${range.step} | ||||
|                             sta  $varname | ||||
|                             lda  $varname+1 | ||||
|                             adc  #>${range.step} | ||||
|                             sta  $varname+1""") | ||||
|                         asmgen.jmp(loopLabel) | ||||
|                         asmgen.out(endLabel) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else -> throw AssemblyError("range expression can only be byte or word") | ||||
|         } | ||||
|         asmgen.loopEndLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|         asmgen.out(""" | ||||
|                 lda  #${range.first} | ||||
|                 sta  $varname | ||||
| $loopLabel""") | ||||
|         asmgen.translate(stmt.body) | ||||
|         if (range.last == 255) { | ||||
|             asmgen.out(""" | ||||
|                 inc  $varname | ||||
|                 bne  $loopLabel | ||||
| $endLabel""") | ||||
|         } else { | ||||
|             asmgen.out(""" | ||||
|                 inc  $varname | ||||
|                 lda  $varname | ||||
|                 cmp  #${range.last+1} | ||||
|                 bne  $loopLabel | ||||
| $endLabel""") | ||||
|         } | ||||
|         asmgen.loopEndLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|         asmgen.out(""" | ||||
|                     lda  #${range.first} | ||||
|                     sta  $varname | ||||
| $loopLabel""") | ||||
|         asmgen.translate(stmt.body) | ||||
|         when (range.last) { | ||||
|             0 -> { | ||||
|                 asmgen.out(""" | ||||
|                     lda  $varname | ||||
|                     beq  $endLabel | ||||
|                     dec  $varname""") | ||||
|                 asmgen.jmp(loopLabel) | ||||
|                 asmgen.out(endLabel) | ||||
|             } | ||||
|             1 -> { | ||||
|                 asmgen.out(""" | ||||
|                     dec  $varname | ||||
|                     bne  $loopLabel | ||||
| $endLabel""") | ||||
|             } | ||||
|             else -> { | ||||
|                 asmgen.out(""" | ||||
|                     dec  $varname | ||||
|                     lda  $varname | ||||
|                     cmp  #${range.last-1} | ||||
|                     bne  $loopLabel | ||||
| $endLabel""") | ||||
|             } | ||||
|         } | ||||
|         asmgen.loopEndLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|         asmgen.out(""" | ||||
|             lda  #<${range.first} | ||||
|             ldy  #>${range.first} | ||||
|             sta  $varname | ||||
|             sty  $varname+1 | ||||
| $loopLabel""") | ||||
|         asmgen.translate(stmt.body) | ||||
|         asmgen.out(""" | ||||
|             lda  $varname | ||||
|             cmp  #<${range.last} | ||||
|             bne  + | ||||
|             lda  $varname+1 | ||||
|             cmp  #>${range.last} | ||||
|             beq  $endLabel | ||||
| +           inc  $varname | ||||
|             bne  $loopLabel | ||||
|             inc  $varname+1""") | ||||
|         asmgen.jmp(loopLabel) | ||||
|         asmgen.out(endLabel) | ||||
|         asmgen.loopEndLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) { | ||||
|         val loopLabel = asmgen.makeLabel("for_loop") | ||||
|         val endLabel = asmgen.makeLabel("for_end") | ||||
|         asmgen.loopEndLabels.push(endLabel) | ||||
|         val varname = asmgen.asmVariableName(stmt.loopVar) | ||||
|         asmgen.out(""" | ||||
|             lda  #<${range.first} | ||||
|             ldy  #>${range.first} | ||||
|             sta  $varname | ||||
|             sty  $varname+1 | ||||
| $loopLabel""") | ||||
|         asmgen.translate(stmt.body) | ||||
|         asmgen.out(""" | ||||
|             lda  $varname | ||||
|             cmp  #<${range.last} | ||||
|             bne  + | ||||
|             lda  $varname+1 | ||||
|             cmp  #>${range.last} | ||||
|             beq  $endLabel | ||||
| +           lda  $varname | ||||
|             bne  + | ||||
|             dec  $varname+1 | ||||
| +           dec  $varname""") | ||||
|         asmgen.jmp(loopLabel) | ||||
|         asmgen.out(endLabel) | ||||
|         asmgen.loopEndLabels.pop() | ||||
|     } | ||||
|  | ||||
|     private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) = | ||||
|         asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.UNDEFINED), stmt.definingSubroutine()) | ||||
| } | ||||
| @@ -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
											
										
									
								
							Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user