mirror of
				https://github.com/irmen/prog8.git
				synced 2025-10-31 00:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			1395 Commits
		
	
	
		
			v1.10
			...
			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 | ||
|  | b37231d0f5 | ||
|  | 3c55719bf1 | ||
|  | af8279a9b9 | ||
|  | c38508c262 | ||
|  | b0e8738ab8 | ||
|  | cae480768e | ||
|  | a70276c190 | ||
|  | 0c461ffe2e | ||
|  | 237511f2d6 | ||
|  | cdcb652033 | ||
|  | 71e678b382 | ||
|  | 3050156325 | ||
|  | 4bfdbad2e4 | ||
|  | 06137ecdc4 | ||
|  | d89f5b0df8 | ||
|  | b6e2b36692 | ||
|  | a6d789cfbc | ||
|  | c07907e7bd | ||
|  | 7d8496c874 | ||
|  | 164ac56db1 | ||
|  | fdddb8ca64 | ||
|  | a9d4b8b0fa | ||
|  | ec7b9f54c2 | ||
|  | 307558a7e7 | ||
|  | febf423eab | ||
|  | a999c23014 | ||
|  | 69f1ade595 | ||
|  | b166576e54 | ||
|  | ee2ba5f398 | ||
|  | cb9825484d | ||
|  | 76cda82e23 | ||
|  | 37b61d9e6b | ||
|  | 52f0222a6d | ||
|  | 75ccac2f2c | ||
|  | 5c771a91f7 | ||
|  | a242ad10e6 | ||
|  | b5086b6a8f | ||
|  | 3e47dad12a | ||
|  | 235610f40c | ||
|  | 6b59559c65 | ||
|  | 23e954f716 | ||
|  | 983c899cad | ||
|  | c2f9385965 | ||
|  | ceb2c9e4f8 | ||
|  | 68a7f9c665 | ||
|  | ffd8d9c7c1 | ||
|  | c66fc8630c | ||
|  | 9ca1c66f2b | ||
|  | 33647a29d0 | ||
|  | 02b12cc762 | ||
|  | 3280993e2a | ||
|  | 3723c22054 | ||
|  | 0a2c4ea0c4 | ||
|  | 58a83c0439 | ||
|  | d665489054 | ||
|  | 9200992024 | ||
|  | 6408cc46a8 | ||
|  | 961bcdb7ae | ||
|  | edee70cf31 | ||
|  | 1978a9815a | ||
|  | f5e6db9d66 | ||
|  | a94bc40ab0 | ||
|  | 534b5ced8f | ||
|  | 5ebd9b54e4 | ||
|  | cc4e272526 | ||
|  | 295e199bfa | ||
|  | df3371b0f0 | ||
|  | e4fe1d2b8d | ||
|  | b8b9244ffa | ||
|  | 3be3989e1c | ||
|  | ed54cf680a | ||
|  | 95e76058d3 | ||
|  | a6bee6a860 | ||
|  | d22780ee44 | ||
|  | f8b0b9575d | ||
|  | 4274fd168e | ||
|  | be7f5957f3 | ||
|  | f2e5d987a9 | ||
|  | f01173d8db | ||
|  | 15e8e0bf6d | ||
|  | 2c59cbdece | ||
|  | b73da4ed02 | ||
|  | 267adb4612 | ||
|  | 05c73fa8bc | ||
|  | bfe9f442e6 | ||
|  | 0deadb694b | ||
|  | bed34378be | ||
|  | 5927cf2d43 | ||
|  | fffe36e358 | ||
|  | fac2a2d7cb | ||
|  | 0af5582ca7 | ||
|  | 582d31263c | ||
|  | 4108a528e1 | ||
|  | ab7d7c2907 | ||
|  | 152888ee93 | ||
|  | 22f8f4f359 | ||
|  | 5f3a9e189a | ||
|  | b734dc44fd | ||
|  | fab224f509 | ||
|  | 2f05ebb966 | ||
|  | a335ba519a | ||
|  | 8805693ed2 | ||
|  | f2bb238e9b | ||
|  | 131fe670a4 | ||
|  | 11e9539416 | ||
|  | 3881ebe429 | ||
|  | 29d1b8802e | ||
|  | bcc75732e9 | ||
|  | 50a85ee6b0 | ||
|  | 2c7424fd43 | ||
|  | 7426587c38 | ||
|  | 1f39749a5e | ||
|  | ca63051c71 | ||
|  | 6dd44aaf0d | ||
|  | f89457ba68 | ||
|  | efef205fcf | ||
|  | 0c561d8528 | ||
|  | 8bfa2c4c02 | ||
|  | f0d4c3aba9 | ||
|  | 3a99115070 | ||
|  | 7232134931 | ||
|  | 954e911eb3 | ||
|  | 63c073c93f | ||
|  | 78feef9d59 | ||
|  | 4fbdd6d570 | ||
|  | 4929c198ba | ||
|  | 9409f17372 | ||
|  | 43781c02d0 | ||
|  | 824f06e17f | ||
|  | 21dbc6da97 | ||
|  | 270ea54ff7 | ||
|  | 771ac7aba7 | ||
|  | 97d36243f2 | ||
|  | 511b47bac4 | ||
|  | f265199fbe | ||
|  | a191ec71a4 | ||
|  | 82dce2dd53 | ||
|  | 29ac160811 | ||
|  | 5e50ea14f8 | ||
|  | 40e6091506 | ||
|  | 0ee4d420b1 | ||
|  | 66acce9e8e | ||
|  | 6c23ae14ab | ||
|  | 6f000d0d26 | ||
|  | 9d7eb3be5a | ||
|  | 835555171e | ||
|  | 68ce4a1bf0 | ||
|  | a995867deb | ||
|  | 6bd99d63b4 | ||
|  | baf5d3041a | ||
|  | a326ffa00a | ||
|  | d28dd92b47 | ||
|  | 1de328b2e8 | ||
|  | 51bb902162 | ||
|  | 4fd14f1366 | ||
|  | 91d9559f79 | ||
|  | 3245a9b157 | ||
|  | 2b28493bba | ||
|  | 1382728bd2 | ||
|  | 0422ad080a | ||
|  | 64d682bfde | ||
|  | b182f7e693 | ||
|  | e6be428589 | ||
|  | 85c7f8314b | ||
|  | 796d07a7f8 | ||
|  | 2af86a10b2 | ||
|  | 7fbe486dff | ||
|  | 87e5a9859a | ||
|  | b036e5ed72 | ||
|  | 5f1ec80ae0 | ||
|  | fbecedaf41 | ||
|  | aa36acd65a | ||
|  | 8d1a4588d3 | ||
|  | 66d2af4453 | ||
|  | ef6c731bb3 | ||
|  | 98a638a2f3 | ||
|  | 96d8a7f0d7 | ||
|  | 3162b10392 | ||
|  | e2358de27c | ||
|  | 7facb4f372 | ||
|  | ee90fed489 | ||
|  | 4796c56c35 | ||
|  | e2cb031386 | ||
|  | a0bc97b90c | ||
|  | fd240899bd | ||
|  | 885b22df40 | ||
|  | 11de3db25f | ||
|  | 14a13da7ec | ||
|  | 875a71c786 | ||
|  | 0ff5b79353 | ||
|  | 8c4d276810 | ||
|  | 3dd38c0ac8 | ||
|  | b8816a0e2f | ||
|  | a01a9e76f9 | ||
|  | 357d704aec | ||
|  | 868df1865c | ||
|  | 654d74da1e | ||
|  | 59939c727a | ||
|  | fbcf190324 | ||
|  | b9922a90cc | ||
|  | 66e0b07428 | ||
|  | 01e617ae8f | ||
|  | 52769decd4 | ||
|  | 165eec4054 | ||
|  | 8c2e602cc7 | ||
|  | b68f141568 | ||
|  | b5d1e8653d | ||
|  | f6d4c90dea | ||
|  | b5b24636ae | ||
|  | 9dedbbf47c | ||
|  | c493c3e5c6 | ||
|  | 61d4ca1d24 | ||
|  | 2cf9af4a6e | ||
|  | bdcd10512f | ||
|  | fec8db6a75 | ||
|  | b400010426 | ||
|  | 28109a39ac | ||
|  | 651f0ec445 | ||
|  | e61d3df380 | ||
|  | 15710207b2 | ||
|  | adfddddac6 | ||
|  | e46982f652 | ||
|  | 900c2aea23 | ||
|  | 42f8e98cab | ||
|  | bed0e33b4f | ||
|  | 8d6542905d | ||
|  | 39798a1a4f | ||
|  | befe4b8e9f | ||
|  | 772e48105e | ||
|  | 9afe451b8d | ||
|  | 89d469e77e | ||
|  | 59a43889a5 | ||
|  | 7caa0daffc | ||
|  | 5e854c2cf8 | ||
|  | 9edc92ec29 | ||
|  | 1d178080a3 | ||
|  | aa94300bdd | ||
|  | 2d768c3f28 | ||
|  | b79af624ae | ||
|  | 38208a7c9e | ||
|  | 8eff51904e | ||
|  | c717f4573d | ||
|  | 984d251a6d | ||
|  | 8c3b43f3ed | ||
|  | 0f1485f30b | ||
|  | eb94c678bd | ||
|  | 50d792a121 | ||
|  | f0d4654917 | ||
|  | 4ce93b5d9d | ||
|  | fb0d7a1908 | ||
|  | bb7b063757 | ||
|  | c495f54bbb | ||
|  | 1cc1f2d91d | ||
|  | d837cc11f9 | ||
|  | cbb7083307 | ||
|  | d4a17dfad1 | ||
|  | 59f8b91e25 | ||
|  | 80113f9208 | ||
|  | 27f987f0ae | ||
|  | 3ae2597261 | ||
|  | 248e7b808c | ||
|  | a983a896f2 | ||
|  | 68df1730f5 | ||
|  | d62ab93b24 | ||
|  | 47297f7e31 | ||
|  | b64d611e02 | ||
|  | 9fb9bcfebd | ||
|  | dff9c5f53e | ||
|  | d4a77321d2 | ||
|  | 2665618fa6 | ||
|  | b5c5560af8 | ||
|  | 065587525e | ||
|  | 58e5d5c071 | ||
|  | b44e76db57 | ||
|  | 2ce6bc5946 | ||
|  | fe5b225732 | ||
|  | d499e40a4b | ||
|  | 62a66d89c6 | ||
|  | e1b26ae287 | ||
|  | 1c151f4a3f | ||
|  | 8917926996 | ||
|  | b54a9b9831 | ||
|  | f08906dba1 | ||
|  | a6bba824d3 | ||
|  | fd84152a2b | ||
|  | 3466106119 | ||
|  | c79b587eea | ||
|  | 4862fb7db1 | ||
|  | 2136db0e61 | ||
|  | 2f0c0f6fcd | ||
|  | 7ddc01f883 | ||
|  | 126c2162f1 | ||
|  | 094c8ab94c | ||
|  | efe2723874 | ||
|  | bccfeb2fa2 | ||
|  | d498d5445c | ||
|  | 5095d090cc | ||
|  | 6544fcdc36 | ||
|  | e834924857 | ||
|  | 2c3b8a9819 | ||
|  | 309c82fc9e | ||
|  | 0f91ce6441 | ||
|  | f29ec3b4e1 | ||
|  | cc1fc869cf | ||
|  | 0431d3cddc | ||
|  | a1cd202cd2 | ||
|  | b842493cf0 | ||
|  | 4718f09cb7 | ||
|  | e9c357a885 | ||
|  | fb00ff74d1 | ||
|  | b740b079db | ||
|  | 6394841041 | ||
|  | 3f4050c647 | ||
|  | 82f01d84c2 | ||
|  | 299ea72d70 | ||
|  | 50aa286d3a | ||
|  | 6f7322150f | ||
|  | cc9965cc96 | ||
|  | ae90a957c6 | ||
|  | 8cec032e7d | ||
|  | 3732ab1e62 | ||
|  | fba149ee28 | ||
|  | 4661cba974 | ||
|  | 025be8cb7c | ||
|  | 3aea32551b | ||
|  | 8e8c112ff0 | ||
|  | b0dda08e74 | ||
|  | 2c25df122a | ||
|  | 7cb5702b37 | ||
|  | b7502c7eaa | ||
|  | fed020825a | ||
|  | 1c411897df | ||
|  | f94e241fb2 | ||
|  | 757cbfd1ba | ||
|  | 3de80319db | ||
|  | f9617d777a | ||
|  | 9961a404ae | ||
|  | 776c844d02 | ||
|  | 03782a37a2 | ||
|  | 173663380b | ||
|  | c6fdd65c63 | ||
|  | d9546f9dc7 | ||
|  | 2a6b0f5db7 | ||
|  | b4e1b42cec | ||
|  | a8898a5993 | ||
|  | e03c68b632 | ||
|  | a0074de12b | ||
|  | 411bedcc46 | ||
|  | 07d8caf884 | ||
|  | c0e83ef8df | ||
|  | 4dbf4b2005 | ||
|  | 61af72b906 | ||
|  | 17be722e2b | ||
|  | 16d7927d2f | ||
|  | 55a7a5d9d5 | ||
|  | 78d7849197 | ||
|  | d5b12fb01d | ||
|  | 31f4e378aa | ||
|  | 8a26b7b248 | ||
|  | 87c28cfdbc | ||
|  | 1f5420010d | ||
|  | a089c48378 | ||
|  | 3e5deda46c | ||
|  | 7500c6efd0 | ||
|  | 717b5f3b07 | ||
|  | 9f6fa60bf1 | ||
|  | 1e9586f635 | ||
|  | 44f9d5e69e | 
							
								
								
									
										18
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,8 @@ | |||||||
| .idea/workspace.xml | .idea/workspace.xml | ||||||
| /build/ | .idea/discord.xml | ||||||
| /dist/ | build/ | ||||||
| /output/ | dist/ | ||||||
|  | output/ | ||||||
| .*cache/ | .*cache/ | ||||||
| *.directory | *.directory | ||||||
| *.prg | *.prg | ||||||
| @@ -11,9 +12,9 @@ | |||||||
| *.vice-mon-list | *.vice-mon-list | ||||||
| docs/build | docs/build | ||||||
| out/ | out/ | ||||||
| **/*.interp | parser/**/*.interp | ||||||
| **/*.tokens | parser/**/*.tokens | ||||||
|  | parser/**/*.java | ||||||
| *.py[cod] | *.py[cod] | ||||||
| *.egg | *.egg | ||||||
| *.egg-info | *.egg-info | ||||||
| @@ -24,10 +25,9 @@ __pycache__/ | |||||||
| parser.out | parser.out | ||||||
| parsetab.py | parsetab.py | ||||||
| .pytest_cache/ | .pytest_cache/ | ||||||
| compiler/src/prog8_kotlin.jar |  | ||||||
| compiler/src/compiled_java |  | ||||||
| .attach_pid* | .attach_pid* | ||||||
|  |  | ||||||
| .gradle | .gradle | ||||||
| build/ |  | ||||||
| /prog8compiler.jar | /prog8compiler.jar | ||||||
|  | sd*.img | ||||||
|  | *.d64 | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | # Default ignored files | ||||||
|  | /shelf/ | ||||||
							
								
								
									
										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
									
									
									
								
							| @@ -1,6 +1,13 @@ | |||||||
| <component name="InspectionProjectProfileManager"> | <component name="InspectionProjectProfileManager"> | ||||||
|   <profile version="1.0"> |   <profile version="1.0"> | ||||||
|     <option name="myName" value="Project Default" /> |     <option name="myName" value="Project Default" /> | ||||||
|  |     <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true"> | ||||||
|  |       <Languages> | ||||||
|  |         <language minSize="70" name="Kotlin" /> | ||||||
|  |         <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"> |     <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true"> | ||||||
|       <option name="processCode" value="false" /> |       <option name="processCode" value="false" /> | ||||||
|       <option name="processLiterals" value="true" /> |       <option name="processLiterals" value="true" /> | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="Kotlin2JvmCompilerArguments"> |   <component name="Kotlin2JvmCompilerArguments"> | ||||||
|     <option name="jvmTarget" value="1.8" /> |     <option name="jvmTarget" value="11" /> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
							
								
								
									
										9
									
								
								.idea/libraries/antlr_4_7_2_complete.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/antlr_4_7_2_complete.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,9 +0,0 @@ | |||||||
| <component name="libraryTable"> |  | ||||||
|   <library name="antlr-4.7.2-complete"> |  | ||||||
|     <CLASSES> |  | ||||||
|       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.7.2-complete.jar!/" /> |  | ||||||
|     </CLASSES> |  | ||||||
|     <JAVADOC /> |  | ||||||
|     <SOURCES /> |  | ||||||
|   </library> |  | ||||||
| </component> |  | ||||||
							
								
								
									
										9
									
								
								.idea/libraries/antlr_4_9_complete.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/antlr_4_9_complete.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="antlr-4.9-complete"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.9-complete.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| <component name="libraryTable"> | <component name="libraryTable"> | ||||||
|   <library name="antlr-runtime-4.7.2"> |   <library name="antlr-runtime-4.9"> | ||||||
|     <CLASSES> |     <CLASSES> | ||||||
|       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.7.2.jar!/" /> |       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.9.jar!/" /> | ||||||
|     </CLASSES> |     </CLASSES> | ||||||
|     <JAVADOC /> |     <JAVADOC /> | ||||||
|     <SOURCES /> |     <SOURCES /> | ||||||
							
								
								
									
										9
									
								
								.idea/libraries/dbus_java_3_2_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/dbus_java_3_2_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="dbus-java-3.2.4"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
							
								
								
									
										10
									
								
								.idea/libraries/javax_json_api_1_1_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/libraries/javax_json_api_1_1_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="javax.json-api-1.1.4"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" /> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
							
								
								
									
										9
									
								
								.idea/libraries/kotlinx_cli_jvm.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/kotlinx_cli_jvm.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | <component name="libraryTable"> | ||||||
|  |   <library name="kotlinx-cli-jvm"> | ||||||
|  |     <CLASSES> | ||||||
|  |       <root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" /> | ||||||
|  |     </CLASSES> | ||||||
|  |     <JAVADOC /> | ||||||
|  |     <SOURCES /> | ||||||
|  |   </library> | ||||||
|  | </component> | ||||||
							
								
								
									
										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> | ||||||
							
								
								
									
										29
									
								
								.idea/markdown-navigator-enh.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.idea/markdown-navigator-enh.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="MarkdownEnhProjectSettings"> | ||||||
|  |     <AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" /> | ||||||
|  |     <HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" /> | ||||||
|  |     <LinkMapSettings> | ||||||
|  |       <textMaps /> | ||||||
|  |     </LinkMapSettings> | ||||||
|  |   </component> | ||||||
|  |   <component name="MarkdownNavigatorHistory"> | ||||||
|  |     <PasteImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30"> | ||||||
|  |       <highlightList /> | ||||||
|  |       <directories /> | ||||||
|  |       <filenames /> | ||||||
|  |     </PasteImageHistory> | ||||||
|  |     <CopyImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30"> | ||||||
|  |       <highlightList /> | ||||||
|  |       <directories /> | ||||||
|  |       <filenames /> | ||||||
|  |     </CopyImageHistory> | ||||||
|  |     <PasteLinkHistory onPasteImageTargetRef="3" onPasteTargetRef="1" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteWikiElement="2" onPasteReferenceElement="2" hideInapplicableOperations="false" preserveLinkFormat="false" useHeadingForLinkText="false" linkFormat="0" saveAsDefaultOnOK="false" /> | ||||||
|  |     <TableToJsonHistory> | ||||||
|  |       <entries /> | ||||||
|  |     </TableToJsonHistory> | ||||||
|  |     <TableSortHistory> | ||||||
|  |       <entries /> | ||||||
|  |     </TableSortHistory> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										57
									
								
								.idea/markdown-navigator.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								.idea/markdown-navigator.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | <project version="4"> | ||||||
|  |   <component name="FlexmarkProjectSettings"> | ||||||
|  |     <FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false"> | ||||||
|  |       <flexmarkSectionLanguages> | ||||||
|  |         <option name="1" value="Markdown" /> | ||||||
|  |         <option name="2" value="HTML" /> | ||||||
|  |         <option name="3" value="flexmark-ast:1" /> | ||||||
|  |       </flexmarkSectionLanguages> | ||||||
|  |     </FlexmarkHtmlSettings> | ||||||
|  |   </component> | ||||||
|  |   <component name="MarkdownProjectSettings"> | ||||||
|  |     <PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false"> | ||||||
|  |       <PanelProvider> | ||||||
|  |         <provider providerId="com.vladsch.md.nav.editor.javafx.html.panel" providerName="JavaFX WebView" /> | ||||||
|  |       </PanelProvider> | ||||||
|  |     </PreviewSettings> | ||||||
|  |     <ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0"> | ||||||
|  |       <PegdownExtensions> | ||||||
|  |         <option name="ANCHORLINKS" value="true" /> | ||||||
|  |         <option name="ATXHEADERSPACE" value="true" /> | ||||||
|  |         <option name="FENCED_CODE_BLOCKS" value="true" /> | ||||||
|  |         <option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" /> | ||||||
|  |         <option name="RELAXEDHRULES" value="true" /> | ||||||
|  |         <option name="STRIKETHROUGH" value="true" /> | ||||||
|  |         <option name="TABLES" value="true" /> | ||||||
|  |         <option name="TASKLISTITEMS" value="true" /> | ||||||
|  |       </PegdownExtensions> | ||||||
|  |       <ParserOptions> | ||||||
|  |         <option name="COMMONMARK_LISTS" value="true" /> | ||||||
|  |         <option name="EMOJI_SHORTCUTS" value="true" /> | ||||||
|  |         <option name="GFM_TABLE_RENDERING" value="true" /> | ||||||
|  |         <option name="PRODUCTION_SPEC_PARSER" value="true" /> | ||||||
|  |         <option name="SIM_TOC_BLANK_LINE_SPACER" value="true" /> | ||||||
|  |       </ParserOptions> | ||||||
|  |     </ParserSettings> | ||||||
|  |     <HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0"> | ||||||
|  |       <GeneratorProvider> | ||||||
|  |         <provider providerId="com.vladsch.md.nav.editor.javafx.html.generator" providerName="JavaFx HTML Generator" /> | ||||||
|  |       </GeneratorProvider> | ||||||
|  |       <headerTop /> | ||||||
|  |       <headerBottom /> | ||||||
|  |       <bodyTop /> | ||||||
|  |       <bodyBottom /> | ||||||
|  |     </HtmlSettings> | ||||||
|  |     <CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true"> | ||||||
|  |       <StylesheetProvider> | ||||||
|  |         <provider providerId="com.vladsch.md.nav.editor.javafx.html.css" providerName="Default JavaFx Stylesheet" /> | ||||||
|  |       </StylesheetProvider> | ||||||
|  |       <ScriptProviders> | ||||||
|  |         <provider providerId="com.vladsch.md.nav.editor.hljs.html.script" providerName="HighlightJS Script" /> | ||||||
|  |       </ScriptProviders> | ||||||
|  |       <cssText /> | ||||||
|  |       <cssUriHistory /> | ||||||
|  |     </CssSettings> | ||||||
|  |   </component> | ||||||
|  | </project> | ||||||
							
								
								
									
										18
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										18
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,22 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK"> |   <component name="ANTLRGenerationPreferences"> | ||||||
|  |     <option name="perGrammarGenerationSettings"> | ||||||
|  |       <list> | ||||||
|  |         <PerGrammarGenerationSettings> | ||||||
|  |           <option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" /> | ||||||
|  |           <option name="autoGen" value="true" /> | ||||||
|  |           <option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" /> | ||||||
|  |           <option name="libDir" value="" /> | ||||||
|  |           <option name="encoding" value="" /> | ||||||
|  |           <option name="pkg" value="" /> | ||||||
|  |           <option name="language" value="" /> | ||||||
|  |           <option name="generateListener" value="false" /> | ||||||
|  |         </PerGrammarGenerationSettings> | ||||||
|  |       </list> | ||||||
|  |     </option> | ||||||
|  |   </component> | ||||||
|  |   <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK"> | ||||||
|     <output url="file://$PROJECT_DIR$/out" /> |     <output url="file://$PROJECT_DIR$/out" /> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
							
								
								
									
										3
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							| @@ -3,8 +3,11 @@ | |||||||
|   <component name="ProjectModuleManager"> |   <component name="ProjectModuleManager"> | ||||||
|     <modules> |     <modules> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" /> |       <module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" /> |       <module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" /> |       <module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" /> | ||||||
|  |       <module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" /> | ||||||
|       <module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" /> |       <module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" /> | ||||||
|     </modules> |     </modules> | ||||||
|   </component> |   </component> | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| language: java | language: java | ||||||
|  | sudo: false | ||||||
| # jdk: openjdk8 | # jdk: openjdk8 | ||||||
| # dist: xenial | # dist: xenial | ||||||
| # sudo: false |  | ||||||
|  |  | ||||||
| before_install: | before_install: | ||||||
|   - chmod +x gradlew |   - chmod +x ./gradlew | ||||||
|  |  | ||||||
| script: | script: | ||||||
|   - ./gradlew 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 |                     GNU GENERAL PUBLIC LICENSE | ||||||
|                        Version 3, 29 June 2007 |                        Version 3, 29 June 2007 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										123
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,60 +1,81 @@ | |||||||
| [](https://saythanks.io/to/irmen) | [](https://saythanks.io/to/irmen) | ||||||
| [](https://travis-ci.org/irmen/prog8) | [](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)* | *Written by Irmen de Jong (irmen@razorvine.net)* | ||||||
|  |  | ||||||
| *Software license: GNU GPL 3.0, see file LICENSE* | This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's | ||||||
|  |  | ||||||
|  |  | ||||||
| This is a structured programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's |  | ||||||
| as used in many home computers from that era. It is a medium to low level programming language, | as used in many home computers from that era. It is a medium to low level programming language, | ||||||
| which aims to provide many conveniences over raw assembly code (even when using a macro assembler): | which aims to provide many conveniences over raw assembly code (even when using a macro assembler). | ||||||
|  |  | ||||||
| - reduction of source code length | Documentation | ||||||
| - easier program understanding (because it's higher level, and way more compact) | ------------- | ||||||
|  | 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 | - modularity, symbol scoping, subroutines | ||||||
| - subroutines have enforced input- and output parameter definitions | - various data types other than just bytes (16-bit words, floats, strings) | ||||||
| - various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs) | - floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do) | ||||||
| - automatic variable allocations, automatic string variables and string sharing | - strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents. | ||||||
| - constant folding in expressions (compile-time evaluation) | - automatic static variable allocations, automatic string and array variables and string sharing | ||||||
|  | - subroutines with input parameters and result values | ||||||
|  | - high-level program optimizations | ||||||
|  | - small program boilerplate/compilersupport overhead | ||||||
|  | - programs can be run multiple times without reloading because of automatic variable (re)initializations. | ||||||
| - conditional branches | - conditional branches | ||||||
| - when statement to provide a 'jump table' alternative to if/elseif chains | - 'when' statement to provide a concise jump table alternative to if/elseif chains | ||||||
| - automatic type conversions | - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse`` | ||||||
| - floating point operations  (uses the C64 Basic ROM routines for this) | - various powerful built-in libraries to do I/O, number conversions, graphics and more   | ||||||
| - abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses | - convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses | ||||||
| - various code optimizations (code structure, logical and numerical expressions, unused code removal...)  | - inline assembly allows you to have full control when every cycle or byte matters | ||||||
|  | - 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 modern PC to work on  | - use a modern PC to do the work on, use nice editors and enjoy quick compilation times | ||||||
| - quick compilation times (less than 1 second) | - can automatically run the program in the Vice emulator after succesful compilation | ||||||
| - option to automatically run the program in the Vice emulator   |  | ||||||
| - breakpoints, that let the Vice emulator drop into the monitor if execution hits them | - breakpoints, that let the Vice emulator drop into the monitor if execution hits them | ||||||
| - source code labels automatically loaded in Vice emulator so it can show them in disassembly | - source code labels automatically loaded in Vice emulator so it can show them in disassembly | ||||||
|  |  | ||||||
|  | *Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): | ||||||
|  |  | ||||||
| It is mainly targeted at the Commodore-64 machine at this time. | - "c64": Commodore-64  (6510 CPU = almost a 6502) | ||||||
| Contributions to add support for other 8-bit (or other?!) machines are welcome. | - "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)! | ||||||
| Documentation is online at https://prog8.readthedocs.io/ |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Required tools: |  | ||||||
| --------------- | Additional required tools | ||||||
|  | ------------------------- | ||||||
|  |  | ||||||
| [64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path. | [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. | A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project. | ||||||
| For other platforms it is very easy to compile it yourself (make ; make install). | For other platforms it is very easy to compile it yourself (make ; make install). | ||||||
|  |  | ||||||
| A **Java runtime (jre or jdk), version 8 or newer**  is required to run a prepackaged version of the compiler. | A **Java runtime (jre or jdk), version 11 or newer**  is required to run a prepackaged version of the compiler. | ||||||
| If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance, | If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance, | ||||||
| IntelliJ IDEA with the Kotlin plugin). | IntelliJ IDEA with the Kotlin plugin). | ||||||
|  |  | ||||||
| It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence | 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/) | 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 | Example code | ||||||
| @@ -62,44 +83,43 @@ Example code | |||||||
|  |  | ||||||
| This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | ||||||
|  |  | ||||||
|     %import c64utils |     %import textio | ||||||
|     %zeropage basicsafe |     %zeropage basicsafe | ||||||
|      |      | ||||||
|     ~ main { |     main { | ||||||
|      |      | ||||||
|         ubyte[256] sieve |         ubyte[256] sieve | ||||||
|         ubyte candidate_prime = 2 |         ubyte candidate_prime = 2       ; is increased in the loop | ||||||
|      |      | ||||||
|         sub start() { |         sub start() { | ||||||
|             memset(sieve, 256, false) |             sys.memset(sieve, 256, false)   ; clear the sieve | ||||||
|  |             txt.print("prime numbers up to 255:\n\n") | ||||||
|             c64scr.print("prime numbers up to 255:\n\n") |  | ||||||
|             ubyte amount=0 |             ubyte amount=0 | ||||||
|             while true { |             repeat { | ||||||
|                 ubyte prime = find_next_prime() |                 ubyte prime = find_next_prime() | ||||||
|                 if prime==0 |                 if prime==0 | ||||||
|                     break |                     break | ||||||
|                 c64scr.print_ub(prime) |                 txt.print_ub(prime) | ||||||
|                 c64scr.print(", ") |                 txt.print(", ") | ||||||
|                 amount++ |                 amount++ | ||||||
|             } |             } | ||||||
|             c64.CHROUT('\n') |             txt.nl() | ||||||
|             c64scr.print("number of primes (expected 54): ") |             txt.print("number of primes (expected 54): ") | ||||||
|             c64scr.print_ub(amount) |             txt.print_ub(amount) | ||||||
|             c64.CHROUT('\n') |             txt.nl() | ||||||
|         } |         } | ||||||
|      |      | ||||||
|  |  | ||||||
|         sub find_next_prime() -> ubyte { |         sub find_next_prime() -> ubyte { | ||||||
|  |  | ||||||
|             while sieve[candidate_prime] { |             while sieve[candidate_prime] { | ||||||
|                 candidate_prime++ |                 candidate_prime++ | ||||||
|                 if candidate_prime==0 |                 if candidate_prime==0 | ||||||
|                     return 0 |                     return 0        ; we wrapped; no more primes | ||||||
|             } |             } | ||||||
|      |      | ||||||
|  |             ; found next one, mark the multiples and return it. | ||||||
|             sieve[candidate_prime] = true |             sieve[candidate_prime] = true | ||||||
|             uword multiple = candidate_prime |             uword multiple = candidate_prime | ||||||
|  |      | ||||||
|             while multiple < len(sieve) { |             while multiple < len(sieve) { | ||||||
|                 sieve[lsb(multiple)] = true |                 sieve[lsb(multiple)] = true | ||||||
|                 multiple += candidate_prime |                 multiple += candidate_prime | ||||||
| @@ -109,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: | 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: | One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this: | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -125,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: | 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,54 +1,51 @@ | |||||||
| buildscript { |  | ||||||
|     dependencies { |  | ||||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| plugins { | plugins { | ||||||
|     // id "org.jetbrains.kotlin.jvm" version $kotlinVersion |  | ||||||
|     id 'application' |  | ||||||
|     id 'org.jetbrains.dokka' version "0.9.18" |  | ||||||
|     id 'com.github.johnrengelman.shadow' version '5.1.0' |  | ||||||
|     id 'java' |     id 'java' | ||||||
|  |     id 'application' | ||||||
|  |     id "org.jetbrains.kotlin.jvm" version "1.5.10" | ||||||
|  |     id 'com.github.johnrengelman.shadow' version '6.1.0' | ||||||
| } | } | ||||||
|  |  | ||||||
| apply plugin: "kotlin" | targetCompatibility = 11 | ||||||
| apply plugin: "java" | sourceCompatibility = 11 | ||||||
|  |  | ||||||
|  |  | ||||||
| repositories { | repositories { | ||||||
|     mavenLocal() |     mavenLocal() | ||||||
|     mavenCentral() |     mavenCentral() | ||||||
|     jcenter() |     maven { url "https://kotlin.bintray.com/kotlinx" } | ||||||
| } | } | ||||||
|  |  | ||||||
| sourceCompatibility = 1.8 |  | ||||||
|  |  | ||||||
| def prog8version = rootProject.file('compiler/res/version.txt').text.trim() | def prog8version = rootProject.file('compiler/res/version.txt').text.trim() | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation project(':parser') |     implementation project(':compilerAst') | ||||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" |     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" | ||||||
|     // implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" |     // implementation "org.jetbrains.kotlin:kotlin-reflect" | ||||||
|     // runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" |     implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2' | ||||||
|     runtime 'org.antlr:antlr4-runtime:4.7.2' |     // implementation 'net.razorvine:ksim65:1.8' | ||||||
|     runtime project(':parser') |     // implementation "com.github.hypfvieh:dbus-java:3.2.4" | ||||||
|  |  | ||||||
|     testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion" |     testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" | ||||||
|     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' |     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' | ||||||
|     testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0' |     testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0' | ||||||
|  |  | ||||||
|     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2' |     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2' | ||||||
| } | } | ||||||
|  |  | ||||||
| compileKotlin { | compileKotlin { | ||||||
|     kotlinOptions { |     kotlinOptions { | ||||||
|         jvmTarget = "1.8" |         jvmTarget = "11" | ||||||
|         verbose = true |         useIR = true | ||||||
|  |         // verbose = true | ||||||
|         // freeCompilerArgs += "-XXLanguage:+NewInference" |         // freeCompilerArgs += "-XXLanguage:+NewInference" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | compileTestKotlin { | ||||||
|  |     kotlinOptions { | ||||||
|  |         jvmTarget = "11" | ||||||
|  |         useIR = true | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| sourceSets { | sourceSets { | ||||||
|     main { |     main { | ||||||
|         java { |         java { | ||||||
| @@ -68,7 +65,8 @@ sourceSets { | |||||||
| startScripts.enabled = true | startScripts.enabled = true | ||||||
|  |  | ||||||
| application { | application { | ||||||
|     mainClassName = 'prog8.CompilerMainKt' |     mainClass = 'prog8.CompilerMainKt' | ||||||
|  |     mainClassName = 'prog8.CompilerMainKt'  // deprecated | ||||||
|     applicationName = 'p8compile' |     applicationName = 'p8compile' | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -76,24 +74,10 @@ artifacts { | |||||||
|     archives shadowJar |     archives shadowJar | ||||||
| } | } | ||||||
|  |  | ||||||
| task p8vmScript(type: CreateStartScripts) { |  | ||||||
|     mainClassName = "prog8.vm.stackvm.MainKt" |  | ||||||
|     applicationName = "p8vm" |  | ||||||
|     outputDir = new File(project.buildDir, 'scripts') |  | ||||||
|     classpath = jar.outputs.files + project.configurations.runtime |  | ||||||
| } |  | ||||||
|  |  | ||||||
| applicationDistribution.into("bin") { |  | ||||||
|     from(p8vmScript) |  | ||||||
|     fileMode = 0755 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // To create a fat-jar use the 'create_compiler_jar' script for now |  | ||||||
| // @todo investigate https://imperceptiblethoughts.com/shadow/introduction/ |  | ||||||
|  |  | ||||||
| shadowJar { | shadowJar { | ||||||
|     baseName = 'prog8compiler' |     archiveBaseName = 'prog8compiler' | ||||||
|     version = prog8version |     archiveVersion = prog8version | ||||||
|     // minimize() |     // minimize() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -107,12 +91,11 @@ test { | |||||||
|  |  | ||||||
|     // Show test results. |     // Show test results. | ||||||
|     testLogging { |     testLogging { | ||||||
|         events "passed", "skipped", "failed" |         events "skipped", "failed" | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| dokka { | task wrapper(type: Wrapper) { | ||||||
|     outputFormat = 'html' |     gradleVersion = '6.7' | ||||||
|     outputDirectory = "$buildDir/kdoc" |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,10 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||||||
| <module type="JAVA_MODULE" version="4"> | <module type="JAVA_MODULE" version="4"> | ||||||
|  |   <component name="FacetManager"> | ||||||
|  |     <facet type="Python" name="Python"> | ||||||
|  |       <configuration sdkName="Python 3.9" /> | ||||||
|  |     </facet> | ||||||
|  |   </component> | ||||||
|   <component name="NewModuleRootManager" inherit-compiler-output="true"> |   <component name="NewModuleRootManager" inherit-compiler-output="true"> | ||||||
|     <exclude-output /> |     <exclude-output /> | ||||||
|     <content url="file://$MODULE_DIR$"> |     <content url="file://$MODULE_DIR$"> | ||||||
| @@ -8,11 +13,12 @@ | |||||||
|       <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> |       <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/build" /> |       <excludeFolder url="file://$MODULE_DIR$/build" /> | ||||||
|     </content> |     </content> | ||||||
|     <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> |     <orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" /> | ||||||
|     <orderEntry type="sourceFolder" forTests="false" /> |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|     <orderEntry type="library" name="KotlinJavaRuntime" level="project" /> |     <orderEntry type="library" name="KotlinJavaRuntime" level="project" /> | ||||||
|     <orderEntry type="library" name="antlr-runtime-4.7.2" level="project" /> |  | ||||||
|     <orderEntry type="module" module-name="parser" /> |  | ||||||
|     <orderEntry type="library" name="unittest-libs" level="project" /> |     <orderEntry type="library" name="unittest-libs" level="project" /> | ||||||
|  |     <orderEntry type="library" name="kotlinx-cli-jvm" level="project" /> | ||||||
|  |     <orderEntry type="module" module-name="compilerAst" /> | ||||||
|  |     <orderEntry type="library" name="Python 3.9 interpreter library" level="application" /> | ||||||
|   </component> |   </component> | ||||||
| </module> | </module> | ||||||
							
								
								
									
										
											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 | ||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 8.9 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 9.0 KiB | 
							
								
								
									
										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 | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										201
									
								
								compiler/res/prog8lib/c64/floats.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								compiler/res/prog8lib/c64/floats.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | |||||||
|  | ; Prog8 definitions for floating point handling on the Commodore-64 | ||||||
|  | ; | ||||||
|  | ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||||
|  | ; | ||||||
|  | ; indent format: TABS, size=8 | ||||||
|  |  | ||||||
|  | %target c64 | ||||||
|  | %option enable_floats | ||||||
|  |  | ||||||
|  | floats { | ||||||
|  | 	; ---- this block contains C-64 floating point related functions ---- | ||||||
|  |  | ||||||
|  |         const float  PI     = 3.141592653589793 | ||||||
|  |         const float  TWOPI  = 6.283185307179586 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ; ---- C64 basic and kernal ROM float constants and 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 $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 | ||||||
|  | romsub $ba90 = FAREADMEM() clobbers(A,Y)                    ; load mflpt value from memory  in $22/$23 into fac2 | ||||||
|  | romsub $bbfc = MOVFA() clobbers(A,X)                        ; copy fac2 to fac1 | ||||||
|  | romsub $bc0c = MOVAF() clobbers(A,X)                        ; copy fac1 to fac2  (rounded) | ||||||
|  | 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 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 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 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 | ||||||
|  | romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y)      ; 8 bit signed A -> float in fac1 | ||||||
|  | romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y)   ; str -> fac1, $22/23 must point to string, A=string length | ||||||
|  | romsub $aabc = FPRINTLN() clobbers(A,X,Y)                   ; print string of fac1, on one line (= with newline) destroys fac1.  (consider FOUT + STROUT as well) | ||||||
|  | romsub $bddd = FOUT() clobbers(X) -> uword @ AY             ; fac1 -> string, address returned in AY ($0100) | ||||||
|  |  | ||||||
|  | romsub $b849 = FADDH() clobbers(A,X,Y)                      ; fac1 += 0.5, for rounding- call this before INT | ||||||
|  | romsub $bae2 = MUL10() clobbers(A,X,Y)                      ; fac1 *= 10 | ||||||
|  | romsub $bafe = DIV10() clobbers(A,X,Y)                      ; fac1 /= 10 , CAUTION: result is always positive! | ||||||
|  | romsub $bc5b = 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 $b86a = FADDT() clobbers(A,X,Y)                      ; fac1 += fac2 | ||||||
|  | romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 += mflpt value from A/Y | ||||||
|  | romsub $b853 = FSUBT() clobbers(A,X,Y)                      ; fac1 = fac2-fac1   mind the order of the operands | ||||||
|  | romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y)       ; fac1 = mflpt from A/Y - fac1 | ||||||
|  | romsub $ba2b = FMULTT() clobbers(A,X,Y)                     ; fac1 *= fac2 | ||||||
|  | romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y)      ; fac1 *= mflpt value from A/Y | ||||||
|  | romsub $bb12 = FDIVT() clobbers(A,X,Y)                      ; fac1 = fac2/fac1  (remainder in fac2)  mind the order of the operands | ||||||
|  | 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 | ||||||
|  | romsub $b9ea = LOG() clobbers(A,X,Y)                        ; fac1 = LN(fac1)  (natural log) | ||||||
|  | romsub $bc39 = SGN() clobbers(A,X,Y)                        ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1) | ||||||
|  | romsub $bc2b = SIGN() -> ubyte @ A                          ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive | ||||||
|  | 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 (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) | ||||||
|  | romsub $e2b4 = TAN() clobbers(A,X,Y)                        ; fac1 = TAN(fac1) | ||||||
|  | romsub $e30e = ATN() clobbers(A,X,Y)                        ; fac1 = ATN(fac1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | asmsub  FREADS32() clobbers(A,X,Y)  { | ||||||
|  | 	; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST) | ||||||
|  | 	%asm {{ | ||||||
|  | 		lda  $62 | ||||||
|  | 		eor  #$ff | ||||||
|  | 		asl  a | ||||||
|  | 		lda  #0 | ||||||
|  | 		ldx  #$a0 | ||||||
|  | 		jmp  $bc4f		; internal BASIC routine | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  FREADUS32  () clobbers(A,X,Y)  { | ||||||
|  | 	; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST) | ||||||
|  | 	%asm {{ | ||||||
|  | 		sec | ||||||
|  | 		lda  #0 | ||||||
|  | 		ldx  #$a0 | ||||||
|  | 		jmp  $bc4f		; internal BASIC routine | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  FREADS24AXY  (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y)  { | ||||||
|  | 	; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes) | ||||||
|  | 	;      note: there is no FREADU24AXY (unsigned), use FREADUS32 instead. | ||||||
|  | 	%asm {{ | ||||||
|  | 		sty  $62 | ||||||
|  | 		stx  $63 | ||||||
|  | 		sta  $64 | ||||||
|  | 		lda  $62 | ||||||
|  | 		eor  #$FF | ||||||
|  | 		asl  a | ||||||
|  | 		lda  #0 | ||||||
|  | 		sta  $65 | ||||||
|  | 		ldx  #$98 | ||||||
|  | 		jmp  $bc4f		; internal BASIC routine | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | asmsub  GIVUAYFAY  (uword value @ AY) clobbers(A,X,Y)  { | ||||||
|  | 	; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1 | ||||||
|  | 	%asm {{ | ||||||
|  | 		sty  $62 | ||||||
|  | 		sta  $63 | ||||||
|  | 		ldx  #$90 | ||||||
|  | 		sec | ||||||
|  | 		jmp  $bc49		; internal BASIC routine | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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_REG | ||||||
|  | 		tya | ||||||
|  | 		ldy  P8ZP_SCRATCH_REG | ||||||
|  | 		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_REG | ||||||
|  | 		tya | ||||||
|  | 		ldy  P8ZP_SCRATCH_REG | ||||||
|  | 		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 | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sub  print_f  (float value) { | ||||||
|  | 	; ---- prints the floating point value (without a newline). | ||||||
|  | 	%asm {{ | ||||||
|  | 		stx  floats_store_reg | ||||||
|  | 		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  - | ||||||
|  | +		ldx  floats_store_reg | ||||||
|  | 		rts | ||||||
|  | 	}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | %asminclude "library:c64/floats.asm" | ||||||
|  | %asminclude "library:c64/floats_funcs.asm" | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										437
									
								
								compiler/res/prog8lib/c64/floats_funcs.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										437
									
								
								compiler/res/prog8lib/c64/floats_funcs.asm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,437 @@ | |||||||
|  | ; --- floating point builtin functions | ||||||
|  |  | ||||||
|  |  | ||||||
|  | abs_f_stack	.proc | ||||||
|  | 		; -- push abs(AY) on stack | ||||||
|  | 		jsr  floats.MOVFM | ||||||
|  | 		jsr  floats.ABS | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | abs_f_fac1	.proc | ||||||
|  | 		; -- FAC1 = abs(AY) | ||||||
|  | 		jsr  floats.MOVFM | ||||||
|  | 		jmp  floats.ABS | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_atan_stack	.proc | ||||||
|  | 		jsr  func_atan_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_atan_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  ATN | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ceil_stack	.proc | ||||||
|  | 		jsr  func_ceil_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ceil_fac1	.proc | ||||||
|  | 		; -- ceil: tr = int(f); if tr==f -> return  else return tr+1 | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		ldx  #<fmath_float1 | ||||||
|  | 		ldy  #>fmath_float1 | ||||||
|  | 		jsr  MOVMF | ||||||
|  | 		jsr  INT | ||||||
|  | 		lda  #<fmath_float1 | ||||||
|  | 		ldy  #>fmath_float1 | ||||||
|  | 		jsr  FCOMP | ||||||
|  | 		cmp  #0 | ||||||
|  | 		beq  + | ||||||
|  | 		lda  #<FL_ONE_const | ||||||
|  | 		ldy  #>FL_ONE_const | ||||||
|  | 		jsr  FADD | ||||||
|  | +		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_floor_stack	.proc | ||||||
|  | 		jsr  func_floor_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_floor_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  INT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_round_stack	.proc | ||||||
|  | 		jsr  func_round_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_round_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  FADDH | ||||||
|  | 		jsr  INT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sin_stack	.proc | ||||||
|  | 		jsr  func_sin_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sin_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  SIN | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_cos_stack	.proc | ||||||
|  | 		jsr  func_cos_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_cos_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  COS | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_tan_stack	.proc | ||||||
|  | 		jsr  func_tan_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_tan_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  TAN | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rad_stack	.proc | ||||||
|  | 		jsr  func_rad_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rad_fac1	.proc | ||||||
|  | 		; -- convert degrees to radians (d * pi / 180) | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  #<_pi_div_180 | ||||||
|  | 		ldy  #>_pi_div_180 | ||||||
|  | 		jsr  FMULT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | _pi_div_180	.byte 123, 14, 250, 53, 18		; pi / 180 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_deg_stack	.proc | ||||||
|  | 		jsr  func_deg_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_deg_fac1	.proc | ||||||
|  | 		; -- convert radians to degrees (d * (1/ pi * 180)) | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  #<_one_over_pi_div_180 | ||||||
|  | 		ldy  #>_one_over_pi_div_180 | ||||||
|  | 		jsr  FMULT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | _one_over_pi_div_180	.byte 134, 101, 46, 224, 211		; 1 / (pi * 180) | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ln_stack	.proc | ||||||
|  | 		jsr  func_ln_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_ln_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  LOG | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_log2_stack	.proc | ||||||
|  | 		jsr  func_log2_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_log2_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  LOG | ||||||
|  | 		jsr  MOVEF | ||||||
|  | 		lda  #<FL_LOG2_const | ||||||
|  | 		ldy  #>FL_LOG2_const | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		jsr  FDIVT | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sign_f_stack	.proc | ||||||
|  | 		jsr  func_sign_f_into_A | ||||||
|  | 		sta  P8ESTACK_LO,x | ||||||
|  | 		dex | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sign_f_into_A	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		jmp  SIGN | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sqrt_stack	.proc | ||||||
|  | 		jsr  func_sqrt_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sqrt_fac1	.proc | ||||||
|  | 		jsr  MOVFM | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		jsr  SQR | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rndf_stack	.proc | ||||||
|  | 		jsr  func_rndf_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_rndf_fac1	.proc | ||||||
|  | 		stx  P8ZP_SCRATCH_REG | ||||||
|  | 		lda  #1 | ||||||
|  | 		jsr  FREADSA | ||||||
|  | 		jsr  RND		; rng into fac1 | ||||||
|  | 		ldx  P8ZP_SCRATCH_REG | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_swap_f	.proc | ||||||
|  | 		; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2 | ||||||
|  | 		ldy  #4 | ||||||
|  | -               lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		lda  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W2),y | ||||||
|  | 		dey | ||||||
|  | 		bpl  - | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_reverse_f	.proc | ||||||
|  | 		; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A) | ||||||
|  | _left_index = P8ZP_SCRATCH_W2 | ||||||
|  | _right_index = P8ZP_SCRATCH_W2+1 | ||||||
|  | _loop_count = P8ZP_SCRATCH_REG | ||||||
|  | 		pha | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		sec | ||||||
|  | 		sbc  #5 | ||||||
|  | 		sta  _right_index | ||||||
|  | 		lda  #0 | ||||||
|  | 		sta  _left_index | ||||||
|  | 		pla | ||||||
|  | 		lsr  a | ||||||
|  | 		sta  _loop_count | ||||||
|  | _loop		; push the left indexed float on the stack | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		iny | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		pha | ||||||
|  | 		; copy right index float to left index float | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		inc  _right_index | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		lda  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		ldy  _left_index | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		; pop the float off the stack into the right index float | ||||||
|  | 		ldy  _right_index | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		dey | ||||||
|  | 		pla | ||||||
|  | 		sta  (P8ZP_SCRATCH_W1),y | ||||||
|  | 		inc  _left_index | ||||||
|  | 		lda  _right_index | ||||||
|  | 		sec | ||||||
|  | 		sbc  #9 | ||||||
|  | 		sta  _right_index | ||||||
|  | 		dec  _loop_count | ||||||
|  | 		bne  _loop | ||||||
|  | 		rts | ||||||
|  |  | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | a_times_5	.proc | ||||||
|  | 		sta  P8ZP_SCRATCH_B1 | ||||||
|  | 		asl  a | ||||||
|  | 		asl  a | ||||||
|  | 		clc | ||||||
|  | 		adc  P8ZP_SCRATCH_B1 | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_any_f_into_A	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_any_b_into_A | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_all_f_into_A	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_all_b_into_A | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_any_f_stack	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_any_b_stack | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_all_f_stack	.proc | ||||||
|  | 		jsr  a_times_5 | ||||||
|  | 		jmp  prog8_lib.func_all_b_stack | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_max_f_stack	.proc | ||||||
|  | 		jsr  func_max_f_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_max_f_fac1	.proc | ||||||
|  | 		; -- max(array) -> fac1,  array in P8ZP_SCRATCH_W1, num elts in A | ||||||
|  | _loop_count = P8ZP_SCRATCH_REG | ||||||
|  | 		stx  floats_store_reg | ||||||
|  | 		sta  _loop_count | ||||||
|  | 		lda  #255 | ||||||
|  | 		sta  _minmax_cmp+1		; modifying | ||||||
|  | 		lda  #<_largest_neg_float | ||||||
|  | 		ldy  #>_largest_neg_float | ||||||
|  | _minmax_entry	jsr  MOVFM | ||||||
|  | -		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		jsr  FCOMP | ||||||
|  | _minmax_cmp	cmp  #255			; modified | ||||||
|  | 		bne  + | ||||||
|  | 		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		jsr  MOVFM | ||||||
|  | +		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		clc | ||||||
|  | 		adc  #5 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		bcc  + | ||||||
|  | 		inc  P8ZP_SCRATCH_W1+1 | ||||||
|  | +		dec  _loop_count | ||||||
|  | 		bne  - | ||||||
|  | 		ldx  floats_store_reg | ||||||
|  | 		rts | ||||||
|  | _largest_neg_float	.byte 255,255,255,255,255		; largest negative float -1.7014118345e+38 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_min_f_stack	.proc | ||||||
|  | 		jsr  func_min_f_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_min_f_fac1	.proc | ||||||
|  | 		; -- min(array) -> fac1,  array in P8ZP_SCRATCH_W1, num elts in A | ||||||
|  | 		sta  func_max_f_fac1._loop_count | ||||||
|  | 		lda  #1 | ||||||
|  | 		sta  func_max_f_fac1._minmax_cmp+1 | ||||||
|  | 		lda  #<_largest_pos_float | ||||||
|  | 		ldy  #>_largest_pos_float | ||||||
|  | 		jmp  func_max_f_fac1._minmax_entry | ||||||
|  | _largest_pos_float	.byte  255,127,255,255,255		; largest positive float | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  |  | ||||||
|  | func_sum_f_stack	.proc | ||||||
|  | 		jsr  func_sum_f_fac1 | ||||||
|  | 		jmp  push_fac1 | ||||||
|  | 		.pend | ||||||
|  |  | ||||||
|  | func_sum_f_fac1	.proc | ||||||
|  | 		; -- sum(array) -> fac1,  array in P8ZP_SCRATCH_W1, num elts in A | ||||||
|  | _loop_count = P8ZP_SCRATCH_REG | ||||||
|  | 		stx  floats_store_reg | ||||||
|  | 		sta  _loop_count | ||||||
|  | 		lda  #<FL_ZERO_const | ||||||
|  | 		ldy  #>FL_ZERO_const | ||||||
|  | 		jsr  MOVFM | ||||||
|  | -		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		ldy  P8ZP_SCRATCH_W1+1 | ||||||
|  | 		jsr  FADD | ||||||
|  | 		lda  P8ZP_SCRATCH_W1 | ||||||
|  | 		clc | ||||||
|  | 		adc  #5 | ||||||
|  | 		sta  P8ZP_SCRATCH_W1 | ||||||
|  | 		bcc  + | ||||||
|  | 		inc  P8ZP_SCRATCH_W1+1 | ||||||
|  | +		dec  _loop_count | ||||||
|  | 		bne  - | ||||||
|  | 		ldx  floats_store_reg | ||||||
|  | 		rts | ||||||
|  | 		.pend | ||||||
							
								
								
									
										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,959 +0,0 @@ | |||||||
| ; Prog8 definitions for floating point handling on the Commodore-64 |  | ||||||
| ; |  | ||||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 |  | ||||||
| ; |  | ||||||
| ; indent format: TABS, size=8 |  | ||||||
|  |  | ||||||
| %option enable_floats |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ~ c64flt { |  | ||||||
| 	; ---- this block contains C-64 floating point related functions ---- |  | ||||||
|  |  | ||||||
| 		const  float  PI	= 3.141592653589793 |  | ||||||
| 		const  float  TWOPI	= 6.283185307179586 |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ---- C64 basic and kernal ROM float constants and 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. |  | ||||||
|  |  | ||||||
| 		; 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 |  | ||||||
| 		 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: |  | ||||||
| asmsub	MOVFM		(uword mflpt @ AY) clobbers(A,Y)	= $bba2		; load mflpt value from memory  in A/Y into fac1 |  | ||||||
| asmsub	FREADMEM	() clobbers(A,Y)			= $bba6		; load mflpt value from memory  in $22/$23 into fac1 |  | ||||||
| asmsub	CONUPK		(uword mflpt @ AY) clobbers(A,Y)	= $ba8c		; load mflpt value from memory  in A/Y into fac2 |  | ||||||
| asmsub	FAREADMEM	() clobbers(A,Y)			= $ba90		; load mflpt value from memory  in $22/$23 into fac2 |  | ||||||
| asmsub	MOVFA		() clobbers(A,X)			= $bbfc		; copy fac2 to fac1 |  | ||||||
| asmsub	MOVAF		() clobbers(A,X)			= $bc0c		; copy fac1 to fac2  (rounded) |  | ||||||
| asmsub	MOVEF		() clobbers(A,X)			= $bc0f		; copy fac1 to fac2 |  | ||||||
| asmsub	MOVMF		(uword mflpt @ XY) clobbers(A,Y)	= $bbd4		; 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) |  | ||||||
| asmsub	FTOSWORDYA	() clobbers(X) -> ubyte @ Y, ubyte @ A  = $b1aa |  | ||||||
|  |  | ||||||
| ; 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) |  | ||||||
| asmsub	GETADR		() clobbers(X) -> ubyte @ Y, ubyte @ A  = $b7f7 |  | ||||||
|  |  | ||||||
| asmsub	QINT		() clobbers(A,X,Y)			= $bc9b		; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST. |  | ||||||
| asmsub	AYINT		() clobbers(A,X,Y)			= $b1bf		; 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) |  | ||||||
| asmsub	GIVAYF		(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)	= $b391 |  | ||||||
|  |  | ||||||
| asmsub	FREADUY		(ubyte value @ Y) clobbers(A,X,Y)	= $b3a2		; 8 bit unsigned Y -> float in fac1 |  | ||||||
| asmsub	FREADSA		(byte value @ A) clobbers(A,X,Y)	= $bc3c		; 8 bit signed A -> float in fac1 |  | ||||||
| asmsub	FREADSTR	(ubyte length @ A) clobbers(A,X,Y)	= $b7b5		; str -> fac1, $22/23 must point to string, A=string length |  | ||||||
| asmsub	FPRINTLN	() clobbers(A,X,Y)			= $aabc		; print string of fac1, on one line (= with newline) destroys fac1.  (consider FOUT + STROUT as well) |  | ||||||
| asmsub	FOUT		() clobbers(X) -> uword @ AY		= $bddd		; fac1 -> string, address returned in AY ($0100) |  | ||||||
|  |  | ||||||
| asmsub	FADDH		() clobbers(A,X,Y)			= $b849		; fac1 += 0.5, for rounding- call this before INT |  | ||||||
| asmsub	MUL10		() clobbers(A,X,Y)			= $bae2		; fac1 *= 10 |  | ||||||
| asmsub	DIV10		() clobbers(A,X,Y)			= $bafe		; fac1 /= 10 , CAUTION: result is always positive! |  | ||||||
| asmsub	FCOMP		(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A = $bc5b		; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than |  | ||||||
|  |  | ||||||
| asmsub	FADDT		() clobbers(A,X,Y)			= $b86a		; fac1 += fac2 |  | ||||||
| asmsub	FADD		(uword mflpt @ AY) clobbers(A,X,Y)	= $b867		; fac1 += mflpt value from A/Y |  | ||||||
| asmsub	FSUBT		() clobbers(A,X,Y)			= $b853		; fac1 = fac2-fac1   mind the order of the operands |  | ||||||
| asmsub	FSUB		(uword mflpt @ AY) clobbers(A,X,Y)	= $b850		; fac1 = mflpt from A/Y - fac1 |  | ||||||
| asmsub	FMULTT 		() clobbers(A,X,Y)			= $ba2b		; fac1 *= fac2 |  | ||||||
| asmsub	FMULT		(uword mflpt @ AY) clobbers(A,X,Y)	= $ba28		; fac1 *= mflpt value from A/Y |  | ||||||
| asmsub	FDIVT 		() clobbers(A,X,Y)			= $bb12		; fac1 = fac2/fac1  (remainder in fac2)  mind the order of the operands |  | ||||||
| asmsub	FDIV  		(uword mflpt @ AY) clobbers(A,X,Y)	= $bb0f		; fac1 = mflpt in A/Y / fac1  (remainder in fac2) |  | ||||||
| asmsub	FPWRT		() clobbers(A,X,Y)			= $bf7b		; fac1 = fac2 ** fac1 |  | ||||||
| asmsub	FPWR		(uword mflpt @ AY) clobbers(A,X,Y)	= $bf78		; fac1 = fac2 ** mflpt from A/Y |  | ||||||
|  |  | ||||||
| asmsub	NOTOP		() clobbers(A,X,Y)			= $aed4		; fac1 = NOT(fac1) |  | ||||||
| asmsub	INT		() clobbers(A,X,Y)			= $bccc		; INT() truncates, use FADDH first to round instead of trunc |  | ||||||
| asmsub	LOG		() clobbers(A,X,Y)			= $b9ea		; fac1 = LN(fac1)  (natural log) |  | ||||||
| asmsub	SGN		() clobbers(A,X,Y)			= $bc39		; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1) |  | ||||||
| asmsub	SIGN		() -> ubyte @ A				= $bc2b		; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive |  | ||||||
| asmsub	ABS		()					= $bc58		; fac1 = ABS(fac1) |  | ||||||
| asmsub	SQR		() clobbers(A,X,Y)			= $bf71		; fac1 = SQRT(fac1) |  | ||||||
| asmsub	SQRA		() clobbers(A,X,Y)			= $bf74		; fac1 = SQRT(fac2) |  | ||||||
| asmsub	EXP		() clobbers(A,X,Y)			= $bfed		; fac1 = EXP(fac1)  (e ** fac1) |  | ||||||
| asmsub	NEGOP		() clobbers(A)				= $bfb4		; switch the sign of fac1 |  | ||||||
| asmsub	RND		() clobbers(A,X,Y)			= $e097		; fac1 = RND(fac1) float random number generator |  | ||||||
| asmsub	COS		() clobbers(A,X,Y)			= $e264		; fac1 = COS(fac1) |  | ||||||
| asmsub	SIN		() clobbers(A,X,Y)			= $e26b		; fac1 = SIN(fac1) |  | ||||||
| asmsub	TAN		() clobbers(A,X,Y)			= $e2b4		; fac1 = TAN(fac1) |  | ||||||
| asmsub	ATN		() clobbers(A,X,Y)			= $e30e		; fac1 = ATN(fac1) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| asmsub  FREADS32  () clobbers(A,X,Y)  { |  | ||||||
| 	; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST) |  | ||||||
| 	%asm {{ |  | ||||||
| 		lda  $62 |  | ||||||
| 		eor  #$ff |  | ||||||
| 		asl  a |  | ||||||
| 		lda  #0 |  | ||||||
| 		ldx  #$a0 |  | ||||||
| 		jmp  $bc4f		; internal BASIC routine |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| asmsub  FREADUS32  () clobbers(A,X,Y)  { |  | ||||||
| 	; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST) |  | ||||||
| 	%asm {{ |  | ||||||
| 		sec |  | ||||||
| 		lda  #0 |  | ||||||
| 		ldx  #$a0 |  | ||||||
| 		jmp  $bc4f		; internal BASIC routine |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| asmsub  FREADS24AXY  (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y)  { |  | ||||||
| 	; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes) |  | ||||||
| 	;      note: there is no FREADU24AXY (unsigned), use FREADUS32 instead. |  | ||||||
| 	%asm {{ |  | ||||||
| 		sty  $62 |  | ||||||
| 		stx  $63 |  | ||||||
| 		sta  $64 |  | ||||||
| 		lda  $62 |  | ||||||
| 		eor  #$FF |  | ||||||
| 		asl  a |  | ||||||
| 		lda  #0 |  | ||||||
| 		sta  $65 |  | ||||||
| 		ldx  #$98 |  | ||||||
| 		jmp  $bc4f		; internal BASIC routine |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| asmsub  GIVUAYFAY  (uword value @ AY) clobbers(A,X,Y)  { |  | ||||||
| 	; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1 |  | ||||||
| 	%asm {{ |  | ||||||
| 		sty  $62 |  | ||||||
| 		sta  $63 |  | ||||||
| 		ldx  #$90 |  | ||||||
| 		sec |  | ||||||
| 		jmp  $bc49		; internal BASIC routine |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
| 		tya |  | ||||||
| 		ldy  c64.SCRATCH_ZPREG |  | ||||||
| 		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  c64.SCRATCH_ZPREG |  | ||||||
| 		tya |  | ||||||
| 		ldy  c64.SCRATCH_ZPREG |  | ||||||
| 		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  c64.SCRATCH_ZPB1 |  | ||||||
| 		tya |  | ||||||
| 		ldy  c64.SCRATCH_ZPB1 |  | ||||||
| 		rts |  | ||||||
| 	}} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| sub  print_f  (float value) { |  | ||||||
| 	; ---- prints the floating point value (without a newline) using basic rom routines. |  | ||||||
| 	%asm {{ |  | ||||||
| 		stx  c64.SCRATCH_ZPREGX |  | ||||||
| 		lda  #<print_f_value |  | ||||||
| 		ldy  #>print_f_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 |  | ||||||
| 		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  #<print_fln_value |  | ||||||
| 		ldy  #>print_fln_value |  | ||||||
| 		jsr  MOVFM		; load float into fac1 |  | ||||||
| 		jsr  FPRINTLN		; print fac1 with newline |  | ||||||
| 		ldx  c64.SCRATCH_ZPREGX |  | ||||||
| 		rts |  | ||||||
| 	}} |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; --- low level floating point assembly routines |  | ||||||
| %asm {{ |  | ||||||
| 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 |  | ||||||
| 		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 |  | ||||||
| 		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 |  | ||||||
| }} |  | ||||||
|  |  | ||||||
| }  ; ------ end of block c64flt |  | ||||||
| @@ -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 ---- |  | ||||||
|  |  | ||||||
| asmsub	CLEARSCR	() clobbers(A,X,Y)		= $E544		; clear the screen |  | ||||||
| asmsub	HOMECRSR	() clobbers(A,X,Y)		= $E566		; cursor to top left of screen |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ---- end of C64 basic routines ---- |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ; ---- C64 kernal routines ---- |  | ||||||
|  |  | ||||||
| asmsub	STROUT   (uword strptr @ AY) clobbers(A, X, Y)	= $AB1E		; print null-terminated string (use c64scr.print instead) |  | ||||||
| asmsub	IRQDFRT  () clobbers(A,X,Y)			= $EA31		; default IRQ routine |  | ||||||
| asmsub	IRQDFEND () clobbers(A,X,Y)			= $EA81		; default IRQ end/cleanup |  | ||||||
| asmsub	CINT     () clobbers(A,X,Y)			= $FF81		; (alias: SCINIT) initialize screen editor and video chip |  | ||||||
| asmsub	IOINIT   () clobbers(A, X)			= $FF84		; initialize I/O devices (CIA, SID, IRQ) |  | ||||||
| asmsub	RAMTAS   () clobbers(A,X,Y)			= $FF87		; initialize RAM, tape buffer, screen |  | ||||||
| asmsub	RESTOR   () clobbers(A,X,Y)			= $FF8A		; restore default I/O vectors |  | ||||||
| asmsub	VECTOR   (ubyte dir @ Pc, uword userptr @ XY) clobbers(A,Y)  = $FF8D		; read/set I/O vector table |  | ||||||
| asmsub	SETMSG   (ubyte value @ A)			= $FF90		; set Kernal message control flag |  | ||||||
| asmsub	SECOND   (ubyte address @ A) clobbers(A)	= $FF93		; (alias: LSTNSA) send secondary address after LISTEN |  | ||||||
| asmsub	TKSA     (ubyte address @ A) clobbers(A)	= $FF96		; (alias: TALKSA) send secondary address after TALK |  | ||||||
| asmsub	MEMTOP   (ubyte dir @ Pc, uword address @ XY) -> uword @ XY	= $FF99		; read/set top of memory  pointer |  | ||||||
| asmsub	MEMBOT   (ubyte dir @ Pc, uword address @ XY) -> uword @ XY	= $FF9C		; read/set bottom of memory  pointer |  | ||||||
| asmsub	SCNKEY   () clobbers(A,X,Y)			= $FF9F		; scan the keyboard |  | ||||||
| asmsub	SETTMO   (ubyte timeout @ A)			= $FFA2		; set time-out flag for IEEE bus |  | ||||||
| asmsub	ACPTR    () -> ubyte @ A			= $FFA5		; (alias: IECIN) input byte from serial bus |  | ||||||
| asmsub	CIOUT    (ubyte databyte @ A)			= $FFA8		; (alias: IECOUT) output byte to serial bus |  | ||||||
| asmsub	UNTLK    () clobbers(A)				= $FFAB		; command serial bus device to UNTALK |  | ||||||
| asmsub	UNLSN    () clobbers(A)				= $FFAE		; command serial bus device to UNLISTEN |  | ||||||
| asmsub	LISTEN   (ubyte device @ A) clobbers(A)		= $FFB1		; command serial bus device to LISTEN |  | ||||||
| asmsub	TALK     (ubyte device @ A) clobbers(A)		= $FFB4		; command serial bus device to TALK |  | ||||||
| asmsub	READST   () -> ubyte @ A			= $FFB7		; read I/O status word |  | ||||||
| asmsub	SETLFS   (ubyte logical @ A, ubyte device @ X, ubyte address @ Y) = $FFBA	; set logical file parameters |  | ||||||
| asmsub	SETNAM   (ubyte namelen @ A, str filename @ XY)	= $FFBD		; set filename parameters |  | ||||||
| asmsub	OPEN     () clobbers(A,X,Y)			= $FFC0		; (via 794 ($31A)) open a logical file |  | ||||||
| asmsub	CLOSE    (ubyte logical @ A) clobbers(A,X,Y)	= $FFC3		; (via 796 ($31C)) close a logical file |  | ||||||
| asmsub	CHKIN    (ubyte logical @ X) clobbers(A,X)	= $FFC6		; (via 798 ($31E)) define an input channel |  | ||||||
| asmsub	CHKOUT   (ubyte logical @ X) clobbers(A,X)	= $FFC9		; (via 800 ($320)) define an output channel |  | ||||||
| asmsub	CLRCHN   () clobbers(A,X)			= $FFCC		; (via 802 ($322)) restore default devices |  | ||||||
| asmsub	CHRIN    () clobbers(Y) -> ubyte @ A		= $FFCF		; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read. |  | ||||||
| asmsub	CHROUT   (ubyte char @ A)			= $FFD2		; (via 806 ($326)) output a character |  | ||||||
| asmsub	LOAD     (ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y = $FFD5	; (via 816 ($330)) load from device |  | ||||||
| asmsub	SAVE     (ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A = $FFD8	; (via 818 ($332)) save to a device |  | ||||||
| asmsub	SETTIM   (ubyte low @ A, ubyte middle @ X, ubyte high @ Y)	= $FFDB		; set the software clock |  | ||||||
| asmsub	RDTIM    () -> ubyte @ A, ubyte @ X, ubyte @ Y	= $FFDE	; read the software clock |  | ||||||
| asmsub	STOP     () clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc	= $FFE1		; (via 808 ($328)) check the STOP key |  | ||||||
| asmsub	GETIN    () clobbers(X,Y) -> ubyte @ A		= $FFE4		; (via 810 ($32A)) get a character |  | ||||||
| asmsub	CLALL    () clobbers(A,X)			= $FFE7		; (via 812 ($32C)) close all files |  | ||||||
| asmsub	UDTIM    () clobbers(A,X)			= $FFEA		; update the software clock |  | ||||||
| asmsub	SCREEN   () -> ubyte @ X, ubyte @ Y		= $FFED		; read number of screen rows and columns |  | ||||||
| asmsub	PLOT     (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> ubyte @ X, ubyte @ Y	= $FFF0		; read/set position of cursor on screen.  Use c64scr.plot for a 'safe' wrapper that preserves X. |  | ||||||
| asmsub	IOBASE   () -> uword @ XY			= $FFF3		; 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 | ; 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" | ||||||
| ~ math { |  | ||||||
| 	%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 @@ | |||||||
| 1.10 | 7.0-BETA2 | ||||||
|   | |||||||
| @@ -1,25 +1,23 @@ | |||||||
| package prog8 | package prog8 | ||||||
|  |  | ||||||
| import prog8.vm.astvm.AstVm | import kotlinx.cli.* | ||||||
| import prog8.vm.stackvm.stackVmMain | import prog8.ast.base.AstException | ||||||
| import prog8.compiler.* | import prog8.compiler.CompilationResult | ||||||
| import java.nio.file.Paths | import prog8.compiler.compileProgram | ||||||
|  | import prog8.compiler.target.C64Target | ||||||
|  | import prog8.compiler.target.Cx16Target | ||||||
|  | import prog8.parser.ParsingFailedError | ||||||
|  | import java.io.File | ||||||
|  | import java.nio.file.FileSystems | ||||||
|  | import java.nio.file.Path | ||||||
|  | import java.nio.file.StandardWatchEventKinds | ||||||
|  | import java.time.LocalDateTime | ||||||
| import kotlin.system.exitProcess | import kotlin.system.exitProcess | ||||||
|  |  | ||||||
|  |  | ||||||
| fun main(args: Array<String>) { | fun main(args: Array<String>) { | ||||||
|  |  | ||||||
|     // check if the user wants to launch the VM instead |  | ||||||
|     if("-vm" in args) { |  | ||||||
|         val newArgs = args.toMutableList() |  | ||||||
|         newArgs.remove("-vm") |  | ||||||
|         return stackVmMain(newArgs.toTypedArray()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     printSoftwareHeader("compiler") |     printSoftwareHeader("compiler") | ||||||
|  |  | ||||||
|     if (args.isEmpty()) |  | ||||||
|         usage() |  | ||||||
|     compileMain(args) |     compileMain(args) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -30,72 +28,106 @@ internal fun printSoftwareHeader(what: String) { | |||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | fun pathFrom(stringPath: String, vararg rest: String): Path  = FileSystems.getDefault().getPath(stringPath, *rest) | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun compileMain(args: Array<String>) { | private fun compileMain(args: Array<String>) { | ||||||
|     var emulatorToStart = "" |     val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM) | ||||||
|     var moduleFile = "" |     val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation") | ||||||
|     var writeVmCode = false |     val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") | ||||||
|     var writeAssembly = true |     val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") | ||||||
|     var optimize = true |     val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") | ||||||
|     var optimizeInlining = true |     val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed") | ||||||
|     var launchAstVm = false |     val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") | ||||||
|     for (arg in args) { |     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) | ||||||
|         if(arg=="-emu") |     val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator) | ||||||
|             emulatorToStart = "x64" |     val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) | ||||||
|         else if(arg=="-emu2") |  | ||||||
|             emulatorToStart = "x64sc" |  | ||||||
|         else if(arg=="-writevm") |  | ||||||
|             writeVmCode = true |  | ||||||
|         else if(arg=="-noasm") |  | ||||||
|             writeAssembly = false |  | ||||||
|         else if(arg=="-noopt") |  | ||||||
|             optimize = false |  | ||||||
|         else if(arg=="-nooptinline") |  | ||||||
|             optimizeInlining = false |  | ||||||
|         else if(arg=="-avm") |  | ||||||
|             launchAstVm = true |  | ||||||
|         else if(!arg.startsWith("-")) |  | ||||||
|             moduleFile = arg |  | ||||||
|         else |  | ||||||
|             usage() |  | ||||||
|     } |  | ||||||
|     if(moduleFile.isBlank()) |  | ||||||
|         usage() |  | ||||||
|  |  | ||||||
|     val filepath = Paths.get(moduleFile).normalize() |     try { | ||||||
|  |         cli.parse(args) | ||||||
|     val (programAst, programName) = compileProgram(filepath, optimize, optimizeInlining, |     } catch (e: IllegalStateException) { | ||||||
|             !launchAstVm, writeVmCode, writeAssembly) |         System.err.println(e.message) | ||||||
|  |  | ||||||
|     if(launchAstVm) { |  | ||||||
|         println("\nLaunching AST-based vm...") |  | ||||||
|         val vm = AstVm(programAst) |  | ||||||
|         vm.run() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if(emulatorToStart.isNotEmpty()) { |  | ||||||
|         if(programName==null) |  | ||||||
|             println("\nCan't start emulator because no program was assembled.") |  | ||||||
|         else { |  | ||||||
|             println("\nStarting C-64 emulator $emulatorToStart...") |  | ||||||
|             val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programName.vice-mon-list", |  | ||||||
|                     "-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg") |  | ||||||
|             val process = ProcessBuilder(cmdline).inheritIO().start() |  | ||||||
|             process.waitFor() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun usage() { |  | ||||||
|     System.err.println("Missing argument(s):") |  | ||||||
|     System.err.println("    [-emu]          auto-start the 'x64' C-64 emulator after successful compilation") |  | ||||||
|     System.err.println("    [-emu2]         auto-start the 'x64sc' C-64 emulator after successful compilation") |  | ||||||
|     System.err.println("    [-writevm]      write intermediate vm code to a file as well") |  | ||||||
|     System.err.println("    [-noasm]        don't create assembly code") |  | ||||||
|     System.err.println("    [-vm]           launch the prog8 virtual machine instead of the compiler") |  | ||||||
|     System.err.println("    [-avm]          launch the prog8 ast-based virtual machine after compilation") |  | ||||||
|     System.err.println("    [-noopt]        don't perform any optimizations") |  | ||||||
|     System.err.println("    [-nooptinline]  don't perform subroutine inlining optimizations") |  | ||||||
|     System.err.println("    modulefile      main module file to compile") |  | ||||||
|         exitProcess(1) |         exitProcess(1) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     val outputPath = pathFrom(outputDir) | ||||||
|  |     if(!outputPath.toFile().isDirectory) { | ||||||
|  |         System.err.println("Output path doesn't exist") | ||||||
|  |         exitProcess(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) { | ||||||
|  |             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) | ||||||
|  |  | ||||||
|  |             println("Imported files (now watching:)") | ||||||
|  |             for (importedFile in allImportedFiles) { | ||||||
|  |                 print("  ") | ||||||
|  |                 println(importedFile) | ||||||
|  |                 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 | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } else { | ||||||
|  |         for(filepathRaw in moduleFiles) { | ||||||
|  |             val filepath = pathFrom(filepathRaw).normalize() | ||||||
|  |             val compilationResult: CompilationResult | ||||||
|  |             try { | ||||||
|  |                 compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) | ||||||
|  |                 if(!compilationResult.success) | ||||||
|  |                     exitProcess(1) | ||||||
|  |             } catch (x: ParsingFailedError) { | ||||||
|  |                 exitProcess(1) | ||||||
|  |             } catch (x: AstException) { | ||||||
|  |                 exitProcess(1) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (startEmulator==true) { | ||||||
|  |                 if (compilationResult.programName.isEmpty()) | ||||||
|  |                     println("\nCan't start emulator because no program was assembled.") | ||||||
|  |                 else { | ||||||
|  |                     compilationResult.compTarget.machine.launchEmulator(compilationResult.programName) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,92 +0,0 @@ | |||||||
| package prog8.ast |  | ||||||
|  |  | ||||||
| import prog8.ast.base.FatalAstException |  | ||||||
| import prog8.ast.base.NameError |  | ||||||
| import prog8.ast.base.ParentSentinel |  | ||||||
| import prog8.ast.base.Position |  | ||||||
| import prog8.ast.statements.Block |  | ||||||
| import prog8.ast.statements.Label |  | ||||||
| import prog8.ast.statements.Subroutine |  | ||||||
| import prog8.ast.statements.VarDecl |  | ||||||
| import prog8.compiler.HeapValues |  | ||||||
| import prog8.functions.BuiltinFunctions |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /*********** Everything starts from here, the Program; zero or more modules *************/ |  | ||||||
|  |  | ||||||
| class Program(val name: String, val modules: MutableList<Module>) { |  | ||||||
|     val namespace = GlobalNamespace(modules) |  | ||||||
|     val heap = HeapValues() |  | ||||||
|  |  | ||||||
|     val loadAddress: Int |  | ||||||
|         get() = modules.first().loadAddress |  | ||||||
|  |  | ||||||
|     fun entrypoint(): Subroutine? { |  | ||||||
|         val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block } |  | ||||||
|         if(mainBlocks.size > 1) |  | ||||||
|             throw FatalAstException("more than one 'main' block") |  | ||||||
|         return if(mainBlocks.isEmpty()) { |  | ||||||
|             null |  | ||||||
|         } else { |  | ||||||
|             mainBlocks[0].subScopes()["start"] as Subroutine? |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class Module(override val name: String, |  | ||||||
|              override var statements: MutableList<IStatement>, |  | ||||||
|              override val position: Position, |  | ||||||
|              val isLibraryModule: Boolean, |  | ||||||
|              val source: Path) : Node, INameScope { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     lateinit var program: Program |  | ||||||
|     val importedBy = mutableListOf<Module>() |  | ||||||
|     val imports = mutableSetOf<Module>() |  | ||||||
|  |  | ||||||
|     var loadAddress: Int = 0        // can be set with the %address directive |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         statements.forEach {it.linkParents(this)} |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun definingScope(): INameScope = program.namespace |  | ||||||
|  |  | ||||||
|     override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class GlobalNamespace(val modules: List<Module>): Node, INameScope { |  | ||||||
|     override val name = "<<<global>>>" |  | ||||||
|     override val position = Position("<<<global>>>", 0, 0, 0) |  | ||||||
|     override val statements = mutableListOf<IStatement>() |  | ||||||
|     override var parent: Node = ParentSentinel |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         modules.forEach { it.linkParents(this) } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun lookup(scopedName: List<String>, localContext: Node): IStatement? { |  | ||||||
|         if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) { |  | ||||||
|             // builtin functions always exist, return a dummy localContext for them |  | ||||||
|             val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position) |  | ||||||
|             builtinPlaceholder.parent = ParentSentinel |  | ||||||
|             return builtinPlaceholder |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         val stmt = localContext.definingModule().lookup(scopedName, localContext) |  | ||||||
|         return when (stmt) { |  | ||||||
|             is Label, is VarDecl, is Block, is Subroutine -> stmt |  | ||||||
|             null -> null |  | ||||||
|             else -> throw NameError("wrong identifier target: $stmt", stmt.position) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| object BuiltinFunctionScopePlaceholder : INameScope { |  | ||||||
|     override val name = "<<builtin-functions-scope-placeholder>>" |  | ||||||
|     override val position = Position("<<placeholder>>", 0, 0, 0) |  | ||||||
|     override var statements = mutableListOf<IStatement>() |  | ||||||
|     override var parent: Node = ParentSentinel |  | ||||||
|     override fun linkParents(parent: Node) {} |  | ||||||
| } |  | ||||||
| @@ -1,202 +0,0 @@ | |||||||
| package prog8.ast |  | ||||||
|  |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.processing.IAstModifyingVisitor |  | ||||||
| import prog8.ast.processing.IAstVisitor |  | ||||||
| import prog8.ast.statements.* |  | ||||||
|  |  | ||||||
| interface Node { |  | ||||||
|     val position: Position |  | ||||||
|     var parent: Node             // will be linked correctly later (late init) |  | ||||||
|     fun linkParents(parent: Node) |  | ||||||
|  |  | ||||||
|     fun definingModule(): Module { |  | ||||||
|         if(this is Module) |  | ||||||
|             return this |  | ||||||
|         return findParentNode<Module>(this)!! |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun definingSubroutine(): Subroutine?  = findParentNode<Subroutine>(this) |  | ||||||
|  |  | ||||||
|     fun definingScope(): INameScope { |  | ||||||
|         val scope = findParentNode<INameScope>(this) |  | ||||||
|         if(scope!=null) { |  | ||||||
|             return scope |  | ||||||
|         } |  | ||||||
|         if(this is Label && this.name.startsWith("builtin::")) { |  | ||||||
|             return BuiltinFunctionScopePlaceholder |  | ||||||
|         } |  | ||||||
|         if(this is GlobalNamespace) |  | ||||||
|             return this |  | ||||||
|         throw FatalAstException("scope missing from $this") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface IStatement : Node { |  | ||||||
|     fun accept(visitor: IAstModifyingVisitor) : IStatement |  | ||||||
|     fun accept(visitor: IAstVisitor) |  | ||||||
|     fun makeScopedName(name: String): String { |  | ||||||
|         // easy way out is to always return the full scoped name. |  | ||||||
|         // it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now. |  | ||||||
|         // and like this, we can cache the name even, |  | ||||||
|         // like in a lazy property on the statement object itself (label, subroutine, vardecl) |  | ||||||
|         val scope = mutableListOf<String>() |  | ||||||
|         var statementScope = this.parent |  | ||||||
|         while(statementScope !is ParentSentinel && statementScope !is Module) { |  | ||||||
|             if(statementScope is INameScope) { |  | ||||||
|                 scope.add(0, statementScope.name) |  | ||||||
|             } |  | ||||||
|             statementScope = statementScope.parent |  | ||||||
|         } |  | ||||||
|         if(name.isNotEmpty()) |  | ||||||
|             scope.add(name) |  | ||||||
|         return scope.joinToString(".") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val expensiveToInline: Boolean |  | ||||||
|  |  | ||||||
|     fun definingBlock(): Block { |  | ||||||
|         if(this is Block) |  | ||||||
|             return this |  | ||||||
|         return findParentNode<Block>(this)!! |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface IFunctionCall { |  | ||||||
|     var target: IdentifierReference |  | ||||||
|     var arglist: MutableList<IExpression> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface INameScope { |  | ||||||
|     val name: String |  | ||||||
|     val position: Position |  | ||||||
|     val statements: MutableList<IStatement> |  | ||||||
|     val parent: Node |  | ||||||
|  |  | ||||||
|     fun linkParents(parent: Node) |  | ||||||
|  |  | ||||||
|     fun subScopes(): Map<String, INameScope> { |  | ||||||
|         val subscopes = mutableMapOf<String, INameScope>() |  | ||||||
|         for(stmt in statements) { |  | ||||||
|             when(stmt) { |  | ||||||
|                 // NOTE: if other nodes are introduced that are a scope of contain subscopes, they must be added here! |  | ||||||
|                 is INameScope -> subscopes[stmt.name] = stmt |  | ||||||
|                 is ForLoop -> subscopes[stmt.body.name] = stmt.body |  | ||||||
|                 is RepeatLoop -> subscopes[stmt.body.name] = stmt.body |  | ||||||
|                 is WhileLoop -> subscopes[stmt.body.name] = stmt.body |  | ||||||
|                 is BranchStatement -> { |  | ||||||
|                     subscopes[stmt.truepart.name] = stmt.truepart |  | ||||||
|                     if(stmt.elsepart.containsCodeOrVars()) |  | ||||||
|                         subscopes[stmt.elsepart.name] = stmt.elsepart |  | ||||||
|                 } |  | ||||||
|                 is IfStatement -> { |  | ||||||
|                     subscopes[stmt.truepart.name] = stmt.truepart |  | ||||||
|                     if(stmt.elsepart.containsCodeOrVars()) |  | ||||||
|                         subscopes[stmt.elsepart.name] = stmt.elsepart |  | ||||||
|                 } |  | ||||||
|                 is WhenStatement -> { |  | ||||||
|                     stmt.choices.forEach { subscopes[it.statements.name] = it.statements } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return subscopes |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun getLabelOrVariable(name: String): IStatement? { |  | ||||||
|         // this is called A LOT and could perhaps be optimized a bit more, |  | ||||||
|         // but adding a memoization cache didn't make much of a practical runtime difference |  | ||||||
|         for (stmt in statements) { |  | ||||||
|             if (stmt is VarDecl && stmt.name==name) return stmt |  | ||||||
|             if (stmt is Label && stmt.name==name) return stmt |  | ||||||
|         } |  | ||||||
|         return null |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun allDefinedSymbols(): List<Pair<String, IStatement>>  { |  | ||||||
|         return statements.mapNotNull { |  | ||||||
|             when (it) { |  | ||||||
|                 is Label -> it.name to it |  | ||||||
|                 is VarDecl -> it.name to it |  | ||||||
|                 is Subroutine -> it.name to it |  | ||||||
|                 is Block -> it.name to it |  | ||||||
|                 else -> null |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun lookup(scopedName: List<String>, localContext: Node) : IStatement? { |  | ||||||
|         if(scopedName.size>1) { |  | ||||||
|             // it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program) |  | ||||||
|             for(module in localContext.definingModule().program.modules) { |  | ||||||
|                 var scope: INameScope? = module |  | ||||||
|                 for(name in scopedName.dropLast(1)) { |  | ||||||
|                     scope = scope?.subScopes()?.get(name) |  | ||||||
|                     if(scope==null) |  | ||||||
|                         break |  | ||||||
|                 } |  | ||||||
|                 if(scope!=null) { |  | ||||||
|                     val result = scope.getLabelOrVariable(scopedName.last()) |  | ||||||
|                     if(result!=null) |  | ||||||
|                         return result |  | ||||||
|                     return scope.subScopes()[scopedName.last()] as IStatement? |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return null |  | ||||||
|         } else { |  | ||||||
|             // unqualified name, find the scope the localContext is in, look in that first |  | ||||||
|             var statementScope = localContext |  | ||||||
|             while(statementScope !is ParentSentinel) { |  | ||||||
|                 val localScope = statementScope.definingScope() |  | ||||||
|                 val result = localScope.getLabelOrVariable(scopedName[0]) |  | ||||||
|                 if (result != null) |  | ||||||
|                     return result |  | ||||||
|                 val subscope = localScope.subScopes()[scopedName[0]] as IStatement? |  | ||||||
|                 if (subscope != null) |  | ||||||
|                     return subscope |  | ||||||
|                 // not found in this scope, look one higher up |  | ||||||
|                 statementScope = statementScope.parent |  | ||||||
|             } |  | ||||||
|             return null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"} |  | ||||||
|     fun containsNoCodeNorVars() = !containsCodeOrVars() |  | ||||||
|  |  | ||||||
|     fun remove(stmt: IStatement) { |  | ||||||
|         if(!statements.remove(stmt)) |  | ||||||
|             throw FatalAstException("stmt to remove wasn't found in scope") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| interface IExpression: Node { |  | ||||||
|     fun constValue(program: Program): LiteralValue? |  | ||||||
|     fun accept(visitor: IAstModifyingVisitor): IExpression |  | ||||||
|     fun accept(visitor: IAstVisitor) |  | ||||||
|     fun referencesIdentifiers(vararg name: String): Boolean |  | ||||||
|     fun inferType(program: Program): DataType? |  | ||||||
|  |  | ||||||
|     infix fun isSameAs(other: IExpression): Boolean { |  | ||||||
|         if(this===other) |  | ||||||
|             return true |  | ||||||
|         when(this) { |  | ||||||
|             is RegisterExpr -> |  | ||||||
|                 return (other is RegisterExpr && other.register==register) |  | ||||||
|             is IdentifierReference -> |  | ||||||
|                 return (other is IdentifierReference && other.nameInSource==nameInSource) |  | ||||||
|             is PrefixExpression -> |  | ||||||
|                 return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression) |  | ||||||
|             is BinaryExpression -> |  | ||||||
|                 return (other is BinaryExpression && other.operator==operator |  | ||||||
|                         && other.left isSameAs left |  | ||||||
|                         && other.right isSameAs right) |  | ||||||
|             is ArrayIndexedExpression -> { |  | ||||||
|                 return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource |  | ||||||
|                         && other.arrayspec.index isSameAs arrayspec.index) |  | ||||||
|             } |  | ||||||
|             is LiteralValue -> return (other is LiteralValue && other==this) |  | ||||||
|         } |  | ||||||
|         return false |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,586 +0,0 @@ | |||||||
| package prog8.ast.antlr |  | ||||||
|  |  | ||||||
| import org.antlr.v4.runtime.IntStream |  | ||||||
| import org.antlr.v4.runtime.ParserRuleContext |  | ||||||
| import org.antlr.v4.runtime.tree.TerminalNode |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.* |  | ||||||
| import java.io.CharConversionException |  | ||||||
| import java.io.File |  | ||||||
| import java.nio.file.Path |  | ||||||
| import prog8.compiler.target.c64.Petscii |  | ||||||
| import prog8.parser.CustomLexer |  | ||||||
| import prog8.parser.prog8Parser |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /***************** Antlr Extension methods to create AST ****************/ |  | ||||||
|  |  | ||||||
| private data class NumericLiteral(val number: Number, val datatype: DataType) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module { |  | ||||||
|     val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name |  | ||||||
|     return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun ParserRuleContext.toPosition() : Position { |  | ||||||
|     val customTokensource = this.start.tokenSource as? CustomLexer |  | ||||||
|     val filename = |  | ||||||
|             when { |  | ||||||
|                 customTokensource!=null -> customTokensource.modulePath.fileName.toString() |  | ||||||
|                 start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@" |  | ||||||
|                 else -> File(start.inputStream.sourceName).name |  | ||||||
|             } |  | ||||||
|     // note: be ware of TAB characters in the source text, they count as 1 column... |  | ||||||
|     return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement { |  | ||||||
|     val directive = directive()?.toAst() |  | ||||||
|     if(directive!=null) return directive |  | ||||||
|  |  | ||||||
|     val block = block()?.toAst(isInLibrary) |  | ||||||
|     if(block!=null) return block |  | ||||||
|  |  | ||||||
|     throw FatalAstException(text) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement = |  | ||||||
|         Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> = |  | ||||||
|         statement().asSequence().map { it.toAst() }.toMutableList() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.StatementContext.toAst() : IStatement { |  | ||||||
|     vardecl()?.let { |  | ||||||
|         return VarDecl(VarDeclType.VAR, |  | ||||||
|                 it.datatype().toAst(), |  | ||||||
|                 it.ZEROPAGE() != null, |  | ||||||
|                 it.arrayindex()?.toAst(), |  | ||||||
|                 it.identifier().text, |  | ||||||
|                 null, |  | ||||||
|                 it.ARRAYSIG() != null || it.arrayindex() != null, |  | ||||||
|                 false, |  | ||||||
|                 it.toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     varinitializer()?.let { |  | ||||||
|         val vd = it.vardecl() |  | ||||||
|         return VarDecl(VarDeclType.VAR, |  | ||||||
|                 vd.datatype().toAst(), |  | ||||||
|                 vd.ZEROPAGE() != null, |  | ||||||
|                 vd.arrayindex()?.toAst(), |  | ||||||
|                 vd.identifier().text, |  | ||||||
|                 it.expression().toAst(), |  | ||||||
|                 vd.ARRAYSIG() != null || vd.arrayindex() != null, |  | ||||||
|                 false, |  | ||||||
|                 it.toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     constdecl()?.let { |  | ||||||
|         val cvarinit = it.varinitializer() |  | ||||||
|         val vd = cvarinit.vardecl() |  | ||||||
|         return VarDecl(VarDeclType.CONST, |  | ||||||
|                 vd.datatype().toAst(), |  | ||||||
|                 vd.ZEROPAGE() != null, |  | ||||||
|                 vd.arrayindex()?.toAst(), |  | ||||||
|                 vd.identifier().text, |  | ||||||
|                 cvarinit.expression().toAst(), |  | ||||||
|                 vd.ARRAYSIG() != null || vd.arrayindex() != null, |  | ||||||
|                 false, |  | ||||||
|                 cvarinit.toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     memoryvardecl()?.let { |  | ||||||
|         val mvarinit = it.varinitializer() |  | ||||||
|         val vd = mvarinit.vardecl() |  | ||||||
|         return VarDecl(VarDeclType.MEMORY, |  | ||||||
|                 vd.datatype().toAst(), |  | ||||||
|                 vd.ZEROPAGE() != null, |  | ||||||
|                 vd.arrayindex()?.toAst(), |  | ||||||
|                 vd.identifier().text, |  | ||||||
|                 mvarinit.expression().toAst(), |  | ||||||
|                 vd.ARRAYSIG() != null || vd.arrayindex() != null, |  | ||||||
|                 false, |  | ||||||
|                 mvarinit.toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     assignment()?.let { |  | ||||||
|         return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     augassignment()?.let { |  | ||||||
|         return Assignment(it.assign_target().toAst(), |  | ||||||
|                 it.operator.text, |  | ||||||
|                 it.expression().toAst(), |  | ||||||
|                 it.toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     postincrdecr()?.let { |  | ||||||
|         return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val directive = directive()?.toAst() |  | ||||||
|     if(directive!=null) return directive |  | ||||||
|  |  | ||||||
|     val label = labeldef()?.toAst() |  | ||||||
|     if(label!=null) return label |  | ||||||
|  |  | ||||||
|     val jump = unconditionaljump()?.toAst() |  | ||||||
|     if(jump!=null) return jump |  | ||||||
|  |  | ||||||
|     val fcall = functioncall_stmt()?.toAst() |  | ||||||
|     if(fcall!=null) return fcall |  | ||||||
|  |  | ||||||
|     val ifstmt = if_stmt()?.toAst() |  | ||||||
|     if(ifstmt!=null) return ifstmt |  | ||||||
|  |  | ||||||
|     val returnstmt = returnstmt()?.toAst() |  | ||||||
|     if(returnstmt!=null) return returnstmt |  | ||||||
|  |  | ||||||
|     val sub = subroutine()?.toAst() |  | ||||||
|     if(sub!=null) return sub |  | ||||||
|  |  | ||||||
|     val asm = inlineasm()?.toAst() |  | ||||||
|     if(asm!=null) return asm |  | ||||||
|  |  | ||||||
|     val branchstmt = branch_stmt()?.toAst() |  | ||||||
|     if(branchstmt!=null) return branchstmt |  | ||||||
|  |  | ||||||
|     val forloop = forloop()?.toAst() |  | ||||||
|     if(forloop!=null) return forloop |  | ||||||
|  |  | ||||||
|     val repeatloop = repeatloop()?.toAst() |  | ||||||
|     if(repeatloop!=null) return repeatloop |  | ||||||
|  |  | ||||||
|     val whileloop = whileloop()?.toAst() |  | ||||||
|     if(whileloop!=null) return whileloop |  | ||||||
|  |  | ||||||
|     val breakstmt = breakstmt()?.toAst() |  | ||||||
|     if(breakstmt!=null) return breakstmt |  | ||||||
|  |  | ||||||
|     val continuestmt = continuestmt()?.toAst() |  | ||||||
|     if(continuestmt!=null) return continuestmt |  | ||||||
|  |  | ||||||
|     val asmsubstmt = asmsubroutine()?.toAst() |  | ||||||
|     if(asmsubstmt!=null) return asmsubstmt |  | ||||||
|  |  | ||||||
|     val whenstmt = whenstmt()?.toAst() |  | ||||||
|     if(whenstmt!=null) return whenstmt |  | ||||||
|  |  | ||||||
|     throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement { |  | ||||||
|     val name = identifier().text |  | ||||||
|     val address = asmsub_address()?.address?.toAst()?.number?.toInt() |  | ||||||
|     val params = asmsub_params()?.toAst() ?: emptyList() |  | ||||||
|     val returns = asmsub_returns()?.toAst() ?: emptyList() |  | ||||||
|     val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) } |  | ||||||
|     val normalReturnvalues = returns.map { it.type } |  | ||||||
|     val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } |  | ||||||
|     val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } |  | ||||||
|     val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet() |  | ||||||
|     val statements = statement_block()?.toAst() ?: mutableListOf() |  | ||||||
|     return Subroutine(name, normalParameters, normalReturnvalues, |  | ||||||
|             paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private class AsmSubroutineParameter(name: String, |  | ||||||
|                                      type: DataType, |  | ||||||
|                                      val registerOrPair: RegisterOrPair?, |  | ||||||
|                                      val statusflag: Statusflag?, |  | ||||||
|                                      val stack: Boolean, |  | ||||||
|                                      position: Position) : SubroutineParameter(name, type, position) |  | ||||||
|  |  | ||||||
| private class AsmSubroutineReturn(val type: DataType, |  | ||||||
|                                   val registerOrPair: RegisterOrPair?, |  | ||||||
|                                   val statusflag: Statusflag?, |  | ||||||
|                                   val stack: Boolean, |  | ||||||
|                                   val position: Position) |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ClobberContext.toAst(): Set<Register> |  | ||||||
|         = this.register().asSequence().map { it.toAst() }.toSet() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn> |  | ||||||
|         = asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> |  | ||||||
|         = asmsub_param().map { |  | ||||||
|     AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(), |  | ||||||
|             it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement { |  | ||||||
|     val location = scoped_identifier().toAst() |  | ||||||
|     return if(expression_list() == null) |  | ||||||
|         FunctionCallStatement(location, mutableListOf(), toPosition()) |  | ||||||
|     else |  | ||||||
|         FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall { |  | ||||||
|     val location = scoped_identifier().toAst() |  | ||||||
|     return if(expression_list() == null) |  | ||||||
|         FunctionCall(location, mutableListOf(), toPosition()) |  | ||||||
|     else |  | ||||||
|         FunctionCall(location, expression_list().toAst().toMutableList(), toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.InlineasmContext.toAst() = |  | ||||||
|         InlineAssembly(INLINEASMBLOCK().text, toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ReturnstmtContext.toAst() : Return { |  | ||||||
|     return Return(expression()?.toAst(), toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump { |  | ||||||
|     val address = integerliteral()?.toAst()?.number?.toInt() |  | ||||||
|     val identifier = scoped_identifier()?.toAst() |  | ||||||
|     return Jump(address, identifier, null, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.LabeldefContext.toAst(): IStatement = |  | ||||||
|         Label(children[0].text, toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.SubroutineContext.toAst() : Subroutine { |  | ||||||
|     return Subroutine(identifier().text, |  | ||||||
|             sub_params()?.toAst() ?: emptyList(), |  | ||||||
|             sub_return_part()?.toAst() ?: emptyList(), |  | ||||||
|             emptyList(), |  | ||||||
|             emptyList(), |  | ||||||
|             emptySet(), |  | ||||||
|             null, |  | ||||||
|             false, |  | ||||||
|             statement_block()?.toAst() ?: mutableListOf(), |  | ||||||
|             toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> { |  | ||||||
|     val returns = sub_returns() ?: return emptyList() |  | ||||||
|     return returns.datatype().map { it.toAst() } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> = |  | ||||||
|         vardecl().map { |  | ||||||
|             SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition()) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget { |  | ||||||
|     val register = register()?.toAst() |  | ||||||
|     val identifier = scoped_identifier() |  | ||||||
|     return when { |  | ||||||
|         register!=null -> AssignTarget(register, null, null, null, toPosition()) |  | ||||||
|         identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition()) |  | ||||||
|         arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition()) |  | ||||||
|         directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition()) |  | ||||||
|         else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase()) |  | ||||||
|  |  | ||||||
| private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase()) |  | ||||||
|  |  | ||||||
| private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex = |  | ||||||
|         ArrayIndex(expression().toAst(), toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.DirectiveContext.toAst() : Directive = |  | ||||||
|         Directive(directivename.text, directivearg().map { it.toAst() }, toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg = |  | ||||||
|         DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral { |  | ||||||
|     fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral { |  | ||||||
|         val integer: Int |  | ||||||
|         var datatype = DataType.UBYTE |  | ||||||
|         when (radix) { |  | ||||||
|             10 -> { |  | ||||||
|                 integer = try { |  | ||||||
|                     text.toInt() |  | ||||||
|                 } catch(x: NumberFormatException) { |  | ||||||
|                     throw AstException("${toPosition()} invalid decimal literal ${x.message}") |  | ||||||
|                 } |  | ||||||
|                 datatype = when(integer) { |  | ||||||
|                     in 0..255 -> DataType.UBYTE |  | ||||||
|                     in -128..127 -> DataType.BYTE |  | ||||||
|                     in 0..65535 -> DataType.UWORD |  | ||||||
|                     in -32768..32767 -> DataType.WORD |  | ||||||
|                     else -> DataType.FLOAT |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             2 -> { |  | ||||||
|                 if(text.length>8) |  | ||||||
|                     datatype = DataType.UWORD |  | ||||||
|                 try { |  | ||||||
|                     integer = text.toInt(2) |  | ||||||
|                 } catch(x: NumberFormatException) { |  | ||||||
|                     throw AstException("${toPosition()} invalid binary literal ${x.message}") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             16 -> { |  | ||||||
|                 if(text.length>2) |  | ||||||
|                     datatype = DataType.UWORD |  | ||||||
|                 try { |  | ||||||
|                     integer = text.toInt(16) |  | ||||||
|                 } catch(x: NumberFormatException) { |  | ||||||
|                     throw AstException("${toPosition()} invalid hexadecimal literal ${x.message}") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw FatalAstException("invalid radix") |  | ||||||
|         } |  | ||||||
|         return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype) |  | ||||||
|     } |  | ||||||
|     val terminal: TerminalNode = children[0] as TerminalNode |  | ||||||
|     val integerPart = this.intpart.text |  | ||||||
|     return when (terminal.symbol.type) { |  | ||||||
|         prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null) |  | ||||||
|         prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null) |  | ||||||
|         prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null) |  | ||||||
|         else -> throw FatalAstException(terminal.text) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ExpressionContext.toAst() : IExpression { |  | ||||||
|  |  | ||||||
|     val litval = literalvalue() |  | ||||||
|     if(litval!=null) { |  | ||||||
|         val booleanlit = litval.booleanliteral()?.toAst() |  | ||||||
|         return if(booleanlit!=null) { |  | ||||||
|             LiteralValue.fromBoolean(booleanlit, litval.toPosition()) |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             val intLit = litval.integerliteral()?.toAst() |  | ||||||
|             when { |  | ||||||
|                 intLit!=null -> when(intLit.datatype) { |  | ||||||
|                     DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) |  | ||||||
|                     DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) |  | ||||||
|                     DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) |  | ||||||
|                     DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) |  | ||||||
|                     DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = intLit.number.toDouble(), position = litval.toPosition()) |  | ||||||
|                     else -> throw FatalAstException("invalid datatype for numeric literal") |  | ||||||
|                 } |  | ||||||
|                 litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition()) |  | ||||||
|                 litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition()) |  | ||||||
|                 litval.charliteral()!=null -> { |  | ||||||
|                     try { |  | ||||||
|                         LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition()) |  | ||||||
|                     } catch (ce: CharConversionException) { |  | ||||||
|                         throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition()) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 litval.arrayliteral()!=null -> { |  | ||||||
|                     val array = litval.arrayliteral()?.toAst() |  | ||||||
|                     // the actual type of the arraysize can not yet be determined here (missing namespace & heap) |  | ||||||
|                     // the ConstantFolder takes care of that and converts the type if needed. |  | ||||||
|                     LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition()) |  | ||||||
|                 } |  | ||||||
|                 else -> throw FatalAstException("invalid parsed literal") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if(register()!=null) |  | ||||||
|         return RegisterExpr(register().toAst(), register().toPosition()) |  | ||||||
|  |  | ||||||
|     if(scoped_identifier()!=null) |  | ||||||
|         return scoped_identifier().toAst() |  | ||||||
|  |  | ||||||
|     if(bop!=null) |  | ||||||
|         return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition()) |  | ||||||
|  |  | ||||||
|     if(prefix!=null) |  | ||||||
|         return PrefixExpression(prefix.text, expression(0).toAst(), toPosition()) |  | ||||||
|  |  | ||||||
|     val funcall = functioncall()?.toAst() |  | ||||||
|     if(funcall!=null) return funcall |  | ||||||
|  |  | ||||||
|     if (rangefrom!=null && rangeto!=null) { |  | ||||||
|         val step = rangestep?.toAst() ?: LiteralValue(DataType.UBYTE, 1, position = toPosition()) |  | ||||||
|         return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if(childCount==3 && children[0].text=="(" && children[2].text==")") |  | ||||||
|         return expression(0).toAst()        // expression within ( ) |  | ||||||
|  |  | ||||||
|     if(arrayindexed()!=null) |  | ||||||
|         return arrayindexed().toAst() |  | ||||||
|  |  | ||||||
|     if(typecast()!=null) |  | ||||||
|         return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition()) |  | ||||||
|  |  | ||||||
|     if(directmemory()!=null) |  | ||||||
|         return DirectMemoryRead(directmemory().expression().toAst(), toPosition()) |  | ||||||
|  |  | ||||||
|     if(addressof()!=null) |  | ||||||
|         return AddressOf(addressof().scoped_identifier().toAst(), toPosition()) |  | ||||||
|  |  | ||||||
|     throw FatalAstException(text) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression { |  | ||||||
|     return ArrayIndexedExpression(scoped_identifier().toAst(), |  | ||||||
|             arrayindex().toAst(), |  | ||||||
|             toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference = |  | ||||||
|         IdentifierReference(listOf(text), toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference = |  | ||||||
|         IdentifierReference(NAME().map { it.text }, toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.BooleanliteralContext.toAst() = when(text) { |  | ||||||
|     "true" -> true |  | ||||||
|     "false" -> false |  | ||||||
|     else -> throw FatalAstException(text) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ArrayliteralContext.toAst() : Array<IExpression> = |  | ||||||
|         expression().map { it.toAst() }.toTypedArray() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.If_stmtContext.toAst(): IfStatement { |  | ||||||
|     val condition = expression().toAst() |  | ||||||
|     val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) |  | ||||||
|     val elseStatements = else_part()?.toAst() ?: mutableListOf() |  | ||||||
|     val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() |  | ||||||
|             ?: statement().toPosition()) |  | ||||||
|     val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition()) |  | ||||||
|     return IfStatement(condition, trueScope, elseScope, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Else_partContext.toAst(): MutableList<IStatement> { |  | ||||||
|     return statement_block()?.toAst() ?: mutableListOf(statement().toAst()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement { |  | ||||||
|     val branchcondition = branchcondition().toAst() |  | ||||||
|     val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) |  | ||||||
|     val elseStatements = else_part()?.toAst() ?: mutableListOf() |  | ||||||
|     val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() |  | ||||||
|             ?: statement().toPosition()) |  | ||||||
|     val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition()) |  | ||||||
|     return BranchStatement(branchcondition, trueScope, elseScope, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ForloopContext.toAst(): ForLoop { |  | ||||||
|     val loopregister = register()?.toAst() |  | ||||||
|     val datatype = datatype()?.toAst() |  | ||||||
|     val zeropage = ZEROPAGE()!=null |  | ||||||
|     val loopvar = identifier()?.toAst() |  | ||||||
|     val iterable = expression()!!.toAst() |  | ||||||
|     val scope = |  | ||||||
|             if(statement()!=null) |  | ||||||
|                 AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition()) |  | ||||||
|             else |  | ||||||
|                 AnonymousScope(statement_block().toAst(), statement_block().toPosition()) |  | ||||||
|     return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition()) |  | ||||||
|  |  | ||||||
| private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition()) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.WhileloopContext.toAst(): WhileLoop { |  | ||||||
|     val condition = expression().toAst() |  | ||||||
|     val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) |  | ||||||
|     val scope = AnonymousScope(statements, statement_block()?.toPosition() |  | ||||||
|             ?: statement().toPosition()) |  | ||||||
|     return WhileLoop(condition, scope, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop { |  | ||||||
|     val untilCondition = expression().toAst() |  | ||||||
|     val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) |  | ||||||
|     val scope = AnonymousScope(statements, statement_block()?.toPosition() |  | ||||||
|             ?: statement().toPosition()) |  | ||||||
|     return RepeatLoop(scope, untilCondition, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement { |  | ||||||
|     val condition = expression().toAst() |  | ||||||
|     val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf() |  | ||||||
|     return WhenStatement(condition, choices, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun prog8Parser.When_choiceContext.toAst(): WhenChoice { |  | ||||||
|     val values = expression_list()?.toAst() |  | ||||||
|     val stmt = statement()?.toAst() |  | ||||||
|     val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf() |  | ||||||
|     if(stmt!=null) |  | ||||||
|         stmt_block.add(stmt) |  | ||||||
|     val scope = AnonymousScope(stmt_block, toPosition()) |  | ||||||
|     return WhenChoice(values, scope, toPosition()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r") |  | ||||||
|  |  | ||||||
| internal fun unescape(str: String, position: Position): String { |  | ||||||
|     val result = mutableListOf<Char>() |  | ||||||
|     val iter = str.iterator() |  | ||||||
|     while(iter.hasNext()) { |  | ||||||
|         val c = iter.nextChar() |  | ||||||
|         if(c=='\\') { |  | ||||||
|             val ec = iter.nextChar() |  | ||||||
|             result.add(when(ec) { |  | ||||||
|                 '\\' -> '\\' |  | ||||||
|                 'n' -> '\n' |  | ||||||
|                 'r' -> '\r' |  | ||||||
|                 '"' -> '"' |  | ||||||
|                 'u' -> { |  | ||||||
|                     "${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar() |  | ||||||
|                 } |  | ||||||
|                 else -> throw SyntaxError("invalid escape char in string: \\$ec", position) |  | ||||||
|             }) |  | ||||||
|         } else { |  | ||||||
|             result.add(c) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return result.joinToString("") |  | ||||||
| } |  | ||||||
| @@ -1,137 +0,0 @@ | |||||||
| package prog8.ast.base |  | ||||||
|  |  | ||||||
| import prog8.ast.Node |  | ||||||
|  |  | ||||||
| /**************************** AST Data classes ****************************/ |  | ||||||
|  |  | ||||||
| enum class DataType { |  | ||||||
|     UBYTE, |  | ||||||
|     BYTE, |  | ||||||
|     UWORD, |  | ||||||
|     WORD, |  | ||||||
|     FLOAT, |  | ||||||
|     STR, |  | ||||||
|     STR_S, |  | ||||||
|     ARRAY_UB, |  | ||||||
|     ARRAY_B, |  | ||||||
|     ARRAY_UW, |  | ||||||
|     ARRAY_W, |  | ||||||
|     ARRAY_F; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * is the type assignable to the given other type? |  | ||||||
|      */ |  | ||||||
|     infix fun isAssignableTo(targetType: DataType) = |  | ||||||
|             // what types are assignable to others without loss of precision? |  | ||||||
|             when(this) { |  | ||||||
|                 UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT |  | ||||||
|                 BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT |  | ||||||
|                 UWORD -> targetType == UWORD || targetType == FLOAT |  | ||||||
|                 WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT |  | ||||||
|                 FLOAT -> targetType == FLOAT |  | ||||||
|                 STR -> targetType == STR || targetType==STR_S |  | ||||||
|                 STR_S -> targetType == STR || targetType==STR_S |  | ||||||
|                 in ArrayDatatypes -> targetType === this |  | ||||||
|                 else -> false |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it } |  | ||||||
|  |  | ||||||
|     infix fun largerThan(other: DataType) = |  | ||||||
|             when(this) { |  | ||||||
|                 in ByteDatatypes -> false |  | ||||||
|                 in WordDatatypes -> other in ByteDatatypes |  | ||||||
|                 else -> true |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|     infix fun equalsSize(other: DataType) = |  | ||||||
|             when(this) { |  | ||||||
|                 in ByteDatatypes -> other in ByteDatatypes |  | ||||||
|                 in WordDatatypes -> other in WordDatatypes |  | ||||||
|                 else -> false |  | ||||||
|             } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| enum class Register { |  | ||||||
|     A, |  | ||||||
|     X, |  | ||||||
|     Y |  | ||||||
| } |  | ||||||
|  |  | ||||||
| enum class RegisterOrPair { |  | ||||||
|     A, |  | ||||||
|     X, |  | ||||||
|     Y, |  | ||||||
|     AX, |  | ||||||
|     AY, |  | ||||||
|     XY |  | ||||||
| }       // only used in parameter and return value specs in asm subroutines |  | ||||||
|  |  | ||||||
| enum class Statusflag { |  | ||||||
|     Pc, |  | ||||||
|     Pz, |  | ||||||
|     Pv, |  | ||||||
|     Pn |  | ||||||
| } |  | ||||||
|  |  | ||||||
| enum class BranchCondition { |  | ||||||
|     CS, |  | ||||||
|     CC, |  | ||||||
|     EQ, |  | ||||||
|     Z, |  | ||||||
|     NE, |  | ||||||
|     NZ, |  | ||||||
|     VS, |  | ||||||
|     VC, |  | ||||||
|     MI, |  | ||||||
|     NEG, |  | ||||||
|     PL, |  | ||||||
|     POS |  | ||||||
| } |  | ||||||
|  |  | ||||||
| enum class VarDeclType { |  | ||||||
|     VAR, |  | ||||||
|     CONST, |  | ||||||
|     MEMORY |  | ||||||
| } |  | ||||||
|  |  | ||||||
| val IterableDatatypes = setOf( |  | ||||||
|         DataType.STR, DataType.STR_S, |  | ||||||
|         DataType.ARRAY_UB, DataType.ARRAY_B, |  | ||||||
|         DataType.ARRAY_UW, DataType.ARRAY_W, |  | ||||||
|         DataType.ARRAY_F) |  | ||||||
| val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE) |  | ||||||
| val WordDatatypes = setOf(DataType.UWORD, DataType.WORD) |  | ||||||
| val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) |  | ||||||
| val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) |  | ||||||
| val StringDatatypes = setOf(DataType.STR, DataType.STR_S) |  | ||||||
| val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F) |  | ||||||
| val ArrayElementTypes = mapOf( |  | ||||||
|         DataType.ARRAY_B to DataType.BYTE, |  | ||||||
|         DataType.ARRAY_UB to DataType.UBYTE, |  | ||||||
|         DataType.ARRAY_W to DataType.WORD, |  | ||||||
|         DataType.ARRAY_UW to DataType.UWORD, |  | ||||||
|         DataType.ARRAY_F to DataType.FLOAT) |  | ||||||
|  |  | ||||||
| // find the parent node of a specific type or interface |  | ||||||
| // (useful to figure out in what namespace/block something is defined, etc) |  | ||||||
| inline fun <reified T> findParentNode(node: Node): T? { |  | ||||||
|     var candidate = node.parent |  | ||||||
|     while(candidate !is T && candidate !is ParentSentinel) |  | ||||||
|         candidate = candidate.parent |  | ||||||
|     return if(candidate is ParentSentinel) |  | ||||||
|         null |  | ||||||
|     else |  | ||||||
|         candidate as T |  | ||||||
| } |  | ||||||
|  |  | ||||||
| object ParentSentinel : Node { |  | ||||||
|     override val position = Position("<<sentinel>>", 0, 0, 0) |  | ||||||
|     override var parent: Node = this |  | ||||||
|     override fun linkParents(parent: Node) {} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { |  | ||||||
|     override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]" |  | ||||||
| } |  | ||||||
| @@ -1,37 +0,0 @@ | |||||||
| package prog8.ast.base |  | ||||||
|  |  | ||||||
| import prog8.parser.ParsingFailedError |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fun printErrors(errors: List<Any>, moduleName: String) { |  | ||||||
|     val reportedMessages = mutableSetOf<String>() |  | ||||||
|     print("\u001b[91m")  // bright red |  | ||||||
|     errors.forEach { |  | ||||||
|         val msg = it.toString() |  | ||||||
|         if(msg !in reportedMessages) { |  | ||||||
|             System.err.println(msg) |  | ||||||
|             reportedMessages.add(msg) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     print("\u001b[0m")  // reset color |  | ||||||
|     if(reportedMessages.isNotEmpty()) |  | ||||||
|         throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.") |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fun printWarning(msg: String, position: Position, detailInfo: String?=null) { |  | ||||||
|     print("\u001b[93m")  // bright yellow |  | ||||||
|     print("$position Warning: $msg") |  | ||||||
|     if(detailInfo==null) |  | ||||||
|         print("\n") |  | ||||||
|     else |  | ||||||
|         println(": $detailInfo\n") |  | ||||||
|     print("\u001b[0m")  // normal |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| fun printWarning(msg: String) { |  | ||||||
|     print("\u001b[93m")  // bright yellow |  | ||||||
|     print("Warning: $msg") |  | ||||||
|     print("\u001b[0m\n")  // normal |  | ||||||
| } |  | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| package prog8.ast.base |  | ||||||
|  |  | ||||||
| import prog8.ast.expressions.IdentifierReference |  | ||||||
|  |  | ||||||
| class FatalAstException (override var message: String) : Exception(message) |  | ||||||
|  |  | ||||||
| open class AstException (override var message: String) : Exception(message) |  | ||||||
|  |  | ||||||
| class SyntaxError(override var message: String, val position: Position) : AstException(message) { |  | ||||||
|     override fun toString() = "$position Syntax error: $message" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class NameError(override var message: String, val position: Position) : AstException(message) { |  | ||||||
|     override fun toString() = "$position Name error: $message" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| open class ExpressionError(message: String, val position: Position) : AstException(message) { |  | ||||||
|     override fun toString() = "$position Error: $message" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class UndefinedSymbolError(symbol: IdentifierReference) |  | ||||||
|     : ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) |  | ||||||
| @@ -1,92 +0,0 @@ | |||||||
| package prog8.ast.base |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.expressions.IdentifierReference |  | ||||||
| import prog8.ast.processing.* |  | ||||||
| import prog8.ast.statements.Assignment |  | ||||||
| import prog8.ast.statements.ForLoop |  | ||||||
| import prog8.compiler.CompilationOptions |  | ||||||
| import prog8.optimizer.RemoveNops |  | ||||||
|  |  | ||||||
|  |  | ||||||
| // the name of the subroutine that should be called for every block to initialize its variables |  | ||||||
| internal const val initvarsSubName="prog8_init_vars" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| // prefix for literal values that are turned into a variable on the heap |  | ||||||
| internal const val autoHeapValuePrefix = "auto_heap_value_" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun Program.removeNops() { |  | ||||||
|     val remover = RemoveNops() |  | ||||||
|     remover.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun Program.checkValid(compilerOptions: CompilationOptions) { |  | ||||||
|     val checker = AstChecker(this, compilerOptions) |  | ||||||
|     checker.visit(this) |  | ||||||
|     printErrors(checker.result(), name) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun Program.reorderStatements() { |  | ||||||
|     val initvalueCreator = VarInitValueAndAddressOfCreator(namespace) |  | ||||||
|     initvalueCreator.visit(this) |  | ||||||
|  |  | ||||||
|     val checker = StatementReorderer(this) |  | ||||||
|     checker.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Module.checkImportedValid() { |  | ||||||
|     val checker = ImportedModuleDirectiveRemover() |  | ||||||
|     checker.visit(this) |  | ||||||
|     printErrors(checker.result(), name) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| internal fun Program.checkRecursion() { |  | ||||||
|     val checker = AstRecursionChecker(namespace) |  | ||||||
|     checker.visit(this) |  | ||||||
|     printErrors(checker.result(), name) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun Program.checkIdentifiers() { |  | ||||||
|     val checker = AstIdentifiersChecker(namespace) |  | ||||||
|     checker.visit(this) |  | ||||||
|  |  | ||||||
|     if(modules.map {it.name}.toSet().size != modules.size) { |  | ||||||
|         throw FatalAstException("modules should all be unique") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // add any anonymous variables for heap values that are used, |  | ||||||
|     // and replace an iterable literalvalue by identifierref to new local variable |  | ||||||
|     // TODO: this is't doing anything anymore? |  | ||||||
|     for (variable in checker.anonymousVariablesFromHeap.values) { |  | ||||||
|         val scope = variable.first.definingScope() |  | ||||||
|         scope.statements.add(variable.second) |  | ||||||
|         val parent = variable.first.parent |  | ||||||
|         when { |  | ||||||
|             parent is Assignment && parent.value === variable.first -> { |  | ||||||
|                 val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) |  | ||||||
|                 idref.linkParents(parent) |  | ||||||
|                 parent.value = idref |  | ||||||
|             } |  | ||||||
|             parent is IFunctionCall -> { |  | ||||||
|                 val parameterPos = parent.arglist.indexOf(variable.first) |  | ||||||
|                 val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) |  | ||||||
|                 idref.linkParents(parent) |  | ||||||
|                 parent.arglist[parameterPos] = idref |  | ||||||
|             } |  | ||||||
|             parent is ForLoop -> { |  | ||||||
|                 val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position) |  | ||||||
|                 idref.linkParents(parent) |  | ||||||
|                 parent.iterable = idref |  | ||||||
|             } |  | ||||||
|             else -> TODO("replace literalvalue by identifierref: $variable  (in $parent)") |  | ||||||
|         } |  | ||||||
|         variable.second.linkParents(scope as Node) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     printErrors(checker.result(), name) |  | ||||||
| } |  | ||||||
| @@ -1,789 +0,0 @@ | |||||||
| package prog8.ast.expressions |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.antlr.escape |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.processing.IAstModifyingVisitor |  | ||||||
| import prog8.ast.processing.IAstVisitor |  | ||||||
| import prog8.ast.statements.* |  | ||||||
| import prog8.compiler.HeapValues |  | ||||||
| import prog8.compiler.IntegerOrAddressOf |  | ||||||
| import prog8.compiler.target.c64.Petscii |  | ||||||
| import prog8.functions.BuiltinFunctions |  | ||||||
| import prog8.functions.NotConstArgumentException |  | ||||||
| import prog8.functions.builtinFunctionReturnType |  | ||||||
| import kotlin.math.abs |  | ||||||
|  |  | ||||||
|  |  | ||||||
| val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=") |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         expression.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun constValue(program: Program): LiteralValue? = null |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name) |  | ||||||
|     override fun inferType(program: Program): DataType? = expression.inferType(program) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Prefix($operator $expression)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class BinaryExpression(var left: IExpression, var operator: String, var right: IExpression, override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         left.linkParents(this) |  | ||||||
|         right.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "[$left $operator $right]" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // binary expression should actually have been optimized away into a single value, before const value was requested... |  | ||||||
|     override fun constValue(program: Program): LiteralValue? = null |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name) |  | ||||||
|     override fun inferType(program: Program): DataType? { |  | ||||||
|         val leftDt = left.inferType(program) |  | ||||||
|         val rightDt = right.inferType(program) |  | ||||||
|         return when (operator) { |  | ||||||
|             "+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else { |  | ||||||
|                 try { |  | ||||||
|                     arithmeticOpDt(leftDt, rightDt) |  | ||||||
|                 } catch (x: FatalAstException) { |  | ||||||
|                     null |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             "/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt) |  | ||||||
|             "&" -> leftDt |  | ||||||
|             "|" -> leftDt |  | ||||||
|             "^" -> leftDt |  | ||||||
|             "and", "or", "xor", |  | ||||||
|             "<", ">", |  | ||||||
|             "<=", ">=", |  | ||||||
|             "==", "!=" -> DataType.UBYTE |  | ||||||
|             "<<", ">>" -> leftDt |  | ||||||
|             else -> throw FatalAstException("resulting datatype check for invalid operator $operator") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType { |  | ||||||
|             return when (leftDt) { |  | ||||||
|                 DataType.UBYTE -> when (rightDt) { |  | ||||||
|                     DataType.UBYTE, DataType.UWORD -> DataType.UBYTE |  | ||||||
|                     DataType.BYTE, DataType.WORD -> DataType.WORD |  | ||||||
|                     DataType.FLOAT -> DataType.BYTE |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.BYTE -> when (rightDt) { |  | ||||||
|                     in NumericDatatypes -> DataType.BYTE |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.UWORD -> when (rightDt) { |  | ||||||
|                     DataType.UBYTE, DataType.UWORD -> DataType.UWORD |  | ||||||
|                     DataType.BYTE, DataType.WORD -> DataType.WORD |  | ||||||
|                     DataType.FLOAT -> DataType.FLOAT |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.WORD -> when (rightDt) { |  | ||||||
|                     in NumericDatatypes -> DataType.WORD |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.FLOAT -> when (rightDt) { |  | ||||||
|                     in NumericDatatypes -> DataType.FLOAT |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType { |  | ||||||
|             return when (leftDt) { |  | ||||||
|                 DataType.UBYTE -> when (rightDt) { |  | ||||||
|                     DataType.UBYTE -> DataType.UBYTE |  | ||||||
|                     DataType.BYTE -> DataType.BYTE |  | ||||||
|                     DataType.UWORD -> DataType.UWORD |  | ||||||
|                     DataType.WORD -> DataType.WORD |  | ||||||
|                     DataType.FLOAT -> DataType.FLOAT |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.BYTE -> when (rightDt) { |  | ||||||
|                     in ByteDatatypes -> DataType.BYTE |  | ||||||
|                     in WordDatatypes -> DataType.WORD |  | ||||||
|                     DataType.FLOAT -> DataType.FLOAT |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.UWORD -> when (rightDt) { |  | ||||||
|                     DataType.UBYTE, DataType.UWORD -> DataType.UWORD |  | ||||||
|                     DataType.BYTE, DataType.WORD -> DataType.WORD |  | ||||||
|                     DataType.FLOAT -> DataType.FLOAT |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.WORD -> when (rightDt) { |  | ||||||
|                     in IntegerDatatypes -> DataType.WORD |  | ||||||
|                     DataType.FLOAT -> DataType.FLOAT |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 DataType.FLOAT -> when (rightDt) { |  | ||||||
|                     in NumericDatatypes -> DataType.FLOAT |  | ||||||
|                     else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|                 } |  | ||||||
|                 else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun commonDatatype(leftDt: DataType, rightDt: DataType, |  | ||||||
|                        left: IExpression, right: IExpression): Pair<DataType, IExpression?> { |  | ||||||
|         // byte + byte -> byte |  | ||||||
|         // byte + word -> word |  | ||||||
|         // word + byte -> word |  | ||||||
|         // word + word -> word |  | ||||||
|         // a combination with a float will be float (but give a warning about this!) |  | ||||||
|  |  | ||||||
|         if(this.operator=="/") { |  | ||||||
|             // division is a bit weird, don't cast the operands |  | ||||||
|             val commondt = divisionOpDt(leftDt, rightDt) |  | ||||||
|             return Pair(commondt, null) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return when (leftDt) { |  | ||||||
|             DataType.UBYTE -> { |  | ||||||
|                 when (rightDt) { |  | ||||||
|                     DataType.UBYTE -> Pair(DataType.UBYTE, null) |  | ||||||
|                     DataType.BYTE -> Pair(DataType.BYTE, left) |  | ||||||
|                     DataType.UWORD -> Pair(DataType.UWORD, left) |  | ||||||
|                     DataType.WORD -> Pair(DataType.WORD, left) |  | ||||||
|                     DataType.FLOAT -> Pair(DataType.FLOAT, left) |  | ||||||
|                     else -> throw FatalAstException("non-numeric datatype $rightDt") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.BYTE -> { |  | ||||||
|                 when (rightDt) { |  | ||||||
|                     DataType.UBYTE -> Pair(DataType.BYTE, right) |  | ||||||
|                     DataType.BYTE -> Pair(DataType.BYTE, null) |  | ||||||
|                     DataType.UWORD -> Pair(DataType.WORD, left) |  | ||||||
|                     DataType.WORD -> Pair(DataType.WORD, left) |  | ||||||
|                     DataType.FLOAT -> Pair(DataType.FLOAT, left) |  | ||||||
|                     else -> throw FatalAstException("non-numeric datatype $rightDt") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.UWORD -> { |  | ||||||
|                 when (rightDt) { |  | ||||||
|                     DataType.UBYTE -> Pair(DataType.UWORD, right) |  | ||||||
|                     DataType.BYTE -> Pair(DataType.UWORD, right) |  | ||||||
|                     DataType.UWORD -> Pair(DataType.UWORD, null) |  | ||||||
|                     DataType.WORD -> Pair(DataType.WORD, left) |  | ||||||
|                     DataType.FLOAT -> Pair(DataType.FLOAT, left) |  | ||||||
|                     else -> throw FatalAstException("non-numeric datatype $rightDt") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.WORD -> { |  | ||||||
|                 when (rightDt) { |  | ||||||
|                     DataType.UBYTE -> Pair(DataType.WORD, right) |  | ||||||
|                     DataType.BYTE -> Pair(DataType.WORD, right) |  | ||||||
|                     DataType.UWORD -> Pair(DataType.WORD, right) |  | ||||||
|                     DataType.WORD -> Pair(DataType.WORD, null) |  | ||||||
|                     DataType.FLOAT -> Pair(DataType.FLOAT, left) |  | ||||||
|                     else -> throw FatalAstException("non-numeric datatype $rightDt") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             DataType.FLOAT -> { |  | ||||||
|                 Pair(DataType.FLOAT, right) |  | ||||||
|             } |  | ||||||
|             else -> throw FatalAstException("non-numeric datatype $leftDt") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class ArrayIndexedExpression(val identifier: IdentifierReference, |  | ||||||
|                              var arrayspec: ArrayIndex, |  | ||||||
|                              override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         identifier.linkParents(this) |  | ||||||
|         arrayspec.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun constValue(program: Program): LiteralValue? = null |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name) |  | ||||||
|  |  | ||||||
|     override fun inferType(program: Program): DataType? { |  | ||||||
|         val target = identifier.targetStatement(program.namespace) |  | ||||||
|         if (target is VarDecl) { |  | ||||||
|             return when (target.datatype) { |  | ||||||
|                 in NumericDatatypes -> null |  | ||||||
|                 in StringDatatypes -> DataType.UBYTE |  | ||||||
|                 DataType.ARRAY_UB -> DataType.UBYTE |  | ||||||
|                 DataType.ARRAY_B -> DataType.BYTE |  | ||||||
|                 DataType.ARRAY_UW -> DataType.UWORD |  | ||||||
|                 DataType.ARRAY_W -> DataType.WORD |  | ||||||
|                 DataType.ARRAY_F -> DataType.FLOAT |  | ||||||
|                 else -> throw FatalAstException("invalid dt") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return null |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class TypecastExpression(var expression: IExpression, var type: DataType, val implicit: Boolean, override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         expression.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name) |  | ||||||
|     override fun inferType(program: Program): DataType? = type |  | ||||||
|     override fun constValue(program: Program): LiteralValue? { |  | ||||||
|         val cv = expression.constValue(program) ?: return null |  | ||||||
|         return cv.cast(type) |  | ||||||
|         // val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type) |  | ||||||
|         // return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Typecast($expression as $type)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         identifier.parent=this |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var scopedname: String? = null     // will be set in a later state by the compiler |  | ||||||
|     override fun constValue(program: Program): LiteralValue? = null |  | ||||||
|     override fun referencesIdentifiers(vararg name: String) = false |  | ||||||
|     override fun inferType(program: Program) = DataType.UWORD |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         this.addressExpression.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String) = false |  | ||||||
|     override fun inferType(program: Program): DataType? = DataType.UBYTE |  | ||||||
|     override fun constValue(program: Program): LiteralValue? = null |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "DirectMemoryRead($addressExpression)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| open class LiteralValue(val type: DataType, |  | ||||||
|                         val bytevalue: Short? = null, |  | ||||||
|                         val wordvalue: Int? = null, |  | ||||||
|                         val floatvalue: Double? = null, |  | ||||||
|                         val strvalue: String? = null, |  | ||||||
|                         val arrayvalue: Array<IExpression>? = null, |  | ||||||
|                         initHeapId: Int? =null, |  | ||||||
|                         override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun referencesIdentifiers(vararg name: String) = arrayvalue?.any { it.referencesIdentifiers(*name) } ?: false |  | ||||||
|  |  | ||||||
|     val isString = type in StringDatatypes |  | ||||||
|     val isNumeric = type in NumericDatatypes |  | ||||||
|     val isArray = type in ArrayDatatypes |  | ||||||
|     var heapId = initHeapId |  | ||||||
|         private set |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         fun fromBoolean(bool: Boolean, position: Position) = |  | ||||||
|                 LiteralValue(DataType.UBYTE, bytevalue = if (bool) 1 else 0, position = position) |  | ||||||
|  |  | ||||||
|         fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue { |  | ||||||
|             return when(type) { |  | ||||||
|                 in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position) |  | ||||||
|                 in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position) |  | ||||||
|                 DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position) |  | ||||||
|                 else -> throw FatalAstException("non numeric datatype") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun optimalNumeric(value: Number, position: Position): LiteralValue { |  | ||||||
|             return if(value is Double) { |  | ||||||
|                 LiteralValue(DataType.FLOAT, floatvalue = value, position = position) |  | ||||||
|             } else { |  | ||||||
|                 when (val intval = value.toInt()) { |  | ||||||
|                     in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = intval.toShort(), position = position) |  | ||||||
|                     in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = intval.toShort(), position = position) |  | ||||||
|                     in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position) |  | ||||||
|                     in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position) |  | ||||||
|                     else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun optimalInteger(value: Number, position: Position): LiteralValue { |  | ||||||
|             val intval = value.toInt() |  | ||||||
|             if(intval.toDouble() != value.toDouble()) |  | ||||||
|                 throw FatalAstException("value is not an integer: $value") |  | ||||||
|             return when (intval) { |  | ||||||
|                 in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position) |  | ||||||
|                 in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = value.toShort(), position = position) |  | ||||||
|                 in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = value.toInt(), position = position) |  | ||||||
|                 else -> throw FatalAstException("integer overflow: $value") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         when(type){ |  | ||||||
|             in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue") |  | ||||||
|             in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue") |  | ||||||
|             DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue") |  | ||||||
|             in StringDatatypes -> |  | ||||||
|                 if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId") |  | ||||||
|             in ArrayDatatypes -> |  | ||||||
|                 if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId") |  | ||||||
|             else -> throw FatalAstException("invalid type $type") |  | ||||||
|         } |  | ||||||
|         if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null) |  | ||||||
|             throw FatalAstException("literal value without actual value") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val asNumericValue: Number? = when { |  | ||||||
|         bytevalue!=null -> bytevalue |  | ||||||
|         wordvalue!=null -> wordvalue |  | ||||||
|         floatvalue!=null -> floatvalue |  | ||||||
|         else -> null |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val asIntegerValue: Int? = when { |  | ||||||
|         bytevalue!=null -> bytevalue.toInt() |  | ||||||
|         wordvalue!=null -> wordvalue |  | ||||||
|         // don't round a float value, otherwise code will not detect that it's not an integer |  | ||||||
|         else -> null |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val asBooleanValue: Boolean = |  | ||||||
|             (floatvalue!=null && floatvalue != 0.0) || |  | ||||||
|             (bytevalue!=null && bytevalue != 0.toShort()) || |  | ||||||
|             (wordvalue!=null && wordvalue != 0) || |  | ||||||
|             (strvalue!=null && strvalue.isNotEmpty()) || |  | ||||||
|             (arrayvalue != null && arrayvalue.isNotEmpty()) |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         arrayvalue?.forEach {it.linkParents(this)} |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun constValue(program: Program): LiteralValue? { |  | ||||||
|         if(arrayvalue!=null) { |  | ||||||
|             for(v in arrayvalue) { |  | ||||||
|                 if(v.constValue(program)==null) return null |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return this |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         val vstr = when(type) { |  | ||||||
|             DataType.UBYTE -> "ubyte:$bytevalue" |  | ||||||
|             DataType.BYTE -> "byte:$bytevalue" |  | ||||||
|             DataType.UWORD -> "uword:$wordvalue" |  | ||||||
|             DataType.WORD -> "word:$wordvalue" |  | ||||||
|             DataType.FLOAT -> "float:$floatvalue" |  | ||||||
|             in StringDatatypes -> "str:'${escape(strvalue?:"")}'" |  | ||||||
|             in ArrayDatatypes -> "array:$arrayvalue" |  | ||||||
|             else -> throw FatalAstException("weird datatype") |  | ||||||
|         } |  | ||||||
|         return "LiteralValue($vstr)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun inferType(program: Program) = type |  | ||||||
|  |  | ||||||
|     override fun hashCode(): Int { |  | ||||||
|         val bh = bytevalue?.hashCode() ?: 0x10001234 |  | ||||||
|         val wh = wordvalue?.hashCode() ?: 0x01002345 |  | ||||||
|         val fh = floatvalue?.hashCode() ?: 0x00103456 |  | ||||||
|         val sh = strvalue?.hashCode() ?: 0x00014567 |  | ||||||
|         val ah = arrayvalue?.hashCode() ?: 0x11119876 |  | ||||||
|         var hash = bh * 31 xor wh |  | ||||||
|         hash = hash*31 xor fh |  | ||||||
|         hash = hash*31 xor sh |  | ||||||
|         hash = hash*31 xor ah |  | ||||||
|         hash = hash*31 xor type.hashCode() |  | ||||||
|         return hash |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun equals(other: Any?): Boolean { |  | ||||||
|         if(other==null || other !is LiteralValue) |  | ||||||
|             return false |  | ||||||
|         if(isNumeric && other.isNumeric) |  | ||||||
|             return asNumericValue?.toDouble()==other.asNumericValue?.toDouble() |  | ||||||
|         if(isArray && other.isArray) |  | ||||||
|             return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId |  | ||||||
|         if(isString && other.isString) |  | ||||||
|             return strvalue==other.strvalue && heapId==other.heapId |  | ||||||
|  |  | ||||||
|         if(type!=other.type) |  | ||||||
|             return false |  | ||||||
|  |  | ||||||
|         return compareTo(other) == 0 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     operator fun compareTo(other: LiteralValue): Int { |  | ||||||
|         val numLeft = asNumericValue?.toDouble() |  | ||||||
|         val numRight = other.asNumericValue?.toDouble() |  | ||||||
|         if(numLeft!=null && numRight!=null) |  | ||||||
|             return numLeft.compareTo(numRight) |  | ||||||
|  |  | ||||||
|         if(strvalue!=null && other.strvalue!=null) |  | ||||||
|             return strvalue.compareTo(other.strvalue) |  | ||||||
|  |  | ||||||
|         throw ExpressionError("cannot order compare type $type with ${other.type}", other.position) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun cast(targettype: DataType): LiteralValue? { |  | ||||||
|         if(type==targettype) |  | ||||||
|             return this |  | ||||||
|         when(type) { |  | ||||||
|             DataType.UBYTE -> { |  | ||||||
|                 if(targettype== DataType.BYTE && bytevalue!! <= 127) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = bytevalue, position = position) |  | ||||||
|                 if(targettype== DataType.WORD || targettype== DataType.UWORD) |  | ||||||
|                     return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position) |  | ||||||
|                 if(targettype== DataType.FLOAT) |  | ||||||
|                     return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position) |  | ||||||
|             } |  | ||||||
|             DataType.BYTE -> { |  | ||||||
|                 if(targettype== DataType.UBYTE && bytevalue!! >= 0) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = bytevalue, position = position) |  | ||||||
|                 if(targettype== DataType.UWORD && bytevalue!! >= 0) |  | ||||||
|                     return LiteralValue(targettype, wordvalue = bytevalue.toInt(), position = position) |  | ||||||
|                 if(targettype== DataType.WORD) |  | ||||||
|                     return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position) |  | ||||||
|                 if(targettype== DataType.FLOAT) |  | ||||||
|                     return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position) |  | ||||||
|             } |  | ||||||
|             DataType.UWORD -> { |  | ||||||
|                 if(targettype== DataType.BYTE && wordvalue!! <= 127) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) |  | ||||||
|                 if(targettype== DataType.UBYTE && wordvalue!! <= 255) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) |  | ||||||
|                 if(targettype== DataType.WORD && wordvalue!! <= 32767) |  | ||||||
|                     return LiteralValue(targettype, wordvalue = wordvalue, position = position) |  | ||||||
|                 if(targettype== DataType.FLOAT) |  | ||||||
|                     return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position) |  | ||||||
|             } |  | ||||||
|             DataType.WORD -> { |  | ||||||
|                 if(targettype== DataType.BYTE && wordvalue!! in -128..127) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) |  | ||||||
|                 if(targettype== DataType.UBYTE && wordvalue!! in 0..255) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position) |  | ||||||
|                 if(targettype== DataType.UWORD && wordvalue!! >=0) |  | ||||||
|                     return LiteralValue(targettype, wordvalue = wordvalue, position = position) |  | ||||||
|                 if(targettype== DataType.FLOAT) |  | ||||||
|                     return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position) |  | ||||||
|             } |  | ||||||
|             DataType.FLOAT -> { |  | ||||||
|                 val value = floatvalue!!.toInt() |  | ||||||
|                 if (targettype == DataType.BYTE && value in -128..127) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = value.toShort(), position = position) |  | ||||||
|                 if (targettype == DataType.UBYTE && value in 0..255) |  | ||||||
|                     return LiteralValue(targettype, bytevalue = value.toShort(), position = position) |  | ||||||
|                 if (targettype == DataType.WORD && value in -32768..32767) |  | ||||||
|                     return LiteralValue(targettype, wordvalue = value, position = position) |  | ||||||
|                 if (targettype == DataType.UWORD && value in 0..65535) |  | ||||||
|                     return LiteralValue(targettype, wordvalue = value, position = position) |  | ||||||
|             } |  | ||||||
|             in StringDatatypes -> { |  | ||||||
|                 if(targettype in StringDatatypes) |  | ||||||
|                     return this |  | ||||||
|             } |  | ||||||
|             else -> {} |  | ||||||
|         } |  | ||||||
|         return null    // invalid type conversion from $this to $targettype |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun addToHeap(heap: HeapValues) { |  | ||||||
|         if(heapId==null) { |  | ||||||
|             if (strvalue != null) { |  | ||||||
|                 heapId = heap.addString(type, strvalue) |  | ||||||
|             } |  | ||||||
|             else if (arrayvalue!=null) { |  | ||||||
|                 if(arrayvalue.any {it is AddressOf }) { |  | ||||||
|                     val intArrayWithAddressOfs = arrayvalue.map { |  | ||||||
|                         when (it) { |  | ||||||
|                             is AddressOf -> IntegerOrAddressOf(null, it) |  | ||||||
|                             is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null) |  | ||||||
|                             else -> throw FatalAstException("invalid datatype in array") |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray()) |  | ||||||
|                 } else { |  | ||||||
|                     val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! } |  | ||||||
|                     heapId = if(type== DataType.ARRAY_F) { |  | ||||||
|                         val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray() |  | ||||||
|                         heap.addDoublesArray(doubleArray) |  | ||||||
|                     } else { |  | ||||||
|                         val integerArray = valuesInArray.map { it.toInt() } |  | ||||||
|                         heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray()) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class RangeExpr(var from: IExpression, |  | ||||||
|                 var to: IExpression, |  | ||||||
|                 var step: IExpression, |  | ||||||
|                 override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         from.linkParents(this) |  | ||||||
|         to.linkParents(this) |  | ||||||
|         step.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun constValue(program: Program): LiteralValue? = null |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String): Boolean  = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name) |  | ||||||
|     override fun inferType(program: Program): DataType? { |  | ||||||
|         val fromDt=from.inferType(program) |  | ||||||
|         val toDt=to.inferType(program) |  | ||||||
|         return when { |  | ||||||
|             fromDt==null || toDt==null -> null |  | ||||||
|             fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.UBYTE |  | ||||||
|             fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.UWORD |  | ||||||
|             fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR |  | ||||||
|             fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S |  | ||||||
|             fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.WORD |  | ||||||
|             fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.BYTE |  | ||||||
|             else -> DataType.UBYTE |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "RangeExpr(from $from, to $to, step $step, pos=$position)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun size(): Int? { |  | ||||||
|         val fromLv = (from as? LiteralValue) |  | ||||||
|         val toLv = (to as? LiteralValue) |  | ||||||
|         if(fromLv==null || toLv==null) |  | ||||||
|             return null |  | ||||||
|         return toConstantIntegerRange()?.count() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun toConstantIntegerRange(): IntProgression? { |  | ||||||
|         val fromLv = from as? LiteralValue |  | ||||||
|         val toLv = to as? LiteralValue |  | ||||||
|         if(fromLv==null || toLv==null) |  | ||||||
|             return null         // non-constant range |  | ||||||
|         val fromVal: Int |  | ||||||
|         val toVal: Int |  | ||||||
|         if(fromLv.isString && toLv.isString) { |  | ||||||
|             // string range -> int range over petscii values |  | ||||||
|             fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt() |  | ||||||
|             toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt() |  | ||||||
|         } else { |  | ||||||
|             // integer range |  | ||||||
|             fromVal = (from as LiteralValue).asIntegerValue!! |  | ||||||
|             toVal = (to as LiteralValue).asIntegerValue!! |  | ||||||
|         } |  | ||||||
|         val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1 |  | ||||||
|         return when { |  | ||||||
|             fromVal <= toVal -> when { |  | ||||||
|                 stepVal <= 0 -> IntRange.EMPTY |  | ||||||
|                 stepVal == 1 -> fromVal..toVal |  | ||||||
|                 else -> fromVal..toVal step stepVal |  | ||||||
|             } |  | ||||||
|             else -> when { |  | ||||||
|                 stepVal >= 0 -> IntRange.EMPTY |  | ||||||
|                 stepVal == -1 -> fromVal downTo toVal |  | ||||||
|                 else -> fromVal downTo toVal step abs(stepVal) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class RegisterExpr(val register: Register, override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun constValue(program: Program): LiteralValue? = null |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "RegisterExpr(register=$register, pos=$position)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun inferType(program: Program) = DataType.UBYTE |  | ||||||
| } |  | ||||||
|  |  | ||||||
| data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : IExpression { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     fun targetStatement(namespace: INameScope) = |  | ||||||
|         if(nameInSource.size==1 && nameInSource[0] in BuiltinFunctions) |  | ||||||
|             BuiltinFunctionStatementPlaceholder(nameInSource[0], position) |  | ||||||
|         else |  | ||||||
|             namespace.lookup(nameInSource, this) |  | ||||||
|  |  | ||||||
|     fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl |  | ||||||
|     fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun constValue(program: Program): LiteralValue? { |  | ||||||
|         val node = program.namespace.lookup(nameInSource, this) |  | ||||||
|                 ?: throw UndefinedSymbolError(this) |  | ||||||
|         val vardecl = node as? VarDecl |  | ||||||
|         if(vardecl==null) { |  | ||||||
|             return null |  | ||||||
|         } else if(vardecl.type!= VarDeclType.CONST) { |  | ||||||
|             return null |  | ||||||
|         } |  | ||||||
|         return vardecl.value?.constValue(program) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "IdentifierRef($nameInSource)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name   // @todo is this correct all the time? |  | ||||||
|  |  | ||||||
|     override fun inferType(program: Program): DataType? { |  | ||||||
|         val targetStmt = targetStatement(program.namespace) |  | ||||||
|         if(targetStmt is VarDecl) { |  | ||||||
|             return targetStmt.datatype |  | ||||||
|         } else { |  | ||||||
|             throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun heapId(namespace: INameScope): Int { |  | ||||||
|         val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) |  | ||||||
|         return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class FunctionCall(override var target: IdentifierReference, |  | ||||||
|                    override var arglist: MutableList<IExpression>, |  | ||||||
|                    override val position: Position) : IExpression, IFunctionCall { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         target.linkParents(this) |  | ||||||
|         arglist.forEach { it.linkParents(this) } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun constValue(program: Program) = constValue(program, true) |  | ||||||
|  |  | ||||||
|     private fun constValue(program: Program, withDatatypeCheck: Boolean): LiteralValue? { |  | ||||||
|         // if the function is a built-in function and the args are consts, should try to const-evaluate! |  | ||||||
|         // lenghts of arrays and strings are constants that are determined at compile time! |  | ||||||
|         if(target.nameInSource.size>1) return null |  | ||||||
|         try { |  | ||||||
|             var resultValue: LiteralValue? = null |  | ||||||
|             val func = BuiltinFunctions[target.nameInSource[0]] |  | ||||||
|             if(func!=null) { |  | ||||||
|                 val exprfunc = func.constExpressionFunc |  | ||||||
|                 if(exprfunc!=null) |  | ||||||
|                     resultValue = exprfunc(arglist, position, program) |  | ||||||
|                 else if(func.returntype==null) |  | ||||||
|                     throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(withDatatypeCheck) { |  | ||||||
|                 val resultDt = this.inferType(program) |  | ||||||
|                 if(resultValue==null || resultDt == resultValue.type) |  | ||||||
|                     return resultValue |  | ||||||
|                 throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position") |  | ||||||
|             } else { |  | ||||||
|                 return resultValue |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         catch(x: NotConstArgumentException) { |  | ||||||
|             // const-evaluating the builtin function call failed. |  | ||||||
|             return null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "FunctionCall(target=$target, pos=$position)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)} |  | ||||||
|  |  | ||||||
|     override fun inferType(program: Program): DataType? { |  | ||||||
|         val constVal = constValue(program ,false) |  | ||||||
|         if(constVal!=null) |  | ||||||
|             return constVal.type |  | ||||||
|         val stmt = target.targetStatement(program.namespace) ?: return null |  | ||||||
|         when (stmt) { |  | ||||||
|             is BuiltinFunctionStatementPlaceholder -> { |  | ||||||
|                 if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" || |  | ||||||
|                         target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { |  | ||||||
|                     return null // these have no return value |  | ||||||
|                 } |  | ||||||
|                 return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program) |  | ||||||
|             } |  | ||||||
|             is Subroutine -> { |  | ||||||
|                 if(stmt.returntypes.isEmpty()) |  | ||||||
|                     return null     // no return value |  | ||||||
|                 if(stmt.returntypes.size==1) |  | ||||||
|                     return stmt.returntypes[0] |  | ||||||
|                 return null     // has multiple return types... so not a single resulting datatype possible |  | ||||||
|             } |  | ||||||
|             is Label -> return null |  | ||||||
|         } |  | ||||||
|         return null     // calling something we don't recognise... |  | ||||||
|     } |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,215 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.base.autoHeapValuePrefix |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.* |  | ||||||
| import prog8.functions.BuiltinFunctions |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstModifyingVisitor { |  | ||||||
|     private val checkResult: MutableList<AstException> = mutableListOf() |  | ||||||
|  |  | ||||||
|     private var blocks: MutableMap<String, Block> = mutableMapOf() |  | ||||||
|  |  | ||||||
|     internal fun result(): List<AstException> { |  | ||||||
|         return checkResult |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun nameError(name: String, position: Position, existing: IStatement) { |  | ||||||
|         checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(module: Module) { |  | ||||||
|         blocks.clear()  // blocks may be redefined within a different module |  | ||||||
|         super.visit(module) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(block: Block): IStatement { |  | ||||||
|         val existing = blocks[block.name] |  | ||||||
|         if(existing!=null) |  | ||||||
|             nameError(block.name, block.position, existing) |  | ||||||
|         else |  | ||||||
|             blocks[block.name] = block |  | ||||||
|  |  | ||||||
|         return super.visit(block) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCall: FunctionCall): IExpression { |  | ||||||
|         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.arglist.single(), DataType.UBYTE, false, functionCall.position) |  | ||||||
|             typecast.linkParents(functionCall.parent) |  | ||||||
|             return super.visit(typecast) |  | ||||||
|         } |  | ||||||
|         return super.visit(functionCall) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(decl: VarDecl): IStatement { |  | ||||||
|         // first, check if there are datatype errors on the vardecl |  | ||||||
|         decl.datatypeErrors.forEach { checkResult.add(it) } |  | ||||||
|  |  | ||||||
|         // now check the identifier |  | ||||||
|         if(decl.name in BuiltinFunctions) |  | ||||||
|             // the builtin functions can't be redefined |  | ||||||
|             checkResult.add(NameError("builtin function cannot be redefined", decl.position)) |  | ||||||
|  |  | ||||||
|         val existing = namespace.lookup(listOf(decl.name), decl) |  | ||||||
|         if (existing != null && existing !== decl) |  | ||||||
|             nameError(decl.name, decl.position, existing) |  | ||||||
|  |  | ||||||
|         return super.visit(decl) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(subroutine: Subroutine): IStatement { |  | ||||||
|         if(subroutine.name in BuiltinFunctions) { |  | ||||||
|             // the builtin functions can't be redefined |  | ||||||
|             checkResult.add(NameError("builtin function cannot be redefined", subroutine.position)) |  | ||||||
|         } else { |  | ||||||
|             if (subroutine.parameters.any { it.name in BuiltinFunctions }) |  | ||||||
|                 checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position)) |  | ||||||
|  |  | ||||||
|             val existing = namespace.lookup(listOf(subroutine.name), subroutine) |  | ||||||
|             if (existing != null && existing !== subroutine) |  | ||||||
|                 nameError(subroutine.name, subroutine.position, existing) |  | ||||||
|  |  | ||||||
|             // check that there are no local variables, labels, or other subs that redefine the subroutine's parameters |  | ||||||
|             val symbolsInSub = subroutine.allDefinedSymbols() |  | ||||||
|             val namesInSub = symbolsInSub.map{ it.first }.toSet() |  | ||||||
|             val paramNames = subroutine.parameters.map { it.name }.toSet() |  | ||||||
|             val paramsToCheck = paramNames.intersect(namesInSub) |  | ||||||
|             for(name in paramsToCheck) { |  | ||||||
|                 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} |  | ||||||
|                 if(sub!=null) |  | ||||||
|                     nameError(name, sub.position, subroutine) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters) |  | ||||||
|             // NOTE: |  | ||||||
|             // - numeric types BYTE and WORD and FLOAT are passed by value; |  | ||||||
|             // - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter) |  | ||||||
|             // - do NOT do this is the statement can be transformed into an asm subroutine later! |  | ||||||
|             if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) { |  | ||||||
|                 if(subroutine.asmParameterRegisters.isEmpty()) { |  | ||||||
|                     subroutine.parameters |  | ||||||
|                             .filter { it.name !in namesInSub } |  | ||||||
|                             .forEach { |  | ||||||
|                                 val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, |  | ||||||
|                                         isArray = false, autoGenerated = true, position = subroutine.position) |  | ||||||
|                                 vardecl.linkParents(subroutine) |  | ||||||
|                                 subroutine.statements.add(0, vardecl) |  | ||||||
|                             } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return super.visit(subroutine) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(label: Label): IStatement { |  | ||||||
|         if(label.name in BuiltinFunctions) { |  | ||||||
|             // the builtin functions can't be redefined |  | ||||||
|             checkResult.add(NameError("builtin function cannot be redefined", label.position)) |  | ||||||
|         } else { |  | ||||||
|             val existing = namespace.lookup(listOf(label.name), label) |  | ||||||
|             if (existing != null && existing !== label) |  | ||||||
|                 nameError(label.name, label.position, existing) |  | ||||||
|         } |  | ||||||
|         return super.visit(label) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(forLoop: ForLoop): IStatement { |  | ||||||
|         // If the for loop has a decltype, it means to declare the loopvar inside the loop body |  | ||||||
|         // rather than reusing an already declared loopvar from an outer scope. |  | ||||||
|         // For loops that loop over an interable variable (instead of a range of numbers) get an |  | ||||||
|         // additional interation count variable in their scope. |  | ||||||
|         if(forLoop.loopRegister!=null) { |  | ||||||
|             if(forLoop.decltype!=null) |  | ||||||
|                 checkResult.add(SyntaxError("register loop variables have a fixed implicit datatype", forLoop.position)) |  | ||||||
|             if(forLoop.loopRegister == Register.X) |  | ||||||
|                 printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position) |  | ||||||
|         } else if(forLoop.loopVar!=null) { |  | ||||||
|             val varName = forLoop.loopVar.nameInSource.last() |  | ||||||
|             if(forLoop.decltype!=null) { |  | ||||||
|                 val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first()) |  | ||||||
|                 if(existing==null) { |  | ||||||
|                     // create the local scoped for loop variable itself |  | ||||||
|                     val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, |  | ||||||
|                             isArray = false, autoGenerated = true, position = forLoop.loopVar.position) |  | ||||||
|                     vardecl.linkParents(forLoop.body) |  | ||||||
|                     forLoop.body.statements.add(0, vardecl) |  | ||||||
|                     forLoop.loopVar.parent = forLoop.body   // loopvar 'is defined in the body' |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(forLoop.iterable !is RangeExpr) { |  | ||||||
|                 val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first()) |  | ||||||
|                 if(existing==null) { |  | ||||||
|                     // create loop iteration counter variable (without value, to avoid an assignment) |  | ||||||
|                     val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null, |  | ||||||
|                             isArray = false, autoGenerated = true, position = forLoop.loopVar.position) |  | ||||||
|                     vardecl.linkParents(forLoop.body) |  | ||||||
|                     forLoop.body.statements.add(0, vardecl) |  | ||||||
|                     forLoop.loopVar.parent = forLoop.body   // loopvar 'is defined in the body' |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return super.visit(forLoop) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(assignTarget: AssignTarget): AssignTarget { |  | ||||||
|         if(assignTarget.register== Register.X) |  | ||||||
|             printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position) |  | ||||||
|         return super.visit(assignTarget) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(returnStmt: Return): IStatement { |  | ||||||
|         if(returnStmt.value!=null) { |  | ||||||
|             // possibly adjust any literal values returned, into the desired returning data type |  | ||||||
|             val subroutine = returnStmt.definingSubroutine()!! |  | ||||||
|             if(subroutine.returntypes.size!=1) |  | ||||||
|                 return returnStmt  // mismatch in number of return values, error will be printed later. |  | ||||||
|             val newValue: IExpression |  | ||||||
|             val lval = returnStmt.value as? LiteralValue |  | ||||||
|             if(lval!=null) { |  | ||||||
|                 val adjusted = lval.cast(subroutine.returntypes.single()) |  | ||||||
|                 if(adjusted!=null && adjusted !== lval) |  | ||||||
|                     newValue = adjusted |  | ||||||
|                 else |  | ||||||
|                     newValue = lval |  | ||||||
|             } else |  | ||||||
|                 newValue = returnStmt.value!! |  | ||||||
|  |  | ||||||
|             returnStmt.value = newValue |  | ||||||
|         } |  | ||||||
|         return super.visit(returnStmt) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>() |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     override fun visit(literalValue: LiteralValue): LiteralValue { |  | ||||||
|         if(literalValue.heapId!=null && literalValue.parent !is VarDecl) { |  | ||||||
|             // a literal value that's not declared as a variable, which refers to something on the heap. |  | ||||||
|             // we need to introduce an auto-generated variable for this to be able to refer to the value! |  | ||||||
|             // (note: ususally, this has been taken care of already when the var was created) |  | ||||||
|             val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue, |  | ||||||
|                     isArray = false, autoGenerated = false, position = literalValue.position) |  | ||||||
|             anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable) |  | ||||||
|         } |  | ||||||
|         return super.visit(literalValue) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(addressOf: AddressOf): IExpression { |  | ||||||
|         // register the scoped name of the referenced identifier |  | ||||||
|         val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf |  | ||||||
|         addressOf.scopedname = variable.scopedname |  | ||||||
|         return super.visit(addressOf) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,117 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.AstException |  | ||||||
| import prog8.ast.expressions.FunctionCall |  | ||||||
| import prog8.ast.statements.FunctionCallStatement |  | ||||||
| import prog8.ast.statements.Subroutine |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor { |  | ||||||
|     private val callGraph = DirectedGraph<INameScope>() |  | ||||||
|  |  | ||||||
|     internal fun result(): List<AstException> { |  | ||||||
|         val cycle = callGraph.checkForCycle() |  | ||||||
|         if(cycle.isEmpty()) |  | ||||||
|             return emptyList() |  | ||||||
|         val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" } |  | ||||||
|         return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain")) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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,219 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.* |  | ||||||
|  |  | ||||||
| interface IAstModifyingVisitor { |  | ||||||
|     fun visit(program: Program) { |  | ||||||
|         program.modules.forEach { visit(it) } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(module: Module) { |  | ||||||
|         module.statements = module.statements.asSequence().map { it.accept(this) }.toMutableList() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(expr: PrefixExpression): IExpression { |  | ||||||
|         expr.expression = expr.expression.accept(this) |  | ||||||
|         return expr |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(expr: BinaryExpression): IExpression { |  | ||||||
|         expr.left = expr.left.accept(this) |  | ||||||
|         expr.right = expr.right.accept(this) |  | ||||||
|         return expr |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(directive: Directive): IStatement { |  | ||||||
|         return directive |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(block: Block): IStatement { |  | ||||||
|         block.statements = block.statements.asSequence().map { it.accept(this) }.toMutableList() |  | ||||||
|         return block |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(decl: VarDecl): IStatement { |  | ||||||
|         decl.value = decl.value?.accept(this) |  | ||||||
|         decl.arraysize?.accept(this) |  | ||||||
|         return decl |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(subroutine: Subroutine): IStatement { |  | ||||||
|         subroutine.statements = subroutine.statements.asSequence().map { it.accept(this) }.toMutableList() |  | ||||||
|         return subroutine |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(functionCall: FunctionCall): IExpression { |  | ||||||
|         val newtarget = functionCall.target.accept(this) |  | ||||||
|         if(newtarget is IdentifierReference) |  | ||||||
|             functionCall.target = newtarget |  | ||||||
|         functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList() |  | ||||||
|         return functionCall |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(functionCallStatement: FunctionCallStatement): IStatement { |  | ||||||
|         val newtarget = functionCallStatement.target.accept(this) |  | ||||||
|         if(newtarget is IdentifierReference) |  | ||||||
|             functionCallStatement.target = newtarget |  | ||||||
|         functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList() |  | ||||||
|         return functionCallStatement |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(identifier: IdentifierReference): IExpression { |  | ||||||
|         // note: this is an identifier that is used in an expression. |  | ||||||
|         // other identifiers are simply part of the other statements (such as jumps, subroutine defs etc) |  | ||||||
|         return identifier |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(jump: Jump): IStatement { |  | ||||||
|         if(jump.identifier!=null) { |  | ||||||
|             val ident = jump.identifier.accept(this) |  | ||||||
|             if(ident is IdentifierReference && ident!==jump.identifier) { |  | ||||||
|                 return Jump(null, ident, null, jump.position) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return jump |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(ifStatement: IfStatement): IStatement { |  | ||||||
|         ifStatement.condition = ifStatement.condition.accept(this) |  | ||||||
|         ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope |  | ||||||
|         ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope |  | ||||||
|         return ifStatement |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(branchStatement: BranchStatement): IStatement { |  | ||||||
|         branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope |  | ||||||
|         branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope |  | ||||||
|         return branchStatement |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(range: RangeExpr): IExpression { |  | ||||||
|         range.from = range.from.accept(this) |  | ||||||
|         range.to = range.to.accept(this) |  | ||||||
|         range.step = range.step.accept(this) |  | ||||||
|         return range |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(label: Label): IStatement { |  | ||||||
|         return label |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(literalValue: LiteralValue): LiteralValue { |  | ||||||
|         if(literalValue.arrayvalue!=null) { |  | ||||||
|             for(av in literalValue.arrayvalue.withIndex()) { |  | ||||||
|                 val newvalue = av.value.accept(this) |  | ||||||
|                 literalValue.arrayvalue[av.index] = newvalue |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return literalValue |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(assignment: Assignment): IStatement { |  | ||||||
|         assignment.target = assignment.target.accept(this) |  | ||||||
|         assignment.value = assignment.value.accept(this) |  | ||||||
|         return assignment |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(postIncrDecr: PostIncrDecr): IStatement { |  | ||||||
|         postIncrDecr.target = postIncrDecr.target.accept(this) |  | ||||||
|         return postIncrDecr |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(contStmt: Continue): IStatement { |  | ||||||
|         return contStmt |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(breakStmt: Break): IStatement { |  | ||||||
|         return breakStmt |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(forLoop: ForLoop): IStatement { |  | ||||||
|         forLoop.loopVar?.accept(this) |  | ||||||
|         forLoop.iterable = forLoop.iterable.accept(this) |  | ||||||
|         forLoop.body = forLoop.body.accept(this) as AnonymousScope |  | ||||||
|         return forLoop |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(whileLoop: WhileLoop): IStatement { |  | ||||||
|         whileLoop.condition = whileLoop.condition.accept(this) |  | ||||||
|         whileLoop.body = whileLoop.body.accept(this) as AnonymousScope |  | ||||||
|         return whileLoop |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(repeatLoop: RepeatLoop): IStatement { |  | ||||||
|         repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this) |  | ||||||
|         repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope |  | ||||||
|         return repeatLoop |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(returnStmt: Return): IStatement { |  | ||||||
|         returnStmt.value = returnStmt.value?.accept(this) |  | ||||||
|         return returnStmt |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(arrayIndexedExpression: ArrayIndexedExpression): IExpression { |  | ||||||
|         arrayIndexedExpression.identifier.accept(this) |  | ||||||
|         arrayIndexedExpression.arrayspec.accept(this) |  | ||||||
|         return arrayIndexedExpression |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(assignTarget: AssignTarget): AssignTarget { |  | ||||||
|         assignTarget.arrayindexed?.accept(this) |  | ||||||
|         assignTarget.identifier?.accept(this) |  | ||||||
|         assignTarget.memoryAddress?.let { visit(it) } |  | ||||||
|         return assignTarget |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(scope: AnonymousScope): IStatement { |  | ||||||
|         scope.statements = scope.statements.asSequence().map { it.accept(this) }.toMutableList() |  | ||||||
|         return scope |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(typecast: TypecastExpression): IExpression { |  | ||||||
|         typecast.expression = typecast.expression.accept(this) |  | ||||||
|         return typecast |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(memread: DirectMemoryRead): IExpression { |  | ||||||
|         memread.addressExpression = memread.addressExpression.accept(this) |  | ||||||
|         return memread |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(memwrite: DirectMemoryWrite) { |  | ||||||
|         memwrite.addressExpression = memwrite.addressExpression.accept(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(addressOf: AddressOf): IExpression { |  | ||||||
|         addressOf.identifier.accept(this) |  | ||||||
|         return addressOf |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(inlineAssembly: InlineAssembly): IStatement { |  | ||||||
|         return inlineAssembly |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(registerExpr: RegisterExpr): IExpression { |  | ||||||
|         return registerExpr |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): IStatement { |  | ||||||
|         return builtinFunctionStatementPlaceholder |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(nopStatement: NopStatement): IStatement { |  | ||||||
|         return nopStatement |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(whenStatement: WhenStatement): IStatement { |  | ||||||
|         whenStatement.condition.accept(this) |  | ||||||
|         whenStatement.choices.forEach { it.accept(this) } |  | ||||||
|         return whenStatement |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun visit(whenChoice: WhenChoice) { |  | ||||||
|         whenChoice.values?.forEach { it.accept(this) } |  | ||||||
|         whenChoice.statements.accept(this) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,35 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.SyntaxError |  | ||||||
| import prog8.ast.base.printWarning |  | ||||||
| import prog8.ast.statements.Directive |  | ||||||
|  |  | ||||||
| internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor { |  | ||||||
|     private val checkResult: MutableList<SyntaxError> = mutableListOf() |  | ||||||
|  |  | ||||||
|     internal fun result(): List<SyntaxError> { |  | ||||||
|         return checkResult |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Most global directives don't apply for imported modules, so remove them |  | ||||||
|      */ |  | ||||||
|     override fun visit(module: Module) { |  | ||||||
|         super.visit(module) |  | ||||||
|         val newStatements : MutableList<IStatement> = mutableListOf() |  | ||||||
|  |  | ||||||
|         val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address") |  | ||||||
|         for (sourceStmt in module.statements) { |  | ||||||
|             val stmt = sourceStmt.accept(this) |  | ||||||
|             if(stmt is Directive && stmt.parent is Module) { |  | ||||||
|                 if(stmt.directive in moduleLevelDirectives) { |  | ||||||
|                     printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive) |  | ||||||
|                     continue |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             newStatements.add(stmt) |  | ||||||
|         } |  | ||||||
|         module.statements = newStatements |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,322 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import kotlin.comparisons.nullsLast |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.DataType |  | ||||||
| import prog8.ast.base.FatalAstException |  | ||||||
| import prog8.ast.base.initvarsSubName |  | ||||||
| import prog8.ast.base.printWarning |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.statements.* |  | ||||||
| import prog8.functions.BuiltinFunctions |  | ||||||
|  |  | ||||||
| internal class StatementReorderer(private val program: Program): IAstModifyingVisitor { |  | ||||||
|     // Reorders the statements in a way the compiler needs. |  | ||||||
|     // - 'main' block must be the very first statement UNLESS it has an address set. |  | ||||||
|     // - blocks are ordered by address, where blocks without address are put at the end. |  | ||||||
|     // - in every scope: |  | ||||||
|     //      -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first. |  | ||||||
|     //      -- all vardecls then follow. |  | ||||||
|     //      -- the remaining statements then follow in their original order. |  | ||||||
|     // |  | ||||||
|     // - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives. |  | ||||||
|     // - all other subroutines will be moved to the end of their block. |  | ||||||
|     // - sorts the choices in when statement. |  | ||||||
|     // |  | ||||||
|     // Also, makes 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 directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") |  | ||||||
|  |  | ||||||
|     override fun visit(module: Module) { |  | ||||||
|         super.visit(module) |  | ||||||
|  |  | ||||||
|         val (blocks, other) = module.statements.partition { it is Block } |  | ||||||
|         module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList() |  | ||||||
|  |  | ||||||
|         // make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything |  | ||||||
|         val nonLibraryBlocks = module.statements.withIndex() |  | ||||||
|                 .filter { it.value is Block && !(it.value as Block).isInLibrary } |  | ||||||
|                 .map { it.index to it.value } |  | ||||||
|                 .reversed() |  | ||||||
|         for(nonLibBlock in nonLibraryBlocks) |  | ||||||
|             module.statements.removeAt(nonLibBlock.first) |  | ||||||
|         for(nonLibBlock in nonLibraryBlocks) |  | ||||||
|             module.statements.add(0, nonLibBlock.second) |  | ||||||
|         val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } |  | ||||||
|         if(mainBlock!=null && (mainBlock as Block).address==null) { |  | ||||||
|             module.remove(mainBlock) |  | ||||||
|             module.statements.add(0, mainBlock) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         val varDecls = module.statements.filterIsInstance<VarDecl>() |  | ||||||
|         module.statements.removeAll(varDecls) |  | ||||||
|         module.statements.addAll(0, varDecls) |  | ||||||
|  |  | ||||||
|         val directives = module.statements.filter {it is Directive && it.directive in directivesToMove} |  | ||||||
|         module.statements.removeAll(directives) |  | ||||||
|         module.statements.addAll(0, directives) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(block: Block): IStatement { |  | ||||||
|  |  | ||||||
|         val subroutines = block.statements.filterIsInstance<Subroutine>() |  | ||||||
|         var numSubroutinesAtEnd = 0 |  | ||||||
|         // move all subroutines to the end of the block |  | ||||||
|         for (subroutine in subroutines) { |  | ||||||
|             if(subroutine.name!="start" || block.name!="main") { |  | ||||||
|                 block.remove(subroutine) |  | ||||||
|                 block.statements.add(subroutine) |  | ||||||
|             } |  | ||||||
|             numSubroutinesAtEnd++ |  | ||||||
|         } |  | ||||||
|         // move the "start" subroutine to the top |  | ||||||
|         if(block.name=="main") { |  | ||||||
|             block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let { |  | ||||||
|                 block.remove(it) |  | ||||||
|                 block.statements.add(0, it) |  | ||||||
|                 numSubroutinesAtEnd-- |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // make sure there is a 'return' in front of the first subroutine |  | ||||||
|         // (if it isn't the first statement in the block itself, and isn't the program's entrypoint) |  | ||||||
|         if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) { |  | ||||||
|             val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine |  | ||||||
|             if(firstSub.name != "start" && block.name != "main") { |  | ||||||
|                 val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1] |  | ||||||
|                 if (stmtBeforeFirstSub !is Return |  | ||||||
|                         && stmtBeforeFirstSub !is Jump |  | ||||||
|                         && stmtBeforeFirstSub !is Subroutine |  | ||||||
|                         && stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) { |  | ||||||
|                     val ret = Return(null, stmtBeforeFirstSub.position) |  | ||||||
|                     ret.linkParents(block) |  | ||||||
|                     block.statements.add(block.statements.size - numSubroutinesAtEnd, ret) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         val varDecls = block.statements.filterIsInstance<VarDecl>() |  | ||||||
|         block.statements.removeAll(varDecls) |  | ||||||
|         block.statements.addAll(0, varDecls) |  | ||||||
|         val directives = block.statements.filter {it is Directive && it.directive in directivesToMove} |  | ||||||
|         block.statements.removeAll(directives) |  | ||||||
|         block.statements.addAll(0, directives) |  | ||||||
|         block.linkParents(block.parent) |  | ||||||
|  |  | ||||||
|         // create subroutine that initializes the block's variables (if any) |  | ||||||
|         val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment } |  | ||||||
|         if(varInits.isNotEmpty()) { |  | ||||||
|             val statements = varInits.map{it.value}.toMutableList() |  | ||||||
|             val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(), |  | ||||||
|                     emptySet(), null, false, statements, block.position) |  | ||||||
|             varInitSub.keepAlways = true |  | ||||||
|             varInitSub.linkParents(block) |  | ||||||
|             block.statements.add(varInitSub) |  | ||||||
|  |  | ||||||
|             // remove the varinits from the block's statements |  | ||||||
|             for(index in varInits.map{it.index}.reversed()) |  | ||||||
|                 block.statements.removeAt(index) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return super.visit(block) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(subroutine: Subroutine): IStatement { |  | ||||||
|         super.visit(subroutine) |  | ||||||
|  |  | ||||||
|         val varDecls = subroutine.statements.filterIsInstance<VarDecl>() |  | ||||||
|         subroutine.statements.removeAll(varDecls) |  | ||||||
|         subroutine.statements.addAll(0, varDecls) |  | ||||||
|         val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove} |  | ||||||
|         subroutine.statements.removeAll(directives) |  | ||||||
|         subroutine.statements.addAll(0, directives) |  | ||||||
|  |  | ||||||
|         if(subroutine.returntypes.isEmpty()) { |  | ||||||
|             // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine. |  | ||||||
|             // and if an assembly block doesn't contain a rts/rti |  | ||||||
|             if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) { |  | ||||||
|                 if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) { |  | ||||||
|                     val returnStmt = Return(null, subroutine.position) |  | ||||||
|                     returnStmt.linkParents(subroutine) |  | ||||||
|                     subroutine.statements.add(returnStmt) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return subroutine |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(expr: BinaryExpression): IExpression { |  | ||||||
|         val leftDt = expr.left.inferType(program) |  | ||||||
|         val rightDt = expr.right.inferType(program) |  | ||||||
|         if(leftDt!=null && rightDt!=null && leftDt!=rightDt) { |  | ||||||
|             // determine common datatype and add typecast as required to make left and right equal types |  | ||||||
|             val (commonDt, toFix) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right) |  | ||||||
|             if(toFix!=null) { |  | ||||||
|                 when { |  | ||||||
|                     toFix===expr.left -> { |  | ||||||
|                         expr.left = TypecastExpression(expr.left, commonDt, true, expr.left.position) |  | ||||||
|                         expr.left.linkParents(expr) |  | ||||||
|                     } |  | ||||||
|                     toFix===expr.right -> { |  | ||||||
|                         expr.right = TypecastExpression(expr.right, commonDt, true, expr.right.position) |  | ||||||
|                         expr.right.linkParents(expr) |  | ||||||
|                     } |  | ||||||
|                     else -> throw FatalAstException("confused binary expression side") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return super.visit(expr) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(assignment: Assignment): IStatement { |  | ||||||
|         // see if a typecast is needed to convert the value's type into the proper target type |  | ||||||
|         val valuetype = assignment.value.inferType(program) |  | ||||||
|         val targettype = assignment.target.inferType(program, assignment) |  | ||||||
|         if(targettype!=null && valuetype!=null && valuetype!=targettype) { |  | ||||||
|             if(valuetype isAssignableTo targettype) { |  | ||||||
|                 assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position) |  | ||||||
|                 assignment.value.linkParents(assignment) |  | ||||||
|             } |  | ||||||
|             // if they're not assignable, we'll get a proper error later from the AstChecker |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return super.visit(assignment) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCallStatement: FunctionCallStatement): IStatement { |  | ||||||
|         checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope()) |  | ||||||
|         return super.visit(functionCallStatement) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCall: FunctionCall): IExpression { |  | ||||||
|         checkFunctionCallArguments(functionCall, functionCall.definingScope()) |  | ||||||
|         return super.visit(functionCall) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) { |  | ||||||
|         // see if a typecast is needed to convert the arguments into the required parameter's type |  | ||||||
|         when(val sub = call.target.targetStatement(scope)) { |  | ||||||
|             is Subroutine -> { |  | ||||||
|                 for(arg in sub.parameters.zip(call.arglist.withIndex())) { |  | ||||||
|                     val argtype = arg.second.value.inferType(program) |  | ||||||
|                     if(argtype!=null) { |  | ||||||
|                         val requiredType = arg.first.type |  | ||||||
|                         if (requiredType != argtype) { |  | ||||||
|                             if (argtype isAssignableTo requiredType) { |  | ||||||
|                                 val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position) |  | ||||||
|                                 typecasted.linkParents(arg.second.value.parent) |  | ||||||
|                                 call.arglist[arg.second.index] = typecasted |  | ||||||
|                             } |  | ||||||
|                             // if they're not assignable, we'll get a proper error later from the AstChecker |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             is BuiltinFunctionStatementPlaceholder -> { |  | ||||||
|                 // if(sub.name in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "memset", "memcopy", "memsetw", "swap")) |  | ||||||
|                 val func = BuiltinFunctions.getValue(sub.name) |  | ||||||
|                 if(func.pure) { |  | ||||||
|                     // non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters |  | ||||||
|                     for (arg in func.parameters.zip(call.arglist.withIndex())) { |  | ||||||
|                         val argtype = arg.second.value.inferType(program) |  | ||||||
|                         if (argtype != null) { |  | ||||||
|                             if (arg.first.possibleDatatypes.any { argtype == it }) |  | ||||||
|                                 continue |  | ||||||
|                             for (possibleType in arg.first.possibleDatatypes) { |  | ||||||
|                                 if (argtype isAssignableTo possibleType) { |  | ||||||
|                                     val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position) |  | ||||||
|                                     typecasted.linkParents(arg.second.value.parent) |  | ||||||
|                                     call.arglist[arg.second.index] = typecasted |  | ||||||
|                                     break |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             null -> {} |  | ||||||
|             else -> TODO("call to something weird $sub   ${call.target}") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> { |  | ||||||
|         val sequence= mutableListOf(first) |  | ||||||
|         var trailing: IStatement? = null |  | ||||||
|         while(stmtIter.hasNext()) { |  | ||||||
|             val next = stmtIter.next() |  | ||||||
|             if(next is Assignment) { |  | ||||||
|                 val constValue = next.value.constValue(program) |  | ||||||
|                 if(constValue==null) { |  | ||||||
|                     trailing = next |  | ||||||
|                     break |  | ||||||
|                 } |  | ||||||
|                 sequence.add(next) |  | ||||||
|             } |  | ||||||
|             else { |  | ||||||
|                 trailing=next |  | ||||||
|                 break |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         val sorted = sequence.sortedWith(compareBy({it.value.inferType(program)}, {it.target.shortString(true)})) |  | ||||||
|         return Pair(sorted, trailing) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(typecast: TypecastExpression): IExpression { |  | ||||||
|         // 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)) { |  | ||||||
|             printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) |  | ||||||
|         } |  | ||||||
|         return super.visit(typecast) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(whenStatement: WhenStatement): IStatement { |  | ||||||
|         // make sure all choices are just for one single value |  | ||||||
|         val choices = whenStatement.choices.toList() |  | ||||||
|         for(choice in choices) { |  | ||||||
|             if(choice.values==null || choice.values.size==1) |  | ||||||
|                 continue |  | ||||||
|             for(v in choice.values) { |  | ||||||
|                 val newchoice=WhenChoice(listOf(v), choice.statements, choice.position) |  | ||||||
|                 newchoice.parent = choice.parent |  | ||||||
|                 whenStatement.choices.add(newchoice) |  | ||||||
|             } |  | ||||||
|             whenStatement.choices.remove(choice) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // sort the choices in low-to-high value order (nulls last) |  | ||||||
|         whenStatement.choices |  | ||||||
|                 .sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.asIntegerValue})) |  | ||||||
|         return super.visit(whenStatement) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(memread: DirectMemoryRead): IExpression { |  | ||||||
|         // make sure the memory address is an uword |  | ||||||
|         val dt = memread.addressExpression.inferType(program) |  | ||||||
|         if(dt!=DataType.UWORD) { |  | ||||||
|             val literaladdr = memread.addressExpression as? LiteralValue |  | ||||||
|             if(literaladdr!=null) { |  | ||||||
|                 memread.addressExpression = literaladdr.cast(DataType.UWORD)!! |  | ||||||
|             } else { |  | ||||||
|                 memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position) |  | ||||||
|                 memread.addressExpression.parent = memread |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return super.visit(memread) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(memwrite: DirectMemoryWrite) { |  | ||||||
|         val dt = memwrite.addressExpression.inferType(program) |  | ||||||
|         if(dt!=DataType.UWORD) { |  | ||||||
|             val literaladdr = memwrite.addressExpression as? LiteralValue |  | ||||||
|             if(literaladdr!=null) { |  | ||||||
|                 memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!! |  | ||||||
|             } else { |  | ||||||
|                 memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position) |  | ||||||
|                 memwrite.addressExpression.parent = memwrite |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         super.visit(memwrite) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,125 +0,0 @@ | |||||||
| package prog8.ast.processing |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.base.autoHeapValuePrefix |  | ||||||
| import prog8.ast.expressions.AddressOf |  | ||||||
| import prog8.ast.expressions.FunctionCall |  | ||||||
| import prog8.ast.expressions.IdentifierReference |  | ||||||
| import prog8.ast.expressions.LiteralValue |  | ||||||
| import prog8.ast.statements.* |  | ||||||
|  |  | ||||||
| internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstModifyingVisitor { |  | ||||||
|     // For VarDecls that declare an initialization value: |  | ||||||
|     // Replace the vardecl with an assignment (to set the initial value), |  | ||||||
|     // and add a new vardecl with the default constant value of that type (usually zero) to the scope. |  | ||||||
|     // This makes sure the variables get reset to the intended value on a next run of the program. |  | ||||||
|     // Variable decls without a value don't get this treatment, which means they retain the last |  | ||||||
|     // value they had when restarting the program. |  | ||||||
|     // This is done in a separate step because it interferes with the namespace lookup of symbols |  | ||||||
|     // in other ast processors. |  | ||||||
|  |  | ||||||
|     // Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc). |  | ||||||
|  |  | ||||||
|     private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>() |  | ||||||
|  |  | ||||||
|     override fun visit(module: Module) { |  | ||||||
|         vardeclsToAdd.clear() |  | ||||||
|         super.visit(module) |  | ||||||
|  |  | ||||||
|         // add any new vardecls to the various scopes |  | ||||||
|         for(decl in vardeclsToAdd) |  | ||||||
|             for(d in decl.value) { |  | ||||||
|                 d.value.linkParents(decl.key as Node) |  | ||||||
|                 decl.key.statements.add(0, d.value) |  | ||||||
|             } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(decl: VarDecl): IStatement { |  | ||||||
|         super.visit(decl) |  | ||||||
|         if(decl.type!= VarDeclType.VAR || decl.value==null) |  | ||||||
|             return decl |  | ||||||
|  |  | ||||||
|         if(decl.datatype in NumericDatatypes) { |  | ||||||
|             val scope = decl.definingScope() |  | ||||||
|             addVarDecl(scope, decl.asDefaultValueDecl(null)) |  | ||||||
|             val declvalue = decl.value!! |  | ||||||
|             val value = |  | ||||||
|                     if(declvalue is LiteralValue) { |  | ||||||
|                         val converted = declvalue.cast(decl.datatype) |  | ||||||
|                         converted ?: declvalue |  | ||||||
|                     } |  | ||||||
|                     else |  | ||||||
|                         declvalue |  | ||||||
|             val identifierName = listOf(decl.name)    //  // TODO this was: (scoped name) decl.scopedname.split(".") |  | ||||||
|             return VariableInitializationAssignment( |  | ||||||
|                     AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position), |  | ||||||
|                     null, |  | ||||||
|                     value, |  | ||||||
|                     decl.position |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|         return decl |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCall: FunctionCall): IExpression { |  | ||||||
|         val targetStatement = functionCall.target.targetSubroutine(namespace) |  | ||||||
|         if(targetStatement!=null) { |  | ||||||
|             var node: Node = functionCall |  | ||||||
|             while(node !is IStatement) |  | ||||||
|                 node=node.parent |  | ||||||
|             addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node) |  | ||||||
|         } |  | ||||||
|         return functionCall |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun visit(functionCallStatement: FunctionCallStatement): IStatement { |  | ||||||
|         val targetStatement = functionCallStatement.target.targetSubroutine(namespace) |  | ||||||
|         if(targetStatement!=null) |  | ||||||
|             addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement) |  | ||||||
|         return functionCallStatement |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) { |  | ||||||
|         // functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead. |  | ||||||
|         for(argparam in subroutine.parameters.withIndex().zip(arglist)) { |  | ||||||
|             if(argparam.first.value.type== DataType.UWORD || argparam.first.value.type in StringDatatypes) { |  | ||||||
|                 if(argparam.second is AddressOf) |  | ||||||
|                     continue |  | ||||||
|                 val idref = argparam.second as? IdentifierReference |  | ||||||
|                 val strvalue = argparam.second as? LiteralValue |  | ||||||
|                 if(idref!=null) { |  | ||||||
|                     val variable = idref.targetVarDecl(namespace) |  | ||||||
|                     if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) { |  | ||||||
|                         val pointerExpr = AddressOf(idref, idref.position) |  | ||||||
|                         pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single()) |  | ||||||
|                         pointerExpr.linkParents(arglist[argparam.first.index].parent) |  | ||||||
|                         arglist[argparam.first.index] = pointerExpr |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 else if(strvalue!=null) { |  | ||||||
|                     if(strvalue.isString) { |  | ||||||
|                         // replace the argument with &autovar |  | ||||||
|                         val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}" |  | ||||||
|                         val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position) |  | ||||||
|                         val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) |  | ||||||
|                         pointerExpr.scopedname = parent.makeScopedName(autoVarName) |  | ||||||
|                         pointerExpr.linkParents(arglist[argparam.first.index].parent) |  | ||||||
|                         arglist[argparam.first.index] = pointerExpr |  | ||||||
|                         // add a vardecl so that the autovar can be resolved in later lookups |  | ||||||
|                         val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue, |  | ||||||
|                                 isArray = false, autoGenerated = false, position = strvalue.position) |  | ||||||
|                         addVarDecl(strvalue.definingScope(), variable) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun addVarDecl(scope: INameScope, variable: VarDecl) { |  | ||||||
|         if(scope !in vardeclsToAdd) |  | ||||||
|             vardeclsToAdd[scope] = mutableMapOf() |  | ||||||
|         vardeclsToAdd.getValue(scope)[variable.name]=variable |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,739 +0,0 @@ | |||||||
| package prog8.ast.statements |  | ||||||
|  |  | ||||||
| import prog8.ast.* |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.expressions.* |  | ||||||
| import prog8.ast.processing.IAstModifyingVisitor |  | ||||||
| import prog8.ast.processing.IAstVisitor |  | ||||||
| import prog8.compiler.HeapValues |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement { |  | ||||||
|     override var parent: Node = ParentSentinel |  | ||||||
|     override fun linkParents(parent: Node) {} |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder |  | ||||||
|     override val expensiveToInline = false |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean?) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Block(override val name: String, |  | ||||||
|             val address: Int?, |  | ||||||
|             override var statements: MutableList<IStatement>, |  | ||||||
|             val isInLibrary: Boolean, |  | ||||||
|             override val position: Position) : IStatement, INameScope { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline |  | ||||||
|         get() = statements.any { it.expensiveToInline } |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         statements.forEach {it.linkParents(this)} |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Block(name=$name, address=$address, ${statements.size} statements)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = false |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         args.forEach{it.linkParents(this)} |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| data class Label(val name: String, override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = false |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Label(name=$name, pos=$position)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val scopedname: String by lazy { makeScopedName(name) } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| open class Return(var value: IExpression?, override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = value!=null && value !is LiteralValue |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         value?.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Return($value, pos=$position)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class ReturnFromIrq(override val position: Position) : Return(null, position) { |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "ReturnFromIrq(pos=$position)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class Continue(override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = false |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent=parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class Break(override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = false |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent=parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class VarDecl(val type: VarDeclType, |  | ||||||
|               private val declaredDatatype: DataType, |  | ||||||
|               val zeropage: Boolean, |  | ||||||
|               var arraysize: ArrayIndex?, |  | ||||||
|               val name: String, |  | ||||||
|               var value: IExpression?, |  | ||||||
|               val isArray: Boolean, |  | ||||||
|               val autoGenerated: Boolean, |  | ||||||
|               override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline |  | ||||||
|             get() = value!=null && value !is LiteralValue |  | ||||||
|  |  | ||||||
|     val datatypeErrors = mutableListOf<SyntaxError>()       // don't crash at init time, report them in the AstChecker |  | ||||||
|     val datatype = |  | ||||||
|             if (!isArray) declaredDatatype |  | ||||||
|             else when (declaredDatatype) { |  | ||||||
|                 DataType.UBYTE -> DataType.ARRAY_UB |  | ||||||
|                 DataType.BYTE -> DataType.ARRAY_B |  | ||||||
|                 DataType.UWORD -> DataType.ARRAY_UW |  | ||||||
|                 DataType.WORD -> DataType.ARRAY_W |  | ||||||
|                 DataType.FLOAT -> DataType.ARRAY_F |  | ||||||
|                 else -> { |  | ||||||
|                     datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position)) |  | ||||||
|                     DataType.UBYTE |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         arraysize?.linkParents(this) |  | ||||||
|         value?.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     val scopedname: String by lazy { makeScopedName(name) } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun asDefaultValueDecl(parent: Node?): VarDecl { |  | ||||||
|         val constValue = when(declaredDatatype) { |  | ||||||
|             DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position = position) |  | ||||||
|             DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position = position) |  | ||||||
|             DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = 0, position = position) |  | ||||||
|             DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = 0, position = position) |  | ||||||
|             DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position) |  | ||||||
|             else -> throw FatalAstException("can only set a default value for a numeric type") |  | ||||||
|         } |  | ||||||
|         val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position) |  | ||||||
|         if(parent!=null) |  | ||||||
|             decl.linkParents(parent) |  | ||||||
|         return decl |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class ArrayIndex(var index: IExpression, override val position: Position) : Node { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         index.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex { |  | ||||||
|             val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize |  | ||||||
|             return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun accept(visitor: IAstModifyingVisitor) { |  | ||||||
|         index = index.accept(visitor) |  | ||||||
|     } |  | ||||||
|     fun accept(visitor: IAstVisitor) { |  | ||||||
|         index.accept(visitor) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return("ArrayIndex($index, pos=$position)") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun size() = (index as? LiteralValue)?.asIntegerValue |  | ||||||
| } |  | ||||||
|  |  | ||||||
| open class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline |  | ||||||
|             get() = value !is LiteralValue |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         this.target.linkParents(this) |  | ||||||
|         value.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // This is a special class so the compiler can see if the assignments are for initializing the vars in the scope, |  | ||||||
| // or just a regular assignment. It may optimize the initialization step from this. |  | ||||||
| class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position) |  | ||||||
|     : Assignment(target, aug_op, value, position) |  | ||||||
|  |  | ||||||
| data class AssignTarget(val register: Register?, |  | ||||||
|                         val identifier: IdentifierReference?, |  | ||||||
|                         val arrayindexed: ArrayIndexedExpression?, |  | ||||||
|                         var memoryAddress: DirectMemoryWrite?, |  | ||||||
|                         override val position: Position) : Node { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         identifier?.linkParents(this) |  | ||||||
|         arrayindexed?.linkParents(this) |  | ||||||
|         memoryAddress?.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         fun fromExpr(expr: IExpression): AssignTarget { |  | ||||||
|             return when (expr) { |  | ||||||
|                 is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position) |  | ||||||
|                 is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position) |  | ||||||
|                 is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position) |  | ||||||
|                 is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position) |  | ||||||
|                 else -> throw FatalAstException("invalid expression object $expr") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun inferType(program: Program, stmt: IStatement): DataType? { |  | ||||||
|         if(register!=null) |  | ||||||
|             return DataType.UBYTE |  | ||||||
|  |  | ||||||
|         if(identifier!=null) { |  | ||||||
|             val symbol = program.namespace.lookup(identifier.nameInSource, stmt) ?: return null |  | ||||||
|             if (symbol is VarDecl) return symbol.datatype |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(arrayindexed!=null) { |  | ||||||
|             val dt = arrayindexed.inferType(program) |  | ||||||
|             if(dt!=null) |  | ||||||
|                 return dt |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(memoryAddress!=null) |  | ||||||
|             return DataType.UBYTE |  | ||||||
|  |  | ||||||
|         return null |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun shortString(withTypePrefix: Boolean=false): String { |  | ||||||
|         if(register!=null) |  | ||||||
|             return (if(withTypePrefix) "0register::" else "") + register.name |  | ||||||
|         if(identifier!=null) |  | ||||||
|             return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last() |  | ||||||
|         if(arrayindexed!=null) |  | ||||||
|             return (if(withTypePrefix) "2arrayidx::" else "") + arrayindexed.identifier.nameInSource.last() |  | ||||||
|         val address = memoryAddress?.addressExpression |  | ||||||
|         if(address is LiteralValue) |  | ||||||
|             return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString() |  | ||||||
|         return if(withTypePrefix) "???::???" else "???" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun isMemoryMapped(namespace: INameScope): Boolean = |  | ||||||
|             memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type== VarDeclType.MEMORY) |  | ||||||
|  |  | ||||||
|     infix fun isSameAs(value: IExpression): Boolean { |  | ||||||
|         return when { |  | ||||||
|             this.memoryAddress!=null -> false |  | ||||||
|             this.register!=null -> value is RegisterExpr && value.register==register |  | ||||||
|             this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier.nameInSource |  | ||||||
|             this.arrayindexed!=null -> value is ArrayIndexedExpression && |  | ||||||
|                     value.identifier.nameInSource==arrayindexed.identifier.nameInSource && |  | ||||||
|                     value.arrayspec.size()!=null && |  | ||||||
|                     arrayindexed.arrayspec.size()!=null && |  | ||||||
|                     value.arrayspec.size()==arrayindexed.arrayspec.size() |  | ||||||
|             else -> false |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun isSameAs(other: AssignTarget, program: Program): Boolean { |  | ||||||
|         if(this===other) |  | ||||||
|             return true |  | ||||||
|         if(this.register!=null && other.register!=null) |  | ||||||
|             return this.register==other.register |  | ||||||
|         if(this.identifier!=null && other.identifier!=null) |  | ||||||
|             return this.identifier.nameInSource==other.identifier.nameInSource |  | ||||||
|         if(this.memoryAddress!=null && other.memoryAddress!=null) { |  | ||||||
|             val addr1 = this.memoryAddress!!.addressExpression.constValue(program) |  | ||||||
|             val addr2 = other.memoryAddress!!.addressExpression.constValue(program) |  | ||||||
|             return addr1!=null && addr2!=null && addr1==addr2 |  | ||||||
|         } |  | ||||||
|         if(this.arrayindexed!=null && other.arrayindexed!=null) { |  | ||||||
|             if(this.arrayindexed.identifier.nameInSource == other.arrayindexed.identifier.nameInSource) { |  | ||||||
|                 val x1 = this.arrayindexed.arrayspec.index.constValue(program) |  | ||||||
|                 val x2 = other.arrayindexed.arrayspec.index.constValue(program) |  | ||||||
|                 return x1!=null && x2!=null && x1==x2 |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return false |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun isNotMemory(namespace: INameScope): Boolean { |  | ||||||
|         if(this.register!=null) |  | ||||||
|             return true |  | ||||||
|         if(this.memoryAddress!=null) |  | ||||||
|             return false |  | ||||||
|         if(this.arrayindexed!=null) { |  | ||||||
|             val targetStmt = this.arrayindexed.identifier.targetVarDecl(namespace) |  | ||||||
|             if(targetStmt!=null) |  | ||||||
|                 return targetStmt.type!= VarDeclType.MEMORY |  | ||||||
|         } |  | ||||||
|         if(this.identifier!=null) { |  | ||||||
|             val targetStmt = this.identifier.targetVarDecl(namespace) |  | ||||||
|             if(targetStmt!=null) |  | ||||||
|                 return targetStmt.type!= VarDeclType.MEMORY |  | ||||||
|         } |  | ||||||
|         return false |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = false |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         target.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "PostIncrDecr(op: $operator, target: $target, pos=$position)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class Jump(val address: Int?, |  | ||||||
|            val identifier: IdentifierReference?, |  | ||||||
|            val generatedLabel: String?,             // used in code generation scenarios |  | ||||||
|            override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = false |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         identifier?.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel;  pos=$position)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class FunctionCallStatement(override var target: IdentifierReference, |  | ||||||
|                             override var arglist: MutableList<IExpression>, |  | ||||||
|                             override val position: Position) : IStatement, IFunctionCall { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline |  | ||||||
|             get() = arglist.any { it !is LiteralValue } |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         target.linkParents(this) |  | ||||||
|         arglist.forEach { it.linkParents(this) } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "FunctionCallStatement(target=$target, pos=$position)" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class InlineAssembly(val assembly: String, override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = true |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class AnonymousScope(override var statements: MutableList<IStatement>, |  | ||||||
|                      override val position: Position) : INameScope, IStatement { |  | ||||||
|     override val name: String |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline |  | ||||||
|         get() = statements.any { it.expensiveToInline } |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         name = "<anon-$sequenceNumber>"     // make sure it's an invalid soruce code identifier so user source code can never produce it |  | ||||||
|         sequenceNumber++ |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         private var sequenceNumber = 1 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         statements.forEach { it.linkParents(this) } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class NopStatement(override val position: Position): IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = false |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         fun insteadOf(stmt: IStatement): NopStatement { |  | ||||||
|             val nop = NopStatement(stmt.position) |  | ||||||
|             nop.parent = stmt.parent |  | ||||||
|             return nop |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // the subroutine class covers both the normal user-defined subroutines, |  | ||||||
| // and also the predefined/ROM/register-based subroutines. |  | ||||||
| // (multiple return types can only occur for the latter type) |  | ||||||
| class Subroutine(override val name: String, |  | ||||||
|                  val parameters: List<SubroutineParameter>, |  | ||||||
|                  val returntypes: List<DataType>, |  | ||||||
|                  val asmParameterRegisters: List<RegisterOrStatusflag>, |  | ||||||
|                  val asmReturnvaluesRegisters: List<RegisterOrStatusflag>, |  | ||||||
|                  val asmClobbers: Set<Register>, |  | ||||||
|                  val asmAddress: Int?, |  | ||||||
|                  val isAsmSubroutine: Boolean, |  | ||||||
|                  override var statements: MutableList<IStatement>, |  | ||||||
|                  override val position: Position) : IStatement, INameScope { |  | ||||||
|  |  | ||||||
|     var keepAlways: Boolean = false |  | ||||||
|     override val expensiveToInline |  | ||||||
|             get() = statements.any { it.expensiveToInline } |  | ||||||
|  |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     val calledBy = mutableListOf<Node>() |  | ||||||
|     val calls = mutableSetOf<Subroutine>() |  | ||||||
|  |  | ||||||
|     val scopedname: String by lazy { makeScopedName(name) } |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         parameters.forEach { it.linkParents(this) } |  | ||||||
|         statements.forEach { it.linkParents(this) } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun amountOfRtsInAsm(): Int = statements |  | ||||||
|             .asSequence() |  | ||||||
|             .filter { it is InlineAssembly } |  | ||||||
|             .map { (it as InlineAssembly).assembly } |  | ||||||
|             .count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it } |  | ||||||
|  |  | ||||||
|     val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine |  | ||||||
| //            !isAsmSubroutine |  | ||||||
| //                    && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD)) |  | ||||||
| //                    || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })) |  | ||||||
|  |  | ||||||
|     fun intoAsmSubroutine(): Subroutine { |  | ||||||
|         // TODO turn subroutine into asm calling convention.   Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...) |  | ||||||
|         return this // TODO |  | ||||||
|  |  | ||||||
| //        println("TO ASM   $this")  // TODO |  | ||||||
| //        val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE)) |  | ||||||
| //            listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null)) |  | ||||||
| //        else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD)) |  | ||||||
| //            listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null)) |  | ||||||
| //        else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }) |  | ||||||
| //            listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null)) |  | ||||||
| //        else throw FatalAstException("cannot convert subroutine to asm parameters") |  | ||||||
| // |  | ||||||
| //        val asmsub=Subroutine( |  | ||||||
| //                name, |  | ||||||
| //                parameters, |  | ||||||
| //                returntypes, |  | ||||||
| //                paramregs, |  | ||||||
| //                emptyList(), |  | ||||||
| //                emptySet(), |  | ||||||
| //                null, |  | ||||||
| //                true, |  | ||||||
| //                statements, |  | ||||||
| //                position |  | ||||||
| //        ) |  | ||||||
| //        asmsub.linkParents(parent) |  | ||||||
| //        return asmsub |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| open class SubroutineParameter(val name: String, |  | ||||||
|                                val type: DataType, |  | ||||||
|                                override val position: Position) : Node { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class IfStatement(var condition: IExpression, |  | ||||||
|                   var truepart: AnonymousScope, |  | ||||||
|                   var elsepart: AnonymousScope, |  | ||||||
|                   override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline: Boolean |  | ||||||
|         get() = truepart.expensiveToInline || elsepart.expensiveToInline |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         condition.linkParents(this) |  | ||||||
|         truepart.linkParents(this) |  | ||||||
|         elsepart.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class BranchStatement(var condition: BranchCondition, |  | ||||||
|                       var truepart: AnonymousScope, |  | ||||||
|                       var elsepart: AnonymousScope, |  | ||||||
|                       override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline: Boolean |  | ||||||
|         get() = truepart.expensiveToInline || elsepart.expensiveToInline |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         truepart.linkParents(this) |  | ||||||
|         elsepart.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class ForLoop(val loopRegister: Register?, |  | ||||||
|               val decltype: DataType?, |  | ||||||
|               val zeropage: Boolean, |  | ||||||
|               val loopVar: IdentifierReference?, |  | ||||||
|               var iterable: IExpression, |  | ||||||
|               var body: AnonymousScope, |  | ||||||
|               override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = true |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent=parent |  | ||||||
|         loopVar?.linkParents(if(decltype==null) this else body) |  | ||||||
|         iterable.linkParents(this) |  | ||||||
|         body.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         const val iteratorLoopcounterVarname = "prog8forloopcounter" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class WhileLoop(var condition: IExpression, |  | ||||||
|                 var body: AnonymousScope, |  | ||||||
|                 override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = true |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         condition.linkParents(this) |  | ||||||
|         body.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class RepeatLoop(var body: AnonymousScope, |  | ||||||
|                  var untilCondition: IExpression, |  | ||||||
|                  override val position: Position) : IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline = true |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         untilCondition.linkParents(this) |  | ||||||
|         body.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class WhenStatement(val condition: IExpression, |  | ||||||
|                     val choices: MutableList<WhenChoice>, |  | ||||||
|                     override val position: Position): IStatement { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|     override val expensiveToInline: Boolean = true |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         condition.linkParents(this) |  | ||||||
|         choices.forEach { it.linkParents(this) } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> { |  | ||||||
|         // only gives sensible results when the choices are all valid (constant integers) |  | ||||||
|         val result = mutableListOf<Pair<List<Int>?, WhenChoice>>() |  | ||||||
|         for(choice in choices) { |  | ||||||
|             if(choice.values==null) |  | ||||||
|                 result.add(null to choice) |  | ||||||
|             else { |  | ||||||
|                 val values = choice.values.map { it.constValue(program)?.asNumericValue?.toInt() } |  | ||||||
|                 if(values.contains(null)) |  | ||||||
|                     result.add(null to choice) |  | ||||||
|                 else |  | ||||||
|                     result.add(values.filterNotNull() to choice) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return result |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class WhenChoice(val values: List<IExpression>?,           // if null,  this is the 'else' part |  | ||||||
|                  val statements: AnonymousScope, |  | ||||||
|                  override val position: Position) : Node { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         values?.forEach { it.linkParents(this) } |  | ||||||
|         statements.linkParents(this) |  | ||||||
|         this.parent = parent |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "Choice($values at $position)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : Node { |  | ||||||
|     override lateinit var parent: Node |  | ||||||
|  |  | ||||||
|     override fun linkParents(parent: Node) { |  | ||||||
|         this.parent = parent |  | ||||||
|         this.addressExpression.linkParents(this) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "DirectMemoryWrite($addressExpression)" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun accept(visitor: IAstVisitor) = visitor.visit(this) |  | ||||||
|     fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										3
									
								
								compiler/src/prog8/compiler/AssemblyError.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								compiler/src/prog8/compiler/AssemblyError.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | package prog8.compiler | ||||||
|  |  | ||||||
|  | internal class AssemblyError(msg: String) : RuntimeException(msg) | ||||||
							
								
								
									
										354
									
								
								compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,354 @@ | |||||||
|  | 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.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: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() { | ||||||
|  |  | ||||||
|  |     override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { | ||||||
|  |         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>().filter { it.type == VarDeclType.VAR } | ||||||
|  |         subroutineVariables.addAll(decls.map { it.name to it }) | ||||||
|  |  | ||||||
|  |         val sub = scope.definingSubroutine() | ||||||
|  |         if (sub != null) { | ||||||
|  |             // 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)) | ||||||
|  |             } | ||||||
|  |             return replacements + movements | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { | ||||||
|  |         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 | ||||||
|  |                 && subroutine.statements.last() !is Subroutine) { | ||||||
|  |             mods += IAstModification.InsertLast(returnStmt, subroutine) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // precede a subroutine with a return to avoid falling through into the subroutine from code above it | ||||||
|  |         val outerScope = subroutine.definingScope() | ||||||
|  |         val outerStatements = outerScope.statements | ||||||
|  |         val subroutineStmtIdx = outerStatements.indexOf(subroutine) | ||||||
|  |         if (subroutineStmtIdx > 0 | ||||||
|  |                 && outerStatements[subroutineStmtIdx - 1] !is Jump | ||||||
|  |                 && outerStatements[subroutineStmtIdx - 1] !is Subroutine | ||||||
|  |                 && outerStatements[subroutineStmtIdx - 1] !is Return | ||||||
|  |                 && outerScope !is Block) { | ||||||
|  |             mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope) | ||||||
|  |         } | ||||||
|  |         return mods | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { | ||||||
|  |         // 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.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)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // 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) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         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 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										57
									
								
								compiler/src/prog8/compiler/ErrorReporting.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								compiler/src/prog8/compiler/ErrorReporting.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | package prog8.compiler | ||||||
|  |  | ||||||
|  | import prog8.ast.base.Position | ||||||
|  | import prog8.parser.ParsingFailedError | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  |     } | ||||||
|  |     private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position) | ||||||
|  |  | ||||||
|  |     private val messages = mutableListOf<CompilerMessage>() | ||||||
|  |     private val alreadyReportedMessages = mutableSetOf<String>() | ||||||
|  |  | ||||||
|  |     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)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun report() { | ||||||
|  |         var numErrors = 0 | ||||||
|  |         var numWarnings = 0 | ||||||
|  |         messages.forEach { | ||||||
|  |             when(it.severity) { | ||||||
|  |                 MessageSeverity.ERROR -> System.err.print("\u001b[91m")  // bright red | ||||||
|  |                 MessageSeverity.WARNING -> System.err.print("\u001b[93m")  // bright yellow | ||||||
|  |             } | ||||||
|  |             val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim() | ||||||
|  |             if(msg !in alreadyReportedMessages) { | ||||||
|  |                 System.err.println(msg) | ||||||
|  |                 alreadyReportedMessages.add(msg) | ||||||
|  |                 when(it.severity) { | ||||||
|  |                     MessageSeverity.WARNING -> numWarnings++ | ||||||
|  |                     MessageSeverity.ERROR -> numErrors++ | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             System.err.print("\u001b[0m")  // reset color | ||||||
|  |         } | ||||||
|  |         messages.clear() | ||||||
|  |         if(numErrors>0) | ||||||
|  |             throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR } | ||||||
|  | } | ||||||
| @@ -1,188 +0,0 @@ | |||||||
| package prog8.compiler |  | ||||||
|  |  | ||||||
| import prog8.ast.Program |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.base.checkIdentifiers |  | ||||||
| import prog8.ast.base.checkValid |  | ||||||
| import prog8.ast.base.reorderStatements |  | ||||||
| import prog8.ast.statements.Directive |  | ||||||
| import prog8.compiler.target.c64.AsmGen |  | ||||||
| import prog8.compiler.target.c64.C64Zeropage |  | ||||||
| import prog8.optimizer.constantFold |  | ||||||
| import prog8.optimizer.optimizeStatements |  | ||||||
| import prog8.optimizer.simplifyExpressions |  | ||||||
| import prog8.parser.ParsingFailedError |  | ||||||
| import prog8.parser.importLibraryModule |  | ||||||
| import prog8.parser.importModule |  | ||||||
| import prog8.parser.moduleName |  | ||||||
| import java.io.File |  | ||||||
| import java.io.PrintStream |  | ||||||
| import java.lang.Exception |  | ||||||
| import java.nio.file.Path |  | ||||||
| import kotlin.system.exitProcess |  | ||||||
| import kotlin.system.measureTimeMillis |  | ||||||
|  |  | ||||||
| fun compileProgram(filepath: Path, |  | ||||||
|                    optimize: Boolean, optimizeInlining: Boolean, |  | ||||||
|                    generateVmCode: Boolean, writeVmCode: Boolean, |  | ||||||
|                    writeAssembly: Boolean): Pair<Program, String?> { |  | ||||||
|     lateinit var programAst: Program |  | ||||||
|     var programName: String? = null |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|         val totalTime = measureTimeMillis { |  | ||||||
|             // import main module and everything it needs |  | ||||||
|             println("Parsing...") |  | ||||||
|             programAst = Program(moduleName(filepath.fileName), mutableListOf()) |  | ||||||
|             importModule(programAst, filepath) |  | ||||||
|  |  | ||||||
|             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) { |  | ||||||
|                 importLibraryModule(programAst, "c64lib") |  | ||||||
|                 importLibraryModule(programAst, "c64utils") |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // always import prog8lib and math |  | ||||||
|             importLibraryModule(programAst, "math") |  | ||||||
|             importLibraryModule(programAst, "prog8lib") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|             // perform initial syntax checks and constant folding |  | ||||||
|             println("Syntax check...") |  | ||||||
|             val time1 = measureTimeMillis { |  | ||||||
|                 programAst.checkIdentifiers() |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             //println(" time1: $time1") |  | ||||||
|             val time2 = measureTimeMillis { |  | ||||||
|                 programAst.constantFold() |  | ||||||
|             } |  | ||||||
|             //println(" time2: $time2") |  | ||||||
|             val time3 = measureTimeMillis { |  | ||||||
|                 programAst.reorderStatements()     // reorder statements and add type casts, to please the compiler later |  | ||||||
|             } |  | ||||||
|             //println(" time3: $time3") |  | ||||||
|             val time4 = measureTimeMillis { |  | ||||||
|                 programAst.checkValid(compilerOptions)          // check if tree is valid |  | ||||||
|             } |  | ||||||
|             //println(" time4: $time4") |  | ||||||
|  |  | ||||||
|             programAst.checkIdentifiers() |  | ||||||
|             if (optimize) { |  | ||||||
|                 // 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(optimizeInlining) |  | ||||||
|                     if (optsDone1 + optsDone2 == 0) |  | ||||||
|                         break |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             programAst.removeNops() |  | ||||||
|             programAst.checkValid(compilerOptions)          // check if final tree is valid |  | ||||||
|             programAst.checkRecursion()         // check if there are recursive subroutine calls |  | ||||||
|  |  | ||||||
|             // printAst(programAst) |  | ||||||
|             // namespace.debugPrint() |  | ||||||
|  |  | ||||||
|             if(generateVmCode) { |  | ||||||
|                 // compile the syntax tree into stackvmProg form, and optimize that |  | ||||||
|                 val compiler = Compiler(programAst) |  | ||||||
|                 val intermediate = compiler.compile(compilerOptions) |  | ||||||
|                 if (optimize) |  | ||||||
|                     intermediate.optimize() |  | ||||||
|  |  | ||||||
|                 if (writeVmCode) { |  | ||||||
|                     val stackVmFilename = intermediate.name + ".vm.txt" |  | ||||||
|                     val stackvmFile = PrintStream(File(stackVmFilename), "utf-8") |  | ||||||
|                     intermediate.writeCode(stackvmFile) |  | ||||||
|                     stackvmFile.close() |  | ||||||
|                     println("StackVM program code written to '$stackVmFilename'") |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if (writeAssembly) { |  | ||||||
|                     val zeropage = C64Zeropage(compilerOptions) |  | ||||||
|                     intermediate.allocateZeropage(zeropage) |  | ||||||
|                     val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize) |  | ||||||
|                     assembly.assemble(compilerOptions) |  | ||||||
|                     programName = assembly.name |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") |  | ||||||
|  |  | ||||||
|     } catch (px: ParsingFailedError) { |  | ||||||
|         System.err.print("\u001b[91m")  // bright red |  | ||||||
|         System.err.println(px.message) |  | ||||||
|         System.err.print("\u001b[0m")  // reset |  | ||||||
|         exitProcess(1) |  | ||||||
|     } catch (ax: AstException) { |  | ||||||
|         System.err.print("\u001b[91m")  // bright red |  | ||||||
|         System.err.println(ax.toString()) |  | ||||||
|         System.err.print("\u001b[0m")  // reset |  | ||||||
|         exitProcess(1) |  | ||||||
|     } 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 Pair(programAst, programName) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| fun printAst(programAst: Program) { |  | ||||||
|     println() |  | ||||||
|     val printer = AstToSourceCode(::print) |  | ||||||
|     printer.visit(programAst) |  | ||||||
|     println() |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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 |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| @@ -1,7 +1,6 @@ | |||||||
| package prog8.compiler | package prog8.compiler | ||||||
|  |  | ||||||
| import prog8.ast.base.* | import prog8.ast.base.* | ||||||
| import prog8.ast.base.printWarning |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ZeropageDepletedError(message: String) : Exception(message) | class ZeropageDepletedError(message: String) : Exception(message) | ||||||
| @@ -9,15 +8,46 @@ class ZeropageDepletedError(message: String) : Exception(message) | |||||||
|  |  | ||||||
| abstract class Zeropage(protected val options: CompilationOptions) { | 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>>() |     private val allocations = mutableMapOf<Int, Pair<String, DataType>>() | ||||||
|     val free = mutableListOf<Int>()     // subclasses must set this to the appropriate free locations. |     val free = mutableListOf<Int>()     // subclasses must set this to the appropriate free locations. | ||||||
|  |  | ||||||
|     val allowedDatatypes = NumericDatatypes |     val allowedDatatypes = NumericDatatypes | ||||||
|  |  | ||||||
|     fun available() = 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?): Int { |         val words = free.windowed(2).filter { it[0] == it[1]-1 } | ||||||
|         assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"} |         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") | ||||||
|  |  | ||||||
|         val size = |         val size = | ||||||
|                 when (datatype) { |                 when (datatype) { | ||||||
| @@ -26,9 +56,9 @@ abstract class Zeropage(protected val options: CompilationOptions) { | |||||||
|                     DataType.FLOAT -> { |                     DataType.FLOAT -> { | ||||||
|                         if (options.floats) { |                         if (options.floats) { | ||||||
|                             if(position!=null) |                             if(position!=null) | ||||||
|                                 printWarning("allocated a large value (float) in zeropage", position) |                                 errors.warn("allocated a large value (float) in zeropage", position) | ||||||
|                             else |                             else | ||||||
|                                 printWarning("$scopedname: allocated a large value (float) in zeropage") |                                 errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY) | ||||||
|                             5 |                             5 | ||||||
|                         } else throw CompilerException("floating point option not enabled") |                         } else throw CompilerException("floating point option not enabled") | ||||||
|                     } |                     } | ||||||
| @@ -37,13 +67,13 @@ abstract class Zeropage(protected val options: CompilationOptions) { | |||||||
|  |  | ||||||
|         if(free.size > 0) { |         if(free.size > 0) { | ||||||
|             if(size==1) { |             if(size==1) { | ||||||
|                 for(candidate in free.min()!! .. free.max()!!+1) { |                 for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) { | ||||||
|                     if(loneByte(candidate)) |                     if(loneByte(candidate)) | ||||||
|                         return makeAllocation(candidate, 1, datatype, scopedname) |                         return makeAllocation(candidate, 1, datatype, scopedname) | ||||||
|                 } |                 } | ||||||
|                 return makeAllocation(free[0], 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)) |                 if (sequentialFree(candidate, size)) | ||||||
|                     return makeAllocation(candidate, size, datatype, scopedname) |                     return makeAllocation(candidate, size, datatype, scopedname) | ||||||
|             } |             } | ||||||
| @@ -56,18 +86,10 @@ abstract class Zeropage(protected val options: CompilationOptions) { | |||||||
|  |  | ||||||
|     private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { |     private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { | ||||||
|         free.removeAll(address until address+size) |         free.removeAll(address until address+size) | ||||||
|         allocations[address] = Pair(name ?: "<unnamed>", datatype) |         allocations[address] = (name ?: "<unnamed>") to datatype | ||||||
|         return address |         return address | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free |     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()) |     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 |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1381
									
								
								compiler/src/prog8/compiler/astprocessing/AstChecker.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1381
									
								
								compiler/src/prog8/compiler/astprocessing/AstChecker.kt
									
									
									
									
									
										Normal file
									
								
							
										
											
												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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,142 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | import prog8.ast.Module | ||||||
|  | import prog8.ast.Program | ||||||
|  | import prog8.ast.base.Position | ||||||
|  | import prog8.ast.expressions.StringLiteralValue | ||||||
|  | import prog8.ast.statements.* | ||||||
|  | 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: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor { | ||||||
|  |     private var blocks = mutableMapOf<String, Block>() | ||||||
|  |  | ||||||
|  |     private fun nameError(name: String, position: Position, existing: Statement) { | ||||||
|  |         errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun visit(module: Module) { | ||||||
|  |         blocks.clear()  // blocks may be redefined within a different module | ||||||
|  |  | ||||||
|  |         super.visit(module) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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 compTarget.machine.opcodeNames) | ||||||
|  |             errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) | ||||||
|  |  | ||||||
|  |         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 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 | ||||||
|  |             errors.err("builtin function cannot be redefined", subroutine.position) | ||||||
|  |         } else { | ||||||
|  |             // already reported elsewhere: | ||||||
|  |             // if (subroutine.parameters.any { it.name in BuiltinFunctions }) | ||||||
|  |             //    checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position)) | ||||||
|  |  | ||||||
|  |             val existing = program.namespace.lookup(listOf(subroutine.name), subroutine) | ||||||
|  |             if (existing != null && existing !== subroutine) | ||||||
|  |                 nameError(subroutine.name, subroutine.position, existing) | ||||||
|  |  | ||||||
|  |             // 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() | ||||||
|  |             val paramsToCheck = paramNames.intersect(namesInSub) | ||||||
|  |             for(name in paramsToCheck) { | ||||||
|  |                 val labelOrVar = subroutine.getLabelOrVariable(name) | ||||||
|  |                 if(labelOrVar!=null && labelOrVar.position != subroutine.position) | ||||||
|  |                     nameError(name, labelOrVar.position, subroutine) | ||||||
|  |                 val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name} | ||||||
|  |                 if(sub!=null) | ||||||
|  |                     nameError(name, subroutine.position, sub) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { | ||||||
|  |                 errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         super.visit(subroutine) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun visit(label: Label) { | ||||||
|  |         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) { | ||||||
|  |             // the builtin functions can't be redefined | ||||||
|  |             errors.err("builtin function cannot be redefined", label.position) | ||||||
|  |         } else { | ||||||
|  |             val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList() | ||||||
|  |             for(el in existing) { | ||||||
|  |                 if(el === label || el.name != label.name) | ||||||
|  |                     continue | ||||||
|  |                 else { | ||||||
|  |                     nameError(label.name, label.position, el) | ||||||
|  |                     break | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         super.visit(label) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun visit(string: StringLiteralValue) { | ||||||
|  |         if (string.value.length > 255) | ||||||
|  |             errors.err("string literal length max is 255", string.position) | ||||||
|  |  | ||||||
|  |         super.visit(string) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,71 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | This is here for reference only, reflection based ast walking is very slow | ||||||
|  | when compared to the more verbose visitor pattern interfaces. | ||||||
|  | Too bad, because the code is very small | ||||||
|  | */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | //import prog8.ast.NoAstWalk | ||||||
|  | //import prog8.ast.Node | ||||||
|  | //import prog8.ast.Program | ||||||
|  | //import prog8.ast.base.Position | ||||||
|  | //import prog8.ast.expressions.BinaryExpression | ||||||
|  | //import prog8.ast.expressions.NumericLiteralValue | ||||||
|  | //import kotlin.reflect.KClass | ||||||
|  | //import kotlin.reflect.KVisibility | ||||||
|  | //import kotlin.reflect.full.declaredMemberProperties | ||||||
|  | //import kotlin.reflect.full.isSubtypeOf | ||||||
|  | //import kotlin.reflect.full.starProjectedType | ||||||
|  | // | ||||||
|  | // | ||||||
|  | //class ReflectionAstWalker { | ||||||
|  | //    private val nodeType = Node::class.starProjectedType | ||||||
|  | //    private val collectionType = Collection::class.starProjectedType | ||||||
|  | // | ||||||
|  | // | ||||||
|  | //    fun walk(node: Node, nesting: Int) { | ||||||
|  | //        val nodetype: KClass<out Node> = node::class | ||||||
|  | //        val indent = "  ".repeat(nesting) | ||||||
|  | //        //println("$indent VISITING ${nodetype.simpleName}") | ||||||
|  | //        val visibleAstMembers = nodetype.declaredMemberProperties.filter { | ||||||
|  | //            it.visibility!=KVisibility.PRIVATE && !it.isLateinit && | ||||||
|  | //                    !(it.annotations.any{a->a is NoAstWalk}) | ||||||
|  | //        } | ||||||
|  | //        for(prop in visibleAstMembers) { | ||||||
|  | //            if(prop.returnType.isSubtypeOf(nodeType)) { | ||||||
|  | //                // println("$indent +PROP: ${prop.name}") | ||||||
|  | //                walk(prop.call(node) as Node, nesting + 1) | ||||||
|  | //            } | ||||||
|  | //            else if(prop.returnType.isSubtypeOf(collectionType)) { | ||||||
|  | //                val elementType = prop.returnType.arguments.single().type | ||||||
|  | //                if(elementType!=null && elementType.isSubtypeOf(nodeType)) { | ||||||
|  | //                    val nodes = prop.call(node) as Collection<Node> | ||||||
|  | //                    nodes.forEach { walk(it, nesting+1) } | ||||||
|  | //                } | ||||||
|  | //            } | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
|  | //    fun walk(program: Program) { | ||||||
|  | //        for(module in program.modules) { | ||||||
|  | //            println("---MODULE $module---") | ||||||
|  | //            walk(module, 0) | ||||||
|  | //        } | ||||||
|  | //    } | ||||||
|  | //} | ||||||
|  | // | ||||||
|  | // | ||||||
|  | //fun main() { | ||||||
|  | //    val ast = BinaryExpression( | ||||||
|  | //            NumericLiteralValue.optimalInteger(100, Position.DUMMY), | ||||||
|  | //            "+", | ||||||
|  | //            NumericLiteralValue.optimalInteger(200, Position.DUMMY), | ||||||
|  | //            Position.DUMMY | ||||||
|  | //    ) | ||||||
|  | // | ||||||
|  | //    val walker = ReflectionAstWalker() | ||||||
|  | //    walker.walk(ast,0) | ||||||
|  | // | ||||||
|  | //} | ||||||
							
								
								
									
										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)) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										236
									
								
								compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								compiler/src/prog8/compiler/astprocessing/TypecastsAdder.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,236 @@ | |||||||
|  | package prog8.compiler.astprocessing | ||||||
|  |  | ||||||
|  | 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.ast.walk.AstWalker | ||||||
|  | import prog8.ast.walk.IAstModification | ||||||
|  | import prog8.compiler.IErrorReporter | ||||||
|  | import prog8.compiler.functions.BuiltinFunctions | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  |      */ | ||||||
|  |  | ||||||
|  |     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.UNDEFINED), rightDt.typeOrElse(DataType.UNDEFINED), expr.left, expr.right) | ||||||
|  |             if(toFix!=null) { | ||||||
|  |                 return when { | ||||||
|  |                     toFix===expr.left -> listOf(IAstModification.ReplaceNode( | ||||||
|  |                             expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr)) | ||||||
|  |                     toFix===expr.right -> listOf(IAstModification.ReplaceNode( | ||||||
|  |                             expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr)) | ||||||
|  |                     else -> throw FatalAstException("confused binary expression side") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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) | ||||||
|  |         if(targetItype.isKnown && valueItype.isKnown) { | ||||||
|  |             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> { | ||||||
|  |                         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() | ||||||
|  |                         // more complex comparisons if the type is different, but the constant value is compatible | ||||||
|  |                         if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) { | ||||||
|  |                             if(number>0) | ||||||
|  |                                 return castLiteral(cvalue) | ||||||
|  |                         } else if (valuetype == DataType.WORD && targettype == DataType.UWORD) { | ||||||
|  |                             if(number>0) | ||||||
|  |                                 return castLiteral(cvalue) | ||||||
|  |                         } else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) { | ||||||
|  |                             if(number<0x80) | ||||||
|  |                                 return castLiteral(cvalue) | ||||||
|  |                         } else if (valuetype == DataType.UWORD && targettype == DataType.WORD) { | ||||||
|  |                             if(number<0x8000) | ||||||
|  |                                 return castLiteral(cvalue) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return afterFunctionCallArgs(functionCallStatement) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { | ||||||
|  |         return afterFunctionCallArgs(functionCall) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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(program)) { | ||||||
|  |             is Subroutine -> { | ||||||
|  |                 sub.parameters.zip(call.args).forEachIndexed { index, pair -> | ||||||
|  |                     val argItype = pair.second.inferType(program) | ||||||
|  |                     if(argItype.isKnown) { | ||||||
|  |                         val argtype = argItype.typeOrElse(DataType.UNDEFINED) | ||||||
|  |                         val requiredType = pair.first.type | ||||||
|  |                         if (requiredType != argtype) { | ||||||
|  |                             if (argtype isAssignableTo requiredType) { | ||||||
|  |                                 modifications += IAstModification.ReplaceNode( | ||||||
|  |                                         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[index], | ||||||
|  |                                             AddressOf(pair.second as IdentifierReference, pair.second.position), | ||||||
|  |                                             call as Node) | ||||||
|  |                                 } | ||||||
|  |                             } 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) | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             is BuiltinFunctionStatementPlaceholder -> { | ||||||
|  |                 val func = BuiltinFunctions.getValue(sub.name) | ||||||
|  |                 func.parameters.zip(call.args).forEachIndexed { index, pair -> | ||||||
|  |                     val argItype = pair.second.inferType(program) | ||||||
|  |                     if (argItype.isKnown) { | ||||||
|  |                         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[index], | ||||||
|  |                                             TypecastExpression(pair.second, possibleType, true, pair.second.position), | ||||||
|  |                                             call as Node) | ||||||
|  |                                     break | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else -> { } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return modifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position) | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { | ||||||
|  |         // 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)?.valueOrZero() | ||||||
|  |                     ?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position) | ||||||
|  |             return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread)) | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> { | ||||||
|  |         // 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)?.valueOrZero() | ||||||
|  |                     ?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position) | ||||||
|  |             return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite)) | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> { | ||||||
|  |         // add a typecast to the return type if it doesn't match the subroutine's signature | ||||||
|  |         val returnValue = returnStmt.value | ||||||
|  |         if(returnValue!=null) { | ||||||
|  |             val subroutine = returnStmt.definingSubroutine()!! | ||||||
|  |             if(subroutine.returntypes.size==1) { | ||||||
|  |                 val subReturnType = subroutine.returntypes.first() | ||||||
|  |                 if (returnValue.inferType(program).istype(subReturnType)) | ||||||
|  |                     return noModifications | ||||||
|  |                 if (returnValue is NumericLiteralValue) { | ||||||
|  |                     val cast = returnValue.cast(subroutine.returntypes.single()) | ||||||
|  |                     if(cast.isValid) | ||||||
|  |                         returnStmt.value = cast.valueOrZero() | ||||||
|  |                 } else { | ||||||
|  |                     return listOf(IAstModification.ReplaceNode( | ||||||
|  |                             returnValue, | ||||||
|  |                             TypecastExpression(returnValue, subReturnType, true, returnValue.position), | ||||||
|  |                             returnStmt)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return noModifications | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										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,51 +0,0 @@ | |||||||
| package prog8.compiler.intermediate |  | ||||||
|  |  | ||||||
| import prog8.vm.RuntimeValue |  | ||||||
| import prog8.vm.stackvm.Syscall |  | ||||||
|  |  | ||||||
| open class Instruction(val opcode: Opcode, |  | ||||||
|                        val arg: RuntimeValue? = null, |  | ||||||
|                        val arg2: RuntimeValue? = null, |  | ||||||
|                        val callLabel: String? = null, |  | ||||||
|                        val callLabel2: String? = null) |  | ||||||
| { |  | ||||||
|     var branchAddress: Int? = null |  | ||||||
|  |  | ||||||
|     override fun toString(): String { |  | ||||||
|         val argStr = arg?.toString() ?: "" |  | ||||||
|         val result = |  | ||||||
|                 when { |  | ||||||
|                     opcode==Opcode.LINE -> "_line  $callLabel" |  | ||||||
|                     opcode==Opcode.INLINE_ASSEMBLY -> { |  | ||||||
|                         // inline assembly is not written out (it can't be processed as intermediate language) |  | ||||||
|                         // instead, it is converted into a system call that can be intercepted by the vm |  | ||||||
|                         if(callLabel!=null) |  | ||||||
|                             "syscall  SYSASM.$callLabel\n    return" |  | ||||||
|                         else |  | ||||||
|                             "inline_assembly" |  | ||||||
|                     } |  | ||||||
|                     opcode==Opcode.INCLUDE_FILE -> { |  | ||||||
|                         "include_file \"$callLabel\" $arg $arg2" |  | ||||||
|                     } |  | ||||||
|                     opcode==Opcode.SYSCALL -> { |  | ||||||
|                         val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() } |  | ||||||
|                         "syscall  $syscall" |  | ||||||
|                     } |  | ||||||
|                     opcode in opcodesWithVarArgument -> { |  | ||||||
|                         // opcodes that manipulate a variable |  | ||||||
|                         "${opcode.name.toLowerCase()}  ${callLabel?:""}  ${callLabel2?:""}".trimEnd() |  | ||||||
|                     } |  | ||||||
|                     callLabel==null -> "${opcode.name.toLowerCase()}  $argStr" |  | ||||||
|                     else -> "${opcode.name.toLowerCase()}  $callLabel  $argStr" |  | ||||||
|                 } |  | ||||||
|                 .trimEnd() |  | ||||||
|  |  | ||||||
|         return "    $result" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class LabelInstr(val name: String, val asmProc: Boolean) : Instruction(Opcode.NOP, null, null) { |  | ||||||
|     override fun toString(): String { |  | ||||||
|         return "\n$name:" |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,536 +0,0 @@ | |||||||
| package prog8.compiler.intermediate |  | ||||||
|  |  | ||||||
| import prog8.ast.antlr.escape |  | ||||||
| import prog8.ast.base.* |  | ||||||
| import prog8.ast.base.printWarning |  | ||||||
| import prog8.ast.expressions.LiteralValue |  | ||||||
| import prog8.ast.statements.VarDecl |  | ||||||
| import prog8.vm.RuntimeValue |  | ||||||
| import prog8.compiler.CompilerException |  | ||||||
| import prog8.compiler.HeapValues |  | ||||||
| import prog8.compiler.Zeropage |  | ||||||
| import prog8.compiler.ZeropageDepletedError |  | ||||||
| import java.io.PrintStream |  | ||||||
| import java.nio.file.Path |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) { |  | ||||||
|  |  | ||||||
|     class ProgramBlock(val name: String, |  | ||||||
|                        var address: Int?, |  | ||||||
|                        val instructions: MutableList<Instruction> = mutableListOf(), |  | ||||||
|                        val variables: MutableMap<String, RuntimeValue> = mutableMapOf(),           // names are fully scoped |  | ||||||
|                        val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(), |  | ||||||
|                        val labels: MutableMap<String, Instruction> = mutableMapOf(),        // names are fully scoped |  | ||||||
|                        val force_output: Boolean) |  | ||||||
|     { |  | ||||||
|         val numVariables: Int |  | ||||||
|             get() { return variables.size } |  | ||||||
|         val numInstructions: Int |  | ||||||
|             get() { return instructions.filter { it.opcode!= Opcode.LINE }.size } |  | ||||||
|         val variablesMarkedForZeropage: MutableSet<String> = mutableSetOf() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>() |  | ||||||
|     val blocks = mutableListOf<ProgramBlock>() |  | ||||||
|     val memory = mutableMapOf<Int, List<RuntimeValue>>() |  | ||||||
|     private lateinit var currentBlock: ProgramBlock |  | ||||||
|  |  | ||||||
|     val numVariables: Int |  | ||||||
|         get() = blocks.sumBy { it.numVariables } |  | ||||||
|     val numInstructions: Int |  | ||||||
|         get() = blocks.sumBy { it.numInstructions } |  | ||||||
|  |  | ||||||
|     fun allocateZeropage(zeropage: Zeropage) { |  | ||||||
|         // allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP) |  | ||||||
|         var notAllocated = 0 |  | ||||||
|         for(block in blocks) { |  | ||||||
|             val zpVariables = block.variables.filter { it.key in block.variablesMarkedForZeropage } |  | ||||||
|             if (zpVariables.isNotEmpty()) { |  | ||||||
|                 for (variable in zpVariables) { |  | ||||||
|                     try { |  | ||||||
|                         val address = zeropage.allocate(variable.key, variable.value.type, null) |  | ||||||
|                         allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type) |  | ||||||
|                     } catch (x: ZeropageDepletedError) { |  | ||||||
|                         printWarning(x.toString() + " variable ${variable.key} type ${variable.value.type}") |  | ||||||
|                         notAllocated++ |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if(notAllocated>0) |  | ||||||
|             printWarning("$notAllocated variables marked for Zeropage could not be allocated there") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun optimize() { |  | ||||||
|         println("Optimizing stackVM code...") |  | ||||||
|         // remove nops (that are not a label) |  | ||||||
|         for (blk in blocks) { |  | ||||||
|             blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         optimizeDataConversionAndUselessDiscards() |  | ||||||
|         optimizeVariableCopying() |  | ||||||
|         optimizeMultipleSequentialLineInstrs() |  | ||||||
|         optimizeCallReturnIntoJump() |  | ||||||
|         optimizeConditionalBranches() |  | ||||||
|         // todo: add more optimizations to intermediate code! |  | ||||||
|  |  | ||||||
|         optimizeRemoveNops()    //  must be done as the last step |  | ||||||
|         optimizeMultipleSequentialLineInstrs()      // once more |  | ||||||
|         optimizeRemoveNops()    // once more |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun optimizeConditionalBranches() { |  | ||||||
|         // conditional branches that consume the value on the stack |  | ||||||
|         // sometimes these are just constant values, so we can statically determine the branch |  | ||||||
|         // or, they are preceded by a NOT instruction so we can simply remove that and flip the branch condition |  | ||||||
|         val pushvalue = setOf(Opcode.PUSH_BYTE, Opcode.PUSH_WORD) |  | ||||||
|         val notvalue = setOf(Opcode.NOT_BYTE, Opcode.NOT_WORD) |  | ||||||
|         val branchOpcodes = setOf(Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW) |  | ||||||
|         for(blk in blocks) { |  | ||||||
|             val instructionsToReplace = mutableMapOf<Int, Instruction>() |  | ||||||
|             blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach { |  | ||||||
|                 if (it[1].value.opcode in branchOpcodes) { |  | ||||||
|                     if (it[0].value.opcode in pushvalue) { |  | ||||||
|                         val value = it[0].value.arg!!.asBoolean |  | ||||||
|                         instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                         val replacement: Instruction = |  | ||||||
|                                 if (value) { |  | ||||||
|                                     when (it[1].value.opcode) { |  | ||||||
|                                         Opcode.JNZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel) |  | ||||||
|                                         Opcode.JNZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel) |  | ||||||
|                                         else -> Instruction(Opcode.NOP) |  | ||||||
|                                     } |  | ||||||
|                                 } else { |  | ||||||
|                                     when (it[1].value.opcode) { |  | ||||||
|                                         Opcode.JZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel) |  | ||||||
|                                         Opcode.JZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel) |  | ||||||
|                                         else -> Instruction(Opcode.NOP) |  | ||||||
|                                     } |  | ||||||
|                                 } |  | ||||||
|                         instructionsToReplace[it[1].index] = replacement |  | ||||||
|                     } |  | ||||||
|                     else if (it[0].value.opcode in notvalue) { |  | ||||||
|                         instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                         val replacement: Instruction = |  | ||||||
|                                 when (it[1].value.opcode) { |  | ||||||
|                                     Opcode.JZ -> Instruction(Opcode.JNZ, callLabel = it[1].value.callLabel) |  | ||||||
|                                     Opcode.JZW -> Instruction(Opcode.JNZW, callLabel = it[1].value.callLabel) |  | ||||||
|                                     Opcode.JNZ -> Instruction(Opcode.JZ, callLabel = it[1].value.callLabel) |  | ||||||
|                                     Opcode.JNZW -> Instruction(Opcode.JZW, callLabel = it[1].value.callLabel) |  | ||||||
|                                     else -> Instruction(Opcode.NOP) |  | ||||||
|                                 } |  | ||||||
|                         instructionsToReplace[it[1].index] = replacement |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             for (rins in instructionsToReplace) { |  | ||||||
|                 blk.instructions[rins.key] = rins.value |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun optimizeRemoveNops() { |  | ||||||
|         // remove nops (that are not a label) |  | ||||||
|         for (blk in blocks) |  | ||||||
|             blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun optimizeCallReturnIntoJump() { |  | ||||||
|         // replaces call X followed by return, by jump X |  | ||||||
|         for(blk in blocks) { |  | ||||||
|             val instructionsToReplace = mutableMapOf<Int, Instruction>() |  | ||||||
|  |  | ||||||
|             blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach { |  | ||||||
|                 if(it[0].value.opcode==Opcode.CALL && it[1].value.opcode==Opcode.RETURN) { |  | ||||||
|                     instructionsToReplace[it[1].index] = Instruction(Opcode.JUMP, callLabel = it[0].value.callLabel) |  | ||||||
|                     instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             for (rins in instructionsToReplace) { |  | ||||||
|                 blk.instructions[rins.key] = rins.value |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun optimizeMultipleSequentialLineInstrs() { |  | ||||||
|         for(blk in blocks) { |  | ||||||
|             val instructionsToReplace = mutableMapOf<Int, Instruction>() |  | ||||||
|  |  | ||||||
|             blk.instructions.asSequence().withIndex().windowed(2).toList().forEach { |  | ||||||
|                 if (it[0].value.opcode == Opcode.LINE && it[1].value.opcode == Opcode.LINE) |  | ||||||
|                     instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             for (rins in instructionsToReplace) { |  | ||||||
|                 blk.instructions[rins.key] = rins.value |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun optimizeVariableCopying() { |  | ||||||
|         for(blk in blocks) { |  | ||||||
|  |  | ||||||
|             val instructionsToReplace = mutableMapOf<Int, Instruction>() |  | ||||||
|  |  | ||||||
|             blk.instructions.asSequence().withIndex().windowed(2).toList().forEach { |  | ||||||
|                 when (it[0].value.opcode) { |  | ||||||
|                     Opcode.PUSH_VAR_BYTE -> |  | ||||||
|                         if (it[1].value.opcode == Opcode.POP_VAR_BYTE) { |  | ||||||
|                             if (it[0].value.callLabel == it[1].value.callLabel) { |  | ||||||
|                                 instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                                 instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     Opcode.PUSH_VAR_WORD -> |  | ||||||
|                         if (it[1].value.opcode == Opcode.POP_VAR_WORD) { |  | ||||||
|                             if (it[0].value.callLabel == it[1].value.callLabel) { |  | ||||||
|                                 instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                                 instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     Opcode.PUSH_VAR_FLOAT -> |  | ||||||
|                         if (it[1].value.opcode == Opcode.POP_VAR_FLOAT) { |  | ||||||
|                             if (it[0].value.callLabel == it[1].value.callLabel) { |  | ||||||
|                                 instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                                 instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> |  | ||||||
|                         if(it[1].value.opcode == Opcode.POP_MEM_BYTE) { |  | ||||||
|                             if(it[0].value.arg == it[1].value.arg) { |  | ||||||
|                                 instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                                 instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> |  | ||||||
|                         if(it[1].value.opcode == Opcode.POP_MEM_WORD) { |  | ||||||
|                             if(it[0].value.arg == it[1].value.arg) { |  | ||||||
|                                 instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                                 instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     Opcode.PUSH_MEM_FLOAT -> |  | ||||||
|                         if(it[1].value.opcode == Opcode.POP_MEM_FLOAT) { |  | ||||||
|                             if(it[0].value.arg == it[1].value.arg) { |  | ||||||
|                                 instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) |  | ||||||
|                                 instructionsToReplace[it[1].index] = Instruction(Opcode.NOP) |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     else -> {} |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             for (rins in instructionsToReplace) { |  | ||||||
|                 blk.instructions[rins.key] = rins.value |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun optimizeDataConversionAndUselessDiscards() { |  | ||||||
|         // - push value followed by a data type conversion -> push the value in the correct type and remove the conversion |  | ||||||
|         // - push something followed by a discard -> remove both |  | ||||||
|         val instructionsToReplace = mutableMapOf<Int, Instruction>() |  | ||||||
|  |  | ||||||
|         fun optimizeDiscardAfterPush(index0: Int, index1: Int, ins1: Instruction) { |  | ||||||
|             if (ins1.opcode == Opcode.DISCARD_FLOAT || ins1.opcode == Opcode.DISCARD_WORD || ins1.opcode == Opcode.DISCARD_BYTE) { |  | ||||||
|                 instructionsToReplace[index0] = Instruction(Opcode.NOP) |  | ||||||
|                 instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun optimizeFloatConversion(index0: Int, index1: Int, ins1: Instruction) { |  | ||||||
|             when (ins1.opcode) { |  | ||||||
|                 Opcode.DISCARD_FLOAT -> { |  | ||||||
|                     instructionsToReplace[index0] = Instruction(Opcode.NOP) |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.DISCARD_BYTE, Opcode.DISCARD_WORD -> throw CompilerException("invalid discard type following a float") |  | ||||||
|                 else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a float") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun optimizeWordConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) { |  | ||||||
|             when (ins1.opcode) { |  | ||||||
|                 Opcode.CAST_UW_TO_B, Opcode.CAST_W_TO_B -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_BYTE, ins0.arg!!.cast(DataType.BYTE)) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() and 255)) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.MSB -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255)) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble())) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_UW_TO_W -> { |  | ||||||
|                     val cv = ins0.arg!!.cast(DataType.WORD) |  | ||||||
|                     instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv) |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_W_TO_UW -> { |  | ||||||
|                     val cv = ins0.arg!!.cast(DataType.UWORD) |  | ||||||
|                     instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv) |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.DISCARD_WORD -> { |  | ||||||
|                     instructionsToReplace[index0] = Instruction(Opcode.NOP) |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.DISCARD_BYTE, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte") |  | ||||||
|                 else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a word") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         fun optimizeByteConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) { |  | ||||||
|             when (ins1.opcode) { |  | ||||||
|                 Opcode.CAST_B_TO_UB, Opcode.CAST_UB_TO_B, |  | ||||||
|                 Opcode.CAST_W_TO_B, Opcode.CAST_W_TO_UB, |  | ||||||
|                 Opcode.CAST_UW_TO_B, Opcode.CAST_UW_TO_UB -> instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 Opcode.MSB -> throw CompilerException("msb of a byte") |  | ||||||
|                 Opcode.CAST_UB_TO_UW -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.UWORD, ins0.arg!!.integerValue())) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_B_TO_W -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.WORD, ins0.arg!!.integerValue())) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_B_TO_UW -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.UWORD)) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_UB_TO_W -> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.WORD)) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F-> { |  | ||||||
|                     val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble())) |  | ||||||
|                     instructionsToReplace[index0] = ins |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F-> throw CompilerException("invalid conversion following a byte") |  | ||||||
|                 Opcode.DISCARD_BYTE -> { |  | ||||||
|                     instructionsToReplace[index0] = Instruction(Opcode.NOP) |  | ||||||
|                     instructionsToReplace[index1] = Instruction(Opcode.NOP) |  | ||||||
|                 } |  | ||||||
|                 Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte") |  | ||||||
|                 Opcode.MKWORD -> {} |  | ||||||
|                 else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         for(blk in blocks) { |  | ||||||
|             instructionsToReplace.clear() |  | ||||||
|  |  | ||||||
|             val typeConversionOpcodes = setOf( |  | ||||||
|                     Opcode.MSB, |  | ||||||
|                     Opcode.MKWORD, |  | ||||||
|                     Opcode.CAST_UB_TO_B, |  | ||||||
|                     Opcode.CAST_UB_TO_UW, |  | ||||||
|                     Opcode.CAST_UB_TO_W, |  | ||||||
|                     Opcode.CAST_UB_TO_F, |  | ||||||
|                     Opcode.CAST_B_TO_UB, |  | ||||||
|                     Opcode.CAST_B_TO_UW, |  | ||||||
|                     Opcode.CAST_B_TO_W, |  | ||||||
|                     Opcode.CAST_B_TO_F, |  | ||||||
|                     Opcode.CAST_UW_TO_UB, |  | ||||||
|                     Opcode.CAST_UW_TO_B, |  | ||||||
|                     Opcode.CAST_UW_TO_W, |  | ||||||
|                     Opcode.CAST_UW_TO_F, |  | ||||||
|                     Opcode.CAST_W_TO_UB, |  | ||||||
|                     Opcode.CAST_W_TO_B, |  | ||||||
|                     Opcode.CAST_W_TO_UW, |  | ||||||
|                     Opcode.CAST_W_TO_F, |  | ||||||
|                     Opcode.CAST_F_TO_UB, |  | ||||||
|                     Opcode.CAST_F_TO_B, |  | ||||||
|                     Opcode.CAST_F_TO_UW, |  | ||||||
|                     Opcode.CAST_F_TO_W, |  | ||||||
|                     Opcode.DISCARD_BYTE, |  | ||||||
|                     Opcode.DISCARD_WORD, |  | ||||||
|                     Opcode.DISCARD_FLOAT |  | ||||||
|             ) |  | ||||||
|             blk.instructions.asSequence().withIndex().windowed(2).toList().forEach { |  | ||||||
|                 if (it[1].value.opcode in typeConversionOpcodes) { |  | ||||||
|                     when (it[0].value.opcode) { |  | ||||||
|                         Opcode.PUSH_BYTE -> optimizeByteConversion(it[0].index, it[0].value, it[1].index, it[1].value) |  | ||||||
|                         Opcode.PUSH_WORD -> optimizeWordConversion(it[0].index, it[0].value, it[1].index, it[1].value) |  | ||||||
|                         Opcode.PUSH_FLOAT -> optimizeFloatConversion(it[0].index, it[1].index, it[1].value) |  | ||||||
|                         Opcode.PUSH_VAR_FLOAT, |  | ||||||
|                         Opcode.PUSH_VAR_WORD, |  | ||||||
|                         Opcode.PUSH_VAR_BYTE, |  | ||||||
|                         Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB, |  | ||||||
|                         Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW, |  | ||||||
|                         Opcode.PUSH_MEM_FLOAT -> optimizeDiscardAfterPush(it[0].index, it[1].index, it[1].value) |  | ||||||
|                         else -> { |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             for (rins in instructionsToReplace) { |  | ||||||
|                 blk.instructions[rins.key] = rins.value |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun variable(scopedname: String, decl: VarDecl) { |  | ||||||
|         when(decl.type) { |  | ||||||
|             VarDeclType.VAR -> { |  | ||||||
|                 val value = when(decl.datatype) { |  | ||||||
|                     in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!) |  | ||||||
|                     in StringDatatypes -> { |  | ||||||
|                         val litval = (decl.value as LiteralValue) |  | ||||||
|                         if(litval.heapId==null) |  | ||||||
|                             throw CompilerException("string should already be in the heap") |  | ||||||
|                         RuntimeValue(decl.datatype, heapId = litval.heapId) |  | ||||||
|                     } |  | ||||||
|                     in ArrayDatatypes -> { |  | ||||||
|                         val litval = (decl.value as LiteralValue) |  | ||||||
|                         if(litval.heapId==null) |  | ||||||
|                             throw CompilerException("array should already be in the heap") |  | ||||||
|                         RuntimeValue(decl.datatype, heapId = litval.heapId) |  | ||||||
|                     } |  | ||||||
|                     else -> throw CompilerException("weird datatype") |  | ||||||
|                 } |  | ||||||
|                 currentBlock.variables[scopedname] = value |  | ||||||
|                 if(decl.zeropage) |  | ||||||
|                     currentBlock.variablesMarkedForZeropage.add(scopedname) |  | ||||||
|             } |  | ||||||
|             VarDeclType.MEMORY -> { |  | ||||||
|                 // note that constants are all folded away, but assembly code may still refer to them |  | ||||||
|                 val lv = decl.value as LiteralValue |  | ||||||
|                 if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE) |  | ||||||
|                     throw CompilerException("expected integer memory address $lv") |  | ||||||
|                 currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype) |  | ||||||
|             } |  | ||||||
|             VarDeclType.CONST -> { |  | ||||||
|                 // note that constants are all folded away, but assembly code may still refer to them (if their integers) |  | ||||||
|                 // floating point constants are not generated at all!! |  | ||||||
|                 val lv = decl.value as LiteralValue |  | ||||||
|                 if(lv.type in IntegerDatatypes) |  | ||||||
|                     currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun instr(opcode: Opcode, arg: RuntimeValue? = null, arg2: RuntimeValue? = null, callLabel: String? = null, callLabel2: String? = null) { |  | ||||||
|         currentBlock.instructions.add(Instruction(opcode, arg, arg2, callLabel, callLabel2)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun label(labelname: String, asmProc: Boolean=false) { |  | ||||||
|         val instr = LabelInstr(labelname, asmProc) |  | ||||||
|         currentBlock.instructions.add(instr) |  | ||||||
|         currentBlock.labels[labelname] = instr |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun line(position: Position) { |  | ||||||
|         currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}")) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun removeLastInstruction() { |  | ||||||
|         currentBlock.instructions.removeAt(currentBlock.instructions.lastIndex) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun memoryPointer(name: String, address: Int, datatype: DataType) { |  | ||||||
|         currentBlock.memoryPointers[name] = Pair(address, datatype) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun newBlock(name: String, address: Int?, options: Set<String>) { |  | ||||||
|         currentBlock = ProgramBlock(name, address, force_output="force_output" in options) |  | ||||||
|         blocks.add(currentBlock) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) { |  | ||||||
|         out.println("; stackVM program code for '$name'") |  | ||||||
|         writeMemory(out) |  | ||||||
|         writeHeap(out) |  | ||||||
|         for(blk in blocks) { |  | ||||||
|             writeBlock(out, blk, embeddedLabels) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun writeHeap(out: PrintStream) { |  | ||||||
|         out.println("%heap") |  | ||||||
|         heap.allEntries().forEach { |  | ||||||
|             out.print("${it.key}  ${it.value.type.name.toLowerCase()}  ") |  | ||||||
|             when { |  | ||||||
|                 it.value.str!=null -> |  | ||||||
|                     out.println("\"${escape(it.value.str!!)}\"") |  | ||||||
|                 it.value.array!=null -> { |  | ||||||
|                     // this array can contain both normal integers, and pointer values |  | ||||||
|                     val arrayvalues = it.value.array!!.map { av -> |  | ||||||
|                         when { |  | ||||||
|                             av.integer!=null -> av.integer.toString() |  | ||||||
|                             av.addressOf!=null -> { |  | ||||||
|                                 if(av.addressOf.scopedname==null) |  | ||||||
|                                     throw CompilerException("AddressOf scopedname should have been set") |  | ||||||
|                                 else |  | ||||||
|                                     "&${av.addressOf.scopedname}" |  | ||||||
|                             } |  | ||||||
|                             else -> throw CompilerException("weird array value") |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     out.println(arrayvalues) |  | ||||||
|                 } |  | ||||||
|                 it.value.doubleArray!=null -> |  | ||||||
|                     out.println(it.value.doubleArray!!.toList()) |  | ||||||
|                 else -> throw CompilerException("invalid heap entry $it") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         out.println("%end_heap") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun writeBlock(out: PrintStream, blk: ProgramBlock, embeddedLabels: Boolean) { |  | ||||||
|         out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}") |  | ||||||
|  |  | ||||||
|         out.println("%variables") |  | ||||||
|         for (variable in blk.variables) { |  | ||||||
|             val valuestr = variable.value.toString() |  | ||||||
|             out.println("${variable.key}  ${variable.value.type.name.toLowerCase()}  $valuestr") |  | ||||||
|         } |  | ||||||
|         out.println("%end_variables") |  | ||||||
|         out.println("%memorypointers") |  | ||||||
|         for (iconst in blk.memoryPointers) { |  | ||||||
|             out.println("${iconst.key}  ${iconst.value.second.name.toLowerCase()}  uw:${iconst.value.first.toString(16)}") |  | ||||||
|         } |  | ||||||
|         out.println("%end_memorypointers") |  | ||||||
|         out.println("%instructions") |  | ||||||
|         val labels = blk.labels.entries.associateBy({ it.value }) { it.key } |  | ||||||
|         for (instr in blk.instructions) { |  | ||||||
|             if (!embeddedLabels) { |  | ||||||
|                 val label = labels[instr] |  | ||||||
|                 if (label != null) |  | ||||||
|                     out.println("$label:") |  | ||||||
|             } else { |  | ||||||
|                 out.println(instr) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         out.println("%end_instructions") |  | ||||||
|  |  | ||||||
|         out.println("%end_block") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun writeMemory(out: PrintStream) { |  | ||||||
|         out.println("%memory") |  | ||||||
|         if (memory.isNotEmpty()) |  | ||||||
|             TODO("add support for writing/reading initial memory values") |  | ||||||
|         out.println("%end_memory") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,291 +0,0 @@ | |||||||
| package prog8.compiler.intermediate |  | ||||||
|  |  | ||||||
| enum class Opcode { |  | ||||||
|  |  | ||||||
|     // pushing values on the (evaluation) stack |  | ||||||
|     PUSH_BYTE,       // push byte value |  | ||||||
|     PUSH_WORD,       // push word value   (or 'address' of string / array) |  | ||||||
|     PUSH_FLOAT,      // push float value |  | ||||||
|     PUSH_MEM_B,      // push byte value from memory to stack |  | ||||||
|     PUSH_MEM_UB,     // push unsigned byte value from memory to stack |  | ||||||
|     PUSH_MEM_W,      // push word value from memory to stack |  | ||||||
|     PUSH_MEM_UW,     // push unsigned word value from memory to stack |  | ||||||
|     PUSH_MEM_FLOAT,  // push float value from memory to stack |  | ||||||
|     PUSH_MEMREAD,    // push memory value from address that's on the stack |  | ||||||
|     PUSH_VAR_BYTE,   // push byte variable (ubyte, byte) |  | ||||||
|     PUSH_VAR_WORD,   // push word variable (uword, word) |  | ||||||
|     PUSH_VAR_FLOAT,  // push float variable |  | ||||||
|     PUSH_REGAX_WORD, // push registers A/X as a 16-bit word |  | ||||||
|     PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word |  | ||||||
|     PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word |  | ||||||
|     PUSH_ADDR_HEAPVAR,  // push the address of the variable that's on the heap (string or array) |  | ||||||
|     DUP_B,          // duplicate the top byte on the stack |  | ||||||
|     DUP_W,          // duplicate the top word on the stack |  | ||||||
|  |  | ||||||
|     // popping values off the (evaluation) stack, possibly storing them in another location |  | ||||||
|     DISCARD_BYTE,    // discard top byte value |  | ||||||
|     DISCARD_WORD,    // discard top word value |  | ||||||
|     DISCARD_FLOAT,   // discard top float value |  | ||||||
|     POP_MEM_BYTE,    // pop (u)byte value into destination memory address |  | ||||||
|     POP_MEM_WORD,    // pop (u)word value into destination memory address |  | ||||||
|     POP_MEM_FLOAT,   // pop float value into destination memory address |  | ||||||
|     POP_MEMWRITE,    // pop address and byte stack and write the byte to the memory address |  | ||||||
|     POP_VAR_BYTE,    // pop (u)byte value into variable |  | ||||||
|     POP_VAR_WORD,    // pop (u)word value into variable |  | ||||||
|     POP_VAR_FLOAT,   // pop float value into variable |  | ||||||
|     POP_REGAX_WORD,  // pop uword from stack into A/X registers |  | ||||||
|     POP_REGAY_WORD,  // pop uword from stack into A/Y registers |  | ||||||
|     POP_REGXY_WORD,  // pop uword from stack into X/Y registers |  | ||||||
|  |  | ||||||
|     // numeric arithmetic |  | ||||||
|     ADD_UB, |  | ||||||
|     ADD_B, |  | ||||||
|     ADD_UW, |  | ||||||
|     ADD_W, |  | ||||||
|     ADD_F, |  | ||||||
|     SUB_UB, |  | ||||||
|     SUB_B, |  | ||||||
|     SUB_UW, |  | ||||||
|     SUB_W, |  | ||||||
|     SUB_F, |  | ||||||
|     MUL_UB, |  | ||||||
|     MUL_B, |  | ||||||
|     MUL_UW, |  | ||||||
|     MUL_W, |  | ||||||
|     MUL_F, |  | ||||||
|     IDIV_UB, |  | ||||||
|     IDIV_B, |  | ||||||
|     IDIV_UW, |  | ||||||
|     IDIV_W, |  | ||||||
|     DIV_F, |  | ||||||
|     REMAINDER_UB,   // signed remainder is undefined/unimplemented |  | ||||||
|     REMAINDER_UW,   // signed remainder is undefined/unimplemented |  | ||||||
|     POW_F, |  | ||||||
|     NEG_B, |  | ||||||
|     NEG_W, |  | ||||||
|     NEG_F, |  | ||||||
|     ABS_B, |  | ||||||
|     ABS_W, |  | ||||||
|     ABS_F, |  | ||||||
|  |  | ||||||
|     // bit shifts and bitwise arithmetic |  | ||||||
|     SHIFTEDL_BYTE,      // shifts stack value rather than in-place mem/var |  | ||||||
|     SHIFTEDL_WORD,      // shifts stack value rather than in-place mem/var |  | ||||||
|     SHIFTEDR_UBYTE,     // shifts stack value rather than in-place mem/var |  | ||||||
|     SHIFTEDR_SBYTE,     // shifts stack value rather than in-place mem/var |  | ||||||
|     SHIFTEDR_UWORD,     // shifts stack value rather than in-place mem/var |  | ||||||
|     SHIFTEDR_SWORD,     // shifts stack value rather than in-place mem/var |  | ||||||
|     SHL_BYTE, |  | ||||||
|     SHL_WORD, |  | ||||||
|     SHL_MEM_BYTE, |  | ||||||
|     SHL_MEM_WORD, |  | ||||||
|     SHL_VAR_BYTE, |  | ||||||
|     SHL_VAR_WORD, |  | ||||||
|     SHR_UBYTE, |  | ||||||
|     SHR_SBYTE, |  | ||||||
|     SHR_UWORD, |  | ||||||
|     SHR_SWORD, |  | ||||||
|     SHR_MEM_UBYTE, |  | ||||||
|     SHR_MEM_SBYTE, |  | ||||||
|     SHR_MEM_UWORD, |  | ||||||
|     SHR_MEM_SWORD, |  | ||||||
|     SHR_VAR_UBYTE, |  | ||||||
|     SHR_VAR_SBYTE, |  | ||||||
|     SHR_VAR_UWORD, |  | ||||||
|     SHR_VAR_SWORD, |  | ||||||
|     ROL_BYTE, |  | ||||||
|     ROL_WORD, |  | ||||||
|     ROL_MEM_BYTE, |  | ||||||
|     ROL_MEM_WORD, |  | ||||||
|     ROL_VAR_BYTE, |  | ||||||
|     ROL_VAR_WORD, |  | ||||||
|     ROR_BYTE, |  | ||||||
|     ROR_WORD, |  | ||||||
|     ROR_MEM_BYTE, |  | ||||||
|     ROR_MEM_WORD, |  | ||||||
|     ROR_VAR_BYTE, |  | ||||||
|     ROR_VAR_WORD, |  | ||||||
|     ROL2_BYTE, |  | ||||||
|     ROL2_WORD, |  | ||||||
|     ROL2_MEM_BYTE, |  | ||||||
|     ROL2_MEM_WORD, |  | ||||||
|     ROL2_VAR_BYTE, |  | ||||||
|     ROL2_VAR_WORD, |  | ||||||
|     ROR2_BYTE, |  | ||||||
|     ROR2_WORD, |  | ||||||
|     ROR2_MEM_BYTE, |  | ||||||
|     ROR2_MEM_WORD, |  | ||||||
|     ROR2_VAR_BYTE, |  | ||||||
|     ROR2_VAR_WORD, |  | ||||||
|     BITAND_BYTE, |  | ||||||
|     BITAND_WORD, |  | ||||||
|     BITOR_BYTE, |  | ||||||
|     BITOR_WORD, |  | ||||||
|     BITXOR_BYTE, |  | ||||||
|     BITXOR_WORD, |  | ||||||
|     INV_BYTE, |  | ||||||
|     INV_WORD, |  | ||||||
|  |  | ||||||
|     // numeric type conversions |  | ||||||
|     MSB,        // note: lsb is equivalent to  CAST_UW_TO_UB  or CAST_W_TO_UB |  | ||||||
|     MKWORD,        // create a word from lsb + msb |  | ||||||
|     CAST_UB_TO_B, |  | ||||||
|     CAST_UB_TO_UW, |  | ||||||
|     CAST_UB_TO_W, |  | ||||||
|     CAST_UB_TO_F, |  | ||||||
|     CAST_B_TO_UB, |  | ||||||
|     CAST_B_TO_UW, |  | ||||||
|     CAST_B_TO_W, |  | ||||||
|     CAST_B_TO_F, |  | ||||||
|     CAST_W_TO_UB, |  | ||||||
|     CAST_W_TO_B, |  | ||||||
|     CAST_W_TO_UW, |  | ||||||
|     CAST_W_TO_F, |  | ||||||
|     CAST_UW_TO_UB, |  | ||||||
|     CAST_UW_TO_B, |  | ||||||
|     CAST_UW_TO_W, |  | ||||||
|     CAST_UW_TO_F, |  | ||||||
|     CAST_F_TO_UB, |  | ||||||
|     CAST_F_TO_B, |  | ||||||
|     CAST_F_TO_UW, |  | ||||||
|     CAST_F_TO_W, |  | ||||||
|  |  | ||||||
|     // logical operations |  | ||||||
|     AND_BYTE, |  | ||||||
|     AND_WORD, |  | ||||||
|     OR_BYTE, |  | ||||||
|     OR_WORD, |  | ||||||
|     XOR_BYTE, |  | ||||||
|     XOR_WORD, |  | ||||||
|     NOT_BYTE, |  | ||||||
|     NOT_WORD, |  | ||||||
|  |  | ||||||
|     // increment, decrement |  | ||||||
|     INC_VAR_B, |  | ||||||
|     INC_VAR_UB, |  | ||||||
|     INC_VAR_W, |  | ||||||
|     INC_VAR_UW, |  | ||||||
|     INC_VAR_F, |  | ||||||
|     DEC_VAR_B, |  | ||||||
|     DEC_VAR_UB, |  | ||||||
|     DEC_VAR_W, |  | ||||||
|     DEC_VAR_UW, |  | ||||||
|     DEC_VAR_F, |  | ||||||
|     INC_MEMORY,             // increment direct address |  | ||||||
|     DEC_MEMORY,             // decrement direct address |  | ||||||
|     POP_INC_MEMORY,         // increment address from stack |  | ||||||
|     POP_DEC_MEMORY,         // decrement address from address |  | ||||||
|  |  | ||||||
|     // comparisons |  | ||||||
|     LESS_B, |  | ||||||
|     LESS_UB, |  | ||||||
|     LESS_W, |  | ||||||
|     LESS_UW, |  | ||||||
|     LESS_F, |  | ||||||
|     GREATER_B, |  | ||||||
|     GREATER_UB, |  | ||||||
|     GREATER_W, |  | ||||||
|     GREATER_UW, |  | ||||||
|     GREATER_F, |  | ||||||
|     LESSEQ_B, |  | ||||||
|     LESSEQ_UB, |  | ||||||
|     LESSEQ_W, |  | ||||||
|     LESSEQ_UW, |  | ||||||
|     LESSEQ_F, |  | ||||||
|     GREATEREQ_B, |  | ||||||
|     GREATEREQ_UB, |  | ||||||
|     GREATEREQ_W, |  | ||||||
|     GREATEREQ_UW, |  | ||||||
|     GREATEREQ_F, |  | ||||||
|     EQUAL_BYTE, |  | ||||||
|     EQUAL_WORD, |  | ||||||
|     EQUAL_F, |  | ||||||
|     NOTEQUAL_BYTE, |  | ||||||
|     NOTEQUAL_WORD, |  | ||||||
|     NOTEQUAL_F, |  | ||||||
|     CMP_B,          // sets processor status flags based on comparison, instead of pushing a result value |  | ||||||
|     CMP_UB,         // sets processor status flags based on comparison, instead of pushing a result value |  | ||||||
|     CMP_W,          // sets processor status flags based on comparison, instead of pushing a result value |  | ||||||
|     CMP_UW,         // sets processor status flags based on comparison, instead of pushing a result value |  | ||||||
|  |  | ||||||
|     // array access and simple manipulations |  | ||||||
|     READ_INDEXED_VAR_BYTE, |  | ||||||
|     READ_INDEXED_VAR_WORD, |  | ||||||
|     READ_INDEXED_VAR_FLOAT, |  | ||||||
|     WRITE_INDEXED_VAR_BYTE, |  | ||||||
|     WRITE_INDEXED_VAR_WORD, |  | ||||||
|     WRITE_INDEXED_VAR_FLOAT, |  | ||||||
|     INC_INDEXED_VAR_B, |  | ||||||
|     INC_INDEXED_VAR_UB, |  | ||||||
|     INC_INDEXED_VAR_W, |  | ||||||
|     INC_INDEXED_VAR_UW, |  | ||||||
|     INC_INDEXED_VAR_FLOAT, |  | ||||||
|     DEC_INDEXED_VAR_B, |  | ||||||
|     DEC_INDEXED_VAR_UB, |  | ||||||
|     DEC_INDEXED_VAR_W, |  | ||||||
|     DEC_INDEXED_VAR_UW, |  | ||||||
|     DEC_INDEXED_VAR_FLOAT, |  | ||||||
|  |  | ||||||
|     // branching, without consuming a value from the stack |  | ||||||
|     JUMP, |  | ||||||
|     BCS,       // branch if carry set |  | ||||||
|     BCC,       // branch if carry clear |  | ||||||
|     BZ,        // branch if zero flag |  | ||||||
|     BNZ,       // branch if not zero flag |  | ||||||
|     BNEG,      // branch if negative flag |  | ||||||
|     BPOS,      // branch if not negative flag |  | ||||||
|     BVS,       // branch if overflow flag |  | ||||||
|     BVC,       // branch if not overflow flag |  | ||||||
|     // branching, based on value on the stack (which is consumed) |  | ||||||
|     JZ,         // branch if value is zero (byte) |  | ||||||
|     JNZ,        // branch if value is not zero (byte) |  | ||||||
|     JZW,         // branch if value is zero (word) |  | ||||||
|     JNZW,        // branch if value is not zero (word) |  | ||||||
|  |  | ||||||
|     // subroutines |  | ||||||
|     CALL, |  | ||||||
|     RETURN, |  | ||||||
|     SYSCALL, |  | ||||||
|     START_PROCDEF, |  | ||||||
|     END_PROCDEF, |  | ||||||
|  |  | ||||||
|     // misc |  | ||||||
|     SEC,        // set carry status flag  NOTE: is mostly fake, carry flag is not affected by any numeric operations |  | ||||||
|     CLC,        // clear carry status flag  NOTE: is mostly fake, carry flag is not affected by any numeric operations |  | ||||||
|     SEI,        // set irq-disable status flag |  | ||||||
|     CLI,        // clear irq-disable status flag |  | ||||||
|     CARRY_TO_A, // load var/register A with carry status bit |  | ||||||
|     RSAVE,      // save all internal registers and status flags |  | ||||||
|     RSAVEX,     // save just X (the evaluation stack pointer) |  | ||||||
|     RRESTORE,   // restore all internal registers and status flags |  | ||||||
|     RRESTOREX,  // restore just X (the evaluation stack pointer) |  | ||||||
|  |  | ||||||
|     NOP,        // do nothing |  | ||||||
|     BREAKPOINT, // breakpoint |  | ||||||
|     TERMINATE,  // end the program |  | ||||||
|     LINE,       // track source file line number |  | ||||||
|     INLINE_ASSEMBLY,        // container to hold inline raw assembly code |  | ||||||
|     INCLUDE_FILE            // directive to include a file at this position in the memory of the program |  | ||||||
| } |  | ||||||
|  |  | ||||||
| val opcodesWithVarArgument = setOf( |  | ||||||
|         Opcode.INC_VAR_B, Opcode.INC_VAR_W, Opcode.DEC_VAR_B, Opcode.DEC_VAR_W, |  | ||||||
|         Opcode.INC_VAR_UB, Opcode.INC_VAR_UW, Opcode.DEC_VAR_UB, Opcode.DEC_VAR_UW, |  | ||||||
|         Opcode.SHR_VAR_SBYTE, Opcode.SHR_VAR_UBYTE, Opcode.SHR_VAR_SWORD, Opcode.SHR_VAR_UWORD, |  | ||||||
|         Opcode.SHL_VAR_BYTE, Opcode.SHL_VAR_WORD, |  | ||||||
|         Opcode.ROL_VAR_BYTE, Opcode.ROL_VAR_WORD, Opcode.ROR_VAR_BYTE, Opcode.ROR_VAR_WORD, |  | ||||||
|         Opcode.ROL2_VAR_BYTE, Opcode.ROL2_VAR_WORD, Opcode.ROR2_VAR_BYTE, Opcode.ROR2_VAR_WORD, |  | ||||||
|         Opcode.POP_VAR_BYTE, Opcode.POP_VAR_WORD, Opcode.POP_VAR_FLOAT, |  | ||||||
|         Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_WORD, Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_ADDR_HEAPVAR, |  | ||||||
|         Opcode.READ_INDEXED_VAR_BYTE, Opcode.READ_INDEXED_VAR_WORD, Opcode.READ_INDEXED_VAR_FLOAT, |  | ||||||
|         Opcode.WRITE_INDEXED_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD, Opcode.WRITE_INDEXED_VAR_FLOAT, |  | ||||||
|         Opcode.INC_INDEXED_VAR_UB, Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UW, |  | ||||||
|         Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT, |  | ||||||
|         Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UW, |  | ||||||
|         Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_FLOAT |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| val branchOpcodes = setOf( |  | ||||||
|         Opcode.BCS, Opcode.BCC, Opcode.BZ, Opcode.BNZ, |  | ||||||
|         Opcode.BNEG, Opcode.BPOS, Opcode.BVS, Opcode.BVC |  | ||||||
| ) |  | ||||||
							
								
								
									
										16
									
								
								compiler/src/prog8/compiler/target/IAssemblyGenerator.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								compiler/src/prog8/compiler/target/IAssemblyGenerator.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | package prog8.compiler.target | ||||||
|  |  | ||||||
|  | import prog8.compiler.CompilationOptions | ||||||
|  |  | ||||||
|  | internal interface IAssemblyGenerator { | ||||||
|  |     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 | ||||||
|  |     fun assemble(options: CompilationOptions) | ||||||
|  | } | ||||||
							
								
								
									
										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) | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								compiler/src/prog8/compiler/target/IMachineDefinition.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								compiler/src/prog8/compiler/target/IMachineDefinition.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | package prog8.compiler.target | ||||||
|  |  | ||||||
|  | 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 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,735 +0,0 @@ | |||||||
| package prog8.compiler.target.c64 |  | ||||||
|  |  | ||||||
| // note: to put stuff on the stack, we use Absolute,X  addressing mode which is 3 bytes / 4 cycles |  | ||||||
| // possible space optimization is to use zeropage (indirect),Y  which is 2 bytes, but 5 cycles |  | ||||||
|  |  | ||||||
| import prog8.ast.antlr.escape |  | ||||||
| import prog8.ast.base.DataType |  | ||||||
| import prog8.ast.base.initvarsSubName |  | ||||||
| import prog8.vm.RuntimeValue |  | ||||||
| import prog8.compiler.* |  | ||||||
| import prog8.compiler.intermediate.* |  | ||||||
| import java.io.File |  | ||||||
| import java.util.* |  | ||||||
| import kotlin.math.abs |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AssemblyError(msg: String) : RuntimeException(msg) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun intVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue() |  | ||||||
| internal fun hexVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue().toHex() |  | ||||||
| internal fun hexValPlusOne(valueInstr: Instruction) = (valueInstr.arg!!.integerValue()+1).toHex() |  | ||||||
| internal fun getFloatConst(value: RuntimeValue): String = |  | ||||||
|         globalFloatConsts[value.numericValue().toDouble()] |  | ||||||
|                 ?: throw AssemblyError("should have a global float const for number $value") |  | ||||||
|  |  | ||||||
| internal val globalFloatConsts = mutableMapOf<Double, String>() |  | ||||||
|  |  | ||||||
| internal fun signExtendA(into: String) = |  | ||||||
|         """ |  | ||||||
|         ora  #$7f |  | ||||||
|         bmi  + |  | ||||||
|         lda  #0 |  | ||||||
| +       sta  $into |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
| class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram, |  | ||||||
|              private val heap: HeapValues, private val zeropage: Zeropage) { |  | ||||||
|     private val assemblyLines = mutableListOf<String>() |  | ||||||
|     private lateinit var block: IntermediateProgram.ProgramBlock |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         // Convert invalid label names (such as "<anon-1>") to something that's allowed. |  | ||||||
|         val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>() |  | ||||||
|         for(block in program.blocks) { |  | ||||||
|             val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap() |  | ||||||
|             val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet() |  | ||||||
|             val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap() |  | ||||||
|             val newinstructions = block.instructions.asSequence().map { |  | ||||||
|                 when { |  | ||||||
|                     it is LabelInstr -> LabelInstr(symname(it.name, block), it.asmProc) |  | ||||||
|                     it.opcode == Opcode.INLINE_ASSEMBLY -> it |  | ||||||
|                     else -> |  | ||||||
|                         Instruction(it.opcode, it.arg, it.arg2, |  | ||||||
|                             callLabel = if (it.callLabel != null) symname(it.callLabel, block) else null, |  | ||||||
|                             callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null) |  | ||||||
|                 } |  | ||||||
|             }.toMutableList() |  | ||||||
|             val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap() |  | ||||||
|             val newblock = IntermediateProgram.ProgramBlock( |  | ||||||
|                     block.name, |  | ||||||
|                     block.address, |  | ||||||
|                     newinstructions, |  | ||||||
|                     newvars, |  | ||||||
|                     newMempointers, |  | ||||||
|                     newlabels, |  | ||||||
|                     force_output = block.force_output) |  | ||||||
|             newblock.variablesMarkedForZeropage.clear() |  | ||||||
|             newblock.variablesMarkedForZeropage.addAll(newvarsZeropaged) |  | ||||||
|             newblocks.add(newblock) |  | ||||||
|         } |  | ||||||
|         program.blocks.clear() |  | ||||||
|         program.blocks.addAll(newblocks) |  | ||||||
|  |  | ||||||
|         val newAllocatedZp = program.allocatedZeropageVariables.map { symname(it.key, null) to it.value} |  | ||||||
|         program.allocatedZeropageVariables.clear() |  | ||||||
|         program.allocatedZeropageVariables.putAll(newAllocatedZp) |  | ||||||
|  |  | ||||||
|         // make a list of all const floats that are used |  | ||||||
|         for(block in program.blocks) { |  | ||||||
|             for(ins in block.instructions.filter{it.arg?.type== DataType.FLOAT}) { |  | ||||||
|                 val float = ins.arg!!.numericValue().toDouble() |  | ||||||
|                 if(float !in globalFloatConsts) |  | ||||||
|                     globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun compileToAssembly(optimize: Boolean): AssemblyProgram { |  | ||||||
|         println("Generating assembly code from intermediate code... ") |  | ||||||
|  |  | ||||||
|         assemblyLines.clear() |  | ||||||
|         header() |  | ||||||
|         for(b in program.blocks) |  | ||||||
|             block2asm(b) |  | ||||||
|  |  | ||||||
|         if(optimize) { |  | ||||||
|             var optimizationsDone = 1 |  | ||||||
|             while (optimizationsDone > 0) { |  | ||||||
|                 optimizationsDone = optimizeAssembly(assemblyLines) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         File("${program.name}.asm").printWriter().use { |  | ||||||
|             for (line in assemblyLines) { it.println(line) } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return AssemblyProgram(program.name) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun out(str: String, splitlines: Boolean=true) { |  | ||||||
|         if(splitlines) { |  | ||||||
|             for (line in str.split('\n')) { |  | ||||||
|                 val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim() |  | ||||||
|                 // trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t")  // sanitize local label indentation |  | ||||||
|                 assemblyLines.add(trimmed) |  | ||||||
|             } |  | ||||||
|         } else assemblyLines.add(str) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     // convert a fully scoped name (defined in the given block) to a valid assembly symbol name |  | ||||||
|     private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String { |  | ||||||
|         if(' ' in scoped) |  | ||||||
|             return scoped |  | ||||||
|         val blockLocal: Boolean |  | ||||||
|         var name = if (block!=null && scoped.startsWith("${block.name}.")) { |  | ||||||
|             blockLocal = true |  | ||||||
|             scoped.substring(block.name.length+1) |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             blockLocal = false |  | ||||||
|             scoped |  | ||||||
|         } |  | ||||||
|         name = name.replace("<", "prog8_").replace(">", "")     // take care of the autogenerated invalid (anon) label names |  | ||||||
|         if(name=="-") |  | ||||||
|             return "-" |  | ||||||
|         if(blockLocal) |  | ||||||
|             name = name.replace(".", "_") |  | ||||||
|         else { |  | ||||||
|             val parts = name.split(".", limit=2) |  | ||||||
|             if(parts.size>1) |  | ||||||
|                 name = "${parts[0]}.${parts[1].replace(".", "_")}" |  | ||||||
|         } |  | ||||||
|         return name.replace("-", "") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun makeFloatFill(flt: Mflpt5): String { |  | ||||||
|         val b0 = "$"+flt.b0.toString(16).padStart(2, '0') |  | ||||||
|         val b1 = "$"+flt.b1.toString(16).padStart(2, '0') |  | ||||||
|         val b2 = "$"+flt.b2.toString(16).padStart(2, '0') |  | ||||||
|         val b3 = "$"+flt.b3.toString(16).padStart(2, '0') |  | ||||||
|         val b4 = "$"+flt.b4.toString(16).padStart(2, '0') |  | ||||||
|         return "$b0, $b1, $b2, $b3, $b4" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun header() { |  | ||||||
|         val ourName = this.javaClass.name |  | ||||||
|         out("; 6502 assembly code for '${program.name}'") |  | ||||||
|         out("; generated by $ourName on ${Date()}") |  | ||||||
|         out("; assembler syntax is for the 64tasm cross-assembler") |  | ||||||
|         out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}") |  | ||||||
|         out("\n.cpu  '6502'\n.enc  'none'\n") |  | ||||||
|  |  | ||||||
|         if(program.loadAddress==0)   // fix load address |  | ||||||
|             program.loadAddress = if(options.launcher==LauncherType.BASIC) BASIC_LOAD_ADDRESS else RAW_LOAD_ADDRESS |  | ||||||
|  |  | ||||||
|         when { |  | ||||||
|             options.launcher == LauncherType.BASIC -> { |  | ||||||
|                 if (program.loadAddress != 0x0801) |  | ||||||
|                     throw AssemblyError("BASIC output must have load address $0801") |  | ||||||
|                 out("; ---- basic program with sys call ----") |  | ||||||
|                 out("* = ${program.loadAddress.toHex()}") |  | ||||||
|                 val year = Calendar.getInstance().get(Calendar.YEAR) |  | ||||||
|                 out("  .word  (+), $year") |  | ||||||
|                 out("  .null  $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'") |  | ||||||
|                 out("+\t.word  0") |  | ||||||
|                 out("_prog8_entrypoint\t; assembly code starts here\n") |  | ||||||
|                 out("  jsr  prog8_lib.init_system") |  | ||||||
|             } |  | ||||||
|             options.output == OutputType.PRG -> { |  | ||||||
|                 out("; ---- program without basic sys call ----") |  | ||||||
|                 out("* = ${program.loadAddress.toHex()}\n") |  | ||||||
|                 out("  jsr  prog8_lib.init_system") |  | ||||||
|             } |  | ||||||
|             options.output == OutputType.RAW -> { |  | ||||||
|                 out("; ---- raw assembler program ----") |  | ||||||
|                 out("* = ${program.loadAddress.toHex()}\n") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(zeropage.exitProgramStrategy!=Zeropage.ExitProgramStrategy.CLEAN_EXIT) { |  | ||||||
|             // disable shift-commodore charset switching and run/stop key |  | ||||||
|             out("  lda  #$80") |  | ||||||
|             out("  lda  #$80") |  | ||||||
|             out("  sta  657\t; disable charset switching") |  | ||||||
|             out("  lda  #239") |  | ||||||
|             out("  sta  808\t; disable run/stop key") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         out("  ldx  #\$ff\t; init estack pointer") |  | ||||||
|         out("  ; initialize the variables in each block") |  | ||||||
|         for(block in program.blocks) { |  | ||||||
|             val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name== initvarsSubName } as? LabelInstr |  | ||||||
|             if(initVarsLabel!=null) |  | ||||||
|                 out("  jsr  ${block.name}.${initVarsLabel.name}") |  | ||||||
|         } |  | ||||||
|         out("  clc") |  | ||||||
|         when(zeropage.exitProgramStrategy) { |  | ||||||
|             Zeropage.ExitProgramStrategy.CLEAN_EXIT -> { |  | ||||||
|                 out("  jmp  main.start\t; jump to program entrypoint") |  | ||||||
|             } |  | ||||||
|             Zeropage.ExitProgramStrategy.SYSTEM_RESET -> { |  | ||||||
|                 out("  jsr  main.start\t; call program entrypoint") |  | ||||||
|                 out("  jmp  (c64.RESET_VEC)\t; cold reset") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         out("") |  | ||||||
|  |  | ||||||
|         // the global list of all floating point constants for the whole program |  | ||||||
|         for(flt in globalFloatConsts) { |  | ||||||
|             val floatFill = makeFloatFill(Mflpt5.fromNumber(flt.key)) |  | ||||||
|             out("${flt.value}\t.byte  $floatFill  ; float ${flt.key}") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun block2asm(blk: IntermediateProgram.ProgramBlock) { |  | ||||||
|         block = blk |  | ||||||
|         out("\n; ---- block: '${block.name}' ----") |  | ||||||
|         if(!blk.force_output) |  | ||||||
|             out("${block.name}\t.proc\n") |  | ||||||
|         if(block.address!=null) { |  | ||||||
|             out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'") |  | ||||||
|             out("* = ${block.address?.toHex()}") |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // deal with zeropage variables |  | ||||||
|         for(variable in blk.variables) { |  | ||||||
|             val sym = symname(blk.name+"."+variable.key, null) |  | ||||||
|             val zpVar = program.allocatedZeropageVariables[sym] |  | ||||||
|             if(zpVar==null) { |  | ||||||
|                 // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) |  | ||||||
|                 if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) { |  | ||||||
|                     try { |  | ||||||
|                         val address = zeropage.allocate(sym, variable.value.type, null) |  | ||||||
|                         out("${variable.key} = $address\t; auto zp ${variable.value.type}") |  | ||||||
|                         // make sure we add the var to the set of zpvars for this block |  | ||||||
|                         blk.variablesMarkedForZeropage.add(variable.key) |  | ||||||
|                         program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type) |  | ||||||
|                     } catch (x: ZeropageDepletedError) { |  | ||||||
|                         // leave it as it is. |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else { |  | ||||||
|                 // it was already allocated on the zp |  | ||||||
|                 out("${variable.key} = ${zpVar.first}\t; zp ${zpVar.second}") |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         out("\n; memdefs and kernel subroutines") |  | ||||||
|         memdefs2asm(block) |  | ||||||
|         out("\n; non-zeropage variables") |  | ||||||
|         vardecls2asm(block) |  | ||||||
|         out("") |  | ||||||
|  |  | ||||||
|         val instructionPatternWindowSize = 8        // increase once patterns occur longer than this. |  | ||||||
|         var processed = 0 |  | ||||||
|  |  | ||||||
|         for (ins in block.instructions.windowed(instructionPatternWindowSize, partialWindows = true)) { |  | ||||||
|             if (processed == 0) { |  | ||||||
|                 processed = instr2asm(ins) |  | ||||||
|                 if (processed == 0) { |  | ||||||
|                     // the instructions are not recognised yet and can't be translated into assembly |  | ||||||
|                     throw CompilerException("no asm translation found for instruction pattern: $ins") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             processed-- |  | ||||||
|         } |  | ||||||
|         if(!blk.force_output) |  | ||||||
|             out("\n\t.pend\n") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun memdefs2asm(block: IntermediateProgram.ProgramBlock) { |  | ||||||
|         for(m in block.memoryPointers) { |  | ||||||
|             out("  ${m.key} = ${m.value.first.toHex()}") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) { |  | ||||||
|         // these are the non-zeropage variables |  | ||||||
|         val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type } |  | ||||||
|         for (v in sortedVars) { |  | ||||||
|             when (v.second.type) { |  | ||||||
|                 DataType.UBYTE -> out("${v.first}\t.byte  0") |  | ||||||
|                 DataType.BYTE -> out("${v.first}\t.char  0") |  | ||||||
|                 DataType.UWORD -> out("${v.first}\t.word  0") |  | ||||||
|                 DataType.WORD -> out("${v.first}\t.sint  0") |  | ||||||
|                 DataType.FLOAT -> out("${v.first}\t.byte  0,0,0,0,0  ; float") |  | ||||||
|                 DataType.STR, DataType.STR_S -> { |  | ||||||
|                     val rawStr = heap.get(v.second.heapId!!).str!! |  | ||||||
|                     val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') } |  | ||||||
|                     out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"") |  | ||||||
|                     for (chunk in bytes.chunked(16)) |  | ||||||
|                         out("  .byte  " + chunk.joinToString()) |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_UB -> { |  | ||||||
|                     // unsigned integer byte arraysize |  | ||||||
|                     val data = makeArrayFillDataUnsigned(v.second) |  | ||||||
|                     if (data.size <= 16) |  | ||||||
|                         out("${v.first}\t.byte  ${data.joinToString()}") |  | ||||||
|                     else { |  | ||||||
|                         out(v.first) |  | ||||||
|                         for (chunk in data.chunked(16)) |  | ||||||
|                             out("  .byte  " + chunk.joinToString()) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_B -> { |  | ||||||
|                     // signed integer byte arraysize |  | ||||||
|                     val data = makeArrayFillDataSigned(v.second) |  | ||||||
|                     if (data.size <= 16) |  | ||||||
|                         out("${v.first}\t.char  ${data.joinToString()}") |  | ||||||
|                     else { |  | ||||||
|                         out(v.first) |  | ||||||
|                         for (chunk in data.chunked(16)) |  | ||||||
|                             out("  .char  " + chunk.joinToString()) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_UW -> { |  | ||||||
|                     // unsigned word arraysize |  | ||||||
|                     val data = makeArrayFillDataUnsigned(v.second) |  | ||||||
|                     if (data.size <= 16) |  | ||||||
|                         out("${v.first}\t.word  ${data.joinToString()}") |  | ||||||
|                     else { |  | ||||||
|                         out(v.first) |  | ||||||
|                         for (chunk in data.chunked(16)) |  | ||||||
|                             out("  .word  " + chunk.joinToString()) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_W -> { |  | ||||||
|                     // signed word arraysize |  | ||||||
|                     val data = makeArrayFillDataSigned(v.second) |  | ||||||
|                     if (data.size <= 16) |  | ||||||
|                         out("${v.first}\t.sint  ${data.joinToString()}") |  | ||||||
|                     else { |  | ||||||
|                         out(v.first) |  | ||||||
|                         for (chunk in data.chunked(16)) |  | ||||||
|                             out("  .sint  " + chunk.joinToString()) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 DataType.ARRAY_F -> { |  | ||||||
|                     // float arraysize |  | ||||||
|                     val array = heap.get(v.second.heapId!!).doubleArray!! |  | ||||||
|                     val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) } |  | ||||||
|                     out(v.first) |  | ||||||
|                     for(f in array.zip(floatFills)) |  | ||||||
|                         out("  .byte  ${f.second}  ; float ${f.first}") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun encodeStr(str: String, dt: DataType): List<Short> { |  | ||||||
|         return when(dt) { |  | ||||||
|             DataType.STR -> { |  | ||||||
|                 val bytes = Petscii.encodePetscii(str, true) |  | ||||||
|                 bytes.plus(0) |  | ||||||
|             } |  | ||||||
|             DataType.STR_S -> { |  | ||||||
|                 val bytes = Petscii.encodeScreencode(str, true) |  | ||||||
|                 bytes.plus(0) |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("invalid str type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> { |  | ||||||
|         val array = heap.get(value.heapId!!).array!! |  | ||||||
|         return when { |  | ||||||
|             value.type== DataType.ARRAY_UB -> |  | ||||||
|                 // byte array can never contain pointer-to types, so treat values as all integers |  | ||||||
|                 array.map { "$"+it.integer!!.toString(16).padStart(2, '0') } |  | ||||||
|             value.type== DataType.ARRAY_UW -> array.map { |  | ||||||
|                 when { |  | ||||||
|                     it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0') |  | ||||||
|                     it.addressOf!=null -> symname(it.addressOf.scopedname!!, block) |  | ||||||
|                     else -> throw AssemblyError("weird type in array") |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else -> throw AssemblyError("invalid arraysize type") |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> { |  | ||||||
|         val array = heap.get(value.heapId!!).array!! |  | ||||||
|         // note: array of signed value can never contain pointer-to type, so simply accept values as being all integers |  | ||||||
|         return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) { |  | ||||||
|             array.map { |  | ||||||
|                 if(it.integer!!>=0) |  | ||||||
|                     "$"+it.integer.toString(16).padStart(2, '0') |  | ||||||
|                 else |  | ||||||
|                     "-$"+abs(it.integer).toString(16).padStart(2, '0') |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else throw AssemblyError("invalid arraysize type") |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun instr2asm(ins: List<Instruction>): Int { |  | ||||||
|         // find best patterns (matching the most of the lines, then with the smallest weight) |  | ||||||
|         val fragments = findPatterns(ins).sortedByDescending { it.segmentSize } |  | ||||||
|         if(fragments.isEmpty()) { |  | ||||||
|             // we didn't find any matching patterns (complex multi-instruction fragments), try simple ones |  | ||||||
|             val firstIns = ins[0] |  | ||||||
|             val singleAsm = simpleInstr2Asm(firstIns, block) |  | ||||||
|             if(singleAsm != null) { |  | ||||||
|                 outputAsmFragment(singleAsm) |  | ||||||
|                 return 1 |  | ||||||
|             } |  | ||||||
|             return 0 |  | ||||||
|         } |  | ||||||
|         val best = fragments[0] |  | ||||||
|         outputAsmFragment(best.asm) |  | ||||||
|         return best.segmentSize |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun outputAsmFragment(singleAsm: String) { |  | ||||||
|         if (singleAsm.isNotEmpty()) { |  | ||||||
|             if(singleAsm.startsWith("@inline@")) |  | ||||||
|                 out(singleAsm.substring(8), false) |  | ||||||
|             else { |  | ||||||
|                 val withNewlines = singleAsm.replace('|', '\n') |  | ||||||
|                 out(withNewlines) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun findPatterns(segment: List<Instruction>): List<AsmFragment> { |  | ||||||
|         val opcodes = segment.map { it.opcode } |  | ||||||
|         val result = mutableListOf<AsmFragment>() |  | ||||||
|  |  | ||||||
|         // check for operations that modify a single value, by putting it on the stack (and popping it afterwards) |  | ||||||
|         if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[2]==Opcode.POP_VAR_BYTE) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_VAR_WORD && opcodes[2]==Opcode.POP_VAR_WORD) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_VAR_FLOAT && opcodes[2]==Opcode.POP_VAR_FLOAT)) { |  | ||||||
|             if (segment[0].callLabel == segment[2].callLabel) { |  | ||||||
|                 val fragment = sameVarOperation(segment[0].callLabel!!, segment[1]) |  | ||||||
|                 if (fragment != null) { |  | ||||||
|                     fragment.segmentSize = 3 |  | ||||||
|                     result.add(fragment) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB, |  | ||||||
|                         Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT, |  | ||||||
|                         Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W, |  | ||||||
|                         Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) { |  | ||||||
|             val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[1]) |  | ||||||
|             if(fragment!=null) { |  | ||||||
|                 fragment.segmentSize=2 |  | ||||||
|                 result.add(fragment) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB, |  | ||||||
|                         Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT, |  | ||||||
|                         Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W, |  | ||||||
|                         Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) { |  | ||||||
|             val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[1]) |  | ||||||
|             if(fragment!=null) { |  | ||||||
|                 fragment.segmentSize=2 |  | ||||||
|                 result.add(fragment) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if((opcodes[0]==Opcode.PUSH_MEM_UB && opcodes[2]==Opcode.POP_MEM_BYTE) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_MEM_B && opcodes[2]==Opcode.POP_MEM_BYTE) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_MEM_UW && opcodes[2]==Opcode.POP_MEM_WORD) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_MEM_W && opcodes[2]==Opcode.POP_MEM_WORD) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_MEM_FLOAT && opcodes[2]==Opcode.POP_MEM_FLOAT)) { |  | ||||||
|             if(segment[0].arg==segment[2].arg) { |  | ||||||
|                 val fragment = sameMemOperation(segment[0].arg!!.integerValue(), segment[1]) |  | ||||||
|                 if(fragment!=null) { |  | ||||||
|                     fragment.segmentSize = 3 |  | ||||||
|                     result.add(fragment) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE && |  | ||||||
|                         opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD && |  | ||||||
|                         opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) { |  | ||||||
|             if(segment[0].arg==segment[3].arg && segment[1].callLabel==segment[4].callLabel) { |  | ||||||
|                 val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[2]) |  | ||||||
|                 if(fragment!=null){ |  | ||||||
|                     fragment.segmentSize = 5 |  | ||||||
|                     result.add(fragment) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE && |  | ||||||
|                         opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) || |  | ||||||
|                 (opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD && |  | ||||||
|                         opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) { |  | ||||||
|             if(segment[0].callLabel==segment[3].callLabel && segment[1].callLabel==segment[4].callLabel) { |  | ||||||
|                 val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[2]) |  | ||||||
|                 if(fragment!=null){ |  | ||||||
|                     fragment.segmentSize = 5 |  | ||||||
|                     result.add(fragment) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // add any matching patterns from the big list |  | ||||||
|         for(pattern in patterns) { |  | ||||||
|             if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size)) |  | ||||||
|                 continue        //  don't accept patterns that don't fit |  | ||||||
|             val opcodesList = opcodes.subList(0, pattern.sequence.size) |  | ||||||
|             if(pattern.sequence == opcodesList) { |  | ||||||
|                 val asm = pattern.asm(segment) |  | ||||||
|                 if(asm!=null) |  | ||||||
|                     result.add(AsmFragment(asm, pattern.sequence.size)) |  | ||||||
|             } else if(pattern.altSequence!=null) { |  | ||||||
|                 val opcodesListAlt = opcodes.subList(0, pattern.altSequence.size) |  | ||||||
|                 if(pattern.altSequence == opcodesListAlt) { |  | ||||||
|                     val asm = pattern.asm(segment) |  | ||||||
|                     if (asm != null) |  | ||||||
|                         result.add(AsmFragment(asm, pattern.sequence.size)) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return result |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun sameConstantIndexedVarOperation(variable: String, index: Int, ins: Instruction): AsmFragment? { |  | ||||||
|         // an in place operation that consists of a push-value / op / push-index-value / pop-into-indexed-var |  | ||||||
|         return when(ins.opcode) { |  | ||||||
|             Opcode.SHL_BYTE -> AsmFragment(" asl  $variable+$index", 8) |  | ||||||
|             Opcode.SHR_UBYTE -> AsmFragment(" lsr  $variable+$index", 8) |  | ||||||
|             Opcode.SHR_SBYTE -> AsmFragment(" lda  $variable+$index |  asl  a |  ror  $variable+$index") |  | ||||||
|             Opcode.SHL_WORD -> AsmFragment(" asl  $variable+${index*2+1} |  rol  $variable+${index*2}", 8) |  | ||||||
|             Opcode.SHR_UWORD -> AsmFragment(" lsr  $variable+${index*2+1} |  ror  $variable+${index*2}", 8) |  | ||||||
|             Opcode.SHR_SWORD -> AsmFragment(" lda  $variable+${index*2+1} |  asl  a |  ror  $variable+${index*2+1} |  ror  $variable+${index*2}", 8) |  | ||||||
|             Opcode.ROL_BYTE -> AsmFragment(" rol  $variable+$index", 8) |  | ||||||
|             Opcode.ROR_BYTE -> AsmFragment(" ror  $variable+$index", 8) |  | ||||||
|             Opcode.ROL_WORD -> AsmFragment(" rol  $variable+${index*2+1} |  rol  $variable+${index*2}", 8) |  | ||||||
|             Opcode.ROR_WORD -> AsmFragment(" ror  $variable+${index*2+1} |  ror  $variable+${index*2}", 8) |  | ||||||
|             Opcode.ROL2_BYTE -> AsmFragment(" lda  $variable+$index |  cmp  #\$80 |  rol  $variable+$index", 8) |  | ||||||
|             Opcode.ROR2_BYTE -> AsmFragment(" lda  $variable+$index |  lsr  a |  bcc  + |  ora  #\$80 |+ |  sta  $variable+$index", 10) |  | ||||||
|             Opcode.ROL2_WORD -> AsmFragment(" asl  $variable+${index*2+1} |  rol  $variable+${index*2} |  bcc  + |  inc  $variable+${index*2+1} |+",20) |  | ||||||
|             Opcode.ROR2_WORD -> AsmFragment(" lsr  $variable+${index*2+1} |  ror  $variable+${index*2} |  bcc  + |  lda  $variable+${index*2+1} |  ora  #\$80 |  sta  $variable+${index*2+1} |+", 30) |  | ||||||
|             Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc  $variable+$index", 2) |  | ||||||
|             Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec  $variable+$index", 5) |  | ||||||
|             Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc  $variable+${index*2} |  bne  + |  inc  $variable+${index*2+1} |+") |  | ||||||
|             Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda  $variable+${index*2} |  bne  + |  dec  $variable+${index*2+1} |+ |  dec  $variable+${index*2}") |  | ||||||
|             Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment( |  | ||||||
|                 """ |  | ||||||
|                 lda  #<($variable+${index*Mflpt5.MemorySize}) |  | ||||||
|                 ldy  #>($variable+${index*Mflpt5.MemorySize}) |  | ||||||
|                 jsr  c64flt.inc_var_f |  | ||||||
|                 """) |  | ||||||
|             Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment( |  | ||||||
|                 """ |  | ||||||
|                 lda  #<($variable+${index*Mflpt5.MemorySize}) |  | ||||||
|                 ldy  #>($variable+${index*Mflpt5.MemorySize}) |  | ||||||
|                 jsr  c64flt.dec_var_f |  | ||||||
|                 """) |  | ||||||
|  |  | ||||||
|             else -> null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? { |  | ||||||
|         // an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var |  | ||||||
|         val saveX = " stx  ${C64Zeropage.SCRATCH_B1} |" |  | ||||||
|         val restoreX = " | ldx  ${C64Zeropage.SCRATCH_B1}" |  | ||||||
|         val loadXWord: String |  | ||||||
|         val loadX: String |  | ||||||
|  |  | ||||||
|         when(indexVar) { |  | ||||||
|             "X" -> { |  | ||||||
|                 loadX = "" |  | ||||||
|                 loadXWord = " txa |  asl  a |  tax |" |  | ||||||
|             } |  | ||||||
|             "Y" -> { |  | ||||||
|                 loadX = " tya |  tax |" |  | ||||||
|                 loadXWord = " tya |  asl  a |  tax |" |  | ||||||
|             } |  | ||||||
|             "A" -> { |  | ||||||
|                 loadX = " tax |" |  | ||||||
|                 loadXWord = " asl  a |  tax |" |  | ||||||
|             } |  | ||||||
|             else -> { |  | ||||||
|                 // the indexvar is a real variable, not a register |  | ||||||
|                 loadX = " ldx  $indexVar |" |  | ||||||
|                 loadXWord = " lda  $indexVar |  asl  a |  tax |" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return when (ins.opcode) { |  | ||||||
|             Opcode.SHL_BYTE -> AsmFragment(" txa |  $loadX  asl  $variable,x |  tax", 10) |  | ||||||
|             Opcode.SHR_UBYTE -> AsmFragment(" txa |  $loadX  lsr  $variable,x |  tax", 10) |  | ||||||
|             Opcode.SHR_SBYTE -> AsmFragment("$saveX  $loadX  lda  $variable,x |  asl a |  ror  $variable,x  $restoreX", 10) |  | ||||||
|             Opcode.SHL_WORD -> AsmFragment("$saveX $loadXWord  asl  $variable,x |  rol  $variable+1,x  $restoreX", 10) |  | ||||||
|             Opcode.SHR_UWORD -> AsmFragment("$saveX $loadXWord  lsr  $variable+1,x |  ror  $variable,x  $restoreX", 10) |  | ||||||
|             Opcode.SHR_SWORD -> AsmFragment("$saveX $loadXWord  lda  $variable+1,x |  asl a |  ror  $variable+1,x |  ror  $variable,x  $restoreX", 10) |  | ||||||
|             Opcode.ROL_BYTE -> AsmFragment(" txa |  $loadX  rol  $variable,x |  tax", 10) |  | ||||||
|             Opcode.ROR_BYTE -> AsmFragment(" txa |  $loadX  ror  $variable,x |  tax", 10) |  | ||||||
|             Opcode.ROL_WORD -> AsmFragment("$saveX $loadXWord  rol  $variable,x |  rol  $variable+1,x  $restoreX", 10) |  | ||||||
|             Opcode.ROR_WORD -> AsmFragment("$saveX $loadXWord  ror  $variable+1,x |  ror  $variable,x  $restoreX", 10) |  | ||||||
|             Opcode.ROL2_BYTE -> AsmFragment("$saveX $loadX  lda  $variable,x |  cmp  #\$80 |  rol  $variable,x  $restoreX", 10) |  | ||||||
|             Opcode.ROR2_BYTE -> AsmFragment("$saveX $loadX  lda  $variable,x |  lsr  a |  bcc  + |  ora  #\$80 |+ |  sta  $variable,x  $restoreX", 10) |  | ||||||
|             Opcode.ROL2_WORD -> AsmFragment(" txa |  $loadXWord  asl  $variable,x |  rol  $variable+1,x |  bcc  + |  inc  $variable,x  |+  |  tax", 30) |  | ||||||
|             Opcode.ROR2_WORD -> AsmFragment("$saveX $loadXWord  lsr  $variable+1,x |  ror  $variable,x |  bcc  + |  lda  $variable+1,x |  ora  #\$80 |  sta  $variable+1,x |+  $restoreX", 30) |  | ||||||
|             Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" txa |  $loadX  inc  $variable,x |  tax", 10) |  | ||||||
|             Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" txa |  $loadX  dec  $variable,x |  tax", 10) |  | ||||||
|             Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord  inc  $variable,x |  bne  + |  inc  $variable+1,x  |+  $restoreX", 10) |  | ||||||
|             Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord  lda  $variable,x |  bne  + |  dec  $variable+1,x |+ |  dec  $variable,x  $restoreX", 10) |  | ||||||
|             Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(" lda  #<$variable |  ldy  #>$variable |  $saveX   $loadX   jsr  c64flt.inc_indexed_var_f  $restoreX") |  | ||||||
|             Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(" lda  #<$variable |  ldy  #>$variable |  $saveX   $loadX   jsr  c64flt.dec_indexed_var_f  $restoreX") |  | ||||||
|  |  | ||||||
|             else -> null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun sameMemOperation(address: Int, ins: Instruction): AsmFragment? { |  | ||||||
|         // an in place operation that consists of  push-mem / op / pop-mem |  | ||||||
|         val addr = address.toHex() |  | ||||||
|         val addrHi = (address+1).toHex() |  | ||||||
|         return when(ins.opcode) { |  | ||||||
|             Opcode.SHL_BYTE -> AsmFragment(" asl  $addr", 10) |  | ||||||
|             Opcode.SHR_UBYTE -> AsmFragment(" lsr  $addr", 10) |  | ||||||
|             Opcode.SHR_SBYTE -> AsmFragment(" lda  $addr |  asl  a |  ror  $addr", 10) |  | ||||||
|             Opcode.SHL_WORD -> AsmFragment(" asl  $addr |  rol  $addrHi", 10) |  | ||||||
|             Opcode.SHR_UWORD -> AsmFragment(" lsr  $addrHi |  ror  $addr", 10) |  | ||||||
|             Opcode.SHR_SWORD -> AsmFragment(" lda  $addrHi |  asl a |  ror  $addrHi |  ror  $addr", 10) |  | ||||||
|             Opcode.ROL_BYTE -> AsmFragment(" rol  $addr", 10) |  | ||||||
|             Opcode.ROR_BYTE -> AsmFragment(" ror  $addr", 10) |  | ||||||
|             Opcode.ROL_WORD -> AsmFragment(" rol  $addr |  rol  $addrHi", 10) |  | ||||||
|             Opcode.ROR_WORD -> AsmFragment(" ror  $addrHi |  ror  $addr", 10) |  | ||||||
|             Opcode.ROL2_BYTE -> AsmFragment(" lda  $addr |  cmp  #\$80 |  rol  $addr", 10) |  | ||||||
|             Opcode.ROR2_BYTE -> AsmFragment(" lda  $addr |  lsr  a |  bcc  + |  ora  #\$80 |+ |  sta  $addr", 10) |  | ||||||
|             Opcode.ROL2_WORD -> AsmFragment(" lda  $addr |  cmp #\$80 |  rol  $addr |  rol  $addrHi", 10) |  | ||||||
|             Opcode.ROR2_WORD -> AsmFragment(" lsr  $addrHi |  ror  $addr |  bcc  + |  lda  $addrHi |  ora  #$80 |  sta  $addrHi |+", 20) |  | ||||||
|             else -> null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun sameVarOperation(variable: String, ins: Instruction): AsmFragment? { |  | ||||||
|         // an in place operation that consists of a push-var / op / pop-var |  | ||||||
|         return when(ins.opcode) { |  | ||||||
|             Opcode.SHL_BYTE -> { |  | ||||||
|                 when (variable) { |  | ||||||
|                     "A" -> AsmFragment(" asl  a", 10) |  | ||||||
|                     "X" -> AsmFragment(" txa |  asl  a |  tax", 10) |  | ||||||
|                     "Y" -> AsmFragment(" tya |  asl  a |  tay", 10) |  | ||||||
|                     else -> AsmFragment(" asl  $variable", 10) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Opcode.SHR_UBYTE -> { |  | ||||||
|                 when (variable) { |  | ||||||
|                     "A" -> AsmFragment(" lsr  a", 10) |  | ||||||
|                     "X" -> AsmFragment(" txa |  lsr  a |  tax", 10) |  | ||||||
|                     "Y" -> AsmFragment(" tya |  lsr  a |  tay", 10) |  | ||||||
|                     else -> AsmFragment(" lsr  $variable", 10) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Opcode.SHR_SBYTE -> { |  | ||||||
|                 // arithmetic shift right (keep sign bit) |  | ||||||
|                 when (variable) { |  | ||||||
|                     "A" -> AsmFragment(" cmp  #$80 |  ror  a", 10) |  | ||||||
|                     "X" -> AsmFragment(" txa |  cmp  #$80 |  ror  a |  tax", 10) |  | ||||||
|                     "Y" -> AsmFragment(" tya |  cmp  #$80 |  ror  a |  tay", 10) |  | ||||||
|                     else -> AsmFragment(" lda  $variable |  asl  a  | ror  $variable", 10) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Opcode.SHL_WORD -> { |  | ||||||
|                 AsmFragment(" asl  $variable |  rol  $variable+1", 10) |  | ||||||
|             } |  | ||||||
|             Opcode.SHR_UWORD -> { |  | ||||||
|                 AsmFragment(" lsr  $variable+1 |  ror  $variable", 10) |  | ||||||
|             } |  | ||||||
|             Opcode.SHR_SWORD -> { |  | ||||||
|                 // arithmetic shift right (keep sign bit) |  | ||||||
|                 AsmFragment(" lda  $variable+1 |  asl  a |  ror  $variable+1 |  ror  $variable", 10) |  | ||||||
|             } |  | ||||||
|             Opcode.ROL_BYTE -> { |  | ||||||
|                 when (variable) { |  | ||||||
|                     "A" -> AsmFragment(" rol  a", 10) |  | ||||||
|                     "X" -> AsmFragment(" txa |  rol  a |  tax", 10) |  | ||||||
|                     "Y" -> AsmFragment(" tya |  rol  a |  tay", 10) |  | ||||||
|                     else -> AsmFragment(" rol  $variable", 10) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Opcode.ROR_BYTE -> { |  | ||||||
|                 when (variable) { |  | ||||||
|                     "A" -> AsmFragment(" ror  a", 10) |  | ||||||
|                     "X" -> AsmFragment(" txa |  ror  a |  tax", 10) |  | ||||||
|                     "Y" -> AsmFragment(" tya |  ror  a |  tay", 10) |  | ||||||
|                     else -> AsmFragment(" ror  $variable", 10) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Opcode.ROL_WORD -> { |  | ||||||
|                 AsmFragment(" rol  $variable |  rol  $variable+1", 10) |  | ||||||
|             } |  | ||||||
|             Opcode.ROR_WORD -> { |  | ||||||
|                 AsmFragment(" ror  $variable+1 |  ror  $variable", 10) |  | ||||||
|             } |  | ||||||
|             Opcode.ROL2_BYTE -> {       // 8-bit rol |  | ||||||
|                 when (variable) { |  | ||||||
|                     "A" -> AsmFragment(" cmp  #\$80 |  rol  a", 10) |  | ||||||
|                     "X" -> AsmFragment(" txa |  cmp  #\$80 |  rol  a |  tax", 10) |  | ||||||
|                     "Y" -> AsmFragment(" tya |  cmp  #\$80 |  rol  a |  tay", 10) |  | ||||||
|                     else -> AsmFragment(" lda  $variable |  cmp  #\$80  | rol  $variable", 10) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Opcode.ROR2_BYTE -> {       // 8-bit ror |  | ||||||
|                 when (variable) { |  | ||||||
|                     "A" -> AsmFragment(" lsr  a | bcc  + |  ora  #\$80  |+", 10) |  | ||||||
|                     "X" -> AsmFragment(" txa |  lsr  a |  bcc  + |  ora  #\$80  |+ |  tax", 10) |  | ||||||
|                     "Y" -> AsmFragment(" tya |  lsr  a |  bcc  + |  ora  #\$80  |+ |  tay", 10) |  | ||||||
|                     else -> AsmFragment(" lda  $variable |  lsr  a |  bcc  + |  ora  #\$80 |+ |  sta  $variable", 10) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Opcode.ROL2_WORD -> { |  | ||||||
|                 AsmFragment(" lda  $variable |  cmp #\$80 |  rol  $variable |  rol  $variable+1", 10) |  | ||||||
|             } |  | ||||||
|             Opcode.ROR2_WORD -> { |  | ||||||
|                 AsmFragment(" lsr  $variable+1 |  ror  $variable |  bcc  + |  lda  $variable+1 |  ora  #\$80 |  sta  $variable+1 |+", 30) |  | ||||||
|             } |  | ||||||
|             else -> null |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private class AsmFragment(val asm: String, var segmentSize: Int=0) |  | ||||||
| } |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,56 +0,0 @@ | |||||||
| package prog8.compiler.target.c64 |  | ||||||
|  |  | ||||||
| import prog8.compiler.CompilationOptions |  | ||||||
| import prog8.compiler.OutputType |  | ||||||
| import java.io.File |  | ||||||
| import kotlin.system.exitProcess |  | ||||||
|  |  | ||||||
| class AssemblyProgram(val name: String) { |  | ||||||
|     private val assemblyFile = "$name.asm" |  | ||||||
|     private val viceMonListFile = "$name.vice-mon-list" |  | ||||||
|  |  | ||||||
|     fun assemble(options: CompilationOptions) { |  | ||||||
|         // add "-Wlong-branch"  to see warnings about conversion of branch instructions to jumps |  | ||||||
|         val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", "-Wall", "-Wno-strict-bool", |  | ||||||
|                 "-Werror", "-Wno-error=long-branch", "--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor") |  | ||||||
|  |  | ||||||
|         val outFile = when(options.output) { |  | ||||||
|             OutputType.PRG -> { |  | ||||||
|                 command.add("--cbm-prg") |  | ||||||
|                 println("\nCreating C-64 prg.") |  | ||||||
|                 "$name.prg" |  | ||||||
|             } |  | ||||||
|             OutputType.RAW -> { |  | ||||||
|                 command.add("--nostart") |  | ||||||
|                 println("\nCreating raw binary.") |  | ||||||
|                 "$name.bin" |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         command.addAll(listOf("--output", outFile, assemblyFile)) |  | ||||||
|  |  | ||||||
|         val proc = ProcessBuilder(command).inheritIO().start() |  | ||||||
|         val result = proc.waitFor() |  | ||||||
|         if(result!=0) { |  | ||||||
|             System.err.println("assembler failed with returncode $result") |  | ||||||
|             exitProcess(result) |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         generateBreakpointList() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private fun generateBreakpointList() { |  | ||||||
|         // builds list of breakpoints, appends to monitor list file |  | ||||||
|         val breakpoints = mutableListOf<String>() |  | ||||||
|         val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""")      // gather breakpoints by the source label that's generated for them |  | ||||||
|         for(line in File(viceMonListFile).readLines()) { |  | ||||||
|             val match = pattern.matchEntire(line) |  | ||||||
|             if(match!=null) |  | ||||||
|             breakpoints.add("break \$" + match.groupValues[1]) |  | ||||||
|         } |  | ||||||
|         val num = breakpoints.size |  | ||||||
|         breakpoints.add(0, "; vice monitor breakpoint list now follows") |  | ||||||
|         breakpoints.add(1, "; $num breakpoints have been defined") |  | ||||||
|         breakpoints.add(2, "del") |  | ||||||
|         File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										207
									
								
								compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										207
									
								
								compiler/src/prog8/compiler/target/c64/C64MachineDefinition.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,207 @@ | |||||||
|  | package prog8.compiler.target.c64 | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  |     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) | ||||||
|  |     override val ESTACK_LO = 0xce00        //  $ce00-$ceff inclusive | ||||||
|  |     override val ESTACK_HI = 0xcf00        //  $ce00-$ceff inclusive | ||||||
|  |  | ||||||
|  |     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", | ||||||
|  |             "beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc", | ||||||
|  |             "cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey", | ||||||
|  |             "eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs", | ||||||
|  |             "inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las", | ||||||
|  |             "lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php", | ||||||
|  |             "pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx", | ||||||
|  |             "sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre", | ||||||
|  |             "sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa") | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) { | ||||||
|  |  | ||||||
|  |         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 { | ||||||
|  |             if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE )) | ||||||
|  |                 throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'") | ||||||
|  |  | ||||||
|  |             if (options.zeropage == ZeropageType.FULL) { | ||||||
|  |                 free.addAll(0x04..0xf9) | ||||||
|  |                 free.add(0xff) | ||||||
|  |                 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, | ||||||
|  |                             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, | ||||||
|  |                             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, | ||||||
|  |                             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 '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( | ||||||
|  |                             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, 0xff | ||||||
|  |                     )) | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if(options.zeropage!=ZeropageType.DONTUSE) { | ||||||
|  |                     // 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, | ||||||
|  |                             0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6, | ||||||
|  |                             0xb0, 0xb1, 0xbe, 0xbf, 0xf9)) | ||||||
|  |                 } else { | ||||||
|  |                     // don't use the zeropage at all | ||||||
|  |                     free.clear() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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) | ||||||
|  |             fun fromNumber(num: Number): Mflpt5 { | ||||||
|  |                 // see https://en.wikipedia.org/wiki/Microsoft_Binary_Format | ||||||
|  |                 // and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a | ||||||
|  |                 // and https://en.wikipedia.org/wiki/IEEE_754-1985 | ||||||
|  |  | ||||||
|  |                 val flt = num.toDouble() | ||||||
|  |                 if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) | ||||||
|  |                     throw CompilerException("floating point number out of 5-byte mflpt range: $this") | ||||||
|  |                 if (flt == 0.0) | ||||||
|  |                     return zero | ||||||
|  |  | ||||||
|  |                 val sign = if (flt < 0.0) 0x80L else 0x00L | ||||||
|  |                 var exponent = 128 + 32    // 128 is cbm's bias, 32 is this algo's bias | ||||||
|  |                 var mantissa = flt.absoluteValue | ||||||
|  |  | ||||||
|  |                 // if mantissa is too large, shift right and adjust exponent | ||||||
|  |                 while (mantissa >= 0x100000000) { | ||||||
|  |                     mantissa /= 2.0 | ||||||
|  |                     exponent++ | ||||||
|  |                 } | ||||||
|  |                 // if mantissa is too small, shift left and adjust exponent | ||||||
|  |                 while (mantissa < 0x80000000) { | ||||||
|  |                     mantissa *= 2.0 | ||||||
|  |                     exponent-- | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return when { | ||||||
|  |                     exponent < 0 -> zero  // underflow, use zero instead | ||||||
|  |                     exponent > 255 -> throw CompilerException("floating point overflow: $this") | ||||||
|  |                     exponent == 0 -> zero | ||||||
|  |                     else -> { | ||||||
|  |                         val mantLong = mantissa.toLong() | ||||||
|  |                         Mflpt5( | ||||||
|  |                                 exponent.toShort(), | ||||||
|  |                                 (mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(), | ||||||
|  |                                 (mantLong.and(0x00ff0000L) ushr 16).toShort(), | ||||||
|  |                                 (mantLong.and(0x0000ff00L) ushr 8).toShort(), | ||||||
|  |                                 (mantLong.and(0x000000ffL)).toShort()) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         override fun toDouble(): Double { | ||||||
|  |             if (this == zero) return 0.0 | ||||||
|  |             val exp = b0 - 128 | ||||||
|  |             val sign = (b1.toInt() and 0x80) > 0 | ||||||
|  |             val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong()) | ||||||
|  |             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" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,242 +0,0 @@ | |||||||
| package prog8.compiler.target.c64 |  | ||||||
|  |  | ||||||
| import prog8.compiler.CompilationOptions |  | ||||||
| import prog8.compiler.CompilerException |  | ||||||
| import prog8.compiler.Zeropage |  | ||||||
| import prog8.compiler.ZeropageType |  | ||||||
| import java.awt.Color |  | ||||||
| import java.awt.image.BufferedImage |  | ||||||
| import javax.imageio.ImageIO |  | ||||||
| import kotlin.math.absoluteValue |  | ||||||
| import kotlin.math.pow |  | ||||||
|  |  | ||||||
|  |  | ||||||
| // 5-byte cbm MFLPT format limitations: |  | ||||||
| const val FLOAT_MAX_POSITIVE = 1.7014118345e+38         // bytes: 255,127,255,255,255 |  | ||||||
| const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38        // bytes: 255,255,255,255,255 |  | ||||||
|  |  | ||||||
| const val BASIC_LOAD_ADDRESS = 0x0801 |  | ||||||
| const val RAW_LOAD_ADDRESS = 0xc000 |  | ||||||
|  |  | ||||||
| // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) |  | ||||||
| const val ESTACK_LO = 0xce00        //  $ce00-$ceff inclusive |  | ||||||
| const val ESTACK_HI = 0xcf00        //  $cf00-$cfff inclusive |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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 -> ExitProgramStrategy.CLEAN_EXIT |  | ||||||
|         ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     init { |  | ||||||
|         if(options.floats && options.zeropage!=ZeropageType.FLOATSAFE && options.zeropage!=ZeropageType.BASICSAFE) |  | ||||||
|             throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'") |  | ||||||
|  |  | ||||||
|         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(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, |  | ||||||
|                         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, |  | ||||||
|                         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, |  | ||||||
|                         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' |  | ||||||
|                 )) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             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, |  | ||||||
|                         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 |  | ||||||
|                 )) |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // 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: |  | ||||||
|             free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e, |  | ||||||
|                     0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa, |  | ||||||
|                     0xb5, 0xb6, 0xf7, 0xf8, 0xf9)) |  | ||||||
|         } |  | ||||||
|         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) |  | ||||||
|  |  | ||||||
|         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) { |  | ||||||
|  |  | ||||||
|     companion object { |  | ||||||
|         const val MemorySize = 5 |  | ||||||
|  |  | ||||||
|         val zero = Mflpt5(0, 0,0,0,0) |  | ||||||
|         fun fromNumber(num: Number): Mflpt5 { |  | ||||||
|             // see https://en.wikipedia.org/wiki/Microsoft_Binary_Format |  | ||||||
|             // and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a |  | ||||||
|             // and https://en.wikipedia.org/wiki/IEEE_754-1985 |  | ||||||
|  |  | ||||||
|             val flt = num.toDouble() |  | ||||||
|             if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) |  | ||||||
|                 throw CompilerException("floating point number out of 5-byte mflpt range: $this") |  | ||||||
|             if(flt==0.0) |  | ||||||
|                 return zero |  | ||||||
|  |  | ||||||
|             val sign = if(flt<0.0) 0x80L else 0x00L |  | ||||||
|             var exponent = 128 + 32	// 128 is cbm's bias, 32 is this algo's bias |  | ||||||
|             var mantissa = flt.absoluteValue |  | ||||||
|  |  | ||||||
|             // if mantissa is too large, shift right and adjust exponent |  | ||||||
|             while(mantissa >= 0x100000000) { |  | ||||||
|                 mantissa /= 2.0 |  | ||||||
|                 exponent ++ |  | ||||||
|             } |  | ||||||
|             // if mantissa is too small, shift left and adjust exponent |  | ||||||
|             while(mantissa < 0x80000000) { |  | ||||||
|                 mantissa *= 2.0 |  | ||||||
|                 exponent -- |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return when { |  | ||||||
|                 exponent<0 -> zero  // underflow, use zero instead |  | ||||||
|                 exponent>255 -> throw CompilerException("floating point overflow: $this") |  | ||||||
|                 exponent==0 -> zero |  | ||||||
|                 else -> { |  | ||||||
|                     val mantLong = mantissa.toLong() |  | ||||||
|                     Mflpt5( |  | ||||||
|                             exponent.toShort(), |  | ||||||
|                             (mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(), |  | ||||||
|                             (mantLong.and(0x00ff0000L) ushr 16).toShort(), |  | ||||||
|                             (mantLong.and(0x0000ff00L) ushr 8).toShort(), |  | ||||||
|                             (mantLong.and(0x000000ffL)).toShort()) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fun toDouble(): Double { |  | ||||||
|         if(this == zero) return 0.0 |  | ||||||
|         val exp = b0 - 128 |  | ||||||
|         val sign = (b1.toInt() and 0x80) > 0 |  | ||||||
|         val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong()) |  | ||||||
|         val result = number.toDouble() * (2.0).pow(exp) / 0x100000000 |  | ||||||
|         return if(sign) -result else result |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| object Charset { |  | ||||||
|     private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png")) |  | ||||||
|     private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png")) |  | ||||||
|  |  | ||||||
|     private fun scanChars(img: BufferedImage): Array<BufferedImage> { |  | ||||||
|  |  | ||||||
|         val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB) |  | ||||||
|         transparent.createGraphics().drawImage(img, 0, 0, null) |  | ||||||
|  |  | ||||||
|         val black = Color(0,0,0).rgb |  | ||||||
|         val nopixel = Color(0,0,0,0).rgb |  | ||||||
|         for(y in 0 until transparent.height) { |  | ||||||
|             for(x in 0 until transparent.width) { |  | ||||||
|                 val col = transparent.getRGB(x, y) |  | ||||||
|                 if(col==black) |  | ||||||
|                     transparent.setRGB(x, y, nopixel) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         val numColumns = transparent.width / 8 |  | ||||||
|         val charImages = (0..255).map { |  | ||||||
|             val charX = it % numColumns |  | ||||||
|             val charY = it/ numColumns |  | ||||||
|             transparent.getSubimage(charX*8, charY*8, 8, 8) |  | ||||||
|         } |  | ||||||
|         return charImages.toTypedArray() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     val normalChars = scanChars(normalImg) |  | ||||||
|     val shiftedChars = scanChars(shiftedImg) |  | ||||||
|  |  | ||||||
|     private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>() |  | ||||||
|  |  | ||||||
|     fun getColoredChar(screenCode: Short, color: Short): BufferedImage { |  | ||||||
|         val colorIdx = (color % Colors.palette.size).toShort() |  | ||||||
|         val chars = coloredNormalChars[colorIdx] |  | ||||||
|         if(chars!=null) |  | ||||||
|             return chars[screenCode.toInt()] |  | ||||||
|  |  | ||||||
|         val coloredChars = mutableListOf<BufferedImage>() |  | ||||||
|         val transparent = Color(0,0,0,0).rgb |  | ||||||
|         val rgb = Colors.palette[colorIdx.toInt()].rgb |  | ||||||
|         for(c in normalChars) { |  | ||||||
|             val colored = c.copy() |  | ||||||
|             for(y in 0 until colored.height) |  | ||||||
|                 for(x in 0 until colored.width) { |  | ||||||
|                     if(colored.getRGB(x, y)!=transparent) { |  | ||||||
|                         colored.setRGB(x, y, rgb) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             coloredChars.add(colored) |  | ||||||
|         } |  | ||||||
|         coloredNormalChars[colorIdx] = coloredChars.toTypedArray() |  | ||||||
|         return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()] |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| private fun BufferedImage.copy(): BufferedImage { |  | ||||||
|     val bcopy = BufferedImage(this.width, this.height, this.type) |  | ||||||
|     val g = bcopy.graphics |  | ||||||
|     g.drawImage(this, 0, 0, null) |  | ||||||
|     g.dispose() |  | ||||||
|     return bcopy |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| object Colors { |  | ||||||
|     val palette = listOf(         // this is Pepto's Commodore-64 palette  http://www.pepto.de/projects/colorvic/ |  | ||||||
|             Color(0x000000),  // 0 = black |  | ||||||
|             Color(0xFFFFFF),  // 1 = white |  | ||||||
|             Color(0x813338),  // 2 = red |  | ||||||
|             Color(0x75cec8),  // 3 = cyan |  | ||||||
|             Color(0x8e3c97),  // 4 = purple |  | ||||||
|             Color(0x56ac4d),  // 5 = green |  | ||||||
|             Color(0x2e2c9b),  // 6 = blue |  | ||||||
|             Color(0xedf171),  // 7 = yellow |  | ||||||
|             Color(0x8e5029),  // 8 = orange |  | ||||||
|             Color(0x553800),  // 9 = brown |  | ||||||
|             Color(0xc46c71),  // 10 = light red |  | ||||||
|             Color(0x4a4a4a),  // 11 = dark grey |  | ||||||
|             Color(0x7b7b7b),  // 12 = medium grey |  | ||||||
|             Color(0xa9ff9f),  // 13 = light green |  | ||||||
|             Color(0x706deb),  // 14 = light blue |  | ||||||
|             Color(0xb2b2b2)   // 15 = light grey |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
										
											
												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