mirror of
				https://github.com/irmen/prog8.git
				synced 2025-10-31 00:16:08 +00:00 
			
		
		
		
	Compare commits
	
		
			1284 Commits
		
	
	
		
			v1.52
			...
			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 | 
							
								
								
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,8 @@ | ||||
| .idea/workspace.xml | ||||
| /build/ | ||||
| /dist/ | ||||
| /output/ | ||||
| .idea/discord.xml | ||||
| build/ | ||||
| dist/ | ||||
| output/ | ||||
| .*cache/ | ||||
| *.directory | ||||
| *.prg | ||||
| @@ -11,9 +12,9 @@ | ||||
| *.vice-mon-list | ||||
| docs/build | ||||
| out/ | ||||
| **/*.interp | ||||
| **/*.tokens | ||||
|  | ||||
| parser/**/*.interp | ||||
| parser/**/*.tokens | ||||
| parser/**/*.java | ||||
| *.py[cod] | ||||
| *.egg | ||||
| *.egg-info | ||||
| @@ -27,5 +28,6 @@ parsetab.py | ||||
| .attach_pid* | ||||
|  | ||||
| .gradle | ||||
| build/ | ||||
| /prog8compiler.jar | ||||
| sd*.img | ||||
| *.d64 | ||||
|   | ||||
							
								
								
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="CompilerConfiguration"> | ||||
|     <option name="BUILD_PROCESS_HEAP_SIZE" value="1200" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										7
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,13 @@ | ||||
| <component name="InspectionProjectProfileManager"> | ||||
|   <profile version="1.0"> | ||||
|     <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"> | ||||
|       <option name="processCode" value="false" /> | ||||
|       <option name="processLiterals" value="true" /> | ||||
|   | ||||
							
								
								
									
										2
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/kotlinc.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="Kotlin2JvmCompilerArguments"> | ||||
|     <option name="jvmTarget" value="1.8" /> | ||||
|     <option name="jvmTarget" value="11" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										9
									
								
								.idea/libraries/antlr_4_7_2_complete.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/antlr_4_7_2_complete.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,9 +0,0 @@ | ||||
| <component name="libraryTable"> | ||||
|   <library name="antlr-4.7.2-complete"> | ||||
|     <CLASSES> | ||||
|       <root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.7.2-complete.jar!/" /> | ||||
|     </CLASSES> | ||||
|     <JAVADOC /> | ||||
|     <SOURCES /> | ||||
|   </library> | ||||
| </component> | ||||
							
								
								
									
										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"> | ||||
|   <library name="antlr-runtime-4.7.2"> | ||||
|   <library name="antlr-runtime-4.9"> | ||||
|     <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> | ||||
|     <JAVADOC /> | ||||
|     <SOURCES /> | ||||
							
								
								
									
										9
									
								
								.idea/libraries/dbus_java_3_2_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.idea/libraries/dbus_java_3_2_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <component name="libraryTable"> | ||||
|   <library name="dbus-java-3.2.4"> | ||||
|     <CLASSES> | ||||
|       <root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" /> | ||||
|     </CLASSES> | ||||
|     <JAVADOC /> | ||||
|     <SOURCES /> | ||||
|   </library> | ||||
| </component> | ||||
							
								
								
									
										10
									
								
								.idea/libraries/javax_json_api_1_1_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/libraries/javax_json_api_1_1_4.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <component name="libraryTable"> | ||||
|   <library name="javax.json-api-1.1.4"> | ||||
|     <CLASSES> | ||||
|       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" /> | ||||
|       <root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" /> | ||||
|     </CLASSES> | ||||
|     <JAVADOC /> | ||||
|     <SOURCES /> | ||||
|   </library> | ||||
| </component> | ||||
							
								
								
									
										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"?> | ||||
| <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" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										4
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							| @@ -2,10 +2,12 @@ | ||||
| <project version="4"> | ||||
|   <component name="ProjectModuleManager"> | ||||
|     <modules> | ||||
|       <module fileurl="file://$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" filepath="$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.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$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" /> | ||||
|       <module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" /> | ||||
|       <module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" /> | ||||
|     </modules> | ||||
|   </component> | ||||
|   | ||||
| @@ -4,8 +4,8 @@ sudo: false | ||||
| # dist: xenial | ||||
|  | ||||
| before_install: | ||||
|   - chmod +x gradlew | ||||
|   - chmod +x ./gradlew | ||||
|  | ||||
| script: | ||||
|   - gradle test | ||||
|   - ./gradlew test | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,51 +0,0 @@ | ||||
| package 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,548 +0,0 @@ | ||||
| package compiler.intermediate | ||||
|  | ||||
| import prog8.ast.antlr.escape | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.NumericLiteralValue | ||||
| import prog8.ast.expressions.ReferenceLiteralValue | ||||
| import prog8.ast.statements.StructDecl | ||||
| import prog8.ast.statements.VarDecl | ||||
| import prog8.ast.statements.ZeropageWish | ||||
| import prog8.compiler.CompilerException | ||||
| import prog8.compiler.HeapValues | ||||
| import prog8.compiler.Zeropage | ||||
| import prog8.compiler.ZeropageDepletedError | ||||
| import prog8.vm.RuntimeValue | ||||
| import java.io.PrintStream | ||||
| import java.nio.file.Path | ||||
|  | ||||
|  | ||||
| class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) { | ||||
|  | ||||
|     class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?) | ||||
|     class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters) | ||||
|  | ||||
|     class ProgramBlock(val name: String, | ||||
|                        var address: Int?, | ||||
|                        val instructions: MutableList<Instruction> = mutableListOf(), | ||||
|                        val variables: MutableList<Variable> = mutableListOf(), | ||||
|                        val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(), | ||||
|                        val labels: MutableMap<String, Instruction> = mutableMapOf(),        // names are fully scoped | ||||
|                        val force_output: Boolean) | ||||
|  | ||||
|     val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>() | ||||
|     val blocks = mutableListOf<ProgramBlock>() | ||||
|     val memory = mutableMapOf<Int, List<RuntimeValue>>() | ||||
|     private lateinit var currentBlock: ProgramBlock | ||||
|  | ||||
|     fun allocateZeropage(zeropage: Zeropage) {          // TODO not used anymore??? | ||||
|         // 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.params.zp==ZeropageWish.REQUIRE_ZEROPAGE || it.params.zp==ZeropageWish.PREFER_ZEROPAGE } | ||||
|             if (zpVariables.isNotEmpty()) { | ||||
|                 for (variable in zpVariables) { | ||||
|                     if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null) | ||||
|                         throw CompilerException("zp conflict") | ||||
|                     try { | ||||
|                         val address = zeropage.allocate(variable.scopedname, variable.value.type, null) | ||||
|                         allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type) | ||||
|                     } catch (x: ZeropageDepletedError) { | ||||
|                         printWarning(x.toString() + " variable ${variable.scopedname} 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 -> { | ||||
|                 // var decls that are defined inside of a StructDecl are skipped in the output | ||||
|                 //   because every occurrence of the members will have a separate mangled vardecl for that occurrence | ||||
|                 if(decl.parent is StructDecl) | ||||
|                     return | ||||
|  | ||||
|                 val valueparams = VariableParameters(decl.zeropage, decl.struct) | ||||
|                 val value = when(decl.datatype) { | ||||
|                     in NumericDatatypes -> { | ||||
|                         RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number) | ||||
|                     } | ||||
|                     in StringDatatypes -> { | ||||
|                         val litval = (decl.value as ReferenceLiteralValue) | ||||
|                         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? ReferenceLiteralValue) | ||||
|                         if(litval!=null && litval.heapId==null) | ||||
|                             throw CompilerException("array should already be in the heap") | ||||
|                         if(litval!=null){ | ||||
|                             RuntimeValue(decl.datatype, heapId = litval.heapId) | ||||
|                         } else { | ||||
|                             throw CompilerException("initialization value expected") | ||||
|                         } | ||||
|                     } | ||||
|                     DataType.STRUCT -> { | ||||
|                         // struct variables have been flattened already | ||||
|                         return | ||||
|                     } | ||||
|                     else -> throw CompilerException("weird datatype") | ||||
|                 } | ||||
|                 currentBlock.variables.add(Variable(scopedname, value, valueparams)) | ||||
|             } | ||||
|             VarDeclType.MEMORY -> { | ||||
|                 // note that constants are all folded away, but assembly code may still refer to them | ||||
|                 val lv = decl.value as NumericLiteralValue | ||||
|                 if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE) | ||||
|                     throw CompilerException("expected integer memory address $lv") | ||||
|                 currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), 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 NumericLiteralValue | ||||
|                 if(lv.type in IntegerDatatypes) | ||||
|                     currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), 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) { | ||||
|             if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE) | ||||
|                 throw CompilerException("zp conflict") | ||||
|             val valuestr = variable.value.toString() | ||||
|             val struct =  if(variable.params.memberOfStruct==null) "" else "struct=${variable.params.memberOfStruct.name}" | ||||
|             out.println("${variable.scopedname}  ${variable.value.type.name.toLowerCase()}  $valuestr  zp=${variable.params.zp} s=$struct") | ||||
|         } | ||||
|         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 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 | ||||
| ) | ||||
| @@ -1,761 +0,0 @@ | ||||
| package compiler.target.c64.codegen | ||||
|  | ||||
| // 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.ast.statements.ZeropageWish | ||||
| import prog8.compiler.* | ||||
| import prog8.compiler.intermediate.Instruction | ||||
| import prog8.compiler.intermediate.IntermediateProgram | ||||
| import prog8.compiler.intermediate.LabelInstr | ||||
| import prog8.compiler.intermediate.Opcode | ||||
| import prog8.compiler.target.c64.AssemblyProgram | ||||
| import prog8.compiler.target.c64.MachineDefinition | ||||
| import prog8.compiler.target.c64.Petscii | ||||
| import prog8.vm.RuntimeValue | ||||
| 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 { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList() | ||||
|             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) | ||||
|             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: MachineDefinition.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) | ||||
|                 MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.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(MachineDefinition.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.scopedname, 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.params.zp != ZeropageWish.NOT_IN_ZEROPAGE && | ||||
|                         variable.value.type in zeropage.allowedDatatypes | ||||
|                         && variable.value.type != DataType.FLOAT) { | ||||
|                     try { | ||||
|                         val address = zeropage.allocate(sym, variable.value.type, null) | ||||
|                         out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}") | ||||
|                         // make sure we add the var to the set of zpvars for this block | ||||
|                         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.scopedname} = ${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) { | ||||
|         val uniqueNames = block.variables.map { it.scopedname }.toSet() | ||||
|         if (uniqueNames.size != block.variables.size) | ||||
|             throw AssemblyError("not all variables have unique names") | ||||
|  | ||||
|         // these are the non-zeropage variables. | ||||
|         // first get all the flattened struct members, they MUST remain in order | ||||
|         out(";  flattened struct members") | ||||
|         val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null } | ||||
|         structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) } | ||||
|  | ||||
|         // sort the other variables by type | ||||
|         out(";  other variables sorted by type") | ||||
|         val sortedVars = normalVars.sortedBy { it.value.type } | ||||
|         for (variable in sortedVars) { | ||||
|             val sym = symname(block.name + "." + variable.scopedname, null) | ||||
|             if(sym in program.allocatedZeropageVariables) | ||||
|                 continue  // skip the ones that already belong in the zero page | ||||
|             vardecl2asm(variable.scopedname, variable.value, variable.params) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) { | ||||
|         when (value.type) { | ||||
|             DataType.UBYTE -> out("$varname\t.byte  0") | ||||
|             DataType.BYTE -> out("$varname\t.char  0") | ||||
|             DataType.UWORD -> out("$varname\t.word  0") | ||||
|             DataType.WORD -> out("$varname\t.sint  0") | ||||
|             DataType.FLOAT -> out("$varname\t.byte  0,0,0,0,0  ; float") | ||||
|             DataType.STR, DataType.STR_S -> { | ||||
|                 val rawStr = heap.get(value.heapId!!).str!! | ||||
|                 val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') } | ||||
|                 out("$varname\t; ${value.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(value) | ||||
|                 if (data.size <= 16) | ||||
|                     out("$varname\t.byte  ${data.joinToString()}") | ||||
|                 else { | ||||
|                     out(varname) | ||||
|                     for (chunk in data.chunked(16)) | ||||
|                         out("  .byte  " + chunk.joinToString()) | ||||
|                 } | ||||
|             } | ||||
|             DataType.ARRAY_B -> { | ||||
|                 // signed integer byte arraysize | ||||
|                 val data = makeArrayFillDataSigned(value) | ||||
|                 if (data.size <= 16) | ||||
|                     out("$varname\t.char  ${data.joinToString()}") | ||||
|                 else { | ||||
|                     out(varname) | ||||
|                     for (chunk in data.chunked(16)) | ||||
|                         out("  .char  " + chunk.joinToString()) | ||||
|                 } | ||||
|             } | ||||
|             DataType.ARRAY_UW -> { | ||||
|                 // unsigned word arraysize | ||||
|                 val data = makeArrayFillDataUnsigned(value) | ||||
|                 if (data.size <= 16) | ||||
|                     out("$varname\t.word  ${data.joinToString()}") | ||||
|                 else { | ||||
|                     out(varname) | ||||
|                     for (chunk in data.chunked(16)) | ||||
|                         out("  .word  " + chunk.joinToString()) | ||||
|                 } | ||||
|             } | ||||
|             DataType.ARRAY_W -> { | ||||
|                 // signed word arraysize | ||||
|                 val data = makeArrayFillDataSigned(value) | ||||
|                 if (data.size <= 16) | ||||
|                     out("$varname\t.sint  ${data.joinToString()}") | ||||
|                 else { | ||||
|                     out(varname) | ||||
|                     for (chunk in data.chunked(16)) | ||||
|                         out("  .sint  " + chunk.joinToString()) | ||||
|                 } | ||||
|             } | ||||
|             DataType.ARRAY_F -> { | ||||
|                 // float arraysize | ||||
|                 val array = heap.get(value.heapId!!).doubleArray!! | ||||
|                 val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) } | ||||
|                 out(varname) | ||||
|                 for (f in array.zip(floatFills)) | ||||
|                     out("  .byte  ${f.second}  ; float ${f.first}") | ||||
|             } | ||||
|             DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     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.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 * MachineDefinition.Mflpt5.MemorySize}) | ||||
|                 ldy  #>($variable+${index * MachineDefinition.Mflpt5.MemorySize}) | ||||
|                 jsr  c64flt.inc_var_f | ||||
|                 """) | ||||
|             Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment( | ||||
|                     """ | ||||
|                 lda  #<($variable+${index * MachineDefinition.Mflpt5.MemorySize}) | ||||
|                 ldy  #>($variable+${index * MachineDefinition.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  ${MachineDefinition.C64Zeropage.SCRATCH_B1} |" | ||||
|         val restoreX = " | ldx  ${MachineDefinition.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,559 +0,0 @@ | ||||
| package compiler.target.c64.codegen | ||||
|  | ||||
| import prog8.compiler.CompilerException | ||||
| import prog8.compiler.intermediate.Instruction | ||||
| import prog8.compiler.intermediate.IntermediateProgram | ||||
| import prog8.compiler.intermediate.LabelInstr | ||||
| import prog8.compiler.intermediate.Opcode | ||||
| import prog8.compiler.target.c64.MachineDefinition.C64Zeropage | ||||
| import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX | ||||
| import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX | ||||
| import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX | ||||
| import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX | ||||
| import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX | ||||
| import prog8.compiler.toHex | ||||
| import prog8.vm.stackvm.Syscall | ||||
| import prog8.vm.stackvm.syscallsForStackVm | ||||
|  | ||||
|  | ||||
| // note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations | ||||
|  | ||||
|  | ||||
| private var breakpointCounter = 0 | ||||
|  | ||||
| internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? { | ||||
|     // a label 'instruction' is simply translated into a asm label | ||||
|     if(ins is LabelInstr) { | ||||
|         val labelresult = | ||||
|                 if(ins.name.startsWith("${block.name}.")) | ||||
|                     ins.name.substring(block.name.length+1) | ||||
|                 else | ||||
|                     ins.name | ||||
|         return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult | ||||
|     } | ||||
|  | ||||
|     // simple opcodes that are translated directly into one or a few asm instructions | ||||
|     return when(ins.opcode) { | ||||
|         Opcode.LINE -> " ;\tsrc line: ${ins.callLabel}" | ||||
|         Opcode.NOP -> " nop"      // shouldn't be present anymore though | ||||
|         Opcode.START_PROCDEF -> ""  // is done as part of a label | ||||
|         Opcode.END_PROCDEF -> " .pend" | ||||
|         Opcode.TERMINATE -> " brk" | ||||
|         Opcode.SEC -> " sec" | ||||
|         Opcode.CLC -> " clc" | ||||
|         Opcode.SEI -> " sei" | ||||
|         Opcode.CLI -> " cli" | ||||
|         Opcode.CARRY_TO_A -> " lda  #0 |  adc  #0" | ||||
|         Opcode.JUMP -> { | ||||
|             if(ins.callLabel!=null) | ||||
|                 " jmp  ${ins.callLabel}" | ||||
|             else | ||||
|                 " jmp  ${hexVal(ins)}" | ||||
|         } | ||||
|         Opcode.CALL -> { | ||||
|             if(ins.callLabel!=null) | ||||
|                 " jsr  ${ins.callLabel}" | ||||
|             else | ||||
|                 " jsr  ${hexVal(ins)}" | ||||
|         } | ||||
|         Opcode.RETURN -> " rts" | ||||
|         Opcode.RSAVE -> { | ||||
|             // save cpu status flag and all registers A, X, Y. | ||||
|             // see http://6502.org/tutorials/register_preservation.html | ||||
|             " php |  sta  ${C64Zeropage.SCRATCH_REG} | pha  | txa  | pha  | tya  | pha  | lda  ${C64Zeropage.SCRATCH_REG}" | ||||
|         } | ||||
|         Opcode.RRESTORE -> { | ||||
|             // restore all registers and cpu status flag | ||||
|             " pla |  tay |  pla |  tax |  pla |  plp" | ||||
|         } | ||||
|         Opcode.RSAVEX -> " sta  ${C64Zeropage.SCRATCH_REG} |  txa |  pha |  lda  ${C64Zeropage.SCRATCH_REG}" | ||||
|         Opcode.RRESTOREX -> " sta  ${C64Zeropage.SCRATCH_REG} |  pla |  tax |  lda  ${C64Zeropage.SCRATCH_REG}" | ||||
|         Opcode.DISCARD_BYTE -> " inx" | ||||
|         Opcode.DISCARD_WORD -> " inx" | ||||
|         Opcode.DISCARD_FLOAT -> " inx |  inx |  inx" | ||||
|         Opcode.DUP_B -> { | ||||
|             " lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | dex | ;DUP_B " | ||||
|         } | ||||
|         Opcode.DUP_W -> { | ||||
|             " lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex " | ||||
|         } | ||||
|  | ||||
|         Opcode.CMP_B, Opcode.CMP_UB -> { | ||||
|             " inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B " | ||||
|         } | ||||
|  | ||||
|         Opcode.CMP_W, Opcode.CMP_UW -> { | ||||
|             """ | ||||
|             inx | ||||
|             lda   $ESTACK_HI_HEX,x | ||||
|             cmp   #>${ins.arg!!.integerValue().toHex()} | ||||
|             bne   + | ||||
|             lda   $ESTACK_LO_HEX,x | ||||
|             cmp   #<${ins.arg.integerValue().toHex()} | ||||
|             ; bne   +    not necessary? | ||||
|             ; lda   #0   not necessary? | ||||
| +                            | ||||
|             """ | ||||
|         } | ||||
|  | ||||
|         Opcode.INLINE_ASSEMBLY ->  "@inline@" + (ins.callLabel2 ?: "")      // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it. | ||||
|         Opcode.INCLUDE_FILE -> { | ||||
|             val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}" | ||||
|             val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}" | ||||
|             " .binary  \"${ins.callLabel}\" $offset $length" | ||||
|         } | ||||
|         Opcode.SYSCALL -> { | ||||
|             if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr }) | ||||
|                 throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}") | ||||
|             val call = Syscall.values().find { it.callNr==ins.arg.numericValue() } | ||||
|             when(call) { | ||||
|                 Syscall.FUNC_SIN, | ||||
|                 Syscall.FUNC_COS, | ||||
|                 Syscall.FUNC_ABS, | ||||
|                 Syscall.FUNC_TAN, | ||||
|                 Syscall.FUNC_ATAN, | ||||
|                 Syscall.FUNC_LN, | ||||
|                 Syscall.FUNC_LOG2, | ||||
|                 Syscall.FUNC_SQRT, | ||||
|                 Syscall.FUNC_RAD, | ||||
|                 Syscall.FUNC_DEG, | ||||
|                 Syscall.FUNC_ROUND, | ||||
|                 Syscall.FUNC_FLOOR, | ||||
|                 Syscall.FUNC_CEIL, | ||||
|                 Syscall.FUNC_RNDF, | ||||
|                 Syscall.FUNC_ANY_F, | ||||
|                 Syscall.FUNC_ALL_F, | ||||
|                 Syscall.FUNC_MAX_F, | ||||
|                 Syscall.FUNC_MIN_F, | ||||
|                 Syscall.FUNC_SUM_F -> " jsr  c64flt.${call.name.toLowerCase()}" | ||||
|                 null -> "" | ||||
|                 else -> " jsr  prog8_lib.${call.name.toLowerCase()}" | ||||
|             } | ||||
|         } | ||||
|         Opcode.BREAKPOINT -> { | ||||
|             breakpointCounter++ | ||||
|             "_prog8_breakpoint_$breakpointCounter\tnop" | ||||
|         } | ||||
|  | ||||
|         Opcode.PUSH_BYTE -> { | ||||
|             " lda  #${hexVal(ins)} |  sta  $ESTACK_LO_HEX,x |  dex" | ||||
|         } | ||||
|         Opcode.PUSH_WORD -> { | ||||
|             val value = hexVal(ins) | ||||
|             " lda  #<$value |  sta  $ESTACK_LO_HEX,x |  lda  #>$value |  sta  $ESTACK_HI_HEX,x |  dex" | ||||
|         } | ||||
|         Opcode.PUSH_FLOAT -> { | ||||
|             val floatConst = getFloatConst(ins.arg!!) | ||||
|             " lda  #<$floatConst |  ldy  #>$floatConst |  jsr  c64flt.push_float" | ||||
|         } | ||||
|         Opcode.PUSH_VAR_BYTE -> { | ||||
|             when(ins.callLabel) { | ||||
|                 "X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)") | ||||
|                 "A" -> " sta  $ESTACK_LO_HEX,x |  dex" | ||||
|                 "Y" -> " tya |  sta  $ESTACK_LO_HEX,x |  dex" | ||||
|                 else -> " lda  ${ins.callLabel} |  sta  $ESTACK_LO_HEX,x |  dex" | ||||
|             } | ||||
|         } | ||||
|         Opcode.PUSH_VAR_WORD -> { | ||||
|             " lda  ${ins.callLabel} |  sta  $ESTACK_LO_HEX,x |  lda  ${ins.callLabel}+1 |    sta  $ESTACK_HI_HEX,x |  dex" | ||||
|         } | ||||
|         Opcode.PUSH_VAR_FLOAT -> " lda  #<${ins.callLabel} |  ldy  #>${ins.callLabel}|  jsr  c64flt.push_float" | ||||
|         Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> { | ||||
|             """ | ||||
|                 lda  ${hexVal(ins)} | ||||
|                 sta  $ESTACK_LO_HEX,x | ||||
|                 dex | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> { | ||||
|             """ | ||||
|                 lda  ${hexVal(ins)} | ||||
|                 sta  $ESTACK_LO_HEX,x | ||||
|                 lda  ${hexValPlusOne(ins)} | ||||
|                 sta  $ESTACK_HI_HEX,x | ||||
|                 dex | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.PUSH_MEM_FLOAT -> { | ||||
|             " lda  #<${hexVal(ins)} |  ldy  #>${hexVal(ins)}|  jsr  c64flt.push_float" | ||||
|         } | ||||
|         Opcode.PUSH_MEMREAD -> { | ||||
|             """ | ||||
|                 lda  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 sta  (+) +1 | ||||
|                 lda  $ESTACK_HI_PLUS1_HEX,x | ||||
|                 sta  (+) +2 | ||||
| +               lda  65535    ; modified | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 """ | ||||
|         } | ||||
|  | ||||
|         Opcode.PUSH_REGAY_WORD -> { | ||||
|             " sta  $ESTACK_LO_HEX,x |  tya |  sta  $ESTACK_HI_HEX,x |  dex " | ||||
|         } | ||||
|         Opcode.PUSH_ADDR_HEAPVAR -> { | ||||
|             " lda  #<${ins.callLabel} |  sta  $ESTACK_LO_HEX,x |  lda  #>${ins.callLabel}  |  sta  $ESTACK_HI_HEX,x |  dex" | ||||
|         } | ||||
|         Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself") | ||||
|         Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself") | ||||
|         Opcode.POP_REGAY_WORD -> { | ||||
|             " inx |  lda  $ESTACK_LO_HEX,x |  ldy  $ESTACK_HI_HEX,x " | ||||
|         } | ||||
|  | ||||
|         Opcode.READ_INDEXED_VAR_BYTE -> { | ||||
|             """ | ||||
|                 ldy  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 lda  ${ins.callLabel},y | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.READ_INDEXED_VAR_WORD -> { | ||||
|             """ | ||||
|                 lda  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 asl  a | ||||
|                 tay | ||||
|                 lda  ${ins.callLabel},y | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 lda  ${ins.callLabel}+1,y | ||||
|                 sta  $ESTACK_HI_PLUS1_HEX,x | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.READ_INDEXED_VAR_FLOAT -> { | ||||
|             """ | ||||
|                 lda  #<${ins.callLabel} | ||||
|                 ldy  #>${ins.callLabel} | ||||
|                 jsr  c64flt.push_float_from_indexed_var | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.WRITE_INDEXED_VAR_BYTE -> { | ||||
|             """ | ||||
|                 inx | ||||
|                 ldy  $ESTACK_LO_HEX,x | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 sta  ${ins.callLabel},y | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.WRITE_INDEXED_VAR_WORD -> { | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 asl  a | ||||
|                 tay | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 sta  ${ins.callLabel},y | ||||
|                 lda  $ESTACK_HI_HEX,x | ||||
|                 sta  ${ins.callLabel}+1,y | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.WRITE_INDEXED_VAR_FLOAT -> { | ||||
|             """ | ||||
|                 lda  #<${ins.callLabel} | ||||
|                 ldy  #>${ins.callLabel} | ||||
|                 jsr  c64flt.pop_float_to_indexed_var | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.POP_MEM_BYTE -> { | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 sta  ${hexVal(ins)} | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.POP_MEM_WORD -> { | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 sta  ${hexVal(ins)} | ||||
|                 lda  $ESTACK_HI_HEX,x | ||||
|                 sta  ${hexValPlusOne(ins)} | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.POP_MEM_FLOAT -> { | ||||
|             " lda  ${hexVal(ins)} |  ldy  ${hexValPlusOne(ins)} |  jsr  c64flt.pop_float" | ||||
|         } | ||||
|         Opcode.POP_MEMWRITE -> { | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 sta  (+) +1 | ||||
|                 lda  $ESTACK_HI_HEX,x | ||||
|                 sta  (+) +2 | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
| +               sta  65535       ; modified | ||||
|                 """ | ||||
|         } | ||||
|  | ||||
|         Opcode.POP_VAR_BYTE -> { | ||||
|             when (ins.callLabel) { | ||||
|                 "X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself") | ||||
|                 "A" -> " inx |  lda  $ESTACK_LO_HEX,x" | ||||
|                 "Y" -> " inx |  ldy  $ESTACK_LO_HEX,x" | ||||
|                 else -> " inx |  lda  $ESTACK_LO_HEX,x |  sta  ${ins.callLabel}" | ||||
|             } | ||||
|         } | ||||
|         Opcode.POP_VAR_WORD -> { | ||||
|             " inx |  lda  $ESTACK_LO_HEX,x |  ldy  $ESTACK_HI_HEX,x |  sta  ${ins.callLabel} |  sty  ${ins.callLabel}+1" | ||||
|         } | ||||
|         Opcode.POP_VAR_FLOAT -> { | ||||
|             " lda  #<${ins.callLabel} |  ldy  #>${ins.callLabel} |  jsr  c64flt.pop_float" | ||||
|         } | ||||
|  | ||||
|         Opcode.INC_VAR_UB, Opcode.INC_VAR_B -> { | ||||
|             when (ins.callLabel) { | ||||
|                 "A" -> " clc |  adc  #1" | ||||
|                 "X" -> " inx" | ||||
|                 "Y" -> " iny" | ||||
|                 else -> " inc  ${ins.callLabel}" | ||||
|             } | ||||
|         } | ||||
|         Opcode.INC_VAR_UW, Opcode.INC_VAR_W -> { | ||||
|             " inc  ${ins.callLabel} |  bne  + |  inc  ${ins.callLabel}+1 |+" | ||||
|         } | ||||
|         Opcode.INC_VAR_F -> { | ||||
|             """ | ||||
|                 lda  #<${ins.callLabel} | ||||
|                 ldy  #>${ins.callLabel} | ||||
|                 jsr  c64flt.inc_var_f | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.POP_INC_MEMORY -> { | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 sta  (+) +1 | ||||
|                 lda  $ESTACK_HI_HEX,x | ||||
|                 sta  (+) +2 | ||||
| +               inc  65535     ; modified | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.POP_DEC_MEMORY -> { | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 sta  (+) +1 | ||||
|                 lda  $ESTACK_HI_HEX,x | ||||
|                 sta  (+) +2 | ||||
| +               dec  65535     ; modified | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.DEC_VAR_UB, Opcode.DEC_VAR_B -> { | ||||
|             when (ins.callLabel) { | ||||
|                 "A" -> " sec |  sbc  #1" | ||||
|                 "X" -> " dex" | ||||
|                 "Y" -> " dey" | ||||
|                 else -> " dec  ${ins.callLabel}" | ||||
|             } | ||||
|         } | ||||
|         Opcode.DEC_VAR_UW, Opcode.DEC_VAR_W -> { | ||||
|             " lda  ${ins.callLabel} |  bne  + |  dec  ${ins.callLabel}+1 |+ |  dec  ${ins.callLabel}" | ||||
|         } | ||||
|         Opcode.DEC_VAR_F -> { | ||||
|             """ | ||||
|                 lda  #<${ins.callLabel} | ||||
|                 ldy  #>${ins.callLabel} | ||||
|                 jsr  c64flt.dec_var_f | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.INC_MEMORY -> " inc  ${hexVal(ins)}" | ||||
|         Opcode.DEC_MEMORY -> " dec  ${hexVal(ins)}" | ||||
|         Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx |  txa |  pha |  lda  $ESTACK_LO_HEX,x |  tax |  inc  ${ins.callLabel},x |  pla |  tax" | ||||
|         Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx |  txa |  pha |  lda  $ESTACK_LO_HEX,x |  tax |  dec  ${ins.callLabel},x |  pla |  tax" | ||||
|  | ||||
|         Opcode.NEG_B -> " jsr  prog8_lib.neg_b" | ||||
|         Opcode.NEG_W -> " jsr  prog8_lib.neg_w" | ||||
|         Opcode.NEG_F -> " jsr  c64flt.neg_f" | ||||
|         Opcode.ABS_B -> " jsr  prog8_lib.abs_b" | ||||
|         Opcode.ABS_W -> " jsr  prog8_lib.abs_w" | ||||
|         Opcode.ABS_F -> " jsr  c64flt.abs_f" | ||||
|         Opcode.POW_F -> " jsr  c64flt.pow_f" | ||||
|         Opcode.INV_BYTE -> { | ||||
|             """ | ||||
|                 lda  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 eor  #255 | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.INV_WORD -> " jsr  prog8_lib.inv_word" | ||||
|         Opcode.NOT_BYTE -> " jsr  prog8_lib.not_byte" | ||||
|         Opcode.NOT_WORD -> " jsr  prog8_lib.not_word" | ||||
|         Opcode.BCS -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " bcs  $label" | ||||
|         } | ||||
|         Opcode.BCC -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " bcc  $label" | ||||
|         } | ||||
|         Opcode.BNEG -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " bmi  $label" | ||||
|         } | ||||
|         Opcode.BPOS -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " bpl  $label" | ||||
|         } | ||||
|         Opcode.BVC -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " bvc  $label" | ||||
|         } | ||||
|         Opcode.BVS -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " bvs  $label" | ||||
|         } | ||||
|         Opcode.BZ -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " beq  $label" | ||||
|         } | ||||
|         Opcode.BNZ -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             " bne  $label" | ||||
|         } | ||||
|         Opcode.JZ -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 beq  $label | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.JZW -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 beq  $label | ||||
|                 lda  $ESTACK_HI_HEX,x | ||||
|                 beq  $label | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.JNZ -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 bne  $label | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.JNZW -> { | ||||
|             val label = ins.callLabel ?: hexVal(ins) | ||||
|             """ | ||||
|                 inx | ||||
|                 lda  $ESTACK_LO_HEX,x | ||||
|                 bne  $label | ||||
|                 lda  $ESTACK_HI_HEX,x | ||||
|                 bne  $label | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.CAST_B_TO_UB -> ""  // is a no-op, just carry on with the byte as-is | ||||
|         Opcode.CAST_UB_TO_B -> ""  // is a no-op, just carry on with the byte as-is | ||||
|         Opcode.CAST_W_TO_UW -> ""  // is a no-op, just carry on with the word as-is | ||||
|         Opcode.CAST_UW_TO_W -> ""  // is a no-op, just carry on with the word as-is | ||||
|         Opcode.CAST_W_TO_UB -> ""  // is a no-op, just carry on with the lsb of the word as-is | ||||
|         Opcode.CAST_W_TO_B -> ""  // is a no-op, just carry on with the lsb of the word as-is | ||||
|         Opcode.CAST_UW_TO_UB -> ""  // is a no-op, just carry on with the lsb of the uword as-is | ||||
|         Opcode.CAST_UW_TO_B -> ""  // is a no-op, just carry on with the lsb of the uword as-is | ||||
|         Opcode.CAST_UB_TO_F -> " jsr  c64flt.stack_ub2float" | ||||
|         Opcode.CAST_B_TO_F -> " jsr  c64flt.stack_b2float" | ||||
|         Opcode.CAST_UW_TO_F -> " jsr  c64flt.stack_uw2float" | ||||
|         Opcode.CAST_W_TO_F -> " jsr  c64flt.stack_w2float" | ||||
|         Opcode.CAST_F_TO_UB -> " jsr  c64flt.stack_float2ub" | ||||
|         Opcode.CAST_F_TO_B -> " jsr  c64flt.stack_float2b" | ||||
|         Opcode.CAST_F_TO_UW -> " jsr  c64flt.stack_float2uw" | ||||
|         Opcode.CAST_F_TO_W -> " jsr  c64flt.stack_float2w" | ||||
|         Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda  #0 |  sta  $ESTACK_HI_PLUS1_HEX,x"     // clear the msb | ||||
|         Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda  $ESTACK_LO_PLUS1_HEX,x |  ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}"     // sign extend the lsb | ||||
|         Opcode.MSB -> " lda  $ESTACK_HI_PLUS1_HEX,x |  sta  $ESTACK_LO_PLUS1_HEX,x" | ||||
|         Opcode.MKWORD -> " inx |  lda  $ESTACK_LO_HEX,x |  sta  $ESTACK_HI_PLUS1_HEX,x " | ||||
|  | ||||
|         Opcode.ADD_UB, Opcode.ADD_B -> {        // TODO inline better (pattern with more opcodes) | ||||
|             """ | ||||
|                 lda  $ESTACK_LO_PLUS2_HEX,x | ||||
|                 clc | ||||
|                 adc  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 inx | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.SUB_UB, Opcode.SUB_B -> {        // TODO inline better (pattern with more opcodes) | ||||
|             """ | ||||
|                 lda  $ESTACK_LO_PLUS2_HEX,x | ||||
|                 sec | ||||
|                 sbc  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 inx | ||||
|                 sta  $ESTACK_LO_PLUS1_HEX,x | ||||
|                 """ | ||||
|         } | ||||
|         Opcode.ADD_W, Opcode.ADD_UW -> "  jsr  prog8_lib.add_w" | ||||
|         Opcode.SUB_W, Opcode.SUB_UW -> "  jsr  prog8_lib.sub_w" | ||||
|         Opcode.MUL_B, Opcode.MUL_UB -> "  jsr  prog8_lib.mul_byte" | ||||
|         Opcode.MUL_W, Opcode.MUL_UW -> "  jsr  prog8_lib.mul_word" | ||||
|         Opcode.MUL_F -> "  jsr  c64flt.mul_f" | ||||
|         Opcode.ADD_F -> "  jsr  c64flt.add_f" | ||||
|         Opcode.SUB_F -> "  jsr  c64flt.sub_f" | ||||
|         Opcode.DIV_F -> "  jsr  c64flt.div_f" | ||||
|         Opcode.IDIV_UB -> "  jsr  prog8_lib.idiv_ub" | ||||
|         Opcode.IDIV_B -> "  jsr  prog8_lib.idiv_b" | ||||
|         Opcode.IDIV_W -> "  jsr  prog8_lib.idiv_w" | ||||
|         Opcode.IDIV_UW -> "  jsr  prog8_lib.idiv_uw" | ||||
|  | ||||
|         Opcode.AND_BYTE -> "  jsr  prog8_lib.and_b" | ||||
|         Opcode.OR_BYTE -> "  jsr  prog8_lib.or_b" | ||||
|         Opcode.XOR_BYTE -> "  jsr  prog8_lib.xor_b" | ||||
|         Opcode.AND_WORD -> "  jsr  prog8_lib.and_w" | ||||
|         Opcode.OR_WORD -> "  jsr  prog8_lib.or_w" | ||||
|         Opcode.XOR_WORD -> "  jsr  prog8_lib.xor_w" | ||||
|  | ||||
|         Opcode.BITAND_BYTE -> "  jsr  prog8_lib.bitand_b" | ||||
|         Opcode.BITOR_BYTE -> "  jsr  prog8_lib.bitor_b" | ||||
|         Opcode.BITXOR_BYTE -> "  jsr  prog8_lib.bitxor_b" | ||||
|         Opcode.BITAND_WORD -> "  jsr  prog8_lib.bitand_w" | ||||
|         Opcode.BITOR_WORD -> "  jsr  prog8_lib.bitor_w" | ||||
|         Opcode.BITXOR_WORD -> "  jsr  prog8_lib.bitxor_w" | ||||
|  | ||||
|         Opcode.REMAINDER_UB -> "  jsr  prog8_lib.remainder_ub" | ||||
|         Opcode.REMAINDER_UW -> "  jsr  prog8_lib.remainder_uw" | ||||
|  | ||||
|         Opcode.GREATER_B -> "  jsr  prog8_lib.greater_b" | ||||
|         Opcode.GREATER_UB -> "  jsr  prog8_lib.greater_ub" | ||||
|         Opcode.GREATER_W -> "  jsr  prog8_lib.greater_w" | ||||
|         Opcode.GREATER_UW -> "  jsr  prog8_lib.greater_uw" | ||||
|         Opcode.GREATER_F -> "  jsr  c64flt.greater_f" | ||||
|  | ||||
|         Opcode.GREATEREQ_B -> "  jsr  prog8_lib.greatereq_b" | ||||
|         Opcode.GREATEREQ_UB -> "  jsr  prog8_lib.greatereq_ub" | ||||
|         Opcode.GREATEREQ_W -> "  jsr  prog8_lib.greatereq_w" | ||||
|         Opcode.GREATEREQ_UW -> "  jsr  prog8_lib.greatereq_uw" | ||||
|         Opcode.GREATEREQ_F -> "  jsr  c64flt.greatereq_f" | ||||
|  | ||||
|         Opcode.EQUAL_BYTE -> "  jsr  prog8_lib.equal_b" | ||||
|         Opcode.EQUAL_WORD -> "  jsr  prog8_lib.equal_w" | ||||
|         Opcode.EQUAL_F -> "  jsr  c64flt.equal_f" | ||||
|         Opcode.NOTEQUAL_BYTE -> "  jsr prog8_lib.notequal_b" | ||||
|         Opcode.NOTEQUAL_WORD -> "  jsr prog8_lib.notequal_w" | ||||
|         Opcode.NOTEQUAL_F -> "  jsr  c64flt.notequal_f" | ||||
|  | ||||
|         Opcode.LESS_UB -> "  jsr  prog8_lib.less_ub" | ||||
|         Opcode.LESS_B -> "  jsr  prog8_lib.less_b" | ||||
|         Opcode.LESS_UW -> "  jsr  prog8_lib.less_uw" | ||||
|         Opcode.LESS_W -> "  jsr  prog8_lib.less_w" | ||||
|         Opcode.LESS_F -> "  jsr  c64flt.less_f" | ||||
|  | ||||
|         Opcode.LESSEQ_UB -> "  jsr  prog8_lib.lesseq_ub" | ||||
|         Opcode.LESSEQ_B -> "  jsr  prog8_lib.lesseq_b" | ||||
|         Opcode.LESSEQ_UW -> "  jsr  prog8_lib.lesseq_uw" | ||||
|         Opcode.LESSEQ_W -> "  jsr  prog8_lib.lesseq_w" | ||||
|         Opcode.LESSEQ_F -> "  jsr  c64flt.lesseq_f" | ||||
|  | ||||
|         Opcode.SHIFTEDL_BYTE -> "  asl  $ESTACK_LO_PLUS1_HEX,x" | ||||
|         Opcode.SHIFTEDL_WORD -> "  asl  $ESTACK_LO_PLUS1_HEX,x |  rol  $ESTACK_HI_PLUS1_HEX,x" | ||||
|         Opcode.SHIFTEDR_SBYTE -> "  lda  $ESTACK_LO_PLUS1_HEX,x |  asl  a |  ror  $ESTACK_LO_PLUS1_HEX,x" | ||||
|         Opcode.SHIFTEDR_UBYTE -> "  lsr  $ESTACK_LO_PLUS1_HEX,x" | ||||
|         Opcode.SHIFTEDR_SWORD -> "  lda  $ESTACK_HI_PLUS1_HEX,x |  asl a  |  ror  $ESTACK_HI_PLUS1_HEX,x |  ror  $ESTACK_LO_PLUS1_HEX,x" | ||||
|         Opcode.SHIFTEDR_UWORD -> "  lsr  $ESTACK_HI_PLUS1_HEX,x |  ror  $ESTACK_LO_PLUS1_HEX,x" | ||||
|  | ||||
|         else -> null | ||||
|     } | ||||
| } | ||||
| @@ -1,47 +0,0 @@ | ||||
| package prog8.vm.stackvm | ||||
|  | ||||
| import prog8.printSoftwareHeader | ||||
| import prog8.vm.astvm.ScreenDialog | ||||
| import java.awt.EventQueue | ||||
| import javax.swing.Timer | ||||
| import kotlin.system.exitProcess | ||||
|  | ||||
| fun main(args: Array<String>) { | ||||
|     stackVmMain(args) | ||||
| } | ||||
|  | ||||
| fun stackVmMain(args: Array<String>) { | ||||
|     printSoftwareHeader("StackVM") | ||||
|  | ||||
|     if(args.size != 1) { | ||||
|         System.err.println("requires one argument: name of stackvm sourcecode file") | ||||
|         exitProcess(1) | ||||
|     } | ||||
|  | ||||
|     val program = Program.load(args.first()) | ||||
|     val vm = StackVm(traceOutputFile = null) | ||||
|     val dialog = ScreenDialog("StackVM") | ||||
|     vm.load(program, dialog.canvas) | ||||
|     EventQueue.invokeLater { | ||||
|         dialog.pack() | ||||
|         dialog.isVisible = true | ||||
|         dialog.start() | ||||
|  | ||||
|         val programTimer = Timer(10) { a -> | ||||
|             try { | ||||
|                 vm.step() | ||||
|             } catch(bp: VmBreakpointException) { | ||||
|                 println("Breakpoint: execution halted. Press enter to resume.") | ||||
|                 readLine() | ||||
|             } catch (tx: VmTerminationException) { | ||||
|                 println("Execution halted: ${tx.message}") | ||||
|                 (a.source as Timer).stop() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         val irqTimer = Timer(1000/60) { a -> vm.irq(a.`when`) } | ||||
|  | ||||
|         programTimer.start() | ||||
|         irqTimer.start() | ||||
|     } | ||||
| } | ||||
| @@ -1,302 +0,0 @@ | ||||
| package prog8.vm.stackvm | ||||
|  | ||||
| import prog8.ast.antlr.unescape | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.AddressOf | ||||
| import prog8.ast.expressions.IdentifierReference | ||||
| import prog8.compiler.HeapValues | ||||
| import prog8.compiler.IntegerOrAddressOf | ||||
| import prog8.compiler.intermediate.Instruction | ||||
| import prog8.compiler.intermediate.LabelInstr | ||||
| import prog8.compiler.intermediate.Opcode | ||||
| import prog8.compiler.intermediate.opcodesWithVarArgument | ||||
| import prog8.vm.RuntimeValue | ||||
| import java.io.File | ||||
| import java.util.* | ||||
| import java.util.regex.Pattern | ||||
|  | ||||
| class Program (val name: String, | ||||
|                val program: MutableList<Instruction>, | ||||
|                val variables: Map<String, RuntimeValue>, | ||||
|                val memoryPointers: Map<String, Pair<Int, DataType>>, | ||||
|                val labels: Map<String, Int>, | ||||
|                val memory: Map<Int, List<RuntimeValue>>, | ||||
|                val heap: HeapValues) | ||||
| { | ||||
|     init { | ||||
|         // add end of program marker and some sentinel instructions, to correctly connect all others | ||||
|         program.add(LabelInstr("____program_end", false)) | ||||
|         program.add(Instruction(Opcode.TERMINATE)) | ||||
|         program.add(Instruction(Opcode.NOP)) | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         fun load(filename: String): Program { | ||||
|             val lines = File(filename).readLines().withIndex().iterator() | ||||
|             val memory = mutableMapOf<Int, List<RuntimeValue>>() | ||||
|             val heap = HeapValues() | ||||
|             val program = mutableListOf<Instruction>() | ||||
|             val variables = mutableMapOf<String, RuntimeValue>() | ||||
|             val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>() | ||||
|             val labels = mutableMapOf<String, Int>() | ||||
|  | ||||
|             while(lines.hasNext()) { | ||||
|                 val (lineNr, line) = lines.next() | ||||
|                 if(line.startsWith(';') || line.isEmpty()) | ||||
|                     continue | ||||
|                 else if(line=="%memory") | ||||
|                     loadMemory(lines, memory) | ||||
|                 else if(line=="%heap") | ||||
|                     loadHeap(lines, heap) | ||||
|                 else if(line.startsWith("%block ")) | ||||
|                     loadBlock(lines, heap, program, variables, memoryPointers, labels) | ||||
|                 else throw VmExecutionException("syntax error at line ${lineNr + 1}") | ||||
|             } | ||||
|             return Program(filename, program, variables, memoryPointers, labels, memory, heap) | ||||
|         } | ||||
|  | ||||
|         private fun loadBlock(lines: Iterator<IndexedValue<String>>, | ||||
|                               heap: HeapValues, | ||||
|                               program: MutableList<Instruction>, | ||||
|                               variables: MutableMap<String, RuntimeValue>, | ||||
|                               memoryPointers: MutableMap<String, Pair<Int, DataType>>, | ||||
|                               labels: MutableMap<String, Int>) | ||||
|         { | ||||
|             while(true) { | ||||
|                 val (_, line) = lines.next() | ||||
|                 if(line.isEmpty()) | ||||
|                     continue | ||||
|                 else if(line=="%end_block") | ||||
|                     return | ||||
|                 else if(line=="%variables") | ||||
|                     loadVars(lines, variables) | ||||
|                 else if(line=="%memorypointers") | ||||
|                     loadMemoryPointers(lines, memoryPointers, heap) | ||||
|                 else if(line=="%instructions") { | ||||
|                     val (blockInstructions, blockLabels) = loadInstructions(lines, heap) | ||||
|                     val baseIndex = program.size | ||||
|                     program.addAll(blockInstructions) | ||||
|                     val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) } | ||||
|                     labels.putAll(labelsWithIndex) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun loadHeap(lines: Iterator<IndexedValue<String>>, heap: HeapValues) { | ||||
|             val splitpattern = Pattern.compile("\\s+") | ||||
|             val heapvalues = mutableListOf<Triple<Int, DataType, String>>() | ||||
|             while(true) { | ||||
|                 val (_, line) = lines.next() | ||||
|                 if (line == "%end_heap") | ||||
|                     break | ||||
|                 val parts = line.split(splitpattern, limit=3) | ||||
|                 val value = Triple(parts[0].toInt(), DataType.valueOf(parts[1].toUpperCase()), parts[2]) | ||||
|                 heapvalues.add(value) | ||||
|             } | ||||
|             heapvalues.sortedBy { it.first }.forEach { | ||||
|                 when(it.second) { | ||||
|                     DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length - 1), Position("<stackvmsource>", 0, 0, 0))) | ||||
|                     DataType.ARRAY_UB, DataType.ARRAY_B, | ||||
|                     DataType.ARRAY_UW, DataType.ARRAY_W -> { | ||||
|                         val numbers = it.third.substring(1, it.third.length-1).split(',') | ||||
|                         val intarray = numbers.map{number-> | ||||
|                             val num=number.trim() | ||||
|                             if(num.startsWith("&")) { | ||||
|                                 // it's AddressOf | ||||
|                                 val scopedname = num.substring(1) | ||||
|                                 val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0, 0, 0)) | ||||
|                                 val addrOf = AddressOf(iref, Position("<intermediate>", 0, 0, 0)) | ||||
|                                 addrOf.scopedname=scopedname | ||||
|                                 IntegerOrAddressOf(null, addrOf) | ||||
|                             } else { | ||||
|                                 IntegerOrAddressOf(num.toInt(), null) | ||||
|                             } | ||||
|                         }.toTypedArray() | ||||
|                         heap.addIntegerArray(it.second, intarray) | ||||
|                     } | ||||
|                     DataType.ARRAY_F -> { | ||||
|                         val numbers = it.third.substring(1, it.third.length-1).split(',') | ||||
|                         val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray() | ||||
|                         heap.addDoublesArray(doublearray) | ||||
|                     } | ||||
|                     in NumericDatatypes -> throw VmExecutionException("invalid heap value type ${it.second}") | ||||
|                     else -> throw VmExecutionException("weird datatype") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun loadInstructions(lines: Iterator<IndexedValue<String>>, heap: HeapValues): Pair<MutableList<Instruction>, Map<String, Instruction>> { | ||||
|             val instructions = mutableListOf<Instruction>() | ||||
|             val labels = mutableMapOf<String, Instruction>() | ||||
|             val splitpattern = Pattern.compile("\\s+") | ||||
|             val nextInstructionLabels = Stack<String>()     // more than one label can occur on the isSameAs line | ||||
|  | ||||
|             while(true) { | ||||
|                 val (lineNr, line) = lines.next() | ||||
|                 if(line.isEmpty()) | ||||
|                     continue | ||||
|                 if(line=="%end_instructions") | ||||
|                     return Pair(instructions, labels) | ||||
|                 if(!line.startsWith(' ') && line.endsWith(':')) { | ||||
|                     nextInstructionLabels.push(line.substring(0, line.length-1)) | ||||
|                 } else if(line.startsWith(' ')) { | ||||
|                     val parts = line.trimStart().split(splitpattern, limit = 2) | ||||
|                     val opcodeStr = parts[0].toUpperCase() | ||||
|                     val opcode= Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr) | ||||
|                     val args = if(parts.size==2) parts[1] else null | ||||
|                     val instruction = when(opcode) { | ||||
|                         Opcode.LINE -> Instruction(opcode, null, callLabel = args) | ||||
|                         Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS, | ||||
|                         Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC, | ||||
|                         Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> { | ||||
|                             if(args!!.startsWith('$')) { | ||||
|                                 Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16))) | ||||
|                             } else { | ||||
|                                 Instruction(opcode, callLabel = args) | ||||
|                             } | ||||
|                         } | ||||
|                         in opcodesWithVarArgument -> { | ||||
|                             val withoutQuotes = | ||||
|                                     if(args!!.startsWith('"') && args.endsWith('"')) | ||||
|                                         args.substring(1, args.length-1) else args | ||||
|  | ||||
|                             Instruction(opcode, callLabel = withoutQuotes) | ||||
|                         } | ||||
|                         Opcode.SYSCALL -> { | ||||
|                             if(args!! in syscallNames) { | ||||
|                                 val call = Syscall.valueOf(args) | ||||
|                                 Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr)) | ||||
|                             } else { | ||||
|                                 val args2 = args.replace('.', '_') | ||||
|                                 if(args2 in syscallNames) { | ||||
|                                     val call = Syscall.valueOf(args2) | ||||
|                                     Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr)) | ||||
|                                 } else { | ||||
|                                     // the syscall is not yet implemented. emit a stub. | ||||
|                                     Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2) | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         Opcode.INCLUDE_FILE -> { | ||||
|                             val argparts = args!!.split(' ') | ||||
|                             val filename = argparts[0] | ||||
|                             val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null | ||||
|                             val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null | ||||
|                             Instruction(opcode, offset, length, filename) | ||||
|                         } | ||||
|                         else -> { | ||||
|                             Instruction(opcode, getArgValue(args, heap)) | ||||
|                         } | ||||
|                     } | ||||
|                     instructions.add(instruction) | ||||
|                     while(nextInstructionLabels.isNotEmpty()) { | ||||
|                         val label = nextInstructionLabels.pop() | ||||
|                         labels[label] = instruction | ||||
|                     } | ||||
|                 } else throw VmExecutionException("syntax error at line ${lineNr + 1}") | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun getArgValue(args: String?, heap: HeapValues): RuntimeValue? { | ||||
|             if(args==null) | ||||
|                 return null | ||||
|             if(args[0]=='"' && args[args.length-1]=='"') { | ||||
|                 throw VmExecutionException("encountered a string arg value, but all strings should already have been moved into the heap") | ||||
|             } | ||||
|             val (type, valueStr) = args.split(':') | ||||
|             return when(type) { | ||||
|                 "b" -> RuntimeValue(DataType.BYTE, valueStr.toShort(16)) | ||||
|                 "ub" -> RuntimeValue(DataType.UBYTE, valueStr.toShort(16)) | ||||
|                 "w" -> RuntimeValue(DataType.WORD, valueStr.toInt(16)) | ||||
|                 "uw" -> RuntimeValue(DataType.UWORD, valueStr.toInt(16)) | ||||
|                 "f" -> RuntimeValue(DataType.FLOAT, valueStr.toDouble()) | ||||
|                 "heap" -> { | ||||
|                     val heapId = valueStr.toInt() | ||||
|                     RuntimeValue(heap.get(heapId).type, heapId = heapId) | ||||
|                 } | ||||
|                 else -> throw VmExecutionException("invalid datatype $type") | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun loadVars(lines: Iterator<IndexedValue<String>>, | ||||
|                              vars: MutableMap<String, RuntimeValue>) { | ||||
|             val splitpattern = Pattern.compile("\\s+") | ||||
|             while(true) { | ||||
|                 val (_, line) = lines.next() | ||||
|                 if(line=="%end_variables") | ||||
|                     return | ||||
|                 val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3) | ||||
|                 if(valueStr[0] !='"' && ':' !in valueStr) | ||||
|                     throw VmExecutionException("missing value type character") | ||||
|                 val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) { | ||||
|                     DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).substringBefore(' ').toShort(16))// TODO process ZP and struct info? | ||||
|                     DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).substringBefore(' ').toShort(16))// TODO process ZP and struct info? | ||||
|                     DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).substringBefore(' ').toInt(16))// TODO process ZP and struct info? | ||||
|                     DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).substringBefore(' ').toInt(16))// TODO process ZP and struct info? | ||||
|                     DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info? | ||||
|                     in StringDatatypes -> { | ||||
|                         if(valueStr.startsWith('"') && valueStr.endsWith('"')) | ||||
|                             throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap") | ||||
|                         else if(!valueStr.startsWith("heap:")) | ||||
|                             throw VmExecutionException("invalid string value, should be a heap reference") | ||||
|                         else { | ||||
|                             val heapId = valueStr.substring(5).substringBefore(' ').toInt()     // TODO process ZP and struct info? | ||||
|                             RuntimeValue(type, heapId = heapId) | ||||
|                         } | ||||
|                     } | ||||
|                     in ArrayDatatypes -> { | ||||
|                         if(!valueStr.startsWith("heap:")) | ||||
|                             throw VmExecutionException("invalid array value, should be a heap reference") | ||||
|                         else { | ||||
|                             val heapId = valueStr.substring(5).substringBefore(' ').toInt()     // TODO process ZP and struct info? | ||||
|                             RuntimeValue(type, heapId = heapId) | ||||
|                         } | ||||
|                     } | ||||
|                     else -> throw VmExecutionException("weird datatype") | ||||
|                 } | ||||
|                 vars[name] = value | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun loadMemoryPointers(lines: Iterator<IndexedValue<String>>, | ||||
|                                        pointers: MutableMap<String, Pair<Int, DataType>>, | ||||
|                                        heap: HeapValues) { | ||||
|             val splitpattern = Pattern.compile("\\s+") | ||||
|             while(true) { | ||||
|                 val (_, line) = lines.next() | ||||
|                 if(line=="%end_memorypointers") | ||||
|                     return | ||||
|                 val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3) | ||||
|                 if(valueStr[0] !='"' && ':' !in valueStr) | ||||
|                     throw VmExecutionException("missing value type character") | ||||
|                 val type = DataType.valueOf(typeStr.toUpperCase()) | ||||
|                 val value = getArgValue(valueStr, heap)!!.integerValue() | ||||
|                 pointers[name] = Pair(value, type) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<RuntimeValue>>): Map<Int, List<RuntimeValue>> { | ||||
|             while(true) { | ||||
|                 val (lineNr, line) = lines.next() | ||||
|                 if(line=="%end_memory") | ||||
|                     return memory | ||||
|                 val address = line.substringBefore(' ').toInt(16) | ||||
|                 val rest = line.substringAfter(' ').trim() | ||||
|                 if(rest.startsWith('"')) { | ||||
|                     TODO("memory init with char/string") | ||||
|                 } else { | ||||
|                     val valueStrings = rest.split(' ') | ||||
|                     val values = mutableListOf<RuntimeValue>() | ||||
|                     valueStrings.forEach { | ||||
|                         when(it.length) { | ||||
|                             2 -> values.add(RuntimeValue(DataType.UBYTE, it.toShort(16))) | ||||
|                             4 -> values.add(RuntimeValue(DataType.UWORD, it.toInt(16))) | ||||
|                             else -> throw VmExecutionException("invalid value at line $lineNr+1") | ||||
|                         } | ||||
|                     } | ||||
|                     memory[address] = values | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										7
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,3 +1,10 @@ | ||||
|  | ||||
| This sofware license is for Prog8 the compiler + associated libraries. | ||||
| The software generated by running the compiler is excluded from this. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 3, 29 June 2007 | ||||
|  | ||||
|   | ||||
							
								
								
									
										119
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,64 +1,81 @@ | ||||
| [](https://saythanks.io/to/irmen) | ||||
| [](https://travis-ci.org/irmen/prog8) | ||||
| [](https://prog8.readthedocs.io/) | ||||
|  | ||||
| Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors | ||||
| =========================================================================== | ||||
| Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors | ||||
| ============================================================================ | ||||
|  | ||||
| *Written by Irmen de Jong (irmen@razorvine.net)* | ||||
|  | ||||
| *Software license: GNU GPL 3.0, see file LICENSE* | ||||
|  | ||||
|  | ||||
| This is a structured programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's | ||||
| This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's | ||||
| as used in many home computers from that era. It is a medium to low level programming language, | ||||
| which aims to provide many conveniences over raw assembly code (even when using a macro assembler): | ||||
| which aims to provide many conveniences over raw assembly code (even when using a macro assembler). | ||||
|  | ||||
| - reduction of source code length | ||||
| Documentation | ||||
| ------------- | ||||
| Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at: | ||||
| https://prog8.readthedocs.io/ | ||||
|  | ||||
| Software license | ||||
| ---------------- | ||||
| GNU GPL 3.0, see file LICENSE | ||||
|  | ||||
| - prog8 (the compiler + libraries) is licensed under GNU GPL 3.0 | ||||
| - *exception:* the resulting files created by running the compiler are free to use in whatever way desired. | ||||
|  | ||||
|  | ||||
| What does Prog8 provide? | ||||
| ------------------------ | ||||
|  | ||||
| - reduction of source code length over raw assembly | ||||
| - fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8. | ||||
| - modularity, symbol scoping, subroutines | ||||
| - various data types other than just bytes (16-bit words, floats, strings) | ||||
| - automatic variable allocations, automatic string and array variables and string sharing | ||||
| - subroutines with a input- and output parameter signature | ||||
| - constant folding in expressions | ||||
| - floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do) | ||||
| - strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents. | ||||
| - automatic static variable allocations, automatic string and array variables and string sharing | ||||
| - subroutines with input parameters and result values | ||||
| - high-level program optimizations | ||||
| - small program boilerplate/compilersupport overhead | ||||
| - programs can be run multiple times without reloading because of automatic variable (re)initializations. | ||||
| - conditional branches | ||||
| - 'when' statement to provide a concise jump table alternative to if/elseif chains | ||||
| - structs to group together sets of variables and manipulate them at once | ||||
| - floating point operations  (requires the C64 Basic ROM routines for this) | ||||
| - abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses | ||||
| - various code optimizations (code structure, logical and numerical expressions, unused code removal...) | ||||
| - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse`` | ||||
| - various powerful built-in libraries to do I/O, number conversions, graphics and more   | ||||
| - convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses | ||||
| - inline assembly allows you to have full control when every cycle or byte matters | ||||
| - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy`` | ||||
| - 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  | ||||
| - quick compilation times (seconds) | ||||
| - option to automatically run the program in the Vice emulator   | ||||
| - use a modern PC to do the work on, use nice editors and enjoy quick compilation times | ||||
| - can automatically run the program in the Vice emulator after succesful compilation | ||||
| - breakpoints, that let the Vice emulator drop into the monitor if execution hits them | ||||
| - source code labels automatically loaded in Vice emulator so it can show them in disassembly | ||||
| - virtual machine that can execute compiled code directy on the host system, | ||||
|   without having to actually convert it to assembly to run on a real 6502  | ||||
|  | ||||
| It is mainly targeted at the Commodore-64 machine at this time. | ||||
| Contributions to add support for other 8-bit (or other?!) machines are welcome. | ||||
| *Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): | ||||
|  | ||||
| Documentation/manual | ||||
| -------------------- | ||||
| See https://prog8.readthedocs.io/ | ||||
| - "c64": Commodore-64  (6510 CPU = almost a 6502) | ||||
| - "cx16": [CommanderX16](https://www.commanderx16.com)  (65c02 CPU) | ||||
| - If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)! | ||||
|  | ||||
|  | ||||
| Required tools | ||||
| -------------- | ||||
|  | ||||
| Additional required tools | ||||
| ------------------------- | ||||
|  | ||||
| [64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path. | ||||
| A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project. | ||||
| For other platforms it is very easy to compile it yourself (make ; make install). | ||||
|  | ||||
| A **Java runtime (jre or jdk), version 8 or newer**  is required to run a prepackaged version of the compiler. | ||||
| A **Java runtime (jre or jdk), version 11 or newer**  is required to run a prepackaged version of the compiler. | ||||
| If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance, | ||||
| IntelliJ IDEA with the Kotlin plugin). | ||||
|  | ||||
| It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence | ||||
| of the [Vice emulator](http://vice-emu.sourceforge.net/) | ||||
| It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence | ||||
| of the [Vice emulator](http://vice-emu.sourceforge.net/)  for the C64 target, | ||||
| and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target. | ||||
|  | ||||
|  | ||||
| Example code | ||||
| @@ -66,44 +83,43 @@ Example code | ||||
|  | ||||
| This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | ||||
|  | ||||
|     %import c64utils | ||||
|     %import textio | ||||
|     %zeropage basicsafe | ||||
|      | ||||
|     main { | ||||
|      | ||||
|         ubyte[256] sieve | ||||
|         ubyte candidate_prime = 2 | ||||
|         ubyte candidate_prime = 2       ; is increased in the loop | ||||
|      | ||||
|         sub start() { | ||||
|             memset(sieve, 256, false) | ||||
|  | ||||
|             c64scr.print("prime numbers up to 255:\n\n") | ||||
|             sys.memset(sieve, 256, false)   ; clear the sieve | ||||
|             txt.print("prime numbers up to 255:\n\n") | ||||
|             ubyte amount=0 | ||||
|             while true { | ||||
|             repeat { | ||||
|                 ubyte prime = find_next_prime() | ||||
|                 if prime==0 | ||||
|                     break | ||||
|                 c64scr.print_ub(prime) | ||||
|                 c64scr.print(", ") | ||||
|                 txt.print_ub(prime) | ||||
|                 txt.print(", ") | ||||
|                 amount++ | ||||
|             } | ||||
|             c64.CHROUT('\n') | ||||
|             c64scr.print("number of primes (expected 54): ") | ||||
|             c64scr.print_ub(amount) | ||||
|             c64.CHROUT('\n') | ||||
|             txt.nl() | ||||
|             txt.print("number of primes (expected 54): ") | ||||
|             txt.print_ub(amount) | ||||
|             txt.nl() | ||||
|         } | ||||
|      | ||||
|  | ||||
|         sub find_next_prime() -> ubyte { | ||||
|  | ||||
|             while sieve[candidate_prime] { | ||||
|                 candidate_prime++ | ||||
|                 if candidate_prime==0 | ||||
|                     return 0 | ||||
|                     return 0        ; we wrapped; no more primes | ||||
|             } | ||||
|      | ||||
|             ; found next one, mark the multiples and return it. | ||||
|             sieve[candidate_prime] = true | ||||
|             uword multiple = candidate_prime | ||||
|      | ||||
|             while multiple < len(sieve) { | ||||
|                 sieve[lsb(multiple)] = true | ||||
|                 multiple += candidate_prime | ||||
| @@ -113,11 +129,12 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| when compiled an ran on a C-64 you'll get: | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this: | ||||
|  | ||||
|  | ||||
| @@ -129,3 +146,9 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube: | ||||
| If you want to play a video game, a fully working Tetris clone is included in the examples: | ||||
|  | ||||
|  | ||||
|  | ||||
| There are a couple of examples specially made for the CommanderX16 compiler target. | ||||
| For instance here's a well known space ship animated in 3D with hidden line removal, | ||||
| in the CommanderX16 emulator: | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,55 +1,48 @@ | ||||
| buildscript { | ||||
|     dependencies { | ||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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 'application' | ||||
|     id "org.jetbrains.kotlin.jvm" version "1.5.10" | ||||
|     id 'com.github.johnrengelman.shadow' version '6.1.0' | ||||
| } | ||||
|  | ||||
| apply plugin: "kotlin" | ||||
| apply plugin: "java" | ||||
|  | ||||
| targetCompatibility = 11 | ||||
| sourceCompatibility = 11 | ||||
|  | ||||
| repositories { | ||||
|     mavenLocal() | ||||
|     mavenCentral() | ||||
|     jcenter() | ||||
|     maven { url "https://kotlin.bintray.com/kotlinx" } | ||||
| } | ||||
|  | ||||
| def prog8version = rootProject.file('compiler/res/version.txt').text.trim() | ||||
|  | ||||
| dependencies { | ||||
|     implementation project(':parser') | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" | ||||
|     // implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" | ||||
|     // runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" | ||||
|     runtime 'org.antlr:antlr4-runtime:4.7.2' | ||||
|     runtime project(':parser') | ||||
|     implementation project(':compilerAst') | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" | ||||
|     // implementation "org.jetbrains.kotlin:kotlin-reflect" | ||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2' | ||||
|     // implementation 'net.razorvine:ksim65:1.8' | ||||
|     // implementation "com.github.hypfvieh:dbus-java:3.2.4" | ||||
|  | ||||
|     testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion" | ||||
|     testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" | ||||
|     testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' | ||||
|     testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0' | ||||
|  | ||||
|     testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2' | ||||
| } | ||||
|  | ||||
| compileKotlin { | ||||
|     kotlinOptions { | ||||
|         jvmTarget = "1.8" | ||||
|         verbose = true | ||||
|         jvmTarget = "11" | ||||
|         useIR = true | ||||
|         // verbose = true | ||||
|         // freeCompilerArgs += "-XXLanguage:+NewInference" | ||||
|     } | ||||
| } | ||||
|  | ||||
| compileTestKotlin { | ||||
|     kotlinOptions { | ||||
|         jvmTarget = "1.8" | ||||
|         jvmTarget = "11" | ||||
|         useIR = true | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -72,7 +65,8 @@ sourceSets { | ||||
| startScripts.enabled = true | ||||
|  | ||||
| application { | ||||
|     mainClassName = 'prog8.CompilerMainKt' | ||||
|     mainClass = 'prog8.CompilerMainKt' | ||||
|     mainClassName = 'prog8.CompilerMainKt'  // deprecated | ||||
|     applicationName = 'p8compile' | ||||
| } | ||||
|  | ||||
| @@ -81,12 +75,9 @@ artifacts { | ||||
| } | ||||
|  | ||||
|  | ||||
| // To create a fat-jar use the 'create_compiler_jar' script for now | ||||
| // @todo investigate https://imperceptiblethoughts.com/shadow/introduction/ | ||||
|  | ||||
| shadowJar { | ||||
|     baseName = 'prog8compiler' | ||||
|     version = prog8version | ||||
|     archiveBaseName = 'prog8compiler' | ||||
|     archiveVersion = prog8version | ||||
|     // minimize() | ||||
| } | ||||
|  | ||||
| @@ -100,12 +91,11 @@ test { | ||||
|  | ||||
|     // Show test results. | ||||
|     testLogging { | ||||
|         events "passed", "skipped", "failed" | ||||
|         events "skipped", "failed" | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| dokka { | ||||
|     outputFormat = 'html' | ||||
|     outputDirectory = "$buildDir/kdoc" | ||||
| task wrapper(type: Wrapper) { | ||||
|     gradleVersion = '6.7' | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <module type="JAVA_MODULE" version="4"> | ||||
|   <component name="FacetManager"> | ||||
|     <facet type="Python" name="Python"> | ||||
|       <configuration sdkName="Python 3.9" /> | ||||
|     </facet> | ||||
|   </component> | ||||
|   <component name="NewModuleRootManager" inherit-compiler-output="true"> | ||||
|     <exclude-output /> | ||||
|     <content url="file://$MODULE_DIR$"> | ||||
| @@ -8,11 +13,12 @@ | ||||
|       <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> | ||||
|       <excludeFolder url="file://$MODULE_DIR$/build" /> | ||||
|     </content> | ||||
|     <orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" /> | ||||
|     <orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|     <orderEntry type="library" name="KotlinJavaRuntime" level="project" /> | ||||
|     <orderEntry type="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="kotlinx-cli-jvm" level="project" /> | ||||
|     <orderEntry type="module" module-name="compilerAst" /> | ||||
|     <orderEntry type="library" name="Python 3.9 interpreter library" level="application" /> | ||||
|   </component> | ||||
| </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,969 +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     ; note: calls AYINT. | ||||
|  | ||||
| ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15) | ||||
| ; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order) | ||||
| 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  #<value | ||||
| 		ldy  #>value | ||||
| 		jsr  MOVFM		; load float into fac1 | ||||
| 		jsr  FOUT		; fac1 to string in A/Y | ||||
| 		jsr  c64.STROUT			; print string in A/Y | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		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               ; also used for float2b | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  AYINT | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		lda  $64 | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		lda  $65 | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| stack_float2uw	.proc               ; also used for float2ub | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  GETADR | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		tya | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| push_float	.proc | ||||
| 		; ---- push mflpt5 in A/Y onto stack | ||||
| 		; (taking 3 stack positions = 6 bytes of which 1 is padding) | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		ldy  #0 | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		dex | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_HI,x | ||||
| 		dex | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| func_rndf	.proc | ||||
| 		; -- put a random floating point value on the stack | ||||
| 		stx  c64.SCRATCH_ZPREG | ||||
| 		lda  #1 | ||||
| 		jsr  FREADSA | ||||
| 		jsr  RND		; rng into fac1 | ||||
| 		ldx  #<_rndf_rnum5 | ||||
| 		ldy  #>_rndf_rnum5 | ||||
| 		jsr  MOVMF	; fac1 to mem X/Y | ||||
| 		ldx  c64.SCRATCH_ZPREG | ||||
| 		lda  #<_rndf_rnum5 | ||||
| 		ldy  #>_rndf_rnum5 | ||||
| 		jmp  push_float | ||||
| _rndf_rnum5	.byte  0,0,0,0,0 | ||||
| 		.pend | ||||
|  | ||||
| push_float_from_indexed_var	.proc | ||||
| 		; -- push the float from the array at A/Y with index on stack, onto the stack. | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  prog8_lib.pop_index_times_5 | ||||
| 		jsr  prog8_lib.add_a_to_zpword | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jmp  push_float | ||||
| 		.pend | ||||
|  | ||||
| pop_float	.proc | ||||
| 		; ---- pops mflpt5 from stack to memory A/Y | ||||
| 		; (frees 3 stack positions = 6 bytes of which 1 is padding) | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		ldy  #4 | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_HI,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_HI,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		lda  c64.ESTACK_LO,x | ||||
| 		sta  (c64.SCRATCH_ZPWORD1),y | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| pop_float_fac1	.proc | ||||
| 		; -- pops float from stack into FAC1 | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jmp  MOVFM | ||||
| 		.pend | ||||
|  | ||||
| pop_float_to_indexed_var	.proc | ||||
| 		; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  prog8_lib.pop_index_times_5 | ||||
| 		jsr  prog8_lib.add_a_to_zpword | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jmp  pop_float | ||||
| 		.pend | ||||
|  | ||||
| copy_float	.proc | ||||
| 		; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1, | ||||
| 		;    into the 5 bytes pointed to by A/Y.  Clobbers A,Y. | ||||
| 		sta  c64.SCRATCH_ZPWORD2 | ||||
| 		sty  c64.SCRATCH_ZPWORD2+1 | ||||
| 		ldy  #0 | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		iny | ||||
| 		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		sta  (c64.SCRATCH_ZPWORD2),y | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| inc_var_f	.proc | ||||
| 		; -- add 1 to float pointed to by A/Y | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  MOVFM | ||||
| 		lda  #<FL_FONE | ||||
| 		ldy  #>FL_FONE | ||||
| 		jsr  FADD | ||||
| 		ldx  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| dec_var_f	.proc | ||||
| 		; -- subtract 1 from float pointed to by A/Y | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		sty  c64.SCRATCH_ZPWORD1+1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<FL_FONE | ||||
| 		ldy  #>FL_FONE | ||||
| 		jsr  MOVFM | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  FSUB | ||||
| 		ldx  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  MOVMF | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| inc_indexed_var_f	.proc | ||||
| 		; -- add 1 to float in array pointed to by A/Y, at index X | ||||
| 		pha | ||||
| 		txa | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		pla | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		jmp  inc_var_f | ||||
| 		.pend | ||||
|  | ||||
| dec_indexed_var_f	.proc | ||||
| 		; -- subtract 1 to float in array pointed to by A/Y, at index X | ||||
| 		pha | ||||
| 		txa | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		pla | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		jmp  dec_var_f | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| pop_2_floats_f2_in_fac1	.proc | ||||
| 		; -- pop 2 floats from stack, load the second one in FAC1 as well | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jmp  MOVFM | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| fmath_float1	.byte 0,0,0,0,0	; storage for a mflpt5 value | ||||
| fmath_float2	.byte 0,0,0,0,0	; storage for a mflpt5 value | ||||
|  | ||||
| push_fac1_as_result	.proc | ||||
| 		; -- push the float in FAC1 onto the stack, and return from calculation | ||||
| 		ldx  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVMF | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		jmp  push_float | ||||
| 		.pend | ||||
|  | ||||
| pow_f		.proc | ||||
| 		; -- push f1 ** f2 on stack | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  CONUPK		; fac2 = float1 | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  FPWR | ||||
| 		ldx  c64.SCRATCH_ZPREGX | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| div_f		.proc | ||||
| 		; -- push f1/f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FDIV | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| add_f		.proc | ||||
| 		; -- push f1+f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FADD | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| sub_f		.proc | ||||
| 		; -- push f1-f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FSUB | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| mul_f		.proc | ||||
| 		; -- push f1*f2 on stack | ||||
| 		jsr  pop_2_floats_f2_in_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FMULT | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| neg_f		.proc | ||||
| 		; -- push -flt back on stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  NEGOP | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| abs_f		.proc | ||||
| 		; -- push abs(float) on stack (as float) | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  ABS | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| equal_f		.proc | ||||
| 		; -- are the two mflpt5 numbers on the stack identical? | ||||
| 		inx | ||||
| 		inx | ||||
| 		inx | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO-3,x | ||||
| 		cmp  c64.ESTACK_LO,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_LO-2,x | ||||
| 		cmp  c64.ESTACK_LO+1,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_LO-1,x | ||||
| 		cmp  c64.ESTACK_LO+2,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_HI-2,x | ||||
| 		cmp  c64.ESTACK_HI+1,x | ||||
| 		bne  _equals_false | ||||
| 		lda  c64.ESTACK_HI-1,x | ||||
| 		cmp  c64.ESTACK_HI+2,x | ||||
| 		bne  _equals_false | ||||
| _equals_true	lda  #1 | ||||
| _equals_store	inx | ||||
| 		sta  c64.ESTACK_LO+1,x | ||||
| 		rts | ||||
| _equals_false	lda  #0 | ||||
| 		beq  _equals_store | ||||
| 		.pend | ||||
|  | ||||
| notequal_f	.proc | ||||
| 		; -- are the two mflpt5 numbers on the stack different? | ||||
| 		jsr  equal_f | ||||
| 		eor  #1		; invert the result | ||||
| 		sta  c64.ESTACK_LO+1,x | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| less_f		.proc | ||||
| 		; -- is f1 < f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #255 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
|  | ||||
| lesseq_f	.proc | ||||
| 		; -- is f1 <= f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #255 | ||||
| 		beq  compare_floats._return_true | ||||
| 		cmp  #0 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| greater_f	.proc | ||||
| 		; -- is f1 > f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #1 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| greatereq_f	.proc | ||||
| 		; -- is f1 >= f2? | ||||
| 		jsr  compare_floats | ||||
| 		cmp  #1 | ||||
| 		beq  compare_floats._return_true | ||||
| 		cmp  #0 | ||||
| 		beq  compare_floats._return_true | ||||
| 		bne  compare_floats._return_false | ||||
| 		.pend | ||||
|  | ||||
| compare_floats	.proc | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  pop_float | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVFM		; fac1 = flt1 | ||||
| 		lda  #<fmath_float2 | ||||
| 		ldy  #>fmath_float2 | ||||
| 		stx  c64.SCRATCH_ZPREG | ||||
| 		jsr  FCOMP		; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2) | ||||
| 		ldx  c64.SCRATCH_ZPREG | ||||
| 		rts | ||||
| _return_false	lda  #0 | ||||
| _return_result  sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| _return_true	lda  #1 | ||||
| 		bne  _return_result | ||||
| 		.pend | ||||
|  | ||||
| func_sin	.proc | ||||
| 		; -- push sin(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  SIN | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_cos	.proc | ||||
| 		; -- push cos(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  COS | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_tan	.proc | ||||
| 		; -- push tan(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  TAN | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_atan	.proc | ||||
| 		; -- push atan(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  ATN | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_ln		.proc | ||||
| 		; -- push ln(f) back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  LOG | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_log2	.proc | ||||
| 		; -- push log base 2, ln(f)/ln(2), back onto stack | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  LOG | ||||
| 		jsr  MOVEF | ||||
| 		lda  #<c64.FL_LOG2 | ||||
| 		ldy  #>c64.FL_LOG2 | ||||
| 		jsr  MOVFM | ||||
| 		jsr  FDIVT | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_sqrt	.proc | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  SQR | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_rad	.proc | ||||
| 		; -- convert degrees to radians (d * pi / 180) | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<_pi_div_180 | ||||
| 		ldy  #>_pi_div_180 | ||||
| 		jsr  FMULT | ||||
| 		jmp  push_fac1_as_result | ||||
| _pi_div_180	.byte 123, 14, 250, 53, 18		; pi / 180 | ||||
| 		.pend | ||||
|  | ||||
| func_deg	.proc | ||||
| 		; -- convert radians to degrees (d * (1/ pi * 180)) | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		lda  #<_one_over_pi_div_180 | ||||
| 		ldy  #>_one_over_pi_div_180 | ||||
| 		jsr  FMULT | ||||
| 		jmp  push_fac1_as_result | ||||
| _one_over_pi_div_180	.byte 134, 101, 46, 224, 211		; 1 / (pi * 180) | ||||
| 		.pend | ||||
|  | ||||
| func_round	.proc | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  FADDH | ||||
| 		jsr  INT | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_floor	.proc | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		jsr  INT | ||||
| 		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_ceil	.proc | ||||
| 		; -- ceil: tr = int(f); if tr==f -> return  else return tr+1 | ||||
| 		jsr  pop_float_fac1 | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| 		ldx  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  MOVMF | ||||
| 		jsr  INT | ||||
| 		lda  #<fmath_float1 | ||||
| 		ldy  #>fmath_float1 | ||||
| 		jsr  FCOMP | ||||
| 		cmp  #0 | ||||
| 		beq  + | ||||
| 		lda  #<FL_FONE | ||||
| 		ldy  #>FL_FONE | ||||
| 		jsr  FADD | ||||
| +		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
|  | ||||
| func_any_f	.proc | ||||
| 		inx | ||||
| 		lda  c64.ESTACK_LO,x	; array size | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1	; times 5 because of float | ||||
| 		jmp  prog8_lib.func_any_b._entry | ||||
| 		.pend | ||||
|  | ||||
| func_all_f	.proc | ||||
| 		inx | ||||
| 		jsr  prog8_lib.peek_address | ||||
| 		lda  c64.ESTACK_LO,x	; array size | ||||
| 		sta  c64.SCRATCH_ZPB1 | ||||
| 		asl  a | ||||
| 		asl  a | ||||
| 		clc | ||||
| 		adc  c64.SCRATCH_ZPB1	; times 5 because of float | ||||
| 		tay | ||||
| 		dey | ||||
| -		lda  (c64.SCRATCH_ZPWORD1),y | ||||
| 		clc | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		adc  (c64.SCRATCH_ZPWORD1),y | ||||
| 		dey | ||||
| 		cmp  #0 | ||||
| 		beq  + | ||||
|         cpy  #255 | ||||
|         bne  - | ||||
| 		lda  #1 | ||||
| 		sta  c64.ESTACK_LO+1,x | ||||
| 		rts | ||||
| +		sta  c64.ESTACK_LO+1,x | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| func_max_f	.proc | ||||
| 		lda  #255 | ||||
| 		sta  _minmax_cmp+1 | ||||
| 		lda  #<_largest_neg_float | ||||
| 		ldy  #>_largest_neg_float | ||||
| _minmax_entry	jsr  MOVFM | ||||
| 		jsr  prog8_lib.pop_array_and_lengthmin1Y | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| -		sty  c64.SCRATCH_ZPREG | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  FCOMP | ||||
| _minmax_cmp	cmp  #255			; modified | ||||
| 		bne  + | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  MOVFM | ||||
| +		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		clc | ||||
| 		adc  #5 | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		bcc  + | ||||
| 		inc  c64.SCRATCH_ZPWORD1+1 | ||||
| +		ldy  c64.SCRATCH_ZPREG | ||||
| 		dey | ||||
| 		cpy  #255 | ||||
| 		bne  - | ||||
| 		jmp  push_fac1_as_result | ||||
| _largest_neg_float	.byte 255,255,255,255,255		; largest negative float -1.7014118345e+38 | ||||
| 		.pend | ||||
|  | ||||
| func_min_f	.proc | ||||
| 		lda  #1 | ||||
| 		sta  func_max_f._minmax_cmp+1 | ||||
| 		lda  #<_largest_pos_float | ||||
| 		ldy  #>_largest_pos_float | ||||
| 		jmp  func_max_f._minmax_entry | ||||
| _largest_pos_float	.byte  255,127,255,255,255		; largest positive float | ||||
| 		rts | ||||
| 		.pend | ||||
|  | ||||
| func_sum_f	.proc | ||||
| 		lda  #<FL_ZERO | ||||
| 		ldy  #>FL_ZERO | ||||
| 		jsr  MOVFM | ||||
| 		jsr  prog8_lib.pop_array_and_lengthmin1Y | ||||
| 		stx  c64.SCRATCH_ZPREGX | ||||
| -		sty  c64.SCRATCH_ZPREG | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		ldy  c64.SCRATCH_ZPWORD1+1 | ||||
| 		jsr  FADD | ||||
| 		ldy  c64.SCRATCH_ZPREG | ||||
| 		dey | ||||
| 		cpy  #255 | ||||
| 		beq  + | ||||
| 		lda  c64.SCRATCH_ZPWORD1 | ||||
| 		clc | ||||
| 		adc  #5 | ||||
| 		sta  c64.SCRATCH_ZPWORD1 | ||||
| 		bcc  - | ||||
| 		inc  c64.SCRATCH_ZPWORD1+1 | ||||
| 		bne  - | ||||
| +		jmp  push_fac1_as_result | ||||
| 		.pend | ||||
| 		 | ||||
| sign_f		.proc | ||||
| 		jsr  pop_float_fac1 | ||||
| 		jsr  SIGN | ||||
| 		sta  c64.ESTACK_LO,x | ||||
| 		dex | ||||
| 		rts | ||||
| 		.pend | ||||
| 		 | ||||
|  | ||||
| }} | ||||
|  | ||||
| }  ; ------ 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   (uword userptr @ XY, ubyte dir @ Pc) 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   (uword address @ XY, ubyte dir @ Pc) -> uword @ XY	= $FF99		; read/set top of memory  pointer | ||||
| asmsub	MEMBOT   (uword address @ XY, ubyte dir @ Pc) -> 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 col @ Y, ubyte row @ X, ubyte dir @ Pc) -> 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 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %import c64lib | ||||
|  | ||||
| math { | ||||
| 	%asminclude "library:math.asm", "" | ||||
| 	%asminclude "library:math.asm" | ||||
| } | ||||
|   | ||||
							
								
								
									
										1107
									
								
								compiler/res/prog8lib/prog8_funcs.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1107
									
								
								compiler/res/prog8lib/prog8_funcs.asm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1085
									
								
								compiler/res/prog8lib/prog8_lib.asm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1085
									
								
								compiler/res/prog8lib/prog8_lib.asm
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										84
									
								
								compiler/res/prog8lib/prog8_lib.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								compiler/res/prog8lib/prog8_lib.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| ; Internal library routines - always included by the compiler | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
|  | ||||
| prog8_lib { | ||||
| 	%asminclude "library:prog8_lib.asm" | ||||
| 	%asminclude "library:prog8_funcs.asm" | ||||
|  | ||||
| 	uword @zp retval_interm_uw      ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||
| 	word @zp retval_interm_w        ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||
| 	ubyte @zp retval_interm_ub      ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||
| 	byte @zp retval_interm_b        ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size) | ||||
|  | ||||
| 	asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A { | ||||
| 		%asm {{ | ||||
| ; pattern matching of a string. | ||||
| ; Input:  cx16.r0:  A NUL-terminated, <255-length pattern | ||||
| ;              AY:  A NUL-terminated, <255-length string | ||||
| ; | ||||
| ; Output: A = 1 if the string matches the pattern, A = 0 if not. | ||||
| ; | ||||
| ; Notes:  Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack. | ||||
| ; | ||||
| ; see http://6502.org/source/strings/patmatch.htm | ||||
|  | ||||
| str = P8ZP_SCRATCH_W1 | ||||
|  | ||||
| 	stx  P8ZP_SCRATCH_REG | ||||
| 	sta  str | ||||
| 	sty  str+1 | ||||
| 	lda  cx16.r0 | ||||
| 	sta  modify_pattern1+1 | ||||
| 	sta  modify_pattern2+1 | ||||
| 	lda  cx16.r0+1 | ||||
| 	sta  modify_pattern1+2 | ||||
| 	sta  modify_pattern2+2 | ||||
| 	jsr  _match | ||||
| 	lda  #0 | ||||
| 	adc  #0 | ||||
| 	ldx  P8ZP_SCRATCH_REG | ||||
| 	rts | ||||
|  | ||||
|  | ||||
| _match | ||||
| 	ldx #$00        ; x is an index in the pattern | ||||
| 	ldy #$ff        ; y is an index in the string | ||||
| modify_pattern1 | ||||
| next    lda $ffff,x   ; look at next pattern character    MODIFIED | ||||
| 	cmp #'*'     ; is it a star? | ||||
| 	beq star        ; yes, do the complicated stuff | ||||
| 	iny             ; no, let's look at the string | ||||
| 	cmp #'?'     ; is the pattern caracter a ques? | ||||
| 	bne reg         ; no, it's a regular character | ||||
| 	lda (str),y     ; yes, so it will match anything | ||||
| 	beq fail        ;  except the end of string | ||||
| reg     cmp (str),y     ; are both characters the same? | ||||
| 	bne fail        ; no, so no match | ||||
| 	inx             ; yes, keep checking | ||||
| 	cmp #0          ; are we at end of string? | ||||
| 	bne next        ; not yet, loop | ||||
| found   rts             ; success, return with c=1 | ||||
|  | ||||
| star    inx             ; skip star in pattern | ||||
| modify_pattern2 | ||||
| 	cmp $ffff,x   	; string of stars equals one star	MODIFIED | ||||
| 	beq star        ;  so skip them also | ||||
| stloop  txa             ; we first try to match with * = "" | ||||
| 	pha             ;  and grow it by 1 character every | ||||
| 	tya             ;  time we loop | ||||
| 	pha             ; save x and y on stack | ||||
| 	jsr next        ; recursive call | ||||
| 	pla             ; restore x and y | ||||
| 	tay | ||||
| 	pla | ||||
| 	tax | ||||
| 	bcs found       ; we found a match, return with c=1 | ||||
| 	iny             ; no match yet, try to grow * string | ||||
| 	lda (str),y     ; are we at the end of string? | ||||
| 	bne stloop      ; not yet, add a character | ||||
| fail    clc             ; yes, no match found, return with c=0 | ||||
| 	rts | ||||
| 		}} | ||||
| 	} | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,11 +0,0 @@ | ||||
| ; Prog8 internal library routines - always included by the compiler | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
| ; | ||||
| ; indent format: TABS, size=8 | ||||
|  | ||||
| %import c64lib | ||||
|  | ||||
| prog8_lib { | ||||
| 	%asminclude "library:prog8lib.asm", "" | ||||
| } | ||||
							
								
								
									
										235
									
								
								compiler/res/prog8lib/string.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								compiler/res/prog8lib/string.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,235 @@ | ||||
| ; 0-terminated string manipulation routines. | ||||
| ; | ||||
| ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 | ||||
|  | ||||
|  | ||||
| string { | ||||
|  | ||||
|     asmsub length(uword string @AY) clobbers(A) -> ubyte @Y { | ||||
|         ; Returns the number of bytes in the string. | ||||
|         ; This value is determined during runtime and counts upto the first terminating 0 byte in the string, | ||||
|         ; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof! | ||||
|  | ||||
|         %asm {{ | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #0 | ||||
| -		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		beq  + | ||||
| 		iny | ||||
| 		bne  - | ||||
| +		rts | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     asmsub left(uword source @R0, ubyte length @A, uword target @R1) clobbers(A, Y) { | ||||
|         ; Copies the left side of the source string of the given length to target string. | ||||
|         ; It is assumed the target string buffer is large enough to contain the result. | ||||
|         ; Also, you have to make sure yourself that length is smaller or equal to the length of the source string. | ||||
|         ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). | ||||
|         %asm {{ | ||||
|                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||
| 		ldy  cx16.r0 | ||||
| 		sty  P8ZP_SCRATCH_W1 | ||||
| 		ldy  cx16.r0+1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  cx16.r1 | ||||
| 		sty  P8ZP_SCRATCH_W2 | ||||
| 		ldy  cx16.r1+1 | ||||
| 		sty  P8ZP_SCRATCH_W2+1 | ||||
| 		tay | ||||
| 		lda  #0 | ||||
| 		sta  (P8ZP_SCRATCH_W2),y | ||||
| 		cpy  #0 | ||||
| 		bne  _loop | ||||
| 		rts | ||||
| _loop		dey | ||||
| 		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  (P8ZP_SCRATCH_W2),y | ||||
| 		cpy  #0 | ||||
| 		bne  _loop | ||||
| +		rts | ||||
|         }} | ||||
| ;                asmgen.out("  jsr  prog8_lib.func_leftstr") | ||||
|     } | ||||
|  | ||||
|     asmsub right(uword source @R0, ubyte length @A, uword target @R1) clobbers(A,Y) { | ||||
|         ; Copies the right side of the source string of the given length to target string. | ||||
|         ; It is assumed the target string buffer is large enough to contain the result. | ||||
|         ; Also, you have to make sure yourself that length is smaller or equal to the length of the source string. | ||||
|         ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). | ||||
|         %asm {{ | ||||
|                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||
|                 sta  P8ZP_SCRATCH_B1 | ||||
|                 lda  cx16.r0 | ||||
|                 ldy  cx16.r0+1 | ||||
|                 jsr  string.length | ||||
|                 tya | ||||
|                 sec | ||||
|                 sbc  P8ZP_SCRATCH_B1 | ||||
|                 clc | ||||
|                 adc  cx16.r0 | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		lda  cx16.r0+1 | ||||
| 		adc  #0 | ||||
| 		sta  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  cx16.r1 | ||||
| 		sty  P8ZP_SCRATCH_W2 | ||||
| 		ldy  cx16.r1+1 | ||||
| 		sty  P8ZP_SCRATCH_W2+1 | ||||
| 		ldy  P8ZP_SCRATCH_B1 | ||||
| 		lda  #0 | ||||
| 		sta  (P8ZP_SCRATCH_W2),y | ||||
| 		cpy  #0 | ||||
| 		bne  _loop | ||||
| 		rts | ||||
| _loop		dey | ||||
| 		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  (P8ZP_SCRATCH_W2),y | ||||
| 		cpy  #0 | ||||
| 		bne  _loop | ||||
| +		rts | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     asmsub slice(uword source @R0, ubyte start @A, ubyte length @Y, uword target @R1) clobbers(A, Y) { | ||||
|         ; Copies a segment from the source string, starting at the given index, | ||||
|         ;  and of the given length to target string. | ||||
|         ; It is assumed the target string buffer is large enough to contain the result. | ||||
|         ; Also, you have to make sure yourself that start and length are within bounds of the strings. | ||||
|         ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). | ||||
|         %asm {{ | ||||
|                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||
| 		; substr(source, target, start, length) | ||||
| 		sta  P8ZP_SCRATCH_B1 | ||||
| 		lda  cx16.r0 | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		lda  cx16.r0+1 | ||||
| 		sta  P8ZP_SCRATCH_W1+1 | ||||
| 		lda  cx16.r1 | ||||
| 		sta  P8ZP_SCRATCH_W2 | ||||
| 		lda  cx16.r1+1 | ||||
| 		sta  P8ZP_SCRATCH_W2+1 | ||||
|  | ||||
| 		; adjust src location | ||||
| 		clc | ||||
| 		lda  P8ZP_SCRATCH_W1 | ||||
| 		adc  P8ZP_SCRATCH_B1 | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		bcc  + | ||||
| 		inc  P8ZP_SCRATCH_W1+1 | ||||
| +		lda  #0 | ||||
| 		sta  (P8ZP_SCRATCH_W2),y | ||||
| 		beq  _startloop | ||||
| -		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		sta  (P8ZP_SCRATCH_W2),y | ||||
| _startloop	dey | ||||
| 		cpy  #$ff | ||||
| 		bne  - | ||||
| 		rts | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     asmsub find(uword string @R0, ubyte character @A) -> uword @AY { | ||||
|         ; Locates the first position of the given character in the string, | ||||
|         ;  returns the string starting with this character or $0000 if the character is not found. | ||||
|         %asm {{ | ||||
|                 ; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... | ||||
|                 sta  P8ZP_SCRATCH_B1 | ||||
| 		lda  cx16.r0 | ||||
| 		ldy  cx16.r0+1 | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		ldy  #0 | ||||
| -		lda  (P8ZP_SCRATCH_W1),y | ||||
| 		beq  _notfound | ||||
| 		cmp  P8ZP_SCRATCH_B1 | ||||
| 		beq  _found | ||||
| 		iny | ||||
| 		bne  - | ||||
| _notfound	lda  #0 | ||||
| 		ldy  #0 | ||||
| 		rts | ||||
| _found		sty  P8ZP_SCRATCH_B1 | ||||
| 		ldy  P8ZP_SCRATCH_W1+1 | ||||
| 		lda  P8ZP_SCRATCH_W1 | ||||
| 		clc | ||||
| 		adc  P8ZP_SCRATCH_B1 | ||||
| 		bcc  + | ||||
| 		iny | ||||
| +		rts | ||||
|  | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     asmsub copy(uword source @R0, uword target @AY) clobbers(A) -> ubyte @Y { | ||||
|         ; Copy a string to another, overwriting that one. | ||||
|         ; Returns the length of the string that was copied. | ||||
|         ; Often you don’t have to call this explicitly and can just write string1 = string2 | ||||
|         ; but this function is useful if you’re dealing with addresses for instance. | ||||
|         %asm {{ | ||||
| 		sta  P8ZP_SCRATCH_W1 | ||||
| 		sty  P8ZP_SCRATCH_W1+1 | ||||
| 		lda  cx16.r0 | ||||
| 		ldy  cx16.r0+1 | ||||
| 		jmp  prog8_lib.strcpy | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A { | ||||
|         ; Compares two strings for sorting. | ||||
|         ; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2. | ||||
|         ; Note that you can also directly compare strings and string values with eachother using | ||||
|         ; comparison operators ==, < etcetera (it will use strcmp for you under water automatically). | ||||
|         %asm {{ | ||||
| 		sta  P8ZP_SCRATCH_W2 | ||||
| 		sty  P8ZP_SCRATCH_W2+1 | ||||
| 		lda  cx16.r0 | ||||
| 		ldy  cx16.r0+1 | ||||
| 		jmp  prog8_lib.strcmp_mem | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     asmsub lower(uword st @AY) -> ubyte @Y { | ||||
|         ; Lowercases the petscii string in-place. Returns length of the string. | ||||
|         ; (for efficiency, non-letter characters > 128 will also not be left intact, | ||||
|         ;  but regular text doesn't usually contain those characters anyway.) | ||||
|         %asm {{ | ||||
|             sta  P8ZP_SCRATCH_W1 | ||||
|             sty  P8ZP_SCRATCH_W1+1 | ||||
|             ldy  #0 | ||||
| -           lda  (P8ZP_SCRATCH_W1),y | ||||
|             beq  _done | ||||
|             and  #$7f | ||||
|             cmp  #97 | ||||
|             bcc  + | ||||
|             cmp  #123 | ||||
|             bcs  + | ||||
|             and  #%11011111 | ||||
| +           sta  (P8ZP_SCRATCH_W1),y | ||||
|             iny | ||||
|             bne  - | ||||
| _done       rts | ||||
|         }} | ||||
|     } | ||||
|  | ||||
|     asmsub upper(uword st @AY) -> ubyte @Y { | ||||
|         ; Uppercases the petscii string in-place. Returns length of the string. | ||||
|         %asm {{ | ||||
|             sta  P8ZP_SCRATCH_W1 | ||||
|             sty  P8ZP_SCRATCH_W1+1 | ||||
|             ldy  #0 | ||||
| -           lda  (P8ZP_SCRATCH_W1),y | ||||
|             beq  _done | ||||
|             cmp  #65 | ||||
|             bcc  + | ||||
|             cmp  #91 | ||||
|             bcs  + | ||||
|             ora  #%00100000 | ||||
| +           sta  (P8ZP_SCRATCH_W1),y | ||||
|             iny | ||||
|             bne  - | ||||
| _done       rts | ||||
|         }} | ||||
|     } | ||||
| } | ||||
							
								
								
									
										50
									
								
								compiler/res/prog8lib/test_stack.p8
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								compiler/res/prog8lib/test_stack.p8
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| ; utility debug code to print the X (evalstack) and SP (cpu stack) registers. | ||||
|  | ||||
| %import textio | ||||
|  | ||||
| test_stack { | ||||
|  | ||||
|     asmsub test() { | ||||
|         %asm {{ | ||||
| 	stx  _saveX | ||||
| 	lda  #13 | ||||
| 	jsr  txt.chrout | ||||
| 	lda  #'-' | ||||
| 	ldy  #12 | ||||
| -	jsr  txt.chrout | ||||
| 	dey | ||||
| 	bne  - | ||||
| 	lda  #13 | ||||
| 	jsr  txt.chrout | ||||
| 	lda  #'x' | ||||
| 	jsr  txt.chrout | ||||
| 	lda  #'=' | ||||
| 	jsr  txt.chrout | ||||
| 	lda  _saveX | ||||
| 	jsr  txt.print_ub | ||||
| 	lda  #' ' | ||||
| 	jsr  txt.chrout | ||||
| 	lda  #'s' | ||||
| 	jsr  txt.chrout | ||||
| 	lda  #'p' | ||||
| 	jsr  txt.chrout | ||||
| 	lda  #'=' | ||||
| 	jsr  txt.chrout | ||||
| 	tsx | ||||
| 	txa | ||||
| 	jsr  txt.print_ub | ||||
| 	lda  #13 | ||||
| 	jsr  txt.chrout | ||||
| 	lda  #'-' | ||||
| 	ldy  #12 | ||||
| -	jsr  txt.chrout | ||||
| 	dey | ||||
| 	bne  - | ||||
| 	lda  #13 | ||||
| 	jsr  txt.chrout | ||||
| 	ldx  _saveX | ||||
| 	rts | ||||
| _saveX	.byte 0 | ||||
|         }} | ||||
|     } | ||||
| } | ||||
| @@ -1 +1 @@ | ||||
| 1.52 | ||||
| 7.0-BETA2 | ||||
|   | ||||
| @@ -1,24 +1,23 @@ | ||||
| package prog8 | ||||
|  | ||||
| import kotlinx.cli.* | ||||
| import prog8.ast.base.AstException | ||||
| import prog8.compiler.CompilationResult | ||||
| import prog8.compiler.compileProgram | ||||
| import prog8.compiler.target.C64Target | ||||
| import prog8.compiler.target.Cx16Target | ||||
| import prog8.parser.ParsingFailedError | ||||
| import prog8.vm.astvm.AstVm | ||||
| import java.io.File | ||||
| import java.nio.file.FileSystems | ||||
| import java.nio.file.Path | ||||
| import java.nio.file.Paths | ||||
| import java.nio.file.StandardWatchEventKinds | ||||
| import java.util.* | ||||
| import java.time.LocalDateTime | ||||
| import kotlin.system.exitProcess | ||||
|  | ||||
|  | ||||
| fun main(args: Array<String>) { | ||||
|  | ||||
|     printSoftwareHeader("compiler") | ||||
|  | ||||
|     if (args.isEmpty()) | ||||
|         usage() | ||||
|     compileMain(args) | ||||
| } | ||||
|  | ||||
| @@ -29,72 +28,91 @@ internal fun printSoftwareHeader(what: String) { | ||||
| } | ||||
|  | ||||
|  | ||||
| fun pathFrom(stringPath: String, vararg rest: String): Path  = FileSystems.getDefault().getPath(stringPath, *rest) | ||||
|  | ||||
|  | ||||
| private fun compileMain(args: Array<String>) { | ||||
|     var emulatorToStart = "" | ||||
|     var moduleFile = "" | ||||
|     var writeAssembly = true | ||||
|     var optimize = true | ||||
|     var launchAstVm = false | ||||
|     var watchMode = false | ||||
|     for (arg in args) { | ||||
|         if(arg=="-emu") | ||||
|             emulatorToStart = "x64" | ||||
|         else if(arg=="-emu2") | ||||
|             emulatorToStart = "x64sc" | ||||
|         else if(arg=="-noasm") | ||||
|             writeAssembly = false | ||||
|         else if(arg=="-noopt") | ||||
|             optimize = false | ||||
|         else if(arg=="-avm") | ||||
|             launchAstVm = true | ||||
|         else if(arg=="-watch") | ||||
|             watchMode = true | ||||
|         else if(!arg.startsWith("-")) | ||||
|             moduleFile = arg | ||||
|         else | ||||
|             usage() | ||||
|     } | ||||
|  | ||||
|     if(watchMode) { | ||||
|         if(moduleFile.isBlank()) | ||||
|             usage() | ||||
|  | ||||
|         val watchservice = FileSystems.getDefault().newWatchService() | ||||
|  | ||||
|         while(true) { | ||||
|             val filepath = Paths.get(moduleFile).normalize() | ||||
|             println("Continuous watch mode active. Main module: $filepath") | ||||
|     val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM) | ||||
|     val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation") | ||||
|     val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") | ||||
|     val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") | ||||
|     val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") | ||||
|     val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed") | ||||
|     val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") | ||||
|     val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name) | ||||
|     val libDirs by cli.option(ArgType.String, fullName="libdirs", description = "list of extra paths to search in for imported modules").multiple().delimiter(File.pathSeparator) | ||||
|     val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) | ||||
|  | ||||
|     try { | ||||
|                 val compilationResult = compileProgram(filepath, optimize, writeAssembly) | ||||
|         cli.parse(args) | ||||
|     } catch (e: IllegalStateException) { | ||||
|         System.err.println(e.message) | ||||
|         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 compilationResult.importedFiles) { | ||||
|             for (importedFile in allImportedFiles) { | ||||
|                 print("  ") | ||||
|                 println(importedFile) | ||||
|                     importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY) | ||||
|                 val watchDir = importedFile.parent ?: Path.of(".") | ||||
|                 watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY) | ||||
|             } | ||||
|                 println("${Date()}: Waiting for file changes.") | ||||
|             println("[${LocalDateTime.now().withNano(0)}]  Waiting for file changes.") | ||||
|  | ||||
|             var recompile=false | ||||
|             while(!recompile) { | ||||
|                 val event = watchservice.take() | ||||
|                 for (changed in event.pollEvents()) { | ||||
|                     val changedPath = changed.context() as Path | ||||
|                     if(allImportedFiles.any { it.fileName == changedPath.fileName }) { | ||||
|                         println("  change detected: $changedPath") | ||||
|                         recompile = true | ||||
|                     } | ||||
|                 } | ||||
|                 event.reset() | ||||
|                 println("\u001b[H\u001b[2J")      // clear the screen | ||||
|             } catch (x: Exception) { | ||||
|                 throw x | ||||
|             } | ||||
|  | ||||
|             println("\u001b[H\u001b[2J")      // clear the screen | ||||
|         } | ||||
|  | ||||
|     } else { | ||||
|         if(moduleFile.isBlank()) | ||||
|             usage() | ||||
|  | ||||
|         val filepath = Paths.get(moduleFile).normalize() | ||||
|         for(filepathRaw in moduleFiles) { | ||||
|             val filepath = pathFrom(filepathRaw).normalize() | ||||
|             val compilationResult: CompilationResult | ||||
|  | ||||
|             try { | ||||
|             compilationResult = compileProgram(filepath, optimize, writeAssembly) | ||||
|                 compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, libdirs, outputPath) | ||||
|                 if(!compilationResult.success) | ||||
|                     exitProcess(1) | ||||
|             } catch (x: ParsingFailedError) { | ||||
| @@ -103,35 +121,13 @@ private fun compileMain(args: Array<String>) { | ||||
|                 exitProcess(1) | ||||
|             } | ||||
|  | ||||
|         if (launchAstVm) { | ||||
|             println("\nLaunching AST-based vm...") | ||||
|             val vm = AstVm(compilationResult.programAst) | ||||
|             vm.run() | ||||
|         } | ||||
|  | ||||
|         if (emulatorToStart.isNotEmpty()) { | ||||
|             if (startEmulator==true) { | ||||
|                 if (compilationResult.programName.isEmpty()) | ||||
|                     println("\nCan't start emulator because no program was assembled.") | ||||
|                 else { | ||||
|                 println("\nStarting C-64 emulator $emulatorToStart...") | ||||
|                 val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list", | ||||
|                         "-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg") | ||||
|                 val process = ProcessBuilder(cmdline).inheritIO().start() | ||||
|                 process.waitFor() | ||||
|                     compilationResult.compTarget.machine.launchEmulator(compilationResult.programName) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| private fun usage() { | ||||
|     System.err.println("Missing argument(s):") | ||||
|     System.err.println("    [-noasm]        don't create assembly code") | ||||
|     System.err.println("    [-noopt]        don't perform any optimizations") | ||||
|     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("    [-avm]          launch the prog8 ast-based virtual machine after compilation") | ||||
|     System.err.println("    [-watch]        continuous compilation mode (watches for file changes)") | ||||
|     System.err.println("    modulefile      main module file to compile") | ||||
|     exitProcess(1) | ||||
| } | ||||
|   | ||||
| @@ -1,263 +0,0 @@ | ||||
| package prog8.ast | ||||
|  | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.Expression | ||||
| import prog8.ast.expressions.IdentifierReference | ||||
| import prog8.ast.statements.* | ||||
| import prog8.compiler.HeapValues | ||||
| import prog8.functions.BuiltinFunctions | ||||
| import java.nio.file.Path | ||||
|  | ||||
|  | ||||
| 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 IFunctionCall { | ||||
|     var target: IdentifierReference | ||||
|     var arglist: MutableList<Expression> | ||||
| } | ||||
|  | ||||
| interface INameScope { | ||||
|     val name: String | ||||
|     val position: Position | ||||
|     val statements: MutableList<Statement> | ||||
|     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, or contain subscopes, they must be added here! | ||||
|                 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 } | ||||
|                 } | ||||
|                 is INameScope -> subscopes[stmt.name] = stmt | ||||
|                 else -> {} | ||||
|             } | ||||
|         } | ||||
|         return subscopes | ||||
|     } | ||||
|  | ||||
|     fun getLabelOrVariable(name: String): Statement? { | ||||
|         // 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 | ||||
|             if (stmt is AnonymousScope) { | ||||
|                 val sub = stmt.getLabelOrVariable(name) | ||||
|                 if(sub!=null) | ||||
|                     return sub | ||||
|             } | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
|  | ||||
|     fun allDefinedSymbols(): List<Pair<String, Statement>>  { | ||||
|         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) : Statement? { | ||||
|         if(scopedName.size>1) { | ||||
|             // a scoped name can a) refer to a member of a struct, or b) refer to a name in another module. | ||||
|             // try the struct first. | ||||
|             val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl | ||||
|             val struct = thing?.struct | ||||
|             if (struct != null) { | ||||
|                 if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) { | ||||
|                     // return ref to the mangled name variable | ||||
|                     val mangled = mangledStructMemberName(thing.name, scopedName.last()) | ||||
|                     return thing.definingScope().getLabelOrVariable(mangled) | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // 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 Statement? | ||||
|                 } | ||||
|             } | ||||
|             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 Statement? | ||||
|                 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: Statement) { | ||||
|         if(!statements.remove(stmt)) | ||||
|             throw FatalAstException("stmt to remove wasn't found in scope") | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /*********** 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 definedLoadAddress: Int | ||||
|         get() = modules.first().loadAddress | ||||
|  | ||||
|     var actualLoadAddress: Int = 0 | ||||
|  | ||||
|     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? | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() } | ||||
| } | ||||
|  | ||||
| class Module(override val name: String, | ||||
|              override var statements: MutableList<Statement>, | ||||
|              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<Statement>() | ||||
|     override var parent: Node = ParentSentinel | ||||
|  | ||||
|     override fun linkParents(parent: Node) { | ||||
|         modules.forEach { it.linkParents(this) } | ||||
|     } | ||||
|  | ||||
|     override fun lookup(scopedName: List<String>, localContext: Node): Statement? { | ||||
|         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 | ||||
|         } | ||||
|  | ||||
|         if(scopedName.size>1) { | ||||
|             // a scoped name can a) refer to a member of a struct, or b) refer to a name in another module. | ||||
|             // try the struct first. | ||||
|             val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl | ||||
|             val struct = thing?.struct | ||||
|             if (struct != null) { | ||||
|                 if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) { | ||||
|                     // return ref to the mangled name variable | ||||
|                     val mangled = mangledStructMemberName(thing.name, scopedName.last()) | ||||
|                     return thing.definingScope().getLabelOrVariable(mangled) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         // lookup something from the module. | ||||
|         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<Statement>() | ||||
|     override var parent: Node = ParentSentinel | ||||
|     override fun linkParents(parent: Node) {} | ||||
| } | ||||
|  | ||||
|  | ||||
| // prefix for struct member variables | ||||
| internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName" | ||||
| @@ -1,646 +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.Module | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.statements.* | ||||
| import prog8.compiler.target.c64.Petscii | ||||
| import prog8.parser.CustomLexer | ||||
| import prog8.parser.prog8Parser | ||||
| import java.io.CharConversionException | ||||
| import java.io.File | ||||
| import java.nio.file.Path | ||||
|  | ||||
|  | ||||
| /***************** 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) : Statement { | ||||
|     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) : Statement = | ||||
|         Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition()) | ||||
|  | ||||
|  | ||||
| private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> = | ||||
|         statement().asSequence().map { it.toAst() }.toMutableList() | ||||
|  | ||||
|  | ||||
| private fun prog8Parser.StatementContext.toAst() : Statement { | ||||
|     vardecl()?.let { return it.toAst() } | ||||
|  | ||||
|     varinitializer()?.let { | ||||
|         val vd = it.vardecl() | ||||
|         return VarDecl( | ||||
|                 VarDeclType.VAR, | ||||
|                 vd.datatype()?.toAst() ?: DataType.STRUCT, | ||||
|                 if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, | ||||
|                 vd.arrayindex()?.toAst(), | ||||
|                 vd.varname.text, | ||||
|                 null, | ||||
|                 it.expression().toAst(), | ||||
|                 vd.ARRAYSIG() != null || vd.arrayindex() != null, | ||||
|                 false, | ||||
|                 it.toPosition() | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     structvarinitializer()?.let { | ||||
|         val vd = it.structvardecl() | ||||
|         return VarDecl( | ||||
|                 VarDeclType.VAR, | ||||
|                 DataType.STRUCT, | ||||
|                 ZeropageWish.NOT_IN_ZEROPAGE, | ||||
|                 null, | ||||
|                 vd.varname.text, | ||||
|                 vd.structname.text, | ||||
|                 it.expression().toAst(), | ||||
|                 isArray = false, | ||||
|                 autogeneratedDontRemove = false, | ||||
|                 position = it.toPosition() | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     structvardecl()?.let { | ||||
|         return VarDecl( | ||||
|                 VarDeclType.VAR, | ||||
|                 DataType.STRUCT, | ||||
|                 ZeropageWish.NOT_IN_ZEROPAGE, | ||||
|                 null, | ||||
|                 it.varname.text, | ||||
|                 it.structname.text, | ||||
|                 null, | ||||
|                 isArray = false, | ||||
|                 autogeneratedDontRemove = false, | ||||
|                 position = it.toPosition() | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     constdecl()?.let { | ||||
|         val cvarinit = it.varinitializer() | ||||
|         val vd = cvarinit.vardecl() | ||||
|         return VarDecl( | ||||
|                 VarDeclType.CONST, | ||||
|                 vd.datatype()?.toAst() ?: DataType.STRUCT, | ||||
|                 if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, | ||||
|                 vd.arrayindex()?.toAst(), | ||||
|                 vd.varname.text, | ||||
|                 null, | ||||
|                 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() ?: DataType.STRUCT, | ||||
|                 if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, | ||||
|                 vd.arrayindex()?.toAst(), | ||||
|                 vd.varname.text, | ||||
|                 null, | ||||
|                 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 | ||||
|  | ||||
|     structdecl()?.let { | ||||
|         return StructDecl(it.identifier().text, | ||||
|                 it.vardecl().map { vd->vd.toAst() }.toMutableList(), | ||||
|                 toPosition()) | ||||
|     } | ||||
|  | ||||
|     throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text") | ||||
| } | ||||
|  | ||||
| private fun prog8Parser.AsmsubroutineContext.toAst(): Statement { | ||||
|     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 { | ||||
|     val vardecl = it.vardecl() | ||||
|     val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT | ||||
|     AsmSubroutineParameter(vardecl.varname.text, datatype, | ||||
|             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(): Statement { | ||||
|     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(): Statement = | ||||
|         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 { | ||||
|             val datatype = it.datatype()?.toAst() ?: DataType.STRUCT | ||||
|             SubroutineParameter(it.varname.text, datatype, 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() : Expression { | ||||
|  | ||||
|     val litval = literalvalue() | ||||
|     if(litval!=null) { | ||||
|         val booleanlit = litval.booleanliteral()?.toAst() | ||||
|         return if(booleanlit!=null) { | ||||
|             NumericLiteralValue.fromBoolean(booleanlit, litval.toPosition()) | ||||
|         } | ||||
|         else { | ||||
|             val intLit = litval.integerliteral()?.toAst() | ||||
|             when { | ||||
|                 intLit!=null -> when(intLit.datatype) { | ||||
|                     DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition()) | ||||
|                     DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition()) | ||||
|                     DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition()) | ||||
|                     DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition()) | ||||
|                     DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition()) | ||||
|                     else -> throw FatalAstException("invalid datatype for numeric literal") | ||||
|                 } | ||||
|                 litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition()) | ||||
|                 litval.stringliteral()!=null -> StringLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition()) | ||||
|                 litval.charliteral()!=null -> { | ||||
|                     try { | ||||
|                         NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], 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 ConstantFold takes care of that and converts the type if needed. | ||||
|                     ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition()) | ||||
|                 } | ||||
|                 litval.structliteral()!=null -> { | ||||
|                     val values = litval.structliteral().expression().map { it.toAst() } | ||||
|                     StructLiteralValue(values, 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() ?: NumericLiteralValue(DataType.UBYTE, 1, 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<Expression> = | ||||
|         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<Statement> { | ||||
|     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 = if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE | ||||
|     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 stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf() | ||||
|     if(stmt!=null) | ||||
|         stmtBlock.add(stmt) | ||||
|     val scope = AnonymousScope(stmtBlock, toPosition()) | ||||
|     return WhenChoice(values, scope, toPosition()) | ||||
| } | ||||
|  | ||||
| private fun prog8Parser.VardeclContext.toAst(): VarDecl { | ||||
|     return VarDecl( | ||||
|             VarDeclType.VAR, | ||||
|             datatype()?.toAst() ?: DataType.STRUCT, | ||||
|             if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, | ||||
|             arrayindex()?.toAst(), | ||||
|             varname.text, | ||||
|             null, | ||||
|             null, | ||||
|             ARRAYSIG() != null || arrayindex() != null, | ||||
|             false, | ||||
|             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,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" | ||||
| } | ||||
|  | ||||
| open class NameError(override var message: String, val position: Position) : AstException(message) { | ||||
|     override fun toString() = "$position Name error: $message" | ||||
| } | ||||
|  | ||||
| class ExpressionError(message: String, val position: Position) : AstException(message) { | ||||
|     override fun toString() = "$position Error: $message" | ||||
| } | ||||
|  | ||||
| class UndefinedSymbolError(symbol: IdentifierReference) | ||||
|     : NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) | ||||
| @@ -1,70 +0,0 @@ | ||||
| package prog8.ast.base | ||||
|  | ||||
| import prog8.ast.Module | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.processing.* | ||||
| import prog8.compiler.CompilationOptions | ||||
| import prog8.compiler.target.c64.codegen.AnonymousScopeVarsCleanup | ||||
| import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops | ||||
|  | ||||
|  | ||||
| // the name of the subroutine that should be called for every block to initialize its variables | ||||
| internal const val initvarsSubName="prog8_init_vars" | ||||
|  | ||||
|  | ||||
| internal fun Program.removeNopsFlattenAnonScopes() { | ||||
|     val flattener = FlattenAnonymousScopesAndRemoveNops() | ||||
|     flattener.visit(this) | ||||
| } | ||||
|  | ||||
|  | ||||
| internal fun Program.checkValid(compilerOptions: CompilationOptions) { | ||||
|     val checker = AstChecker(this, compilerOptions) | ||||
|     checker.visit(this) | ||||
|     printErrors(checker.result(), name) | ||||
| } | ||||
|  | ||||
|  | ||||
| internal fun Program.anonscopeVarsCleanup() { | ||||
|     val mover = AnonymousScopeVarsCleanup(this) | ||||
|     mover.visit(this) | ||||
|     printErrors(mover.result(), name) | ||||
| } | ||||
|  | ||||
|  | ||||
| internal fun Program.reorderStatements() { | ||||
|     val initvalueCreator = VarInitValueAndAddressOfCreator(this) | ||||
|     initvalueCreator.visit(this) | ||||
|  | ||||
|     val checker = StatementReorderer(this) | ||||
|     checker.visit(this) | ||||
| } | ||||
|  | ||||
| internal fun Program.addTypecasts() { | ||||
|     val caster = TypecastsAdder(this) | ||||
|     caster.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(this) | ||||
|     checker.visit(this) | ||||
|  | ||||
|     if(modules.map {it.name}.toSet().size != modules.size) { | ||||
|         throw FatalAstException("modules should all be unique") | ||||
|     } | ||||
|  | ||||
|     printErrors(checker.result(), name) | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,409 +0,0 @@ | ||||
| package prog8.ast.processing | ||||
|  | ||||
| import prog8.ast.INameScope | ||||
| import prog8.ast.Module | ||||
| import prog8.ast.Node | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.statements.* | ||||
| import prog8.compiler.HeapValues | ||||
| import prog8.compiler.target.c64.AssemblyProgram | ||||
| import prog8.functions.BuiltinFunctions | ||||
|  | ||||
|  | ||||
| internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor { | ||||
|  | ||||
|     private val checkResult: MutableList<AstException> = mutableListOf() | ||||
|  | ||||
|     private var blocks = mutableMapOf<String, Block>() | ||||
|     private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>() | ||||
|  | ||||
|     internal fun result(): List<AstException> { | ||||
|         return checkResult | ||||
|     } | ||||
|  | ||||
|     private fun nameError(name: String, position: Position, existing: Statement) { | ||||
|         checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)) | ||||
|     } | ||||
|  | ||||
|     override fun visit(module: Module) { | ||||
|         vardeclsToAdd.clear() | ||||
|         blocks.clear()  // blocks may be redefined within a different module | ||||
|         super.visit(module) | ||||
|         // add any new vardecls to the various scopes | ||||
|         for((where, decls) in vardeclsToAdd) { | ||||
|             where.statements.addAll(0, decls) | ||||
|             decls.forEach { it.linkParents(where as Node) } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun visit(block: Block): Statement { | ||||
|         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): Expression { | ||||
|         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): Statement { | ||||
|         // 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)) | ||||
|  | ||||
|         if(decl.name in AssemblyProgram.opcodeNames) | ||||
|             checkResult.add(NameError("can't use a cpu opcode name as a symbol", decl.position)) | ||||
|  | ||||
|         // is it a struct variable? then define all its struct members as mangled names, | ||||
|         //    and include the original decl as well. | ||||
|         if(decl.datatype==DataType.STRUCT) { | ||||
|             if(decl.structHasBeenFlattened) | ||||
|                 return super.visit(decl)    // don't do this multiple times | ||||
|  | ||||
|             if(decl.struct==null) { | ||||
|                 checkResult.add(NameError("undefined struct type", decl.position)) | ||||
|                 return super.visit(decl) | ||||
|             } | ||||
|  | ||||
|             if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes}) | ||||
|                 return super.visit(decl)     // a non-numeric member, not supported. proper error is given by AstChecker later | ||||
|  | ||||
|             if(decl.value is NumericLiteralValue) { | ||||
|                 checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position)) | ||||
|                 return super.visit(decl) | ||||
|             } | ||||
|  | ||||
|             val decls = decl.flattenStructMembers() | ||||
|             decls.add(decl) | ||||
|             val result = AnonymousScope(decls, decl.position) | ||||
|             result.linkParents(decl.parent) | ||||
|             return result | ||||
|         } | ||||
|  | ||||
|         val existing = program.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): Statement { | ||||
|         if(subroutine.name in AssemblyProgram.opcodeNames) { | ||||
|             checkResult.add(NameError("can't use a cpu opcode name as a symbol", subroutine.position)) | ||||
|         } else if(subroutine.name in BuiltinFunctions) { | ||||
|             // the builtin functions can't be redefined | ||||
|             checkResult.add(NameError("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) | ||||
|  | ||||
|             // does the parameter redefine a variable declared elsewhere? | ||||
|             for(param in subroutine.parameters) { | ||||
|                 val existingVar = subroutine.lookup(listOf(param.name), subroutine) | ||||
|                 if (existingVar != null && existingVar.parent !== subroutine) { | ||||
|                     nameError(param.name, param.position, existingVar) | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // check that there are no local variables, labels, or other subs that redefine the subroutine's parameters | ||||
|             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) | ||||
|             if(subroutine.asmAddress==null) { | ||||
|                 if(subroutine.asmParameterRegisters.isEmpty()) { | ||||
|                     subroutine.parameters | ||||
|                             .filter { it.name !in namesInSub } | ||||
|                             .forEach { | ||||
|                                 val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null, | ||||
|                                         isArray = false, autogeneratedDontRemove = true, position = subroutine.position) | ||||
|                                 vardecl.linkParents(subroutine) | ||||
|                                 subroutine.statements.add(0, vardecl) | ||||
|                             } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { | ||||
|                 checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position)) | ||||
|             } | ||||
|         } | ||||
|         return super.visit(subroutine) | ||||
|     } | ||||
|  | ||||
|     override fun visit(label: Label): Statement { | ||||
|         if(label.name in AssemblyProgram.opcodeNames) | ||||
|             checkResult.add(NameError("can't use a cpu opcode name as a symbol", label.position)) | ||||
|  | ||||
|         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 = program.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): Statement { | ||||
|         // 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 { | ||||
|             val loopVar = forLoop.loopVar | ||||
|             if (loopVar != null) { | ||||
|                 val varName = loopVar.nameInSource.last() | ||||
|                 if (forLoop.decltype != null) { | ||||
|                     val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(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, null, | ||||
|                                 isArray = false, autogeneratedDontRemove = true, position = loopVar.position) | ||||
|                         vardecl.linkParents(forLoop.body) | ||||
|                         forLoop.body.statements.add(0, vardecl) | ||||
|                         loopVar.parent = forLoop.body   // loopvar 'is defined in the body' | ||||
|                     } else if(existing.parent!==forLoop && existing.parent.parent!==forLoop) { | ||||
|                         checkResult.add(NameError("for loop var was already defined at ${existing.position}", loopVar.position)) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "") | ||||
|                 val loopvarName = "prog8_loopvar_$validName" | ||||
|                 if (forLoop.iterable !is RangeExpr) { | ||||
|                     val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), 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, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null, | ||||
|                                 isArray = false, autogeneratedDontRemove = true, position = loopVar.position) | ||||
|                         vardecl.linkParents(forLoop.body) | ||||
|                         forLoop.body.statements.add(0, vardecl) | ||||
|                         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): Statement { | ||||
|         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: Expression | ||||
|             val lval = returnStmt.value as? NumericLiteralValue | ||||
|             if(lval!=null) { | ||||
|                 newValue = lval.cast(subroutine.returntypes.single()) | ||||
|             } else { | ||||
|                 newValue = returnStmt.value!! | ||||
|             } | ||||
|  | ||||
|             returnStmt.value = newValue | ||||
|         } | ||||
|         return super.visit(returnStmt) | ||||
|     } | ||||
|  | ||||
|     override fun visit(arrayLiteral: ArrayLiteralValue): Expression { | ||||
|         val array = super.visit(arrayLiteral) | ||||
|         if(array is ArrayLiteralValue) { | ||||
|             val vardecl = array.parent as? VarDecl | ||||
|             return if (vardecl!=null) { | ||||
|                 fixupArrayDatatype(array, vardecl, program.heap) | ||||
|             } else { | ||||
|                 // fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array | ||||
|                 // (we don't know the desired datatype here exactly so we guess) | ||||
|                 val datatype = determineArrayDt(array.value) | ||||
|                 val litval2 = array.cast(datatype)!! | ||||
|                 litval2.parent = array.parent | ||||
|                 // finally, replace the literal array by a identifier reference. | ||||
|                 makeIdentifierFromRefLv(litval2) | ||||
|             } | ||||
|         } | ||||
|         return array | ||||
|     } | ||||
|  | ||||
|     override fun visit(stringLiteral: StringLiteralValue): Expression { | ||||
|         val string = super.visit(stringLiteral) | ||||
|         if(string is StringLiteralValue) { | ||||
|             val vardecl = string.parent as? VarDecl | ||||
|             // intern the string; move it into the heap | ||||
|             if (string.value.length !in 1..255) | ||||
|                 checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position)) | ||||
|             else { | ||||
|                 string.addToHeap(program.heap) | ||||
|             } | ||||
|             return if (vardecl != null) | ||||
|                 string | ||||
|             else | ||||
|                 makeIdentifierFromRefLv(string)  // replace the literal string by a identifier reference. | ||||
|         } | ||||
|         return string | ||||
|     } | ||||
|  | ||||
|     private fun determineArrayDt(array: Array<Expression>): DataType { | ||||
|         val datatypesInArray = array.map { it.inferType(program) } | ||||
|         if(datatypesInArray.isEmpty() || datatypesInArray.any { !it.isKnown }) | ||||
|             throw IllegalArgumentException("can't determine type of empty array") | ||||
|         val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) } | ||||
|         return when { | ||||
|             DataType.FLOAT in dts -> DataType.ARRAY_F | ||||
|             DataType.WORD in dts -> DataType.ARRAY_W | ||||
|             DataType.UWORD in dts -> DataType.ARRAY_UW | ||||
|             DataType.BYTE in dts -> DataType.ARRAY_B | ||||
|             DataType.UBYTE in dts -> DataType.ARRAY_UB | ||||
|             else -> throw IllegalArgumentException("can't determine type of array") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference { | ||||
|         // a referencetype literal value that's not declared as a variable | ||||
|         // we need to introduce an auto-generated variable for this to be able to refer to the value | ||||
|         // note: if the var references the same literal value, it is not yet de-duplicated here. | ||||
|         array.addToHeap(program.heap) | ||||
|         val scope = array.definingScope() | ||||
|         val variable = VarDecl.createAuto(array) | ||||
|         return replaceWithIdentifier(variable, scope, array.parent) | ||||
|     } | ||||
|  | ||||
|     private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference { | ||||
|         // a referencetype literal value that's not declared as a variable | ||||
|         // we need to introduce an auto-generated variable for this to be able to refer to the value | ||||
|         // note: if the var references the same literal value, it is not yet de-duplicated here. | ||||
|         string.addToHeap(program.heap) | ||||
|         val scope = string.definingScope() | ||||
|         val variable = VarDecl.createAuto(string) | ||||
|         return replaceWithIdentifier(variable, scope, string.parent) | ||||
|     } | ||||
|  | ||||
|     private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference { | ||||
|         val variable1 = addVarDecl(scope, variable) | ||||
|         // replace the reference literal by a identifier reference | ||||
|         val identifier = IdentifierReference(listOf(variable1.name), variable1.position) | ||||
|         identifier.parent = parent | ||||
|         return identifier | ||||
|     } | ||||
|  | ||||
|     override fun visit(structDecl: StructDecl): Statement { | ||||
|         for(member in structDecl.statements){ | ||||
|             val decl = member as? VarDecl | ||||
|             if(decl!=null && decl.datatype !in NumericDatatypes) | ||||
|                 checkResult.add(SyntaxError("structs can only contain numerical types", decl.position)) | ||||
|         } | ||||
|  | ||||
|         return super.visit(structDecl) | ||||
|     } | ||||
|  | ||||
|     override fun visit(expr: BinaryExpression): Expression { | ||||
|         return when { | ||||
|             expr.left is StringLiteralValue -> | ||||
|                 processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr) | ||||
|             expr.right is StringLiteralValue -> | ||||
|                 processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr) | ||||
|             else -> super.visit(expr) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression { | ||||
|         val constvalue = operand.constValue(program) | ||||
|         if(constvalue!=null) { | ||||
|             if (expr.operator == "*") { | ||||
|                 // repeat a string a number of times | ||||
|                 val idt = string.inferType(program) | ||||
|                 return StringLiteralValue(idt.typeOrElse(DataType.STR), | ||||
|                         string.value.repeat(constvalue.number.toInt()), null, expr.position) | ||||
|             } | ||||
|         } | ||||
|         if(expr.operator == "+" && operand is StringLiteralValue) { | ||||
|             // concatenate two strings | ||||
|             val idt = string.inferType(program) | ||||
|             return StringLiteralValue(idt.typeOrElse(DataType.STR), | ||||
|                     "${string.value}${operand.value}", null, expr.position) | ||||
|         } | ||||
|         return expr | ||||
|     } | ||||
|  | ||||
|     private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl { | ||||
|         if(scope !in vardeclsToAdd) | ||||
|             vardeclsToAdd[scope] = mutableListOf() | ||||
|         val declList = vardeclsToAdd.getValue(scope) | ||||
|         val existing = declList.singleOrNull { it.name==variable.name } | ||||
|         return if(existing!=null) { | ||||
|             existing | ||||
|         } else { | ||||
|             declList.add(variable) | ||||
|             variable | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| internal fun fixupArrayDatatype(array: ArrayLiteralValue, vardecl: VarDecl, heap: HeapValues): ArrayLiteralValue { | ||||
|     if(array.heapId!=null) { | ||||
|         val arrayDt = array.type | ||||
|         if(arrayDt!=vardecl.datatype) { | ||||
|             // fix the datatype of the array (also on the heap) to match the vardecl | ||||
|             val litval2 = | ||||
|                     try { | ||||
|                         array.cast(vardecl.datatype)!! | ||||
|                     } catch(x: ExpressionError) { | ||||
|                         // couldn't cast permanently. | ||||
|                         // instead, simply adjust the array type and trust the AstChecker to report the exact error | ||||
|                         ArrayLiteralValue(vardecl.datatype, array.value, array.heapId, array.position) | ||||
|                     } | ||||
|             vardecl.value = litval2 | ||||
|             litval2.linkParents(vardecl) | ||||
|             litval2.addToHeap(heap) | ||||
|             return litval2 | ||||
|         } | ||||
|     } else { | ||||
|         array.addToHeap(heap) | ||||
|     } | ||||
|     return array | ||||
| } | ||||
| @@ -1,117 +0,0 @@ | ||||
| package prog8.ast.processing | ||||
|  | ||||
| import prog8.ast.INameScope | ||||
| 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,263 +0,0 @@ | ||||
| package prog8.ast.processing | ||||
|  | ||||
| import prog8.ast.Module | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.FatalAstException | ||||
| 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.map { it.accept(this) }.toMutableList() | ||||
|     } | ||||
|  | ||||
|     fun visit(expr: PrefixExpression): Expression { | ||||
|         expr.expression = expr.expression.accept(this) | ||||
|         return expr | ||||
|     } | ||||
|  | ||||
|     fun visit(expr: BinaryExpression): Expression { | ||||
|         expr.left = expr.left.accept(this) | ||||
|         expr.right = expr.right.accept(this) | ||||
|         return expr | ||||
|     } | ||||
|  | ||||
|     fun visit(directive: Directive): Statement { | ||||
|         return directive | ||||
|     } | ||||
|  | ||||
|     fun visit(block: Block): Statement { | ||||
|         block.statements = block.statements.map { it.accept(this) }.toMutableList() | ||||
|         return block | ||||
|     } | ||||
|  | ||||
|     fun visit(decl: VarDecl): Statement { | ||||
|         decl.value = decl.value?.accept(this) | ||||
|         decl.arraysize?.accept(this) | ||||
|         return decl | ||||
|     } | ||||
|  | ||||
|     fun visit(subroutine: Subroutine): Statement { | ||||
|         subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList() | ||||
|         return subroutine | ||||
|     } | ||||
|  | ||||
|     fun visit(functionCall: FunctionCall): Expression { | ||||
|         val newtarget = functionCall.target.accept(this) | ||||
|         if(newtarget is IdentifierReference) | ||||
|             functionCall.target = newtarget | ||||
|         else | ||||
|             throw FatalAstException("cannot change class of function call target") | ||||
|         functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList() | ||||
|         return functionCall | ||||
|     } | ||||
|  | ||||
|     fun visit(functionCallStatement: FunctionCallStatement): Statement { | ||||
|         val newtarget = functionCallStatement.target.accept(this) | ||||
|         if(newtarget is IdentifierReference) | ||||
|             functionCallStatement.target = newtarget | ||||
|         else | ||||
|             throw FatalAstException("cannot change class of function call target") | ||||
|         functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList() | ||||
|         return functionCallStatement | ||||
|     } | ||||
|  | ||||
|     fun visit(identifier: IdentifierReference): Expression { | ||||
|         // 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): Statement { | ||||
|         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): Statement { | ||||
|         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): Statement { | ||||
|         branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope | ||||
|         branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope | ||||
|         return branchStatement | ||||
|     } | ||||
|  | ||||
|     fun visit(range: RangeExpr): Expression { | ||||
|         range.from = range.from.accept(this) | ||||
|         range.to = range.to.accept(this) | ||||
|         range.step = range.step.accept(this) | ||||
|         return range | ||||
|     } | ||||
|  | ||||
|     fun visit(label: Label): Statement { | ||||
|         return label | ||||
|     } | ||||
|  | ||||
|     fun visit(literalValue: NumericLiteralValue): NumericLiteralValue { | ||||
|         return literalValue | ||||
|     } | ||||
|  | ||||
|     fun visit(stringLiteral: StringLiteralValue): Expression { | ||||
|         return stringLiteral | ||||
|     } | ||||
|  | ||||
|     fun visit(arrayLiteral: ArrayLiteralValue): Expression { | ||||
|         for(av in arrayLiteral.value.withIndex()) { | ||||
|             val newvalue = av.value.accept(this) | ||||
|             arrayLiteral.value[av.index] = newvalue | ||||
|         } | ||||
|         return arrayLiteral | ||||
|     } | ||||
|  | ||||
|     fun visit(assignment: Assignment): Statement { | ||||
|         assignment.target = assignment.target.accept(this) | ||||
|         assignment.value = assignment.value.accept(this) | ||||
|         return assignment | ||||
|     } | ||||
|  | ||||
|     fun visit(postIncrDecr: PostIncrDecr): Statement { | ||||
|         postIncrDecr.target = postIncrDecr.target.accept(this) | ||||
|         return postIncrDecr | ||||
|     } | ||||
|  | ||||
|     fun visit(contStmt: Continue): Statement { | ||||
|         return contStmt | ||||
|     } | ||||
|  | ||||
|     fun visit(breakStmt: Break): Statement { | ||||
|         return breakStmt | ||||
|     } | ||||
|  | ||||
|     fun visit(forLoop: ForLoop): Statement { | ||||
|         val newloopvar = forLoop.loopVar?.accept(this) | ||||
|         when(newloopvar) { | ||||
|             is IdentifierReference -> forLoop.loopVar = newloopvar | ||||
|             null -> forLoop.loopVar = null | ||||
|             else -> throw FatalAstException("can't change class of loopvar") | ||||
|         } | ||||
|         forLoop.iterable = forLoop.iterable.accept(this) | ||||
|         forLoop.body = forLoop.body.accept(this) as AnonymousScope | ||||
|         return forLoop | ||||
|     } | ||||
|  | ||||
|     fun visit(whileLoop: WhileLoop): Statement { | ||||
|         whileLoop.condition = whileLoop.condition.accept(this) | ||||
|         whileLoop.body = whileLoop.body.accept(this) as AnonymousScope | ||||
|         return whileLoop | ||||
|     } | ||||
|  | ||||
|     fun visit(repeatLoop: RepeatLoop): Statement { | ||||
|         repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this) | ||||
|         repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope | ||||
|         return repeatLoop | ||||
|     } | ||||
|  | ||||
|     fun visit(returnStmt: Return): Statement { | ||||
|         returnStmt.value = returnStmt.value?.accept(this) | ||||
|         return returnStmt | ||||
|     } | ||||
|  | ||||
|     fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression { | ||||
|         val ident = arrayIndexedExpression.identifier.accept(this) | ||||
|         if(ident is IdentifierReference) | ||||
|             arrayIndexedExpression.identifier = ident | ||||
|         arrayIndexedExpression.arrayspec.accept(this) | ||||
|         return arrayIndexedExpression | ||||
|     } | ||||
|  | ||||
|     fun visit(assignTarget: AssignTarget): AssignTarget { | ||||
|         val ident = assignTarget.identifier?.accept(this) | ||||
|         when (ident) { | ||||
|             is IdentifierReference -> assignTarget.identifier = ident | ||||
|             null -> assignTarget.identifier = null | ||||
|             else -> throw FatalAstException("can't change class of assign target identifier") | ||||
|         } | ||||
|         assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this) | ||||
|         assignTarget.memoryAddress?.let { visit(it) } | ||||
|         return assignTarget | ||||
|     } | ||||
|  | ||||
|     fun visit(scope: AnonymousScope): Statement { | ||||
|         scope.statements = scope.statements.map { it.accept(this) }.toMutableList() | ||||
|         return scope | ||||
|     } | ||||
|  | ||||
|     fun visit(typecast: TypecastExpression): Expression { | ||||
|         typecast.expression = typecast.expression.accept(this) | ||||
|         return typecast | ||||
|     } | ||||
|  | ||||
|     fun visit(memread: DirectMemoryRead): Expression { | ||||
|         memread.addressExpression = memread.addressExpression.accept(this) | ||||
|         return memread | ||||
|     } | ||||
|  | ||||
|     fun visit(memwrite: DirectMemoryWrite) { | ||||
|         memwrite.addressExpression = memwrite.addressExpression.accept(this) | ||||
|     } | ||||
|  | ||||
|     fun visit(addressOf: AddressOf): Expression { | ||||
|         val ident = addressOf.identifier.accept(this) | ||||
|         if(ident is IdentifierReference) | ||||
|             addressOf.identifier = ident | ||||
|         else | ||||
|             throw FatalAstException("can't change class of addressof identifier") | ||||
|         return addressOf | ||||
|     } | ||||
|  | ||||
|     fun visit(inlineAssembly: InlineAssembly): Statement { | ||||
|         return inlineAssembly | ||||
|     } | ||||
|  | ||||
|     fun visit(registerExpr: RegisterExpr): Expression { | ||||
|         return registerExpr | ||||
|     } | ||||
|  | ||||
|     fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement { | ||||
|         return builtinFunctionStatementPlaceholder | ||||
|     } | ||||
|  | ||||
|     fun visit(nopStatement: NopStatement): Statement { | ||||
|         return nopStatement | ||||
|     } | ||||
|  | ||||
|     fun visit(whenStatement: WhenStatement): Statement { | ||||
|         whenStatement.condition = whenStatement.condition.accept(this) | ||||
|         whenStatement.choices.forEach { it.accept(this) } | ||||
|         return whenStatement | ||||
|     } | ||||
|  | ||||
|     fun visit(whenChoice: WhenChoice) { | ||||
|         whenChoice.values = whenChoice.values?.map { it.accept(this) } | ||||
|         val stmt = whenChoice.statements.accept(this) | ||||
|         if(stmt is AnonymousScope) | ||||
|             whenChoice.statements = stmt | ||||
|         else { | ||||
|             whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position) | ||||
|             whenChoice.statements.linkParents(whenChoice) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun visit(structDecl: StructDecl): Statement { | ||||
|         structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList() | ||||
|         return structDecl | ||||
|     } | ||||
|  | ||||
|     fun visit(structLv: StructLiteralValue): Expression { | ||||
|         structLv.values = structLv.values.map { it.accept(this) } | ||||
|         return structLv | ||||
|     } | ||||
| } | ||||
| @@ -1,36 +0,0 @@ | ||||
| package prog8.ast.processing | ||||
|  | ||||
| import prog8.ast.Module | ||||
| import prog8.ast.base.SyntaxError | ||||
| import prog8.ast.base.printWarning | ||||
| import prog8.ast.statements.Directive | ||||
| import prog8.ast.statements.Statement | ||||
|  | ||||
| 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<Statement> = 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,237 +0,0 @@ | ||||
| package prog8.ast.processing | ||||
|  | ||||
| import prog8.ast.Module | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.DataType | ||||
| import prog8.ast.base.FatalAstException | ||||
| import prog8.ast.base.initvarsSubName | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.mangledStructMemberName | ||||
| import prog8.ast.statements.* | ||||
|  | ||||
|  | ||||
| private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> { | ||||
|     val identifier = structAssignment.target.identifier!! | ||||
|     val identifierName = identifier.nameInSource.single() | ||||
|     val targetVar = identifier.targetVarDecl(program.namespace)!! | ||||
|     val struct = targetVar.struct!! | ||||
|     when { | ||||
|         structAssignment.value is IdentifierReference -> { | ||||
|             val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!! | ||||
|             if (sourceVar.struct == null) | ||||
|                 throw FatalAstException("can only assign arrays or structs to structs") | ||||
|             // struct memberwise copy | ||||
|             val sourceStruct = sourceVar.struct!! | ||||
|             if(sourceStruct!==targetVar.struct) { | ||||
|                 // structs are not the same in assignment | ||||
|                 return listOf()     // error will be printed elsewhere | ||||
|             } | ||||
|             return struct.statements.zip(sourceStruct.statements).map { member -> | ||||
|                 val targetDecl = member.first as VarDecl | ||||
|                 val sourceDecl = member.second as VarDecl | ||||
|                 if(targetDecl.name != sourceDecl.name) | ||||
|                     throw FatalAstException("struct member mismatch") | ||||
|                 val mangled = mangledStructMemberName(identifierName, targetDecl.name) | ||||
|                 val idref = IdentifierReference(listOf(mangled), structAssignment.position) | ||||
|                 val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name) | ||||
|                 val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position) | ||||
|                 val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position), | ||||
|                         null, sourceIdref, member.second.position) | ||||
|                 assign.linkParents(structAssignment) | ||||
|                 assign | ||||
|             } | ||||
|         } | ||||
|         structAssignment.value is StructLiteralValue -> { | ||||
|             throw IllegalArgumentException("not going to flatten a structLv assignment here") | ||||
|         } | ||||
|         else -> throw FatalAstException("strange struct value") | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| 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. | ||||
|  | ||||
|     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): Statement { | ||||
|  | ||||
|         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): Statement { | ||||
|         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(assignment: Assignment): Statement { | ||||
|         val assg = super.visit(assignment) | ||||
|         if(assg !is Assignment) | ||||
|             return assg | ||||
|  | ||||
|         // see if a typecast is needed to convert the value's type into the proper target type | ||||
|         val valueItype = assg.value.inferType(program) | ||||
|         val targetItype = assg.target.inferType(program, assg) | ||||
|  | ||||
|         if(targetItype.isKnown && valueItype.isKnown) { | ||||
|             val targettype = targetItype.typeOrElse(DataType.STRUCT) | ||||
|             val valuetype = valueItype.typeOrElse(DataType.STRUCT) | ||||
|  | ||||
|             // struct assignments will be flattened (if it's not a struct literal) | ||||
|             if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) { | ||||
|                 if (assg.value is StructLiteralValue) | ||||
|                     return assg  // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed) | ||||
|  | ||||
|                 val assignments = flattenStructAssignmentFromIdentifier(assg, program)    //   'structvar1 = structvar2' | ||||
|                 return if (assignments.isEmpty()) { | ||||
|                     // something went wrong (probably incompatible struct types) | ||||
|                     // we'll get an error later from the AstChecker | ||||
|                     assg | ||||
|                 } else { | ||||
|                     val scope = AnonymousScope(assignments.toMutableList(), assg.position) | ||||
|                     scope.linkParents(assg.parent) | ||||
|                     scope | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if(assg.aug_op!=null) { | ||||
|             // transform augmented assg into normal assg so we have one case less to deal with later | ||||
|             val newTarget: Expression = | ||||
|                     when { | ||||
|                         assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position) | ||||
|                         assg.target.identifier != null -> assg.target.identifier!! | ||||
|                         assg.target.arrayindexed != null -> assg.target.arrayindexed!! | ||||
|                         assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position) | ||||
|                         else -> throw FatalAstException("strange assg") | ||||
|                     } | ||||
|  | ||||
|             val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position) | ||||
|             expression.linkParents(assg.parent) | ||||
|             val convertedAssignment = Assignment(assg.target, null, expression, assg.position) | ||||
|             convertedAssignment.linkParents(assg.parent) | ||||
|             return super.visit(convertedAssignment) | ||||
|         } | ||||
|  | ||||
|         return assg | ||||
|     } | ||||
| } | ||||
| @@ -1,198 +0,0 @@ | ||||
| package prog8.ast.processing | ||||
|  | ||||
| import prog8.ast.IFunctionCall | ||||
| import prog8.ast.INameScope | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.DataType | ||||
| import prog8.ast.base.FatalAstException | ||||
| import prog8.ast.base.printWarning | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.statements.* | ||||
| import prog8.functions.BuiltinFunctions | ||||
|  | ||||
|  | ||||
| internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor { | ||||
|     // 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 visit(expr: BinaryExpression): Expression { | ||||
|         val expr2 = super.visit(expr) | ||||
|         if(expr2 !is BinaryExpression) | ||||
|             return expr2 | ||||
|         val leftDt = expr2.left.inferType(program) | ||||
|         val rightDt = expr2.right.inferType(program) | ||||
|         if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) { | ||||
|             // determine common datatype and add typecast as required to make left and right equal types | ||||
|             val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr2.left, expr2.right) | ||||
|             if(toFix!=null) { | ||||
|                 when { | ||||
|                     toFix===expr2.left -> { | ||||
|                         expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position) | ||||
|                         expr2.left.linkParents(expr2) | ||||
|                     } | ||||
|                     toFix===expr2.right -> { | ||||
|                         expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position) | ||||
|                         expr2.right.linkParents(expr2) | ||||
|                     } | ||||
|                     else -> throw FatalAstException("confused binary expression side") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return expr2 | ||||
|     } | ||||
|  | ||||
|     override fun visit(assignment: Assignment): Statement { | ||||
|         val assg = super.visit(assignment) | ||||
|         if(assg !is Assignment) | ||||
|             return assg | ||||
|  | ||||
|         // see if a typecast is needed to convert the value's type into the proper target type | ||||
|         val valueItype = assg.value.inferType(program) | ||||
|         val targetItype = assg.target.inferType(program, assg) | ||||
|  | ||||
|         if(targetItype.isKnown && valueItype.isKnown) { | ||||
|             val targettype = targetItype.typeOrElse(DataType.STRUCT) | ||||
|             val valuetype = valueItype.typeOrElse(DataType.STRUCT) | ||||
|             if (valuetype != targettype) { | ||||
|                 if (valuetype isAssignableTo targettype) { | ||||
|                     assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position) | ||||
|                     assg.value.linkParents(assg) | ||||
|                 } | ||||
|                 // if they're not assignable, we'll get a proper error later from the AstChecker | ||||
|             } | ||||
|         } | ||||
|         return assg | ||||
|     } | ||||
|  | ||||
|     override fun visit(functionCallStatement: FunctionCallStatement): Statement { | ||||
|         checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope()) | ||||
|         return super.visit(functionCallStatement) | ||||
|     } | ||||
|  | ||||
|     override fun visit(functionCall: FunctionCall): Expression { | ||||
|         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 argItype = arg.second.value.inferType(program) | ||||
|                     if(argItype.isKnown) { | ||||
|                         val argtype = argItype.typeOrElse(DataType.STRUCT) | ||||
|                         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 -> { | ||||
|                 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 argItype = arg.second.value.inferType(program) | ||||
|                         if (argItype.isKnown) { | ||||
|                             val argtype = argItype.typeOrElse(DataType.STRUCT) | ||||
|                             if (arg.first.possibleDatatypes.any { argtype == it }) | ||||
|                                 continue | ||||
|                             for (possibleType in arg.first.possibleDatatypes) { | ||||
|                                 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 -> throw FatalAstException("call to something weird $sub   ${call.target}") | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun visit(typecast: TypecastExpression): Expression { | ||||
|         // 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(memread: DirectMemoryRead): Expression { | ||||
|         // make sure the memory address is an uword | ||||
|         val dt = memread.addressExpression.inferType(program) | ||||
|         if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) { | ||||
|             val literaladdr = memread.addressExpression as? NumericLiteralValue | ||||
|             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.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) { | ||||
|             val literaladdr = memwrite.addressExpression as? NumericLiteralValue | ||||
|             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) | ||||
|     } | ||||
|  | ||||
|     override fun visit(structLv: StructLiteralValue): Expression { | ||||
|         val litval = super.visit(structLv) | ||||
|         if(litval !is StructLiteralValue) | ||||
|             return litval | ||||
|  | ||||
|         val decl = litval.parent as? VarDecl | ||||
|         if(decl != null) { | ||||
|             val struct = decl.struct | ||||
|             if(struct != null) { | ||||
|                 addTypecastsIfNeeded(litval, struct) | ||||
|             } | ||||
|         } else { | ||||
|             val assign = litval.parent as? Assignment | ||||
|             if (assign != null) { | ||||
|                 val decl2 = assign.target.identifier?.targetVarDecl(program.namespace) | ||||
|                 if(decl2 != null) { | ||||
|                     val struct = decl2.struct | ||||
|                     if(struct != null) { | ||||
|                         addTypecastsIfNeeded(litval, struct) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return litval | ||||
|     } | ||||
|  | ||||
|     private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) { | ||||
|         structLv.values = struct.statements.zip(structLv.values).map { | ||||
|             val memberDt = (it.first as VarDecl).datatype | ||||
|             val valueDt = it.second.inferType(program) | ||||
|             if (valueDt.typeOrElse(memberDt) != memberDt) | ||||
|                 TypecastExpression(it.second, memberDt, true, it.second.position) | ||||
|             else | ||||
|                 it.second | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,157 +0,0 @@ | ||||
| package prog8.ast.processing | ||||
|  | ||||
| import prog8.ast.INameScope | ||||
| import prog8.ast.Module | ||||
| import prog8.ast.Node | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.expressions.* | ||||
| import prog8.ast.statements.* | ||||
| import prog8.compiler.CompilerException | ||||
| import prog8.functions.BuiltinFunctions | ||||
| import prog8.functions.FunctionSignature | ||||
|  | ||||
|  | ||||
| internal class VarInitValueAndAddressOfCreator(private val program: Program): 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, MutableList<VarDecl>>() | ||||
|  | ||||
|     override fun visit(module: Module) { | ||||
|         vardeclsToAdd.clear() | ||||
|         super.visit(module) | ||||
|         // add any new vardecls to the various scopes | ||||
|         for((where, decls) in vardeclsToAdd) { | ||||
|             where.statements.addAll(0, decls) | ||||
|             decls.forEach { it.linkParents(where as Node) } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun visit(decl: VarDecl): Statement { | ||||
|         super.visit(decl) | ||||
|  | ||||
|         if(decl.isArray && decl.value==null) { | ||||
|             // array datatype without initialization value, add list of zeros | ||||
|             val arraysize = decl.arraysize!!.size()!! | ||||
|             val array = ArrayLiteralValue(decl.datatype, | ||||
|                     Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) }, | ||||
|                     null, decl.position) | ||||
|             array.addToHeap(program.heap) | ||||
|             decl.value = array | ||||
|         } | ||||
|  | ||||
|         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 NumericLiteralValue) | ||||
|                         declvalue.cast(decl.datatype) | ||||
|                     else | ||||
|                         declvalue | ||||
|             val identifierName = listOf(decl.name)    // 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): Expression { | ||||
|         var parentStatement: Node = functionCall | ||||
|         while(parentStatement !is Statement) | ||||
|             parentStatement = parentStatement.parent | ||||
|         val targetStatement = functionCall.target.targetSubroutine(program.namespace) | ||||
|         if(targetStatement!=null) { | ||||
|             addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement) | ||||
|         } else { | ||||
|             val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")] | ||||
|             if(builtinFunc!=null) | ||||
|                 addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement) | ||||
|         } | ||||
|         return functionCall | ||||
|     } | ||||
|  | ||||
|     override fun visit(functionCallStatement: FunctionCallStatement): Statement { | ||||
|         val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace) | ||||
|         if(targetStatement!=null) { | ||||
|             addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement) | ||||
|         } else { | ||||
|             val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")] | ||||
|             if(builtinFunc!=null) | ||||
|                 addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement) | ||||
|         } | ||||
|         return functionCallStatement | ||||
|     } | ||||
|  | ||||
|     private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) { | ||||
|         // 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? StringLiteralValue | ||||
|                 if(idref!=null) { | ||||
|                     val variable = idref.targetVarDecl(program.namespace) | ||||
|                     if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) { | ||||
|                         val pointerExpr = AddressOf(idref, idref.position) | ||||
|                         pointerExpr.linkParents(arglist[argparam.first.index].parent) | ||||
|                         arglist[argparam.first.index] = pointerExpr | ||||
|                     } | ||||
|                 } | ||||
|                 else if(strvalue!=null) { | ||||
|                     // add a vardecl so that the autovar can be resolved in later lookups | ||||
|                     val variable = VarDecl.createAuto(strvalue) | ||||
|                     addVarDecl(strvalue.definingScope(), variable) | ||||
|                     // replace the argument with &autovar | ||||
|                     val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position) | ||||
|                     val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) | ||||
|                     pointerExpr.linkParents(arglist[argparam.first.index].parent) | ||||
|                     arglist[argparam.first.index] = pointerExpr | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) { | ||||
|         // val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD | ||||
|         for(arg in args.withIndex().zip(signature.parameters)) { | ||||
|             val argvalue = arg.first.value | ||||
|             val argDt = argvalue.inferType(program) | ||||
|             if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) { | ||||
|                 if(argvalue !is IdentifierReference) | ||||
|                     throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue") | ||||
|                 val addrOf = AddressOf(argvalue, argvalue.position) | ||||
|                 args[arg.first.index] = addrOf | ||||
|                 addrOf.linkParents(parent) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private fun addVarDecl(scope: INameScope, variable: VarDecl) { | ||||
|         if(scope !in vardeclsToAdd) | ||||
|             vardeclsToAdd[scope] = mutableListOf() | ||||
|         val declList = vardeclsToAdd.getValue(scope) | ||||
|         if(declList.all{it.name!=variable.name}) | ||||
|             declList.add(variable) | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,797 +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 | ||||
|  | ||||
|  | ||||
| sealed class Statement : Node { | ||||
|     abstract fun accept(visitor: IAstModifyingVisitor) : Statement | ||||
|     abstract 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(".") | ||||
|     } | ||||
|  | ||||
|     abstract val expensiveToInline: Boolean | ||||
|  | ||||
|     fun definingBlock(): Block { | ||||
|         if(this is Block) | ||||
|             return this | ||||
|         return findParentNode<Block>(this)!! | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() { | ||||
|     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<Statement>, | ||||
|             val isInLibrary: Boolean, | ||||
|             override val position: Position) : Statement(), 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) : Statement() { | ||||
|     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) : Statement() { | ||||
|     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)" | ||||
|     } | ||||
| } | ||||
|  | ||||
| open class Return(var value: Expression?, override val position: Position) : Statement() { | ||||
|     override lateinit var parent: Node | ||||
|     override val expensiveToInline = value!=null && value !is NumericLiteralValue | ||||
|  | ||||
|     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) : Statement() { | ||||
|     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) : Statement() { | ||||
|     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) | ||||
| } | ||||
|  | ||||
|  | ||||
| enum class ZeropageWish { | ||||
|     REQUIRE_ZEROPAGE, | ||||
|     PREFER_ZEROPAGE, | ||||
|     DONTCARE, | ||||
|     NOT_IN_ZEROPAGE | ||||
| } | ||||
|  | ||||
| class VarDecl(val type: VarDeclType, | ||||
|               private val declaredDatatype: DataType, | ||||
|               val zeropage: ZeropageWish, | ||||
|               var arraysize: ArrayIndex?, | ||||
|               val name: String, | ||||
|               private val structName: String?, | ||||
|               var value: Expression?, | ||||
|               val isArray: Boolean, | ||||
|               val autogeneratedDontRemove: Boolean, | ||||
|               override val position: Position) : Statement() { | ||||
|     override lateinit var parent: Node | ||||
|     var struct: StructDecl? = null        // set later (because at parse time, we only know the name) | ||||
|         private set | ||||
|     var structHasBeenFlattened = false      // set later | ||||
|         private set | ||||
|  | ||||
|     override val expensiveToInline | ||||
|             get() = value!=null && value !is NumericLiteralValue | ||||
|  | ||||
|     // prefix for literal values that are turned into a variable on the heap | ||||
|  | ||||
|     companion object { | ||||
|         private var autoHeapValueSequenceNumber = 0 | ||||
|  | ||||
|         fun createAuto(string: StringLiteralValue): VarDecl { | ||||
|             if(string.heapId==null) | ||||
|                 throw FatalAstException("can only create autovar for a string that has a heapid  $string") | ||||
|             val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}" | ||||
|             return VarDecl(VarDeclType.VAR, string.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string, | ||||
|                         isArray = false, autogeneratedDontRemove = true, position = string.position) | ||||
|         } | ||||
|  | ||||
|         fun createAuto(array: ArrayLiteralValue): VarDecl { | ||||
|             if(array.heapId==null) | ||||
|                 throw FatalAstException("can only create autovar for an array that has a heapid  $array") | ||||
|  | ||||
|             val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}" | ||||
|             val declaredType = ArrayElementTypes.getValue(array.type) | ||||
|             val arraysize = ArrayIndex.forArray(array) | ||||
|             return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array, | ||||
|                     isArray = true, autogeneratedDontRemove = true, position = array.position) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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.ARRAY_UB | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|     override fun linkParents(parent: Node) { | ||||
|         this.parent = parent | ||||
|         arraysize?.linkParents(this) | ||||
|         value?.linkParents(this) | ||||
|         if(structName!=null) { | ||||
|             val structStmt = definingScope().lookup(listOf(structName), this) | ||||
|             if(structStmt!=null) | ||||
|                 struct = definingScope().lookup(listOf(structName), this) as StructDecl | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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, struct=$structName, value=$value, pos=$position)" | ||||
|     } | ||||
|  | ||||
|     fun asDefaultValueDecl(parent: Node?): VarDecl { | ||||
|         val constValue = when(declaredDatatype) { | ||||
|             DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0,  position) | ||||
|             DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0,  position) | ||||
|             DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position) | ||||
|             DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position) | ||||
|             DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position) | ||||
|             else -> throw FatalAstException("can only set a default value for a numeric type") | ||||
|         } | ||||
|         val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position) | ||||
|         if(parent!=null) | ||||
|             decl.linkParents(parent) | ||||
|         return decl | ||||
|     } | ||||
|  | ||||
|     fun flattenStructMembers(): MutableList<Statement> { | ||||
|         val result = struct!!.statements.withIndex().map { | ||||
|             val member = it.value as VarDecl | ||||
|             val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null | ||||
|             VarDecl( | ||||
|                     VarDeclType.VAR, | ||||
|                     member.datatype, | ||||
|                     ZeropageWish.NOT_IN_ZEROPAGE, | ||||
|                     member.arraysize, | ||||
|                     mangledStructMemberName(name, member.name), | ||||
|                     struct!!.name, | ||||
|                     initvalue, | ||||
|                     member.isArray, | ||||
|                     true, | ||||
|                     member.position | ||||
|             ) as Statement | ||||
|         }.toMutableList() | ||||
|         structHasBeenFlattened = true | ||||
|         return result | ||||
|     } | ||||
| } | ||||
|  | ||||
| class ArrayIndex(var index: Expression, 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: ArrayLiteralValue): ArrayIndex { | ||||
|             return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, 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? NumericLiteralValue)?.number?.toInt() | ||||
| } | ||||
|  | ||||
| open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() { | ||||
|     override lateinit var parent: Node | ||||
|     override val expensiveToInline | ||||
|             get() = value !is NumericLiteralValue | ||||
|  | ||||
|     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: Expression, position: Position) | ||||
|     : Assignment(target, aug_op, value, position) | ||||
|  | ||||
| data class AssignTarget(val register: Register?, | ||||
|                         var identifier: IdentifierReference?, | ||||
|                         var arrayindexed: ArrayIndexedExpression?, | ||||
|                         val 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: Expression): 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: Statement): InferredTypes.InferredType { | ||||
|         if(register!=null) | ||||
|             return InferredTypes.knownFor(DataType.UBYTE) | ||||
|  | ||||
|         if(identifier!=null) { | ||||
|             val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown() | ||||
|             if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype) | ||||
|         } | ||||
|  | ||||
|         if(arrayindexed!=null) { | ||||
|             return arrayindexed!!.inferType(program) | ||||
|         } | ||||
|  | ||||
|         if(memoryAddress!=null) | ||||
|             return InferredTypes.knownFor(DataType.UBYTE) | ||||
|  | ||||
|         return InferredTypes.unknown() | ||||
|     } | ||||
|  | ||||
|     infix fun isSameAs(value: Expression): 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) : Statement() { | ||||
|     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) : Statement() { | ||||
|     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<Expression>, | ||||
|                             override val position: Position) : Statement(), IFunctionCall { | ||||
|     override lateinit var parent: Node | ||||
|     override val expensiveToInline | ||||
|             get() = arglist.any { it !is NumericLiteralValue } | ||||
|  | ||||
|     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) : Statement() { | ||||
|     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<Statement>, | ||||
|                      override val position: Position) : INameScope, Statement() { | ||||
|     override val name: String | ||||
|     override lateinit var parent: Node | ||||
|     override val expensiveToInline | ||||
|         get() = statements.any { it.expensiveToInline } | ||||
|  | ||||
|     companion object { | ||||
|         private var sequenceNumber = 1 | ||||
|     } | ||||
|  | ||||
|     init { | ||||
|         name = "<anon-$sequenceNumber>"     // make sure it's an invalid soruce code identifier so user source code can never produce it | ||||
|         sequenceNumber++ | ||||
|     } | ||||
|  | ||||
|     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): Statement() { | ||||
|     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: Statement): 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<Statement>, | ||||
|                  override val position: Position) : Statement(), 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 } | ||||
| } | ||||
|  | ||||
| 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: Expression, | ||||
|                   var truepart: AnonymousScope, | ||||
|                   var elsepart: AnonymousScope, | ||||
|                   override val position: Position) : Statement() { | ||||
|     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) : Statement() { | ||||
|     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: ZeropageWish, | ||||
|               var loopVar: IdentifierReference?, | ||||
|               var iterable: Expression, | ||||
|               var body: AnonymousScope, | ||||
|               override val position: Position) : Statement() { | ||||
|     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)" | ||||
|     } | ||||
| } | ||||
|  | ||||
| class WhileLoop(var condition: Expression, | ||||
|                 var body: AnonymousScope, | ||||
|                 override val position: Position) : Statement() { | ||||
|     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: Expression, | ||||
|                  override val position: Position) : Statement() { | ||||
|     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(var condition: Expression, | ||||
|                     var choices: MutableList<WhenChoice>, | ||||
|                     override val position: Position): Statement() { | ||||
|     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)?.number?.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(var values: List<Expression>?,           // if null,  this is the 'else' part | ||||
|                  var 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 StructDecl(override val name: String, | ||||
|                  override var statements: MutableList<Statement>,      // actually, only vardecls here | ||||
|                  override val position: Position): Statement(), INameScope { | ||||
|  | ||||
|     override lateinit var parent: Node | ||||
|     override val expensiveToInline: Boolean = true | ||||
|  | ||||
|     override fun linkParents(parent: Node) { | ||||
|         this.parent = parent | ||||
|         this.statements.forEach { it.linkParents(this) } | ||||
|     } | ||||
|  | ||||
|     val numberOfElements: Int | ||||
|         get() = this.statements.size | ||||
|  | ||||
|     override fun accept(visitor: IAstVisitor) = visitor.visit(this) | ||||
|     override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) | ||||
|  | ||||
|     fun nameOfFirstMember() = (statements.first() as VarDecl).name | ||||
| } | ||||
|  | ||||
| class DirectMemoryWrite(var addressExpression: Expression, 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 | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,14 +1,30 @@ | ||||
| package prog8.compiler | ||||
|  | ||||
| import prog8.ast.base.ArrayDatatypes | ||||
| import prog8.ast.base.DataType | ||||
| import prog8.ast.base.StringDatatypes | ||||
| import prog8.ast.expressions.AddressOf | ||||
| import prog8.ast.AstToSourceCode | ||||
| import prog8.ast.IBuiltinFunctions | ||||
| import prog8.ast.IMemSizer | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.AstException | ||||
| import prog8.ast.base.Position | ||||
| import prog8.ast.expressions.Expression | ||||
| import prog8.ast.expressions.NumericLiteralValue | ||||
| import prog8.ast.statements.Directive | ||||
| import prog8.compiler.astprocessing.* | ||||
| import prog8.compiler.functions.* | ||||
| import prog8.compiler.target.C64Target | ||||
| import prog8.compiler.target.Cx16Target | ||||
| import prog8.compiler.target.ICompilationTarget | ||||
| import prog8.compiler.target.asmGeneratorFor | ||||
| import prog8.optimizer.* | ||||
| import prog8.parser.ModuleImporter | ||||
| import prog8.parser.ParsingFailedError | ||||
| import prog8.parser.moduleName | ||||
| import java.io.File | ||||
| import java.io.InputStream | ||||
| import java.nio.file.Path | ||||
| import java.util.* | ||||
| import kotlin.math.abs | ||||
| import kotlin.system.exitProcess | ||||
| import kotlin.system.measureTimeMillis | ||||
|  | ||||
|  | ||||
| enum class OutputType { | ||||
|     RAW, | ||||
| @@ -28,33 +44,304 @@ enum class ZeropageType { | ||||
|     DONTUSE | ||||
| } | ||||
|  | ||||
| data class IntegerOrAddressOf(val integer: Int?, val addressOf: AddressOf?) | ||||
|  | ||||
| data class CompilationOptions(val output: OutputType, | ||||
|                               val launcher: LauncherType, | ||||
|                               val zeropage: ZeropageType, | ||||
|                               val zpReserved: List<IntRange>, | ||||
|                               val floats: Boolean) | ||||
|                               val floats: Boolean, | ||||
|                               val noSysInit: Boolean, | ||||
|                               val compTarget: ICompilationTarget) { | ||||
|     var slowCodegenWarnings = false | ||||
|     var optimize = false | ||||
| } | ||||
|  | ||||
|  | ||||
| class CompilerException(message: String?) : Exception(message) | ||||
|  | ||||
| fun Number.toHex(): String { | ||||
|     //  0..15 -> "0".."15" | ||||
|     //  16..255 -> "$10".."$ff" | ||||
|     //  256..65536 -> "$0100".."$ffff" | ||||
|     // negative values are prefixed with '-'. | ||||
|     val integer = this.toInt() | ||||
|     if(integer<0) | ||||
|         return '-' + abs(integer).toHex() | ||||
|     return when (integer) { | ||||
|         in 0 until 16 -> integer.toString() | ||||
|         in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0') | ||||
|         in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0') | ||||
|         else -> throw CompilerException("number too large for 16 bits $this") | ||||
| class CompilationResult(val success: Boolean, | ||||
|                         val programAst: Program, | ||||
|                         val programName: String, | ||||
|                         val compTarget: ICompilationTarget, | ||||
|                         val importedFiles: List<Path>) | ||||
|  | ||||
|  | ||||
| fun compileProgram(filepath: Path, | ||||
|                    optimize: Boolean, | ||||
|                    writeAssembly: Boolean, | ||||
|                    slowCodegenWarnings: Boolean, | ||||
|                    compilationTarget: String, | ||||
|                    libdirs: List<String>, | ||||
|                    outputDir: Path): CompilationResult { | ||||
|     var programName = "" | ||||
|     lateinit var programAst: Program | ||||
|     lateinit var importedFiles: List<Path> | ||||
|     val errors = ErrorReporter() | ||||
|  | ||||
|     val compTarget = | ||||
|         when(compilationTarget) { | ||||
|             C64Target.name -> C64Target | ||||
|             Cx16Target.name -> Cx16Target | ||||
|             else -> { | ||||
|                 System.err.println("invalid compilation target") | ||||
|                 exitProcess(1) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     try { | ||||
|         val totalTime = measureTimeMillis { | ||||
|             // import main module and everything it needs | ||||
|             val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget, libdirs) | ||||
|             compilationOptions.slowCodegenWarnings = slowCodegenWarnings | ||||
|             compilationOptions.optimize = optimize | ||||
|             programAst = ast | ||||
|             importedFiles = imported | ||||
|             processAst(programAst, errors, compilationOptions) | ||||
|             if (compilationOptions.optimize) | ||||
|                 optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions) | ||||
|             postprocessAst(programAst, errors, compilationOptions) | ||||
|  | ||||
|             // printAst(programAst) | ||||
|  | ||||
|             if(writeAssembly) | ||||
|                 programName = writeAssembly(programAst, errors, outputDir, compilationOptions) | ||||
|         } | ||||
|         System.out.flush() | ||||
|         System.err.flush() | ||||
|         println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") | ||||
|         return CompilationResult(true, programAst, programName, compTarget, importedFiles) | ||||
|  | ||||
|     } catch (px: ParsingFailedError) { | ||||
|         System.err.print("\u001b[91m")  // bright red | ||||
|         System.err.println(px.message) | ||||
|         System.err.print("\u001b[0m")  // reset | ||||
|     } catch (ax: AstException) { | ||||
|         System.err.print("\u001b[91m")  // bright red | ||||
|         System.err.println(ax.toString()) | ||||
|         System.err.print("\u001b[0m")  // reset | ||||
|     } catch (x: Exception) { | ||||
|         print("\u001b[91m")  // bright red | ||||
|         println("\n* internal error *") | ||||
|         print("\u001b[0m")  // reset | ||||
|         System.out.flush() | ||||
|         throw x | ||||
|     } catch (x: NotImplementedError) { | ||||
|         print("\u001b[91m")  // bright red | ||||
|         println("\n* internal error: missing feature/code *") | ||||
|         print("\u001b[0m")  // reset | ||||
|         System.out.flush() | ||||
|         throw x | ||||
|     } | ||||
|  | ||||
|     val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget) | ||||
|     return CompilationResult(false, failedProgram, programName, compTarget, emptyList()) | ||||
| } | ||||
|  | ||||
| private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions { | ||||
|     lateinit var program: Program | ||||
|  | ||||
|     override val names = functions.keys | ||||
|     override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet() | ||||
|  | ||||
|     override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? { | ||||
|         val func = BuiltinFunctions[name] | ||||
|         if(func!=null) { | ||||
|             val exprfunc = func.constExpressionFunc | ||||
|             if(exprfunc!=null) { | ||||
|                 return try { | ||||
|                     exprfunc(args, position, program, memsizer) | ||||
|                 } catch(x: NotConstArgumentException) { | ||||
|                     // const-evaluating the builtin function call failed. | ||||
|                     null | ||||
|                 } catch(x: CannotEvaluateException) { | ||||
|                     // const-evaluating the builtin function call failed. | ||||
|                     null | ||||
|                 } | ||||
|             } | ||||
|             else if(func.known_returntype==null) | ||||
|                 return null  // builtin function $name can't be used here because it doesn't return a value | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
|     override fun returnType(name: String, args: MutableList<Expression>) = | ||||
|         builtinFunctionReturnType(name, args, program) | ||||
| } | ||||
|  | ||||
| private fun parseImports(filepath: Path, | ||||
|                          errors: IErrorReporter, | ||||
|                          compTarget: ICompilationTarget, | ||||
|                          libdirs: List<String>): Triple<Program, CompilationOptions, List<Path>> { | ||||
|     val compilationTargetName = compTarget.name | ||||
|     println("Compiler target: $compilationTargetName. Parsing...") | ||||
|     val bf = BuiltinFunctionsFacade(BuiltinFunctions) | ||||
|     val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget) | ||||
|     bf.program = programAst | ||||
|  | ||||
|     val importer = ModuleImporter(programAst, compTarget, compilationTargetName, libdirs) | ||||
|     importer.importModule(filepath) | ||||
|     errors.report() | ||||
|  | ||||
|     val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source } | ||||
|     val compilerOptions = determineCompilationOptions(programAst, compTarget) | ||||
|     if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) | ||||
|         throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.") | ||||
|  | ||||
|     // depending on the machine and compiler options we may have to include some libraries | ||||
|     for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName)) | ||||
|         importer.importLibraryModule(lib) | ||||
|  | ||||
|     // always import prog8_lib and math | ||||
|     importer.importLibraryModule("math") | ||||
|     importer.importLibraryModule("prog8_lib") | ||||
|     errors.report() | ||||
|     return Triple(programAst, compilerOptions, importedFiles) | ||||
| } | ||||
|  | ||||
| private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions { | ||||
|     val mainModule = program.mainModule | ||||
|     val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" } | ||||
|             as? Directive)?.args?.single()?.name?.uppercase() | ||||
|     val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } | ||||
|             as? Directive)?.args?.single()?.name?.uppercase() | ||||
|     val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" } | ||||
|             as? Directive)?.args?.single()?.name?.uppercase() | ||||
|     val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" } | ||||
|         .flatMap { (it as Directive).args }.toSet() | ||||
|     val floatsEnabled = allOptions.any { it.name == "enable_floats" } | ||||
|     val noSysInit = allOptions.any { it.name == "no_sysinit" } | ||||
|     var zpType: ZeropageType = | ||||
|         if (zpoption == null) | ||||
|             if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE | ||||
|         else | ||||
|             try { | ||||
|                 ZeropageType.valueOf(zpoption) | ||||
|             } catch (x: IllegalArgumentException) { | ||||
|                 ZeropageType.KERNALSAFE | ||||
|                 // error will be printed by the astchecker | ||||
|             } | ||||
|  | ||||
|     if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) { | ||||
|         System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target") | ||||
|         zpType = ZeropageType.BASICSAFE | ||||
|     } | ||||
|  | ||||
|     val zpReserved = mainModule.statements | ||||
|         .asSequence() | ||||
|         .filter { it is Directive && it.directive == "%zpreserved" } | ||||
|         .map { (it as Directive).args } | ||||
|         .map { it[0].int!!..it[1].int!! } | ||||
|         .toList() | ||||
|  | ||||
|     if (outputType != null && !OutputType.values().any { it.name == outputType }) { | ||||
|         System.err.println("invalid output type $outputType") | ||||
|         exitProcess(1) | ||||
|     } | ||||
|     if (launcherType != null && !LauncherType.values().any { it.name == launcherType }) { | ||||
|         System.err.println("invalid launcher type $launcherType") | ||||
|         exitProcess(1) | ||||
|     } | ||||
|  | ||||
|     return CompilationOptions( | ||||
|         if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), | ||||
|         if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), | ||||
|         zpType, zpReserved, floatsEnabled, noSysInit, | ||||
|         compTarget | ||||
|     ) | ||||
| } | ||||
|  | ||||
| private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { | ||||
|     // perform initial syntax checks and processings | ||||
|     println("Processing for target ${compilerOptions.compTarget.name}...") | ||||
|     programAst.checkIdentifiers(errors, compilerOptions) | ||||
|     errors.report() | ||||
|     programAst.constantFold(errors, compilerOptions.compTarget) | ||||
|     errors.report() | ||||
|     programAst.reorderStatements(errors) | ||||
|     errors.report() | ||||
|     programAst.addTypecasts(errors) | ||||
|     errors.report() | ||||
|     programAst.variousCleanups(programAst, errors) | ||||
|     errors.report() | ||||
|     programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) | ||||
|     errors.report() | ||||
|     programAst.checkIdentifiers(errors, compilerOptions) | ||||
|     errors.report() | ||||
| } | ||||
|  | ||||
| private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) { | ||||
|     // optimize the parse tree | ||||
|     println("Optimizing...") | ||||
|  | ||||
|     val remover = UnusedCodeRemover(programAst, errors, compTarget) | ||||
|     remover.visit(programAst) | ||||
|     remover.applyModifications() | ||||
|  | ||||
|     while (true) { | ||||
|         // keep optimizing expressions and statements until no more steps remain | ||||
|         val optsDone1 = programAst.simplifyExpressions() | ||||
|         val optsDone2 = programAst.splitBinaryExpressions(compTarget) | ||||
|         val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget) | ||||
|         programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away | ||||
|         errors.report() | ||||
|         if (optsDone1 + optsDone2 + optsDone3 == 0) | ||||
|             break | ||||
|     } | ||||
|  | ||||
|     val inliner = SubroutineInliner(programAst, errors, options) | ||||
|     inliner.visit(programAst) | ||||
|     errors.report() | ||||
|     if(errors.noErrors()) { | ||||
|         inliner.applyModifications() | ||||
|         inliner.fixCallsToInlinedSubroutines() | ||||
|         val remover2 = UnusedCodeRemover(programAst, errors, compTarget) | ||||
|         remover2.visit(programAst) | ||||
|         remover2.applyModifications() | ||||
|     } | ||||
|  | ||||
|     errors.report() | ||||
| } | ||||
|  | ||||
| private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { | ||||
|     programAst.addTypecasts(errors) | ||||
|     errors.report() | ||||
|     programAst.variousCleanups(programAst, errors) | ||||
|     programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)          // check if final tree is still valid | ||||
|     errors.report() | ||||
|     val callGraph = CallGraph(programAst) | ||||
|     callGraph.checkRecursiveCalls(errors) | ||||
|     errors.report() | ||||
|     programAst.verifyFunctionArgTypes() | ||||
|     programAst.moveMainAndStartToFirst() | ||||
| } | ||||
|  | ||||
| private fun writeAssembly(programAst: Program, | ||||
|                           errors: IErrorReporter, | ||||
|                           outputDir: Path, | ||||
|                           compilerOptions: CompilationOptions): String { | ||||
|     // asm generation directly from the Ast | ||||
|     programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget) | ||||
|     errors.report() | ||||
|  | ||||
|     // printAst(programAst) | ||||
|  | ||||
|     compilerOptions.compTarget.machine.initializeZeropage(compilerOptions) | ||||
|     val assembly = asmGeneratorFor(compilerOptions.compTarget, | ||||
|             programAst, | ||||
|             errors, | ||||
|             compilerOptions.compTarget.machine.zeropage, | ||||
|             compilerOptions, | ||||
|             outputDir).compileToAssembly() | ||||
|     assembly.assemble(compilerOptions) | ||||
|     errors.report() | ||||
|     return assembly.name | ||||
| } | ||||
|  | ||||
| fun printAst(programAst: Program) { | ||||
|     println() | ||||
|     val printer = AstToSourceCode(::print, programAst) | ||||
|     printer.visit(programAst) | ||||
|     println() | ||||
| } | ||||
|  | ||||
| fun loadAsmIncludeFile(filename: String, source: Path): String { | ||||
|     return if (filename.startsWith("library:")) { | ||||
|         val resource = tryGetEmbeddedResource(filename.substring(8)) | ||||
| @@ -73,77 +360,3 @@ fun loadAsmIncludeFile(filename: String, source: Path): String { | ||||
| internal fun tryGetEmbeddedResource(name: String): InputStream? { | ||||
|     return object{}.javaClass.getResourceAsStream("/prog8lib/$name") | ||||
| } | ||||
|  | ||||
| class HeapValues { | ||||
|     data class HeapValue(val type: DataType, val str: String?, val array: Array<IntegerOrAddressOf>?, val doubleArray: DoubleArray?) { | ||||
|         override fun equals(other: Any?): Boolean { | ||||
|             if (this === other) return true | ||||
|             if (javaClass != other?.javaClass) return false | ||||
|             other as HeapValue | ||||
|             return type==other.type && str==other.str && Arrays.equals(array, other.array) && Arrays.equals(doubleArray, other.doubleArray) | ||||
|         } | ||||
|  | ||||
|         override fun hashCode(): Int = Objects.hash(str, array, doubleArray) | ||||
|  | ||||
|         val arraysize: Int = array?.size ?: doubleArray?.size ?: 0 | ||||
|     } | ||||
|  | ||||
|     private val heap = mutableMapOf<Int, HeapValue>() | ||||
|     private var heapId = 1 | ||||
|  | ||||
|     fun size(): Int = heap.size | ||||
|  | ||||
|     fun addString(type: DataType, str: String): Int { | ||||
|         if (str.length > 255) | ||||
|             throw IllegalArgumentException("string length must be 0-255") | ||||
|  | ||||
|         // strings are 'interned' and shared if they're the isSameAs | ||||
|         val value = HeapValue(type, str, null, null) | ||||
|  | ||||
|         val existing = heap.filter { it.value==value }.map { it.key }.firstOrNull() | ||||
|         if(existing!=null) | ||||
|             return existing | ||||
|         val newId = heapId++ | ||||
|         heap[newId] = value | ||||
|         return newId | ||||
|     } | ||||
|  | ||||
|     fun addIntegerArray(type: DataType, array: Array<IntegerOrAddressOf>): Int { | ||||
|         // arrays are never shared, don't check for existing | ||||
|         if(type !in ArrayDatatypes) | ||||
|             throw CompilerException("wrong array type") | ||||
|         val newId = heapId++ | ||||
|         heap[newId] = HeapValue(type, null, array, null) | ||||
|         return newId | ||||
|     } | ||||
|  | ||||
|     fun addDoublesArray(darray: DoubleArray): Int { | ||||
|         // arrays are never shared, don't check for existing | ||||
|         val newId = heapId++ | ||||
|         heap[newId] = HeapValue(DataType.ARRAY_F, null, null, darray) | ||||
|         return newId | ||||
|     } | ||||
|  | ||||
|     fun update(heapId: Int, str: String) { | ||||
|         val oldVal = heap[heapId] ?: throw IllegalArgumentException("heapId not found in heap") | ||||
|         if(oldVal.type in StringDatatypes) { | ||||
|             if (oldVal.str!!.length != str.length) | ||||
|                 throw IllegalArgumentException("heap string length mismatch") | ||||
|             heap[heapId] = oldVal.copy(str = str) | ||||
|         } | ||||
|         else throw IllegalArgumentException("heap data type mismatch") | ||||
|     } | ||||
|  | ||||
|     fun update(heapId: Int, heapval: HeapValue) { | ||||
|         if(heapId !in heap) | ||||
|             throw IllegalArgumentException("heapId not found in heap") | ||||
|         heap[heapId] = heapval | ||||
|     } | ||||
|  | ||||
|     fun get(heapId: Int): HeapValue { | ||||
|         return heap[heapId] ?: | ||||
|         throw IllegalArgumentException("heapId $heapId not found in heap") | ||||
|     } | ||||
|  | ||||
|     fun allEntries() = heap.entries | ||||
| } | ||||
|   | ||||
							
								
								
									
										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,179 +0,0 @@ | ||||
| package prog8.compiler | ||||
|  | ||||
| import prog8.ast.AstToSourceCode | ||||
| import prog8.ast.Program | ||||
| import prog8.ast.base.* | ||||
| import prog8.ast.statements.Directive | ||||
| import prog8.compiler.target.c64.MachineDefinition | ||||
| import prog8.compiler.target.c64.codegen.AsmGen | ||||
| 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.nio.file.Path | ||||
| import kotlin.system.measureTimeMillis | ||||
|  | ||||
|  | ||||
| class CompilationResult(val success: Boolean, | ||||
|                         val programAst: Program, | ||||
|                         val programName: String, | ||||
|                         val importedFiles: List<Path>) | ||||
|  | ||||
|  | ||||
| fun compileProgram(filepath: Path, | ||||
|                    optimize: Boolean, | ||||
|                    writeAssembly: Boolean): CompilationResult { | ||||
|     lateinit var programAst: Program | ||||
|     var programName: String? = null | ||||
|  | ||||
|     var importedFiles: List<Path> = emptyList() | ||||
|     var success=false | ||||
|  | ||||
|     try { | ||||
|         val totalTime = measureTimeMillis { | ||||
|             // import main module and everything it needs | ||||
|             println("Parsing...") | ||||
|             programAst = Program(moduleName(filepath.fileName), mutableListOf()) | ||||
|             importModule(programAst, filepath) | ||||
|  | ||||
|             importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source } | ||||
|  | ||||
|             val compilerOptions = determineCompilationOptions(programAst) | ||||
|             if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) | ||||
|                 throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.") | ||||
|  | ||||
|             // if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries | ||||
|             if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) { | ||||
|                 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.removeNopsFlattenAnonScopes() | ||||
|                 programAst.reorderStatements() | ||||
|                 programAst.addTypecasts() | ||||
|             } | ||||
|             //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() | ||||
|                     if (optsDone1 + optsDone2 == 0) | ||||
|                         break | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             programAst.addTypecasts() | ||||
|             programAst.removeNopsFlattenAnonScopes() | ||||
|             programAst.checkValid(compilerOptions)          // check if final tree is valid | ||||
|             programAst.checkRecursion()         // check if there are recursive subroutine calls | ||||
|  | ||||
|             // printAst(programAst) | ||||
|  | ||||
|             if(writeAssembly) { | ||||
|                 // asm generation directly from the Ast, no need for intermediate code | ||||
|                 val zeropage = MachineDefinition.C64Zeropage(compilerOptions) | ||||
|                 programAst.anonscopeVarsCleanup() | ||||
|                 val assembly = AsmGen(programAst, compilerOptions, zeropage).compileToAssembly(optimize) | ||||
|                 assembly.assemble(compilerOptions) | ||||
|                 programName = assembly.name | ||||
|             } | ||||
|             success = true | ||||
|         } | ||||
|         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 | ||||
|     } catch (ax: AstException) { | ||||
|         System.err.print("\u001b[91m")  // bright red | ||||
|         System.err.println(ax.toString()) | ||||
|         System.err.print("\u001b[0m")  // reset | ||||
|     } catch (x: Exception) { | ||||
|         print("\u001b[91m")  // bright red | ||||
|         println("\n* internal error *") | ||||
|         print("\u001b[0m")  // reset | ||||
|         System.out.flush() | ||||
|         throw x | ||||
|     } catch (x: NotImplementedError) { | ||||
|         print("\u001b[91m")  // bright red | ||||
|         println("\n* internal error: missing feature/code *") | ||||
|         print("\u001b[0m")  // reset | ||||
|         System.out.flush() | ||||
|         throw x | ||||
|     } | ||||
|     return CompilationResult(success, programAst, programName ?: "", importedFiles) | ||||
| } | ||||
|  | ||||
| fun printAst(programAst: Program) { | ||||
|     println() | ||||
|     val printer = AstToSourceCode(::print, programAst) | ||||
|     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 | ||||
|     ) | ||||
| } | ||||
| @@ -8,15 +8,43 @@ class ZeropageDepletedError(message: String) : Exception(message) | ||||
|  | ||||
| abstract class Zeropage(protected val options: CompilationOptions) { | ||||
|  | ||||
|     abstract val SCRATCH_B1 : Int      // temp storage for a single byte | ||||
|     abstract val SCRATCH_REG : Int     // temp storage for a register | ||||
|     abstract val SCRATCH_W1 : Int      // temp storage 1 for a word  $fb+$fc | ||||
|     abstract val SCRATCH_W2 : Int      // temp storage 2 for a word  $fb+$fc | ||||
|  | ||||
|  | ||||
|     private val allocations = mutableMapOf<Int, Pair<String, DataType>>() | ||||
|     val free = mutableListOf<Int>()     // subclasses must set this to the appropriate free locations. | ||||
|  | ||||
|     val allowedDatatypes = NumericDatatypes | ||||
|  | ||||
|     fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size | ||||
|     fun availableBytes() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size | ||||
|     fun hasByteAvailable() = if(options.zeropage==ZeropageType.DONTUSE) false else free.isNotEmpty() | ||||
|     fun availableWords(): Int { | ||||
|         if(options.zeropage==ZeropageType.DONTUSE) | ||||
|             return 0 | ||||
|  | ||||
|     fun allocate(scopedname: String, datatype: DataType, position: Position?): Int { | ||||
|         assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"} | ||||
|         val words = free.windowed(2).filter { it[0] == it[1]-1 } | ||||
|         var nonOverlappingWordsCount = 0 | ||||
|         var prevMsbLoc = -1 | ||||
|         for(w in words) { | ||||
|             if(w[0]!=prevMsbLoc) { | ||||
|                 nonOverlappingWordsCount++ | ||||
|                 prevMsbLoc = w[1] | ||||
|             } | ||||
|         } | ||||
|         return nonOverlappingWordsCount | ||||
|     } | ||||
|     fun hasWordAvailable(): Boolean { | ||||
|         if(options.zeropage==ZeropageType.DONTUSE) | ||||
|             return false | ||||
|  | ||||
|         return free.windowed(2).any { it[0] == it[1] - 1 } | ||||
|     } | ||||
|  | ||||
|     fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int { | ||||
|         assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"} | ||||
|  | ||||
|         if(options.zeropage==ZeropageType.DONTUSE) | ||||
|             throw CompilerException("zero page usage has been disabled") | ||||
| @@ -28,9 +56,9 @@ abstract class Zeropage(protected val options: CompilationOptions) { | ||||
|                     DataType.FLOAT -> { | ||||
|                         if (options.floats) { | ||||
|                             if(position!=null) | ||||
|                                 printWarning("allocated a large value (float) in zeropage", position) | ||||
|                                 errors.warn("allocated a large value (float) in zeropage", position) | ||||
|                             else | ||||
|                                 printWarning("$scopedname: allocated a large value (float) in zeropage") | ||||
|                                 errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY) | ||||
|                             5 | ||||
|                         } else throw CompilerException("floating point option not enabled") | ||||
|                     } | ||||
| @@ -39,13 +67,13 @@ abstract class Zeropage(protected val options: CompilationOptions) { | ||||
|  | ||||
|         if(free.size > 0) { | ||||
|             if(size==1) { | ||||
|                 for(candidate in free.min()!! .. free.max()!!+1) { | ||||
|                 for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) { | ||||
|                     if(loneByte(candidate)) | ||||
|                         return makeAllocation(candidate, 1, datatype, scopedname) | ||||
|                 } | ||||
|                 return makeAllocation(free[0], 1, datatype, scopedname) | ||||
|             } | ||||
|             for(candidate in free.min()!! .. free.max()!!+1) { | ||||
|             for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) { | ||||
|                 if (sequentialFree(candidate, size)) | ||||
|                     return makeAllocation(candidate, size, datatype, scopedname) | ||||
|             } | ||||
| @@ -58,18 +86,10 @@ abstract class Zeropage(protected val options: CompilationOptions) { | ||||
|  | ||||
|     private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { | ||||
|         free.removeAll(address until address+size) | ||||
|         allocations[address] = Pair(name ?: "<unnamed>", datatype) | ||||
|         allocations[address] = (name ?: "<unnamed>") to datatype | ||||
|         return address | ||||
|     } | ||||
|  | ||||
|     private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free | ||||
|     private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList()) | ||||
|  | ||||
|     enum class ExitProgramStrategy { | ||||
|         CLEAN_EXIT, | ||||
|         SYSTEM_RESET | ||||
|     } | ||||
|  | ||||
|     abstract val exitProgramStrategy: ExitProgramStrategy | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										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}") | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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,71 +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" | ||||
|  | ||||
|     companion object { | ||||
|         // 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names | ||||
|         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") | ||||
|     } | ||||
|  | ||||
|  | ||||
|     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", "-Wno-shadow", "-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") | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user